Keeping scripts in Drafts and using js eval() in code editor

Is there any reason not to do this? I’ve been looking for a way to be able to test my scripts as I’m developing them, and I get tired of doing so much tapping to get in and out of the code editor.

For one small script, I tested putting the script in a draft and calling JavaScript eval() on the content of the draft. I have already done this with storing CSS files inside Drafts (which works great), so it got me thinking about scripts.

I would imagine there’s some kind of speed difference once the draft gets complex enough, but I’d sure love to merge all of the text editing features of Drafts with the JavaScript abilities. It would also be nice to be able to reuse common snippets of code in a modular way. I’m fairly new to Drafts and not a professional scripter by any means, so maybe there is some way of doing this I haven’t thought of.

Nick

3 Likes

eval itself is pretty fast – my guess is than any slight lag would just come from finding and fetching the draft(s)

And as long as you know exactly what code you are passing to eval, it shouldn’t lead to any tears – it’s not very popular in textbooks or workplaces, for fear of code injection etc., but personal scripting on a system over which you have full control seems a pretty good use for it.

on macOS I use something like this to pull in files containing my sets of generic functions, so that a top level function f can use the functions defined in any of the files whose paths are listed in the fps argument.

// Evaluate a function f :: (() -> a)
// in the context of the JS libraries whose source
// filePaths are listed in fps :: [FilePath]
// usingLibs :: [FilePath] -> (() -> a) -> a
const usingLibs = (fps, f) =>
    fps.every(doesFileExist) ? (
        eval(`(() => {
            'use strict';
            ${fps.map(readFile).join('\n\n')}
            return (${f})();
        })();`)
    ) : 'Library not found at: ' + [].concat(...fps.map(
        fp => doesFileExist(fp) ? [] : [fp]
    )).join('\n');

I’ve thought of adjusting it slightly to pull in ‘library’ drafts – I haven’t yet tested that for speed, but I wouldn’t expect it to be hopelessly slow.

1 Like

A first test looks fine …

The following, in which a trivial main() uses two functions (showJSON and enumFromTo) from a draft of over 2500 lines, containing 350+ other functions, seems to run without obvious lag or delay.

( On a reasonably veteran iPad for example, the whole process of finding the draft by UUID, fetching and loading 2500+ lines of source code containing 350+ functions, and returning a value from a simple computation using two of those functions, seems to take about 7 milliseconds ).

(() => {

    // Evaluated in the context of a library of functions
    // stored in a draft, and retrieved by UUID.
    const main = () => {
        return showJSON(
            enumFromTo(80, 100)
        );
    };

    // Evaluate a function f :: (() -> a)
    // in the context of the JS libraries whose
    // Source drafts are listed in uuids :: [UUID]

    // usingDraftLibs :: [UUID] -> (() -> a) -> a
    const usingDraftLibs = (uuids, f) =>
        eval(
            `(() => {
                ${uuids.map(k => Draft.find(k).content).join('\n\n')}
                return (${f})();
            })()`
        );

    const uuidPrelude = '2501E0D0-793F-4143-A836-9DE987007673';
    return alert(
        usingDraftLibs(
            [uuidPrelude],
            main
        )
    );
})()

This is how I store all my scripts! Every one of them is a draft and the action to call them is this:

var s = Draft.find("UUID of script");
this.eval(s.content);

It makes creating and editing scripts SO much easier!

5 Likes

Or, FWIW, expanding the template just slightly:

  • to avoid any puzzling variable clashes between successive script steps, by making all (eval)uated name-bindings local – by adding the outer (() => { ... })() bracketing, and
  • to speed up access to any error messages, so that they will be shown immediately in an alert, rather than hiding a few clicks down in the log of the draft – by adding the try ... catch wrapping
(() => {

    // Name of script:
    //     'Some script'

    // UUID of draft containing script;
    // e.g.
    // drafts5://open?uuid=55895654-820B-41E8-9534-0DD6350FC7D9
    const uuid = '55895654-820B-41E8-9534-0DD6350FC7D9';

    try {
        eval( // line 11, so we adjust the error message alert below
            Draft.find(uuid).content
        )
    } catch (e) {
        alert(`
    Error: ${e.message}
    
    line: ${e.line - 11}
    column: ${e.column}`)
    }
})();
5 Likes

