Display an outline as a nested table: Difference between revisions
Display an outline as a nested table (view source)
Revision as of 15:03, 6 September 2021
, 2 years ago→{{header|JavaScript}}: Updated primitives, reshaped to handle forests (multiple roots) and foreground parallels with other versions
(→{{header|JavaScript}}: Updated primitives, reshaped to handle forests (multiple roots) and foreground parallels with other versions) |
|||
Line 673:
=={{header|JavaScript}}==
<lang javascript>(() => {
// ----------- NESTED TABLES FROM OUTLINE ------------
// wikiTablesFromOutline :: [String] -> String -> String
const wikiTablesFromOutline = colorSwatch =>
outline => forestFromIndentedLines(
indentLevelsFromLines(lines(outline))
)
.map(wikiTableFromTree(colorSwatch));
// wikiTableFromTree :: [String] -> Tree String -> String
const wikiTableFromTree = colorSwatch =>
compose(
wikiTableFromRows,
levels,
paintedTree(colorSwatch),
widthLabelledTree,
ap(paddedTree(""))(treeDepth)
);
// ---------------------- TEST -----------------------
// main :: IO ()
const main = () => {
Line 688 ⟶ 709:
and write out a table with 'colspan' values
either as a wiki table,
or as HTML.`;
"#e6ffff",
};
//
//
// [Tree String]
const forestFromIndentedLines = tuples => {
const [indented, body] = Array.from(
xs[0]
),
[tree, rest] = Array.from(
span(compose(lt(indented), fst))(
tail(xs)
)
);
// followed by the rest.
return [
return go(tuples);
};
// indentLevelsFromLines :: [String] -> [(Int, String)]
const indentLevelsFromLines = xs => {
const
pairs = xs.map
span(isSpace)(list(x))
return 0 < i ? (
i < a ? i : a
) : a;
},
Infinity
);
return [Infinity, 0].includes(indentUnit) ? (
pairs
) : pairs.map(first(n => n / indentUnit));
};
// ------------ TREE PADDED TO EVEN DEPTH ------------
// paddedTree :: a
const paddedTree = padValue =>
// with empty nodes
node =>
const go = n
return Node(root(tree))(
(
0 < children.length ? (
children
) : [Node(padValue)([])]
).map(go(n - 1))
);
})() : tree;
return go(depth)(node);
};
// treeDepth :: Tree a -> Int
const treeDepth = tree =>
foldTree(
() => xs => 0 < xs.length ? (
1 + maximum(xs)
) : 1
)(tree);
// ------------- SUBTREE WIDTHS MEASURED -------------
// widthLabelledTree :: Tree a -> Tree (a, Int)
const widthLabelledTree = tree =>
// A tree in which each node is labelled with
// the width of its own subtree.
foldTree(x => xs =>
0 < xs.length ? (
Node(Tuple(x)(
xs.reduce(
(a, node) => a + snd(root(node)),
0
)
))(xs)
) : Node(Tuple(x)(1))([])
)(tree);
// -------------- COLOR SWATCH APPLIED ---------------
// paintedTree :: [String] -> Tree a -> Tree (String, a)
const paintedTree = colorSwatch =>
tree => 0 < colorSwatch.length ? (
Node(
Tuple(colorSwatch[0])(root(tree))
)(
zipWith(compose(fmapTree, Tuple))(
cycle(colorSwatch.slice(1))
)(
nest(tree)
)
)
) : fmapTree(Tuple(""))(tree);
// --------------- WIKITABLE RENDERED ----------------
// wikiTableFromRows ::
// [[(String, (String, Int))]] -> String
const wikiTableFromRows = rows => {
const
cw = color => width => {
const go = w =>
1 < w ? (
`colspan=${w} `
) : "";
return `style="background:${color}; "` + (
` ${go(width)}`
);
cellText = ctw =>
const [color, tw] = Array.
return 0 < txt.length ? (
`| ${cw(color)(width)}| ${txt}`
) : "| |";
},
classText = "class=\"wikitable\"",
styleText = "style=\"text-align:center;\"",
header = `{| ${classText} ${styleText}\n|-`,
tableBody = rows.map(
cells => cells.map(cellText).join("\n")
).join("\n|-\n");
return `${header}\n${tableBody}\n|}`;
};
// ------------------ GENERIC TREES ------------------
const Node = v =>
// value of some
// more child
xs =>
type:
root:
nest: xs ||
// fmapTree :: (a -> b) -> Tree a -> Tree
const fmapTree = f => {
// structure-preserving application of f
// to each root in the existing tree.
const go = t => Node(
f(t.root)
)(
t.nest.map(go)
);
return go;
};
//
const foldTree = f => {
// The catamorphism on trees. A summary
// value obtained by a depth-first fold.
const go = tree
);
return go;
};
// levels :: Tree a -> [[a]]
const levels = tree
// A list of lists, grouping the root
// values of each level of the tree.
const [h, ...t] = 0 < a.length ? (
) : [
[],
[]
];
return [
[node.root, ...h],
...node.nest.slice(0)
.reverse()
.reduce(go, t)
];
};
return go([], tree);
};
// 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(tree));
};
// root :: Tree a -> a
const root = tree =>
// The value attached to a tree node.
tree.root;
// --------------------- GENERIC ---------------------
// Just :: a -> Maybe a
const Just = x => ({
type:
Nothing: false,
Just: x
});
// Nothing :: Maybe a
const Nothing = () => ({
type:
Nothing: true
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
length: 2
});
// apFn :: (a -> b -> c) -> (a -> b) -> (a -> c)
const ap = f =>
// Applicative instance for functions.
// f(x) applied to g(x).
g => x => f(x)(
g(x)
);
// bimap :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
const bimap = f =>
// Tuple instance of bimap.
// A tuple of the application of f and g to the
// first and second values respectively.
g => tpl => Tuple(f(tpl[0]))(
g(tpl[1])
);
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
// composition of all the functions in fs.
fs.reduce(
(f, g) => x => f(g(x)),
x => x
);
// cycle :: [a] -> Generator [a]
const cycle = function*
// An infinite repetition of xs,
// from which an arbitrary prefix
// may be taken.
const lng = xs.length;
let i = 0;
while (true) {
yield
i = (1 + i) % lng;
}
};
//
const
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
xy =>
const tpl = Tuple(f(xy[0]))(xy[1]);
Array.from(tpl)
// fst :: (a, b) -> a
const fst = tpl =>
// First member of a pair.
tpl[0];
// isSpace :: Char -> Bool
const isSpace = c =>
// True if c is a white space character.
(/\s/u).test(c);
// length :: [a] -> Int
const length = xs =>
// Returns Infinity over objects without finite
// length. This enables zip and zipWith to choose
// the shorter argument when one is non-finite,
// like cycle, repeat etc
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
xs.length
) : Infinity;
// lines :: String -> [String]
const lines = s =>
// A list of strings derived from a single
// string delimited by newline and or CR.
0 < s.length ? (
s.split(/[\r\n]+/u)
) : [];
//
const
// or an Array derived from xs.
Array.isArray(xs) ? (
xs
) :
//
const
//
const
// The largest value in a non-empty list.
ys => 0 < ys.length ? (
ys.slice(1).reduce(
(a, y) => y > a ? (
y
) : a, ys[0]
)
) : undefined
)(list(xs));
// snd :: (a, b) -> b
const snd = tpl =>
// Second member of a pair.
tpl[1];
// span :: (a -> Bool) -> [a] -> ([a], [a])
const span = p =>
// all satisfy p, tupled with the remainder of xs.
const i = xs.findIndex(x
) : Tuple(xs)([]);
};
// tail :: [a] -> [a]
const tail = xs =>
// A new list consisting of all
// items of xs except the first.
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
(ys => 0 < ys.length ? ys.slice(1) : [])(
xs
)
) : (take(1)(xs), xs);
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n
// The first n elements of a list,
// string of characters, or stream.
xs => "GeneratorFunction" !== xs
.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
//
const
// Just a tuple of the head of xs and its tail,
// Or Nothing if xs is an empty list.
const lng = length(xs);
// Lazy generator
const nxt = take(1)(xs);
return 0 < nxt.length ? (
Just(Tuple(nxt[0])(xs))
) : Nothing();
})()
) : Nothing();
};
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f
// A list with the length of the shorter of
// xs and
// default tuple constructor.
xs => ys => {
const n = Math.min(length(xs), length(ys));
return Infinity > n ? (
(([as, bs]) => Array.from({
length: n
}, (_, i) => f(as[i])(
bs[i]
)))([xs, ys].map(
take(n)
))
) : zipWithGen(f)(xs)(ys);
};
// zipWithGen :: (a -> b -> c) ->
// Gen [a] -> Gen [b] -> Gen [c]
const zipWithGen = f => ga => gb => {
const go = function* (ma, mb) {
let
a = ma,
b = mb;
while (!a.Nothing && !b.Nothing) {
const
ta = a.Just,
tb = b.Just;
yield f(fst(ta))(fst(tb));
a = uncons(snd(ta));
b = uncons(snd(tb));
}
};
return go(uncons(ga), uncons(gb));
};
// MAIN ---
return main();
// return sj(main());
})();</lang>
{{Out}}
{| class="wikitable" style="text-align:
|-
| style="background:
|-
| style="background:
| style="background:
| style="background:
|-
| style="background:
| style="background:
| style="background:
| style="background:
| style="background:
| style="background:
| style="background:
|-
|
|
|
|
| style="background:
|
|
|}
|