Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ls and cd #3

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ru.spbau.mit.softwaredesign.cli.interpreter

import java.io._
import java.io.{File, _}
import java.net.URLDecoder
import java.nio.channels._

import org.apache.commons.io.IOUtils
Expand All @@ -15,6 +16,7 @@ import scala.util.Properties

/** Stores environment and performs command interpretation */
class CommandLineInterpreter {
private var workDirectory = System.getProperty("user.dir")
private val environment = mutable.Map[String, String]().withDefaultValue("")

/** Evaluate a pipeline of commands */
Expand Down Expand Up @@ -45,6 +47,8 @@ class CommandLineInterpreter {
case "wc" => evalWc(args, in, out)
case "pwd" => evalPwd(out)
case "grep" => evalGrep(args, in, out)
case "cd" => evalCd(args, out)
case "ls" => evalLs(args, out)
case "exit" => evalExit()
case commandName => evalExternal(commandName, args, in, out)
}
Expand All @@ -58,15 +62,17 @@ class CommandLineInterpreter {
} else {
args.foreach { arg =>
val fileName = processBlock(arg)
for (fileIn <- managed(new FileInputStream(fileName))) {
for (fileIn <- managed(new FileInputStream(getAbsolutePath(fileName)))) {
IOUtils.copy(fileIn, out)
}
}
}
}

private def evalEcho(args: List[Block], out: OutputStream): Unit = {
val concatArgs = args.map { processBlock } mkString " "
val concatArgs = args.map {
processBlock
} mkString " "
out.write((concatArgs + Properties.lineSeparator).getBytes)
}

Expand All @@ -85,7 +91,7 @@ class CommandLineInterpreter {
} else {
args.foreach { arg =>
val fileName = processBlock(arg)
for (fileIn <- managed(new FileInputStream(fileName))) {
for (fileIn <- managed(new FileInputStream(getAbsolutePath(fileName)))) {
val source = Source.fromInputStream(fileIn).mkString
val (lines, words, bytes) = countLinesWordsBytes(source)
out.write(s"$lines $words $bytes $fileName${Properties.lineSeparator}".getBytes)
Expand All @@ -95,8 +101,7 @@ class CommandLineInterpreter {
}

private def evalPwd(out: OutputStream): Unit = {
val userDir = System.getProperty("user.dir")
out.write((userDir + Properties.lineSeparator).getBytes)
out.write((workDirectory + Properties.lineSeparator).getBytes)
}

private def evalGrep(args: List[Block], in: InputStream, out: OutputStream): Unit = {
Expand All @@ -109,23 +114,25 @@ class CommandLineInterpreter {
val parser = new OptionParser[Config]("grep") {
head("grep", "0.99")

opt[Unit]('i', "ignore-case").action( (_, config) =>
config.copy(ignoreCase = true) ).text("Ignore case distinctions in both the <pattern> and the input files.")
opt[Unit]('i', "ignore-case").action((_, config) =>
config.copy(ignoreCase = true)).text("Ignore case distinctions in both the <pattern> and the input files.")

opt[Unit]('w', "word_regexp").action( (_, config) =>
config.copy(wordRegexp = true) ).text("Select only those lines containing matches that form whole words.")
opt[Unit]('w', "word_regexp").action((_, config) =>
config.copy(wordRegexp = true)).text("Select only those lines containing matches that form whole words.")

opt[Int]('A', "after-context").action( (value, config) =>
config.copy(afterContext = value) ).text("Print <value> lines of trailing context after matching lines.")
opt[Int]('A', "after-context").action((value, config) =>
config.copy(afterContext = value)).text("Print <value> lines of trailing context after matching lines.")

arg[String]("<pattern>").required().action( (regex, config) =>
config.copy(pattern = regex) ).text("Use regex as the pattern.")
arg[String]("<pattern>").required().action((regex, config) =>
config.copy(pattern = regex)).text("Use regex as the pattern.")

arg[String]("<file>...").unbounded().optional().action( (fileName, config) =>
config.copy(fileNames = config.fileNames :+ fileName) ).text("Obtain patterns from files, one per line.")
arg[String]("<file>...").unbounded().optional().action((fileName, config) =>
config.copy(fileNames = config.fileNames :+ fileName)).text("Obtain patterns from files, one per line.")
}

parser.parse(args.map { processBlock }, Config()) match {
parser.parse(args.map {
processBlock
}, Config()) match {
case Some(Config(ignoreCase, wordRegexp, afterContext, pattern, fileNames)) =>
var regexBuilder = pattern
if (ignoreCase)
Expand Down Expand Up @@ -158,7 +165,7 @@ class CommandLineInterpreter {
handleInput(in)
} else {
fileNames.foreach { fileName =>
for (fileIn <- managed(new FileInputStream(fileName))) {
for (fileIn <- managed(new FileInputStream(getAbsolutePath(fileName)))) {
handleInput(fileIn)
}
}
Expand All @@ -167,12 +174,49 @@ class CommandLineInterpreter {
}
}

private def evalCd(args: List[Block], out: OutputStream): Unit = {
if (args.isEmpty) {
workDirectory = System.getProperty("user.home")
} else {
val dirName = processBlock(args.head)
val dir = new File(getAbsolutePath(dirName))
if (dir.exists() && dir.isDirectory) {
workDirectory = dir.getCanonicalPath
} else {
throw new IOException(getAbsolutePath(dirName) + ": Нет такого каталога")
}
}
}

private def evalLs(args: List[Block], out: OutputStream): Unit = {
var destName = workDirectory
if (args.nonEmpty) {
destName = processBlock(args.head)
destName = getAbsolutePath(destName)
}
val destination = new File(destName)
if (destination.exists()) {
if (destination.isDirectory) {
val notHiddenFiles = destination.listFiles().filterNot(_.isHidden)
notHiddenFiles.foreach { file =>
out.write((file.getName + Properties.lineSeparator).getBytes)
}
} else if (destination.isFile) {
out.write((destination.getName + Properties.lineSeparator).getBytes())
}
} else {
throw new IOException(destName + ": Нет такого файла или каталога")
}
}

private def evalExit(): Unit = {
System.exit(0)
}

private def evalExternal(commandName: String, args: List[Block], in: InputStream, out: OutputStream): Unit = {
val concatArgs = args.map { processBlock } mkString " "
val concatArgs = args.map {
processBlock
} mkString " "
val logger = ProcessLogger(line => out.write((line + Properties.lineSeparator).getBytes), _ => ())
if (in.equals(System.in)) {
s"$commandName $concatArgs" ! logger
Expand All @@ -185,4 +229,13 @@ class CommandLineInterpreter {
case Literal(value) => value
case Substitution(Literal(value)) => environment(value)
}.mkString

private def getAbsolutePath(fileName: String): String = {
val decodedFileName = URLDecoder.decode(fileName, "UTF-8")
if (decodedFileName.startsWith(workDirectory)) {
decodedFileName
} else {
workDirectory + "/" + decodedFileName
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ru.spbau.mit.softwaredesign.cli.interpreter

import java.io.{InputStream, OutputStream}
import java.net.URLDecoder
import java.nio.channels.{Channels, Pipe}

import org.scalatest.FlatSpec
Expand All @@ -26,16 +27,32 @@ class InterpreterSpec extends FlatSpec {
}
}

def evalCommandLine(commandLine: String, cli: CommandLineInterpreter, sink: OutputStream): Unit = {
def parseCommandLine(commandLine: String): Composition = {
CommandLineParsers.parse(commandLine) match {
case CommandLineParsers.Success(pipeline, _) => pipeline
case _ => fail
}
def parseCommandLine(commandLine: String): Composition = {
CommandLineParsers.parse(commandLine) match {
case CommandLineParsers.Success(pipeline, _) => pipeline
case _ => fail
}
}

def evalCommandLine(commandLine: String, cli: CommandLineInterpreter, sink: OutputStream): Unit = {
val parsedCommandLine = parseCommandLine(commandLine)
cli.eval(parsedCommandLine, System.in, sink)
sink.close()
}

def evalCdCommand(commandLine: String, cli: CommandLineInterpreter, sink: OutputStream) : Unit ={
val userDir = System.getProperty("user.dir")
cli.eval(parseCommandLine("cd " + userDir), System.in, sink)
val parsedCommandLine = parseCommandLine(commandLine)
cli.eval(parsedCommandLine, System.in, sink)
cli.eval(parseCommandLine("pwd"), System.in, sink)
sink.close()
}

def evalLsCommand(commandLine : String, cli: CommandLineInterpreter, sink:OutputStream) : Unit ={
val testDirPath = URLDecoder.decode(getClass.getResource("/test_folder").getPath, "UTF-8")
cli.eval(parseCommandLine("cd " + testDirPath), System.in, sink)
cli.eval(parseCommandLine(commandLine), System.in, sink)
sink.close()
}

Expand Down Expand Up @@ -64,7 +81,7 @@ class InterpreterSpec extends FlatSpec {
val testFilePath = getClass.getResource("/lorem_ipsum.txt").getPath
val commandLine = s"cat $testFilePath"
evalCommandLine(commandLine, cli, sink)
val expected = Source.fromFile(testFilePath).mkString
val expected = Source.fromFile(URLDecoder.decode(testFilePath, "UTF-8")).mkString
val actual = Source.fromInputStream(source).mkString
assertResult(expected)(actual)
}
Expand Down Expand Up @@ -184,9 +201,52 @@ class InterpreterSpec extends FlatSpec {
}
}

it should "interpret cd with no args" in withInterpreter { cli =>
withPipe { (sink, source) =>
evalCdCommand("cd", cli, sink)
val expected = System.getProperty("user.home") + Properties.lineSeparator
val actual = Source.fromInputStream(source).mkString
assertResult(expected)(actual)
}
}

it should "interpret cd with args" in withInterpreter { cli =>
withPipe { (sink, source) =>
val testDirPath = URLDecoder.decode(getClass.getResource("/test_folder").getPath, "UTF-8")
evalCdCommand("cd " + testDirPath, cli, sink)
val expected = testDirPath + Properties.lineSeparator
val actual = Source.fromInputStream(source).mkString
assertResult(expected)(actual)
}
}

it should "interpret ls with no args" in withInterpreter { cli =>
withPipe { (sink, source) =>
evalLsCommand("ls", cli, sink)
val testFilePath = URLDecoder.decode(getClass
.getResource("/test_folder/ls_command_result.txt")
.getPath, "UTF-8")
val expected = Source.fromFile(testFilePath).mkString
val actual = Source.fromInputStream(source).mkString + Properties.lineSeparator
assertResult(expected)(actual)
}
}

it should "interpret ls with args" in withInterpreter { cli =>
withPipe { (sink, source) =>
evalLsCommand("ls test_folder_2", cli, sink)
val testFilePath = URLDecoder.decode(getClass
.getResource("/test_folder/test_folder_2/ls_command_result.txt")
.getPath, "UTF-8")
val expected = Source.fromFile(testFilePath).mkString
val actual = Source.fromInputStream(source).mkString + Properties.lineSeparator
assertResult(expected)(actual)
}
}

it should "interpret external command with args" in withInterpreter { cli =>
withPipe { (sink, source) =>
val testFilePath = getClass.getResource("/lorem_ipsum.txt").getPath
val testFilePath = URLDecoder.decode(getClass.getResource("/lorem_ipsum.txt").getPath, "UTF-8")
val commandLine = s"head -n 1 $testFilePath"
evalCommandLine(commandLine, cli, sink)
val expected = Source.fromFile(testFilePath).getLines().next() + Properties.lineSeparator
Expand Down