/** * 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);