Possible code for a KM Execute a JavaScript for Automation action:
Expand disclosure triangle to view JS Source
(() => {
"use strict";
const test = `My apologies for this long and possibly convoluted process I am trying to describe. I wish I could sum it up a bit better than I did.
I am scouring the forums trying to figure out a way to count the first 160 characters of selected text and parse out the messages. I have to send text messages with 160 characters in length or less. If it is longer than that I have to type (1/2) or (1/3) at the end by breaking up the message that was typed. Then send (2/2) or (2/3) etc. I do this hundreds of times and I figured this was a job for Keyboard Maestro to select the text and parse it out into either two clipboards or three depending on how long the message is (it won't be longer than three).
If anyone has an idea to get me pointed in the right direction how to divide the message out that would be great. The added complication is that I would really like to end after a word and not cut a word off so basically look for the first space after 145 characters and chop the message into one, two or three different groups.
This seemed useful but not sure how to piece it together.`;
const main = () => {
const
maxTweetLength = 160,
// " (1/n)"
padLength = 6;
const
targetSize = maxTweetLength - padLength,
experimentalTweak = 5;
return twitterChunks(
targetSize - experimentalTweak
)(test)
// optionally checking lengths:
// .map(x => x.length)
.join("\n\n***\n\n");
};
// ---------------- TWITTER SEGMENTS -----------------
const twitterChunks = limit => s => {
const
wcs = mapAccumL(a => w => {
// Cost of an additional word and space.
const n = 1 + a + w.length;
return Tuple(n)(
Tuple(w)(n)
);
})(0)(
words(s)
)[1];
const go = allSofar =>
measuredWords => {
const
nextLimit = limit + allSofar,
chunk = takeWhile(
ab => nextLimit >= ab[1]
)(
measuredWords
),
rest = drop(chunk.length)(
measuredWords
);
return 0 < rest.length ? (
[chunk].concat(
go(last(chunk)[1])(rest)
)
) : [chunk];
};
const
chunks = 0 < wcs.length ? (
go(0)(wcs)
) : [],
total = chunks.length;
return chunks.map(
(wns, i) =>
`${wns.map(fst).join(" ")} (${1 + i}/${total})`
);
};
// --------------------- GENERIC ---------------------
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
b => ({
type: "Tuple",
"0": a,
"1": b,
length: 2
});
// drop :: Int -> [a] -> [a]
// drop :: Int -> Generator [a] -> Generator [a]
// drop :: Int -> String -> String
const drop = n =>
xs => xs.slice(n);
// fst :: (a, b) -> a
const fst = tpl =>
// First member of a pair.
tpl[0];
// last :: [a] -> a
const last = xs =>
// The last item of a list.
0 < xs.length ? (
xs.slice(-1)[0]
) : null;
// mapAccumL :: (acc -> x -> (acc, y)) ->
// acc -> [x] -> (acc, [y])
const mapAccumL = f =>
// A tuple of an accumulation and a list
// obtained by a combined map and fold,
// with accumulation from left to right.
acc => xs => [...xs].reduce(
(a, x) => {
const tpl = f(a[0])(x);
return [tpl[0], a[1].concat(tpl[1])];
},
[acc, []]
);
// takeWhile :: (a -> Bool) -> [a] -> [a]
// takeWhile :: (Char -> Bool) -> String -> String
const takeWhile = p =>
// The longest prefix of xs in which
// every element satisfies p.
xs => {
const n = xs.length;
return xs.slice(
0, 0 < n ? until(
i => n === i || !p(xs[i])
)(i => 1 + i)(0) : 0
);
};
// until :: (a -> Bool) -> (a -> a) -> a -> a
const until = p =>
// The value resulting from repeated applications
// of f to the seed value x, terminating when
// that result returns true for the predicate p.
f => x => {
let v = x;
while (!p(v)) {
v = f(v);
}
return v;
};
// words :: String -> [String]
const words = s =>
// List of space-delimited sub-strings.
s.split(/\s+/u);
// MAIN ---
return main();
})();
Sample output from preceding post:
My apologies for this long and possibly convoluted process I am trying to describe. I wish I could sum it up a bit better than I did. I am scouring (1/8)
the forums trying to figure out a way to count the first 160 characters of selected text and parse out the messages. I have to send text messages (2/8)
with 160 characters in length or less. If it is longer than that I have to type (1/2) or (1/3) at the end by breaking up the message that was typed. (3/8)
Then send (2/2) or (2/3) etc. I do this hundreds of times and I figured this was a job for Keyboard Maestro to select the text and parse it out into (4/8)
either two clipboards or three depending on how long the message is (it won't be longer than three). If anyone has an idea to get me pointed in the (5/8)
right direction how to divide the message out that would be great. The added complication is that I would really like to end after a word and not (6/8)
cut a word off so basically look for the first space after 145 characters and chop the message into one, two or three different groups. This seemed (7/8)
useful but not sure how to piece it together. (8/8)