AppleScript: Batch creating user attributes

I think it would be very helpful for adding new functionality to existing docs. For example, I have sets of agents and stamps that I use frequently. If I clone a template doc, that’s already set up. But if I want to add those agents and stamps to an older document, I have to do all the setup manually.

It would also help for sharing code, for the same reason. I have a couple things I’ve shared here that I’ve since upgraded on my machine, but haven’t shared the updates since I haven’t written all the upgrade instructions yet. It would be a lot easier if I could write a script to make the necessary modifications, rather than have a long list of upgrade steps that people might not follow correctly.

3 Likes

I accord - in part. But, if it’s engineering pain/big cost to do, could these things be handled as XML file imports as with Colour Schemes. I grant it’s not as easy as script someone else wrote (though you’d still need to customise the per attribute info). But if you could export stamps/custom attribute lists to file where the XML would be easily customisable (for most of those likely to need such features) before import to new TBXs, this might be a simpler solution?

Being able to create an attribute via script would reduce friction in setting up and modifying documents and getting information into TBX.

Meanwhile, I see one can use

exists attribute "MyAttr" 

to check whether an attribute is already there and, if not, alert to add it manually.

This is the only major “gee, it really would be nice if I could do this” thing I’ve come across. The rest has been more along the lines of “wow it can easily do this even though the Help seems to suggest otherwise.” Really a nice implementation, minimizing idiosyncrasies and gaps that one just has to learn to live with in AppleScript (and I assume JXA). It must have been a major effort behind the scenes. Thanks.

1 Like

Yeah that would be a good start.

That said, scripts enable more powerful data migrations from version to version.

I can only speculate, but the functionality is already there… I’m not an AppleScript wizard, but my understanding is that AppleScript is basically a hook into whatever functionality the application already provides. Based on @eastgate’s comment above I suspect the main reason for not exposing it is that he didn’t foresee people wanting / needing it, rather than it being complicated to do. But like I said, pure speculation on my part :slight_smile:

1 Like

I don’t think this will be terribly difficult. Building the foundations was tough (and required a lot of help from some fellow indie developers, some of the top people in the field). But now that it’s done, I think adding this won’t be terribly hard.

3 Likes

Being able to add well-named attributes would be useful in the script here.

For now, stuck to the existing sandbox attributes MyString, MyBoolean, MyDate so it should run “out of the box” on a new document.

Posted two faster and less convoluted scripts as an edit to the post here.

I ran into the three reply limit on the forum (to prevent spam?) and I wasn’t sure if an edit would pop up on the radar, so I mention it here.

The last script again illustrates the creation of a prototype for imported Mail messages (it was easy). Still would have been very helpful to be able to create attributes via script. But the existing AppleScript implementation in Tinderbox is great, very speedy (well, at least speedy for AppleScript😀).

1 Like

yeah! Hope Tinderbox team will bring in more AppleScript commands and more automation capability into Tinderbox for sure.

Ideally, stamps, prototypes, agents, user-created attributes, themes built in one tbx document could be saved as shared template and used to clone/generate any number of new starting tbx document for any upcoming projects without much extra manual work because Tinderbox is so versatile and indeed could be used for a wide variety of scenario.

And the best practice it to allow users to build-up and maintain/update only one classical template for each kind of typical working scenario, then easily New/generate new blank starting doc for any new project/task for the same kind of scenario sharing all the assets such as prototypes, stamps, agent, attributes, and scpt as of v8 in just one click/press.

Note that the AppleScript (and JXA) support is already robust, though the documentation hasn’t yet caught up.

You already have the two really big items on the wish list: prototypes and agents.

To create a prototype do something like this (click to expand)
tell application "Tinderbox 8"
	tell front document
		set newPrototype to make new note
		tell newPrototype
			set value of attribute "Name" to "pMyPrototypeName"
			set value of attribute "KeyAttributes" to "MyString;MySet;MyList;MyNumber"
			set value of attribute "badge" to "clock"
			set value of attribute "IsPrototype" to true
		end tell
	end tell
