diff --git a/package-lock.json b/package-lock.json
index dfbcc4fb..d7c9ef43 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -265,6 +265,12 @@
"integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==",
"dev": true
},
+ "@types/humanize-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@types/humanize-string/-/humanize-string-1.0.0.tgz",
+ "integrity": "sha512-lfaNfcTSt2DLiF1V8kXMhT4rX7ggkc10wI9SqTrxFMNTIfaafXHCL5DS1q2J/i+Be3EBQyG+Ls8GSbKngvSIkw==",
+ "dev": true
+ },
"@types/inquirer": {
"version": "0.0.43",
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-0.0.43.tgz",
@@ -341,6 +347,16 @@
"@types/request": "*"
}
},
+ "@types/rimraf": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.2.tgz",
+ "integrity": "sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==",
+ "dev": true,
+ "requires": {
+ "@types/glob": "*",
+ "@types/node": "*"
+ }
+ },
"@types/rx": {
"version": "4.1.1",
"resolved": "http://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz",
@@ -887,9 +903,9 @@
}
},
"commander": {
- "version": "2.18.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz",
- "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==",
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
+ "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
"dev": true
},
"compare-func": {
@@ -1799,6 +1815,15 @@
"sshpk": "^1.7.0"
}
},
+ "humanize-string": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/humanize-string/-/humanize-string-1.0.2.tgz",
+ "integrity": "sha512-PH5GBkXqFxw5+4eKaKRIkD23y6vRd/IXSl7IldyJxEXpDH9SEIXRORkBtkGni/ae2P7RVOw6Wxypd2tGXhha1w==",
+ "dev": true,
+ "requires": {
+ "decamelize": "^1.0.0"
+ }
+ },
"humps": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz",
diff --git a/package.json b/package.json
index 6fff7b67..aa8bfb95 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"scripts": {
"build": "npm run tslint && npm run compile && npm run pack && npm run schema && npm run documentation",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
- "compile": "tsc",
+ "compile": "tsc && rimraf lib/cli.js lib/common.js lib/types.js",
"documentation": "typedoc --includeDeclarations --excludeExternals --mode modules --out docs src",
"pack": "openstapps-pack",
"prepareOnly": "npm run build",
@@ -46,11 +46,17 @@
"@openstapps/logger": "0.0.3",
"@openstapps/projectmanagement": "0.0.1",
"@types/chai": "4.1.7",
+ "@types/humanize-string": "1.0.0",
"@types/node": "10.12.10",
+ "@types/rimraf": "2.0.2",
+ "async-pool-native": "0.1.0",
"chai": "4.2.0",
+ "commander": "2.19.0",
"conventional-changelog-cli": "2.0.11",
+ "humanize-string": "1.0.2",
"mocha": "5.2.0",
"mocha-typescript": "1.1.17",
+ "rimraf": "2.6.2",
"ts-node": "7.0.1",
"tslint": "5.11.0",
"typedoc": "0.13.0",
diff --git a/src/cli.ts b/src/cli.ts
new file mode 100644
index 00000000..a28716c2
--- /dev/null
+++ b/src/cli.ts
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 StApps
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+import {execSync} from 'child_process';
+import * as commander from 'commander';
+import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs';
+import {resolve} from 'path';
+import {ProjectReflection} from 'typedoc';
+import {gatherRouteInformation, generateDocumentationForRoute, logger, rimrafPromisifed} from './common';
+import {NodesWithMetaInformation} from './types';
+
+commander.version(JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json')).toString()).version);
+
+commander
+ .command('routes ')
+ .action(async (relativeMdPath) => {
+ if (!existsSync(resolve('tmp'))) {
+ // create tmp directory
+ mkdirSync(resolve('tmp'));
+ }
+
+ const command = resolve('node_modules', '.bin', 'typedoc');
+ const jsonPath = resolve('tmp', 'out.json');
+ const mdPath = resolve(relativeMdPath);
+
+ logger.info(`Using Typedoc from ${command}.`);
+
+ const result = execSync(`${command} --includeDeclarations --excludeExternals --mode modules --json ${jsonPath}`);
+
+ result.toString().split('\n').forEach((line) => {
+ if (line.length > 0) {
+ logger.info(line);
+ }
+ });
+
+ const jsonContent: ProjectReflection = JSON.parse(readFileSync(jsonPath).toString());
+
+ const nodes: NodesWithMetaInformation = {};
+
+ jsonContent.children.forEach((module: any) => {
+ if (Array.isArray(module.children) && module.children.length > 0) {
+ module.children.forEach((node: any) => {
+ nodes[node.name] = {
+ module: module.name.substring(1, module.name.length - 1),
+ type: node.kindString,
+ };
+ });
+ }
+ });
+
+ const routes = await gatherRouteInformation(jsonContent);
+
+ let output: string = '# Routes\n\n';
+
+ routes.forEach((routeWithMetaInformation) => {
+ output += generateDocumentationForRoute(routeWithMetaInformation, nodes);
+ });
+
+ writeFileSync(mdPath, output);
+
+ logger.ok(`Route documentation written to ${mdPath}.`);
+
+ // remove temporary files
+ await rimrafPromisifed(resolve('tmp'));
+ });
+
+commander.parse(process.argv);
+
+if (commander.args.length < 2) {
+ commander.outputHelp();
+ process.exit(1);
+}
diff --git a/src/common.ts b/src/common.ts
new file mode 100644
index 00000000..363a2817
--- /dev/null
+++ b/src/common.ts
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2018 StApps
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+import {Logger} from '@openstapps/logger';
+import {asyncPool} from 'async-pool-native/dist/async-pool';
+import humanizeString = require('humanize-string');
+import * as rimraf from 'rimraf';
+import {ProjectReflection} from 'typedoc';
+import {promisify} from 'util';
+import {NodesWithMetaInformation, NodeWithMetaInformation, RouteWithMetaInformation} from './types';
+
+/**
+ * Initialized logger
+ */
+export const logger = new Logger();
+
+/**
+ * Promisified rimraf
+ */
+export const rimrafPromisifed = promisify(rimraf);
+
+/**
+ * Gather relevant information of routes
+ *
+ * This gathers the information for all routes that implement the abstract class SCAbstractRoute.
+ * Furthermore it instantiates every route and adds it to the information.
+ *
+ * @param reflection Contents of the JSON representation which Typedoc generates
+ */
+export async function gatherRouteInformation(reflection: ProjectReflection): Promise {
+ const routes: RouteWithMetaInformation[] = [];
+
+ await asyncPool(2, reflection.children, async (module: any) => {
+ if (Array.isArray(module.children) && module.children.length > 0) {
+ await asyncPool(2, module.children, (async (node: any) => {
+ if (Array.isArray(node.extendedTypes) && node.extendedTypes.length > 0) {
+ if (node.extendedTypes.some((extendedType: any) => {
+ return extendedType.name === 'SCAbstractRoute';
+ })) {
+ logger.info(`Found ${node.name} in ${module.originalName}.`);
+
+ const importedModule = await import(module.originalName);
+
+ const route = new importedModule[node.name]();
+
+ routes.push({description: node.comment, name: node.name, route});
+ }
+ }
+ }));
+ }
+ });
+
+ return routes;
+}
+
+/**
+ * Get a linked name for a node
+ *
+ * @param name Name of the node
+ * @param node Node itself
+ * @param humanize Whether to humanize the name or not
+ */
+export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation, humanize: boolean = false): string {
+ let printableName = name;
+
+ if (humanize) {
+ printableName = humanizeString(name.substr(2));
+ }
+
+ let link = `[${printableName}]`;
+ link += `(${getLinkForNode(name, node)})`;
+ return link;
+}
+
+/**
+ * Get link for a node
+ *
+ * @param name Name of the node
+ * @param node Node itself
+ */
+export function getLinkForNode(name: string, node: NodeWithMetaInformation): string {
+ let link = 'https://openstapps.gitlab.io/core/';
+ const module = node.module.toLowerCase().split('/').join('_');
+
+ if (node.type === 'Type alias') {
+ link += 'modules/';
+ link += `_${module}_`;
+ link += `.html#${name.toLowerCase()}`;
+ return link;
+ }
+
+ let type = 'classes';
+ if (node.type !== 'Class') {
+ type = `${node.type.toLowerCase()}s`;
+ }
+
+ link += `${type}/`;
+ link += `_${module}_`;
+ link += `.${name.toLowerCase()}.html`;
+ return link;
+}
+
+/**
+ * Generate documentation snippet for one route
+ *
+ * @param routeWithInfo A route instance with its meta information
+ * @param nodes
+ */
+export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInformation,
+ nodes: NodesWithMetaInformation): string {
+ let output = '';
+
+ const route = routeWithInfo.route;
+
+ output += `## \`${route.method} ${route.urlFragment}\``;
+ output += ` ${getLinkedNameForNode(routeWithInfo.name, nodes[routeWithInfo.name], true)}\n\n`;
+
+ if (typeof routeWithInfo.description.shortText === 'string') {
+ output += `**${routeWithInfo.description.shortText}**\n\n`;
+ }
+
+ if (typeof routeWithInfo.description.text === 'string') {
+ output += `${routeWithInfo.description.text.replace('\n', '
')}\n\n`;
+ }
+
+ output += `### Definition
+
+| parameter | value |
+| --- | --- |
+| request | ${getLinkedNameForNode(route.requestBodyName, nodes[route.requestBodyName])} |
+| response | ${getLinkedNameForNode(route.responseBodyName, nodes[route.responseBodyName])} |
+| success code | ${route.statusCodeSuccess} |
+| errors | ${route.errorNames.map((errorName) => {
+ return getLinkedNameForNode(errorName, nodes[errorName]);
+ }).join('
')} |
+`;
+ if (typeof route.obligatoryParameters === 'object' && Object.keys(route.obligatoryParameters).length > 0) {
+ let parameterTable = '| parameter | type |
';
+
+ Object.keys(route.obligatoryParameters).forEach((parameter) => {
+ let type = route.obligatoryParameters![parameter];
+
+ if (typeof nodes[type] !== 'undefined') {
+ type = getLinkedNameForNode(type, nodes[type]);
+ }
+
+ parameterTable += `| ${parameter} | ${type} |
`;
+ });
+
+ parameterTable += '
';
+
+ output += `| obligatory parameters | ${parameterTable} |`;
+ }
+ output += '\n\n';
+
+ return output;
+}
diff --git a/src/core/protocol/routes/bookAvailability/BookAvailabilityRequest.ts b/src/core/protocol/routes/bookAvailability/BookAvailabilityRequest.ts
index f28f5595..3f28e362 100644
--- a/src/core/protocol/routes/bookAvailability/BookAvailabilityRequest.ts
+++ b/src/core/protocol/routes/bookAvailability/BookAvailabilityRequest.ts
@@ -41,7 +41,19 @@ export interface SCBookAvailabilityRequestByUuid {
}
/**
- * Route for book availiability
+ * Route for book availability
+ *
+ * This checks if a book is available in a library.
+ *
+ * **Example**:
+ *
+ * `POST https://example.com/bookAvailability`
+ *
+ * ```json
+ * {
+ * "isbn": "978-3-16-148410-0"
+ * }
+ * ```
*/
export class SCBookAvailabilityRoute extends SCAbstractRoute {
constructor() {
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 00000000..43ca248a
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 StApps
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+import {SCAbstractRoute} from './core/Route';
+
+/**
+ * A route instance with its relevant meta information
+ */
+export interface RouteWithMetaInformation {
+ /**
+ * Description of the route
+ */
+ description: {
+ /**
+ * Short text of the description - title
+ */
+ shortText?: string;
+ /**
+ * Text of the description
+ */
+ text?: string;
+ };
+ /**
+ * Name of the route
+ */
+ name: string;
+ /**
+ * Instance of the route
+ */
+ route: SCAbstractRoute;
+}
+
+/**
+ * A node with its relevant meta information
+ */
+export interface NodeWithMetaInformation {
+ /**
+ * Module the node belongs to
+ */
+ module: string;
+ /**
+ * Type of the node
+ */
+ type: string;
+}
+
+/**
+ * A map of nodes indexed by their name
+ */
+export interface NodesWithMetaInformation {
+ /**
+ * Index signature
+ */
+ [k: string]: NodeWithMetaInformation;
+}