How to log actual value without introducing a variable in AppleScript [duplicate] - applescript

I am just trying to log the state of objects throughout the life of my applescript. In other languages, object's toString() methods will render the text equivalent and I can use those. In AppleScript, this does not seem to be the case.
convert applescript object to string (similar to toString)
Will output the finder object (and its properties) to the "Results" window of AppleScript Editor, but only if it is the last statement which gets executed.
If I have a trace() statement (which takes a message for logging purposes):
on trace(message)
do shell script "cat >>~/log/applescript.txt <<END_OF_THE_LOG
" & (message as text) & "
END_OF_THE_LOG"
end trace
and try to log the same object, I get
Can’t make properties of application "Finder" into type text.
I'm open to better ways of logging to a console, but would like to find out how to write an object's properties (like AppleScript Editor does) in the middle of a script for testing either way.

AppleScript doesn't make it easy:
log only logs while running in AppleScript Editor or when running via osascript (to stderr in that case) - the output will be lost in other cases, such as when applications run a script with the NSAppleScript Cocoa class.
log only accepts one argument; while it does accept any object type, it doesn't make it easy to get a meaningful representation of non-built-in types: try log me to get information about the script itself, for instance; frequently, log (get properties of <someObj>) must be used to get meaningful information; note the cumbersome syntax, which is required, because just using log properties of <someObj> typically merely prints the name of the reference form instead of the properties it points to (e.g, log properties of me uselessly outputs just (*properties*)).
In general, AppleScript makes it very hard to get meaningful text representations of objects of non-built-in types: <someObj> as text (same as: <someObj> as string) annoyingly breaks - throws a runtime error - for such objects; try me as text.
Below are helper subroutines that address these issues:
dlog() is a subroutine that combines deriving meaningful text representations of any objects with the ability to write to multiple log targets (including syslog and files) based on a global config variable.
toString() (effectively embedded in dlog()) is a subroutine that takes a single object of any type and derives a meaningful text representation from it.
Tip of the hat to #1.61803; his answer provided pointers for implementing the various logging targets.
Examples:
# Setup: Log to syslog and a file in the home dir.
# Other targets supported: "log", "alert"
# Set to {} to suppress logging.
set DLOG_TARGETS to { "syslog", "~/as.log" }
# Log properties of the front window of frontmost application.
dlog(front window of application (path to frontmost application as text))
# Log properties of own front window; note the *list* syntax for multiple args.
dlog({"my front window: ", front window})
# Get properties of the running script as string.
toString(me) # ->, e.g.: [script name="sandbox"] {selection:insertion point after character 2475 of text of document "sandbox2.scpt", frontmost:true, class:application, name:"AppleScript Editor", version:"2.6"}
See the source-code comments above each subroutine for details.
dlog() source code
# Logs a text representation of the specified object or objects, which may be of any type, typically for debugging.
# Works hard to find a meaningful text representation of each object.
# SYNOPSIS
# dlog(anyObjOrListOfObjects)
# USE EXAMPLES
# dlog("before") # single object
# dlog({ "front window: ", front window }) # list of objects
# SETUP
# At the top of your script, define global variable DLOG_TARGETS and set it to a *list* of targets (even if you only have 1 target).
# set DLOG_TARGETS to {} # must be a list with any combination of: "log", "syslog", "alert", <posixFilePath>
# An *empty* list means that logging should be *disabled*.
# If you specify a POSIX file path, the file will be *appended* to; variable references in the path
# are allowed, and as a courtesy the path may start with "~" to refer to your home dir.
# Caveat: while you can *remove* the variable definition to disable logging, you'll take an additional performance hit.
# SETUP EXAMPLES
# For instance, to use both AppleScript's log command *and* display a GUI alert, use:
# set DLOG_TARGETS to { "log", "alert" }
# Note:
# - Since the subroutine is still called even when DLOG_TARGETS is an empty list,
# you pay a performancy penalty for leaving dlog() calls in your code.
# - Unlike with the built-in log() method, you MUST use parentheses around the parameter.
# - To specify more than one object, pass a *list*. Note that while you could try to synthesize a single
# output string by concatenation yourself, you'd lose the benefit of this subroutine's ability to derive
# readable text representations even of objects that can't simply be converted with `as text`.
on dlog(anyObjOrListOfObjects)
global DLOG_TARGETS
try
if length of DLOG_TARGETS is 0 then return
on error
return
end try
# The following tries hard to derive a readable representation from the input object(s).
if class of anyObjOrListOfObjects is not list then set anyObjOrListOfObjects to {anyObjOrListOfObjects}
local lst, i, txt, errMsg, orgTids, oName, oId, prefix, logTarget, txtCombined, prefixTime, prefixDateTime
set lst to {}
repeat with anyObj in anyObjOrListOfObjects
set txt to ""
repeat with i from 1 to 2
try
if i is 1 then
if class of anyObj is list then
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}} # '
set txt to ("{" & anyObj as string) & "}"
set AppleScript's text item delimiters to orgTids # '
else
set txt to anyObj as string
end if
else
set txt to properties of anyObj as string
end if
on error errMsg
# Trick for records and record-*like* objects:
# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9).
try
set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg
end try
end try
if txt is not "" then exit repeat
end repeat
set prefix to ""
if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then
set prefix to "[" & class of anyObj
set oName to ""
set oId to ""
try
set oName to name of anyObj
if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\""
end try
try
set oId to id of anyObj
if oId is not missing value then set prefix to prefix & " id=" & oId
end try
set prefix to prefix & "] "
set txt to prefix & txt
end if
set lst to lst & txt
end repeat
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {" "}} # '
set txtCombined to lst as string
set prefixTime to "[" & time string of (current date) & "] "
set prefixDateTime to "[" & short date string of (current date) & " " & text 2 thru -1 of prefixTime
set AppleScript's text item delimiters to orgTids # '
# Log the result to every target specified.
repeat with logTarget in DLOG_TARGETS
if contents of logTarget is "log" then
log prefixTime & txtCombined
else if contents of logTarget is "alert" then
display alert prefixTime & txtCombined
else if contents of logTarget is "syslog" then
do shell script "logger -t " & quoted form of ("AS: " & (name of me)) & " " & quoted form of txtCombined
else # assumed to be a POSIX file path to *append* to.
set fpath to contents of logTarget
if fpath starts with "~/" then set fpath to "$HOME/" & text 3 thru -1 of fpath
do shell script "printf '%s\\n' " & quoted form of (prefixDateTime & txtCombined) & " >> \"" & fpath & "\""
end if
end repeat
end dlog
toString() source code
# Converts the specified object - which may be of any type - into a string representation for logging/debugging.
# Tries hard to find a readable representation - sadly, simple conversion with `as text` mostly doesn't work with non-primitive types.
# An attempt is made to list the properties of non-primitive types (does not always work), and the result is prefixed with the type (class) name
# and, if present, the object's name and ID.
# EXAMPLE
# toString(path to desktop) # -> "[alias] Macintosh HD:Users:mklement:Desktop:"
# To test this subroutine and see the various representations, use the following:
# repeat with elem in {42, 3.14, "two", true, (current date), {"one", "two", "three"}, {one:1, two:"deux", three:false}, missing value, me, path to desktop, front window of application (path to frontmost application as text)}
# log my toString(contents of elem)
# end repeat
on toString(anyObj)
local i, txt, errMsg, orgTids, oName, oId, prefix
set txt to ""
repeat with i from 1 to 2
try
if i is 1 then
if class of anyObj is list then
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}}
set txt to ("{" & anyObj as string) & "}"
set AppleScript's text item delimiters to orgTids # '
else
set txt to anyObj as string
end if
else
set txt to properties of anyObj as string
end if
on error errMsg
# Trick for records and record-*like* objects:
# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9).
try
set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg
end try
end try
if txt is not "" then exit repeat
end repeat
set prefix to ""
if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then
set prefix to "[" & class of anyObj
set oName to ""
set oId to ""
try
set oName to name of anyObj
if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\""
end try
try
set oId to id of anyObj
if oId is not missing value then set prefix to prefix & " id=" & oId
end try
set prefix to prefix & "] "
end if
return prefix & txt
end toString