end tell
To create an agent do something like this (click to expand)
tell application "Tinderbox 8"
	tell front document
		set myAgent to make new agent
		tell myAgent
			set name to "MyNewAgentName"
			set value of attribute "AgentQuery" to "$Name.contains(" & quote & "1" & quote & ")"
			set value of attribute "AgentAction" to "$Color=" & quote & "red" & quote
		end tell
	end tell
end tell

Being able to create User Attributes, as discussed above, would be most useful.

And it would be nice (though for me less important) to be able to somehow automate access to the built-in prototypes and templates and (if possible) create stamps.

1 Like

The current backstage release does support creating new user attributes.

I’m open to other scripting facilities, but want to be careful not to add them simply for completeness. Extended scripting support is moderately tricky to add.

2 Likes

Great to hear about creating attributes. After experimenting further, I see reproducing the built-in prototypes and templates (or setting up other prototypes and templates) is already fully supported, so no extending needed there anyway.

SG

One central application of run-time attribute creation (either directly through osascript, or with the Tinderbox action language) would be in importing data.

When importing TaskPaper files containing both key-only tags and key-value tags for example, I would like to be able to create options for handling TaskPaper tags which are not yet present as Tinderbox attributes.

Options might include handling unknown keys by:

  • Deferring import and returning an explanatory message,
  • importing only tags with names already known as attributes and listing the omissions, or
  • Creating new attributes for any unknown tag names, and reporting any such creations.

Tags (a.k.a keywords) are normally mapped to $Tags in Tinderbox. Is the issue that Task Paper has no internal metadata other than an everything-bucket in which both individual strands of metadata and the latter’s per item values must be dumped?

I’d concur with the notion that now AppleScript support is present, it would be an easier(?) way to support discrete-to-source-app imports. Currently, these are essentially baked into the app and prone to breakage when the source app makes undocumented changes.

I do see that some tech-averse users my take fright at being asked to run a script. However, I think supporting script-based import (as well as, not necessarily instead of) might lessen the load on Eastgate for workflows which are used heavily but only by a small sub-set of the user base. Additionally, it may aid the user community (or those who understand the arcana of AppleScript) to assist in supporting niche import (and export) tasks.

If taken further I think it important, that attribute creations covers:

  • New attributes names must meet current Tinderbox attribute naming rules, the method fail if the supplied name is not legal. IOW, the user must not be allowed to use any old name. It is the users/script runners responsibility to track source to TB attribute name mapping. Scripting users should be experience enough to know - or look up such roles.
  • Data type. For my 2¢, I’d make supplying a data type a mandatory input. Defaulting to a String is likely only kicking a support issue down the road in this context.
  • Input data should match the data type. The script should do any data type coercion rather than simply dump the task on the receiving app (i.e. Tinderbox).

If the above seem prescriptive, it is really to aid the scripter so they handle errors where they occur (i.e. in the context of the script) rather than pass ‘bad’ data into Tinderbox causing potentially hard-to-spot process failures better fixed at source.

TaskPaper tagging in rather more sophisticated than in Tinderbox. Tags in TP can be key/value pairs. The value assigned to a tag can then be used for filtering and other operations. For example:

I think the case Rob described would be useful. Albeit, perhaps limited to expert TP and TB users who are automation savvy.

1 Like

Is the issue that …

No - it has key value pairs (strings, ISO 8601 dates, numbers), with an outline-aware (XPath-like) query language.

https://guide.taskpaper.com/reference/searches/

(and quite fast and low-friction outlining and key-value creation – a zero widget-count. I would typically find it faster to sketch out initial structures in TaskPaper and then import them into a fresh Tinderbox file than to start with the admirable, but more viscous, set of Tinderbox widgets)

So they aren’t tags (i.e. keywords) as such, as keywords in the generic sense are values. In Tinderbox terms is sounds as if every TP ‘tag’ is actually an attribute in Tinderbox terms, or at least all ‘tags’ with values are, whereas all other ‘tags’ could be treated as tags.

