Code generation from OG UML?


#1

@SupportHumans and anyone else who may be paying attention:

I am playing around with the JS automation to try to generate code from UML diagrams… but I’m up against a few different issues.

I have tried a few different ways of accessing the properties and methods on the various JS APIs - things like foo.keys() or “for ( i in document.windows[0]) { console.log(i) }” so I can inspect the API and figure out what pieces and parts contain the data I need to spit out my code. I am wondering if I am missing something, because none of these techniques are working out very well.

I was wondering if there is, or if there are plans to, an integration of something like jQuery into the JS APIs so that there’s the ability to do something like “$(typeof Table).forEach(function() {//do stuff here})”?

Is there a way I can interface the OmniJS to use the developer tools in Chrome or Safari to example the objects/properties/values in the object model?

And finally, are there any examples out there of plugins or actions being used for code generation? I am looking at the JS APIs and considering whether trying to write something like that myself is worth the effort… especially considering the lack of tools for introspection/inspection or visualization, and the scanty documentation available at this point.


#2

Have you checked out https://omni-automation.com? There are some nice examples there I’ve used as a base in the past.


#3

Not quite sure which JSC embedding you are working with (JXA/osa, or omniJS inside omniGraffle), but neither is an embedding in a browser, so you won’t find any of the DOM objects that much of jQuery depends on.

The basic incantation for listing the objects available to the global context of a particular JS interpreter is, of course:

Object.getOwnPropertyNames(this)

(The DOM is, as you know, not part of Javascript – it’s just an API to any browser that a particular JS interpreter may be embedded in – where there are no HTML documents there is also no DOM)


#4

Can you use the Copy as JavaScript option from the context menu on canvas to generate some JavaScript? You might need to alter the output some to make it fit with your script. I can go into the Keyboard Shortcuts and assign a custom shortcut to Copy as JavaScript. I’d try using a System Event to trigger the command with a delay to give the copy time to work (it isn’t instant). It might help you get some information about the way to recreate some items on canvas. I’d love to hear back how it goes if you try this.

I’ve never seen an example like you describe. It’s worth sending an email to request it at omnigraffle@omnigroup.com. When you send feature requests for scripting, it helps to explain what you are trying to accomplish and what is limiting you. That way, we can tell if the solution matches your workflow.

Would using forEach work for any of this? Here’s an example from Sal. I wanted to check that all of the graphic names matched the graphic IDs for part of a test, and this is the approach he recommended for checking each graphic.id. Of course, this example works only at the per document level.

console.clear()
console.info("Beginning check of every graphic...")
canvases.forEach(function(cnvs){
	var cnvsName = cnvs.name
	cnvs.graphics.forEach(function(graphic, index){
		obj = new Object()
		obj.canvasName = cnvsName
		obj.layerName = graphic.layer.name
		obj.name = graphic.name
		obj.id = graphic.id
		obj.type = graphic instanceof Line ? "Line" : graphic.shape
		if (obj.type != "Line"){
			obj.image = graphic.image instanceof ImageReference ? true : false
		} else {
			obj.image = false
		}
		obj.locked = graphic.locked
		obj.prints = graphic.layer.prints
		objStr = JSON.stringify(obj)
		objIDStr = obj.id.toString()
		if (obj.name != objIDStr){
			console.error("Mismatch Graphic ID:",objStr)
		}
	})
})

Happy Scripting,

Lanette


#5

@draft8 - thank you for your comments…

I’m using the Automation menu, OmniJS inside OmniGraffle.

As for the comment about JS, blinks I had to think about it for a second… I’ve been working with C# and Java for so long, I was in the JS console in Graffle and was just thinking “WHERE ARE MY SELECTORS!!” lol I will try playing around with getOwnPropertyNames() and see what I can drum up… so yeah, I get what you mean about jQuery being essentially an HTML DOM library that has no role in something like OmniJS.

What I WOULD ask for is a way to a) visualize the object model… like watch variables in Visual Studio, or anywhere… where complex objects are displayed visually with “twisties” to expose their children. Breakpoints? Developer tools… :D I know that’s asking for a lot for something that’s supposed to be a convenience feature, you could spend years just fleshing out the JS tooling in OG. I appreciate what you’ve done tho… it looks awesome and I look forward to seeing it mature over time, man…

Ultimately my goal is to build UML docs in Graffle and then run a script that stubs out the object model with method signatures, “extends” statements, etc., using the Connection lines to establish inheritance chains and composition where it can be discerned based on the diagram. No code gen method is perfect, but if Graffle+automation can get be CLOSE I’ll be giddy… I am not yet sure if I will have to do the code generation into a text file, into the console or into an object inserted in the canvas…

I’ll report my progress tho! Thanks for the input. :)


#6

@lanettetest Yes! I am looking at the copy as JS feature to give me some guidance. Very good suggestion.


#7

My only real question about that code is whether it doesn’t seem a bit of a pity to interrogate every single graphic, over the automation interface, for all of its details, before actually testing whether it has an ID mismatch or not ?

Another approach might be to :

  • first harvest a list of any graphics which do have mismatching ids,
  • then apply a (possibly reusable) JSON generating function to just the stragglers in that list.

Perhaps something along the lines of:

