2012-05-22 3 views
8

0 erstellen Ich muss eine API für die Benutzer entwickeln, die nicht mit scala vertraut sind (kein Java) aber vertraut mit Shell. Sie schreiben im Grunde Shell-Skripte innerhalb einer Scala-Klasse (ich weiß, dass ich nur externe Shell-Skripte aufrufen kann, aber komm schon! Außerdem werden wir später einige Funktionen für allgemeine Shell-Aufgaben haben).Wie man DSL in Scala für Befehlszeilen mit minimalem zusätzlichem boilerplate

Ich habe gehofft, wie etwas zu erreichen:

1 object MyCoolScript extends MyMagicTrait { 
2 $ "mkdir /tmp/test" 
3 $ "cd /tmp/test" 
4 $ "wget some-url" 
5 } 

direktere Sein, wie kann ich Linien drehen 2-4 (oder eine möglicherweise weniger Kurzfassung) in Seq [Zeichenfolge], dass ich in MyMagicTrait verarbeiten konnte ?

Ich weiß über sys.process.stringToProcess aber wenn ich:

object MyCoolScript extends MyMagicTrait { 
    "mkdir /tmp/test" !! 
    "cd /tmp/test" !! 
    "wget some-url" !! 
} 

Wie kann ich das Ergebnis jeden Befehl in prägnanter Weise bekommen? Außerdem hatte ich auf eine $ xxx-Notation gehofft.

Beitrag Antworten Update:

Dank @debilski, @tenshi und @ daniel-c-sobral ich in der Lage war sehr nahe an die gewünschte Umsetzung zu kommen: https://gist.github.com/2777994

+2

Ich mag es, wenn wir Zeilennummern im Code bekommen. Es ist so schwer, bis 5 zu zählen! Und es hält mein sed-Erlebnis immer frisch. –

+0

Ich tat das, weil ich mich auf die Zeilen 2-4 bezog. Sehe die Verwendung für sed jetzt nicht :). –

+1

Übrigens, verwenden Sie nicht '$'. Sie können Code verwenden, der es verwendet, aber es ist kein gesetzlicher Code und es kann ohne Ankündigung brechen. –

Antwort

4

Scheint, dass die String-Interpolation, die mit Scala 2.10 kommt, Ihnen hier helfen kann. Zunächst können Sie die einfache Methode $ implementieren, die den Befehl einfach sofort ausführt. Um es zu machen, müssen Sie diese benutzerdefinierte Methode auf StringContext hinzuzufügen:

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") 
      } 

    } 
} 

Jetzt können Sie es wie folgt verwenden können:

import ShellSupport._ 

val testDir = "/tmp/test" 

$"mkdir $testDir" 
$"cd $testDir" 
$""" 
    wget some-url 
    wget another-url 
""" 

Sie Vorteil nehmen kann davon Syntax ist (es ist einzige Nachteil ist dass zwischen $ und " kein Leerzeichen eingefügt werden kann und die Interpolation innerhalb des Befehls interpoliert wird.


Jetzt versuchen wir, Ihre magische Eigenschaft zu implementieren. Es ist im Allgemeinen die gleiche Idee, aber ich verwende auch DelayedInit, um Befehle richtig zu definieren und sie dann automatisch während der Klassenerstellung auszuführen.

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 
    } 
} 

Und es ist Nutzung:

class MyCoolScript extends MyMagicTrait { 
    val downloader = "wget" 

    $"mkdir /tmp/test" 
    $"cd /tmp/test" 
    $""" 
    $downloader some-url 
    $downloader another-url 
    """ 
} 

new MyCoolScript 

Beide Lösungen produzieren die gleiche Leistung:

Executing: mkdir /tmp/test 
Executing: cd /tmp/test 
Executing: wget some-url 
Executing: wget another-url 
2

Dummy Ansatz

class Shell(commands: String) { 
    commands.lines foreach { 
     command => 
      //run your command 
    } 
} 

class MyCoolScript extends Shell(""" 

    mkdir /tmp/test 
    cd /tmp/test 
    wget some-url 

""") 
+0

Das Problem mit diesem Ansatz ist, dass ich keine Scala-Anweisungen zwischen Befehlszeilen oder als Teil von ihnen haben kann. Vielleicht kann mir die 2.10-String-Interpolation von scala dabei helfen. Ich hüpfte immer noch nach einer besseren Lösung. Vielen Dank. –

+0

@JhonnyEverson: Ich hoffe auf eine bessere Lösung auch :-). –

1

EDIT:

ich würde dies tun:

res = List("mkdir /tmp/test", "cd /tmp/test", "wget some-url").map(_!!) 
// res is a List[String] of the output of your commands 

Sie System ausführen können Befehle wie so:

scala> import scala.sys.process._ 
import scala.sys.process._ 

scala> "pwd"! 
/Users/kgann 
res0: Int = 0 

Sie sollten diese in der Lage sein, in einem DSL-wickeln, aber es ist schon ziemlich kurz und bündig.

+0

Danke, aber ich weiß das, meine Frage ist, wie kann ich Zeilen von Strings gruppieren und sie in eine Liste verwandeln, damit ich sie verarbeiten kann. Ich muss das Ergebnis jedes Prozesses erhalten –

+0

Das Problem mit dieser Lösung ist, dass die Skripte schnell unlesbar werden. Ich spreche von 10-50 Zeilen Skript. Ich denke, ich könnte eine Zeile + Komma für die Elemente der Liste verwenden, aber das ist nicht sehr sauber. –

5
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" 
+0

Jetzt reden wir! Wenn ich $ anstelle von Shell anrufe, habe ich $> "". Jetzt brauche ich nur eine Möglichkeit, das automatisch anzuwenden. Vielen Dank. –

3

Dies wird Riffing nur auf @ Debilski ist und @ Ideen Tomaszs Sie ihnen zu zeigen, kann gut kombinieren:

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 
    """ 
} 

ich nicht wirklich sehen, wie Sie eine weniger vorformulierten bekommen könnte, wenn Sie wollen kombinieren Sie Shell mit Scala-Befehlen, da Sie etwas benötigen, um zu zeigen, wo die Shell beginnt und endet.

2

Diese Ergebnisse sind ganz über dem Platz. Ach, ich denke nicht, dass sys.process für dich ausreichen wird. Lassen Sie uns schreiben, zuerst das Beispiel so, dass es das tut, was Sie gefragt:

val result = (
    "mkdir /tmp/test ### 
    "cd /tmp/test" ### 
    "wget someurl 
).lines_! 

Nun, warum es nicht funktioniert:

Zuerst cd eine Ausnahme ausgelöst wird - es gibt keine cd ausführbar. Das ist ein interner Shell-Befehl, also kann nur die Shell ihn ausführen.

Als nächstes cd unter der Annahme funktionieren würde, die wget würde nicht bei /tmp/test passieren. Jeder der obigen Befehle wird im aktuellen Arbeitsverzeichnis von Java neu ausgeführt. Sie können das Verzeichnis angeben, in dem jeder Befehl ausgeführt werden soll, aber Sie können kein CWD haben (es ist nicht "veränderbar").

+0

Ich war mir nicht sicher, aber ich hatte das im Sinn. Ich dachte daran, die notwendigen Anpassungen vorzunehmen, damit die Befehle funktionieren. –

Verwandte Themen