diff --git a/lib/index.js b/lib/index.js index 2ac99169..740d7e4c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -89,6 +89,7 @@ export function jsonschema2md(schema, options) { metadata, schemaOut, includeReadme, + linkTitles, links, i18n, language, @@ -159,6 +160,7 @@ export function jsonschema2md(schema, options) { // build readme readme({ readme: true, + linkTitles, }), writereadme({ @@ -195,6 +197,7 @@ export function jsonschema2md(schema, options) { ).split(nodepath.sep).join(nodepath.posix.sep); return target; }, + linkTitles }), // write to files @@ -260,6 +263,11 @@ export async function main(args) { .alias('n', 'no-readme') .describe('n', 'Do not generate a README.md file in the output directory') + .alias('t', 'link-titles') + .describe('t', 'Set this to false to disable rendering titles in markdown links') + .boolean('t') + .default('t', true) + .describe('link-*', 'Add this file as a link the explain the * attribute, e.g. --link-abstract=abstract.md') .alias('i', 'i18n') @@ -297,12 +305,15 @@ export async function main(args) { map(([key, value]) => [key.substr(5), value]), obj, ); + console.log("links", links); + console.log("t", argv.t); const schemaPath = argv.d; const outDir = argv.o; const metadata = argv.m; const schemaOut = argv.x !== '-' ? argv.x : null; const includeReadme = !argv.n; + const linkTitles = argv.t; const i18n = argv.i; const language = argv.l; const exampleFormat = argv.f; @@ -331,6 +342,7 @@ export async function main(args) { metadata, schemaOut, includeReadme, + linkTitles, links, i18n, language, diff --git a/lib/markdownBuilder.js b/lib/markdownBuilder.js index f6addd69..63f37d8b 100644 --- a/lib/markdownBuilder.js +++ b/lib/markdownBuilder.js @@ -42,6 +42,7 @@ export default function build({ links = {}, includeProperties = [], rewritelinks = (x) => x, + linkTitles = true, exampleFormat = 'json', skipProperties = [], } = {}) { @@ -246,6 +247,13 @@ export default function build({ } return []; } + function makelink(url, title, description) { + if (linkTitles) { + return link(url, title, description); + } else { + return link(url, '', description); + } + } /** * Generates the overall header for the schema documentation @@ -266,7 +274,7 @@ export default function build({ headerprops, ({ name, title }) => { if (links[name]) { - return tableCell(link(links[name], i18n`What does ${title} mean?`, text(title))); + return tableCell(makelink(links[name], i18n`What does ${title} mean?`, text(title))); } return tableCell(text(title)); }, @@ -281,7 +289,7 @@ export default function build({ && typeof schema[s.meta][prop.name] === 'object' && schema[s.meta][prop.name].link && schema[s.meta][prop.name].text) { - return tableCell(link(rewritelinks(schema[s.meta][prop.name].link), i18n`open original schema`, [text(schema[s.meta][prop.name].text)])); + return tableCell(makelink(rewritelinks(schema[s.meta][prop.name].link), i18n`open original schema`, [text(schema[s.meta][prop.name].text)])); } const value = schema[s.meta] ? schema[s.meta][prop.name] : undefined; return tableCell(text(prop[`${String(value)}label`] || i18n`Unknown`)); @@ -323,11 +331,11 @@ export default function build({ */ function makepropheader(required = [], ispattern = false, slugger) { return ([name, definition]) => tableRow([ - tableCell(ispattern ? inlineCode(name) : link(`#${slugger.slug(name)}`, '', text(name))), // Property + tableCell(ispattern ? inlineCode(name) : makelink(`#${slugger.slug(name)}`, '', text(name))), // Property tableCell(type(definition)), tableCell(text(required.indexOf(name) > -1 ? i18n`Required` : i18n`Optional`)), tableCell(nullable(definition)), - tableCell(link( + tableCell(makelink( `${definition[s.slug]}.md`, `${definition[s.id]}#${definition[s.pointer]}`, text(definition[s.titles] && definition[s.titles][0] ? definition[s.titles][0] : i18n`Untitled schema`), @@ -358,7 +366,7 @@ export default function build({ tableCell(any ? text('Any') : type(additionalProps)), tableCell(text(i18n`Optional`)), tableCell(any ? text('can be null') : nullable(additionalProps)), - tableCell(any ? text('') : link(`${additionalProps[s.slug]}.md`, `${additionalProps[s.id]}#${additionalProps[s.pointer]}`, text(additionalProps[s.titles][0] || i18n`Untitled schema`))), + tableCell(any ? text('') : makelink(`${additionalProps[s.slug]}.md`, `${additionalProps[s.id]}#${additionalProps[s.pointer]}`, text(additionalProps[s.titles][0] || i18n`Untitled schema`))), ])]; } return []; @@ -388,7 +396,7 @@ export default function build({ paragraph([text(i18n`Type: `), text(i18n`an array where each item follows the corresponding schema in the following list:`)]), list( 'ordered', - [...items.map((schema) => listItem(paragraph(link( + [...items.map((schema) => listItem(paragraph(makelink( `${schema[s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[s.titles], schema[keyword`type`])), @@ -398,7 +406,7 @@ export default function build({ return [listItem(paragraph(text(i18n`and all following items may follow any schema`)))]; } else if (typeof additional === 'object') { return [listItem(paragraph([text(i18n`and all following items must follow the schema: `), - link( + makelink( `${additional[s.slug]}.md`, i18n`check type definition`, text(gentitle(additional[s.titles], additional[keyword`type`])), @@ -448,9 +456,9 @@ export default function build({ const typelink = (() => { if (definition[keyword`title`] && typeof definition[keyword`title`] === 'string') { // if the type has a title, always create a link to the schema - return [text(' ('), link(`${definition[s.slug]}.md`, '', text(definition[keyword`title`])), text(')')]; + return [text(' ('), makelink(`${definition[s.slug]}.md`, '', text(definition[keyword`title`])), text(')')]; } else if (!singletype || firsttype === keyword`object` || merged) { - return [text(' ('), link(`${definition[s.slug]}.md`, '', text(i18n`Details`)), text(')')]; + return [text(' ('), makelink(`${definition[s.slug]}.md`, '', text(i18n`Details`)), text(')')]; } return []; })(); @@ -473,7 +481,7 @@ export default function build({ function makedefinedinfact(definition) { return listItem(paragraph([ text(i18n`defined in: `), - link(`${definition[s.slug]}.md`, `${definition[s.id]}#${definition[s.pointer]}`, text(definition[s.titles] && definition[s.titles][0] ? definition[s.titles][0] : i18n`Untitled schema`)), + makelink(`${definition[s.slug]}.md`, `${definition[s.id]}#${definition[s.pointer]}`, text(definition[s.titles] && definition[s.titles][0] ? definition[s.titles][0] : i18n`Untitled schema`)), ])); } @@ -544,7 +552,7 @@ export default function build({ ]; } else if (depth > 0) { return [ - link(`${schema[s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[s.titles], schema[keyword`type`]))), + makelink(`${schema[s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[s.titles], schema[keyword`type`]))), ]; } else { return []; @@ -621,7 +629,7 @@ export default function build({ // console.log('pattern!', schema[s.filename], schema[s.pointer]); constraints.push(paragraph([strong(text(i18n`pattern`)), text(': '), text(i18n`the string must match the following regular expression: `)])); constraints.push(code('regexp', schema[keyword`pattern`])); - constraints.push(paragraph([link(`https://regexr.com/?expression=${encodeURIComponent(schema[keyword`pattern`])}`, i18n`try regular expression with regexr.com`, text(i18n`try pattern`))])); + constraints.push(paragraph([makelink(`https://regexr.com/?expression=${encodeURIComponent(schema[keyword`pattern`])}`, i18n`try regular expression with regexr.com`, text(i18n`try pattern`))])); } // https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.7.3 if (schema.format && typeof schema.format === 'string' && formats[schema.format]) { @@ -629,7 +637,7 @@ export default function build({ strong(text(formats[keyword([schema.format])].label)), text(': '), text(formats[schema.format].text), - link(formats[schema.format].speclink, i18n`check the specification`, text(formats[schema.format].specname)), + makelink(formats[schema.format].speclink, i18n`check the specification`, text(formats[schema.format].specname)), ])); } else if (schema.format && typeof schema.format === 'string') { constraints.push(paragraph([strong(text(i18n`unknown format`)), text(': '), text(i18n`the value of this string must follow the format: `), inlineCode(String(schema.format))])); @@ -645,7 +653,7 @@ export default function build({ constraints.push(paragraph([ strong(text(i18n`schema`)), text(': '), text(i18n`the contents of this string should follow this schema: `), - link(`${schema[keyword`contentSchema`][s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[keyword`contentSchema`][s.titles], schema[keyword`contentSchema`][keyword`type`])))])); + makelink(`${schema[keyword`contentSchema`][s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[keyword`contentSchema`][s.titles], schema[keyword`contentSchema`][keyword`type`])))])); } // https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.4 @@ -664,12 +672,12 @@ export default function build({ if (schema[keyword`minContains`] !== undefined && schema[keyword`contains`]) { // console.log('minContains!', schema[s.filename], schema[s.pointer]); constraints.push(paragraph([strong(text(i18n`minimum number of contained items`)), text(': '), text(`${i18n`this array may not contain fewer than ${String(schema[keyword`minContains`])} items that validate against the schema:`} `), - link(`${schema[keyword`contains`][s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[keyword`contains`][s.titles], schema[keyword`contains`][keyword`type`])))])); + makelink(`${schema[keyword`contains`][s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[keyword`contains`][s.titles], schema[keyword`contains`][keyword`type`])))])); } if (schema[keyword`maxContains`] !== undefined && schema[keyword`contains`]) { // console.log('maxContains!', schema[s.filename], schema[s.pointer]); constraints.push(paragraph([strong(text(i18n`maximum number of contained items`)), text(': '), text(`${i18n`this array may not contain more than ${String(schema[keyword`maxContains`])} items that validate against the schema:`} `), - link(`${schema[keyword`contains`][s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[keyword`contains`][s.titles], schema[keyword`contains`][keyword`type`])))])); + makelink(`${schema[keyword`contains`][s.slug]}.md`, i18n`check type definition`, text(gentitle(schema[keyword`contains`][s.titles], schema[keyword`contains`][keyword`type`])))])); } // https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.6.5 diff --git a/lib/readmeBuilder.js b/lib/readmeBuilder.js index 733b1cd4..644e1e1e 100644 --- a/lib/readmeBuilder.js +++ b/lib/readmeBuilder.js @@ -43,8 +43,15 @@ function makeversionnote(schemas) { * Generate the README.md * @param {object} opts */ -export default function build({ readme = true }) { +export default function build({ readme = true, linkTitles = true }) { return (schemas) => { + function makelink(url, title, description) { + if (linkTitles) { + return link(url, title, description); + } else { + return link(url, '', description); + } + } if (readme) { console.log('building readme'); const toplevel = flist(pipe( @@ -52,7 +59,7 @@ export default function build({ readme = true }) { filter((schema) => !schema[s.parent]), // remove schemas with a parent mapSort((schema) => gentitle(schema[s.titles], schema[keyword`type`])), map((schema) => listItem(paragraph([ - link(`./${schema[s.slug]}.md`, gendescription(schema), [text(gentitle(schema[s.titles], schema[keyword`type`]))]), + makelink(`./${schema[s.slug]}.md`, gendescription(schema), [text(gentitle(schema[s.titles], schema[keyword`type`]))]), text(' – '), inlineCode(schema[keyword`$id`] || '-'), ]))), @@ -65,7 +72,7 @@ export default function build({ readme = true }) { filter((schema) => !schema.$ref), // it is not a reference mapSort((schema) => gentitle(schema[s.titles], schema[keyword`type`])), map((schema) => listItem(paragraph([ - link(`./${schema[s.slug]}.md`, gendescription(schema), [text(gentitle(schema[s.titles], schema[keyword`type`]))]), + makelink(`./${schema[s.slug]}.md`, gendescription(schema), [text(gentitle(schema[s.titles], schema[keyword`type`]))]), text(' – '), inlineCode(`${schema[s.id]}#${schema[s.pointer]}`), ]))), diff --git a/test/fixtures/skiptitles/complete.schema.json b/test/fixtures/skiptitles/complete.schema.json new file mode 100644 index 00000000..443b0c43 --- /dev/null +++ b/test/fixtures/skiptitles/complete.schema.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Complete JSON Schema", + "description": "Testing for skipped props", + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } +} diff --git a/test/markdownBuilder.test.js b/test/markdownBuilder.test.js index 4fff2381..1d32e532 100644 --- a/test/markdownBuilder.test.js +++ b/test/markdownBuilder.test.js @@ -167,6 +167,23 @@ value: Maximum }); }); +describe('Testing Markdown Builder: Skip titles', () => { + let results; + + before(async () => { + const schemas = await traverseSchemas('skiptitles'); + const builder = build({ header: false, linkTitles: false }); + results = builder(schemas); + }); + + it('Format Schema has JSON examples', () => { + console.log(results.complete) + assertMarkdown(results.complete) + .contains('[Complete JSON Schema](complete-properties-foo.md)') + .doesNotContain('[Complete JSON Schema](complete-properties-foo.md "undefined#/properties/foo")'); + }); +}); + describe('Testing Markdown Builder: enums', () => { let results; diff --git a/test/readmeBuilder.test.js b/test/readmeBuilder.test.js index fb67e292..ba705b5f 100644 --- a/test/readmeBuilder.test.js +++ b/test/readmeBuilder.test.js @@ -84,6 +84,7 @@ describe('Testing Readme Builder', () => { assertMarkdown(result) .contains('# README') .matches(/Top-level Schemas/) + .contains('[Test Schema](./example.md "Not much")') .has('heading > text') .equals('heading > text', { type: 'text', @@ -103,4 +104,34 @@ describe('Testing Readme Builder', () => { ### Arrays`; }); + it('Readme Builder should skip titles in links if flag is set', () => { + const builder = build({ readme: true, linkTitles: false }); + const schemaloader = loader(); + const schemas = [ + schemaloader('example.schema.json', { + type: 'object', + title: 'Test Schema', + description: 'Not much', + properties: { + foo: { + const: 1, + }, + obj: { + type: 'object', + title: 'An Object', + }, + arr: { + type: 'array', + title: 'An Array', + }, + }, + }), + ]; + + const result = builder(schemas); + // eslint-disable-next-line no-unused-expressions + assertMarkdown(result) + .contains('[Test Schema](./example.md)') + .doesNotContain('[Test Schema](./example.md "Not much")'); + }); });