Just use the log statement in AppleScript Editor. When you view the results in Applescript Editor, at the bottom of the window press the "Events" button. Normally the "Results" button is pressed and then you only see the result of the last statement as you mention. So change the button to "Events". This will show you everything that is happening as the script runs, and additionally all of the log statements that you put throughout the code too. Note that the log statements do not have to be text. You can log any object.
This is the best way to debug your script and see what is happening. As an example try this and look at the "Events". Realistically thought you don't need a lot of log statements if you view the Events because everything is already logged!
set someFolder to path to desktop
log someFolder
tell application "Finder"
set someItem to first item of someFolder
log someItem
set itemProps to properties of someItem
log itemProps
end tell

Try any of the following:
# echo to file
do shell script "echo " & quoted form of (myObj as string) & ¬
" > ~/Desktop/as_debug.txt"
# write to file
set myFile to open for access (path to desktop as text) & ¬
"as_debug2.txt" with write permission
write myObj to myFile
close access myFile
# log to syslog
do shell script "logger -t 'AS DEBUG' " & myObj
# show dialog
display dialog "ERROR: " & myObj
If what you're trying to log is not text, you might try:
quoted form of (myObj as string)

Here are examples of console log:
set my_int to 9945
log my_int
set my_srt to "Hamza"
log my_srt
set my_array ["Apple","Mango","Banana","Gava"]
log my_array
set my_obj to {"Ali"} as string
log my_obj

do shell script "echo '" & (current date) & ": Found " & Thisfilename & "' >> ~/logs/MyGreatAppleScript.log"

Similar to toString()...
on TextOf(aVariable)
try
return "" & aVariable
on error errm
if errm begins with "Can’t make " ¬
and errm ends with " into type Unicode text." then ¬
return text 12 through -25 of errm
return "item of class " & (class of aVariable) & return & errm
end try
end TextOf

Easiest way to know your values-
display dialog "my variable: " & myVariableName

