It’s actually not even called by the JXA bridge in case of smart rules. Definitely weird. In the Script Editor this revision seems to work with and without defined custom metadata:
Thanks to all of you. But using the self-executing anonymous function means it doesn’t work for the folder by default like configured in the smart rule, it only works if I select the entities.
@Blanc I would love to use apple script, but I don’t really get the syntax. It’s confusing for me. I was a developer for c# .NET with expertise also in JavaScript, PHP and so on, because of this, I know the JavaScript syntax much better.
But maybe I will try it with AppleScript again, if JavaScript doesn’t work 100%.
Although it does work if one adds the anonymous self-executing function after the smart rule function performsmartrule. Even funnier: This self-executing function actually gets executed inside the smart rule. Try this:
As you can see, the anonymous function is executed first. That should not happen at all, in my opinion: the smart rule is not supposed to execute the JS code but only to call performsmartrule.
The anonymous function then calls performsmartrule passing it the selectedRecords() and everything is fine (note that in my case I used a different metadata field, but that is not important here).
Only after all that performsmartrule is called by the smart rule and fails probably at the n.toString() call.
One final thing: If I use app.selectedRecords()insideperformsmartrule (instead of the records parameter passed into it) , everything is just honky-dory. No error, correct output. I ran the smart rule from the context menu for one single selected record in all these cases.
So I see two issues here:
the smart rule calls the anonymous self-executing function which it shouldn’t do.
there seems to be a difference between the records parameter passed into performsmartrule from DT and the selectedRecords() list although they should (at least in this case) be exactly the same.
in this one case, AppleScript is almost as brief as JS; the basic format would be:
on performSmartRule(theRecords)
tell application id "DNtp"
repeat with theRecord in theRecords
set md to custom meta data of theRecord
set theName to mdcompany of md # use mdNameOfYourMetaData instead of mdcompany
set name of theRecord to theName
end repeat
end tell
end performSmartRule
This presumes your smart rule only directs records which actually contain said metadata to the script; otherwise the script will fail. If you actually choose to use it, let me know - then it should at least contain an error routine. Also, if you were going to be using it on many many files at a time, a routine for the progress indicator could be added.
Note that any changes made by script cannot be undone.
Nope. At least not in theory. The self-executing function is actually there so that you can run the same script in Script Editor and in DT’s smart rule. The smart rule should call performsmartrule only, which gets passed the records selected by the smart rule’s conditions. In Script Editor, you must select record(s) which then are passed from the anonymous function to performsmartrule. Well, all this works in theory. Right now it seems that DT is running the anonymous function first and performsmartrule second, so that the letter gets in fact called twice.
Rant follows @Blanc: Right, of course. It is definitely a lot more fun to use AS given its obscure syntax combined with a lack of functionality and reasonable error handling.
Exactly my point: You have to know that “custom meta data” is an attribute (or property or whatever you want to call it in AppleScript). That’s what I call “obscure syntax” - nobody can see if perhaps custom might be a modifier that is applied to meta data.
Similarly with the word “Record” in DT vs. AppleScript: In DT, “Record” is one of the basic objects in a database. In AS, “Record” is a key-value datastructure. And what might the custom meta data property of a DT Record be? A “Record”, again. Albeit an AS one, so a key-value data structure. Any relevant programming language would have prevented DT from repurposing a keyword for their own structures. How things stand, you have no chance to see immediately even the type of an object/attribute/property. It is a bloody mess. But at least AS is not exhausting our supply of parenthesis
What I really don’t get: Apple had no problems at all to drop old hardware regularly (think FireWire, SCSI, parallel interfaces, all that). But dropping a language that is even worse than Basic – no way. They’re not even trying to build a viable, modern alternative to it. Just letting the old horse stumble along.
I’ll deal with it as soon as I have done with the even more pressing issues.
Thanks for your rant I’ll learn JS one day, and I will then be enlightened
Indeed the naming wasn’t the greatest one 16 years ago but it’s not possible to change this now without breaking all the existing scripts and JXA was definitely not on the horizon either.
Actually smart rules are always executed the same way, only the function is directly called by DEVONthink. But I just compared the output of these AppleScript and JXA scripts:
on performSmartRule(theRecords)
tell application id "DNtp"
log message "perform"
repeat with theRecord in theRecords
log message (name of theRecord as string)
end repeat
end tell
end performSmartRule
on run
tell application id "DNtp" to log message "run"
end run
The first one always works as expected, run is never logged. The second one logs anonymous the first time (!) it’s executed but not anymore afterwards (as compiled scripts are internally cached to speed up smart rules).
Therefore the anonymous function seems to be only called the first time a JXA script is executed after compiling. Due to the lack of JXA documentation it’s hard to tell whether that’s intended or not.
Of course. The whole OO thing is borken. The date functions are clumsy. Scoping is irritating. Some APIs, notably the DOM, were lacking simple functionality for a long time.
But then JavaScript is to Applescript what Pascal was to Basic.
In the end, you can solve any problem in any language. It’s just a matter of time
You’re right of course. So my beloved toy has a nasty side effect I hadn’t thought of and I’ll have to look for another way to achieve the same goal: have but one code run identically in Script Editor and in DT. And preferably only once in both, too.
The original problem remains though: the same code works ok in Script Editor and doesn’t in DT.
I could of course point you to a brilliant site on JXA … but yes, you’re right. Although I find AS badly documented (the language itself and by Apple), there’s a lot of stuff to be found on the Web. Not so for JXA, unfortunately. And it requires a lot of experimentation in some cases.
Anyway, I came up with this now to permit using the same JS script in DT and in Script Edtior:
function performsmartrule(records) {
const app = Application("DEVONthink 3");
records.forEach (r => {
/* CODE goes here */
})
}
(() => {
if (currentAppID() === "DNtp") return;
const app = Application("DEVONthink 3");
performsmartrule(app.selectedRecords());
})()
function currentAppID() {
const p = Application.currentApplication().properties();
return Application(p.name).id();
}
If run from inside DT, i.e. a smart rule, the anonymous function returns immidiately without calling performsmartrule. The latter will then be called directly by the smart rule in the next step – so only once. The function currentAppID is used to determine the id of the currently running app.
tell application id "DNtp"
set theRecords to selected records
if theRecords = {} then error "Please select a record"
repeat with currentRecord in theRecords
set metaData to custom meta data of currentRecord
set recordDate to mddate of metaData
set recordDateDay to get day of recordDate as text
set recordDateMonth to get month of recordDate as integer
if recordDateMonth < 10 then
set recordDateMonth to "0" & recordDateMonth as string
else
set recordDateMonth to recordDateMonth as string
end if
set recordDateYear to get year of recordDate as text
set recordDateString to recordDateYear & "." & recordDateMonth & "." & recordDateDay
set recordCompany to mdcompany of metaData
set recordPurpose to mdpurpose of metaData
set newName to recordDateString
if recordCompany is not equal to "" then set newName to newName & " - " & recordCompany
if recordPurpose is not equal to "" then set newName to newName & " - " & recordPurpose
set name of currentRecord to newName
end repeat
end tell
I got that AppleScript running in the script editor. But now I have the problem of, that a property must not exist. How can I check that? Searched for it, but doesn’t find a solution.
That’s slightly too abstract for me; can you explain in more detail (which property, what needs to happen?), so I can think about it?
I often use a try routine - if a property exists, no error is thrown on trying to read or manipulate the property; but if it doesn’t exist, then an error is thrown which you can trap with on error within the try routine. But as I said, more information on which property would help determine whether you can determine the presents directly or whether you need to go an indirect route as above.
Date → example: 20.10.2021
Company → example: Apple
Purpose → example: Invoice
By the script I want to rename the file to:
2021.10.20 - Apple - Invoice.pdf
But company and purpose can also be empty. So if for example the purpose is not filled the name could also be: 2021.10.20 - Apple.pdf
If the purpose is empty in devonthink it’s not represented as an empty property, the property is missing at all and by this I get the following error:
error "Can’t get mdpurpose of {mdincomingoutgoing:\"Incoming\", mddate:date \"Friday, 1. January 2021 at 01:00:00\", mdbelongsto:\"Denis\", mdcompany:\"fitseveneleven\", mdcustomernumber:\"asdasd\"}." number -1728 from mdpurpose of {mdincomingoutgoing:"Incoming", mddate:date "Friday, 1. January 2021 at 01:00:00", mdbelongsto:"Denis", mdcompany:"fitseveneleven", mdcustomernumber:"asdasd"}
on performSmartRule(theRecords)
tell application id "DNtp"
repeat with currentRecord in theRecords
set metaData to custom meta data of currentRecord
set recordDate to mddate of metaData
set recordDateDay to get day of recordDate as text
set recordDateMonth to get month of recordDate as integer
if recordDateMonth < 10 then
set recordDateMonth to "0" & recordDateMonth as string
else
set recordDateMonth to recordDateMonth as string
end if
set recordDateYear to get year of recordDate as text
set recordDateString to recordDateYear & "." & recordDateMonth & "." & recordDateDay
set newName to recordDateString
try
set newName to newName & " - " & (mdcompany of metaData)
end try
try
set newName to newName & " - " & (mdpurpose of metaData)
end try
set name of currentRecord to newName
end repeat
end tell
end performSmartRule
This is how I have it now in devonthink in a smart rule.
Thanks to all of you for the fast responses. Will definitely buy now devonthink.
Hope also that devonthink will get available for shortcuts on macOS.