AppleScript: Batch creating user attributes

Hi there! Yesterday I wanted to paste a CSV from Numbers, so I first copied the column names in order to create user attributes, cause I learned here that Tinderbox will then match the pasted CSV to those attributes which is very nice.

I then realized I was too lazy to do this all manually and launched UI Browser which is also very nice. However several hours later I’ve got an AppleScript, which seems to be the answer - or at least a good starting point - to the unsolved question of creating (not transfering yet…) user attributes for those of use who use more than one Tinderbox.

This script batch creates user attributes. It handles names separated by comma, tab or new line. If names have characters which aren’t allowed in attribute names it will replace those with underscores, so I never have to think of this again (if I have not forgotten some…).

In the “property” section you can set the options known from Document Inspector’s “User” tab, but not on single attribute level; for each run those options will be assigned to all attributes, meaning that the only difference between attributes created in one run is their name.

Run from Script Editor it should be easy to change options between runs (but there are also other ways to use it, more on this later). After a run you can change options of single attributes of course one by one, but probably it makes more sense to do more runs with different options than changing options manually afterwards.

Tinderbox Inspector must be open as UI scripting is used (and possibly you have to allow UI scripting in macOS, but I’m not sure if this is still necessary).

By changing defaultAnswer you can change what is presented in the dialog that asks for the attribute names:

  • With “the clipboard” (without quotes) you’ll get… e.g. the column names you just copied in Numbers.

  • With “” you’ll just get an empty text field. If you use this I recommend using commas or tabs, as for new lines you’ll have to hit option + return.

But what if you know there are those attributes you’ve created over and over again in every new Tinderbox and you’re just tired of typing (ok, pasting) those again and again? (Yes, I know stationaries…)

  • In this case I’ll bet your choice will be the AppleScript list. Just store your attributes names like this {“A”,“B”,“D”} and you’ll never need to create those manually again!

Sounds nice so far?

Ok.

Now imagine what storing different versions of this script with different AppleScript lists and/or different options could do.

Got it? :smirk:

If you have a script version for every frequently used set of user attributes, then it’s just:

  • Open in Script Editor,
  • run,
  • wait,
  • close.

That’s it.

And if you make an AppleScript app you don’t even need to open Script Editor for this.

One click. There is your set of user attributes. Across Tinderboxes. :sunglasses:

Didn’t really tested yet, only while writing, but it’s seems to be very smooth and with a lot of potential

