Skip to content

Commit

Permalink
feat(CLI): improve handling of report directory overrides
Browse files Browse the repository at this point in the history
Add options and behavior to the CLI
- `-v` nows outputs the version number (same as `--version`)
- check the presence of existing report files, and abort by default
- `--force` option to override any existing files
- `--subdir` option to create the report in a sub-directory named after the input EPUB

Also add some integration tests:
- utility to run the CLI in a spawned process
- tests based on Jest snapshots

Closes #17, #28
  • Loading branch information
rdeltour committed Sep 28, 2017
1 parent e809488 commit e4ab069
Show file tree
Hide file tree
Showing 10 changed files with 849 additions and 13 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"xpath": "^0.0.24"
},
"devDependencies": {
"cross-spawn": "^5.1.0",
"eslint": "^3.19.0",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-plugin-import": "^2.3.0",
Expand Down
61 changes: 49 additions & 12 deletions src/cli/cli.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,84 @@
'use strict';

const ace = require('../core/ace.js');

const fs = require('fs');
const meow = require('meow');
const winston = require('winston');
const path = require('path');

const cli = meow(`
Usage
$ ace [options] <input>
Usage: ace [options] <input>
Options:
-h, --help output usage information
-v, --version output the version number
Options
-o, --outdir <path> save final reports to the specified directory
-t, --tempdir <path> specify a custom directory to store the temporary reports
-v, --version print the version number
-b, --verbose display verbose output
-f, --force override any existing output file or directory
--subdir output reports to a sub-directory named after the input EPUB
-V, --verbose display verbose output
-s, --silent do not display any output
Examples
$ ace -o out ~/Documents/book.epub
`, {
alias: {
f: 'force',
h: 'help',
o: 'outdir',
s: 'silent',
t: 'tempdir',
b: 'verbose',
s: 'silent'
v: 'version',
V: 'verbose',
},
boolean: ['force', 'verbose', 'silent', 'subdir'],
string: ['outdir', 'tempdir'],
});

// Check that an EPUB path is specified
if (cli.input.length === 0) {
console.log('Input required');
cli.showHelp();
process.exit(1);
cli.showHelp(1);
}

// Check that output directories can be overridden
let outdir = cli.flags.outdir;
if (outdir) {
if (cli.flags.subdir) {
outdir = path.join(outdir, path.parse(cli.input[0]).name);
}
if (!cli.flags.force) {
const overrides = ['ace.json', 'report.html', 'data', 'js']
.map(file => path.join(outdir, file))
.filter(fs.existsSync);
if (overrides.length > 0) {
console.log(`\
Output directory is not empty.
Running Ace would override the following files or directories:
${overrides.map(file => ` - ${file}`).join('\n')}
Use option --force to override.
`);
process.exit(1);
}
}
}

// finally, invoke Ace
ace(cli.input[0], {
cwd: cli.flags.cwd || process.cwd(),
outdir: cli.flags.outdir,
outdir,
tmpdir: cli.flags.tempdir,
verbose: cli.flags.verbose,
silent: cli.flags.silent,
jobId: '',
})
.catch((err) => {
winston.error(err.message);
if (err) console.log(err.message);
process.exit(1);
});
69 changes: 69 additions & 0 deletions tests/__tests__/__snapshots__/cli.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`test existing output 1`] = `
"Output directory is not empty.
Running Ace would override the following files or directories:
- report/ace.json
- report/report.html
- report/js
Use option --force to override.
"
`;

exports[`test help 1`] = `
"
Ace by DAISY, an Accessibility Checker for EPUB
Usage: ace [options] <input>
Options:
-h, --help output usage information
-v, --version output the version number
-o, --outdir <path> save final reports to the specified directory
-t, --tempdir <path> specify a custom directory to store the temporary reports
-f, --force override any existing output file or directory
--subdir output reports to a sub-directory named after the input EPUB
-V, --verbose display verbose output
-s, --silent do not display any output
Examples
$ ace -o out ~/Documents/book.epub
"
`;
exports[`test no input 1`] = `
"Input required
Ace by DAISY, an Accessibility Checker for EPUB
Usage: ace [options] <input>
Options:
-h, --help output usage information
-v, --version output the version number
-o, --outdir <path> save final reports to the specified directory
-t, --tempdir <path> specify a custom directory to store the temporary reports
-f, --force override any existing output file or directory
--subdir output reports to a sub-directory named after the input EPUB
-V, --verbose display verbose output
-s, --silent do not display any output
Examples
$ ace -o out ~/Documents/book.epub
"
`;
exports[`test unexisting input 1`] = `
"error: Couldn’t find EPUB file 'unexisting.epub'
"
`;
51 changes: 51 additions & 0 deletions tests/__tests__/cli.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const ace = require('../runAceCLI');
const pkg = require('../../package');

const path = require('path');


test('test no input', () => {
const { stdout, stderr, status } = ace([]);
expect(status).toBe(1);
expect(stderr).toBe('');
expect(stdout).toMatchSnapshot();
});

test('test help', () => {
const { stdout, stderr, status } = ace(['-h']);
expect(status).toBe(0);
expect(stderr).toBe('');
expect(stdout).toMatchSnapshot();
});

test('test version -v', () => {
const { stdout, stderr, status } = ace(['-v']);
expect(status).toBe(0);
expect(stderr).toBe('');
expect(stdout.trim()).toBe(pkg.version);
});

test('test version --version', () => {
const { stdout, stderr, status } = ace(['--version']);
expect(status).toBe(0);
expect(stderr).toBe('');
expect(stdout.trim()).toBe(pkg.version);
});

