├── .gitignore ├── LICENSE ├── README.md ├── demo-workshopper.js ├── exercises ├── menu.json └── test_exercise │ ├── exercise.js │ ├── problem.md │ └── solution │ ├── index.html │ └── solution.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | npm-debug.log 4 | dump.rdb 5 | node_modules 6 | results.tap 7 | results.xml 8 | npm-shrinkwrap.json 9 | .DS_Store 10 | */.DS_Store 11 | */*/.DS_Store 12 | ._* 13 | */._* 14 | */*/._* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Lin Clark 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | demo-workshopper 2 | ================ 3 | 4 | An example Node.js workshopper lesson. 5 | -------------------------------------------------------------------------------- /demo-workshopper.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const workshopper = require('workshopper'), 4 | path = require('path') 5 | 6 | function fpath (f) { 7 | return path.join(__dirname, f) 8 | } 9 | 10 | workshopper({ 11 | name : 'demo-workshopper', 12 | title : 'Demo Workshopper', 13 | subtitle : 'Learn how to create a workshopper lesson', 14 | appDir : __dirname, 15 | menuItems : [], 16 | exerciseDir : fpath('./exercises/') 17 | }) 18 | -------------------------------------------------------------------------------- /exercises/menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | "TEST_EXERCISE" 3 | ] -------------------------------------------------------------------------------- /exercises/test_exercise/exercise.js: -------------------------------------------------------------------------------- 1 | var through2 = require('through2'); 2 | var hyperquest = require('hyperquest'); 3 | var bl = require('bl'); 4 | var exercise = require('workshopper-exercise')(); 5 | var filecheck = require('workshopper-exercise/filecheck'); 6 | var execute = require('workshopper-exercise/execute'); 7 | var comparestdout = require('workshopper-exercise/comparestdout'); 8 | 9 | // the output will be long lines so make the comparison take that into account 10 | exercise.longCompareOutput = true; 11 | 12 | // checks that the submission file actually exists 13 | exercise = filecheck(exercise); 14 | 15 | // execute the solution and submission in parallel with spawn() 16 | exercise = execute(exercise); 17 | 18 | function rndport() { 19 | return 1024 + Math.floor(Math.random() * 64511); 20 | } 21 | 22 | // set up the data file to be passed to the submission 23 | exercise.addSetup(function (mode, callback) { 24 | 25 | this.submissionPort = rndport(); 26 | this.solutionPort = this.submissionPort + 1; 27 | 28 | this.submissionArgs = [this.submissionPort]; 29 | this.solutionArgs = [this.solutionPort]; 30 | 31 | process.nextTick(callback); 32 | }); 33 | 34 | // add a processor for both run and verify calls, added *before* 35 | // the comparestdout processor so we can mess with the stdouts 36 | exercise.addProcessor(function (mode, callback) { 37 | 38 | this.submissionStdout.pipe(process.stdout); 39 | 40 | // replace stdout with our own streams 41 | this.submissionStdout = through2(); 42 | if (mode == 'verify') { 43 | this.solutionStdout = through2(); 44 | } 45 | 46 | setTimeout(query.bind(this, mode), 500); 47 | 48 | process.nextTick(function () { 49 | callback(null, true) 50 | }); 51 | }); 52 | 53 | // delayed for 500ms to wait for servers to start so we can start 54 | // playing with them 55 | function query (mode) { 56 | var exercise = this 57 | 58 | function verify (port, stream) { 59 | 60 | var url = 'http://localhost:' + port; 61 | 62 | function error (err) { 63 | exercise.emit('fail', 'Error connecting to http://localhost:' + port + ': ' + err.code) 64 | } 65 | 66 | hyperquest.get(url) 67 | .on('error', error) 68 | .pipe(bl(function (err, data) { 69 | 70 | if (err) 71 | return stream.emit('error', err) 72 | 73 | stream.write(data.toString() + '\n'); 74 | stream.end(); 75 | })); 76 | } 77 | 78 | verify(this.submissionPort, this.submissionStdout) 79 | 80 | if (mode == 'verify') { 81 | verify(this.solutionPort, this.solutionStdout); 82 | } 83 | } 84 | 85 | // compare stdout of solution and submission 86 | exercise = comparestdout(exercise) 87 | 88 | module.exports = exercise; 89 | -------------------------------------------------------------------------------- /exercises/test_exercise/problem.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linclark/demo-workshopper/9346e40e9877552a026c7a2fe9e78950abb52a1d/exercises/test_exercise/problem.md -------------------------------------------------------------------------------- /exercises/test_exercise/solution/index.html: -------------------------------------------------------------------------------- 1 | 2 |