I’m not sure it’s a matter of sophistication as such but rather a lack of semantic alignment of terminology across both apps, with the same term meaning significantly different things in each.

This is where lack of consistent terminology across apps gets confusing. It’s not a matter of blaming one party or another, but it is important to understand the mapping when working across app boundaries (an experiential lesson from several decades experience doing data transfer work).

In a wider sense, I’d regard TP ‘tags’ and TB 'attributes like database fields. I’ve never previously come across keywords with values, though some apps, especially back in the 90s used to make keyword hierarchies.

Presumably this is being addressed backstage, but I would assume the AppleScript coding for attribute creation will work something like this.

set newAttribute to make new attribute with properties {name:"MyListUserAttribute", type:"list"}

Currently only name and value are listed as attribute properties in the AppleScript Dictionary. Presumably type will be added as well.

Then to set the value of the new attribute one would just do the same as for existing attributes, e.g.,

set value of attribute "MyListUserAttribute" to "bee;ant;cow;ant"

The script below creates a Tinderbox test document and illustrates the current behavior with the built-in sandbox variables. Run, then click the Test Note in Outline view. AppleScript variables can be changed and the script rerun to observe the changes.

Click to expand for testing script (version 2)
# for testing AppleScript to Tinderbox
# creates test Tinderbox document

-- vary these to observe results in Tinderbox:
set theNumber to 10 -- a string "10" is ok too
set theString to "bee;ant;cow;ant"
set theAppleScriptList to {"bee", "ant", "cow", "this is this;that is that", "bee"}
set theAppleScriptDate to current date
set theAppleScriptBoolean to true
set theAppleScriptColor to {65535, 0, 0} -- or "red", "green", etc., in which case do not use RGBtoHEX()

set date1 to (current date) - (12 * hours) + (15 * minutes) + 30
set date2 to current date
set theAppleScriptInterval to date2 - date1 --> result is in seconds


set theEscapedAppleScriptList to escapeAppleScriptList(theAppleScriptList)
--> {"bee", "ant", "cow", "this is this\\;that is that", "bee"}

tell application "Tinderbox 8"
	-- start new document if it does not already exist
	if not (exists document "AppleScript to Attribute Tests") then
		set newDoc to make new document with properties {name:"AppleScript to Attribute Tests"}
	end if
	-- add test note with attributes if it does not already exist
	tell document "AppleScript to Attribute Tests"
		if not (exists note "Test Note") then
			set newNote to make new note
			tell newNote
				set value of attribute "Name" to "Test Note"
				set value of attribute "KeyAttributes" to "MyNumber;MyList;MySet;MyString;MyDate;MyBoolean;MyColor;MyInterval"
			end tell
		end if
	end tell
	
	-- set the attribute values
	tell front document
		tell note "Test Note"
			
			-- no conversion needed!
			set value of attribute "MyString" to theString
			set value of attribute "MyNumber" to theNumber
			set value of attribute "MyBoolean" to theAppleScriptBoolean
			
			-- simple conversion
			set value of attribute "MyDate" to my dateToStr(theAppleScriptDate)
			
			-- simple if no embedded ; -- otherwise need to escape first
			set value of attribute "MySet" to my listToStr(theEscapedAppleScriptList)
			set value of attribute "MyList" to my listToStr(theEscapedAppleScriptList)
			
			-- if not using "red","green", etc need to convert to hex
			set value of attribute "MyColor" to my RGBtoHex(theAppleScriptColor)
			
			-- AppleScript expressed date differences in seconds; need to convert
			set value of attribute "MyInterval" to my secsToHMS(theAppleScriptInterval)
			
		end tell
		activate -- to force refresh of note so new values can be viewed in key attributes
	end tell
end tell


-- handlers (subroutines) to convert to strings that Tinderbox can recognize

to dateToStr(aDate) --> convert AppleScript date to string
	tell aDate to return short date string & ", " & time string
end dateToStr

