diff --git a/esdoc-publish-html-plugin/README.md b/esdoc-publish-html-plugin/README.md index 8b76e51..9023e8c 100644 --- a/esdoc-publish-html-plugin/README.md +++ b/esdoc-publish-html-plugin/README.md @@ -23,6 +23,54 @@ To use a custom template (ex `my-template` placed in the working directory): We recommend that you base on [the original template](https://github.com/esdoc/esdoc-plugins/tree/master/esdoc-publish-html-plugin/src/Builder/template). +## Builder options + +Some of the builders have options to customize the build with. +The defaults are listed in the example below. + +```json +{ + "name": "esdoc-publish-html-plugin", + "option": { + "template": "my-template", + "globalOptions": { // optional + "headerLinks": [ // optional + { "text": "Example link", "href": "local-example-page.html#some-header", "cssClass": "my-example" }, + { "text": "Foo Bar", "href": "https://xkcd.com", "cssClass": "external-link" } + ] + }, + "builders": { + "indetifiersDoc": {}, + "indexDoc": {}, + "classDoc": {}, + "singleDoc": {}, + "fileDoc": {}, + "staticFile": {}, + "searchIndex": {}, + "sourceDoc": { + "coverageFilePath": "coverage.json" + }, + "manual": { + "badgeFileNamePatterns": [ + "(overview.*)", + "(design.*)", + "(installation.*)|(install.*)", + "(usage.*)", + "(configuration.*)|(config.*)", + "(example.*)", + "(faq.*)", + "(changelog.*)" + ] + }, + "testDoc": {}, + "testFileDoc": {} + } + } +} +``` + +If the `builders` option is missing, all default plugins will be used with default configuration. + ## LICENSE MIT diff --git a/esdoc-publish-html-plugin/src/Builder/Builder.js b/esdoc-publish-html-plugin/src/Builder/Builder.js new file mode 100644 index 0000000..ceba0dc --- /dev/null +++ b/esdoc-publish-html-plugin/src/Builder/Builder.js @@ -0,0 +1,167 @@ +/* eslint-disable max-lines */ +import fs from 'fs'; +import path from 'path'; +import IceCap from 'ice-cap'; +import NPMUtil from 'esdoc/out/src/Util/NPMUtil.js'; + +/** + * Builder base class. + */ +export default class Builder { + + /** + * Create builder base instance. + * @param {String} template - template absolute path + * @param {Taffy} data - doc object database. + * @param tags - + * @param builderOptions {object} - options/data specific to the builder. + * @param globalOptions {object} - options/data available to each builder. + */ + constructor(template, data, tags, builderOptions={}, globalOptions={}) { + this._template = template; + this._data = data; + this._tags = tags; + this._builderOptions = builderOptions; + this._globalOptions = globalOptions; + } + + /* eslint-disable no-unused-vars */ + /** + * execute building output. + * @abstract + * @param builderUtil Utility functions to build with. + * @param builderUtil.writeFile {function(html: string, filePath: string)} - to write files with. + * @param builderUtil.copyDir {function(src: string, dest: string)} - to copy directories with. + * @param builderUtil.readFile {function(filePath: string): string} - to read files with. + */ + exec({writeFile, copyDir, readFile}) { + } + + /** + * read html template. + * @param {string} fileName - template file name. + * @return {string} html of template. + * @protected + */ + _readTemplate(fileName) { + const filePath = path.resolve(this._template, `./${fileName}`); + return fs.readFileSync(filePath, {encoding: 'utf-8'}); + } + + + /** + * get output html page title. + * @param {DocObject} doc - target doc object. + * @returns {string} page title. + * @protected + */ + _getTitle(doc = '') { + const name = doc.name || doc.toString(); + + if (name) { + return `${name}`; + } else { + return ''; + } + } + + /** + * get base url html page. it is used html base tag. + * @param {string} fileName - output file path. + * @returns {string} base url. + * @protected + */ + _getBaseUrl(fileName) { + return '../'.repeat(fileName.split('/').length - 1); + } + + /** + * build common layout output. + * @return {IceCap} layout output. + * @protected + */ + _buildLayoutDoc() { + const ice = new IceCap(this._readTemplate('layout.html'), {autoClose: false}); + + const packageObj = NPMUtil.findPackage(); + if (packageObj) { + ice.text('esdocVersion', `(${packageObj.version})`); + } else { + ice.drop('esdocVersion'); + } + + ice.load('pageHeader', this._buildPageHeader()); + ice.load('nav', this._buildNavDoc()); + return ice; + } + + /** + * build common page header output. + * @return {IceCap} layout output for page header. + * @protected + */ + _buildPageHeader() { + const ice = new IceCap(this._readTemplate('header.html'), {autoClose: false}); + + let headerLinks = this._globalOptions.headerLinks; + + // If there is no headerLink configuration available, then use the old behaviour: + // insert default headerLinks based on available data. + if (!headerLinks) { + + headerLinks = []; + + headerLinks.push({ + text: "Home", + href: "./" + }); + + const existManual = this._tags.find(tag => tag.kind.indexOf('manual') === 0); + const manualIndex = this._tags.find(tag => tag.kind === 'manualIndex'); + if (!(!existManual || (manualIndex && manualIndex.globalIndex))) { + headerLinks.push({ + text: "Manual", + href: "manual/index.html", + cssClass: 'header-manual-link' + }); + } + headerLinks.push({ + text: "Reference", + href: "identifiers.html", + cssClass: 'header-reference-link' + }); + headerLinks.push({ + text: "Source", + href: "source.html", + cssClass: 'header-source-link' + }); + + const existTest = this._tags.find(tag => tag.kind.indexOf('test') === 0); + if (existTest) headerLinks.push({ + text: "Test", + href: "test.html", + cssClass: 'header-test-link' + }); + } + + // Insert all headerLinks into the template + ice.loop('headerLink', headerLinks, (i, link, ice)=>{ + ice.text('headerLink', link.text); + ice.attr('headerLink', 'href', link.href); + if (link.cssClass) ice.attr('headerLink', 'class', link.cssClass); + }); + + return ice; + } + + /** + * build common page side-nave output. + * @return {IceCap} layout output for side-nav. + * @protected + */ + _buildNavDoc() { + const html = this._readTemplate('nav.html'); + return new IceCap(html); + // TODO: maybe fill nav with something by default? + } +} \ No newline at end of file diff --git a/esdoc-publish-html-plugin/src/Builder/ClassDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/ClassDocBuilder.js index 8973fb5..9dc6dd6 100644 --- a/esdoc-publish-html-plugin/src/Builder/ClassDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/ClassDocBuilder.js @@ -6,7 +6,7 @@ import {parseExample} from './util.js'; * Class Output Builder class. */ export default class ClassDocBuilder extends DocBuilder { - exec(writeFile) { + exec({writeFile}) { const ice = this._buildLayoutDoc(); ice.autoDrop = false; const docs = this._find({kind: ['class']}); diff --git a/esdoc-publish-html-plugin/src/Builder/DocBuilder.js b/esdoc-publish-html-plugin/src/Builder/DocBuilder.js index 54e1649..c87fb09 100644 --- a/esdoc-publish-html-plugin/src/Builder/DocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/DocBuilder.js @@ -1,37 +1,28 @@ /* eslint-disable max-lines */ -import fs from 'fs'; import path from 'path'; import escape from 'escape-html'; import IceCap from 'ice-cap'; import {shorten, parseExample, escapeURLHash} from './util.js'; import DocResolver from './DocResolver.js'; -import NPMUtil from 'esdoc/out/src/Util/NPMUtil.js'; +import Builder from './Builder'; /** - * Builder base class. + * Class for building docs. */ -export default class DocBuilder { +export default class DocBuilder extends Builder { /** * create instance. * @param {String} template - template absolute path * @param {Taffy} data - doc object database. + * @param tags - Tag data. + * @param builderOptions {object} - options/data specific to the builder. + * @param globalOptions {object} - options/data available to each builder. */ - constructor(template, data, tags) { - this._template = template; - this._data = data; - this._tags = tags; + constructor(template, data, tags, builderOptions, globalOptions) { + super(template, data, tags, builderOptions, globalOptions); new DocResolver(this).resolve(); } - /* eslint-disable no-unused-vars */ - /** - * execute building output. - * @abstract - * @param {function(html: string, filePath: string)} writeFile - is called each manual. - * @param {function(src: string, dest: string)} copyDir - is called asset. - */ - exec(writeFile, copyDir) { - } /** * find doc object. @@ -131,52 +122,11 @@ export default class DocBuilder { } } - /** - * read html template. - * @param {string} fileName - template file name. - * @return {string} html of template. - * @protected - */ - _readTemplate(fileName) { - const filePath = path.resolve(this._template, `./${fileName}`); - return fs.readFileSync(filePath, {encoding: 'utf-8'}); - } - - - /** - * build common layout output. - * @return {IceCap} layout output. - * @private - */ - _buildLayoutDoc() { - const ice = new IceCap(this._readTemplate('layout.html'), {autoClose: false}); - - const packageObj = NPMUtil.findPackage(); - if (packageObj) { - ice.text('esdocVersion', `(${packageObj.version})`); - } else { - ice.drop('esdocVersion'); - } - - const existTest = this._tags.find(tag => tag.kind.indexOf('test') === 0); - ice.drop('testLink', !existTest); - - const existManual = this._tags.find(tag => tag.kind.indexOf('manual') === 0); - ice.drop('manualHeaderLink', !existManual); - - const manualIndex = this._tags.find(tag => tag.kind === 'manualIndex'); - if (manualIndex && manualIndex.globalIndex) { - ice.drop('manualHeaderLink'); - } - - ice.load('nav', this._buildNavDoc()); - return ice; - } /** * build common navigation output. * @return {IceCap} navigation output. - * @private + * @protected */ _buildNavDoc() { const html = this._readTemplate('nav.html'); @@ -503,33 +453,6 @@ export default class DocBuilder { return ice; } - /** - * get output html page title. - * @param {DocObject} doc - target doc object. - * @returns {string} page title. - * @private - */ - _getTitle(doc = '') { - const name = doc.name || doc.toString(); - - if (name) { - return `${name}`; - } else { - return ''; - } - } - - /** - * get base url html page. it is used html base tag. - * @param {string} fileName - output file path. - * @returns {string} base url. - * @protected - */ - _getBaseUrl(fileName) { - const baseUrl = '../'.repeat(fileName.split('/').length - 1); - return baseUrl; - } - /** * gat url of output html page. * @param {DocObject} doc - target doc object. diff --git a/esdoc-publish-html-plugin/src/Builder/FileDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/FileDocBuilder.js index 432117e..fa94692 100644 --- a/esdoc-publish-html-plugin/src/Builder/FileDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/FileDocBuilder.js @@ -5,7 +5,7 @@ import DocBuilder from './DocBuilder.js'; * File output builder class. */ export default class FileDocBuilder extends DocBuilder { - exec(writeFile, copyDir) { + exec({writeFile, copyDir}) { const ice = this._buildLayoutDoc(); const docs = this._find({kind: 'file'}); diff --git a/esdoc-publish-html-plugin/src/Builder/IdentifiersDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/IdentifiersDocBuilder.js index f9b0e1f..874553f 100644 --- a/esdoc-publish-html-plugin/src/Builder/IdentifiersDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/IdentifiersDocBuilder.js @@ -7,7 +7,7 @@ import {escapeURLHash} from './util'; * Identifier output builder class. */ export default class IdentifiersDocBuilder extends DocBuilder { - exec(writeFile, copyDir) { + exec({writeFile, copyDir}) { const ice = this._buildLayoutDoc(); const title = this._getTitle('Reference'); ice.load('content', this._buildIdentifierDoc()); diff --git a/esdoc-publish-html-plugin/src/Builder/IndexDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/IndexDocBuilder.js index c934c40..017da81 100644 --- a/esdoc-publish-html-plugin/src/Builder/IndexDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/IndexDocBuilder.js @@ -7,7 +7,7 @@ import {markdown} from './util.js'; * Index output builder class. */ export default class IndexDocBuilder extends DocBuilder { - exec(writeFile, copyDir) { + exec({writeFile, copyDir}) { const ice = this._buildLayoutDoc(); const title = this._getTitle('Home'); ice.load('content', this._buildIndexDoc()); diff --git a/esdoc-publish-html-plugin/src/Builder/ManualDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/ManualDocBuilder.js index 09a4013..0d8d551 100644 --- a/esdoc-publish-html-plugin/src/Builder/ManualDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/ManualDocBuilder.js @@ -1,15 +1,26 @@ import IceCap from 'ice-cap'; import path from 'path'; -import {Buffer} from 'buffer'; import cheerio from 'cheerio'; import DocBuilder from './DocBuilder.js'; -import {markdown, escapeURLHash} from './util.js'; +import {markdown} from './util.js'; + +const defaultManualRequirements = [ + '(overview.*)', + '(design.*)', + '(installation.*)|(install.*)', + '(usage.*)', + '(configuration.*)|(config.*)', + '(example.*)', + '(faq.*)', + '(changelog.*)' +]; /** * Manual Output Builder class. */ export default class ManualDocBuilder extends DocBuilder { - exec(writeFile, copyDir, readFile) { + + exec({writeFile, copyDir, readFile}) { const manuals = this._tags.filter(tag => tag.kind === 'manual'); const manualIndex = this._tags.find(tag => tag.kind === 'manualIndex'); @@ -21,10 +32,11 @@ export default class ManualDocBuilder extends DocBuilder { ice.autoDrop = false; ice.attr('rootContainer', 'class', ' manual-root'); + const badgeFileNamePatterns = this._builderOptions.badgeFileNamePatterns || defaultManualRequirements; { const fileName = 'manual/index.html'; const baseUrl = this._getBaseUrl(fileName); - const badge = this._writeBadge(manuals, writeFile); + const badge = this._writeBadge(manuals, writeFile, badgeFileNamePatterns); ice.load('content', this._buildManualCardIndex(manuals, manualIndex, badge), IceCap.MODE_WRITE); ice.load('nav', this._buildManualNav(manuals), IceCap.MODE_WRITE); ice.text('title', 'Manual', IceCap.MODE_WRITE); @@ -55,26 +67,17 @@ export default class ManualDocBuilder extends DocBuilder { } /** - * this is + * This creates a badge indicating the completeness of the manual. * @param {manual[]} manuals * @param {function(filePath:string, content:string)} writeFile + * @param badgeFileNamePatterns The patterns to look for when checking for documentation. * @returns {boolean} * @private */ - _writeBadge(manuals, writeFile) { - const specialFileNamePatterns = [ - '(overview.*)', - '(design.*)', - '(installation.*)|(install.*)', - '(usage.*)', - '(configuration.*)|(config.*)', - '(example.*)', - '(faq.*)', - '(changelog.*)' - ]; + _writeBadge(manuals, writeFile, badgeFileNamePatterns) { let count = 0; - for (const pattern of specialFileNamePatterns) { + for (const pattern of badgeFileNamePatterns) { const regexp = new RegExp(pattern, 'i'); for (const manual of manuals) { const fileName = path.parse(manual.name).name; @@ -85,7 +88,9 @@ export default class ManualDocBuilder extends DocBuilder { } } - if (count !== specialFileNamePatterns.length) return false; + // TODO: instead of all-or-nothing, we could "grade" the documentation based + // on a percentage of matching file-name patterns. + if (count !== badgeFileNamePatterns.length) return false; let badge = this._readTemplate('image/manual-badge.svg'); badge = badge.replace(/@value@/g, 'perfect'); diff --git a/esdoc-publish-html-plugin/src/Builder/SearchIndexBuilder.js b/esdoc-publish-html-plugin/src/Builder/SearchIndexBuilder.js index ed74662..644dfbd 100644 --- a/esdoc-publish-html-plugin/src/Builder/SearchIndexBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/SearchIndexBuilder.js @@ -4,7 +4,7 @@ import DocBuilder from './DocBuilder.js'; * Search index of identifier builder class. */ export default class SearchIndexBuilder extends DocBuilder { - exec(writeFile, copyDir) { + exec({writeFile, copyDir}) { const searchIndex = []; const docs = this._find({}); diff --git a/esdoc-publish-html-plugin/src/Builder/SingleDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/SingleDocBuilder.js index 7782efd..361c69b 100644 --- a/esdoc-publish-html-plugin/src/Builder/SingleDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/SingleDocBuilder.js @@ -6,7 +6,7 @@ import DocBuilder from './DocBuilder.js'; * "single" means function, variable, typedef, external, etc... */ export default class SingleDocBuilder extends DocBuilder { - exec(writeFile, copyDir) { + exec({writeFile, copyDir}) { const ice = this._buildLayoutDoc(); ice.autoClose = false; diff --git a/esdoc-publish-html-plugin/src/Builder/SourceDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/SourceDocBuilder.js index 0e522bb..bf5325f 100644 --- a/esdoc-publish-html-plugin/src/Builder/SourceDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/SourceDocBuilder.js @@ -7,8 +7,8 @@ import {dateForUTC} from './util.js'; * Source output html builder class. */ export default class SourceDocBuilder extends DocBuilder { - exec(writeFile, copyDir, coverage) { - this._coverage = coverage; + exec({writeFile, copyDir}) { + this._loadCoverage(this._builderOptions.coverageFilePath || "coverage.json"); const ice = this._buildLayoutDoc(); const fileName = 'source.html'; const baseUrl = this._getBaseUrl(fileName); @@ -21,6 +21,22 @@ export default class SourceDocBuilder extends DocBuilder { writeFile(fileName, ice.html); } + /** + * Load the coverage json file. + * Exits quietly if none could be loaded, coverage is set to null and will be ignored. + * @param coverageFilePath The file path to load. + * @private + */ + _loadCoverage(coverageFilePath) { + let coverage = null; + try { + coverage = JSON.parse(readFile(coverageFilePath)); + } catch (e) { + // nothing, coverage is optional. + } + this._coverage = coverage; + } + /** * build source list output html. * @returns {string} html of source list. diff --git a/esdoc-publish-html-plugin/src/Builder/StaticFileBuilder.js b/esdoc-publish-html-plugin/src/Builder/StaticFileBuilder.js index caaa387..54e0c7b 100644 --- a/esdoc-publish-html-plugin/src/Builder/StaticFileBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/StaticFileBuilder.js @@ -5,7 +5,7 @@ import DocBuilder from './DocBuilder.js'; * Static file output builder class. */ export default class StaticFileBuilder extends DocBuilder { - exec(writeFile, copyDir) { + exec({writeFile, copyDir}) { copyDir(path.resolve(this._template, 'css'), './css'); copyDir(path.resolve(this._template, 'script'), './script'); copyDir(path.resolve(this._template, 'image'), './image'); diff --git a/esdoc-publish-html-plugin/src/Builder/TestDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/TestDocBuilder.js index 41c6fa6..b7231c1 100644 --- a/esdoc-publish-html-plugin/src/Builder/TestDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/TestDocBuilder.js @@ -5,7 +5,7 @@ import DocBuilder from './DocBuilder.js'; * Test file output html builder class. */ export default class TestDocBuilder extends DocBuilder { - exec(writeFile, copyDir) { + exec({writeFile, copyDir}, _) { const testDoc = this._find({kind: 'test'})[0]; if (!testDoc) return; diff --git a/esdoc-publish-html-plugin/src/Builder/TestFileDocBuilder.js b/esdoc-publish-html-plugin/src/Builder/TestFileDocBuilder.js index 769b993..f002b27 100644 --- a/esdoc-publish-html-plugin/src/Builder/TestFileDocBuilder.js +++ b/esdoc-publish-html-plugin/src/Builder/TestFileDocBuilder.js @@ -5,8 +5,11 @@ import DocBuilder from './DocBuilder.js'; * File output html builder class. */ export default class TestFileDocBuilder extends DocBuilder { - exec(writeFile, copyDir) { - const ice = this._buildLayoutDoc(); + exec({writeFile, copyDir}) { + const testDoc = this._find({kind: 'test'})[0]; + if (!testDoc) return; + + const ice = this._buildLayoutDoc(); const docs = this._find({kind: 'testFile'}); for (const doc of docs) { diff --git a/esdoc-publish-html-plugin/src/Builder/template/css/manual.css b/esdoc-publish-html-plugin/src/Builder/template/css/manual.css index 138a07f..b178dd5 100644 --- a/esdoc-publish-html-plugin/src/Builder/template/css/manual.css +++ b/esdoc-publish-html-plugin/src/Builder/template/css/manual.css @@ -1,4 +1,4 @@ -.github-markdown .manual-toc { +.manual-toc { padding-left: 0; } @@ -73,7 +73,7 @@ padding-right: 0.75em; } -.github-markdown .manual-toc-title a { +.manual-toc-title a { color: inherit; } diff --git a/esdoc-publish-html-plugin/src/Builder/template/header.html b/esdoc-publish-html-plugin/src/Builder/template/header.html new file mode 100644 index 0000000..87827a7 --- /dev/null +++ b/esdoc-publish-html-plugin/src/Builder/template/header.html @@ -0,0 +1,10 @@ + + + +
+
+
+
-
-
-