I need to put all of them on one line and rotate through each one as the starting one.
Like this:
AAA BBB CCC DDD EEE FFF GGG HHH III JJJ
BBB CCC DDD EEE FFF GGG HHH III JJJ AAA
CCC DDD EEE FFF GGG HHH III JJJ AAA BBB
DDD EEE FFF GGG HHH III JJJ AAA BBB CCC
EEE FFF GGG HHH III JJJ AAA BBB CCC DDD
FFF GGG HHH III JJJ AAA BBB CCC DDD EEE
GGG HHH III JJJ AAA BBB CCC DDD EEE FFF
HHH III JJJ AAA BBB CCC DDD EEE FFF GGG
III JJJ AAA BBB CCC DDD EEE FFF GGG HHH
JJJ AAA BBB CCC DDD EEE FFF GGG HHH III
The DeviceTypes will change often, and I can easily type in different values, like MMM, NNN, or OOO every day. But the number of items may change from 8, 9, 10, 11, maybe 12.
I could just manually type in 10 concatenate commands, but since the number of devices may change, this will be a nuisance to update.
It seems like loops would be the way to go. I set up a loop but can’t get the dynamic variable to work.
nnn = 100
repeat 10 times
nnn = nnn + 1
DeviceList101 = DeviceList101 + DeviceType%nnn%
Is there a better way to do this? or how do I get the dynamic variable to work?
I’ve played with the percent characters for an hour now and nothing works. Do I need tokens or something?
Applescript needs a little more help (more generic functions to paste), but can still do it in just the same way, if you prefer an Execute Applescript action in KM
-- GENERIC FUNCTIONS -----------------------------------------------------
-- ceiling :: Num -> Int
on ceiling(x)
set {n, r} to properFraction(x)
if r > 0 then
n + 1
else
n
end if
end ceiling
-- concat :: [[a]] -> [a]
-- concat :: [String] -> String
on concat(xs)
if length of xs > 0 and class of (item 1 of xs) is string then
set acc to ""
else
set acc to {}
end if
repeat with i from 1 to length of xs
set acc to acc & item i of xs
end repeat
acc
end concat
-- drop :: Int -> [a] -> [a]
on drop(n, a)
if n < length of a then
if class of a is text then
text (n + 1) thru -1 of a
else
items (n + 1) thru -1 of a
end if
else
{}
end if
end drop
-- enumFromTo :: Int -> Int -> [Int]
on enumFromTo(m, n)
if m > n then
set d to -1
else
set d to 1
end if
set lst to {}
repeat with i from m to n by d
set end of lst to i
end repeat
return lst
end enumFromTo
-- intercalate :: String -> [String] -> String
on intercalate(s, xs)
set {dlm, my text item delimiters} to {my text item delimiters, s}
set str to xs as text
set my text item delimiters to dlm
return str
end intercalate
-- Splits a string on linefeed Chars
-- lines :: String -> [String]
on |lines|(xs)
splitOn(linefeed, 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
-- min :: Ord a => a -> a -> a
on min(x, y)
if y < x then
y
else
x
end if
end min
-- 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
-- properFraction :: Real -> (Int, Real)
on properFraction(n)
set i to (n div 1)
{i, n - i}
end properFraction
-- 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
-- rotate :: Int -> [a] -> [a]
on rotate(n, xs)
set lng to length of xs
if lng > 0 then
takeDropCycle(lng, n, xs)
else
{}
end if
end rotate
-- splitOn :: String -> String -> [String]
on splitOn(strDelim, strMain)
set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
set xs to text items of strMain
set my text item delimiters to dlm
return xs
end splitOn
-- take :: Int -> [a] -> [a]
on take(n, xs)
if class of xs is string then
if n > 0 then
text 1 thru min(n, length of xs) of xs
else
""
end if
else
if n > 0 then
items 1 thru min(n, length of xs) of xs
else
{}
end if
end if
end take
-- take N Members of an infinite cycle of xs, starting from index I
-- takeDropCycle :: Int -> [a] -> [a]
on takeDropCycle(n, i, xs)
set lng to length of xs
set m to n + i
if lng ≥ m then
set ys to xs
else
set ys to concat(replicate(ceiling(m / lng), xs))
end if
drop(i, take(m, ys))
end takeDropCycle
-- unlines :: [String] -> String
on unlines(xs)
intercalate(linefeed, xs)
end unlines
-- unwords :: [String] -> String
on unwords(xs)
intercalate(space, xs)
end unwords
-- ROTATED DEVICES -------------------------------------------------------
on run
tell application "Keyboard Maestro Engine" to set ¬
strLines to getvariable ("deviceList")
script suffix
on |λ|(x)
item 2 of splitOn(" = ", x)
end |λ|
end script
set xs to map(suffix, |lines|(strLines))
script rotated
on |λ|(n)
unwords(rotate(n, xs))
end |λ|
end script
unlines(map(rotated, enumFromTo(0, (length of xs) - 1)))
end run
Thanks for the answers!
I’ve never gone into the scripts, just stayed in KM.
Is there a way to do it all inside KM with the variable name and the ‘%’ symbols?
Maybe it’s time for me to learn how to call an external routine. I have a ton of coding experience, but very little in javascript. But I think this going to be a long and deep rabbit hole.
Reinventing the wheel, and doing the rotation manually, a more DIY version for pasting into a KM Execute Applescript action might be:
-- enumFromTo :: Int -> Int -> [Int]
on enumFromTo(m, n)
if m > n then
set d to -1
else
set d to 1
end if
set lst to {}
repeat with i from m to n by d
set end of lst to i
end repeat
return lst
end enumFromTo
-- intercalate :: String -> [String] -> String
on intercalate(s, xs)
set {dlm, my text item delimiters} to {my text item delimiters, s}
set str to xs as text
set my text item delimiters to dlm
return str
end intercalate
-- 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
-- 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
-- splitOn :: String -> String -> [String]
on splitOn(strDelim, strMain)
set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
set xs to text items of strMain
set my text item delimiters to dlm
return xs
end splitOn
-- unlines :: [String] -> String
on unlines(xs)
intercalate(linefeed, xs)
end unlines
-- unwords :: [String] -> String
on unwords(xs)
intercalate(space, xs)
end unwords
on run
script suffix
on |λ|(x)
item 2 of splitOn(" = ", x)
end |λ|
end script
tell application "Keyboard Maestro Engine" to set ¬
xs to my map(suffix, paragraphs of (getvariable ("deviceList")))
script rotated
on |λ|(n)
if n ≠ 1 then
unwords(items n thru -1 of xs & items 1 thru (n - 1) of xs)
else
unwords(xs)
end if
end |λ|
end script
unlines(map(rotated, enumFromTo(1, length of xs)))
end run
Of course, the KM Wiki shows you how to use scripts with KM, not how to write the scripts (except for access to KM Variables). But there are a lot of experienced scripters, like @ComplexPoint, @ccstone, and others who hang out in the KM forum. So, if you decided to try your hand scripting, and have questions, feel free to post here (in a new topic).
Hey ComplexPoint, I’ve spent many hours on this now, trying to understand everything you did, and learning things I didn’t know about KM. Your skill sets are amazing. You have opened new doors for me. Thank you so much.
------------------------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2017/11/05 14:54
# dMod: 2017/11/05 14:54
# Appl: Vanilla AppleScript
# Task: Build a text array from a specified string.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @Build, @Array, @String
------------------------------------------------------------------------------
set myInput to "
DeviceType101 = AAA
DeviceType102 = BBB
DeviceType103 = CCC
DeviceType104 = DDD
DeviceType105 = EEE
DeviceType106 = FFF
DeviceType107 = GGG
DeviceType108 = HHH
DeviceType109 = III
DeviceType110 = JJJ
"
set AppleScript's text item delimiters to {" = ", linefeed}
set theCodes to text items of myInput
repeat with i in theCodes
if length of i ≠ 3 then
set contents of i to 0
end if
end repeat
set theCodes to (text of theCodes)
set lineLength to length of theCodes
set theCodes to theCodes & theCodes
set myArray to {}
set AppleScript's text item delimiters to space
repeat with i from 1 to lineLength
set end of myArray to (items i thru (i + 9) of theCodes) as text
end repeat
set AppleScript's text item delimiters to linefeed
set myArray to myArray as text
------------------------------------------------------------------------------
When installed — the Satimage.osax adds a wonderful array of features to AppleScript.
This script uses the Satimage.osax’s regular expression support to easily find the 3-digit codes in the input text and is consequently more concise than the above script.
------------------------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2017/11/05 14:54
# dMod: 2017/11/05 14:59
# Appl: AppleScript + the Satimage.osax
# Task: Build a text array from a specified string (SIO Version).
# Libs: None
# Osax: Satimage.osax --> http://tinyurl.com/satimage-osaxen
# Tags: @Applescript, @Script, @Build, @Array, @String, @Satimage.osax
------------------------------------------------------------------------------
set myInput to "
DeviceType101 = AAA
DeviceType102 = BBB
DeviceType103 = CCC
DeviceType104 = DDD
DeviceType105 = EEE
DeviceType106 = FFF
DeviceType107 = GGG
DeviceType108 = HHH
DeviceType109 = III
DeviceType110 = JJJ
"
# Uses the Satimage.osax's regular expression support to easily find the codes:
set theCodes to fnd("(?-i)[A-Z]{3}", myInput, true, true) of me
set lineLength to length of theCodes
set theCodes to theCodes & theCodes
set myArray to {}
set AppleScript's text item delimiters to space
repeat with i from 1 to lineLength
set end of myArray to (items i thru (i + 9) of theCodes) as text
end repeat
set AppleScript's text item delimiters to linefeed
set myArray to myArray as text
------------------------------------------------------------------------------
--» HANDLERS
------------------------------------------------------------------------------
on fnd(_find, _data, _all, strRslt)
try
find text _find in _data all occurrences _all string result strRslt with regexp without case sensitive
on error
return false
end try
end fnd
------------------------------------------------------------------------------
Thanks Chris. I’m still experimenting. This has turned into a learning experience. It feels like it’s time to learn how to add scripts into KM. I was busy all day today, and I’m back at if for a little while.
Keyboard Maestro has such a rich and deep feature set that this is bound to be perpetually true — it is for me at least, and I've been using it since early 2004.
The functionality the osax adds to AppleScript is very deep and very useful.
Anyone serious about AppleScript should also check out Script Debugger.
AppleScript has given me years of fun, but now I get better libraries and better coverage (on iOS Drafts, 1Writer, OmniGraffle, etc, as well as web) from JavaScript.
(Also, you get mapfilter and reduce for free in JS, whereas you have to squeeze them out of AS, and Applescript records are a mild nightmare, compared to the much easier and less error-generating dictionaries in more or less every other scripting language)
So my personal feeling is that for anyone starting an investment in scripting now, Applescript is not looking like the obvious bet, these days … Omni, for example, are moving their scripting efforts to omniJS, essentially because of the elephant in the room ( iOS ).
But, OK, lets avoid interruption by an AppleScript error. Let's first politely ask what keys the record has before we proceed.
The Applescript way:
use framework "Foundation"
set rec to {alpha:1, beta:2, gamma:3}
(current application's NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
--> {"alpha", "beta", "gamma"}
Unzip and place somewhere, changing the Script file: location accordingly.
Note that in the Swift script, the default value for the argument is there just to test the script, you only need to change the variable in the KM macro (Set Variable "argument") if the devices change.
edit: I forgot to mention that it works with whatever number of devices you have in the variable.