Script for populating custom metadata of file with the custom metadata of its folder

I have a custom metadata field called “Citation”.

What I would like to do is set that field for a group, then for every markdown file I create in that group, I would like the Citation field to be auto-populated with the same value as the group. My main intention is to run this as a “Smart Action”, looking for any markdown file in my database that is run on creation.

I am not too experienced with AppleScript, but I hacked together this attempt from reading the forums. However, it’s not producing any result, and I don’t really know how to debug. Would love some tips.

try
	tell application id "DNtp"
		set theParent to parent of thisRecord
		set customMD to custom meta data of theParent
		set mdCitation to Citation of customMD
		add custom meta data mdCitation for "Citation" to thisRecord
	end tell
end try

I’m seeing some odd behavior and testing right now.

Note: Your try block inhibits you from seeing an error in your code, unless you used…

try
-- Do stuff
on error errMsg
display alert "" & errMsg
end try

For example…

Any update on this?

No.
We are discussing unusual returns in the AppleScript. I don’t want to post code that may change with an update. I’m waiting to see what happens.

tell application id "DNtp"
	set theRecord to (item 1 of (selection as list))
	
	try -- This will error if there is no custom meta data, but this try block lets it error silently.
	
		set md to ((custom meta data of (parent 1 of theRecord)))
		-- Remember, a file could have multiple parents if it's a replicant
		
		set recCite to mdcitation of md
		
		add custom meta data recCite for "Citation" to theRecord
	end try
	
end tell

If you’re using this in a smart rule, you’ll have to adapt it a little, e.g., theRecord won’t be the selection; it will be part of the repeat loop.

In JavaScript (which doesn’t error if there is no custom meta data), we would, FWIW, have been able expand a little to give the user a bit more clarity about what is happening.

(Very unusually for an osascript (Apple Events) context, and somewhat against the grain of Apple’s own guidelines, Smart Rules script actions only allow for AppleScript, which makes things, alas, more difficult in this one part of the otherwise very JS-friendly DT3)


A JS approach:

Starting with the JS equivalent of a vanilla on run ... end run wrapping:

(() => {
    'use strict';

    // Define what we need here

})();

(In which 'use strict' is optional, but enables more informative messages to the user)

We could define some of the the constant values that we need:

  1. The metadata key that interests us.
  2. A reference to DEVONthink itself,
  3. and its current selection, as well as:
  4. The md prefixed version of metaData names that is used by macOS.
(() => {
    'use strict';

    const myKey = 'citation';

    const
        devonThink = Application('DEVONthink 3'),
        selns = devonThink.selection(),
        mdKeyName = 'md' + myKey;

})();

Then, if it turns out that no records are selected, we can just feed that back (for example to the Messages panel of Script Editor) and have done with it:

(() => {
    'use strict';

    const myKey = 'citation';

    const
        devonThink = Application('DEVONthink 3'),
        selns = devonThink.selection(),
        mdKeyName = 'md' + myKey;

    if (selns.length > 0) {
    
        // Fill this out for the case where something *is* selected. 
    
    } else {
        console.log('Nothing selected')
        return ''
    }
})();

If any records are selected, then we are ready to define some more of the values that we will need:

(() => {
    'use strict';

    const myKey = 'citation';

    const
        devonThink = Application('DEVONthink 3'),
        selns = devonThink.selection(),
        mdKeyName = 'md' + myKey;

    if (selns.length > 0) {
        const
            theRecord = selns[0],
            dictMD = theRecord.parents.at(0).customMetaData() || {},
            keyValue = dictMD[mdKeyName];


    } else {
        console.log('Nothing selected')
        return ''
    }
})();
  1. The first selected item (JS list indexes begin at zero)
  2. The Meta Data record (key-value ‘dictionary’) of the first parent of that item, or an empty dictionary if that parent doesn’t yet have one. (In JS, or is written as || )
  3. The value (if any) for the key that interests us (e.g. mdcitation) in that dictionary.
(() => {
    'use strict';

    const myKey = 'citation';

    const
        devonThink = Application('DEVONthink 3'),
        selns = devonThink.selection(),
        mdKeyName = 'md' + myKey;

    if (selns.length > 0) {
        const
            theRecord = selns[0],
            dictMD = theRecord.parents.at(0).customMetaData() || {},
            keyValue = dictMD[mdKeyName];

         
    } else {
        console.log('Nothing selected')
        return ''
    }
})();

At this stage ,where AppleScript would have thrown up its hands and errored (if the parent turned out to have no value for the mdcitation key), JS just defines keyValue either:

  • as a String, if an mdcitation value was found,
  • or as the special value undefined, if there was no mdcitation value in the parent’s metadata dictionary.

In JS, is not or , is written as !==

(() => {
    'use strict';

    const myKey = 'citation';

    const
        devonThink = Application('DEVONthink 3'),
        selns = devonThink.selection(),
        mdKeyName = 'md' + myKey;

    if (selns.length > 0) {
        const
            theRecord = selns[0],
            dictMetaData = theRecord.parents.at(0).customMetaData() || {},
            keyValue = dictMetaData[mdKeyName];

        if (keyValue !== undefined) {
                    
            // Expand here to make us of the citation string that was found.
                    
        } else {
            console.log('Key not found in parent metaData: "' + mdKeyName + '"')
            return ''
        }
    } else {
        console.log('Nothing selected')
        return ''
    }

If a string value was defined for citation, then we can pass it on from the parent to the selected child:

(() => {
    'use strict';

    const myKey = 'citation';

    const
        devonThink = Application('DEVONthink 3'),
        selns = devonThink.selection(),
        mdKeyName = 'md' + myKey;

    if (selns.length > 0) {
        const
            theRecord = selns[0],
            dictMetaData = theRecord.parents.at(0).customMetaData() || {},
            keyValue = dictMetaData[mdKeyName];

        if (keyValue !== undefined) {
            theRecord.customMetaData = Object.assign(
                {},
                theRecord.customMetaData, 
                {
                    [mdKeyName]: keyValue
                }
            )
            console.log(JSON.stringify(theRecord.customMetaData()))
            return keyValue
        } else {
            console.log('Key not found in parent metaData: "' + mdKeyName + '"')
            return ''
        }
    } else {
        console.log('Nothing selected')
        return ''
    }
})();

The key expression here is Object.assign, which lets us copy as many key-value pairs as we like from one dictionary to another, (or even copy them them from right to left along a list of several dictionaries).

The updated dictionary which we are defining here copies values (bottom towards top, in this layout):

Object.assign(
    {},
    theRecord.customMetaData, 
    {
        [mdKeyName]: keyValue
    }
)
  • A new dictionary with just the key-value pair we want,
  • is copied up into any customMetaData that the DT record may already have,
  • but that may be undefined, if the record doesn’t yet have any meta data,

so we add a fresh empty dictionary {} at the top, as a final target, just in case.

Finally, with the meta-data inherited from the parent, we can send informative feedback,

both to the Script Editor Messages panel (using the built-in JSON stringify, which gives a readable
representation of dictionaries - often also called ‘Objects’ in JS reference materials)

console.log(JSON.stringify(theRecord.customMetaData()))

and to the Script Editor Results panel:

return keyValue

2 Likes

This does what I’m looking for. thanks!

You’re welcome.
And as a note for peoples’ education:

A record can have more than one parent, so it needed to be specified as parent 1 of….

Cheers!

1 Like