Scripting Tinderbox in v8

Stub to allow thread move.

PS I wonder if you feel entirely happy with the formulation new support for AppleScript (and use of the section name Applescript) ?

One might argue that:

  • itā€™s technically new support for osascript, which has both AppleScript and JavaScript bindings.
  • While use of AppleScript makes good sense for those already familiar with it, it is perhaps less clear whether it now makes a sensible investment (or sound recommendation) for anyone beginning the use of osascript.

Not just because JS:

  • has a richer set of built-in functions for regex, sorting, url encoding/decoding etc, and more user-friendly records/dictionaries, or because
  • familiarity with JS is an investment of time with more generous and more broadly distributed harvests (web scripting, iOS scripting, and now Omni app scripting (OmniGraffle and OmniOutliner already, OmniFocus next) and Quark scripting)

But also because Apple (perhaps because of its iOS revenue focus) has now parked AppleScript in legacy or sunset mode, with no sign of future development, whereas the Apple JS interpreter, used both for osascript and Safari, continues to be actively and competitively maintained, and macOS app houses like Omni and Quark are now doing their new scripting developments in JS interpreters which are quite separate from AppleScript.

(OmniJS uses a non-osascript embedding of the Webkit (Safari) JS interpreter, Quark is now using a non-osascript embedding of the V8 Chrome JS interpreter. I have had years of fun with AppleScript, but if I were starting macOS scripting now, I would certainly start with JS instead, and there are plenty of books and resources for learning it).

Just a thought.

This is broad church with a lot of users who arenā€™t from a tech background. I take the point. But, within aTbRef at least, itā€™s way beyond my expertise or mission to explain the tech-ier point of scripting types.

I donā€™t mean that dismissively. Support for AS, and by inference from your post, support for other script type, is new and welcome. As Iā€™ve intimated elsewhere, a better AS/TB guide is probably something for outside aTbRef. Iā€™m happy to have a go, even happier if anyone else wants to write it!

So, I think weā€™re vaguely on the same page?

No problem with which language you provide examples in - my reservations are about:

  • the technical accuracy of calling it ā€˜support for AppleScriptā€™ (I donā€™t think thatā€™s quite what it is :slight_smile: )
  • the danger of inadvertently encouraging people to start learning AppleScript at this (very) late stage in the day, and failing to mention that they could equally well (or more profitably) be using JS with Tinderbox instead.

I think I would suggest the approach taken in Appleā€™s own scripting guidelines, of mentioning both osascript languages:

Quote:

In OS X 10.10, JavaScript became a peer to AppleScript in OS X

Rob, thanks but this goes way beyond my understanding.

I get that Apple has ben equivocating about ongoing supporting but how is this new feature not offering use of AS scripting of some Tinderbox features. sure, it isnā€™t all, but I think we need to understand:

  • Retro-fitting ability to AS-script an app isnā€™t trivial work
  • The appā€™s maker is a small shop so ROI has to be a factor
  • Many people ā€˜justā€™ want to be able to do a bit more automation.

I get that might seem odd from the perspective of a scripting export, but it is as it is.

Anyway, Iā€™m just a Tinderbox user (albeit for some 14+ years) but not from a Comp Sci background. I learnt things on the fly doing a web start-up in the mid-90s. Iā€™ve learned most of what I know by the kindness of starangers and aTbRef is part of an attempt at karmic balance for that. But, I stand way outside tech wars about definitions, protocols, etc.

Or am I misunsterstanding? (wouldnā€™t be the first time :roll_eyes: )

The Tinderbox scripting interface that has been written (and continues to be developed) is not an AppleScript library ā€“ there is no longer any such thing ā€“ it is an osascript library.

The same osascript library can be used either by JavaScript or by AppleScript, there is nothing at all that is AppleScript-specific about it. It is not an AppleScript library.

You can view and use these libraries either in JS mode or in AS mode, and as Apple themselves explain, these are just two different ways of using exactly the same scripting interface.

(In Script Editor, for example, you choose which syntax to use (JS or AS) with a dropdown switch at top left)

The point is that in 2019 (since Yosemite in fact) is has been incorrect to equate osascript with AppleScript, they are two modes (differing by a single command line switch), of using the same scripting interface.

The new support is for OSA Script - which is now JavaScript (still in active development) or Applescript (no longer actively developed).

You can experiment with it in Terminal.app

1 Like

More practically what I would tend to suggest is that in lieu of cutting it back to ā€˜Applescriptā€™, you use a section title drawn from a set of possibilities that might include:

  • OSA Script support (AppleScript and JavaScript)
  • AppleScript and JavaScript support
  • OSA Script support

and that, ideally, you show examples of a few of the gestes ƩlƩmentaires in both syntaxes (AS and JS, in no particular order)

and perhaps provide a link to Appleā€™s (pretty good) Mac Automation Scripting Guide

Which consistently gives examples in both OSA Script syntaxes (AS and JS). See, for example:

Manipulating lists of items

(Reducing Tinderboxā€™s new osascript interface to ā€˜Support for AppleScriptā€™ just seems unnecessarily procrustean)

1 Like