Perhaps that’s the “missing link” between blank Tinderboxes and stationaries? :slight_smile: What do you think?

			-- Handler from http://applescript.bratis-lover.net

			-- This script batch creates user attributes. It handles names separated by comma, tab or new line. (In the first place I started writing this as I was looking for an easier way to create attributes before pasting CSV from Numbers.app). If the names have characters which aren't allowed in attribute names it will replace those with underscores. 
			-- In the "property" section you can set the options known from Document Inspector's "User" tab, but not on single attribute level; for each run those options will be assigned to all attributes.
			-- Run from Script Editor it should be easy to change options between runs. After a run you can change options of single attributes of course one by one, but probably it makes more sense to do more runs with different options than changing options manually afterwards.

			-- Tinderbox Inspector must be open as UI scripting is used (and possibly you have to allow UI scripting in macOS, not sure.)

			-- You can change what is shown in the dialog that asks for the attribute nanes (defaultAnswer):
			-- With "the clipboard" (without quotes) you'll get.. e.g. the column names you just copied in Numbers.
			-- With "" you'll just get an empty text field. If you use this I recommend using commas or tabs, as for new lines you'll have to hit option + return.
			-- But what if you know there are those attributes you've created over and over again in every new Tinderbox (as long as you don't use stationaries, which, well, are stationary..) and you're tired of typing those again and again? In this case I'll bet your choice will be an AppleScript list. Just store your attributes names like this {"A","B","D"} and you'll never need to create those manually again.

			property _Type : "s" -- s[tring], c[olor], n[umber], f[ile], b[oolean], d[ate], se[t], u[rl], l[ist], i[nterval]
			property _Default : ""
			property _Suggested : "" -- "Ant;Bee;Cow" (Quotes only in AppleScript, see http://www.acrobatfaq.com/atbref7/index/MiscUserInterfaceAspects/Pre-populatingkeyattribu.html)
			property _Sequential : false -- (didn't test this)
			property _KeyAttribute : false
			property _Description : ""

			property replaceChars : {" ", "-", ",", ";", "?", ".", "(", ")", "[", "]", "*", "/", "&", "$"} -- characters doesn't allowed in attribute names (complete?)
			property theDelay : 0.2 -- change if needed

			set defaultAnswer to the clipboard -- the clipboard, "" (blank text field), or an AppleScript List {"A","B","C"}
			#set defaultAnswer to "" -- the clipboard, "" (blank text field), or an AppleScript List {"A","B","C"}
			#set defaultAnswer to "Id	Projekt	Reporter	Priorität	Schweregrad	Reproduzierbar	Produktversion	Meldungsdatum	Betriebssystem	BS-Version	Rechnertyp	Sichtbarkeit	Aktualisiert	Zusammenfassung	Status	Lösung	Behoben in Version" -- the clipboard, "" (blank text field), or an AppleScript List {"A","B","C"}

			tell application "SystemUIServer"
				activate
				set _Names to text returned of (display dialog "Attribute names (separated by comma, tab or new line):" default answer defaultAnswer default button 2) as string
				if _Names is "" then return
			end tell

			if _Names contains "," then
				set _Names to replaceString(_Names, ",", return)
			end if

			if _Names contains tab then
				set _Names to my replaceString(_Names, tab, return)
			end if

			if _Names ends with return or _Names ends with character id 10 then
				set _Names to my trimBoth(_Names)
			end if

			set _Names to paragraphs of _Names

			if _Names = {} then return

			activate application "Tinderbox 7"
			tell application "System Events"
				try
					tell process "Tinderbox"
						try
							set theWindow to (first window whose value of attribute "AXRoleDescription" is "floating window")
							if name of theWindow does not start with "Document Inspector:" then
								click radio button 2 of radio group 1 of theWindow
								set theWindow to (first window whose value of attribute "AXRoleDescription" is "floating window")
								click radio button 2 of tab group 1 of theWindow
							end if
						on error
							display dialog "Please open the Inspector window"
							return
						end try
						
						repeat with thisName in _Names
							
							click menu button 1 of tab group 1 of theWindow
							click menu item 2 of menu 1 of menu button 1 of tab group 1 of theWindow
							
							repeat with thisChar in replaceChars
								if thisName contains thisChar then
									set thisName to my replaceString(thisName, thisChar, "_")
								end if
							end repeat
							
							set focused of text field 2 of tab group 1 of theWindow to true -- Name
							set value of text field 2 of tab group 1 of theWindow to thisName
							key code 36
							delay theDelay
							
							if _Type is not "s" then -- Type
								click pop up button 2 of tab group 1 of theWindow
								keystroke _Type
								key code 36
								delay theDelay
							end if
							
							if _Default is not "" then -- Default
								set focused of text field 1 of tab group 1 of theWindow to true
								set value of text field 1 of tab group 1 of theWindow to _Default
								key code 36
								delay theDelay
							end if
							
							if _Suggested is not "" then -- Suggested
								set focused of text field 4 of tab group 1 of theWindow to true
								set value of text field 4 of tab group 1 of theWindow to _Suggested
								key code 36
								delay theDelay
							end if
							
							if _Sequential is not false then -- sequential
								if value of checkbox 2 of tab group 1 of theWindow is not 1 then
									click checkbox 2 of tab group 1 of theWindow
								end if
							end if
							
							if _KeyAttribute is not false then -- Key attribute
								if value of checkbox 1 of tab group 1 of theWindow is not 1 then
									click checkbox 1 of tab group 1 of theWindow
								end if
							end if
							
							if _Description is not "" then -- Description
								set focused of text field 3 of tab group 1 of theWindow to true
								set value of text field 3 of tab group 1 of theWindow to _Description
								key code 36
								delay theDelay
							end if
							
						end repeat
						
						set theCount to count of _Names
						display dialog "If successful there should be " & theCount & " new attributes. Please check!"
						
					end tell
					
				on error error_message number error_number
					set the clipboard to error_number as string
					display notification "Copied Error Number to clipboard" with title error_number
				end try
			end tell

			on replaceString(theText, oldString, newString)
				local ASTID, theText, oldString, newString, lst
				set ASTID to AppleScript's text item delimiters
				try
					considering case
						set AppleScript's text item delimiters to oldString
						set lst to every text item of theText
						set AppleScript's text item delimiters to newString
						set theText to lst as string
					end considering
					set AppleScript's text item delimiters to ASTID
					return theText
				on error eMsg number eNum
					set AppleScript's text item delimiters to ASTID
					error "Can't replaceString: " & eMsg number eNum
				end try
			end replaceString

			on trimStart(str)
				local str, whiteSpace
				try
					set str to str as string
					set whiteSpace to {character id 10, return, space, tab}
					try
						repeat while str's first character is in whiteSpace
							set str to str's text 2 thru -1
						end repeat
						return str
					on error number -1728
						return ""
					end try
				on error eMsg number eNum
					error "Can't trimStart: " & eMsg number eNum
				end try
			end trimStart

			on trimEnd(str)
				local str, whiteSpace
				try
					set str to str as string
					set whiteSpace to {character id 10, return, space, tab}
					try
						repeat while str's last character is in whiteSpace
							set str to str's text 1 thru -2
						end repeat
						return str
					on error number -1728
						return ""
					end try
				on error eMsg number eNum
					error "Can't trimEnd: " & eMsg number eNum
				end try
			end trimEnd


			on trimBoth(str)
				local str
				try
					return my trimStart(my trimEnd(str))
				on error eMsg number eNum
					error "Can't trimBoth: " & eMsg number eNum
				end try
			end trimBoth
1 Like

This is a very clever idea – I haven’t tried it yet but will test it.

A few notes for anyone not familiar with AppleScript or user interface scripting (UI scripting).

  1. UI scripting can damage a document if not done precisely – this sort of scripting will just “blindly” do what it does and takes somewhat of a bull-in-a-china shop approach. That’s not to say it’s bad, or a criticism of @ian’s contribution. Just beware. Always use a UI script on a new document or a document that you have made a backup copy of immediately before running the script.
  2. The script depends on the version of Tinderbox that existed when the script was written. Things can change in the Inspector and elsewhere, and so letting the script lose on a new version of Tinderbox should be done carefully – see #1, above. (This does happen: in the old forum there are scripts posted that will not work. Things get abandoned.)
  3. The Eastgate-provided, and well seasoned approach to creating documents that encompass a set of user attributes you want to use frequently is to create a document with those attributes and save that document to
~/Library/Application Support/Tinderbox/favorites

And then, use File > Open Favorites to access your favorite documents and have Tinderbox create a new document based on one of your favorite starting points.

1 Like

Interesting idea and thanks for sharing.

Most Tinderbox customisation extensions (badges, colours, etc.) use XML-based data and IIRC there is/was a feature request to let the user store a set of pre-configured user attributes so as to be used by all the user’s new TBXs.

Another easy way to generate attributes en masse is to use the Tab-delim/CSV table import - see here and here.

Another successful TBX pre-customisation route is to store a customised but otherwise blank TBX in the ‘favorites’ app support folder and set in Finder as a stationery file, as mentioned in @PaulWalters’ post above. To expand a little on the latter approach, the result is that when you open said file from the File → Favorites sub menu an unsaved copy of the original file is opened. Thus a freshly minted, customised file each time. An advantage for those taking this tack is you can do lots of other customisations too - stamps, colours, export templates, prototypes, composites, custom doc-level defaults for system attributes, etc.Effectively you’re making a custom configuration file in the form of a TBX.

I’ve edited my last post above to clarify that I’m expanding the explanation of the technique previously mentioned by @PaulWalters. I added the explanation as stationery files are less talked about of late and thus often a surprise to newer Mac users.

Tinderbox 8 new scripting support (nicely implemented but still lightly documented) includes the ‘make new’ command to create new documents, notes, and agents. AFAIK it doesn’t support the creation of new attributes. Is that understanding correct, or should I keep trying?

I believe not. AS integration arrived very close to v8 release so even beta testers have had little time to explore it. Exploring the app’s AS dictionary using Script Debugger indicates it should be possible to make a new object of class ‘attribute’, with code like this:

tell application "Tinderbox 8"
	tell its document "AS-text.tbx"
		make new attribute with properties {name:"MyAttr"}
	end tell
end tell

This compiles but fails, so I assume there is a glitch in the AS support implementation at present. So, for now, I’d say the short answer is no.

No, you can’t use a script to create an attribute.

Do you really need to? That doesn’t seem a very common or onerous task. But perhaps there’s an automation scenario I’m missing.

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.