Another (long) pure applescript one, this time emulating TextMate’s “Filter through command” feature.
This script requires that some text or one or more records be selected. It then prompts the user for a command, and feeds the text or the contents of the record(s) to the command via STDIN (URLs are downloaded first, while Link and Media files are cat’ed based on the record Path property).
The user is also prompted for what to do with the output. The following options are provided:
- Discard : Send results to /dev/null and write exit status to log
- New record : Create a new plain text record with same name and location as current record
- Append record : Append to plan text of current record
- Replace record : Replace plain text of current record
- Import as File : Import output into DT via a temporary file
- Append comment : Append to comments of current record
- Send to clipboard : send to os x clipboard
- Send to log : write a message to DT log
Failures to produce output usually log a reason to the DT log window (e.g. when trying to replace the plain text of a Link record, which always fails).
Also, Import is broken – DT only creates Link records to the temp files. I use file(1) to determine the contents of the temp file, but DT appears to work on extension, so I’ll have to add a routine to change the extension of the temporary file before import.
Oh yeah, there is also a command history. This and the other script use /Config as a repository for script-related config/data files, though that can be disabled or changed.
Here is the code (see above post regarding the embedded newlines):
-- Filter Through Command :
-- Send selected text, record, or records to the specified command using 'do shell script'.
-- Different record types will be handled appropriately:
-- Text, RTF: Plain text of record is echoed to STDIN of command
-- HTML, XML: Source of record is echoed to STDIN of command
-- Link (file), Media files: file (path) of record is cated to STDIN of command
-- Link (URL): Markup of record is downloaded and echoed to STDIN of command
-- Sheet, Form: Cells are Tab-delimited and echoed to STDIN of command
-- Group: every record in group is handled as above
-- The user must select how the output is handled:
-- Discard : results are discarded, but exit status is written to DT log
-- Create : A new Text record is created from results with same name as input record
-- Append : Results are appended to the current record
-- Replace : Results overwrite the plain text of the current record
-- Import : Import as file (BROKEN)
-- Comment : Results are appended to the comment of the current record
-- Clipboard : Results are pasted to the clipboard
-- Log : Results are written to the DevonThink log
--
-- Command History: By default the command history is enabled. This
-- created the top-level group __Config__. The advantage of a command
-- history is that previously-entered commands can be used without
-- retyping them. To disable the command history, set enable_cmd_history
-- to 'false'. To change the location of the settings, modify the config_dir variable.
-- Note that config_dir and all command history records are excluded from classification.
-- TODO: Fix Output option 'import as file' e.g. for pdf etc output
-- Enable Command History:
-- global variable to enable history: set to True to have history stored in DT
global enable_cmd_history
set enable_cmd_history to true -- false
-- Config Dir:
-- location of config directory in DT
-- (used when enable_cmd_history is True)
global config_dir
set config_dir to "/__Config__"
-- location of command history in DT
-- (used when enable_cmd_history is True)
global cmd_history_dir
set cmd_history_dir to config_dir & "/Filter Through Command"
-- name of command history record
-- (used when enable_cmd_history is True)
global cmd_history_file
set cmd_history_file to "Command History"
-- path to record containing command history in DT
-- (used when enable_cmd_history is True)
global cmd_history_rec
set cmd_history_rec to cmd_history_dir & "/" & cmd_history_file
-- global variable for clipboard output
global clipboard_str
set clipboard_str to ""
-- *** Command History ***
-- add history group
on create_history_group()
tell application "DEVONthink Pro"
if not (exists record at config_dir) then
set grp to create location config_dir
set exclude from classification of grp to true
end if
if not (exists record at cmd_history_dir) then
set grp to create location cmd_history_dir
set exclude from classification of grp to true
end if
if not (exists record at cmd_history_rec) then
-- create new history file
set grp to get record at cmd_history_dir
set rec_date to current date
set rec to create record with {name:cmd_history_file, type:txt, plain text:"", date:rec_date} in grp
set exclude from classification of rec to true
end if
end tell
end create_history_group
-- add to history
on add_to_history(cmd_str)
-- Add command to history if it is not already there
-- NOTE: this does not reorder commands when they are executed!
-- TODO: use a group instead of file so we can sort by date
create_history_group()
tell application "DEVONthink Pro"
set rec to get record at cmd_history_rec
copy plain text of rec to hist_str
set idx to offset of cmd_str in hist_str
if idx is equal to 0 then
-- if command is not in history, insert it at top of file
if length of hist_str > 0 then
-- delimit command with embedded newline '\n'
set hist_str to "
" & hist_str
end if
set plain text of rec to cmd_str & hist_str
end if
end tell
end add_to_history
on get_command_list(hist_str)
-- Returns a list of commands (one per line in history)
if length of hist_str is equal to 0 then return {}
-- split string based on embedded newline '\n'
set AppleScript's text item delimiters to "
"
set cmd_list to text items of hist_str
set AppleScript's text item delimiters to ""
return cmd_list
end get_command_list
-- show history listbox
on get_cmd_in_history()
-- Returns the command history selected by the user.
-- Returns an empty string if there are no items in history
-- or if user aborts.
create_history_group()
tell application "DEVONthink Pro"
set rec to get record at cmd_history_rec
copy plain text of rec to hist_str
end tell
set cmd_list to get_command_list(hist_str)
if length of cmd_list is equal to 0 then
display dialog "Command history is empty" buttons {"OK"}
return ""
end if
set cmd_str to choose from list cmd_list OK button name "Select" cancel button name "Back"
if cmd_str is false then set cmd_str to ""
return cmd_str
end get_cmd_in_history
-- get most recent command
on last_cmd_in_history()
-- Returns the top command in the history record or "".
create_history_group()
tell application "DEVONthink Pro"
set rec to get record at cmd_history_rec
copy plain text of rec to hist_str
end tell
if length of hist_str is equal to 0 then return ""
-- search for first embedded newline "\n"
set idx to offset of "
" in hist_str
-- no newline? only one entry in history
if idx is equal to 0 then
set idx to length of hist_str
else
set idx to idx - 1
end if
set cmd_str to (text from character 1 to character idx of hist_str)
return cmd_str
end last_cmd_in_history
-- *** Output Handlers ***
on handle_new_record(rec, output_data, cmd_str)
-- Create a new text record with the same name and path as rec containing output_data
-- Comment contains the name of this script, the coomand, and the
-- id of the original record
tell application "DEVONthink Pro"
set comment_str to "Created by: 'Filter Through Command' Command:'" & cmd_str & "' Original record: " & id of rec as text
set loc to location of rec
set grp to get record at (texts from character 1 to ((length of loc) - 1) of loc)
copy path of rec to path_str
copy URL of rec to url_str
copy name of rec to name_str
set rec_date to current date
create record with {name:name_str, type:txt, comment:comment_str, path:path_str, plain text:output_data, date:rec_date, URL:url_str} in grp
end tell
end handle_new_record
on handle_append_record(rec, output_data, cmd_str)
-- Append output_data to end of plain text of rec data
tell application "DEVONthink Pro"
copy plain text of rec to text_str
if length of text_str > 0 then set text_str to text_str & return
try
set plain text of rec to text_str & output_data
on error msg
log message "Filter through command '" & cmd_str & "'" info "Append plain text of record '" & (path of rec) & (name of rec) & "' failed: '" & (msg as text) & "'"
end try
end tell
end handle_append_record
on handle_replace_record(rec, output_data, cmd_str)
-- Set plain text of record to output data. Logs an error on failure.
tell application "DEVONthink Pro"
try
set plain text of rec to output_data
on error msg
log message "Filter through command '" & cmd_str & "'" info "Replace plain text of record '" & (path of rec) & (name of rec) & "' failed: '" & (msg as text) & "'"
end try
end tell
end handle_replace_record
on get_file_format(path_str)
set format_strings to {"ASCII", "HTML", "Rich", "PostScript", "PDF"} --, "JPEG"}
set file_magic to do shell script "file -b " & path_str & " | cut -f 1 -d ' '"
tell application "DEVONthink Pro"
set file_types to {simple, markup, rich, PDF, PDF} --, image}
set f_type to all
repeat with i from 1 to count of format_strings
if file_magic = item i of format_strings then
set f_type to item i of file_types
exit repeat
end if
end repeat
end tell
return f_type
end get_file_format
on unlink_tmpfile(path_str)
tell application "Finder"
set f to path_str as POSIX file
if exists f then delete f
end tell
end unlink_tmpfile
on handle_import_file(rec, output_data, cmd_str)
-- Import data as file (PDF, JPG, etc)
-- remember, path to tmp file is passed in as output_data
-- BROKEN: DT does not use magic to determine file type based on content...
-- probably have to fudge the file extension
set tmpfile_path to output_data
set tmpfile_fmt to get_file_format(tmpfile_path)
tell application "DEVONthink Pro"
set loc to location of rec
set grp to get record at (texts from character 1 to ((length of loc) - 1) of loc)
try
set rec to import tmpfile_path to grp type tmpfile_fmt
-- erase path of record since we are deleting tmp file
set path of rec to ""
on error msg
log message "Filter through command '" & cmd_str & "'" info "Import as file '" & tmpfile_path & "' failed: '" & (msg as text) & "'"
end try
end tell
-- remove temp file
unlink_tmpfile(tmpfile_path)
end handle_import_file
on handle_append_comment(rec, output_data, cmd_str)
-- Append output_data to comment of rec
tell application "DEVONthink Pro"
copy comment of rec to comment_str
if length of comment_str > 0 then set comment_str to comment_str & " "
set comment of rec to comment_str & output_data
end tell
end handle_append_comment
on handle_clipboard(rec, output_data, cmd_str)
-- Append output data to clipboard string
set clipboard_str to clipboard_str & output_data
end handle_clipboard
on handle_log(rec, output_data, cmd_str)
-- Append output_data to Devon Think log
tell application "DEVONthink Pro"
log message "Filter Through Command '" & cmd_str & "'" info output_data
end tell
end handle_log
on handle_output(output_str, output_data, rec, cmd_str)
-- Use output_str to determine output type, and invoke handler
-- output_data is the actual text to output
-- rec is the current record, used for appending/replacing/etc
-- cmd_str is the command, currently only used by 'new record' and 'log'
set output_strings to {"new", "append", "replace", "import", "comment", "clipboard", "log"}
set output_handlers to {handle_new_record, handle_append_record, handle_replace_record, handle_import_file, handle_append_comment, handle_clipboard, handle_log}
-- get output handler for output_str
global out_handler
set out_handler to handle_log
repeat with i from 1 to count of output_strings
if item i of output_strings is output_str then
set out_handler to item i of output_handlers
exit repeat
end if
end repeat
out_handler(rec, output_data, cmd_str)
end handle_output
-- *** Filter Through Command ***
on perform_command(cmd_str, output_str, rec, orig_cmd)
-- Execute command in cmd_str and invoke output handler
-- cmd_str is the entire command to execute via do shell script
-- output_str selects the output handler
-- rec is the current record, for use by output handler
-- orig_cmd is the original command (sans cat or echo) for logging
if output_str is "discard" then
-- use /dev/null for safe, fast discard
set cmd_str to cmd_str & " > /dev/null"
else if output_str is "import" then
-- let the shell handle file creation instead of applescript
-- NOTE this tmp file is removed in handle_import_file
set tmp_path to "/private/tmp/DT_filter_thru_cmd_" & (random number (10000) as text)
set cmd_str to cmd_str & " > " & tmp_path
end if
-- error cmd_str
set status_code to 0
try
set cmd_results to (do shell script cmd_str)
on error number n
set status_code to n
end try
if output_str is "discard" or status_code is not equal to 0 then
-- discard logs the exit code of the command, as does failure of shell script
-- NOTE: output handler is not called on failure!
set output_str to "log"
set cmd_results to ""
if status_code is not equal to 0 then
set cmd_results to "Failed. "
if output_str is "import" then
-- remove temporary file created for import
unlink_tmpfile(tmp_path)
end if
end if
set cmd_results to cmd_results & "Exit code: " & (status_code as text)
else if output_str is "import" then
-- there are no results when using import; instead pass tmpfile path
set cmd_results to tmp_path
end if
handle_output(output_str, cmd_results, rec, orig_cmd)
end perform_command
on perform_echo_command(input_str, rec, command_str, output_str)
-- Echo input_str and pipe to to command_str
-- NOTE: we use a shell HERE document with embedded newlines (\n)
-- in the command to get around shell/applescript issues.
set cmd_str to "cat <<__EOF__ | " & command_str & "
" & input_str & "
__EOF__
"
perform_command(cmd_str, output_str, rec, command_str)
end perform_echo_command
on perform_cat_command(path_str, rec, command_str, output_str)
-- Cat file and pipe to command_str
set cmd_str to "cat " & path_str & " | " & command_str
perform_command(cmd_str, output_str, rec, command_str)
end perform_cat_command
-- *** Input Handlers ***
on handle_text_rec(rec, command_str, output_str)
-- ASCII or RTF: Get plain text of record and handle as text
tell application "DEVONthink Pro"
copy plain text of rec to text_str
end tell
perform_echo_command(text_str, rec, command_str, output_str)
end handle_text_rec
on handle_source_rec(rec, command_str, output_str)
-- HTML or XML: get source of record and handle as text
tell application "DEVONthink Pro"
copy source of rec to source_str
end tell
perform_echo_command(source_str, rec, command_str, output_str)
end handle_source_rec
on handle_url_rec(rec, command_str, output_str)
-- Link: Download url and handle as text
tell application "DEVONthink Pro"
copy URL of rec to url_str
set html_src to download markup from url_str
end tell
perform_echo_command(html_src, rec, command_str, output_str)
end handle_url_rec
on handle_link_rec(rec, command_str, output_str)
-- Link: Handle as path or as a url rec
tell application "DEVONthink Pro"
set path_str to path of rec
copy path of rec to path_str
end tell
if path_str is "" then
-- This is a link ot a URL
handle_url_rec(rec, command_str, output_str)
else
-- This is an indexed file
perform_cat_command(path_str, rec, command_str, output_str)
end if
end handle_link_rec
on handle_media_rec(rec, command_str, output_str)
-- Picture, Sound or Video: Handle as path
tell application "DEVONthink Pro"
copy path of rec to path_str
end tell
perform_cat_command(path_str, rec, command_str, output_str)
end handle_media_rec
on get_form_data(rec, delim)
-- Return calls in record as a delimited string
set data_str to ""
tell application "DEVONthink Pro"
copy cells of rec to cell_list
end tell
repeat with c in cell_list
if data_str is not "" then set data_str to data_str & delim
set data_str to data_str & (c as text)
end repeat
return data_str
end get_form_data
on handle_form_rec(rec, command_str, output_str)
-- Form: Convert to tab-delim and handle as text
set data_str to get_form_data(rec, " ") -- delim is TAB char '\t'
perform_echo_command(data_str, rec, command_str, output_str)
end handle_form_rec
on handle_sheet_rec(rec, command_str, output_str)
-- Sheet: Send to form handler
tell application "DEVONthink Pro"
set form_list to children of rec
end tell
set data_str to ""
repeat with f in form_list
tell application "DEVONthink Pro"
set f_type to kind of f
end tell
if f_type = "record" then
-- append newline to previous line -- embedded newline '\n'
if data_str is not "" then set data_str to data_str & "
"
set data_str to data_str & get_form_data(f, " ") -- delim is tab '\t'
end if
end repeat
perform_echo_command(data_str, rec, command_str, output_str)
end handle_sheet_rec
on handle_group_rec(rec, command_str, output_str)
-- Group of records: Recursively handle all records in group
tell application "DEVONthink Pro"
set rec_list to children of rec
end tell
repeat with rec in rec_list
handle_record(rec, command_str, output_str)
end repeat
end handle_group_rec
on handle_record(rec, command_str, output_str)
-- Perform command on a single DT record
-- Calls appropriate handler for record type
set record_strings to {"form", "group", "html", "link", "picture", "rtf", "rtfd", "sheet", "txt", "xml", "record"}
set record_handlers to {handle_form_rec, handle_group_rec, handle_source_rec, handle_link_rec, handle_media_rec, handle_text_rec, handle_text_rec, handle_sheet_rec, handle_text_rec, handle_source_rec, handle_form_rec}
tell application "DEVONthink Pro"
set type_str to type of rec as text
end tell
-- get handler for record type
global rec_handler
set rec_handler to handle_media_rec
repeat with i from 1 to count of record_strings
if item i of record_strings is type_str then
set rec_handler to item i of record_handlers
exit repeat
end if
end repeat
rec_handler(rec, command_str, output_str)
end handle_record
on handle_input(command_str, output_str)
-- Get selection from DT and handle as appropriate
-- This supports selected text and one or more selected records.
tell application "DEVONthink Pro"
set rec_list to the selection
set input_data to selected text of think window 1
-- test if input_data is undefined
try
set jnk to input_data
on error
set input_data to ""
end try
end tell
if rec_list is {} then error "Please select some text or at least one record"
if input_data is not "" then
perform_text_command(input_data, item 1 of rec_list, command_str, output_str)
else
repeat with rec in rec_list
handle_record(rec, command_str, output_str)
end repeat
end if
end handle_input
-- *** Get Options ***
on get_command()
-- Get command to execute from user.
-- If history is enabled, the most recent history item is displayed, and a button to
-- select a command form the History appears.
if my enable_cmd_history then
set cmd_str to last_cmd_in_history()
set btn_list to {"Cancel", "History", "OK"}
else
set cmd_str to ""
set btn_list to {"Cancel", "OK"}
end if
display dialog "Enter the command to filter input through:" buttons btn_list default button "OK" default answer cmd_str
if my enable_cmd_history and button returned of result is "History" then
-- show list of items in history
set user_sel to get_cmd_in_history()
-- user did not select a history item; recurse to get command.
if user_sel is "" then return get_command()
else
copy text returned of result to user_sel
end if
return user_sel
end get_command
on get_output_option()
-- Show list of output options to user
-- These include Discard, New Record, Append Record, Replace Record, Append Comment,
-- Send to Clipboard, and Send to DT log.
set output_opts to {"Discard (/dev/null)", "Create New Text Record", "Append to Record Text", "Replace Record Text", "Import as File", "Append Comment of Record", "Send to Clipboard", "Send to DevonThink Log"}
set output_strings to {"discard", "new", "append", "replace", "import", "comment", "clipboard", "log"}
set user_sel to choose from list output_opts with prompt "Select output destination"
repeat with i from 1 to count of output_opts
if item 1 of user_sel = item i of output_opts then
return item i of output_strings
end if
end repeat
return item 1 of output_strings
end get_output_option
-- *** main ***
set command_str to get_command()
set output_str to get_output_option()
handle_input(command_str, output_str)
-- clipboard is the only output handler that has to be
-- dealt with after all records have been filtered.
if output_str is "clipboard" then
tell application "System Events"
set the clipboard to clipboard_str
end tell
end if
-- if we got this far, command is worth saving
if enable_cmd_history then
add_to_history(command_str)
end if
OK, I’m just about ready to start managing our internal data in DT
–Eric