Skip to content

Commit

Permalink
Merge pull request #1 from TheJuanAndOnly99/issue-1068
Browse files Browse the repository at this point in the history
Issue 1068
  • Loading branch information
TheJuanAndOnly99 authored Feb 13, 2024
2 parents f4a46c0 + c449b9e commit 3ebd43a
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 2 deletions.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"preprepare": "npm run typegen && npm run typegen-bridging",
"prepare": "tsdx build",
"typegen": "node s2tQuicktypeUtil.js schemas/context src/context/ContextTypes.ts && tsdx lint src/context/ --fix",
"typegen-bridging": "node s2tQuicktypeUtil.js schemas/api schemas/bridging schemas/context/context.schema.json src/bridging/BridgingTypes.ts && tsdx lint src/bridging/ --fix"
"typegen-bridging": "node s2tQuicktypeUtil.js schemas/api schemas/bridging schemas/context/context.schema.json src/bridging/BridgingTypes.ts && tsdx lint src/bridging/ --fix",
"schema-2-markdown": "node schema2Markdown.js"
},
"peerDependencies": {},
"husky": {
Expand All @@ -51,6 +52,8 @@
"quicktype": "23.0.78",
"tsdx": "^0.14.1",
"tslib": "^2.0.1",
"typescript": "^4.0.3"
"typescript": "^4.0.3",
"fs-extra": "^11.2.0",
"js-yaml": "^4.1.0"
}
}
231 changes: 231 additions & 0 deletions schema2Markdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
const fs = require('fs-extra');
const yaml = require('js-yaml');
const path = require('path');

function processProperty(propertyName, propertyDetails, schemaExamples) {
let markdownContent = '';

if (propertyName === 'type') {
markdownContent += `### ${'Type'|| propertyName}\n\n`;
markdownContent += `\`${propertyDetails.const}\`\n\n`;
} else {
markdownContent += `### ${propertyDetails.title || propertyName}\n\n`;
markdownContent += `\`${propertyName}\`\n\n`;

if (propertyDetails.description != null) {
markdownContent += `${escape(propertyDetails.description)}\n\n`;
}

if (propertyDetails.type) {
markdownContent += `**Type**: ${propertyDetails.type}\n\n`;
} else {
const contextRef = propertyDetails.properties?.context?.$ref || propertyDetails.$ref;

if (contextRef) {
markdownContent += renderRef(contextRef);
}
}

if (propertyDetails.enum) {
markdownContent += `Possible values: ${propertyDetails.enum.join(', ')}\n\n`;
}

if (propertyDetails.allOf) {
markdownContent += `${propertyDetails.allOf.map((item) => renderRef(item.$ref)).join(', ')}\n\n`;
}

if (schemaExamples) {
const example = schemaExamples[0];

if (typeof example[propertyName] === 'object') {
markdownContent += `**Example Value**: \n\`\`\`json\n${JSON.stringify(example[propertyName], null, 2)}\n\`\`\`\n\n`;
} else if (example[propertyName]) {
markdownContent += `**Example Value**: \`'${example[propertyName]}'\`\n\n`;
}
}
}
return markdownContent;
}

function renderRef(contextRef) {
const filePath = contextRef.split('#')[0]; // ../api/api.schema.json
const objectType = filePath.split('/').pop().split('.')[0]; // api

// FROM ../api/api.schema.json#/definitions/AppIdentifier
// TO ../api/schemas/Appidentifier

// FROM timerange.schema.json#
// TO timerange/schemas/timerange

const objectPath = contextRef.split('#')[1]; // /definitions/AppIdentifier
let objectName = objectType;
if (objectPath) {
objectName = objectPath.split('/').pop(); // AppIdentifier
}

let objectRef = objectName;
if (filePath.startsWith('../')) {
objectRef = "../../" + objectType + "/schemas/" + objectName;
}

console.log('from contextRef:', contextRef, 'to objectRef:', objectRef);

return `**Reference**: [${objectType}/${objectName}](${objectRef})\n\n`;
}

function hasAllOf(allOfArray) {
return Array.isArray(allOfArray) &&
allOfArray.length > 0 &&
allOfArray[0] != null &&
allOfArray[0].properties != null
}

function hasProperties(schema) {
return schema.properties != null;
}

