on run
tell application "Keyboard Maestro Engine" to set s to getvariable ("noteLines")
script cols
on |λ|(x)
splitOn(" [[", x)
end |λ|
end script
set xys to map(cols, |lines|(s))
set gap to 3
set w to gap + (length of ¬
fst(maximumBy(comparing(compose(|length|, fst)), xys)))
script dotted
on |λ|(ab)
set {a, b} to ab
justifyLeft(w, ".", a) & "[[" & b
end |λ|
end script
unlines(map(dotted, xys))
end run
-- GENERIC -----------------------------------------------
-- https://github.com/RobTrew/prelude-applescript
-- comparing :: (a -> b) -> (a -> a -> Ordering)
on comparing(f)
script
on |λ|(a, b)
tell mReturn(f)
set fa to |λ|(a)
set fb to |λ|(b)
if fa < fb then
-1
else if fa > fb then
1
else
0
end if
end tell
end |λ|
end script
end comparing
-- compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
on compose(f, g)
script
property mf : mReturn(f)
property mg : mReturn(g)
on |λ|(x)
mf's |λ|(mg's |λ|(x))
end |λ|
end script
end compose
-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
tell mReturn(f)
set v to startValue
set lng to length of xs
repeat with i from 1 to lng
set v to |λ|(v, item i of xs, i, xs)
end repeat
return v
end tell
end foldl
-- fst :: (a, b) -> a
on fst(tpl)
if class of tpl is record then
|1| of tpl
else
item 1 of tpl
end if
end fst
-- justifyLeft :: Int -> Char -> String -> String
on justifyLeft(n, cFiller, strText)
if n > length of strText then
text 1 thru n of (strText & replicate(n, cFiller))
else
strText
end if
end justifyLeft
-- length :: [a] -> Int
on |length|(xs)
set c to class of xs
if list is c or string is c then
length of xs
else
(2 ^ 29 - 1) -- (maxInt - simple proxy for non-finite)
end if
end |length|
-- lines :: String -> [String]
on |lines|(xs)
paragraphs of xs
end |lines|
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
-- maximumBy :: (a -> a -> Ordering) -> [a] -> a
on maximumBy(f, xs)
set cmp to mReturn(f)
script max
on |λ|(a, b)
if a is missing value or cmp's |λ|(a, b) < 0 then
b
else
a
end if
end |λ|
end script
foldl(max, missing value, xs)
end maximumBy
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
if class of f is script then
f
else
script
property |λ| : f
end script
end if
end mReturn
-- Egyptian multiplication - progressively doubling a list, appending
-- stages of doubling to an accumulator where needed for binary
-- assembly of a target length
-- replicate :: Int -> a -> [a]
on replicate(n, a)
set out to {}
if n < 1 then return out
set dbl to {a}
repeat while (n > 1)
if (n mod 2) > 0 then set out to out & dbl
set n to (n div 2)
set dbl to (dbl & dbl)
end repeat
return out & dbl
end replicate
-- splitOn :: String -> String -> [String]
on splitOn(pat, src)
set {dlm, my text item delimiters} to ¬
{my text item delimiters, pat}
set xs to text items of src
set my text item delimiters to dlm
return xs
end splitOn
-- unlines :: [String] -> String
on unlines(xs)
set {dlm, my text item delimiters} to ¬
{my text item delimiters, linefeed}
set str to xs as text
set my text item delimiters to dlm
str
end unlines
This is very interesting. I only problem I see so far is that the "gap = 3" for the first of the list may not be known in advance. Sure in the sample I gave it was but in another case the first in the list would be shorter than the following and require a different number of "." It would be nice to use a set right justification margin. All such actions would have the same look. My page is 80 characters wide and the time stamp with the [[ ]] is always 16 characters. But the titles vary in length unpredictably. This macro works great for this sample but is not reusable yet.
With more testing and use, this seems to work differently than I first thought. The script makes adjustments to the length based on the longest title/time-stamp comb and 3 "."'s there and then adjusting the other lines to match. The overall length of the group of lines would be variable between groups. This is both an advantage and a disadvantage. Advantage because it works and looks good. A disadvantage is when adding to the list and the macro creates a different number of "."'s for the addition as opposed to the first time the macro is run.
This is working pretty good. I see how this was done and am thankful for the help. Now just small tweaks because of my unclarity around all the use cases.
It's beneficial to mention those sorts of specifications when first describing your needed workflow. It saves everyone time and effort (including yourself).
Here's a version that allows you to set the maximum table width. I've preset it to 80 characters.
I have NOT added error-checking to make certain your table line lengths do not already exceed that limit.
#!/usr/bin/env perl -sw
use v5.010;
use utf8;
binmode(STDIN, ":utf8");
binmode(STDOUT, ":utf8");
while (<>) {s!\A\s+|\s+\Z!!g;s!(\h+)(?=\[)!$1.('.' x (80 - length($_)))!ge;say;}
As you have now found, it is in fact reusable and general
The part of the code to look at is the application of the maximumBy function which returns the widest line of text.
The width of the left column is defined as that of the widest line plus a standard amount of padding.
The gap constant in that draft is just based on the observation that you were padding the widest line in that case with an additional three dots – you can set any value that you prefer as a minimum, to prevent text from touching the opening [[, or derive it from a global maximum like 80 chars.
Of course if your global maximum is hard, then you may end up truncating the text sometimes ...
(and with a few more lines of code you could wrap it)
You guys really just reach for a script solution for everything? This is actually easy to do with two actions and no scripting. Search and replace (?m)^(.{1,70})(\[\[\d+\]\])$ for $1.$2. This adds one dot if the line length is less than about 80 (assuming the date stamp is always a consistent size). Adjust the 70 as required. If the line length is longer, it does nothing. Then just repeat this 80 times. Job done.
Good point, Peter. Us scripters often reach for the tool we know/like best. Everyone should know that, while Keyboard Maestro (KM) works very well with scripts, ==one of the main purposes of KM is to provide automated solutions that do NOT require a script,==, making it a great tool for everyone!
And I am the first to say: any solution is a good solution.
Indeed, the scripting on this forum regularly blows me away — I just posted on TidBITS talk about the awesome scripters on this forum and the amazing things you guys do.
I do occasionally worry a novice visitor to the forum will see all these amazing scripts and be scared off, when really they should see all these amazing scripts and generous folks and just ask for help with anything they need and revel in the plethora and variety of answers they get.
Hey guys there is room for everyone. As a beginner I was intimidated by this whole project. This is a new language for me. Scripting rather JavaScript, python, perl, or Regex is at first confusing. But personally I relish the challenge. In the end, I looked at and tried to assimilate everyone's contribution and ccstone's Perl script won. Not sure why I chose his, they all work.
You have all been so helpful to this novice.
Actually, I think RegEx is much easier to use in KM than in any script, especially AppleScript which requires complex ASObjC to use (or a Scripting Addition).
I suspect that a lot more people understand RegEx than AppleScript, since RegEx is used on all platforms, and has been in use for decades.
Having said all that, I will be the first to say that RegEx is NOT trivial to learn, although you can get started and be productive with some simple RegEx patterns.
For more info, see Regular Expressions Quick Start .