From 138ce432e208e1c17120c3a1eb5c76018beffc0e Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 8 Aug 2024 14:15:56 -0700 Subject: [PATCH 01/70] Initial commit of template files From https://github.com/open-ephys/onix1-bonsai-docs/commit/c2e0bd45dd2f89a8e8f79c7a125486c10cf10ef3 Co-authored-by: Cris Co-authored-by: Brandon Parks --- template/api/ManagedReference.extension.js | 622 ++++++++++++++++++ .../api/ManagedReference.html.primary.tmpl | 7 + template/api/ManagedReference.overwrite.js | 5 + template/api/conceptual.html.primary.tmpl | 40 ++ template/api/partials/class.tmpl.partial | 109 +++ template/api/partials/diagram.tmpl.partial | 114 ++++ template/api/partials/enum.tmpl.partial | 38 ++ .../api/partials/propertyTables.tmpl.partial | 37 ++ template/api/toc.extension.js | 74 +++ 9 files changed, 1046 insertions(+) create mode 100644 template/api/ManagedReference.extension.js create mode 100644 template/api/ManagedReference.html.primary.tmpl create mode 100644 template/api/ManagedReference.overwrite.js create mode 100644 template/api/conceptual.html.primary.tmpl create mode 100644 template/api/partials/class.tmpl.partial create mode 100644 template/api/partials/diagram.tmpl.partial create mode 100644 template/api/partials/enum.tmpl.partial create mode 100644 template/api/partials/propertyTables.tmpl.partial create mode 100644 template/api/toc.extension.js diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js new file mode 100644 index 0000000..c5516c8 --- /dev/null +++ b/template/api/ManagedReference.extension.js @@ -0,0 +1,622 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +function removeBottomMargin(str){ + return str.split('').reverse().join('') + .replace( ' o --> output diagrams +function defineInputsAndOutputs(model){ + operators = []; + if (model.oe.hubOrDevice === 'hub'){ + const hubChildrenLength = model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children.length; + for (let i = 0; i < hubChildrenLength; i++){ + if (model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].name[0].value.includes('Process')){ + description = [ + model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].summary, + model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].remarks + ].join(''); + } + input = {}; + if (model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters && model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0]){ + input = { + 'specName': replaceIObservableAndTSource(model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].type.specName[0].value), + 'name': model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), + 'description': removeBottomMargin([ model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].description, + model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].remarks].join(''))}; + input.internal = true; + } + output = {}; + dataFrame = []; + if (model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return){ + output = { + 'specName': replaceIObservableAndTSource(model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.type.specName[0].value), + 'name': model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), + 'description': removeBottomMargin([ model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.description, + model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.remarks].join(''))}; + output.internal = true; + outputYml = [ '~/api/', + model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.type.uid.replaceAll(/(\D*{)|(}$)/g, ''), + '.yml'].join(''); + if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['children'] && (model['__global']['_shared'][outputYml].type === 'class')){ + output.dataFrameDescription = [ + model['__global']['_shared'][outputYml].summary, + model['__global']['_shared'][outputYml].remarks].join(''); + const outputYmlChildrenLength = model['__global']['_shared'][outputYml]['children'].length; + for (let j = 0; j < outputYmlChildrenLength; j++){ + if (model['__global']['_shared'][outputYml]['children'][j].type === 'property'){ + potentialEnumYml = '~/api/' + model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.uid + '.yml'; + let enumFields = []; + if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ + enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); + } + if (enumFields.length > 0){ + output.dataFrameDescription = [ + model['__global']['_shared'][outputYml].summary, + model['__global']['_shared'][outputYml].remarks].join(''), + dataFrame.push({ + 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, + model['__global']['_shared'][outputYml]['children'][j].remarks].join('')), + 'enumFields': enumFields, + 'hasEnum': true + }); + } + else{ + dataFrame.push({ + 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, + model['__global']['_shared'][outputYml]['children'][j].remarks].join('')) + }); + } + } + } + } + } + } + if (Object.keys(input).length && Object.keys(dataFrame).length){ + operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true, 'hasDataFrame': true}); + } + else if (Object.keys(input).length){ + operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true}); + } + else if (Object.keys(dataFrame).length){ + operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasDataFrame': true}); + } + else{ + operators.push({'description': description, 'output': output}); + } + } + else if (model.children){ + const childrenLength = model.children.length; + for (let i = 0; i < childrenLength; i++){ + if (model.children[i].uid.includes('Generate') || model.children[i].uid.includes('Process')){ + description = [model.children[i].summary, model.children[i].remarks].join(''); + input = {}; + if (model.children[i].syntax.parameters && model.children[i].syntax.parameters[0]){ + input = { + 'specName': replaceIObservableAndTSource(model.children[i].syntax.parameters[0].type.specName[0].value), + 'name': model.children[i].syntax.parameters[0].type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), + 'description': removeBottomMargin([model.children[i].syntax.parameters[0].description, model.children[i].syntax.parameters[0].remarks].join('')) + }; + input.dataFrame = []; + if (model.__global._shared['~/api/OpenEphys.Onix1.' + input.name + '.yml']){ + input.internal = true; + inputYml = [ '~/api/', + model.children[i].syntax.parameters[0].type.uid.replaceAll(/(\D*{)|(}$)/g, ''), + '.yml'].join(''); + if (model['__global']['_shared'][inputYml] && model['__global']['_shared'][inputYml]['children'] && (model['__global']['_shared'][inputYml].type === 'class')){ + input.dataFrameDescription = [ + model['__global']['_shared'][inputYml].summary, + model['__global']['_shared'][inputYml].remarks + ].join(''); + const inputYmlChildrenLength = model['__global']['_shared'][inputYml]['children'].length; + for (let j = 0; j < inputYmlChildrenLength; j++){ + if (model['__global']['_shared'][inputYml]['children'][j] && model['__global']['_shared'][inputYml]['children'][j].type === 'property'){ + let enumFields = []; + if (model['__global']['_shared'][inputYml]['children'][j].syntax.parameters[0]){ + potentialEnumYml = '~/api/' + model['__global']['_shared'][inputYml]['children'][j].syntax.parameters[0].type.uid + '.yml'; + if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ + enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); + } + } + if (enumFields.length > 0){ + input.dataFrame.push({ + 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, + model['__global']['_shared'][inputYml]['children'][j].remarks].join('')), + 'enumFields': enumFields, + 'hasEnum': true + }); + } + else{ + input.dataFrame.push({ + 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, + model['__global']['_shared'][inputYml]['children'][j].remarks].join('')) + }); + } + } + } + } + else if (model['__global']['_shared'][inputYml] && model['__global']['_shared'][inputYml]['children'] && (model['__global']['_shared'][inputYml].type === 'enum')){ + input.internal = true; + input.dataFrameDescription = [ + model['__global']['_shared'][inputYml].summary, + model['__global']['_shared'][inputYml].remarks + ].join(''); + const inputYmlChildrenLength = model['__global']['_shared'][inputYml]['children'].length; + for (let j = 0; j < inputYmlChildrenLength; j++){ + if (model['__global']['_shared'][inputYml]['children'][j].type === 'property'){ + potentialEnumYml = '~/api/' + model['__global']['_shared'][inputYml]['children'][j].syntax.parameters[0].type.uid + '.yml'; + let enumFields = []; + if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ + enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); + } + if (enumFields.length > 0){ + dataFrame.push({ + 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, + model['__global']['_shared'][inputYml]['children'][j].remarks].join('')), + 'enumFields': enumFields, + 'hasEnum': true + }); + } + else{ + dataFrame.push({ + 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, + model['__global']['_shared'][inputYml]['children'][j].remarks].join('')) + }); + } + } + } + } + } + else { + input.external = true; + } + } + if (model.children[i].syntax.return){ + output = { + 'specName': replaceIObservableAndTSource(model.children[i].syntax.return.type.specName[0].value), + 'name': model.children[i].syntax.return.type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), + 'description': removeBottomMargin([model.children[i].syntax.return.description, model.children[i].syntax.return.remarks].join(''))}; + dataFrame = []; + outputYml = [ '~/api/', + model.children[i].syntax.return.type.uid.replaceAll(/(\D*{)|(}$)/g, ''), + '.yml'].join(''); + if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['children'] && (model['__global']['_shared'][outputYml].type === 'class')){ + output.internal = true; + output.dataFrameDescription = [ + model['__global']['_shared'][outputYml].summary, + model['__global']['_shared'][outputYml].remarks].join(''); + const outputYmlChildrenLength = model['__global']['_shared'][outputYml]['children'].length; + for (let j = 0; j < outputYmlChildrenLength; j++){ + if (model['__global']['_shared'][outputYml]['children'][j].type === 'property'){ + potentialEnumYml = '~/api/' + model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.uid + '.yml'; + let enumFields = []; + if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ + enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); + } + if (enumFields.length > 0){ + dataFrame.push({ + 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, + model['__global']['_shared'][outputYml]['children'][j].remarks].join('')), + 'enumFields': enumFields, + 'hasEnum': true + }); + } + else{ + dataFrame.push({ + 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, + model['__global']['_shared'][outputYml]['children'][j].remarks].join('')) + }); + } + } + } + } + if (dataFrame.length === 0 && input.dataFrame && input.dataFrame.length > 0){ + dataFrame = input.dataFrame; + output.useInputDataFrame = true;; + } + else if (!output.internal) { + output.external = true; + } + if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['inheritedMembers']){ + output.internal = true; + output.external = false; + const inheritedMembersLength = model['__global']['_shared'][outputYml]['inheritedMembers'].length; + for (let j = 0; j < inheritedMembersLength; j++){ + if (model['__global']['_shared'][outputYml]['inheritedMembers'][j].type === 'property'){ + let inheritedMemberYml = '~/api/' + model['__global']['_shared'][outputYml]['inheritedMembers'][j].parent + '.yml'; + if (model['__global']['_shared'][inheritedMemberYml]['children']){ + let inheritedMemberChildrenLength = model['__global']['_shared'][inheritedMemberYml]['children'].length; + for (let k = 0; k < inheritedMemberChildrenLength; k++){ + if (model['__global']['_shared'][outputYml]['inheritedMembers'][j].uid === model['__global']['_shared'][inheritedMemberYml]['children'][k].uid){ + dataFrame.push({ + 'name': model['__global']['_shared'][outputYml]['inheritedMembers'][j].name[0].value, + 'type': model['__global']['_shared'][inheritedMemberYml]['children'][k].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([model['__global']['_shared'][inheritedMemberYml]['children'][k].summary, + model['__global']['_shared'][inheritedMemberYml]['children'][k].remarks].join('')) + }); + } + } + } + } + } + } + else if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['children'] && (model['__global']['_shared'][outputYml].type === 'enum')){ + output.internal = true; + output.external = false; + output.dataFrameDescription = [ + model['__global']['_shared'][outputYml].summary, + model['__global']['_shared'][outputYml].remarks + ].join(''); + output.enumFields = defineEnumFields(model['__global']['_shared'][outputYml]); + output.isEnum = true; + } + else if (!output.internal){ + output.external = true; + } + } + if (Object.keys(input).length && Object.keys(dataFrame).length){ + operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true, 'hasDataFrame': true}); + } + else if (Object.keys(input).length){ + operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true}); + } + else if (Object.keys(dataFrame).length){ + operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasDataFrame': true}); + } + else { + operators.push({'description': description, 'output': output}); + } + } + } + } + return operators; +} + +// Define source or sink in documentation by checking for an explicit Category tag. If the class does not provide one, +// check the inheritance tree of the class. If the class inherits Bonsai.Sink and Bonsai.Combinator, ignore the Bonsai.Sink +// inheritance overrides the Bonsai.Combinator inheritance for determining whether an operator is a source or a combinator. This +// is because maybe sink operators inherit Bonsai.Combinator in addition to Bonsai.Sink. +function defineOperatorType(model){ + let operatorType = {'source': false, 'sink': false, 'combinator': false}; + if (model.syntax && model.syntax.content[0].value){ + if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Source)]')){ + operatorType.source = true; + } + else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Sink)]')){ + operatorType.sink = true; + } + else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Combinator)]')){ + operatorType.combinator = true; + } + } + if (!(operatorType.source || operatorType.sink || operatorType.combinator)){ + if (model.inheritance){ + const inheritanceLength = model.inheritance.length; + for (let i = 0; i < inheritanceLength; i++){ + if (model.inheritance[i].uid.includes('Bonsai.Source')){ + operatorType.source = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.Sink')){ + operatorType.sink = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.Combinator')){ + operatorType.combinator = true; + } + if (model.inheritance[i].uid.includes('OpenEphys.Onix1.MultiDeviceFactory')){ + operatorType.hub = true; + } + else if (model.inheritance[i].uid.includes('OpenEphys.Onix1.SingleDeviceFactory')){ + operatorType.device = true; + } + } + } + } + operatorType.showWorkflow = operatorType.source | operatorType.sink | operatorType.combinator; + return operatorType; +} + +function defineSubOperators(model){ + let subOperators = [] + if (model.children){ + const childrenLength = model.children.length; + for (let i = 0; i < childrenLength; i++){ + if (model.children[i].type === 'property'){ + potentialSubOperatorYml = '~/api/' + model.children[i].syntax.return.type.uid + '.yml'; + if (model['__global']['_shared'][potentialSubOperatorYml]){ + subOperatorChildrenLength = model['__global']['_shared'][potentialSubOperatorYml]['children'].length; + let subProperties = []; + for (let j = 0; j < subOperatorChildrenLength; j++){ + if (model['__global']['_shared'][potentialSubOperatorYml]['children'][j].type === 'property'){ + let enumFields = []; + potentialEnumYml = '~/api/' + model['__global']['_shared'][potentialSubOperatorYml]['children'][j].syntax.return.type.uid + '.yml'; + if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ + enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); + } + if (enumFields.length > 0){ + subProperties.push({'name': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin( [model['__global']['_shared'][potentialSubOperatorYml]['children'][j].summary, + model['__global']['_shared'][potentialSubOperatorYml]['children'][j].remarks].join('')), + 'enumFields': enumFields, + 'hasEnum': true}); + } + else{ + subProperties.push({'name': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].name[0].value, + 'type': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin( [model['__global']['_shared'][potentialSubOperatorYml]['children'][j].summary, + model['__global']['_shared'][potentialSubOperatorYml]['children'][j].remarks].join('')) + }); + } + } + } + if (subProperties.length > 0){ + subOperators.push({ + 'object': model.children[i].name[0].value, + 'type': model.children[i].syntax.return.type.specName[0].value, + 'subProperties': subProperties, + 'hasSubProperties': true, + 'subOperator': true + }); + } + else if (model.__global._shared['~/api/' + model.children[i].name[0].value + '.yml']){ + subOperators.push({ + 'object': model.children[i].name[0].value, + 'type': model.children[i].syntax.return.type.specName[0].value, + 'subOperator': true + }); + } + } + } + } + } + return subOperators +} + +// compile list of properties +function defineProperties(model){ + properties = []; + if (model.children){ + const childrenLength = model.children.length; + for (let i = 0; i < childrenLength; i++){ + if (model.children[i].type === 'property'){ + potentialEnumYml = '~/api/' + model['children'][i].syntax.return.type.uid + '.yml'; + let enumFields = []; + if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ + enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); + } + if (enumFields.length > 0){ + properties.push({ + 'name': model.children[i].name[0].value, + 'type': model.children[i].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([model.children[i].summary, model.children[i].remarks].join('')), + 'enumFields': enumFields, + 'hasEnum': true + }); + } + else { + properties.push({ + 'name': model.children[i].name[0].value, + 'type': model.children[i].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([model.children[i].summary, model.children[i].remarks].join('')) + }); + } + } + } + } + if (model.inheritedMembers){ + const inheritedMembersLength = model.inheritedMembers.length; + for (let i = 0; i < inheritedMembersLength; i++){ + if (model.inheritedMembers[i].type && (model.inheritedMembers[i].type === 'property')){ + let inheritedMemberYml = '~/api/' + model.inheritedMembers[i].parent + '.yml'; + if (model['__global']['_shared'][inheritedMemberYml]['children']){ + let inheritedMemberChildrenLength = model['__global']['_shared'][inheritedMemberYml]['children'].length; + for (let j = 0; j < inheritedMemberChildrenLength; j++){ + if (model.inheritedMembers[i].uid === model['__global']['_shared'][inheritedMemberYml]['children'][j].uid){ + properties.push( + {'name': model.inheritedMembers[i].name[0].value, + 'type': model['__global']['_shared'][inheritedMemberYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([model['__global']['_shared'][inheritedMemberYml]['children'][j].summary, + model['__global']['_shared'][inheritedMemberYml]['children'][j].remarks].join('')) + }); + } + } + } + } + } + } + return properties; +} + +function sortProperties(properties){ + let propertyNames = []; + let propertyNamesThatFitPattern = []; + const propertiesLength = properties.length; + for (let i = 0; i < propertiesLength; i++){ + propertyNames.push([properties[i].name, + /\D+\d+$/.test(properties[i].name), + String(properties[i].name.match(/\D+/)), + Number(properties[i].name.match(/\d+$/))]); + } + for (let i = 0; i < propertiesLength; i++){ + if (propertyNames[i][1]){ + if (!propertyNamesThatFitPattern.includes(propertyNames[i][2])){ + propertyNamesThatFitPattern.push(propertyNames[i][2]); + } + } + } + const propertyNamesThatFitPatternLength = propertyNamesThatFitPattern.length; + for (let j = 0; j < propertyNamesThatFitPatternLength; j++){ + for (let i = 1; i < propertiesLength; i++){ + for (let k = i; k > 0; k--){ + if ((propertyNames[k][2] === propertyNamesThatFitPattern[j]) && (propertyNames[k - 1][2] === propertyNamesThatFitPattern[j])){ + if (propertyNames[k][3] < propertyNames[k - 1][3]){ + swapElements(properties, k, k - 1); + swapElements(propertyNames, k, k - 1); // why does this need to be here for this sortProperties function to work? + } + } + } + } + } + return properties; +} + +const swapElements = (array, index1, index2) => { + const temp = array[index1]; + array[index1] = array[index2]; + array[index2] = temp; +}; + +function defineEnumFields(model){ + let enumFields = []; + if (model.children){ + const childrenLength = model.children.length; + for (let i = 0; i < childrenLength; i++){ + if (model.children[i].type === 'field'){ + enumFields.push({ + 'field&value': model.children[i].syntax.content[0].value, + 'description': removeBottomMargin([ model.children[i].summary, + model.children[i].remarks].join('')) + }); + } + } + } + return enumFields; +} + + +/** + * This method will be called at the start of exports.transform in ManagedReference.html.primary.js + */ +exports.preTransform = function (model) { + + model.oe = {}; + + model.oe.description = [model.summary, model.remarks].join(''); + + operatorType = defineOperatorType(model); + if (operatorType.source){ + model.oe.operatorType = 'source'; + } + else if (operatorType.sink){ + model.oe.operatorType = 'sink'; + } + else if (operatorType.combinator){ + model.oe.operatorType = 'combinator'; + } + + if (operatorType.showWorkflow) { + model.showWorkflow = operatorType.showWorkflow; + } + else { + + } + + if (operatorType.device) { + model.oe.hubOrDevice = 'device'; + model.oe.device = true; + } + else if (operatorType.hub){ + model.oe.hubOrDevice = 'hub'; + model.oe.hub = true; + } + + operators = defineInputsAndOutputs(model); + if (operators.length > 0){ + model.oe.operators = operators; + } + + properties = defineProperties(model); + if (properties.length > 0){ + model.oe.hasProperties = true; + model.oe.properties = sortProperties(properties); + } + + subOperators = defineSubOperators(model); + if (subOperators.length > 0){ + model.oe.hasSubOperators = true; + model.oe.subOperators = subOperators; + } + + if (model.oe.hasProperties && model.oe.hasSubOperators){ + subPropertyNames = []; + hubProperties = []; + const subOperatorsLength = model.oe.subOperators.length; + for (let i = 0; i < subOperatorsLength; i++){ + subPropertyNames.push(model.oe.subOperators[i].object); + } + const propertiesLength = model.oe.properties.length; + for (let i = 0; i < propertiesLength; i++){ + const subPropertyNamesLength = subPropertyNames.length; + propertyNotInSubOperator = false; + for (let j = 0; j < subPropertyNamesLength; j++){ + if (model.oe.properties[i].name === subPropertyNames[j]){ + propertyNotInSubOperator = true; + } + } + if (!propertyNotInSubOperator){ + hubProperties.push(model.oe.properties[i]); + } + } + if (hubProperties.length > 0){ + model.oe.subOperators.push({ + 'object': 'Misc', + 'subProperties': hubProperties, + 'hasSubProperties': true, + 'subOperator': false + }) + } + } + + enumFields = defineEnumFields(model); + if (enumFields.length > 0){ + model.oe.hasEnumFields = true; + model.oe.enumFields = enumFields; + } + + if (model.uid.includes('DeviceFactory')){ + model.showWorkflow = false; + } + + return model; +} + +/** + * This method will be called at the end of exports.transform in ManagedReference.html.primary.js + */ +exports.postTransform = function (model) { + return model; +} 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/conceptual.html.primary.tmpl b/template/api/conceptual.html.primary.tmpl new file mode 100644 index 0000000..921fadb --- /dev/null +++ b/template/api/conceptual.html.primary.tmpl @@ -0,0 +1,40 @@ +{{!master(layout/_master.tmpl)}} + +

{{title}}

