A first draft of an omniJS plugin for pasting arbitrary JSON from from clipboard as a tree diagram in OmniGraffle.
pasteJSONTree.omnijs.zip (2.5 KB)
JS Source
/* eslint-disable no-console */
/* eslint-disable no-undef */
/* eslint-disable no-return-assign */
/* eslint-disable spaced-comment */
/*{
"author": "Rob Trew",
"targets": ["omnigraffle"],
"type": "action",
"identifier": "com.robtrew.pastejsontree",
"version": "0.1",
"description": "JSON pasted as OmniGraffle tree",
"label": "Paste JSON as tree diagram",
"mediumLabel": "JSON Tree",
"paletteLabel": "JSON Tree",
}*/
(() => {
"use strict";
// main :: () -> Plugin
const main = () => Object.assign(
new PlugIn.Action(
selection => either(
msg => (
new Alert(
"Paste JSON as tree",
`No JSON in clipboard ?\n\n${msg}`
)
.show()
)
)(
x => console.log(JSON.stringify(x))
)(
bindLR(
jsonParseLR(
Pasteboard.general.string || ""
)
)(
jso => {
const
tree = jsoKeyTree(jso),
canvas = newNamedTopCanvas(
"JSON diagram"
);
return (
// In OmniFocus,
selection.view.canvas = canvas,
drawOGTree(canvas)(tree),
canvas.layout(),
// and in JavaScript.
Right(tree)
);
}
)
)
), {
validate: () => true
});
// ---------- LINKED TEXT TO LINKED SHAPES -----------
// newNamedTopCanvas :: String -> IO Canvas
const newNamedTopCanvas = name => {
// A new front canvas, with the given name.
const canvas = globalThis.addCanvas();
return (
canvas.name = name,
canvas.orderBefore(globalThis.canvases[0]),
canvas
);
};
// drawOGTree :: OG Canvas -> Tree a -> OG Shape
const drawOGTree = canvas =>
foldTree(x => xs =>
xs.reduce(
(a, shape) => (canvas.connect(a, shape), a),
(
newShape => (
newShape.text = [
"[ ]", "{ }"
].includes(x) ? (
x
) : JSON.stringify(x),
newShape
)
)(
canvas.addShape(
0 < xs.length ? (
"Circle"
) : "Rectangle",
new Rect(30, 30, 100, 100)
)
)
)
);
// ----------------- JS OBJECT TREE ------------------
// jsoKeyTree :: a -> Tree b
const jsoKeyTree = jsValue => {
const go = v =>
isAtom(v) ? (
Node(v)([])
) : (
Node(
Array.isArray(v) ? "[ ]" : "{ }"
)(
Object.keys(v).map(
k => Node(k)(
(() => {
const subValue = v[k];
return isAtom(subValue) ? (
[Node(subValue)([])]
) : [go(subValue)];
})()
)
)
)
);
return go(jsValue);
};
// isAtom :: a -> Bool
const isAtom = x =>
// True if x is an atomic value:
// Boolean, Number, String, or null.
("object" !== typeof x) || (null === x);
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Node :: a -> [Tree a] -> Tree a
const Node = v =>
// Constructor for a Tree node which connects a
// value of some kind to a list of zero or
// more child trees.
xs => ({
type: "Node",
root: v,
nest: xs || []
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => m.Left ? (
m
) : mf(m.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => e.Left ? (
fl(e.Left)
) : fr(e.Right);
// foldTree :: (a -> [b] -> b) -> Tree a -> b
const foldTree = f => {
// The catamorphism on trees. A summary
// value obtained by a depth-first fold.
const go = tree => f(
root(tree)
)(
nest(tree).map(go)
);
return go;
};
// jsonParseLR :: String -> Either String a
const jsonParseLR = s => {
// Either a message, or a JS value obtained
// from a successful parse of s.
try {
return Right(JSON.parse(s));
} catch (e) {
return Left(
`${e.message} (line:${e.line} col:${e.column})`
);
}
};
// nest :: Tree a -> [a]
const nest = tree => {
// Allowing for lazy (on-demand) evaluation.
// If the nest turns out to be a function –
// rather than a list – that function is applied
// here to the root, and returns a list.
const xs = tree.nest;
return "function" !== typeof xs ? (
xs
) : xs(root(x));
};
// root :: Tree a -> a
const root = tree =>
// The value attached to a tree node.
tree.root;
// MAIN ---
return main();
})();
If the clipboard contains an arbitrarily nested JSON string like:
{
"various": [
1,
2,
{
"alpha": "Aberdeen",
"beta": "Glasgow",
"gamma": "Edinburgh"
}
],
"other": null
}
then this plugin will create a new tree diagram, on a fresh canvas in the active document.