Or FWIW, slightly more cautiously and functionally (easier to refactor, and better behaved with empty input)
JS alternative source
(() => {
'use strict';
const main = () => {
const
kme = Application('Keyboard Maestro Engine'),
kmValue = k => kme.getvariable(k);
const
hanging = monoSpacedHangingIndent(chr('0xA0'))(
parseInt(kmValue('monospacedLineWidth'))
)(
parseInt(kmValue('monospacedIndentWidth'))
);
return hanging(
kmValue('monospacedIndentLabel')
)(
kmValue('monospacedIndentPara')
);
};
// -------------------- HANGING INDENT --------------------
// monoSpacedHangingIndent :: Char ->
// Int -> Int -> String -> String -> String
const monoSpacedHangingIndent = indentChar =>
textWidth => indentWidth => outlineLabel => txt => {
const tab = indentChar.repeat(indentWidth);
return bindMay(
// Wrapped lines, if any.
uncons(''
.concat(...lines(txt))
.replace(/\s{2,}/g, ' ')
.match(RegExp(
'.{1,' + (
textWidth - indentWidth
) + '}(\\s|$)', 'g'
))
)
)(
compose(
unlines,
uncurry(cons),
bimap(
// First line,
append(outlineLabel + indentChar.repeat(
indentWidth - outlineLabel.length
))
)(
// and remaining lines.
map(append(tab))
)
)
)
};
// ------------------ GENERIC FUNCTIONS -------------------
// https://github.com/RobTrew/prelude-jxa
// Just :: a -> Maybe a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});
// Nothing :: Maybe a
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
b => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
// append (++) :: [a] -> [a] -> [a]
// append (++) :: String -> String -> String
const append = xs =>
// A list or string composed by
// the concatenation of two others.
ys => xs.concat(ys);
// 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])
);
// bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
const bindMay = mb =>
mf => mb.Nothing ? (
mb
) : mf(mb.Just);
// chr :: Int -> Char
const chr = x =>
String.fromCodePoint(x);
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
fs.reduce(
(f, g) => x => f(g(x)),
x => x
);
// cons :: a -> [a] -> [a]
const cons = x =>
xs => [x].concat(xs);
// 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
// newline-delimited string.
0 < s.length ? (
s.split(/[\r\n]/)
) : [];
// map :: (a -> b) -> [a] -> [b]
const map = f =>
// The list obtained by applying f
// to each element of xs.
// (The image of xs under f).
xs => [...xs].map(f);
// maybe :: b -> (a -> b) -> Maybe a -> b
const maybe = v =>
// Default value (v) if m is Nothing, or f(m.Just)
f => m => m.Nothing ? v : f(m.Just);
// uncons :: [a] -> Maybe (a, [a])
const uncons = xs =>
// Just a tuple of the head of xs and its tail,
// Or Nothing if xs is an empty list.
0 < xs.length ? (
Just(Tuple(xs[0])(xs.slice(1))) // Finite list
) : Nothing();
// uncurry :: (a -> b -> c) -> ((a, b) -> c)
const uncurry = f =>
// A function over a pair, derived
// from a curried function.
function() {
const
args = arguments,
xy = Boolean(args.length % 2) ? (
args[0]
) : args;
return f(xy[0])(xy[1]);
};
// unlines :: [String] -> String
const unlines = xs =>
// A single string formed by the intercalation
// of a list of strings with the newline character.
xs.join('\n');
// MAIN ---
return main();
})();