This isn't quite what you want, but could probably be adapted to do what you are looking to do by adjusting some of the logic. This is a script I wrote back in 2017 that takes an AIX file and produces the documentation and SVG files for the blocks. It was used as part of the process to build the reference documentation for the old IOT website.
If you want to create draggable block PNGs, then you'd have to adjust the code that produces the SVG to instead call Blockly.exportBlockAsPng
instead.
#!/usr/bin/env phantomjs
// -*- mode: javascript; js-indent-level: 2; -*-
// Copyright © 2017 Massachusetts Institute of Technology, All rights reserved.
'use strict';
var system = require('system'),
fs = require('fs'),
webpage = require('webpage'),
JSZip = require('../lib/jszip/jszip.min'),
platform = '',
components = null;
/**
* Flag that controls whether parameters are listed under methods and events. Since parameter
* annotations were only added in 1.8, we do not have support for documenting parameters in the
* extensions. This will result in to-do messages in the output file.
*
* @type {boolean} true if parameter lists should be autogenerated, otherwise false (default: false)
*/
var includeParameters = false;
/**
* Flag that controls whether or not to generate a single amalgamated file for extension bundles or
* whether to create a file per-extension in the bundle. For extensions that have a single
* component, this flag should have no observable effect.
*
* @type {boolean} true if a file should be created per extension in a bundle (default), otherwise
* false to have a single output file with all extensions together.
*/
var filePerExtension = true;
/**
* Function to create a named anchor for the Markdown content. This is used to make linkable
* references to properties, methods, and events in the documentation.
*
* @type {?function(extensionName: string, targetName: string): string} a function that returns
* HTML for a named anchor for the given extensionName and targetName.
*/
var makeAnchor = null;
/**
* Function to create a link to a named anchor for the Markdown content. This is used to make links
* to other extensions.
*
* @type {?function(extensionName: string, targetName: string): string} a function that returns
* HTML for a link to the given extensionName and targetName.
*/
var makeLink = null;
var titlePrefix = '# ';
var sectionPrefix = '## ';
var linkRegex = /(<a href="#)([^"]*?)(">.*?<\/a>)|(<a href='#)([^']*?)('>.*?<\/a>)/g;
function escapeUnderscores(s) {
return s.replace(/_/g, '\\_');
}
function updateLinks(extensionName, text) {
if (filePerExtension) {
return text;
}
// Technically we should do this by parsing the description as HTML as a context-free grammar
// rather than abusing regular expressions. We assume that the descriptions are well-formed
// enough that <a> tags do not include other <a> tags.
var match, lastPos = 0, newText = [];
while ((match = linkRegex.exec(text)) !== null) {
newText.push(text.substr(lastPos, match.index));
lastPos = match.index + text[0].length + 1;
if (match[1] !== undefined) {
newText.push(match[1], extensionName, '-', match[2], match[3]);
} else if (match[4] !== undefined) {
newText.push(match[4], extensionName, '-', match[5], match[6]);
} else {
system.stderr.write('Matched successfully but the groups were not initialized???');
phantom.exit(2);
}
}
newText.push(text.substr(lastPos));
return newText.join();
}
function generateBlockImage(extension, blockType, descriptor, outputPath, out) {
var page = webpage.create(), workspace, block, mutators, prefix = '';
page.injectJs('../build/blocklyeditor/blockly-all.js');
out.write('\n');
switch (blockType) {
case 'component_get':
blockType = 'component_set_get';
mutators = {
'instance_name': extension.name + '1',
'component_type': extension.name,
'set_or_get': 'get',
'is_generic': false,
'property_name': descriptor.name
};
prefix = 'get ';
break;
case 'component_set':
blockType = 'component_set_get';
mutators = {
'instance_name': extension.name + '1',
'component_type': extension.name,
'set_or_get': 'set',
'is_generic': false,
'property_name': descriptor.name
};
break;
case 'component_method':
mutators = {
'instance_name': extension.name + '1',
'component_type': extension.name,
'is_generic': false,
'method_name': descriptor.name
};
break;
case 'component_event':
mutators = {
'instance_name': extension.name + '1',
'component_type': extension.name,
'is_generic': false,
'event_name': descriptor.name
};
break;
}
block = page.evaluate(function(blockType, mutators, extension) {
workspace = Blockly.BlocklyEditor.create(document.body, '0_Screen1', false, false);
Blockly.mainWorkspace = workspace;
workspace.getComponentDatabase().populateTypes([extension]);
Blockly.ai_inject(document.body, workspace);
var xmlArray = Blockly.Drawer.prototype.blockTypeToXMLArray(blockType, mutators),
/** @type {Blockly.BlockSvg} */
block = Blockly.Xml.domToBlock(xmlArray[0], workspace),
s = new XMLSerializer();
block.initSvg();
block.render();
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
bbox = block.getSvgRoot().getBBox(),
width = bbox.width, height = bbox.height;
if (block.outputConnection) {
width += 8;
block.getSvgRoot().setAttribute('transform', 'translate(8, 0)');
svg.setAttribute('viewport', '0 0 ' + width + ' ' + height);
} else {
svg.setAttribute('viewport', '0 0 ' + width + ' ' + height);
}
svg.setAttribute('width', width);
svg.setAttribute('height', height);
svg.appendChild(document.getElementsByTagName('style')[0].cloneNode(true));
svg.appendChild(block.getSvgRoot().cloneNode(true));
return {
'svg': s.serializeToString(svg).replace(/ /g, ' '),
'alt': block.getSvgRoot().textContent
.replace(new RegExp(Blockly.FieldDropdown.ARROW_CHAR, 'g'), '').replace(/\./g, '')
.replace(/ /g, ' ')
};
}, blockType, mutators, extension);
try {
fs.write(outputPath, block['svg'], 'w');
} catch(e) {
system.stderr.log(e);
phantom.exit(1);
}
out.write('![' + prefix + block['alt'] + '](' + outputPath + ')\n\n');
}
function makeAnchorPackaged(extensionName, targetName) {
return '<a name="' + extensionName + (targetName? '-' + targetName : '') + '"></a>';
}
function makeAnchorSeparate(extensionName, targetName) {
if (targetName) {
return '<a name="' + targetName + '"></a>';
} else {
return '';
}
}
function makeLinkPackaged(extensionName, targetName, text) {
if (!text) {
text = targetName;
targetName = undefined;
}
return '<a href="#' + extensionName + (targetName? '-' + targetName : '') + '">' + text + '</a>';
}
function makeLinkSeparate(extensionName, targetName, text) {
if (!text) {
text = targetName;
targetName = undefined;
}
return '<a href="' + extensionName + '.md' + (targetName? '#' + targetName : '') + '">' + text + '</a>';
}
function writeExtensionDoc(extension) {
var i, j, prop, method, param, event, name = extension['name'], basePath = 'blocks/' + name + '.';
var out = filePerExtension ? fs.open(name + '.md', 'w') : system.out;
system.stderr.write('Generating docs for ' + name + '...\n');
out.write(titlePrefix + escapeUnderscores(name) + '\n\n');
out.write(extension['helpString'] + '\n\n');
if (extension.blockProperties.length > 0) {
out.write(sectionPrefix + 'Properties\n\n');
for (i = 0; i < extension.blockProperties.length; i++) {
prop = extension.blockProperties[i];
out.write('+ ' + makeAnchor(name, prop.name) + '`' + escapeUnderscores(prop.name) + '` – ' + prop.description + '\n\n');
if (prop.rw.indexOf('read') >= 0) {
generateBlockImage(extension, 'component_get', prop, basePath + prop.name + '_getter.svg', out);
}
if (prop.rw.indexOf('write') >= 0) {
generateBlockImage(extension, 'component_set', prop, basePath + prop.name + '_setter.svg', out);
}
}
}
if (extension.methods.length > 0) {
out.write(sectionPrefix + 'Methods\n\n');
for (i = 0; i < extension.methods.length; i++) {
method = extension.methods[i];
out.write('+ ' + makeAnchor(name, method.name) + '`' + escapeUnderscores(method.name) + '` ' +
(method.deprecated === 'true' ? '(deprecated) ' : '') +
'– ' + method.description + '\n');
if (includeParameters) {
for (j = 0; j < method.params.length; j++) {
param = method.params[j];
out.write(' + `' + escapeUnderscores(param.name) + '` (_' + param.type +
'_) – TODO write parameter description\n');
}
}
generateBlockImage(extension, 'component_method', method, basePath + method.name + '.svg', out);
}
}
if (extension.events.length > 0) {
out.write(sectionPrefix + 'Events\n\n');
for (i = 0; i < extension.events.length; i++) {
event = extension.events[i];
out.write('+ ' + makeAnchor(name, event.name) + '`' + escapeUnderscores(event.name) + '` ' +
(event.deprecated === 'true' ? '(deprecated) ' : '') +
'– ' + event.description + '\n');
if (includeParameters) {
for (j = 0; j < extension.events[i].params.length; j++) {
param = event.params[j];
out.write(' + `' + escapeUnderscores(param.name) + '` (_' + param.type +
'_) – TODO write parameter description\n');
}
}
generateBlockImage(extension, 'component_event', event, basePath + event.name + '.svg', out);
}
}
out.write('\n');
if (filePerExtension) {
out.close();
}
}
function writeExtensionDocs(extensions) {
if (!filePerExtension) {
console.log('# Platform: ' + platform + '\n');
}
for (var i = 0; i < extensions.length; i++) {
writeExtensionDoc(extensions[i]);
}
phantom.exit();
}
function readExtensionsAIX(cb) {
return function(zip) {
zip.forEach(function (path, file) {
if (path.lastIndexOf('/components.json') === path.length - '/components.json'.length) {
file.async('string')
.then(function (value) {
components = JSON.parse(value);
cb(components);
});
}
});
}
}
function main() {
platform = system.args[1];
var extFile = system.args[2];
if (filePerExtension) {
makeAnchor = makeAnchorSeparate;
makeLink = makeLinkSeparate;
} else {
makeAnchor = makeAnchorPackaged;
makeLink = makeLinkPackaged;
titlePrefix = '#' + titlePrefix;
sectionPrefix = '#' + sectionPrefix;
}
// Process extensions
if (extFile.lastIndexOf('.aix') === extFile.length - 4) {
new JSZip().loadAsync(fs.read(extFile, {mode: 'rb'}))
.then(readExtensionsAIX(writeExtensionDocs))
.catch(function (err) {
console.log(err);
phantom.exit();
throw err;
});
}
}
if (system.args.length !== 3) {
console.log('Usage: docgen.js <platform-name> <extension-path>');
console.log('Example: docgen.js Microbit edu.mit.appinventor.ble.aix');
phantom.exit(1);
} else {
main();
}