From:
To:
parent
|
+- child
| |
| +- alpha
| | |
| | +- lambda
| | |
| | +- mu
| | |
| | `- nu
| |
| +- beta
| |
| `- gamma
|
`- sibling
cousin
|
+- zeta
| |
| +- xi
| |
| +- omicron
| |
| `- pi
|
+- eta
|
`- theta
To test on macOS:
- Copy the whole of the source code below, and paste it into Script Editor,
- check that the Script Editor language selector (top left) is set to JavaScript
- check that an outline is open in OO, and run.
- The ASCII-tree version will be placed in the clipboard.
(() => {
'use strict';
// ooJSContext :: () -> String
const ooJSContext = () => {
// GENERIC -----------------------------------------------------------
// Node :: a -> [Tree a] -> Tree a
const Node = (v, xs) => ({
type: 'Node',
root: v, // any type of value (but must be consistent acros tree)
nest: xs
});
// append (++) :: [a] -> [a] -> [a]
// append (++) :: String -> String -> String
const append = (xs, ys) => xs.concat(ys);
// concat :: [[a]] -> [a]
// concat :: [String] -> String
const concat = xs =>
xs.length > 0 ? (() => {
const unit = typeof xs[0] === 'string' ? '' : [];
return unit.concat.apply(unit, xs);
})() : [];
// cons :: a -> [a] -> [a]
const cons = (x, xs) => [x, ...xs];
// intercalate :: [a] -> [[a]] -> [a]
// intercalate :: String -> [String] -> String
const intercalate = (sep, xs) =>
concat(intersperse(sep, xs));
// intersperse(0, [1,2,3]) -> [1, 0, 2, 0, 3]
// intersperse :: Char -> String -> String
// intersperse :: a -> [a] -> [a]
const intersperse = (sep, xs) => {
const bool = (typeof xs)[0] === 's';
return xs.length > 1 ? (
(bool ? concat : x => x)(
(bool ? (
xs.split('')
) : xs)
.slice(1)
.reduce((a, x) => a.concat([sep, x]), [xs[0]])
)) : xs;
};
// lines :: String -> [String]
const lines = s => s.split(/[\r\n]/);
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) => xs.map(f);
// replicate :: Int -> a -> [a]
const replicate = (n, x) =>
Array.from({
length: n
}, () => x);
// showJSON :: a -> String
const showJSON = x => JSON.stringify(x, null, 2);
// unlines :: [String] -> String
const unlines = xs => xs.join('\n');
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = (f, xs, ys) =>
Array.from({
length: Math.min(xs.length, ys.length)
}, (_, i) => f(xs[i], ys[i], i));
// DRAWTREE ----------------------------------------------------------
// draw :: Tree String -> [String]
const draw = node => {
// shift :: String -> String -> [String] -> [String]
const shift = (first, other, xs) =>
zipWith(
append,
cons(first, replicate(xs.length - 1, other)),
xs
);
// drawSubTrees :: [Tree String] -> [String]
const drawSubTrees = xs => {
const lng = xs.length;
return lng > 0 ? (
lng > 1 ? append(
cons(
'│',
shift('+- ', '│ ', draw(xs[0]))
),
drawSubTrees(xs.slice(1))
) : cons('│', shift('`- ', ' ', draw(xs[0])))
) : [];
};
return append(
lines(node.root),
drawSubTrees(node.nest)
);
};
// drawForest :: Forest String -> String
const drawForest = trees =>
trees.map(drawTree)
.join('\n\n');
// drawTree :: Tree String -> String
const drawTree = tree =>
unlines(draw(tree));
// OO ----------------------------------------------------------------
// fmapPureOO :: (OOItem -> a) -> OOItem -> Tree OOItem
const fmapPureOO = (f, item) =>
Node(f(item),
item.hasChildren ? (
item.children.map(x => fmapPureOO(f, x))
) : []);
// MAIN --------------------------------------------------------------
return drawForest(
fmapPureOO(x => x.topic, document.outline.rootItem).nest
);
};
// JXA JS CONTEXT --------------------------------------------------------
// standardAdditions :: () -> Application
const standardAdditions = () =>
Object.assign(Application.currentApplication(), {
includeStandardAdditions: true
});
// strClip :: String
const strClip = Application('OmniOutliner')
.evaluateJavascript(
'(' + ooJSContext + ')()'
);
return (
standardAdditions()
.setTheClipboardTo(strClip),
strClip
);
})();