diff --git a/bash/README.md b/bash/README.md old mode 100644 new mode 100755 diff --git a/bash/build.gradle b/bash/build.gradle old mode 100644 new mode 100755 index f232c95..7f073f2 --- a/bash/build.gradle +++ b/bash/build.gradle @@ -10,6 +10,7 @@ repositories { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + testCompile group: 'junit', name: 'junit', version: '4.12' } compileKotlin { diff --git a/bash/gradle/wrapper/gradle-wrapper.jar b/bash/gradle/wrapper/gradle-wrapper.jar old mode 100644 new mode 100755 diff --git a/bash/gradle/wrapper/gradle-wrapper.properties b/bash/gradle/wrapper/gradle-wrapper.properties old mode 100644 new mode 100755 diff --git a/bash/gradlew b/bash/gradlew old mode 100644 new mode 100755 diff --git a/bash/gradlew.bat b/bash/gradlew.bat old mode 100644 new mode 100755 diff --git a/bash/settings.gradle b/bash/settings.gradle old mode 100644 new mode 100755 diff --git a/bash/src/main/kotlin/hse/nedikov/bash/Controller.kt b/bash/src/main/kotlin/hse/nedikov/bash/Controller.kt old mode 100644 new mode 100755 diff --git a/bash/src/main/kotlin/hse/nedikov/bash/Environment.kt b/bash/src/main/kotlin/hse/nedikov/bash/Environment.kt old mode 100644 new mode 100755 index 7176d9c..4ce7846 --- a/bash/src/main/kotlin/hse/nedikov/bash/Environment.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/Environment.kt @@ -1,6 +1,13 @@ package hse.nedikov.bash import hse.nedikov.bash.Environment.State.* +import hse.nedikov.bash.exceptions.DirectoryUpdateException +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.Files + + /** * Environment of the interpreter @@ -12,6 +19,48 @@ class Environment { private val varMap = HashMap() private var state: State = Working + private var curPath = Paths.get(".").toAbsolutePath().normalize() + + /** + * Switches directory + * @param change new directory path + * @return true is change was successful false otherwise + */ + fun updateDir(change: String) { + val newDirectory = curPath.resolve(change) + if (!Files.exists(newDirectory) || !Files.isDirectory(newDirectory)) { + throw DirectoryUpdateException("can't update current directory: specified directory doesn't exist or not a directory") + } + + curPath = newDirectory.toAbsolutePath().normalize() + } + + /** + * Function for full path for current path + * @param path path to convert + * @return full path + */ + fun getPathString(path: String): String { + return getPath(path).toAbsolutePath().normalize().toString() + } + + /** + * Function for getting file for path + * @param path path to file + * @return file for path + */ + private fun getPath(path: String): Path { + return curPath.resolve(path) + } + + /** + * Function for getting Path for path string + * @param path path string + * @return Path for path string + */ + fun getFile(path: String): File { + return getPath(path).toFile() + } /** * Map of the local variables diff --git a/bash/src/main/kotlin/hse/nedikov/bash/Lexer.kt b/bash/src/main/kotlin/hse/nedikov/bash/Lexer.kt old mode 100644 new mode 100755 diff --git a/bash/src/main/kotlin/hse/nedikov/bash/Parser.kt b/bash/src/main/kotlin/hse/nedikov/bash/Parser.kt old mode 100644 new mode 100755 diff --git a/bash/src/main/kotlin/hse/nedikov/bash/exceptions/DirectoryUpdateException.kt b/bash/src/main/kotlin/hse/nedikov/bash/exceptions/DirectoryUpdateException.kt new file mode 100644 index 0000000..b181f26 --- /dev/null +++ b/bash/src/main/kotlin/hse/nedikov/bash/exceptions/DirectoryUpdateException.kt @@ -0,0 +1,8 @@ +package hse.nedikov.bash.exceptions + +import java.lang.Exception + +/** + * Exception for directory updates errors + */ +internal class DirectoryUpdateException(message: String) : Exception("Error occurred while updating directory: $message") \ No newline at end of file diff --git a/bash/src/main/kotlin/hse/nedikov/bash/exceptions/IncorrectArgumentsException.kt b/bash/src/main/kotlin/hse/nedikov/bash/exceptions/IncorrectArgumentsException.kt new file mode 100644 index 0000000..c35827a --- /dev/null +++ b/bash/src/main/kotlin/hse/nedikov/bash/exceptions/IncorrectArgumentsException.kt @@ -0,0 +1,8 @@ +package hse.nedikov.bash.exceptions + +import java.lang.Exception + +/** + * Exception for incorrect arguments + */ +internal class IncorrectArgumentsException(message: String) : Exception("Incorrect arguments exception: $message") \ No newline at end of file diff --git a/bash/src/main/kotlin/hse/nedikov/bash/exceptions/ParseException.kt b/bash/src/main/kotlin/hse/nedikov/bash/exceptions/ParseException.kt old mode 100644 new mode 100755 diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt old mode 100644 new mode 100755 index 25eef02..e8067b4 --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt @@ -9,7 +9,7 @@ import java.io.* /** * Base class for all interpreter commands */ -abstract class Command { +abstract class Command(open val env: Environment) { protected abstract fun execute(input: PipedReader, output: PipedWriter) protected abstract fun execute(output: PipedWriter) @@ -46,12 +46,14 @@ abstract class Command { return Assign(name.take(name.length - 1), args, env) } return when (name) { - "echo" -> Echo(args) - "wc" -> WordCount(args) - "pwd" -> Pwd() + "echo" -> Echo(args, env) + "wc" -> WordCount(args, env) + "pwd" -> Pwd(env) "exit" -> Exit(env) - "cat" -> Cat(args) - else -> OuterCommand(name, args) + "cat" -> Cat(args, env) + "cd" -> Cd(args, env) + "ls" -> Ls(args, env) + else -> OuterCommand(name, args, env) } } } diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/EnvironmentCommand.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/EnvironmentCommand.kt deleted file mode 100644 index 69e7c33..0000000 --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/EnvironmentCommand.kt +++ /dev/null @@ -1,8 +0,0 @@ -package hse.nedikov.bash.logic - -import hse.nedikov.bash.Environment - -/** - * Base class for commands which uses environment - */ -abstract class EnvironmentCommand(open val env: Environment) : Command() \ No newline at end of file diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Cat.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Cat.kt old mode 100644 new mode 100755 index f117f99..f526b24 --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Cat.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Cat.kt @@ -1,5 +1,6 @@ package hse.nedikov.bash.logic.commands +import hse.nedikov.bash.Environment import hse.nedikov.bash.logic.Command import java.io.* import java.lang.Exception @@ -7,7 +8,7 @@ import java.lang.Exception /** * cat command which prints files entries to the output stream */ -class Cat(private val arguments: ArrayList) : Command() { +class Cat(private val arguments: ArrayList, override val env: Environment) : Command(env) { /** * Prints input to the output if has no arguments and prints entries of files from arguments otherwise */ @@ -22,7 +23,7 @@ class Cat(private val arguments: ArrayList) : Command() { override fun execute(output: PipedWriter) { for (arg in arguments) { try { - FileReader(arg).forEachLine { output.write(arg) } + FileReader(env.getPathString(arg)).forEachLine { output.write(arg) } } catch (e: Exception) { output.write("cat: ${e.message}") } diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Cd.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Cd.kt new file mode 100644 index 0000000..15e6926 --- /dev/null +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Cd.kt @@ -0,0 +1,35 @@ +package hse.nedikov.bash.logic.commands + +import hse.nedikov.bash.Environment +import hse.nedikov.bash.exceptions.DirectoryUpdateException +import hse.nedikov.bash.exceptions.IncorrectArgumentsException +import hse.nedikov.bash.logic.Command +import java.io.PipedReader +import java.io.PipedWriter + +/** + * Class that switches current directory + */ +class Cd(private val arguments: ArrayList, override val env: Environment) : Command(env) { + /** + * Changes the working directory + */ + override fun execute(input: PipedReader, output: PipedWriter) { + return execute(output) + } + + /** + * Changes the working directory + * switches to home of zero arguments + * switches to input dir if one argument and success + */ + override fun execute(output: PipedWriter) { + if (arguments.size > 1) { + throw IncorrectArgumentsException("extra arguments in cd command") + } + + val path = arguments.getOrElse(0) { System.getProperty("user.home") } + + env.updateDir(path) + } +} \ No newline at end of file diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Echo.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Echo.kt old mode 100644 new mode 100755 index fc25d94..bd356b2 --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Echo.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Echo.kt @@ -1,5 +1,6 @@ package hse.nedikov.bash.logic.commands +import hse.nedikov.bash.Environment import hse.nedikov.bash.logic.Command import java.io.* import java.util.* @@ -8,7 +9,7 @@ import java.util.* /** * echo command which prints arguments to the output */ -class Echo(private val arguments: ArrayList) : Command() { +class Echo(private val arguments: ArrayList, override val env: Environment = Environment()) : Command(env) { /** * Prints arguments which are joined with spaces to the output */ diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Ls.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Ls.kt new file mode 100644 index 0000000..ebda1f1 --- /dev/null +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Ls.kt @@ -0,0 +1,52 @@ +package hse.nedikov.bash.logic.commands + +import hse.nedikov.bash.Environment +import hse.nedikov.bash.exceptions.IncorrectArgumentsException +import hse.nedikov.bash.logic.Command +import java.io.FileNotFoundException +import java.io.PipedReader +import java.io.PipedWriter + +/** + * Class that prints all files and directories in specified path or current directory + */ +class Ls(private val arguments: ArrayList, override val env: Environment) : Command(env) { + /** + * Prints all files and directories in specified path or current directory + * @param input input stream + * @param output output stream + */ + override fun execute(input: PipedReader, output: PipedWriter) { + return execute(output) + } + + /** + * Prints all files and directories in specified path or current directory + * if arguments more than one error will occur + * @param output output stream + */ + override fun execute(output: PipedWriter) { + if (arguments.size > 1) { + throw IncorrectArgumentsException("extra arguments in ls command") + } + + val arg = env.getFile(arguments.getOrElse(0) { "./" }) + val sep = System.lineSeparator() + + if (!(arg.isFile || arg.isDirectory)) { + throw FileNotFoundException("Not a file or directory") + } + + if (arg.isDirectory) { + arg.listFiles().sorted().forEach { + if (!it.isHidden) { + output.write(it.name + sep) + } + } + + return + } + + output.write(arg.name + sep) + } +} diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/OuterCommand.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/OuterCommand.kt old mode 100644 new mode 100755 index ec44fd5..9dc5963 --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/OuterCommand.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/OuterCommand.kt @@ -1,5 +1,6 @@ package hse.nedikov.bash.logic.commands +import hse.nedikov.bash.Environment import hse.nedikov.bash.logic.Command import java.io.* import java.util.concurrent.Executors @@ -12,7 +13,7 @@ import java.util.concurrent.TimeUnit * Class for calling commands in outer interpreter * @param name name of the command */ -class OuterCommand(private val name: String, private val arguments: ArrayList) : Command() { +class OuterCommand(private val name: String, private val arguments: ArrayList, override val env: Environment) : Command(env) { /** * Calls the command in outer interpreter and print theirs output or error to the output * in case when the command is executed in less than 10 seconds @@ -48,7 +49,7 @@ class OuterCommand(private val name: String, private val arguments: ArrayList arguments.forEach { joiner.add(it) } }.toString() - return Runtime.getRuntime().exec("$environmentStart $command") + return Runtime.getRuntime().exec("$environmentStart $command", null, env.getFile("./")) } private class StreamGobbler(private val inputStream: InputStream, private val consumer: (String) -> Unit) : Runnable { diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Pwd.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Pwd.kt old mode 100644 new mode 100755 index 3084c17..fa0d31e --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Pwd.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Pwd.kt @@ -1,12 +1,13 @@ package hse.nedikov.bash.logic.commands +import hse.nedikov.bash.Environment import hse.nedikov.bash.logic.Command import java.io.* /** * pwd command which prints current working directory */ -class Pwd : Command() { +class Pwd(override val env: Environment) : Command(env) { /** * Prints current working directory to the output */ @@ -19,7 +20,7 @@ class Pwd : Command() { * Prints current working directory to the output */ override fun execute(output: PipedWriter) { - output.write(System.getProperty("user.dir") + "\n") + output.write(env.getPathString("./") + "\n") } } \ No newline at end of file diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/WordCount.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/WordCount.kt old mode 100644 new mode 100755 index a51069f..01e709e --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/WordCount.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/WordCount.kt @@ -1,5 +1,6 @@ package hse.nedikov.bash.logic.commands +import hse.nedikov.bash.Environment import hse.nedikov.bash.logic.Command import java.io.* import java.lang.Exception @@ -7,7 +8,7 @@ import java.lang.Exception /** * wc command which calculates count of lines, words and bytes in files or input */ -class WordCount(private val arguments: ArrayList) : Command() { +class WordCount(private val arguments: ArrayList, override val env: Environment) : Command(env) { /** * Calculates count of lines, words and bytes in input if arguments is empty and in files otherwise */ @@ -27,7 +28,7 @@ class WordCount(private val arguments: ArrayList) : Command() { val result = WCResult() for (arg in arguments) { try { - val r = calcInput(FileReader(arg)) + val r = calcInput(FileReader(env.getPathString(arg))) output.write("${r.lines} ${r.words} ${r.bytes} $arg\n") } catch (e: Exception) { output.write("wc: ${e.message}\n") diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Assign.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Assign.kt old mode 100644 new mode 100755 index ae66e21..2927188 --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Assign.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Assign.kt @@ -1,7 +1,7 @@ package hse.nedikov.bash.logic.environment import hse.nedikov.bash.Environment -import hse.nedikov.bash.logic.EnvironmentCommand +import hse.nedikov.bash.logic.Command import java.io.PipedReader import java.io.PipedWriter import java.lang.Exception @@ -11,7 +11,7 @@ import java.lang.Exception * @param name name of variable */ class Assign(private val name:String, private val arguments: ArrayList, override val env: Environment) - : EnvironmentCommand(env) { + : Command(env) { /** * Do nothing in this case diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Exit.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Exit.kt old mode 100644 new mode 100755 index 1c00c5a..f89d32c --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Exit.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/environment/Exit.kt @@ -1,13 +1,13 @@ package hse.nedikov.bash.logic.environment import hse.nedikov.bash.Environment -import hse.nedikov.bash.logic.EnvironmentCommand +import hse.nedikov.bash.logic.Command import java.io.* /** * Class for command which closes the interpreter */ -class Exit(override val env: Environment) : EnvironmentCommand(env) { +class Exit(override val env: Environment) : Command(env) { /** * Stops the interpreter */ diff --git a/bash/src/test/kotlin/hse/nedikov/bash/LexerTest.kt b/bash/src/test/kotlin/hse/nedikov/bash/LexerTest.kt old mode 100644 new mode 100755 diff --git a/bash/src/test/kotlin/hse/nedikov/bash/ParserTest.kt b/bash/src/test/kotlin/hse/nedikov/bash/ParserTest.kt old mode 100644 new mode 100755 diff --git a/bash/src/test/kotlin/hse/nedikov/bash/TestUtil.kt b/bash/src/test/kotlin/hse/nedikov/bash/TestUtil.kt old mode 100644 new mode 100755 diff --git a/bash/src/test/kotlin/hse/nedikov/bash/logic/commands/CommandsTest.kt b/bash/src/test/kotlin/hse/nedikov/bash/logic/commands/CommandsTest.kt old mode 100644 new mode 100755 index 5b1c74a..159394b --- a/bash/src/test/kotlin/hse/nedikov/bash/logic/commands/CommandsTest.kt +++ b/bash/src/test/kotlin/hse/nedikov/bash/logic/commands/CommandsTest.kt @@ -1,12 +1,17 @@ package hse.nedikov.bash.logic.commands +import hse.nedikov.bash.Environment import org.junit.Test import java.io.PipedReader import java.io.PipedWriter import java.util.* import hse.nedikov.bash.list +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.rules.TemporaryFolder class CommandsTest { @Test @@ -35,25 +40,25 @@ class CommandsTest { @Test fun catInputStream() { - val reader = Cat(list()).execute(readerFromString("kekes leles")) + val reader = Cat(list(), Environment()).execute(readerFromString("kekes leles")) assertEquals("kekes leles", stringFromReader(reader)) } @Test fun pwdSimple() { - val reader = Pwd().execute() + val reader = Pwd(Environment()).execute() assertTrue(stringFromReader(reader).isNotEmpty()) } @Test fun pwdSimpleWithInputStream() { - val reader = Pwd().execute(readerFromString("kekes leles")) + val reader = Pwd(Environment()).execute(readerFromString("kekes leles")) assertTrue(stringFromReader(reader).isNotEmpty()) } @Test fun wordCountSimple() { - val reader = WordCount(list()).execute(readerFromString("lol kek cheburek")) + val reader = WordCount(list(), Environment()).execute(readerFromString("lol kek cheburek")) assertEquals("1 3 16", stringFromReader(reader)) } @@ -70,4 +75,65 @@ class CommandsTest { return joiner.toString() } } + + @Rule + @JvmField + val testDir = TemporaryFolder() + + @Before + fun setup() { + testDir.newFolder("dir") + testDir.newFile("dir/file") + testDir.newFile("file") + } + + @After + fun clear() { + testDir.delete() + } + + /** + * Tests ls from current dir + */ + @Test + fun lsTest() { + val env = Environment() + env.updateDir(testDir.root.canonicalPath) + val reader = Ls(list(), env).execute() + assertEquals("dir\nfile", stringFromReader(reader)) + } + + /** + * Test ls with dir in argument + */ + @Test + fun lsTestSubdir() { + val env = Environment() + env.updateDir(testDir.root.canonicalPath) + val reader = Ls(list("dir"), env).execute() + assertEquals("file", stringFromReader(reader)) + } + + /** + * Test cd command forward step + */ + @Test + fun cdTestForward() { + val env = Environment() + env.updateDir(testDir.root.canonicalPath) + Cd(list("dir"), env).execute() + assertEquals("file", stringFromReader(Ls(list(), env).execute())) + } + + /** + * Test cd command backward step + */ + @Test + fun cdTestBackward() { + val env = Environment() + env.updateDir(testDir.root.canonicalPath) + Cd(list("dir"), env).execute() + Cd(list(".."), env).execute() + assertEquals("dir\nfile", stringFromReader(Ls(list(), env).execute())) + } } \ No newline at end of file diff --git a/bash/src/test/kotlin/hse/nedikov/bash/logic/environment/EnvironmentCommandsTest.kt b/bash/src/test/kotlin/hse/nedikov/bash/logic/environment/EnvironmentCommandsTest.kt old mode 100644 new mode 100755