diff --git a/bash/build.gradle b/bash/build.gradle index 60bedc5..298e0b7 100644 --- a/bash/build.gradle +++ b/bash/build.gradle @@ -15,6 +15,7 @@ repositories { dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + compile "com.xenomachina:kotlin-argparser:2.0.7" // https://mvnrepository.com/artifact/junit/junit testCompile group: 'junit', name: 'junit', version: '4.12' diff --git a/bash/gradle/wrapper/gradle-wrapper.properties b/bash/gradle/wrapper/gradle-wrapper.properties index d2c45a4..80b357e 100644 --- a/bash/gradle/wrapper/gradle-wrapper.properties +++ b/bash/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Tue Feb 12 13:01:42 MSK 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt index 25eef02..8954b4f 100644 --- a/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/Command.kt @@ -51,6 +51,7 @@ abstract class Command { "pwd" -> Pwd() "exit" -> Exit(env) "cat" -> Cat(args) + "grep" -> Grep(args) else -> OuterCommand(name, args) } } diff --git a/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Grep.kt b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Grep.kt new file mode 100644 index 0000000..8cf23b8 --- /dev/null +++ b/bash/src/main/kotlin/hse/nedikov/bash/logic/commands/Grep.kt @@ -0,0 +1,70 @@ +package hse.nedikov.bash.logic.commands + +import com.xenomachina.argparser.ArgParser +import com.xenomachina.argparser.ShowHelpException +import com.xenomachina.argparser.default +import hse.nedikov.bash.logic.Command +import java.io.* +import java.lang.IllegalArgumentException +import java.util.ArrayList + +/** + * grep command which prints all lines with pattern matched substring + */ +class Grep(private val arguments: ArrayList) : Command() { + /** + * Takes lines for grep from input + */ + override fun execute(input: PipedReader, output: PipedWriter) { + val args = parseArguments(::PipedGrepArgs, output) ?: return + grep(input, output, args) + } + + /** + * Takes lines for grep from file from arguments + */ + override fun execute(output: PipedWriter) { + val args = parseArguments(::GrepArgs, output) ?: return + grep(args.file, output, args) + } + + private fun parseArguments(constructor: (ArgParser) -> T, output: Writer): T? { + val arr = Array(arguments.size) { i -> arguments[i] } + try { + return ArgParser(arr).parseInto(constructor) + } catch (e: ShowHelpException) { + e.printUserMessage(output, "grep", 80) + } + return null; + } + + private fun grep(input: Reader, output: PipedWriter, args: PipedGrepArgs) { + var linesToWrite = 0; + input.forEachLine { + val wordEnd = if (args.wordRegexp) "\\b" else "" + val matchPattern = "$wordEnd${args.pattern}$wordEnd" + val regex = if (args.ignoreCase) Regex(matchPattern, RegexOption.IGNORE_CASE) else Regex(matchPattern) + if (it.contains(regex)) { + output.write("$it\n") + linesToWrite = args.afterContext; + } else if (linesToWrite > 0) { + output.write("$it\n") + linesToWrite-- + } + } + } +} + +private open class PipedGrepArgs(parser: ArgParser) { + val ignoreCase by parser.flagging("-i", "--ignore-case", help = "ignore case distinctions") + val wordRegexp by parser.flagging("-w", "--word-regexp", help = "force PATTERN to match only whole words") + val afterContext by parser.storing("-A", "--after-context", argName = "NUM", + help = "print NUM lines of trailing context") { toInt() } + .default(0) + .addValidator { if (this.value < 0) throw IllegalArgumentException("NUM should be non negative") } + val pattern by parser.positional("PATTERN") +} + +private class GrepArgs(parser: ArgParser) : PipedGrepArgs(parser) { + val file by parser.positional("FILE") { FileReader(this) } +} \ No newline at end of file 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 index 5547648..d5b3e8d 100644 --- a/bash/src/test/kotlin/hse/nedikov/bash/logic/commands/CommandsTest.kt +++ b/bash/src/test/kotlin/hse/nedikov/bash/logic/commands/CommandsTest.kt @@ -57,6 +57,57 @@ class CommandsTest { assertEquals("1 3 19", stringFromReader(reader)) } + @Test + fun grepTest() { + val reader = Grep(list("lol")).execute(readerFromString(keklolString)) + assertEquals(""" + lol kek + kek lol + lolol lo + looolol + lol + """.trimIndent(), toLF(stringFromReader(reader))) + } + + @Test + fun grepWordRegexpTest() { + val reader = Grep(list("lol", "-w")).execute(readerFromString(keklolString)) + assertEquals(""" + lol kek + kek lol + lol + """.trimIndent(), toLF(stringFromReader(reader))) + } + + @Test + fun grepIgnoreCaseTest() { + val reader = Grep(list("LoL", "-i")).execute(readerFromString(keklolString)) + assertEquals(""" + lol kek + kek lol + lolol lo + looolol + kekek LOL + lol + """.trimIndent(), toLF(stringFromReader(reader))) + } + + @Test + fun grepAfterContextTest() { + var reader = Grep(list("lol", "-A", "5")).execute(readerFromString(keklolString)) + assertEquals(keklolString, toLF(stringFromReader(reader))) + reader = Grep(list("lol", "-A", "1")).execute(readerFromString(keklolString)) + assertEquals(""" + lol kek + kek lol + kek kek + lolol lo + looolol + kek + lol + """.trimIndent(), toLF(stringFromReader(reader))) + } + companion object { fun readerFromString(string: String): PipedReader { val reader = PipedReader() @@ -69,5 +120,23 @@ class CommandsTest { reader.readLines().forEach { joiner.add(it) } return joiner.toString() } + + fun toLF(str: String) = str.replace("\r\n", "\n") + + + val keklolString = """ + lol kek + kek lol + kek kek + ke + eeek + lolol lo + looolol + kek + kekek LOL + kekekekekek + kekekekekekekek + lol + """.trimIndent() } } \ No newline at end of file