Ver 0.4
Significantly faster menu of KM Variables by descending size.
List KM variables by size, and offer deletion of a selection.kmmacros (27.0 KB)
JS Source
(() => {
"use strict";
// LIST KEYBOARD MAESTRO VARIABLES
// SORTED BY DESCENDING SIZE
// OPTIONALLY EMPTY THE CONTENTS OF SELECTED VARIABLES
// CAUTION: NOT UNDOABLE
// Ver 0.4
/* * *
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.
* * */
// main :: IO ()
const main = () =>
either(
message => "User cancelled" !== message ? (
alert("KM Variables")(message)
) : message
)(
alert("Deleted")
)(
bindLR(
menuOfKMVariablesLR(
kmVarsBySize()
)
)(
selectedStrings => deletedWhereConfirmedLR(
selectedStrings
)
)
);
// deletedWhereConfirmedLR :: [String] -> Either String String
const deletedWhereConfirmedLR = ks => {
const
kmVarNames = ks.map(k => k.split(/\t/u)[1]),
indentedListing = `\t${kmVarNames.join("\n\t")}`;
return "Delete" !== confirm("Selected KM variables")(
`Delete:\n\n${indentedListing}`
)("Delete") ? (
Left("User cancelled")
) : (() => {
const
kme = Application("Keyboard Maestro Engine");
return (
kmVarNames.forEach(k => {
kme.setvariable(k, {
to: ""
});
}),
Right(indentedListing)
);
})();
};
// kmVarsBySize :: () -> [(String, Int)]
const kmVarsBySize = () => {
const
kme = Application("Keyboard Maestro Engine"),
kmVars = kme.variables;
return sortBy(
flip(comparing(snd))
)(
zipWith(
k => v => Tuple(k)(v.length)
)(
kmVars.name()
)(
kmVars.value()
)
);
};
// menuOfKMVariablesLR :: [(String, Int)] ->
// IO Either String String
const menuOfKMVariablesLR = varsBySize => {
const colWidth = varsBySize[0][1].toString().length;
return showMenuLR(true)(
`${varsBySize.length} ` + (
"KM variables by descending size"
)
)(
"⌘-Click for multiple selections"
)(
varsBySize.map(
kv => {
const
valueString = justifyRight(
colWidth
)(" ")(snd(kv).toString());
return `${valueString}\t${fst(kv)}`;
}
)
)([]);
};
// ----------------------- JXA -----------------------
// alert :: String -> String -> IO String
const alert = title =>
s => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
return (
sa.activate(),
sa.displayDialog(s, {
withTitle: title,
buttons: ["OK"],
defaultButton: "OK"
}),
s
);
};
// confirm :: String -> String -> String -> IO String
const confirm = title =>
prompt => buttonName => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
sa.activate();
try {
return sa.displayDialog(prompt, {
withTitle: title,
buttons: ["Cancel", buttonName],
defaultButton: "Cancel"
}).buttonReturned;
} catch (e) {
return "Cancel";
}
};
// showMenuLR :: Bool -> String -> String ->
// [String] -> String -> Either String [String]
const showMenuLR = blnMult =>
// An optionally multi-choice menu, with
// a given title and prompt string.
// Listing the strings in xs, with
// the the string `selected` pre-selected
// if found in xs.
title => prompt => xs =>
selected => 0 < xs.length ? (() => {
const sa = Object.assign(
Application("System Events"), {
includeStandardAdditions: true
});
sa.activate();
const v = sa.chooseFromList(xs, {
withTitle: title,
withPrompt: prompt,
defaultItems: xs.includes(selected) ? (
[]
) : [],
okButtonName: "OK",
cancelButtonName: "Cancel",
multipleSelectionsAllowed: blnMult,
emptySelectionAllowed: false
});
return Array.isArray(v) ? (
Right(v)
) : Left("User cancelled");
})() : Left(`${title}: No items to choose from.`);
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
b => ({
type: "Tuple",
"0": a,
"1": b,
length: 2
});
// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = m =>
mf => undefined !== m.Left ? (
m
) : mf(m.Right);
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => "Either" === e.type ? (
undefined !== e.Left ? (
fl(e.Left)
) : fr(e.Right)
) : undefined;
// comparing :: (a -> b) -> (a -> a -> Ordering)
const comparing = f =>
x => y => {
const
a = f(x),
b = f(y);
return a < b ? -1 : (a > b ? 1 : 0);
};
// flip :: (a -> b -> c) -> b -> a -> c
const flip = op =>
// The binary function op with
// its arguments reversed.
1 < op.length ? (
(a, b) => op(b, a)
) : (x => y => op(y)(x));
// fst :: (a, b) -> a
const fst = tpl =>
// First member of a pair.
tpl[0];
// justifyRight :: Int -> Char -> String -> String
const justifyRight = n =>
// The string s, preceded by enough padding (with
// the character c) to reach the string length n.
c => s => n > s.length ? (
s.padStart(n, c)
) : s;
// list :: StringOrArrayLike b => b -> [a]
const list = xs =>
// xs itself, if it is an Array,
// or an Array derived from xs.
Array.isArray(xs) ? (
xs
) : Array.from(xs || []);
// snd :: (a, b) -> b
const snd = tpl =>
// Second member of a pair.
tpl[1];
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
const sortBy = f =>
xs => list(xs).slice()
.sort((a, b) => f(a)(b));
// zipWith :: [a] -> [b] -> [(a, b)]
const zipWith = f =>
// The paired members of xs and ys, up to
// the length of the shorter of the two lists.
xs => ys => Array.from({
length: Math.min(xs.length, ys.length)
}, (_, i) => f(xs[i])(ys[i]));
// MAIN ---
return main();
})();