For scripts that run long and I’m not looking at the screen, I like to have applescript say out loud what it’s doing.
Ie.
say “This is a log statement.”
If inside a tell statement:
tell me to say “This is a log statement.”

Related

Get text of selected field code in Microsoft Word using AppleScript

I'm making a Automator to jump from citation in Word to the reference software(Zotero). But I can't find a AppleScript to extract text of selected field code (the first step).
The field code in Word is
ADDIN ZOTERO_ITEM CSL_CITATION {"citationID":"AFUiwuqi","properties":{"formattedCitation":"[1]","plainCitation":"[1]","noteIndex":0},"citationItems":[{"id":9752,"uris":["http://zotero.org/users/6410528/items/YYTRWPHH"],"itemData":{"id":9752,"type":"article-journal","container-title":"Nature","DOI":"10.1038/s41586-019-1737-7","ISSN":"0028-0836, 1476-4687","issue":"7782","page":"324-329","title":"Controlled flight of a microrobot powered by soft artificial muscles","volume":"575","author":[{"family":"Chen","given":"Yufeng"},{"family":"Zhao","given":"Huichan"},{"family":"Mao","given":"Jie"},{"family":"Chirarattananon","given":"Pakpong"},{"family":"Helbling","given":"E. Farrell"},{"family":"Hyun","given":"Nak-seung Patrick"},{"family":"Clarke","given":"David R."},{"family":"Wood","given":"Robert J."}],"issued":{"date-parts":[["2019",11,14]]}}}],"schema":"https://github.com/citation-style-language/schema/raw/master/csl-citation.json"}
Here is the script process:
Extract text from selected field code in Word (This is the question)
Get the uris text(http://zotero.org/users/6410528/items/YYTRWPHH)
Get the item-codes (YYTRWPHH).
Open url (zotero://select/library/items?itemKey=YYTRWPHH)
Now I use VBA to extract field code text, see below. But in this way, the file will be changed. So I want to do this via AppleScript.
Sub GetFiledsCodes()
Dim myRange As Range, myCodes As String
Set myRange = Selection.Range
With myRange
If .Fields.Count = 0 Then
MsgBox "No Code!", vbInformation
Exit Sub
Else
.Fields.Update
.TextRetrievalMode.IncludeFieldCodes = True
.TextRetrievalMode.IncludeHiddenText = True
myCodes = .Text
myCodes = VBA.Replace(myCodes, Chr(19), "{")
myCodes = VBA.Replace(myCodes, Chr(21), "}")
.SetRange .End, .End
.InsertAfter myCodes
.Font.Name = "Times New Roman"
.Font.Size = 12
.Cut
End If
End With
End Sub
PS:
Here is my process in Automator(it can work but using VBA):
Run AppleScript
on run {input, parameters}
tell application "Microsoft Word" to activate
tell application "Microsoft Word"
run VB macro macro name "GetFiledsCodes"
delay 0.5
end tell
return input
end run
Get contents from clipboard
Extract URLs from Text
Filter Paragraphs begin with http://zotero.org/users/
Copy to Clipboard
Run AppleScript
set myStr to do shell script "pbpaste"
tell application "Zotero" to activate
set AppleScript's text item delimiters to "
"
set myList to every text item of myStr
set zoterocode to ""
set codes to ""
repeat with j from 1 to the length of myList
set itemValue to item j of myList
set zoterocode to (do shell script "sed -E 's#http://zotero.org/users/[0-9]+/items/##g' <<< " & itemValue)
if j = 1 then
set codes to zoterocode
else
set codes to codes & "," & zoterocode
end if
end repeat
tell application "System Events"
key code 18 using {command down, control down, option down}
delay 0.5
set collectionKey to do shell script "pbpaste"
if collectionKey = myStr then
set theurl to "zotero://select/library/items?itemKey=" & codes
else
set theurl to collectionKey & "/items?itemKey=" & codes
end if
open location theurl
end tell
That helps a lot. Okay, so this isn't a turnkey solution for your question but I don't think you really need that as you'd probably end up having to tell me more about how this app works than is really necessary. So this script focuses on your initial question about getting the field codes/result ranges from a merge document.
I put together a simple mail merge consisting of labels and a data file with 8 records, each of which have 5 fields: {"«LastName»", "«JobTitle»", "«Company»", "«City»", "«Web»"}. The latter is the key field.
Basically, the script runs through the data merge document and cycles first through its fields, then the web field, and finally the web addresses.
Based on your script, I can't really determine what you are doing with each address so it finishes by collecting just the final part of each address in a list. The obscure parts for me are the pbpastes, the codes and the whole System Events block. This area would need tweaking.
Incidentally, it's quite likely that you can avoid some of the shell scripts but I can't say how yet. Obviously the script has some redundancies and could be further refined but I think it demonstrates how to extract the information you need. Take a look at it and let me know what issues there are that need addressing.
tell application "Microsoft Word"
set d1 to document "cardo_labels.docx"
set fContents to {} -- list of mergefield
set fResRange to {} -- list of result range, i.e. field merge data
repeat with x from 1 to (count of fields of d1)
set fcs to content of field code of field x of d1 --> " MERGEFIELD LastName "
set frr to content of result range of field x of d1 --> "Smith"
if fcs is not " NEXT " then -- ignore «Next Record»
set end of fContents to fcs
set end of fResRange to frr
end if
end repeat
--> single record example
fContents --> {" MERGEFIELD LastName ", " MERGEFIELD JobTitle ", " MERGEFIELD Company ", " MERGEFIELD City ", " MERGEFIELD Web "}
fResRange --> {"Smith", "President", "Acme Screw & Gear", "Metz", "http://zotero.org/users/1234/items/smith-metz"}
-- NB when not displaying 'merged data', fResRange will appear thusly: {"«LastName»", "«JobTitle»", "«Company»", "«City»", "«Web»"}
set webList to {}
repeat with y from 1 to (count of fResRange)
if item y of fResRange begins with "http://zotero.org/users/" then
set end of webList to (item y of fResRange)
end if
end repeat
--> {"http://zotero.org/users/1234/items/smith-metz"}
--> {"http://zotero.org/users/1234/items/smith-metz", "http://zotero.org/users/4222/items/branson-metz", "http://zotero.org/users/3236/items/house-metz", "http://zotero.org/users/3342/items/kurtz-london", "http://zotero.org/users/12345/items/jones-london"}
set urlPiece to {}
set AppleScript's text item delimiters to "/"
repeat with z in webList
set end of urlPiece to last text item of z
end repeat
-- contents of z
--> "http://zotero.org/users/1234/items/smith-metz"
set AppleScript's text item delimiters to ""
urlPiece
--> {"smith-metz"}
--> {"smith-metz", "jones-saopaolo", "branson-metz", "house-metz", "kurtz-london", "jones-london"}
end tell
Thanks to ideas from #Mockman.
Combining with the selection, here is the way to extract text from selected field code via AppleScript:
tell application "Microsoft Word"
tell selection
set fcs to content of field code of field of text object
end tell
end tell
fcs

Using Applescript to scan a volume, pull out the path, file name and a number and output results csv

I have been at this for two days.
So, using Automator and Applescript, I need to scan a volume (or volumes) and get a path to each file, the file name plus extension, assetID (if there is one) and output each part to a comma separated csv file.
So far, I have the Automator actions sorted out and most of the Applescript part but I am at my wits end. The paths and file names work but extracting the assetID (if there is one) is the problem. Not every file has an assetID (and those I am not interested in). The assetID is always a 10 digit number at the end of the file preceded by an underscore("_") like so - afilename_1234567890.ext. As it is now, the script will display the assetID's of the files it processes but as soon as it gets to a file with no id, I see the following error, "The action “Run AppleScript” encountered an error: Can’t get text 1 thru -1 of "".” Something is getting munged somewhere.
Any help would be greatly appreciated.
The script so far...
on run {input, parameters}
-- save delimiters to restore old settings
set savedDelimiters to AppleScript's text item delimiters
-- set delimiters to delimiter to be used
set AppleScript's text item delimiters to "/"
repeat with aPath in input
-- set a variable to contain the "/" (POSIX) version of the files path
set filesPath to POSIX path of aPath
-- get the file name
set fileName to last text item of filesPath
-- get the file name without the extension
set thePrefix to text 1 thru ((offset of "." in fileName) - 1) of fileName
-- get the asset ID, if there is one
set assetID to rightStringFromRight(thePrefix, "_")
display dialog assetID
if (class of assetID) is integer then
-- get the path only
set pathOnly to ((text items 1 thru -2 of (get POSIX path of aPath)) as Unicode text) & "/"
-- output the path only, file name and asset ID to a comma delimited csv file
display dialog assetID
end if
end repeat
-- restore the old delimiter setting
set AppleScript's text item delimiters to savedDelimiters
end run
on rightStringFromRight(str, del)
local str, del, oldTIDs
set oldTIDs to AppleScript's text item delimiters
try
set str to str as string
if str does not contain del then return str
set AppleScript's text item delimiters to del
set str to str's last text item
set AppleScript's text item delimiters to oldTIDs
return str
on error eMsg number eNum
set AppleScript's text item delimiters to oldTIDs
error "Can't rightStringFromRight: " & eMsg number eNum
end try
end rightStringFromRight
on is_number(number_string)
try
set number_string to number_string as number
return true
on error
return false
end try
end is_number
I finally got the script working. A few wrong assumptions corrected and all is well.

applescript transforming a list into a txt file

I'm trying to write all the song names my iTunes to a txt document. The first issue I had was that I can't seem to correctly loop the operation. Here is my test case with the first 15 songs in my iTunes:
tell application "TextEdit"
make new document
end tell
tell application "iTunes"
set trNameID1 to name of track 1
set trNameID2 to name of track 2
set trNameID3 to name of track 3
set trNameID4 to name of track 4
set trNameID5 to name of track 5
set trNameID6 to name of track 6
set trNameID7 to name of track 7
set trNameID8 to name of track 8
set trNameID9 to name of track 9
set trNameID10 to name of track 10
set trNameID11 to name of track 11
set trNameID12 to name of track 12
set trNameID13 to name of track 13
set trNameID14 to name of track 14
set trNameID15 to name of track 15
tell application "TextEdit"
set text of document 1 to {trNameID1 & "
", trNameID2 & "
", trNameID3 & "
", trNameID4 & "
", trNameID5 & "
", trNameID6 & "
", trNameID7 & "
", trNameID8 & "
", trNameID9 & "
", trNameID10 & "
", trNameID11 & "
", trNameID12 & "
", trNameID13 & "
", trNameID14 & "
", trNameID15} as text
end tell
end tell
When I try to loop it, the txt document only contains the last song name, for instance:
tell application "TextEdit"
make new document
end tell
tell application "iTunes"
set trNum to 1
repeat 15 times
set trNameID to name of track (trNum)
tell application "TextEdit"
set text of document 1 to trNameID & "
"
end tell
end repeat
end tell
This will only output the fifteenth song's name onto the txt document.
I realize that this may be very basic, but I have literally been using applescript for about 48 hours, and I can't seem to figure this out. I would like all of the song names to be in a txt document so I can read and analyze the strings in c++. Does anyone have any ideas?
Also, I'm not sure if there is a way, in AppleScript, to look at the entire iTunes library and see the last song, record that song's id in iTunes, and then make a repeat loop that goes through that id. This way the loop would work for exactly the number of songs that are in the library.
Any ideas would be very much appreciated!
You don't really need a repeat loop at all. You can get track names directly from iTunes. You get it in list format so we just convert that list into a string separating the list items with a return character. Then we write it to TextEdit. So this code optimizes #Michele Percich's code by eliminating the repeat loop and using applescript's text item delimiters to convert the list to a string for use in TextEdit.
tell application "iTunes"
set trackNames to name of every track in (first playlist whose special kind is Music)
end tell
set text item delimiters to return
set trackNames to trackNames as text
set text item delimiters to ""
tell application "TextEdit"
make new document
set text of document 1 to trackNames
end tell
You need to increment the value of trNum variable at the end of your repeat loop:
set trNum to trNum + 1
Or better use a different repeat syntax:
repeat with trNum from 1 to 15
And also to add (and not replace) the track name to the document:
set text of document 1 to text of document 1 & trNameID & return
However, this probably is a better way to do what you want:
tell application "iTunes"
set trackList to ""
set allTracks to every track in (first playlist whose special kind is Music)
repeat with currentTrack in allTracks
set trNameID to name of currentTrack
set trackList to trackList & trNameID & return
end repeat
end tell
tell application "TextEdit"
make new document
set text of document 1 to trackList
end tell
i see you all use the:
tell application "TextEdit"
make new document
set text of document 1 to trackNames
end tell
command
You can use a faster way:
set textlocation to "/users/yourusername/desktop/test.txt"
set Line_1 to "Hello this is line one, if you want more lines just copy > this script and change the variables."
do shell script "echo " & quoted form of Line_1 & " >> " & quoted form of textlocation
You can see in the script the 2 ">>" signs, this will add each textline in a new line in a txt file.
If there is only one ">" the text will replace the other text.
Here is an example:
First with 2 ">>" lines
do shell script "echo Hey this is one line. >> /Users/Yourusername/desktop/Add.txt"
do shell script "echo And this is the second one. >> /Users/Yourusername/desktop/Add.txt"
This script will make a txt file like this:
Hey this is one line.
And this is the second one.
Now with 2 ">" lines
do shell script "echo Hey this is one line > /Users/Zl109819/desktop/Add.txt"
do shell script "echo And this is the second one > /Users/Zl109819/desktop/Add.txt"
This script will make a txt file like this:
And this is the second one.

How to log objects to a console with AppleScript

I am just trying to log the state of objects throughout the life of my applescript. In other languages, object's toString() methods will render the text equivalent and I can use those. In AppleScript, this does not seem to be the case.
convert applescript object to string (similar to toString)
Will output the finder object (and its properties) to the "Results" window of AppleScript Editor, but only if it is the last statement which gets executed.
If I have a trace() statement (which takes a message for logging purposes):
on trace(message)
do shell script "cat >>~/log/applescript.txt <<END_OF_THE_LOG
" & (message as text) & "
END_OF_THE_LOG"
end trace
and try to log the same object, I get
Can’t make properties of application "Finder" into type text.
I'm open to better ways of logging to a console, but would like to find out how to write an object's properties (like AppleScript Editor does) in the middle of a script for testing either way.
AppleScript doesn't make it easy:
log only logs while running in AppleScript Editor or when running via osascript (to stderr in that case) - the output will be lost in other cases, such as when applications run a script with the NSAppleScript Cocoa class.
log only accepts one argument; while it does accept any object type, it doesn't make it easy to get a meaningful representation of non-built-in types: try log me to get information about the script itself, for instance; frequently, log (get properties of <someObj>) must be used to get meaningful information; note the cumbersome syntax, which is required, because just using log properties of <someObj> typically merely prints the name of the reference form instead of the properties it points to (e.g, log properties of me uselessly outputs just (*properties*)).
In general, AppleScript makes it very hard to get meaningful text representations of objects of non-built-in types: <someObj> as text (same as: <someObj> as string) annoyingly breaks - throws a runtime error - for such objects; try me as text.
Below are helper subroutines that address these issues:
dlog() is a subroutine that combines deriving meaningful text representations of any objects with the ability to write to multiple log targets (including syslog and files) based on a global config variable.
toString() (effectively embedded in dlog()) is a subroutine that takes a single object of any type and derives a meaningful text representation from it.
Tip of the hat to #1.61803; his answer provided pointers for implementing the various logging targets.
Examples:
# Setup: Log to syslog and a file in the home dir.
# Other targets supported: "log", "alert"
# Set to {} to suppress logging.
set DLOG_TARGETS to { "syslog", "~/as.log" }
# Log properties of the front window of frontmost application.
dlog(front window of application (path to frontmost application as text))
# Log properties of own front window; note the *list* syntax for multiple args.
dlog({"my front window: ", front window})
# Get properties of the running script as string.
toString(me) # ->, e.g.: [script name="sandbox"] {selection:insertion point after character 2475 of text of document "sandbox2.scpt", frontmost:true, class:application, name:"AppleScript Editor", version:"2.6"}
See the source-code comments above each subroutine for details.
dlog() source code
# Logs a text representation of the specified object or objects, which may be of any type, typically for debugging.
# Works hard to find a meaningful text representation of each object.
# SYNOPSIS
# dlog(anyObjOrListOfObjects)
# USE EXAMPLES
# dlog("before") # single object
# dlog({ "front window: ", front window }) # list of objects
# SETUP
# At the top of your script, define global variable DLOG_TARGETS and set it to a *list* of targets (even if you only have 1 target).
# set DLOG_TARGETS to {} # must be a list with any combination of: "log", "syslog", "alert", <posixFilePath>
# An *empty* list means that logging should be *disabled*.
# If you specify a POSIX file path, the file will be *appended* to; variable references in the path
# are allowed, and as a courtesy the path may start with "~" to refer to your home dir.
# Caveat: while you can *remove* the variable definition to disable logging, you'll take an additional performance hit.
# SETUP EXAMPLES
# For instance, to use both AppleScript's log command *and* display a GUI alert, use:
# set DLOG_TARGETS to { "log", "alert" }
# Note:
# - Since the subroutine is still called even when DLOG_TARGETS is an empty list,
# you pay a performancy penalty for leaving dlog() calls in your code.
# - Unlike with the built-in log() method, you MUST use parentheses around the parameter.
# - To specify more than one object, pass a *list*. Note that while you could try to synthesize a single
# output string by concatenation yourself, you'd lose the benefit of this subroutine's ability to derive
# readable text representations even of objects that can't simply be converted with `as text`.
on dlog(anyObjOrListOfObjects)
global DLOG_TARGETS
try
if length of DLOG_TARGETS is 0 then return
on error
return
end try
# The following tries hard to derive a readable representation from the input object(s).
if class of anyObjOrListOfObjects is not list then set anyObjOrListOfObjects to {anyObjOrListOfObjects}
local lst, i, txt, errMsg, orgTids, oName, oId, prefix, logTarget, txtCombined, prefixTime, prefixDateTime
set lst to {}
repeat with anyObj in anyObjOrListOfObjects
set txt to ""
repeat with i from 1 to 2
try
if i is 1 then
if class of anyObj is list then
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}} # '
set txt to ("{" & anyObj as string) & "}"
set AppleScript's text item delimiters to orgTids # '
else
set txt to anyObj as string
end if
else
set txt to properties of anyObj as string
end if
on error errMsg
# Trick for records and record-*like* objects:
# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9).
try
set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg
end try
end try
if txt is not "" then exit repeat
end repeat
set prefix to ""
if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then
set prefix to "[" & class of anyObj
set oName to ""
set oId to ""
try
set oName to name of anyObj
if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\""
end try
try
set oId to id of anyObj
if oId is not missing value then set prefix to prefix & " id=" & oId
end try
set prefix to prefix & "] "
set txt to prefix & txt
end if
set lst to lst & txt
end repeat
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {" "}} # '
set txtCombined to lst as string
set prefixTime to "[" & time string of (current date) & "] "
set prefixDateTime to "[" & short date string of (current date) & " " & text 2 thru -1 of prefixTime
set AppleScript's text item delimiters to orgTids # '
# Log the result to every target specified.
repeat with logTarget in DLOG_TARGETS
if contents of logTarget is "log" then
log prefixTime & txtCombined
else if contents of logTarget is "alert" then
display alert prefixTime & txtCombined
else if contents of logTarget is "syslog" then
do shell script "logger -t " & quoted form of ("AS: " & (name of me)) & " " & quoted form of txtCombined
else # assumed to be a POSIX file path to *append* to.
set fpath to contents of logTarget
if fpath starts with "~/" then set fpath to "$HOME/" & text 3 thru -1 of fpath
do shell script "printf '%s\\n' " & quoted form of (prefixDateTime & txtCombined) & " >> \"" & fpath & "\""
end if
end repeat
end dlog
toString() source code
# Converts the specified object - which may be of any type - into a string representation for logging/debugging.
# Tries hard to find a readable representation - sadly, simple conversion with `as text` mostly doesn't work with non-primitive types.
# An attempt is made to list the properties of non-primitive types (does not always work), and the result is prefixed with the type (class) name
# and, if present, the object's name and ID.
# EXAMPLE
# toString(path to desktop) # -> "[alias] Macintosh HD:Users:mklement:Desktop:"
# To test this subroutine and see the various representations, use the following:
# repeat with elem in {42, 3.14, "two", true, (current date), {"one", "two", "three"}, {one:1, two:"deux", three:false}, missing value, me, path to desktop, front window of application (path to frontmost application as text)}
# log my toString(contents of elem)
# end repeat
on toString(anyObj)
local i, txt, errMsg, orgTids, oName, oId, prefix
set txt to ""
repeat with i from 1 to 2
try
if i is 1 then
if class of anyObj is list then
set {orgTids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {", "}}
set txt to ("{" & anyObj as string) & "}"
set AppleScript's text item delimiters to orgTids # '
else
set txt to anyObj as string
end if
else
set txt to properties of anyObj as string
end if
on error errMsg
# Trick for records and record-*like* objects:
# We exploit the fact that the error message contains the desired string representation of the record, so we extract it from there. This (still) works as of AS 2.3 (OS X 10.9).
try
set txt to do shell script "egrep -o '\\{.*\\}' <<< " & quoted form of errMsg
end try
end try
if txt is not "" then exit repeat
end repeat
set prefix to ""
if class of anyObj is not in {text, integer, real, boolean, date, list, record} and anyObj is not missing value then
set prefix to "[" & class of anyObj
set oName to ""
set oId to ""
try
set oName to name of anyObj
if oName is not missing value then set prefix to prefix & " name=\"" & oName & "\""
end try
try
set oId to id of anyObj
if oId is not missing value then set prefix to prefix & " id=" & oId
end try
set prefix to prefix & "] "
end if
return prefix & txt
end toString
Just use the log statement in AppleScript Editor. When you view the results in Applescript Editor, at the bottom of the window press the "Events" button. Normally the "Results" button is pressed and then you only see the result of the last statement as you mention. So change the button to "Events". This will show you everything that is happening as the script runs, and additionally all of the log statements that you put throughout the code too. Note that the log statements do not have to be text. You can log any object.
This is the best way to debug your script and see what is happening. As an example try this and look at the "Events". Realistically thought you don't need a lot of log statements if you view the Events because everything is already logged!
set someFolder to path to desktop
log someFolder
tell application "Finder"
set someItem to first item of someFolder
log someItem
set itemProps to properties of someItem
log itemProps
end tell
Try any of the following:
# echo to file
do shell script "echo " & quoted form of (myObj as string) & ¬
" > ~/Desktop/as_debug.txt"
# write to file
set myFile to open for access (path to desktop as text) & ¬
"as_debug2.txt" with write permission
write myObj to myFile
close access myFile
# log to syslog
do shell script "logger -t 'AS DEBUG' " & myObj
# show dialog
display dialog "ERROR: " & myObj
If what you're trying to log is not text, you might try:
quoted form of (myObj as string)
Here are examples of console log:
set my_int to 9945
log my_int
set my_srt to "Hamza"
log my_srt
set my_array ["Apple","Mango","Banana","Gava"]
log my_array
set my_obj to {"Ali"} as string
log my_obj
do shell script "echo '" & (current date) & ": Found " & Thisfilename & "' >> ~/logs/MyGreatAppleScript.log"
Similar to toString()...
on TextOf(aVariable)
try
return "" & aVariable
on error errm
if errm begins with "Can’t make " ¬
and errm ends with " into type Unicode text." then ¬
return text 12 through -25 of errm
return "item of class " & (class of aVariable) & return & errm
end try
end TextOf
Easiest way to know your values-
display dialog "my variable: " & myVariableName
For scripts that run long and I’m not looking at the screen, I like to have applescript say out loud what it’s doing.
Ie.
say “This is a log statement.”
If inside a tell statement:
tell me to say “This is a log statement.”

