DT4 - show window to review changes

  1. The question
  • Is there a better way to show a “review window”?
  • Does it make sense to ask for a display dialog (AppleScript) where I can
    • view the content of the file
    • see the new filename, attributes and tags - e.g. display review "DNtp" with new name "asdf" new tags {"tag1", "tag2"} in theNewLocation buttons {"Abort", "Cancel", "Apply"}
  1. The Setup
  • I’ve got a directory which can contain a lot files - invoices, health records, …
  • I organise my files by locations - tags are only used for some specific use cases - find files for tax applications, or find target locations
  • Each document is handled by some rules I defined early - e.g. content, kind, tags
  1. Aim
  • Based on the result, I want to get the target location for each document and some more specific tags
  • Before I apply the changes, I would like to get an idea what the document is about
  1. The relevant code
  • For now, I open a new window for the record
  • Show an “Alert”
  • Handle the response
  • Close the window
-- [...]
set theWindow to open window for record theRecord

local alertResponse
set alertResponse to display alert "DNtp" message "File: " & theName & "
Targent path: " & theTargetPath & "
Database: " & theDatabase & "
Tags: " & my convertListToString(theResultTags, ", ") buttons {"Abort", "Ignore", "Move"} default button 2 as warning

close theWindow
-- [...]
  1. The script

Based on other posts in this forum, I made this script not being cached. This is intentional.

set theScriptPath to (system attribute "HOME") & "/Library/Script Libraries/document-invoices-handle.scpt"

set ScriptHandler to load script (POSIX file theScriptPath as alias)

Here comes the “full” script whithout my specific rules.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

-- test performSmartRule
tell application id "DNtp" to my performSmartRule(selected records)

-- test scriptOutput
-- tell application id "DNtp" to my scriptOutput(selected record 1, "")

return

on scriptOutput(theRecord, theInput)
	tell application id "DNtp"
			local theScriptPath
			set theScriptPath to (system attribute "HOME") & "/Library/Script Libraries/document-invoices-handle.scpt"
			
			local ScriptHandler
			set ScriptHandler to load script (POSIX file theScriptPath as alias)
			
			ScriptHandler's ForOne(theRecord)
	end tell
end scriptOutput

on performSmartRule(theRecords)
	tell application id "DNtp"
			local theScriptPath
			set theScriptPath to (system attribute "HOME") & "/Library/Script Libraries/document-invoices-handle.scpt"
			
			local ScriptHandler
			set ScriptHandler to load script (POSIX file theScriptPath as alias)
			ScriptHandler's ForAll(theRecords)
	end tell
end performSmartRule

-- Run job for all given records
-- Uses default model for given engine
on ForAll(theRecords)
	tell application id "DNtp"
		if the length of theRecords is less than 1 then error "One or more record must be selected in DEVONthink."
		
		show progress indicator "Run job for record" steps (count of theRecords)
		
		repeat with theRecord in theRecords
			set theName to the name of theRecord
			step progress indicator theName
			my ForOne(theRecord)
		end repeat
		
		hide progress indicator
	end tell
	
	log "[INFO] Job completed."
end ForAll

-- Run job for single record
-- Uses default model for given engine
on ForOne(theRecord)
	tell application id "DNtp"
		local thisYear
		set thisYear to year of (get creation date of theRecord) as string
		
		local theResult
		set theResult to my filterDocument(theRecord, thisYear)
		
		local theResultTags
		set theResultTags to tags of theResult
		
		if theResultTags is not missing value then
			set tags of theRecord to (tags of theRecord & theResultTags)
		end if
		
		if (company of theResult) is not missing value then
			add custom meta data company of theResult for "company" to theRecord
		end if
		
		if (basePath of theResult) is not missing value and (database of theResult) is not missing value then
			local theTargetPath
			set theTargetPath to (basePath of theResult & "/" & thisYear)
			
			local theDatabase
			set theDatabase to (database of theResult)
			
			local theTarget
			set theTarget to get record at theTargetPath in database theDatabase
			
			if theTarget is missing value then
				local theParent
				set theParent to get record at (basePath of theResult) in database theDatabase
				set theTarget to create record with {record type:group, name:thisYear} in theParent
			end if
			
			local theName
			set theName to name of theRecord
			
			set theWindow to open window for record theRecord
			
			local alertResponse
			set alertResponse to display alert "DNtp" message "File: " & theName & "
			Targent path: " & theTargetPath & "
			Database: " & theDatabase & "
			Tags: " & my convertListToString(theResultTags, ", ") buttons {"Abort", "Ignore", "Move"} default button 2 as warning
			
			close theWindow
			
			if get button returned of alertResponse = "Ignore" then
				return
			end if
			
			if get button returned of alertResponse = "Abort" then
				error "Abort handling of documents"
			end if
			
			log "[DEBUG] Name of file: " & theName
			log "[DEBUG] Target Path: " & theTargetPath
			log "[DEBUG] Database: " & theDatabase
			log "[DEBUG] Tags: " & my convertListToString(theResultTags, ", ")
			
			move record theRecord to theTarget
		end if
		
	end tell
	return theRecord
end ForOne


on filterDocument(theRecord, thisYear)
	tell application id "DNtp"
		local theName
		set theName to name of theRecord
		
		local theContent
		set theContent to plain text of theRecord
		
		local theKind
		set theKind to kind of theRecord
		
		local theTags
		set theTags to tags of theRecord
		
		if theContent contains "company XY" and theContent contains "1234567" then
			return {basePath:"/Finance/Company XY/kontoauszuege", database:"business", tags:{"finance"}, company:"Company XY"}
		end if
		
		-- [ ... ]
		
		if theTags contains "health" then
			return {basePath:"/Health", database:"business", tags:missing value, company:missing value}
		end if
		
		return {basePath:"/Receipts", database:"business", tags:missing value, company:missing value}
	end tell