(() => {
    'use strict';

    // GRAPHICS WITH NAMES NOT MATCHING THEIR IDs

    const main = () => {
        console.clear()
        console.info("Beginning check of every graphic...");

        const problems = misMatches();
        return console.log(
            0 < problems.length ? (
                problems.map(graphicJSON)
                .join('\n')
            ) : 'All graphic names match ids.'
        );
    };

    // misMatches :: OG () -> [Graphic]
    const misMatches = () =>
        concatMap(
            cnv => {
                const strCanvas = cnv.name;
                return concatMap(
                    layer => {
                        const strLayer = layer.name;
                        return concatMap(
                            g => g.name !== g.id.toString() ? [{
                                canvas: strCanvas,
                                layer: strLayer,
                                graphic: g,
                            }] : [],
                            layer.graphics
                        )
                    },
                    cnv.layers
                )
            },
            canvases
        );

    // graphicJSON :: {canvas::String, layer::String, graphic::Graphic}
    //                      -> JSON String
    const graphicJSON = dct => {
        const
            g = dct.graphic,
            strType = g.constructor.name,
            blnNotLine = 'Line' !== strType;
        return showJSON(
            Object.assign(
                ['name', 'id', 'locked']
                .reduce(
                    (a, k) => (a[k] = g[k], a), {}
                ), {
                    canvasName: dct.canvas,
                    layerName: dct.layer,
                    type: blnNotLine ? (
                        g.shape
                    ) : 'Line',
                    isImage: blnNotLine ? (
                        undefined !== g.image
                    ) : false,
                    prints: g.layer.prints
                }
            )
        );
    };

    // GENERIC FUNCTIONS ----------------------------------

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) => [].concat.apply([], xs.map(f));

    // showJSON :: a -> String
    const showJSON = x => JSON.stringify(x, null, 2);


    // MAIN ---
    return main();
})();


#8

dabs a tear from the corner of his eye

That code… good god that code… it’s poetry…


#9

Hahaha ! No satire please …


#10

Nah, man… no satire… coming from a world of corporate OO crap…

Declaring your methods as constants using Lambda syntax? That alone is like… wow… how freakin cool is that?

I haven’t spent much time doing JS outside of the browser, so this is all a bit new to me… you may have guessed that by my original message. ;)


#11

Cool idea! When using the first set of scripts, the graphicIDs weren’t stable, so the chance of a problem was much higher than it is now. I will try this change and see if it works!

Thanks,
Lanette


#12

So I know this is horrendously crappy code… so no worries, this isn’t going to be used for anything other than pre-alpha examination of the API… but I just wanted you to know I came up with this snipped to start giving me the data I’m looking for.

for (var p in Object.getOwnPropertyNames(Document.prototype)) {
	console.info("document[" + Object.getOwnPropertyNames(Document.prototype)[p] + "] is " + 
        document[Object.getOwnPropertyNames(Document.prototype)[p]])
}

#13

Getting somewhere - It’s extremely rudimentary, but it’s starting to look like something… which is nice. Even if it doesn’t turn out to be insanely complete… just the ability to spit out a starting place… that’s huge. Saves me countless time typing boilerplate code!


#14

Dude… check this out… I am finally getting the hang of this, finding the methods and properties I need to make this happen… here’s another screenshot for ya…

Maybe someday I can create a new codegen package that will spit out Java, C#, etc… that would be cool, aye? :)

var alertText = new String()
for (var i in g) {
	if (g[i].constructor.name == "Table") {
		var txt = "namespace Crawler.lib {\n"
        txt += "    public class " + g[i].graphics[0].text + "{\n"
        txt += "        public " + g[i].graphics[0].text + "() {\n"
        txt += "        }\n"
        txt += "    }\n"
		txt += "}"
		alertText += txt + "\n\n\n"
	}
}
new Alert("C# Code Gen", alertText).show(function(result){})
alertText = ""

Looks like this on my machine!


#15

Quick progress !


#16

Well, it’s far from perfect, and it’s still very kludgy and brittle… but, for the moment… it stubs me out way faster than I could do by hand, and after this it’s just touchup.

@all… thank you so much for your input… I intent (someday) to release this as a plugin… I will use the HELL out of such a thing… lots of work to go before that, but here’s the JS I have at the moment:

var g = document.windows[0].selection.canvas.graphics
var alertText = new String()
for (var i in g) {
	if (g[i].constructor.name == "Table") {
		var name = g[i].graphics[0].text
		var props = g[i].graphics[1].text.split(/[\r\n]+/)
		var funcs = g[i].graphics[2].text.split(/[\r\n]+/)
		// found a table, time to build code - namespace
		var txt = "namespace Crawler.lib {\n"
		// class declaration
		txt += "    public class " + g[i].graphics[0].text + "{\n"
	       // default constructor
            txt += "        public " + g[i].graphics[0].text + "() {\n"
		txt += "        }\n"			// here would go the properties
		txt += "        // properties below this comment\n"
		props.forEach(p => {
			txt += "        protected " + p + " " + p + "Prop { get; set; }\n"
		});
		// here would go the functions
		txt += "        // functions below this comment\n"
		funcs.forEach(f => {
			txt += "        public " + f + "{\n"
			txt += "        }\n"
		});
			txt += "    }\n"
		txt += "}"
		alertText += txt + "\n\n\n"
	}
}
new Alert("C# Code Gen", alertText).show(function(result){})
alertText = ""

Yeah, crappy-ass code, but it spits out semi-complete C# that I can actually just drop into files and tweak!


#17

Eventual features I’d like to have:

  • Syntax emitters for at least Java, C# and C++ maybe?
  • Includes and imports as well as the specified functions with scope, return type, name and function stubs
  • Optional for C#, saving all the classes split out into file names based on the classes following the given Namespace folder pattern
  • Or (also for C#), the option to save an entire namespace in one C# file (which is legal syntax in C#)

For my purposes, this would be sufficient… does anyone else have ideas or comments?


#18

Instead of an alert, would it possible to send the output to a file or to the clipboard or to another app like a (possibly AppleScript-able) text editor?