+{{#isGuide}} +
+ +{{#workflow}} +{{#isDevice}} +{{>partials/device.workflow}} +{{/isDevice}} +{{#isHeadstage}} +{{>partials/headstage.workflow}} +
+{{>partials/headstage.devices}} +{{/isHeadstage}} +{{/workflow}} + +{{#visualize}} +
+

Visualize Data

+ +{{#visualize_timeseries}} +{{>partials/device.visualize_timeseries}} +{{/visualize_timeseries}} + +{{#visualize_mat}} +{{>partials/device.visualize_mat}} +{{/visualize_mat}} + +{{#visualize_text}} +{{>partials/device.visualize_text}} +{{/visualize_text}} + +

Refer to Visualizing Data for more information on visualizers and how to download them.

+

Note that data will not be shown until a workflow is running. Check out Running a Workflow to see how to run a workflow.

+ +{{/visualize}} +{{/isGuide}} + +{{{conceptual}}} \ No newline at end of file diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial new file mode 100644 index 0000000..0e25a93 --- /dev/null +++ b/template/api/partials/class.tmpl.partial @@ -0,0 +1,109 @@ +{{#isClass}} + +
+

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

+ {{#oe.operatorType}} +

+ {{oe.operatorType}} Operator +

+ {{/oe.operatorType}} +
+ +{{#oe.device}} + +
+ This operator is a device operator. If you are using Open Ephys hardware, use an aggregate operator that corresponds to your particular headstage(s) instead. Aggregate operators confer the following benefits: +
    +
  • The address properties of aggregate operators are automatically configured whereas a device operators requires manual configuration of its address property. This improves ease of using Open Ephys hardware.
  • +
  • All devices that belong to a hub (which is a collection of devices i.e. a headstage or breakout board) are automatically included, excluding the possibility of omitting configuration of a device that belongs to a hub. This improves ease of using Open Ephys hardware.
  • +
  • The workflow is less cluttered with configuration operators as one aggregate operator can correspond to multiple device operators. This improves workflow readability.
  • +
+
+ +{{/oe.device}} + +
{{{oe.description}}}
+ +{{#showWorkflow}} +

{{name.0.value}} Workflow

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

Inputs & Outputs

+ +{{>partials/diagram}} + +{{/oe.operatorType}} + +{{#oe.hasSubOperators}} + +

Properties

+ +
{{name.0.value}} is a aggregate operator. It comprises of the following sub-operators:
+
+ +{{#oe.subOperators}} + +

{{{object}}}

+ +{{#subOperator}} +
{{{object}}} is a {{{type}}} operator encapsulated by the {{name.0.value}} operator.
+{{/subOperator}} + +{{#hasSubProperties}} + + + + + + + + + + {{#subProperties}} + {{>partials/propertyTables}} + {{/subProperties}} + +
PropertyTypeDescription
+ +{{/hasSubProperties}} + +{{^hasSubProperties}} + +
This subclass has no public properties.
+ +{{/hasSubProperties}} + +{{/oe.subOperators}} + +{{/oe.hasSubOperators}} + +{{^oe.hasSubOperators}} + +{{#oe.hasProperties}} + +

Properties

+ + + + + + + + + + {{#oe.properties}} + {{>partials/propertyTables}} + {{/oe.properties}} + +
PropertyTypeDescription
+ +{{/oe.hasProperties}} + +{{/oe.hasSubOperators}} + +{{/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..c87f104 --- /dev/null +++ b/template/api/partials/diagram.tmpl.partial @@ -0,0 +1,114 @@ +{{#oe.operators}} + +
{{{description}}}
+ + + + + + + + + + + + + + + + +
+ + + {{#input}} + + + {{/input}} + +
+ {{#internal}}
{{{name}}}
{{/internal}} + {{#external}}
{{{specName}}}
{{/external}} + {{{description}}} +
+ right-arrow +
+
+ representation of a source, sink, or combinator operator + + + + + + +
+ right-arrow + + {{#output.internal}}
{{{output.name}}}
{{/output.internal}} + {{#output.external}}
{{{output.specName}}}
{{/output.external}} +
{{{output.description}}}
+
+
+ +{{/oe.operators}} + +{{#oe.operators.0.hasDataFrame}} +{{^oe.operators.0.output.useInputDataFrame}} + +

{{{oe.operators.0.output.name}}}

+
{{{oe.operators.0.output.dataFrameDescription}}}
+ +{{/oe.operators.0.output.useInputDataFrame}} + +{{#oe.operators.0.output.useInputDataFrame}} + +

{{{oe.operators.0.input.name}}}

+
{{{oe.operators.0.input.dataFrameDescription}}}
+ +{{/oe.operators.0.output.useInputDataFrame}} + + + + + + + + + + {{#oe.operators.0.dataFrame}} + {{>partials/propertyTables}} + {{/oe.operators.0.dataFrame}} + +
Data Frame MemberTypeDescription
+ +{{/oe.operators.0.hasDataFrame}} + +{{#oe.operators.0.output.isEnum}} + +

{{{oe.operators.0.output.name}}}

+ +
{{{oe.operators.0.output.dataFrameDescription}}}
+ + + + + + + + + {{#oe.operators.0.output.enumFields}} + + + + + {{/oe.operators.0.output.enumFields}} + +
Field & ValueDescription
+ + {{{field&value}}} + + + {{{description}}} +
+ +{{/oe.operators.0.output.isEnum}} + diff --git a/template/api/partials/enum.tmpl.partial b/template/api/partials/enum.tmpl.partial new file mode 100644 index 0000000..66b28c6 --- /dev/null +++ b/template/api/partials/enum.tmpl.partial @@ -0,0 +1,38 @@ +{{#isEnum}} + +

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

+ +{{{oe.description}}} + +{{#oe.hasEnumFields}} + +

Fields

+ + + + + + + + + {{#oe.enumFields}} + + + + + {{/oe.enumFields}} + +
Field & ValueDescription
+ + {{{field&value}}} + + + {{{description}}} +
+ +{{/oe.hasEnumFields}} + +{{/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..84d3b9e --- /dev/null +++ b/template/api/partials/propertyTables.tmpl.partial @@ -0,0 +1,37 @@ + + + + + {{{name}}} + + + + + + {{{type}}} + + + + + + {{{description}}} + + {{#hasEnum}} + +
+ + {{#enumFields}} + +
{{{field&value}}}
+ +
{{{description}}}
+ + {{/enumFields}} + +
+ + {{/hasEnum}} + + + + \ No newline at end of file diff --git a/template/api/toc.extension.js b/template/api/toc.extension.js new file mode 100644 index 0000000..cac0e81 --- /dev/null +++ b/template/api/toc.extension.js @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/** + * This method will be called at the start of exports.transform in toc.html.js and toc.json.js + */ +exports.preTransform = function (model) { + if (model.items[0].name === 'OpenEphys.Onix1'){ + if (model.items[0].items){ + itemsItemsLength = model.items[0].items.length; + let items = [{ + 'name': 'Infrastructural Operators', + 'items': []}, { + 'name': 'Breakout Board, Headstage, Etc. Configuration Operators', + 'items': []}, { + 'name': 'Data I/O Operators', + 'items': []}, { + 'name': 'Device Configuration Operators (Not Recommended)', + 'items': [] + }]; + for (let i = 0; i < itemsItemsLength; i++) { + globalYml = '~/api/' + model.items[0].items[i].topicUid + '.yml'; + if (model.items[0].items[i].name.includes('DataFrame') || + model.items[0].items[i].name.includes('DeviceFactory') || + model.items[0].items[i].name.includes('ContextTask') || + model.items[0].items[i].name.includes('DeviceNameConverter') || + model.items[0].items[i].name.includes('ConfigureDS90UB9x') || + model.items[0].items[i].name.includes('ConfigureFmcLinkController') || + model.items[0].items[i].name.includes('DeviceContext')){ + model.items[0].items[i].hide = true; + } + else if (model.__global._shared[globalYml] && model.__global._shared[globalYml].type === 'enum') { + model.items[0].items[i].hide = true; + } + else { + if (model.__global._shared[globalYml] && model.__global._shared[globalYml].type === 'class'){ + const inheritanceLength = model.__global._shared[globalYml].inheritance.length; + device = false; + hub = false; + for (let j = 0; j < inheritanceLength; j++){ + if (model.__global._shared[globalYml].inheritance[j].uid === 'OpenEphys.Onix1.SingleDeviceFactory'){ + device = true; + } + else if (model.__global._shared[globalYml].inheritance[j].uid === 'OpenEphys.Onix1.MultiDeviceFactory'){ + hub = true; + } + } + if (model.items[0].items[i].name.includes('CreateContext') || model.items[0].items[i].name.includes('StartAcquisition')){ + items[0].items.push(model.items[0].items[i]); + } + else if (device){ + items[3].items.push(model.items[0].items[i]); + } + else if (hub){ + items[1].items.push(model.items[0].items[i]); + } + else { + items[2].items.push(model.items[0].items[i]); + } + } + } + } + model.items[0].items = items; + } + } + return model; +} + +/** + * This method will be called at the end of exports.transform in toc.html.js and toc.json.js + */ +exports.postTransform = function (model) { + return model; +} From 3053524bfa331a14682b786678ab48868eb07863 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 10:08:05 -0700 Subject: [PATCH 02/70] Delete files not required for API template --- template/api/ManagedReference.overwrite.js | 5 -- template/api/toc.extension.js | 74 ---------------------- 2 files changed, 79 deletions(-) delete mode 100644 template/api/ManagedReference.overwrite.js delete mode 100644 template/api/toc.extension.js diff --git a/template/api/ManagedReference.overwrite.js b/template/api/ManagedReference.overwrite.js deleted file mode 100644 index ff7ef9d..0000000 --- a/template/api/ManagedReference.overwrite.js +++ /dev/null @@ -1,5 +0,0 @@ -exports.getOptions = function (model) { - return { - isShared: true - }; -} \ No newline at end of file diff --git a/template/api/toc.extension.js b/template/api/toc.extension.js deleted file mode 100644 index cac0e81..0000000 --- a/template/api/toc.extension.js +++ /dev/null @@ -1,74 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/** - * This method will be called at the start of exports.transform in toc.html.js and toc.json.js - */ -exports.preTransform = function (model) { - if (model.items[0].name === 'OpenEphys.Onix1'){ - if (model.items[0].items){ - itemsItemsLength = model.items[0].items.length; - let items = [{ - 'name': 'Infrastructural Operators', - 'items': []}, { - 'name': 'Breakout Board, Headstage, Etc. Configuration Operators', - 'items': []}, { - 'name': 'Data I/O Operators', - 'items': []}, { - 'name': 'Device Configuration Operators (Not Recommended)', - 'items': [] - }]; - for (let i = 0; i < itemsItemsLength; i++) { - globalYml = '~/api/' + model.items[0].items[i].topicUid + '.yml'; - if (model.items[0].items[i].name.includes('DataFrame') || - model.items[0].items[i].name.includes('DeviceFactory') || - model.items[0].items[i].name.includes('ContextTask') || - model.items[0].items[i].name.includes('DeviceNameConverter') || - model.items[0].items[i].name.includes('ConfigureDS90UB9x') || - model.items[0].items[i].name.includes('ConfigureFmcLinkController') || - model.items[0].items[i].name.includes('DeviceContext')){ - model.items[0].items[i].hide = true; - } - else if (model.__global._shared[globalYml] && model.__global._shared[globalYml].type === 'enum') { - model.items[0].items[i].hide = true; - } - else { - if (model.__global._shared[globalYml] && model.__global._shared[globalYml].type === 'class'){ - const inheritanceLength = model.__global._shared[globalYml].inheritance.length; - device = false; - hub = false; - for (let j = 0; j < inheritanceLength; j++){ - if (model.__global._shared[globalYml].inheritance[j].uid === 'OpenEphys.Onix1.SingleDeviceFactory'){ - device = true; - } - else if (model.__global._shared[globalYml].inheritance[j].uid === 'OpenEphys.Onix1.MultiDeviceFactory'){ - hub = true; - } - } - if (model.items[0].items[i].name.includes('CreateContext') || model.items[0].items[i].name.includes('StartAcquisition')){ - items[0].items.push(model.items[0].items[i]); - } - else if (device){ - items[3].items.push(model.items[0].items[i]); - } - else if (hub){ - items[1].items.push(model.items[0].items[i]); - } - else { - items[2].items.push(model.items[0].items[i]); - } - } - } - } - model.items[0].items = items; - } - } - return model; -} - -/** - * This method will be called at the end of exports.transform in toc.html.js and toc.json.js - */ -exports.postTransform = function (model) { - return model; -} From 3e629e3b511d35e6ebde20dfa0ccc56deaf35dc0 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 11:43:34 -0700 Subject: [PATCH 03/70] Add refactored Bonsai and Mref js extensions --- template/api/BonsaiCommon.js | 64 ++++++++++++++++++++++ template/api/ManagedReference.extension.js | 47 +--------------- 2 files changed, 67 insertions(+), 44 deletions(-) create mode 100644 template/api/BonsaiCommon.js diff --git a/template/api/BonsaiCommon.js b/template/api/BonsaiCommon.js new file mode 100644 index 0000000..9eb4ebf --- /dev/null +++ b/template/api/BonsaiCommon.js @@ -0,0 +1,64 @@ +// This module contains common functions utilised by the Docfx Template System for Bonsai API pages. + +exports.defineOperatorType = function defineOperatorType(model){ + // 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. + let operatorType = {'source': false, 'combinator': false, 'sink': false, 'transform' : false}; + if (model.syntax && model.syntax.content[0].value){ + if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Source)]')){ + operatorType.source = true; + } + else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Combinator)]')){ + operatorType.combinator = true; + } + else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Sink)]')){ + operatorType.sink = true; + } + else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Transform)]')){ + operatorType.transform = true; + } + } + if (!(operatorType.source || operatorType.combinator || operatorType.sink || operatorType.transform)){ + if (model.inheritance){ + const inheritanceLength = model.inheritance.length; + for (let i = 0; i < inheritanceLength; i++){ + + // This section checks for common Bonsai operator type nodes. Ignore Bonsai.Combinator if Bonsai.Sink or Bonsai.Transform is present + // as many sink and transform operators inherit Bonsai.Combinator + if (model.inheritance[i].uid.includes('Bonsai.Source')){ + operatorType.source = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.Combinator')){ + operatorType.combinator = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.Sink')){ + operatorType.combinator = false; + operatorType.sink = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.Transform')){ + operatorType.combinator = false; + operatorType.transform = true; + } + + // This section checks unique Bonsai operator type nodes + else if (model.inheritance[i].uid.includes('Bonsai.WindowCombinator')){ + operatorType.combinator = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.IO.StreamSink')){ + operatorType.sink = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.IO.FileSink')){ + operatorType.sink = true; + } + else if (model.inheritance[i].uid.includes('Bonsai.Dsp.ArrayTransform')){ + operatorType.transform = true; + } + } + } + } + + // Flag for showing Bonsai workflow container for Bonsai visible operators + operatorType.showWorkflow = operatorType.source | operatorType.combinator | operatorType.sink | operatorType.transform; + return operatorType; + } + diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index c5516c8..2f930d3 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +var BonsaiCommon = require('./BonsaiCommon.js') + function removeBottomMargin(str){ return str.split('').reverse().join('') .replace( ' Date: Wed, 14 Aug 2024 14:02:16 -0700 Subject: [PATCH 04/70] Add back required mref.overwrite.js --- template/api/ManagedReference.overwrite.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 template/api/ManagedReference.overwrite.js 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 From 0d08ec148a837f4c80239a83a4bc637d1a40513e Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 16:43:29 -0700 Subject: [PATCH 05/70] Remove OE specific code from define inputs and outputs --- template/api/ManagedReference.extension.js | 173 +-------------------- 1 file changed, 2 insertions(+), 171 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 2f930d3..66e7b86 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -28,88 +28,7 @@ function replaceIObservableAndTSource(str){ // compile list of data for input --> o --> output diagrams function defineInputsAndOutputs(model){ operators = []; - if (model.oe.hubOrDevice === 'hub'){ - const hubChildrenLength = model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children.length; - for (let i = 0; i < hubChildrenLength; i++){ - if (model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].name[0].value.includes('Process')){ - description = [ - model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].summary, - model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].remarks - ].join(''); - } - input = {}; - if (model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters && model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0]){ - input = { - 'specName': replaceIObservableAndTSource(model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].type.specName[0].value), - 'name': model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), - 'description': removeBottomMargin([ model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].description, - model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.parameters[0].remarks].join(''))}; - input.internal = true; - } - output = {}; - dataFrame = []; - if (model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return){ - output = { - 'specName': replaceIObservableAndTSource(model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.type.specName[0].value), - 'name': model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), - 'description': removeBottomMargin([ model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.description, - model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.remarks].join(''))}; - output.internal = true; - outputYml = [ '~/api/', - model.__global._shared['~/api/OpenEphys.Onix1.MultiDeviceFactory.yml'].children[i].syntax.return.type.uid.replaceAll(/(\D*{)|(}$)/g, ''), - '.yml'].join(''); - if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['children'] && (model['__global']['_shared'][outputYml].type === 'class')){ - output.dataFrameDescription = [ - model['__global']['_shared'][outputYml].summary, - model['__global']['_shared'][outputYml].remarks].join(''); - const outputYmlChildrenLength = model['__global']['_shared'][outputYml]['children'].length; - for (let j = 0; j < outputYmlChildrenLength; j++){ - if (model['__global']['_shared'][outputYml]['children'][j].type === 'property'){ - potentialEnumYml = '~/api/' + model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.uid + '.yml'; - let enumFields = []; - if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ - enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); - } - if (enumFields.length > 0){ - output.dataFrameDescription = [ - model['__global']['_shared'][outputYml].summary, - model['__global']['_shared'][outputYml].remarks].join(''), - dataFrame.push({ - 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, - model['__global']['_shared'][outputYml]['children'][j].remarks].join('')), - 'enumFields': enumFields, - 'hasEnum': true - }); - } - else{ - dataFrame.push({ - 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, - model['__global']['_shared'][outputYml]['children'][j].remarks].join('')) - }); - } - } - } - } - } - } - if (Object.keys(input).length && Object.keys(dataFrame).length){ - operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true, 'hasDataFrame': true}); - } - else if (Object.keys(input).length){ - operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true}); - } - else if (Object.keys(dataFrame).length){ - operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasDataFrame': true}); - } - else{ - operators.push({'description': description, 'output': output}); - } - } - else if (model.children){ + if (model.children){ const childrenLength = model.children.length; for (let i = 0; i < childrenLength; i++){ if (model.children[i].uid.includes('Generate') || model.children[i].uid.includes('Process')){ @@ -122,86 +41,7 @@ function defineInputsAndOutputs(model){ 'description': removeBottomMargin([model.children[i].syntax.parameters[0].description, model.children[i].syntax.parameters[0].remarks].join('')) }; input.dataFrame = []; - if (model.__global._shared['~/api/OpenEphys.Onix1.' + input.name + '.yml']){ - input.internal = true; - inputYml = [ '~/api/', - model.children[i].syntax.parameters[0].type.uid.replaceAll(/(\D*{)|(}$)/g, ''), - '.yml'].join(''); - if (model['__global']['_shared'][inputYml] && model['__global']['_shared'][inputYml]['children'] && (model['__global']['_shared'][inputYml].type === 'class')){ - input.dataFrameDescription = [ - model['__global']['_shared'][inputYml].summary, - model['__global']['_shared'][inputYml].remarks - ].join(''); - const inputYmlChildrenLength = model['__global']['_shared'][inputYml]['children'].length; - for (let j = 0; j < inputYmlChildrenLength; j++){ - if (model['__global']['_shared'][inputYml]['children'][j] && model['__global']['_shared'][inputYml]['children'][j].type === 'property'){ - let enumFields = []; - if (model['__global']['_shared'][inputYml]['children'][j].syntax.parameters[0]){ - potentialEnumYml = '~/api/' + model['__global']['_shared'][inputYml]['children'][j].syntax.parameters[0].type.uid + '.yml'; - if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ - enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); - } - } - if (enumFields.length > 0){ - input.dataFrame.push({ - 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, - model['__global']['_shared'][inputYml]['children'][j].remarks].join('')), - 'enumFields': enumFields, - 'hasEnum': true - }); - } - else{ - input.dataFrame.push({ - 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, - model['__global']['_shared'][inputYml]['children'][j].remarks].join('')) - }); - } - } - } - } - else if (model['__global']['_shared'][inputYml] && model['__global']['_shared'][inputYml]['children'] && (model['__global']['_shared'][inputYml].type === 'enum')){ - input.internal = true; - input.dataFrameDescription = [ - model['__global']['_shared'][inputYml].summary, - model['__global']['_shared'][inputYml].remarks - ].join(''); - const inputYmlChildrenLength = model['__global']['_shared'][inputYml]['children'].length; - for (let j = 0; j < inputYmlChildrenLength; j++){ - if (model['__global']['_shared'][inputYml]['children'][j].type === 'property'){ - potentialEnumYml = '~/api/' + model['__global']['_shared'][inputYml]['children'][j].syntax.parameters[0].type.uid + '.yml'; - let enumFields = []; - if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ - enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); - } - if (enumFields.length > 0){ - dataFrame.push({ - 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, - model['__global']['_shared'][inputYml]['children'][j].remarks].join('')), - 'enumFields': enumFields, - 'hasEnum': true - }); - } - else{ - dataFrame.push({ - 'name': model['__global']['_shared'][inputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][inputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][inputYml]['children'][j].summary, - model['__global']['_shared'][inputYml]['children'][j].remarks].join('')) - }); - } - } - } - } - } - else { - input.external = true; - } + input.external = true; } if (model.children[i].syntax.return){ output = { @@ -503,15 +343,6 @@ exports.preTransform = function (model) { else { } - - if (operatorType.device) { - model.oe.hubOrDevice = 'device'; - model.oe.device = true; - } - else if (operatorType.hub){ - model.oe.hubOrDevice = 'hub'; - model.oe.hub = true; - } operators = defineInputsAndOutputs(model); if (operators.length > 0){ From 45e03d4e5bba2ef39744527c9f47215e14c24d24 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 18:06:12 -0700 Subject: [PATCH 06/70] Add missing source link for enums --- template/api/partials/enum.tmpl.partial | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/api/partials/enum.tmpl.partial b/template/api/partials/enum.tmpl.partial index 66b28c6..cf27bff 100644 --- a/template/api/partials/enum.tmpl.partial +++ b/template/api/partials/enum.tmpl.partial @@ -1,6 +1,6 @@ {{#isEnum}} -

+

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

From bebd2e0eb12b0762a53069d750262a5f52389a02 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 19:58:40 -0700 Subject: [PATCH 07/70] Simplify enum processing and strip OE specific code - The original template has code that enables display of enums values and description on some of the classes that use them - Some of it seems to be specific to OE nodes, but I have kept the one in the defineProperties function that applies generally --- template/api/ManagedReference.extension.js | 58 ++++------------------ template/api/partials/enum.tmpl.partial | 8 +-- 2 files changed, 14 insertions(+), 52 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 66e7b86..df61cff 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -3,6 +3,7 @@ var BonsaiCommon = require('./BonsaiCommon.js') +// This function is important for stripping the extra line that is present in some fields function removeBottomMargin(str){ return str.split('').reverse().join('') .replace( ' 0){ - dataFrame.push({ - 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, - model['__global']['_shared'][outputYml]['children'][j].remarks].join('')), - 'enumFields': enumFields, - 'hasEnum': true - }); - } - else{ dataFrame.push({ 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, @@ -83,7 +68,6 @@ function defineInputsAndOutputs(model){ model['__global']['_shared'][outputYml]['children'][j].remarks].join('')) }); } - } } } if (dataFrame.length === 0 && input.dataFrame && input.dataFrame.length > 0){ @@ -116,16 +100,6 @@ function defineInputsAndOutputs(model){ } } } - else if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['children'] && (model['__global']['_shared'][outputYml].type === 'enum')){ - output.internal = true; - output.external = false; - output.dataFrameDescription = [ - model['__global']['_shared'][outputYml].summary, - model['__global']['_shared'][outputYml].remarks - ].join(''); - output.enumFields = defineEnumFields(model['__global']['_shared'][outputYml]); - output.isEnum = true; - } else if (!output.internal){ output.external = true; } @@ -160,26 +134,11 @@ function defineSubOperators(model){ let subProperties = []; for (let j = 0; j < subOperatorChildrenLength; j++){ if (model['__global']['_shared'][potentialSubOperatorYml]['children'][j].type === 'property'){ - let enumFields = []; - potentialEnumYml = '~/api/' + model['__global']['_shared'][potentialSubOperatorYml]['children'][j].syntax.return.type.uid + '.yml'; - if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ - enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); - } - if (enumFields.length > 0){ - subProperties.push({'name': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin( [model['__global']['_shared'][potentialSubOperatorYml]['children'][j].summary, - model['__global']['_shared'][potentialSubOperatorYml]['children'][j].remarks].join('')), - 'enumFields': enumFields, - 'hasEnum': true}); - } - else{ subProperties.push({'name': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].name[0].value, 'type': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].syntax.return.type.specName[0].value, 'description': removeBottomMargin( [model['__global']['_shared'][potentialSubOperatorYml]['children'][j].summary, model['__global']['_shared'][potentialSubOperatorYml]['children'][j].remarks].join('')) }); - } } } if (subProperties.length > 0){ @@ -212,6 +171,10 @@ function defineProperties(model){ const childrenLength = model.children.length; for (let i = 0; i < childrenLength; i++){ if (model.children[i].type === 'property'){ + + // this section adds enum members and summary to the property table if the property is an enum + // however doesnt always work (In Pulsepal Repo - Outputchannel enum is the only one out of 6 enums that doesnt work) + // bug present in original template so need to troubleshoot potentialEnumYml = '~/api/' + model['children'][i].syntax.return.type.uid + '.yml'; let enumFields = []; if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ @@ -226,6 +189,7 @@ function defineProperties(model){ 'hasEnum': true }); } + else { properties.push({ 'name': model.children[i].name[0].value, @@ -299,6 +263,8 @@ const swapElements = (array, index1, index2) => { array[index2] = temp; }; +// 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 defineEnumFields(model){ let enumFields = []; if (model.children){ @@ -393,12 +359,8 @@ exports.preTransform = function (model) { enumFields = defineEnumFields(model); if (enumFields.length > 0){ - model.oe.hasEnumFields = true; - model.oe.enumFields = enumFields; - } - - if (model.uid.includes('DeviceFactory')){ - model.showWorkflow = false; + model.hasEnumFields = true; + model.enumFields = enumFields; } return model; diff --git a/template/api/partials/enum.tmpl.partial b/template/api/partials/enum.tmpl.partial index cf27bff..32975ee 100644 --- a/template/api/partials/enum.tmpl.partial +++ b/template/api/partials/enum.tmpl.partial @@ -7,7 +7,7 @@ {{{oe.description}}} -{{#oe.hasEnumFields}} +{{#hasEnumFields}}

Fields

@@ -18,7 +18,7 @@ Description - {{#oe.enumFields}} + {{#enumFields}} @@ -29,10 +29,10 @@ {{{description}}} - {{/oe.enumFields}} + {{/enumFields}} -{{/oe.hasEnumFields}} +{{/hasEnumFields}} {{/isEnum}} \ No newline at end of file From 9a1f75dc4df8245d0efef6c43bda0be00e53c14c Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 22:07:12 -0700 Subject: [PATCH 08/70] Strip OE specific suboperator functions --- template/api/ManagedReference.extension.js | 88 ++-------------------- 1 file changed, 6 insertions(+), 82 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index df61cff..67b2c87 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -122,48 +122,6 @@ function defineInputsAndOutputs(model){ return operators; } -function defineSubOperators(model){ - let subOperators = [] - if (model.children){ - const childrenLength = model.children.length; - for (let i = 0; i < childrenLength; i++){ - if (model.children[i].type === 'property'){ - potentialSubOperatorYml = '~/api/' + model.children[i].syntax.return.type.uid + '.yml'; - if (model['__global']['_shared'][potentialSubOperatorYml]){ - subOperatorChildrenLength = model['__global']['_shared'][potentialSubOperatorYml]['children'].length; - let subProperties = []; - for (let j = 0; j < subOperatorChildrenLength; j++){ - if (model['__global']['_shared'][potentialSubOperatorYml]['children'][j].type === 'property'){ - subProperties.push({'name': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][potentialSubOperatorYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin( [model['__global']['_shared'][potentialSubOperatorYml]['children'][j].summary, - model['__global']['_shared'][potentialSubOperatorYml]['children'][j].remarks].join('')) - }); - } - } - if (subProperties.length > 0){ - subOperators.push({ - 'object': model.children[i].name[0].value, - 'type': model.children[i].syntax.return.type.specName[0].value, - 'subProperties': subProperties, - 'hasSubProperties': true, - 'subOperator': true - }); - } - else if (model.__global._shared['~/api/' + model.children[i].name[0].value + '.yml']){ - subOperators.push({ - 'object': model.children[i].name[0].value, - 'type': model.children[i].syntax.return.type.specName[0].value, - 'subOperator': true - }); - } - } - } - } - } - return subOperators -} - // compile list of properties function defineProperties(model){ properties = []; @@ -172,7 +130,7 @@ function defineProperties(model){ for (let i = 0; i < childrenLength; i++){ if (model.children[i].type === 'property'){ - // this section adds enum members and summary to the property table if the property is an enum + // This section adds enum members and summary to the property table if the property is an enum // however doesnt always work (In Pulsepal Repo - Outputchannel enum is the only one out of 6 enums that doesnt work) // bug present in original template so need to troubleshoot potentialEnumYml = '~/api/' + model['children'][i].syntax.return.type.uid + '.yml'; @@ -189,7 +147,7 @@ function defineProperties(model){ 'hasEnum': true }); } - + // This adds the rest of the properties else { properties.push({ 'name': model.children[i].name[0].value, @@ -200,6 +158,7 @@ function defineProperties(model){ } } } + //check if this section is necessary if (model.inheritedMembers){ const inheritedMembersLength = model.inheritedMembers.length; for (let i = 0; i < inheritedMembersLength; i++){ @@ -224,6 +183,8 @@ function defineProperties(model){ return properties; } +// While docfx has an option to sort enum fields alphabetically, it does not have a similar option for class properties +// and this function provides that. function sortProperties(properties){ let propertyNames = []; let propertyNamesThatFitPattern = []; @@ -307,7 +268,6 @@ exports.preTransform = function (model) { model.showWorkflow = operatorType.showWorkflow; } else { - } operators = defineInputsAndOutputs(model); @@ -320,42 +280,6 @@ exports.preTransform = function (model) { model.oe.hasProperties = true; model.oe.properties = sortProperties(properties); } - - subOperators = defineSubOperators(model); - if (subOperators.length > 0){ - model.oe.hasSubOperators = true; - model.oe.subOperators = subOperators; - } - - if (model.oe.hasProperties && model.oe.hasSubOperators){ - subPropertyNames = []; - hubProperties = []; - const subOperatorsLength = model.oe.subOperators.length; - for (let i = 0; i < subOperatorsLength; i++){ - subPropertyNames.push(model.oe.subOperators[i].object); - } - const propertiesLength = model.oe.properties.length; - for (let i = 0; i < propertiesLength; i++){ - const subPropertyNamesLength = subPropertyNames.length; - propertyNotInSubOperator = false; - for (let j = 0; j < subPropertyNamesLength; j++){ - if (model.oe.properties[i].name === subPropertyNames[j]){ - propertyNotInSubOperator = true; - } - } - if (!propertyNotInSubOperator){ - hubProperties.push(model.oe.properties[i]); - } - } - if (hubProperties.length > 0){ - model.oe.subOperators.push({ - 'object': 'Misc', - 'subProperties': hubProperties, - 'hasSubProperties': true, - 'subOperator': false - }) - } - } enumFields = defineEnumFields(model); if (enumFields.length > 0){ @@ -371,4 +295,4 @@ exports.preTransform = function (model) { */ exports.postTransform = function (model) { return model; -} +} \ No newline at end of file From 428370d6e421b6eb89b86962eeb6ca7a09aa8391 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 22:49:11 -0700 Subject: [PATCH 09/70] Strip inherited members in properties table - Not sure what that section was doing but removing it didnt change anything, so it was probably OE specific --- template/api/ManagedReference.extension.js | 26 +------------- template/api/conceptual.html.primary.tmpl | 40 ---------------------- 2 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 template/api/conceptual.html.primary.tmpl diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 67b2c87..5aaf958 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -158,28 +158,6 @@ function defineProperties(model){ } } } - //check if this section is necessary - if (model.inheritedMembers){ - const inheritedMembersLength = model.inheritedMembers.length; - for (let i = 0; i < inheritedMembersLength; i++){ - if (model.inheritedMembers[i].type && (model.inheritedMembers[i].type === 'property')){ - let inheritedMemberYml = '~/api/' + model.inheritedMembers[i].parent + '.yml'; - if (model['__global']['_shared'][inheritedMemberYml]['children']){ - let inheritedMemberChildrenLength = model['__global']['_shared'][inheritedMemberYml]['children'].length; - for (let j = 0; j < inheritedMemberChildrenLength; j++){ - if (model.inheritedMembers[i].uid === model['__global']['_shared'][inheritedMemberYml]['children'][j].uid){ - properties.push( - {'name': model.inheritedMembers[i].name[0].value, - 'type': model['__global']['_shared'][inheritedMemberYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([model['__global']['_shared'][inheritedMemberYml]['children'][j].summary, - model['__global']['_shared'][inheritedMemberYml]['children'][j].remarks].join('')) - }); - } - } - } - } - } - } return properties; } @@ -267,9 +245,7 @@ exports.preTransform = function (model) { if (operatorType.showWorkflow) { model.showWorkflow = operatorType.showWorkflow; } - else { - } - + operators = defineInputsAndOutputs(model); if (operators.length > 0){ model.oe.operators = operators; diff --git a/template/api/conceptual.html.primary.tmpl b/template/api/conceptual.html.primary.tmpl deleted file mode 100644 index 921fadb..0000000 --- a/template/api/conceptual.html.primary.tmpl +++ /dev/null @@ -1,40 +0,0 @@ -{{!master(layout/_master.tmpl)}} - -

{{title}}

-{{#isGuide}} -
- -{{#workflow}} -{{#isDevice}} -{{>partials/device.workflow}} -{{/isDevice}} -{{#isHeadstage}} -{{>partials/headstage.workflow}} -
-{{>partials/headstage.devices}} -{{/isHeadstage}} -{{/workflow}} - -{{#visualize}} -
-

Visualize Data

- -{{#visualize_timeseries}} -{{>partials/device.visualize_timeseries}} -{{/visualize_timeseries}} - -{{#visualize_mat}} -{{>partials/device.visualize_mat}} -{{/visualize_mat}} - -{{#visualize_text}} -{{>partials/device.visualize_text}} -{{/visualize_text}} - -

Refer to Visualizing Data for more information on visualizers and how to download them.

-

Note that data will not be shown until a workflow is running. Check out Running a Workflow to see how to run a workflow.

- -{{/visualize}} -{{/isGuide}} - -{{{conceptual}}} \ No newline at end of file From 56accf53146d2dd6444e761d5c6a597d889d6f4b Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 23:18:07 -0700 Subject: [PATCH 10/70] Revert "Strip inherited members in properties table" This reverts commit 428370d6e421b6eb89b86962eeb6ca7a09aa8391. --- template/api/ManagedReference.extension.js | 26 +++++++++++++- template/api/conceptual.html.primary.tmpl | 40 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 template/api/conceptual.html.primary.tmpl diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 5aaf958..67b2c87 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -158,6 +158,28 @@ function defineProperties(model){ } } } + //check if this section is necessary + if (model.inheritedMembers){ + const inheritedMembersLength = model.inheritedMembers.length; + for (let i = 0; i < inheritedMembersLength; i++){ + if (model.inheritedMembers[i].type && (model.inheritedMembers[i].type === 'property')){ + let inheritedMemberYml = '~/api/' + model.inheritedMembers[i].parent + '.yml'; + if (model['__global']['_shared'][inheritedMemberYml]['children']){ + let inheritedMemberChildrenLength = model['__global']['_shared'][inheritedMemberYml]['children'].length; + for (let j = 0; j < inheritedMemberChildrenLength; j++){ + if (model.inheritedMembers[i].uid === model['__global']['_shared'][inheritedMemberYml]['children'][j].uid){ + properties.push( + {'name': model.inheritedMembers[i].name[0].value, + 'type': model['__global']['_shared'][inheritedMemberYml]['children'][j].syntax.return.type.specName[0].value, + 'description': removeBottomMargin([model['__global']['_shared'][inheritedMemberYml]['children'][j].summary, + model['__global']['_shared'][inheritedMemberYml]['children'][j].remarks].join('')) + }); + } + } + } + } + } + } return properties; } @@ -245,7 +267,9 @@ exports.preTransform = function (model) { if (operatorType.showWorkflow) { model.showWorkflow = operatorType.showWorkflow; } - + else { + } + operators = defineInputsAndOutputs(model); if (operators.length > 0){ model.oe.operators = operators; diff --git a/template/api/conceptual.html.primary.tmpl b/template/api/conceptual.html.primary.tmpl new file mode 100644 index 0000000..921fadb --- /dev/null +++ b/template/api/conceptual.html.primary.tmpl @@ -0,0 +1,40 @@ +{{!master(layout/_master.tmpl)}} + +

{{title}}

+{{#isGuide}} +
+ +{{#workflow}} +{{#isDevice}} +{{>partials/device.workflow}} +{{/isDevice}} +{{#isHeadstage}} +{{>partials/headstage.workflow}} +
+{{>partials/headstage.devices}} +{{/isHeadstage}} +{{/workflow}} + +{{#visualize}} +
+

Visualize Data

+ +{{#visualize_timeseries}} +{{>partials/device.visualize_timeseries}} +{{/visualize_timeseries}} + +{{#visualize_mat}} +{{>partials/device.visualize_mat}} +{{/visualize_mat}} + +{{#visualize_text}} +{{>partials/device.visualize_text}} +{{/visualize_text}} + +

Refer to Visualizing Data for more information on visualizers and how to download them.

+

Note that data will not be shown until a workflow is running. Check out Running a Workflow to see how to run a workflow.

+ +{{/visualize}} +{{/isGuide}} + +{{{conceptual}}} \ No newline at end of file From 18a26abe15a540b32a7cf0881be3548961382566 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 14 Aug 2024 23:31:39 -0700 Subject: [PATCH 11/70] Add doc comments for inherited members property table --- template/api/ManagedReference.extension.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 67b2c87..1073471 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -130,9 +130,10 @@ function defineProperties(model){ for (let i = 0; i < childrenLength; i++){ if (model.children[i].type === 'property'){ - // This section adds enum members and summary to the property table if the property is an enum - // however doesnt always work (In Pulsepal Repo - Outputchannel enum is the only one out of 6 enums that doesnt work) - // bug present in original template so need to troubleshoot + // This section adds enum fields and values to the property table if the property is an enum + // However doesnt always work (In Pulsepal Repo - Outputchannel enum doesnt show sometimes but the others do) + // Bug present in original template so need to troubleshoot + // Nice to have I think but not critical. potentialEnumYml = '~/api/' + model['children'][i].syntax.return.type.uid + '.yml'; let enumFields = []; if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ @@ -147,7 +148,7 @@ function defineProperties(model){ 'hasEnum': true }); } - // This adds the rest of the properties + // This adds the rest of the non-enum properties normally else { properties.push({ 'name': model.children[i].name[0].value, @@ -158,7 +159,8 @@ function defineProperties(model){ } } } - //check if this section is necessary + // This adds properties that belong to the members that it inherits (and which show up in the Bonsai side panel) + // On a default docfx website they dont show, so its pretty important. if (model.inheritedMembers){ const inheritedMembersLength = model.inheritedMembers.length; for (let i = 0; i < inheritedMembersLength; i++){ From b6b75e65f1ac6cef666773620f49e428f7cafb9d Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 10:37:22 -0700 Subject: [PATCH 12/70] Delete unnecessary conceptual markdown template --- template/api/conceptual.html.primary.tmpl | 40 ----------------------- 1 file changed, 40 deletions(-) delete mode 100644 template/api/conceptual.html.primary.tmpl diff --git a/template/api/conceptual.html.primary.tmpl b/template/api/conceptual.html.primary.tmpl deleted file mode 100644 index 921fadb..0000000 --- a/template/api/conceptual.html.primary.tmpl +++ /dev/null @@ -1,40 +0,0 @@ -{{!master(layout/_master.tmpl)}} - -

{{title}}

-{{#isGuide}} -
- -{{#workflow}} -{{#isDevice}} -{{>partials/device.workflow}} -{{/isDevice}} -{{#isHeadstage}} -{{>partials/headstage.workflow}} -
-{{>partials/headstage.devices}} -{{/isHeadstage}} -{{/workflow}} - -{{#visualize}} -
-

Visualize Data

- -{{#visualize_timeseries}} -{{>partials/device.visualize_timeseries}} -{{/visualize_timeseries}} - -{{#visualize_mat}} -{{>partials/device.visualize_mat}} -{{/visualize_mat}} - -{{#visualize_text}} -{{>partials/device.visualize_text}} -{{/visualize_text}} - -

Refer to Visualizing Data for more information on visualizers and how to download them.

-

Note that data will not be shown until a workflow is running. Check out Running a Workflow to see how to run a workflow.

- -{{/visualize}} -{{/isGuide}} - -{{{conceptual}}} \ No newline at end of file From 6c37c73b60ba43b5926b3cfdcd0ca96e227f6ed0 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 12:10:36 -0700 Subject: [PATCH 13/70] Add images and modified code for input/output diagram --- template/api/images/combinator-operator.svg | 67 ++++++++++++++++ template/api/images/right-arrow.svg | 87 ++++++++++++++++++++ template/api/images/sink-operator.svg | 86 ++++++++++++++++++++ template/api/images/source-operator.svg | 88 +++++++++++++++++++++ template/api/images/transform-operator.svg | 58 ++++++++++++++ template/api/partials/class.tmpl.partial | 13 --- template/api/partials/diagram.tmpl.partial | 6 +- 7 files changed, 389 insertions(+), 16 deletions(-) create mode 100644 template/api/images/combinator-operator.svg create mode 100644 template/api/images/right-arrow.svg create mode 100644 template/api/images/sink-operator.svg create mode 100644 template/api/images/source-operator.svg create mode 100644 template/api/images/transform-operator.svg 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 index 0e25a93..b5b36be 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -12,19 +12,6 @@ {{/oe.operatorType}} -{{#oe.device}} - -
- This operator is a device operator. If you are using Open Ephys hardware, use an aggregate operator that corresponds to your particular headstage(s) instead. Aggregate operators confer the following benefits: -
    -
  • The address properties of aggregate operators are automatically configured whereas a device operators requires manual configuration of its address property. This improves ease of using Open Ephys hardware.
  • -
  • All devices that belong to a hub (which is a collection of devices i.e. a headstage or breakout board) are automatically included, excluding the possibility of omitting configuration of a device that belongs to a hub. This improves ease of using Open Ephys hardware.
  • -
  • The workflow is less cluttered with configuration operators as one aggregate operator can correspond to multiple device operators. This improves workflow readability.
  • -
-
- -{{/oe.device}} -
{{{oe.description}}}
{{#showWorkflow}} diff --git a/template/api/partials/diagram.tmpl.partial b/template/api/partials/diagram.tmpl.partial index c87f104..823b370 100644 --- a/template/api/partials/diagram.tmpl.partial +++ b/template/api/partials/diagram.tmpl.partial @@ -17,7 +17,7 @@ {{{description}}} - right-arrow + right-arrow {{/input}} @@ -27,7 +27,7 @@ - representation of a source, sink, or combinator operator + representation of a source, sink, or combinator operator @@ -36,7 +36,7 @@ {{#input}} @@ -27,7 +27,7 @@ @@ -39,7 +39,7 @@ right-arrow @@ -49,66 +49,4 @@
- right-arrow + right-arrow {{#output.internal}}
{{{output.name}}}
{{/output.internal}} From c5e70d743d7f0a0be6a2f79a929b5a2d8397d8af Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 15:21:04 -0700 Subject: [PATCH 14/70] Strip OE specific code from class partial template --- template/api/partials/class.tmpl.partial | 50 +----------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index b5b36be..7288cea 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -2,7 +2,7 @@

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

{{#oe.operatorType}} @@ -21,56 +21,10 @@ {{#oe.operatorType}}

Inputs & Outputs

- {{>partials/diagram}} {{/oe.operatorType}} -{{#oe.hasSubOperators}} - -

Properties

- -
{{name.0.value}} is a aggregate operator. It comprises of the following sub-operators:
-
- -{{#oe.subOperators}} - -

{{{object}}}

- -{{#subOperator}} -
{{{object}}} is a {{{type}}} operator encapsulated by the {{name.0.value}} operator.
-{{/subOperator}} - -{{#hasSubProperties}} - - - - - - - - - - {{#subProperties}} - {{>partials/propertyTables}} - {{/subProperties}} - -
PropertyTypeDescription
- -{{/hasSubProperties}} - -{{^hasSubProperties}} - -
This subclass has no public properties.
- -{{/hasSubProperties}} - -{{/oe.subOperators}} - -{{/oe.hasSubOperators}} - -{{^oe.hasSubOperators}} - {{#oe.hasProperties}}

Properties

@@ -91,6 +45,4 @@ {{/oe.hasProperties}} -{{/oe.hasSubOperators}} - {{/isClass}} \ No newline at end of file From 1c8e075966b56dad19d6e313cc8ee9d449dab7f8 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 16:06:44 -0700 Subject: [PATCH 15/70] Remove operator tables from diagram partial template --- template/api/partials/class.tmpl.partial | 2 +- template/api/partials/diagram.tmpl.partial | 70 ++-------------------- 2 files changed, 5 insertions(+), 67 deletions(-) diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index 7288cea..fa89cd0 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -2,7 +2,7 @@

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

{{#oe.operatorType}} diff --git a/template/api/partials/diagram.tmpl.partial b/template/api/partials/diagram.tmpl.partial index 823b370..b69e817 100644 --- a/template/api/partials/diagram.tmpl.partial +++ b/template/api/partials/diagram.tmpl.partial @@ -12,7 +12,7 @@
- {{#internal}}
{{{name}}}
{{/internal}} + {{#internal}}
{{{specName}}}
{{/internal}} {{#external}}
{{{specName}}}
{{/external}} {{{description}}}
- representation of a source, sink, or combinator operator + representation of a source, sink, transform or combinator operator - {{#output.internal}}
{{{output.name}}}
{{/output.internal}} + {{#output.internal}}
{{{output.specName}}}
{{/output.internal}} {{#output.external}}
{{{output.specName}}}
{{/output.external}}
{{{output.description}}}
-{{/oe.operators}} - -{{#oe.operators.0.hasDataFrame}} -{{^oe.operators.0.output.useInputDataFrame}} - -

{{{oe.operators.0.output.name}}}

-
{{{oe.operators.0.output.dataFrameDescription}}}
- -{{/oe.operators.0.output.useInputDataFrame}} - -{{#oe.operators.0.output.useInputDataFrame}} - -

{{{oe.operators.0.input.name}}}

-
{{{oe.operators.0.input.dataFrameDescription}}}
- -{{/oe.operators.0.output.useInputDataFrame}} - - - - - - - - - - {{#oe.operators.0.dataFrame}} - {{>partials/propertyTables}} - {{/oe.operators.0.dataFrame}} - -
Data Frame MemberTypeDescription
- -{{/oe.operators.0.hasDataFrame}} - -{{#oe.operators.0.output.isEnum}} - -

{{{oe.operators.0.output.name}}}

- -
{{{oe.operators.0.output.dataFrameDescription}}}
- - - - - - - - - {{#oe.operators.0.output.enumFields}} - - - - - {{/oe.operators.0.output.enumFields}} - -
Field & ValueDescription
- - {{{field&value}}} - - - {{{description}}} -
- -{{/oe.operators.0.output.isEnum}} - +{{/oe.operators}} \ No newline at end of file From a575f788b3e5577c366aca0b528283c2c315199f Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 16:30:20 -0700 Subject: [PATCH 16/70] Replace OE with Bonsai in variable names --- template/api/ManagedReference.extension.js | 22 +++++++++--------- template/api/partials/class.tmpl.partial | 26 +++++++++++----------- template/api/partials/diagram.tmpl.partial | 6 ++--- template/api/partials/enum.tmpl.partial | 10 ++++----- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 1073471..6fdb303 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -251,42 +251,42 @@ function defineEnumFields(model){ */ exports.preTransform = function (model) { - model.oe = {}; + model.bonsai = {}; - model.oe.description = [model.summary, model.remarks].join(''); + model.bonsai.description = [model.summary, model.remarks].join(''); operatorType = BonsaiCommon.defineOperatorType(model); if (operatorType.source){ - model.oe.operatorType = 'source'; + model.bonsai.operatorType = 'source'; } else if (operatorType.sink){ - model.oe.operatorType = 'sink'; + model.bonsai.operatorType = 'sink'; } else if (operatorType.combinator){ - model.oe.operatorType = 'combinator'; + model.bonsai.operatorType = 'combinator'; } if (operatorType.showWorkflow) { - model.showWorkflow = operatorType.showWorkflow; + model.bonsai.showWorkflow = operatorType.showWorkflow; } else { } operators = defineInputsAndOutputs(model); if (operators.length > 0){ - model.oe.operators = operators; + model.bonsai.operators = operators; } properties = defineProperties(model); if (properties.length > 0){ - model.oe.hasProperties = true; - model.oe.properties = sortProperties(properties); + model.bonsai.hasProperties = true; + model.bonsai.properties = sortProperties(properties); } enumFields = defineEnumFields(model); if (enumFields.length > 0){ - model.hasEnumFields = true; - model.enumFields = enumFields; + model.bonsai.hasEnumFields = true; + model.bonsai.enumFields = enumFields; } return model; diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index fa89cd0..bc8e2b0 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -2,30 +2,30 @@

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

- {{#oe.operatorType}} + {{#bonsai.operatorType}}

- {{oe.operatorType}} Operator + {{bonsai.operatorType}} Operator

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

{{name.0.value}} Workflow

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

Inputs & Outputs

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

Properties

@@ -37,12 +37,12 @@ Description - {{#oe.properties}} + {{#bonsai.properties}} {{>partials/propertyTables}} - {{/oe.properties}} + {{/bonsai.properties}} -{{/oe.hasProperties}} +{{/bonsai.hasProperties}} {{/isClass}} \ No newline at end of file diff --git a/template/api/partials/diagram.tmpl.partial b/template/api/partials/diagram.tmpl.partial index b69e817..9e621e9 100644 --- a/template/api/partials/diagram.tmpl.partial +++ b/template/api/partials/diagram.tmpl.partial @@ -1,4 +1,4 @@ -{{#oe.operators}} +{{#bonsai.operators}}
{{{description}}}
@@ -27,7 +27,7 @@ - representation of a source, sink, transform or combinator operator + representation of a source, sink, transform or combinator operator @@ -49,4 +49,4 @@ -{{/oe.operators}} \ No newline at end of file +{{/bonsai.operators}} \ No newline at end of file diff --git a/template/api/partials/enum.tmpl.partial b/template/api/partials/enum.tmpl.partial index 32975ee..c2ef63e 100644 --- a/template/api/partials/enum.tmpl.partial +++ b/template/api/partials/enum.tmpl.partial @@ -5,9 +5,9 @@ {{#sourceurl}}{{/sourceurl}} -{{{oe.description}}} +{{{bonsai.description}}} -{{#hasEnumFields}} +{{#bonsai.hasEnumFields}}

Fields

@@ -18,7 +18,7 @@ Description - {{#enumFields}} + {{#bonsai.enumFields}} @@ -29,10 +29,10 @@ {{{description}}} - {{/enumFields}} + {{/bonsai.enumFields}} -{{/hasEnumFields}} +{{/bonsai.hasEnumFields}} {{/isEnum}} \ No newline at end of file From 10337fd15cfbecf382f966a8fc122210dd35bdcb Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 18:14:47 -0700 Subject: [PATCH 17/70] Remove dataframe ref in defineinputoutput --- template/api/ManagedReference.extension.js | 58 +--------------------- template/api/partials/class.tmpl.partial | 2 +- 2 files changed, 3 insertions(+), 57 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 6fdb303..d8b5532 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -41,7 +41,6 @@ function defineInputsAndOutputs(model){ 'name': model.children[i].syntax.parameters[0].type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), 'description': removeBottomMargin([model.children[i].syntax.parameters[0].description, model.children[i].syntax.parameters[0].remarks].join('')) }; - input.dataFrame = []; input.external = true; } if (model.children[i].syntax.return){ @@ -49,69 +48,18 @@ function defineInputsAndOutputs(model){ 'specName': replaceIObservableAndTSource(model.children[i].syntax.return.type.specName[0].value), 'name': model.children[i].syntax.return.type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), 'description': removeBottomMargin([model.children[i].syntax.return.description, model.children[i].syntax.return.remarks].join(''))}; - dataFrame = []; outputYml = [ '~/api/', model.children[i].syntax.return.type.uid.replaceAll(/(\D*{)|(}$)/g, ''), '.yml'].join(''); if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['children'] && (model['__global']['_shared'][outputYml].type === 'class')){ output.internal = true; - output.dataFrameDescription = [ - model['__global']['_shared'][outputYml].summary, - model['__global']['_shared'][outputYml].remarks].join(''); - const outputYmlChildrenLength = model['__global']['_shared'][outputYml]['children'].length; - for (let j = 0; j < outputYmlChildrenLength; j++){ - if (model['__global']['_shared'][outputYml]['children'][j].type === 'property'){ - dataFrame.push({ - 'name': model['__global']['_shared'][outputYml]['children'][j].name[0].value, - 'type': model['__global']['_shared'][outputYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([ model['__global']['_shared'][outputYml]['children'][j].summary, - model['__global']['_shared'][outputYml]['children'][j].remarks].join('')) - }); - } - } - } - if (dataFrame.length === 0 && input.dataFrame && input.dataFrame.length > 0){ - dataFrame = input.dataFrame; - output.useInputDataFrame = true;; - } - else if (!output.internal) { - output.external = true; - } - if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['inheritedMembers']){ - output.internal = true; - output.external = false; - const inheritedMembersLength = model['__global']['_shared'][outputYml]['inheritedMembers'].length; - for (let j = 0; j < inheritedMembersLength; j++){ - if (model['__global']['_shared'][outputYml]['inheritedMembers'][j].type === 'property'){ - let inheritedMemberYml = '~/api/' + model['__global']['_shared'][outputYml]['inheritedMembers'][j].parent + '.yml'; - if (model['__global']['_shared'][inheritedMemberYml]['children']){ - let inheritedMemberChildrenLength = model['__global']['_shared'][inheritedMemberYml]['children'].length; - for (let k = 0; k < inheritedMemberChildrenLength; k++){ - if (model['__global']['_shared'][outputYml]['inheritedMembers'][j].uid === model['__global']['_shared'][inheritedMemberYml]['children'][k].uid){ - dataFrame.push({ - 'name': model['__global']['_shared'][outputYml]['inheritedMembers'][j].name[0].value, - 'type': model['__global']['_shared'][inheritedMemberYml]['children'][k].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([model['__global']['_shared'][inheritedMemberYml]['children'][k].summary, - model['__global']['_shared'][inheritedMemberYml]['children'][k].remarks].join('')) - }); - } - } - } - } - } } else if (!output.internal){ output.external = true; } } - if (Object.keys(input).length && Object.keys(dataFrame).length){ - operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true, 'hasDataFrame': true}); - } - else if (Object.keys(input).length){ - operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasInput': true}); - } - else if (Object.keys(dataFrame).length){ - operators.push({'description': description, 'input': input, 'output': output, 'dataFrame': dataFrame, 'hasDataFrame': true}); + if (Object.keys(input).length){ + operators.push({'description': description, 'input': input, 'output': output, 'hasInput': true}); } else { operators.push({'description': description, 'output': output}); @@ -269,8 +217,6 @@ exports.preTransform = function (model) { if (operatorType.showWorkflow) { model.bonsai.showWorkflow = operatorType.showWorkflow; } - else { - } operators = defineInputsAndOutputs(model); if (operators.length > 0){ diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index bc8e2b0..6acf8a2 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -2,7 +2,7 @@

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

{{#bonsai.operatorType}} From 031e25dc1bff7cc01ee3b0645d0c5096609aa81e Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 18:20:32 -0700 Subject: [PATCH 18/70] Minor code cleanup, finished conversion of template - Final version of OE template that retains all the features we want to port over - From here on out I am adding new features --- template/api/partials/propertyTables.tmpl.partial | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/template/api/partials/propertyTables.tmpl.partial b/template/api/partials/propertyTables.tmpl.partial index 84d3b9e..fe8b055 100644 --- a/template/api/partials/propertyTables.tmpl.partial +++ b/template/api/partials/propertyTables.tmpl.partial @@ -1,37 +1,26 @@ - {{{name}}} - - {{{type}}} - - {{{description}}} {{#hasEnum}} -
{{#enumFields}} -
{{{field&value}}}
-
{{{description}}}
- {{/enumFields}}
- {{/hasEnum}} - \ No newline at end of file From 1094df65c59569f0a77eb8ffbc93e7d8e69f1762 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 23:03:03 -0700 Subject: [PATCH 19/70] Add relationships to classes/enums - Adds namespace and definitions - Add inheritance, implements, and derived classes, but leaves out inherited members --- template/api/partials/class.tmpl.partial | 48 ++++++++++++++++++++++++ template/api/partials/enum.tmpl.partial | 7 ++++ 2 files changed, 55 insertions(+) diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index 6acf8a2..1405e6d 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -45,4 +45,52 @@ {{/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/enum.tmpl.partial b/template/api/partials/enum.tmpl.partial index c2ef63e..f2b2e4d 100644 --- a/template/api/partials/enum.tmpl.partial +++ b/template/api/partials/enum.tmpl.partial @@ -35,4 +35,11 @@ {{/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 From 790470940d27c968e1b4fa2547bc4318b8bec179 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 15 Aug 2024 23:16:37 -0700 Subject: [PATCH 20/70] Add constructors to classes --- template/api/partials/class.tmpl.partial | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index 1405e6d..965ea0e 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -45,6 +45,32 @@ {{/bonsai.hasProperties}} +{{#children}} +{{#inConstructor}} +{{#children}} +

Constructors

+ + + + + + + + + + +
ConstructorsParameters
+ {{{specName.0.value}}}{{{summary}}} + + {{#syntax.parameters}} + {{{type.specName.0.value}}}{{{description}}} + {{/syntax.parameters}} +
+ +{{/children}} +{{/inConstructor}} +{{/children}} +

Relationships

{{__global.namespace}} - {{{namespace.specName.0.value}}}
From 6e2ff2a860fce8d3e68d1d30cf05a7476e601be2 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Sun, 18 Aug 2024 17:52:09 -0700 Subject: [PATCH 21/70] Refactor defineOperatorType function Co-authored-by: Cris --- template/api/BonsaiCommon.js | 70 ++++------------------ template/api/ManagedReference.extension.js | 23 +++---- 2 files changed, 22 insertions(+), 71 deletions(-) diff --git a/template/api/BonsaiCommon.js b/template/api/BonsaiCommon.js index 9eb4ebf..35da095 100644 --- a/template/api/BonsaiCommon.js +++ b/template/api/BonsaiCommon.js @@ -1,64 +1,20 @@ // This module contains common functions utilised by the Docfx Template System for Bonsai API pages. exports.defineOperatorType = function defineOperatorType(model){ - // 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. - let operatorType = {'source': false, 'combinator': false, 'sink': false, 'transform' : false}; - if (model.syntax && model.syntax.content[0].value){ - if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Source)]')){ - operatorType.source = true; - } - else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Combinator)]')){ - operatorType.combinator = true; - } - else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Sink)]')){ - operatorType.sink = true; - } - else if (model.syntax.content[0].value.includes('[WorkflowElementCategory(ElementCategory.Transform)]')){ - operatorType.transform = true; - } - } - if (!(operatorType.source || operatorType.combinator || operatorType.sink || operatorType.transform)){ - if (model.inheritance){ - const inheritanceLength = model.inheritance.length; - for (let i = 0; i < inheritanceLength; i++){ + // 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. - // This section checks for common Bonsai operator type nodes. Ignore Bonsai.Combinator if Bonsai.Sink or Bonsai.Transform is present - // as many sink and transform operators inherit Bonsai.Combinator - if (model.inheritance[i].uid.includes('Bonsai.Source')){ - operatorType.source = true; - } - else if (model.inheritance[i].uid.includes('Bonsai.Combinator')){ - operatorType.combinator = true; - } - else if (model.inheritance[i].uid.includes('Bonsai.Sink')){ - operatorType.combinator = false; - operatorType.sink = true; - } - else if (model.inheritance[i].uid.includes('Bonsai.Transform')){ - operatorType.combinator = false; - operatorType.transform = true; - } + const checkForCategory = (category) => model.syntax?.content[0].value.includes(`[WorkflowElementCategory(ElementCategory.${category})]`); + const checkInheritance = (inheritance) => model.inheritance?.some(inherited => inherited.uid.includes(inheritance)); - // This section checks unique Bonsai operator type nodes - else if (model.inheritance[i].uid.includes('Bonsai.WindowCombinator')){ - operatorType.combinator = true; - } - else if (model.inheritance[i].uid.includes('Bonsai.IO.StreamSink')){ - operatorType.sink = true; - } - else if (model.inheritance[i].uid.includes('Bonsai.IO.FileSink')){ - operatorType.sink = true; - } - else if (model.inheritance[i].uid.includes('Bonsai.Dsp.ArrayTransform')){ - operatorType.transform = true; - } - } - } - } + 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'); - // Flag for showing Bonsai workflow container for Bonsai visible operators - operatorType.showWorkflow = operatorType.source | operatorType.combinator | operatorType.sink | operatorType.transform; - return operatorType; - } + let operatorType = {} + operatorType.type = sink ? 'sink' : source ? 'source' : transform ? 'transform' : combinator ? 'combinator' : false ; + operatorType.showWorkflow = !!operatorType.type + return operatorType; +} \ No newline at end of file diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index d8b5532..38e2756 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -4,12 +4,12 @@ var BonsaiCommon = require('./BonsaiCommon.js') // This function is important for stripping the extra line that is present in some fields -function removeBottomMargin(str){ - return str.split('').reverse().join('') - .replace( ' Date: Mon, 19 Aug 2024 22:57:25 -0700 Subject: [PATCH 22/70] Refactor some functions - replaceIObservableAndTSource edited to fit new refactored functions - sortProperties and defineEnumFields refactored Co-authored-by: Cris --- template/api/ManagedReference.extension.js | 144 +++++++-------------- template/api/partials/diagram.tmpl.partial | 6 +- 2 files changed, 50 insertions(+), 100 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 38e2756..b505806 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -13,8 +13,13 @@ function removeBottomMargin(str) { } // Strip IObservable and replace 'TSource' with 'Anything' +// Added a null check to return an empty string to avoid undefined errors in new refactored code +// Consider replace "Anything" with "Observable" or "Any Observable" with a link to the observable guide function replaceIObservableAndTSource(str){ - if (str.includes('IGroupedObservable')){ + if (!str) { + return ''; + } + else if (str.includes('IGroupedObservable')){ const re = new RegExp(' o --> output diagrams +// this function is a revised version of cris's refactored function that restores the original values (specname, description) +// which were removed function defineInputsAndOutputs(model){ - operators = []; - if (model.children){ - const childrenLength = model.children.length; - for (let i = 0; i < childrenLength; i++){ - if (model.children[i].uid.includes('Generate') || model.children[i].uid.includes('Process')){ - description = [model.children[i].summary, model.children[i].remarks].join(''); - input = {}; - if (model.children[i].syntax.parameters && model.children[i].syntax.parameters[0]){ - input = { - 'specName': replaceIObservableAndTSource(model.children[i].syntax.parameters[0].type.specName[0].value), - 'name': model.children[i].syntax.parameters[0].type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), - 'description': removeBottomMargin([model.children[i].syntax.parameters[0].description, model.children[i].syntax.parameters[0].remarks].join('')) - }; - input.external = true; - } - if (model.children[i].syntax.return){ - output = { - 'specName': replaceIObservableAndTSource(model.children[i].syntax.return.type.specName[0].value), - 'name': model.children[i].syntax.return.type.name[0].value.replaceAll(/(IObservable<)|(>)/g, ''), - 'description': removeBottomMargin([model.children[i].syntax.return.description, model.children[i].syntax.return.remarks].join(''))}; - outputYml = [ '~/api/', - model.children[i].syntax.return.type.uid.replaceAll(/(\D*{)|(}$)/g, ''), - '.yml'].join(''); - if (model['__global']['_shared'][outputYml] && model['__global']['_shared'][outputYml]['children'] && (model['__global']['_shared'][outputYml].type === 'class')){ - output.internal = true; - } - else if (!output.internal){ - output.external = true; - } - } - if (Object.keys(input).length){ - operators.push({'description': description, 'input': input, 'output': output, 'hasInput': true}); - } - else { - operators.push({'description': description, 'output': output}); - } + overloads = model.children + .filter(child => 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('')), } - } - } - return operators; + })) + return overloads; } // compile list of properties @@ -133,67 +113,39 @@ function defineProperties(model){ return properties; } -// While docfx has an option to sort enum fields alphabetically, it does not have a similar option for class properties -// and this function provides that. -function sortProperties(properties){ - let propertyNames = []; - let propertyNamesThatFitPattern = []; - const propertiesLength = properties.length; - for (let i = 0; i < propertiesLength; i++){ - propertyNames.push([properties[i].name, - /\D+\d+$/.test(properties[i].name), - String(properties[i].name.match(/\D+/)), - Number(properties[i].name.match(/\d+$/))]); - } - for (let i = 0; i < propertiesLength; i++){ - if (propertyNames[i][1]){ - if (!propertyNamesThatFitPattern.includes(propertyNames[i][2])){ - propertyNamesThatFitPattern.push(propertyNames[i][2]); - } - } - } - const propertyNamesThatFitPatternLength = propertyNamesThatFitPattern.length; - for (let j = 0; j < propertyNamesThatFitPatternLength; j++){ - for (let i = 1; i < propertiesLength; i++){ - for (let k = i; k > 0; k--){ - if ((propertyNames[k][2] === propertyNamesThatFitPattern[j]) && (propertyNames[k - 1][2] === propertyNamesThatFitPattern[j])){ - if (propertyNames[k][3] < propertyNames[k - 1][3]){ - swapElements(properties, k, k - 1); - swapElements(propertyNames, k, k - 1); // why does this need to be here for this sortProperties function to work? - } - } - } +// 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 sortProperties(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; } - } - return properties; + }); } -const swapElements = (array, index1, index2) => { - const temp = array[index1]; - array[index1] = array[index2]; - array[index2] = temp; -}; - // 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 defineEnumFields(model){ - let enumFields = []; - if (model.children){ - const childrenLength = model.children.length; - for (let i = 0; i < childrenLength; i++){ - if (model.children[i].type === 'field'){ - enumFields.push({ - 'field&value': model.children[i].syntax.content[0].value, - 'description': removeBottomMargin([ model.children[i].summary, - model.children[i].remarks].join('')) - }); - } - } - } - return enumFields; + 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 */ @@ -204,9 +156,9 @@ exports.preTransform = function (model) { model.bonsai.description = [model.summary, model.remarks].join(''); operatorType = BonsaiCommon.defineOperatorType(model); - + if (operatorType.type){ - model.bonsai.operatorType = operatorType.type + model.bonsai.operatorType = operatorType.type; } if (operatorType.showWorkflow) { diff --git a/template/api/partials/diagram.tmpl.partial b/template/api/partials/diagram.tmpl.partial index 9e621e9..915b6c1 100644 --- a/template/api/partials/diagram.tmpl.partial +++ b/template/api/partials/diagram.tmpl.partial @@ -12,8 +12,7 @@ {{#input}} - {{#internal}}
{{{specName}}}
{{/internal}} - {{#external}}
{{{specName}}}
{{/external}} +
{{{specName}}}
{{{description}}} @@ -39,8 +38,7 @@ right-arrow - {{#output.internal}}
{{{output.specName}}}
{{/output.internal}} - {{#output.external}}
{{{output.specName}}}
{{/output.external}} +
{{{output.specName}}}
{{{output.description}}}
From ebcef62fb65496061e4037269ad8f31ee4f15c87 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 22 Aug 2024 13:02:46 -0700 Subject: [PATCH 23/70] Refactor functions, add styles.css for template table formatting Co-authored-by: Cris Co-authored-by: Brandon Parks --- template/api/BonsaiCommon.js | 20 --- template/api/ManagedReference.extension.js | 150 +++++++++--------- template/api/partials/class.tmpl.partial | 2 + template/api/partials/enum.tmpl.partial | 2 +- .../api/partials/propertyTables.tmpl.partial | 29 ++-- template/api/styles/styles.css | 35 ++++ 6 files changed, 125 insertions(+), 113 deletions(-) delete mode 100644 template/api/BonsaiCommon.js create mode 100644 template/api/styles/styles.css diff --git a/template/api/BonsaiCommon.js b/template/api/BonsaiCommon.js deleted file mode 100644 index 35da095..0000000 --- a/template/api/BonsaiCommon.js +++ /dev/null @@ -1,20 +0,0 @@ -// This module contains common functions utilised by the Docfx Template System for Bonsai API pages. - -exports.defineOperatorType = function defineOperatorType(model){ - // 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. - - 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 ; - operatorType.showWorkflow = !!operatorType.type - - return operatorType; -} \ No newline at end of file diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index b505806..0d93590 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -1,7 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -var BonsaiCommon = require('./BonsaiCommon.js') +// 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 ' { + // Remove input if it's empty + if (!item.input.specName && !item.input.description) { + delete item.input; + } + return item; + }); return overloads; } -// compile list of properties -function defineProperties(model){ - properties = []; - if (model.children){ - const childrenLength = model.children.length; - for (let i = 0; i < childrenLength; i++){ - if (model.children[i].type === 'property'){ - - // This section adds enum fields and values to the property table if the property is an enum - // However doesnt always work (In Pulsepal Repo - Outputchannel enum doesnt show sometimes but the others do) - // Bug present in original template so need to troubleshoot - // Nice to have I think but not critical. - potentialEnumYml = '~/api/' + model['children'][i].syntax.return.type.uid + '.yml'; - let enumFields = []; - if (model['__global']['_shared'][potentialEnumYml] && (model['__global']['_shared'][potentialEnumYml]['type'] === 'enum')){ - enumFields = defineEnumFields(model['__global']['_shared'][potentialEnumYml]); - } - if (enumFields.length > 0){ - properties.push({ - 'name': model.children[i].name[0].value, - 'type': model.children[i].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([model.children[i].summary, model.children[i].remarks].join('')), - 'enumFields': enumFields, - 'hasEnum': true - }); - } - // This adds the rest of the non-enum properties normally - else { - properties.push({ - 'name': model.children[i].name[0].value, - 'type': model.children[i].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([model.children[i].summary, model.children[i].remarks].join('')) - }); - } - } +// 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, } } - // This adds properties that belong to the members that it inherits (and which show up in the Bonsai side panel) - // On a default docfx website they dont show, so its pretty important. - if (model.inheritedMembers){ - const inheritedMembersLength = model.inheritedMembers.length; - for (let i = 0; i < inheritedMembersLength; i++){ - if (model.inheritedMembers[i].type && (model.inheritedMembers[i].type === 'property')){ - let inheritedMemberYml = '~/api/' + model.inheritedMembers[i].parent + '.yml'; - if (model['__global']['_shared'][inheritedMemberYml]['children']){ - let inheritedMemberChildrenLength = model['__global']['_shared'][inheritedMemberYml]['children'].length; - for (let j = 0; j < inheritedMemberChildrenLength; j++){ - if (model.inheritedMembers[i].uid === model['__global']['_shared'][inheritedMemberYml]['children'][j].uid){ - properties.push( - {'name': model.inheritedMembers[i].name[0].value, - 'type': model['__global']['_shared'][inheritedMemberYml]['children'][j].syntax.return.type.specName[0].value, - 'description': removeBottomMargin([model['__global']['_shared'][inheritedMemberYml]['children'][j].summary, - model['__global']['_shared'][inheritedMemberYml]['children'][j].remarks].join('')) - }); - } - } - } - } - } - } - return properties; } +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 sortProperties(properties) { +function sortPropertiesData(properties) { return properties.sort((a, b) => { const regex = /\D+|\d+$/g; @@ -137,7 +132,7 @@ function sortProperties(properties) { // 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 defineEnumFields(model){ +function extractEnumData(model){ return model.children .filter(child => child.type === 'field') .map(child => ({ @@ -155,33 +150,30 @@ exports.preTransform = function (model) { model.bonsai.description = [model.summary, model.remarks].join(''); - operatorType = BonsaiCommon.defineOperatorType(model); + operatorType = defineOperatorType(model); if (operatorType.type){ model.bonsai.operatorType = operatorType.type; - } - - if (operatorType.showWorkflow) { - model.bonsai.showWorkflow = operatorType.showWorkflow; - } - - operators = defineInputsAndOutputs(model); - if (operators.length > 0){ + model.bonsai.showWorkflow = true + operators = defineInputsAndOutputs(model); model.bonsai.operators = operators; } - properties = defineProperties(model); - if (properties.length > 0){ - model.bonsai.hasProperties = true; - model.bonsai.properties = sortProperties(properties); + 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 + } } - enumFields = defineEnumFields(model); - if (enumFields.length > 0){ + else if (model.type === 'enum') { + model.bonsai.enumFields = extractEnumData(model); model.bonsai.hasEnumFields = true; - model.bonsai.enumFields = enumFields; } - return model; } diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index 965ea0e..b8fcdb3 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -1,3 +1,5 @@ + + {{#isClass}}
diff --git a/template/api/partials/enum.tmpl.partial b/template/api/partials/enum.tmpl.partial index f2b2e4d..4755efe 100644 --- a/template/api/partials/enum.tmpl.partial +++ b/template/api/partials/enum.tmpl.partial @@ -26,7 +26,7 @@ - {{{description}}} + {{{enumDescription}}} {{/bonsai.enumFields}} diff --git a/template/api/partials/propertyTables.tmpl.partial b/template/api/partials/propertyTables.tmpl.partial index fe8b055..bd47d08 100644 --- a/template/api/partials/propertyTables.tmpl.partial +++ b/template/api/partials/propertyTables.tmpl.partial @@ -9,18 +9,21 @@ - {{{description}}} - - {{#hasEnum}} -
- - {{#enumFields}} -
{{{field&value}}}
-
{{{description}}}
- {{/enumFields}} - -
- {{/hasEnum}} - +
+ {{#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 From a86183449b1a9709fd81e3e700f1a57de217be29 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 22 Aug 2024 15:03:31 -0700 Subject: [PATCH 24/70] Surface links to Bonsai docs for observables and operators --- template/api/ManagedReference.extension.js | 7 ++++--- template/api/partials/class.tmpl.partial | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 0d93590..6f43f52 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -29,18 +29,19 @@ function removeBottomMargin(str) { // Strip IObservable and replace 'TSource' with 'Anything' // Added a null check to return an empty string to avoid undefined errors in new refactored code -// Consider replace "Anything" with "Observable" or "Any Observable" with a link to the observable guide +// Added links to Bonsai user guide on operators function replaceIObservableAndTSource(str){ + const observableLink = 'Observable' if (!str) { return ''; } else if (str.includes('IGroupedObservable')){ const re = new RegExp(' {{#bonsai.operatorType}}

- {{bonsai.operatorType}} Operator + {{bonsai.operatorType}} Operator

{{/bonsai.operatorType}}
From 3000e2171c87fcfb33f4ccd6d3bd836078d17900 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 22 Aug 2024 15:12:50 -0700 Subject: [PATCH 25/70] Remove constructors from API pages --- template/api/partials/class.tmpl.partial | 26 ------------------------ 1 file changed, 26 deletions(-) diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index 8da7b84..006577b 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -47,32 +47,6 @@ {{/bonsai.hasProperties}} -{{#children}} -{{#inConstructor}} -{{#children}} -

Constructors

- - - - - - - - - - -
ConstructorsParameters
- {{{specName.0.value}}}{{{summary}}} - - {{#syntax.parameters}} - {{{type.specName.0.value}}}{{{description}}} - {{/syntax.parameters}} -
- -{{/children}} -{{/inConstructor}} -{{/children}} -

Relationships

{{__global.namespace}} - {{{namespace.specName.0.value}}}
From 74dbb7a2aeb3ecc77f79ca1bc7ee6421baa81f7e Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 22 Aug 2024 16:33:34 -0700 Subject: [PATCH 26/70] Update readme with installation instructions --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e00eb49..42cb2c3 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,45 @@ export default { } } ``` +## Using API TOC Template and API template + +Currently these two templates are bundled together, as the API TOC template relies on an extension that is used by the API template. + +For the API TOC template especially, the local installation of docfx needs to be updated to >= v2.77.0. + +```ps1 +dotnet tool update docfx +``` -## Powershell Scripts +Modify `docfx.json` to include the api template in the template section (note both the workflow container and API TOC template have to be added separately). -This repository also provides helper scripts to automate several content generation steps for package documentation websites. +```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. + +```json +"resource": [ + { + "files": [ + "logo.svg", + "favicon.ico", + "images/**", + "workflows/**", + "bonsai/template/api/images/**", + "bonsai/template/api/styles/**" + ] + } +] +``` -### 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. From 5ee6e60cf8357e2110e7500fed451907f8d7393f Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 22 Aug 2024 16:49:22 -0700 Subject: [PATCH 27/70] Update README with instructions for operator workflow containers --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 42cb2c3..e7f9cba 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 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 +- 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 include @@ -81,6 +81,8 @@ In addition, the images and custom css styles need to be added to the resources } ] ``` +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`. + ## Powershell Scripts - Exporting workflow images From 968ca4a5427a9cb5c4e1081680d38beead2d0fbd Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 23 Aug 2024 15:41:05 -0700 Subject: [PATCH 28/70] Update README after decoupling API TOC from API template --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e7f9cba..d0ecacf 100644 --- a/README.md +++ b/README.md @@ -44,17 +44,9 @@ export default { } } ``` -## Using API TOC Template and API template +## Using API template -Currently these two templates are bundled together, as the API TOC template relies on an extension that is used by the API template. - -For the API TOC template especially, the local installation of docfx needs to be updated to >= v2.77.0. - -```ps1 -dotnet tool update docfx -``` - -Modify `docfx.json` to include the api template in the template section (note both the workflow container and API TOC template have to be added separately). +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": [ From 0f97d2e34cb2c360e5612f3be20fc0624dc16dcc Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Sat, 21 Sep 2024 23:34:32 -0700 Subject: [PATCH 29/70] Refactor IO diagram, reduce size --- template/api/partials/diagram.tmpl.partial | 53 ++++++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/template/api/partials/diagram.tmpl.partial b/template/api/partials/diagram.tmpl.partial index 915b6c1..e2cb8aa 100644 --- a/template/api/partials/diagram.tmpl.partial +++ b/template/api/partials/diagram.tmpl.partial @@ -1,21 +1,54 @@ {{#bonsai.operators}} + +
{{{description}}}
- +
-
- +
+ {{#input}} - - {{/input}} @@ -25,19 +58,19 @@ - -
+
{{{specName}}}
{{{description}}}
+ right-arrow + representation of a source, sink, transform or combinator operator - + - - - - - - - - \ No newline at end of file diff --git a/template/api/styles/styles.css b/template/api/styles/styles.css deleted file mode 100644 index 0598798..0000000 --- a/template/api/styles/styles.css +++ /dev/null @@ -1,35 +0,0 @@ -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
+ - - From 225a16b20befa79ebc50044314eb6ce6dbe6b95e Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Sun, 13 Oct 2024 15:01:56 -0600 Subject: [PATCH 30/70] Add IncludeWorkflow docfx plugin --- .../api/plugins/IncludeWorkflowDocfxPlugin.dll | Bin 0 -> 17408 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 template/api/plugins/IncludeWorkflowDocfxPlugin.dll diff --git a/template/api/plugins/IncludeWorkflowDocfxPlugin.dll b/template/api/plugins/IncludeWorkflowDocfxPlugin.dll new file mode 100644 index 0000000000000000000000000000000000000000..8ed949136d8f6d3e173dfb04f41104676400efaa GIT binary patch literal 17408 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 Date: Sun, 13 Oct 2024 18:39:26 -0600 Subject: [PATCH 31/70] Added python script to patch api TOC --- template/api/plugins/Patch-ApiToc.py | 110 +++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 template/api/plugins/Patch-ApiToc.py diff --git a/template/api/plugins/Patch-ApiToc.py b/template/api/plugins/Patch-ApiToc.py new file mode 100644 index 0000000..1c37a23 --- /dev/null +++ b/template/api/plugins/Patch-ApiToc.py @@ -0,0 +1,110 @@ +import os +import yaml + +def find_bonsai_files(src_folder): + """Search for all .bonsai files in the src folder and return their paths.""" + bonsai_files = [] + for root, _, files in os.walk(src_folder): + for file in files: + if file.endswith(".bonsai"): + # Store the full path to the bonsai file + bonsai_files.append(os.path.join(root, file)) + return bonsai_files + +def extract_namespace(file_path, src_folder): + """Extract namespace by converting the path to a dotted string.""" + # Get the relative path from src folder + relative_path = os.path.relpath(file_path, src_folder) + + # Remove the filename from the path (only keep directories) + namespace_path = os.path.dirname(relative_path) + + # Replace separators with dots + namespace = namespace_path.replace(os.sep, '.') + + return namespace + +def load_existing_toc(toc_path): + """Load the existing TOC or display an error message if it doesn't exist.""" + if os.path.exists(toc_path): + with open(toc_path, 'r') as f: + return yaml.safe_load(f) + else: + raise FileNotFoundError( + "api/toc.yml not found. Execute this script from the docs directory or run `docfx metadata` first to generate toc.yml." + ) + + +def patch_toc(toc_data, new_entries): + """Patch the TOC with new entries.""" + # Create a map of existing namespaces + namespace_map = {item['uid']: item for item in toc_data['items']} + + # Iterate over the new entries + for entry in new_entries: + namespace = entry['namespace'] + item_data = {'uid': entry['uid'], 'name': entry['name']} + + if namespace in namespace_map: + # Add new item to the existing namespace + namespace_map[namespace]['items'].append(item_data) + else: + # Create a new namespace entry with proper key order + new_namespace_entry = { + 'uid': namespace, + 'name': namespace, + 'items': [item_data] + } + toc_data['items'].append(new_namespace_entry) + namespace_map[namespace] = new_namespace_entry + return toc_data + +def generate_toc_entries(bonsai_files, src_folder): + """Generate TOC entries from the list of bonsai files.""" + toc_entries = [] + + for file in bonsai_files: + name = os.path.splitext(os.path.basename(file))[0] + namespace = extract_namespace(file, src_folder) + uid = namespace + "." + name + + toc_entries.append({ + 'namespace': namespace, + 'uid': uid, + 'name': name, + }) + + return toc_entries + +def save_toc(toc_path, toc_items): + """Save the patched TOC file.""" + with open(toc_path, 'w') as f: + # Write the magic header + f.write("### YamlMime:TableOfContent\n") + + yaml.dump(toc_items, f, default_flow_style=False, sort_keys=False) + +def main(): + src_folder = "../src" # Adjust if your src folder is in a different location + toc_path = "api/toc.yml" # Path to the existing TOC file + + # Find all .bonsai files in the src folder + bonsai_files = find_bonsai_files(src_folder) + print(f"Found {len(bonsai_files)} .bonsai files.") + + # Generate TOC entries + new_entries = generate_toc_entries(bonsai_files, src_folder) + + # Load the existing TOC file + toc_items = load_existing_toc(toc_path) + + # # Patch the TOC with new entries + patched_toc = patch_toc(toc_items, new_entries) + + # Save the updated TOC file + save_toc(toc_path, patched_toc) + + print(f"Successfully updated {toc_path}.") + +if __name__ == "__main__": + main() \ No newline at end of file From bdd8dcd7445ce6faa21f2a15f20aff9993a2fc52 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 18 Oct 2024 17:56:40 -0600 Subject: [PATCH 32/70] Modify mref.extension.js to support includeworkflows --- template/api/ManagedReference.extension.js | 5 +++++ .../plugins/IncludeWorkflowDocfxPlugin.dll | Bin 17408 -> 17408 bytes 2 files changed, 5 insertions(+) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 6f43f52..6d1f292 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -98,6 +98,11 @@ function extractPropertiesData(model, sharedModel) { } function extractPropertiesFromInheritedMembersData(model, sharedModel) { + // Ensure inheritedMembers exists and is an array before filtering + // Important for IncludeWorkflow operators which currently do not have any inherited members + if (!Array.isArray(model.inheritedMembers)) { + return []; + } return model.inheritedMembers .filter(inheritedMember => inheritedMember.type === 'property') .map(inheritedMember => { diff --git a/template/api/plugins/IncludeWorkflowDocfxPlugin.dll b/template/api/plugins/IncludeWorkflowDocfxPlugin.dll index 8ed949136d8f6d3e173dfb04f41104676400efaa..91bf061222b1b71fbea743627f85d5b49933cce3 100644 GIT binary patch delta 237 zcmZqZU~K4MoY2AI{^rh{jXe_j0{@Eip6AZX_uQy?ZRO3fmwPvF(YIsQFflYROiVUS zGB-+1vM@JIH8wRhvox|WH8wF%Gf%NhPBTtQO-wa5+bm^%jfo}6(dGH%FO~rUIh#v1 zW?j7Cu_ezdFumc5`s5I+9tEi2j1s6INVRQ-{iUWiOXkg;uco-kdJ2ob34^Jf{p*H_Z??YdC|7uR?!+eRDJ=de42BHm43 Date: Sun, 20 Oct 2024 17:45:40 -0600 Subject: [PATCH 33/70] Modified python patch to update .manifest --- .../plugins/IncludeWorkflowDocfxPlugin.dll | Bin 17408 -> 21504 bytes ...tch-ApiToc.py => Patch-IncludeWorkflow.py} | 57 +++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) rename template/api/plugins/{Patch-ApiToc.py => Patch-IncludeWorkflow.py} (61%) diff --git a/template/api/plugins/IncludeWorkflowDocfxPlugin.dll b/template/api/plugins/IncludeWorkflowDocfxPlugin.dll index 91bf061222b1b71fbea743627f85d5b49933cce3..ffd8d789cbe7b41bbd937388186a2e2eb02b04be 100644 GIT binary patch literal 21504 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%0D_%A9ig!Te| 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 literal 17408 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 diff --git a/template/api/plugins/Patch-ApiToc.py b/template/api/plugins/Patch-IncludeWorkflow.py similarity index 61% rename from template/api/plugins/Patch-ApiToc.py rename to template/api/plugins/Patch-IncludeWorkflow.py index 1c37a23..b470346 100644 --- a/template/api/plugins/Patch-ApiToc.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -1,5 +1,8 @@ +# add yaml flag and check to prevent modification of already modified files import os import yaml +import json +import xml.etree.ElementTree as ET def find_bonsai_files(src_folder): """Search for all .bonsai files in the src folder and return their paths.""" @@ -72,6 +75,8 @@ def generate_toc_entries(bonsai_files, src_folder): 'namespace': namespace, 'uid': uid, 'name': name, + 'file': file, + 'properties': extract_properties(file) }) return toc_entries @@ -84,9 +89,56 @@ def save_toc(toc_path, toc_items): yaml.dump(toc_items, f, default_flow_style=False, sort_keys=False) +def patch_manifest(manifest_path, new_entries): + with open(manifest_path, 'r') as f: + manifest_data = json.load(f) + for entry in new_entries: + manifest_data[entry['uid']] = entry['uid']+".yml" + #generate manifest entries for operator properties + for key in entry['properties'].keys(): + manifest_data[entry['uid']+"."+key] = entry['uid']+".yml" + with open(manifest_path, 'w') as f: + json.dump(manifest_data, f, indent=2, sort_keys=True) + +def extract_properties(entry): + properties = [] + + tree = ET.parse(entry) + root = tree.getroot() + + # Get XML namespaces and prefixes + xml_namespace = {} + for event, elem in ET.iterparse(entry, ["start-ns"]): + xml_namespace[elem[0]] = elem[1] + + # Get the default namespace from the XML (no prefix) + default_ns = xml_namespace[''] + + # Build the full tag name for 'Expression' with the namespace + expression_tag = f"{{{default_ns}}}Expression" # e.g., "{https://bonsai-rx.org/2018/workflow}Expression" + + # Find all visible 'Properties' based on xsi:type Externalized Mapping" + # Note - it seems that some properties are hidden which I did not realise. + # For instance see EventLogger + property_dict = {} + for expression in root.findall(f".//{expression_tag}", xml_namespace): + xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") + if xsi_type == "ExternalizedMapping": + for prop in expression.findall(f"{{{default_ns}}}Property"): + property_name = prop.get('Name') + description = prop.get('Description', "No description available.") + display_name = prop.get('DisplayName', False) + if display_name == False: + property_dict[property_name] = description + else: + property_dict[display_name] = description + return property_dict + + def main(): src_folder = "../src" # Adjust if your src folder is in a different location toc_path = "api/toc.yml" # Path to the existing TOC file + manifest_path ="api/.manifest" # Find all .bonsai files in the src folder bonsai_files = find_bonsai_files(src_folder) @@ -104,7 +156,10 @@ def main(): # Save the updated TOC file save_toc(toc_path, patched_toc) - print(f"Successfully updated {toc_path}.") + # Patch manifest + patch_manifest(manifest_path, new_entries) + + print(f"Successfully updated {toc_path}, {manifest_path}. ") if __name__ == "__main__": main() \ No newline at end of file From 5b413d3c708d928c8e4773acea418157e19ad6aa Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Sun, 20 Oct 2024 21:02:54 -0600 Subject: [PATCH 34/70] Added namespace.yml modification to includeworkflow python patch --- template/api/plugins/Patch-IncludeWorkflow.py | 76 ++++++++++++++++--- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index b470346..5466060 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -1,4 +1,9 @@ -# add yaml flag and check to prevent modification of already modified files +# README: this script modifies several accessory files that seem to be needed for getting IncludeWorkflows +# recognised in dotnet build. Gnerating individual api yml files for the .bonsai IncludeWorkflows +# isnt enough as the API template relies on certain shared models that don't build correctly +# TODO: add yaml flag and check to prevent modification of already modified files +# TODO: make it more efficient (too many loops of new entries in each separate function) + import os import yaml import json @@ -62,16 +67,16 @@ def patch_toc(toc_data, new_entries): namespace_map[namespace] = new_namespace_entry return toc_data -def generate_toc_entries(bonsai_files, src_folder): - """Generate TOC entries from the list of bonsai files.""" - toc_entries = [] +def generate_entries(bonsai_files, src_folder): + """Generate entries from the list of bonsai files.""" + new_entries = [] for file in bonsai_files: name = os.path.splitext(os.path.basename(file))[0] namespace = extract_namespace(file, src_folder) uid = namespace + "." + name - toc_entries.append({ + new_entries.append({ 'namespace': namespace, 'uid': uid, 'name': name, @@ -79,14 +84,57 @@ def generate_toc_entries(bonsai_files, src_folder): 'properties': extract_properties(file) }) - return toc_entries + return new_entries + +def patch_namespace_files(new_entries, api_folder): + for entry in new_entries: + namespace_file = os.path.join(api_folder, entry['namespace']+".yml") + if os.path.exists(namespace_file): + pass + else: + # generate new namespace.yml file if it isnt present + # some items in namespace files dont appear as .cs files + # for instance bonvision collections has GratingTrial and GratingParameters + # even though there are no .cs files + # they are present as classes in CreateGratingTrial and GratingSpecifications specifically + new_namespace_file = {} + new_namespace_file["items"]=[{ + 'uid': entry['namespace'], + 'commentId': "N:"+entry['namespace'], + 'id': entry['namespace'], + 'children': [], + 'langs': ['csharp','vb'], + 'name': entry['namespace'], + 'nameWithType': entry['namespace'], + 'fullName': entry['namespace'], + 'type': "Namespace", + 'assemblies': [entry['namespace'].split('.')[0]] + }] + new_namespace_file["references"]=[] + with open(namespace_file, 'w') as f: + f.write("### YamlMime:ManagedReference\n") + yaml.dump(new_namespace_file, f, default_flow_style=False, sort_keys=False) + with open(namespace_file, 'r') as f: + namespace_file_to_amend = yaml.safe_load(f) + namespace_file_to_amend["items"][0]['children'].append(entry['uid']) + namespace_file_to_amend["references"].append({ + 'uid': entry['uid'], + 'commentId': "T:"+entry['uid'], + 'href': entry['uid']+".html", + 'name': entry['name'], + 'nameWithType': entry['name'], + 'fullName': entry['uid'] + }) + with open(namespace_file, 'w') as f: + f.write("### YamlMime:ManagedReference\n") + yaml.dump(namespace_file_to_amend, f, default_flow_style=False, sort_keys=False) + def save_toc(toc_path, toc_items): """Save the patched TOC file.""" with open(toc_path, 'w') as f: # Write the magic header f.write("### YamlMime:TableOfContent\n") - yaml.dump(toc_items, f, default_flow_style=False, sort_keys=False) def patch_manifest(manifest_path, new_entries): @@ -139,27 +187,31 @@ def main(): src_folder = "../src" # Adjust if your src folder is in a different location toc_path = "api/toc.yml" # Path to the existing TOC file manifest_path ="api/.manifest" + api_folder = "api/" # Find all .bonsai files in the src folder bonsai_files = find_bonsai_files(src_folder) print(f"Found {len(bonsai_files)} .bonsai files.") - # Generate TOC entries - new_entries = generate_toc_entries(bonsai_files, src_folder) + # Generate entries from bonsai files + new_entries = generate_entries(bonsai_files, src_folder) + + # Patch namespace.yml files + patch_namespace_files(new_entries, api_folder) # Load the existing TOC file toc_items = load_existing_toc(toc_path) - # # Patch the TOC with new entries + # Patch the TOC with new entries patched_toc = patch_toc(toc_items, new_entries) # Save the updated TOC file save_toc(toc_path, patched_toc) - # Patch manifest + # Patch manifest with new entries patch_manifest(manifest_path, new_entries) - print(f"Successfully updated {toc_path}, {manifest_path}. ") + print(f"Successfully updated {toc_path}, {manifest_path}, and namespace.yml files") if __name__ == "__main__": main() \ No newline at end of file From 1e442006e1d1cba0ebc366f721c9fd12e2391688 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Mon, 28 Oct 2024 09:41:56 -0600 Subject: [PATCH 35/70] Remove C# plugin, add yml generation to python patch --- .../plugins/IncludeWorkflowDocfxPlugin.dll | Bin 21504 -> 0 bytes template/api/plugins/Patch-IncludeWorkflow.py | 99 ++++++++++++++++++ 2 files changed, 99 insertions(+) delete mode 100644 template/api/plugins/IncludeWorkflowDocfxPlugin.dll diff --git a/template/api/plugins/IncludeWorkflowDocfxPlugin.dll b/template/api/plugins/IncludeWorkflowDocfxPlugin.dll deleted file mode 100644 index ffd8d789cbe7b41bbd937388186a2e2eb02b04be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21504 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%0D_%A9ig!Te| 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 diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 5466060..0b2bf32 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -86,6 +86,95 @@ def generate_entries(bonsai_files, src_folder): return new_entries +def get_git_information(): + branch_name = "" + repo_url = "" + with open("../.git/HEAD", "r") as f: + content = f.read().strip() + if content.startswith("ref:"): + branch_name = content.split("/")[-1] + with open("../.git/config", "r") as f: + for line in f: + if "url = " in line: + repo_url = line.split("=", 1)[1].strip() + break + return(branch_name, repo_url) + + +def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): + for entry in bonsai_entries: + bonsai_yml_file = os.path.join(api_folder, entry['uid']+".yml") + new_bonsai_yml_file = {} + new_bonsai_yml_file["items"]=[{ + 'uid': entry['uid'], + 'commentId': "T:"+entry['uid'], + 'id': entry['name'], + 'parent': entry['namespace'], + 'children': [entry['uid'] + "." + x for x in entry['properties']], + 'langs': ['csharp','vb'], + 'name': entry['name'], + 'nameWithType': entry['name'], + 'fullName': entry['uid'], + 'type': "Class", + 'source': { + 'remote':{ + 'path':entry['file'][3:], + 'branch':branch_name, + 'repo':repo_url + }, + 'id': entry['name'], + 'path': entry['file'], + # this isn't accurate but is hardcoded here because I don't think it affects anything + 'startLine': 9 + }, + 'assemblies': [entry['namespace'].split('.')[0]], + 'namespace': entry['namespace'], + 'syntax':{ + 'content': "public class " + entry['name'], + 'content.vb': "Public Class " + entry['name'] + }, + # this isn't applicable but just added it in case it is needed + 'inheritance': ['System.Object'], + 'inheritedMembers': ['System.Object.GetType'], + }] + for property_name, description in entry['properties'].items(): + new_bonsai_yml_file["items"].append({ + 'uid':entry['uid']+"." + property_name, + 'commentId': 'P:'+ entry['uid']+"." + property_name, + 'id': property_name, + 'parent': entry['uid'], + 'langs': ['csharp','vb'], + 'name': property_name, + 'nameWithType': entry['name']+'.'+property_name, + 'fullName': entry['uid']+'.'+property_name, + 'type':'Property', + 'source': { + 'remote':{ + 'path':entry['file'][3:], + 'branch':branch_name, + 'repo':repo_url + }, + 'id': property_name, + 'path': entry['file'], + # this isn't accurate but is hardcoded here because I don't think it affects anything + 'startLine': 9 + }, + 'assemblies': [entry['namespace'].split('.')[0]], + 'namespace': entry['namespace'], + # this should probably be tailored for each property + 'syntax':{ + 'content': 'public float ' + property_name, + 'parameters': [], + 'return': {'type': 'System.Single'}, + 'content.vb': "Public Property " + property_name + " As Single" + }, + 'overload': entry['uid']+'.'+ property_name +'*' + }) + + with open(bonsai_yml_file, 'w') as f: + f.write("### YamlMime:ManagedReference\n") + yaml.dump(new_bonsai_yml_file, f, default_flow_style=False, sort_keys=False) + def patch_namespace_files(new_entries, api_folder): for entry in new_entries: namespace_file = os.path.join(api_folder, entry['namespace']+".yml") @@ -196,6 +285,16 @@ def main(): # Generate entries from bonsai files new_entries = generate_entries(bonsai_files, src_folder) + # for entry in new_entries: + # print(entry) + + # Get git information to populate yml source field + branch_name, repo_url = get_git_information() + + # Create Bonsai Yml Files + create_bonsai_yml(new_entries, api_folder, branch_name, repo_url) + print(f"Successfully created .bonsai yml files in {api_folder}") + # Patch namespace.yml files patch_namespace_files(new_entries, api_folder) From 13b804275bb5ffaa75742059c893947ba1bee133 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Sun, 3 Nov 2024 16:51:12 -0800 Subject: [PATCH 36/70] Temporarily disable inheritance section in includeworkflow patch --- template/api/plugins/Patch-IncludeWorkflow.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 0b2bf32..0f5b8e5 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -133,9 +133,10 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'content': "public class " + entry['name'], 'content.vb': "Public Class " + entry['name'] }, - # this isn't applicable but just added it in case it is needed - 'inheritance': ['System.Object'], - 'inheritedMembers': ['System.Object.GetType'], + # # this isn't applicable for bonsai files but added it in to avoid mref.extension.js errors + # # TODO: maybe make that section of the code more robust to missing fields + # 'inheritance': ['System.Object'], + # 'inheritedMembers': ['System.Object.GetType'], }] for property_name, description in entry['properties'].items(): new_bonsai_yml_file["items"].append({ @@ -170,6 +171,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): }, 'overload': entry['uid']+'.'+ property_name +'*' }) + # with open(bonsai_yml_file, 'w') as f: f.write("### YamlMime:ManagedReference\n") From 13bbce6cb547725d1710c301d6b6b0eebaf830e2 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Sun, 3 Nov 2024 23:00:17 -0800 Subject: [PATCH 37/70] Add missing fields to ensure docfx build successfully --- template/api/plugins/Patch-IncludeWorkflow.py | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 0f5b8e5..8460f55 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -3,6 +3,7 @@ # isnt enough as the API template relies on certain shared models that don't build correctly # TODO: add yaml flag and check to prevent modification of already modified files # TODO: make it more efficient (too many loops of new entries in each separate function) +# requirements: pyyaml (install with pip install pyyaml) import os import yaml @@ -105,7 +106,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): for entry in bonsai_entries: bonsai_yml_file = os.path.join(api_folder, entry['uid']+".yml") new_bonsai_yml_file = {} - new_bonsai_yml_file["items"]=[{ + new_bonsai_yml_file['items']=[{ 'uid': entry['uid'], 'commentId': "T:"+entry['uid'], 'id': entry['name'], @@ -138,8 +139,9 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): # 'inheritance': ['System.Object'], # 'inheritedMembers': ['System.Object.GetType'], }] + # adds properties for property_name, description in entry['properties'].items(): - new_bonsai_yml_file["items"].append({ + new_bonsai_yml_file['items'].append({ 'uid':entry['uid']+"." + property_name, 'commentId': 'P:'+ entry['uid']+"." + property_name, 'id': property_name, @@ -171,7 +173,68 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): }, 'overload': entry['uid']+'.'+ property_name +'*' }) - # + + # adds references + # adds parent reference + new_bonsai_yml_file['references']=[{ + 'uid': entry['namespace'], + 'commentId': "N:"+entry['namespace'], + 'href': entry['namespace'].split('.')[0]+".html", + 'name': entry['namespace'], + 'nameWithType': entry['namespace'], + 'fullName': entry['namespace'], + }] + # this section modifies the parent reference to include additional information if the parent isn't the root namespace + # Works for 2 namespaces (like Bonvision.Collections), will there be instances where theres more than 2? + if entry['namespace'].split('.')[0] != entry['namespace']: + new_bonsai_yml_file['references'][0]['spec.csharp'] = [{ + 'uid': entry['namespace'].split('.')[0], + 'name': entry['namespace'].split('.')[0], + 'href': entry['namespace'].split('.')[0]+".html" + },{ + 'name':'.' + },{ + 'uid': entry['namespace'], + 'name': entry['namespace'].split('.')[1], + 'href': entry['namespace']+".html" + }] + new_bonsai_yml_file['references'][0]['spec.vb'] = [{ + 'uid': entry['namespace'].split('.')[0], + 'name': entry['namespace'].split('.')[0], + 'href':entry['namespace'].split('.')[0]+".html" + },{ + 'name':'.' + },{ + 'uid': entry['namespace'], + 'name': entry['namespace'].split('.')[1], + 'href': entry['namespace']+".html" + }] + + # adds return value reference + new_bonsai_yml_file['references'].append({ + 'uid': 'System.Single', + 'commentId': 'T:System.Single', + 'parent': 'System', + 'isExternal': 'true', + 'href': 'https://learn.microsoft.com/dotnet/api/system.single', + 'name': 'float', + 'nameWithType': 'float', + 'fullName': 'float', + 'nameWithType.vb': 'Single', + 'fullName.vb': 'Single', + 'name.vb': 'Single' + }) + + # adds properties overload references + for property_name, description in entry['properties'].items(): + new_bonsai_yml_file['references'].append({ + 'uid':entry['uid']+'.'+ property_name +'*', + 'commentId': 'Overload:'+ entry['uid']+'.'+ property_name, + 'href': entry['uid']+'.html#'+entry['uid'].replace('.', '_')+'_'+property_name, + 'name': property_name, + 'nameWithType': entry['name']+'.'+property_name, + 'fullName': entry['uid']+'.'+property_name, + }) with open(bonsai_yml_file, 'w') as f: f.write("### YamlMime:ManagedReference\n") From 7526c53a1a46e4e2b837f30526294dd90b708bf5 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 6 Nov 2024 12:02:15 -0800 Subject: [PATCH 38/70] Fix operator and property descriptions --- template/api/plugins/Patch-IncludeWorkflow.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 8460f55..552a61f 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -76,13 +76,15 @@ def generate_entries(bonsai_files, src_folder): name = os.path.splitext(os.path.basename(file))[0] namespace = extract_namespace(file, src_folder) uid = namespace + "." + name + operator_description, properties = extract_information_from_bonsai(file) new_entries.append({ 'namespace': namespace, 'uid': uid, 'name': name, 'file': file, - 'properties': extract_properties(file) + 'operator_description': operator_description, + 'properties': properties }) return new_entries @@ -130,6 +132,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): }, 'assemblies': [entry['namespace'].split('.')[0]], 'namespace': entry['namespace'], + 'summary': entry['operator_description'], 'syntax':{ 'content': "public class " + entry['name'], 'content.vb': "Public Class " + entry['name'] @@ -140,7 +143,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): # 'inheritedMembers': ['System.Object.GetType'], }] # adds properties - for property_name, description in entry['properties'].items(): + for property_name, property_description in entry['properties'].items(): new_bonsai_yml_file['items'].append({ 'uid':entry['uid']+"." + property_name, 'commentId': 'P:'+ entry['uid']+"." + property_name, @@ -164,6 +167,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): }, 'assemblies': [entry['namespace'].split('.')[0]], 'namespace': entry['namespace'], + 'summary': property_description, # this should probably be tailored for each property 'syntax':{ 'content': 'public float ' + property_name, @@ -302,7 +306,7 @@ def patch_manifest(manifest_path, new_entries): with open(manifest_path, 'w') as f: json.dump(manifest_data, f, indent=2, sort_keys=True) -def extract_properties(entry): +def extract_information_from_bonsai(entry): properties = [] tree = ET.parse(entry) @@ -316,6 +320,12 @@ def extract_properties(entry): # Get the default namespace from the XML (no prefix) default_ns = xml_namespace[''] + # Build the tag name for 'Description' + description_tag = f"{{{default_ns}}}Description" + + # Extract description + operator_description = root.find(description_tag).text + # Build the full tag name for 'Expression' with the namespace expression_tag = f"{{{default_ns}}}Expression" # e.g., "{https://bonsai-rx.org/2018/workflow}Expression" @@ -334,7 +344,7 @@ def extract_properties(entry): property_dict[property_name] = description else: property_dict[display_name] = description - return property_dict + return operator_description, property_dict def main(): @@ -350,9 +360,6 @@ def main(): # Generate entries from bonsai files new_entries = generate_entries(bonsai_files, src_folder) - # for entry in new_entries: - # print(entry) - # Get git information to populate yml source field branch_name, repo_url = get_git_information() From 50028fc79671c8418365e8467de71ccfe5de54d0 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 6 Nov 2024 17:02:49 -0800 Subject: [PATCH 39/70] Extract property descriptions from IncludedWorkflow operators --- template/api/plugins/Patch-IncludeWorkflow.py | 82 ++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 552a61f..5338ca7 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -76,7 +76,7 @@ def generate_entries(bonsai_files, src_folder): name = os.path.splitext(os.path.basename(file))[0] namespace = extract_namespace(file, src_folder) uid = namespace + "." + name - operator_description, properties = extract_information_from_bonsai(file) + operator_description, properties = extract_information_from_bonsai(file, src_folder, 'parse_root') new_entries.append({ 'namespace': namespace, @@ -306,46 +306,72 @@ def patch_manifest(manifest_path, new_entries): with open(manifest_path, 'w') as f: json.dump(manifest_data, f, indent=2, sort_keys=True) -def extract_information_from_bonsai(entry): +def extract_information_from_bonsai(entry, src_folder, method, property = None): properties = [] tree = ET.parse(entry) root = tree.getroot() # Get XML namespaces and prefixes + # Build tags xml_namespace = {} for event, elem in ET.iterparse(entry, ["start-ns"]): xml_namespace[elem[0]] = elem[1] - # Get the default namespace from the XML (no prefix) default_ns = xml_namespace[''] - - # Build the tag name for 'Description' description_tag = f"{{{default_ns}}}Description" - - # Extract description - operator_description = root.find(description_tag).text - - # Build the full tag name for 'Expression' with the namespace - expression_tag = f"{{{default_ns}}}Expression" # e.g., "{https://bonsai-rx.org/2018/workflow}Expression" - - # Find all visible 'Properties' based on xsi:type Externalized Mapping" - # Note - it seems that some properties are hidden which I did not realise. - # For instance see EventLogger - property_dict = {} - for expression in root.findall(f".//{expression_tag}", xml_namespace): - xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") - if xsi_type == "ExternalizedMapping": - for prop in expression.findall(f"{{{default_ns}}}Property"): - property_name = prop.get('Name') - description = prop.get('Description', "No description available.") - display_name = prop.get('DisplayName', False) - if display_name == False: + expression_tag = f"{{{default_ns}}}Expression" + + if method == "parse_root": + operator_description = root.find(description_tag).text + + # Find other IncludeWorkflow files to pull externalised mapping properties from + # This assumes that there might be more than 1 IncludeWorkflow .bonsai file but so far I have only seen 1 + # This for loop is duplicated in the next session, see if its possible to maybe refactor + include_workflow_list = [] + for expression in root.findall(f".//{expression_tag}", xml_namespace): + xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") + if xsi_type == "IncludeWorkflow": + path = expression.get("Path", "No path available") + parts = path.split(":") + # this assumes that there is only 1 folder in the parent namespace, but might need to be modified in case + # I could combine the split with a list comprehension, but sometimes the parent namespace has dots in the folder + # eg. Bonsai.ML.LinearDynamicalSystems + subparts = parts[1].split(".") + file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") + include_workflow_list.append(file_path) + + # Find all visible 'Properties' based on xsi:type Externalized Mapping" + # Note - it seems that some properties are hidden which I did not realise. + # For instance see EventLogger + property_dict = {} + for expression in root.findall(f".//{expression_tag}", xml_namespace): + xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") + if xsi_type == "ExternalizedMapping": + for prop in expression.findall(f"{{{default_ns}}}Property"): + property_name = prop.get('Name') + description = prop.get('Description', False) + display_name = prop.get('DisplayName', False) + if display_name == False: + pass + else: + property_name = display_name + if description == False: + for file in include_workflow_list: + description = extract_information_from_bonsai(file, src_folder, "parse_sub", property_name) property_dict[property_name] = description - else: - property_dict[display_name] = description - return operator_description, property_dict - + return operator_description, property_dict + + if method == "parse_sub": + property_dict = {} + description = False + for expression in root.findall(f".//{expression_tag}", xml_namespace): + xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") + if xsi_type == "ExternalizedMapping": + for prop in expression.findall(f"{{{default_ns}}}Property"): + if property == prop.get('Name') or property == prop.get('DisplayName'): + description = prop.get('Description') + return description def main(): src_folder = "../src" # Adjust if your src folder is in a different location From 29461c2715037b715187ee2c45a5b81b502373d0 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 7 Nov 2024 14:01:40 -0800 Subject: [PATCH 40/70] Add property check to prevent unnecessary looping & erroneous overwriting --- template/api/plugins/Patch-IncludeWorkflow.py | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 5338ca7..46a9796 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -306,7 +306,12 @@ def patch_manifest(manifest_path, new_entries): with open(manifest_path, 'w') as f: json.dump(manifest_data, f, indent=2, sort_keys=True) -def extract_information_from_bonsai(entry, src_folder, method, property = None): +def extract_information_from_cs(operator_name, property_name, src_folder): + with open(entry, "r", encoding="utf-8") as file: + content = file.read() + print(content) # Prints the entire content of the C# file + +def extract_information_from_bonsai(entry, src_folder, method, property_name = None, display_name = False): properties = [] tree = ET.parse(entry) @@ -317,7 +322,8 @@ def extract_information_from_bonsai(entry, src_folder, method, property = None): xml_namespace = {} for event, elem in ET.iterparse(entry, ["start-ns"]): xml_namespace[elem[0]] = elem[1] - + + # print(xml_namespace) default_ns = xml_namespace[''] description_tag = f"{{{default_ns}}}Description" expression_tag = f"{{{default_ns}}}Expression" @@ -352,13 +358,43 @@ def extract_information_from_bonsai(entry, src_folder, method, property = None): property_name = prop.get('Name') description = prop.get('Description', False) display_name = prop.get('DisplayName', False) - if display_name == False: - pass - else: - property_name = display_name + + # print(entry, property_name, display_name, description) + + # Checks to see if property has already been defined to avoid overwrites and unnecessary loops + if property_name in property_dict or display_name in property_dict: + continue + + # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead if description == False: for file in include_workflow_list: - description = extract_information_from_bonsai(file, src_folder, "parse_sub", property_name) + description = extract_information_from_bonsai(file, src_folder, "parse_sub", property_name, display_name) + print("Checking: ", file, "for:", property_name, "in:", entry, description) + + # If the previous section fails, it checks other operator files + if description == False: + for parent in root.iter(): + for child in parent: + if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: + property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") + + # this line checks for property sources that come from outside operators + # avoids empty and incorect property sources from generic display_names + if property_source is not None and ':' in property_source: + # print(entry, property_name, display_name, property_source, description) + property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] + property_assembly = property_source.split(':')[1] + + # this line checks if the property is in the src operator files + # if property_namespace in entry: + # extract_information_from_cs(operator_name, property_name, src_folder) + + # description = extract_information_from_cs(file, src_folder, property_name) + + # Some properties need even further mapping (for instance, GammaLut in GammaCorrection) + + if display_name != False: + property_name = display_name property_dict[property_name] = description return operator_description, property_dict @@ -369,8 +405,12 @@ def extract_information_from_bonsai(entry, src_folder, method, property = None): xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") if xsi_type == "ExternalizedMapping": for prop in expression.findall(f"{{{default_ns}}}Property"): - if property == prop.get('Name') or property == prop.get('DisplayName'): + if {property_name, display_name} & {prop.get('Name'),prop.get('DisplayName')}: + if prop.get('Description') == None: + continue description = prop.get('Description') + # print(entry, property_name, display_name, prop.get('Description')) + # print(entry, property_name, display_name, prop.get('Name'), prop.get('DisplayName'), prop.get('Description')) return description def main(): From c23b816cb4ffd428b2db171698c7e64fe9cffb42 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 7 Nov 2024 15:25:16 -0800 Subject: [PATCH 41/70] Add function to pull property description from C# files --- template/api/plugins/Patch-IncludeWorkflow.py | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 46a9796..f3dd515 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -9,6 +9,7 @@ import yaml import json import xml.etree.ElementTree as ET +import re def find_bonsai_files(src_folder): """Search for all .bonsai files in the src folder and return their paths.""" @@ -306,10 +307,23 @@ def patch_manifest(manifest_path, new_entries): with open(manifest_path, 'w') as f: json.dump(manifest_data, f, indent=2, sort_keys=True) -def extract_information_from_cs(operator_name, property_name, src_folder): - with open(entry, "r", encoding="utf-8") as file: - content = file.read() - print(content) # Prints the entire content of the C# file +def extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name): + filename = os.path.join(src_folder, property_namespace, f"{property_assembly}.cs") + with open(filename, "r", encoding="utf-8") as file: + for line in file: + line = line.strip() + # Check if the line is a Description attribute + if line.startswith("[Description("): + # Extract the description text within quotes + # Might need to update it to pull XML summary descriptions for newer operators + description = re.search(r'\[Description\("([^"]*)"\)\]', line).group(1) + if property_name in line: + break + return description + +def extract_information_from_package(property_namespace, property_assembly, src_folder, property_name): + pass + def extract_information_from_bonsai(entry, src_folder, method, property_name = None, display_name = False): properties = [] @@ -369,7 +383,7 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N if description == False: for file in include_workflow_list: description = extract_information_from_bonsai(file, src_folder, "parse_sub", property_name, display_name) - print("Checking: ", file, "for:", property_name, "in:", entry, description) + # print("Checking: ", file, "for:", property_name, "in:", entry, description) # If the previous section fails, it checks other operator files if description == False: @@ -384,10 +398,13 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # print(entry, property_name, display_name, property_source, description) property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] property_assembly = property_source.split(':')[1] + # print(entry, property_name, display_name, property_source, property_namespace, property_assembly) # this line checks if the property is in the src operator files - # if property_namespace in entry: - # extract_information_from_cs(operator_name, property_name, src_folder) + if property_namespace in entry: + description = extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name) + else: + description = extract_information_from_package(property_namespace, property_assembly, src_folder, property_name) # description = extract_information_from_cs(file, src_folder, property_name) From a36982dd03b7f64f20e05921105003912d3a29dc Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 7 Nov 2024 18:33:13 -0800 Subject: [PATCH 42/70] Extract property descriptions from package XML documentation files --- template/api/plugins/Patch-IncludeWorkflow.py | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index f3dd515..1371dd2 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -77,7 +77,7 @@ def generate_entries(bonsai_files, src_folder): name = os.path.splitext(os.path.basename(file))[0] namespace = extract_namespace(file, src_folder) uid = namespace + "." + name - operator_description, properties = extract_information_from_bonsai(file, src_folder, 'parse_root') + operator_description, properties = extract_information_from_bonsai(file, src_folder, method = 'parse_root') new_entries.append({ 'namespace': namespace, @@ -321,9 +321,36 @@ def extract_information_from_cs(property_namespace, property_assembly, src_folde break return description -def extract_information_from_package(property_namespace, property_assembly, src_folder, property_name): - pass - +def extract_information_from_package(property_namespace, property_assembly, property_name): + filename = os.path.join("../.bonsai", "Bonsai.config") + description = False + try: + with open(filename, "r", encoding="utf-8") as file: + tree = ET.parse(file) + root = tree.getroot() + + # Find the AssemblyLocation element with the specified assemblyName + for assembly_location in root.findall(".//AssemblyLocation"): + if assembly_location.get("assemblyName") == property_namespace: + # Return the location attribute if the assembly is found + property_assembly_description_file = os.path.join("../.bonsai", assembly_location.get("location")[:-4] + ".xml") + try: + with open(property_assembly_description_file, "r", encoding="utf-8") as file2: + tree = ET.parse(file2) + root = tree.getroot() + + for member in root.findall(".//member"): + if member.get("name") == "P:"+property_namespace+"."+property_assembly+"."+property_name: + description = member.find("summary").text.strip() + except: + print(f"{{{property_assembly_description_file}}} not found, have you installed the package dependencies in the .bonsai local environment") + print(f"{{{property_name}}} in {{{property_assembly}}} not extracted") + return None + return description + except: + print("Bonsai.config wasn't found, have you installed .bonsai local environment .") + return None + def extract_information_from_bonsai(entry, src_folder, method, property_name = None, display_name = False): properties = [] @@ -404,7 +431,7 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N if property_namespace in entry: description = extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name) else: - description = extract_information_from_package(property_namespace, property_assembly, src_folder, property_name) + description = extract_information_from_package(property_namespace, property_assembly, property_name) # description = extract_information_from_cs(file, src_folder, property_name) From f2c16e8dd4453df85b9835c609abf41a9b81c6be Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 7 Nov 2024 19:00:31 -0800 Subject: [PATCH 43/70] Modify logic to only skip properties if they have a description --- template/api/plugins/Patch-IncludeWorkflow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 1371dd2..bb7bf79 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -402,9 +402,11 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # print(entry, property_name, display_name, description) - # Checks to see if property has already been defined to avoid overwrites and unnecessary loops + # Skips property if has already been defined to avoid overwrites and unnecessary loops + # But overwrites it if it could not find the description before if property_name in property_dict or display_name in property_dict: - continue + if property_dict.get(property_name) is not False and property_dict.get(display_name) is not False: + continue # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead if description == False: From 2a40d2e8c9801387a5eaffb65be6bb8adfe2672b Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 7 Nov 2024 22:16:40 -0800 Subject: [PATCH 44/70] Further improvments to property search in package files --- template/api/plugins/Patch-IncludeWorkflow.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index bb7bf79..78fce7c 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -342,9 +342,9 @@ def extract_information_from_package(property_namespace, property_assembly, prop for member in root.findall(".//member"): if member.get("name") == "P:"+property_namespace+"."+property_assembly+"."+property_name: description = member.find("summary").text.strip() + # print(property_namespace, property_assembly, property_name, description) except: - print(f"{{{property_assembly_description_file}}} not found, have you installed the package dependencies in the .bonsai local environment") - print(f"{{{property_name}}} in {{{property_assembly}}} not extracted") + print(f"{{{property_name}}} in {{{property_assembly}}} in {{{property_assembly_description_file}}} not found. package not installed in .bonsai or missing doc XML") return None return description except: @@ -405,8 +405,17 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # Skips property if has already been defined to avoid overwrites and unnecessary loops # But overwrites it if it could not find the description before if property_name in property_dict or display_name in property_dict: - if property_dict.get(property_name) is not False and property_dict.get(display_name) is not False: + prop_value = property_dict.get(property_name) + display_value = property_dict.get(display_name) + # print(property_name, display_name, prop_value, display_value) + + if (prop_value is not False and prop_value is not None) or \ + (display_value is not False and display_value is not None): + # print(property_name, display_name, prop_value, display_value) continue + + # if property_dict.get(property_name) is not False and property_dict.get(display_name) is not False: + # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead if description == False: @@ -416,6 +425,7 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # If the previous section fails, it checks other operator files if description == False: + found_description = False for parent in root.iter(): for child in parent: if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: @@ -427,20 +437,28 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # print(entry, property_name, display_name, property_source, description) property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] property_assembly = property_source.split(':')[1] - # print(entry, property_name, display_name, property_source, property_namespace, property_assembly) # this line checks if the property is in the src operator files if property_namespace in entry: description = extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name) else: description = extract_information_from_package(property_namespace, property_assembly, property_name) - - # description = extract_information_from_cs(file, src_folder, property_name) + # print(entry, property_name, display_name, property_namespace, property_assembly, description) + + # The breaks are to prevent overwriting from other definitions in the file. + if description is not False: + found_description = True + break + + # The breaks are to prevent overwriting from other definitions in the file. + if found_description: + break # Some properties need even further mapping (for instance, GammaLut in GammaCorrection) if display_name != False: property_name = display_name + # print(entry, property_name, description) property_dict[property_name] = description return operator_description, property_dict From 262c407187148b6d418cf3a6b7658ef6076973af Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 7 Nov 2024 22:40:05 -0800 Subject: [PATCH 45/70] Improve property processing by keeping track of process properties --- template/api/plugins/Patch-IncludeWorkflow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 78fce7c..1129544 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -392,6 +392,7 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # Note - it seems that some properties are hidden which I did not realise. # For instance see EventLogger property_dict = {} + processed_properties = set() for expression in root.findall(f".//{expression_tag}", xml_namespace): xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") if xsi_type == "ExternalizedMapping": @@ -431,6 +432,9 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") + if (property_source, property_name) in processed_properties: + continue + # this line checks for property sources that come from outside operators # avoids empty and incorect property sources from generic display_names if property_source is not None and ':' in property_source: @@ -448,6 +452,7 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # The breaks are to prevent overwriting from other definitions in the file. if description is not False: found_description = True + processed_properties.add((property_source, property_name)) break # The breaks are to prevent overwriting from other definitions in the file. From f77cc23cfe25d7b1b2eea9abfb9f340fe752e787 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 8 Nov 2024 10:43:31 -0800 Subject: [PATCH 46/70] Hide properties that have been property mapped --- template/api/plugins/Patch-IncludeWorkflow.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 1129544..b4b7d08 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -393,6 +393,12 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # For instance see EventLogger property_dict = {} processed_properties = set() + + # this keeps track of external mapping properties that have a description attached + # and are thus special properies that are to be excluded when filtering properties that are being property mapped + properties_to_not_exclude = [] + + for expression in root.findall(f".//{expression_tag}", xml_namespace): xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") if xsi_type == "ExternalizedMapping": @@ -403,6 +409,9 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N # print(entry, property_name, display_name, description) + if description: + properties_to_not_exclude.append(display_name) + # Skips property if has already been defined to avoid overwrites and unnecessary loops # But overwrites it if it could not find the description before if property_name in property_dict or display_name in property_dict: @@ -465,6 +474,20 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N property_name = display_name # print(entry, property_name, description) property_dict[property_name] = description + + # Remove property mapping properties (these are hidden in the editor) + # As an example see ExtentX and ExtentY in DrawCircle + property_mapping_list = [] + for expression in root.findall(f".//{expression_tag}", xml_namespace): + xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") + if xsi_type == "PropertyMapping": + for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): + property_name = prop.get('Name') + property_mapping_list.append(property_name) + for prop in property_mapping_list: + if prop not in properties_to_not_exclude: + property_dict.pop(prop, None) + return operator_description, property_dict if method == "parse_sub": From f9d49268d754e4d807b7d6bdd9e6af3f0e37d322 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Sat, 9 Nov 2024 12:21:10 -0800 Subject: [PATCH 47/70] Refine skip properties logic --- template/api/plugins/Patch-IncludeWorkflow.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index b4b7d08..6fd7249 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -389,8 +389,6 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N include_workflow_list.append(file_path) # Find all visible 'Properties' based on xsi:type Externalized Mapping" - # Note - it seems that some properties are hidden which I did not realise. - # For instance see EventLogger property_dict = {} processed_properties = set() @@ -407,10 +405,25 @@ def extract_information_from_bonsai(entry, src_folder, method, property_name = N description = prop.get('Description', False) display_name = prop.get('DisplayName', False) - # print(entry, property_name, display_name, description) + print(entry, property_name, display_name, description) if description: properties_to_not_exclude.append(display_name) + # print(entry, property_source, property_name) + + # this section adds the parent of the properties so they can be skipped over + # even if the description is found + found_parent = False + for parent in root.iter(): + for child in parent: + if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: + property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") + # print(entry, property_source, property_name) + processed_properties.add((property_source, property_name)) + found_parent = True + break + if found_parent: + break # Skips property if has already been defined to avoid overwrites and unnecessary loops # But overwrites it if it could not find the description before From ff2dd86550cb8b3b4aaf969abad9cf26dbf0344c Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Mon, 11 Nov 2024 14:05:15 -0800 Subject: [PATCH 48/70] Refactor code to improve debuggability, regressions to be fixed --- template/api/plugins/Patch-IncludeWorkflow.py | 454 ++++++++++++------ 1 file changed, 308 insertions(+), 146 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 6fd7249..925cbb0 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -77,7 +77,7 @@ def generate_entries(bonsai_files, src_folder): name = os.path.splitext(os.path.basename(file))[0] namespace = extract_namespace(file, src_folder) uid = namespace + "." + name - operator_description, properties = extract_information_from_bonsai(file, src_folder, method = 'parse_root') + operator_description, properties = extract_information_from_bonsai(file, src_folder) new_entries.append({ 'namespace': namespace, @@ -350,173 +350,335 @@ def extract_information_from_package(property_namespace, property_assembly, prop except: print("Bonsai.config wasn't found, have you installed .bonsai local environment .") return None - -def extract_information_from_bonsai(entry, src_folder, method, property_name = None, display_name = False): - properties = [] + +def extract_information_from_include_workflow(entry, src_folder, property_name = None, display_name = False): + tree = ET.parse(entry) + root = tree.getroot() + + # Get XML namespaces and prefixes + xml_namespace = {} + for event, elem in ET.iterparse(entry, ["start-ns"]): + xml_namespace[elem[0]] = elem[1] + # Make tags + default_ns = xml_namespace[''] + description_tag = f"{{{default_ns}}}Description" + expression_tag = f"{{{default_ns}}}Expression" + + property_dict = {} + description = False + for expression in root.findall(f".//{expression_tag}", xml_namespace): + xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") + if xsi_type == "ExternalizedMapping": + for prop in expression.findall(f"{{{default_ns}}}Property"): + if {property_name, display_name} & {prop.get('Name'),prop.get('DisplayName')}: + if prop.get('Description') == None: + continue + description = prop.get('Description') + return description + + +def extract_information_from_bonsai(entry, src_folder, property_name = None, display_name = False): tree = ET.parse(entry) root = tree.getroot() # Get XML namespaces and prefixes - # Build tags xml_namespace = {} for event, elem in ET.iterparse(entry, ["start-ns"]): - xml_namespace[elem[0]] = elem[1] + xml_namespace[elem[0]] = elem[1] - # print(xml_namespace) + # Make tags default_ns = xml_namespace[''] description_tag = f"{{{default_ns}}}Description" expression_tag = f"{{{default_ns}}}Expression" - if method == "parse_root": - operator_description = root.find(description_tag).text - - # Find other IncludeWorkflow files to pull externalised mapping properties from - # This assumes that there might be more than 1 IncludeWorkflow .bonsai file but so far I have only seen 1 - # This for loop is duplicated in the next session, see if its possible to maybe refactor - include_workflow_list = [] - for expression in root.findall(f".//{expression_tag}", xml_namespace): - xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") - if xsi_type == "IncludeWorkflow": - path = expression.get("Path", "No path available") - parts = path.split(":") - # this assumes that there is only 1 folder in the parent namespace, but might need to be modified in case - # I could combine the split with a list comprehension, but sometimes the parent namespace has dots in the folder - # eg. Bonsai.ML.LinearDynamicalSystems - subparts = parts[1].split(".") - file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") - include_workflow_list.append(file_path) - - # Find all visible 'Properties' based on xsi:type Externalized Mapping" - property_dict = {} - processed_properties = set() + # Find description + operator_description = root.find(description_tag).text + + # Dictionary to store externalized properties and their descriptions + xml_list = [] + externalized_mappings = {} + processed_properties = set() + include_workflow_list = [] + property_mapping_list = [] + properties_to_keep = [] + + # Find relevant elements and store them sequentially in a list + for expression in root.findall(f".//{expression_tag}", xml_namespace): + xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") + + if xsi_type == "ExternalizedMapping": + for prop in expression.findall(f"{{{default_ns}}}Property"): + property_name = prop.get('Name') + description = prop.get('Description', False) + display_name = prop.get('DisplayName', False) + + if description: + properties_to_keep.append(display_name) + + xml_list.append({ + "type": "ExternalizedMapping", + "property_name": property_name, + "display_name": display_name, + "description": description, + }) + + if xsi_type == "IncludeWorkflow": + path = expression.get("Path", "No path available") + parts = path.split(":") + subparts = parts[1].split(".") + file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") + include_workflow_list.append(file_path) + + if xsi_type in ("Combinator", "Source", "Transform", "Sink"): + operator_elem = expression.find("*", xml_namespace) + property_source = operator_elem.get(f"{{{xml_namespace['xsi']}}}type") + if ':' in property_source: + property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] + property_assembly = property_source.split(':')[1] + property_list = [] + for child in operator_elem: + property_name = child.tag.split("}")[-1] + property_list.append(property_name) + if property_list: + xml_list.append({ + "type": "PropertySource", + "property_namespace": property_namespace, + "property_assembly":property_assembly, + 'property_list': property_list + }) - # this keeps track of external mapping properties that have a description attached - # and are thus special properies that are to be excluded when filtering properties that are being property mapped - properties_to_not_exclude = [] - - - for expression in root.findall(f".//{expression_tag}", xml_namespace): - xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") - if xsi_type == "ExternalizedMapping": - for prop in expression.findall(f"{{{default_ns}}}Property"): - property_name = prop.get('Name') - description = prop.get('Description', False) - display_name = prop.get('DisplayName', False) - - print(entry, property_name, display_name, description) - - if description: - properties_to_not_exclude.append(display_name) - # print(entry, property_source, property_name) - - # this section adds the parent of the properties so they can be skipped over - # even if the description is found - found_parent = False - for parent in root.iter(): - for child in parent: - if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: - property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") - # print(entry, property_source, property_name) - processed_properties.add((property_source, property_name)) - found_parent = True - break - if found_parent: - break - - # Skips property if has already been defined to avoid overwrites and unnecessary loops - # But overwrites it if it could not find the description before - if property_name in property_dict or display_name in property_dict: - prop_value = property_dict.get(property_name) - display_value = property_dict.get(display_name) - # print(property_name, display_name, prop_value, display_value) - - if (prop_value is not False and prop_value is not None) or \ - (display_value is not False and display_value is not None): - # print(property_name, display_name, prop_value, display_value) - continue - - # if property_dict.get(property_name) is not False and property_dict.get(display_name) is not False: + if xsi_type == "PropertyMapping": + for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): + property_name = prop.get('Name') + property_mapping_list.append(property_name) + + print(xml_list) + + # clean up xml list for propert map properties + for prop in property_mapping_list[:]: + if prop not in properties_to_keep: + property_mapping_list.remove(prop) + + for prop in xml_list[:]: + if prop.get("property_name") in property_mapping_list: + xml_list.remove(prop) + + # Go through XML list and extract description for relevant properties + processed_properties = {} + index = -1 + for potential_property in xml_list[:]: + index += 1 + if potential_property['type'] == 'ExternalizedMapping': + if potential_property['description'] == False: + + # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead + for file in include_workflow_list: + description = extract_information_from_include_workflow(file, src_folder, potential_property['property_name'], potential_property['display_name']) + + # This section checks any subsequent PropertySources to see if the property description is defined there instead + if description == False: + for potential_source in xml_list[index+1:]: + if potential_source['type'] == "PropertySource": + if potential_property['property_name'] in potential_source['property_list']: + + # uses a CS file extractor if the propertysource is within the library, but the check is not that robust + if potential_source['property_namespace'] in entry: + description = extract_information_from_cs(potential_source['property_namespace'], potential_source['property_assembly'], src_folder, potential_property['property_name']) + + # uses a package file extractor + else: + description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], property_name) + # xml_list[index]["description"] = description + + if potential_property["display_name"] == False: + processed_properties[potential_property["property_name"]] = description + else: + processed_properties[potential_property["display_name"]] = description + else: + if potential_property["display_name"] == False: + processed_properties[potential_property["property_name"]] = description + else: + processed_properties[potential_property["display_name"]] = description + return(operator_description, processed_properties) + + + + + + + + + + + +# def extract_information_from_bonsai(entry, src_folder, method, property_name = None, display_name = False): +# properties = [] + +# tree = ET.parse(entry) +# root = tree.getroot() + +# # Get XML namespaces and prefixes +# # Build tags +# xml_namespace = {} +# for event, elem in ET.iterparse(entry, ["start-ns"]): +# xml_namespace[elem[0]] = elem[1] + +# # print(xml_namespace) +# default_ns = xml_namespace[''] +# description_tag = f"{{{default_ns}}}Description" +# expression_tag = f"{{{default_ns}}}Expression" + +# if method == "parse_root": +# operator_description = root.find(description_tag).text + +# # Find other IncludeWorkflow files to pull externalised mapping properties from +# # This assumes that there might be more than 1 IncludeWorkflow .bonsai file but so far I have only seen 1 +# # This for loop is duplicated in the next session, see if its possible to maybe refactor +# include_workflow_list = [] +# for expression in root.findall(f".//{expression_tag}", xml_namespace): +# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") +# if xsi_type == "IncludeWorkflow": +# path = expression.get("Path", "No path available") +# parts = path.split(":") +# # this assumes that there is only 1 folder in the parent namespace, but might need to be modified in case +# # I could combine the split with a list comprehension, but sometimes the parent namespace has dots in the folder +# # eg. Bonsai.ML.LinearDynamicalSystems +# subparts = parts[1].split(".") +# file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") +# include_workflow_list.append(file_path) + +# # Find all visible 'Properties' based on xsi:type Externalized Mapping" +# property_dict = {} +# processed_properties = set() + +# # this keeps track of external mapping properties that have a description attached +# # and are thus special properies that are to be excluded when filtering properties that are being property mapped +# properties_to_not_exclude = [] + + +# for expression in root.findall(f".//{expression_tag}", xml_namespace): +# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") +# if xsi_type == "ExternalizedMapping": +# for prop in expression.findall(f"{{{default_ns}}}Property"): +# property_name = prop.get('Name') +# description = prop.get('Description', False) +# display_name = prop.get('DisplayName', False) + +# # print(entry, property_name, display_name, description) + +# if description: +# properties_to_not_exclude.append(display_name) +# # print(entry, property_source, property_name) + +# # this section adds the parent of the properties so they can be skipped over +# # even if the description is found +# found_parent = False +# for parent in root.iter(): +# for child in parent: +# if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: +# property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") +# # print(entry, property_source, property_name, description) +# processed_properties.add((property_source, property_name)) +# found_parent = True +# break +# if found_parent: +# break + +# # Skips property if has already been defined to avoid overwrites and unnecessary loops +# # But overwrites it if it could not find the description before +# if property_name in property_dict or display_name in property_dict: +# prop_value = property_dict.get(property_name) +# display_value = property_dict.get(display_name) +# # print(property_name, display_name, prop_value, display_value) + +# if (prop_value is not False and prop_value is not None) or \ +# (display_value is not False and display_value is not None): +# # print(property_name, display_name, prop_value, display_value) +# continue + +# # if property_dict.get(property_name) is not False and property_dict.get(display_name) is not False: - # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead - if description == False: - for file in include_workflow_list: - description = extract_information_from_bonsai(file, src_folder, "parse_sub", property_name, display_name) - # print("Checking: ", file, "for:", property_name, "in:", entry, description) - - # If the previous section fails, it checks other operator files - if description == False: - found_description = False - for parent in root.iter(): - for child in parent: - if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: - property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") - - if (property_source, property_name) in processed_properties: - continue - - # this line checks for property sources that come from outside operators - # avoids empty and incorect property sources from generic display_names - if property_source is not None and ':' in property_source: - # print(entry, property_name, display_name, property_source, description) - property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] - property_assembly = property_source.split(':')[1] - - # this line checks if the property is in the src operator files - if property_namespace in entry: - description = extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name) - else: - description = extract_information_from_package(property_namespace, property_assembly, property_name) - # print(entry, property_name, display_name, property_namespace, property_assembly, description) +# # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead +# if description == False: +# for file in include_workflow_list: +# description = extract_information_from_bonsai(file, src_folder, "parse_sub", property_name, display_name) +# # print("Checking: ", file, "for:", property_name, "in:", entry, description) + +# # If the previous section fails, it checks other operator files +# if description == False: +# found_description = False +# for parent in root.iter(): +# for child in parent: +# if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: +# property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") + +# if (property_source, property_name) not in processed_properties: +# continue + +# # this line checks for property sources that come from outside operators +# # avoids empty and incorect property sources from generic display_names +# if property_source is not None and ':' in property_source: +# # print(entry, property_name, display_name, property_source, description) +# property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] +# property_assembly = property_source.split(':')[1] + +# # this line checks if the property is in the src operator files +# if property_namespace in entry: +# description = extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name) +# else: +# description = extract_information_from_package(property_namespace, property_assembly, property_name) +# # print(entry, property_name, display_name, property_namespace, property_assembly, description) - # The breaks are to prevent overwriting from other definitions in the file. - if description is not False: - found_description = True - processed_properties.add((property_source, property_name)) - break +# # The breaks are to prevent overwriting from other definitions in the file. +# if description is not False: +# found_description = True +# print(entry, property_source, property_name, display_name, description) +# processed_properties.add((property_source, property_name)) +# break - # The breaks are to prevent overwriting from other definitions in the file. - if found_description: - break +# # The breaks are to prevent overwriting from other definitions in the file. +# if found_description: +# break - # Some properties need even further mapping (for instance, GammaLut in GammaCorrection) +# # Some properties need even further mapping (for instance, GammaLut in GammaCorrection) - if display_name != False: - property_name = display_name - # print(entry, property_name, description) - property_dict[property_name] = description +# if display_name != False: +# property_name = display_name +# # print(entry, property_name, description) +# property_dict[property_name] = description - # Remove property mapping properties (these are hidden in the editor) - # As an example see ExtentX and ExtentY in DrawCircle - property_mapping_list = [] - for expression in root.findall(f".//{expression_tag}", xml_namespace): - xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") - if xsi_type == "PropertyMapping": - for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): - property_name = prop.get('Name') - property_mapping_list.append(property_name) - for prop in property_mapping_list: - if prop not in properties_to_not_exclude: - property_dict.pop(prop, None) - - return operator_description, property_dict +# # Remove property mapping properties (these are hidden in the editor) +# # As an example see ExtentX and ExtentY in DrawCircle +# property_mapping_list = [] +# for expression in root.findall(f".//{expression_tag}", xml_namespace): +# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") +# if xsi_type == "PropertyMapping": +# for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): +# property_name = prop.get('Name') +# property_mapping_list.append(property_name) +# for prop in property_mapping_list: +# if prop not in properties_to_not_exclude: +# property_dict.pop(prop, None) + +# return operator_description, property_dict - if method == "parse_sub": - property_dict = {} - description = False - for expression in root.findall(f".//{expression_tag}", xml_namespace): - xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") - if xsi_type == "ExternalizedMapping": - for prop in expression.findall(f"{{{default_ns}}}Property"): - if {property_name, display_name} & {prop.get('Name'),prop.get('DisplayName')}: - if prop.get('Description') == None: - continue - description = prop.get('Description') - # print(entry, property_name, display_name, prop.get('Description')) - # print(entry, property_name, display_name, prop.get('Name'), prop.get('DisplayName'), prop.get('Description')) - return description +# if method == "parse_sub": +# property_dict = {} +# description = False +# for expression in root.findall(f".//{expression_tag}", xml_namespace): +# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") +# if xsi_type == "ExternalizedMapping": +# for prop in expression.findall(f"{{{default_ns}}}Property"): +# if {property_name, display_name} & {prop.get('Name'),prop.get('DisplayName')}: +# if prop.get('Description') == None: +# continue +# description = prop.get('Description') +# # print(entry, property_name, display_name, prop.get('Description')) +# # print(entry, property_name, display_name, prop.get('Name'), prop.get('DisplayName'), prop.get('Description')) +# return description def main(): src_folder = "../src" # Adjust if your src folder is in a different location From b79243a6fe1d56b9da9c4e619ab6e7e525135ab0 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Mon, 11 Nov 2024 15:18:19 -0800 Subject: [PATCH 49/70] Fix several regressions --- template/api/plugins/Patch-IncludeWorkflow.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 925cbb0..a92b2dc 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -363,10 +363,8 @@ def extract_information_from_include_workflow(entry, src_folder, property_name = # Make tags default_ns = xml_namespace[''] - description_tag = f"{{{default_ns}}}Description" expression_tag = f"{{{default_ns}}}Expression" - property_dict = {} description = False for expression in root.findall(f".//{expression_tag}", xml_namespace): xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") @@ -398,7 +396,6 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis # Dictionary to store externalized properties and their descriptions xml_list = [] - externalized_mappings = {} processed_properties = set() include_workflow_list = [] property_mapping_list = [] @@ -454,7 +451,8 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis property_name = prop.get('Name') property_mapping_list.append(property_name) - print(xml_list) + print(entry, xml_list) + # print(include_workflow_list) # clean up xml list for propert map properties for prop in property_mapping_list[:]: @@ -472,24 +470,28 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis index += 1 if potential_property['type'] == 'ExternalizedMapping': if potential_property['description'] == False: + + description = False # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead for file in include_workflow_list: description = extract_information_from_include_workflow(file, src_folder, potential_property['property_name'], potential_property['display_name']) + # print(entry, description, potential_property['property_name'],potential_property['display_name']) # This section checks any subsequent PropertySources to see if the property description is defined there instead if description == False: for potential_source in xml_list[index+1:]: if potential_source['type'] == "PropertySource": if potential_property['property_name'] in potential_source['property_list']: - + # uses a CS file extractor if the propertysource is within the library, but the check is not that robust if potential_source['property_namespace'] in entry: description = extract_information_from_cs(potential_source['property_namespace'], potential_source['property_assembly'], src_folder, potential_property['property_name']) # uses a package file extractor else: - description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], property_name) + description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_property['property_name']) + # print(potential_property['property_name'],potential_source, description) # xml_list[index]["description"] = description if potential_property["display_name"] == False: @@ -498,9 +500,9 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis processed_properties[potential_property["display_name"]] = description else: if potential_property["display_name"] == False: - processed_properties[potential_property["property_name"]] = description + processed_properties[potential_property["property_name"]] = potential_property['description'] else: - processed_properties[potential_property["display_name"]] = description + processed_properties[potential_property["display_name"]] = potential_property['description'] return(operator_description, processed_properties) From 77844fc44613b26d46ba005e3c5812df8b3d2842 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Mon, 11 Nov 2024 21:38:17 -0800 Subject: [PATCH 50/70] fix remaining regressions in refactored code --- template/api/plugins/Patch-IncludeWorkflow.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index a92b2dc..86e5a56 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -453,14 +453,16 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis print(entry, xml_list) # print(include_workflow_list) - + # print(entry, property_mapping_list, properties_to_keep) # clean up xml list for propert map properties for prop in property_mapping_list[:]: - if prop not in properties_to_keep: + if prop in properties_to_keep: property_mapping_list.remove(prop) + + # print(entry, property_mapping_list) for prop in xml_list[:]: - if prop.get("property_name") in property_mapping_list: + if prop.get("display_name") in property_mapping_list: xml_list.remove(prop) # Go through XML list and extract description for relevant properties @@ -491,13 +493,19 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis # uses a package file extractor else: description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_property['property_name']) - # print(potential_property['property_name'],potential_source, description) + # print(entry, potential_property['property_name'],potential_source, description) + if description: + break + # xml_list[index]["description"] = description - - if potential_property["display_name"] == False: - processed_properties[potential_property["property_name"]] = description - else: - processed_properties[potential_property["display_name"]] = description + # bunch of checks to make sure that it only overwrites previous declarations if they are empty and if it itself is not empty. + if description: + if potential_property["display_name"] == False: + if not processed_properties.get(potential_property["property_name"]): + processed_properties[potential_property["property_name"]] = description + else: + if not processed_properties.get(potential_property["display_name"]): + processed_properties[potential_property["display_name"]] = description else: if potential_property["display_name"] == False: processed_properties[potential_property["property_name"]] = potential_property['description'] From 4a5aa5a360a1b52fb64f5e0e7bf40ffc5b8d8ad1 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Mon, 11 Nov 2024 23:11:30 -0800 Subject: [PATCH 51/70] Make property extraction from CS file more robust --- template/api/plugins/Patch-IncludeWorkflow.py | 187 +----------------- 1 file changed, 9 insertions(+), 178 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 86e5a56..a4881d6 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -312,13 +312,17 @@ def extract_information_from_cs(property_namespace, property_assembly, src_folde with open(filename, "r", encoding="utf-8") as file: for line in file: line = line.strip() + # Check if the line is a Description attribute if line.startswith("[Description("): # Extract the description text within quotes # Might need to update it to pull XML summary descriptions for newer operators description = re.search(r'\[Description\("([^"]*)"\)\]', line).group(1) - if property_name in line: + + # breaks the loop if it finds the property declaration and returns the latest description + if "public" in line and property_name in line: break + return description def extract_information_from_package(property_namespace, property_assembly, property_name): @@ -451,7 +455,8 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis property_name = prop.get('Name') property_mapping_list.append(property_name) - print(entry, xml_list) + if entry == "../src\BonVision\Primitives\DrawImage.bonsai": + print(entry, xml_list) # print(include_workflow_list) # print(entry, property_mapping_list, properties_to_keep) # clean up xml list for propert map properties @@ -489,6 +494,8 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis # uses a CS file extractor if the propertysource is within the library, but the check is not that robust if potential_source['property_namespace'] in entry: description = extract_information_from_cs(potential_source['property_namespace'], potential_source['property_assembly'], src_folder, potential_property['property_name']) + if description: + break # uses a package file extractor else: @@ -514,182 +521,6 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis return(operator_description, processed_properties) - - - - - - - - - -# def extract_information_from_bonsai(entry, src_folder, method, property_name = None, display_name = False): -# properties = [] - -# tree = ET.parse(entry) -# root = tree.getroot() - -# # Get XML namespaces and prefixes -# # Build tags -# xml_namespace = {} -# for event, elem in ET.iterparse(entry, ["start-ns"]): -# xml_namespace[elem[0]] = elem[1] - -# # print(xml_namespace) -# default_ns = xml_namespace[''] -# description_tag = f"{{{default_ns}}}Description" -# expression_tag = f"{{{default_ns}}}Expression" - -# if method == "parse_root": -# operator_description = root.find(description_tag).text - -# # Find other IncludeWorkflow files to pull externalised mapping properties from -# # This assumes that there might be more than 1 IncludeWorkflow .bonsai file but so far I have only seen 1 -# # This for loop is duplicated in the next session, see if its possible to maybe refactor -# include_workflow_list = [] -# for expression in root.findall(f".//{expression_tag}", xml_namespace): -# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") -# if xsi_type == "IncludeWorkflow": -# path = expression.get("Path", "No path available") -# parts = path.split(":") -# # this assumes that there is only 1 folder in the parent namespace, but might need to be modified in case -# # I could combine the split with a list comprehension, but sometimes the parent namespace has dots in the folder -# # eg. Bonsai.ML.LinearDynamicalSystems -# subparts = parts[1].split(".") -# file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") -# include_workflow_list.append(file_path) - -# # Find all visible 'Properties' based on xsi:type Externalized Mapping" -# property_dict = {} -# processed_properties = set() - -# # this keeps track of external mapping properties that have a description attached -# # and are thus special properies that are to be excluded when filtering properties that are being property mapped -# properties_to_not_exclude = [] - - -# for expression in root.findall(f".//{expression_tag}", xml_namespace): -# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") -# if xsi_type == "ExternalizedMapping": -# for prop in expression.findall(f"{{{default_ns}}}Property"): -# property_name = prop.get('Name') -# description = prop.get('Description', False) -# display_name = prop.get('DisplayName', False) - -# # print(entry, property_name, display_name, description) - -# if description: -# properties_to_not_exclude.append(display_name) -# # print(entry, property_source, property_name) - -# # this section adds the parent of the properties so they can be skipped over -# # even if the description is found -# found_parent = False -# for parent in root.iter(): -# for child in parent: -# if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: -# property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") -# # print(entry, property_source, property_name, description) -# processed_properties.add((property_source, property_name)) -# found_parent = True -# break -# if found_parent: -# break - -# # Skips property if has already been defined to avoid overwrites and unnecessary loops -# # But overwrites it if it could not find the description before -# if property_name in property_dict or display_name in property_dict: -# prop_value = property_dict.get(property_name) -# display_value = property_dict.get(display_name) -# # print(property_name, display_name, prop_value, display_value) - -# if (prop_value is not False and prop_value is not None) or \ -# (display_value is not False and display_value is not None): -# # print(property_name, display_name, prop_value, display_value) -# continue - -# # if property_dict.get(property_name) is not False and property_dict.get(display_name) is not False: - - -# # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead -# if description == False: -# for file in include_workflow_list: -# description = extract_information_from_bonsai(file, src_folder, "parse_sub", property_name, display_name) -# # print("Checking: ", file, "for:", property_name, "in:", entry, description) - -# # If the previous section fails, it checks other operator files -# if description == False: -# found_description = False -# for parent in root.iter(): -# for child in parent: -# if child.tag.split("}")[-1] == property_name or child.tag.split("}")[-1] == display_name: -# property_source = parent.get(f"{{{xml_namespace['xsi']}}}type") - -# if (property_source, property_name) not in processed_properties: -# continue - -# # this line checks for property sources that come from outside operators -# # avoids empty and incorect property sources from generic display_names -# if property_source is not None and ':' in property_source: -# # print(entry, property_name, display_name, property_source, description) -# property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] -# property_assembly = property_source.split(':')[1] - -# # this line checks if the property is in the src operator files -# if property_namespace in entry: -# description = extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name) -# else: -# description = extract_information_from_package(property_namespace, property_assembly, property_name) -# # print(entry, property_name, display_name, property_namespace, property_assembly, description) - -# # The breaks are to prevent overwriting from other definitions in the file. -# if description is not False: -# found_description = True -# print(entry, property_source, property_name, display_name, description) -# processed_properties.add((property_source, property_name)) -# break - -# # The breaks are to prevent overwriting from other definitions in the file. -# if found_description: -# break - -# # Some properties need even further mapping (for instance, GammaLut in GammaCorrection) - -# if display_name != False: -# property_name = display_name -# # print(entry, property_name, description) -# property_dict[property_name] = description - -# # Remove property mapping properties (these are hidden in the editor) -# # As an example see ExtentX and ExtentY in DrawCircle -# property_mapping_list = [] -# for expression in root.findall(f".//{expression_tag}", xml_namespace): -# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") -# if xsi_type == "PropertyMapping": -# for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): -# property_name = prop.get('Name') -# property_mapping_list.append(property_name) -# for prop in property_mapping_list: -# if prop not in properties_to_not_exclude: -# property_dict.pop(prop, None) - -# return operator_description, property_dict - -# if method == "parse_sub": -# property_dict = {} -# description = False -# for expression in root.findall(f".//{expression_tag}", xml_namespace): -# xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") -# if xsi_type == "ExternalizedMapping": -# for prop in expression.findall(f"{{{default_ns}}}Property"): -# if {property_name, display_name} & {prop.get('Name'),prop.get('DisplayName')}: -# if prop.get('Description') == None: -# continue -# description = prop.get('Description') -# # print(entry, property_name, display_name, prop.get('Description')) -# # print(entry, property_name, display_name, prop.get('Name'), prop.get('DisplayName'), prop.get('Description')) -# return description - def main(): src_folder = "../src" # Adjust if your src folder is in a different location toc_path = "api/toc.yml" # Path to the existing TOC file From 79645f79c16e5ba39c7227df76346b57249c48d2 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Tue, 12 Nov 2024 15:45:44 -0800 Subject: [PATCH 52/70] Make property extraction from includeworkflow recursive --- template/api/plugins/Patch-IncludeWorkflow.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index a4881d6..cff4548 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -378,10 +378,15 @@ def extract_information_from_include_workflow(entry, src_folder, property_name = if prop.get('Description') == None: continue description = prop.get('Description') + + # hmm can this whole function be simplified? and just have this stop recursion loop + if description == False: + _, temp_property= extract_information_from_bonsai(entry, src_folder, stop_recursion = True) + description = temp_property.get(property_name, False) return description -def extract_information_from_bonsai(entry, src_folder, property_name = None, display_name = False): +def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): tree = ET.parse(entry) root = tree.getroot() @@ -455,7 +460,7 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis property_name = prop.get('Name') property_mapping_list.append(property_name) - if entry == "../src\BonVision\Primitives\DrawImage.bonsai": + if entry == "../src\BonVision\Primitives\DrawText.bonsai": print(entry, xml_list) # print(include_workflow_list) # print(entry, property_mapping_list, properties_to_keep) @@ -481,9 +486,12 @@ def extract_information_from_bonsai(entry, src_folder, property_name = None, dis description = False # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead - for file in include_workflow_list: - description = extract_information_from_include_workflow(file, src_folder, potential_property['property_name'], potential_property['display_name']) - # print(entry, description, potential_property['property_name'],potential_property['display_name']) + if stop_recursion == False: + for file in include_workflow_list: + description = extract_information_from_include_workflow(file, src_folder, potential_property['property_name'], potential_property['display_name']) + # print(entry, description, potential_property['property_name'],potential_property['display_name']) + if description: + break # This section checks any subsequent PropertySources to see if the property description is defined there instead if description == False: From d05cd1e4cb7b1d9dcb6c09dd5c1708708a9d4231 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Tue, 12 Nov 2024 16:02:33 -0800 Subject: [PATCH 53/70] Made prop extraction from embedded IncludeWorkflows fully recursive --- template/api/plugins/Patch-IncludeWorkflow.py | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index cff4548..afb23e6 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -355,37 +355,6 @@ def extract_information_from_package(property_namespace, property_assembly, prop print("Bonsai.config wasn't found, have you installed .bonsai local environment .") return None - -def extract_information_from_include_workflow(entry, src_folder, property_name = None, display_name = False): - tree = ET.parse(entry) - root = tree.getroot() - - # Get XML namespaces and prefixes - xml_namespace = {} - for event, elem in ET.iterparse(entry, ["start-ns"]): - xml_namespace[elem[0]] = elem[1] - - # Make tags - default_ns = xml_namespace[''] - expression_tag = f"{{{default_ns}}}Expression" - - description = False - for expression in root.findall(f".//{expression_tag}", xml_namespace): - xsi_type = expression.get(f"{{{xml_namespace['xsi']}}}type") - if xsi_type == "ExternalizedMapping": - for prop in expression.findall(f"{{{default_ns}}}Property"): - if {property_name, display_name} & {prop.get('Name'),prop.get('DisplayName')}: - if prop.get('Description') == None: - continue - description = prop.get('Description') - - # hmm can this whole function be simplified? and just have this stop recursion loop - if description == False: - _, temp_property= extract_information_from_bonsai(entry, src_folder, stop_recursion = True) - description = temp_property.get(property_name, False) - return description - - def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): tree = ET.parse(entry) root = tree.getroot() @@ -488,7 +457,8 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): # This section checks any embedded IncludeWorkflows to see if the property description is defined there instead if stop_recursion == False: for file in include_workflow_list: - description = extract_information_from_include_workflow(file, src_folder, potential_property['property_name'], potential_property['display_name']) + _, temp_property= extract_information_from_bonsai(file, src_folder, stop_recursion = True) + description = temp_property.get(potential_property['property_name'], False) # print(entry, description, potential_property['property_name'],potential_property['display_name']) if description: break From 94fa17a0847016f0b132bfa9b4ce8c98373e1c67 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Tue, 12 Nov 2024 18:55:02 -0800 Subject: [PATCH 54/70] improve package property extraction logic --- template/api/plugins/Patch-IncludeWorkflow.py | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index afb23e6..8805ce9 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -307,8 +307,8 @@ def patch_manifest(manifest_path, new_entries): with open(manifest_path, 'w') as f: json.dump(manifest_data, f, indent=2, sort_keys=True) -def extract_information_from_cs(property_namespace, property_assembly, src_folder, property_name): - filename = os.path.join(src_folder, property_namespace, f"{property_assembly}.cs") +def extract_information_from_cs(property_assembly, property_operator, src_folder, property_name): + filename = os.path.join(src_folder, property_assembly, f"{property_operator}.cs") with open(filename, "r", encoding="utf-8") as file: for line in file: line = line.strip() @@ -325,7 +325,7 @@ def extract_information_from_cs(property_namespace, property_assembly, src_folde return description -def extract_information_from_package(property_namespace, property_assembly, property_name): +def extract_information_from_package(property_assembly, property_operator, property_name): filename = os.path.join("../.bonsai", "Bonsai.config") description = False try: @@ -335,7 +335,7 @@ def extract_information_from_package(property_namespace, property_assembly, prop # Find the AssemblyLocation element with the specified assemblyName for assembly_location in root.findall(".//AssemblyLocation"): - if assembly_location.get("assemblyName") == property_namespace: + if assembly_location.get("assemblyName") == property_assembly: # Return the location attribute if the assembly is found property_assembly_description_file = os.path.join("../.bonsai", assembly_location.get("location")[:-4] + ".xml") try: @@ -344,11 +344,11 @@ def extract_information_from_package(property_namespace, property_assembly, prop root = tree.getroot() for member in root.findall(".//member"): - if member.get("name") == "P:"+property_namespace+"."+property_assembly+"."+property_name: + if member.get("name") == "P:"+property_assembly+"."+property_operator+"."+property_name: description = member.find("summary").text.strip() - # print(property_namespace, property_assembly, property_name, description) + except: - print(f"{{{property_name}}} in {{{property_assembly}}} in {{{property_assembly_description_file}}} not found. package not installed in .bonsai or missing doc XML") + print(f"{{{property_name}}} in {{{property_operator}}} in {{{property_assembly_description_file}}} not found. package not installed in .bonsai or missing doc XML") return None return description except: @@ -406,12 +406,21 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") include_workflow_list.append(file_path) - if xsi_type in ("Combinator", "Source", "Transform", "Sink"): - operator_elem = expression.find("*", xml_namespace) - property_source = operator_elem.get(f"{{{xml_namespace['xsi']}}}type") + # so far though I have only seen combinators and none of the rest + if xsi_type in ("Combinator", "Source", "Transform", "Sink") or ':' in xsi_type: + if xsi_type in ("Combinator", "Source", "Transform", "Sink"): + operator_elem = expression.find("*", xml_namespace) + property_source = operator_elem.get(f"{{{xml_namespace['xsi']}}}type") + else: + operator_elem = expression + property_source = xsi_type + if ':' in property_source: - property_namespace = xml_namespace[property_source.split(':')[0]].split('=')[1] - property_assembly = property_source.split(':')[1] + property_namespace = xml_namespace[property_source.split(':')[0]].split(':')[1].split(';')[0] + property_assembly = xml_namespace[property_source.split(':')[0]].split('=')[1] + property_operator = property_source.split(':')[1] + if entry == "../src\BonVision\Environment\MeshMapping.bonsai": + print(property_namespace, property_assembly, property_operator) property_list = [] for child in operator_elem: property_name = child.tag.split("}")[-1] @@ -420,17 +429,18 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): xml_list.append({ "type": "PropertySource", "property_namespace": property_namespace, - "property_assembly":property_assembly, + "property_assembly": property_assembly, + 'property_operator': property_operator, 'property_list': property_list }) - + if xsi_type == "PropertyMapping": for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): property_name = prop.get('Name') property_mapping_list.append(property_name) - if entry == "../src\BonVision\Primitives\DrawText.bonsai": - print(entry, xml_list) + # if entry == "../src\BonVision\Environment\MeshMapping.bonsai": + # print(entry, xml_list) # print(include_workflow_list) # print(entry, property_mapping_list, properties_to_keep) # clean up xml list for propert map properties @@ -470,14 +480,14 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): if potential_property['property_name'] in potential_source['property_list']: # uses a CS file extractor if the propertysource is within the library, but the check is not that robust - if potential_source['property_namespace'] in entry: - description = extract_information_from_cs(potential_source['property_namespace'], potential_source['property_assembly'], src_folder, potential_property['property_name']) + if potential_source['property_assembly'] in entry: + description = extract_information_from_cs(potential_source['property_assembly'], potential_source['property_operator'], src_folder, potential_property['property_name']) if description: break # uses a package file extractor else: - description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_property['property_name']) + description = extract_information_from_package(potential_source['property_assembly'], potential_source['property_operator'], potential_property['property_name']) # print(entry, potential_property['property_name'],potential_source, description) if description: break From 86990be011eb17d7ce18016a8ec927b7e82b4c3e Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 13 Nov 2024 12:41:05 -0800 Subject: [PATCH 55/70] Fix package property extraction --- template/api/plugins/Patch-IncludeWorkflow.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 8805ce9..8113e14 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -325,7 +325,7 @@ def extract_information_from_cs(property_assembly, property_operator, src_folder return description -def extract_information_from_package(property_assembly, property_operator, property_name): +def extract_information_from_package(property_namespace, property_assembly, property_operator, property_name): filename = os.path.join("../.bonsai", "Bonsai.config") description = False try: @@ -344,9 +344,12 @@ def extract_information_from_package(property_assembly, property_operator, prope root = tree.getroot() for member in root.findall(".//member"): - if member.get("name") == "P:"+property_assembly+"."+property_operator+"."+property_name: - description = member.find("summary").text.strip() - + if property_namespace == property_assembly: + if member.get("name") == "P:"+property_assembly+"."+property_operator+"."+property_name: + description = member.find("summary").text.strip() + else: + if member.get("name") == "P:"+property_namespace+"."+property_operator+"."+property_name: + description = member.find("summary").text.strip() except: print(f"{{{property_name}}} in {{{property_operator}}} in {{{property_assembly_description_file}}} not found. package not installed in .bonsai or missing doc XML") return None @@ -487,7 +490,7 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): # uses a package file extractor else: - description = extract_information_from_package(potential_source['property_assembly'], potential_source['property_operator'], potential_property['property_name']) + description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], potential_property['property_name']) # print(entry, potential_property['property_name'],potential_source, description) if description: break From 6b3e2ec489e037159fc8587456b77c38fb62719a Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 13 Nov 2024 17:31:49 -0800 Subject: [PATCH 56/70] Add property extraction logic for subject operators --- template/api/plugins/Patch-IncludeWorkflow.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 8113e14..83facb3 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -409,7 +409,9 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") include_workflow_list.append(file_path) - # so far though I have only seen combinators and none of the rest + # finds embededded operators to pull parameter descriptions from + # so far though I have only seen combinators and none of the rest + # ':' in xsi_type catches some older operators that aren't enclosed by a Bonsai xsi:type (like io.csvreader) if xsi_type in ("Combinator", "Source", "Transform", "Sink") or ':' in xsi_type: if xsi_type in ("Combinator", "Source", "Transform", "Sink"): operator_elem = expression.find("*", xml_namespace) @@ -437,11 +439,25 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): 'property_list': property_list }) + + # Subject operators have a different kind of logic + if xsi_type in ['MulticastSubject', 'SubscribeSubject']: + xml_list.append({ + "type": "PropertySource", + "property_namespace": 'Bonsai.Expressions', + "property_assembly": 'Bonsai.Core', + 'property_operator': xsi_type, + 'property_list': ['Name'] + }) + + # finds properties that have been mapped and are thus hidden or represented by some other property name if xsi_type == "PropertyMapping": for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): property_name = prop.get('Name') property_mapping_list.append(property_name) + + # if entry == "../src\BonVision\Environment\MeshMapping.bonsai": # print(entry, xml_list) # print(include_workflow_list) From 642e641f80c0d55a25ea2de8d370ea7c2ecd7359 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 13 Nov 2024 17:44:53 -0800 Subject: [PATCH 57/70] Add edge case logic for Format operator --- template/api/plugins/Patch-IncludeWorkflow.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 83facb3..d762458 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -440,7 +440,7 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): }) - # Subject operators have a different kind of logic + # Subject operators have a different kind of logic, they dont use namespace declarations in the IncludeWorkflow XML file if xsi_type in ['MulticastSubject', 'SubscribeSubject']: xml_list.append({ "type": "PropertySource", @@ -449,6 +449,16 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): 'property_operator': xsi_type, 'property_list': ['Name'] }) + + # The format operator is another special case, it doesnt even list the properties. + if xsi_type in ['Format']: + xml_list.append({ + "type": "PropertySource", + "property_namespace": 'Bonsai.Expressions', + "property_assembly": 'Bonsai.Core', + 'property_operator': 'FormatBuilder', + 'property_list': ['Format', 'Selector'] + }) # finds properties that have been mapped and are thus hidden or represented by some other property name if xsi_type == "PropertyMapping": @@ -456,8 +466,6 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): property_name = prop.get('Name') property_mapping_list.append(property_name) - - # if entry == "../src\BonVision\Environment\MeshMapping.bonsai": # print(entry, xml_list) # print(include_workflow_list) From 12b955f75fb4f01994bc5d2a65f7a50e80fa2e02 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 13 Nov 2024 20:25:22 -0800 Subject: [PATCH 58/70] Refine handling of edge cases --- template/api/plugins/Patch-IncludeWorkflow.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index d762458..9d770d5 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -425,11 +425,14 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): property_assembly = xml_namespace[property_source.split(':')[0]].split('=')[1] property_operator = property_source.split(':')[1] if entry == "../src\BonVision\Environment\MeshMapping.bonsai": - print(property_namespace, property_assembly, property_operator) + print(property_source, property_namespace, property_assembly, property_operator) property_list = [] for child in operator_elem: property_name = child.tag.split("}")[-1] property_list.append(property_name) + # Edge case: This operator does not have property child element + if property_source == 'gl:WarpPerspective': + property_list.append('Destination') if property_list: xml_list.append({ "type": "PropertySource", @@ -440,7 +443,7 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): }) - # Subject operators have a different kind of logic, they dont use namespace declarations in the IncludeWorkflow XML file + # Edge case: Subject operators do not have XML namespace declaration if xsi_type in ['MulticastSubject', 'SubscribeSubject']: xml_list.append({ "type": "PropertySource", @@ -450,7 +453,7 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): 'property_list': ['Name'] }) - # The format operator is another special case, it doesnt even list the properties. + # Edge case: Format operator does not have XML namespace declaration and property child elements if xsi_type in ['Format']: xml_list.append({ "type": "PropertySource", From cfdd3063db6bf4a89465a38e50fca92664a9af4a Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 13 Nov 2024 22:13:26 -0800 Subject: [PATCH 59/70] Add edge case for PropertySource --- template/api/plugins/Patch-IncludeWorkflow.py | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 9d770d5..d49b8f2 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -415,38 +415,52 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): if xsi_type in ("Combinator", "Source", "Transform", "Sink") or ':' in xsi_type: if xsi_type in ("Combinator", "Source", "Transform", "Sink"): operator_elem = expression.find("*", xml_namespace) - property_source = operator_elem.get(f"{{{xml_namespace['xsi']}}}type") + property_reference = operator_elem.get(f"{{{xml_namespace['xsi']}}}type") else: operator_elem = expression - property_source = xsi_type - - if ':' in property_source: - property_namespace = xml_namespace[property_source.split(':')[0]].split(':')[1].split(';')[0] - property_assembly = xml_namespace[property_source.split(':')[0]].split('=')[1] - property_operator = property_source.split(':')[1] - if entry == "../src\BonVision\Environment\MeshMapping.bonsai": - print(property_source, property_namespace, property_assembly, property_operator) + property_reference = xsi_type + + if ':' in property_reference: + property_namespace = xml_namespace[property_reference.split(':')[0]].split(':')[1].split(';')[0] + property_assembly = xml_namespace[property_reference.split(':')[0]].split('=')[1] + property_operator = property_reference.split(':')[1] + # if entry == "../src\BonVision\Environment\MeshMapping.bonsai": + # print(property_reference, property_namespace, property_assembly, property_operator) property_list = [] for child in operator_elem: property_name = child.tag.split("}")[-1] property_list.append(property_name) # Edge case: This operator does not have property child element - if property_source == 'gl:WarpPerspective': + if property_reference == 'gl:WarpPerspective': property_list.append('Destination') if property_list: xml_list.append({ - "type": "PropertySource", + "type": "PropertyReference", "property_namespace": property_namespace, "property_assembly": property_assembly, 'property_operator': property_operator, 'property_list': property_list }) - + + # Edge case: gl.LoadImage (there might be other PropertySources that might need to be added) + # This one is especially problematic because the property_name is 'Value' and display_name is 'GammaLut' + # but it needs to match to 'FileName' in gl:LoadImage. + if xsi_type == "PropertySource": + if expression.get("TypeArguments") == "gl:LoadImage,sys:String": + xml_list.append({ + "type": "PropertyReference", + "property_namespace": 'Bonsai.Shaders', + "property_assembly": 'Bonsai.Shaders', + 'property_operator': 'LoadImage', + 'property_list': ['Value'], + 'edge_case':True, + 'edge_case_property_name':'FileName' + }) # Edge case: Subject operators do not have XML namespace declaration if xsi_type in ['MulticastSubject', 'SubscribeSubject']: xml_list.append({ - "type": "PropertySource", + "type": "PropertyReference", "property_namespace": 'Bonsai.Expressions', "property_assembly": 'Bonsai.Core', 'property_operator': xsi_type, @@ -456,12 +470,13 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): # Edge case: Format operator does not have XML namespace declaration and property child elements if xsi_type in ['Format']: xml_list.append({ - "type": "PropertySource", + "type": "PropertyReference", "property_namespace": 'Bonsai.Expressions', "property_assembly": 'Bonsai.Core', 'property_operator': 'FormatBuilder', 'property_list': ['Format', 'Selector'] }) + # finds properties that have been mapped and are thus hidden or represented by some other property name if xsi_type == "PropertyMapping": @@ -503,13 +518,13 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): if description: break - # This section checks any subsequent PropertySources to see if the property description is defined there instead + # This section checks any subsequent PropertyReferences to see if the property description is defined there instead if description == False: for potential_source in xml_list[index+1:]: - if potential_source['type'] == "PropertySource": + if potential_source['type'] == "PropertyReference": if potential_property['property_name'] in potential_source['property_list']: - # uses a CS file extractor if the propertysource is within the library, but the check is not that robust + # uses a CS file extractor if the PropertyReference is within the library, but the check is not that robust if potential_source['property_assembly'] in entry: description = extract_information_from_cs(potential_source['property_assembly'], potential_source['property_operator'], src_folder, potential_property['property_name']) if description: @@ -517,7 +532,10 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): # uses a package file extractor else: - description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], potential_property['property_name']) + if potential_source.get('edge_case'): + description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], potential_source['edge_case_property_name']) + else: + description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], potential_property['property_name']) # print(entry, potential_property['property_name'],potential_source, description) if description: break From 03841cd3f1bc8759d02170a2455318053c84db68 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Wed, 13 Nov 2024 23:34:27 -0800 Subject: [PATCH 60/70] Fix PropertySource extraction logic --- template/api/plugins/Patch-IncludeWorkflow.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index d49b8f2..169630c 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -442,20 +442,31 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): 'property_list': property_list }) - # Edge case: gl.LoadImage (there might be other PropertySources that might need to be added) - # This one is especially problematic because the property_name is 'Value' and display_name is 'GammaLut' - # but it needs to match to 'FileName' in gl:LoadImage. + # Edge case: PropertySources have a different XML expression format, need to swap Property Values and even Operator Names if xsi_type == "PropertySource": - if expression.get("TypeArguments") == "gl:LoadImage,sys:String": - xml_list.append({ + if expression.get("TypeArguments").split(',')[0] == "gl:LoadImage": + edge_case_property_name = expression.find("MemberName",xml_namespace).text + xml_list.append({ "type": "PropertyReference", "property_namespace": 'Bonsai.Shaders', "property_assembly": 'Bonsai.Shaders', 'property_operator': 'LoadImage', 'property_list': ['Value'], 'edge_case':True, - 'edge_case_property_name':'FileName' - }) + 'edge_case_property_name': edge_case_property_name + }) + + elif expression.get("TypeArguments").split(',')[0] == "drw:AddTextBox": + edge_case_property_name = expression.find("MemberName",xml_namespace).text + xml_list.append({ + "type": "PropertyReference", + "property_namespace": 'Bonsai.Vision.Drawing', + "property_assembly": 'Bonsai.Vision', + 'property_operator': 'AddTextBase', + 'property_list': ['Value'], + 'edge_case':True, + 'edge_case_property_name': edge_case_property_name + }) # Edge case: Subject operators do not have XML namespace declaration if xsi_type in ['MulticastSubject', 'SubscribeSubject']: From 5e91e1673906fb2368b1f6e98248ec4c8389ad65 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 14 Nov 2024 00:11:00 -0800 Subject: [PATCH 61/70] Property extraction feature complete: validated with Bonvision --- template/api/plugins/Patch-IncludeWorkflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 169630c..0ed905e 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -169,7 +169,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'assemblies': [entry['namespace'].split('.')[0]], 'namespace': entry['namespace'], 'summary': property_description, - # this should probably be tailored for each property + # Placeholder - needs to be tailored for each property or have it empty 'syntax':{ 'content': 'public float ' + property_name, 'parameters': [], From c1f02ca97eb7ac432b429dee9fea2dbdef471e28 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 14 Nov 2024 15:14:35 -0800 Subject: [PATCH 62/70] Minor updates to support Bonsai.ML operators --- template/api/plugins/Patch-IncludeWorkflow.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index d49b8f2..19fd6d2 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -307,8 +307,16 @@ def patch_manifest(manifest_path, new_entries): with open(manifest_path, 'w') as f: json.dump(manifest_data, f, indent=2, sort_keys=True) -def extract_information_from_cs(property_assembly, property_operator, src_folder, property_name): +def extract_information_from_cs(property_namespace, property_assembly, property_operator, src_folder, property_name): filename = os.path.join(src_folder, property_assembly, f"{property_operator}.cs") + if os.path.exists(filename): + pass + # Edge case - Bonsai.ML operators seem to have a difference namespace/assembly configuration + else: + namespace_parts = set(property_namespace.split(".")) + assembly_parts = set(property_assembly.split(".")) + difference = list(namespace_parts - assembly_parts)[0] + filename = os.path.join(src_folder, property_assembly, difference, f"{property_operator}.cs") with open(filename, "r", encoding="utf-8") as file: for line in file: line = line.strip() @@ -372,8 +380,12 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): description_tag = f"{{{default_ns}}}Description" expression_tag = f"{{{default_ns}}}Expression" - # Find description - operator_description = root.find(description_tag).text + # Find description (if no description found should the operator be skipped?) + # some of the bonsai files in machine_learning repo dont have description and not sure if they are supposed to be hidden or not + if root.find(description_tag) is not None: + operator_description = root.find(description_tag).text + else: + operator_description = "No Description Found" # Dictionary to store externalized properties and their descriptions xml_list = [] @@ -526,7 +538,8 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): # uses a CS file extractor if the PropertyReference is within the library, but the check is not that robust if potential_source['property_assembly'] in entry: - description = extract_information_from_cs(potential_source['property_assembly'], potential_source['property_operator'], src_folder, potential_property['property_name']) + print(entry) + description = extract_information_from_cs(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], src_folder, potential_property['property_name']) if description: break From 7a0f41c7bc5ae245affd25d9bf14b137e715d429 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 14 Nov 2024 21:34:36 -0800 Subject: [PATCH 63/70] Add more Bonsai.ML edge cases tor property extraction --- template/api/plugins/Patch-IncludeWorkflow.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 19fd6d2..2a5951f 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -442,9 +442,16 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): for child in operator_elem: property_name = child.tag.split("}")[-1] property_list.append(property_name) - # Edge case: This operator does not have property child element + + # Edge cases: These operators have hidden properties that are not exposed in the .bonsai XML if property_reference == 'gl:WarpPerspective': property_list.append('Destination') + if property_reference == 'p2:ModelParameters': + property_list.append('StateParameters') + if property_reference == 'p1:KFModelParameters': + property_list.append('P') + property_list.append('X') + if property_list: xml_list.append({ "type": "PropertyReference", From bdeaea58a871b2c4934cfe3b5a9cb10c47683949 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 14 Nov 2024 21:58:50 -0800 Subject: [PATCH 64/70] Improve property matching for cs files with regex --- template/api/plugins/Patch-IncludeWorkflow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 2a5951f..5bd699e 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -328,7 +328,7 @@ def extract_information_from_cs(property_namespace, property_assembly, property_ description = re.search(r'\[Description\("([^"]*)"\)\]', line).group(1) # breaks the loop if it finds the property declaration and returns the latest description - if "public" in line and property_name in line: + if "public" in line and re.search(rf"\b{property_name}\b", line): break return description @@ -545,7 +545,7 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): # uses a CS file extractor if the PropertyReference is within the library, but the check is not that robust if potential_source['property_assembly'] in entry: - print(entry) + # print(entry) description = extract_information_from_cs(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], src_folder, potential_property['property_name']) if description: break From a068897acec4ec14d8bbb39d6f90fde03b90d4d2 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Thu, 14 Nov 2024 22:24:20 -0800 Subject: [PATCH 65/70] Rename type placeholder: float for clarity --- template/api/plugins/Patch-IncludeWorkflow.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 6b37d23..b700ae2 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -169,12 +169,12 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'assemblies': [entry['namespace'].split('.')[0]], 'namespace': entry['namespace'], 'summary': property_description, - # Placeholder - needs to be tailored for each property or have it empty + # Type Placeholder - needs to be tailored for each property or have it empty 'syntax':{ - 'content': 'public float ' + property_name, + 'content': 'public placeholder ' + property_name, 'parameters': [], - 'return': {'type': 'System.Single'}, - 'content.vb': "Public Property " + property_name + " As Single" + 'return': {'type': 'Placeholder'}, + 'content.vb': "Public Property " + property_name + " As Placeholder" }, 'overload': entry['uid']+'.'+ property_name +'*' }) @@ -215,19 +215,19 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'href': entry['namespace']+".html" }] - # adds return value reference + # adds Type value reference (placeholder for now) new_bonsai_yml_file['references'].append({ - 'uid': 'System.Single', - 'commentId': 'T:System.Single', + 'uid': 'Placeholder', + 'commentId': 'T:Placeholder', 'parent': 'System', 'isExternal': 'true', - 'href': 'https://learn.microsoft.com/dotnet/api/system.single', - 'name': 'float', - 'nameWithType': 'float', - 'fullName': 'float', - 'nameWithType.vb': 'Single', - 'fullName.vb': 'Single', - 'name.vb': 'Single' + # 'href': 'https://learn.microsoft.com/dotnet/api/system.single', + 'name': 'Placeholder', + 'nameWithType': 'Placeholder', + 'fullName': 'Placeholder', + 'nameWithType.vb': 'Placeholder', + 'fullName.vb': 'Placeholder', + 'name.vb': 'Placeholder' }) # adds properties overload references From 31ddc35cbcf730d13c92fe41669398494d07b4e3 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 15 Nov 2024 12:46:17 -0800 Subject: [PATCH 66/70] Add Workflow operator class to API template and IncludeWorkflow patch --- template/api/ManagedReference.extension.js | 3 ++- template/api/plugins/Patch-IncludeWorkflow.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js index 6d1f292..b41288c 100644 --- a/template/api/ManagedReference.extension.js +++ b/template/api/ManagedReference.extension.js @@ -11,9 +11,10 @@ function defineOperatorType(model){ 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'); + workflow = checkForCategory('Workflow') let operatorType = {} - operatorType.type = sink ? 'sink' : source ? 'source' : transform ? 'transform' : combinator ? 'combinator' : false ; + operatorType.type = sink ? 'sink' : source ? 'source' : transform ? 'transform' : combinator ? 'combinator' : workflow ? 'workflow' : false ; return operatorType; } diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index b700ae2..7d4a59b 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -135,10 +135,10 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'namespace': entry['namespace'], 'summary': entry['operator_description'], 'syntax':{ - 'content': "public class " + entry['name'], + 'content': "[WorkflowElementCategory(ElementCategory.Workflow)]"+"/n"+"public class " + entry['name'], 'content.vb': "Public Class " + entry['name'] }, - # # this isn't applicable for bonsai files but added it in to avoid mref.extension.js errors + # # this isn't applicable for bonsai files # # TODO: maybe make that section of the code more robust to missing fields # 'inheritance': ['System.Object'], # 'inheritedMembers': ['System.Object.GetType'], From e602e25deea4ca9151ba71aa7667747f47a5ebd6 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 15 Nov 2024 16:29:34 -0800 Subject: [PATCH 67/70] Fix assembly name --- template/api/partials/class.tmpl.partial | 2 -- template/api/plugins/Patch-IncludeWorkflow.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial index 006577b..85858af 100644 --- a/template/api/partials/class.tmpl.partial +++ b/template/api/partials/class.tmpl.partial @@ -67,7 +67,6 @@ {{/inheritance.0}} - {{#implements.0}}
{{__global.implements}}
@@ -94,5 +93,4 @@
{{/derivedClasses.0}} - {{/isClass}} \ No newline at end of file diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 7d4a59b..db0bdc4 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -131,7 +131,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): # this isn't accurate but is hardcoded here because I don't think it affects anything 'startLine': 9 }, - 'assemblies': [entry['namespace'].split('.')[0]], + 'assemblies': [entry['name']], 'namespace': entry['namespace'], 'summary': entry['operator_description'], 'syntax':{ From c99466c10c417e7420cc0052bf3c851f364127be Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 15 Nov 2024 19:43:46 -0800 Subject: [PATCH 68/70] Add IncludeWorkflow patch instructions to README --- README.md | 52 +++++++++++++++++-- template/api/plugins/Patch-IncludeWorkflow.py | 2 +- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d0ecacf..073bb32 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # docfx-tools 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. +- Workflow Container Template patching the modern template to provide stylesheets and scripts for rendering custom workflow containers with copy functionality. +- API Template that revamps the API page to enhance user-friendliness. +- IncludeWorkflow Operator Patch that adds support for Bonsai `IncludeWorkflow` Operators - Powershell Scripts that automate several content generation steps for package documentation websites. ## How to include @@ -44,7 +44,7 @@ export default { } } ``` -## Using API template +## 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). @@ -75,6 +75,50 @@ In addition, the images and custom css styles need to be added to the resources ``` 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`. +## Using IncludeWorkflow Operator Patch + +This patch adds support for [IncludeWorkflow](https://bonsai-rx.org/docs/api/Bonsai.Expressions.IncludeWorkflowBuilder.html) operators if they are included in your package. This patch requires a [Python](https://www.python.org/) installation and the [PyYAML](https://pypi.org/project/PyYAML/) package. + +1) Assuming you already have `Python` installed, install pyyaml: + +```cmd +pip install pyyaml +``` + +2) Instead of running `dotnet docfx` which executes the standard `docfx` pipeline, run these commands instead to inject the patch: + +```cmd +cd docs +dotnet docfx metadata +python bonsai/template/api/plugins/Patch-IncludeWorkflow.py +dotnet docfx build +dotnet docfx serve _site +``` + +- The `metadata` command generates [.yaml](https://dotnet.github.io/docfx/docs/dotnet-yaml-format.html) files for standard operators (C# .cs files) as well as the table of contents (TOC). +- The `Patch-IncludeWorkflow.py` file generates `.yaml` files for `IncludeWorkflow` .bonsai operators and modifies the TOC. +- The `build` command generates `.html` files from `.yaml` and places them in a `_site` folder in `docs`. +- The `serve` command serves a local preview of the website from `_site`. + +3) The `dotnet docfx` command in the `docs/build.ps1` script that is used to build the `docfx` website on GitHub needs to be modified with: + +```ps1 +dotnet docfx metadata +python ./bonsai/template/api/plugins/Patch-IncludeWorkflow.py +dotnet docfx build +``` + +4) Lastly, the GitHub Actions recipe `.github/workflows/docs.yml` needs to have these lines added before the execution of the `build.ps1` script. + +```yaml + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Setup Pyyaml + run: pip install pyyaml==6.0.2 +``` ## Powershell Scripts - Exporting workflow images diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index db0bdc4..06954bc 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -3,7 +3,7 @@ # isnt enough as the API template relies on certain shared models that don't build correctly # TODO: add yaml flag and check to prevent modification of already modified files # TODO: make it more efficient (too many loops of new entries in each separate function) -# requirements: pyyaml (install with pip install pyyaml) +# requirements: pyyaml (install with pip install pyyaml to test locally) import os import yaml From d79696c220de8d18b178fdf8703bb9ed0fd8f1b5 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 15 Nov 2024 20:38:49 -0800 Subject: [PATCH 69/70] Remove debugging print statements and clean up code --- template/api/plugins/Patch-IncludeWorkflow.py | 109 +++++++----------- 1 file changed, 44 insertions(+), 65 deletions(-) diff --git a/template/api/plugins/Patch-IncludeWorkflow.py b/template/api/plugins/Patch-IncludeWorkflow.py index 06954bc..75b51f6 100644 --- a/template/api/plugins/Patch-IncludeWorkflow.py +++ b/template/api/plugins/Patch-IncludeWorkflow.py @@ -1,9 +1,11 @@ -# README: this script modifies several accessory files that seem to be needed for getting IncludeWorkflows -# recognised in dotnet build. Gnerating individual api yml files for the .bonsai IncludeWorkflows -# isnt enough as the API template relies on certain shared models that don't build correctly +# This script generates .yml files for IncludeWorkflow .bonsai operators, +# Adds them to namespace.yml and toc.yml files, +# And modifies the .manifest file in api/ (not sure if this step is really necessary). +# Requirements: pyyaml as yaml support isn't part of standard python library +# Install pyyaml with `pip install pyyaml` # TODO: add yaml flag and check to prevent modification of already modified files -# TODO: make it more efficient (too many loops of new entries in each separate function) -# requirements: pyyaml (install with pip install pyyaml to test locally) +# TODO: add type and input/output +# TODO: refactor and clean up code import os import yaml @@ -17,21 +19,14 @@ def find_bonsai_files(src_folder): for root, _, files in os.walk(src_folder): for file in files: if file.endswith(".bonsai"): - # Store the full path to the bonsai file bonsai_files.append(os.path.join(root, file)) return bonsai_files def extract_namespace(file_path, src_folder): """Extract namespace by converting the path to a dotted string.""" - # Get the relative path from src folder relative_path = os.path.relpath(file_path, src_folder) - - # Remove the filename from the path (only keep directories) namespace_path = os.path.dirname(relative_path) - - # Replace separators with dots namespace = namespace_path.replace(os.sep, '.') - return namespace def load_existing_toc(toc_path): @@ -44,7 +39,6 @@ def load_existing_toc(toc_path): "api/toc.yml not found. Execute this script from the docs directory or run `docfx metadata` first to generate toc.yml." ) - def patch_toc(toc_data, new_entries): """Patch the TOC with new entries.""" # Create a map of existing namespaces @@ -55,11 +49,12 @@ def patch_toc(toc_data, new_entries): namespace = entry['namespace'] item_data = {'uid': entry['uid'], 'name': entry['name']} + # Add new item to the existing namespace if namespace in namespace_map: - # Add new item to the existing namespace namespace_map[namespace]['items'].append(item_data) + + # Create a new namespace entry with proper key order if namespace doesn't exist else: - # Create a new namespace entry with proper key order new_namespace_entry = { 'uid': namespace, 'name': namespace, @@ -67,12 +62,13 @@ def patch_toc(toc_data, new_entries): } toc_data['items'].append(new_namespace_entry) namespace_map[namespace] = new_namespace_entry + return toc_data def generate_entries(bonsai_files, src_folder): - """Generate entries from the list of bonsai files.""" + """Generate entries from .bonsai file list to feed into create_bonsai_yml""" new_entries = [] - + for file in bonsai_files: name = os.path.splitext(os.path.basename(file))[0] namespace = extract_namespace(file, src_folder) @@ -91,6 +87,7 @@ def generate_entries(bonsai_files, src_folder): return new_entries def get_git_information(): + """Get git information to populate one of the yml fields""" branch_name = "" repo_url = "" with open("../.git/HEAD", "r") as f: @@ -106,6 +103,7 @@ def get_git_information(): def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): + """Generate .yml file from the .bonsai entries""" for entry in bonsai_entries: bonsai_yml_file = os.path.join(api_folder, entry['uid']+".yml") new_bonsai_yml_file = {} @@ -128,8 +126,6 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): }, 'id': entry['name'], 'path': entry['file'], - # this isn't accurate but is hardcoded here because I don't think it affects anything - 'startLine': 9 }, 'assemblies': [entry['name']], 'namespace': entry['namespace'], @@ -138,12 +134,9 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'content': "[WorkflowElementCategory(ElementCategory.Workflow)]"+"/n"+"public class " + entry['name'], 'content.vb': "Public Class " + entry['name'] }, - # # this isn't applicable for bonsai files - # # TODO: maybe make that section of the code more robust to missing fields - # 'inheritance': ['System.Object'], - # 'inheritedMembers': ['System.Object.GetType'], }] - # adds properties + + # Adds properties for property_name, property_description in entry['properties'].items(): new_bonsai_yml_file['items'].append({ 'uid':entry['uid']+"." + property_name, @@ -163,13 +156,11 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): }, 'id': property_name, 'path': entry['file'], - # this isn't accurate but is hardcoded here because I don't think it affects anything - 'startLine': 9 }, 'assemblies': [entry['namespace'].split('.')[0]], 'namespace': entry['namespace'], 'summary': property_description, - # Type Placeholder - needs to be tailored for each property or have it empty + # Type Placeholder - needs to be tailored for each property or have it be removed in the template for IncludeWorkflow operators 'syntax':{ 'content': 'public placeholder ' + property_name, 'parameters': [], @@ -179,8 +170,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'overload': entry['uid']+'.'+ property_name +'*' }) - # adds references - # adds parent reference + # Adds namespace references new_bonsai_yml_file['references']=[{ 'uid': entry['namespace'], 'commentId': "N:"+entry['namespace'], @@ -189,7 +179,8 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'nameWithType': entry['namespace'], 'fullName': entry['namespace'], }] - # this section modifies the parent reference to include additional information if the parent isn't the root namespace + + # This section modifies the parent reference to include additional information if the parent isn't the root namespace (eg. Bonvision.Collections) # Works for 2 namespaces (like Bonvision.Collections), will there be instances where theres more than 2? if entry['namespace'].split('.')[0] != entry['namespace']: new_bonsai_yml_file['references'][0]['spec.csharp'] = [{ @@ -215,7 +206,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'href': entry['namespace']+".html" }] - # adds Type value reference (placeholder for now) + # Adds Type value reference (placeholder for now) new_bonsai_yml_file['references'].append({ 'uid': 'Placeholder', 'commentId': 'T:Placeholder', @@ -230,7 +221,7 @@ def create_bonsai_yml(bonsai_entries, api_folder, branch_name, repo_url): 'name.vb': 'Placeholder' }) - # adds properties overload references + # Adds properties overload references for property_name, description in entry['properties'].items(): new_bonsai_yml_file['references'].append({ 'uid':entry['uid']+'.'+ property_name +'*', @@ -252,10 +243,6 @@ def patch_namespace_files(new_entries, api_folder): pass else: # generate new namespace.yml file if it isnt present - # some items in namespace files dont appear as .cs files - # for instance bonvision collections has GratingTrial and GratingParameters - # even though there are no .cs files - # they are present as classes in CreateGratingTrial and GratingSpecifications specifically new_namespace_file = {} new_namespace_file["items"]=[{ 'uid': entry['namespace'], @@ -270,9 +257,11 @@ def patch_namespace_files(new_entries, api_folder): 'assemblies': [entry['namespace'].split('.')[0]] }] new_namespace_file["references"]=[] + with open(namespace_file, 'w') as f: f.write("### YamlMime:ManagedReference\n") yaml.dump(new_namespace_file, f, default_flow_style=False, sort_keys=False) + with open(namespace_file, 'r') as f: namespace_file_to_amend = yaml.safe_load(f) namespace_file_to_amend["items"][0]['children'].append(entry['uid']) @@ -284,6 +273,7 @@ def patch_namespace_files(new_entries, api_folder): 'nameWithType': entry['name'], 'fullName': entry['uid'] }) + with open(namespace_file, 'w') as f: f.write("### YamlMime:ManagedReference\n") yaml.dump(namespace_file_to_amend, f, default_flow_style=False, sort_keys=False) @@ -311,23 +301,24 @@ def extract_information_from_cs(property_namespace, property_assembly, property_ filename = os.path.join(src_folder, property_assembly, f"{property_operator}.cs") if os.path.exists(filename): pass + # Edge case - Bonsai.ML operators seem to have a difference namespace/assembly configuration else: namespace_parts = set(property_namespace.split(".")) assembly_parts = set(property_assembly.split(".")) difference = list(namespace_parts - assembly_parts)[0] filename = os.path.join(src_folder, property_assembly, difference, f"{property_operator}.cs") + with open(filename, "r", encoding="utf-8") as file: for line in file: line = line.strip() - # Check if the line is a Description attribute + # Check if the line is a Description attribute and extract the description text within quotes + # Do we want to pull from XML summary descriptions for newer operators instead? if line.startswith("[Description("): - # Extract the description text within quotes - # Might need to update it to pull XML summary descriptions for newer operators description = re.search(r'\[Description\("([^"]*)"\)\]', line).group(1) - # breaks the loop if it finds the property declaration and returns the latest description + # Breaks the loop if it finds the property declaration and returns the latest description if "public" in line and re.search(rf"\b{property_name}\b", line): break @@ -381,7 +372,6 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): expression_tag = f"{{{default_ns}}}Expression" # Find description (if no description found should the operator be skipped?) - # some of the bonsai files in machine_learning repo dont have description and not sure if they are supposed to be hidden or not if root.find(description_tag) is not None: operator_description = root.find(description_tag).text else: @@ -421,8 +411,8 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): file_path = os.path.join(src_folder, parts[0], subparts[0], f"{subparts[1]}.bonsai") include_workflow_list.append(file_path) - # finds embededded operators to pull parameter descriptions from - # so far though I have only seen combinators and none of the rest + # Finds embededded operators to pull parameter descriptions from + # So far though I have only seen combinators and none of the rest # ':' in xsi_type catches some older operators that aren't enclosed by a Bonsai xsi:type (like io.csvreader) if xsi_type in ("Combinator", "Source", "Transform", "Sink") or ':' in xsi_type: if xsi_type in ("Combinator", "Source", "Transform", "Sink"): @@ -436,8 +426,6 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): property_namespace = xml_namespace[property_reference.split(':')[0]].split(':')[1].split(';')[0] property_assembly = xml_namespace[property_reference.split(':')[0]].split('=')[1] property_operator = property_reference.split(':')[1] - # if entry == "../src\BonVision\Environment\MeshMapping.bonsai": - # print(property_reference, property_namespace, property_assembly, property_operator) property_list = [] for child in operator_elem: property_name = child.tag.split("}")[-1] @@ -508,22 +496,17 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): }) - # finds properties that have been mapped and are thus hidden or represented by some other property name + # Finds properties that have been mapped and are thus hidden or represented by some other property name if xsi_type == "PropertyMapping": for prop in expression.findall(f".//{{{default_ns}}}PropertyMappings/{{{default_ns}}}Property"): property_name = prop.get('Name') property_mapping_list.append(property_name) - # if entry == "../src\BonVision\Environment\MeshMapping.bonsai": - # print(entry, xml_list) - # print(include_workflow_list) - # print(entry, property_mapping_list, properties_to_keep) - # clean up xml list for propert map properties + + # Remove mapped properties from XML list for prop in property_mapping_list[:]: if prop in properties_to_keep: property_mapping_list.remove(prop) - - # print(entry, property_mapping_list) for prop in xml_list[:]: if prop.get("display_name") in property_mapping_list: @@ -544,7 +527,6 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): for file in include_workflow_list: _, temp_property= extract_information_from_bonsai(file, src_folder, stop_recursion = True) description = temp_property.get(potential_property['property_name'], False) - # print(entry, description, potential_property['property_name'],potential_property['display_name']) if description: break @@ -554,25 +536,22 @@ def extract_information_from_bonsai(entry, src_folder, stop_recursion = False): if potential_source['type'] == "PropertyReference": if potential_property['property_name'] in potential_source['property_list']: - # uses a CS file extractor if the PropertyReference is within the library, but the check is not that robust + # Uses a CS file extractor if the PropertyReference is within the library, but the check could be more robust if potential_source['property_assembly'] in entry: - # print(entry) description = extract_information_from_cs(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], src_folder, potential_property['property_name']) if description: break - # uses a package file extractor + # Uses a package file extractor else: if potential_source.get('edge_case'): description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], potential_source['edge_case_property_name']) else: description = extract_information_from_package(potential_source['property_namespace'], potential_source['property_assembly'], potential_source['property_operator'], potential_property['property_name']) - # print(entry, potential_property['property_name'],potential_source, description) if description: break - # xml_list[index]["description"] = description - # bunch of checks to make sure that it only overwrites previous declarations if they are empty and if it itself is not empty. + # Bunch of checks to make sure that it only overwrites previous declarations if they are empty and if it itself is not empty. if description: if potential_property["display_name"] == False: if not processed_properties.get(potential_property["property_name"]): @@ -598,13 +577,13 @@ def main(): bonsai_files = find_bonsai_files(src_folder) print(f"Found {len(bonsai_files)} .bonsai files.") - # Generate entries from bonsai files - new_entries = generate_entries(bonsai_files, src_folder) - - # Get git information to populate yml source field + # Get git information to populate .yml source field branch_name, repo_url = get_git_information() - # Create Bonsai Yml Files + # Generate entries from .bonsai file list to feed into create_bonsai_yml + new_entries = generate_entries(bonsai_files, src_folder) + + # Create .bonsai .yml files create_bonsai_yml(new_entries, api_folder, branch_name, repo_url) print(f"Successfully created .bonsai yml files in {api_folder}") From c1012685186f85764be9ef4c54ecd0e56be82a67 Mon Sep 17 00:00:00 2001 From: Shawn Tan Date: Fri, 15 Nov 2024 22:49:51 -0800 Subject: [PATCH 70/70] Split includeworkflow patch from API-template --- README.md | 33 +-- template/api/ManagedReference.extension.js | 192 ------------------ .../api/ManagedReference.html.primary.tmpl | 7 - template/api/ManagedReference.overwrite.js | 5 - template/api/images/combinator-operator.svg | 67 ------ template/api/images/right-arrow.svg | 87 -------- template/api/images/sink-operator.svg | 86 -------- template/api/images/source-operator.svg | 88 -------- template/api/images/transform-operator.svg | 58 ------ template/api/partials/class.tmpl.partial | 96 --------- template/api/partials/diagram.tmpl.partial | 83 -------- template/api/partials/enum.tmpl.partial | 45 ---- .../api/partials/propertyTables.tmpl.partial | 29 --- template/api/styles/styles.css | 35 ---- 14 files changed, 1 insertion(+), 910 deletions(-) delete mode 100644 template/api/ManagedReference.extension.js delete mode 100644 template/api/ManagedReference.html.primary.tmpl delete mode 100644 template/api/ManagedReference.overwrite.js delete mode 100644 template/api/images/combinator-operator.svg delete mode 100644 template/api/images/right-arrow.svg delete mode 100644 template/api/images/sink-operator.svg delete mode 100644 template/api/images/source-operator.svg delete mode 100644 template/api/images/transform-operator.svg delete mode 100644 template/api/partials/class.tmpl.partial delete mode 100644 template/api/partials/diagram.tmpl.partial delete mode 100644 template/api/partials/enum.tmpl.partial delete mode 100644 template/api/partials/propertyTables.tmpl.partial delete mode 100644 template/api/styles/styles.css diff --git a/README.md b/README.md index 073bb32..8fd2a4a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # docfx-tools A repository of docfx tools for Bonsai package documentation: -- Workflow Container Template patching the modern template to provide stylesheets and scripts for rendering custom workflow containers with copy functionality. -- API Template that revamps the API page to enhance user-friendliness. +- Workflow Container Template patching the modern template to provide stylesheets and scripts for rendering custom workflow containers with copy functionality. - IncludeWorkflow Operator Patch that adds support for Bonsai `IncludeWorkflow` Operators - Powershell Scripts that automate several content generation steps for package documentation websites. @@ -44,36 +43,6 @@ 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. - -```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`. ## Using IncludeWorkflow Operator Patch diff --git a/template/api/ManagedReference.extension.js b/template/api/ManagedReference.extension.js deleted file mode 100644 index b41288c..0000000 --- a/template/api/ManagedReference.extension.js +++ /dev/null @@ -1,192 +0,0 @@ -// 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'); - workflow = checkForCategory('Workflow') - - let operatorType = {} - operatorType.type = sink ? 'sink' : source ? 'source' : transform ? 'transform' : combinator ? 'combinator' : workflow ? 'workflow' : 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) { - // Ensure inheritedMembers exists and is an array before filtering - // Important for IncludeWorkflow operators which currently do not have any inherited members - if (!Array.isArray(model.inheritedMembers)) { - return []; - } - 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 deleted file mode 100644 index 5f4ab4f..0000000 --- a/template/api/ManagedReference.html.primary.tmpl +++ /dev/null @@ -1,7 +0,0 @@ -{{!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 deleted file mode 100644 index ff7ef9d..0000000 --- a/template/api/ManagedReference.overwrite.js +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 4a4afac..0000000 --- a/template/api/images/combinator-operator.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - diff --git a/template/api/images/right-arrow.svg b/template/api/images/right-arrow.svg deleted file mode 100644 index 85ebb00..0000000 --- a/template/api/images/right-arrow.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/template/api/images/sink-operator.svg b/template/api/images/sink-operator.svg deleted file mode 100644 index 918318c..0000000 --- a/template/api/images/sink-operator.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/template/api/images/source-operator.svg b/template/api/images/source-operator.svg deleted file mode 100644 index f39cb61..0000000 --- a/template/api/images/source-operator.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/template/api/images/transform-operator.svg b/template/api/images/transform-operator.svg deleted file mode 100644 index 43846fd..0000000 --- a/template/api/images/transform-operator.svg +++ /dev/null @@ -1,58 +0,0 @@ - - diff --git a/template/api/partials/class.tmpl.partial b/template/api/partials/class.tmpl.partial deleted file mode 100644 index 85858af..0000000 --- a/template/api/partials/class.tmpl.partial +++ /dev/null @@ -1,96 +0,0 @@ - - -{{#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

- -
+ right-arrow +
{{{output.specName}}}
{{{output.description}}}
- - - - - - - - {{#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 deleted file mode 100644 index e2cb8aa..0000000 --- a/template/api/partials/diagram.tmpl.partial +++ /dev/null @@ -1,83 +0,0 @@ -{{#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 deleted file mode 100644 index 4755efe..0000000 --- a/template/api/partials/enum.tmpl.partial +++ /dev/null @@ -1,45 +0,0 @@ -{{#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 deleted file mode 100644 index bd47d08..0000000 --- a/template/api/partials/propertyTables.tmpl.partial +++ /dev/null @@ -1,29 +0,0 @@ -
- {{{name}}} - - {{{type}}} - -
- {{#propertyDescription}} - {{{propertyDescription.text}}} - {{#hasEnum}} - - {{#enum}} - - - - - {{/enum}} -
{{{field&value}}}{{{enumDescription}}}
- {{/hasEnum}} - {{/propertyDescription}} -
-