diff --git a/README.md b/README.md index e00eb49..d0ecacf 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,22 @@ # docfx-tools -A docfx template for package documentation, patching the modern template to provide stylesheets and scripts for rendering custom workflow containers with copy functionality. +A repository of docfx tools for Bonsai package documentation: +- Docfx Workflow Container template patching the modern template to provide stylesheets and scripts for rendering custom workflow containers with copy functionality. +- Docfx API TOC template that groups nodes by operator type in the table of contents(TOC) on API pages. +- Docfx API template that revamps the API page to enhance user-friendliness. +- Powershell Scripts that automate several content generation steps for package documentation websites. -## How to use +## How to include -To include this template in a docfx website, first clone this repository as a submodule: +To include this repo in a docfx website, first clone this repository as a submodule: ``` git submodule add https://github.com/bonsai-rx/docfx-tools bonsai ``` -Then modify `docfx.json` to include the template immediately after the modern template: +## Using Workflow Container Template + +Modify `docfx.json` to include the template immediately after the modern template: ```json "template": [ @@ -38,12 +44,39 @@ export default { } } ``` +## Using API template + +Modify `docfx.json` to include the api template in the template section (note both the workflow container and API template have to be added separately). + +```json +"template": [ + "default", + "modern", + "bonsai/template", + "bonsai/template/api", + "template" +] +``` +In addition, the images and custom css styles need to be added to the resources section. -## Powershell Scripts +```json +"resource": [ + { + "files": [ + "logo.svg", + "favicon.ico", + "images/**", + "workflows/**", + "bonsai/template/api/images/**", + "bonsai/template/api/styles/**" + ] + } +] +``` +To add individual operator workflows for the API pages, open Bonsai, add the operator, and save each individual operator workflow as `OperatorName.bonsai` (case sensitive) in `docs/workflows/operators`. -This repository also provides helper scripts to automate several content generation steps for package documentation websites. -### Exporting workflow images +## Powershell Scripts - Exporting workflow images Exporting SVG images for all example workflows can be automated by placing all `.bonsai` files in a `workflows` folder and calling the below script pointing to the bin directory to include. A bonsai environment is assumed to be available in the `.bonsai` folder in the repository root. diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js new file mode 100644 index 0000000..6f43f52 --- /dev/null +++ b/template/api/ManagedReference.extension.js @@ -0,0 +1,186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Define Bonsai operator types in documentation by checking for an explicit Category tag. If the class does not provide one, +// check the inheritance tree of the class. +function defineOperatorType(model){ + const checkForCategory = (category) => model.syntax?.content[0].value.includes(`[WorkflowElementCategory(ElementCategory.${category})]`); + const checkInheritance = (inheritance) => model.inheritance?.some(inherited => inherited.uid.includes(inheritance)); + + source = checkForCategory('Source') || checkInheritance('Bonsai.Source'); + sink = checkForCategory('Sink') || checkInheritance('Bonsai.Sink') || checkInheritance('Bonsai.IO.StreamSink') || checkInheritance('Bonsai.IO.FileSink'); + combinator = checkForCategory('Combinator') || checkInheritance('Bonsai.Combinator') || checkInheritance('Bonsai.WindowCombinator'); + transform = checkForCategory('Transform') || checkInheritance('Bonsai.Transform') || checkInheritance('Bonsai.Transform'); + + let operatorType = {} + operatorType.type = sink ? 'sink' : source ? 'source' : transform ? 'transform' : combinator ? 'combinator' : false ; + + return operatorType; +} + +// This function is important for stripping the extra line that is present in some fields +// replace last instance of ' child.name[0].value.includes('Process') || child.name[0].value.includes('Generate')) + .map(child => ({ + 'description': [child.summary, child.remarks].join(''), + 'input': { + 'specName': replaceIObservableAndTSource(child.syntax?.parameters[0].type.specName[0].value), + 'description': removeBottomMargin([child.syntax?.parameters[0].description, child.syntax?.parameters[0].remarks].join('')) + }, + 'output': { + 'specName': replaceIObservableAndTSource(child.syntax.return.type.specName[0].value), + 'description': removeBottomMargin([child.syntax.return.description, child.syntax.return.remarks].join('')), + } + })) + .map(item => { + // Remove input if it's empty + if (!item.input.specName && !item.input.description) { + delete item.input; + } + return item; + }); + return overloads; +} + +// extracts enums so that they can be expanded in the properties table +function processChildProperty(child, sharedModel) { + const enumFields = sharedModel[`~/api/${child.syntax.return.type.uid}.yml`]?.type === 'enum' ? + extractEnumData(sharedModel[`~/api/${child.syntax.return.type.uid}.yml`]) : + []; + return { + 'name': child.name[0].value, + 'type': child.syntax.return.type.specName[0].value, + 'propertyDescription': { + 'text': enumFields.length > 0 + ? [child.summary, child.remarks].join('') + : removeBottomMargin([child.summary, child.remarks].join('')), + 'hasEnum': enumFields.length > 0, + 'enum': enumFields, + } + } +} + +function extractPropertiesData(model, sharedModel) { + return model?.children + .filter(child => child.type === 'property' && child.syntax) + .map(child => processChildProperty(child, sharedModel)); +} + +function extractPropertiesFromInheritedMembersData(model, sharedModel) { + return model.inheritedMembers + .filter(inheritedMember => inheritedMember.type === 'property') + .map(inheritedMember => { + return processChildProperty( + sharedModel[`~/api/${inheritedMember.parent}.yml`].children.find(inheritedMemberChild => inheritedMemberChild.uid === inheritedMember.uid), + sharedModel + ); + }); +} + + +// Properties are usually already listed in declaration order which mirrors Bonsai UI. +// However a bug in docfx messes up properties that have a numeric endvalue ie Device10 < Device2 +// and this function fixes that. +function sortPropertiesData(properties) { + return properties.sort((a, b) => { + const regex = /\D+|\d+$/g; + + // Extract parts for property 'a' + const [prefixA, numberA] = a.name.match(regex); + const numA = Number(numberA); + + // Extract parts for property 'b' + const [prefixB, numberB] = b.name.match(regex); + const numB = Number(numberB); + + // If prefix is the same, compare numbers + if (prefixA == prefixB) { + return numA - numB; + } + }); +} + +// While enum fields can be accessed directly using the mustache template, this function is +// still important for stripping the extra line that is present in the summary/remarks field +function extractEnumData(model){ + return model.children + .filter(child => child.type === 'field') + .map(child => ({ + 'field&value': child.syntax.content[0].value, + 'enumDescription': removeBottomMargin([child.summary, child.remarks].join('')) + })); +} + +/** + * This method will be called at the start of exports.transform in ManagedReference.html.primary.js + */ +exports.preTransform = function (model) { + + model.bonsai = {}; + + model.bonsai.description = [model.summary, model.remarks].join(''); + + operatorType = defineOperatorType(model); + + if (operatorType.type){ + model.bonsai.operatorType = operatorType.type; + model.bonsai.showWorkflow = true + operators = defineInputsAndOutputs(model); + model.bonsai.operators = operators; + } + + if (model.type === 'class') { + properties = sortPropertiesData([ + ...extractPropertiesData(model, model.__global._shared), + ...extractPropertiesFromInheritedMembersData(model, model.__global._shared), + ]); + if (properties.length > 0){ + model.bonsai.hasProperties = true; + model.bonsai.properties = properties + } + } + + else if (model.type === 'enum') { + model.bonsai.enumFields = extractEnumData(model); + model.bonsai.hasEnumFields = true; + } + return model; +} + +/** + * This method will be called at the end of exports.transform in ManagedReference.html.primary.js + */ +exports.postTransform = function (model) { + return model; +} \ No newline at end of file diff --git a/template/api/ManagedReference.html.primary.tmpl b/template/api/ManagedReference.html.primary.tmpl new file mode 100644 index 0000000..5f4ab4f --- /dev/null +++ b/template/api/ManagedReference.html.primary.tmpl @@ -0,0 +1,7 @@ +{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}} + +{{!master(layout/_master.tmpl)}} + +{{>partials/class}} + +{{>partials/enum}} \ No newline at end of file diff --git a/template/api/ManagedReference.overwrite.js b/template/api/ManagedReference.overwrite.js new file mode 100644 index 0000000..ff7ef9d --- /dev/null +++ b/template/api/ManagedReference.overwrite.js @@ -0,0 +1,5 @@ +exports.getOptions = function (model) { + return { + isShared: true + }; +} \ No newline at end of file diff --git a/template/api/images/combinator-operator.svg b/template/api/images/combinator-operator.svg new file mode 100644 index 0000000..4a4afac --- /dev/null +++ b/template/api/images/combinator-operator.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + diff --git a/template/api/images/right-arrow.svg b/template/api/images/right-arrow.svg new file mode 100644 index 0000000..85ebb00 --- /dev/null +++ b/template/api/images/right-arrow.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + diff --git a/template/api/images/sink-operator.svg b/template/api/images/sink-operator.svg new file mode 100644 index 0000000..918318c --- /dev/null +++ b/template/api/images/sink-operator.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + diff --git a/template/api/images/source-operator.svg b/template/api/images/source-operator.svg new file mode 100644 index 0000000..f39cb61 --- /dev/null +++ b/template/api/images/source-operator.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + diff --git a/template/api/images/transform-operator.svg b/template/api/images/transform-operator.svg new file mode 100644 index 0000000..43846fd --- /dev/null +++ b/template/api/images/transform-operator.svg @@ -0,0 +1,58 @@ + + diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial new file mode 100644 index 0000000..006577b --- /dev/null +++ b/template/api/partials/class.tmpl.partial @@ -0,0 +1,98 @@ + + +{{#isClass}} + +
+

