Script: To save tags into metadata field for safekeeping in some cloud storage solutions

While I am looking into new cloud storage solutions, I had to face the fact that some/a lot of them are not supporting macos finder tags.

To be on the safe side, I came up with the following script which stores the tags into a custom metadata field.

I got a lot of inspiration from other forum members and I found the posts from @Bernardo_V very helpful. Of course all errors in the script are mine, I hope there aren’t any :slight_smile: and it is only a bit clumsy and could be improved. Happy to receive feedback on this.

It should also be clear that anyone who want to run the script is doing it on her/his own peril. I have used it on my databases and it works as designed.

Keep in mind the following:

  • The script is called from a smart rule and has the following option set in the script:
    • property overwriteTags : true – overwrite existing metadata tags
  • Metatags are written with a leading #-sign and spaces in tags are replaced with an underscore.
  • Create a custom metadata field
  • my name for the metadata field is “mpTags” you might want to change it in the script to the name you are going to use.

My thinking is that cloud storage is the last resort after TimeMachine and local backups can’t restore the system. In that case I would restore from the cloud and write the reverse script to create the tags from the metatags again.

Please feel free to play around with it.

property overwriteTags : true -- overwrite existing metadata tags
property metaDataField : "mpTags"

on performSmartRule(theRecords)
	tell application id "DNtp"
		try
			repeat with thisRecord in theRecords
				if overwriteTags is true then
					set metaTags to ""
				else
					set metaTags to get custom meta data for metaDataField from thisRecord
				end if
				set theTags to tags of thisRecord
				repeat with thisTag in theTags
					if thisTag contains " " then
						-- replace spaces with underscore in tags
						set thisTag to my replaceChars(thisTag, " ", "_")
						
					end if
					set metaTags to metaTags & "#" & thisTag & space
				end repeat
				if metaTags is not "" then
					add custom meta data metaTags for metaDataField to thisRecord
				end if
				set metaTags to ""
			end repeat
			-- end tell
			
		on error errMsg number errNum
			display alert (localized string "An error occured when adding the document.") & space & errMsg
			
		end try
	end tell
end performSmartRule


on replaceChars(theText, searchString, replacementString)
	-- standard find and replace
	set AppleScript's text item delimiters to the searchString
	set the item_list to every text item of theText
	set AppleScript's text item delimiters to the replacementString
	set theText to the item_list as string
	set AppleScript's text item delimiters to ""
	return theText
end replaceChars

I’ll comment on your script below. But first my take on it in JavaScript – simply to show off how much less code is needed for that task in a modern language:

function onperformsmartrule(records) {
  let overwriteTags = true;
  let metaDataField = "mpTags";

  const app = Application("DEVONthink 3")
  records.forEach(r => {
    let metaTags = overwriteTags ? "" : app.getCustomMetadata({for: metaDataField, from: r});
    /* Get list of tags, all space characters replaced by underlines, then append to metaTags */
    metaTags += r.tags().map(t => t.replaceAll(/\s/g,'_')).join("#");
    if (metaTags.length) {
      app.addCustomMetaData(metaTags, {for: metaDataField, to: r});
    }
  })
};

Comments

  • I’m not sure if the if theTags is not """ then condition is needed in AppleScript. I’d expect the repeat with this Tag in the Tags to simply do nothing if theTags is an empty list.
  • Since we’re at it: tags is a list, why do you compare it to "" instead of {}?
  • Why do you always append a space to the list of tags in metaTags? If I understand your code correctly, metaTags would contain #tag1 #tag2 #tag3 , and you’d have to remove the spaces later on. Also, I’d probably use another separator (semicolon or comma) of which one can be sure that it’s never part of a tag. Perhaps that’s the case with the hash sign too, I don’t know.
  • Why did you comment out the if metaTags is not {} condition? Do you really want to always set the mpTags field, even if there’s nothing to set it to? Regardless: metaTags is a string, so comparing it to a list with {} is irritating.

JavaScript details note that the code is not tested at all.

  • I used the same global variables (properties) as your script
  • records.forEach loops over every record, similar to repeat with thisRecord in theRecords, r corresponding to thisRecord (having confusing names like “thisRecord” and “theRecords” is considered bad practice everywhere but in AppleScript)
  • ´metaTags = overwriteTags ? …is shorthand forif overwriteTags then do this else do that , the :representing theelse. The ?…:…` is called a ternary operator, and its overuse is discouraged because it tends to make code harder to understand.
  • r.tags() gets all tags as a list. map(t => t.replaceAll(…)) loops over the elements of this list and applies the replaceAll method to each of its item, replacing all strings with underlines. replaceAll is the built-in version of your replaceChars subroutine. A tad shorter and probably more efficient.
  • After the map, we still have a list (or rather an Array in JavaScript parlance). The join('#') converts this list to a string, separating all list elements with #(see my question above regarding the appended space in your code).
  • Finally, if the string metaTags does contain anything (i.e. it’s longer than 0 characters), the metaDataField of the current record r is set to this value. See my question above regarding your commented out condition for writing metaDataField

I store the tags by appending as keywords to the filename
Safekeeping, and added context
Example: 2022-12-20 [Groceries] cType-Receipt aVendor-Walmart cBudget-FoodGrocery $-26.63

Likewise, this is accomplished using a script

btw We’re actually working with Devonthink tags; not macos finder tags

Thank you for your feedback. I have made most of the changes you recommended and updated the code. What I left in, is that I use the " " as separator between the metatags. It is easier to read for me. There shouldn’t be an issue as I replace all " " in tags to underscore.
All other comments very helpful.

I will take more time and look at your javascript example.

Thanks for your reply. I thought about that as well but had decided against it for readability reasons. I have sometimes three tags to an item. But will think about it again.

In my case it is about Finder tags as well as I have a lot of documents indexed into DT.

Thanks for your thoughts.

Then why not use only spaces like tag1 tag2 tag3?

You are right of course. For the purpose described in my post that would be sufficient.

I am still playing with the idea to move the metadata tags into the files where it is possible. Have to think this through. That is the reason, why I leave the hashtag in for now.

Thanks for your time and thoughts.

What kind of files are you dealing with?

Markdown and other plain text files.