How best to debug Applescripts running inside DT3

Here a more general question than my little problem in https://discourse.devontechnologies.com/t/trouble-with-performsmartrule/52461:

What is the best way to debug Applescripts running inside DT3? For example but not limited to Smart Rule scripts. I was hoping the Log window would show some details, but there is nothing. - Has DT3 something like a “Debug” switch?

2 Likes

Hi Leo,
I use ScriptDebugger. May be that would be something for you too.

Best,
Stefanie

The next release will improve this and display an error message at the bottom of the window and select the range causing it in case of compile issues.

Thanks for the tip, Stefanie.

Can Script Debugger debug scripts which are triggered inside DEVONthink like the rule scripts?

I think the difficulty is that you are flying blind:

  1. Two distinct evaluation spaces (Applescript can’t see what the shell is up to)
  2. A mutating loop which leaves no trace.

You can lose less time in debugging by making things visible:

  1. Try to do everything in one evaluation space (all in AppleScript, for example)
  2. Use a map, which can show you all the values generated, rather than an immediately mutating loop, which is a blind process.

If I wanted an update to some property of a set of records (as in the title adjustments of your performSmartRule example), I would probably generate two visible sets of values (existing titles, and then updated title values), to check that they are what I wanted, before committing them to my record titles:

e.g.

Existing titles of some selected records -->

{"What I Wish I Knew When Learning Haskell 2.2 ( Stephen Diehl )", 
"Translation of “Monads are just Monoids in the Category of Endofunctors” 
| tailcalled", "tackling the Awkward squad.pdf", 
"Recommended Haskell reading and watching - link", "mpickering - Automatically Apply HLint Suggestions",
 "Lecture notes and assignments.webloc", "lambdaCalculus.pdf", 
"Imperative Functional Programming.pdf"}

What their values would be if transformed by some function (e.g. toUpper) -->

{"WHAT I WISH I KNEW WHEN LEARNING HASKELL 2.2 ( STEPHEN DIEHL )", 
"TRANSLATION OF “MONADS ARE JUST MONOIDS IN THE CATEGORY OF ENDOFUNCTORS” 
| TAILCALLED", "TACKLING THE AWKWARD SQUAD.PDF", 
"RECOMMENDED HASKELL READING AND WATCHING - LINK", "MPICKERING - AUTOMATICALLY APPLY HLINT SUGGESTIONS",
 "LECTURE NOTES AND ASSIGNMENTS.WEBLOC", "LAMBDACALCULUS.PDF", 
"IMPERATIVE FUNCTIONAL PROGRAMMING.PDF"}

And then finally, if things look OK, uncomment and recomment to get a version which updates the records:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

on run
    tell application id "DNtp"
        set xs to selection
        script smartRuleApplied
            on |λ|(x)
                -- 1. Check existing values
                name of x
                
                -- 2. Check values returned by some function (here just case-change)
                -- toUpper(name of x)
                
                -- 3. Update values
                -- set name of x to toUpper(name of x)
            end |λ|
        end script
        
        my map(smartRuleApplied, xs)
    end tell
end run


-- GENERAL FUNCTIONS 
-- https://github.com/RobTrew/prelude-applescript


-- A SIMPLE TEST FUNCTION TO APPLY 

-- toUpper :: String -> String
on toUpper(str)
    set ca to current application
    ((ca's NSString's stringWithString:(str))'s ¬
        uppercaseStringWithLocale:(ca's NSLocale's currentLocale())) as text
end toUpper


-- MAP RATHER THAN LOOP, TO KEEP THINGS VISIBLE

-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of 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

-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

(You could also, of course, do it in JavaScript for Automation – map is available to JS out of the box)

(() => {
    'use strict';

    const
        dt = Application("DEVONthink 3"),
        xs = dt.selection();

    return xs.map(x => x.name());

    // return xs.map(x => x.name().toUpperCase());

    // return xs.map(x => x.name = x.name().toUpperCase());
})();

That’s would be great! So I guess for the moment the best is to write stuff to the system logs. (I’m sure that’s somehow possible in Applescript.)

How can I use “Javascript for Automation” in the Smart Rules? - This might be a question for @cgrunenberg

You can’t. Only AppleScript is supported in the smart rule’s Execute Script action.

Also, the solution is much simpler than all that :slight_smile:
Use this form in Script Editor and you can test the Execute Script function on a selection…

tell application id "DNtp"
set sel to (selection as list)
my performSmartRule(sel)
end tell

on performSmartRule(theRecords)
tell application id "DNtp" 
repeat with theRecord in theRecords 
-- Do stuff
end repeat 
end tell 
end performSmartRule

You can use JavaScript in general, and JavaScript for Automation in particular (JS with an instance of Apple’s Automation library) wherever you can use AppleScript.

The best solution would, of course be to ask @cgrunenberg to consider implementing the approach used by Hook for macOS in this kind of context (interpreting any script starting with a //Javascript comment line as JS rather than AS).

That would allow for the ideal - a single evaluation context.

It’s not strictly necessary though – we can always use JS within AS, and that can be a good way of filling a number of fairly basic gaps in AppleScript’s libraries.

Two approaches (code examples below)

  1. Vanilla JS from AppleScript with NSContext.evaluateScript
  2. (JXA) JavaScript for Automation with do shell script "osascript ..."
Vanilla JS from Applescript
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

on run
    evalJS("[1, 2, 3, 4].map(x => x * x)")
end run

-- evalJS :: String -> String
on evalJS(strJS)
    set JSC to current application's JSContext's new()
    unwrap((JSC's evaluateScript:(strJS))'s toObject())
end evalJS

-- unwrap :: NSObject -> a
on unwrap(objCValue)
    if objCValue is missing value then
        missing value
    else
        set ca to current application
        item 1 of ((ca's NSArray's arrayWithObject:objCValue) as list)
    end if
end unwrap
JXA from AppleScript
on run
    evalJS("Application('DEVONthink 3').selection().map(x => x.name())")
end run



-- evalJS :: String -> IO String
on evalJS(s)
    do shell script ¬
        unlines({"osascript -l JavaScript <<JXA_END 2>/dev/null"} & {s} & {"JXA_END"})
end evalJS


-- unlines :: [String] -> String
on unlines(xs)
    -- A single string formed by the intercalation
    -- of a list of strings with the newline character.
    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

If we define ‘simple’ as high signal-to-noise, brief, uncluttered, or fewer moving parts rather than just as 'familiar to me personally', then JS often allows for much simpler formulations than AppleScript, particularly in terms of basics like Regexes, URL encoding and decoding, String and Number functions, etc etc.

Learning JS also reaps rewards in iOS scripting, where AppleScript is unusable.

I have a script that saves all URLs from current window to DevonThink.

The first time I execute it, it doest work, it only works if I execute it the second time. This also happens if I dont execute the script for a while.

Note 1: If I execute the script again after a few min then works fine (No second execution required)
Note 2: If I execute it with Apple’s Script Editor then there is no issues.
Note 3: I execute the script using key shortcut (eg: N N) with Bettertouchtool app.

Any idea what could cause the first execution to be lost?
This would make sense if DevonThink isnt open, but when I execute the script the first time it is in the background.

Thanks.

I wonder if there is an inadvertent dependence on window focus.

Are you able to show us the script ?

Yes! Do you have BetterTouchTool? perhaps you can test it out and let me know if you have the same issue.

Sure