Applescript repetitive renaming

I'm trying to make an AppleScript droplet to rename a bunch of images annoyingly formatted, but I found out my AppleScript skills have become nonexistent and I'm getting nowhere. So if possible, full code, not just snippets.
The file setup is always the same, but there are many variations (ex: Yellowst.Nat.Park.D12P55.DMS.3248.jpg)
It starts with a place name, should be a find and replace for a bunch of different strings, ("Yellowst.Nat.Park" -> "Yellowstone National Park")
Then it is followed by two numbers that should be changed in format (D12P55 -> [12x55]). They're always set up in a "D" followed by two numbers, a "P" and again two numbers.
And it ends with a random string, can be numbers, letters etc, which all have to go. They differ in format and length, no pattern in them.
Basically I want to go from "Yellowst.Nat.Park.D12P55.DMS.3248.jpg" to "Yellowstone National Park [02x03] .jpg" I want to add text afterwards so want to end with a space.
The best way to do this seems to me a repetitive find and replace for the first part, Make a list for a bunch of terms wich have to be replaced by a bunch of respective terms. Followed by a detection of the number format and ending with deleting of the random string after it.
Here is another approach.
property pictureFolder : (alias "Mac OS X:Users:Sam:Pictures:test:")
property findList : {"Yellowst.Nat.Park", "Jellyst.Nat.Park"}
property replaceList : {"Yellowstone National Park", "Jellystone \\& National Park"}
tell application "System Events"
set nameList to (name of every file of pictureFolder whose visible = true)
repeat with i from 1 to count of (list folder pictureFolder without invisibles)
set fileName to item i of nameList
set fileExtension to (name extension of (file fileName of pictureFolder))
repeat with j from 1 to count of findList
if fileName contains item j of findList then
set tempName to do shell script "echo " & fileName & " | sed 's/.D\\([0-9][0-9]\\)P\\([0-9][0-9]\\).*/[\\1x\\2] " & i & "." & fileExtension & "/'"
set tempName to do shell script "echo " & tempName & " | sed 's/^" & item j of findList & "/" & item j of replaceList & " /'"
set name of (file fileName of pictureFolder) to tempName
exit repeat
else if j = (count of findList) then
set tempName to do shell script "echo " & fileName & " | sed 's/[.]/ /g'"
set tempName to do shell script "echo " & tempName & " | sed 's/.D\\([0-9][0-9]\\)P\\([0-9][0-9]\\).*/ [\\1x\\2] " & i & "." & fileExtension & "/'"
set name of (file fileName of pictureFolder) to tempName
end if
end repeat
end repeat
end tell
To avoid duplicate names, I added a counter to the end of the file name. If there are no duplicates, you can use this instead:
set tempName to do shell script "echo " & fileName & " | sed 's/.D\\([0-9][0-9]\\)P\\([0-9][0-9]\\).*/[\\1x\\2] " & "." & fileExtension & "/'"
I like small challenges like this Sam. They're fun to me... maybe I'm sick ;). Anyway, I wrote you a handler to clean the file name as you requested. It's not really hard to manipulate text in applescript if you're comfortable with text item delimiters and such. These small challenges keep my text skills sharp.
NOTE: in the nameList property the name must end with a period or whatever character is just before the letter D in the number sequence DxxPxx as you mentioned.
So give this a try. Plug in a variety of fileNames and ensure it works how you want. Of course you need to put more values into the nameList and nameReplaceList properties too.
property nameList : {"Yellowst.Nat.Park."}
property nameReplaceList : {"Yellowstone National Park"}
set fileName to "Yellowst.Nat.Park.D12P55.DMS.3248.jpg"
cleanFilename(fileName)
(*================ SUBROUTINES ================*)
on cleanFilename(fileName)
-- first find the base name and file extension of the file name
set tids to AppleScript's text item delimiters
set ext to ""
if fileName contains "." then
set AppleScript's text item delimiters to "."
set textItems to text items of fileName
set ext to "." & item -1 of textItems
set baseName to (items 1 thru -2 of textItems) as text
set text item delimiters to ""
else
set baseName to fileName
end if
-- next find the pattern D, 2 numbers, P, and 2 numbers in the baseName
set chars to characters of baseName
set theSequence to missing value
repeat with i from 1 to (count of chars) - 6
set thisChar to item i of chars
if thisChar is "d" and item (i + 3) of baseName is "p" then
try
set firstNum to text (i + 1) thru (i + 2) of baseName
firstNum as number
set secondNum to text (i + 4) thru (i + 5) of baseName
secondNum as number
set theSequence to text i through (i + 5) of baseName
exit repeat
end try
end if
end repeat
-- now make the changes
if theSequence is not missing value then
set AppleScript's text item delimiters to theSequence
set theParts to text items of baseName
set fixedFirstPart to item 1 of theParts
repeat with i from 1 to count of nameList
if item i of nameList is fixedFirstPart then
set fixedFirstPart to item i of nameReplaceList
exit repeat
end if
end repeat
set fixedName to fixedFirstPart & " [" & firstNum & "x" & secondNum & "]" & ext
else
set fixedName to fileName
end if
set AppleScript's text item delimiters to tids
return fixedName
end cleanFilename
Now if you want to automate this for a folder full of files you can use this code. Just replace lines 3 and 4 of the above script with this. I didn't check this code but it's simple enough it should work as-is.
NOTE: you don't need to worry if non-image files are in the folder you choose with this code because they won't (I'm assuming this) have the DxxPxx number sequence and thus this script will not change them in any way.
set theFolder to choose folder
tell application "Finder"
set theFiles to files of theFolder
repeat with aFile in theFiles
set thisName to name of aFile
set newName to my cleanFilename(thisName)
if newName is not thisName then
set name of aFile to newName
end if
end repeat
end tell

Resources