diff --git a/Makefile b/Makefile index 44047c50..1467310e 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ log/jshint.log: log/npm-dev-install.log lib/spellcast.js test/spellcast-test.js # Mocha BDD STDOUT test log/mocha.log: log/npm-dev-install.log lib/spellcast.js test/spellcast-test.js - ${MOCHA} test/spellcast-test.js -R list | tee log/mocha.log ; exit $${PIPESTATUS[0]} + ${MOCHA} test/spellcast-test.js -R spec | tee log/mocha.log ; exit $${PIPESTATUS[0]} # README README.md: documentation.md bdd-spec.md diff --git a/README.md b/README.md index 8aa7c56f..343f5f70 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ Full BDD spec generated by Mocha: echo delayed-echo bob blihblih one +scroll line: one +scroll line: three +scroll line: two +scroll line: one +scroll line: three +scroll line: two one more time: one one more time: two one more time: three @@ -74,7 +80,7 @@ cleanup( function() { } ) ; ``` -should substitute variable (aka formula) accordingly. +should substitute variable (aka formula) accordingly in 'sh' block. ```js cleanup( function() { @@ -90,6 +96,22 @@ cleanup( function() { } ) ; ``` +should write a new formula with the output of an 'sh' block. + +```js +cleanup( function() { + + var book = new spellcast.Book( fs.readFileSync( 'spellbook' ).toString() ) ; + + book.cast( 'write-formula' , function( error ) + { + expect( error ).not.ok() ; + expect( getCastedLog( 'write-formula' ) ).to.be( 'scroll line: one\nscroll line: three\nscroll line: two\nscroll line: one\nscroll line: three\nscroll line: two\n' ) ; + done() ; + } ) ; +} ) ; +``` + # 'foreach' block should . diff --git a/bdd-spec.md b/bdd-spec.md index 8354713e..d6d34ba2 100644 --- a/bdd-spec.md +++ b/bdd-spec.md @@ -1,6 +1,12 @@ echo delayed-echo bob blihblih one +scroll line: one +scroll line: three +scroll line: two +scroll line: one +scroll line: three +scroll line: two one more time: one one more time: two one more time: three @@ -56,7 +62,7 @@ cleanup( function() { } ) ; ``` -should substitute variable (aka formula) accordingly. +should substitute variable (aka formula) accordingly in 'sh' block. ```js cleanup( function() { @@ -72,6 +78,22 @@ cleanup( function() { } ) ; ``` +should write a new formula with the output of an 'sh' block. + +```js +cleanup( function() { + + var book = new spellcast.Book( fs.readFileSync( 'spellbook' ).toString() ) ; + + book.cast( 'write-formula' , function( error ) + { + expect( error ).not.ok() ; + expect( getCastedLog( 'write-formula' ) ).to.be( 'scroll line: one\nscroll line: three\nscroll line: two\nscroll line: one\nscroll line: three\nscroll line: two\n' ) ; + done() ; + } ) ; +} ) ; +``` + # 'foreach' block should . diff --git a/format.md b/format.md index 3ac1620e..adc510bf 100644 --- a/format.md +++ b/format.md @@ -5,15 +5,17 @@ # Variable substitution -Variables aka formula are defined in the 'formula' block. +Variables aka *formula* are defined in the *formula* block. -A formula only consists of a string, no other type are supported ATM. +A *formula* only consists of a string, no other type are supported ATM. Once a formula is defined, it can be used with the syntax `${variableName}`, where 'variableName' is the name of the variable. When a variable is encountered, it is substituted by its string value. +TODOC: list formula + # References @@ -36,8 +38,19 @@ This block defines formula, i.e. variables and values used for substitution. * parallel: this shell block will execute each command in parallel mode, if a number is passed, this is the maximum of commands running in parallel * ignore: if a command return a non-zero status, it will continue nontheless +* write-formula: this specify a variable name (aka a *formula*) which will be populated by each line of the output + of this shell block, the formula is used as a list +* splitter: this specify a splitter for the 'write' argument, by default '\n' is the splitter +* silence: dispell all output to stdout +* amnesia: dispell all output to log files Each child of this block is a shell command to execute. +## foreach + +TODOC: everything about foreach + + + diff --git a/lib/spellcast.js b/lib/spellcast.js index ecb6d2bd..2b3b4f46 100644 --- a/lib/spellcast.js +++ b/lib/spellcast.js @@ -26,6 +26,7 @@ /* TODO: + * turn arguments using hyphen in spellbooks into camelCase (e.g. write-formula should be writeFormula in code) * automatically check versus the 'spellbook' file (just like if 'spellbook' was added to all 'summon' block) * escape formula variable substitution * new blocks: @@ -33,6 +34,7 @@ - wand - zap - ssh : like sh but remotely + * find the right regexp for split(/,/) because '\,' should not split */ @@ -661,31 +663,50 @@ spellcast.Book.prototype.castSummon = function castSummon( summon , castExecutio spellcast.Book.prototype.castShell = function castShell( shell , castExecution , callback ) { - var plan ; + var plan , splitter , self = this ; plan = async - .foreach( shell.shellCommands , this.execShellCommand.bind( this , castExecution ) ) + .map( shell.shellCommands , this.execShellCommand.bind( this , castExecution , shell.args ) ) .nice( 0 ) ; //console.log( shell ) ; - if ( shell.args.parallel ) - { - if ( shell.args.parallel === true ) { plan.parallel( shell.args.parallel ) ; } - else { plan.parallel( parseInt( shell.args.parallel ) ) ; } - } + if ( ! shell.args.parallel ) { plan.parallel( false ) ; } + else if ( shell.args.parallel === true ) { plan.parallel() ; } + else { plan.parallel( parseInt( shell.args.parallel ) ) ; } if ( ! shell.args.ignore ) { plan.fatal( true ) ; } - plan.exec( callback ) ; + if ( shell.args['write-formula'] && typeof shell.args['write-formula'] !== 'string' ) { delete shell.args['write-formula'] ; } + + + // Let's exec the plan and process the final outcome! + + plan.exec( function( error , outputMap ) { + + if ( error ) { callback( error ) ; return ; } + + if ( shell.args['write-formula'] ) + { + splitter = shell.args.splitter || '\n' ; + self.formula[ shell.args['write-formula'] ] = outputMap.join( '' ).trim().split( splitter ) ; + + /* + console.log( "outputMap:" , outputMap[0] ) ; + console.log( "full output:" , output ) ; + */ + } + + callback() ; + } ) ; //plan.exec( function() { console.log( 'castShell: Done!' ) ; callback() ; } ) ; } ; -spellcast.Book.prototype.execShellCommand = function execShellCommand( castExecution , shellCommand , callback ) +spellcast.Book.prototype.execShellCommand = function execShellCommand( castExecution , args , shellCommand , callback ) { - var child , onStdout , onStderr , onStdin , onceExit , onIgnoredError ; + var child , onStdout , onStderr , onStdin , onceExit , onIgnoredError , output = '' ; //console.log( 'Exec command: ' , shellCommand ) ; @@ -705,41 +726,36 @@ spellcast.Book.prototype.execShellCommand = function execShellCommand( castExecu // For some reason, 'exit' can be triggered before some 'data' event, so we have to delay removeListener() a bit setTimeout( function() { + child.stdout.removeListener( 'data' , onStdout ) ; child.stderr.removeListener( 'data' , onStderr ) ; - } , 200 ) ; + + // If there is an 'write-formula' arguments, we must trigger the callback in this timeout event, + // if not we take the risk that we will miss some output and we don't want that + if ( args['write-formula'] ) { callback( status , output ) ; } + + } , 0 ) ; - callback( status ) ; + // If there isn't an 'write-formula' arguments, then we can trigger the callback now + if ( ! args['write-formula'] ) { callback( status ) ; } } ; onStdout = function( chunk ) { // Send the command's stdout to the process stdout and the output file - process.stdout.write( chunk ) ; - //castExecution.outputFile.write( 'Stdout:\n' ) ; - castExecution.outputFile.write( chunk ) ; - //castExecution.outputFile.write( '\n' ) ; + if ( ! args.silence ) { process.stdout.write( chunk ) ; } + if ( ! args.amnesia ) { castExecution.outputFile.write( chunk ) ; } + if ( args['write-formula'] ) { output += chunk ; } } ; onStderr = function( chunk ) { // Send the command's stderr to the process stderr and the output file - process.stderr.write( chunk ) ; - - //castExecution.outputFile.write( 'Stderr:\n' ) ; - castExecution.outputFile.write( chunk ) ; - //castExecution.outputFile.write( '\n' ) ; + if ( ! args.silence ) { process.stderr.write( chunk ) ; } + if ( ! args.amnesia ) { castExecution.outputFile.write( chunk ) ; } } ; onStdin = function( chunk ) { // Send the process stdin to the command's stdin child.stdin.write( chunk ) ; - - // tmp: - /* - console.log( 'Received STDIN:' , chunk.toString() ) ; - castExecution.outputFile.write( 'Stdin:\n' ) ; - castExecution.outputFile.write( chunk ) ; - castExecution.outputFile.write( '\n' ) ; - */ } ; // Prevent message sent to command that ignore them, then emit ECONNRESET when finished diff --git a/test/ls/one b/test/ls/one new file mode 100644 index 00000000..43dd47ea --- /dev/null +++ b/test/ls/one @@ -0,0 +1 @@ +one \ No newline at end of file diff --git a/test/ls/three b/test/ls/three new file mode 100644 index 00000000..1d19714f --- /dev/null +++ b/test/ls/three @@ -0,0 +1 @@ +three \ No newline at end of file diff --git a/test/ls/two b/test/ls/two new file mode 100644 index 00000000..64c5e588 --- /dev/null +++ b/test/ls/two @@ -0,0 +1 @@ +two \ No newline at end of file diff --git a/test/spellbook b/test/spellbook index 60566803..5e97a328 100644 --- a/test/spellbook +++ b/test/spellbook @@ -27,7 +27,15 @@ formula sh echo end: ${list} - +.write-formula + sh amnesia silence write-formula:scroll + ls ls/ + ls ls/ + + foreach scroll + sh + echo scroll line: ${scroll} + diff --git a/test/spellcast-test.js b/test/spellcast-test.js index 4a8ddede..4779a4db 100644 --- a/test/spellcast-test.js +++ b/test/spellcast-test.js @@ -122,7 +122,7 @@ describe( "'sh' block" , function() { } ) ; } ) ; - it( "should substitute variable (aka formula) accordingly" , function( done ) { + it( "should substitute variable (aka formula) accordingly in 'sh' block" , function( done ) { cleanup( function() { @@ -137,6 +137,21 @@ describe( "'sh' block" , function() { } ) ; } ) ; + it( "should write a new formula with the output of an 'sh' block" , function( done ) { + + cleanup( function() { + + var book = new spellcast.Book( fs.readFileSync( 'spellbook' ).toString() ) ; + + book.cast( 'write-formula' , function( error ) + { + expect( error ).not.ok() ; + expect( getCastedLog( 'write-formula' ) ).to.be( 'scroll line: one\nscroll line: three\nscroll line: two\nscroll line: one\nscroll line: three\nscroll line: two\n' ) ; + done() ; + } ) ; + } ) ; + } ) ; + /* it( "should launch editor" , function( done ) {