to listToStr(aList) -- convert AppleScript list to ;-delimited string
	-- just use this if confident there are no embedded ;
	-- otherwise also need to use  'escape' handler
	set text item delimiters to ";"
	return aList as string
end listToStr

to escapeAppleScriptList(aList) -- escape embedded ; in AppleScript list items
	set escapedLst to {}
	repeat with i from 1 to length of aList
		tell item i of aList
			if it contains ";" then
				set my text item delimiters to ";"
				set textItems to text items of it
				set my text item delimiters to "\\;"
				-- Tinderbox needs a \ to escape the ;
				-- AppleScript needs an extra \ to escape the \
				set theItem to textItems as string
			else
				set theItem to it
			end if
		end tell
		set the end of escapedLst to theItem
	end repeat
	return escapedLst
end escapeAppleScriptList

to RGBtoHex(rgbTriplet)
	-- https://macosxautomation.com/applescript/sbrt/sbrt-04.html
	-- converts RBG triplet {0 to 65535,0 to 65535,0 to 65535} to hex
	set the hexList to {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}
	set the the hexValue to ""
	repeat with i from 1 to the count of the rgbTriplet
		set thisValue to (item i of the rgbTriplet) div 256
		if thisValue is 256 then set thisValue to 255
		set x to item ((thisValue div 16) + 1) of the hexList
		set y to item (((thisValue / 16 mod 1) * 16) + 1) of the hexList
		set the hexValue to (the hexValue & x & y) as string
	end repeat
	return ("#" & the hexValue) as string
end RGBtoHex

on secsToHMS(secs)
	-- Nigel Garvey https://macscripter.net/viewtopic.php?id=27203
	-- needs adapting to handle more than 24 hours
	tell (1000000 + secs div hours * 10000 + secs mod hours div minutes * 100 + secs mod minutes) as string to return text 2 thru 3 & ":" & text 4 thru 5 & ":" & text 6 thru 7
end secsToHMS

The result after running the script should look something like this:

AppleScript strings, numbers and “number strings” all come over directly. And Tinderbox directly recognizes AppleScript booleans. AppleScript dates require conversion to strings in a format that Tinderbox recognizes. AppleScript lists must first be converted to ;-delimited strings for placement in list or set attributes.

I don’t know enough about color and interval attribute types to comment.

All of this is reasonably straightforward. The main headache I’ve encountered is dealing with any ; embedded within items of AppleScript lists. The script shows one way they could be ‘escaped’. That works fine for MyList but results in surrounding quotes around one member in MySet (or MyList.unique) that would then need to be stripped out.

I’d suggest adding optional “default” and “suggested” properties, for completeness:

set newAttribute to make new attribute with properties {name:"MyListUserAttribute", type:"list", default: "red;green;blue", suggested:"red;blue"}
1 Like

For dates, from my notes based on testing data imports, there is no built-in coercion paths from [some format] to a Tinderbox Date-type. Try changing source dates to a Unix epoch base, something like as discussed here.

Colours are essentially a string. You can give the (case-sensitive) name of a colour or a short/long form hex value string (i.e. “#fff” or “#ffffff”).

Per my aTbRef link above, the only forced type-choice you can make when populating a new attribute is Boolean/Number or else (default) String. This is why I think attribute creation without an explicit type parameter is just a vector for causing problems. Anyone knowing enough to script Tinderbox in AppleScript should be able to figure out the type of attribute needed and state it explicitly.

Just like slashes and ampersands in Name/Path strings, semi-colons are a problem as list-handling under-the-hood in Tinderbox offers no way to (temporarily) escape semi-colons. This is why processing $Text can be troublesome unless you convert semi-colons to some other string and then back when done.

In fairness, step back 18 years (!) to Tinderbox’s inception and these sort of issues were likely not design concerns and retrofitting support for escaping some characters isn’t easy (cheap) to do.

1 Like

I see now how the disclosure works:

<details>blah blah blah</details>

yields

blah blah blah

Nice feature - thanks for “disclosing” it @sumnerg