// Function to generate Markdown content from JSON schema
function generateObjectMD(schema, title, schemaFolderName) {

const objectName = schema.title

if (schema.title != null) {
title = schema.title;
}

let markdownContent = `# ${title}\n\n`;

if (schema.description != null) {
markdownContent += `${escape(schema.description)}\n\n`;
}

console.log('hasAllOf/hasProperties', hasAllOf(schema.allOf), hasProperties(schema));
if (hasAllOf(schema.allOf) || hasProperties(schema)) {
// Extract properties, required fields, and $ref from the first allOf object
let root = schema;
if (hasAllOf(schema.allOf)) {
root = schema.allOf[0];
}

const properties = root.properties;
const required = root.required;
const ref = root.$ref;

markdownContent += `## Properties\n\n`;

for (const [propertyName, propertyDetails] of Object.entries(properties)) {
markdownContent += processProperty(propertyName, propertyDetails, schema.examples);
}

// show required properties
if (required && required.length > 0) {
markdownContent += `### Required Properties:\n\n`;
// for each required property show the name
required.forEach((propertyName) => {
markdownContent += `* \`${propertyName}\`\n`;
});
markdownContent += '\n';
}

if (ref) {
markdownContent += `ref: ${ref}\n\n`;
}

if (schema.examples) {
markdownContent += `## Examples\n\n`;
markdownContent += '```json\n';
markdownContent += JSON.stringify(schema.examples, null, 2);
markdownContent += '\n```';
}
}

const frontMatter = generateFrontMatter(objectName, schema.description);
console.log('frontMatter:', frontMatter);

const outputFileName = `./website/versioned_docs/version-2.1/${schemaFolderName}/schemas/${title.replace(/\s+/g, '')}.md`;

fs.outputFileSync(outputFileName, `---\n${yaml.dump(frontMatter)}\n---\n\n${markdownContent}`);

// objectName must not contain any spaces
if (objectName != null) {
return schemaFolderName + '/schemas/' + objectName.replace(/\s+/g, '');
}
}

function escape(text) {
let output = text;
output = output.replace(/>/g, '\\>');

return output;
}

function generateFrontMatter(title, description) {
return {
title: title + ' Schema',
description: description,
sidebar_label: title + ' Schema',
};
}

function processSchemaFile(schemaFile, schemaFolderName) {
const schemaData = fs.readJSONSync(schemaFile);

// if there is allOf, then it is an object
const allOfArray = schemaData.allOf;
let sidebarItems = [];
if (Array.isArray(allOfArray) && allOfArray.length > 0) {
sidebarItems.push(generateObjectMD(schemaData, null, schemaFolderName));
}
if (schemaData.definitions) {
for (const [objectName, objectDetails] of Object.entries(schemaData.definitions)) {
sidebarItems.push(generateObjectMD(objectDetails, objectName, schemaFolderName));
}
}

// return sidebarItems.flat().filter(item => !item.endsWith('undefined'));
return sidebarItems;
}

function parseSchemaFolder(schemaFolderName) {
// Read all files in the schema folder
const schemaFiles = fs.readdirSync("./schemas/"+schemaFolderName)
.filter(file => file.endsWith('.json'))
.map(file => path.join("./schemas/"+schemaFolderName, file));

// Process each schema file
let sidebarItems = [];
for (const schemaFile of schemaFiles) {
sidebarItems.push(processSchemaFile(schemaFile, schemaFolderName));
}

// filter out null values
return sidebarItems.flat().filter(item => item);
}

function main() {
let sidebarObject = require('./website/versioned_sidebars/version-2.1-sidebars.json')

let sidebarContextObject = {
"type": "category",
"label": "Context Schemas Part",
"items": []
}

let sidebarApiObject = {
"type": "category",
"label": "API Schemas Part",
"items": []
}

sidebarApiObject.items = parseSchemaFolder('api');
sidebarContextObject.items = parseSchemaFolder('context');

sidebarObject.docs["FDC3 Standard"].push(sidebarContextObject)
sidebarObject.docs["FDC3 Standard"].push(sidebarApiObject)

fs.outputJSONSync('./website/versioned_sidebars/version-2.1-sidebars.json', sidebarObject, { spaces: 2 });
}

if (require.main === module) {
main();
}

0 comments on commit 3ebd43a

Please sign in to comment.