I'm trying to create a macro that collects all text (single words and word groups) between several kinds of quotation marks and round brackets:
Zwölf “Boxkämpfer” jagen ‘Viktor’ zum „Spaß‟ quer über den (Sylter) Deich.
I'm stuck at the point where I can save the found words/word groups to variables and to list them, e.g. via the Prompt with list action (but any other way to display and select them would be fine too).
The Prompt with list could show:
a - "Boxkämpfer"
b - "Viktor"
c - "Spaß"
d - (Sylter)
etc.
When I press the leading letter (or number), the selected variable is typed (or pasted) to an editor.
(() => {
"use strict";
// Lines of quoted strings only.
const main = () => {
const quoteChars = "“”‘’„‟()";
return groupBy(
on(eq)(
c => quoteChars.includes(c)
)
)(
" " + Application("Keyboard Maestro Engine")
.getvariable("quotedSource")
)
.reduce(
([s, xs], cs, i) => 0 !== i % 4 ? (
[`${s}${cs.join("")}`, xs]
) : Boolean(s) ? (
["", xs.concat(s)]
) : [s, xs],
["", []]
)[1]
.join("\n");
};
// --------------------- GENERIC ---------------------
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
// A pair of values, possibly of
// different types.
b => ({
type: "Tuple",
"0": a,
"1": b,
length: 2,
*[Symbol.iterator]() {
for (const k in this) {
if (!isNaN(k)) {
yield this[k];
}
}
}
});
// eq (==) :: Eq a => a -> a -> Bool
const eq = a =>
// True when a and b are equivalent in the terms
// defined below for their shared data type.
b => a === b;
// groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
const groupBy = eqOp =>
// A list of lists, each containing only elements
// equal under the given equality operator,
// such that the concatenation of these lists is xs.
xs => 0 < xs.length ? (() => {
const [h, ...t] = xs;
const [groups, g] = t.reduce(
([gs, a], x) => eqOp(x)(a[0]) ? (
Tuple(gs)([...a, x])
) : Tuple([...gs, a])([x]),
Tuple([])([h])
);
return [...groups, g];
})() : [];
// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
const on = f =>
// e.g. groupBy(on(eq)(length))
g => a => b => f(g(a))(g(b));
// MAIN ---
return main();
})();
FWIW the python itertools module supplies a pre-defined groupby (inherited from the SML and Haskell tradition – see the preface to the itertools documentation) so we could quickly sketch this kind of thing in python too,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''Lines containing the string quoted in the input'''
from os import environ
from itertools import groupby
from functools import reduce
# quotedTokens :: String -> [String]
def quotedTokens(source):
'''List of quoted tokens in the source'''
quoteChars = "“”‘’„‟()"
def go(a, ipair):
s, xs = a
i, (_, v) = ipair
return (s + "".join(v), xs) if bool(i % 4) else (
"", xs + [s]
)
return [
x for x in reduce(
go,
enumerate(
groupby(
" " + source,
lambda c: c in quoteChars
)
),
("", [])
)[1] if x
]
if __name__ == '__main__':
print(
'\n'.join(
quotedTokens(environ["KMVAR_localSource"])
)
)
but with JavaScript for Automation at least we know which version we are running.
(People often have more than one python on their system, installed at variable paths, and Python 2 requires a special utf8 incantation, whereas Python 3 doesn't)
(() => {
"use strict";
// Lines of quoted strings only.
// quotedTokens :: String -> [String]
const quotedTokens = source => {
// A list of the quoted tokens in the source.
const quoteChars = "“”‘’„‟()";
return groupBy(
on(eq)(
c => quoteChars.includes(c)
)
)(` ${source}`)
.reduce(
([s, xs], cs, i) => 0 !== i % 4 ? (
[`${s}${cs.join("")}`, xs]
) : Boolean(s) ? (
["", xs.concat(s)]
) : [s, xs],
["", []]
)[1];
};
const main = () => {
const
prefixes = "abcdefghijklmnopqrstuvwxyz0123456789";
const
tokens = quotedTokens(
Application("Keyboard Maestro Engine")
.getvariable("quotedSource")
);
return prefixes.length >= tokens.length ? (
zipWith(
prefix => token => `${prefix} - ${token}`
)(
prefixes.split("")
)(
tokens
).join("\n")
) : "Not enough prefixes defined for token list";
};
// --------------------- GENERIC ---------------------
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
// A pair of values, possibly of
// different types.
b => ({
type: "Tuple",
"0": a,
"1": b,
length: 2,
*[Symbol.iterator]() {
for (const k in this) {
if (!isNaN(k)) {
yield this[k];
}
}
}
});
// eq (==) :: Eq a => a -> a -> Bool
const eq = a =>
// True when a and b are equivalent in the terms
// defined below for their shared data type.
b => a === b;
// groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
const groupBy = eqOp =>
// A list of lists, each containing only elements
// equal under the given equality operator,
// such that the concatenation of these lists is xs.
xs => 0 < xs.length ? (() => {
const [h, ...t] = xs;
const [groups, g] = t.reduce(
([gs, a], x) => eqOp(x)(a[0]) ? (
Tuple(gs)([...a, x])
) : Tuple([...gs, a])([x]),
Tuple([])([h])
);
return [...groups, g];
})() : [];
// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
const on = f =>
// e.g. groupBy(on(eq)(length))
g => a => b => f(g(a))(g(b));
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
// A list constructed by zipping with a
// custom function, rather than with the
// default tuple constructor.
xs => ys => xs.map(
(x, i) => f(x)(ys[i])
).slice(
0, Math.min(xs.length, ys.length)
);
// MAIN ---
return main();
})();