san650/ember-cli-page-object

View on GitHub
docs/index.js

Summary

Maintainability
C
7 hrs
Test Coverage
'use strict';
/* eslint no-console:0 */
 
/*
* Parses the JavaScript files in `addon/src/` and
* creates documentation Markdown files for use in GitHub Pages.
* The docs files are written to the 'gh-pages' branch
* in the directory 'api/methods'.
*/
 
const fs = require('fs');
const cp = require('child_process');
const path = require('path');
const ncp = require('ncp');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
const RSVP = require('rsvp');
const packageJSON = require('../addon/package.json');
const walkSync = require('walk-sync');
 
const addonDir = path.resolve('../addon');
 
/* Runs an arbitrary shell command.
*
* @param {string} command - The shell command to execute
* @returns {Promise} (resolves {string}, rejects {Error}) A promise that resolves with the stdout of the command, or rejects with an Error that has the stderr as its message.
*/
function execCmd(command) {
return new RSVP.Promise(function (resolve, reject) {
var result;
 
console.log('> ' + command + '\n');
try {
result = cp.execSync(command);
resolve(result.toString());
} catch (error) {
reject(error);
}
});
}
 
/* Parses a source file for JSDoc comments.
*
* @param {string} filePath - The path to the source JavaScript file
* @returns {Promise} (resolves {string}) A promise that resolves with the Markdown text representation
*/
function parseSourceFile(filePath) {
return execCmd(
'./node_modules/.bin/documentation build "' +
filePath +
'" -f md --shallow'
);
}
 
/* Takes Markdown and adds yml frontmatter and a table of contents, and adds 1 to
* the level of headings.
*
* @param {string} markdown - Unmassaged markdown.
* @returns {Promise} (resolves {string}) A promise that resolves with the massaged Markdown text representation.
*/
Function `massageMarkdown` has 32 lines of code (exceeds 25 allowed). Consider refactoring.
function massageMarkdown(markdown, options) {
var headerRegex = /^#{1,6} /;
var h2Regex = /^## (.*)$/;
var lines = markdown.split('\n');
var tableOfContents = ['### Methods\n'];
// The jekyll yml frontmatter
var frontmatter = ['---', 'layout: page', 'title: ' + options.title, '---'];
var processedMarkdown;
var h2;
var tocLine;
 
function dasherize(str) {
return str.toLowerCase().replace(/[^\w]+/g, '-');
}
 
// For each line, if the line is a heading, increase its level by 1.
// (I.e., there shouldn't be any H1s in the Markdown.)
for (var i = 0; i < lines.length; i++) {
if (lines[i].match(headerRegex)) {
lines[i] = '#' + lines[i];
}
 
h2 = lines[i].match(h2Regex);
 
if (h2) {
// - [Header text](#header-text)
tocLine = '- [' + h2[1] + ']' + '(#' + dasherize(h2[1]) + ')';
 
tableOfContents.push(tocLine);
}
}
 
// Place the markdown inside a Liquid '{% raw %}{% endraw %}' block
// so that '{{component-name}}' hbs tags are rendered in code blocks.
// (Liquid will parse them as Liquid tags otherwise.)
processedMarkdown =
frontmatter.join('\n') +
'\n\n' +
'{% raw %}\n' +
tableOfContents.join('\n') +
'\n\n' +
lines.join('\n') +
'{% endraw %}';
 
return new RSVP.Promise(function (resolve) {
resolve(processedMarkdown);
});
}
 
/* Write a documentation Markdown file.
*
* @param {string} srcPath - The full path of the source file or directory.
* @param {string} destDir - The directory in which to write the Markdown file.
* @param {Object} options
* @param {string} options.slug - The slug of the documentation. Used to construct the filename.
* @param {string} options.title - The page title of the documentation.
* @returns {Promise}
*/
Function `writeDocsFile` has 34 lines of code (exceeds 25 allowed). Consider refactoring.
Function `writeDocsFile` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.
function writeDocsFile(srcPath, destDir, options) {
// capture stdout of 'documentation'
FIXME found
// FIXME: Change this to use documentation Node API
var filename = options.slug + '.md';
var destPath = path.join(destDir, filename);
var promises = [];
var filePath;
var files;
 
if (fs.statSync(srcPath).isDirectory()) {
// If the path is a directory,
// get a list of files in the directory
files = fs.readdirSync(srcPath);
} else {
// Otherwise, the path is a file name
files = [srcPath];
}
 
for (var i = 0; i < files.length; i++) {
if (files[0] !== srcPath) {
// If it's just a filename, add the path
filePath = path.join(srcPath, files[i]);
} else {
filePath = srcPath;
}
 
// Only try to parse files
if (fs.statSync(filePath).isFile()) {
promises.push(parseSourceFile(filePath));
}
}
 
return RSVP.all(promises)
.then(function (markdownArray) {
return massageMarkdown(markdownArray.join('\n'), options);
})
.then(function (markdown) {
return new RSVP.Promise(function (resolve, reject) {
// use {'flags': 'a'} to append and {'flags': 'w'} to erase and write a new file
var stream = fs.createWriteStream(destPath, { flags: 'w' });
 
stream.on('finish', resolve);
stream.on('error', reject);
 
stream.write(markdown);
 
stream.end();
 
console.log('Wrote Markdown to file ' + destPath);
});
});
}
 
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
 
