Applescript Support in TaskPaper 3

Hi Jesse

I’m trying to find an overview of Applescript support in TP 3. The posts I’ve come across are all referring to the state of scripting in FoldingText and what will also be present in TP 3. I haven’t used FT but have invested quite a lot of time in scripting TaskPaper 2 (including such offbeat use cases as using TP for rough drafts of Keynote presentations - Hog Bay Software) . Could you clarify what the Applescript dictionary for TP 3 is going to look like? Can we expect that all of the elements of TP 2 Applescript will remain? If not, what’s likely to be cut?

Thanks
Derick

TP3 has very rich scripting support. Not yet documented, but the intrepid can begin some preliminary explorations though ⇧ ⌘ W > resources > birch.js, and I’m sure that full documentation will follow, at some stage.

The core of the application is written in JavaScript, and the natural choice for its programming interface is therefore JavaScript rather than AppleScript. (You should still be able to find the FT discussion thread where the problem of maintaining two parallel APIs for these applications was discussed - the mood was that it would be sensible to just maintain a JS interface, with a bridge from AppleScript for calling the JS code for those who prefer to do it that way.

Your existing AS scripts will continue to run from copies of TP2, and learning a little JavaScript will enable you to write faster scripts, with even more reach, in TP3.

( I do remember pausing for breath before jumping into JavaScript from AS, so I sympathise, and I can assure you that having made the leap, JS turns out to be much easier and more pleasant that AS in several respects (easier records, richer libraries etc). It’s also more omnipresent - more useful not just on the web, but on iOS too (Drafts, TextExpander, and various other apps).

(I now do all of my OS X scripting in JavaScript for Applications, incidentally, after years of investing quite heavily in AS )

Can you give us an example of a script and how to run that script in the current TaskPaper? It can get many pointing into the right direction.

Of course – I will sit down at some point tonight and translate @derickfay’s TP2 → Keynote script into a TP3 version.

In the meanwhile, if you are running Yosemite onwards you can:

  1. Open OS X Script Editor,
  2. change the drop-down control at top left from AppleScript to "JavaScript*
  3. copy and paste the code from Script: move a @now or @next tag on to the next item
  4. (with a TP3 document open) click the Run button in Script Editor.

While it may seem daunting at first, I’m firmly in the camp that believes the move to javascript is a valid and worthwhile one. I’m still learning and looking at scripts made by @complexpoint and others helps. The weird part is how you get javascript code to be pared within TaskPaper. However, once you get over that hurdle, the rest is plain old javascript and to learn that, there is a wealth of information.

Thanks for the replies, from which it sounds like there isn’t a plan for Applescript support.

I get that JavaScript is probably the future automation on the Mac and in the long term probably worth investing some time in, esp. as I last worked in the language in 2002. At the moment though I don’t have the time to refresh and update my knowledge then poke through 52000+ lines of code in birch.js , and was hoping for a pathway whereby I wouldn’t need to rewrite all my existing scripts.

At this point I’ve already rewritten (or found equivalents for) many of them in Python for use with Editorial on iOS. So I’m thinking it will be more productive to work in Python and manipulate TaskPaper files directly. Unlike TP 2, TP 3 seems to immediately refresh when a file has changed externally, which would eliminate a significant obstacle to this approach.

poke through 52000+ lines of code in birch.js

Absolutely – I take your point, and I should have mentioned that all of the scripting interface is attached to the editor object of TP3.

If anyone did want to get a sense of what is there, before the documentation is released, the thing to search for would be OutlineEditor.prototype.

The interface may not be fully finalised yet, but it you look at the methods of OutlineEditor – things like:

    .isExpanded()
    .isCollapsed()
    .setExpanded()
    .setCollapsed()
    .expandAll()
    .collapseAll()
    .expandToIndentLevel(n)
    ...
    etc. etc.

(ignoring anything that starts with an underscore (it will be for internal use) you may get some idea of the richness and depth of the scripting interface.

I’m thinking it will be more productive to work in Python and manipulate TaskPaper files directly

Well, that’s possible – everyone’s mileage varies. I think, however, that when the documentation comes out, you may find that it automatically lifts a lot of weight which you would have to shift manually in Python.

(Amongst other things, it immediately gives you a particular array of lines (and all their properties) in exchange for any TP3 search path, including all the axes like descendant, following-sibling etc and all the filtering by tags and their values, and by line type).

And, of course, because of web browser competition, JavaScript is very highly optimised. It runs a lot faster than Python and Applescript : - )

Here is a quick TP3 translation of @derickfay’s Keynote script for TP2.

(Tested on Keynote 6.6.1 - Keynote’s applescript has changed, so Derick’s original script would need tweaking for TP2 too at this stage)

A few points:

  1. It assumes that a TP3 document, and an empty KeyNote presentation are both already open
  2. Copy paste the code into Script Editor (checking that it says AppleScript in the top left drop down, and click run.
  3. Here, I am calling the TP JS script from Applescript. It’s usually simpler really to do it from JavaScript for Applications, so that you don’t have a single page containing two languages. (Embedding JS in AS requires, for example, adding an escaping backslash before things like \n and \t for newline and tab).

Essentially, TP evaluates the JS script in the context of the front document, and returns a value.


-- RUN IN SCRIPT EDITOR WITH THE LANGUAGE SET TO APPLESCRIPT

-- This part of the code is a literal JavaScript string
-- Below, in AppleScript,
-- you will see a line in which this script is evaluated
-- in the context of the front TP3 document

set strTPCode to "
  function TP3Code(editor, options) {

    var oOutline = editor.itemBuffer.outline,
      lstProjects = oOutline.evaluateItemPath('//@type=project');

    return lstProjects.map(
      function (oProj) {
        var lstTasks = oProj.evaluateItemPath('descendant::@type=task'),
          lstNotes = oProj.evaluateItemPath('descendant::@type=note');

        return {
          title: oProj.body.string.split(':')[0],

          tasks: lstTasks.map(
            function (oTask) {
              return oTask.body.string.slice(2).split('@')[0];
            }
          ).join('\\n'),

          notes: lstNotes.map(
            function (oNote) {
              return oNote.body.string;
            }
          ).join('\\n')
        }
      }
    );
  }
"

tell application "TaskPaper"
  tell front document
    set lstProjects to evaluate script strTPCode
  end tell
end tell

tell application "Keynote"
  activate
  tell document 1
    repeat with theProject in lstProjects
      tell (make new slide)
        
        set object text of text item 1 to title of theProject
        set object text of text item 2 to tasks of theProject
        
        set strNotes to notes of theProject
        if strNotes is not "" then set presenter notes to strNotes
      end tell
    end repeat
  end tell
end tell

2 Likes

You may already have seen this:

https://jessegrosjean.gitbooks.io/taskpaper/content/scripting.html

Thanks very much for these resources - I hadn’t seen that link.

1 Like

Here, for comparison, is a translation of the TP to KeyNote script for use in Script Editor from JavaScript for Applications, rather than from AppleScript.

(Change the language selector at the top left of Script Editor to JavaScript)

A couple of notes on the code:

  1. The whole thing is optionally wrapped in an unnamed and immediately executed function. This makes all the variables local, rather than mixed in with all of JavaScript’s global library variables. One big advantage of doing this is that if we use the Safari JavaScript debugger in El Capitan, we will immediately see all our variables grouped together in the Local panel, rather than having to hunt for them among the globals.

  2. We’ve used the optional use strict line. It just checks that we have explicitly declared all our variables (a helpful habit) and gives us a few more useful error messages.

CODE for running from JavaScript for Applications (Yosemite onwards).

(function () {
  'use strict';

  function TaskPaperContextScript(editor, options) {

    var oOutline = editor.itemBuffer.outline;

    return oOutline.evaluateItemPath(
      '//@type=project'
    ).map(function (oProj) {
      return {
        title: oProj.bodyString.split(':')[0],

        tasks: oProj.evaluateItemPath(
          'descendant::@type=task'
        ).map(function (oTask) {
          return oTask.bodyString.slice(2).split('@')[0];
        }).join('\n'),

        notes: oProj.evaluateItemPath(
          'descendant::@type=note'
        ).map(function (oNote) {
          return oNote.bodyString;
        }).join('\n')
      }
    });
  }

  var TaskPaper = Application("TaskPaper"),
    lstProjects = TaskPaper.documents[0].evaluate({
      script: TaskPaperContextScript.toString()
    });

  var KeyNote = Application("Keynote"),
    kdoc = KeyNote.documents[0],

    oBulletSlide = kdoc.masterSlides['Bullets'],
    slides = kdoc.slides;

  // To experiment with Safari's JavaScript Debugger in El Capitan,
  // 1. uncomment the following line:

  // debugger;

  // 2. follow the instructions in the
  // OSX 10.11 JavaScript for Automation Release Notes

  KeyNote.activate();

  lstProjects.forEach(
    function (dctProj) {

      var oSlide = KeyNote.Slide();
      slides.push(oSlide);


      if (!dctProj.title) oSlide.baseSlide = oBulletSlide;

      oSlide.defaultTitleItem.objectText = dctProj.title;

      oSlide.defaultBodyItem.objectText = dctProj.tasks;

      oSlide.presenterNotes = dctProj.notes;
    }
  );
})();  

VIEW FROM THE JAVASCRIPT DEBUGGER IN EL CAPITAN

(we can step through the execution of the code line by line, watching the value of variables change. We can also explore application objects, and see their methods and properties)

1 Like

I suspect the new version will be more like FoldingText in its scripting support, but I’m one of those who is hoping the same level of Applescript support makes it into the new version eventually. Over the years that I’ve had TaskPaper 2, I’ve put together a lot of scripts that I trigger using Alfred, and it’s going to be a good deal of work to learn Javascript and recreate those for version 3. Applescript is challenging, but Javascript is much more opaque to look at for me as a nonprogrammer. Learning it and rewriting the scripts that I barely got working in v2’s Applescript is going to be a pain.

Assuming the scripting support is going to change – which it sounds like is happening – what would make the situation a lot better would be the ability to run TaskPaper 2 scripts at the same time TaskPaper 3 is open. That way I could use my existing scripts to add tasks from Alfred and change due dates without manually shutting down the new version first.

I think this should be possible right now. You can tell a specific version of an app with applescript like this:

Jesse

Well…the upshot of this was that I’ve ended up moving all my serious task & project management to OmniFocus. I’d given it a try a few times before and kept coming back to TP, but it stuck this time – I realized that 90% of what I was doing with scripts in TP (around scheduling, deferring, customizing views, quick entry, etc.) was already baked into OF, or trivially easy with Applescript. (And for other reasons – notably the realization that TP may be too intrinsically unstructured (however agile it may be) for my work processes). So rather than rewrite a whole bunch of scripts for a new language on the same platform, I’ve ended up switching platforms…I’ll still definitely buy TP 3 but am probably not going to make the investment in scripting that I had anticipated.

hi @complexpoint I’m trying to use this code from 5 years ago as base however I’m getting an error
like
error "Can’t get title of \"T\"." number -1728 from title of "T"
But I can’t see something about T on this code :confused:

-- RUN IN SCRIPT EDITOR WITH THE LANGUAGE SET TO APPLESCRIPT

-- This part of the code is a literal JavaScript string
-- Below, in AppleScript,
-- you will see a line in which this script is evaluated
-- in the context of the front TP3 document

set strTPCode to "
  function TP3Code(editor, options) {

    var oOutline = editor.itemBuffer.outline;
      lstProjects = oOutline.evaluateItemPath('//@type=project');

    return lstProjects.map(
      function (oProj) {
        var lstTasks = oProj.evaluateItemPath('descendant::@type=task'),
          lstNotes = oProj.evaluateItemPath('descendant::@type=note');

        return {
          title: oProj.body.string.split(':')[0],

          tasks: lstTasks.map(
            function (oTask) {
              return oTask.body.string.slice(2).split('@')[0];
            }
          ).join('\\n'),

          notes: lstNotes.map(
            function (oNote) {
              return oNote.body.string;
            }
          ).join('\\n')
        }
      }
    );
  }
"

tell application "Applications/TaskPaper.app"
	tell front document
		set lstProjects to evaluate script strTPCode
	end tell
end tell
tell application "Keynote"
	activate
	set thisDocument to ¬
		make new document with properties ¬
			{document theme:theme "White", width:1920, height:1080}
	tell document 1
		repeat with theProject in lstProjects
			tell (make new slide)
				
				set object text of text item 1 to title of theProject
				set object text of text item 2 to tasks of theProject
				
				set strNotes to notes of theProject
				if strNotes is not "" then set presenter notes to strNotes
			end tell
		end repeat
	end tell
end tell
1 Like

First guess would be that the Keynote scripting interface has changed.

Why don’t we start afresh.

What’s the use case ?

Now I’m using this to convert my taskpaper docs to keynote but with this way I can only seperate line and indents not projects, tasks and notes,
your code is better for a taskpaper document
but somehow I cannot make it work :confused:

property KeynoteTextFile : "/Users/mac/Dropbox/txt/sunum.txt"

property PresentationTitle : "My Presentation"
property _W : 1920 -- The width of each slide
property _H : 1080 -- The Height of each slide

-- Text properties for the cover title and each slide title
property CoverTextStyle : {font:"Gilroy Light", color:"black", size:96}
property TitleTextStyle : {font:"Gilroy Medium", color:"black", size:48}

-- Spacing above and below the title of each slide
property TitleMargins : {top:512, bottom:100}
-- Spacing between lines in the body of each slide
property VerticalSpacing : 75

-- Text properties for the body of each slide for
-- each level of tabulation
property Tabulations : {0.1, 0.2, 0.3, 0.4, 0.5}
property TextSizes : {32, 28, 24, 20, 16}
property TextColours : {"black"}
property TextFonts : {"Arial"}


set AppleScript's text item delimiters to tab
set notes to paragraphs of (read KeynoteTextFile)

-- Create new presentation with cover slide
tell application "Keynote" to tell (make new document with properties ¬
{document theme:theme "White", width:_W, height:_H})

set MyPresentation to it

set base slide of current slide to master slide "Blank"

tell the first slide to ¬
	set CoverTitle to make new text item ¬
		with properties {object text:PresentationTitle}

set properties of object text of the CoverTitle to CoverTextStyle
end tell

-- Create slides with content from Keynote text file
repeat with i from 1 to number of notes
if item i of notes is "" then exit repeat -- EOF

-- Get the text (without tabstops)
-- and the level of indentation
set [TextContent, TabValue] to ¬
	[last text item, number of rest of reverse of text items] ¬
		of item i of notes

if TabValue is 0 then -- Indicates title of new slide
	tell application "Keynote"
		set filePathLong to TextContent
		set mySubject to replaceText(":", "", filePathLong)
		
		tell (make new slide at end of slides of MyPresentation) to ¬
			set Title to make new text item ¬
				with properties {object text:mySubject}
		
		set properties of object text of the Title to TitleTextStyle
		copy position of Title to [_x, _y]
		set position of Title to [_x, |top| of TitleMargins]
		
	end tell
else -- TabValue is not 0, indicating slide content
	if TabValue > 5 then set TabValue to 5
	
	tell application "Keynote" to tell current slide of MyPresentation
		set n to number of text items
		
		set T to make new text item with properties ¬
			{object text:TextContent}
		
		tell object text of T
			set its color to item TabValue of TextColours
			set its font to item TabValue of TextFonts
			set its size to item TabValue of TextSizes
		end tell
		
		set position of T to ¬
			[(item TabValue of Tabulations) * _W, ¬
				VerticalSpacing * n + (|bottom| of TitleMargins)]
		
	end tell
end if
end repeat

-- Go to first slide of presentation and bring Keynote
-- into the foreground
tell application "Keynote"
set current slide of MyPresentation to first slide of MyPresentation
activate
end tell