Added extension architecture. Fixes #945 (#956)

* WIP #945 Added some structure for the extensions support

* WIP #945 Added ext installation using npm

* WIP #945 Added some structure for the extensions support

* WIP #945 Added ext installation using npm

* WIP #945 Restructured export formats

* WIP #945 Added ExportFormat:Pipeline installation

* WIP #945 Updated extension installer

* WIP #945 Fixed format.js format path

* WIP #945 Added reinstalling message

* WIP #945 Updated the default Basic CLI

* WIP Updated deserializers and cli deserializer

* WIP #945 Added simple static ext config

* WIP #945 Added custom config dialog for exporting pipelines

* WIP #945 Added dynamic updating based on config

* WIP #945 Added section headers

* WIP #945 Renamed to Export:Pipeline

* WIP #945 moved cli export to first option

* WIP #945 Renamed plugin 'GenerateExecFile' -> 'Export'

* WIP #945 Renamed GenExecFile -> Export

* WIP #945 Added 'deepforge extension remove X'

* WIP #945 Added 'list' command for extensions

* WIP #945 fixed minor linting issues

* WIP #945 aliased remove/rm, list/ls

* WIP #945 Moved gen installation fn to utils/extender

* WIP #945 Added ext reinstall script on postinstall

* WIP #945 don't prompt if no options

* WIP #945 Updated the plugin name in registry

* WIP #945 Updated format template

* WIP #945 Fixed cli tests

* WIP #945 Removed unused main method