/* Write documentation for the files in a directory.
*
* Individual files in the directory will each have their own documentation
* files. Subdirectories will group the docs for all files together.
*
* @param {string} srcDir - The directory in which the source files are contained.
* @param {string} destDir - The directory in which to write the documentation files.
* @returns {Promise}
*/
function writeApiDocs(srcPaths, destDir) {
return removeDir(destDir)
.then(() => createDir(destDir))
.then(() => {
return RSVP.map(srcPaths, (srcPath) => {
let slug = path.basename(srcPath, '.js');
let title = capitalizeFirstLetter(slug.replace('-', ' '));
 
return writeDocsFile(path.join(addonDir, srcPath), destDir, { slug, title });
});
});
}
 
/* Copies the documentation files from the temporary directory to the Jekyll
* docs directory.
*
* @param {string} srcDir - The directory in which the source files are contained.
* @param {string} destDir - The directory in which to write the documentation files.
* @returns {Promise}
*/
function copyDocs(srcDir, destDir) {
return new RSVP.Promise(function (resolve, reject) {
// Copy the docs directory
ncp(srcDir, destDir, function (err) {
if (err) {
reject(err);
} else {
console.log('Copied docs to ' + destDir);
resolve();
}
});
});
}
 
/* Creates a directory (and its parent directories, if necessary).
*
* @param {string} dir - The directory to create.
* @returns {Promise}
*/
Similar blocks of code found in 2 locations. Consider refactoring.
function createDir(dir) {
return new RSVP.Promise(function (resolve, reject) {
// Delete the temporary compiled docs directory.
mkdirp(dir, function (err) {
if (err) {
reject(err);
} else {
console.log('Created directory ' + dir);
resolve();
}
});
});
}
 
/* Deletes a directory recursively.
*
* @param {string} dir - The directory to delete.
* @returns {Promise}
*/
Similar blocks of code found in 2 locations. Consider refactoring.
function removeDir(dir) {
return new RSVP.Promise(function (resolve, reject) {
// Delete the temporary compiled docs directory.
rimraf(dir, function (err) {
if (err) {
reject(err);
} else {
console.log('Removed directory ' + dir);
resolve();
}
});
});
}
 
(function () {
var versionArray = packageJSON.version.split('.');
// ex., '1.0.3' -> 'v1.0.x'
var version = 'v' + versionArray[0] + '.' + versionArray[1] + '.x';
 
var tmpDir = path.join(path.dirname(__dirname), 'tmp_docs');
var destDir = path.join(path.dirname(__dirname), 'docs', version);
var guidesDir = path.join(__dirname, 'guides');
 
const apiSourcesPaths = walkSync(addonDir, {
globs: ['src/{create.js,properties/*.js,macros/*.js}'],
});
 
// Create the temporary directory for the docs
createDir(tmpDir)
.then(() => copyDocs(guidesDir, tmpDir))
.then(() => writeApiDocs(apiSourcesPaths, path.join(tmpDir, 'api')))
.then(() => execCmd('git branch'))
.then((branches) => {
const branch = 'gh-pages';
 
// Switch to the GitHub Pages branch
if (branches.includes(branch)) {
return execCmd(`git checkout ${branch}`);
} else {
return execCmd(`git checkout -b ${branch}`);
}
})
.then(() => removeDir(destDir))
.then(() => createDir(destDir))
.then(() => copyDocs(tmpDir, destDir))
.then(() => removeDir(tmpDir))
.then(() => {
console.log('Finished writing documentation files.');
})
.catch(function (reason) {
console.log(reason.stack);
});
})();