diff --git a/build.sbt b/build.sbt index 4111508..f195ed9 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,26 @@ lazy val `scala-courses` = library.scalaTestPlusMock % Test ) ) + .aggregate(`file-system-exercise`) + +lazy val `file-system-exercise` = + project + .in(file("filesystem")) + .enablePlugins(AutomateHeaderPlugin, GitVersioning, JavaAppPackaging, AshScriptPlugin) + .settings(name := "Virtual Filesystem (Exercise)") + .settings(settings) + .settings( + libraryDependencies ++= Seq( + library.log4j2Api, + library.log4j2Core, + library.log4j2Scala, + library.scalaLogging, + library.slf4j, + library.scalaCheck % Test, + library.specs2 % Test + ), + Docker / version := "0.1.0-SNAPSHOT" + ) // ***************************************************************************** // Library dependencies diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/FileSystem.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/FileSystem.scala new file mode 100644 index 0000000..7b2985d --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/FileSystem.scala @@ -0,0 +1,27 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs + +import java.util.Scanner + +import com.codingmaniacs.scala.exercises.fs.commands.Command +import com.codingmaniacs.scala.exercises.fs.directories.Directory + +object FileSystem { + + def main(args: Array[String]): Unit = { + + val root = Directory.ROOT + var state = State(root, root) + + val scanner = new Scanner(System.in) + + while (true) { + state.show() + state = Command.from(scanner.nextLine()).apply(state) + } + } + +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/State.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/State.scala new file mode 100644 index 0000000..82e71df --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/State.scala @@ -0,0 +1,23 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs + +import com.codingmaniacs.scala.exercises.fs.directories.Directory + +class State(val root: Directory, val workingDir: Directory, val output: String) { + + def show(): Unit = { + println(output) + print(State.SHELL_PROMPT) + } + + def setMessage(message: String): State = State(root, workingDir, message) +} + +object State { + val SHELL_PROMPT = "$ " + + def apply(root: Directory, workingDir: Directory, output: String = ""): State = new State(root, workingDir, output) +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Cat.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Cat.scala new file mode 100644 index 0000000..2612fc0 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Cat.scala @@ -0,0 +1,21 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State + +class Cat(filename: String) extends Command { + + override def apply(state: State): State = { + val wd = state.workingDir + val dirEntry = wd.findEntry(filename) + if (dirEntry == null || !dirEntry.isFile) { + state.setMessage(s"$filename: No such file") + } else { + state.setMessage(dirEntry.asFile.contents) + } + } + +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Cd.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Cd.scala new file mode 100644 index 0000000..4060702 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Cd.scala @@ -0,0 +1,73 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State +import com.codingmaniacs.scala.exercises.fs.directories.{ DirEntry, Directory } + +import scala.annotation.tailrec + +class Cd(dir: String) extends Command { + + override def apply(state: State): State = { + val root = state.root + + val wd = state.workingDir + + val absPath = if (dir.startsWith(Directory.SEPARATOR)) { + dir + } else if (wd.isRoot) { + wd.path + dir + } else { + wd.path + Directory.SEPARATOR + dir + } + + val dstDir = doFindEntry(root, absPath) + + if (dstDir == null || !dstDir.isDirectory) { + state.setMessage(s"$dir: Not such directory") + } else { + State(root, dstDir.asDirectory) + } + } + + def doFindEntry(root: Directory, absPath: String): DirEntry = { + + @tailrec + def findEntryHelper(currDir: Directory, path: List[String]): DirEntry = + if (path.isEmpty || path.head.isEmpty) currDir + else if (path.tail.isEmpty) currDir.findEntry(path.head) + else { + val nextDir = currDir.findEntry(path.head) + if (nextDir == null || !nextDir.isDirectory) null + else findEntryHelper(nextDir.asDirectory, path.tail) + } + + val tokens = absPath.substring(1).split(Directory.SEPARATOR).toList + + val newTokens = collapseRelativeTokens(tokens) + + if (newTokens == null) null else findEntryHelper(root, newTokens) + } + + def collapseRelativeTokens(tokens: List[String]): List[String] = { + @tailrec + def collapseTokensRec(tokens: List[String], res: List[String]): List[String] = + tokens match { + case List() => res + case h :: tail if h.equals(".") => collapseTokensRec(tail, res) + case h :: tail if h.equals("..") => + res match { + case List() => List() + case init :+ _ => collapseTokensRec(tail, init) + case _ => List() + } + case h :: tail => collapseTokensRec(tail, res :+ h) + } + + collapseTokensRec(tokens, List()) + } + +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Command.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Command.scala new file mode 100644 index 0000000..9eaee0c --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Command.scala @@ -0,0 +1,83 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State + +trait Command { + def apply(state: State): State +} + +object Command { + + val MKDIR = "mkdir" + val LS = "ls" + val PWD = "pwd" + val TOUCH = "touch" + val CD = "cd" + val RM = "rm" + val ECHO = "echo" + val CAT = "cat" + + def emptyCommand: Command = + new Command { + override def apply(state: State): State = state + } + + def incompleteCommand(name: String): Command = + new Command { + override def apply(state: State): State = state.setMessage(s"$name is an incomplete command") + } + + def from(input: String): Command = { + val tokens = input.split(" ") + if (input.isEmpty || tokens.isEmpty) { + emptyCommand + } else if (MKDIR.equals(tokens(0))) { + if (tokens.length < 2) { + incompleteCommand(MKDIR) + } else { + new MkDir(tokens(1)) + } + } else if (LS.equals(tokens(0))) { + new Ls() + } else if (PWD.equals(tokens(0))) { + new Pwd() + } else if (TOUCH.equals(tokens(0))) { + if (tokens.length < 2) { + incompleteCommand(TOUCH) + } else { + new Touch(tokens(1)) + } + } else if (CD.equals(tokens(0))) { + if (tokens.length < 2) { + incompleteCommand(CD) + } else { + new Cd(tokens(1)) + } + } else if (RM.equals(tokens(0))) { + if (tokens.length < 2) { + incompleteCommand(RM) + } else { + new Rm(tokens(1)) + } + } else if (ECHO.equals(tokens(0))) { + if (tokens.length < 2) { + incompleteCommand(ECHO) + } else { + new Echo(tokens.tail) + } + } else if (CAT.equals(tokens(0))) { + if (tokens.length < 2) { + incompleteCommand(CAT) + } else { + new Cat(tokens(1)) + } + } else { + new UnknownCommand + } + } + +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/CreateEntry.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/CreateEntry.scala new file mode 100644 index 0000000..da48863 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/CreateEntry.scala @@ -0,0 +1,51 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State +import com.codingmaniacs.scala.exercises.fs.directories.{ DirEntry, Directory } + +abstract class CreateEntry(name: String) extends Command { + + def updateStructure(currentDir: Directory, paths: List[String], newEntry: DirEntry): Directory = + if (paths.isEmpty) { + currentDir.addEntry(newEntry) + } else { + val oldEntry = currentDir.findEntry(paths.head).asDirectory + currentDir.replaceEntry(oldEntry.name, updateStructure(oldEntry, paths.tail, newEntry)) + } + + override def apply(state: State): State = { + val wd = state.workingDir + if (wd.hasEntry(name)) { + state.setMessage(s"$name already exists") + } else if (name.contains(Directory.SEPARATOR)) { + state.setMessage(s"$name must not contain separators") + } else if (checkIllegal(name)) { + state.setMessage(s"$name: illegal entry name!") + } else { + doCreateEntry(state) + } + } + + def checkIllegal(name: String): Boolean = name.contains(".") + + def doCreateEntry(state: State): State = { + val wd = state.workingDir + + val allDirsInPath = wd.getAllFoldersInPath + + val newEntry = createSpecificEntry(state) + + val newRoot = updateStructure(state.root, allDirsInPath, newEntry) + + val newWD = newRoot.findDescendant(allDirsInPath) + + State(newRoot, newWD) + + } + + def createSpecificEntry(state: State): DirEntry +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Echo.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Echo.scala new file mode 100644 index 0000000..a7fbfc5 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Echo.scala @@ -0,0 +1,94 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State +import com.codingmaniacs.scala.exercises.fs.directories.Directory +import com.codingmaniacs.scala.exercises.fs.files.File + +import scala.annotation.tailrec + +class Echo(items: Array[String]) extends Command { + + def createContent(items: Array[String], topIndex: Int): String = { + + @tailrec + def createContentRec(currIdx: Int, acc: String): String = + currIdx match { + case i if i >= topIndex => acc + case i => createContentRec(i + 1, acc + items(currIdx)) + } + + createContentRec(0, "") + } + + def getRootAfterEcho( + currentDir: Directory, + path: List[String], + contents: String, + append: Boolean + ): Directory = + if (path.isEmpty) { + currentDir + } else if (path.tail.isEmpty) { + val dirEntry = currentDir.findEntry(path.head) + if (dirEntry == null) { + currentDir.addEntry(new File(currentDir.path, path.head, contents)) + } else if (dirEntry.isDirectory) { + currentDir + } else { + if (append) { + currentDir.replaceEntry(path.head, dirEntry.asFile.appendContents(contents)) + } else { + currentDir.replaceEntry(path.head, dirEntry.asFile.setContents(contents)) + } + } + } else { + val nextDir = currentDir.findEntry(path.head).asDirectory + val newNextDir = getRootAfterEcho(nextDir, path.tail, contents, append) + + if (newNextDir == nextDir) { + currentDir + } else { + currentDir.replaceEntry(path.head, newNextDir) + } + } + + def doEcho(state: State, contents: String, filename: String, append: Boolean): State = + if (filename.contains(Directory.SEPARATOR)) { + state.setMessage("Echo: Filename must not contain separators") + } else { + val newRoot: Directory = getRootAfterEcho( + state.root, + state.workingDir.getAllFoldersInPath :+ filename, + contents, + append + ) + if (newRoot == state.root) { + state.setMessage(s"$filename: no such file") + } else { + State(newRoot, newRoot.findDescendant(state.workingDir.getAllFoldersInPath)) + } + } + + override def apply(state: State): State = + if (items.isEmpty) { + state + } else if (items.size == 1) { + state.setMessage(items(0)) + } else { + val op = items(items.length - 2) + val filename = items(items.length - 1) + val contents = createContent(items, items.length - 2) + if (">>".equals(op)) { + doEcho(state, contents, filename, append = true) + } else if (">".equals(op)) { + doEcho(state, contents, filename, append = false) + } else { + state.setMessage(createContent(items, items.length)) + } + } + +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Ls.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Ls.scala new file mode 100644 index 0000000..0ea0370 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Ls.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State +import com.codingmaniacs.scala.exercises.fs.directories.DirEntry + +class Ls extends Command { + + override def apply(state: State): State = { + val contents = state.workingDir.contents + val outputString = createOutputString(contents) + state.setMessage(outputString) + } + + def createOutputString(contents: List[DirEntry]): String = + if (contents.isEmpty) "" + else { + val entry = contents.head + s"${entry.name} [${entry.getType}]\n${createOutputString(contents.tail)}" + } + +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/MkDir.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/MkDir.scala new file mode 100644 index 0000000..76f6f2c --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/MkDir.scala @@ -0,0 +1,12 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State +import com.codingmaniacs.scala.exercises.fs.directories.{ DirEntry, Directory } + +class MkDir(name: String) extends CreateEntry(name) { + override def createSpecificEntry(state: State): DirEntry = Directory.empty(state.workingDir.path, name) +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Pwd.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Pwd.scala new file mode 100644 index 0000000..46d4303 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Pwd.scala @@ -0,0 +1,11 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State + +class Pwd extends Command { + override def apply(state: State): State = state.setMessage(state.workingDir.path) +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Rm.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Rm.scala new file mode 100644 index 0000000..9fc10a3 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Rm.scala @@ -0,0 +1,60 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State +import com.codingmaniacs.scala.exercises.fs.directories.Directory + +class Rm(name: String) extends Command { + + override def apply(state: State): State = { + val wd = state.workingDir + + val absPath = if (name.startsWith(Directory.SEPARATOR)) { + name + } else if (wd.isRoot) { + wd.path + name + } else { + wd.path + Directory.SEPARATOR + name + } + + if (Directory.ROOT_PATH.equals(absPath)) { + state.setMessage("Function not supported yet") + } else { + doRm(state, absPath) + } + } + + def doRm(state: State, path: String): State = { + def rmHelper(currDirectory: Directory, path: List[String]): Directory = + if (path.isEmpty) { + currDirectory + } else if (path.tail.isEmpty) { + currDirectory.removeEntry(path.head) + } else { + val nextDir = currDirectory.findEntry(path.head) + if (!nextDir.isDirectory) { + currDirectory + } else { + val newNextDir = rmHelper(nextDir.asDirectory, path.tail) + if (newNextDir == nextDir) { + currDirectory + } else { + currDirectory.replaceEntry(path.head, newNextDir) + } + } + } + + val tokens = path.substring(1).split(Directory.SEPARATOR).toList + val newRoot: Directory = rmHelper(state.root, tokens) + + if (newRoot == state.root) { + state.setMessage(s"$path: No such file or directory") + } else { + State(newRoot, newRoot.findDescendant(state.workingDir.path.substring(1))) + } + } + +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Touch.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Touch.scala new file mode 100644 index 0000000..0c58914 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/Touch.scala @@ -0,0 +1,13 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State +import com.codingmaniacs.scala.exercises.fs.directories.DirEntry +import com.codingmaniacs.scala.exercises.fs.files.File + +class Touch(name: String) extends CreateEntry(name) { + override def createSpecificEntry(state: State): DirEntry = File.empty(state.workingDir.path, name) +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/UnknownCommand.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/UnknownCommand.scala new file mode 100644 index 0000000..f618877 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/commands/UnknownCommand.scala @@ -0,0 +1,11 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.commands + +import com.codingmaniacs.scala.exercises.fs.State + +class UnknownCommand extends Command { + override def apply(state: State): State = state.setMessage("Command not found!") +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/directories/DirEntry.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/directories/DirEntry.scala new file mode 100644 index 0000000..dc235fa --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/directories/DirEntry.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.directories + +import com.codingmaniacs.scala.exercises.fs.files.File + +abstract class DirEntry(val parentPath: String, val name: String) { + + def path: String = { + val separatorIfNecessary = + if (Directory.ROOT_PATH.equals(parentPath)) "" else Directory.SEPARATOR + s"$parentPath$separatorIfNecessary$name" + } + + def asDirectory: Directory + + def asFile: File + + def getType: String + + def isDirectory: Boolean + def isFile: Boolean +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/directories/Directory.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/directories/Directory.scala new file mode 100644 index 0000000..db042e3 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/directories/Directory.scala @@ -0,0 +1,69 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.directories + +import com.codingmaniacs.scala.exercises.fs.files.{ File, FileSystemException } + +import scala.annotation.tailrec + +class Directory( + override val parentPath: String, + override val name: String, + val contents: List[DirEntry] +) extends DirEntry(parentPath, name) { + + def hasEntry(name: String): Boolean = findEntry(name) != null + + def getAllFoldersInPath: List[String] = path.substring(1).split(Directory.SEPARATOR).toList.filter(p => !p.isEmpty) + + def findDescendant(paths: List[String]): Directory = + if (paths.isEmpty) this + else findEntry(paths.head).asDirectory.findDescendant(paths.tail) + + def findDescendant(relativePath: String): Directory = + if (relativePath.isEmpty) this + else findDescendant(relativePath.split(Directory.SEPARATOR).toList) + + def addEntry(newEntry: DirEntry): Directory = new Directory(parentPath, name, contents :+ newEntry) + + def findEntry(entryName: String): DirEntry = { + + @tailrec + def findEntryHelper(name: String, contentsList: List[DirEntry]): DirEntry = + if (contentsList.isEmpty) { + null + } else if (contentsList.head.name.equals(name)) { + contentsList.headOption.get + } else findEntryHelper(name, contentsList.tail) + findEntryHelper(entryName, contents) + } + + def replaceEntry(entryName: String, newEntry: DirEntry): Directory = + new Directory(parentPath, name, contents.filter(e => !e.name.equals(entryName)) :+ newEntry) + + def isRoot: Boolean = parentPath.isEmpty + + def removeEntry(entryName: String): Directory = + if (!hasEntry(entryName)) this + else new Directory(parentPath, name, contents.filter(x => !x.name.equals(entryName))) + + override def asDirectory: Directory = this + + override def getType: String = "Directory" + + override def asFile: File = throw new FileSystemException("A folder cannot be converted to a file!") + + override def isDirectory: Boolean = true + override def isFile: Boolean = !isDirectory +} + +object Directory { + val SEPARATOR = "/" + val ROOT_PATH = "/" + + def empty(parentPath: String, name: String): Directory = new Directory(parentPath, name, List()) + + def ROOT: Directory = Directory.empty("", "") +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/files/File.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/files/File.scala new file mode 100644 index 0000000..cfd5bd0 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/files/File.scala @@ -0,0 +1,29 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.files + +import com.codingmaniacs.scala.exercises.fs.directories.{ DirEntry, Directory } + +class File(override val parentPath: String, override val name: String, val contents: String) + extends DirEntry(parentPath, name) { + override def asDirectory: Directory = throw new FileSystemException("A file cannot be converted to a directory!") + + override def getType: String = "File" + + override def asFile: File = this + + override def isDirectory: Boolean = false + override def isFile: Boolean = !isDirectory + + def setContents(newContents: String): File = new File(parentPath, name, newContents) + + def appendContents(newContents: String): File = setContents(contents + "\n" + newContents) + +} + +object File { + + def empty(parentPath: String, name: String): File = new File(parentPath, name, "") +} diff --git a/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/files/FileSystemException.scala b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/files/FileSystemException.scala new file mode 100644 index 0000000..adb06a4 --- /dev/null +++ b/filesystem/src/main/scala/com/codingmaniacs/scala/exercises/fs/files/FileSystemException.scala @@ -0,0 +1,7 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.files + +class FileSystemException(msg: String) extends RuntimeException(msg) {} diff --git a/filesystem/src/test/scala/com/codingmaniacs/scala/exercises/fs/directories/DirectorySpec.scala b/filesystem/src/test/scala/com/codingmaniacs/scala/exercises/fs/directories/DirectorySpec.scala new file mode 100644 index 0000000..f8019e1 --- /dev/null +++ b/filesystem/src/test/scala/com/codingmaniacs/scala/exercises/fs/directories/DirectorySpec.scala @@ -0,0 +1,9 @@ +/* + * Copyright (c) Geektimus + */ + +package com.codingmaniacs.scala.exercises.fs.directories + +import org.specs2.mutable.Specification + +class DirectorySpec extends Specification {}