Two different Script Editor views (JavaScript and AppleScript) of the same file: Tinderbox8.sdef (the scripting definition file). Notice that there are small (systematic) differences in the two function name conventions.

/Applications/Tinderbox 8.app/Contents/Resources/Tinderbox.sdef


1 Like

All understood, Iā€™ve taken that onboard and will expand the definition. Iā€™ll likely keep AppleScript as the over-arching label, as I think thatā€™s more familiar to a lay audience, but will improve the note re what scripting implies.

Also, aTbRef is just my personal outboard manual. You might want to pass the same observation to Support for the app Help (although I do make tangential contributions to the source TBX for the app Help, I donā€™t own that doc).

1 Like

I agree. What are your favorites, Rob? Books and ā€œresourcesā€?

Itā€™s been said a few times in this forum that scripting is a ā€œnicheā€ for most Tinderbox users. I disagree ā€“ Tinderbox is full of procedural and objective concepts and tools. Queries, actions, templates, stamps, rules, etc. Itā€™s not possible to do much of value with Tinderbox without stepping into that facet of Tinderbox. Developing action coding, template building, etc., skills is the path to realizing the full value of the software we purchased.

To date, the new osascript capabilities have to some extent been characterized as ā€œhereā€™s something interesting that was recently bolted onā€ rather than ā€œhereā€™s a significant extension of Tinderboxā€™s personalityā€. The latter characterization is, I suggest, something exciting and its value worth explaining to users. The message is not ā€œoh, thatā€™s for a few advanced users ā€“ you donā€™t really need to know about itā€, but rather "out of the box, Tinderbox is terrific ā€“ and letā€™s show you how to really crank up the value ā€¦ "

Amen.

Scripting of Tinderbox (as opposed to internal actions, etc) seems to fall across two existing fora - Automation and Inter-app. As few folk read all sub-fora/posts, i.e. like to focus on an area, I do wonder if there is reasonable scope to add a new category (sub-forum) focussing on AppleScipt/OSAscript. Iā€™d expect that might allow a subset of members here to have an active discussion on this subject without it being distracting to those who find this aspect of work a bit hard to follow.

Apropos the quote above, that doesnā€™t stop us posting the finished/polished outcomes of that sub-forum to those more widely read.

I had the same thought. The related threads that have blossomed since v8 could be moved there.

1 Like

The best reference for JavaScript (which also includes introductory material) is always Mozillaā€™s excellent and canonical set of pages:

(not w3schools, which is less reliable, and rather less ā€˜officialā€™ than I think it may strive to appear :slight_smile: )

For books and learning generally, the key point is that the JavaScript language itself has no connection with HTML, but for historical reasons teaching materials often assume that you are using a JavaScript interpreter embedded in a browser, rather than in a desktop application or in generic macOS scripting.

That means, for example, that in the case of the popular and well-written ā€˜Eloquent JavaScriptā€™ book by Marijn Haverbeke.

https://eloquentjavascript.net/

  • The first 10 chapters are good and relevant
  • the rest assumes that you are using JS in a browser, and is not relevant to macOS scripting.

In particular, people often assume that if you are using JavaScript, then the DOM (Document Object Model) must be available and relevant. In fact the DOM is only present and relevant for JS interpreters embedded in browsers ā€“ it is not part of JavaScript itself.

A quick way to list the full set of:

  • Core JavaScript library objects and,
  • macOS Automation library objects

Is to:

  1. Launch macOS Script Editor,
  2. switch the language tab at top left to ā€˜JavaScriptā€™
  3. enter the keyword this
  4. and hit the run button.

You will see a listing of the top level terms of core JavaScript, followed by the top level terms of the macOS Automation library.


Note, for contrast, that if you do something equivalent at the console prompt of a browser, you will see a huge list of DOM library objects (not part of JS itself) which form the interface to the browser page.

FWIW, in the case of Safari, you could try this contrastive experiment by:

  1. Activating the Safari Develop menu (Preferences > Advanced > Show develop menu in menu bar)
  2. Getting to the JS console prompt (Develop > Show JavaScript console)
  3. Entering the keyword this

On the macOS Automation library which is automatically available to osascript in JS mode, and is referred to at the marketing level as either JavaScript for Automation or JXA, there is:

and you can list all the top-level terms of that library by

  • Pasting the the whole of the code below into Script Editor (scrolling down to get the full script)
  • checking that the language tab at top left is set to JavaScript,
  • tapping the Run button,
  • pasting the new clipboard contents into an editor.

(In the early stages, you can ignore the very useful but more advanced ObjC sub-tree of the Automation library, which gives full access to all of Appleā€™s Foundation and Webkit etc libraries)