Awesome, thanks! I’m glad your tests show that it’s still really fast to do this. I’ll try your example template script, too.

Now all I need is a JS formatter script. I’d love to have a button on the keyboard to make the code prettier without having to copy/paste to a website and back. Anyone got one? :wink:

Nick

Thank you for the great idea.

1 Like

wow really great ideas @draft8 & @David_Crandall thank you!

In addition its awesome to use the action group from @nahumck https://actions.getdrafts.com/g/1C3 to create the scripts in a draft!

I use a “script” tag + “s_develop”,“s_released” to quickly filter out the scripts which I can use anywhere else or develop them!
personally I’d use the [[title]] line as comment to specify the script and also add an action to quickly get the uuid of the current draft :slight_smile: this fits to my workflow and made it a lot quicker :slight_smile:

I was using the title line at first but I think the UUID is safer. Good idea to use an action to get the UUID quickly.

Could you explain a little bit about this “library” idea? Are you talking about being able to use it for something like moment.js?

Sorry that was not clear enough, of course the uuid is the way to got in the script.
But when I filter my drafts with the tag „script“ I want to see a short description of the script and not the first line of the script
That’s what I wanted to say
The first line is always a comment with a short description and then the script follows as [[body]]

I don’t know whether it would be feasible to use things like Moment – I’m just bringing in my own library of generic functions, essentially those listed below – and modelled on the Haskell Prelude – tho for Drafts I filter out all of SystemIO file functions – of which only readFile and writeFile can be written in terms of the Drafts 5 FileManager object.

(The rest of the file-related functions I use on macOS through JXA $.NSFileManager)

Composing things from a library of generics lets me click things together (and rearrange them) quite rapidly, without having to reinvent wheels, remember how to do things, or write much new code. (I have a parallel set of functions for AppleScript, tho I use that less, these days)

