Source: generateSchemaDocs.js

/**
 * Roboliq: Automation for liquid-handling robots
 * @copyright 2017, ETH Zurich, Ellis Whitehead
 * @license GPL-3.0
 */

/**
 * Generate documentation from the schemas in `schemas/*.yaml`.
 * Running this module will create these files:
 *
 * * `tutorials/Object_Types.md`
 * * `tutorials/Commands.md`
 *
 * @module
 */

import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import yaml from 'yamljs';

/**
 * Convert a name/schema pair to markdown text.
 * @param {array} pair - [name, schema]
 * @return A markdown string.
 */
function toMarkdown(pair) {
	const [name, o] = pair;
	//console.log({name, o})
	if (o.module) {
		return `\n## <a name="${name}"></a>${name}\n\n${o.module}`;
	}
	else {
		return _.flattenDeep([
			`### \`${name}\``,
			"",
			o.description ? [o.description, ""] : [],
			"Properties:",
			"",
			"Name | Type | Argument | Description",
			"-----|------|----------|------------",
			_.map(o.properties, (p, pName) => {
				const isRequired = _.includes(o.required, pName);
				// const nameText = (isRequired) ? pName : `[${pName}]`;
				// const nameTypeText = (p.type) ? `${nameText}: ${p.type}` : nameText;
				// const descriptionText = p.description || "";
				// return `* \`${nameTypeText}\` -- ${descriptionText}`;
				return `${pName} | ${p.type || ""} | ${(isRequired) ? "" : "*optional*"} | ${p.description || ""}`;
			}),
			(_.isEmpty(o.example)) ? [] : [
				"",
				"Example:",
				"",
				o.example,
				""
			]
		]).join('\n');
	}
}

/**
 * Take a string, split it on all newlines, prepend each newline with " * ", and then rejoin.
 * @param {string} s - string to indent
 * @return {string} - string indented by " * "
 */
function indentJsdocComment(s) {
	return s.trim().split("\n").map(s => " * "+s).join("\n");
}

/**
 * Convert a name/schema pair to jsdoc text.
 * @param {array} pair - [name, schema]
 * @param {boolean} isCommand - true if pair is a command, in which case the `memberOf` field is handled differently.
 * @return A markdown string.
 */
function typeToJsdoc(pair, isCommand = false) {
	const [name, o] = pair;
	//console.log({name, o})
	const s = _.flattenDeep([
		o.description ? [o.description, ""] : [],
		(isCommand) ? `@typedef "${name}"` : `@class ${name}`,
		//(isCommand) ? `@memberof ${_.initial(name.split(".")).join(".")}` : [],
		//(isCommand) ? `@memberof commands`: "@memberof types",
		_.map(o.properties, (p, pName) => {
			const isRequired = _.includes(o.required, pName);
			const nameText = (isRequired) ? pName : `[${pName}]`;
			const typeText = (p.type) ? `{${_.flatten([p.type]).join("|").replace(/ /g, "")}}` : "";
			const descriptionText = (p.description) ? `- ${p.description}` : "";
			return `@property ${typeText} ${nameText} ${descriptionText}`;
		}),
		(_.isEmpty(o.example)) ? [] : [
			"@example",
			o.example
		]
	]).join('\n').trim().split("\n").map(s => " * "+s).join("\n");
	return "/**\n" + s + "\n */\n\n";
}

/**
 * Convert a name/schema pair to markdown text.
 * @param {array} pair - [name, schema]
 * @return A markdown string.
 */
function commandToJsdoc(pair) {
	const [name, o] = pair;
	//console.log({name, o})
	if (o.module) {
		return `\n/**\n${indentJsdocComment(o.module)}\n *\n * @module ${name}\n */\n`;
	}
	else {
		return typeToJsdoc(pair, true);
	}
}

// All files in the schema/ directory
const filename_l = _.filter(fs.readdirSync(__dirname+"/schemas/"), s => path.extname(s) === ".yaml");
// Load the schemas from the files
const schemas_l = _.map(filename_l, filename => yaml.load(__dirname+"/schemas/"+filename));
// Merge all schemas together
const schemas = _.merge.apply(_, [{}].concat(schemas_l));
// Separate object schemas from command schemas
const [objectSchemas, commandSchemas] = _.partition(_.toPairs(schemas), ([name, schema]) => name[0] === name[0].toUpperCase());

// Generate documentation for object types
const objectSchemasText = _.map(objectSchemas, toMarkdown).join('\n\n');
fs.writeFileSync(__dirname+"/../tutorials/Object_Types.md", objectSchemasText);

// Generate documentation for commands
const commandSchemasText = _.map(commandSchemas, toMarkdown).join('\n\n');
fs.writeFileSync(__dirname+"/../tutorials/Commands.md", commandSchemasText);
fs.writeFileSync(__dirname+"/../generated/content/commands.md", commandSchemasText);

// Generate documentation for object types
const generatedTypesText = "/**\n * Namespace for the object types available in Roboliq protocols.\n * @namespace types\n * @version v1 \n */\n\n" + _.map(objectSchemas, x => typeToJsdoc(x)).join('\n\n');
fs.writeFileSync(__dirname+"/../generated/types.jsdoc", generatedTypesText);

// Generate documentation for commands
const generatedCommandsText = "/**\n * Namespace for the commands available in Roboliq protocols.\n * @namespace commands\n * @version v1 \n */\n\n" + _.map(commandSchemas, commandToJsdoc).join('\n\n');
fs.writeFileSync(__dirname+"/../generated/commands.jsdoc", generatedCommandsText);