+ {{name.0.value}} + {{#sourceurl}}{{/sourceurl}} +

+ {{#bonsai.operatorType}} +

+ {{bonsai.operatorType}} Operator +

+ {{/bonsai.operatorType}} +
+ +
{{{bonsai.description}}}
+ +{{#bonsai.showWorkflow}} +

{{name.0.value}} Workflow

+{{/bonsai.showWorkflow}} + +{{#bonsai.operatorType}} + +

Inputs & Outputs

+{{>partials/diagram}} + +{{/bonsai.operatorType}} + +{{#bonsai.hasProperties}} + +

Properties

+ + + + + + + + + + {{#bonsai.properties}} + {{>partials/propertyTables}} + {{/bonsai.properties}} + +
PropertyTypeDescription
+ +{{/bonsai.hasProperties}} + +

Relationships

+
+
{{__global.namespace}} - {{{namespace.specName.0.value}}}
+ {{#assemblies.0}}
{{__global.assembly}} - {{assemblies.0}}.dll
{{/assemblies.0}} +
+ +{{#inheritance.0}} +
+
{{__global.inheritance}}
+
+{{/inheritance.0}} +{{#inheritance}} +
{{{specName.0.value}}}
+{{/inheritance}} +
{{name.0.value}}
+{{#inheritance.0}} +
+
+{{/inheritance.0}} + + +{{#implements.0}} +
+
{{__global.implements}}
+
+{{/implements.0}} +{{#implements}} +
{{{specName.0.value}}}
+{{/implements}} +{{#implements.0}} +
+
+{{/implements.0}} + +{{#derivedClasses.0}} +
+
{{__global.derived}}
+
+{{/derivedClasses.0}} +{{#derivedClasses}} +
{{{specName.0.value}}}
+{{/derivedClasses}} +{{#derivedClasses.0}} +
+
+{{/derivedClasses.0}} + + +{{/isClass}} \ No newline at end of file diff --git a/template/api/partials/diagram.tmpl.partial b/template/api/partials/diagram.tmpl.partial new file mode 100644 index 0000000..e2cb8aa --- /dev/null +++ b/template/api/partials/diagram.tmpl.partial @@ -0,0 +1,83 @@ +{{#bonsai.operators}} + + + +
{{{description}}}
+ + + + + + + + + + + + + + + + +
+ + + {{#input}} + + + {{/input}} + +
+
{{{specName}}}
+ {{{description}}} +
+ right-arrow +
+
+ representation of a source, sink, transform or combinator operator + + + + + + +
+ right-arrow + +
{{{output.specName}}}
+
{{{output.description}}}
+
+
+ +{{/bonsai.operators}} \ No newline at end of file diff --git a/template/api/partials/enum.tmpl.partial b/template/api/partials/enum.tmpl.partial new file mode 100644 index 0000000..4755efe --- /dev/null +++ b/template/api/partials/enum.tmpl.partial @@ -0,0 +1,45 @@ +{{#isEnum}} + +

+ {{name.0.value}} + {{#sourceurl}}{{/sourceurl}} +

+ +{{{bonsai.description}}} + +{{#bonsai.hasEnumFields}} + +

Fields

+ + + + + + + + + {{#bonsai.enumFields}} + + + + + {{/bonsai.enumFields}} + +
Field & ValueDescription
+ + {{{field&value}}} + + + {{{enumDescription}}} +
+ +{{/bonsai.hasEnumFields}} + +

Relationships

+
+
{{__global.namespace}} - {{{namespace.specName.0.value}}}
+ {{#assemblies.0}}
{{__global.assembly}} - {{assemblies.0}}.dll
{{/assemblies.0}} +
+ + +{{/isEnum}} \ No newline at end of file diff --git a/template/api/partials/propertyTables.tmpl.partial b/template/api/partials/propertyTables.tmpl.partial new file mode 100644 index 0000000..bd47d08 --- /dev/null +++ b/template/api/partials/propertyTables.tmpl.partial @@ -0,0 +1,29 @@ + + + + {{{name}}} + + + + {{{type}}} + + + +
+ {{#propertyDescription}} + {{{propertyDescription.text}}} + {{#hasEnum}} + + {{#enum}} + + + + + {{/enum}} +
{{{field&value}}}{{{enumDescription}}}
+ {{/hasEnum}} + {{/propertyDescription}} +
+ + + \ No newline at end of file diff --git a/template/api/styles/styles.css b/template/api/styles/styles.css new file mode 100644 index 0000000..0598798 --- /dev/null +++ b/template/api/styles/styles.css @@ -0,0 +1,35 @@ +div.tableFormat *:last-child { + margin-bottom: 0; + } + + div.tableFormat table { + display: grid; + grid-template-columns: auto; + margin-top: 1rem; + margin: 0; + padding: 0; + border: 0 hidden; + border-collapse: collapse; + } + + div.tableFormat td { + padding: 0; + border: 0 hidden; + } + + div.tableFormat td.term { + /* !important tag prevents overrides from dotnet.css associated with: + body[data-yaml-mime="ManagedReference"] article td.term, body[data-yaml-mime="ApiPage"] article td.term + important tags not good practice*/ + font-weight: var(--bs-body-font-weight) !important; + white-space: nowrap; + } + + div.tableFormat td.description { + padding-left: 1rem; + } + + .quick-links > .table-responsive > table.table.table-bordered.table-condensed { + border: 0px hidden transparent; + } + \ No newline at end of file