EQ :: Ordering
GT :: Ordering
Just :: a -> Just a
LT :: Ordering
Left :: a -> Either a b
Node :: a -> [Tree a] -> Tree a
Nothing :: () -> Nothing
Ordering :: Int -> Ordering
Ratio :: Int -> Int -> Ratio
Right :: b -> Either a b
Tuple (,) :: a -> b -> (a, b)
TupleN :: a -> b ...  -> (a, b ... )
abs :: Num -> Num
all :: (a -> Bool) -> [a] -> Bool
and :: [Bool] -> Bool
any :: (a -> Bool) -> [a] -> Bool
ap (<*>) :: Monad m => m (a -> b) -> m a -> m b
apLR (<*>) :: Either e (a -> b) -> Either e a -> Either e b
apList (<*>) :: [(a -> b)] -> [a] -> [b]
apMaybe (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
apTuple (<*>) :: Monoid m => (m, (a -> b)) -> (m, a) -> (m, b)
append (++) :: [a] -> [a] -> [a]
appendFile :: FilePath -> String -> IO Bool
appendFileMay :: FilePath -> String -> Maybe IO FilePath
apply ($) :: (a -> b) -> a -> b
approxRatio :: Real -> Real -> Ratio
argvLength :: Function -> Int
assocs :: Map k a -> [(k, a)]
bind (>>=) :: Monad m => m a -> (a -> m b) -> m b
bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
bindList (>>=) :: [a] -> (a -> [b]) -> [b]
bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
bindTuple (>>=) :: Monoid a => (a, a) -> (a -> (a, b)) -> (a, b)
break :: (a -> Bool) -> [a] -> ([a], [a])
breakOn :: String -> String -> (String, String)
breakOnAll :: String -> String -> [(String, String)]
breakOnMay :: String -> String -> Maybe (String, String)
cartesianProduct :: [a] -> [b] -> [(a, b)]
caseOf :: [(a -> Bool, b)] -> b -> a ->  b
catMaybes :: [Maybe a] -> [a]
ceiling :: Num -> Int
center :: Int -> Char -> String -> String
chars :: String -> [Char]
chr :: Int -> Char
chunksOf :: Int -> [a] -> [[a]]
compare :: a -> a -> Ordering
comparing :: (a -> b) -> (a -> a -> Ordering)
compose :: (b -> c) -> (a -> b) -> a -> c
composeListLR :: [(a -> a)] -> (a -> a)
composeListRL :: [(a -> a)] -> (a -> a)
concat :: [[a]] -> [a]
concatMap :: (a -> [b]) -> [a] -> [b]
cons :: a -> [a] -> [a]
const_ :: a -> b -> a
createDirectoryIfMissingMay :: Bool -> FilePath -> Maybe IO ()
curry :: ((a, b) -> c) -> a -> b -> c
curry2 :: ((a, b) -> c) -> a -> b -> c
delete :: Eq a => a -> [a] -> [a]
deleteAt :: Int -> [a] -> [a]
deleteBy :: (a -> a -> Bool) -> a -> [a] -> [a]
deleteFirst :: a -> [a] -> [a]
deleteFirstsBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]
deleteMap :: k -> Dict -> Dict
difference (\\) :: Eq a => [a] -> [a] -> [a]
div :: Int -> Int -> Int
doesDirectoryExist :: FilePath -> IO Bool
doesFileExist :: FilePath -> IO Bool
doesPathExist :: FilePath -> IO Bool
draw :: Tree String -> [String]
drawForest :: Forest String -> String
drawTree :: Tree String -> String
drop :: Int -> [a] -> [a]
dropAround :: (Char -> Bool) -> String -> String
dropFileName :: FilePath -> FilePath
dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhileEnd :: (Char -> Bool) -> String -> String
either :: (a -> c) -> (b -> c) -> Either a b -> c
elem :: Eq a => a -> [a] -> Bool
elemAtMay :: Int -> Dict -> Maybe (String, a)
elemIndex :: Eq a => a -> [a] -> Maybe Int
elemIndices :: Eq a => a -> [a] -> [Int]
elems :: Dict -> [a]
enumFromThenTo :: Enum a => a -> a -> a -> [a]
enumFromThenToChar :: Char -> Char -> Char -> [Char]
enumFromThenToInt :: Int -> Int -> Int -> [Int]
enumFromTo :: Enum a => a -> a -> [a]
enumFromToChar :: Char -> Char -> [Char]
enumFromToInt :: Int -> Int -> [Int]
eq (==) :: Eq a => a -> a -> Bool
evalJSMay :: String -> Maybe a
even :: Int -> Bool
exp :: Float -> Float
fanArrow (&&&) :: (a -> b) -> (a -> c) -> (a -> (b, c))
filePath :: String -> FilePath
fileSize :: FilePath -> Either String Int
fileStatus :: FilePath -> Either String Dict
filter :: (a -> Bool) -> [a] -> [a]
find :: (a -> Bool) -> [a] -> Maybe a
findIndex :: (a -> Bool) -> [a] -> Maybe Int
findIndexR :: (a -> Bool) -> [a] -> Maybe Int
findIndices :: (a -> Bool) -> [a] -> [Int]
firstArrow :: (a -> b) -> ((a, c) -> (b, c))
flatten :: NestedList a -> [a]
flip :: (a -> b -> c) -> b -> a -> c
floor :: Num -> Int
fmap (<$>) :: Functor f => (a -> b) -> f a -> f b
fmapLR (<$>) :: (a -> b) -> Either a a -> Either a b
fmapMay (<$>) :: (a -> b) -> Maybe a -> Maybe b
fmapTree :: (a -> b) -> Tree a -> Tree b
fmapTuple (<$>) :: (a -> b) -> (a, a) -> (a, b)
foldMapTree :: Monoid m => (a -> m) -> Tree a -> m
foldTree :: (a -> [b] -> b) -> Tree a -> b
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl1 :: (a -> a -> a) -> [a] -> a
foldl1May :: (a -> a -> a) -> [a] -> Maybe a
foldlTree :: (b -> a -> b) -> b -> Tree a -> b
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr1 :: (a -> a -> a) -> [a] -> a
foldr1May :: (a -> a -> a) -> [a] -> Maybe a
fromEnum :: Enum a => a -> Int
fromLeft :: a -> Either a b -> a
fromMaybe :: a -> Maybe a -> a
fromRight :: b -> Either a b -> b
fst :: (a, b) -> a
gcd :: Int -> Int -> Int
genericIndexMay :: [a] -> Int -> Maybe a
getCurrentDirectory :: IO FilePath
getDirectoryContents :: FilePath -> IO [FilePath]
getFinderDirectory :: IO FilePath
getHomeDirectory :: IO FilePath
getTemporaryDirectory :: IO FilePath
group :: Eq a => [a] -> [[a]]
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
groupSortOn :: Ord b => (a -> b) -> [a] -> [a]
head :: [a] -> a
headMay :: [a] -> Maybe a
id :: a -> a
indented :: String -> String -> String
index (!!) :: [a] -> Int -> a
init :: [a] -> [a]
initMay :: [a] -> Maybe [a]
inits :: [a] -> [[a]]
insert :: Ord a => a -> [a] -> [a]
insertBy :: (a -> a -> Ordering) -> a -> [a] -> [a]
insertMap :: Dict -> String -> a -> Dict
intToDigit :: Int -> Char
intercalate :: [a] -> [[a]] -> [a]
intercalateS :: String -> [String] -> String
intersect :: (Eq a) => [a] -> [a] -> [a]
intersectBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]
intersectionBy:: (a -> a -> Bool) -> [[a]] -> [a]
intersperse :: Char -> String -> String
isAlpha::Char - > Bool
isChar :: a -> Bool
isDigit :: Char -> Bool
isInfixOf :: Eq a => [a] -> [a] -> Bool
isLeft :: Either a b -> Bool
isLower :: Char -> Bool
isMaybe :: a -> Bool
isNull :: [a] -> Bool
isPrefixOf :: [a] -> [a] -> Bool
isRight :: Either a b -> Bool
isSortedBy :: (a -> a -> Bool) -> [a] -> Bool
isSpace :: Char -> Bool
isSubsequenceOf :: Eq a => [a] -> [a] -> Bool
isSuffixOf :: Eq a => [a] -> [a] -> Bool
isUpper :: Char -> Bool
iso8601Local :: Date -> String
iterateUntil :: (a -> Bool) -> (a -> a) -> a -> [a]
jsonLog :: a -> IO ()
justifyLeft :: Int -> Char -> String -> String
justifyRight :: Int -> Char -> String -> String
keys :: Dict -> [String]
kleisliCompose (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
last :: [a] -> a
lastMay :: [a] -> Maybe a
lcm :: Int -> Int -> Int
lefts :: [Either a b] -> [a]
length :: [a] -> Int
levelNodes :: Tree a -> [[Tree a]]
levels :: Tree a -> [[a]]
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2LR :: (a -> b -> c) -> Either d a -> Either d b -> Either d c
liftA2List :: (a -> b -> c) -> [a] -> [b] -> [c]
liftA2Maybe :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
liftA2Tuple :: Monoid m => (a -> b -> c) -> (m, a) -> (m, b) -> (m, c)
liftM2 :: (a -> b -> c) -> [a] -> [b] -> [c]
liftMmay :: (a -> b) -> (Maybe a -> Maybe b)
lines :: String -> [String]
listDirectory :: FilePath -> [FilePath]
listFromTuple (a, a ...) -> [a]
listToMaybe :: [a] -> Maybe a
log :: Float -> Float
lookup :: Eq a => a -> Container -> Maybe b
lookupDict :: a -> Dict -> Maybe b
lookupTuples :: Eq a => a -> [(a, b)] -> Maybe b
mReturn :: First-class m => (a -> b) -> m (a -> b)
map :: (a -> b) -> [a] -> [b]
mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
mapAccumL_Tree :: (acc -> x -> (acc, y)) -> acc -> Tree -> (acc, Tree)
mapAccumR :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
mapFromList :: [(k, v)] -> Dict
mapKeys :: (Key -> Key) -> IntMap a -> IntMap a
mapMaybe :: (a -> Maybe b) -> [a] -> [b]
mappend (<>) :: Monoid a => a -> a -> a
mappendComparing :: [((a -> b), Bool)] -> (a -> a -> Ordering)
mappendMaybe (<>) :: Maybe a -> Maybe a -> Maybe a
mappendOrdering (<>) :: Ordering -> Ordering -> Ordering
mappendTuple (<>) :: (a, b) -> (a, b) -> (a, b)
max :: Ord a => a -> a -> a
maximum :: Ord a => [a] -> a
maximumBy :: (a -> a -> Ordering) -> [a] -> a
maximumByMay :: (a -> a -> Ordering) -> [a] -> Maybe a
maximumMay :: Ord a => [a] -> Maybe a
maybe :: b -> (a -> b) -> Maybe a -> b
maybeToList :: Maybe a -> [a]
mean :: [Num] -> Num
member :: Key -> Dict -> Bool
min :: Ord a => a -> a -> a
minimum :: Ord a => [a] -> a
minimumBy :: (a -> a -> Ordering) -> [a] -> a
minimumByMay :: (a -> a -> Ordering) -> [a] -> Maybe a
minimumMay :: [a] -> Maybe a
mod :: Int -> Int -> Int
negate :: Num -> Num
newUUID :: () -> IO UUID String
not :: Bool -> Bool
notElem :: Eq a => a -> [a] -> Bool
nub :: [a] -> [a]
nubBy :: (a -> a -> Bool) -> [a] -> [a]
odd :: Int -> Bool
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
or :: [Bool] -> Bool
ord :: Char -> Int
outdented :: String -> String
partition :: (a -> Bool) -> [a] -> ([a], [a])
partitionEithers :: [Either a b] -> ([a], [b])
permutations :: [a] -> [[a]]
permutationsWithRepetition :: Int -> [a] -> [[a]]
pi :: Float
plus :: Num -> Num -> Num
pred :: Enum a => a -> a
product :: [Num] -> Num
properFraction :: Real -> (Int, Real)
pureLR :: a -> Either e a
pureList :: a -> [a]
pureMay :: a -> Maybe a
pureT :: f a -> (a -> f a)
pureTree :: a -> Tree a
pureTuple :: a -> (a, a)
quickSort :: (Ord a) => [a] -> [a]
quickSortBy :: (a -> a -> Ordering) -> [a] -> [a]
quot :: Int -> Int -> Int
quotRem :: Int -> Int -> (Int, Int)
raise :: Num -> Int -> Num
randomRInt :: Int -> Int -> Int
range :: Ix a => (a, a) -> [a]
read :: Read a => String -> a
readFile :: FilePath -> IO String
readFileMay :: FilePath -> Maybe String
readJSON :: String -> a
readJSONLR :: Read a => String -> Either String a
readMay :: Read a => String -> Maybe a
recip :: Num -> Num
recipMay :: Num -> Maybe Num
regexMatches :: String -> String -> [[String]]
rem :: Int -> Int -> Int
removeFile :: FilePath -> Either String String
replace :: String -> String -> String -> String
replicate :: Int -> a -> [a]
replicateM :: Int -> [a] -> [[a]]
replicateString :: Int -> String -> String
reverse :: [a] -> [a]
rights :: [Either a b] -> [b]
rotate :: Int -> [a] -> [a]
round :: a -> Int
safeMay :: (a -> Bool) -> (a -> b) -> Maybe b
scanl :: (b -> a -> b) -> b -> [a] -> [b]
scanl1 :: (a -> a -> a) -> [a] -> [a]
scanr :: (b -> a -> b) -> b -> [a] -> [b]
scanr1 :: (a -> a -> a) -> [a] -> [a]
secondArrow :: (a -> b) -> ((c, a) -> (c, b))
sequenceAList :: Applicative f => [f a] -> f [a]
setCurrentDirectory :: String -> IO ()
show :: a -> String
showBinary :: Int -> String
showDate :: Date -> String
showDict :: Dict -> String
showHex :: Int -> String
showIntAtBase :: Int -> (Int -> Char) -> Int -> String -> String
showJSON :: a -> String
showLR :: Either a b -> String
showList :: [a] -> String
showLog :: a -> IO ()
showMaybe :: Maybe a -> String
showOrdering :: Ordering -> String
showRatio :: Ratio -> String
showTuple :: Tuple -> String
showTuple3 :: Tuple3 -> String
showTuple4 :: Tuple4 -> String
showUndefined :: () -> String
signum :: Num -> Num
snd :: (a, b) -> b
snoc :: [a] -> a -> [a]
sort :: Ord a => [a] -> [a]
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
sortOn :: Ord b => (a -> b) -> [a] -> [a]
span :: (a -> Bool) -> [a] -> ([a],[a])
splitArrow (***) :: (a -> b) -> (c -> d) -> ((a, c) -> (b, d))
splitAt :: Int -> [a] -> ([a],[a])
splitBy :: (a -> a -> Bool) -> [a] -> [[a]]
splitEvery :: Int -> [a] -> [[a]]
splitFileName :: FilePath -> (String, String)
splitOn :: String -> String -> [String]
splitRegex :: Regex -> String -> [String]
sqrt :: Num -> Num
sqrtMay :: Num -> Maybe Num
strip :: String -> String
stripEnd :: String -> String
stripPrefix :: Eq a => [a] -> [a] -> Maybe [a]
stripStart :: String -> String
subsequences :: [a] -> [[a]]
subtract :: Num -> Num -> Num
succ :: Enum a => a -> a
sum :: [Num] -> Num
swap :: (a, b) -> (b, a)
tail :: [a] -> [a]
tailMay :: [a] -> Maybe [a]
tails :: [a] -> [[a]]
take :: Int -> [a] -> [a]
takeAround :: (a -> Bool) -> [a] -> [a]
takeBaseName :: FilePath -> String
takeCycle :: Int -> [a] -> [a]
takeDirectory :: FilePath -> FilePath
takeDropCycle :: Int -> [a] -> [a]
takeExtension :: FilePath -> String
takeFileName :: FilePath -> FilePath
takeIterate :: Int -> (a -> a) -> a -> [a]
takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhileR :: (a -> Bool) -> [a] -> [a]
tempFilePath :: String -> IO FilePath
then (>>) :: Monad m => m a -> m b -> m b
thenIO (>>) :: IO a -> IO b -> IO b
thenList (>>) :: [a] -> [b] -> [b]
thenMay (>>) :: Maybe a -> Maybe b -> Maybe b
toInitialCaps :: String -> String
toListTree :: Tree a -> [a]
toLower :: String -> String
toRatio :: Real -> Ratio
toSentence :: String -> String
toTitle :: String -> String
toUpper :: String -> String
transpose :: [[a]] -> [[a]]
traverseList :: (Applicative f) => (a -> f b) -> [a] -> f (t b)
treeLeaves :: Node -> [Node]
truncate :: Num -> Int
tupleFromArray [a] -> (a, a ...)
typeName :: a -> String
unQuoted :: String -> String
uncons :: [a] -> Maybe (a, [a])
uncurry :: (a -> b -> c) -> ((a, b) -> c)
unfoldForest :: (b -> (a, [b])) -> [b] -> Forest
unfoldTree :: (b -> (a, [b])) -> b -> Tree a
unfoldl :: (b -> Maybe (a, b)) -> b -> [a]
unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
union :: [a] -> [a] -> [a]
unionBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]
unlines :: [String] -> String
unsnoc :: [a] -> Maybe ([a], a)
until :: (a -> Bool) -> (a -> a) -> a -> a
unwords :: [String] -> String
unwrap :: NSObject -> a
unzip :: [(a,b)] -> ([a],[b])
unzip3 :: [(a,b,c)] -> ([a],[b],[c])
unzip4 :: [(a,b,c,d)] -> ([a],[b],[c],[d])
variance :: [Num] -> Num
words :: String -> [String]
wrap :: a -> NSObject
writeFile :: FilePath -> String -> IO ()
writeFileMay :: FilePath -> String -> Maybe FilePath
writeTempFile :: String -> String -> IO FilePath
zip :: [a] -> [b] -> [(a, b)]
zip3 :: [a] -> [b] -> [c] -> [(a, b, c)]
zip4 :: [a] -> [b] -> [c] -> [d] -> [(a, b, c, d)]
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
zipWith4 :: (a -> b -> c -> d -> e) -> [a] -> [b] -> [c] -> [d] -> [e]