(() => {
    'use strict';

    // Rob Trew 2019

    // Indented outline of macOS Automation library keys
    // (copied to clipboard)

    // main :: IO ()
    const main = () => {

        // keyTree :: Object -> Tree String
        const keyTree = except => name => obj => {
            const go = seen => o =>
                concatMap(
                    k => !elem(k, seen) ? [
                        Node(k, go(seen.concat(k))(o[k]))
                    ] : [],
                    sort(Object.getOwnPropertyNames(o))
                );
            return Node(name, go(except)(obj));
        };

        // showIndentedTree :: Tree String -> String
        const showIndentedTree = tree => {
            const go = indent => tree =>
                unlines(
                    [indent + tree.root]
                    .concat(concatMap(go('    ' + indent), tree.nest))
                );
            return go('')(tree);
        };

        const strOutline = showIndentedTree(
            keyTree([
                'Automation', 'name', 'prototype',
                '__private__', '0', 'length'
            ])('Automation')(Automation)
        );

        return (
            standardSEAdditions()
            .setTheClipboardTo(strOutline),
            strOutline
        )
    };


    // AUTOMATION LIBRARY ---------------------------------

    // standardSEAdditions :: () -> Application
    const standardSEAdditions = () =>
        Object.assign(Application('System Events'), {
            includeStandardAdditions: true
        });

    // GENERIC FUNCTIONS ----------------------------
    // https://github.com/RobTrew/prelude-jxa

    // Node :: a -> [Tree a] -> Tree a
    const Node = (v, xs) => ({
        type: 'Node',
        root: v, // any type of value (consistent across tree)
        nest: xs || []
    });

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) =>
        xs.reduce((a, x) => a.concat(f(x)), []);

    // elem :: Eq a => a -> [a] -> Bool
    const elem = (x, xs) => xs.includes(x);

    // sort :: Ord a => [a] -> [a]
    const sort = xs => xs.slice()
        .sort((a, b) => a < b ? -1 : (a > b ? 1 : 0));

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

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

Finally, JS has been through various evolutionary stages, and osascript JS uses the up-to-date ā€œSixth editionā€ syntax of EcmaScript 2015, also known as ES6.

Haverbeke and the Mozilla pages show ES6 syntax - some earlier materials may show only ES5 syntax.

7 Likes

Extremely helpful post, Rob.

Looks like my ā€œsummer readingā€ just got mapped out :sunglasses::raised_hands:

Thank you for the thorough answer and pointers.

ā€¦ and, when will we the publication of ā€œThe JavaScript for macOS Bibleā€, by Rob Trew?

Happy summer experimentation and reading :slight_smile:

The JavaScript for macOS Bible

Now that we (and Appleā€™s revenues and priorities) have been all overrun by this appalling telephone epidemic, I think it might, alas, have to be the JavaScript for iOS and macOS bible ā€¦

(The concepts part could pretty much be shared, but the tutorial, cookbook, and reference sections might need some subdivision)

1 Like

Outdated already. Now iPadOS needs to be in the title too.

1 Like

Indeed. And a little hard to imagine 3 platforms being long sustained.

(Quite apart from the impact that more ā€˜trade warā€™ would have on Appleā€™s supply lines, margins, and iOS market in China)

1 Like

A tale of caution in reaction to the discussions further upthread. I heard about the advantages of JavaScript and the demise of AppleScript a while back. Brimming with more enthusiasm than expertise, I set about to make the transition. Alas, I found it hard or even impossible to do really basic things that are easily done in AS. For example, take this and this.

I thought those unhappy encounters were just part of the inevitable struggles on the way up the learning curve, until I learned that, in contrast to core JS , there are gaps in the implementation of JXA and far less documentation and far fewer examples than are available for AppleScript. So for those reasons I would guess that AppleScript, for all its warts, might be with us for a while longer. Itā€™s just too useful.

That said, I look forward to seeing more examples here of scripting Tinderbox with JS/JXA!

Havenā€™t noticed any friction in practice ā€“ if one does hit an edge case with a particular library (this did happen to me once with OmniGraffle, some time back) you can actually call a line of AppleScript from inside a JS script.

The main advantage of JS for first steps in scripting (apart from easier records, sorting, url (en/de)coding etc etc) is just that it can also be used with on iOS/iPadOS (and, of course, on web pages).

(And the only current cross-platform scripting model is the omniJS one, which already allows automation code written for OmniGraffle/OmniOutliner to be used on phones and iPads as well as on macs ā€“ OmniFocus too, in due course, apparently. )

Footnote:

A crude way of adding a line of AS to a JS script would be:

// evalAS :: String -> IO String
const evalAS = s =>
    sa.doShellScript(
        ['osascript -l AppleScript <<OSA_END 2>/dev/null']
        .concat([s])
        .concat('OSA_END')
        .join('\n')
    );

But this is a little faster and more complete:

// jsEvalOSA :: String -> String -> IO String
const jsEvalOSA = (strLang, strCode) => {
    const
        oScript = ($.OSAScript || (
            ObjC.import('OSAKit'),
            $.OSAScript))
        .alloc.initWithSourceLanguage(
            strCode, $.OSALanguage.languageForName(strLang)
        ),
        js = ObjC.unwrap,
        errText = e => js(js(error)
            .NSLocalizedDescription),
        error = $(),
        isCompiled = oScript.compileAndReturnError(error);

    return isCompiled ? (() => {
        const v = oScript.executeAndReturnError(error);
        return js(v) !== undefined ? (
            js(v.stringValue)
        ) : errText(error);
    })() : errText(error);
};