* WIP #945 Fixed linter issues
Esse commit está contido em:
Brian Broll
2017-01-25 19:27:39 -06:00
commit de GitHub
commit a6625cfadc
18 arquivos alterados com 614 adições e 114 exclusões
+4
Ver Arquivo
@@ -447,6 +447,10 @@ program
}
});
// extensions
program
.command('extensions <command>', 'Manage deepforge extensions');
module.exports = function(cmd) {
var cmds = cmd.split(/\s+/).filter(w => !!w);
cmds.unshift('./bin/deepforge');
Arquivo executável
+67
Ver Arquivo
@@ -0,0 +1,67 @@
#!/usr/bin/env node
var Command = require('commander').Command,
program = new Command(),
extender = require('../utils/extender');
// Supported commands
// - add
// - remove
// - list
// - update
program
.command('add <project>')
.description('Add an extension to deepforge')
.option('-n, --name <name>', 'Project name (if different from <project>)')
.action(project => {
console.log('loading extension from: ' + project);
extender.install(project)
.then(extConfig =>
console.log(`The ${extConfig.name} extension has been added to deepforge.`))
.fail(err => {
console.error('Could not install extension:\n');
console.error(err);
process.exit(1);
});
});
program
.command('remove <name>').alias('rm')
.description('Remove an extension from deepforge')
.action(name => {
try {
extender.uninstall(name);
console.log(`${name} has been successfully removed!`);
} catch (e) {
console.error('Could not remove extension:');
console.error(e);
process.exit(1);
}
});
program
.command('list').alias('ls')
.description('List installed deepforge extensions')
.action(() => {
var allExtConfigs = extender.getExtensionsConfig(),
types = Object.keys(allExtConfigs),
hasContents = false,
names;
for (var i = types.length; i--;) {
names = Object.keys(allExtConfigs[types[i]]);
if (names.length) {
hasContents = true;
console.log(types[i]);
for (var j = names.length; j--;) {
console.log(` ${names[j]}`);
}
}
}
if (!hasContents) {
console.log('No installed extensions');
}
});
program.parse(process.argv);
+1 -1
Ver Arquivo
@@ -49,7 +49,7 @@
"icon": "import_export",
"priority": -1
},
"GenerateExecFile": {
"Export": {
"icon": "play_for_work",
"priority": -1
}
+3
Ver Arquivo
@@ -5,6 +5,7 @@
},
"scripts": {
"start": "node app.js",
"postinstall": "node utils/reinstall-extensions.js",
"start-dev": "NODE_ENV=dev node app.js",
"local": "node ./bin/start-local.js",
"worker": "node ./bin/start-worker.js",
@@ -21,8 +22,10 @@
"graceful-fs": "^4.1.10",
"lodash.difference": "^4.1.2",
"lodash.merge": "^4.5.1",
"lodash.template": "^4.4.0",
"mongodb": "^2.2.10",
"nodemon": "^1.9.2",
"npm": "^4.0.5",
"q": "1.4.1",
"rimraf": "^2.4.0",
"webgme": "^2.7.1",
@@ -3,7 +3,6 @@
define([
'text!./metadata.json',
'text!./toboolean.lua',
'./format',
'plugin/PluginBase',
'deepforge/plugin/PtrCodeGen',
@@ -13,7 +12,6 @@ define([
'q'
], function (
pluginMetadata,
TOBOOLEAN,
FORMATS,
PluginBase,
PtrCodeGen,
@@ -33,13 +31,13 @@ define([
RESERVED = /^(and|break|do|else|elseifend|false|for|function|if|in|local|nil|not|orrepeat|return|then|true|until|while|print)$/;
/**
* Initializes a new instance of GenerateExecFile.
* Initializes a new instance of Export.
* @class
* @augments {PluginBase}
* @classdesc This class represents the plugin GenerateExecFile.
* @classdesc This class represents the plugin Export.
* @constructor
*/
var GenerateExecFile = function () {
var Export = function () {
// Call base class' constructor.
PluginBase.call(this);
this.initRecords();
@@ -50,13 +48,13 @@ define([
* This is also available at the instance at this.pluginMetadata.
* @type {object}
*/
GenerateExecFile.metadata = pluginMetadata;
Export.metadata = pluginMetadata;
// Prototypical inheritance from PluginBase.
GenerateExecFile.prototype = Object.create(PluginBase.prototype);
GenerateExecFile.prototype.constructor = GenerateExecFile;
Export.prototype = Object.create(PluginBase.prototype);
Export.prototype.constructor = Export;
GenerateExecFile.prototype.initRecords = function() {
Export.prototype.initRecords = function() {
this.pluginMetadata = pluginMetadata;
this._srcIdFor = {}; // input path -> output data node path
@@ -94,7 +92,7 @@ define([
*
* @param {function(string, plugin.PluginResult)} callback - the result callback
*/
GenerateExecFile.prototype.main = function (callback) {
Export.prototype.main = function (callback) {
this.initRecords();
// Get all the children and call generate exec file
@@ -107,6 +105,7 @@ define([
return this.core.loadChildren(this.activeNode)
.then(nodes => this.generateOutputFiles(nodes))
.catch(err => callback(err))
.then(hash => {
this.result.addArtifact(hash);
this.result.setSuccess(true);
@@ -115,20 +114,20 @@ define([
.fail(err => callback(err));
};
GenerateExecFile.prototype.getCurrentConfig = function () {
Export.prototype.getCurrentConfig = function () {
var config = PluginBase.prototype.getCurrentConfig.call(this);
config.staticInputs = config.staticInputs || [];
return config;
};
GenerateExecFile.prototype.generateOutputFiles = function (children) {
Export.prototype.generateOutputFiles = function (children) {
var name = this.core.getAttribute(this.activeNode, 'name');
return this.createCodeSections(children)
.then(sections => {
// Get the selected format
var config = this.getCurrentConfig(),
format = config.format || 'Torch CLI',
format = config.format || 'Basic CLI',
generate = FORMATS[format],
staticInputs,
files;
@@ -167,7 +166,7 @@ define([
});
};
GenerateExecFile.prototype.createCodeSections = function (children) {
Export.prototype.createCodeSections = function (children) {
// Convert opNodes' jobs to the nested operations
var opNodes,
nodes;
@@ -211,7 +210,7 @@ define([
.fail(err => this.logger.error(err));
};
GenerateExecFile.prototype.unpackJobs = function (nodes) {
Export.prototype.unpackJobs = function (nodes) {
return Q.all(
nodes.map(node => {
if (!this.isMetaTypeOf(node, this.META.Job)) {
@@ -225,7 +224,7 @@ define([
);
};
GenerateExecFile.prototype.sortOperations = function (operationDict, opIds) {
Export.prototype.sortOperations = function (operationDict, opIds) {
var nextIds = [],
sorted = opIds,
dstIds,
@@ -253,7 +252,7 @@ define([
.concat(this.sortOperations(operationDict, nextIds));
};
GenerateExecFile.prototype.generateCodeSections = function(sortedOps) {
Export.prototype.generateCodeSections = function(sortedOps) {
// Create the code sections:
// - operation definitions
// - pipeline definition
@@ -286,8 +285,8 @@ define([
// Define the serializers/deserializers
this.addCodeSerializers(code);
// Define the main body
this.addCodeMain(code);
// Define the main input names
code.mainInputNames = Object.keys(this.isInputOp).map(id => this._nameFor[id]);
// Add custom class definitions
this.addCustomClasses(code);
@@ -299,12 +298,12 @@ define([
};
// expose this utility function to format extensions
var indent = GenerateExecFile.prototype.indent = function(text, spaces) {
var indent = Export.prototype.indent = function(text, spaces) {
spaces = spaces || 3;
return text.replace(/^/mg, new Array(spaces+1).join(' '));
};
GenerateExecFile.prototype.defineOperationFn = function(operation) {
Export.prototype.defineOperationFn = function(operation) {
var lines = [],
args = operation.inputNames || [];
@@ -322,7 +321,7 @@ define([
return lines.join('\n');
};
GenerateExecFile.prototype.definePipelineFn = function(sortedOps, outputOps) {
Export.prototype.definePipelineFn = function(sortedOps, outputOps) {
var inputArgs = Object.keys(this.isInputOp).map(id => this._nameFor[id]),
name = this.core.getAttribute(this.activeNode, 'name'),
safename = getUniqueName(name, this._opBaseNames),
@@ -348,7 +347,7 @@ define([
return result;
};
GenerateExecFile.prototype.getOutputPair = function(operation) {
Export.prototype.getOutputPair = function(operation) {
var input = operation.inputValues[0].slice(),
value;
@@ -358,11 +357,9 @@ define([
return [this._nameFor[operation.id], value];
};
GenerateExecFile.prototype.addCodeSerializers = function(sections) {
Export.prototype.addCodeSerializers = function(sections) {
var loadNodes = {},
saveNodes = {},
hasBool = false;
saveNodes = {};
// Add the serializer fn names for each input
sections.serializerFor = {};
@@ -376,21 +373,10 @@ define([
// Add the serializer definitions
Object.keys(this.isInputOp).forEach(id => {
var node = this.inputNode[id],
base = this.core.getBase(node),
type = this.core.getAttribute(base, 'name'),
name = this._nameFor[id];
if (type === 'boolean') {
hasBool = true;
sections.deserializerFor[name] = 'toboolean';
} else if (type === 'number') {
sections.deserializerFor[name] = 'tonumber';
} else if (type === 'string') {
sections.deserializerFor[name] = 'tostring';
} else {
loadNodes[id] = node;
sections.deserializerFor[name] = `__load['${this._nameFor[id]}']`;
}
loadNodes[id] = node;
sections.deserializerFor[name] = `__load['${this._nameFor[id]}']`;
});
sections.deserializers = this.createTorchFnDict(
@@ -417,10 +403,6 @@ define([
'path, data'
);
if (hasBool) { // add toboolean def
sections.deserializers += '\n' + TOBOOLEAN;
}
// Add a saveOutputs method for convenience
sections.serializeOutputsDef = [
'local function __saveOutputs(data)',
@@ -438,18 +420,7 @@ define([
sections.serializeOutputs = '__saveOutputs(outputs)';
};
GenerateExecFile.prototype.addCodeMain = function(sections) {
var pipelineName = Object.keys(sections.pipelines)[0],
args;
// Create some names for the inputs
sections.mainInputNames = Object.keys(this.isInputOp).map(id => this._nameFor[id]);
args = sections.mainInputNames.map(name => `${sections.deserializerFor[name]}(${name})`);
sections.main = `local outputs = ${pipelineName}(${args.join(', ')})`;
};
GenerateExecFile.prototype.createTorchFnDict = function(name, nodeDict, attr, args) {
Export.prototype.createTorchFnDict = function(name, nodeDict, attr, args) {
return [
`local ${name} = {}`,
Object.keys(nodeDict).map(id => {
@@ -463,7 +434,7 @@ define([
].join('\n');
};
GenerateExecFile.prototype.addCustomClasses = function(sections) {
Export.prototype.addCustomClasses = function(sections) {
var metaDict = this.core.getAllMetaNodes(this.rootNode),
isClass,
metanodes,
@@ -528,7 +499,7 @@ define([
});
};
GenerateExecFile.prototype.addCustomLayers = function(sections) {
Export.prototype.addCustomLayers = function(sections) {
var metaDict = this.core.getAllMetaNodes(this.rootNode),
isCustomLayer,
metanodes,
@@ -552,7 +523,7 @@ define([
};
GenerateExecFile.prototype.getTypeDictFor = function (name, metanodes) {
Export.prototype.getTypeDictFor = function (name, metanodes) {
var isType = {};
// Get all the custom layers
for (var i = metanodes.length; i--;) {
@@ -570,7 +541,7 @@ define([
return `"${attr}"`;
};
GenerateExecFile.prototype.getOpInvocation = function(op) {
Export.prototype.getOpInvocation = function(op) {
var lines = [],
attrs,
refInits = [],
@@ -603,13 +574,13 @@ define([
return lines.join('\n');
};
GenerateExecFile.prototype.getOutputName = function(node) {
Export.prototype.getOutputName = function(node) {
var basename = this.core.getAttribute(node, 'saveName');
return getUniqueName(basename, this._outputNames, true);
};
GenerateExecFile.prototype.getVariableName = function (/*node*/) {
Export.prototype.getVariableName = function (/*node*/) {
var c = Object.keys(this.isInputOp).length;
if (c !== 1) {
@@ -619,7 +590,7 @@ define([
return 'input';
};
GenerateExecFile.prototype.registerNode = function (node) {
Export.prototype.registerNode = function (node) {
if (this.isMetaTypeOf(node, this.META.Operation)) {
return this.registerOperation(node);
} else if (this.isMetaTypeOf(node, this.META.Transporter)) {
@@ -648,7 +619,7 @@ define([
return name;
};
GenerateExecFile.prototype.registerOperation = function (node) {
Export.prototype.registerOperation = function (node) {
var name = this.core.getAttribute(node, 'name'),
id = this.core.getPath(node),
base = this.core.getBase(node),
@@ -710,7 +681,7 @@ define([
});
};
GenerateExecFile.prototype.registerTransporter = function (node) {
Export.prototype.registerTransporter = function (node) {
var outputData = this.core.getPointerPath(node, 'src'),
inputData = this.core.getPointerPath(node, 'dst'),
srcOpId = this.getOpIdFor(outputData),
@@ -729,7 +700,7 @@ define([
this._incomingCnts[dstOpId]++;
};
GenerateExecFile.prototype.getOpIdFor = function (dataId) {
Export.prototype.getOpIdFor = function (dataId) {
var ids = dataId.split('/'),
depth = ids.length;
@@ -744,7 +715,7 @@ define([
// - add the references
// - generate the code
// - replace the `return <thing>` w/ `<ref-name> = <thing>`
GenerateExecFile.prototype.createOperation = function (node) {
Export.prototype.createOperation = function (node) {
var id = this.core.getPath(node),
baseId = this.core.getPath(this.core.getBase(node)),
attrNames = this.core.getValidAttributeNames(node),
@@ -819,12 +790,12 @@ define([
});
};
GenerateExecFile.prototype.genPtrSnippet = function (ptrName, pId) {
Export.prototype.genPtrSnippet = function (ptrName, pId) {
return this.getPtrCodeHash(pId)
.then(hash => this.blobClient.getObjectAsString(hash));
};
GenerateExecFile.prototype.createHeader = function (title, length) {
Export.prototype.createHeader = function (title, length) {
var len;
title = ` ${title} `;
length = length || HEADER_LENGTH;
@@ -842,7 +813,7 @@ define([
};
GenerateExecFile.prototype.genOperationCode = function (operation) {
Export.prototype.genOperationCode = function (operation) {
var header = this.createHeader(`"${operation.name}" Operation`),
codeParts = [],
body = [];
@@ -869,15 +840,7 @@ define([
return operation;
};
GenerateExecFile.prototype.assignResultToVar = function (code, name) {
var i = code.lastIndexOf('return');
_.extend(Export.prototype, PtrCodeGen.prototype);
return code.substring(0, i) +
code.substring(i)
.replace('return', `${name} = `);
};
_.extend(GenerateExecFile.prototype, PtrCodeGen.prototype);
return GenerateExecFile;
return Export;
});
@@ -1,12 +1,13 @@
/* globals define*/
// The supported export formats and metadata
define([
'./formats/cli'
'./formats/cli/cli'
], function(
TorchCLI
Format0
) {
return {
'Torch CLI': TorchCLI
'Basic CLI': Format0
};
});
+21
Ver Arquivo
@@ -0,0 +1,21 @@
<% // Add default format
formats.unshift({ name: 'cli', main: 'cli.js', displayName: 'Basic CLI' })
%>
/* globals define*/
// The supported export formats and metadata
define([
<%= formats.map(function(format) {
return ' \'./formats/' + format.name + '/' +
path.basename(format.main.replace(/\.js$/, '')) + '\''
})
.join(',\n') %>
], function(
<%= formats.map(function(f, index) { return ' Format' + index; }).join(',\n') %>
) {
return {
<%= formats.map(function(f, index) {
return ' \'' + f.displayName + '\': Format' + index;
}).join(',\n') %>
};
});
@@ -8,16 +8,46 @@ define([
var INIT_CLASSES_FN = '__initClasses',
INIT_LAYERS_FN = '__initLayers',
TOBOOLEAN,
DEEPFORGE_CODE; // defined at the bottom (after the embedded template)
var deserializersFromString = function(sections) {
var hasBool = false;
// Add serializers given cli string input
Object.keys(this.isInputOp).forEach(id => {
var node = this.inputNode[id],
base = this.core.getBase(node),
type = this.core.getAttribute(base, 'name'),
name = this._nameFor[id];
if (type === 'boolean') {
hasBool = true;
sections.deserializerFor[name] = 'toboolean';
} else if (type === 'number') {
sections.deserializerFor[name] = 'tonumber';
} else if (type === 'string') {
sections.deserializerFor[name] = 'tostring';
}
});
if (hasBool) {
sections.deserializers += '\n' + TOBOOLEAN;
}
return sections;
};
var createExecFile = function (sections, staticInputs) {
var classes,
initClassFn,
initLayerFn,
code = [];
// concat all the sections into a single file
// Update deserializers for cli input
deserializersFromString.call(this, sections);
// concat all the sections into a single file
// wrap the class/layer initialization in a fn
// Add the classes ordered wrt their deps
classes = sections.orderedClasses
@@ -86,21 +116,28 @@ define([
return files;
} else {
var pipelineName = Object.keys(sections.pipelines)[0],
main,
args;
// Create some names for the inputs
args = sections.mainInputNames.map(name => `${sections.deserializerFor[name]}(${name})`);
main = `local outputs = ${pipelineName}(${args.join(', ')})`;
// Grab the args from the cli
code.push(sections.mainInputNames.map((name, index) => {
return `local ${name} = arg[${index + 1}]`;
}).join('\n'));
// Add the main fn
code.push(sections.main);
code.push(main);
// Save outputs to disk
code.push(sections.serializeOutputs);
return code.join('\n\n');
}
return code.join('\n\n');
};
var deepforgeTxt =
@@ -152,6 +189,15 @@ function deepforge.Image:title(name)
-- nop
end`;
TOBOOLEAN =
`local function toboolean(str)
if str == 'true' then
return true
elseif str == 'false' then
return false
end
end`;
DEEPFORGE_CODE = _.template(deepforgeTxt)({
initCode: `${INIT_CLASSES_FN}()\n${' '}${INIT_LAYERS_FN}()`
});
@@ -1,7 +1,7 @@
{
"id": "GenerateExecFile",
"name": "Generate Execution File",
"version": "0.1.0",
"id": "Export",
"name": "Export",
"version": "1.0.0",
"description": "",
"icon": {
"class": "glyphicon glyphicon-cog",
@@ -0,0 +1,5 @@
.config-section-header {
margin-top: 0;
color: #888;
font-style: italic;
}
@@ -0,0 +1,159 @@
/* globals define, $*/
define([
'js/Dialogs/PluginConfig/PluginConfigDialog',
'text!js/Dialogs/PluginConfig/templates/PluginConfigDialog.html',
'plugin/Export/Export/format',
'css!./ConfigDialog.css'
], function(
PluginConfigDialog,
pluginConfigDialogTemplate,
ExportFormats
) {
var SECTION_DATA_KEY = 'section',
ATTRIBUTE_DATA_KEY = 'attribute',
//jscs:disable maximumLineLength
PLUGIN_CONFIG_SECTION_BASE = $('<div><fieldset><form class="form-horizontal" role="form"></form><fieldset></div>'),
ENTRY_BASE = $('<div class="form-group"><div class="row"><label class="col-sm-4 control-label">NAME</label><div class="col-sm-8 controls"></div></div><div class="row description"><div class="col-sm-4"></div></div></div>'),
//jscs:enable maximumLineLength
DESCRIPTION_BASE = $('<div class="desc muted col-sm-8"></div>'),
SECTION_HEADER = $('<h6 class="config-section-header">');
var ConfigDialog = function(client, nodeId) {
PluginConfigDialog.call(this, {client: client});
this._widgets = {};
this._node = this._client.getNode(nodeId);
};
ConfigDialog.prototype = Object.create(PluginConfigDialog.prototype);
ConfigDialog.prototype.show = function(globalOptions, pluginMetadata, extMetadata, callback) {
this._extMetadata = extMetadata;
return PluginConfigDialog.prototype.show.call(this, globalOptions, pluginMetadata, {}, callback);
};
ConfigDialog.prototype._initDialog = function() {
this._dialog = $(pluginConfigDialogTemplate);
this._btnSave = this._dialog.find('.btn-save');
this._divContainer = this._dialog.find('.modal-body');
this._saveConfigurationCb = this._dialog.find('.save-configuration');
this._modalHeader = this._dialog.find('.modal-header');
// Create the header
var iconEl = $('<i/>', {
class: this._pluginMetadata.icon.class || 'glyphicon glyphicon-cog'
});
iconEl.addClass('plugin-icon pull-left');
this._modalHeader.prepend(iconEl);
this._title = this._modalHeader.find('.modal-title');
this._title.text(this._pluginMetadata.id + ' ' + 'v' + this._pluginMetadata.version);
// Generate the config options
var formats = Object.keys(ExportFormats),
format = formats[0],
sectionHeader = SECTION_HEADER.clone();
sectionHeader.text('Static Artifacts');
this._divContainer.append(sectionHeader);
this.generateConfigSection(this._pluginMetadata);
if (formats.length > 1) {
this._divContainer.append($('<hr class="extension-config-divider">'));
sectionHeader = SECTION_HEADER.clone();
sectionHeader.text('Export Options');
this._divContainer.append(sectionHeader);
this.generateConfigSection({
id: 'FormatOptions',
configStructure: this._globalOptions
});
this._widgets.FormatOptions.exportFormat.el.find('select').on('change', event => {
var format = event.target.value;
// Update the ext config
this.updateExtConfig(format);
});
}
this.updateExtConfig(format);
};
ConfigDialog.prototype.updateExtConfig = function (format) {
var extConfig = {
class: 'extension-config',
configStructure: ExportFormats[format].getConfigStructure ?
ExportFormats[format].getConfigStructure(this._client, this._node) : []
};
this._divContainer.find('.extension-config').remove();
if (extConfig.configStructure.length) {
this.generateConfigSection(extConfig);
}
};
ConfigDialog.prototype.generateConfigSection = function (metadata) {
var len = metadata.configStructure.length,
i,
el,
pluginConfigEntry,
widget,
descEl,
containerEl,
pluginSectionEl = PLUGIN_CONFIG_SECTION_BASE.clone();
pluginSectionEl.data(SECTION_DATA_KEY, metadata.id);
this._divContainer.append(pluginSectionEl);
containerEl = pluginSectionEl.find('.form-horizontal');
if (metadata.class) {
pluginSectionEl.addClass(metadata.class);
}
this._widgets[metadata.id] = {};
for (i = 0; i < len; i += 1) {
pluginConfigEntry = metadata.configStructure[i];
descEl = undefined;
// Make sure not modify the global metadata.
pluginConfigEntry = JSON.parse(JSON.stringify(pluginConfigEntry));
if (this._client.getProjectAccess().write === false && pluginConfigEntry.writeAccessRequired === true) {
pluginConfigEntry.readOnly = true;
}
widget = this._propertyGridWidgetManager.getWidgetForProperty(pluginConfigEntry);
this._widgets[metadata.id][pluginConfigEntry.name] = widget;
el = ENTRY_BASE.clone();
el.data(ATTRIBUTE_DATA_KEY, pluginConfigEntry.name);
el.find('label.control-label').text(pluginConfigEntry.displayName);
if (pluginConfigEntry.description && pluginConfigEntry.description !== '') {
descEl = descEl || DESCRIPTION_BASE.clone();
descEl.text(pluginConfigEntry.description);
}
if (pluginConfigEntry.minValue !== undefined &&
pluginConfigEntry.minValue !== null &&
pluginConfigEntry.minValue !== '') {
descEl = descEl || DESCRIPTION_BASE.clone();
descEl.append(' The minimum value is: ' + pluginConfigEntry.minValue + '.');
}
if (pluginConfigEntry.maxValue !== undefined &&
pluginConfigEntry.maxValue !== null &&
pluginConfigEntry.maxValue !== '') {
descEl = descEl || DESCRIPTION_BASE.clone();
descEl.append(' The maximum value is: ' + pluginConfigEntry.maxValue + '.');
}
el.find('.controls').append(widget.el);
if (descEl) {
el.find('.description').append(descEl);
}
containerEl.append(el);
}
};
return ConfigDialog;
});
@@ -4,7 +4,7 @@
define([
'blob/BlobClient',
'js/Utils/SaveToDisk',
'js/Dialogs/PluginConfig/PluginConfigDialog',
'./ConfigDialog',
'js/Constants',
'panel/FloatingActionButton/FloatingActionButton',
'deepforge/viz/PipelineControl',
@@ -17,11 +17,11 @@ define([
'q',
'deepforge/globals',
'deepforge/Constants',
'plugin/GenerateExecFile/GenerateExecFile/format'
'plugin/Export/Export/format'
], function (
BlobClient,
SaveToDisk,
PluginConfigDialog,
ConfigDialog,
GME_CONSTANTS,
PluginButton,
PipelineControl,
@@ -388,7 +388,7 @@ define([
/// Export Pipeline Support
ForgeActionButton.prototype.exportPipeline = function() {
var deferred = Q.defer(),
pluginId = 'GenerateExecFile',
pluginId = 'Export',
metadata = WebGMEGlobal.allPluginsMetadata[pluginId],
id = this._currentNodeId,
node = this.client.getNode(id),
@@ -429,9 +429,16 @@ define([
.map(id => this.client.getNode(id))
.filter(output => output.getAttribute('data'));
// get the output data node name
// get the name of node referenced from the input op
inputNames = inputData
.map(node => node.getAttribute('name'))
.map(node => {
var cntrId = node.getParentId(),
opId = this._client.getNode(cntrId).getParentId(),
inputOp = this._client.getNode(opId),
targetNodeId = inputOp.getPointer('artifact').to;
return this._client.getNode(targetNodeId).getAttribute('name');
})
.sort();
// create config options from inputs
@@ -447,18 +454,11 @@ define([
});
var exportFormats = Object.keys(ExportFormatDict),
configDialog = new PluginConfigDialog({client: this.client}),
configDialog = new ConfigDialog(this.client, this._currentNodeId),
inputConfig = _.extend({}, metadata),
extOptions = [],
globalOpts = [];
// Hide the divider if missing inputOpts or globalOpts
configDialog._initDialog = function() {
PluginConfigDialog.prototype._initDialog.apply(this, arguments);
if (!globalOpts.length || !inputOpts.length) {
this._divContainer.find('.global-and-plugin-divider').remove();
}
};
if (exportFormats.length > 1) {
globalOpts.push({ // format options
name: 'exportFormat',
@@ -471,8 +471,9 @@ define([
}
inputConfig.configStructure = inputOpts;
if (inputOpts.length || exportFormats.length > 1) {
configDialog.show(globalOpts, inputConfig, {}, (formatOpts, inputOpts) => {
// Try to get the extension options
if (inputOpts.length || exportFormats.length > 1|| extOptions.length) {
configDialog.show(globalOpts, inputConfig, (formatOpts, inputOpts) => {
var context = this.client.getCurrentPluginContext(pluginId),
exportFormat = (globalOpts.length && formatOpts) ? formatOpts.exportFormat : exportFormats[0],
staticInputs = Object.keys(inputOpts || {}).filter(input => inputOpts[input]);
@@ -508,5 +509,6 @@ define([
return deferred.promise;
};
return ForgeActionButton;
});
@@ -1,7 +1,7 @@
/*jshint node:true, mocha:true*/
'use strict';
describe('GenerateExecFile', function () {
describe('Export', function () {
var testFixture = require('../../globals'),
lua = require('../../../src/common/lua'),
path = testFixture.path,
@@ -9,12 +9,12 @@ describe('GenerateExecFile', function () {
SEED_DIR = path.join(testFixture.DF_SEED_DIR, 'devProject'),
gmeConfig = testFixture.getGmeConfig(),
expect = testFixture.expect,
logger = testFixture.logger.fork('GenerateExecFile'),
logger = testFixture.logger.fork('Export'),
PluginCliManager = testFixture.WebGME.PluginCliManager,
manager = new PluginCliManager(null, logger, gmeConfig),
BlobClient = require('webgme/src/server/middleware/blob/BlobClientWithFSBackend'),
projectName = 'testProject',
pluginName = 'GenerateExecFile',
pluginName = 'Export',
project,
gmeAuth,
storage,
+199
Ver Arquivo
@@ -0,0 +1,199 @@
// Utility for applying and removing deepforge extensions
// This utility is run by the cli when executing:
//
// deepforge extensions add <project>
// deepforge extensions remove <name>
//
var path = require('path'),
fs = require('fs'),
npm = require('npm'),
Q = require('q'),
rm_rf = require('rimraf'),
exists = require('exists-file'),
makeTpl = require('lodash.template'),
CONFIG_DIR = path.join(process.env.HOME, '.deepforge'),
EXT_CONFIG_NAME = 'extension.json',
EXTENSION_REGISTRY_NAME = 'extensions.json',
extConfigPath = path.join(CONFIG_DIR, EXTENSION_REGISTRY_NAME),
allExtConfigs;
var values = obj => Object.keys(obj).map(key => obj[key]);
// Create the extensions.json if doesn't exist. Otherwise, load it
if (!exists.sync(extConfigPath)) {
allExtConfigs = {};
} else {
try {
allExtConfigs = JSON.parse(fs.readFileSync(extConfigPath, 'utf8'));
} catch (e) {
throw `Invalid config at ${extConfigPath}: ${e.toString()}`;
}
}
var persistExtConfig = () => {
fs.writeFileSync(extConfigPath, JSON.stringify(allExtConfigs, null, 2));
};
var extender = {};
extender.EXT_CONFIG_NAME = EXT_CONFIG_NAME;
extender.isSupportedType = function(type) {
return extender.install[type] && extender.uninstall[type];
};
extender.getExtensionsConfig = function() {
return allExtConfigs;
};
extender.getInstalledConfig = function(name) {
var group = values(allExtConfigs).find(typeGroup => {
return !!typeGroup[name];
});
return group && group[name];
};
extender.install = function(project, isReinstall) {
// Install the project
return Q.ninvoke(npm, 'load', {})
.then(() => Q.ninvoke(npm, 'install', project))
.then(results => {
var installed = results[0],
extProject,
extRoot;
extProject = installed[0][0];
extRoot = installed[0][1];
// Check for the extensions.json in the project (look up type, etc)
var extConfigPath = path.join(extRoot, extender.EXT_CONFIG_NAME),
extConfig,
extType;
// Check that the extensions file exists
if (!exists.sync(extConfigPath)) {
throw [
`Could not find ${extender.EXT_CONFIG_NAME} for ${project}.`,
'',
`This is likely an issue w/ the deepforge extension (${project})`
].join('\n');
}
try {
extConfig = JSON.parse(fs.readFileSync(extConfigPath, 'utf8'));
} catch(e) { // Invalid JSON
throw `Invalid ${extender.EXT_CONFIG_NAME}: ${e}`;
}
// Try to add the extension to the project (using the extender)
extType = extConfig.type;
if (!extender.isSupportedType(extType)) {
throw `Unrecognized extension type: "${extType}"`;
}
extender.install[extType](extConfig, {
arg: project,
root: extRoot,
name: extProject
}, !!isReinstall);
return extConfig;
});
};
extender.uninstall = function(name) {
// Look up the extension in ~/.deepforge/extensions.json
var extConfig = extender.getInstalledConfig(name);
if (!extConfig) {
throw `Extension "${name}" not found`;
}
// Run the uninstaller using the extender
var extType = extConfig.type;
extender.uninstall[extType](name);
};
var makeInstallFor = function(typeCfg) {
var saveExtensions = () => {
// regenerate the format.js file from the template
var installedExts = values(allExtConfigs[typeCfg.type]),
formatTemplate = makeTpl(fs.readFileSync(typeCfg.template, 'utf8')),
formatsIndex = formatTemplate({path: path, formats: installedExts}),
dstPath = typeCfg.template.replace(/\.ejs$/, '');
fs.writeFileSync(dstPath, formatsIndex);
persistExtConfig();
};
// Given a...
// - template file
// - extension type
// - target path tpl
// create the installation/uninstallation functions
extender.install[typeCfg.type] = (config, project, isReinstall) => {
var dstPath,
pkgJsonPath = path.join(project.root, 'package.json'),
pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')),
content;
// add the config to the current installed extensions of this type
project = project || config.project;
config.version = pkgJson.version;
config.project = project;
allExtConfigs[typeCfg.type] = allExtConfigs[typeCfg.type] || {};
if (allExtConfigs[typeCfg.type][config.name] && !isReinstall) {
// eslint-disable-next-line no-console
console.error(`Extension ${config.name} already installed. Reinstalling...`);
}
allExtConfigs[typeCfg.type][config.name] = config;
// copy the main script to src/plugins/Export/formats/<name>/<main>
dstPath = makeTpl(typeCfg.targetDir)(config);
if (!exists.sync(dstPath)) {
fs.mkdirSync(dstPath);
}
try {
content = fs.readFileSync(path.join(project.root, config.main), 'utf8');
} catch (e) {
throw 'Could not read the extension\'s main file: ' + e;
}
dstPath = path.join(dstPath, path.basename(config.main));
fs.writeFileSync(dstPath, content);
saveExtensions();
};
// uninstall
extender.uninstall['Export:Pipeline'] = name => {
// Remove from config
allExtConfigs[typeCfg.type] = allExtConfigs[typeCfg.type] || {};
if (!allExtConfigs[typeCfg.type][name]) {
// eslint-disable-next-line no-console
console.log(`Extension ${name} not installed`);
return;
}
var config = allExtConfigs[typeCfg.type][name],
dstPath = makeTpl(typeCfg.targetDir)(config);
// Remove the dstPath
delete allExtConfigs[typeCfg.type][name];
rm_rf.sync(dstPath);
// Re-generate template file
saveExtensions();
};
};
var PLUGIN_ROOT = path.join(__dirname, '..', 'src', 'plugins', 'Export');
makeInstallFor({
type: 'Export:Pipeline',
template: path.join(PLUGIN_ROOT, 'format.js.ejs'),
targetDir: path.join(PLUGIN_ROOT, 'formats', '<%=name%>')
});
module.exports = extender;
+30
Ver Arquivo
@@ -0,0 +1,30 @@
// Re-install all extensions
var extender = require('./extender'),
Q = require('q'),
extConfig = extender.getExtensionsConfig(),
types,
names,
currentInstall = Q(),
installCount = 0,
config;
// Read the extensions and reinstall each of them
types = Object.keys(extConfig);
for (var i = types.length; i--;) {
names = Object.keys(extConfig[types[i]]);
if (names.length) {
installCount += names.length;
for (var j = names.length; j--;) {
// eslint-disable-next-line no-console
console.log(`Re-installing ${names[j]} extension...`);
config = extConfig[types[i]][names[j]];
currentInstall = currentInstall
.then(() => extender.install(config.project.arg, true));
}
}
}
if (installCount) {
// eslint-disable-next-line no-console
currentInstall.then(() => console.log('Extensions reinstalled successfully'));
}
+3 -3
Ver Arquivo
@@ -29,9 +29,9 @@
"src": "src/plugins/CreateExecution",
"test": "test/plugins/CreateExecution"
},
"GenerateExecFile": {
"src": "src/plugins/GenerateExecFile",
"test": "test/plugins/GenerateExecFile"
"Export": {
"src": "src/plugins/Export",
"test": "test/plugins/Export"
},
"ImportArtifact": {
"src": "src/plugins/ImportArtifact",