diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c405483 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 8 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100755 index 0000000..74ba1e7 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,16 @@ +name: CI +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: 'adopt' + - name: Build with Gradle + run: ./gradlew build + - name: Test with Gradle + run: ./gradlew test \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12ddda9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +.gradle +.idea diff --git a/README.md b/README.md index 1ec6ae4..8a3d0bd 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ -# sd-homework \ No newline at end of file +# simplecli + +### How to run + +Run [main](src/main/java/ru/itmo/simplecli/Main.java) +method from IDE **or** `./gradlew run`. + +### What's there + +Implemented build-ins: +- [cat](src/main/java/ru/itmo/simplecli/executor/commands/Cat.java) +- [echo](src/main/java/ru/itmo/simplecli/executor/commands/Echo.java) +- [exit](src/main/java/ru/itmo/simplecli/executor/commands/Exit.java) +- [pwd](src/main/java/ru/itmo/simplecli/executor/commands/Pwd.java) +- [wc](src/main/java/ru/itmo/simplecli/executor/commands/WC.java) +- [variable assignment](src/main/java/ru/itmo/simplecli/executor/commands/Assignment.java) + +[Main](src/main/java/ru/itmo/simplecli/Main.java) runs loop until `exit` command is given. +If the entered string contains incomplete quotes, it asks to continue typing. + +[Parser](src/main/java/ru/itmo/simplecli/executor/Parser.java) supports double and single quotes. +It also performs substitution for values beginning with `$` if it is not surrounded by single quotes. + +The CLI [supports](src/main/java/ru/itmo/simplecli/executor/PipedCommand.java) a pipeline of commands, separated by a sign `|`, +each command receives as input the output of the previous one. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..e175653 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + application +} + +application { + mainClass.set("ru.itmo.simplecli.Main") +} + +group = "ru.itmo" +version = "1.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("commons-cli:commons-cli:1.5.0") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +tasks.test { + useJUnitPlatform() +} + +tasks.named("run") { + standardInput = System.`in` +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ffed3a2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..744e882 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..e895432 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "simplecli" + diff --git a/src/main/java/ru/itmo/simplecli/Main.java b/src/main/java/ru/itmo/simplecli/Main.java new file mode 100644 index 0000000..63d37a8 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/Main.java @@ -0,0 +1,53 @@ +package ru.itmo.simplecli; + +import ru.itmo.simplecli.executor.*; + +import java.io.PrintStream; +import java.util.*; + +/** + * Entry point. Loop: read - parse - execute. + */ +public class Main { + private static final Scanner input = new Scanner(System.in); + private static final PrintStream output = new PrintStream(System.out); + private static final AbstractCommandFactory commandFactory = new PipedCommandFactory(); + + public static void main(String[] args) { + EnvironmentManager environment = new EnvironmentManager(); + Parser parser = new Parser(environment); + + while (true) { + output.print(">> "); + String nextLine = input.nextLine(); + if (nextLine.trim().equals("")) { + continue; + } + + parser.parse(nextLine); + while (parser.hasUnclosedQuote()) { + output.print("> "); + nextLine = nextLine + input.nextLine(); + parser.parse(nextLine); + } + + if (parser.getResult().equals(List.of(""))) { + continue; + } + + var command = commandFactory.construct(parser.getResult(), environment); + if (command == null) { + output.println("Can't construct command"); + continue; + } + + command.execute(null); + if (command.getEndStatus() == Executable.EndStatus.EXIT) { + break; + } + if (command.getOutput() != null) { + output.println(command.getOutput()); + } + } + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/AbstractCommandFactory.java b/src/main/java/ru/itmo/simplecli/executor/AbstractCommandFactory.java new file mode 100644 index 0000000..42a9a81 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/AbstractCommandFactory.java @@ -0,0 +1,15 @@ +package ru.itmo.simplecli.executor; + +import java.util.List; +import java.util.regex.Pattern; + +public abstract class AbstractCommandFactory { + public abstract Executable construct(List args, EnvironmentManager environment); + + protected boolean isAssignment(List args) { + return args.size() == 3 + && Pattern.matches("\\w+", args.get(0)) + && args.get(1).equals("=") + && Pattern.matches("\\w+", args.get(2)); + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/EnvironmentManager.java b/src/main/java/ru/itmo/simplecli/executor/EnvironmentManager.java new file mode 100644 index 0000000..4acdc40 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/EnvironmentManager.java @@ -0,0 +1,36 @@ +package ru.itmo.simplecli.executor; + +import java.util.HashMap; + +/** + * Class for local environment + */ +public class EnvironmentManager { + private final HashMap environment = new HashMap<>(); + + /** + * If there is no local variable uses global environment + * + * @param name name of the variable + * @return value of the variable + */ + public final String get(String name) { + if (environment.containsKey(name)) { + return environment.get(name); + } + if (System.getenv().containsKey(name)) { + return System.getenv(name); + } + return ""; + } + + /** + * Set or rewrites variable value + * + * @param name name of a variable + * @param value value of a variable + */ + public final void set(String name, String value) { + environment.put(name, value); + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/Executable.java b/src/main/java/ru/itmo/simplecli/executor/Executable.java new file mode 100644 index 0000000..4306b91 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/Executable.java @@ -0,0 +1,29 @@ +package ru.itmo.simplecli.executor; + +/** + * Class for anything that can be executed + */ +public interface Executable { + /** + * Executes with the given input + * + * @param input + */ + void execute(String input); + + /** + * @return result of execution or error message + */ + String getOutput(); + + /** + * @return status of execution + */ + EndStatus getEndStatus(); + + enum EndStatus { + SUCCESS, + ERROR, + EXIT + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/Parser.java b/src/main/java/ru/itmo/simplecli/executor/Parser.java new file mode 100644 index 0000000..0816475 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/Parser.java @@ -0,0 +1,147 @@ +package ru.itmo.simplecli.executor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple parser that handles single and double quotes + */ +public class Parser { + private final EnvironmentManager environment; + private List result; + private State state; + + public Parser(EnvironmentManager environment) { + this.environment = environment; + } + + /** + * @param input string that contains command with args + */ + public void parse(String input) { + result = new ArrayList<>(); + state = State.NO_QUOTED; + runParse(input); + } + + /** + * @return result of parsing + */ + public List getResult() { + return result.stream().map(Wrapped::getValue).toList(); + } + + /** + * @return value if input string had proper quotes + */ + public boolean hasUnclosedQuote() { + return state != State.NO_QUOTED; + } + + private void runParse(String string) { + StringBuilder current = new StringBuilder(); + for (int pos = 0; pos < string.length(); pos++) { + var symbol = string.charAt(pos); + + if (state == State.SINGLE_QUOTED) { + if (symbol == '\'') { + result.add(new SingleQuoted(current)); + current = new StringBuilder(); + state = State.NO_QUOTED; + } else { + current.append(symbol); + } + } else if (state == State.DOUBLE_QUOTED) { + if (symbol == '"') { + result.add(new DoubleQuoted(current)); + current = new StringBuilder(); + state = State.NO_QUOTED; + } else { + current.append(symbol); + } + } else { + if (symbol == ' ') { + if (!current.isEmpty()) { + result.add(new Wrapped(current)); + current = new StringBuilder(); + } + } else if (symbol == '|' || symbol == '=') { + if (!current.isEmpty()) { + result.add(new Wrapped(current)); + current = new StringBuilder(); + } + result.add(new Wrapped(new StringBuilder().append(symbol))); + } else if (symbol == '\'') { + if (!current.isEmpty()) { + result.add(new Wrapped(current)); + current = new StringBuilder(); + } + state = State.SINGLE_QUOTED; + } else if (symbol == '"') { + if (!current.isEmpty()) { + result.add(new Wrapped(current)); + current = new StringBuilder(); + } + state = State.DOUBLE_QUOTED; + } else { + current.append(symbol); + } + } + } + if (state == State.NO_QUOTED && !current.isEmpty()) { + result.add(new Wrapped(current)); + } + } + + private enum State { + NO_QUOTED, + DOUBLE_QUOTED, + SINGLE_QUOTED + } + + private class Wrapped { + protected StringBuilder value; + + Wrapped(StringBuilder value) { + this.value = value; + } + + public String getValue() { + if (value.indexOf("$") == -1) { + return value.toString(); + } + var start = value.indexOf("$"); + var end = start + 1; + while (end < value.length()) { + var symbol = value.charAt(end); + if (!Character.isAlphabetic(symbol) + && !Character.isDigit(symbol) + && !(symbol == '_')) { + break; + } + end++; + } + var varName = value.substring(start + 1, end); + value.delete(start, end); + value.insert(start, environment.get(varName)); + return getValue(); + } + } + + private class SingleQuoted extends Wrapped { + SingleQuoted(StringBuilder value) { + super(value); + } + + @Override + public String getValue() { + return value.toString(); + } + } + + private class DoubleQuoted extends Wrapped { + DoubleQuoted(StringBuilder value) { + super(value); + } + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/PipedCommand.java b/src/main/java/ru/itmo/simplecli/executor/PipedCommand.java new file mode 100644 index 0000000..120ca8a --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/PipedCommand.java @@ -0,0 +1,39 @@ +package ru.itmo.simplecli.executor; + +import java.util.List; + +/** + * Piped command executes every command, pass output of one as input to the next one + */ +class PipedCommand implements Executable { + private final List executables; + private EndStatus status; + private String output; + + public PipedCommand(List executables) { + this.executables = executables; + } + + @Override + public void execute(String input) { + output = input; + for (var executable : executables) { + executable.execute(output); + output = executable.getOutput(); + status = executable.getEndStatus(); + if (status != EndStatus.SUCCESS) { + return; + } + } + } + + @Override + public String getOutput() { + return output; + } + + @Override + public EndStatus getEndStatus() { + return status; + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/PipedCommandFactory.java b/src/main/java/ru/itmo/simplecli/executor/PipedCommandFactory.java new file mode 100644 index 0000000..a3da42a --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/PipedCommandFactory.java @@ -0,0 +1,41 @@ +package ru.itmo.simplecli.executor; + +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; + +/** + * Class to construct piped commands + */ +public class PipedCommandFactory extends AbstractCommandFactory { + + @Override + public Executable construct(List args, EnvironmentManager environment) { + var lists = splitIntoCommands(args); + var executables = new ArrayList(); + var singleCommandFactory = new SingleCommandFactory(); + for (var list : lists) { + var cmd = singleCommandFactory.construct(list, environment); + if (cmd == null) { + return null; + } + executables.add(cmd); + } + return new PipedCommand(executables); + } + + private List> splitIntoCommands(List args) { + List> result = new ArrayList<>(); + List current = new ArrayList<>(); + for (var element : args) { + if (element.equals("|")) { + result.add(current); + current = new ArrayList<>(); + } else { + current.add(element); + } + } + result.add(current); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/ru/itmo/simplecli/executor/SingleCommandFactory.java b/src/main/java/ru/itmo/simplecli/executor/SingleCommandFactory.java new file mode 100644 index 0000000..fdbd66f --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/SingleCommandFactory.java @@ -0,0 +1,31 @@ +package ru.itmo.simplecli.executor; + +import ru.itmo.simplecli.executor.commands.*; + +import java.util.List; +import java.util.regex.Pattern; + +/** + * Class to construct commands + */ +public class SingleCommandFactory extends AbstractCommandFactory { + @Override + public Executable construct(List args, EnvironmentManager environment) { + if (args.size() == 0) { + return null; + } + if (isAssignment(args)) { + return new Assignment(args, environment); + } + var name = args.get(0); + return switch (name) { + case "cat" -> new Cat(args.subList(1, args.size()), environment); + case "echo" -> new Echo(args.subList(1, args.size()), environment); + case "exit" -> new Exit(args.subList(1, args.size()), environment); + case "pwd" -> new Pwd(args.subList(1, args.size()), environment); + case "wc" -> new WC(args.subList(1, args.size()), environment); + default -> new External(args, environment); + }; + } + +} diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/Assignment.java b/src/main/java/ru/itmo/simplecli/executor/commands/Assignment.java new file mode 100644 index 0000000..fc10688 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/Assignment.java @@ -0,0 +1,20 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.util.List; + +/** + * Changes local variable value + */ +public class Assignment extends Command { + public Assignment(List args, EnvironmentManager environment) { + super(args, environment); + } + + @Override + public void execute(String input) { + environment.set(args.get(0), args.get(2)); + status = EndStatus.SUCCESS; + } +} \ No newline at end of file diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/Cat.java b/src/main/java/ru/itmo/simplecli/executor/commands/Cat.java new file mode 100644 index 0000000..24d125d --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/Cat.java @@ -0,0 +1,58 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +/** + * Concatenates given files content + */ +public class Cat extends Command { + + + public Cat(List args, EnvironmentManager environment) { + super(args, environment); + } + + @Override + public void execute(String input) { + if (args.size() == 0 && input == null) { + status = EndStatus.ERROR; + output = "Not enough arguments for `cat` command"; + return; + } + + if (args.size() == 0) { + output = handle(new BufferedReader(new StringReader(input))); + status = EndStatus.SUCCESS; + return; + } + + try { + var sb = new StringBuilder(); + for (var arg : args) { + var file = new BufferedReader(new FileReader(arg)); + sb.append(handle(file)); + } + output = sb.toString(); + status = EndStatus.SUCCESS; + } catch (IOException e) { + status = EndStatus.ERROR; + output = e.getMessage(); + } + + } + + private String handle(BufferedReader file) { + var sb = new StringBuilder(); + for (var line : file.lines().toList()) { + sb.append(line).append("\n"); + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/Command.java b/src/main/java/ru/itmo/simplecli/executor/commands/Command.java new file mode 100644 index 0000000..a0548d1 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/Command.java @@ -0,0 +1,35 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; +import ru.itmo.simplecli.executor.Executable; + +import java.util.List; + +/** + * Abstract class for an explicit command + */ +public abstract class Command implements Executable { + protected EndStatus status; + protected List args; + protected String output = null; + protected EnvironmentManager environment; + + public Command(List args, EnvironmentManager environment) { + this.args = args; + this.environment = environment; + } + + @Override + public String getOutput() { + return output; + } + + @Override + public EndStatus getEndStatus() { + return status; + } + + @Override + public abstract void execute(String input); + +} \ No newline at end of file diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/Echo.java b/src/main/java/ru/itmo/simplecli/executor/commands/Echo.java new file mode 100644 index 0000000..67df190 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/Echo.java @@ -0,0 +1,22 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.util.List; + +/** + * Returns given args without changes + */ +public class Echo extends Command { + + public Echo(List args, EnvironmentManager environment) { + super(args, environment); + } + + @Override + public void execute(String input) { + output = String.join(" ", args); + status = EndStatus.SUCCESS; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/Exit.java b/src/main/java/ru/itmo/simplecli/executor/commands/Exit.java new file mode 100644 index 0000000..8290296 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/Exit.java @@ -0,0 +1,21 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.util.List; + +/** + * Exits application + */ +public class Exit extends Command { + + public Exit(List args, EnvironmentManager environment) { + super(args, environment); + } + + @Override + public void execute(String input) { + status = EndStatus.EXIT; + } + +} diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/External.java b/src/main/java/ru/itmo/simplecli/executor/commands/External.java new file mode 100644 index 0000000..07795d9 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/External.java @@ -0,0 +1,44 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Class for not build-ins command + */ +public class External extends Command { + + public External(List args, EnvironmentManager environment) { + super(args, environment); + } + + @Override + public void execute(String input) { + try { + var process = new ProcessBuilder(args).start(); + if (input != null) { + var stream = process.getOutputStream(); + stream.write(input.getBytes(StandardCharsets.UTF_8)); + stream.close(); + } + var exitCode = process.waitFor(); + var sb = new StringBuilder(); + var stream = exitCode == 0 ? process.getInputStream() : process.getErrorStream(); + var reader = new BufferedReader(new InputStreamReader(stream)); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + output = sb.toString(); + status = exitCode == 0 ? EndStatus.SUCCESS : EndStatus.ERROR; + } catch (IOException | InterruptedException e) { + status = EndStatus.ERROR; + output = e.getMessage(); + } + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/Pwd.java b/src/main/java/ru/itmo/simplecli/executor/commands/Pwd.java new file mode 100644 index 0000000..7f1ab5c --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/Pwd.java @@ -0,0 +1,21 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.util.List; + +/** + * Returns PWD value + */ +public class Pwd extends Command { + + public Pwd(List args, EnvironmentManager environment) { + super(args, environment); + } + + @Override + public void execute(String string) { + output = System.getProperty("user.dir"); + status = EndStatus.SUCCESS; + } +} diff --git a/src/main/java/ru/itmo/simplecli/executor/commands/WC.java b/src/main/java/ru/itmo/simplecli/executor/commands/WC.java new file mode 100644 index 0000000..2058569 --- /dev/null +++ b/src/main/java/ru/itmo/simplecli/executor/commands/WC.java @@ -0,0 +1,79 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +/** + * Counts number of lines, symbols and bytes for every given file + */ +public class WC extends Command { + + public WC(List args, EnvironmentManager environment) { + super(args, environment); + } + + @Override + public void execute(String input) { + if (args.size() == 0 && input == null) { + status = EndStatus.ERROR; + output = "Not enough arguments for `wc` command"; + return; + } + try { + if (args.size() == 0) { + var reader = new BufferedReader(new StringReader(input)); + output = handle(reader, "").toString(); + } else { + int totalLines = 0, totalWords = 0, totalBytes = 0; + var builder = new StringBuilder(); + for (var arg : args) { + var file = new BufferedReader(new FileReader(arg)); + var result = handle(file, arg); + totalLines += result.lines; + totalWords += result.words; + totalBytes += result.bytes; + builder.append(result); + } + if (args.size() > 1) { + builder.append(new ResultLine(totalLines, totalWords, totalBytes, "total")); + } + output = builder.toString(); + } + status = EndStatus.SUCCESS; + } catch (IOException e) { + status = EndStatus.ERROR; + output = e.getMessage(); + } + } + + private ResultLine handle(BufferedReader file, String name) throws IOException { + int lines = 0, words = 0, bytes = 0; + int prev = ' ', c; + while ((c = file.read()) != -1) { + bytes += 1; + if ((char) c == '\n') { + lines += 1; + } + if (Character.isWhitespace(prev) && !Character.isWhitespace(c)) { + words += 1; + } + prev = c; + } + return new ResultLine(lines, words, bytes, name); + } + + private record ResultLine(int lines, int words, int bytes, String name) { + public String toString() { + return lines + "\t" + + words + "\t" + + bytes + "\t" + + name + + "\n"; + } + } +} diff --git a/src/test/java/ru/itmo/simplecli/executor/ParserTest.java b/src/test/java/ru/itmo/simplecli/executor/ParserTest.java new file mode 100644 index 0000000..151fc02 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/ParserTest.java @@ -0,0 +1,74 @@ +package ru.itmo.simplecli.executor; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ParserTest { + + private final EnvironmentManager env = new EnvironmentManager(); + private final Parser parser = new Parser(env); + + @Test + public void testLonelyCommand() { + parser.parse("cat file1 file2"); + assertEquals(parser.getResult(), List.of("cat", "file1", "file2")); + + parser.parse("echo input"); + assertEquals(parser.getResult(), List.of("echo", "input")); + + parser.parse("exit"); + assertEquals(parser.getResult(), List.of("exit")); + + parser.parse("grep -i -w -A 10 pattern file1 file2"); + assertEquals(parser.getResult(), + List.of("grep", "-i", "-w", "-A", "10", "pattern", "file1", "file2")); + + parser.parse("pwd"); + assertEquals(parser.getResult(), List.of("pwd")); + + parser.parse("wc file1 file2"); + assertEquals(parser.getResult(), List.of("wc", "file1", "file2")); + } + + @Test + public void testExtraSpaces() { + parser.parse("cmd arg1 arg2"); + assertEquals(parser.getResult(), List.of("cmd", "arg1", "arg2")); + } + + @Test + public void testDoubleQuoted() { + parser.parse("cmd \"arg1\" \" arg2\" "); + assertEquals(parser.getResult(), List.of("cmd", "arg1", " arg2")); + + parser.parse("cmd \"arg"); + assertTrue(parser.hasUnclosedQuote()); + + parser.parse(" \"$PWD\" "); + assertEquals(parser.getResult(), List.of(Objects.requireNonNull(env.get("PWD")))); + } + + @Test + public void testSingleQuoted() { + parser.parse("cmd 'arg1' ' arg2' "); + assertEquals(parser.getResult(), List.of("cmd", "arg1", " arg2")); + + parser.parse("cmd 'arg"); + assertTrue(parser.hasUnclosedQuote()); + + parser.parse(" '$PWD' "); + assertEquals(parser.getResult(), List.of("$PWD")); + } + + @Test + public void testPiped() { + parser.parse(" cmd1 arg | cmd2 arg | cmd3 arg "); + assertEquals(parser.getResult(), + List.of("cmd1", "arg", "|", "cmd2", "arg", "|", "cmd3", "arg")); + } +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/PipedCommandFactoryTest.java b/src/test/java/ru/itmo/simplecli/executor/PipedCommandFactoryTest.java new file mode 100644 index 0000000..5d125c9 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/PipedCommandFactoryTest.java @@ -0,0 +1,25 @@ +package ru.itmo.simplecli.executor; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class PipedCommandFactoryTest { + + public AbstractCommandFactory instance = new PipedCommandFactory(); + + @Test + public void testPipe() { + var env = new EnvironmentManager(); + var args = List.of( + "cat", "|", "echo", "|", "grep", "|", "exit", "|", "pwd", "|", "wc" + ); + assertNotNull(instance.construct(args, env)); + assertNull(instance.construct(List.of(), env)); + } + +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/SingleCommandFactoryTest.java b/src/test/java/ru/itmo/simplecli/executor/SingleCommandFactoryTest.java new file mode 100644 index 0000000..5dcc4e2 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/SingleCommandFactoryTest.java @@ -0,0 +1,65 @@ +package ru.itmo.simplecli.executor; + +import org.junit.jupiter.api.Test; +import ru.itmo.simplecli.executor.commands.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class SingleCommandFactoryTest { + private final AbstractCommandFactory instance = new SingleCommandFactory(); + public EnvironmentManager env = new EnvironmentManager(); + + @Test + public void testCatImplemented() { + var args = List.of("cat"); + assertEquals(Objects.requireNonNull(instance.construct(args, env)).getClass(), + Cat.class); + } + + @Test + public void testEchoImplemented() { + var args = List.of("echo"); + assertEquals(Objects.requireNonNull(instance.construct(args, env)).getClass(), + Echo.class); + } + + @Test + public void testExitImplemented() { + var args = List.of("exit"); + assertEquals(Objects.requireNonNull(instance.construct(args, env)).getClass(), + Exit.class); + } + + @Test + public void testPwdImplemented() { + var args = List.of("pwd"); + assertEquals(Objects.requireNonNull(instance.construct(args, env)).getClass(), + Pwd.class); + } + + @Test + public void testWCImplemented() { + var args = List.of("wc"); + assertEquals(Objects.requireNonNull(instance.construct(args, env)).getClass(), + WC.class); + } + + @Test + public void testExternalImplemented() { + var args = List.of("unknowncommand"); + assertEquals(Objects.requireNonNull(instance.construct(args, env)).getClass(), + External.class); + + assertNull(instance.construct(new ArrayList<>(), env)); + } + + @Test + public void testConstructorWithEmptyInput() { + assertNull(instance.construct(new ArrayList<>(), env)); + } +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/commands/AbstractCommandTest.java b/src/test/java/ru/itmo/simplecli/executor/commands/AbstractCommandTest.java new file mode 100644 index 0000000..ff675f8 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/commands/AbstractCommandTest.java @@ -0,0 +1,17 @@ +package ru.itmo.simplecli.executor.commands; + +import ru.itmo.simplecli.executor.Executable; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class AbstractCommandTest { + + public void assertSuccess(Command cmd) { + assertEquals(Executable.EndStatus.SUCCESS, cmd.getEndStatus()); + } + + public void assertError(Command cmd) { + assertEquals(Executable.EndStatus.ERROR, cmd.getEndStatus()); + } + +} diff --git a/src/test/java/ru/itmo/simplecli/executor/commands/AssignmentTest.java b/src/test/java/ru/itmo/simplecli/executor/commands/AssignmentTest.java new file mode 100644 index 0000000..0fd58d4 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/commands/AssignmentTest.java @@ -0,0 +1,19 @@ +package ru.itmo.simplecli.executor.commands; + +import org.junit.jupiter.api.Test; +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class AssignmentTest extends AbstractCommandTest { + @Test + void test() { + var env = new EnvironmentManager(); + var cmd = new Assignment(List.of("var", "=", "value"), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals("value", env.get("var")); + } +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/commands/CatTest.java b/src/test/java/ru/itmo/simplecli/executor/commands/CatTest.java new file mode 100644 index 0000000..5ce7766 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/commands/CatTest.java @@ -0,0 +1,53 @@ +package ru.itmo.simplecli.executor.commands; + +import org.junit.jupiter.api.Test; +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +abstract class CatTest extends AbstractCommandTest { + private final EnvironmentManager env = new EnvironmentManager(); + private final String path = "src/test/"; + + @Test + void testFile() { + var cmd = new Cat(List.of(path + "testfile"), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals("test file", cmd.getOutput().trim()); + } + + @Test + void testFile2() { + var cmd = new Cat(List.of(path + "testfile2"), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals("test\nfile", cmd.getOutput().trim()); + } + + @Test + void testFiles() { + var cmd = new Cat(List.of(path + "testfile", path + "testfile"), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals("test file\ntest file", cmd.getOutput().trim()); + } + + + void testNoFile() { + var cmd = new Cat(new ArrayList<>(), env); + cmd.execute("test cat"); + assertSuccess(cmd); + assertEquals("test cat", cmd.getOutput().trim()); + } + + @Test + void testNoFileNoInput() { + var cmd = new Cat(new ArrayList<>(), env); + cmd.execute(null); + assertError(cmd); + } +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/commands/EchoTest.java b/src/test/java/ru/itmo/simplecli/executor/commands/EchoTest.java new file mode 100644 index 0000000..09e4ec8 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/commands/EchoTest.java @@ -0,0 +1,49 @@ +package ru.itmo.simplecli.executor.commands; + +import org.junit.jupiter.api.Test; +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EchoTest extends AbstractCommandTest { + + @Test + void testOneArg() { + var env = new EnvironmentManager(); + var cmd = new Echo(List.of("arg"), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals("arg", cmd.getOutput()); + } + + @Test + void testMultipleArg() { + var env = new EnvironmentManager(); + var cmd = new Echo(List.of("arg1", "arg2"), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals("arg1 arg2", cmd.getOutput()); + } + + @Test + void testNonemptyInput() { + var env = new EnvironmentManager(); + var cmd = new Echo(List.of("arg"), env); + cmd.execute("something"); + assertSuccess(cmd); + assertEquals("arg", cmd.getOutput()); + } + + @Test + void testNoArg() { + var env = new EnvironmentManager(); + var cmd = new Echo(new ArrayList<>(), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals("", cmd.getOutput()); + } + +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/commands/ExitTest.java b/src/test/java/ru/itmo/simplecli/executor/commands/ExitTest.java new file mode 100644 index 0000000..1705380 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/commands/ExitTest.java @@ -0,0 +1,23 @@ +package ru.itmo.simplecli.executor.commands; + +import org.junit.jupiter.api.Test; +import ru.itmo.simplecli.executor.EnvironmentManager; +import ru.itmo.simplecli.executor.Executable; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class ExitTest extends AbstractCommandTest { + + @Test + void test() { + var env = new EnvironmentManager(); + var cmd = new Exit(new ArrayList<>(), env); + cmd.execute(null); + assertEquals(Executable.EndStatus.EXIT, cmd.getEndStatus()); + assertNull(cmd.getOutput()); + } + +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/commands/PwdTest.java b/src/test/java/ru/itmo/simplecli/executor/commands/PwdTest.java new file mode 100644 index 0000000..216827b --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/commands/PwdTest.java @@ -0,0 +1,22 @@ +package ru.itmo.simplecli.executor.commands; + +import org.junit.jupiter.api.Test; +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.nio.file.Paths; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class PwdTest extends AbstractCommandTest { + + @Test + void test() { + var env = new EnvironmentManager(); + var cmd = new Pwd(new ArrayList<>(), env); + cmd.execute(null); + assertSuccess(cmd); + assertEquals(Paths.get("").toAbsolutePath().toString(), cmd.getOutput()); + } + +} \ No newline at end of file diff --git a/src/test/java/ru/itmo/simplecli/executor/commands/WCTest.java b/src/test/java/ru/itmo/simplecli/executor/commands/WCTest.java new file mode 100644 index 0000000..8650ac2 --- /dev/null +++ b/src/test/java/ru/itmo/simplecli/executor/commands/WCTest.java @@ -0,0 +1,79 @@ +package ru.itmo.simplecli.executor.commands; + +import org.junit.jupiter.api.Test; +import ru.itmo.simplecli.executor.EnvironmentManager; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class WCTest extends AbstractCommandTest { + private final EnvironmentManager env = new EnvironmentManager(); + private final String path = "src/test/"; + + private void assertSpaceInsensitive(String expected, String actual) { + var value1 = expected.trim().split("\\s+"); + var value2 = actual.trim().split("\\s+"); + assertArrayEquals(value1, value2); + } + + private String callExternal(List args) throws IOException, InterruptedException { + var process = new ProcessBuilder(args).start(); + process.waitFor(); + return new String(process.getInputStream().readAllBytes()); + } + + private String callExternal(List args, String input) throws IOException, InterruptedException { + var process = new ProcessBuilder(args).start(); + process.getOutputStream().write(input.getBytes()); + process.getOutputStream().close(); + return new String(process.getInputStream().readAllBytes()); + } + + @Test + void testFile() { + var filename = path + "testfile"; + var cmd = new WC(List.of(filename), env); + cmd.execute(null); + assertSuccess(cmd); + try { + assertSpaceInsensitive(callExternal(List.of("wc", filename)), cmd.getOutput()); + } catch (IOException | InterruptedException e) { + fail(); + } + } + + @Test + void testFiles() { + var filename = path + "testfile"; + var cmd = new WC(List.of(filename, filename), env); + cmd.execute(null); + assertSuccess(cmd); + try { + assertSpaceInsensitive(callExternal(List.of("wc", filename, filename)), cmd.getOutput()); + } catch (IOException | InterruptedException e) { + fail(); + } + } + + @Test + void testNoFile() { + var cmd = new WC(new ArrayList<>(), env); + var input = "test wc"; + cmd.execute(input); + try { + assertSpaceInsensitive(callExternal(List.of("wc", "-"), input), cmd.getOutput() + "-"); + } catch (IOException | InterruptedException e) { + fail(); + } + } + + @Test + void testNoFileNoInput() { + var cmd = new WC(List.of(), env); + cmd.execute(null); + assertError(cmd); + } +} diff --git a/src/test/testfile b/src/test/testfile new file mode 100644 index 0000000..16b14f5 --- /dev/null +++ b/src/test/testfile @@ -0,0 +1 @@ +test file diff --git a/src/test/testfile2 b/src/test/testfile2 new file mode 100644 index 0000000..cd0ece9 --- /dev/null +++ b/src/test/testfile2 @@ -0,0 +1,2 @@ +test +file