AppleScript: Batch creating user attributes

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

Beat me to it :grin:. This is how you set a caption to replace the default ā€˜Detailsā€™:

[details="Click to expand for more info"]
```
blah blah blah
```
or perhaps just normal text
[/details]

The result is this:

Click to expand for more info
blah blah blah

or perhaps just normal text

It looks like you can use either square or angle brackets for the details tag. Markdown has more than one mark-up type for quite a few styling methods.

You learn something new every day!

@PaulWalters @mwra And those (like me) who are a little weak on Markdown can click the ā€˜gearā€™ icon while composing and choose Hide Details.

I saw Rob Trew @ComplexPoint do that nifty disclosure triangle thing when posting a script in another thread and was envious not just of the script but of the triangle itself, and, now aware it could be done, I set about to reverse engineer it!

1 Like

It seems you need to use square brackets if you want to use use a custom caption for the ā€˜detailsā€™ section.

Spoiler alert

Mileage may vary.

A detail block can be used more than once in a post:

Another alert

Yes, mileage may vary.

1 Like

Iā€™ve edited the post above to include a revised script with a stab at including color and interval.

In the vast majority of cases (string, number, list without embedded ; and set without embedded ; and date) it would seem at most a simple conversion on the AppleScript side does the job. My suggested simple date-to-string conversion works well for me, as it lets AppleScript worry about Unix and all that, but there many other approaches.

color (if using AppleScript RGB triplet list and not just "red","green", etc.) and interval require more complicated conversions, with possible approaches in the script.

And if there are embedded ; within AppleScript list items (presumably not a common scenario) the conversion can get a little messy. Tinderbox needs a \ to escape the ; and AppleScript needs a \ to escape the \ that it passes to Tinderbox. Also, as noted, after you get the escaping right, you find it works well with a list attribute but results in surrounding "" in a set attribute (part of the internal ā€œde-dupingā€?). From a user perspective it would make sense to have the same behavior with a list as with a set. Perhaps one should be using surrounding "" for both?

But, yes, this is a powerful and already highly usable implementation! These sorts of issues are normal in the AppleScript world. When using AppleScript with this or any other app it seems there is no substitute for the old ā€œshove the stick down into the pile, twist it around, and see what emergesā€ approach. Just back up carefully first and wear thick gloves and boots.

I had just assumed that type: will be mandatory in the make new attribute statement. It would be helpful to have default: and suggested: but perhaps their use can be optional in the statement?

Over time the AppleScript-related documentation presumably will touch on such points as permissible attribute names.

1 Like

Iā€™m going to be real busy until I get my thesis submitted but I had been considering making a new aTbRef-like but separate resource for AppleScripts (ir would overload an already large aTbRef) but Iā€™d not given much thuoght as to what contents should be.

Incidentally just found this nice (64-bit [sic]) donationware app ā€˜AppleScriptHTMLā€™ (here) which might prove useful if such a resource goes ahead.

Very happy to contribute any JavaScript parallels if you feel they would be useful.

(FWIW Iā€™ve been maintaining AppleScript ā‡„ JavaScript for Automation parallels for my generic functions)

(and just using Github Markdown - I did look at AppleScriptHTML but for some reason I err on the side of caution with unsigned apps )

Github MD example (renders in with syntax colodring with Github CSS) at:
https://github.com/RobTrew/prelude-jxa/blob/master/Applescript%20and%20Javascript%20parallel%20functions/readFile.md

2 Likes

Interesting. Itā€™s been about 10 years since Iast did much AppleScript work. Sounds like itā€™s time to dust off some rusty skills.

Yes, very useful for me. Plus any corrections/enhancements to the AppleScript handlers, provided they donā€™t get too abstract for casual scripters to follow reasonably easily.

I think an example handler/function for each attribute type will reduce a lot of the friction associated with getting data into Tinderbox via script.

2 Likes

I see an encouraging note on the release email for Tinderbox 8.02:

ā€˜Scripting improves to let you add user attributesā€™

Havenā€™t yet figured out the syntax - I seem to be able to create an Attribute object, but not, so far, to add it to the existing collection of attributes.

Any working examples in AppleScript or JS so far ?

I tried this:

Doesnā€™t seem to work yet (in 8.0.2 b373).

Concur. The example code supplied for this task:

tell application "Tinderbox 8"
	set doc to document "test1.tbx"
	set a to make attribute in doc
	set the kind of a to "date"
	set the name of a to "myNewAttribute"
end tell

Works except you canā€™t name/rename the new attribute. Worse, its default name ā€˜newAttributeā€™ whilst allowed doesnā€™t even follow Tinderbox style which would use ā€˜NewAttributeā€™.

Tried making 2 new attributes:

tell application "Tinderbox 8"
	set doc to document "test1.tbx"
	set a to make attribute in doc
	set the kind of a to "date"
	set the name of a to "NewAttributeA"
	set b to make attribute in doc
	set the kind of b to "boolean"
	set the name of b to "NewAttributeB"
end tell

Running this crashes AppleScript Debugger (unclear why - TB itself is unaffected) but regardless the result is one new attribute.

For now, Iā€™d avoid trying to make new attributes via AppleScript. Iā€™m sure this will get fixed. There seem to be some experienced AppleScripter users here and Iā€™m sure (if not doing so already) that Eastgate would welcome extra detail on problems via email (or in the Backstage forum, for those with access). Iā€™m conscious that too much discussion here might put off casual users of AppleScript before this wonderful new addition to the app is truly settled down. Iā€™m sure that behind the curtain none of the fixes needed are simple.

properties are not supported for any Tinderbox scripting. Itā€™s on the list.

properties are not supported for any Tinderbox scripting

Easy workaround for that in meantime. After name default to ā€˜newAttributeā€™ with no way of changing it is addressed, one can just do something like this, similar to what works now for creating new notes without using with properties.

tell application "Tinderbox 8"
	tell front document
		set newAttribute to make new attribute
		tell newAttribute
			set name to "MyNewAttribute"
			set kind to "list"
		end tell
		-- and elsewhere in script ...
		tell note "My Note"
			set value of attribute "MyNewAttribute" to "cow;bee;dog"
		end tell
	end tell
end tell