Skip to content

Instantly share code, notes, and snippets.

@steveblamey
Forked from rich-biker/documentation.ajs
Last active October 9, 2019 17:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steveblamey/19ac84ff21ea572aff5dee695d682d15 to your computer and use it in GitHub Desktop.
Save steveblamey/19ac84ff21ea572aff5dee695d682d15 to your computer and use it in GitHub Desktop.
Generate Markdown documentation from a driving view in Archi using jArchi scripting.
/*
Script: Documentation Generation
Purpose: To generate architecture output based on a driving view
Author: Richard Heward - Tame Blue Lion Ltd
This generates a markdown file with the embedded images and text based upon a driving view in Archi of groups that trigger each other and embedded views.
See my blog for a more detailed explaination. https://www.tamebluelion.co.uk/archimate-documentation
Setting embed to false will have the images saved to file and references placed in the document. It's then up to your markdown engine. This isn't that well tested.
Setting
Note - markdown can be converted to PDF / Word Docs or anything. I've used pandoc command line to do this.
Date: 8-Oct-2019
2019-10-09 Steve Blamey - Some changes to add sketch views and pandoc yaml
frontmatter so that pandoc makes the toc, etc. Some frontmatter settings can
be added as view properties.
*/
// Get current date
var currentDate = new Date().toLocaleString("en-US", {day: 'numeric', month: 'short', year: 'numeric'});
console.show();
console.clear();
console.log("Documentation Generation @", currentDate);
var Sections = [];
var nextOne = null;
var outInfo = "";
var theToc = "";
var path = "";
var fileName = "";
var embed = false;
var IncludeViewElements = true; // a property called 'ExcludeViewElements' on the driving view will override this.
var drivingView = "Docs"; // will be set to the selected view that has all the groups and view references on.
var documentviewObjects = true;
function generateLink(theString) {
// this function thanks to Steven Mileham
var regex = /[\[\]\(\)\#\\\/\"]/gi;
return "#"+theString.toLowerCase().replace(regex, "")
.replaceAll(" ", "-")
.replaceAll("\<", "lt")
.replaceAll("\>", "gt");
}
function getviewObjects(view) {
// document the elements (not relations) on the view
if (documentviewObjects) {
var elements = $(view).children("element");
// Output child element docs, if elements found
if (elements.length != 0) {
outInfo += "\n### View Elements \n";
elements.each(function(viewObj) {
//console.log(" viewObj: ", viewObj.name);
outInfo += "\n[" +viewObj.type +"] __" +viewObj.name +"__ \t" +viewObj.documentation;
outInfo += "\n";
});
outInfo += "\n\n";
}
}
}
function getViews(Level, Levelobj) {
var thisView = null;
var thisPath = "";
var imageURL = "";
if (!Levelobj) {
return null;
}
else {
// find the view references composed within this group
$(Levelobj).children().each(function(viewRefs) {
if ( (viewRefs) && ((viewRefs.type == 'archimate-diagram-model' ) || (viewRefs.type == 'sketch-model' )) ) {
outputHdr (Level+1, viewRefs.name);
// Find the actual linked views
var viewsCollection = $('archimate-diagram-model');
viewsCollection.add($('sketch-model'));
viewsCollection.each(function(linkedView) {
if (linkedView.name == viewRefs.name) {
console.log(" Linked Item: ", linkedView.name, " -> ", linkedView.id);
var bytes = $.model.renderViewAsBase64(linkedView, "PNG", {scale: 1, margin: 10});
if (embed) {
outInfo+="\n!["+linkedView.name+"](data:image/png;base64,"+bytes+")\n";
}
else {
thisPath = path +linkedView.name;
$.fs.writeFile(thisPath +".png", bytes, "BASE64");
imageURL = thisPath.replaceAll(" ","%20");
outInfo+="\n!["+linkedView.name+"][" +linkedView.name +"]\n";
outInfo += "\n[" +linkedView.name +"]: " +imageURL +".png\n";
}
linkedView.documentation!=""?outInfo+="\n"+linkedView.documentation+"\n":true;
// Now document the view details, by default, if there is no ExcludeViewElements property on the driving view.
if (IncludeViewElements) {
getviewObjects(linkedView);
}
}
});
}
});
}
}
function outputHdr (Level, Name, Doc) {
var indent = "";
var tocIndent = "";
for (var i = 0; i < Level; i++) {
indent = indent +"#";
}
for (var j = 0; j < Level-1; j++) { // ToC needs one less indent tab.
tocIndent = tocIndent +"\t";
}
console.log("Level ", Level, " ", indent, " (", Name, ")");
/*
.repeat doesn't work on the PC?..
var indent = "#".repeat(Level);
var tocIndent = " ".repeat(Level);
*/
var outHdr = "\n"+indent +" " +Name+"\n";
var thisLink = generateLink(Name);
// if (Level == 1) {
//outInfo += '<div style="page-break-before: always;"></div>';
//outInfo += "\n --- \n"; // horiz line before level 1s
//outInfo += '\\newpage'; // latex pagebreak
//}
outInfo += "\n" +outHdr;
outInfo += "\n" +"[](" +thisLink +")\n";
if (Doc) {
outInfo += "\n" +Doc;
}
// console.log(indent +" " +Name);
// theToc += tocIndent +"* [" +Name +"](" +thisLink +")\n";
}
function getSubGroups(Group, lvl) {
var nextsubGroup = null;
var nextsLevel = lvl + 1;
$(Group).outRels("composition-relationship").each(function(rel) {
var incomingRels2 = $(rel.target).inRels("triggering-relationship").size();
var outgoingRels2 = $(rel.target).outRels("triggering-relationship").size();
var subGroup = rel.target;
if (incomingRels2 == 0) {
// It's the first child in the sub group
Sections.push([subGroup,nextsLevel]); // from lvl
// There's another trigger out so find the next..
nextsubGroup = $(subGroup).outRels("triggering-relationship").first();
// add the next one onto the array
if (nextsubGroup) {
Sections.push([nextsubGroup.target,nextsLevel]); //from lvl
// recurse to get the next subgroup
getSubGroups(nextsubGroup.target, nextsLevel);
}
}
else {
// just ignore the rest, the getSubGroups will take care of them.
return(null);
}
});
}
function getNextGroup(Group, glvl) {
var nextGroup = null;
var outgoingRels = $(Group).outRels("triggering-relationship").size();
var nextLevel = glvl + 1;
//check for sub groups
getSubGroups(Group, glvl);
if (outgoingRels == 1) {
// There's a triggering out so find the next..
nextGroup = $(Group).outRels("triggering-relationship").first();
if (nextGroup) {
// add the next one onto the array
Sections.push([nextGroup.target,glvl]);
// recurse to get the next group
getNextGroup (nextGroup.target, glvl);
}
else {
window.alert("The groups should all use triggering relationships");
return(null);
}
}
}
function useDrivingView() {
var nextGroup = null;
drivingView = selection.filter("archimate-diagram-model").first();
if (!drivingView) {
window.alert("Please open and select a Driving View for the documentation");
}
else
{
console.log("Driving view is: " + drivingView.name);
if (drivingView.prop("ExcludeViewElements")) {
console.log ("Excluding View Elements")
IncludeViewElements = false;
}
else {
IncludeViewElements = true;
}
$(drivingView).children("grouping").each(function(thisGroup) {
if (thisGroup) {
var incomingRels = $(thisGroup).inRels("triggering-relationship").size();
var outgoingRels = $(thisGroup).outRels("triggering-relationship").size();
if (incomingRels == 0) {
// It's the first section, put it in the array.
Sections.push([thisGroup,1]);
getSubGroups(thisGroup, 1);
if (outgoingRels == 1) {
// There is a next group, lets get it..
nextOne = $(thisGroup).outRels("triggering-relationship").first();
// Add the next one to the array
if (nextOne) {
Sections.push([nextOne.target, 1]);
getNextGroup (nextOne.target, 1);
}
else {
window.alert("The groups should all use triggering relationships");
return(null);
}
}
return(null);
}
else {
// just ignore the rest, the getNextGroup will take care of them.
return(null);
}
}
});
}
return(true);
} // end of useDrivingView
var docGen = "";
if (useDrivingView())
{
var exportFile = window.promptSaveFile({ title: "Export to File", filterExtensions: [ "*.md" ], fileName: drivingView.name+".md" } );
// where's the path. Find where the last slash delimiter is
var lastSlash = "";
if (exportFile) {
if (exportFile.indexOf("/") == -1) {
lastSlash = exportFile.lastIndexOf("\\"); // Windows
} else {
lastSlash = exportFile.lastIndexOf("/"); // Mac or Linux
}
path = exportFile.substring(0,lastSlash+1);
fileName = exportFile.substring(lastSlash+1, exportFile.length);
console.log("path: ", exportFile.substring(0,lastSlash+1));
console.log("fileName: ", exportFile.substring(lastSlash+1,exportFile.length));
// Define some yaml frontmatter for pandoc
var frontmatter = "---\n";
frontmatter += "title: "+drivingView.name+"\n";
frontmatter += "subtitle: "+drivingView.prop('subtitle')+"\n";
frontmatter += "author: "+drivingView.prop('author')+"\n";
frontmatter += "institute: "+drivingView.prop('institute')+"\n"; //company name
frontmatter += "logo: "+drivingView.prop('logo')+"\n"; //path to your logo file
frontmatter += "date: "+currentDate+"\n";
frontmatter += "toc: true\n";
frontmatter += "toc-depth: 2\n";
frontmatter += "numbersections: true\n";
frontmatter += "mainfont: 'Roboto'\n";
frontmatter += "papersize: a4\n";
frontmatter += "pagestyle: report\n";
frontmatter += "geometry:\n";
frontmatter += " - top=3.5cm\n";
frontmatter += " - bottom=3.5cm\n";
frontmatter += " - right=2.5cm\n";
frontmatter += " - left=2.5cm\n";
frontmatter += " - footskip=2cm\n";
frontmatter += "---\n";
// go through the array, and output.
for (var i = 0; i < Sections.length; i++) {
outputHdr (Sections[i][1], Sections[i][0].name, Sections[i][0].documentation);
getViews (Sections[i][1], Sections[i][0]);
}
//docGen = "# "+drivingView.name +"\n"
//docGen += "\n---\n";
//docGen += theToc +"\n"; // pandoc will make a toc for us
docGen += frontmatter;
docGen += outInfo;
//docGen += "\n\nGenerated on: " +currentDate;
$.fs.writeFile(exportFile, docGen);
}
}
// end of script
console.log("Done");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment