From 7aaa308bddb77391708d309935b765222a1ff96f Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 28 Nov 2021 13:06:10 +0800 Subject: [PATCH] EOL Ammonite-Ops and Ammonite-Shell (#1227) These two modules were an interesting experiment almost a decade ago, but overall usage has not panned out. Ammonite-Ops is largely replaced by https://github.com/com-lihaoyi/os-lib, even if it's not an exact match. Ammonite-Shell never really saw widespread usage: the vast majority of Ammonite users use Ammonite as a Scala REPL and script runner in addition to (rather than instead of) their default Bash/Zsh/etc. shells. This PR deletes everything related to these modules: their implementation, build config, and docs. This should help shrink the codebase, simplify the codebase, and let us focus more clearly on what's important going forward --- .../scala/ammonite/repl/api/ReplAPI.scala | 3 +- .../main/scala/ammonite/repl/ApiImpls.scala | 9 +- .../main/scala/ammonite/repl/PPrints.scala | 27 -- .../scala/ammonite/session/BuiltinTests.scala | 12 +- .../scala/ammonite/session/ProjectTests.scala | 8 +- .../test/scala/ammonite/unit/ToolsTests.scala | 105 ---- .../scripts/predefWithLoad/PredefLoadExec.sc | 2 +- .../main/InProcessMainMethodRunner.scala | 2 +- build.sc | 133 +++--- ci/upload.sc | 5 +- .../ammonite/integration/basic/Print.sc | 3 +- .../ammonite/integration/basic/Resources.sc | 5 +- .../ammonite/integration/basic/Spark2.sc | 1 - .../integration/ProjectTests213.scala | 5 +- .../ammonite/integration/BasicTests.scala | 35 -- .../integration/ErrorTruncationTests.scala | 12 +- .../integration/LineNumberTests.scala | 5 +- .../ammonite/integration/ProjectTests.scala | 6 +- .../ammonite/integration/TestUtils.scala | 28 +- internals-docs/packages.md | 8 +- .../ammonite/ops/FilterMapExt.scala | 60 --- .../main/scala-2.12/ammonite/ops/LsSeq.scala | 11 - .../ammonite/ops/FilterMapExt.scala | 68 --- .../main/scala-2.13/ammonite/ops/LsSeq.scala | 24 - .../main/scala/ammonite/ops/Extensions.scala | 129 ----- ops/src/main/scala/ammonite/ops/FileOps.scala | 148 ------ .../main/scala/ammonite/ops/Shellout.scala | 213 --------- ops/src/main/scala/ammonite/ops/package.scala | 134 ------ ops/src/test/resources/scripts/echo | 1 - ops/src/test/resources/scripts/echo_with_wd | 1 - .../test/ammonite/ops/folder/file.txt | 1 - ops/src/test/resources/testdata/File.txt | 4 - .../folder1/Yoghurt Curds Cream Cheese.txt | 4 - .../testdata/folder2/folder2a/I am.txt | 6 - .../resources/testdata/folder2/folder2b/b.txt | 6 - .../test/ammonite/ops/ExampleTests.scala | 323 ------------- .../scala/test/ammonite/ops/OpTests.scala | 281 ----------- .../scala/test/ammonite/ops/PathTests.scala | 363 -------------- .../test/ammonite/ops/ShelloutTests.scala | 110 ----- .../test/scala/test/ammonite/ops/unix.scala | 13 - readme.md | 5 - readme/Cookbook.scalatex | 15 +- readme/Footer.scalatex | 34 +- readme/Index.scalatex | 29 +- readme/Ops.scalatex | 427 ----------------- readme/Repl.scalatex | 12 +- readme/Sample.scala | 9 +- readme/Scripts.scalatex | 4 +- readme/Shell.scalatex | 448 ------------------ .../resources/ammonite/shell/empty-predef.sc | 1 - .../ammonite/shell/example-predef-bare.sc | 16 - .../ammonite/shell/example-predef.sc | 11 - .../main/scala/ammonite/shell/Configure.scala | 29 -- .../scala/ammonite/shell/PathComplete.scala | 232 --------- .../scala/ammonite/shell/ShellSession.scala | 63 --- .../ammonite/shell/PathCompleteTests.scala | 66 --- .../scala/ammonite/shell/SessionTests.scala | 263 ---------- .../test/scala/ammonite/shell/TestMain.scala | 17 - .../main/scala/ammonite/sshd/SshServer.scala | 1 - 59 files changed, 131 insertions(+), 3865 deletions(-) delete mode 100644 amm/repl/src/test/scala/ammonite/unit/ToolsTests.scala delete mode 100644 ops/src/main/scala-2.12/ammonite/ops/FilterMapExt.scala delete mode 100644 ops/src/main/scala-2.12/ammonite/ops/LsSeq.scala delete mode 100644 ops/src/main/scala-2.13/ammonite/ops/FilterMapExt.scala delete mode 100644 ops/src/main/scala-2.13/ammonite/ops/LsSeq.scala delete mode 100644 ops/src/main/scala/ammonite/ops/Extensions.scala delete mode 100644 ops/src/main/scala/ammonite/ops/FileOps.scala delete mode 100644 ops/src/main/scala/ammonite/ops/Shellout.scala delete mode 100644 ops/src/main/scala/ammonite/ops/package.scala delete mode 100755 ops/src/test/resources/scripts/echo delete mode 100755 ops/src/test/resources/scripts/echo_with_wd delete mode 100644 ops/src/test/resources/test/ammonite/ops/folder/file.txt delete mode 100644 ops/src/test/resources/testdata/File.txt delete mode 100644 ops/src/test/resources/testdata/folder1/Yoghurt Curds Cream Cheese.txt delete mode 100644 ops/src/test/resources/testdata/folder2/folder2a/I am.txt delete mode 100644 ops/src/test/resources/testdata/folder2/folder2b/b.txt delete mode 100644 ops/src/test/scala/test/ammonite/ops/ExampleTests.scala delete mode 100644 ops/src/test/scala/test/ammonite/ops/OpTests.scala delete mode 100644 ops/src/test/scala/test/ammonite/ops/PathTests.scala delete mode 100644 ops/src/test/scala/test/ammonite/ops/ShelloutTests.scala delete mode 100644 ops/src/test/scala/test/ammonite/ops/unix.scala delete mode 100644 readme/Ops.scalatex delete mode 100644 readme/Shell.scalatex delete mode 100644 shell/src/main/resources/ammonite/shell/empty-predef.sc delete mode 100644 shell/src/main/resources/ammonite/shell/example-predef-bare.sc delete mode 100644 shell/src/main/resources/ammonite/shell/example-predef.sc delete mode 100644 shell/src/main/scala/ammonite/shell/Configure.scala delete mode 100644 shell/src/main/scala/ammonite/shell/PathComplete.scala delete mode 100644 shell/src/main/scala/ammonite/shell/ShellSession.scala delete mode 100644 shell/src/test/scala/ammonite/shell/PathCompleteTests.scala delete mode 100644 shell/src/test/scala/ammonite/shell/SessionTests.scala delete mode 100644 shell/src/test/scala/ammonite/shell/TestMain.scala diff --git a/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala b/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala index 4efa62aaf..1f6413e2c 100644 --- a/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala +++ b/amm/repl/api/src/main/scala/ammonite/repl/api/ReplAPI.scala @@ -1,6 +1,5 @@ package ammonite.repl.api -import ammonite.ops.Internals import ammonite.util._ import scala.reflect.runtime.universe._ @@ -220,5 +219,5 @@ trait Clipboard{ * * @param data New contents for the clipboard. */ - def write(data: Internals.Writable): Unit + def write(data: geny.Writable): Unit } diff --git a/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala b/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala index 5e8957197..07c13b74c 100644 --- a/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala +++ b/amm/repl/src/main/scala/ammonite/repl/ApiImpls.scala @@ -1,13 +1,12 @@ package ammonite.repl -import ammonite.ops.Internals import ammonite.repl.api.{Clipboard, FrontEnd, FrontEndAPI, Session} import ammonite.runtime._ import ammonite.util.Util._ import ammonite.util.{Frame => _, _} - import java.awt.Toolkit import java.awt.datatransfer.{DataFlavor, StringSelection} +import java.io.ByteArrayOutputStream import java.util.Locale import scala.collection.mutable @@ -130,9 +129,11 @@ object ClipboardImpl extends Clipboard { case _ => "" } - override def write(data: Internals.Writable): Unit = { + override def write(data: geny.Writable): Unit = { + val out = new ByteArrayOutputStream() + data.writeBytesTo(out) val newContents = new StringSelection( - data.writeableData.map(new String(_)).mkString + new String(out.toByteArray) ) systemClipboard.setContents(newContents, newContents) } diff --git a/amm/repl/src/main/scala/ammonite/repl/PPrints.scala b/amm/repl/src/main/scala/ammonite/repl/PPrints.scala index bbb76a5b6..9b9e7f5da 100644 --- a/amm/repl/src/main/scala/ammonite/repl/PPrints.scala +++ b/amm/repl/src/main/scala/ammonite/repl/PPrints.scala @@ -1,6 +1,5 @@ package ammonite.repl -import ammonite.ops.{CommandResult, LsSeq} import ammonite.repl.api.History import ammonite.runtime.tools.GrepResult import ammonite.util.Util @@ -8,29 +7,12 @@ import pprint.Renderer object PPrints{ def replPPrintHandlers(width: => Int): PartialFunction[Any, pprint.Tree] = { - case x: ammonite.ops.LsSeq => PPrints.lsSeqRepr(x, width) // case x: os.Path => PPrints.pathRepr(x) // case x: os.RelPath => PPrints.relPathRepr(x) -// case x: ammonite.ops.Path => PPrints.pathRepr(os.Path(x.toString)) -// case x: ammonite.ops.RelPath => PPrints.relPathRepr(os.RelPath(x.toString)) - case x: ammonite.ops.CommandResult => PPrints.commandResultRepr(x) case t: History => pprint.Tree.Lazy(ctx => Iterator(t.mkString(Util.newLine))) case t: GrepResult => pprint.Tree.Lazy(ctx => Iterator(GrepResult.grepResultRepr(t, ctx))) case t: scala.xml.Elem => pprint.Tree.Lazy(_ => Iterator(t.toString)) } - def lsSeqRepr(t: LsSeq, width: Int) = pprint.Tree.Lazy { ctx => - val renderer = new Renderer( - ctx.width, ctx.applyPrefixColor, ctx.literalColor, ctx.indentStep - ) - val snippets = for (p <- t) yield { - fansi.Str.join( - renderer.rec(relPathRepr(os.RelPath(p.relativeTo(t.base).toString)), 0, 0) - .iter - .toStream:_* - ) - } - Iterator(Util.newLine) ++ FrontEndUtils.tabulate(snippets, width) - } def reprSection(s: String, cfg: pprint.Tree.Ctx): fansi.Str = { @@ -53,14 +35,5 @@ object PPrints{ Iterator("root") ++ p.segments.map("/" + reprSection(_, ctx)) ) - def commandResultRepr(x: CommandResult) = pprint.Tree.Lazy(ctx => - x.chunks.iterator.flatMap { chunk => - val (color, s) = chunk match{ - case Left(s) => (ctx.literalColor, s) - case Right(s) => (fansi.Color.Red, s) - } - Iterator(Util.newLine, color(new String(s.array)).render) - } - ) } diff --git a/amm/repl/src/test/scala/ammonite/session/BuiltinTests.scala b/amm/repl/src/test/scala/ammonite/session/BuiltinTests.scala index 1c40150e5..45c603f7c 100644 --- a/amm/repl/src/test/scala/ammonite/session/BuiltinTests.scala +++ b/amm/repl/src/test/scala/ammonite/session/BuiltinTests.scala @@ -71,20 +71,18 @@ object BuiltinTests extends TestSuite{ test("loadCP"){ check.session(""" - @ import ammonite.ops._, ImplicitWd._ + @ val javaSrc = os.pwd/"amm"/"src"/"test"/"resources"/"loadable"/"hello"/"Hello.java" - @ val javaSrc = pwd/"amm"/"src"/"test"/"resources"/"loadable"/"hello"/"Hello.java" + @ os.makeDir.all(os.pwd/"target"/"loadCP"/"hello") - @ mkdir! pwd/"target"/"loadCP"/"hello" + @ os.copy.over(javaSrc, os.pwd/"target"/"loadCP"/"hello"/"Hello.java") - @ cp.over(javaSrc, pwd/"target"/"loadCP"/"hello"/"Hello.java") - - @ %javac "target"/"loadCP"/"hello"/"Hello.java" //This line causes problems in windows + @ os.proc("javac", os.rel / "target"/"loadCP"/"hello"/"Hello.java").call() //This line causes problems in windows @ import $cp.target.loadCP //This line causes problems in windows @ hello.Hello.hello() - res6: String = "Hello!" + res5: String = "Hello!" """) } test("settings"){ diff --git a/amm/repl/src/test/scala/ammonite/session/ProjectTests.scala b/amm/repl/src/test/scala/ammonite/session/ProjectTests.scala index 17d671598..e6e7a7608 100644 --- a/amm/repl/src/test/scala/ammonite/session/ProjectTests.scala +++ b/amm/repl/src/test/scala/ammonite/session/ProjectTests.scala @@ -154,18 +154,16 @@ object ProjectTests extends TestSuite{ // duplicate type CC#44165; previous was type CC#44157 // (2.13 / 3 compatibility issue?) if (check.scala2 && !check.scala2_12) check.session(""" - @ import ammonite.ops._ - @ val path = { - @ resource/"org"/"apache"/"jackrabbit"/"oak"/"plugins"/"blob"/"blobstore.properties" + @ os.resource/"org"/"apache"/"jackrabbit"/"oak"/"plugins"/"blob"/"blobstore.properties" @ } - @ read! path + @ os.read(path) error: ResourceNotFoundException @ import $ivy.`org.apache.jackrabbit:oak-core:1.3.16` - @ read! path // Should work now + @ os.read(path) // Should work now """) } test("scalaparse"){ diff --git a/amm/repl/src/test/scala/ammonite/unit/ToolsTests.scala b/amm/repl/src/test/scala/ammonite/unit/ToolsTests.scala deleted file mode 100644 index 1c995827d..000000000 --- a/amm/repl/src/test/scala/ammonite/unit/ToolsTests.scala +++ /dev/null @@ -1,105 +0,0 @@ -package ammonite.unit - - -import ammonite.runtime.tools._ -import ammonite.util.Util.newLine -import utest._ - - -object ToolsTests extends TestSuite{ - - val tests = Tests { - var wd = os.pwd - /** - * Convert the highlighter colors into angle brackets for easy testing - */ - implicit val defaultHighlightColor = ammonite.runtime.tools.GrepResult.Color( - fansi.Color.Red, fansi.Attrs.Empty - ) - - test("grep"){ - - implicit val pprinter = pprint.PPrinter.Color.copy( - colorLiteral = fansi.Attr.Reset, - defaultWidth = 25, - additionalHandlers = { - case t: GrepResult => pprint.Tree.Lazy(ctx => Iterator(GrepResult.grepResultRepr(t, ctx))) - } - ) - val items = Seq(123, 456, 789) - import ammonite.ops._ - test("filter"){ - assert( - (items |? grep! "45") == Seq(456), - (items |? grep! "45".r) == Seq(456), - (items |? grep! "[123456]+".r) == Seq(123, 456), - (items |? grep! "^[123456]+$".r) == Seq(123, 456), - (items |? grep! "[123456]".r) == Seq(123, 456), - (items |? grep! "^[123456]$".r) == Seq() - ) - } - test("flatMap"){ - def check[T: Grepper](items: Seq[Any], regex: T, expected: Seq[String]) = { - - val grepped = items || grep! regex - val displayed = - for(g <- grepped) - yield { - pprinter.tokenize(g) - .mkString - .replace(fansi.Color.Red.escape, "<") - .replace(fansi.Color.Reset.escape, ">") - } - assert(displayed == expected) - } - test("string"){ - check(items, "12", Seq("<12>3")) - check(items, "23", Seq("1<23>")) - - } - test("regex"){ - check(items, "^[123456]".r, Seq("<1>23", "<4>56")) - check(items, "[123456]$".r, Seq("12<3>", "45<6>")) - } - test("long"){ - val longItems = Seq("123456789012345678901234567890") - - // If you grep near the start, peg the context to the start - test("truncateStart") - check( - longItems, - "\"123".r, - Seq("<\"123>45678901234567...") - ) - // If you grep near the end, peg the context to the end - test("truncateEnd") - check( - longItems, - "890\"".r, - Seq("...45678901234567<890\">") - ) - - // If your greps are close together, peg around the middle - test("closeTogether") - check( - longItems, - "0123", - Seq("...89<0123>456789<0123>45...") - ) - - // If your greps are far apart, peg each one - test("farApart") - check( - longItems, - "\"123|890\"".r, - Seq("<\"123>45678901234567..." + newLine + "...45678901234567<890\">") - ) - - // Make sure that when the different matches are relatively close - // together, the snippets of context displayed do not overlap. - test("noOverlap") - check( - longItems, - "123", - Seq("\"<123>4567890<123>4567..." + newLine + "...890<123>4567890\"") - ) - } - } - } - } -} diff --git a/amm/src/test/resources/scripts/predefWithLoad/PredefLoadExec.sc b/amm/src/test/resources/scripts/predefWithLoad/PredefLoadExec.sc index 49a5535a2..148679f1a 100644 --- a/amm/src/test/resources/scripts/predefWithLoad/PredefLoadExec.sc +++ b/amm/src/test/resources/scripts/predefWithLoad/PredefLoadExec.sc @@ -1,5 +1,5 @@ repl.load.exec( - ammonite.ops.pwd/"amm"/"src"/"test"/"resources"/"scripts"/"predefWithLoad"/"Loaded.sc" + os.pwd/"amm"/"src"/"test"/"resources"/"scripts"/"predefWithLoad"/"Loaded.sc" ) @ diff --git a/amm/src/test/scala/ammonite/main/InProcessMainMethodRunner.scala b/amm/src/test/scala/ammonite/main/InProcessMainMethodRunner.scala index c261627b4..d187c2683 100644 --- a/amm/src/test/scala/ammonite/main/InProcessMainMethodRunner.scala +++ b/amm/src/test/scala/ammonite/main/InProcessMainMethodRunner.scala @@ -30,7 +30,7 @@ class InProcessMainMethodRunner(p: os.RelPath, preArgs: List[String], args: Seq[ Console.withErr(err0){ Console.withOut(out0){ ammonite.Main.main0( - List("--home", ammonite.ops.tmp.dir().toString) ++ + List("--home", os.temp.dir().toString) ++ preArgs ++ Seq(path.toString) ++ args.toList, diff --git a/build.sc b/build.sc index 9b33a884a..b617f8d0e 100644 --- a/build.sc +++ b/build.sc @@ -1,5 +1,4 @@ import mill._, scalalib._, publish._ -import ammonite.ops._, ImplicitWd._ import coursier.mavenRepositoryString import $file.ci.upload @@ -96,7 +95,7 @@ object Deps { val pprint = ivy"com.lihaoyi::pprint:0.6.6" val requests = ivy"com.lihaoyi::requests:0.6.9" val scalacheck = ivy"org.scalacheck::scalacheck:1.14.0" - val scalaCollectionCompat = ivy"org.scala-lang.modules::scala-collection-compat:2.4.4" + val scalaCollectionCompat = ivy"org.scala-lang.modules::scala-collection-compat:2.6.0" def scalaCompiler(scalaVersion: String) = ivy"org.scala-lang:scala-compiler:${scalaVersion}" val scalaJava8Compat = ivy"org.scala-lang.modules::scala-java8-compat:0.9.0" val scalaparse = ivy"com.lihaoyi::scalaparse:$fastparseVersion" @@ -281,16 +280,6 @@ trait AmmDependenciesResourceFileModule extends JavaModule{ } } -object ops extends Cross[OpsModule](binCrossScalaVersions:_*) -class OpsModule(val crossScalaVersion: String) extends AmmModule{ - def ivyDeps = Agg( - Deps.osLib, - withDottyCompat(Deps.scalaCollectionCompat, scalaVersion()) - ) - def scalacOptions = super.scalacOptions().filter(!_.contains("acyclic")) - object test extends Tests -} - object terminal extends Cross[TerminalModule](binCrossScalaVersions:_*) class TerminalModule(val crossScalaVersion: String) extends AmmModule{ def ivyDeps = T{ @@ -316,19 +305,23 @@ class TerminalModule(val crossScalaVersion: String) extends AmmModule{ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ object util extends Cross[UtilModule](binCrossScalaVersions:_*) class UtilModule(val crossScalaVersion: String) extends AmmModule{ - def moduleDeps = Seq(ops()) + def moduleDeps = Seq() def ivyDeps = T{ - if (scala3Versions.contains(crossScalaVersion)) - Agg( - ivy"com.lihaoyi:pprint_3:${Deps.pprint.dep.version}", - ivy"com.lihaoyi:fansi_3:${Deps.fansi.dep.version}", - ivy"org.scala-lang:scala3-library_3:$crossScalaVersion" - ) - else - Agg( - Deps.pprint, - Deps.fansi + Agg( + + Deps.osLib, + Deps.scalaCollectionCompat, + Deps.fansi + ) ++ ( + if (scala3Versions.contains(crossScalaVersion)) + Agg( + ivy"com.lihaoyi:pprint_3:${Deps.pprint.dep.version}", + ivy"org.scala-lang:scala3-library_3:$crossScalaVersion" + ) + else Agg( + Deps.pprint ) + ) } def compileIvyDeps = Agg( Deps.scalaReflect(scalaVersion()) @@ -337,7 +330,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ object runtime extends Cross[RuntimeModule](fullCrossScalaVersions:_*) class RuntimeModule(val crossScalaVersion: String) extends AmmModule{ - def moduleDeps = Seq(ops(), amm.util(), interp.api(), amm.repl.api()) + def moduleDeps = Seq(amm.util(), interp.api(), amm.repl.api()) def crossFullScalaVersion = true def ivyDeps = Agg( Deps.upickle, @@ -398,7 +391,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ object interp extends Cross[InterpModule](fullCrossScalaVersions:_*){ object api extends Cross[InterpApiModule](fullCrossScalaVersions:_*) class InterpApiModule(val crossScalaVersion: String) extends AmmModule with AmmDependenciesResourceFileModule{ - def moduleDeps = Seq(amm.compiler.interface(), ops(), amm.util()) + def moduleDeps = Seq(amm.compiler.interface(), amm.util()) def crossFullScalaVersion = true def dependencyResourceFileName = "amm-interp-api-dependencies.txt" def ivyDeps = Agg( @@ -422,7 +415,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ } } class InterpModule(val crossScalaVersion: String) extends AmmModule{ - def moduleDeps = Seq(ops(), amm.util(), amm.runtime(), amm.compiler.interface()) + def moduleDeps = Seq(amm.util(), amm.runtime(), amm.compiler.interface()) def crossFullScalaVersion = true def ivyDeps = Agg( Deps.bsp4j, @@ -446,10 +439,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ class ReplApiModule(val crossScalaVersion: String) extends AmmModule with AmmDependenciesResourceFileModule{ def crossFullScalaVersion = true def dependencyResourceFileName = "amm-dependencies.txt" - def moduleDeps = Seq( - ops(), amm.util(), - interp.api() - ) + def moduleDeps = Seq(amm.util(), interp.api()) def ivyDeps = Agg( withDottyCompat(Deps.mainargs, scalaVersion()) ) @@ -470,7 +460,7 @@ object amm extends Cross[MainModule](fullCrossScalaVersions:_*){ class ReplModule(val crossScalaVersion: String) extends AmmModule{ def crossFullScalaVersion = true def moduleDeps = Seq( - ops(), amm.util(), + amm.util(), amm.runtime(), amm.interp(), terminal(), amm.compiler.interface() @@ -541,7 +531,7 @@ class MainModule(val crossScalaVersion: String) def mainClass = Some("ammonite.Main") def moduleDeps = Seq( - terminal(), ops(), + terminal(), amm.util(), amm.runtime(), amm.interp.api(), amm.repl.api(), @@ -551,7 +541,6 @@ class MainModule(val crossScalaVersion: String) def runClasspath = super.runClasspath() ++ - ops().sources() ++ terminal().sources() ++ amm.util().sources() ++ amm.runtime().sources() ++ @@ -640,7 +629,6 @@ class MainModule(val crossScalaVersion: String) // Need to duplicate this from MainModule due to Mill not properly propagating it through def runClasspath = super.runClasspath() ++ - ops().sources() ++ terminal().sources() ++ amm.util().sources() ++ amm.runtime().sources() ++ @@ -680,33 +668,9 @@ def generateApiWhitelist(replApiCp: Seq[PathRef])(implicit ctx: mill.api.Ctx.Des PathRef(ctx.dest) } -object shell extends Cross[ShellModule](fullCrossScalaVersions:_*) -class ShellModule(val crossScalaVersion: String) extends AmmModule{ - def moduleDeps = Seq(ops(), amm()) - def crossFullScalaVersion = true - object test extends Tests{ - def moduleDeps = super.moduleDeps ++ Seq(amm.repl().test) - def thinWhitelist = T{ - generateApiWhitelist( - amm.repl.api().exposedClassPath() ++ - amm.compiler().exposedClassPath() ++ - Seq(amm.repl().test.compile().classes, compile().classes) ++ - resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() - ) - } - - def localClasspath = T{ - super.localClasspath() ++ Agg(thinWhitelist()) - } - def forkEnv = super.forkEnv() ++ Seq( - "AMMONITE_SHELL" -> shell().jar().path.toString, - "AMMONITE_ASSEMBLY" -> amm().assembly().path.toString - ) - } -} object integration extends Cross[IntegrationModule](fullCrossScalaVersions:_*) class IntegrationModule(val crossScalaVersion: String) extends AmmInternalModule{ - def moduleDeps = Seq(ops(), amm()) + def moduleDeps = Seq(amm()) def ivyDeps = T{ if (scalaVersion().startsWith("2.13.")) Agg(Deps.cask) @@ -715,7 +679,6 @@ class IntegrationModule(val crossScalaVersion: String) extends AmmInternalModule } object test extends Tests { def forkEnv = super.forkEnv() ++ Seq( - "AMMONITE_SHELL" -> shell().jar().path.toString, "AMMONITE_ASSEMBLY" -> amm().launcher().path.toString ) } @@ -723,7 +686,7 @@ class IntegrationModule(val crossScalaVersion: String) extends AmmInternalModule object sshd extends Cross[SshdModule](fullCrossScalaVersions:_*) class SshdModule(val crossScalaVersion: String) extends AmmModule{ - def moduleDeps = Seq(ops(), amm()) + def moduleDeps = Seq(amm()) def crossFullScalaVersion = true def ivyDeps = Agg( // sshd-core 1.3.0 requires java8 @@ -741,11 +704,9 @@ class SshdModule(val crossScalaVersion: String) extends AmmModule{ } def unitTest(scalaVersion: String = sys.env("TRAVIS_SCALA_VERSION")) = T.command{ - ops(scalaVersion).test.test()() terminal(scalaVersion).test.test()() amm.repl(scalaVersion).test.test()() amm(scalaVersion).test.test()() - shell(scalaVersion).test.test()() sshd(scalaVersion).test.test()() } @@ -779,7 +740,7 @@ def generateConstantsFile(version: String = buildVersion, """ println("Writing Constants.scala") - write(ctx.dest/"Constants.scala", versionTxt) + os.write(ctx.dest/"Constants.scala", versionTxt) ctx.dest/"Constants.scala" } @@ -801,10 +762,10 @@ def generateDependenciesFile(fileName: String, } .mkString("\n") - mkdir(dir) + os.makeDir(dir) println(s"Writing $dest") dir.toIO.mkdirs() - write(dest, content.getBytes("UTF-8")) + os.write(dest, content.getBytes("UTF-8")) dir } @@ -866,17 +827,26 @@ def publishDocs() = { // need to make significant changes to the readme and that'll time. if (!isMasterCommit) T.command{ println("MISC COMMIT: Building readme for verification") - %sbt( - "readme/run", - AMMONITE_SHELL=shell("2.13.1").jar().path, - AMMONITE_ASSEMBLY=amm("2.13.1").assembly().path, - CONSTANTS_FILE=generateConstantsFile() - ) + try { + os.proc( + "sbt", + "readme/run", + ).call( + env = Map( + "AMMONITE_ASSEMBLY" -> amm("2.13.1").assembly().path.toString, + "CONSTANTS_FILE" -> generateConstantsFile().toString + ) + ) + }catch{case e => + println(e) + e.printStackTrace() + throw e + } }else T.command{ println("MASTER COMMIT: Updating version and publishing to Github Pages") - val publishDocs = sys.env("DEPLOY_KEY").replace("\\n", "\n") - write(pwd / 'deploy_key, publishDocs) + val deployKey = sys.env("DEPLOY_KEY").replace("\\n", "\n") + os.write(os.pwd / 'deploy_key, deployKey) val (stableKey, unstableKey, oldStableKeys, oldUnstableKeys) = if (!unstable){ @@ -912,13 +882,16 @@ def publishDocs() = { yield (k, s"https://github.com/${ghOrg}/${ghRepo}/releases/download/$k") ) - %sbt( + os.proc( "readme/run", - AMMONITE_SHELL=shell("2.13.1").jar().path, - AMMONITE_ASSEMBLY=amm("2.13.1").assembly().path, - CONSTANTS_FILE=constantsFile + + ).call( + env = Map( + "AMMONITE_ASSEMBLY" -> amm("2.13.1").assembly().path.toString, + "CONSTANTS_FILE" -> constantsFile.toString + ) ) - %("ci/deploy_master_docs.sh") + os.proc("ci/deploy_master_docs.sh").call() } } @@ -953,7 +926,7 @@ def publishSonatype(publishArtifacts: mill.main.Tasks[PublishModule.PublishData] divisionCount: Int) = T.command{ - val x: Seq[(Seq[(Path, String)], Artifact)] = { + val x: Seq[(Seq[(os.Path, String)], Artifact)] = { mill.define.Target.sequence(partition(publishArtifacts, shard, divisionCount))().map{ case PublishModule.PublishData(a, s) => (s.map{case (p, f) => (p.path, f)}, a) } diff --git a/ci/upload.sc b/ci/upload.sc index abf7c4412..26f44ba41 100644 --- a/ci/upload.sc +++ b/ci/upload.sc @@ -1,8 +1,7 @@ #!/usr/bin/env amm -import ammonite.ops._ @main -def apply(uploadedFile: Path, +def apply(uploadedFile: os.Path, tagName: String, uploadName: String, authKey: String, @@ -39,7 +38,7 @@ def apply(uploadedFile: Path, ), connectTimeout = 5000, readTimeout = 60000, - data = read.bytes(uploadedFile) + data = os.read.bytes(uploadedFile) ).text diff --git a/integration/src/test/resources/ammonite/integration/basic/Print.sc b/integration/src/test/resources/ammonite/integration/basic/Print.sc index 20229d3de..ff414d203 100644 --- a/integration/src/test/resources/ammonite/integration/basic/Print.sc +++ b/integration/src/test/resources/ammonite/integration/basic/Print.sc @@ -1,2 +1 @@ -import ammonite.ops._ -println(ls(pwd/"out")) \ No newline at end of file +println(os.list(os.pwd/"out")) \ No newline at end of file diff --git a/integration/src/test/resources/ammonite/integration/basic/Resources.sc b/integration/src/test/resources/ammonite/integration/basic/Resources.sc index 9fac2b088..a4169537c 100644 --- a/integration/src/test/resources/ammonite/integration/basic/Resources.sc +++ b/integration/src/test/resources/ammonite/integration/basic/Resources.sc @@ -1,5 +1,4 @@ -import ammonite.ops._ interp.load.ivy("org.apache.jackrabbit" % "oak-core" % "1.3.16") @ -val path = resource/"org"/"apache"/"jackrabbit"/"oak"/"plugins"/"blob"/"blobstore.properties" -println(read(path).length) // Should work \ No newline at end of file +val path = os.resource/"org"/"apache"/"jackrabbit"/"oak"/"plugins"/"blob"/"blobstore.properties" +println(os.read(path).length) // Should work \ No newline at end of file diff --git a/integration/src/test/resources/ammonite/integration/basic/Spark2.sc b/integration/src/test/resources/ammonite/integration/basic/Spark2.sc index 89a06b2a0..cf65c15e0 100644 --- a/integration/src/test/resources/ammonite/integration/basic/Spark2.sc +++ b/integration/src/test/resources/ammonite/integration/basic/Spark2.sc @@ -2,7 +2,6 @@ import $ivy.{ `org.apache.spark::spark-core:2.1.0`, `org.apache.spark::spark-sql:2.1.0` } -import ammonite.ops._ import org.apache.spark.sql.SparkSession val spark = SparkSession.builder.master("local[*]").appName("bxm").getOrCreate diff --git a/integration/src/test/scala-2.13/ammonite/integration/ProjectTests213.scala b/integration/src/test/scala-2.13/ammonite/integration/ProjectTests213.scala index b4b3ca7ca..957d8d59e 100644 --- a/integration/src/test/scala-2.13/ammonite/integration/ProjectTests213.scala +++ b/integration/src/test/scala-2.13/ammonite/integration/ProjectTests213.scala @@ -1,7 +1,6 @@ package ammonite.integration import ammonite.integration.TestUtils._ -import ammonite.ops._ import utest._ object ProjectTests213 extends TestSuite{ @@ -13,7 +12,7 @@ object ProjectTests213 extends TestSuite{ def addPostTest() = jsonplaceholder.withServer { url => val res = execWithEnv( Seq("JSONPLACEHOLDER" -> url), - 'basic / "HttpApi.sc", + os.rel / 'basic / "HttpApi.sc", "addPost", "title", "some text" ) assert(res.out.trim.contains("101")) @@ -21,7 +20,7 @@ object ProjectTests213 extends TestSuite{ def commentsTest() = jsonplaceholder.withServer { url => val res = execWithEnv( Seq("JSONPLACEHOLDER" -> url), - 'basic / "HttpApi.sc", + os.rel / 'basic / "HttpApi.sc", "comments", "40" ) assert(res.out.trim.contains("totam vel saepe aut")) diff --git a/integration/src/test/scala/ammonite/integration/BasicTests.scala b/integration/src/test/scala/ammonite/integration/BasicTests.scala index 0374c4a4f..dd67a0925 100644 --- a/integration/src/test/scala/ammonite/integration/BasicTests.scala +++ b/integration/src/test/scala/ammonite/integration/BasicTests.scala @@ -118,41 +118,6 @@ object BasicTests extends TestSuite{ } } - - def shellTest() = { - // make sure you can load the example-predef.sc, have it pull stuff in - // from ivy, and make use of `cd!` and `wd` inside the executed script. - val res = os.proc( - executable, - "--thin", - "--no-home-predef", - "--predef", - exampleBarePredef, - "-c", - """val x = wd - |@ - |cd! "amm"/"src" - |@ - |println(wd relativeTo x)""".stripMargin, - "-s" - ).call() - - val output = res.out.trim - - if (!Util.windowsPlatform) - // seems the script is run only until the first '@' on Windows - assert(output == "amm/src") - } - test("shell"){ - // FIXME In Scala 3.0.0-M1, etting errors like - // java.lang.AssertionError: assertion failed: - // duplicate type CC#31508; previous was type CC#31500 - if (isScala2) - shellTest() - else - "Disabled in Scala 3" - } - // Ensure we can load the source code of the built-in Java standard library test("source"){ // This fails on windows because for some reason the windows subprocess diff --git a/integration/src/test/scala/ammonite/integration/ErrorTruncationTests.scala b/integration/src/test/scala/ammonite/integration/ErrorTruncationTests.scala index 4e3230c18..e21ecce5e 100644 --- a/integration/src/test/scala/ammonite/integration/ErrorTruncationTests.scala +++ b/integration/src/test/scala/ammonite/integration/ErrorTruncationTests.scala @@ -1,8 +1,6 @@ package ammonite.integration import ammonite.integration.TestUtils._ -import ammonite.ops.ImplicitWd._ -import ammonite.ops._ import ammonite.util.Util import utest._ @@ -13,7 +11,7 @@ import utest._ */ object ErrorTruncationTests extends TestSuite{ - def checkErrorMessage(file: RelPath, expected: String): Unit = { + def checkErrorMessage(file: os.RelPath, expected: String): Unit = { val e = fansi.Str( Util.normalizeNewlines( intercept[os.SubprocessException]{ exec(file) } @@ -29,7 +27,7 @@ object ErrorTruncationTests extends TestSuite{ val tests = Tests { println("ErrorTruncationTests") test("compileError") - checkErrorMessage( - file = 'errorTruncation/"compileError.sc", + file = os.rel/'errorTruncation/"compileError.sc", expected = Util.normalizeNewlines( if (isScala2) s"""compileError.sc:1: not found: value doesntexist @@ -45,7 +43,7 @@ object ErrorTruncationTests extends TestSuite{ ) ) test("multiExpressionError") - checkErrorMessage( - file = 'errorTruncation/"compileErrorMultiExpr.sc", + file = os.rel / 'errorTruncation/"compileErrorMultiExpr.sc", expected = Util.normalizeNewlines( if (isScala2) s"""compileErrorMultiExpr.sc:11: not found: value doesntexist @@ -64,7 +62,7 @@ object ErrorTruncationTests extends TestSuite{ test("parseError"){ if(!Util.windowsPlatform){ checkErrorMessage( - file = 'errorTruncation/"parseError.sc", + file = os.rel/'errorTruncation/"parseError.sc", expected = Util.normalizeNewlines( if (isScala2) """parseError.sc:1:1 expected end-of-input @@ -84,7 +82,7 @@ object ErrorTruncationTests extends TestSuite{ "ammonite.$file.integration.src.test.resources.ammonite.integration.errorTruncation" test("runtimeError") - checkErrorMessage( - file = 'errorTruncation/"runtimeError.sc", + file = os.rel/'errorTruncation/"runtimeError.sc", expected = Util.normalizeNewlines( if (scalaVersion.startsWith("2.12")) s"""java.lang.ArithmeticException: / by zero diff --git a/integration/src/test/scala/ammonite/integration/LineNumberTests.scala b/integration/src/test/scala/ammonite/integration/LineNumberTests.scala index 93d838c24..125b5f5d7 100644 --- a/integration/src/test/scala/ammonite/integration/LineNumberTests.scala +++ b/integration/src/test/scala/ammonite/integration/LineNumberTests.scala @@ -1,5 +1,4 @@ package ammonite.integration -import ammonite.ops._ import ammonite.util.Util import utest._ import TestUtils._ @@ -11,7 +10,7 @@ import TestUtils._ object LineNumberTests extends TestSuite{ val tests = this{ - def checkErrorMessage(file: RelPath, expected: String): Unit = { + def checkErrorMessage(file: os.RelPath, expected: String): Unit = { val e = intercept[os.SubprocessException]{ exec(file) }.result.err.string @@ -20,7 +19,7 @@ object LineNumberTests extends TestSuite{ test("compilationErrorInSecondBlock") - checkErrorMessage( - file = 'lineNumbers/"compilationErrorInSecondBlock.sc", + file = os.rel/'lineNumbers/"compilationErrorInSecondBlock.sc", expected = Util.normalizeNewlines( if (isScala2) """compilationErrorInSecondBlock.sc:14: not found: value printnl diff --git a/integration/src/test/scala/ammonite/integration/ProjectTests.scala b/integration/src/test/scala/ammonite/integration/ProjectTests.scala index 5338c519f..c9204df14 100644 --- a/integration/src/test/scala/ammonite/integration/ProjectTests.scala +++ b/integration/src/test/scala/ammonite/integration/ProjectTests.scala @@ -1,8 +1,6 @@ package ammonite.integration import ammonite.integration.TestUtils._ -import ammonite.ops.ImplicitWd._ -import ammonite.ops._ import ammonite.util.Util import utest._ @@ -24,7 +22,7 @@ object ProjectTests extends TestSuite{ test("playframework"){ if (!Util.windowsPlatform) { if (scalaVersion.startsWith("2.11.") && javaVersion.startsWith("1.8.")){ - val evaled = exec('basic/"PlayFramework.sc") + val evaled = exec(os.rel/'basic/"PlayFramework.sc") assert(evaled.out.string.contains("Hello bar")) } } @@ -46,7 +44,7 @@ object ProjectTests extends TestSuite{ // be run as an integration test, or via `sbt amm/test:assembly && amm/target/amm` if (!Util.windowsPlatform) { if (scalaVersion.startsWith("2.11.") && javaVersion.startsWith("1.8.")){ - val evaled = exec('basic/"Spark2.sc") + val evaled = exec(os.rel / 'basic/"Spark2.sc") assert(evaled.out.string.contains("fake db write")) } } diff --git a/integration/src/test/scala/ammonite/integration/TestUtils.scala b/integration/src/test/scala/ammonite/integration/TestUtils.scala index 4c7cf8d9b..348deef28 100644 --- a/integration/src/test/scala/ammonite/integration/TestUtils.scala +++ b/integration/src/test/scala/ammonite/integration/TestUtils.scala @@ -1,8 +1,6 @@ package ammonite.integration -import ammonite.ops._ import ammonite.util.Util -import ImplicitWd._ /** * Created by haoyi on 6/4/16. @@ -19,14 +17,12 @@ object TestUtils { else Seq("bash", p) } - val intTestResources = pwd/'integration/'src/'test/'resources + val intTestResources = os.pwd/'integration/'src/'test/'resources val replStandaloneResources = intTestResources/'ammonite/'integration - val shellAmmoniteResources = pwd/'shell/'src/'main/'resources/'ammonite/'shell - val emptyPrefdef = shellAmmoniteResources/"empty-predef.sc" - val exampleBarePredef = shellAmmoniteResources/"example-predef-bare.sc" + val shellAmmoniteResources = os.pwd/'shell/'src/'main/'resources/'ammonite/'shell //we use an empty predef file here to isolate the tests from external forces. - def execBase(name: RelPath, + def execBase(name: os.RelPath, extraAmmArgs: Seq[String], home: os.Path, args: Seq[String], @@ -46,16 +42,16 @@ object TestUtils { stderr = os.Pipe ) } - def exec(name: RelPath, args: String*) = - execBase(name, Nil, tmp.dir(), args, thin = true, Nil) - def execWithEnv(env: Iterable[(String, String)], name: RelPath, args: String*) = - execBase(name, Nil, tmp.dir(), args, thin = true, env) - def execNonThin(name: RelPath, args: String*) = - execBase(name, Nil, tmp.dir(), args, thin = false, Nil) - def execWithHome(home: os.Path, name: RelPath, args: String*) = + def exec(name: os.RelPath, args: String*) = + execBase(name, Nil, os.temp.dir(), args, thin = true, Nil) + def execWithEnv(env: Iterable[(String, String)], name: os.RelPath, args: String*) = + execBase(name, Nil, os.temp.dir(), args, thin = true, env) + def execNonThin(name: os.RelPath, args: String*) = + execBase(name, Nil, os.temp.dir(), args, thin = false, Nil) + def execWithHome(home: os.Path, name: os.RelPath, args: String*) = execBase(name, Nil, home, args, thin = true, Nil) - def execSilent(name: RelPath, args: String*) = - execBase(name, Seq("-s"), tmp.dir(), args, thin = true, Nil) + def execSilent(name: os.RelPath, args: String*) = + execBase(name, Seq("-s"), os.temp.dir(), args, thin = true, Nil) /** *Counts number of non-overlapping occurrences of `subs` in `s` diff --git a/internals-docs/packages.md b/internals-docs/packages.md index 24f3f8e05..c0985134f 100644 --- a/internals-docs/packages.md +++ b/internals-docs/packages.md @@ -6,18 +6,12 @@ most of them, it also contains some synthetic packages that house user code compiled and run within the Ammonite REPL. The main static packages containing Ammonite code are: - -- `ammonite.ops`: a standalone usable filesystem library that feel great to - use from the REPL, but can be used as part of any other program - + - `ammonite.terminal`: a standalone replacement for JLine, with more features such as syntax-highlighting, GUI-style keybindings and good multiline editing - `ammonite`: the main Ammonite REPL -- `ammonite.shell`: an extension package for the Ammonite REPL to configure it - for use as a system shell: a pwd-based-prompt, file-path-autocomplete, etc. - - `ammonite.sshd`: spin up an Ammonite server in any existing process you can connect to via SSH diff --git a/ops/src/main/scala-2.12/ammonite/ops/FilterMapExt.scala b/ops/src/main/scala-2.12/ammonite/ops/FilterMapExt.scala deleted file mode 100644 index c4cdc9db9..000000000 --- a/ops/src/main/scala-2.12/ammonite/ops/FilterMapExt.scala +++ /dev/null @@ -1,60 +0,0 @@ -package ammonite.ops - -import scala.collection.{Seq, GenTraversableOnce, TraversableLike} - -import scala.collection.generic.{CanBuildFrom => CBF, GenericTraversableTemplate, SeqFactory} - -/** - * Extends collections to give short aliases for the commonly - * used operations, so we can make it easy to use from the - * command line. - */ -class FilterMapExt[+T, Repr](i: TraversableLike[T, Repr]) { - /** - * Alias for `map` - */ - def |[B, That](f: T => B)(implicit bf: CBF[Repr, B, That]): That = i.map(f) - - /** - * Alias for `flatMap` - */ - def ||[B, That](f: T => GenTraversableOnce[B])(implicit bf: CBF[Repr, B, That]): That = { - i.flatMap(f) - } - - /** - * Alias for `foreach` - */ - def |![B, That](f: T => Unit) = i.foreach(f) - - /** - * Alias for `filter` - */ - def |?(p: T => Boolean): Repr = i.filter(p) - - /** - * Alias for `reduce` - */ - def |&[A1 >: T](op: (A1, A1) => A1): A1 = i.reduceLeft(op) -} - -trait FilterMapExtConv { - - implicit def FilterMapExtImplicit[T, Repr](i: TraversableLike[T, Repr]): FilterMapExt[T, Repr] = - new FilterMapExt(i) - /** - * Lets you call [[FilterMapExt]] aliases on Arrays too - */ - implicit def FilterMapArraysImplicit[T](a: Array[T]): FilterMapExt[T, Array[T]] = - new FilterMapExt(a) - - /** - * Allows you to pipe sequences into other sequences to convert them, - * e.g. Seq(1, 2, 3) |> Vector - */ - implicit def SeqFactoryFunc[T, CC[X] <: Seq[X] with GenericTraversableTemplate[X, CC]] - (s: SeqFactory[CC]) = { - (t: Seq[T]) => s(t:_*) - } - -} \ No newline at end of file diff --git a/ops/src/main/scala-2.12/ammonite/ops/LsSeq.scala b/ops/src/main/scala-2.12/ammonite/ops/LsSeq.scala deleted file mode 100644 index 57357bf30..000000000 --- a/ops/src/main/scala-2.12/ammonite/ops/LsSeq.scala +++ /dev/null @@ -1,11 +0,0 @@ -package ammonite.ops - -/** - * A specialized Seq[Path] used to provide better a better pretty-printed - * experience - */ -case class LsSeq(base: Path, listed: RelPath*) extends Seq[Path]{ - def length = listed.length - def apply(idx: Int) = base/listed.apply(idx) - def iterator = listed.iterator.map(base / _) -} diff --git a/ops/src/main/scala-2.13/ammonite/ops/FilterMapExt.scala b/ops/src/main/scala-2.13/ammonite/ops/FilterMapExt.scala deleted file mode 100644 index 885742c9e..000000000 --- a/ops/src/main/scala-2.13/ammonite/ops/FilterMapExt.scala +++ /dev/null @@ -1,68 +0,0 @@ -package ammonite.ops - -import scala.collection.generic.{IsIterable, IsSeq} -import scala.collection.{BuildFrom, SeqFactory, SeqOps} -import scala.reflect.ClassTag - -/** - * Extends collections to give short aliases for the commonly - * used operations, so we can make it easy to use from the - * command line. - */ -class FilterMapExt[+T, Repr](i: Repr, coll: IsIterable[Repr] { type A = T }) { - /** - * Alias for `map` - */ - def |[B, That](f: T => B)(implicit bf: BuildFrom[Repr, B, That]): That = { - val b = bf.newBuilder(i) - b.addAll(coll(i).iterator.map(f)) - b.result() - } - - /** - * Alias for `flatMap` - */ - def ||[B, That](f: T => IterableOnce[B])(implicit bf: BuildFrom[Repr, B, That]): That = { - val b = bf.newBuilder(i) - b.addAll(coll(i).iterator.flatMap(f)) - b.result() - } - - /** - * Alias for `foreach` - */ - def |![B, That](f: T => Unit) = coll(i).foreach(f) - - /** - * Alias for `filter` - */ - def |?(p: T => Boolean): Seq[T] = - coll(i).iterator.filter(p).toSeq - - /** - * Alias for `reduce` - */ - def |&[A1 >: T](op: (A1, A1) => A1): A1 = coll(i).reduceLeft(op) -} - -trait FilterMapExtConv { - - implicit def FilterMapExtImplicit[Repr](repr: Repr)(implicit - i: IsIterable[Repr]): FilterMapExt[i.A, Repr] = - new FilterMapExt[i.A, Repr](repr, i) - /** - * Lets you call [[FilterMapExt]] aliases on Arrays too - */ - implicit def FilterMapArraysImplicit[T: ClassTag](a: Array[T]): FilterMapExt[T, Array[T]] = - new FilterMapExt[T, Array[T]](a, IsSeq.arrayIsSeq[T]) - - /** - * Allows you to pipe sequences into other sequences to convert them, - * e.g. Seq(1, 2, 3) |> Vector - */ - implicit def SeqFactoryFunc[T, CC[X] <: Seq[X] with SeqOps[X, CC, CC[X]]] - (s: SeqFactory[CC]) = { - (t: Seq[T]) => s(t:_*) - } - -} \ No newline at end of file diff --git a/ops/src/main/scala-2.13/ammonite/ops/LsSeq.scala b/ops/src/main/scala-2.13/ammonite/ops/LsSeq.scala deleted file mode 100644 index 884bb7ade..000000000 --- a/ops/src/main/scala-2.13/ammonite/ops/LsSeq.scala +++ /dev/null @@ -1,24 +0,0 @@ -package ammonite.ops - -import scala.collection.generic.IsIterable - -/** - * A specialized Seq[Path] used to provide better a better pretty-printed - * experience - */ -case class LsSeq(base: Path, listed: RelPath*) extends Seq[Path]{ - def length = listed.length - def apply(idx: Int) = base/listed.apply(idx) - def iterator = listed.iterator.map(base / _) -} - -object LsSeq { - // not sure why this one is necessary - implicit def isIterable: IsIterable[LsSeq] { type A = Path; type C = Seq[Path] } = - new IsIterable[LsSeq] { - type A = Path - type C = Seq[Path] - def apply(coll: LsSeq) = - implicitly[IsIterable[Seq[Path]] { type A = Path; type C = Seq[Path] }].apply(coll) - } -} diff --git a/ops/src/main/scala/ammonite/ops/Extensions.scala b/ops/src/main/scala/ammonite/ops/Extensions.scala deleted file mode 100644 index ad0cf60b2..000000000 --- a/ops/src/main/scala/ammonite/ops/Extensions.scala +++ /dev/null @@ -1,129 +0,0 @@ -package ammonite.ops - -import scala.collection.GenTraversableOnce - -trait Extensions extends FilterMapExtConv{ - implicit def PipeableImplicit[T](t: T): Pipeable[T] = new Pipeable(t) - - implicit def FilterMapIteratorsImplicit[T](a: Iterator[T]): FilterMapExt2[T] = - new FilterMapExt2(a) - - implicit def FilterMapGeneratorsImplicit[T](a: geny.Generator[T]): FilterMapExtGen[T] = - new FilterMapExtGen(a) - - - implicit class iterShow[T](t: Iterator[T]){ - def !! = t.foreach(println) - } - - implicit def RegexContextMaker(s: StringContext): RegexContext = new RegexContext(s) - - implicit def Callable1Implicit[T1, R](f: (T1 => R)): Callable1[T1, R] = new Callable1(f) -} - -/** - * Provides `a.! b` as an alternative to the `a(b)` syntax for calling a - * function with one argument - */ -class Callable1[T1, R](f: (T1 => R)) extends (T1 => R){ - def !(arg: T1): R = f(arg) - def apply(arg: T1): R = f(arg) -} - -/** - * Provides `a! b` as an alternative to the `(a(b, _)` syntax for partially - * applying a function with two arguments - */ -class Callable2[T1, T2, R](f: (T1, T2) => R) extends ((T1, T2) => R){ - def !(arg1: T1) = new Callable1[T2, R](arg2 => f(arg1, arg2)) - def apply(arg1: T1, arg2: T2) = f(arg1, arg2) -} -object Extensions extends Extensions - -/** - * Lets you pipe values through functions - */ - -class Pipeable[T](t: T) { - def |>[V](f: T => V) = f(t) -} -/** - * Extends collections to give short aliases for the commonly - * used operations, so we can make it easy to use from the - * command line. - */ -class FilterMapExt2[+T](i: Iterator[T]) { - /** - * Alias for `map` - */ - def |[B, That](f: T => B) = i.map(f) - - /** - * Alias for `flatMap` - */ - def ||[B, That](f: T => GenTraversableOnce[B]) = i.flatMap(f) - - /** - * Alias for `foreach` - */ - def |![B, That](f: T => Unit) = i.foreach(f) - - /** - * Alias for `filter` - */ - def |?(p: T => Boolean) = i.filter(p) - - /** - * Alias for `reduce` - */ - def |&[A1 >: T](op: (A1, A1) => A1): A1 = i.reduceLeft(op) -} -/** - * Extends collections to give short aliases for the commonly - * used operations, so we can make it easy to use from the - * command line. - */ -class FilterMapExtGen[+T](i: geny.Generator[T]) { - /** - * Alias for `map` - */ - def |[B, That](f: T => B) = i.map(f) - - /** - * Alias for `flatMap` - */ - def ||[B, That](f: T => Iterable[B]) = i.flatMap(f(_)) - - /** - * Alias for `foreach` - */ - def |![B, That](f: T => Unit) = i.foreach(f) - - /** - * Alias for `filter` - */ - def |?(p: T => Boolean) = i.filter(p) - - /** - * Alias for `reduce` - */ - def |&[A1 >: T](op: (A1, A1) => A1): A1 = i.reduceLeft(op) -} - -object RegexContext{ - class Interped(parts: Seq[String]){ - def unapplySeq(s: String) = { - val Seq(head, tail@_*) = parts.map(java.util.regex.Pattern.quote) - - val regex = head + tail.map("(.*)" + _).mkString - regex.r.unapplySeq(s) - } - } -} - -/** - * Lets you pattern match strings with interpolated glob-variables - */ -class RegexContext(sc: StringContext) { - def r = new RegexContext.Interped(sc.parts) -} \ No newline at end of file diff --git a/ops/src/main/scala/ammonite/ops/FileOps.scala b/ops/src/main/scala/ammonite/ops/FileOps.scala deleted file mode 100644 index 945178fd1..000000000 --- a/ops/src/main/scala/ammonite/ops/FileOps.scala +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Basic operations that take place on files. Intended to be - * both light enough to use from the command line as well as - * powerful and flexible enough to use in real applications to - * perform filesystem operations - */ -package ammonite.ops - -import java.io.File -import java.nio.file.{Path => _, _} - - -object Internals{ - - - trait Mover{ - def check: Boolean - def apply(t: PartialFunction[String, String])(from: Path) = { - if (check || t.isDefinedAt(from.last)){ - val dest = from/RelPath.up/t(from.last) - new File(from.toString).renameTo(new File(dest.toString)) - } - } - def *(t: PartialFunction[Path, Path])(from: Path) = { - if (check || t.isDefinedAt(from)) { - val dest = t(from) - mkdir(dest/RelPath.up) - new File(from.toString).renameTo(new File(t(from).toString)) - } - } - } - - - class Writable(val writeableData: geny.Generator[Array[Byte]]) - - object Writable extends LowPri{ - implicit def WritableString(s: String) = new Writable( - geny.Generator(s.getBytes(java.nio.charset.StandardCharsets.UTF_8)) - ) - implicit def WritableBytes(a: Array[Byte]): Writable = new Writable(geny.Generator(a)) - - } - trait LowPri{ - - implicit def WritableGenerator[M[_], T](a: M[T]) - (implicit f: T => Writable, - i: M[T] => geny.Generator[T]) = { - new Writable( - i(a).flatMap(f(_).writeableData) - ) - } - } -} - -/** - * An [[Callable1]] that returns a Seq[R], but can also do so - * lazily (Iterator[R]) via `op.iter! arg`. You can then use - * the iterator however you wish - */ -trait StreamableOp1[T1, R, C <: Seq[R]] extends Function1[T1, C]{ - def materialize(src: T1, i: geny.Generator[R]): C - def apply(arg: T1) = materialize(arg, iter(arg)) - - /** - * Returns a lazy [[Iterator]] instead of an eager sequence of results. - */ - val iter: T1 => geny.Generator[R] -} - - -trait CopyMove extends Function2[Path, Path, Unit]{ - - /** - * Copy or move a file into a particular folder, rather - * than into a particular path - */ - object into extends Function2[Path, Path, Unit]{ - def apply(from: Path, to: Path) = { - CopyMove.this(from, to/from.last) - } - } - - /** - * Copy or move a file, stomping over anything - * that may have been there before - */ - object over extends Function2[Path, Path, Unit]{ - def apply(from: Path, to: Path) = { - rm(to) - CopyMove.this(from, to) - } - } -} - -/** - * Moves a file or folder from one place to another. - * - * Creates any necessary directories - */ -object mv extends Function2[Path, Path, Unit] with Internals.Mover with CopyMove{ - def apply(from: Path, to: Path) = { - require( - !to.startsWith(from), - s"Can't move a directory into itself: $to is inside $from" - ) - java.nio.file.Files.move(from.wrapped, to.wrapped) - } - - - def check = false - - object all extends Internals.Mover{ - def check = true - } -} - -/** - * Copies a file or folder from one place to another. - * Creates any necessary directories, and copies folders - * recursively. - */ -object cp extends Function2[Path, Path, Unit] with CopyMove{ - def apply(from: Path, to: Path) = { - require( - !to.startsWith(from), - s"Can't copy a directory into itself: $to is inside $from" - ) - def copyOne(p: Path) = { - Files.copy(p.wrapped, (to/(p relativeTo from)).wrapped) - } - - copyOne(from) - if (stat(from).isDir) os.walk(from) | copyOne - } - -} - - - -/** - * Kills the given process with the given signal, e.g. - * `kill(9)! pid` - */ -case class kill(signal: Int)(implicit wd: Path) extends Function1[Int, CommandResult]{ - def apply(pid: Int): CommandResult = { - Shellout.%%('kill, "-" + signal, pid.toString) - } -} diff --git a/ops/src/main/scala/ammonite/ops/Shellout.scala b/ops/src/main/scala/ammonite/ops/Shellout.scala deleted file mode 100644 index f523e3a23..000000000 --- a/ops/src/main/scala/ammonite/ops/Shellout.scala +++ /dev/null @@ -1,213 +0,0 @@ -package ammonite.ops - - -import java.nio.charset.StandardCharsets - -import scala.annotation.tailrec -import scala.io.Codec -import scala.language.dynamics - -/** - * Internal utilities to support spawning subprocesses - */ -object Shellout{ - val % = Command(Vector.empty, Map.empty, Shellout.executeInteractive) - val %% = Command(Vector.empty, Map.empty, Shellout.executeStream) - def executeInteractive(wd: Path, cmd: Command[_]) = { - val builder = new java.lang.ProcessBuilder() - import collection.JavaConverters._ - for ((k, v) <- cmd.envArgs){ - if (v != null) builder.environment().put(k, v) - else builder.environment().remove(k) - } - builder.directory(new java.io.File(wd.toString)) - - val proc = - builder - .command(cmd.cmd:_*) - .inheritIO() - .start() - - // If someone `Ctrl C`s the Ammonite REPL while we are waiting on a - // subprocess, don't stop waiting! - // - // - For "well behaved" subprocess like `ls` or `yes`, they will terminate - // on their own and return control to us when they receive a `Ctrl C` - // - // - For "capturing" processes like `vim` or `python` or `bash`, those - // should *not* exit on Ctrl-C, and in fact we do not even receive an - // interrupt because they do terminal magic - // - // - For weird processes like `less` or `git log`, without this - // ignore-exceptions tail recursion it would stop waiting for the - // subprocess but the *less* subprocess will still be around! This messes - // up all our IO for as long as the subprocess lives. We can't force-quit - // the subprocess because *it's* children may hand around and do the same - // thing (e.g. in the case of `git log`, which leaves a `less` grandchild - // hanging around). Thus we simply don't let `Ctrl C` interrupt these - // fellas, and force you to use e.g. `q` to exit `less` gracefully. - - @tailrec def run(): Int = - try proc.waitFor() - catch {case e: Throwable => run() } - - val exitCode = run() - if (exitCode == 0) () - else throw InteractiveShelloutException() - } - - def executeStream(wd: Path, cmd: Command[_]) = { - val builder = new java.lang.ProcessBuilder() - import collection.JavaConverters._ - for ((k, v) <- cmd.envArgs){ - if (v != null) builder.environment().put(k, v) - else builder.environment().remove(k) - } - - builder.directory(new java.io.File(wd.toString)) - val process = - builder - .command(cmd.cmd:_*) - .start() - val stdout = process.getInputStream - val stderr = process.getErrorStream - val chunks = collection.mutable.Buffer.empty[Either[Bytes, Bytes]] - val sources = Seq(stdout -> (Left(_: Bytes)), stderr -> (Right(_: Bytes))) - while( - process.isAlive || - stdout.available() > 0 || - stderr.available() > 0 - ){ - var readSomething = false - for ((std, wrapper) <- sources){ - while (std.available() > 0){ - readSomething = true - val array = new Array[Byte](std.available()) - val actuallyRead = std.read(array) - chunks.append(wrapper( - if (actuallyRead == array.length) new Bytes(array) - else new Bytes(array.take(actuallyRead)) - )) - } - } - // if we did not read anything sleep briefly to avoid spinning - if(!readSomething) - Thread.sleep(2) - } - - val res = CommandResult(process.exitValue(), chunks.toSeq) - if (res.exitCode == 0) res - else throw ShelloutException(res) - } -} - -/** - * A staged sub-process command that has yet to be executed. - */ -case class Command[T](cmd: Vector[String], - envArgs: Map[String, String], - execute: (Path, Command[_]) => T) extends Dynamic { - def extend(cmd2: Traversable[String], envArgs2: Traversable[(String, String)]) = - Command(cmd ++ cmd2, envArgs ++ envArgs2, execute) - def selectDynamic(name: String)(implicit wd: Path) = execute(wd, extend(Vector(name), Map())) - def opArg(op: String) = if (op == "apply") Nil else Vector(op) - - def applyDynamic(op: String)(args: Shellable*)(implicit wd: Path): T = { - - execute(wd, this.extend(opArg(op) ++ args.flatMap(_.s), Map())) - } - def applyDynamicNamed(op: String) - (args: (String, Shellable)*) - (implicit wd: Path): T = { - val (namedArgs, posArgs) = args.map{ - case (k, v) => (k, if (v == null) null else v.s) - }.partition(_._1 != "") - execute(wd, this.extend(opArg(op) ++ posArgs.flatMap(_._2), - namedArgs.map{case (k, v) => (k, if (v == null) null else v.head)})) - } -} - -/** - * Trivial wrapper around `Array[Byte]` with sane equality and useful toString - */ -class Bytes(val array: Array[Byte]){ - override def equals(other: Any) = other match{ - case otherBytes: Bytes => java.util.Arrays.equals(array, otherBytes.array) - case _ => false - } - override def toString = new String(array, java.nio.charset.StandardCharsets.UTF_8) -} -/** - * Contains the accumulated output for the invocation of a subprocess command. - * - * Apart from the exit code, the primary data-structure is a sequence of byte - * chunks, tagged with [[Left]] for stdout and [[Right]] for stderr. This is - * interleaved roughly in the order it was emitted by the subprocess, and - * reflects what a user would have see if the subprocess was run manually. - * - * Derived from that, is the aggregate `out` and `err` [[StreamValue]]s, - * wrapping stdout/stderr respectively, and providing convenient access to - * the aggregate output of each stream, as bytes or strings or lines. - */ -case class CommandResult(exitCode: Int, - chunks: Seq[Either[Bytes, Bytes]]) { - /** - * The standard output of the executed command, exposed in a number of ways - * for convenient access - */ - val out = StreamValue(chunks.collect{case Left(s) => s}) - /** - * The standard error of the executed command, exposed in a number of ways - * for convenient access - */ - val err = StreamValue(chunks.collect{case Right(s) => s}) - override def toString() = { - s"CommandResult $exitCode\n" + - chunks.iterator - .collect{case Left(s) => s case Right(s) => s} - .map(x => new String(x.array)) - .mkString - } -} - -/** - * Thrown when a shellout command results in a non-zero exit code. - * - * Doesn't contain any additional information apart from the [[CommandResult]] - * that is normally returned, but ensures that failures in subprocesses happen - * loudly and won't get ignored unless intentionally caught - */ -case class ShelloutException(result: CommandResult) extends Exception(result.toString) - -case class InteractiveShelloutException() extends Exception() - -/** - * Encapsulates one of the output streams from a subprocess and provides - * convenience methods for accessing it in a variety of forms - */ -case class StreamValue(chunks: Seq[Bytes]){ - def bytes = chunks.iterator.map(_.array).toArray.flatten - - lazy val string: String = string(StandardCharsets.UTF_8) - def string(codec: Codec): String = new String(bytes, codec.charSet) - - lazy val trim: String = string.trim - def trim(codec: Codec): String = string(codec).trim - - lazy val lines: Vector[String] = Predef.augmentString(string).lines.toVector - def lines(codec: Codec): Vector[String] = Predef.augmentString(string(codec)).lines.toVector -} -/** - * An implicit wrapper defining the things that can - * be "interpolated" directly into a subprocess call. - */ -case class Shellable(s: Seq[String]) -object Shellable{ - implicit def StringShellable(s: String): Shellable = Shellable(Seq(s)) - implicit def SeqShellable(s: Seq[String]): Shellable = Shellable(s) - implicit def OptShellable(s: Option[String]): Shellable = Shellable(s.toSeq) - implicit def SymbolShellable(s: Symbol): Shellable = Shellable(Seq(s.name)) - implicit def BasePathShellable(s: BasePath): Shellable = Shellable(Seq(s.toString)) - implicit def NumericShellable[T: Numeric](s: T): Shellable = Shellable(Seq(s.toString)) -} - diff --git a/ops/src/main/scala/ammonite/ops/package.scala b/ops/src/main/scala/ammonite/ops/package.scala deleted file mode 100644 index 39cc934e6..000000000 --- a/ops/src/main/scala/ammonite/ops/package.scala +++ /dev/null @@ -1,134 +0,0 @@ -package ammonite - -package object ops extends Extensions { - implicit val postfixOps = scala.language.postfixOps - type ResourceNotFoundException = os.ResourceNotFoundException - val ResourceNotFoundException = os.ResourceNotFoundException - - val PathError = os.PathError - - type Path = os.Path - val Path = os.Path - - type RelPath = os.RelPath - val RelPath = os.RelPath - - type FilePath = os.FilePath - val FilePath = os.FilePath - - type BasePath = os.BasePath - val BasePath = os.BasePath - - type ResourceRoot = os.ResourceRoot - val ResourceRoot = os.ResourceRoot - - type FileType = os.FileType - val FileType = os.FileType - - type PermSet = os.PermSet - val PermSet = os.PermSet - - object stat extends Function1[Path, os.StatInfo]{ - def apply(s: Path, followLinks: Boolean = true) = os.stat(s, followLinks) - def apply(s: Path) = os.stat(s) - val posix = os.stat.posix - } - implicit def SymPath(s: Symbol): RelPath = StringPath(s.name) - implicit def StringPath(s: String): RelPath = { - BasePath.checkSegment(s) - RelPath(s) - } - - val exists = os.exists - val read = os.read - val write = os.write - val rm = os.remove.all - val mkdir = os.makeDir.all - object ls extends Function1[Path, LsSeq]{ - def !(implicit arg: Path) = apply(arg) - def apply(src: Path) = { - LsSeq(src, os.list(src).map(_ relativeTo src).toArray.sorted:_*) - } - def iter(p: Path) = os.list.stream(p) - object rec extends Function1[Path, LsSeq]{ - def apply(src: Path) = - LsSeq(src, os.walk(src).map(_ relativeTo src).toArray.sorted:_*) - } - } - - - object ln extends Function2[Path, Path, Unit]{ - def apply(src: Path, dest: Path) = os.hardlink(src, dest) - val s = os.symlink - } - /** - * The root of the filesystem - */ - val root = os.root - val empty = os.rel - - def resource(implicit resRoot: ResourceRoot = Thread.currentThread().getContextClassLoader) = - os.resource - - /** - * The user's home directory - */ - val home = os.home - - val tmp = os.temp - /** - * The current working directory for this process. - */ - val pwd = os.pwd - - val up = os.up - - /** - * If you want to call subprocesses using [[%]] or [[%%]] and don't care - * what working directory they use, import this via - * - * `import ammonite.ops.ImplicitWd._` - * - * To make them use the process's working directory for each subprocess - */ - object ImplicitWd{ - implicit lazy val implicitCwd = ops.pwd - } - - /** - * Extractor to let you easily pattern match on [[ops.Path]]s. Lets you do - * - * {{{ - * @ val base/segment/filename = pwd - * base: Path = Path(Vector("Users", "haoyi", "Dropbox (Personal)")) - * segment: String = "Workspace" - * filename: String = "Ammonite" - * }}} - * - * To break apart a path and extract various pieces of it. - */ - val / = os./ - - /** - * Lets you treat any path as a file, letting you access any property you'd - * normally access through [[stat]]-ing it by [[stat]]-ing the file for you - * when necessary. - */ - implicit def statFileData(p: Path): os.StatInfo = stat(p) - implicit def posixFileData(p: Path): os.PosixStatInfo = stat.posix(p) - - /** - * Used to spawn a subprocess interactively; any output gets printed to the - * console and any input gets requested from the current console. Can be - * used to run interactive subprocesses like `%vim`, `%python`, - * `%ssh "www.google.com"` or `%sbt`. - */ - val % = Shellout.% - /** - * Spawns a subprocess non-interactively, waiting for it to complete and - * collecting all output into a [[CommandResult]] which exposes it in a - * convenient form. Call via `%%('whoami).out.trim` or - * `%%('git, 'commit, "-am", "Hello!").exitCode` - */ - val %% = Shellout.%% -} diff --git a/ops/src/test/resources/scripts/echo b/ops/src/test/resources/scripts/echo deleted file mode 100755 index ef9907913..000000000 --- a/ops/src/test/resources/scripts/echo +++ /dev/null @@ -1 +0,0 @@ -echo $@ \ No newline at end of file diff --git a/ops/src/test/resources/scripts/echo_with_wd b/ops/src/test/resources/scripts/echo_with_wd deleted file mode 100755 index b293377aa..000000000 --- a/ops/src/test/resources/scripts/echo_with_wd +++ /dev/null @@ -1 +0,0 @@ -echo "$@ $PWD" \ No newline at end of file diff --git a/ops/src/test/resources/test/ammonite/ops/folder/file.txt b/ops/src/test/resources/test/ammonite/ops/folder/file.txt deleted file mode 100644 index 1dfeed9ce..000000000 --- a/ops/src/test/resources/test/ammonite/ops/folder/file.txt +++ /dev/null @@ -1 +0,0 @@ -file contents lols \ No newline at end of file diff --git a/ops/src/test/resources/testdata/File.txt b/ops/src/test/resources/testdata/File.txt deleted file mode 100644 index 0d7c24547..000000000 --- a/ops/src/test/resources/testdata/File.txt +++ /dev/null @@ -1,4 +0,0 @@ -I am cow -Hear me moo -I weigh twice as much as you -And I look good on the barbecue \ No newline at end of file diff --git a/ops/src/test/resources/testdata/folder1/Yoghurt Curds Cream Cheese.txt b/ops/src/test/resources/testdata/folder1/Yoghurt Curds Cream Cheese.txt deleted file mode 100644 index 03a2dd580..000000000 --- a/ops/src/test/resources/testdata/folder1/Yoghurt Curds Cream Cheese.txt +++ /dev/null @@ -1,4 +0,0 @@ -And butter -comes from liquid from my udder -I am cow I am cow -Hear me moo ooo \ No newline at end of file diff --git a/ops/src/test/resources/testdata/folder2/folder2a/I am.txt b/ops/src/test/resources/testdata/folder2/folder2a/I am.txt deleted file mode 100644 index 08855043e..000000000 --- a/ops/src/test/resources/testdata/folder2/folder2a/I am.txt +++ /dev/null @@ -1,6 +0,0 @@ -Cow eating grass -methane gas games out my arse -and out my muzzle when I belchhhh -Oh the ozone layer's thinner -from the outcome of my dinner -I am cow I am cow I've got ga-aaas \ No newline at end of file diff --git a/ops/src/test/resources/testdata/folder2/folder2b/b.txt b/ops/src/test/resources/testdata/folder2/folder2b/b.txt deleted file mode 100644 index 08855043e..000000000 --- a/ops/src/test/resources/testdata/folder2/folder2b/b.txt +++ /dev/null @@ -1,6 +0,0 @@ -Cow eating grass -methane gas games out my arse -and out my muzzle when I belchhhh -Oh the ozone layer's thinner -from the outcome of my dinner -I am cow I am cow I've got ga-aaas \ No newline at end of file diff --git a/ops/src/test/scala/test/ammonite/ops/ExampleTests.scala b/ops/src/test/scala/test/ammonite/ops/ExampleTests.scala deleted file mode 100644 index ed19ef63a..000000000 --- a/ops/src/test/scala/test/ammonite/ops/ExampleTests.scala +++ /dev/null @@ -1,323 +0,0 @@ -package test.ammonite.ops - -import java.nio.file.attribute.{GroupPrincipal, FileTime} - -import ammonite.ops._ -import utest._ - -object ExampleTests extends TestSuite{ - - val tests = Tests { - test("reference"){ - import ammonite.ops._ - - // Let's pick our working directory - val wd: Path = pwd/'ops/'target/"scala-2.11"/"test-classes"/'example3 - - // And make sure it's empty - rm! wd - mkdir! wd - - // Reading and writing to files is done through the read! and write! - // You can write `Strings`, `Traversable[String]`s or `Array[Byte]`s - write(wd/"file1.txt", "I am cow") - write(wd/"file2.txt", Seq("I am cow\n", "hear me moo")) - write(wd/'file3, "I weigh twice as much as you".getBytes) - - // When reading, you can either `read!` a `String`, `read.lines!` to - // get a `Vector[String]` or `read.bytes` to get an `Array[Byte]` - read! wd/"file1.txt" ==> "I am cow" - read! wd/"file2.txt" ==> "I am cow\nhear me moo" - read.lines! wd/"file2.txt" ==> Vector("I am cow", "hear me moo") - read.bytes! wd/"file3" ==> "I weigh twice as much as you".getBytes - - // These operations are mirrored in `read.resource`, - // `read.resource.lines` and `read.resource.bytes` to conveniently read - // files from your classpath: - val resourcePath = resource/'test/'ammonite/'ops/'folder/"file.txt" - read(resourcePath).length ==> 18 - read.bytes(resourcePath).length ==> 18 - read.lines(resourcePath).length ==> 1 - - // You can read resources relative to any particular class, including - // the "current" class by passing in `getClass` - val relResourcePath = resource(getClass)/'folder/"file.txt" - read(relResourcePath).length ==> 18 - read.bytes(relResourcePath).length ==> 18 - read.lines(relResourcePath).length ==> 1 - - // By default, `write` fails if there is already a file in place. Use - // `write.append` or `write.over` if you want to append-to/overwrite - // any existing files - write.append(wd/"file1.txt", "\nI eat grass") - write.over(wd/"file2.txt", "I am cow\nHere I stand") - - read! wd/"file1.txt" ==> "I am cow\nI eat grass" - read! wd/"file2.txt" ==> "I am cow\nHere I stand" - - // You can create folders through `mkdir!`. This behaves the same as - // `mkdir -p` in Bash, and creates and parents necessary - val deep = wd/'this/'is/'very/'deep - mkdir! deep - // Writing to a file also creates necessary parents - write(deep/'deeeep/"file.txt", "I am cow", createFolders = true) - - // `ls` provides a listing of every direct child of the given folder. - // Both files and folders are included - ls! wd ==> Seq(wd/"file1.txt", wd/"file2.txt", wd/'file3, wd/'this) - - // `ls.rec` does the same thing recursively - ls.rec! deep ==> Seq(deep/'deeeep, deep/'deeeep/"file.txt") - - // You can move files or folders with `mv` and remove them with `rm!` - ls! deep ==> Seq(deep/'deeeep) - mv(deep/'deeeep, deep/'renamed_deeeep) - ls! deep ==> Seq(deep/'renamed_deeeep) - - // `mv.into` lets you move a file into a - // particular folder, rather than to particular path - mv.into(deep/'renamed_deeeep/"file.txt", deep) - ls! deep/'renamed_deeeep ==> Seq() - ls! deep ==> Seq(deep/"file.txt", deep/'renamed_deeeep) - - // `mv.over` lets you move a file to a particular path, but - // if something was there before it stomps over it - mv.over(deep/"file.txt", deep/'renamed_deeeep) - ls! deep ==> Seq(deep/'renamed_deeeep) - read! deep/'renamed_deeeep ==> "I am cow" // contents from file.txt - - // `rm!` behaves the same as `rm -rf` in Bash, and deletes anything: - // file, folder, even a folder filled with contents - rm! deep/'renamed_deeeep - rm! deep/"file.txt" - ls! deep ==> Seq() - - // You can stat paths to find out information about any file or - // folder that exists there - val info = stat! wd/"file1.txt" - info.isDir ==> false - info.isFile ==> true - info.size ==> 20 - - // Ammonite provides an implicit conversion from `Path` to - // `stat`, so you can use these attributes directly - (wd/"file1.txt").size ==> 20 - () - } - test("longExample"){ - import ammonite.ops._ - - // Pick the directory you want to work with, - // relative to the process working dir - val wd = pwd/'ops/'target/"scala-2.11"/"test-classes"/'example2 - - // Delete a file or folder, if it exists - rm! wd - - // Make a folder named "folder" - mkdir! wd/'folder - - // Copy a file or folder to a particular path - cp(wd/'folder, wd/'folder1) - // Copy a file or folder *into* another folder at a particular path - // There's also `cp.over` which copies it to a path and stomps over - // anything that was there before. - cp.into(wd/'folder, wd/'folder1) - - - // List the current directory - val listed = ls! wd - - // Write to a file without pain! - // Necessary enclosing directories are created automatically - write(wd/'dir2/"file1.scala", "package example\nclass Foo{}\n", createFolders = true) - write(wd/'dir2/"file2.scala", "package example\nclass Bar{}\n", createFolders = true) - - // Rename all .scala files inside the folder d into .java files - ls! wd/'dir2 | mv{case r"$x.scala" => s"$x.java"} - - // List files in a folder - val renamed = ls! wd/'dir2 - - // Line-count of all .java files recursively in wd - val lineCount = ls.rec! wd |? (_.ext == "java") | read.lines | (_.size) sum - - // Find and concatenate all .java files directly in the working directory - mkdir(wd / 'target) - ls! wd/'dir2 |? (_.ext == "java") | read |> (write(wd/'target/"bundled.java", _)) - - assert( - listed == Seq(wd/'folder, wd/'folder1), - ls(wd/'folder1) == Seq(wd/'folder1/'folder), - lineCount == 4, - renamed == Seq(wd/'dir2/"file1.java", wd/'dir2/"file2.java"), - read(wd/'target/"bundled.java") == - "package example\nclass Foo{}\npackage example\nclass Bar{}\n" - ) - - - write(wd/'py/"cow.scala", "Hello World", createFolders = true) - write(wd/".file", "Hello") - // Chains - - // Move all files inside the "py" folder out of it - ls! wd/"py" | mv.all*{case d/"py"/x => d/x } - - // Find all dot-files in the current folder - val dots = ls! wd |? (_.last(0) == '.') - - // Find the names of the 10 largest files in the current working directory - ls.rec! wd | (x => x.size -> x) sortBy (-_._1) take 10 - - // Sorted list of the most common words in your .scala source files - def txt = ls.rec! wd |? (_.ext == "scala") | read - def freq(s: Seq[String]) = s groupBy (x => x) mapValues (_.length) toSeq - val map = txt || (_.split("[^a-zA-Z0-9_]")) |> freq sortBy (-_._2) - - assert( - ls(wd).toSeq.contains(wd/"cow.scala"), - dots == Seq(wd/".file"), - map == Seq("Hello" -> 1, "World" -> 1) - ) - } - test("comparison"){ - rm! pwd/'target/'folder/'thing/'file - write(pwd/'target/'folder/'thing/'file, "Hello!", createFolders = true) - - def removeAll(path: String) = { - def getRecursively(f: java.io.File): Seq[java.io.File] = { - f.listFiles.filter(_.isDirectory).flatMap(getRecursively) ++ f.listFiles - } - getRecursively(new java.io.File(path)).foreach{f => - println(f) - if (!f.delete()) - throw new RuntimeException("Failed to delete " + f.getAbsolutePath) - } - new java.io.File(path).delete - } - removeAll("target/folder/thing") - - assert(ls(pwd/'target/'folder).toSeq == Nil) - - write(pwd/'target/'folder/'thing/'file, "Hello!", createFolders = true) - - rm! pwd/'target/'folder/'thing - assert(ls(pwd/'target/'folder).toSeq == Nil) - } - - test("constructingPaths"){ - // Get the process' Current Working Directory. As a convention - // the directory that "this" code cares about (which may differ - // from the pwd) is called `wd` - val wd = pwd - - // A path nested inside `wd` - wd/'folder/'file - - // A path starting from the root - root/'folder/'file - - // A path with spaces or other special characters - wd/"My Folder"/"My File.txt" - - // Up one level from the wd - wd/up - - // Up two levels from the wd - wd/up/up - } - test("newPath"){ - val target = pwd/'target - } - test("relPaths"){ - // The path "folder/file" - val rel1 = 'folder/'file - val rel2 = 'folder/'file - - // The path "file"; will get converted to a RelPath by an implicit - val rel3 = 'file - - // The relative difference between two paths - val target = pwd/'target/'file - assert((target relativeTo pwd) == 'target/'file) - - // `up`s get resolved automatically - val minus = pwd relativeTo target - val ups = up/up - assert(minus == ups) - rel1: RelPath - rel2: RelPath - rel3: RelPath - } - test("relPathCombine"){ - val target = pwd/'target/'file - val rel = target relativeTo pwd - val newBase = root/'code/'server - assert(newBase/rel == root/'code/'server/'target/'file) - } - test("relPathUp"){ - val target = root/'target/'file - assert(target/up == root/'target) - } - test("canonical"){if (Unix()){ - - assert((root/'folder/'file/up).toString == "/folder") - // not "/folder/file/.." - - assert(('folder/'file/up).toString == "folder") - // not "folder/file/.." - }} - test("findWc"){ - val wd = pwd/'ops/'src/'test/'resources/'testdata - - // find . -name '*.txt' | xargs wc -l - val lines = ls.rec(wd) |? (_.ext == "txt") | read.lines | (_.length) sum - - assert(lines == 20) - } - test("addUpScalaSize"){ - ls.rec! pwd |? (_.ext == "scala") | (_.size) |& (_ + _) - } - test("concatAll"){if (Unix()){ - - ls.rec! pwd |? (_.ext == "scala") | read |> ( - write(pwd/'target/'test/"omg.txt", _, createFolders = true) - ) - }} - - test("noLongLines"){ - // Ensure that we don't have any Scala files in the current working directory - // which have lines more than 100 characters long, excluding generated sources - // in `src_managed` folders. - - def longLines(p: Path) = - (p, read.lines(p).zipWithIndex |? (_._1.length > 100) | (_._2)) - - val filesWithTooLongLines = ( - %%("git", "ls-files")(pwd).out.lines - | (Path(_, pwd)) - |? (_.ext == "scala") - | longLines - |? (_._2.length > 0) - |? (!_._1.segments.contains("src_managed")) - ).map { - case (path, lines) => - (path relativeTo ammonite.ops.pwd, lines.map(_ + 1)) - } - - assert(filesWithTooLongLines.length == 0) - } - test("rename"){ -// val d1/"omg"/x1 = wd -// val d2/"omg"/x2 = wd -// ls! wd |? (_.ext == "scala") | (x => mv! x ! x.pref) - } - test("allSubpathsResolveCorrectly"){ - for(abs <- ls.rec! pwd){ - val rel = abs relativeTo pwd - assert(rel.ups == 0) - assert(pwd / rel == abs) - } - } - } -} diff --git a/ops/src/test/scala/test/ammonite/ops/OpTests.scala b/ops/src/test/scala/test/ammonite/ops/OpTests.scala deleted file mode 100644 index 46d9ccf01..000000000 --- a/ops/src/test/scala/test/ammonite/ops/OpTests.scala +++ /dev/null @@ -1,281 +0,0 @@ -package test.ammonite.ops - -import java.nio.file.NoSuchFileException -import java.nio.{file => nio} - -import ammonite.ops._ - -import utest._ -object OpTests extends TestSuite{ - - val tests = Tests { - val res = pwd/'ops/'src/'test/'resources/'testdata - test("ls") - assert( - ls(res).toSet == Set(res/'folder1, res/'folder2, res/"File.txt"), - ls(res/'folder2).toSet == Set( - res/'folder2/'folder2a, - res/'folder2/'folder2b - ) - -// ls(res/'folder2/'folder2b) == Seq() - ) - test("lsR"){ - ls.rec(res).foreach(println) - intercept[java.nio.file.NoSuchFileException](ls.rec(pwd/'target/'nonexistent)) - assert( - ls.rec(res/'folder2/'folder2b) == Seq(res/'folder2/'folder2b/"b.txt"), - ls.rec(res/'folder2) == Seq( - res/'folder2/'folder2a, - res/'folder2/'folder2b, - res/'folder2/'folder2a/"I am.txt", - res/'folder2/'folder2b/"b.txt" - ), - ls.rec(res) == Seq( - res/"File.txt", - res/'folder1, - res/'folder2, - res/'folder1/"Yoghurt Curds Cream Cheese.txt", - res/'folder2/'folder2a, - res/'folder2/'folder2b, - res/'folder2/'folder2a/"I am.txt", - res/'folder2/'folder2b/"b.txt" - ) - ) - } - test("lsRecPermissions"){ - if(Unix()){ - assert(ls.rec(root/'var/'run).nonEmpty) - } - } - test("readResource"){ - test("positive"){ - test("absolute"){ - val contents = read(resource/'test/'ammonite/'ops/'folder/"file.txt") - assert(contents.contains("file contents lols")) - - val cl = getClass.getClassLoader - val contents2 = read(resource(cl)/'test/'ammonite/'ops/'folder/"file.txt") - assert(contents2.contains("file contents lols")) - } - - test("relative"){ - val cls = classOf[_root_.test.ammonite.ops.Testing] - val contents = read! resource(cls)/'folder/"file.txt" - assert(contents.contains("file contents lols")) - - val contents2 = read! resource(getClass)/'folder/"file.txt" - assert(contents2.contains("file contents lols")) - } - } - test("negative"){ - test - intercept[ResourceNotFoundException]{ - read(resource/'folder/"file.txt") - } - - test - intercept[ResourceNotFoundException]{ - val cls = classOf[_root_.test.ammonite.ops.Testing] - read( - resource(cls)/'test/'ammonite/'ops/'folder/"file.txt" - ) - } - test - intercept[ResourceNotFoundException]{ - read(resource(getClass)/'test/'ammonite/'ops/'folder/"file.txt") - } - test - intercept[ResourceNotFoundException]{ - read(resource(getClass.getClassLoader)/'folder/"file.txt") - } - } - } - test("rm"){ - // shouldn't crash - rm! pwd/'target/'nonexistent - } - test("Mutating"){ - val testPath = pwd/'target/'test - rm! testPath - mkdir! testPath - test("cp"){ - val d = testPath/'copying - test("basic"){ - assert( - !exists(d/'folder), - !exists(d/'file) - ) - mkdir! d/'folder - write(d/'file, "omg") - assert( - exists(d/'folder), - exists(d/'file), - read(d/'file) == "omg" - ) - cp(d/'folder, d/'folder2) - cp(d/'file, d/'file2) - - assert( - exists(d/'folder), - exists(d/'file), - read(d/'file) == "omg", - exists(d/'folder2), - exists(d/'file2), - read(d/'file2) == "omg" - ) - } - test("deep"){ - write(d/'folderA/'folderB/'file, "Cow", createFolders = true) - cp(d/'folderA, d/'folderC) - assert(read(d/'folderC/'folderB/'file) == "Cow") - } - } - test("mv"){ - test("basic"){ - val d = testPath/'moving - mkdir! d/'folder - assert(ls(d) == Seq(d/'folder)) - mv(d/'folder, d/'folder2) - assert(ls(d) == Seq(d/'folder2)) - } - test("shallow"){ - val d = testPath/'moving2 - mkdir(d) - write(d/"A.scala", "AScala") - write(d/"B.scala", "BScala") - write(d/"A.py", "APy") - write(d/"B.py", "BPy") - def fileSet = ls(d).map(_.last).toSet - assert(fileSet == Set("A.scala", "B.scala", "A.py", "B.py")) - test("partialMoves"){ - ls! d | mv{case r"$x.scala" => s"$x.java"} - assert(fileSet == Set("A.java", "B.java", "A.py", "B.py")) - ls! d | mv{case r"A.$x" => s"C.$x"} - assert(fileSet == Set("C.java", "B.java", "C.py", "B.py")) - } - test("fullMoves"){ - ls! d | mv.all{case r"$x.$y" => s"$y.$x"} - assert(fileSet == Set("scala.A", "scala.B", "py.A", "py.B")) - def die = ls! d | mv.all{case r"A.$x" => s"C.$x"} - intercept[MatchError]{ die } - } - } - test("deep"){ - val d = testPath/'moving2 - mkdir(d) - mkdir(d/'scala) - mkdir(d/'py) - write(d/'scala/'A, "AScala") - write(d/'scala/'B, "BScala") - write(d/'py/'A, "APy") - write(d/'py/'B, "BPy") - test("partialMoves"){ - ls.rec! d | mv*{case d/"py"/x => d/x } - assert( - ls.rec(d).toSet == Set( - d/'py, - d/'scala, - d/'scala/'A, - d/'scala/'B, - d/'A, - d/'B - ) - ) - } - test("fullMoves"){ - def die = ls.rec! d | mv.all*{case d/"py"/x => d/x } - intercept[MatchError]{ die } - - ls.rec! d |? (_.isFile) | mv.all*{ - case d/"py"/x => d/'scala/'py/x - case d/"scala"/x => d/'py/'scala/x - case d => println("NOT FOUND " + d); d - } - - assert( - ls.rec(d).toSet == Set( - d/'py, - d/'scala, - d/'py/'scala, - d/'scala/'py, - d/'scala/'py/'A, - d/'scala/'py/'B, - d/'py/'scala/'A, - d/'py/'scala/'B - ) - ) - } - } - // ls! wd | mv* - } - test("mkdirRm"){ - test("singleFolder"){ - val single = testPath/'single - mkdir! single/'inner - assert(ls(single) == Seq(single/'inner)) - rm! single/'inner - assert(ls(single) == Seq()) - } - test("nestedFolders"){ - val nested = testPath/'nested - mkdir! nested/'inner/'innerer/'innerest - assert( - ls(nested) == Seq(nested/'inner), - ls(nested/'inner) == Seq(nested/'inner/'innerer), - ls(nested/'inner/'innerer) == Seq(nested/'inner/'innerer/'innerest) - ) - rm! nested/'inner - assert(ls(nested) == Seq()) - } - } - test("readWrite"){ - val d = testPath/'readWrite - mkdir! d - test("simple"){ - write(d/'file, "i am a cow") - assert(read(d/'file) == "i am a cow") - } - test("autoMkdir"){ - write(d/'folder/'folder/'file, "i am a cow", createFolders = true) - assert(read(d/'folder/'folder/'file) == "i am a cow") - } - test("binary"){ - write(d/'file, Array[Byte](1, 2, 3, 4)) - assert(read(d/'file).toSeq == Array[Byte](1, 2, 3, 4).toSeq) - } - test("concatenating"){ - write(d/'concat1, Seq("a", "b", "c")) - assert(read(d/'concat1) == "abc") - write(d/'concat2, Seq(Array[Byte](1, 2), Array[Byte](3, 4))) - assert(read.bytes(d/'concat2).toSeq == Array[Byte](1, 2, 3, 4).toSeq) - } - test("writeAppend"){ - write.append(d/"append.txt", "Hello") - assert(read(d/"append.txt") == "Hello") - write.append(d/"append.txt", " World") - assert(read(d/"append.txt") == "Hello World") - } - test("writeOver"){ - write.over(d/"append.txt", "Hello") - assert(read(d/"append.txt") == "Hello") - write.over(d/"append.txt", " Wor") - assert(read(d/"append.txt") == " Wor") - } - } - test("Failures"){ - val d = testPath/'failures - mkdir! d - test("nonexistant"){ - test - intercept[nio.NoSuchFileException](ls! d/'nonexistent) - test - intercept[nio.NoSuchFileException](read! d/'nonexistent) - test - intercept[ResourceNotFoundException](read! resource/'failures/'nonexistent) - test - intercept[nio.NoSuchFileException](cp(d/'nonexistent, d/'yolo)) - test - intercept[nio.NoSuchFileException](mv(d/'nonexistent, d/'yolo)) - } - test("collisions"){ - mkdir! d/'folder - write(d/'file, "lolol") - test - intercept[nio.FileAlreadyExistsException](mv(d/'file, d/'folder)) - test - intercept[nio.FileAlreadyExistsException](cp(d/'file, d/'folder)) - test - intercept[nio.FileAlreadyExistsException](write(d/'file, "lols")) - } - } - } - } -} diff --git a/ops/src/test/scala/test/ammonite/ops/PathTests.scala b/ops/src/test/scala/test/ammonite/ops/PathTests.scala deleted file mode 100644 index 3b6f48f31..000000000 --- a/ops/src/test/scala/test/ammonite/ops/PathTests.scala +++ /dev/null @@ -1,363 +0,0 @@ -package test.ammonite.ops - -import java.nio.file.Paths - -import ammonite.ops._ -import utest._ -object PathTests extends TestSuite{ - val tests = Tests { - test("Basic"){ - val rel = 'src/'main/'scala - test("Transformers"){ - if(Unix()){ - assert( - // ammonite.Path to java.nio.file.Path - (root/'omg).toNIO == Paths.get("/omg"), - - // java.nio.file.Path to ammonite.Path - root/'omg == Path(Paths.get("/omg")), - empty/'omg == RelPath(Paths.get("omg")), - - // ammonite.Path to String - (root/'omg).toString == "/omg", - (empty/'omg).toString == "omg", - (up/'omg).toString == "../omg", - (up/up/'omg).toString == "../../omg", - - // String to ammonite.Path - root/'omg == Path("/omg"), - empty/'omg == RelPath("omg") - ) - } - } - - test("RelPath"){ - test("Constructors"){ - test("Symbol"){ - if (Unix()){ - val rel1 = rel / 'ammonite - assert( - rel1.segments == Seq("src", "main", "scala", "ammonite"), - rel1.toString == "src/main/scala/ammonite" - ) - } - } - test("String"){ - if (Unix()){ - val rel1 = rel / "Path.scala" - assert( - rel1.segments == Seq("src", "main", "scala", "Path.scala"), - rel1.toString == "src/main/scala/Path.scala" - ) - } - } - test("Combos"){ - def check(rel1: RelPath) = assert( - rel1.segments == Seq("src", "main", "scala", "sub1", "sub2"), - rel1.toString == "src/main/scala/sub1/sub2" - ) - test("ArrayString"){ - if (Unix()){ - val arr = Array("sub1", "sub2") - check(rel / arr) - } - } - test("ArraySymbol"){ - if (Unix()){ - val arr = Array('sub1, 'sub2) - check(rel / arr) - } - } - test("SeqString"){ - if (Unix()) check(rel / Seq("sub1", "sub2")) - } - test("SeqSymbol"){ - if (Unix()) check(rel / Seq('sub1, 'sub2)) - } - test("SeqSeqSeqSymbol"){ - if (Unix()){ - check( - rel / Seq(Seq(Seq('sub1), Seq()), Seq(Seq('sub2)), Seq()) - ) - } - } - } - } - test("Relativize"){ - def eq[T](p: T, q: T) = assert(p == q) - test - eq('omg/'bbq/'wtf relativeTo 'omg/'bbq/'wtf, empty) - test - eq('omg/'bbq relativeTo 'omg/'bbq/'wtf, up) - test - eq('omg/'bbq/'wtf relativeTo 'omg/'bbq, empty/'wtf) - test - eq('omg/'bbq relativeTo 'omg/'bbq/'wtf, up) - test - eq(up/'omg/'bbq relativeTo 'omg/'bbq, up/up/up/'omg/'bbq) - test - intercept[PathError.NoRelativePath]('omg/'bbq relativeTo up/'omg/'bbq) - } - } - test("AbsPath"){ - val d = pwd - val abs = d / rel - test("Constructor"){ - if (Unix()) assert( - abs.toString.drop(d.toString.length) == "/src/main/scala", - abs.toString.length > d.toString.length - ) - } - test("Relativize"){ - def eq[T](p: T, q: T) = assert(p == q) - test - eq(root/'omg/'bbq/'wtf relativeTo root/'omg/'bbq/'wtf, empty) - test - eq(root/'omg/'bbq relativeTo root/'omg/'bbq/'wtf, up) - test - eq(root/'omg/'bbq/'wtf relativeTo root/'omg/'bbq, empty/'wtf) - test - eq(root/'omg/'bbq relativeTo root/'omg/'bbq/'wtf, up) - test - intercept[PathError.NoRelativePath]('omg/'bbq relativeTo up/'omg/'bbq) - } - } - test("Ups"){ - test("RelativeUps"){ - val rel2 = rel/up - assert( - rel2 == 'src/'main, - rel/up/up == empty/'src, - rel/up/up/up == empty, - rel/up/up/up/up == up, - rel/up/up/up/up/up == up/up, - up/rel == up/'src/'main/'scala - ) - } - test("AbsoluteUps"){ - // Keep applying `up` and verify that the path gets - // shorter and shorter and eventually errors. - var abs = pwd - var i = abs.segmentCount - while(i > 0){ - abs/=up - i-=1 - assert(abs.segmentCount == i) - } - intercept[PathError.AbsolutePathOutsideRoot.type]{ abs/up } - } - test("RootUpBreak"){ - intercept[PathError.AbsolutePathOutsideRoot.type]{ root/up } - val x = root/"omg" - val y = x/up - intercept[PathError.AbsolutePathOutsideRoot.type]{ y / up } - } - } - test("Comparison"){ - test("Relative") - assert( - 'omg/'wtf == 'omg/'wtf, - 'omg/'wtf != 'omg/'wtf/'bbq, - 'omg/'wtf/'bbq startsWith 'omg/'wtf, - 'omg/'wtf startsWith 'omg/'wtf, - up/'omg/'wtf startsWith up/'omg/'wtf, - !('omg/'wtf startsWith 'omg/'wtf/'bbq), - !(up/'omg/'wtf startsWith 'omg/'wtf), - !('omg/'wtf startsWith up/'omg/'wtf) - ) - test("Absolute") - assert( - root/'omg/'wtf == root/'omg/'wtf, - root/'omg/'wtf != root/'omg/'wtf/'bbq, - root/'omg/'wtf/'bbq startsWith root/'omg/'wtf, - root/'omg/'wtf startsWith root/'omg/'wtf, - !(root/'omg/'wtf startsWith root/'omg/'wtf/'bbq) - ) - test("Invalid"){ - compileError("""root/'omg/'wtf < 'omg/'wtf""") - compileError("""root/'omg/'wtf > 'omg/'wtf""") - compileError("""'omg/'wtf < root/'omg/'wtf""") - compileError("""'omg/'wtf > root/'omg/'wtf""") - } - } - } - - test("Errors"){ - test("InvalidChars"){ - val ex = intercept[PathError.InvalidSegment]('src/"Main/.scala") - - val PathError.InvalidSegment("Main/.scala", msg1) = ex - - assert(msg1.contains("[/] is not a valid character to appear in a path segment")) - - val ex2 = intercept[PathError.InvalidSegment](root/"hello"/".."/"world") - - val PathError.InvalidSegment("..", msg2) = ex2 - - assert(msg2.contains("use the `up` segment from `os.up`")) - } - test("InvalidSegments"){ - intercept[PathError.InvalidSegment]{root/ "core/src/test"} - intercept[PathError.InvalidSegment]{root/ ""} - intercept[PathError.InvalidSegment]{root/ "."} - intercept[PathError.InvalidSegment]{root/ ".."} - } - test("EmptySegment"){ - intercept[PathError.InvalidSegment]('src / "") - intercept[PathError.InvalidSegment]('src / ".") - intercept[PathError.InvalidSegment]('src / "..") - } - test("CannotRelativizeAbsAndRel"){ - val abs = pwd - val rel = 'omg/'wtf - compileError(""" - abs relativeTo rel - """).check( - """ - abs relativeTo rel - ^ - """, - "type mismatch" - ) - compileError(""" - rel relativeTo abs - """).check( - """ - rel relativeTo abs - ^ - """, - "type mismatch" - ) - } - test("InvalidCasts"){ - if(Unix()){ - intercept[IllegalArgumentException](Path("omg/cow")) - intercept[IllegalArgumentException](RelPath("/omg/cow")) - } - } - } - test("Extractors"){ - test("regex"){ - val r"omg$x" = "omgasd" - assert(x == "asd") - val r"${y}omg" = "asdomg" - assert(y == "asd") - val r"omg${z}bbq" = "omgasdbbq" - assert(z == "asd") - val r"omg${a}b${b}bq" = "omgasdbbq" - assert(a == "asd", b == "") - } - test("paths"){ - val a/b/c/d/"omg" = pwd/'A/'B/'C/'D/"omg" - assert( - a == pwd/'A, - b == "B", - c == "C", - d == "D" - ) - - // If the paths aren't deep enough, it - // just doesn't match but doesn't blow up - root/'omg match { - case a3/b3/c3/d3/e3 => assert(false) - case _ => - } - } - } - test("sorting"){ - assert( - Seq(root/'c, root, root/'b, root/'a).sorted == Seq(root, root/'a, root/'b, root/'c), - Seq(up/'c, up/up/'c, 'b/'c, 'a/'c, 'a/'d).sorted == - Seq('a/'c, 'a/'d, 'b/'c, up/'c, up/up/'c) - ) - } - test("construction"){ - test("success"){ - if(Unix()){ - val relStr = "hello/cow/world/.." - val absStr = "/hello/world" - - assert( - RelPath(relStr) == 'hello/'cow, - // Path(...) also allows paths starting with ~, - // which is expanded to become your home directory - Path(absStr) == root/'hello/'world - ) - - // You can also pass in java.io.File and java.nio.file.Path - // objects instead of Strings when constructing paths - val relIoFile = new java.io.File(relStr) - val absNioFile = java.nio.file.Paths.get(absStr) - - assert( - RelPath(relIoFile) == 'hello/'cow, - Path(absNioFile) == root/'hello/'world, - Path(relIoFile, root/'base) == root/'base/'hello/'cow - ) - } - } - test("basepath"){ - if(Unix()){ - val relStr = "hello/cow/world/.." - val absStr = "/hello/world" - assert( - FilePath(relStr) == 'hello/'cow, - FilePath(absStr) == root/'hello/'world - ) - } - } - test("based"){ - if(Unix()){ - val relStr = "hello/cow/world/.." - val absStr = "/hello/world" - val basePath: FilePath = FilePath(relStr) - assert( - Path(relStr, root/'base) == root/'base/'hello/'cow, - Path(absStr, root/'base) == root/'hello/'world, - Path(basePath, root/'base) == root/'base/'hello/'cow, - Path(".", pwd).last != "" - ) - } - } - test("failure"){ - if(Unix()){ - val relStr = "hello/.." - intercept[java.lang.IllegalArgumentException]{ - Path(relStr) - } - - val absStr = "/hello" - intercept[java.lang.IllegalArgumentException]{ - RelPath(absStr) - } - - val tooManyUpsStr = "/hello/../.." - intercept[PathError.AbsolutePathOutsideRoot.type]{ - Path(tooManyUpsStr) - } - } - } - test("symlinks"){ - - val names = Seq('test123, 'test124, 'test125, 'test126) - val twd = tmp.dir() - - test("nestedSymlinks"){ - if(Unix()) { - names.foreach(p => rm ! twd/p) - mkdir ! twd/'test123 - ln.s(twd/'test124, twd/'test123) - ln.s(twd/'test125, twd/'test124) - ln.s(twd/'test126, twd/'test125) - assert(os.followLink(twd/'test126).get == os.followLink(twd/'test123).get) - names.foreach(p => rm ! twd/p) - names.foreach(p => assert(!exists(twd/p))) - } - } - - test("danglingSymlink"){ - if(Unix()) { - names.foreach(p => rm ! twd/p) - mkdir ! twd/'test123 - ln.s(twd/'test124, twd/'test123) - ln.s(twd/'test125, twd/'test124) - ln.s(twd/'test126, twd/'test125) - rm ! twd / 'test123 - assert( os.followLink(twd / 'test126).isEmpty) - names.foreach(p => rm ! twd / p) - names.foreach(p => assert(!exists(twd / p))) - names.foreach(p => rm ! twd/p) - names.foreach(p => assert(!exists(twd/p))) - } - } - } - } - } -} diff --git a/ops/src/test/scala/test/ammonite/ops/ShelloutTests.scala b/ops/src/test/scala/test/ammonite/ops/ShelloutTests.scala deleted file mode 100644 index b757fcd88..000000000 --- a/ops/src/test/scala/test/ammonite/ops/ShelloutTests.scala +++ /dev/null @@ -1,110 +0,0 @@ -package test.ammonite.ops - -import ammonite.ops._ -import utest._ - -object ShelloutTests extends TestSuite{ - val scriptFolder = pwd/'ops/'src/'test/'resources/'scripts - - val tests = Tests { - test("implicitWd"){ - import ammonite.ops.ImplicitWd._ - test("lines"){ - val res = %%('ls, "ops/src/test/resources/testdata") - assert(res.out.lines == Seq("File.txt", "folder1", "folder2")) - } - test("string"){ - val res = %%('ls, "ops/src/test/resources/testdata") - assert(res.out.string == "File.txt\nfolder1\nfolder2\n") - } - test("bytes"){ - if(Unix()){ - val res = %%('echo, "abc") - val listed = res.out.bytes - // assert(listed == "File.txt\nfolder\nfolder2\nFile.txt".getBytes) - listed.toSeq - } - } - test("chained"){ - assert(%%('git, 'init).out.string.contains("Reinitialized existing Git repository")) - assert(%%('git, "init").out.string.contains("Reinitialized existing Git repository")) - assert(%%('ls, pwd).out.string.contains("readme.md")) - } - test("basicList"){ - val files = List("readme.md", "build.sbt") - val output = %%('ls, files).out.string - assert(files.forall(output.contains)) - } - test("listMixAndMatch"){ - val stuff = List("I", "am", "bovine") - val result = %%('echo, "Hello,", stuff, "hear me roar") - assert(result.out.string.contains("Hello, " + stuff.mkString(" ") + " hear me roar")) - } - test("failures"){ - val ex = intercept[ShelloutException]{ %%('ls, "does-not-exist") } - val res: CommandResult = ex.result - assert( - res.exitCode != 0, - res.err.string.contains("No such file or directory") - ) - } - - test("filebased"){ - if(Unix()){ - assert(%%(scriptFolder/'echo, 'HELLO).out.lines.mkString == "HELLO") - - val res: CommandResult = - %%(root/'bin/'bash, "-c", "echo 'Hello'$ENV_ARG", ENV_ARG=123) - - assert(res.out.string.trim == "Hello123") - } - } - test("filebased2"){ - if(Unix()){ - val res = %%('which, 'echo) - val echoRoot = Path(res.out.string.trim) - assert(echoRoot == root/'bin/'echo || echoRoot == root/'usr/'bin/'echo) - - assert(%%(echoRoot, 'HELLO).out.lines == Seq("HELLO")) - } - } - - test("envArgs"){ - val res0 = %%('bash, "-c", "echo \"Hello$ENV_ARG\"", ENV_ARG=12) - assert(res0.out.lines == Seq("Hello12")) - - val res1 = %%('bash, "-c", "echo \"Hello$ENV_ARG\"", ENV_ARG=12) - assert(res1.out.lines == Seq("Hello12")) - - val res2 = %%('bash, "-c", "echo 'Hello$ENV_ARG'", ENV_ARG=12) - assert(res2.out.lines == Seq("Hello$ENV_ARG")) - - val res3 = %%('bash, "-c", "echo 'Hello'$ENV_ARG", ENV_ARG=123) - assert(res3.out.lines == Seq("Hello123")) - } - - } - test("workingDirectory"){ - implicit var wd = pwd - val listed1 = %%('ls) - - wd /= up - - val listed2 = %%('ls) - - assert(listed2 != listed1) - } - test("customWorkingDir"){ - val res1 = %.ls()(pwd) // explicitly - // or implicitly - import ammonite.ops.ImplicitWd._ - val res2 = %ls - } - test("fileCustomWorkingDir"){ - if(Unix()){ - val output = %%.apply(scriptFolder/'echo_with_wd, 'HELLO)(root/'usr) - assert(output.out.lines == Seq("HELLO /usr")) - } - } - } -} diff --git a/ops/src/test/scala/test/ammonite/ops/unix.scala b/ops/src/test/scala/test/ammonite/ops/unix.scala deleted file mode 100644 index e04495e1b..000000000 --- a/ops/src/test/scala/test/ammonite/ops/unix.scala +++ /dev/null @@ -1,13 +0,0 @@ -package test.ammonite.ops - -/** - * Created by haoyi on 2/17/16. - */ -object Unix { - def apply() = java.nio.file.Paths.get("").toAbsolutePath.getRoot.toString == "/" -} - -/** - * Dummy class just used to test classloader relative/absolute resource logic - */ -class Testing \ No newline at end of file diff --git a/readme.md b/readme.md index 157771b1e..6c580ea6d 100644 --- a/readme.md +++ b/readme.md @@ -85,11 +85,6 @@ Although most features should be unit tested, it's still useful to fire up a REP command. You can also pass in the path to a `.sc` file to run it using Ammonite's script runner -- `mill -i -w shell[2.12.6].test.run` brings up a fully-loaded shell with all filesystem - utilities included: `wd`, `cd!`, autocomplete for filesystem paths, and more. - This uses `readme/resources/example-predef.scala` instead of your default - predef, for easier experimentation and development. - - `mill -i -w integration[2.12.6].test.run` runs the trivial main method in the `integration` subproject, letting you manually test running Ammonite programmatically, whether through `run` or `debug` diff --git a/readme/Cookbook.scalatex b/readme/Cookbook.scalatex index 3ec618649..0cfd273ae 100644 --- a/readme/Cookbook.scalatex +++ b/readme/Cookbook.scalatex @@ -9,8 +9,7 @@ The Ammonite Scala REPL and Scripts are meant to be extended: you can load in arbitrary Java/Scala modules from the internet via @sect.ref{import $ivy}. Using this third-party code, you - extend the REPL to do anything you wish to do, and tools like - @sect.ref{Ammonite-Shell} are simply modules like any other. Simple + extend the REPL to do anything you wish to do. Simple install Java, @sect.ref("Ammonite-REPL", "download Ammonite") onto any Linux/OSX machine, and try out one of these fun snippets! They work directly in the @sect.ref{Ammonite-REPL}, or you can save them to @@ -37,9 +36,6 @@ @hl.scala Welcome to the Ammonite Repl - @@ import ammonite.ops._ - import ammonite.ops._ - @@ val resp = requests.get("https://api.github.com/repos/scala/scala") @@ val parsed = upickle.json.read(resp.text()).asInstanceOf[upickle.Js.Obj] @@ -55,9 +51,9 @@ ("login", Str("scala")), ... - @@ for((k, v) <- parsed.value) write(pwd/'target/'temp/k, upickle.json.write(v)) + @@ for((k, v) <- parsed.value) os.write(os.pwd/'target/'temp/k, upickle.json.write(v)) - @@ ls! pwd/'target/'temp + @@ os.list(os.pwd/'target/'temp) res6: LsSeq = LsSeq( /Users/haoyi/Dropbox (Personal)/Workspace/Ammonite/target/temp/archive_url, /Users/haoyi/Dropbox (Personal)/Workspace/Ammonite/target/temp/assignees_url, @@ -66,7 +62,7 @@ ... @p - In this example, we use the @lnk("Requests-Scala", "https://github.com/lihaoyi/requests-scala") library to download a URL, and we use @lnk("uPickle", "http://lihaoyi.github.io/upickle-pprint/upickle/") and @sect.ref{Ammonite-Ops} to parse the JSON and write it into files. uPickle and Ammonite-Ops are bundled with the Ammonite REPL and are used internally. + In this example, we use the @lnk("Requests-Scala", "https://github.com/lihaoyi/requests-scala") library to download a URL, and we use @lnk("uPickle", "http://lihaoyi.github.io/upickle-pprint/upickle/") and OS-Lib to parse the JSON and write it into files. uPickle and Ammonite-Ops are bundled with the Ammonite REPL and are used internally. @p This is a small example, but it illustrates the potential: if you find yourself needing to scrape some website or bulk-download large quantities of data from some website's HTTP/JSON API, you can start doing so within a matter of seconds using Ammonite. The results are given to you in nicely structured data, and you can deal with them using any Java or Scala libraries or tools you are used to rather than being forced to munge around in Bash. Sometimes, you may find that you need to get data from somewhere without a nice JSON API, which means you'd need to fall back to @sect.ref{Scraping HTML}... @@ -168,13 +164,12 @@ @hl.scala @@ import $ivy.`org.apache.poi:poi-ooxml:3.13` - @@ import ammonite.ops._ // Prepare to deal with some files @@ import org.apache.poi.xwpf.usermodel._ // Bring Ms-Word APIs into scope @@ import collection.JavaConversions._ // Make use of Java collections easier @@ val path = pwd/'amm/'src/'test/'resources/'testdata/"Resume.docx" - @@ val docx = new XWPFDocument(new java.io.ByteArrayInputStream(read.bytes(path))) + @@ val docx = new XWPFDocument(new java.io.ByteArrayInputStream(os.read.bytes(path))) @@ docx.get getAllEmbedds getParagraphArray diff --git a/readme/Footer.scalatex b/readme/Footer.scalatex index c053ae3bd..38a838bfd 100644 --- a/readme/Footer.scalatex +++ b/readme/Footer.scalatex @@ -619,7 +619,7 @@ Importing @code{$file}s from within your predef now properly makes them available within your REPL, the same way other imports are @issue(616) @li - The predef required for @sect.ref{Ammonite-Shell} was tweaked slightly; + The predef required for Ammonite-Shell was tweaked slightly; if you are using it please update your @code{~/.ammonite/predef.sc} with the new @lnk("predef", "https://git.io/vH4ju") code. @@ -886,7 +886,7 @@ Added @code{show} and @code{typeOf} back into the default REPL imports, since they are pretty useful @li - @code{write.over} in @sect.ref{Ammonite-Ops} should now properly + @code{write.over} in Ammonite-Ops should now properly truncate files if the thing being written is shorter than the original @issue(441) @li @@ -1085,12 +1085,12 @@ much more consistent. @li Moved tools such as @sect.ref{grep}, @sect.ref{time}, - @sect.ref{browse} from @sect.ref{Ammonite-Shell} into the base + @sect.ref{browse} from Ammonite-Shell into the base @sect.ref{Ammonite-REPL}, and made them imported by default, so everyone can enjoy them by default. @li - Imported the various @sect.ref("Extensions", "pipe operations") from - @sect.ref{Ammonite-Ops}: aliasing @code{.map} as @code{|}, + Imported the various pipe operations from + Ammonite-Ops: aliasing @code{.map} as @code{|}, @code{.filter} as @code{|?}, etc. to make it more convenient to use tools like @sect.ref{grep} from the base REPL @li @@ -1122,7 +1122,7 @@ @li @hl.scala{write} no longer inserts newlines between items by default. @li - Introduced the @sect.ref{browse} helper to @sect.ref{Ammonite-Shell}, + Introduced the @sect.ref{browse} helper to Ammonite-Shell, letting you easily open up large data structures in external editors like Vim or Emacs to browse them without spamming the console @li @@ -1164,7 +1164,7 @@ @hl.scala{Thread.currentThread.getContextClassLoader} by default, fixing @issue(348) @li - Re-organize @sect.ref{Reading Resources} in Ammonite-Ops to allow + Re-organize Reading Resources in Ammonite-Ops to allow proper handling of absolute and relative resources by passing in @hl.scala{Class}s or @hl.scala{ClassLoader}s @li @@ -1224,7 +1224,6 @@ Construction of @hl.scala{Path}s from various types (@hl.scala{String}s, @hl.scala{java.nio.file.Path}, @hl.scala{java.io.File}) is much more well behaved & consistent now. - See @sect.ref{Constructing Paths} for details. @li @hl.scala{read.resource! root/'foo} is now @hl.scala{read! resource/'foo} @@ -1336,8 +1335,7 @@ @li Refactor of the @hl.scala{CommandResult} type being returned from the @hl.scala{%%} operator, to now properly capture the raw byte output, - stdout, stderr, exit code. See @sect.ref{Spawning Subprocesses} for - details. @issue(207) + stdout, stderr, exit code. @issue(207) @li Added a new @sect.ref{grep} command. @li @@ -1410,7 +1408,7 @@ Ammonite's filesystem functionality (@hl.scala{cd!}, @hl.scala{wd}, path-completion) has been pulled out of Ammonite-REPL, and is now available separately as - @sect.ref{Ammonite-Shell}. + Ammonite-Shell. @li Improve the pretty-printing of the @hl.scala{ls} and @hl.scala{ls.rec} commands @@ -1427,12 +1425,12 @@ folders, before fetching from maven central @li Wrote up a good amount of documentation for - @sect.ref{Ammonite-Shell}: using Ammonite as a Bash replacement + Ammonite-Shell: using Ammonite as a Bash replacement @sect{0.4.6} @ul @li - Provide a way of @sect.ref{Invoking Files} and passing - @sect.ref{Environment Variables} + Provide a way of Invoking Files and passing + Environment Variables @li Documented existing approach for setting @sect.ref{Compiler Flags} @li @@ -1550,9 +1548,9 @@ Removed the ability to reload classes; using @hl.scala{load.ivy} no longer causes all existing values to be lazily recomputed. @li - Added the @sect.ref("Filesystem Operations", "cd! and wd") + Added the cd! and wd built-ins to make working with filesystem operations via - @sect.ref{Ammonite-Ops} more pleasant + Ammonite-Ops more pleasant @li Evaluated values of type @hl.scala{Unit} are no longer echo-ed to the user @@ -1568,8 +1566,8 @@ Standardized the use of @sect.ref{Refs} for configuration, including the ability to bind them "live" to the value of an expression. @li - Allows you to trivially @sect.ref("Spawning Subprocesses", - "spawn subprocesses"), letting you run @code{git} commands, edit + Allows you to trivially + spawn subprocesses, letting you run @code{git} commands, edit files via @code{vim}, open @code{ssh} sessions or even start @code{SBT} or @code{Python} shells right from your Scala REPL @sect{0.3.x} diff --git a/readme/Index.scalatex b/readme/Index.scalatex index f892e3319..f5be9d4a5 100644 --- a/readme/Index.scalatex +++ b/readme/Index.scalatex @@ -1,6 +1,5 @@ @import Main._ @import readme.Sample._ -@import ammonite.ops._ @script (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ @@ -49,10 +48,8 @@ @p Ammonite lets you use the @lnk("Scala language", "https://www.scala-lang.org/") for - scripting purposes: in the @sect.ref("Ammonite-REPL", "REPL"), as - @sect.ref("Scala Scripts", "scripts"), as a @sect.ref("Ammonite-Ops", - "library to use in existing projects"), or as a standalone - @sect.ref("Ammonite-Shell", "systems shell"). + scripting purposes: in the @sect.ref("Ammonite-REPL", "REPL") or as + @sect.ref("Scala Scripts", "scripts"). @val logos = Seq( ( @@ -76,26 +73,6 @@ setting up a "project" or waiting for SBT's slow startup times. """ - ), - ( - "fa-gears", - "Ammonite-Ops", - "A Rock-solid Filesystem Library for Scala", - """ - Deal with the filesystem - easily from your existing Scala projects or applications, as easily - as you would from a Bash or Python script. - """ - ), - ( - "fa-terminal", - "Ammonite-Shell", - "A modern replacement for the Bash system shell", - """ - Provides a systems shell in the high-level Scala language, letting you - seamlessly mix system operations with real code without the hassle or - the frustration of trying to write complex code in Bash. - """ ) ) @@ -196,7 +173,5 @@ @Repl() @Scripts() - @Ops() - @Shell() @Cookbook() @Footer() diff --git a/readme/Ops.scalatex b/readme/Ops.scalatex deleted file mode 100644 index d1ed8788f..000000000 --- a/readme/Ops.scalatex +++ /dev/null @@ -1,427 +0,0 @@ -@import Main._ -@import readme.Sample._ -@import ammonite.ops._ -@val fileOps = read! cwd/'ops/'src/'main/'scala/'ammonite/'ops/"FileOps.scala" - -@sect("Ammonite-Ops", "A Rock-solid Filesystem Library for Scala") - @p - @b{Ammonite-Ops} is a library to make common filesystem operations in Scala as concise and easy-to-use as from the Bash shell, while being robust enough to use in large applications without getting messy. It lives in the same repo as the Ammonite REPL, but can easily be used stand-alone in a normal SBT/maven project. - - @p - To get started with Ammonite-Ops, add this to your @code{build.sbt}: - - @hl.scala{libraryDependencies += "com.lihaoyi" %% "ammonite-ops" % "@ammonite.Constants.version"} - - @p - And you're all set! Here's an example of some common operations you can do with Ammonite-Ops - - @val opsTests = 'ops/'src/'test/'scala/'test/'ammonite/'ops - @val opsExamples = opsTests/"ExampleTests.scala" - - @hl.ref(opsExamples, Seq("longExample", "import"), "assert(") - - @p - These examples make heavy use of Ammonite-Ops' @sect.ref{Paths}, @sect.ref{Operations} and @sect.ref{Extensions} to achieve their minimal, concise syntax. - @p - As you can see, Ammonite-Ops replaces the common mess of boilerplate: - - @hl.ref('ops/'src/'test/'scala/'test/'ammonite/'ops/"ExampleTests.scala", Seq("comparison", "def removeAll"), "assert(") - - @p - With a single, sleek expression: - - @hl.ref(opsExamples, Seq("comparison", "assert", "rm!"), "assert(") - - @p - That handles the common case for you: recursively deleting folders, not-failing if the file doesn't exist, etc. - - @sect{Paths} - @p - Ammonite uses strongly-typed data-structures to represent filesystem paths. The two basic versions are: - - @ul - @li - @code{Path}: an absolute path, starting from the root - - @li - @code{RelPath}: a relative path, not rooted anywhere - - @p - Generally, almost all commands take absolute @code{Path}s. These have a number of useful operations that can be performed on them. Absolute paths can be created in a few ways: - - @hl.ref(opsExamples, Seq("constructingPaths", "//"), "}") - - @p - Note that there are no in-built operations to change the @code{pwd}. In general you should not need to: simply defining a new path, e.g. - - @hl.ref(opsExamples, Seq("newPath", "val"), "}") - - @p - Should be sufficient for most needs. - - @p - Above, we made use of the @hl.scala{pwd} built-in path. There are a number of @hl.scala{Path}s built into Ammonite: - - @ul - @li - @hl.scala{pwd}: The current working directory of the process. This can't be changed in Java, so if you need another path to work with the convention is to define a @hl.scala{wd} variable. - @li - @hl.scala{root}: The root of the filesystem. - @li - @hl.scala{home}: The home directory of the current user. - @li - @hl.scala{tmp()}/@hl.scala{tmp.dir()}: Creates a temporary file/folder and returns the path. - - @sect{RelPaths} - @p - @hl.scala{RelPath}s represent relative paths. These are basically the same data structure as @hl.scala{Path}s, except that they can represent a number of @hl.scala{up}s before the relative path is applied. They can be created in the following ways: - - @hl.ref(opsExamples, Seq("relPaths", "//"), ":") - - @p - In general, very few APIs take relative paths. Their main purpose is to be combined with absolute paths in order to create new absolute paths. e.g. - - @hl.ref(opsExamples, Seq("relPathCombine", "val"), "}") - - @p - @hl.scala{up} is a relative path that comes in-built: - - @hl.ref(opsExamples, Seq("relPathUp", "val"), "}") - - @p - Note that all paths, both relative and absolute, are always expressed in a canonical manner: - - @hl.ref(opsExamples, Seq("canonical", "assert"), "}") - - @p - So you don't need to worry about canonicalizing your paths before comparing them for equality or otherwise manipulating them. - - @sect{Path Operations} - - @p - Ammonite's paths are transparent data-structures, and you can always access the @hl.scala{segments} and @hl.scala{ups} directly. Nevertheless, Ammonite defines a number of useful operations that handle the common cases of dealing with these paths. - - @p - In this definition, @hl.scala{ThisType} represents the same type as the current path; e.g. a @hl.scala{Path}'s @hl.scala{/} returns a @hl.scala{Path} while a @hl.scala{RelPath}'s @hl.scala{/} returns a @hl.scala{RelPath}. Similarly, you can only compare or subtract paths of the same type. - - @p - Apart from @hl.scala{RelPath}s themselves, a number of other data structures are convertible into @hl.scala{RelPath}s when spliced into a path using @hl.scala{/}: - - @ul - @li - @hl.scala{String}s - @li - @hl.scala{Symbol}s - @li - @hl.scala{Array[T]}s where @hl.scala{T} is convertible into a @hl.scala{RelPath} - @li - @hl.scala{Seq[T]}s where @hl.scala{T} is convertible into a @hl.scala{RelPath} - - @sect{Constructing Paths} - @p - Apart from built-ins like @hl.scala{pwd} or @hl.scala{root} or @hl.scala{home}, you can also construct Ammonite's @hl.scala{Path}s from @hl.scala{String}s, @hl.scala{java.io.File}s or @hl.scala{java.nio.file.Path}s: - - @hl.ref(opsTests/"PathTests.scala", Seq("construction", "success", "", "")) - - @p - Trying to construct invalid paths fails with exceptions: - - @hl.ref(opsTests/"PathTests.scala", Seq("construction", "failure", "", "")) - @p - As you can see, attempting to parse a relative path with @hl.scala{Path} or an absolute path with @hl.scala{RelPath} throws an exception. If you're uncertain about what kind of path you are getting, you could use @hl.scala{BasePath} to parse it: - - @hl.ref(opsTests/"PathTests.scala", Seq("construction", "basepath", "", "")) - - @p - This converts it into a @hl.scala{BasePath}, which is either a @hl.scala{Path} or @hl.scala{RelPath}. It's then up to you to pattern-match on the types and decide what you want to do in each case. - - @p - You can also pass in a second argument to @hl.scala{Path(..., base)}. If the path being parsed is a relative path, this @hl.scala{base} will be used to coerce it into an absolute path: - - @hl.ref(opsTests/"PathTests.scala", Seq("construction", "based", "", "")) - - @p - For example, if you wanted the common behavior of converting relative paths to absolute based on your current working directory, you can pass in @hl.scala{pwd} as the second argument to @hl.scala{Path(...)}. Apart from passing in @hl.scala{String}s or @hl.scala{java.io.File}s or @hl.scala{java.nio.file.Path}s, you can also pass in @hl.scala{BasePath}s you parsed early as a convenient way of converting it to a absolute path, if it isn't already one. - - @hr - - @p - In general, Ammonite is very picky about the distinction between relative and absolute paths, and doesn't allow "automatic" conversion between them based on current-working-directory the same way many other filesystem APIs (Bash, Java, Python, ...) do. Even in cases where it's uncertain, e.g. you're taking user input as a @hl.scala{String}, you have to either handle both possibilities with @hl.scala{BasePath} or explicitly choose to convert relative paths to absolute using some base. - @p - While this adds some boilerplate, it should overall result in more robust filesystem code that doesn't contain bugs like @lnk("this one", "https://github.com/valvesoftware/steam-for-linux/issues/3671"). - - @sect{Operations} - @p - @sect.ref{Paths} are not interesting on their own, but serve as a base to use to perform filesystem operations in a concise and easy to use way. Here is a quick tour of the core capabilities that Ammonite-Ops provides: - - @hl.ref(opsExamples, Seq("""test("reference")""", "import")) - - @p - In these definitions, @hl.scala{Op1} and @hl.scala{Op2} are isomorphic to @hl.scala{Function1} and @hl.scala{Function2}. The main difference is that ops can be called in two ways: - - @hl.scala - rm(filepath) - rm! filepath - - @p - The latter syntax allows you to use it more easily from the command line, where remembering to close all your parenthesis is a hassle. Indentation signifies nesting, e.g. in addition to @hl.scala{write!} you also have @hl.scala{write.append!} and @hl.scala{write.over!} - - @sect{Operator Reference} - @p - All of these operations are pre-defined and strongly typed, so feel free to jump to their implementation to look at what they do or what else is available. Here's a shortlist of the one that may interest you: - - @ul - @val docBase = "https://ammonite.io/api/ops/index.html#ammonite.ops." - @li - @hl.scala{ls! path} @a("[doc]", href := (docBase+ "ls$")) returning @hl.scala{Vector[Path]}, and @hl.scala{ls.iter! path} returning a @hl.scala{Iterator[Path]} - @li - @hl.scala{ls.rec! path} @a("[doc]", href := (docBase+ "ls$$rec$")) and @hl.scala{ls.rec.iter!} - @li - @hl.scala{read! path} @a("[doc]", href := (docBase+ "read$")) returning a @hl.scala{String}, and @hl.scala{read.lines! path} and @hl.scala{read.bytes! path} returning @hl.scala{Seq[String]} and @hl.scala{Array[Byte]}. You can also use the various @code{read!} commands for @sect.ref{Reading Resources} or reading @hl.scala{java.io.InputStream}s - @li - @hl.scala{write(path, contents)}, @a("[doc]", href := (docBase+ "write$")), which lets you write @hl.scala{String}s, @hl.scala{Array[Byte]}s, and @hl.scala{Seq}s of those - @li - @hl.scala{rm! path}, @a("[doc]", href := (docBase+ "rm$")), roughly Bash's @code{rm -rf} - @li - @hl.scala{mv(src, dest)}, @a("[doc]", href := (docBase+ "mv$")) - @li - @hl.scala{cp(src, dest)}, @a("[doc]", href := (docBase+ "cp$")), roughly Bash's @code{cp -r} - @li - @hl.scala{exists! path}, @a("[doc]", href := (docBase+ "exists$")) - @li - @hl.scala{stat! path}, @a("[doc]", href := (docBase+ "stat$")) - @li - @hl.scala{stat.posix! path}, @a("[doc]", href := (docBase+ "stat$$posix$")) - @li - @hl.scala{ln(src, dest)}, @a("[doc]", href := (docBase+ "ln$")) - @li - @hl.scala{kill(9)! processId}, @a("[doc]", href := (docBase+ "kill")) - - @p - In general, each operator has sensible/safe defaults: - - @ul - @li - @hl.scala{rm} and @hl.scala{cp} are recursive - @li - @hl.scala{rm} ignores the file if it doesn't exist - @li - all operations that create a file or folder (@hl.scala{mkdir}, @hl.scala{write}, @hl.scala{mv}) automatically create any necessary parent directories - @li - @hl.scala{write} also does @i{not} stomp over existing files by default. You need to use @hl.scala{write.over} - @p - In general, this should make these operations much easier to use; the defaults should cover the 99% use case without needing any special flags or fiddling. - - - @sect{Extensions} - @p - Ammonite-Ops contains a set of extension methods on common types, which serve no purpose other than to make things more concise. These turn Scala from a "relatively-concise" language into one as tight as Bash scripts, while still maintaining the high level of type-safety and maintainability that comes with Scala code. - - @sect{Traversable} - @p - These extensions apply to any @hl.scala{Traversable}: @hl.scala{Seq}s, @hl.scala{List}s, @hl.scala{Array}s, and others. - @ul - @li - @hl.scala{things | f} is an alias for @hl.scala{things map f} - @li - @hl.scala{things || f} is an alias for @hl.scala{things flatMap f} - @li - @hl.scala{things |? f} is an alias for @hl.scala{things filter f} - @li - @hl.scala{things |& f} is an alias for @hl.scala{things reduce f} - @li - @hl.scala{things |! f} is an alias for @hl.scala{things foreach f} - @p - These should behave exactly the same as their implementations; their sole purpose is to make things more concise at the command-line. - - @sect{Pipeable} - @ul - @li - @hl.scala{thing |> f} is an alias for @hl.scala{f(thing)} - - @p - This lets you flip around the function and argument, and fits nicely into the Ammonite's @hl.scala{|} pipelines. - - @sect{Callable} - @ul - @li - @hl.scala{f! thing} is an alias for @hl.scala{f(thing)} - - @p - This is another syntax-saving extension, that makes it easy to call functions without having to constantly be opening and closing brackets. It does nothing else. - - @sect{Chaining} - @p - The real value of Ammonite is the fact that you can pipe things together as easily as you could in Bash. No longer do you need to write reams of boilerplate to accomplish simple tasks. Some of these chains are listed at the top of this readme, here are a few more fun examples: - - @hl.ref(opsExamples, Seq("longExample", "// Chains", "// Move all files"), "assert(") - - @p - As you can see, you can often compose elaborate operations entirely naturally using the available pipes, without needing to remember any special flags or techniques. - - @p - Here's another example: - - @hl.ref(opsExamples, Seq("noLongLines", "")) - - @sect{Reading Resources} - @p - You can also manipulate @code{resource} paths in order to read resources from the Java classpath. - By default, the path used to load resources is absolute, using the @hl.scala{Thread.currentThread().getContextClassLoader}. You can also pass in a classloader explicitly to the @hl.scala{resource} call: - - @hl.ref(opsTests/"OpTests.scala", Seq("readResource", "absolute", "")) - - @p - If you want to load resources relative to a particular class, pass in a class for the resource to be relative, or @hl.scala{getClass} to get something relative to the current class. - - @hl.ref(opsTests/"OpTests.scala", Seq("readResource", "relative", "")) - - @p - In both cases, reading resources is performed as if you did not pass a leading slash into the @hl.scala{getResource("foo/bar")} call. In the case of @hl.scala{ClassLoader#getResource}, passing in a leading slash is never valid, and in the case of @hl.scala{Class#getResource}, passing in a leading slash is equivalent to calling @hl.scala{getResource} on the @hl.scala{ClassLoader}. - @p - Ammonite-Ops ensures you only use the two valid cases in the API, without a leading slash, and not the two cases with a leading slash which are redundant (in the case of @hl.scala{Class#getResource}, which can be replaced by @hl.scala{ClassLoader#getResource}) or invalid (a leading slash with @hl.scala{ClassLoader#getResource}) - @p - Note that you can only @hl.scala{read!} from paths; you can't write to them or perform any other filesystem operations on them, since they're not really files. - @p - Note also that resources belong to classloaders, and you may have multiple classloaders in your application e.g. if you are running in a servlet or REPL. Make sure you use the correct classloader (or a class belonging to the correct classloader) to load the resources you want, or else it might not find them. - - @sect{Spawning Subprocesses} - @p - Ammonite-Ops provides easy syntax for anyone who wants to spawn sub-processes, e.g. commands like @code{ls} or @code{git commit -am "wip"}. This is provided through the @hl.scala{%} and @hl.scala{%%} operators, which are used as follows: - - @hl.scala - @@ import ammonite.ops._ - @@ import ammonite.ops.ImplicitWd._ - @@ %ls - build.sbt log ops readme repl terminal - echo modules project readme.md target shell - res2: Int = 0 - @@ %%('ls) - res3: CommandResult = - build.sbt - echo - log - modules - ops - project - readme - readme.md - repl - target - terminal - ... - - @p - In short, @hl.scala{%} lets you run a command as you would in bash, and dumps the output to standard-out in a similar way, returning the zero return-code upon successful command completion. This lets you run @code{git} commands, edit files via @code{vim}, open @code{ssh} sessions or even start @code{SBT} or @code{Python} shells right from your Scala REPL! - @p - @hl.scala{%} throws an @hl.scala{InteractiveShelloutException} if the return-code is non-zero. - @p - @hl.scala{%%} on the other hand is intended for programmatic usage: rather than printing to stdout, it returns a @hl.scala{CommandResult}, which contains the standard output @hl.scala{.out} and standard error @hl.scala{.err} of the subprocess. These provide helper methods to retrieve the stdout or stderr as a list of lines - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("lines")""", "")) - - @p - Or as a single string: - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("string")""", "")) - - @p - Or as an array of bytes: - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("bytes")""", "")) - - @p - @hl.scala{%%} throws an @hl.scala{ShelloutException} containing the @hl.scala{CommandResult} if the return-code is non-zero. - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("failures")""", "")) - - @p - In both cases, you end up with a @hl.scala{CommandResult} can then be used however you like. - @p - You can also use backticks to execute commands which aren't valid Scala identifiers, e.g. - - @hl.scala - @@ %`ssh-add` - Enter passphrase for /Users/haoyi/.ssh/id_rsa: - - @p - Lastly, you can also pass arguments into these subprocess calls, as Strings, Symbols or Seqs of Strings: - @hl.scala - @@ %git 'branch - gh-pages - history - * master - speedip - res4: Int = 0 - - @@ %%('git, 'branch) - res5: CommandResult = - gh-pages - history - * master - speedip - - @@ %%('git, 'checkout, "master") - Already on 'master' - res6: CommandResult = - M readme/Index.scalatex - Your branch is up-to-date with 'origin/master'. - - @@ %git("checkout", 'master) - M readme/Index.scalatex - Already on 'master' - Your branch is up-to-date with 'origin/master'. - res8: Int = 0 - - @@ val stuff = List("readme.md", "build.sbt") - stuff: List[String] = List("readme.md", "build.sbt") - @@ %('ls, '".gitignore", stuff) - .gitignore build.sbt readme.md - @p - Ammonite-Ops currently does not provide many convenient ways of piping together multiple processes, but support may come in future if someone finds it useful enough to implement. - - @p - @hl.scala{%} calls subprocesses in a way that is compatible with a normal terminal. That means you can easily call things like @hl.scala{%vim} to open a text editor, @hl.scala{%python} to open up a Python terminal, or @hl.scala{%sbt} to open up the SBT prompt! - @hl.scala - @@ %python - Python 2.7.6 (default, Sep 9 2014, 15:04:36) - [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin - Type "help", "copyright", "credits" or "license" for more information. - >>> print "Hello %s%s" % ("World", "!"*3) - Hello World!!! - >>> ^D - res3: Int = 0 - - @@ %sbt - [info] Loading global plugins from /Users/haoyi/.sbt/0.13/plugins - [info] Updating {file:/Users/haoyi/.sbt/0.13/plugins/}global-plugins... - [info] Resolving org.fusesource.jansi#jansi;1.4 ... - [info] Done updating. - [info] Set current project to haoyi (in build file:/Users/haoyi/) - > - - @p - @hl.scala{%%} does not do this. - - @sect{Environment Variables} - @p - Ammonite lets you pass in environment variables to subprocess calls; just pass them in as named arguments when you invoke the subprocess ia @code{%} or @code{%%}: - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("envArgs")""", "val")) - - @sect{Invoking Files} - @p - You can invoke files on disk using @code{%} and @code{%%} the same way you can invoke shell commands: - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("filebased")""", "val")) - - @sect{Current Working Directory} - @p - In Ammonite the current working directory is not a side-effect unlike in bash. Instead it is an argument to the command you are invoking. It can be passed in explicitly or implicitly. - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("customWorkingDir")""", "")) - - @p - Note how passing it in explicitly, you need to use a @code{.} before the command-name in order for it to parse properly. That's a limitation of the Scala syntax that isn't likely to change. Another limitation is that when invoking a file, you need to call @code{.apply} explicitly rather than relying on the plain-function-call syntax: - - @hl.ref(opsTests/"ShelloutTests.scala", Seq("""test("fileCustomWorkingDir")""", "")) diff --git a/readme/Repl.scalatex b/readme/Repl.scalatex index 53904df5e..b82cbc449 100644 --- a/readme/Repl.scalatex +++ b/readme/Repl.scalatex @@ -27,8 +27,7 @@ "https://ipython.org/") or @lnk("Zsh", "http://www.zsh.org/"). @p - It can be combined with @sect.ref{Ammonite-Ops} to replace Bash as your - systems shell, but can also be used alone as a @sect.ref("Features", + Ammonite-REPL is a @sect.ref("Features", "superior") version of the default Scala REPL, as a @sect.ref("Debugging", "debugging tool"), and for many other @sect.ref("Ammonite Cookbook", "fun and interesting things")! @@ -88,7 +87,7 @@ @p If you want to use Ammonite as a filesystem - shell, take a look at @sect.ref{Ammonite-Shell}. If you're not sure what + shell, take a look at Ammonite-Shell. If you're not sure what to do with Ammonite, check out the @sect.ref{Ammonite Cookbook} for some fun ideas! @@ -596,7 +595,7 @@ @sect.ref{desugar} @p - The REPL also imports the @sect.ref("Extensions", "pipe-operators") + The REPL also imports the pipe-operators from Ammonite-Ops by default to make it easy for you to use tools like @sect.ref{grep} interactively, and imports all the @sect.ref{Builtins} from the @hl.scala{repl}. @@ -952,13 +951,12 @@ @hl.scala{prompt} to always include your current working directory @hl.scala - import ammonite.ops._ - repl.prompt.bind(pwd.toString + " @@ ") + repl.prompt.bind(os.pwd.toString + " @@ ") @p As is common practice in other shells. Further modifications to make it include e.g. your current branch in Git (which you can call through - Ammonite's @sect.ref("Spawning Subprocesses", "subprocess API") or the + Ammonite's subprocess API or the current timestamp/user are similarly possible. @sect{Compiler Flags} diff --git a/readme/Sample.scala b/readme/Sample.scala index 0cb609646..11c5b56c7 100644 --- a/readme/Sample.scala +++ b/readme/Sample.scala @@ -2,7 +2,6 @@ package readme import java.io.{BufferedReader, ByteArrayOutputStream, InputStreamReader} -import ammonite.ops._ import scalatags.Text.all._ object Sample{ @@ -23,11 +22,11 @@ object Sample{ val cacheVersion = 6 def cached(key: Any)(calc: => String) = { - val path = cwd/'target/'cache/(key.hashCode + cacheVersion).toString - try read! path + val path = os.pwd/'target/'cache/(key.hashCode + cacheVersion).toString + try os.read(path) catch { case e : Throwable => val newValue = calc - write.over(path, newValue) + os.write.over(path, newValue, createFolders=true) newValue } } @@ -35,14 +34,12 @@ object Sample{ def ammSample(ammoniteCode: String) = { val scalaVersion = scala.util.Properties.versionNumberString val ammVersion = ammonite.Constants.version - val predef = "shell/src/main/resources/ammonite/shell/example-predef-bare.sc" val out = exec( Seq( sys.env.getOrElse("AMMONITE_ASSEMBLY", "amm"), "--color", "true", "--no-remote-logging", "--no-home-predef", - "--predef", predef ), s"${ammoniteCode.trim}\nexit\n", args = Map("JAVA_OPTS" -> "-Xmx600m") diff --git a/readme/Scripts.scalatex b/readme/Scripts.scalatex index 96b476320..a6dc9f3be 100644 --- a/readme/Scripts.scalatex +++ b/readme/Scripts.scalatex @@ -278,7 +278,7 @@ @hl.scala{mainargs.Parser*} parsers, which provides parsers for primitives like @code{Int}, @code{Double}, @code{String}, as well as basic data-structures like @code{Seq}s (taken as a comma-separated list) and - common types like @sect.ref{Paths}. + common types like Paths. @p If you pass in the wrong number of arguments, or if an argument fails @@ -716,7 +716,7 @@ @p Furthermore, Ammonite makes it really easy to include that sort of recursive/iterative logic inside a single script: you can use - @hl.scala{ls!} or @hl.scala{ls.rec!} from @sect.ref{Ammonite-Ops} to + @hl.scala{ls!} or @hl.scala{ls.rec!} from Ammonite-Ops to traverse the filesystem and work on multiple files all within the same process, which avoids duplicating the startup overhead on all the files you are manipulating. diff --git a/readme/Shell.scalatex b/readme/Shell.scalatex deleted file mode 100644 index a623b02aa..000000000 --- a/readme/Shell.scalatex +++ /dev/null @@ -1,448 +0,0 @@ -@import Main._ -@import readme.Sample._ -@sect("Ammonite-Shell", "Replacing Bash for the 21st Century") - @p - The @b{Ammonite-Shell} is a rock-solid system shell that can replace Bash as the interface to your operating system, @sect.ref("Scala as the Language", "using Scala") as the primary command and scripting language, @sect.ref("Running on the JVM", "running on the JVM"). Apart from @sect.ref("Shell Basics", "system operations"), Ammonite-Shell provides the full-range of @sect.ref("Scala/Java APIs", "Java APIs") for usage at the command-line, including loading libraries from Maven Central. - - @p - Why would you want to use Ammonite-Shell instead of Bash? Possible reasons include: - - @ul - @li - You can @lnk("never remember the syntax to write an if-statement in Bash", "http://stackoverflow.com/questions/19343390/bash-if-statement-with-white-space-in-variable") - @li - You are sick of googling the same set of inconsistent, ad-hoc commands over and over: "@lnk("obviously", "http://stackoverflow.com/questions/12522269/bash-how-to-find-the-largest-file-in-a-directory-and-its-subdirectories") you need the flag @code{-nrk 7} to sort by file size!" - @li - You've seen Bash's dynamic/sloppy nature @lnk("fail hard", "https://github.com/valvesoftware/steam-for-linux/issues/3671"), and don't want your future work to fall victim to the same bugs - @li - You think that technology has improved in the last @lnk("38 years", "https://en.wikipedia.org/wiki/Bourne_shell") and a modern systems shell should be better than the shells of our forefathers - @p - If none of these apply to you, then likely you won't be interested. If any of these bullet points strikes a chord, then read on to get started. For more discussion about why this project exists, take a look at the presentation slides for @lnk("Beyond Bash: shell scripting in a typed, OO language", "https://tinyurl.com/beyondbash"), presented at Scala by the Bay 2015, or check out the section on @sect.ref{Design Decisions & Tradeoffs}. - - - @p - To begin using Ammonite-Shell, simply download the default @code{predef.sc} to configure your REPL to be a usable systems shell before downloading the @sect.ref{Ammonite-REPL} executable (below): - - @hl.sh - @filesystemCurl - @hl.sh - @replCurl - - @p - If you're on @lnk("FreeBSD", "https://www.freebsd.org/") then you can simply install the package by @code{pkg install ammonite} or compile it from the @code{shells/ammonite} ports directory. Be sure to setup your @code{predef.sc} correctly though. - - @p - You can then start using Ammonite as a replacement for Bash: - - @img(src:="SystemShell.png", width:="100%", loadingLazy) - - @sect{Shell Basics} - @p - Ammonite-Shell isn't backwards compatible with Bash. It isn't even the same language, giving you access to all of Scala instead of the quirky Bash scripting language. Nevertheless, lots of things you'd expect in Bash turn up in Ammonite-Shell: - - @sect{Working Directory} - @compare("pwd", "wd") - - @p - Bash's @hl.sh{pwd} is instead called @hl.scala{wd}. Instead of being a subprocess that prints to stdout, @hl.scala{wd} is simply a variable holding the working directory. - @p - As you can see, the path syntax is also different: as an absolute path, @hl.scala{wd} must start from @hl.scala{root} and the path segments must be quoted as Scala @hl.scala{"string"}s or @hl.scala{'symbol}s. Apart from that, however, it is basically the same. The @sect.ref("Paths", "documentation about Paths") goes over the syntax and semantics of @sect.ref{Paths} in more detail. - - @p - You can navigate around the filesystem using @hl.scala{cd!}, instead of Bash's @hl.sh{cd}: - - @compare( - """ - |pwd - |cd target - |pwd - |cd .. - |pwd""".stripMargin, - """ - |wd - |cd! 'target - |wd - |cd! up - |wd - """.stripMargin - ) - - @sect{Listing Files} - @compare("ls", "ls!") - - @p - Bash's @hl.sh{ls} syntax is tweaked slightly to become @hl.scala{ls!}. Apart from that, it basically does the same thing. - - @p - Listing files in other folders behaves similarly: - - @compare("ls project", "ls! 'project") - @compare("ls project/target", "ls! 'project/'target") - - @p - Again, we have to use the quoted @hl.scala{'symbol}/@hl.scala{"string"} syntax when defining @sect.ref{Paths}, but otherwise it behaves identically. You can press @code{} at any point after a @code{/} or halfway through a file-name to auto-complete it, just like in Bash. - @p - Listing recursively is done via @hl.scala{ls.rec}, instead of @code{find}: - - @compare("find ops/src/main", "ls.rec! 'ops/'src/'main") - - @p - @hl.scala{ls}, @hl.scala{ls.rec} and other commands are all functions defined by @sect.ref{Ammonite-Ops}. - - @sect{Filesystem Operations} - @p - Ammonite-Shell uses @sect.ref{Ammonite-Ops} to provide a nice API to use filesystem operations. The default setup will @hl.scala{import ammonite.ops._} into your @sect.ref{Ammonite-REPL}, gives the nice path-completion shown above, and also provides some additional command-line-friendly functionality on top of the default @sect.ref{Ammonite-Ops} commands: - - @compare( - """mkdir target/test - |echo "hello" > target/test/hello.txt - |cat target/test/hello.txt - |ls target/test - |cp target/test/hello.txt target/test/hello2.txt - |ls target/test - |mv target/test/hello.txt target/test/hello3.txt - |ls target/test - |rm -rf target/test""".stripMargin, - """mkdir! 'target/'test - |write('target/'test/"hello.txt", "hello") - |read('target/'test/"hello.txt") - |ls! 'target/'test - |cp('target/'test/"hello.txt", 'target/'test/"hello2.txt") - |ls! 'target/'test - |mv('target/'test/"hello.txt", 'target/'test/"hello3.txt") - |ls! 'target/'test - |rm! 'target/'test""".stripMargin - ) - @sect{Piping} - @p - Ammonite allows piping similar to how Bash does it. Unlike Bash, Ammonite has a variety of @sect.ref("Extensions", "pipes") you can use that do different things: - @ul - @li - @hl.scala{things | f} is an alias for @hl.scala{things map f} - @li - @hl.scala{things || f} is an alias for @hl.scala{things flatMap f} - @li - @hl.scala{things |? f} is an alias for @hl.scala{things filter f} - @li - @hl.scala{things |& f} is an alias for @hl.scala{things reduce f} - @li - @hl.scala{things |! f} is an alias for @hl.scala{things foreach f} - - @p - For example, this is how you can get the dot-files in the current directory: - - @compare("""ls -a | grep "^\." """, "ls! pwd |? (_.last(0) == '.')") - - @p - Here, we're using the @hl.scala{|?} pipe, which basically performs a filter on the paths coming in on the left. In this case, we're checking that for each path, the first character of the last segment of that path is the character @hl.scala{'.'}. This is slightly more verbose than Bash the bash equivalent shown above, but not by too much. - @p - Here is how to find the largest 3 files in a given directory tree: - - @compare( - "find ./amm/src -ls | sort -nrk 7 | head -3", - "ls.rec! wd/'amm/'src | (x => x.size -> x.last) sortBy (-_._1) take 3" - ) - - @p - And lastly, here is how to perform a recursive line count of all the Scala files in your current directory tree: - - @compare( - "find ./ops/src/main -name '*.scala' | xargs wc -l", - """ls.rec! wd/'ops/'src/'main |? (_.ext == "scala") | read.lines | (_.size) sum""" - ) - - @p - For more examples of how to use Ammonite's pipes, check out the section on @sect.ref{Extensions} and @sect.ref{Chaining} - @sect{Subprocesses} - @p - Ammonite provides a convenient way to spawn subprocesses using the @hl.scala{%} and @hl.scala{%%} commands: - - @ul - @li - @hl.scala{%cmd(arg1, arg2)}: Spawn a subprocess with the command @hl.scala{cmd} and command-line arguments @hl.scala{arg1}, @hl.scala{arg2}. print out any stdout or stderr, take any input from the current console, and return the exit code when all is done. - @li - @hl.scala{%%cmd(arg1, arg2)}: Spawn a subprocess similar to using @hl.scala{%}, but return the stdout of the subprocess as a String, and throw an exception if the exit code is non-zero. - - @p - For example, this is how you use the @code{bash} command to run a standalone bash script in Bash and Ammonite: - - @compare( - "bash ops/src/test/resources/scripts/echo HELLO", - """%bash('ops/'src/'test/'resources/'scripts/'echo, "HELLO")""" - ) - @p - Note that apart from quoting each path segment as a @hl.scala{'symbol}, we also need to quote @hl.scala{"HELLO"} as a string. That makes things slightly more verbose than a traditional shell, but also makes it much clearer when arguments are literals v.s. variables. - - @p - If you are only passing a single argument, or no arguments, Scala allows you to leave off parentheses, as shown: - - @compare( - "git branch", - "%git 'branch" - ) - @compare( - "date", - "%date" - ) - - @p - You can use Ammonite-Ops' support for @sect.ref{Spawning Subprocesses} to call any external programs, even interactive ones like Python or SBT! - - @hl.scala - @@ %python - Python 2.7.6 (default, Sep 9 2014, 15:04:36) - [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin - Type "help", "copyright", "credits" or "license" for more information. - >>> print "Hello %s%s" % ("World", "!"*3) - Hello World!!! - >>> ^D - res3: Int = 0 - - @@ %sbt - [info] Loading global plugins from /Users/haoyi/.sbt/0.13/plugins - [info] Updating {file:/Users/haoyi/.sbt/0.13/plugins/}global-plugins... - [info] Resolving org.fusesource.jansi#jansi;1.4 ... - [info] Done updating. - [info] Set current project to haoyi (in build file:/Users/haoyi/) - > - - @sect{Scripting} - @p - Ammonite-Shell uses @lnk("Scala", "https://www.scala-lang.org/") as its command and scripting language. Although the commands seem short and concise, you have the full power of the language available at any time. This lets you do things that are difficult or infeasible to do when using a traditional shell like Bash. - - @sect{Scala Scripting} - @p - Since Ammonite-Shell runs Scala code, you can perform math: - - @pre(ammSample( - """(1 + 2) * 3 - |math.pow(4, 4) - """.stripMargin - )) - @p - Assign things to values (@hl.scala{val}s): - - @pre(ammSample( - """val x = (1 + 2) * 3 - |x + x - """.stripMargin - )) - - @p - Define re-usable functions: - - @pre(ammSample( - """def addMul(x: Int) = (x + 2) * 3 - |addMul(5) - |addMul(5) + 1 - |addMul(5 + 1) - """.stripMargin - )) - - @p - Or make use of mutable @hl.scala{var}s, conditionals or loops: - @pre(ammSample( - """var total = 0 - |for(i <- 0 until 100){ if (i % 2 == 0) total += 1 } - |total - """.stripMargin - )) - @sect{Typed Values} - @p - In Ammonite-Shell, everything is a typed value and not just a stream of bytes as is the case in Bash. That means you can assign them to variables and call methods on them just like you can in any programming language: - - @pre(ammSample( - """val files = ls! wd - |val count = files.length - """.stripMargin - )) - @p - As is the case in Scala, you can annotate types. - - @pre(ammSample( - """val files: LsSeq = ls! wd - |val count: Int = files.length - """.stripMargin - )) - - @p - This is often not required (e.g. in the earlier example), since Scala has type inference, but it may make your code clearer. Furthermore, if you make a mistake, having types annotated will help the compiler give a more specific error message. - - @p - The fact that variables are typed means if you try to perform the wrong operation on a variable, you get an error even before the code runs: - - @pre(ammSample( - """val files = ls! wd - |ls + 123 - """.stripMargin - )) - - @p - The fact that Ammonite-Shell uses typed, structured values instead of byte streams makes a lot of things easier. For example, all the common data structures like Arrays and Maps are present: - - @pre(ammSample( - """val numbers = Array(1, 3, 6, 10) - |numbers(0) - |numbers(3) - |numbers.sum - |numbers(3) = 100 - |numbers.sum - |val scores = Map("txt" -> 5, "scala" -> 0) - |scores("txt") - """.stripMargin - )) - @p - Naturally, these data structures are typed too! Trying to put the wrong sort of value inside of them results in compilation errors before the code gets a chance to run: - @pre(ammSample( - """val numbers = Array(1, 3, 6, 10) - |val myValue = "3" - |numbers(myValue) // Doesn't work - |numbers(1) = myValue // Also doesn't work - | // Need to convert the string to an Int - |numbers(myValue.toInt) - |numbers(1) = myValue.toInt - |numbers(1) = "2".toInt - """.stripMargin - )) - @p - In general, apart from the filesystem-specific commands, you should be able to do anything you would expect to be able to do in a Scala shell or Java project. This documentation isn't intended to be a full tutorial on the Scala language, check out the @lnk("Scala Documentation", "https://www.scala-lang.org/documentation/") if you want to learn more! - @sect{Scala/Java APIs} - @p - Apart from the pipe operators described in the earlier section on @sect.ref{Piping}, Ammonite-Shell allows you to call any valid Scala method on any value; it's just Scala after all! Here's an example using normal Scala collection operations to deal with a list of files, counting how many files exist for each extension: - - @pre(ammSample( - """val allFiles = ls.rec! 'ops/'src/'test/'resources - |val extensionCounts = allFiles.groupBy(_.ext).mapValues(_.length) - """.stripMargin - )) - - @p - Any Java APIs are likewise available: - - @pre(ammSample( - """System.out.println("Hello from Java!") - |import java.util._ - |val date = new Date() - |date.getDay() - """.stripMargin - )) - - @p - In fact, Ammonite-Shell allows you to ask for any published third-party Java/Scala library for usage in the shell, and have them downloaded, automatically cached, and made available for use. e.g. we can load popular libraries like @lnk("Google Guava", "https://github.com/google/guava") and using it in the shell: - - @pre(ammSample( - """import com.google.common.collect.ImmutableBiMap // Doesn't work - |import $ivy.`com.google.guava:guava:18.0` // Load from Maven Central - |import com.google.common.collect.ImmutableBiMap // Works now - |val bimap = ImmutableBiMap.of(1, "one", 2, "two", 3, "three") - |bimap.get(1) - |bimap.inverse.get("two") - """.stripMargin - )) - - @p - Or Joda Time: - - @pre(ammSample( - """import $ivy.`joda-time:joda-time:2.8.2` - |import org.joda.time.{DateTime, Period, Duration} - |val dt = new DateTime(2005, 3, 26, 12, 0, 0, 0) - |val plusPeriod = dt.plus(Period.days(1)) - |dt.plus(new Duration(24L*60L*60L*1000L)) - """.stripMargin - )) - - @p - See the section on @sect.ref{import $ivy} to learn more. - - @sect{Writing/Loading Scripts} - @p - You can write scripts in the same way you write commands, and load them using @hl.scala("import $file"). To read more about this, check out the documentation on @sect.ref{Script Files}. - - - @sect{Design Decisions & Tradeoffs} - @p - Ammonite-Shell takes a fundamentally different architecture from traditional shells, or even more-modern shell-alternatives. Significant differences include: - @ul - @li - The command & scripting language is a standard, well-known application language (@lnk("Scala", "https://www.scala-lang.org/")) rather than one specially-designed for the shell - @li - The shell runs on the @lnk("JVM", "https://en.wikipedia.org/wiki/Java_virtual_machine"), and can execute or integrate-with arbitrary Java/JVM code or libraries. - @p - In this section we'll examine each of these decisions and their consequences in turn. As the incumbents in this space, we'll be looking at traditional system shells like @lnk("Bash", "https://en.wikipedia.org/wiki/Bash_(Unix_shell)"), @lnk("Zsh", "https://en.wikipedia.org/wiki/Z_shell") or @lnk("Fish", "http://fishshell.com/"), as well as popular non-system REPLs like the Python/@lnk("IPython", "https://ipython.org/") REPL. - - @sect{Scala as the Language} - @p - The use of Scala as the command & scripting language is unusual among shells, for many reasons. Firstly, most shells implement their own, purpose built language: Bash, Zsh, Fish, and even more obscure ones like Xonsh each implement their own language. Secondly, all of these languages are extremely dynamic, and apart from those most popular languages with REPLs (Python, Ruby, Javascript, ...) tend to be dynamical, interpreted languages. Scala falls at the opposite end of the spectrum: statically typed and compiled. - @p - Scala brings many changes over using traditional dynamic, interpreted REPL languages: - @ul - @li - The code being entered in the shell takes time to compile. The first command easily takes 3-4 to compile, and even when the compiler is "warm" there is a 0.2-0.3 second delay before any command begins executing. - @li - Once compiled, the code runs extremely fast: compute-intensive code easily runs @lnk("50-100x faster", "http://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=scala&lang2=python3") in Scala vs Python or Bash, once you've paid the cost of compiling it. - @li - Many mistakes get caught even before a command begins executing. This is less valuable for small commands that execute quickly, but for slower commands processing more data, it is nice to get an error after 0.2s of compilation rather than 10s into execution. - @p - Apart from the differences between Scala and dynamic languages (Python, Ruby, etc.) for REPL usage, Scala is even further away from the sort of ad-hoc, ultra-dynamic languages most often associated with traditional shells (Bash, sh, zsh, etc.). In particular: - @ul - @li - Scala provides a set of proper data structures for you to work with. Rather than just byte-streams, you have unicode @code{String}s, proper numbers like @code{Int} or @code{Double}, absolute @code{Path}s and relative @code{RelPath}s, @code{Array}s, @code{Map}s, @code{Iterator}s and all sorts of other handy data-structures. Many commands return objects which have fields: this sounds simple until you realize that none of bash/zsh/fish behave this way. - @li - Scala is a general-purpose language: you can do math, you can work with Strings, you can write non-trivial algorithms quickly and easily. While this is not surprising coming from a Python REPL, these simple tasks are difficult-to-impossible in traditional system shells like Bash. - @li - Scala runs most code in the same process. While you can @sect.ref("Spawning Subprocesses", "shell-out to subprocesses in Ammonite") using the @code{%} syntax, most commands like @code{ls!} and @code{rm!} are simple functions living in-process rather than in separate processes. This reduces the overhead as compared to spawning new processes each time, but does cause some additional risk: if a command causes the process to crash hard, the entire shell fails. In bash, only that command would fail. - @p - The latter set of tradeoffs would be also present in many of the shell-replacements written in dynamic languages, like @lnk("Xonsh", "http://xonsh.org/") which is written in Python. The earlier set, on the other hand, are pretty unique to Ammonite using Scala. There are both positive and negative points in this list. - - @sect{Running on the JVM} - @p - Running Ammonite directly on the @lnk("JVM", "https://en.wikipedia.org/wiki/Java_virtual_machine") again is very different from how most shells work: most have their own scripting language, and their own interpreter. Most are implemented in C. What is it like running your code directly as bytecode on the JVM? Here are some of the negatives: - - @ul - @li - You get JVM boot time; although some of the initial several-second delay is due to the Scala compiler's slowness, some of it is also due to the cost of JVM classloading and initialization. While a hello-world JVM project loads instantly, one which uses a large number of class-files takes longer. In contrast, shells written in C load basically instantly. - @li - You get the JVM bloat: Ammonite, implemented in only a few thousand lines of code, wraps up to become a 30mb @code{.jar} file. That's already larger than most other shells out there, and gets >100mb larger if you bundle the JVM along with it! In general, the JVM class-file format is bloated and inefficient, and there is no way to exclude to numerous un-needed parts of the JVM during the initial download. @lnk("Project Jigsaw", "http://openjdk.java.net/projects/jigsaw/") will help with this when it finally lands in @lnk("Java 9", "http://openjdk.java.net/projects/jdk9/"). - @li - Ammonite uses hundreds of megabytes (~500mb at last count) of memory, again orders-of-magnitude more than an interpreter written in C or Python. The JVM has traditionally been a very pointer-heavy, memory-intensive platform for running code, and it shows. @lnk("Project Valhalla", "http://openjdk.java.net/projects/valhalla/") would help with this, also scheduled to land in Java 9. - - @p - In general, the JVM has traditionally been used as a server-side platform for long-running services, and its slow-startup and bloated disk/memory footprints are a symptom of that. Running on the JVM also has some upsides, though: - - @ul - @li - Ammonite code runs ridiculously fast, once you've paid 0.2-0.3s for its compilation. 50x faster than Python or Bash! This is not a trivial multiplier, and really makes a different if you're dealing with non-trivial data sets: a computation that takes a minute in Ammonite might take an hour if done at the Python REPL! This means that a moderately-large computation which may require special tools, libraries or optimizations to perform in Python might be trivial to implement naively in Ammonite while still enjoying reasonable speed. - @li - Ammonite can make use of any JVM APIs, and there are a lot of them! The JVM is a general-purpose platform for a general purpose language (Java), and thus has APIs for doing all sorts of things: dealing with @lnk("dates and times", "https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html"), @lnk("math", "https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html"), @lnk("networks", "https://docs.oracle.com/javase/8/docs/api/java/net/package-summary.html"), @lnk("threads", "https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html"), and many other things. While you may not always need all of these capabilities, it is nice to have them at your disposal where necessary. - @li - Ammonite can make use of any Java/JVM libraries, and the excellent infrastructure used by Java developers to download and manage them! Any library is just a @sect.ref{import $ivy} away. Need to parse Python source into an AST? Load @lnk("Jython", "https://search.maven.org/#search%7Cga%7C1%7Ca%3A%22jython-standalone%22") and just do it. Need a high-performance web server? Load @lnk("Akka-HTTP", "http://doc.akka.io/docs/akka-stream-and-http-experimental/1.0-M2/scala/http/"). Need some data someone stored in a YAML file? Load @lnk("SnakeYAML", "https://bitbucket.org/asomov/snakeyaml") and parse it. You don't even need to download and manage these libraries yourself: just @hl.scala{import $ivy} them from Ammonite, and Java's excellent dependency-management infrastructure will download them (along with any transitive dependencies!), cache them locally, and make them available to your code. - - @p - There are both pros and cons with running Ammonite on the JVM: we gain its heavy startup/memory overhead, but also get access to its high-performance @lnk("JIT", "https://en.wikipedia.org/wiki/Just-in-time_compilation"), massive ecosystem of available packages. - - @sect{Goals of Ammonite-Shell} - @p - Overall, Ammonite-Shell blurs the line between a "programming language REPL" like IPython or Ruby's IRB and a "system shell" like Bash or Zsh. Like system shells, Ammonite-Shell provides concise filesystem operations, path-completion, and easy spawning of subprocesses. Like programming language REPLs, it provides a full-fledged, general-purpose language for you to use, rather than a crippled cut-down command-language that is available in most system shells. - - @p - The goal is to provide something general enough to use as both a system shell and a general-purpose programming language. Traditionally, there has always been some tension when deciding between these: - - @ul - @li - Should I write this script in Bash? I'm already using Bash as my shell - @li - It's getting complicated, I can't follow the logic in Bash. Should I re-write it in Python? - @li - If I write it in Python, I'll need to deal with argument-parsing and forwarding between Bash and Python and Bash, which is annoying - @li - Since my scripts are in Python, should I use Python/IPython as my shell instead of Bash when dealing with these things? - @li - But if I use Python/IPython as my shell, basic filesystem operations becomes impossible - - @p - Traditionally, there really has been no good answer to this dilemma: whether you use Bash or Python to write your scripts, whether you use Bash or Python as your shell, there is always something frustrating about the set-up. - - @p - With Ammonite-Shell, there is no dilemma. You can use the same concise, general-purpose language for your shell as you would for your scripts, large or small. In Ammonite-Shell, you can concisely deal with files at the command-line with the same language you use to write maintainable scripts, large or small, and the same language that you use to write rock-solid application code. - diff --git a/shell/src/main/resources/ammonite/shell/empty-predef.sc b/shell/src/main/resources/ammonite/shell/empty-predef.sc deleted file mode 100644 index c94f20ecc..000000000 --- a/shell/src/main/resources/ammonite/shell/empty-predef.sc +++ /dev/null @@ -1 +0,0 @@ -//this file is intentionally empty. diff --git a/shell/src/main/resources/ammonite/shell/example-predef-bare.sc b/shell/src/main/resources/ammonite/shell/example-predef-bare.sc deleted file mode 100644 index f7b10a47f..000000000 --- a/shell/src/main/resources/ammonite/shell/example-predef-bare.sc +++ /dev/null @@ -1,16 +0,0 @@ -// Basically a copy of example-predef.sc that's used for the test suite -import ammonite.ops._ -import ammonite.interp.api.IvyConstructor.scalaBinaryVersion -val scalaVersion = scala.util.Properties.versionNumberString -val ammVersion = ammonite.Constants.version -interp.load.cp(Path(sys.env("AMMONITE_SHELL"), pwd)) -@ -val shellSession = ammonite.shell.ShellSession() -import shellSession._ -import ammonite.ops._ -import ammonite.shell._ - -// Doesn't work in the test suite, where it's run as a script rather than -// as a REPL - -// ammonite.shell.Configure(interp, repl, wd) diff --git a/shell/src/main/resources/ammonite/shell/example-predef.sc b/shell/src/main/resources/ammonite/shell/example-predef.sc deleted file mode 100644 index c27778360..000000000 --- a/shell/src/main/resources/ammonite/shell/example-predef.sc +++ /dev/null @@ -1,11 +0,0 @@ -interp.load.ivy( - "com.lihaoyi" % - s"ammonite-shell_${scala.util.Properties.versionNumberString}" % - ammonite.Constants.version -) -@ -val shellSession = ammonite.shell.ShellSession() -import shellSession._ -import ammonite.ops._ -import ammonite.shell._ -ammonite.shell.Configure(interp, repl, wd) diff --git a/shell/src/main/scala/ammonite/shell/Configure.scala b/shell/src/main/scala/ammonite/shell/Configure.scala deleted file mode 100644 index 446d26625..000000000 --- a/shell/src/main/scala/ammonite/shell/Configure.scala +++ /dev/null @@ -1,29 +0,0 @@ -package ammonite.shell - -import ammonite.interp.api.InterpAPI -import ammonite.repl.api.ReplAPI - -/** - * Created by haoyi on 9/1/15. - */ -object Configure { - - def apply(interp: InterpAPI, repl: ReplAPI, wd: => ammonite.ops.Path) = { - if (scala.util.Properties.isWin) { - repl.frontEnd() = new ammonite.repl.FrontEnds.JLineWindows(ammonite.compiler.Parsers) - interp.colors() = ammonite.util.Colors.BlackWhite - } else { - repl.frontEnd() = ammonite.repl.AmmoniteFrontEnd( - ammonite.compiler.Parsers, - ammonite.shell.PathComplete.pathCompleteFilter(wd, interp.colors()) - ) - } - - repl.prompt.bind( - sys.props("user.name") + - "-" + - wd.segments.toSeq.lastOption.getOrElse("") + - "@ " - ) - } -} diff --git a/shell/src/main/scala/ammonite/shell/PathComplete.scala b/shell/src/main/scala/ammonite/shell/PathComplete.scala deleted file mode 100644 index c87d21ac4..000000000 --- a/shell/src/main/scala/ammonite/shell/PathComplete.scala +++ /dev/null @@ -1,232 +0,0 @@ -package ammonite.shell - -import java.io.OutputStreamWriter - -import ammonite.terminal._ -import Filter._ -import ammonite.repl.FrontEndUtils -import ammonite.util.Colors -import ammonite.compiler.Parsers -import ammonite.terminal._ -import ammonite.terminal.LazyList.~: -/** - * Logic to find path "literals" so we can attempt to autocomplete them based - * on what's actually on the filesystem. - */ -object PathComplete { - /** - * @param base An identifier representing the absolute path which this - * path literal is based on. If `None`, then we've only - * found a relative path - * @param body The various path segments, as `String`s. `None` means it - * is an `up` instead of a literal path segment - * @param frag The last, potentially incomplete section of the path - * just before the cursor - * @param offset - */ - case class PathLiteralInfo(base: Option[String], - body: Seq[Option[String]], - frag: Option[String], - offset: Int) - /** - * Searches the current snippet for path-like forms ending at the - * current cursor. Doesn't actually go to the filesystem to see what - * exists, just returns a [[PathLiteralInfo]] representing the - * structure of the path-like-syntax it found - * - * The overall approach is somewhat convoluted, but roughly: - * - * - Use `highlightIndices` to search for all syntactic instances of - * strings, identifiers or symbols within the text, since those are - * the forms that can comprise path literals - * - Convert the raw index-tuples into a sequence of objects representing - * each found form - * - Search backwards from the end of the that list. Take up any initial - * (possibly half-complete) Symbol/String literal to become the `frag`, - * gobble up any `/`s followed by `ups` or Symbol/String literals to - * form the path, and stop if you reach a absolute path "literal" - * `wd`/`pwd`/`home`/`root` or if you can't parse anything anymore. - * - * @param snippet The entire code snippet in which we are requesting - * autocomplete - * @param cursor The position of the cursor when the user hits Tab - * @return `None` if no autocomplete is possible, otherwise a [[PathLiteralInfo]] - */ - def findPathLiteral(snippet: String, cursor: Int): Option[PathLiteralInfo] = { - val indices = Parsers.highlightIndices( - snippet.toVector, - { - case "Id" => Interval.Id - case "String" => Interval.String - case "Symbol" => Interval.Symbol - }, - Interval.End - ) - - val spans = - indices - .drop(1) - // Weird hack to get around other weird hack in Highlighter, where it - // uses 999999 to represent incomplete input - .map{case (a, b) if a > 9999 => (cursor, b) case x => x} - .grouped(2) - .collect{ - case Seq((s, Interval.Id), (e, Interval.End)) => Span.Id(s, e) - case Seq((s, Interval.String), (e, Interval.End)) => Span.String(s, e) - case Seq((s, Interval.Symbol), (e, Interval.End)) => Span.Symbol(s, e) - } - .toVector - .reverse - - spans.headOption match{ - case None => None - case Some(head) => - // None means we're not in a path autocomplete, Some(None) means - // we are but there is no incomplete segment before the cursor - val (frag: Option[Option[String]], prev0: Int) = - head match{ - case span: Span.Id => - if (snippet.substring(span.start, span.end) == "/") (Some(None), cursor) - else (None, 0) - case span: Span.String if span.end == cursor => - (Some(Some(snippet.slice(span.start, span.end))), span.start) - case span: Span.Symbol if span.end == cursor => - (Some(Some(snippet.slice(span.start, span.end))), span.start) - case _ => (None, 0) - } - - def rec(prev: Int, - spans: List[Span], - segments: List[Option[String]]): (Seq[Option[String]], Option[String]) = { - - spans match{ - case Span.Id(start, end) :: next :: rest - if snippet.slice(start, end) == "/" - && snippet.slice(end, prev).trim() == "" - && snippet.slice(next.end, start).trim() == "" => - - (next, snippet.slice(next.start, next.end)) match{ - case (_: Span.Id, "up") => rec(next.start, rest, None :: segments) - case (_: Span.Id, x) if rootMap.keySet.flatten.contains(x) => (segments, Some(x)) - case (_: Span.String, v) => - val mangled = v.drop(1).dropRight(1).replace("\\\"", "\"").replace("\\\\", "\\") - rec(next.start, rest, Some(mangled) :: segments) - case (_: Span.Symbol, v) => rec(next.start, rest, Some(v.drop(1)) :: segments) - case _ => (segments, None) - } - case _ => (segments, None) - } - } - for { - frag <- frag - (body, base) = rec( - prev0, - if (frag.isDefined) spans.toList.drop(1) else spans.toList, - Nil - ) - if !(body ++ frag ++ base).isEmpty - } yield PathLiteralInfo(base, body, frag, cursor - prev0) - } - } - - import ammonite.ops._ - - /** - * Small hard-coded list of how to convert the names of various - * path-literal-roots into actual Paths. Some depend on the REPL's - * `wd`, others don't - */ - val rootMap = Map[Option[String], Path => Path]( - None -> (wd => wd), - Some("wd") -> (wd => wd), - Some("pwd") -> (wd => pwd), - Some("root") -> (wd => root), - Some("home") -> (wd => home) - ) - - def colorPath(path: Path) = { - stat(path).fileType match{ - case ammonite.ops.FileType.Dir => fansi.Color.Cyan - case ammonite.ops.FileType.File => fansi.Color.Green - case ammonite.ops.FileType.SymLink => fansi.Color.Yellow - case ammonite.ops.FileType.Other => fansi.Color.Red - } - } - def pathCompleteFilter(wd: => Path, - colors: => Colors): Filter = partial{ - case TermInfo(TermState(9 ~: rest, b, c, _), width) - if PathComplete.findPathLiteral(b.mkString, c).isDefined => - - val Some(PathComplete.PathLiteralInfo(base, seq, frag, cursorOffset)) = - PathComplete.findPathLiteral(b.mkString, c) - - val path = rootMap(base)(wd) / seq.map { case None => os.up; case Some(s) => s: RelPath } - - if (!exists(path)) TermState(rest, b, c) - else { - val fragPrefix = frag.getOrElse("") - - def wrap(s: String) = "\"" + pprint.Util.literalize(s) + "\"" - val options = ( - ls ! path | (x => (x, wrap(x.last))) - |? (_._2.startsWith(fragPrefix)) - ) - val (completions, details) = options.partition(_._2 != fragPrefix) - - val coloredCompletions = for((path, name) <- completions) yield{ - val color = colorPath(path) - color(name).render - } - - - - - val details2 = details.map(x => pprint.tokenize(stat(x._1)).mkString) - - val stdout = - FrontEndUtils.printCompletions(coloredCompletions, details2) - .mkString - - if (details.length != 0 || completions.length == 0) - Printing(TermState(rest, b, c), stdout) - else { - val common = FrontEndUtils.findPrefix(completions.map(_._2), 0) - val newBuffer = b.take(c - cursorOffset) ++ common ++ b.drop(c) - Printing(TermState(rest, newBuffer, c - cursorOffset + common.length + 1), stdout) - } - } - } - - /** - * Enum used to tag the indices being returned by [[Parsers]] - */ - sealed trait Interval - object Interval{ - object Id extends Interval - object String extends Interval - object Symbol extends Interval - object End extends Interval - } - - /** - * More-convenient data-structures to work with, compared to the raw - * tuple-output of [[Parsers]] - */ - sealed trait Span{ - def parseStart: Int; - def end: Int - - /** - * Different from [[parseStart]], because [[Span.Symbol]] starts - * parsing one-character late. - */ - def start: Int = parseStart - } - object Span{ - case class Id(parseStart: Int, end: Int) extends Span - case class String(parseStart: Int, end: Int) extends Span - case class Symbol(parseStart: Int, end: Int) extends Span{ - override def start = parseStart - 1 - } - } -} diff --git a/shell/src/main/scala/ammonite/shell/ShellSession.scala b/shell/src/main/scala/ammonite/shell/ShellSession.scala deleted file mode 100644 index bd678b7eb..000000000 --- a/shell/src/main/scala/ammonite/shell/ShellSession.scala +++ /dev/null @@ -1,63 +0,0 @@ -package ammonite.shell - -import java.nio.file.NotDirectoryException -import java.nio.file.attribute.PosixFilePermission - -import ammonite.ops._ -import ammonite.repl.FrontEndUtils -import pprint.Renderer - -import scala.util.Try - -case class ShellSession() extends OpsAPI { - var wd0 = pwd - /** - * The current working directory of the shell, that will get picked up by - * any ammonite.ops commands you use - */ - implicit def wd = wd0 - /** - * Change the working directory `wd`; if the provided path is relative it - * gets appended on to the current `wd`, if it's absolute it replaces. - */ - val cd = (arg: Path) => { - /* - * `arg` should be a directory or a symlink to a directory - * `realPath will be None if that is not the case, - * otherwise - Some(realPath) - */ - val realPath = Option(arg) - .filter(_.isDir) - .orElse(os.followLink(arg).filter(_.isDir)) - - realPath match { - case None => throw new NotDirectoryException(arg.toString) - case Some(path) => wd0 = arg; wd0 - } - } - - implicit def Relativizer[T](p: T)(implicit b: Path, f: T => RelPath): Path = b/f(p) -} - -trait OpsAPI{ - /** - * The current working directory of the shell, that will get picked up by - * any [[Relativizer]] below, and can be modified using [[cd]] - */ - implicit def wd: Path - /** - * Change the working directory `wd`; if the provided path is relative it - * gets appended on to the current `wd`, if it's absolute it replaces. It - * returns the resultant absolute path. - */ - val cd: os.Path => os.Path - - /** - * Allows you to use relative paths (and anything convertible to a relative - * path) as absolute paths when working in the REPL. Note that this isn't - * available when using Ammonite-Ops in a standalone project! In such cases, - * it's good practice to convert paths from relative to absolute explicitly. - */ - implicit def Relativizer[T](p: T)(implicit b: Path, f: T => RelPath): Path - -} \ No newline at end of file diff --git a/shell/src/test/scala/ammonite/shell/PathCompleteTests.scala b/shell/src/test/scala/ammonite/shell/PathCompleteTests.scala deleted file mode 100644 index 08e29bed8..000000000 --- a/shell/src/test/scala/ammonite/shell/PathCompleteTests.scala +++ /dev/null @@ -1,66 +0,0 @@ -package ammonite.shell - -import ammonite.TestUtils.scala2 -import utest._ - -object PathCompleteTests extends TestSuite{ - val mainTests = Tests{ - test("path"){ - test("parse"){ - def check(s: String, - expected: (Option[String], Seq[Option[String]], Option[String], Int)) = { - val cursor = s.indexOf("") - val value = PathComplete.findPathLiteral(s.take(cursor), cursor).get - assert(value == PathComplete.PathLiteralInfo.tupled(expected)) - } - def checkNeg(s: String) = { - val cursor = s.indexOf("") - val res = PathComplete.findPathLiteral(s.take(cursor), cursor) - assert(res == None) - } - test("pos"){ - check("""'hello/""", (None, Seq(Some("hello")), None, 0)) - check("""'hello / """, (None, Seq(Some("hello")), None, 0)) - check("""'hello / 'worl""", (None, Seq(Some("hello")), Some("'worl"), 5)) - check( - """'hello / "world" / """, - (None, Seq(Some("hello"), Some("world")), None, 0) - ) - check( - """'hello / "world" / "foo""", - (None, Seq(Some("hello"), Some("world")), Some("\"foo"), 4) - ) - check( - """'hello / "\"" / "foo""", - (None, Seq(Some("hello"), Some("\"")), Some("\"foo"), 4) - ) - check( - """wd/ 'hello / "\"" / "foo""", - (Some("wd"), Seq(Some("hello"), Some("\"")), Some("\"foo"), 4) - ) - - check( - """wd / up / 'hello / up / "\"" / "foo""", - (Some("wd"), Seq(None, Some("hello"), None, Some("\"")), Some("\"foo"), 4) - ) - - check("""home/'fi""", (Some("home"), Nil, Some("'fi"), 3)) - check("""home/'find""", (Some("home"), Nil, Some("'fi"), 3)) - } - test("neg"){ - checkNeg(""" "hello".""") - checkNeg(""" omg/""") - checkNeg(""" omg / """) - // We only do dumb "literal" paths; any extraneous syntax should - // cause it to fail - checkNeg(""" wd / "" / ("omg") / """) - } - } - } - - } - - val tests = - if (scala2) mainTests - else Tests { test("Disabled in Scala 3") - "Disabled in Scala 3" } -} diff --git a/shell/src/test/scala/ammonite/shell/SessionTests.scala b/shell/src/test/scala/ammonite/shell/SessionTests.scala deleted file mode 100644 index ed7443e30..000000000 --- a/shell/src/test/scala/ammonite/shell/SessionTests.scala +++ /dev/null @@ -1,263 +0,0 @@ -package ammonite.shell - -import ammonite.TestRepl -import ammonite.TestUtils._ -import utest._ - -/** - * Created by haoyi on 8/30/15. - */ -object SessionTests extends TestSuite{ - - val bareSrc = - """pwd/"shell"/"src"/"main"/"resources"/"ammonite"/"shell"/"example-predef-bare.sc"""" - - val tests = Tests{ - val check = new TestRepl() -// test("workingDir"){ -// check.session(s""" -// @ import ammonite.ops._ -// -// @ interp.load.module($bareSrc) -// -// @ val originalWd = wd -// -// @ val originalLs1 = %%ls -// -// @ val originalLs2 = ls! -// -// @ cd! up -// -// @ assert(wd == originalWd/up) -// -// @ cd! root -// -// @ assert(wd == root) -// -// @ assert(originalLs1 != (%%ls)) -// -// @ assert(originalLs2 != (ls!)) -// """) -// } - test("specialPPrint"){ - // Make sure these various "special" data structures get pretty-printed - // correctly, i.e. not as their underlying type but as something more - // pleasantly human-readable - check.session(s""" - @ import ammonite.ops._ - - @ interp.load.module($bareSrc) - - @ import ammonite.ops.ImplicitWd - - @ %%ls "ops/src/test" - res3: CommandResult = - resources - scala - """) - } - - def cdIntoDirSymlinkTest() = { - check.session( - s""" - @ import ammonite.ops._ - - @ interp.load.module($bareSrc) - - @ val originalWd = wd - - @ val tmpdir = tmp.dir() - - @ cd! tmpdir - - @ mkdir! "srcDir0" - - @ ln.s("destSymLink", os.FilePath("srcDir0")) - - @ cd! "destSymLink" - - @ assert("srcDir0" == os.followLink(wd).get.last) - - @ assert("destSymLink" == wd.last) - - @ cd! originalWd - - @ rm! tmpdir - """) - } - test("cdIntoDirSymlink"){ - // Getting weird errors in Scala 3: - // java.lang.AssertionError: assertion failed: os.BasePathImpl & os.FilePath / - // TypeRef(ThisType(TypeRef(NoPrefix,module class os)),trait BasePathImpl) & - // HKTypeLambda(List(CC), List(TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix,module class - // scala)),class Nothing),HKTypeLambda(List(_), List(TypeBounds(TypeRef(ThisType(TypeRef( - // NoPrefix,module class scala)),class Nothing),TypeRef(ThisType(TypeRef(NoPrefix,module - // class scala)),class Any))), TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class - // Any), List()))), AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class os)),trait - // FilePath),List(TypeParamRef(CC)))) - if (scala2) cdIntoDirSymlinkTest() - else "Disabled in Scala 3" - } - - def nestedSymlinksTest() = { - check.session( - s""" - @ import ammonite.ops._ - - @ interp.load.module($bareSrc) - - @ val originalWd = wd - - @ val tmpdir = tmp.dir() - - @ cd! tmpdir - - @ val names = Seq("test123", "test124", "test125", "test126") - - @ mkdir! wd/"test123" - - @ ln.s(wd/"test124", wd/"test123") - - @ ln.s(wd/"test125", wd/"test124") - - @ ln.s(wd/"test126", wd/"test125") - - @ cd! "test126" - - @ assert(os.followLink(wd).get == os.followLink(tmpdir/"test123").get) - - @ assert(wd == tmpdir/"test126") - - @ cd! tmpdir - - @ assert(wd == tmpdir) - - @ rm! "test123" - - @ assert(os.followLink(wd/"test126") == None) - - @ names.foreach(p => rm! wd/p) - - @ names.foreach(p => assert(!exists(wd/p))) - - @ cd! originalWd - - @ rm! tmpdir - """) - } - test("nestedSymlinks"){ - // Disabled in Scala 3, it seems the Path -> FilePath conversion in the `ln.s` calls - // triggers an assertion in dotty: - // java.lang.AssertionError: assertion failed: os.BasePathImpl & os.FilePath / TypeRef( - // ThisType(TypeRef(NoPrefix,module class os)),trait BasePathImpl) & HKTypeLambda(List(CC), - // List(TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Nothing), - // HKTypeLambda(List(_), List(TypeBounds(TypeRef(ThisType(TypeRef(NoPrefix,module class - // scala)),class Nothing),TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Any - // ))), TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Any), List()))), - // AppliedType(TypeRef(ThisType(TypeRef(NoPrefix,module class os)),trait FilePath),List( - // TypeParamRef(CC)))) - if (scala2) nestedSymlinksTest() - else "Disabled in Scala 3" - } - def opsInSymlinkedDir() = { - // test mkdir, write, read, stat/stat.full, cp, and ls inside a symlinked directory - // (both while wd = symlinked and outside) - check.session( - s""" - @ import ammonite.ops._ - - @ interp.load.module($bareSrc) - - @ val originalWd = wd - - @ val tmpdir = tmp.dir() - - @ cd! tmpdir - - @ mkdir! wd/"test123" - - @ ln.s(wd/"test124", wd/"test123") - - @ ln.s(wd/"test125", wd/"test124") - - @ ln.s(wd/"test126", wd/"test125") - - @ cd! "test126" - - @ mkdir! "test130" - - @ assert(exists(wd/"test130")) - - @ cd! "test130" - - @ assert(wd == tmpdir/"test126"/"test130") - - @ write("test131", "abcdef") // writing while inside symlinked dir - - @ assert(read("test131") == "abcdef") // reading while inside symlinked dir - - @ cp("test131", "test132") - - @ cd! up - - @ assert(wd == tmpdir/"test126") - - @ write("test132", "qqq") - - @ assert(read("test132") == "qqq") - - @ cp("test132", "test133") // cp inside symlinked dir while inside that dir - - @ assert( (ls!).toList == List(wd/"test130", wd/"test132", wd/"test133")) // ls inside dir - - @ assert(!stat("test132").isDir && !stat("test132").isSymLink && stat("test130").isDir) - - @ assert(!stat("test132").isDir && !stat("test132").isSymLink) - - @ assert(stat("test130").isDir) - - @ assert(ls.rec(wd).length == 5) - - @ cd! originalWd - - @ val t = tmpdir/"test126" // this dir is symlinked - - @ assert(read(t/"test130"/"test131") == "abcdef") // reading while outside symlinked dir - - @ assert(read(t/"test132") == "qqq") - - @ write.over(t/"test132", "www") // writing into file in symlinked dir - - @ assert(read(t/"test132") == "www") - - @ assert(read(t/"test133") == "qqq") - - @ assert(ls(t).toList == List(t/"test130", t/"test132", t/"test133")) - - @ cp(t/"test133", t/"test134") // cp inside symlinked dir while outside that dir - - @ assert(read(t/"test134") == "qqq") - - @ assert(ls.rec(tmpdir).length == 10) // ls.rec of symlinked dirs while outside that dir - - @ assert(ls.rec(t).length == 6) - - @ assert(!stat(t/"test132").isDir && !stat(t/"test132").isSymLink) - - @ assert(!stat(t/"test132").isDir && !stat(t/"test132").isSymLink) - - @ assert(stat(t/"test130").isDir && stat(t, followLinks = false).isSymLink) - - @ assert(stat(t/"test130").isDir && stat(t, followLinks = false).isSymLink) - - @ rm! tmpdir - """) - } - test("opsInSymlinkedDir"){ - // Just like nestedSymlinks, getting some weird error in Scala 3 - if (scala2) opsInSymlinkedDir() - else "Disabled in Scala 3" - } - - } -} diff --git a/shell/src/test/scala/ammonite/shell/TestMain.scala b/shell/src/test/scala/ammonite/shell/TestMain.scala deleted file mode 100644 index fe57e3737..000000000 --- a/shell/src/test/scala/ammonite/shell/TestMain.scala +++ /dev/null @@ -1,17 +0,0 @@ -package ammonite.shell - -import ammonite.ops._ -/** - * Convenience entry-point useful to kick off a shell with - */ -object TestMain { - val examplePredef = "shell/src/main/resources/ammonite/shell/example-predef-bare.sc" - def main(args: Array[String]): Unit = { - System.setProperty("ammonite-sbt-build", "true") - ammonite.Main.main(args ++ Array( - "--home", "target/tempAmmoniteHome", - "--predef", examplePredef, - "--no-home-predef" - )) - } -} diff --git a/sshd/src/main/scala/ammonite/sshd/SshServer.scala b/sshd/src/main/scala/ammonite/sshd/SshServer.scala index 2b0d565db..25c227b64 100644 --- a/sshd/src/main/scala/ammonite/sshd/SshServer.scala +++ b/sshd/src/main/scala/ammonite/sshd/SshServer.scala @@ -70,7 +70,6 @@ object SshServer { options.ammoniteHome/'cache/'ssh/'hostkeys def touch(file: os.Path): os.Path = { - import ammonite.ops._ if (!os.exists(file)) { os.write(file, Array.empty[Byte], createFolders = true) }