test('test unexisting input', () => {
const { stdout, stderr, status } = ace(['unexisting.epub']);
expect(status).toBe(1);
expect(stdout.trim()).toBe('');
expect(stderr).toMatchSnapshot();
});

test('test existing output', () => {
const { stdout, stderr, status } = ace(['-o', 'report', 'foo'], {
cwd: path.resolve(__dirname, '../data'),
});
expect(status).toBe(1);
expect(stderr).toBe('');
expect(stdout).toMatchSnapshot();
});
61 changes: 61 additions & 0 deletions tests/data/report/ace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"@type": "earl:report",
"@context": "http://ace.daisy.org/ns/ace-report.jsonld",
"earl:assertedBy": {
"@type": "earl:software",
"doap:name": "DAISY Ace",
"doap:description": "DAISY Accessibility Checker for EPUB",
"doap:homepage": "http://ace.daisy.org",
"doap:created": "2017-07-01",
"doap:release": {
"doap:revision": "v0.2.0"
}
},
"dct:date": "9/28/2017, 3:02:56 PM",
"dct:title": "ACE Report",
"dct:description": "Accessibility Checker Report",
"earl:testSubject": {
"url": "base-epub-30.epub",
"metadata": {
"dc:title": "Minimal EPUB 3.0",
"dc:language": "en",
"dc:identifier": "NOID",
"dcterms:modified": "2017-01-01T00:00:01Z"
}
},
"a11y-metadata": {
"missing": [
"schema:accessMode",
"schema:accessibilityFeature",
"schema:accessibilityHazard",
"schema:accessibilitySummary",
"schema:accessModeSufficient",
"schema:accessibilityAPI",
"schema:accessibilityControl",
"a11y:certifiedBy"
],
"empty": [],
"present": []
},
"outlines": {
"toc": "<ol xmlns=\"http://www.w3.org/1999/xhtml\">\n<li>content 001</li>\n</ol>",
"headings": "<ul><li><span class=\"toc-h1\">Loomings</span></li></ul>",
"html": "<ol><li><ol><li>Loomings</li></ol></li></ol>"
},
"assertions": [
{
"@type": "earl:assertion",
"assertions": [],
"earl:testSubject": {
"url": "content_001.xhtml",
"dct:title": "Minimal EPUB"
}
}
],
"earl:result": {
"earl:outcome": "pass"
},
"data": {
"images": []
}
}
100 changes: 100 additions & 0 deletions tests/data/report/js/ace-report-viewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
function AceReport(data) {
// lists of unique values for the UI filtering
this.ruleFilter = ["all"];
this.impactFilter = ["all"];
this.fileFilter = ["all"];
// maps filepath to titles
this.fileTitles = {};

this.metadata = this.parseMetadata(data);
this.a11ymetadata = data["a11y-metadata"];
this.flatData = [];
this.flattenData(data);
}

// returns {"filterName": [labels,... ],...}
AceReport.prototype.getFilters = function() {
return [
{"name": "rule", "values": this.ruleFilter},
{"name": "impact", "values": this.impactFilter},
{"name": "file", "values": this.fileFilter}
];
}

AceReport.prototype.getMetadata = function(filename) {
return this.metadata;
}
AceReport.prototype.getA11yMetadata = function() {
return this.a11ymetadata;
}
AceReport.prototype.getTitleForFile = function(filename) {
if (this.fileTitles[filename] === "") {
return "No title";
}
return this.fileTitles[filename];
}

AceReport.prototype.getAllViolations = function() {
return this.flatData;
}

// expects:
// {"rule": "all", "impact": "serious", "file": "p1.xhtml"}
AceReport.prototype.filterViolations = function(filters) {
var filteredList = [];
this.flatData.forEach(function(item) {
if (
(filters["rule"] == "all" || item["rule"] === filters["rule"])
&&
(filters["impact"] == "all" || item["impact"] === filters["impact"])
&&
(filters["file"] == "all" || item["file"] === filters["file"])
)
{
filteredList.push(item);
}
});
return filteredList;
}

// make a flat list of the violations
AceReport.prototype.flattenData = function(data) {
var thiz = this;
data.assertions.forEach(function(assertion) {
var filename = assertion["earl:testSubject"]["url"];
var filetitle = assertion["earl:testSubject"]["dct:title"];
assertion.assertions.forEach(function(item) {
var obj = {
"file": filename,
"engine": item["earl:assertedBy"],
"kburl": item["earl:test"]["help"]["url"],
"kbtitle": item["earl:test"]["help"]["title"],
"rule": item["earl:test"]["dct:title"],
"desc": item["earl:result"]["dct:description"],
"pointer": item["earl:result"]["earl:pointer"],
"impact": item["earl:test"]["earl:impact"],
"location": filename + "#epubcfi(" + item["earl:result"]["earl:pointer"]["cfi"] + ")"
};
thiz.flatData.push(obj);

thiz.addIfUnique(obj["file"], thiz.fileFilter);
thiz.addIfUnique(obj["rule"], thiz.ruleFilter);
thiz.addIfUnique(obj["impact"], thiz.impactFilter);
});
thiz.fileTitles[filename] = filetitle;
});
}

AceReport.prototype.parseMetadata = function(data) {
return {
"softwareName": data["earl:assertedBy"]["doap:name"],
"softwareVersion": data["earl:assertedBy"]["doap:release"]["doap:revision"],
"pubUrl": data["earl:testSubject"]["url"],
"reportDate": data["dct:date"]
};
}
AceReport.prototype.addIfUnique = function(value, list) {
if (list.indexOf(value) == -1) {
list.push(value);
}
}
Loading

0 comments on commit e4ab069

Please sign in to comment.