Thanks again for this idea, I’ve been using it and it works great. Currently, I have your eval() script calling the UUID of the draft script. I also have a library of generic functions that I call with your usingDraftLibs() example function. Is there any way to combine those two? Sort of like this pseudo-code:

// Reference generic function library Draft
import 'draft_containing_generic_functions';
// Evaluate action script Draft which references generic function library
eval('draft_containing_action_script'); 

Currently I am putting usingDraftLibs() in every action script; it works well, but it adds about 15 lines of code to each one. Consolidating them would be great using the methods you described in this thread, ensuring that there are no clashes between the scripts and that the errors are caught immediately.

(There is an action script in the directory for Inport Script, but I wasn’t able to get it to work for some reason.)

Nick

If your library is a simple list of functions (not wrapped in a module or object) then you should be able to use a template like this (which allows for more than one library file) for each of your script actions:

(() => {
    
    const
        // UUID of a single draft, containing a JS script/module
        script = '0CCC587E-5F63-42A4-B66E-6E2903C452E9',

       // UUID(s) of one or more drafts, each containing lists of 
       // globally declared functions.
       // i.e. functions that are not wrapped by any module or object.
        libs = [
            '2501E0D0-793E-4143-A136-9DE987007645'
        ];

    eval(
        libs.concat(script)
        .map(uuid => Draft.find(uuid).content)
        .join('\n\n')
    );
})()

