如何在Scala中为命令行创建DSL,并使用最少的额外样板文件
我需要为不熟悉scala(既不是Java)但熟悉Shell的用户开发一个API。他们将,基本上在scala类中编写shell脚本(我知道我可以调用外部shell脚本,但是,来吧!而且,稍后我们将有一些用于常见shell任务的函数) 我希望能完成以下任务:如何在Scala中为命令行创建DSL,并使用最少的额外样板文件,scala,dsl,Scala,Dsl,我需要为不熟悉scala(既不是Java)但熟悉Shell的用户开发一个API。他们将,基本上在scala类中编写shell脚本(我知道我可以调用外部shell脚本,但是,来吧!而且,稍后我们将有一些用于常见shell任务的函数) 我希望能完成以下任务: 1 object MyCoolScript extends MyMagicTrait { 2 $ "mkdir /tmp/test" 3 $ "cd /tmp/test" 4 $ "wget some-url" 5 } 更直
1 object MyCoolScript extends MyMagicTrait {
2 $ "mkdir /tmp/test"
3 $ "cd /tmp/test"
4 $ "wget some-url"
5 }
更直接地说,我如何将第2-4行(或可能不太简洁的版本)转换成可以在MyMagicTrait中处理的Seq[String]
我知道sys.process.stringToProcess,但如果我有:
object MyCoolScript extends MyMagicTrait {
"mkdir /tmp/test" !!
"cd /tmp/test" !!
"wget some-url" !!
}
如何以简洁的方式获得每个命令的结果?另外,我希望有一个$“xxx”符号
发布答案更新:
多亏了@debilski、@tenshi和@daniel-c-sobral,我才能够找到一个非常接近理想实现的方法:虚拟方法
编辑:
我会这样做:
res = List("mkdir /tmp/test", "cd /tmp/test", "wget some-url").map(_!!)
// res is a List[String] of the output of your commands
您可以执行如下系统命令:
scala> import scala.sys.process._
import scala.sys.process._
scala> "pwd"!
/Users/kgann
res0: Int = 0
您应该能够将其封装到DSL中,但它已经相当简洁了 这只是重复@Debilski和@Tomasz的想法,表明您可以将它们很好地结合起来:
class Shell {
var elems = Vector[String]()
def >(str: String) = elems :+= str
def run() = elems.map( whatever )
}
val shell = new Shell
shell> "mkdir /tmp/test.dir"
shell> "cd /tmp/test.dir"
trait Magic {
object shell {
var lines = IndexedSeq[String]()
def >(xs: String) { lines ++= xs.split("\n").map(_.trim) }
}
def doSomethingWithCommands { shell.lines foreach println }
}
object MyCoolScript extends App with Magic {
println("this is Scala")
shell> """
mkdir /tmp/test
cd /tmp/test
"""
doSomethingWithCommands
shell> """
wget some-url
"""
}
如果您想将shell与Scala命令相结合,我真的不知道如何减少样板文件,因为您需要一些东西来显示shell的开始和结束位置。Scala 2.10附带的字符串插值似乎可以在这方面帮助您。首先,您可以实现简单的
$
方法,该方法只需立即执行命令。为了实现此目的,您需要在StringContext
上添加此自定义方法:
object ShellSupport {
implicit class ShellStrings(sc: StringContext) {
def $(args: Any*) =
sc.s(args: _*) split "\n" map (_.trim) filterNot (_.isEmpty) foreach { cmd =>
// your excution logic goes here
println(s"Executing: $cmd")
}
}
}
现在您可以这样使用它:
import ShellSupport._
val testDir = "/tmp/test"
$"mkdir $testDir"
$"cd $testDir"
$"""
wget some-url
wget another-url
"""
您可以利用它的语法(唯一的缺点是不能在$
和“
之间添加空格)和命令中的字符串插值
现在,让我们尝试实现您的神奇特性。这通常是相同的想法,但我也使用了
DelayedInit
,以便正确定义命令,然后在创建类时自动执行它们
trait MyMagicTrait extends DelayedInit {
private var cmds: List[String] = Nil
def commands = cmds
implicit class ShellStrings(sc: StringContext) {
def $(args: Any*) = {
val newCmds = sc.s(args: _*) split "\n" map (_.trim) filterNot (_.isEmpty)
cmds = cmds ++ newCmds
}
}
def delayedInit(x: => Unit) {
// your excution logic goes here
x
cmds map ("Excutintg: " + _) foreach println
}
}
它的用法是:
class MyCoolScript extends MyMagicTrait {
val downloader = "wget"
$"mkdir /tmp/test"
$"cd /tmp/test"
$"""
$downloader some-url
$downloader another-url
"""
}
new MyCoolScript
这两种解决方案产生相同的输出:
Executing: mkdir /tmp/test
Executing: cd /tmp/test
Executing: wget some-url
Executing: wget another-url
这些结果到处都是。唉,我认为sys.process不足以满足您的需要。让我们首先写出这个示例,这样它就能满足您的要求:
val result = (
"mkdir /tmp/test ###
"cd /tmp/test" ###
"wget someurl
).lines_!
现在,为什么它不起作用:
首先,cd
将抛出一个异常——没有cd
可执行文件。这是一个内部shell命令,因此只有shell可以执行它
接下来,假设
cd
可以工作,那么wget
不会在/tmp/test
发生。上面的每个命令都会在Java的当前工作目录中重新执行。您可以指定执行每个命令的目录,但不能有CWD(它不是“可变的”).谢谢,但我知道,我的问题是如何将字符串行分组并将它们转换为列表,以便我可以处理它们。我需要得到每个处理的结果。此解决方案的问题是脚本很快会变得不可读。我说的是10-50行脚本。我想我可以使用一行+逗号作为元素ts是列表的一部分,但这不是很清楚。这种方法的问题是,我不能在命令行之间或作为命令行的一部分使用scala语句。也许scala的2.10字符串插值可以帮助我实现这一点。但我还是在寻找更好的解决方案。谢谢。@JhonnyEverson:我也希望有更好的解决方案:-)。现在我们在讨论!如果我调用$而不是shell,则我有$>”“现在我只需要一种自动应用的方法。谢谢。我喜欢代码中的行号。数到5真是太难了!它让我的sed体验始终保持新鲜。我这样做是因为我参考了第2-4行。现在看不到sed的用法:)。顺便说一句,不要使用$
。您可以使用它编写代码,但它不是法律代码,它可能会在未经通知的情况下中断。我不确定,但我已经想到了这一点。我正在考虑对命令进行必要的调整,使其正常工作。
val result = (
"mkdir /tmp/test ###
"cd /tmp/test" ###
"wget someurl
).lines_!