end filterDocument

on setTags(theRecord, theNewTags)
	tell application id "DNtp"
		set theTags to tags of theRecord
		set tags of theRecord to theTags & theNewTags
	end tell
end setTags

on setCompany(theRecord, theName)
	tell application id "DNtp"
		add custom meta data theName for "company" to theRecord
	end tell
end setCompany

on convertListToString(theList, theDelimiter)
	set AppleScript's text item delimiters to theDelimiter
	set theString to theList as string
	set AppleScript's text item delimiters to ""
	return theString
end convertListToString

Is this AI-generated code?

Is it that bad? :slight_smile: It’s written by hand, but redacted for the forum. Getting some hints what I can improve are definitively appreciated.

Haha! I wouldn’t say it’s “bad” but it just looked suspiciously similar to AI code, especially the repeated local elements and a bunch of handlers.

  • You also have errant try statements in two of your handlers.
  • For what are you using script libraries?

I use “Script Debugger” as my favourite IDE. It requires local variables to be declared with “local”. Otherwise they do not occur within variable inspector. :person_shrugging:

“try” is fixed. That happened during cleaning up the code for the forum.

All my scripts are stored within “script libraries”. For only a few I use the load mechanism to get “dynamic” loading which does not require a restart of DEVONthink, if I need to change the rules.

Normally I use set CompressImages to script "compress-images".

on performSmartRule(theRecords)
	tell application id "DNtp"
		try
			set CompressImages to script "compress-images"
			CompressImages's ForAll(theRecords)
			
		on error error_message number error_number
			display alert "DNtp" message error_message as warning
			error number -199
		end try
	end tell
end performSmartRule

The Script Libraries directory is intended for reusable code, not a general storage location for scripts.


External smart action scripts need to be located in a specific directory. Are you only using embedded scripts and just calling a large number of handlers?


Why are you using on scriptOutput(theRecord, theInput) to handle one record and on performSmartRule(theRecords) for all ?

That would probably take up too much screen space. The UI stuff available in AppleScript/JXA is quite limited.

So, the files are still in your file system, not in DT?

As to your script

  • I’d try to catch the “no record selected” situation earlier.
  • Getting the plain text of an arbitrary record might not give you what you want.
  • kind is localized. record type is guaranteed to be always the same, regardless of the locale.
  • Instead of get record at, you could use create location in the first place – that’s more robust (imo), since it creates the target group if it doesn’t exist already. Saves an if, too.
  • variable names – I’ll never understand when people use “the” or “this” in AppleScript and when they don’t. Or why. What is the difference between scriptHandler and theResult? It’s alertResponse, but theName. Is there any system to that?
  • I don’t quite get where the tags are coming from that you use in your filterDocument. In my understanding, you have a bunch of files without any information that you want to file and name in DT.

Personally, I’d use another language that permits to write terser code. But you might know that already :wink:

Thanks a lot.

So, the files are still in your file system, not in DT?

No, they are within DT

  • Getting the plain text of an arbitrary record might not give you what you want.

What attribute would use instead? It was the best guess. Content and Source don’t give me the results I expect.

It’s alertResponse , but theName . Is there any system to that?

Of course not. I just try to imitate the ecosystem.

  • I don’t quite get where the tags are coming from that you use in your filterDocument. In my understanding, you have a bunch of files without any information that you want to file and name in DT.
INBOX
 |
 + - _PROCESS

I pre-process some files with rules in INBOX - e.g. adding some tags upfront, cleaning up the filename etc. Then move the files within DT to _PROCESS directory. That approach gives me more control if some files need more attention.

Personally, I’d use another language that permits to write terser code. But you might know that already :wink:

Sure, I do. Unfortunately there’s no debugger for that language. :wink:

plain text is fine if it exists. If you use only PDFs and e-mails – no problem. But I’d at least check for record type before accessing plain text.

Which seems to be blissfully empty of any system for variable names. I think that all these useless thes come from the fact that AS would not accept a variable called name if you’re inside a tell id "DNtp" block, as name is a property of record (or theRecord:wink: ).

I see. So, you presumably set tags in the pre-process and then in some cases (eg “health”) use (one of) the tags to decide on the target group? In that case, ie if the tag suffices to decide on the target, I’d just move the files to the corresponding group. No need to go through all the subsequent processing. I would want to get the simple cases out of the way as early as possible.

Only brain 1.0, or sometimes even 0.9. And Script Editor is EOL, too. Scripting on macOS will not become easier. Linus Torvalds, btw, despises debuggers :wink:

My scripts are mostly shims wrapping the library code. This makes testing/developing easier for me.

I use embedded scripts which call my libraries - see comment below.

The given code is a template to be copied into embedded scripts - Smart Rules, Batch processing. While developing in Script Debugger, I use this code to call the same method as from within the embedded script. This makes sure the code in the embedded script is working/correct.

tell application id "DNtp" to my performSmartRule(selected records)

First part of the script

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

-- test performSmartRule
tell application id "DNtp" to my performSmartRule(selected records)

-- Copy and paste template used in DEVONthink
on performSmartRule(theRecords)
	tell application id "DNtp"
		local theScriptPath
		set theScriptPath to (system attribute "HOME") & "/Library/Script Libraries/document-invoices-handle.scpt"
		
		local theScriptHandler
		set theScriptHandler to load script (POSIX file theScriptPath as alias)
		theScriptHandler's ForAll(theRecords)
	end tell
end performSmartRule

-- Run job for all given records
-- Uses default model for given engine
on ForAll(theRecords)
-- [...]
end ForAll