omniJS – old fashioned ASCII tree version of OO Outline

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
    );
})();

2 Likes

Or, using some utf8 corners:

parent
│
├─ child
│  │
│  ├─ alpha
│  │  │
│  │  ├─ lambda
│  │  │
│  │  ├─ mu
│  │  │
│  │  └─ nu
│  │
│  ├─ beta
│  │
│  └─ gamma
│
└─ sibling

cousin
│
├─ zeta
│  │
│  ├─ xi
│  │
│  ├─ omicron
│  │
│  └─ pi
│
├─ eta
│
└─ theta
(() => {
    '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
    );
})();