├── .gitignore ├── test ├── comments ├── basic ├── expected │ ├── comments │ ├── basic │ ├── integration │ └── error ├── makefile └── index.js ├── .npmignore ├── images └── tap-spec.png ├── bin.js ├── .travis.yml ├── package.json ├── LICENCE ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | -------------------------------------------------------------------------------- /test/comments: -------------------------------------------------------------------------------- 1 | ## Description 2 | #test 3 | output 4 | #test 2 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | images 3 | .nyc_output 4 | coverage 5 | .travis.yml 6 | -------------------------------------------------------------------------------- /images/tap-spec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArnaudRinquin/make2tap/HEAD/images/tap-spec.png -------------------------------------------------------------------------------- /test/basic: -------------------------------------------------------------------------------- 1 | ## Task one 2 | # step 1-1 3 | # step 1-2 4 | ## Task two 5 | # step 2-1 6 | # step 2-2 7 | -------------------------------------------------------------------------------- /test/expected/comments: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Description 3 | ok 1 - test 4 | output 5 | ok 2 - test 2 6 | 1..2 7 | -------------------------------------------------------------------------------- /test/expected/basic: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Task one 3 | ok 1 - step 1-1 4 | ok 2 - step 1-2 5 | # Task two 6 | ok 3 - step 2-1 7 | ok 4 - step 2-2 8 | 1..4 9 | -------------------------------------------------------------------------------- /test/expected/integration: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Clean 3 | ok 1 - Clean dist 4 | cleaning /dist 5 | # Compile 6 | ok 2 - First step 7 | some output 8 | ok 3 - Second step 9 | other output 10 | 1..3 11 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var split = require('split'); 4 | var make2tap = require('./index'); 5 | 6 | process.stdin 7 | .pipe(split()) 8 | .pipe(make2tap()) 9 | .pipe(process.stdout); 10 | -------------------------------------------------------------------------------- /test/expected/error: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Successful task 3 | ok 1 - This works fine 4 | working 5 | # Failing task 6 | ok 2 - Try something that works 7 | that was easy 8 | not ok 3 - Trying something desperate 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.2" 4 | - "stable" 5 | addons: 6 | code_climate: 7 | repo_token: 3f287ed2ed77830d99264010e6a99b9b31b664c3b8f5575e17ddc55b15cee73d 8 | after_script: 9 | - npm run report-coverage 10 | -------------------------------------------------------------------------------- /test/makefile: -------------------------------------------------------------------------------- 1 | compile: 2 | ## Compile 3 | 4 | # First step 5 | @echo 'some output' 6 | 7 | # Second step 8 | @echo 'other output' 9 | 10 | clean: 11 | 12 | ## Clean 13 | # Clean dist 14 | @echo 'cleaning /dist' 15 | 16 | break: 17 | ## Successful task 18 | 19 | # This works fine 20 | @echo 'working' 21 | 22 | ## Failing task 23 | 24 | # Try something that works 25 | @echo 'that was easy' 26 | 27 | # Trying something desperate 28 | @thiswillbreak 29 | 30 | break-direct: 31 | @thiswillbreak 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "make2tap", 3 | "version": "0.1.3", 4 | "description": "Leverage TAP to transform your ugly make outputs into nice readable ones", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/ArnaudRinquin/make2tap.git" 9 | }, 10 | "bin": { 11 | "make2tap": "./bin.js" 12 | }, 13 | "scripts": { 14 | "test": "tap test/index.js -R tap --cov --coverage-report=lcov", 15 | "report-coverage": "codeclimate-test-reporter < coverage/lcov.info" 16 | }, 17 | "keywords": [ 18 | "tap", 19 | "make", 20 | "makefile" 21 | ], 22 | "author": "Arnaud Rinquin", 23 | "license": "MIT", 24 | "dependencies": { 25 | "split": "^1.0.0", 26 | "through2": "^2.0.0" 27 | }, 28 | "devDependencies": { 29 | "codeclimate-test-reporter": "^0.3.1", 30 | "concat-stream": "^1.5.1", 31 | "tap": "^5.4.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Songkick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # make2tap 2 | 3 | [![Build Status](https://travis-ci.org/ArnaudRinquin/make2tap.svg?branch=master)](https://travis-ci.org/ArnaudRinquin/make2tap) [![Code Climate](https://codeclimate.com/github/ArnaudRinquin/make2tap/badges/gpa.svg)](https://codeclimate.com/github/ArnaudRinquin/make2tap) [![Test Coverage](https://codeclimate.com/github/ArnaudRinquin/make2tap/badges/coverage.svg)](https://codeclimate.com/github/ArnaudRinquin/make2tap/coverage) 4 | 5 | 6 | Leverage [`TAP`](https://testanything.org/) to transform your ugly `make` outputs into nice readable ones using any [TAP reporter](https://www.npmjs.com/search?q=TAP++reporter) like `tap-spec` or `tap-dot`. 7 | 8 | ## Usage 9 | 10 | Assuming `make2tap` is in your path (using `npm scripts`), simply pipe `make` output to it 11 | 12 | Assuming this is your `makefile` 13 | 14 | ``` 15 | clean: 16 | ## Clean 17 | # remove /dist 18 | @rm -rf dist 19 | 20 | build: 21 | ## Build js files 22 | # compile to js 23 | @echo 'compile output...' 24 | # minify 25 | @echo 'minify output...' 26 | 27 | ``` 28 | 29 | Then 30 | 31 | ```bash 32 | make clean compile 2>&1 | make2tap 33 | ``` 34 | 35 | Note: `2>&1` is quite important here, it allows `make` stderr stream to be piped to `make2tap` as well so we can handle errors. 36 | 37 | Ouputs: 38 | 39 | ``` 40 | TAP version 13 41 | # Clean 42 | ok 1 - remove /dist 43 | TAP version 13 44 | # Build js files 45 | ok 2 - compile to js 46 | compile output... 47 | ok 3 - minify 48 | minify output... 49 | 1..3 50 | ``` 51 | 52 | Of course, you should pipe it to the [TAP reporter](https://www.npmjs.com/search?q=TAP++reporter) of your choice: 53 | 54 | ```bash 55 | make clean build | make2tap | tap-spec 56 | ``` 57 | 58 | ![tap-spec](./images/tap-spec.png) 59 | 60 | ## Comment syntax 61 | 62 | `make2tap` understand a few kinds of lines: 63 | 64 | * `## Task title`: as a TAP Diagnostic, aka a _section_, or _title_ 65 | * `# Step name`: as a TAP test line, which will use a _single step_ 66 | * `anything else`: as regular output, not handled as part of the result but still shown, will us it to show commands output. 67 | 68 | You'll probably want to structure your makefile as such: 69 | 70 | ``` 71 | build: 72 | ## Build js files 73 | 74 | # compile to js 75 | @echo 'compile output...' 76 | 77 | # minify 78 | @echo 'minify output...' 79 | ``` 80 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var tap = require('tap'); 5 | 6 | function getContent(testName) { 7 | return fs.readFileSync(path.join(__dirname, 'expected', testName)).toString(); 8 | } 9 | 10 | tap.test('make2tap CLI', function(test){ 11 | test.plan(4); 12 | 13 | test.test('Basic', function(test){ 14 | 15 | test.plan(3); 16 | exec('cat basic | ../bin.js', { 17 | cwd: __dirname 18 | }, function(error, stdout, stderr){ 19 | test.equal(error, null, 'exists without error'); 20 | test.equal('', stderr, 'do not output on stderr'); 21 | test.equal(stdout, getContent('basic'), 'outputs expected TAP'); 22 | }); 23 | }); 24 | 25 | test.test('With comments', function(test){ 26 | 27 | test.plan(3); 28 | exec('cat comments | ../bin.js', { 29 | cwd: __dirname 30 | }, function(error, stdout, stderr){ 31 | test.equal(error, null, 'exists without error'); 32 | test.equal('', stderr, 'do not output on stderr'); 33 | test.equal(stdout, getContent('comments'), 'outputs expected TAP'); 34 | }); 35 | }); 36 | 37 | test.test('Proper make integration', function(test){ 38 | 39 | test.plan(3); 40 | exec('make clean compile 2>&1 | ../bin.js', { 41 | cwd: __dirname 42 | }, function(error, stdout, stderr){ 43 | test.equal(error, null, 'exists without error'); 44 | test.equal('', stderr, 'do not output on stderr'); 45 | test.equal(stdout, getContent('integration'), 'outputs expected TAP'); 46 | }); 47 | }); 48 | 49 | test.test('Error handling', function(test){ 50 | 51 | test.plan(8); 52 | exec('make break 2>&1 | ../bin.js', { 53 | cwd: __dirname 54 | }, function(error, stdout, stderr){ 55 | test.notEqual(error, null, 'exits with error'); 56 | test.equal('', stderr, 'do not output on stderr'); 57 | test.ok(stdout.indexOf('# Successful task') >= 0, 'outputs successful steps'); 58 | test.ok(stdout.indexOf('ok 1 - This works fine') >= 0, 'outputs sucessful tests'); 59 | test.ok(stdout.startsWith(getContent('error')), 'contains expected TAP'); 60 | test.ok(stdout.indexOf('thiswillbreak') >= 0, 'outputs original stderr'); 61 | test.ok(stdout.indexOf('1..3') >= 0, 'has proper test count'); 62 | }); 63 | 64 | test.test('when the first intput is an error', function(test){ 65 | test.plan(4); 66 | exec('make break-direct 2>&1 | ../bin.js', { 67 | cwd: __dirname 68 | }, function(error, stdout, stderr){ 69 | test.notEqual(error, null, 'exits with error'); 70 | test.equal('', stderr, 'do not output on stderr'); 71 | test.ok(stdout.indexOf('thiswillbreak') >= 0, 'outputs original stderr'); 72 | test.ok(stdout.indexOf('1..0') >= 0, 'has proper test count'); 73 | }); 74 | }) 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var split = require('split'); 2 | var through2 = require('through2'); 3 | 4 | function isEmpty(line) { 5 | return !line.trim().length; 6 | } 7 | 8 | function isHeader(line) { 9 | return line.startsWith('##'); 10 | } 11 | 12 | function isStep(line) { 13 | return line.startsWith('#'); 14 | } 15 | 16 | function isError(line) { 17 | return line.startsWith('make: '); 18 | } 19 | 20 | function processHeader(stream, line) { 21 | writeLine(stream, '# ' + line.slice(2).trim()); 22 | } 23 | 24 | function writeStep(stream, index, line, passing) { 25 | writeLine(stream, (passing ? '': 'not ') + 'ok ' + index + ' - ' + line.slice(1).trim()); 26 | } 27 | 28 | function processComment(stream, line) { 29 | var trimmed = line.trim(); 30 | writeLine(stream, trimmed); 31 | } 32 | 33 | function writeLine(stream, line) { 34 | stream.push(line + '\n'); 35 | } 36 | 37 | function processError(stream, line) { 38 | writeLine(stream, line.slice(5).trim()); 39 | } 40 | 41 | module.exports = function(){ 42 | 43 | var initialised = false; 44 | var steps = 0; 45 | var buf = []; 46 | var previousStep; 47 | var failed = false; 48 | 49 | 50 | function emptyBuffer(stream) { 51 | buf.forEach(processComment.bind(null, stream)); 52 | buf = []; 53 | } 54 | 55 | function validatePreviousStep(stream) { 56 | if (previousStep) { 57 | writeStep(stream, ++steps, previousStep, true); 58 | } 59 | emptyBuffer(stream) 60 | } 61 | 62 | function convertLine(chunk, enc, callback) { 63 | var line = chunk.toString(); 64 | 65 | if (isEmpty(line)) { 66 | callback(); 67 | return; 68 | } 69 | 70 | if (!initialised) { 71 | initialised = true; 72 | writeLine(this, 'TAP version 13') 73 | } 74 | 75 | if (isError(line)) { 76 | if (!failed) { 77 | if (previousStep) { 78 | writeStep(this, ++steps, previousStep, false); 79 | } 80 | previousStep = null; 81 | emptyBuffer(this); 82 | } 83 | failed = true; 84 | processError(this, line); 85 | callback(); 86 | return; 87 | } 88 | 89 | if (isHeader(line)) { 90 | validatePreviousStep(this); 91 | previousStep = null; 92 | processHeader(this, line); 93 | callback(); 94 | return; 95 | } 96 | 97 | if (isStep(line)) { 98 | validatePreviousStep(this); 99 | previousStep = line; 100 | callback(); 101 | return; 102 | } 103 | 104 | // comment line 105 | buf.push(line); 106 | callback(); 107 | } 108 | 109 | function finish(callback) { 110 | validatePreviousStep(this); 111 | writeLine(this, '1..' + steps); 112 | if (failed) { 113 | process.exit(1); 114 | } 115 | } 116 | 117 | return through2(convertLine, finish); 118 | } 119 | --------------------------------------------------------------------------------