To which I might personally be inclined to add provision for alerts when any of the drafts (script or library) are not found.

You could, of course, have an action which simply produced a fresh copy of a template like this.

1 Like

This might well be a bit more wrapping that you really need or want, but I personally, out of habit, tend to give a name to each (script and library) UUID, and arrange for intelligible alerts when any of them couldn’t be found by that reference.

I might create an action which served up a fresh copy of a template like:

(() => {
    const
        script = {
            name: 'test',
            uuid: '0CCC587E-5F63-42A4-B66E-6E2903C452E9'
        },
        libs = [{
            name: 'jsPrelude',
            uuid: '2501E0D0-793E-4143-A136-9DE987007645'
        }];

    const main = () => {
        const
            jsLRs = libs.concat(script)
            .map(dct => {
                const d = Draft.find(dct.uuid)
                return d !== undefined ? (
                    Right(d.content)
                ) : Left(
                    `UUID not found: ${
                    '\n' + dct.name
                    } :: ${dct.uuid}`
                );
            }),
            lefts = jsLRs.filter(x => x.Left !== undefined);
        return lefts.length > 0 ? (
            alert(lefts.map(x => x.Left).join('\n\n'))
        ) : eval(jsLRs.map(x => x.Right).join('\n\n'));
    };

    // 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
    });

    // MAIN ---
    return main();
})()
1 Like

Thanks! This gives me some great ideas. I already got the first version working. I am going to make a template draft with it, and then add an action to the Scripting keyboard to copy it to the clipboard with the current draft UUID. It will make it really easy to generate an Action script to execute against the current draft.

Nick

2 Likes

You can also create a single action with this script, and then include it in other actions using the Include Action step.

Here’s my version of the script @draft8 provided. It will load all drafts marked with a “script” tag and a “lib” tag.

Here’s an action I have that used this approach. In this example, wrapSelection() is a function I have defined in one of my Library drafts.

2 Likes

Interesting. So are there any upsides to doing this via a separate action vs. including the reference in the script template?

The primary upside is that you have one copy of the action to maintain. If/when you want to make changes to how it works, you can do that in one place and all your other actions don’t have to change. With the copy/paste approach, you’ll need to replicate those changes on every action where you pasted the original code.

Posted a quick update to my updateLibs action this evening. Ran across an issue with the contants defined in the function having adverse side affects (exposing a bug in another script). Also removed the filter step - turns out I didn’t need it.