2012-05-22 11 views
8

Necesito desarrollar una API para usuarios que no estén familiarizados con Scala (ni Java) pero que estén familiarizados con Shell. Básicamente, escribirán scripts de shell dentro de una clase scala (sé que podría simplemente llamar a scripts de shell externos, pero vamos! Además, más adelante tendremos algunas funciones para tareas de shell comunes).Cómo crear DSL en Scala para líneas de comando con un número mínimo de texto adicional

Tenía la esperanza de lograr algo como:

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

ser más directo, ¿cómo puedo convertir líneas 2-4 (o una versión concisa posiblemente menos) en Seq [String] que podía procesar en MyMagicTrait ?

que sé sobre sys.process.stringToProcess pero si tengo:

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

¿Cómo puedo obtener el resultado de cada comando de una manera concisa? también, esperaba una notación $ "xxx".

mensaje Respuestas Actualización:

Gracias a @debilski, y @tenshi @ Daniel-c-sobral yo era capaz de llegar a una relación muy estrecha con la aplicación deseada: https://gist.github.com/2777994

+2

Me gusta cuando recibimos números de línea en el código. ¡Es tan difícil contar hasta 5! Y mantiene mi experiencia sed siempre fresca. –

+0

Lo hice porque me refería a las líneas 2-4. No veo el uso de sed ahora :). –

+1

Por cierto, NO USE '$'. Puede escribir código usándolo, pero no es un código legal, y puede romperse sin previo aviso. –

Respuesta

4

Parece que la interpolación de cadenas que viene con Scala 2.10 puede ayudarlo aquí. Al principio puede implementar el método simple $, que simplemente ejecuta el comando inmediatamente. Con el fin de hacer que es necesario añadir este método personalizado en 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") 
      } 

    } 
} 

ya se puede utilizar de esta manera:

import ShellSupport._ 

val testDir = "/tmp/test" 

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

Usted puede tomar ventaja de ello es la sintaxis (Es único inconveniente, es que no puede agregar espacio entre $ y ") e interpolación de cadenas dentro del comando.


Ahora intentemos implementar su rasgo mágico. En general, es la misma idea, pero también estoy usando DelayedInit para definir correctamente los comandos y luego ejecutarlos automáticamente durante la creación de la clase.

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

Y es el uso de:

class MyCoolScript extends MyMagicTrait { 
    val downloader = "wget" 

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

new MyCoolScript 

Ambas soluciones producen la misma salida:

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

enfoque maniquí

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

El problema con este enfoque es que no puedo tener declaraciones scala entre líneas de comando o como parte de ellas. Tal vez la interpolación de cadenas de 2.10 de Scala puede ayudarme con eso. Todavía estaba saltando para una mejor solución. Gracias. –

+0

@JhonnyEverson: Espero también mejores soluciones :-). –

1

EDIT:

me gustaría hacer esto:

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

Puede ejecutar comandos del sistema, así:

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

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

Usted debe ser capaz de envolver esto en un DSL pero ya es bastante escueta.

+0

Gracias, pero lo sé, mi pregunta es cómo puedo agrupar líneas de cadenas y convertirlas en una lista para poder procesarlas. Necesito obtener el resultado de cada proceso –

+0

El problema con esta solución es que los scripts se volverán ilegibles rápidamente. Estoy hablando de 10-50 líneas de script. Creo que podría usar una línea + coma para los elementos de la Lista, pero eso no es muy limpio. –

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

¡Ahora estamos hablando! Si llamo $ en lugar de shell, tengo $> "". Ahora solo necesito una forma de aplicar eso automáticamente. Gracias. –

3

esto es sólo riffs de @ Debilski de e @ ideas de Tomasz para mostrar los puede combinar muy bien:

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

Realmente no veo cómo se podría conseguir menos repetitivo si quieres combine el shell con los comandos de Scala, ya que necesita algo para mostrar dónde está comenzando y terminando el shell.

2

Estos resultados son por todo el lugar. Por desgracia, no creo que el proceso del sistema sea suficiente para ti. Vamos a escribir en primer lugar que ejemplo, de tal manera que lo hace lo que pidieron:

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

Ahora, ¿por qué no funciona:

En primer lugar, cd lanzará una excepción - no hay cd ejecutable. Es un comando de shell interno, por lo que solo Shell puede ejecutarlo.

A continuación, suponiendo cd funcionaría, sería el wget no suceder en /tmp/test. Cada uno de los comandos anteriores se ejecuta nuevamente en el directorio de trabajo actual de Java. Puede especificar el directorio en el que se ejecutará cada comando, pero no puede tener un CWD (no es "mutable").

+0

No estaba seguro, pero lo tenía en mente. Estaba pensando en hacer los ajustes necesarios para que los comandos funcionen. –

Cuestiones relacionadas