Add choice to go back to previous list dialog

The subcategory list dialog and the full category list dialog each now
have an option to return to the previously displayed list dialog.

(*
	Log Page - Log categorized web page bookmarks to a text file

	Version: @@VERSION@@
	Date:    2012-12-20
	Author:  Steve Wheeler

	Get the title, URL, current date and time, and a user-definable
	category for the frontmost Safari window and log the info to a text
	file.

	This program is free software available under the terms of a
	BSD-style (3-clause) open source license detailed below.
*)

(*
Copyright (c) 2011-2012 Steve Wheeler
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*)

(*
	TODO: Change the following local-only config before any public release.
	The log file should be: $GTD_IN/logs/urls/urls.YYYY.txt
	Or just: $GTD_IN/logs/urls.txt

	TODO: The edit_log() function is currently incomplete. It will only launch
	an editor from the shell and only if that editor is a GUI editor (in this
	case, MacVim). If I release this script publicly, I will still need to
	implement funtionality to launch a GUI editor normally (with the 'open'
	AppleScript commmand) as well as a command-line editor run in the Terminal.
*)

property script_name : "Log Page"
property script_version : "@@VERSION@@"

property uRule : data utxt2500 as Unicode text -- BOX DRAWINGS LIGHT HORIZONTAL

--property p_list_types : {"top", "sub", "all"}

global g_top_categories, g_all_categories, g_previous_categories
global g_list_type, g_previous_list_type

set g_list_type to missing value
set g_previous_list_type to missing value

---------- USER CONFIGURATION ----------------------------------------
-- Default log directory to use if one is not read from config file:
--set log_dir to POSIX path of (path to application support folder from user domain) & "Jazzhead/Log Page/"
set log_dir to POSIX path of (path to desktop folder) & "Log Page/" -- :DEBUG:

-- Log file name:
set log_name to "urls.txt"

-- Default categories/labels if none are found in (or there is no)
-- URL file. Modify as desired:
set default_cats to "Development
Development:AppleScript
Development:AppleScript:Mail
Design
Productivity:GTD
Apple:Mac OS X
Apple:iOS
Career
Education
Health
Health:Diet
Health:Fitness
General"

-- Text editor to use if manually editing the log file.
--
-- This example:
--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
--	2. Switches the local working directory for just that buffer to the
--	    directory containing the file ('lcd')
--	3. Gives the MacVim window a name ('--servername').
--
set text_editor to "/opt/local/bin/mvim \"+lcd %:p:h\" --servername LOG_PAGE"

-- Should the specified editor be launched from the unix shell?
set should_use_shell to true
---------- END User Configuration ----------------------------------------

---------- ADVANCED (EXPERIMENTAL) ---------------------------------------------
-- For advanced users familiar with the command-line, override the default log
-- directory path. This probably won't be in a public release.
--
-- Grab an exported shell environment variable from a config file to use as the
-- log directory path. For this example, I have a "~/.gtdrc" config file that I
-- source from my "~/.bash_profile" file so that it exportss environment
-- variables relating to my GTD system. That makes those variables available to
-- any of the shell scripts and tools that I use. For instance, when I'm
-- editing files in Vim, I can write paths to other files using one of the
-- environment variables rather than a full or relative path and the 'gf'
-- command will work for jumping to that file. The config file looks like this:
--
--         # ~/.gtdrc
--         #
--         # File locations for GTD documents
--         #
--         export GTD_DIR=$HOME/Documents/org  # GTD root directory
--         export GTD_IN=$GTD_DIR/content      # source/input files
--         export GTD_OUT=$GTD_DIR/html        # generated HTML output files
--         export GTD_LIB=$GTD_DIR/lib         # code library files
--         export GTD_BIN=$GTD_LIB/script      # scripts
--
-- Remove (or comment out) the "(*" and "*)" comment markers below to use this
-- section. Make sure you also configure the two variables for your system.
--(*
--------------- USER CONFIGURATION ----------
-- Path to your config file:
set cfg_file to POSIX path of (path to home folder) & ".gtdrc"
-- The environment variable you're interested in:
set env_var to "GTD_IN"
--------------- END User Configuration ---------

-- Check for environment variable in config file:
set s to "egrep '\\<" & env_var & "=' " & quoted form of cfg_file &
	" > /dev/null 2>&1; echo $?"
set shell_status to (do shell script s) as integer
-- Source the config file and echo the desired variable:
if shell_status is 0 then -- (means that grep found a match)
	set s to ". " & quoted form of cfg_file & " && echo $" & env_var
	set gtd_in_dir to do shell script s
	set log_dir to gtd_in_dir & "/logs/"

	--------------------
	-- OPTIONAL: Override the Text editor to integrate with my GTD system.
	--
	-- This example:
	--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
	--	2. Switches the local working directory ('lcd') for just that buffer to
	--	    my GTD content directory ($GTD_IN after escaping potential spaces)
	--	3. Gives the MacVim window a name ('--servername').

	----- First, escape any potential spaces in the directory path:
	--
	--set gtd_in_dir to gtd_in_dir & " foo    bar" -- :DEBUG: spaces in path
	--
	set s to "echo " & quoted form of gtd_in_dir & " | sed 's/ \\{1,\\}/\\\\ /g'" -- escape spaces
	set gtd_in_dir to do shell script s
	--
	--log gtd_in_dir -- :DEBUG: need to use 'log' to see the value w/o AppleScript escapes

	----- Finally, set the editor with all of the options:
	--
	set text_editor to "/opt/local/bin/mvim \"+lcd " & gtd_in_dir & "\" --servername GTD --remote-silent"
	--
	--log text_editor -- :DEBUG:
	--return text_editor -- :DEBUG:
	--------------------
end if
---------- END Advanced --------------------------------------------------------
--*)

--return log_dir -- :DEBUG:

--
-- Get URL and title from front browser window
--
tell application "Safari"
	--activate
	try
		set this_url to URL of front document
		set this_title to name of first tab of front window whose visible is true
	on error
		return false
	end try
end tell
-- Transliterate non-ASCII characters to ASCII
set this_title to convert_to_ascii(this_title)

--return this_title -- :DEBUG:

--
-- Format the record separator between log entries
--
set rule_char to "-"
set rule_width to 80
set field_width to 7
set field_sep to " | "
set rec_sep to "" & multiply_text(rule_char, field_width - 1) &
	"+" & multiply_text(rule_char, rule_width - field_width)

--
-- Format date-time string as "YYYY-MM-DD HH:MM:SS"
--
set {year:y, month:m, day:d, hours:hh, minutes:mm, seconds:ss} to current date
set m to m as integer -- coerce month string
-- Pad numbers with leading zeros:
set tmp_list to {}
repeat with this_item in {m, d, hh, mm, ss}
	set this_item to text 2 thru -1 of (100 + this_item as text)
	set end of tmp_list to this_item
end repeat
set {m, d, hh, mm, ss} to tmp_list
-- Final string:
set date_time to join_list({y, m, d}, "-") & " " & join_list({hh, mm, ss}, ":")

-- Log file name
--set log_name to "urls." & y & ".txt"

--
-- Create log directory if it doesn't already exist. (Go ahead and use a shell
-- command since it's easy to create intermediate directories all in one step.):
--
try
	set s to "mkdir -pm700 " & quoted form of log_dir
	do shell script s
on error err_msg number err_num
	error "Can't create log directory: " & err_msg number err_num
	return "Fatal Error: Can't create log directory"
end try

set log_file_posix to "" & log_dir & log_name
set log_file to POSIX file log_file_posix

--
-- Add header to new or empty log file
--
set should_init to false
tell application "Finder"
	if (not (exists log_file)) or (_IO's read_file(log_file) is "") then
		set should_init to true
	end if
end tell

set head_sep to multiply_text("#", 80)
if should_init then
	set file_header to head_sep & "
# urls.txt - Timestamped and categorized URL archive
# ==================================================
# Optionally list some categories/labels of URLs below that might not yet
# be used. Any categories/lables listed here or in the web page records
# will be presented as a list to select from when saving new URLs.
" & head_sep & linefeed & default_categories & linefeed & head_sep & "
# END: category/label list
" & head_sep & linefeed
	tell _IO to write_file(log_file, file_header)
	set all_category_txt to default_categories
else
	-- Read categories from file
	(*
		:TODO: Error handling.
	*)
	set all_category_txt to text 2 thru -2 of (item 3 of split_text(_IO's read_file(log_file), head_sep))
end if

-- Parse any existing labels from the "Label" fields of the URLs file:
set s to "LANG=C sed -n 's/^Label | //p' " & quoted form of log_file_posix & " | sort | uniq"
--return s
set categories_used to do shell script s without altering line endings
-- Sort those along with the manually entered categories/labels:
set s to "echo \"" & all_category_txt & linefeed & categories_used & "\" | egrep -v '^$' | sort | uniq"
set all_category_txt to do shell script s without altering line endings
--return all_category_txt -- :DEBUG:
-- Coerce the lines into a list:
set g_all_categories to paragraphs of all_category_txt
if g_all_categories's last item is "" then set g_all_categories to g_all_categories's items 1 thru -2
--return g_all_categories -- :DEBUG:

-- Get top-level categories:
set s to "LANG=C echo \"" & all_category_txt & "\" | sed -n 's/^\\([^:]\\{1,\\}\\).*/\\1/p' | uniq"
set top_category_txt to do shell script s without altering line endings
--return top_category_txt -- :DEBUG:
-- Coerce the lines into a list:
set g_top_categories to paragraphs of top_category_txt
if g_top_categories's last item is "" then set g_top_categories to g_top_categories's items 1 thru -2
--return g_top_categories -- :DEBUG:

--
-- Prompt for title, category/subcategories and optional note for URL
--

-- The number of dialog prompts will vary depending on if a subcategory list is requested.
set prompt_count to 1 -- increment with every dialog or list prompt
set prompt_total to 4 -- increment if the subcategory list is displayed

-- Accept or modify URL title
set b to {"Cancel", "Manually Edit Log File", "Next..."}
set t to "" & script_name & ": Title (" & prompt_count & "/" & prompt_total & ")"
set m to "To log the URL for:" & return & return
	& tab & "\"" & this_title & "\"" & return & return &
	"first accept or edit the title."
display dialog m default answer this_title with title t buttons b default button last item of b
set {this_title, btn_pressed} to {text returned of result, button returned of result}
set prompt_count to prompt_count + 1
if btn_pressed is item 2 of b then
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	edit_log(this_log_file, text_editor, should_use_shell)
	return "Script ended with '" & (item 2 of b as text) & "'"
end if

-- Select a category
set chosen_category to choose_category(g_top_categories, "top")
if chosen_category is false then error number -128 -- User canceled

-- Modify selected category or enter a new category
set t to "" & script_name & ": Category (" & prompt_count & "/" & prompt_total & ")"
set m to "Please provide a category and any optional subcategories (or edit your selected category) for the URL. Example: \"Development:AppleScript:Mail\""
display dialog m default answer chosen_category with title t buttons b default button last item of b
set {this_label, btn_pressed} to {text returned of result, button returned of result}
set prompt_count to prompt_count + 1

-- Optionally add note
if btn_pressed is last item of b then
	set last item of b to "Append URL to Log File"
	set t to "" & script_name & ": Note (" & prompt_count & "/" & prompt_total & ")"
	set m to "Optionally add a short note. Just leave the field blank if you don't want to add a note."
	display dialog m default answer "" with title t buttons b default button last item of b
	set {this_note, btn_pressed} to {text returned of result, button returned of result}
end if

--
-- Append or edit the log file
--
if btn_pressed is last item of b then
	set final_text to join_list({
		"Date " & field_sep & date_time,
		"Label" & field_sep & this_label,
		"Title" & field_sep & this_title,
		"URL  " & field_sep & this_url}, linefeed) & linefeed
	if this_note is not "" then
		set final_text to final_text & "Note " & field_sep & this_note & linefeed
	end if
	set final_text to final_text & rec_sep & linefeed
	--return "[DEBUG]" & return & final_text
	_IO's append_file(log_file, final_text)
else
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	edit_log(this_log_file, text_editor, should_use_shell)
end if

--display alert "Log Page" message "DEBUG: Script complete."

(************************************************************
 *	Subroutines
 ************************************************************)

-- ===== Main Functions =====

on choose_category(cur_list, cur_list_type)
	log "[debug] in choose_category(_list_, \"" & cur_list_type & "\")"

	if cur_list_type is not "all" then copy cur_list to g_previous_categories

	log "[debug] g_previous_categories: " & joinList(g_previous_categories, ", ")

	set_list_type(cur_list_type)

	set extra_items to get_extra_items(cur_list_type)
	set list_rule to extra_items's last item

	-- :TODO: Customize dialog message
	-- :TODO: Customize dialog title with dialog number

	repeat 10 times -- limit loops as a precaution
		set chosen_category to choose from list extra_items & cur_list
		if chosen_category as text is not list_rule then exit repeat
	end repeat
	if chosen_category is false then return false

	-- Call this handler recursively if another list was requested:
	if cur_list_type is in {"top", "sub"} and chosen_category as text is extra_items's first item then
		log "[debug] recurse to choose_category(g_all_categories, \"all\")"
		set chosen_category to choose_category(g_all_categories, "all")
	else if cur_list_type is "sub" and chosen_category as text is extra_items's second item then
		log "[debug] recurse to choose_category(g_top_categories, \"top\")"
		set chosen_category to choose_category(g_top_categories, "top")
	else if cur_list_type is "all" and chosen_category as text is extra_items's first item then
		if g_previous_list_type is "sub" then
			log "[debug] recurse to choose_category(g_previous_categories, \"sub\")"
			set chosen_category to choose_category(g_previous_categories, "sub")
		else if g_previous_list_type is "top" then
			log "[debug] recurse to choose_category(g_top_categories, \"top\")"
			set chosen_category to choose_category(g_top_categories, "top")
		end if
	else if cur_list_type is "top" then
		set sub_categories to get_subcategories(chosen_category)
		log "[debug] recurse to choose_category(sub_categories, \"sub\")"
		set chosen_category to choose_category(sub_categories, "sub")
	end if

	return chosen_category
end choose_category

on get_subcategories(chosen_category)
	set sub_categories to {}
	repeat with this_cat in g_all_categories
		if (this_cat as text) is (chosen_category as text)
			or (this_cat as text) starts with (chosen_category & ":") then
			set end of sub_categories to this_cat as text
		end if
	end repeat
	return sub_categories
end get_subcategories

on set_list_type(cur_type)
	-- verify that 'cur_type' is in 'p_list_types'

	set g_previous_list_type to g_list_type
	set g_list_type to cur_type
end set_list_type

on get_extra_items(list_type)
	if list_type is "top" then

		-- :TODO: Add choice to make a new category

		return {"Show full list with subcategories...", multiplyTxt(uRule, 20)}
	else if list_type is "sub" then
		return {"Show full list of categories...", "Go back to previous list...", multiplyTxt(uRule, 35)}
	else if list_type is "all" then
		return {"Go back to previous list...", multiplyTxt(uRule, 35)}
	else
		return false
	end if
end get_extra_items

on edit_log(log_file, text_editor, should_use_shell)
	(*
		TODO:MAYBE:
		Have a configuration variable in the main script to specify which text
		editor to use. Have another variable for configuring whether or not
		it's a GUI application. Then this function would use 'open' to launch a
		GUI app or script the Terminal to launch a command-line editor.
		Optionally use 'do shell script' to launch a compatible GUI app from a
		command line (so that the working directory can be changed to that of
		the file first), but I don't know if that will really be necessary
		since I have a Vim shortcut for quickly changing the working directory
		if need be.
	*)
	if should_use_shell then
		set s to text_editor & space & log_file & space & "> /dev/null 2>&1"
		do shell script s
		return
	else
		set t to script_name
		set m to ":TODO: edit_log() function: use non-shell-invoked editor."
		set b to {"Cancel"}
		display alert t message m buttons b cancel button 1 as critical
		return false
	end if
end edit_log

-- ===== Utility Functions =====

on convert_to_ascii(non_ascii_txt)
	--
	-- From 'man iconv_open':
	--
	--    When  the string "//TRANSLIT" is appended to tocode, transliteration is
	--    activated. This means that when a character cannot  be  represented  in
	--    the target character set, it can be approximated through one or several
	--    characters that look similar to the original character.
	--
	set s to "iconv -f UTF-8 -t US-ASCII//TRANSLIT <<<" & quoted form of non_ascii_txt
	do shell script s
end convert_to_ascii

on multiply_text(str, n)
	if n < 1 or str = "" then return ""
	set lst to {}
	repeat n times
		set end of lst to str
	end repeat
	return lst as string
end multiply_text

on split_text(txt, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set lst to every text item of (txt as string)
		set AppleScript's text item delimiters to old_tids
		return lst
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't split_text(): " & err_msg number err_num
	end try
end split_text

on join_list(lst, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set txt to lst as string
		set AppleScript's text item delimiters to old_tids
		return txt
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't join_list(): " & err_msg number err_num
	end try
end join_list

(************************************************************
 *	Script Objects
 ************************************************************)

script _IO

	(* == PUBLIC == *)

	on write_file(file_path, this_data)
		_write_file(file_path, this_data, string, false) -- overwrite existing file
	end write_file

	on append_file(file_path, this_data)
		_write_file(file_path, this_data, string, true) -- append new data to end of existing file
	end append_file

	on read_file(file_path)
		_read_file(file_path, string)
	end read_file

	(* == PRIVATE == *)

	on _write_file(file_path, this_data, data_class, should_append_data)
		try
			set file_path to file_path as text
			set file_handle to
				open for access file file_path with write permission
			if not should_append_data then
				set eof of the file_handle to 0
			write this_data as data_class to file_handle starting at eof
			close access file_handle
			return true
		on error err_msg number err_num
			try
				close access file file_path
			end try
			error err_msg number err_num
			return false
		end try
	end _write_file

	on _read_file(file_path, data_class)
		try
			set file_path to file_path as text
			if (get eof file file_path) is 0 then
				set file_contents to ""
			else
				set file_contents to read file file_path as data_class
			end if
			return file_contents
		on error
			return 0
		end try
	end _read_file
end script

(*
	Log Page - Log categorized web page bookmarks to a text file

	Version: @@VERSION@@
	Date:    2012-12-19
	Author:  Steve Wheeler

	Get the title, URL, current date and time, and a user-definable
	category for the frontmost Safari window and log the info to a text
	file.

	This program is free software available under the terms of a
	BSD-style (3-clause) open source license detailed below.
*)

(*
Copyright (c) 2011-2012 Steve Wheeler
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*)

(*
	TODO: Change the following local-only config before any public release.
	The log file should be: $GTD_IN/logs/urls/urls.YYYY.txt
	Or just: $GTD_IN/logs/urls.txt

	TODO: The edit_log() function is currently incomplete. It will only launch
	an editor from the shell and only if that editor is a GUI editor (in this
	case, MacVim). If I release this script publicly, I will still need to
	implement funtionality to launch a GUI editor normally (with the 'open'
	AppleScript commmand) as well as a command-line editor run in the Terminal.
*)

property script_name : "Log Page"
property script_version : "@@VERSION@@"

---------- USER CONFIGURATION ----------------------------------------
-- Default log directory to use if one is not read from config file:
--set log_dir to POSIX path of (path to application support folder from user domain) & "Jazzhead/Log Page/"
set log_dir to POSIX path of (path to desktop folder) & "Log Page/" -- :DEBUG:

-- Log file name:
set log_name to "urls.txt"

-- Default categories/labels if none are found in (or there is no)
-- URL file. Modify as desired:
set default_cats to "Development
Development:AppleScript
Development:AppleScript:Mail
Design
Productivity:GTD
Apple:Mac OS X
Apple:iOS
Career
Education
Health
Health:Diet
Health:Fitness
General"

-- Text editor to use if manually editing the log file.
--
-- This example:
--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
--	2. Switches the local working directory for just that buffer to the
--	    directory containing the file ('lcd')
--	3. Gives the MacVim window a name ('--servername').
--
set text_editor to "/opt/local/bin/mvim \"+lcd %:p:h\" --servername LOG_PAGE"

-- Should the specified editor be launched from the unix shell?
set should_use_shell to true
---------- END User Configuration ----------------------------------------

---------- ADVANCED (EXPERIMENTAL) ---------------------------------------------
-- For advanced users familiar with the command-line, override the default log
-- directory path. This probably won't be in a public release.
--
-- Grab an exported shell environment variable from a config file to use as the
-- log directory path. For this example, I have a "~/.gtdrc" config file that I
-- source from my "~/.bash_profile" file so that it exportss environment
-- variables relating to my GTD system. That makes those variables available to
-- any of the shell scripts and tools that I use. For instance, when I'm
-- editing files in Vim, I can write paths to other files using one of the
-- environment variables rather than a full or relative path and the 'gf'
-- command will work for jumping to that file. The config file looks like this:
--
--         # ~/.gtdrc
--         #
--         # File locations for GTD documents
--         #
--         export GTD_DIR=$HOME/Documents/org  # GTD root directory
--         export GTD_IN=$GTD_DIR/content      # source/input files
--         export GTD_OUT=$GTD_DIR/html        # generated HTML output files
--         export GTD_LIB=$GTD_DIR/lib         # code library files
--         export GTD_BIN=$GTD_LIB/script      # scripts
--
-- Remove (or comment out) the "(*" and "*)" comment markers below to use this
-- section. Make sure you also configure the two variables for your system.
--(*
--------------- USER CONFIGURATION ----------
-- Path to your config file:
set cfg_file to POSIX path of (path to home folder) & ".gtdrc"
-- The environment variable you're interested in:
set env_var to "GTD_IN"
--------------- END User Configuration ---------

-- Check for environment variable in config file:
set s to "egrep '\\<" & env_var & "=' " & quoted form of cfg_file &
	" > /dev/null 2>&1; echo $?"
set shell_status to (do shell script s) as integer
-- Source the config file and echo the desired variable:
if shell_status is 0 then -- (means that grep found a match)
	set s to ". " & quoted form of cfg_file & " && echo $" & env_var
	set gtd_in_dir to do shell script s
	set log_dir to gtd_in_dir & "/logs/"

	--------------------
	-- OPTIONAL: Override the Text editor to integrate with my GTD system.
	--
	-- This example:
	--	1. Opens the file using MacVim ('mvim' as installed by MacPorts)
	--	2. Switches the local working directory ('lcd') for just that buffer to
	--	    my GTD content directory ($GTD_IN after escaping potential spaces)
	--	3. Gives the MacVim window a name ('--servername').

	----- First, escape any potential spaces in the directory path:
	--
	--set gtd_in_dir to gtd_in_dir & " foo    bar" -- :DEBUG: spaces in path
	--
	set s to "echo " & quoted form of gtd_in_dir & " | sed 's/ \\{1,\\}/\\\\ /g'" -- escape spaces
	set gtd_in_dir to do shell script s
	--
	--log gtd_in_dir -- :DEBUG: need to use 'log' to see the value w/o AppleScript escapes

	----- Finally, set the editor with all of the options:
	--
	set text_editor to "/opt/local/bin/mvim \"+lcd " & gtd_in_dir & "\" --servername GTD --remote-silent"
	--
	--log text_editor -- :DEBUG:
	--return text_editor -- :DEBUG:
	--------------------
end if
---------- END Advanced --------------------------------------------------------
--*)

--return log_dir -- :DEBUG:

property uRule : data utxt2500 as Unicode text -- BOX DRAWINGS LIGHT HORIZONTAL

--
-- Get URL and title from front browser window
--
tell application "Safari"
	--activate
	try
		set this_url to URL of front document
		set this_title to name of first tab of front window whose visible is true
	on error
		return false
	end try
end tell
-- Transliterate non-ASCII characters to ASCII
set this_title to convert_to_ascii(this_title)

--return this_title -- :DEBUG:

--
-- Format the record separator between log entries
--
set rule_char to "-"
set rule_width to 80
set field_width to 7
set field_sep to " | "
set rec_sep to "" & multiply_text(rule_char, field_width - 1) &
	"+" & multiply_text(rule_char, rule_width - field_width)

--
-- Format date-time string as "YYYY-MM-DD HH:MM:SS"
--
set {year:y, month:m, day:d, hours:hh, minutes:mm, seconds:ss} to current date
set m to m as integer -- coerce month string
-- Pad numbers with leading zeros:
set tmp_list to {}
repeat with this_item in {m, d, hh, mm, ss}
	set this_item to text 2 thru -1 of (100 + this_item as text)
	set end of tmp_list to this_item
end repeat
set {m, d, hh, mm, ss} to tmp_list
-- Final string:
set date_time to join_list({y, m, d}, "-") & " " & join_list({hh, mm, ss}, ":")

-- Log file name
--set log_name to "urls." & y & ".txt"

--
-- Create log directory if it doesn't already exist. (Go ahead and use a shell
-- command since it's easy to create intermediate directories all in one step.):
--
try
	set s to "mkdir -pm700 " & quoted form of log_dir
	do shell script s
on error err_msg number err_num
	error "Can't create log directory: " & err_msg number err_num
	return "Fatal Error: Can't create log directory"
end try

set log_file_posix to "" & log_dir & log_name
set log_file to POSIX file log_file_posix

--
-- Add header to new or empty log file
--
set should_init to false
tell application "Finder"
	if (not (exists log_file)) or (_IO's read_file(log_file) is "") then
		set should_init to true
	end if
end tell

set head_sep to multiply_text("#", 80)
if should_init then
	set file_header to head_sep & "
# urls.txt - Timestamped and categorized URL archive
# ==================================================
# Optionally list some categories/labels of URLs below that might not yet
# be used. Any categories/lables listed here or in the web page records
# will be presented as a list to select from when saving new URLs.
" & head_sep & linefeed & default_cats & linefeed & head_sep & "
# END: category/label list
" & head_sep & linefeed
	tell _IO to write_file(log_file, file_header)
	set cat_txt_all to default_cats
else
	-- Read categories from file
	(*
		:TODO: Error handling.
	*)
	set cat_txt_all to text 2 thru -2 of (item 3 of split_text(_IO's read_file(log_file), head_sep))
end if

-- Parse any existing labels from the "Label" fields of the URLs file:
set s to "LANG=C sed -n 's/^Label | //p' " & quoted form of log_file_posix & " | sort | uniq"
--return s
set used_cats to do shell script s without altering line endings
-- Sort those along with the manually entered categories/labels:
set s to "echo \"" & cat_txt_all & linefeed & used_cats & "\" | egrep -v '^$' | sort | uniq"
set cat_txt_all to do shell script s without altering line endings
--return cat_txt_all -- :DEBUG:
-- Coerce the lines into a list:
set cat_list_all to paragraphs of cat_txt_all
if cat_list_all's last item is "" then set cat_list_all to cat_list_all's items 1 thru -2
--return cat_list_all -- :DEBUG:

-- Get top-level categories:
set s to "LANG=C echo \"" & cat_txt_all & "\" | sed -n 's/^\\([^:]\\{1,\\}\\).*/\\1/p' | uniq"
set cat_txt_top to do shell script s without altering line endings
--return cat_txt_top -- :DEBUG:
-- Coerce the lines into a list:
set cat_list_top to paragraphs of cat_txt_top
if cat_list_top's last item is "" then set cat_list_top to cat_list_top's items 1 thru -2
--return cat_list_top -- :DEBUG:

--
-- Prompt for title, category/subcategories and optional note for URL
--

-- The number of dialog prompts will vary depending on if a subcategory list is requested.
set prompt_count to 1 -- increment with every dialog or list prompt
set prompt_total to 4 -- increment if the subcategory list is displayed

-- Accept or modify URL title
set b to {"Cancel", "Manually Edit Log File", "Next..."}
set t to "" & script_name & ": Title (" & prompt_count & "/" & prompt_total & ")"
set m to "To log the URL for:" & return & return
	& tab & "\"" & this_title & "\"" & return & return &
	"first accept or edit the title."
display dialog m default answer this_title with title t buttons b default button last item of b
set {this_title, btn_pressed} to {text returned of result, button returned of result}
set prompt_count to prompt_count + 1
if btn_pressed is item 2 of b then
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	edit_log(this_log_file, text_editor, should_use_shell)
	return "Script ended with '" & (item 2 of b as text) & "'"
end if

-- Select an existing top-level category
set extra_items to {"Show full list with subcategories...", multiply_text(uRule, 20)}
set cat_choice to choose_top_category(cat_list_top, extra_items, prompt_count, prompt_total)
set prompt_count to prompt_count + 1
--return cat_choice

--if cat_choice is false then return false
if cat_choice is false then -- skip the subcategory list
	set cat_choice to "Please enter a category..."
else
	set prompt_threshold to 1
	if cat_choice as text is extra_items's first item then
		set cat_list_sub to cat_list_all
	else
		set extra_items to {"Show full list of existing categories...", multiply_text(uRule, 35)}
		--set cat_list_sub to {}
		copy extra_items to cat_list_sub -- use 'copy' instead of 'set' for separate lists
		-- Account for extra menu items:
		set prompt_threshold to prompt_threshold + (count of extra_items)
		-- Build subcategory list:
		repeat with this_cat in cat_list_all
			if (this_cat as text) is (cat_choice as text)
				or (this_cat as text) starts with (cat_choice & ":") then
				set end of cat_list_sub to this_cat as text
			end if
		end repeat
	end if
	--return cat_list_sub

	-- Only show a subcategory list if there is more than one item:
	if (count of cat_list_sub) is greater than prompt_threshold then
		set cat_choice to choose_subcategory(cat_list_all, cat_list_sub, extra_items, prompt_count, prompt_total)
		set prompt_count to prompt_count + 1
		set prompt_total to prompt_total + 1
	end if
end if

-- Modify selected category or enter a new category
set t to "" & script_name & ": Category (" & prompt_count & "/" & prompt_total & ")"
set m to "Please provide a category and any optional subcategories (or edit your selected category) for the URL. Example: \"Development:AppleScript:Mail\""
display dialog m default answer cat_choice with title t buttons b default button last item of b
set {this_label, btn_pressed} to {text returned of result, button returned of result}
set prompt_count to prompt_count + 1

-- Optionally add note
if btn_pressed is last item of b then
	set last item of b to "Append URL to Log File"
	set t to "" & script_name & ": Note (" & prompt_count & "/" & prompt_total & ")"
	set m to "Optionally add a short note. Just leave the field blank if you don't want to add a note."
	display dialog m default answer "" with title t buttons b default button last item of b
	set {this_note, btn_pressed} to {text returned of result, button returned of result}
end if

--
-- Append or edit the log file
--
if btn_pressed is last item of b then
	set final_text to join_list({
		"Date " & field_sep & date_time,
		"Label" & field_sep & this_label,
		"Title" & field_sep & this_title,
		"URL  " & field_sep & this_url}, linefeed) & linefeed
	if this_note is not "" then
		set final_text to final_text & "Note " & field_sep & this_note & linefeed
	end if
	set final_text to final_text & rec_sep & linefeed
	--return "[DEBUG]" & return & final_text
	_IO's append_file(log_file, final_text)
else
	if should_use_shell then
		set this_log_file to quoted form of log_file_posix
	else
		set this_log_file to log_file
	end if
	edit_log(this_log_file, text_editor, should_use_shell)
end if

--display alert "Log Page" message "DEBUG: Script complete."

(************************************************************
 *	Subroutines
 ************************************************************)

-- ===== Main Functions =====

on choose_top_category(cat_list_top, extra_items, prompt_count, prompt_total)
	--log "[debug] in choose_top_category()"
	set t to "" & script_name & ": Category (" & prompt_count & "/" & prompt_total & ")"
	set m to "Please select a top-level category for the URL you want to log. Next you will be able to select subcategories."
	set cat_choice to choose from list extra_items & cat_list_top with title t with prompt m OK button name "Next..."
	-- If the list separator was selected instead of a list item, then recursively prompt again:
	if cat_choice as text is extra_items's last item then
		set cat_choice to choose_top_category(cat_list_top, extra_items, prompt_count, prompt_total)
	end if
	return cat_choice
end choose_top_category

on choose_subcategory(cat_list_all, cat_list_sub, extra_items, prompt_count, prompt_total)
	--log "[debug] in choose_subcategory()"

	-- Select an existing category
	set t to "" & script_name & ": Category (" & prompt_count & "/" & prompt_total & ")"
	set m to "Please select a category or subcategory for the URL you want to log. You will have a chance to edit your choice (to add a new category or subcategory)."
	--set cat_choice to choose from list cat_list_all with title t with prompt m OK button name "Next..."
	set cat_choice to choose from list cat_list_sub with title t with prompt m OK button name "Next..."
	--if cat_choice is false then return false
	if cat_choice is false then set cat_choice to "Please enter a category..."

	if cat_choice as text is extra_items's last item then
		--log "[debug] choose_subcategory(): choice is extra_items's last item"
		-- If list separator was selected instead of a list item, then recursively prompt again:
		set cat_choice to choose_subcategory(cat_list_all, cat_list_sub, extra_items, prompt_count, prompt_total)
	else if cat_choice as text is extra_items's first item then
		--log "[debug] choose_subcategory(): choice is extra_items's first item"
		-- If full list was selected, then recursively prompt using that list (note that the full list is used for both list arguments):
		set cat_choice to choose_subcategory(cat_list_all, cat_list_all, extra_items, prompt_count, prompt_total)
	end if
	return cat_choice
end choose_subcategory

on edit_log(log_file, text_editor, should_use_shell)
	(*
		TODO:MAYBE:
		Have a configuration variable in the main script to specify which text
		editor to use. Have another variable for configuring whether or not
		it's a GUI application. Then this function would use 'open' to launch a
		GUI app or script the Terminal to launch a command-line editor.
		Optionally use 'do shell script' to launch a compatible GUI app from a
		command line (so that the working directory can be changed to that of
		the file first), but I don't know if that will really be necessary
		since I have a Vim shortcut for quickly changing the working directory
		if need be.
	*)
	if should_use_shell then
		set s to text_editor & space & log_file & space & "> /dev/null 2>&1"
		do shell script s
		return
	else
		set t to script_name
		set m to ":TODO: edit_log() function: use non-shell-invoked editor."
		set b to {"Cancel"}
		display alert t message m buttons b cancel button 1 as critical
		return false
	end if
end edit_log

-- ===== Utility Functions =====

on convert_to_ascii(non_ascii_txt)
	--
	-- From 'man iconv_open':
	--
	--    When  the string "//TRANSLIT" is appended to tocode, transliteration is
	--    activated. This means that when a character cannot  be  represented  in
	--    the target character set, it can be approximated through one or several
	--    characters that look similar to the original character.
	--
	set s to "iconv -f UTF-8 -t US-ASCII//TRANSLIT <<<" & quoted form of non_ascii_txt
	do shell script s
end convert_to_ascii

on multiply_text(str, n)
	if n < 1 or str = "" then return ""
	set lst to {}
	repeat n times
		set end of lst to str
	end repeat
	return lst as string
end multiply_text

on split_text(txt, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set lst to every text item of (txt as string)
		set AppleScript's text item delimiters to old_tids
		return lst
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't split_text(): " & err_msg number err_num
	end try
end split_text

on join_list(lst, delim)
	set old_tids to AppleScript's text item delimiters
	try
		set AppleScript's text item delimiters to (delim as string)
		set txt to lst as string
		set AppleScript's text item delimiters to old_tids
		return txt
	on error err_msg number err_num
		set AppleScript's text item delimiters to old_tids
		error "Can't join_list(): " & err_msg number err_num
	end try
end join_list

(************************************************************
 *	Script Objects
 ************************************************************)

script _IO

	(* == PUBLIC == *)

	on write_file(file_path, this_data)
		_write_file(file_path, this_data, string, false) -- overwrite existing file
	end write_file

	on append_file(file_path, this_data)
		_write_file(file_path, this_data, string, true) -- append new data to end of existing file
	end append_file

	on read_file(file_path)
		_read_file(file_path, string)
	end read_file

	(* == PRIVATE == *)

	on _write_file(file_path, this_data, data_class, should_append_data)
		try
			set file_path to file_path as text
			set file_handle to
				open for access file file_path with write permission
			if not should_append_data then
				set eof of the file_handle to 0
			write this_data as data_class to file_handle starting at eof
			close access file_handle
			return true
		on error err_msg number err_num
			try
				close access file file_path
			end try
			error err_msg number err_num
			return false
		end try
	end _write_file

	on _read_file(file_path, data_class)
		try
			set file_path to file_path as text
			if (get eof file file_path) is 0 then
				set file_contents to ""
			else
				set file_contents to read file file_path as data_class
			end if
			return file_contents
		on error
			return 0
		end try
	end _read_file
end script


Fix recall script to set background more reliably

#!/usr/bin/env osascript

set journal to "~/ia/Journal/Journal.md"
tell application "Terminal"
	set lineCount to (do shell script "wc -l < " & journal) as integer
	set randomLine to (random number from 1 to lineCount) as integer
	activate
	do script "vim -c ':Goyo' -c ':set bg=light' " & journal & ":" & randomLine
	set current settings of first window to settings set "Solarized Light"
	tell application "System Events" to keystroke "f" using {command down, control down}
end tell

#!/usr/bin/env osascript

set journal to "~/ia/Journal/Journal.md"
tell application "Terminal"
	set lineCount to (do shell script "wc -l < " & journal) as integer
	set randomLine to (random number from 1 to lineCount) as integer
	activate
	do script "vim -c ':call ToggleBackground()' -c ':Goyo' -c '" & randomLine & "' " & journal
	tell application "System Events" to keystroke "f" using {command down, control down}
end tell


Aflred checkin

Aflred checkin

FasdUAS 1.101.10
l
i
I
.aevtoappnull****
o0argv
r
I

.sysodsct****scpt

l
b

b
mL
	tell application "System Events"
		set _app to item 1 of (every process whose frontmost is true)
	end tell

	tell application "System Events"
		tell _app
			tell
n
4

cobj
m
o0argv

m
				if (it exists) then perform action "AXPress"
			end tell
		end tell
	end tell
	o0scpt
.aevtoappnull****0scpt
.aevtoappnull****0argv0argv

cobj
.sysodsct****scpt0scptk/%%jE !"!#$#%%&
misccura

pcap&''Script Editor

mbar

mbri$((File

menE"))File

menI ** Open Dictionary &

actT++AXPressascrޭ

FasdUAS 1.101.10
l
i
I
.aevtoappnull****
o0argv
r
I

.sysodsct****scpt

l
b

b
mL
	tell application "System Events"
		set _app to item 1 of (every process whose frontmost is true)
	end tell

	tell application "System Events"
		tell _app
			tell
n
4

cobj
m
o0argv

m
				if (it exists) then perform action "AXPress"
			end tell
		end tell
	end tell
	o0scpt
.aevtoappnull****0scpt
.aevtoappnull****0argv0argv

cobj
.sysodsct****scpt0scptk/%%jE !"!#$#%&%'(')*)+,+--.
misccura

pcap.//iTerm2

mbar

mbri,00Edit

menE*11Edit

menI(22*Marks and Annotations

menE&33*Marks and Annotations

menI$44Alerts

menE"55Alerts

menI 66$Alert on Next Mark

actT77AXPressascrޭ


align the Keynote (5) scripting to the PowerPoint variants

--
--  slideshow -- Observe and Control Slideshow Applications
--  Copyright (c) 2014 Ralf S. Engelschall <http://engelschall.com>
--
--  This Source Code Form is subject to the terms of the Mozilla Public
--  License (MPL), version 2.0. If a copy of the MPL was not distributed
--  with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
--  File:     connector-osx-kn.js
--  Purpose:  connector engine for Apple Keynote 5 under Mac OS X
--  Language: AppleScript
--

--  utility function
on filterText(this_text, allowed_chars)
   set new_text to ""
   repeat with this_char in this_text
       set x to the offset of this_char in allowed_chars
       if x is not 0 then
           set new_text to (new_text & this_char) as string
       end if
   end repeat
   return new_text
end filterText

--  utility function
on replaceText(this_text, search_string, replacement_string)
   set AppleScript's text item delimiters to the search_string
   set the item_list to every text item of this_text
   set AppleScript's text item delimiters to the replacement_string
   set this_text to the item_list as string
   set AppleScript's text item delimiters to ""
   return this_text
end replace_chars

--  utility function
on asciiCharset()
    set charset to " \"'!#$%()*+,-./:;<=>?@[\\]^_{|}~"
    set charset to (charset & "0123456789")
    set charset to (charset & "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    set charset to (charset & "abcdefghijklmnopqrstuvwxyz")
    return charset
end asciiCharset

--  get application state
on knGetState()
    set state to "closed"
    tell application "System Events"
        set is_running to (exists (some process whose name is "Keynote"))
    end tell
    if is_running then
       try
            set state to "running"
            tell application "Keynote"
                if playing then
                    set state to "viewing"
                else if (get count of slides of front document) > 0 then
                    set state to "editing"
                end if
            end tell
        on error errMsg
        end try
    end if
    return state
end knGetState

--  get Apple Keynote current slide
on knGetCurSlide()
    try
        tell application "Keynote"
            if playing then
                return (get slide number of current slide of front document)
            else
                return (get slide number of current slide of front document)
            end if
        end tell
    on error errMsg
        return 0
    end try
end knGetCurSlide

--  get Apple Keynote maximum slide
on knGetMaxSlide()
    try
        tell application "Keynote"
            return (get count of slides of front document)
        end tell
    on error errMsg
        return 0
    end try
end knGetMaxSlide

--  the STATE command
on cmdSTATE()
    set state to knGetState()
    if state is "closed" then
        set position to 0
        set slides to 0
    else
        set position to knGetCurSlide()
        set slides to knGetMaxSlide()
    end if
    return ("{ \"response\": { " & ¬
        "\"state\": \"" & state & "\", " & ¬
        "\"position\": " & position & ", " & ¬
        "\"slides\": " & slides & " " & ¬
    "} }")
end cmdSTATE

--  the INFO command
on cmdINFO()
    set output to ""
    if knGetMaxSlide() is 0 then
        error "still no active presentation"
    end if
    tell application "Keynote"
        set thePresentation to front document
        set theTitles to ""
        set theNotes to ""
        set slideCount to (get count of slides of thePresentation)
        repeat with slideNum from 1 to slideCount
            set theSlide to slide slideNum of thePresentation

            set theTitle to (title of theSlide) as string
            if theTitles is not "" then
                set theTitles to (theTitles & ", ")
            end if
            set theTitles to (theTitles & "\"" & (my replaceText(theTitle, "\"", "\\\"")) & "\"")

            set theNote to (notes of theSlide) as string
            set theNote to (my filterText(theNote, my asciiCharset()))
            if theNotes is not "" then
                set theNotes to (theNotes & ", ")
            end if
            set theNotes to (theNotes & "\"" & (my replaceText(theNote, "\"", "\\\"")) & "\"")

        end repeat
        set theTitles to ("[ " & theTitles & " ]")
        set theNotes to ("[ " & theNotes & " ]")
        set output to ("{ \"response\": { \"titles\": " & theTitles & ", \"notes\": " & theNotes & " } }")
    end tell
    return output
end cmdINFO

--  the control commands
on cmdCTRL(command, arg)
    set state to knGetState()
    if command is "BOOT" then
        if state is not "closed" then
            error "application already running"
        end if
        tell application "Keynote"
            activate
        end tell
    else if command is "QUIT" then
        if state is "closed" then
            error "application already closed"
        end if
        tell application "Keynote"
            quit
        end tell
    else if command is "OPEN" then
        if state is "editing" or state is "viewing" then
            error "active presentation already existing"
        end if
        tell application "Keynote"
            tell application "Finder" to set thePath to ¬
                POSIX file (POSIX path of (container of (path to me) as string) & (arg)) as alias
            open thePath
        end tell
    else if command is "CLOSE" then
        if state is "closed" or state is "running" then
            error "still no active presentation"
        end if
        tell application "Keynote"
            close front document
        end tell
    else if command is "START" then
        if state is "closed" or state is "running" then
            error "still no active presentation"
        end if
        if state is "viewing" then
            error "active presentation already viewing"
        end if
        tell application "Keynote"
            start from (slide 1 of front document)
        end tell
    else if command is "STOP" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            stop slideshow (slideshow 1)
        end tell
    else if command is "PAUSE" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            activate
            tell application "System Events" to keystroke "b"
        end tell
    else if command is "RESUME" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            activate
            tell application "System Events" to keystroke "b"
        end tell
    else if command is "FIRST" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            show (item 1 of slides of slideshow 1)
        end tell
    else if command is "LAST" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            show (item (get count of slides of slideshow 1) of slides of slideshow 1)
        end tell
    else if command is "GOTO" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            show (item (arg as integer) of slides of slideshow 1)
        end tell
    else if command is "PREV" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            show previous
        end tell
    else if command is "NEXT" then
        if state is not "viewing" then
            error "no active slideshow"
        end if
        tell application "Keynote"
            show next
        end tell
    end if
    return "{ \"result\": \"OK\" }"
end cmdCTRL

--  main procedure
on run argv
    set cmd to item 1 of argv
    set arg to ""
    if count of argv is 2 then
        set arg to item 2 of argv
    end if
    try
        if cmd is "STAT" then
            set output to cmdSTATE()
        else if cmd is "INFO" then
            set output to cmdINFO()
        else if cmd is "BOOT" ¬
            or cmd is "QUIT" ¬
            or cmd is "OPEN" ¬
            or cmd is "CLOSE" ¬
            or cmd is "START" ¬
            or cmd is "STOP" ¬
            or cmd is "PAUSE" ¬
            or cmd is "RESUME" ¬
            or cmd is "FIRST" ¬
            or cmd is "LAST" ¬
            or cmd is "GOTO" ¬
            or cmd is "PREV" ¬
            or cmd is "NEXT" then
            set output to cmdCTRL(cmd, arg)
        else
            set output to "{ \"error\": \"invalid command\" }"
        end if
    on error errMsg
        set output to ("{ \"error\": \"" & errMsg & "\" }")
    end try
    copy output to stdout
end run

--
--  slideshow -- Observe and Control Slideshow Applications
--  Copyright (c) 2014 Ralf S. Engelschall <http://engelschall.com>
--
--  This Source Code Form is subject to the terms of the Mozilla Public
--  License (MPL), version 2.0. If a copy of the MPL was not distributed
--  with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
--
--  File:     connector-osx-keynote.js
--  Purpose:  connector engine for Apple Keynote under Mac OS X
--  Language: AppleScript
--

--  utility function
on offsetOf(theString, subString)
    if theString does not contain subString then
        return 0
    end if
    set stringCharacterCount    to (get count of characters in theString)
    set substringCharacterCount to (get count of characters in subString)
    set lastCharacter to (stringCharacterCount - substringCharacterCount + 1)
    repeat with n from 1 to lastCharacter
        set m to (n + substringCharacterCount) - 1
        set currentSubstring to (get characters n thru m of theString) as string
        if currentSubstring is subString then
            return n
        end if
    end repeat
    return 0
end offsetOf

--  get Apple Keynote current slide
on knGetCurSlide()
    try
        tell application "Keynote"
            return (get slide number of current slide of front document)
        end tell
    on error errMsg
        return 0
    end try
end knGetCurSlide

--  get Apple Keynote maximum slide
on knGetMaxSlide()
    try
        tell application "Keynote"
            return (get count of slides of front document)
        end tell
    on error errMsg
        return 0
    end try
end knGetMaxSlide

--  get Apple Keynote current slide
on knGetSlideInfo()
    try
        tell application "Keynote"
            set thePresentation to front document
            set slideCount to (get count of slides of thePresentation)
            set slideInfo to "[ "
            repeat with slideNum from 1 to slideCount
                set theSlide to slide slideNum of thePresentation
                set theNote to ""
                repeat with theChars in (presenter notes of theSlide)
                    set theText to ("" & (theChars as string) & "")
                    set offStart to my offsetOf(theText, "TK<")
                    if offStart is not 0 then
                        set offStart to (offStart + 3)
                        set tmp to (get characters offStart thru ((get count of characters in theText)) of theText) as string
                        set offEnd to my offsetOf(tmp, ">")
                        if offEnd is not 0 and offENd is not 1 then
                            set theNote to (get characters offStart thru (offStart + (offEnd - 1) - 1) of theText) as string
                            exit repeat
                        end if
                    end if
                end repeat
                if slideInfo is "[ " then
                    set slideInfo to (slideInfo & "\"" & theNote & "\"")
                else
                    set slideInfo to (slideInfo & ", \"" & theNote & "\"")
                end if
            end repeat
            set slideInfo to (slideInfo & " ]")
            return slideInfo
        end tell
    on error errMsg
        return "[]"
    end try
end knGetSlideInfo

--  get Apple Keynote slide titles
on knGetSlideTitles()
    -- try
        tell application "Keynote"
            set thePresentation to front document
            set slideCount to (get count of slides of thePresentation)
            set slideTitles to "[ "
            repeat with slideNum from 1 to slideCount
                set theSlide to slide slideNum of thePresentation
                set theTitle to object text of default title item of theSlide
                if slideTitles is "[ " then
                    set slideTitles to (slideTitles & "\"" & theTitle & "\"")
                else
                    set slideTitles to (slideTitles & ", \"" & theTitle & "\"")
                end if
            end repeat
            set slideTitles to (slideTitles & " ]")
            return slideTitles
        end tell
    -- on error errMsg
        return 0
    -- end try
end knGetCurSlide

--  control slide show
on knSlideShowControl(command, arg)
    tell application "Keynote"
        if command is "START" then
            start (front document) from (slide 1 of front document)
        else if command is "NEXT" then
            show next
        else if command is "GOTO" then
            show slide (arg as integer) of front document
        else if command is "PREV" then
            show previous
        else if command is "STOP" then
            stop front document
        end if
    end tell
end knSlideShowControl

--  main procedure
on run argv
    set cmd to item 1 of argv
    set arg to ""
    if count of argv is 2 then
        set arg to item 2 of argv
    end if
    if cmd is "STAT" then
        set curSlide to knGetCurSlide()
        set maxSlide to knGetMaxSlide()
        set output to ("{ \"curSlide\": " & curSlide & ", \"maxSlide\": " & maxSlide & " }")
    else if cmd is "INFO" then
        set slideInfo to knGetSlideInfo()
        set output to ("{ \"slideInfo\": " & slideInfo & " }")
    else if cmd is "TITLES" then
        set theTitles to knGetSlideTitles()
        set output to ("{ \"titles\": " & theTitles & " }")
    else if cmd is "START" or cmd is "NEXT" or cmd is "GOTO" or cmd is "PREV" or cmd is "STOP" then
        my knSlideShowControl(cmd, arg)
        set output to "{}"
    end if
    copy output to stdout
end run


revert reminders change

revert reminders change

tell application "Reminders"
	activate
	set theAccount to name of first account
	tell account theAccount
		set theList to name of first list
		set myReminder to (make new reminder at list theList with properties {name:"{popclip text}"})
	end tell
end tell

on sortList(unsortedList)
	set oldDelimiters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to (ASCII character 10)
	set sortedList to paragraphs of (do shell script "echo " & quoted form of (unsortedList as string) & "| sort  -d -f")
	set AppleScript's text item delimiters to oldDelimiters
	return sortedList
end sortList

-- sort the list
tell application "Reminders"
	activate
	get the name of every account
	set theAccount to name of first account
	tell account theAccount
		set allLists to the name of every list
	end tell
end tell
set sortedList to sortList(allLists)
set theList to first item of sortedList

tell application "Reminders"
	tell account theAccount to set myReminder to (make new reminder at list theList with properties {name:"{popclip text}"})
end tell


Modified the poll script to work with iTunes 4.7 and tracks that don't have artwork.

FasdUAS 1.101.10
k
l
Q

O
k

r

m
fsck
o0returnvaluereturnValue
r
1

pPlS
o0astateaState
Z
=
o0astateaState
m
ePlSkPSP
k
r!"
!1

pTrk
"o0atrackaTrack #$
#r$%&
%n"'(
'1 "

pnam
(1

pPla
&o0alibraryaLibrary$)*
)r%*+,
+n%(-.
-1&(

pDID
.o%&0atrackaTrack
,o0atrackidaTrackID*/0
/r+012
1n+.34
31,.

pnam
4o+,0atrackaTrack
2o0
atrackname
aTrackName056
5r1678
7n149:
9124

pTim
:o120atrackaTrack
8o0atracklengthaTracklength6;<
;r7@=>
=n7<?@
?18<

pAlb
@o780atrackaTrack
>o0atrackalbumaTrackAlbum<AB
ArAJCD
CnAFEF
E1BF

pArt
FoAB0atrackaTrack
Do0atrackartistaTrackArtistBGH
GrKTIJ
InKPKL
K1LP

pAnt
LoKL0atrackaTrack
Jo&0atrackcompilationaTrackCompilationHMN
MrU^OP
OnUZQR
Q1VZ

pRte
RoUV0atrackaTrack
Po0atrackratingaTrackRatingNST
Sr_fUV
Um_b

null
Vo0
atrackartwork
aTrackArtworkTWX
WZgYZ
Y@gp[\
[Ign]
.corecnte********
]mgj

cArt
\mno
Zrs^_
^ns}`a
`1y}

pPCT
ansybc
b4tyd

cArt
dmwx
cost0atrackaTrack
_o0
atrackartwork
aTrackArtworkXe
erfg
fJhhij
io0atrackidaTrackIDjkl
ko0
atrackname
aTrackNamelmn
mo0atracklengthaTracklengthnop
oo0atrackartistaTrackArtistpqr
qo0atrackalbumaTrackAlbumrst
so&0atrackcompilationaTrackCompilationtuv
uo0atrackratingaTrackRatingvwx
wo0
atrackartwork
aTrackArtworkxy
yo0alibraryaLibrary
go0returnvaluereturnValuez{
z=|}
|o0astateaState
}m
ePlSkPSp{~
~r
mpaused
o0returnvaluereturnValue
r
m
stopped
o0returnvaluereturnValue
mnull
iTunes.app0L7 $)4(K @hookalisLMacintosh HDFwH+
iTunes.app@T	 ApplicationsF$Macintosh HD:Applications:iTunes.app
iTunes.appMacintosh HDApplications/iTunes.app/
	R
.ascrerr ********

r
m
fsck
o0returnvaluereturnValue
l
L
o0returnvaluereturnValue
l
.aevtoappnull****0returnvaluereturnValue0astateaState0atrackaTrack0alibraryaLibrary0atrackidaTrackID0
atrackname
aTrackName0atracklengthaTracklength0atrackalbumaTrackAlbum0atrackartistaTrackArtist&0atrackcompilationaTrackCompilation0atrackratingaTrackRating0
atrackartwork
aTrackArtwork
.aevtoappnull****
k$~}|{z0returnvaluereturnValue

pPlS0astateaState
ePlSkPSP

pTrk0atrackaTrack

pPla

pnam0alibraryaLibrary

pDID0atrackidaTrackID0
atrackname
aTrackName

pTim0atracklengthaTracklength

pAlb0atrackalbumaTrackAlbum

pArt0atrackartistaTrackArtist

pAnt&0atrackcompilationaTrackCompilation

pRte0atrackratingaTrackRating

null0
atrackartwork
aTrackArtwork

cArt
.corecnte********
~
pPCT}
|ePlSkPSp{zEO*,EO*,EO*,,EO,EO,EO,EOa,E`Oa,E`Oa,E`Oa,E`OaE`Oajkak/a,E`YhO_____avEYa
aEYa EUWX!"a#EOyy		xwvuxH(Random Drinking Song3:32
wboovfalsv<
u
nullParty Shuffle
ePlSkPSPtsrqponml
n
cSrcm#
lkfrmID
q
cUsPpE
okfrmID
t
cFlTs4.
rkfrmID  H
boovfals<

nullascr
ޭ

FasdUAS 1.101.10
k
l
Q

O
k

r

m
fsck
o0returnvaluereturnValue
r
1

pPlS
o0astateaState
Z
=
o0astateaState
m
ePlSkPSP
k
r!"
!1

pTrk
"o0atrackaTrack #$
#r$%&
%n"'(
'1 "

pnam
(1

pPla
&o0alibraryaLibrary$)*
)r%*+,
+n%(-.
-1&(

pDID
.o%&0atrackaTrack
,o0atrackidaTrackID*/0
/r+012
1n+.34
31,.

pnam
4o+,0atrackaTrack
2o0
atrackname
aTrackName056
5r1678
7n149:
9124

pTim
:o120atrackaTrack
8o0atracklengthaTracklength6;<
;r7@=>
=n7<?@
?18<

pAlb
@o780atrackaTrack
>o0atrackalbumaTrackAlbum<AB
ArAJCD
CnAFEF
E1BF

pArt
FoAB0atrackaTrack
Do0atrackartistaTrackArtistBGH
GrKTIJ
InKPKL
K1LP

pAnt
LoKL0atrackaTrack
Jo&0atrackcompilationaTrackCompilationHMN
MrU^OP
OnUZQR
Q1VZ

pRte
RoUV0atrackaTrack
Po0atrackratingaTrackRatingNST
Sr_fUV
Um_b

null
Vo0
atrackartwork
aTrackArtworkTWX
WZgYZ
Y@gs[\
[ngq]^
]1mq

pKnd
^ngm_`
_4hma

cArt
amkl
`ogh0atrackaTrack
\mqr
Zrvbc
bnvde
d1|

pPCT
env|fg
f4w|h

cArt
hmz{
govw0atrackaTrack
co0
atrackartwork
aTrackArtworkXi
irjk
jJllmn
mo0atrackidaTrackIDnop
oo0
atrackname
aTrackNamepqr
qo0atracklengthaTracklengthrst
so0atrackartistaTrackArtisttuv
uo0atrackalbumaTrackAlbumvwx
wo&0atrackcompilationaTrackCompilationxyz
yo0atrackratingaTrackRatingz{|
{o0
atrackartwork
aTrackArtwork|}
}o0alibraryaLibrary
ko0returnvaluereturnValue~
~=
o0astateaState
m
ePlSkPSp
r
mpaused
o0returnvaluereturnValue
r
m
stopped
o0returnvaluereturnValue
mnull
iTunes.app07 $)( @hookalis8ShireOH+
iTunes.appxͼG	 ApplicationsOShire:Applications:iTunes.app
iTunes.appShireApplications/iTunes.app/
	R
.ascrerr ********

r
m
fsck
o0returnvaluereturnValue
l
L
o0returnvaluereturnValue
l
.aevtoappnull****
.aevtoappnull****
k$0returnvaluereturnValue

pPlS0astateaState
ePlSkPSP

pTrk0atrackaTrack

pPla

pnam0alibraryaLibrary

pDID0atrackidaTrackID0
atrackname
aTrackName

pTim0atracklengthaTracklength

pAlb0atrackalbumaTrackAlbum

pArt0atrackartistaTrackArtist

pAnt&0atrackcompilationaTrackCompilation

pRte0atrackratingaTrackRating

null0
atrackartwork
aTrackArtwork

cArt

pKnd

pPCT
ePlSkPSpEO*,EO*,EO*,,EO,EO,EO,EOa,E`Oa,E`Oa,E`Oa,E`OaE`Oak/a,jak/a,E`YhO_____avEYa
aEYa EUWX!"a#EOascr
ޭ


Part 2 of the fix for #114 (GrowlTunes hang on dead pictd): Only accept data from artworks that are not QuickDraw pictures.

FasdUAS 1.101.10
k
lr
Qr

Oe
kd

r

mfsck
o0returnvaluereturnValue
r
n
1

pnam
1

pPla
o0alibraryaLibrary
r
1

pTrk
o0atrackaTrack
r !
 m

null
!o0
atrackartwork
aTrackArtwork"#
"r"$%
$l &
&n '(
'2

cArt
(o0atrackaTrack
%o0allartworksallArtworks#)*
)Z#]+,
+@#*-.
-l#(/
/I#(0
.corecnte********
0o#$0allartworksallArtworks
.m()
,Y-Y123
1Z;T45
4>;C67
6n;A89
81?A

pFmt
9n;?:;
:4<?<

cArt
<o=>0i
;o;<0atrackaTrack
7mAB

PICT
5kFP==>?
>rFN@A
@nFLBC
B1JL

pRaw
CnFJDE
D4GJF

cArt
FoHI0i
EoFG0atrackaTrack
Ao0
atrackartwork
aTrackArtwork?G
GSOP0i
2m01
3l16H
HI16I
.corecnte********
Io120allartworksallArtworks*J
Jr^dKL
KJ^bMMNO
No^_0
atrackartwork
aTrackArtworkOP
Po_`0alibraryaLibrary
Lo0returnvaluereturnValue
mQQhookalisLSnow-Leopardƺ8H+
iTunes.appuƀ*	 ApplicationsƻƁM$Snow-Leopard:Applications:iTunes.app
iTunes.appSnow-LeopardApplications/iTunes.app/
	R
.ascrerr ********

rmrRS
RmmpTTUUfsck
So0returnvaluereturnValueVW
VlsuX
XLsuY
Yost0returnvaluereturnValueWZ
Zl[\[
.aevtoappnull****\]^_
.aevtoappnull****
]ku``aaV^0i_QT0returnvaluereturnValue

pPla

pnam0alibraryaLibrary

pTrk0atrackaTrack

null0
atrackartwork
aTrackArtwork

cArt0allartworksallArtworks
.corecnte********

pFmt

PICT

pRawvg_EO*,,EO*,EOEO-EOjk1+kjkh/,/,EOYh[OYYhOlvEUWXaEOascrޭ

FasdUAS 1.101.10
k
lH
QH

O=
k<

r

mfsck
o0returnvaluereturnValue
r
n
1

pnam
1

pPla
o0alibraryaLibrary
r
1

pTrk
o0atrackaTrack
r !
 m

null
!o0
atrackartwork
aTrackArtwork"#
"Z5$%
$@&&'
&l$(
(I$)
.corecnte********
)n *+
*m

cArt
+o0atrackaTrack
'm$%
%r)1,-
,n)/./
.1-/

pRaw
/n)-01
04*-2

cArt
2m+,
1o)*0atrackaTrack
-o0
atrackartwork
aTrackArtwork#3
3r6<45
4J6:6678
7o670
atrackartwork
aTrackArtwork89
9o780alibraryaLibrary
5o0returnvaluereturnValue
m::hookalisLSnow-Leopardƺ8H+
iTunes.appuƀ*	 ApplicationsƻƁM$Snow-Leopard:Applications:iTunes.app
iTunes.appSnow-LeopardApplications/iTunes.app/
	R
.ascrerr ********

rEH;<
;mEF==>>fsck
<o0returnvaluereturnValue?@
?lIKA
ALIKB
BoIJ0returnvaluereturnValue@C
ClDED
.aevtoappnull****EFGH
.aevtoappnull****
FkKIIJJ?GH:=0returnvaluereturnValue

pPla

pnam0alibraryaLibrary

pTrk0atrackaTrack

null0
atrackartwork
aTrackArtwork

cArt
.corecnte********

pRawL?7EO*,,EO*,EOEO,jk
k/,EYhOlvEUW
X
EOascrޭ


small optimization

small optimization

property kExplicitlyProcessedOptions : {"--human-dir ", "--machine-dir ", "--output-dir ", "--log-command"}

tell application "Xcode"
	if not (exists active project document) then
		error "No active project. Please open an Xcode project and re-run the script."
	try
		my updateProjectXmod(project of active project document)
	on error errMsg
		my logger("Xmod.scpt exception: " & errMsg)
	end try
end tell

on updateProjectXmod(_project)
	tell application "Xcode"
		-- Iterate over every .xcdatamodel in the project.
		set modelList to every file reference of _project whose file kind is "wrapper.xcdatamodel" and comments contains "xmod"
		repeat with modelItr in modelList
			my updateModel(_project, modelItr, comments of modelItr)
		end repeat
	end tell
end updateProjectXmod

on updateModel(_project, modelItr, theComments)
	tell application "Xcode"

				set modelInfo to my getModelInfo(full path of modelItr, theComments)
				set humanGroupRef to null
				set machineGroupRef to null

				-- Figure out the model's parent group.
				-- Unversioned models are simple files so their group is the answer.
				-- Version models are bundles, which are represented as pseudo-groups in Xcode. You can't ask a pseudo-group for its parent group,
				-- so we discover it by querying project-wide all groups that contain the pseudo-group and using the first result.
				set modelItemRef to item 1 of (every item reference of _project whose full path is (full path of modelItr))
				set modelGroupRef to group of modelItemRef
				if isBundle of modelInfo then
					set modelGroupRef to item 1 of (every group of _project whose groups contains modelGroupRef)
				end if

				-- look for a group in the project that has the full path of the human manageable files
				set groupRefs to (every group of _project whose full path is (humanDirPath of modelInfo))
				-- if we don't find one, create it in the same group as the model file
				if (count of groupRefs) = 0 then
					tell modelGroupRef
						-- if we have different human and machine paths, then append '_human' to the model name for the group name
						if (humanDirPath of modelInfo) is not equal to (machineDirPath of modelInfo) then
							make new group with properties {full path:(humanDirPath of modelInfo), name:(name of modelInfo & "_human")}
						else
							make new group with properties {full path:(humanDirPath of modelInfo), name:(name of modelInfo)}
						end if
						set groupRefs to (every group of _project whose full path is (humanDirPath of modelInfo))
					end tell
				end if
				set humanGroupRef to item 1 of groupRefs

				-- if we have different human and machine paths, then look for a group for the machine files
				if (humanDirPath of modelInfo) is not equal to (machineDirPath of modelInfo) then
					set groupRefs to (every group of _project whose full path is (machineDirPath of modelInfo))
					-- if we don't find one, create it in the same group as the model file... named with '_machine' appended to the model name
					if (count of groupRefs) = 0 then
						tell modelGroupRef
							make new group with properties {full path:(machineDirPath of modelInfo), name:(name of modelInfo & "_machine")}
						end tell
						set groupRefs to (every group of _project whose full path is (machineDirPath of modelInfo))
					end if
					set machineGroupRef to item 1 of groupRefs
				end if

				-- Create the do shell script string and append any custom per model options to it, skipping the ones we've already parsed out
				set logCommand to false
				set theScript to "/usr/bin/mogenerator --model '" & full path of modelItr & "' --human-dir '" & (humanDirPath of modelInfo) & "' --machine-dir '" & (machineDirPath of modelInfo) & "'"
				set theParagraphs to every paragraph of theComments
				repeat with theParagraph in theParagraphs
					if theParagraph starts with "--" then
						set isExplicitlyProcessedOption to false
						repeat with explicitlyProcessedOption in kExplicitlyProcessedOptions
							if theParagraph starts with explicitlyProcessedOption then set isExplicitlyProcessedOption to true
						end repeat
						if not isExplicitlyProcessedOption then
							set theScript to theScript & " " & the text of theParagraph
						end if
						if theParagraph starts with "--log-command" then
							set logCommand to true
						end if
					end if
				end repeat

				--	Meat.
				if logCommand then
					my logger(theScript)
				end if
				do shell script theScript

				my addFilesFromPathToGroup(_project, modelItr, humanGroupRef)
				if machineGroupRef is not null then
					my addFilesFromPathToGroup(_project, modelItr, machineGroupRef)
				end if
	end tell
end updateModel

on everyTargetWithBuildFilePath(_project, _buildFilePath)
	set theResult to {}
	tell application "Xcode"
		repeat with targetItr in (every target of _project)
			repeat with buildFileItr in build files of targetItr
				if full path of file reference of buildFileItr is _buildFilePath then set theResult to theResult & {(targetItr as anything)}
			end repeat
		end repeat
	end tell
	return theResult
end everyTargetWithBuildFilePath

on getModelInfo(modelFileUnixPath, theComments)
	set modelFilePosixRef to POSIX file modelFileUnixPath
	set modelFileAlias to modelFilePosixRef as alias
	set theOutputDir to ""
	set theHumanDir to ""
	set theMachineDir to ""

	-- check for any directory path options specified in the commments
	set theParagraphs to every paragraph of theComments
	repeat with theParagraph in theParagraphs
		set paragraphLength to (length of theParagraph)
		if (offset of "--human-dir " in theParagraph) = 1 then
			set remainderOffset to (length of "--human-dir ") + 1
			if remainderOffset > paragraphLength then
				set theHumanDir to ""
			else
				set theHumanDir to text remainderOffset thru -1 of theParagraph
			end if
		else if (offset of "--machine-dir " in theParagraph) = 1 then
			set remainderOffset to (length of "--machine-dir ") + 1
			if remainderOffset > paragraphLength then
				set theMachineDir to ""
			else
				set theMachineDir to text remainderOffset thru -1 of theParagraph
			end if
		else if (offset of "--output-dir " in theParagraph) = 1 then
			set remainderOffset to (length of "--output-dir ") + 1
			if remainderOffset > paragraphLength then
				set theOutputDir to ""
			else
				set theOutputDir to text remainderOffset thru -1 of theParagraph
			end if
		end if
	end repeat

	-- remove any leading/trailing spaces or tabs from the paths
	if (length of theHumanDir) > 0 then
		repeat while character 1 of theHumanDir = tab or character 1 of theHumanDir = space
			set theHumanDir to text 2 thru -1 of theHumanDir
		end repeat
		repeat while last character of theHumanDir = tab or last character of theHumanDir = space
			set theHumanDir to text 1 thru -2 of theHumanDir
		end repeat
	end if

	if (length of theMachineDir) > 0 then
		repeat while character 1 of theMachineDir = tab or character 1 of theMachineDir = space
			set theMachineDir to text 2 thru -1 of theMachineDir
		end repeat
		repeat while last character of theMachineDir = tab or last character of theMachineDir = space
			set theMachineDir to text 1 thru -2 of theMachineDir
		end repeat
	end if

	if (length of theOutputDir) > 0 then
		repeat while character 1 of theOutputDir = tab or character 1 of theOutputDir = space
			set theOutputDir to text 2 thru -1 of theOutputDir
		end repeat
		repeat while last character of theOutputDir = tab or last character of theOutputDir = space
			set theOutputDir to text 1 thru -2 of theOutputDir
		end repeat
	end if

	-- get actual the model info and modify value for theOutputDir if necessary
	tell application "Finder"
		set modelFileFolder to folder of modelFileAlias

		set isModelBundle to name of modelFileFolder ends with ".xcdatamodeld"
		if isModelBundle then
			set modelFileAlias to (folder of modelFileAlias) as alias -- it's a bundle, go up one folder
			set modelFileFolder to folder of modelFileAlias
		end if

		set modelFileName to name of modelFileAlias
		if isModelBundle then
			set extensionLength to length of ".xcdatamodeld"
		else
			set extensionLength to length of ".xcdatamodel"
		end if

		set modelName to text 1 thru -(extensionLength + 1) of modelFileName -- pull off the extension

		-- if we didn't find any directory specifiers in the comments, then make theOutputDir the folder containg the model file
		if ((length of theOutputDir) = 0) and ((length of theHumanDir) = 0) and ((length of theMachineDir) = 0) then
			if not (exists folder modelName of modelFileFolder) then
				make folder at modelFileFolder with properties {name:modelName}
			set modelSrcFolder to folder modelName of modelFileFolder
			set theOutputDir to text 1 thru -2 of (POSIX path of (modelSrcFolder as alias))
			-- otherwise, if theOutputDir isn't a full path itself already, set theOutputDir to a full path relative to the model file
		else if theOutputDir does not start with "/" then
			set modelFolderPath to text 1 thru -2 of (POSIX path of (modelFileFolder as alias))
			if (length of theOutputDir) = 0 then
				set theOutputDir to modelFolderPath
			else
				copy modelFolderPath & "/" & theOutputDir to theOutputDir
			end if
		end if

	end tell

	-- if theHumanDir is empty, use theOutputDir value instead, if theHumanDir not empty and it isn't a full path, then treat it as a relative path to theOutputDir
	if (length of theHumanDir) = 0 then
		set theHumanDir to theOutputDir
	else if theHumanDir does not start with "/" then
		copy theOutputDir & "/" & theHumanDir to theHumanDir
	end if

	-- if theMachineDir is empty, use theOutputDir value instead, if theMachineDir not empty and it isn't a full path, then treat it as a relative path to theOutputDir
	if (length of theMachineDir) = 0 then
		set theMachineDir to theOutputDir
	else if theMachineDir does not start with "/" then
		copy theOutputDir & "/" & theMachineDir to theMachineDir
	end if

	-- ensure the directories exist
	do shell script "mkdir -p '" & theHumanDir & "'"
	do shell script "mkdir -p '" & theMachineDir & "'"

	-- the following should resolve any symlinks, '..', or '~' in the paths
	tell application "System Events"
		set dirAlias to POSIX file theHumanDir as alias
		set theHumanDir to (POSIX path of (dirAlias as alias))
		set dirAlias to POSIX file theMachineDir as alias
		set theMachineDir to (POSIX path of (dirAlias as alias))
	end tell

	return {name:modelName, isBundle:isModelBundle, machineDirPath:theMachineDir, humanDirPath:theHumanDir}

end getModelInfo

on logger(msg)
	do shell script "logger '" & msg & "'"
end logger

on addFilesFromPathToGroup(_project, modelItr, groupRef)
	tell application "Xcode"
		-- get the full path of the directory that the groupRef represents
		set groupPath to full path of groupRef

		--	Build a list of source files in the group's directory
		tell application "System Events"
			set modelSrcDirAlias to POSIX file groupPath as alias
			set fileList to (every file of modelSrcDirAlias whose (name ends with ".m" or name ends with ".mm" or name ends with ".h"))
			set pathList to {}
			repeat with fileItem in fileList
				set pathList to pathList & POSIX path of fileItem
			end repeat
		end tell

		--	Add the source files to the group and the model's targets, but only if they don't exist already
		set targetList to null
		repeat with pathItr in pathList
			tell groupRef
				set fileRefs to (every file reference of _project whose full path is pathItr)
				if (count of fileRefs) = 0 then
					set modelSrcFileRef to make new file reference with properties {full path:pathItr, name:name of (info for POSIX file pathItr)}
					if targetList is null then
						set targetList to my everyTargetWithBuildFilePath(_project, full path of modelItr)
					end if
					repeat with targetIndex from 1 to (count of targetList)
						set targetItr to item targetIndex of targetList
						add modelSrcFileRef to targetItr
					end repeat
				end if
			end tell
		end repeat
	end tell
end addFilesFromPathToGroup

property kExplicitlyProcessedOptions : {"--human-dir ", "--machine-dir ", "--output-dir ", "--log-command"}

tell application "Xcode"
	if not (exists active project document) then
		error "No active project. Please open an Xcode project and re-run the script."
	try
		my updateProjectXmod(project of active project document)
	on error errMsg
		my logger("Xmod.scpt exception: " & errMsg)
	end try
end tell

on updateProjectXmod(_project)
	tell application "Xcode"
		-- Iterate over every .xcdatamodel in the project.
		set modelList to every file reference of _project whose file kind is "wrapper.xcdatamodel"
		repeat with modelItr in modelList
			set theComments to the comments of modelItr
			if theComments contains "xmod" then
				my updateModel(_projet, modelItr, theComments)
			end if
		end repeat
	end tell
end updateProjectXmod

on updateModel(_project, modelItr, theComments)
	tell application "Xcode"

				set modelInfo to my getModelInfo(full path of modelItr, theComments)
				set humanGroupRef to null
				set machineGroupRef to null

				-- Figure out the model's parent group.
				-- Unversioned models are simple files so their group is the answer.
				-- Version models are bundles, which are represented as pseudo-groups in Xcode. You can't ask a pseudo-group for its parent group,
				-- so we discover it by querying project-wide all groups that contain the pseudo-group and using the first result.
				set modelItemRef to item 1 of (every item reference of _project whose full path is (full path of modelItr))
				set modelGroupRef to group of modelItemRef
				if isBundle of modelInfo then
					set modelGroupRef to item 1 of (every group of _project whose groups contains modelGroupRef)
				end if

				-- look for a group in the project that has the full path of the human manageable files
				set groupRefs to (every group of _project whose full path is (humanDirPath of modelInfo))
				-- if we don't find one, create it in the same group as the model file
				if (count of groupRefs) = 0 then
					tell modelGroupRef
						-- if we have different human and machine paths, then append '_human' to the model name for the group name
						if (humanDirPath of modelInfo) is not equal to (machineDirPath of modelInfo) then
							make new group with properties {full path:(humanDirPath of modelInfo), name:(name of modelInfo & "_human")}
						else
							make new group with properties {full path:(humanDirPath of modelInfo), name:(name of modelInfo)}
						end if
						set groupRefs to (every group of _project whose full path is (humanDirPath of modelInfo))
					end tell
				end if
				set humanGroupRef to item 1 of groupRefs

				-- if we have different human and machine paths, then look for a group for the machine files
				if (humanDirPath of modelInfo) is not equal to (machineDirPath of modelInfo) then
					set groupRefs to (every group of _project whose full path is (machineDirPath of modelInfo))
					-- if we don't find one, create it in the same group as the model file... named with '_machine' appended to the model name
					if (count of groupRefs) = 0 then
						tell modelGroupRef
							make new group with properties {full path:(machineDirPath of modelInfo), name:(name of modelInfo & "_machine")}
						end tell
						set groupRefs to (every group of _project whose full path is (machineDirPath of modelInfo))
					end if
					set machineGroupRef to item 1 of groupRefs
				end if

				-- Create the do shell script string and append any custom per model options to it, skipping the ones we've already parsed out
				set logCommand to false
				set theScript to "/usr/bin/mogenerator --model '" & full path of modelItr & "' --human-dir '" & (humanDirPath of modelInfo) & "' --machine-dir '" & (machineDirPath of modelInfo) & "'"
				set theParagraphs to every paragraph of theComments
				repeat with theParagraph in theParagraphs
					if theParagraph starts with "--" then
						set isExplicitlyProcessedOption to false
						repeat with explicitlyProcessedOption in kExplicitlyProcessedOptions
							if theParagraph starts with explicitlyProcessedOption then set isExplicitlyProcessedOption to true
						end repeat
						if not isExplicitlyProcessedOption then
							set theScript to theScript & " " & the text of theParagraph
						end if
						if theParagraph starts with "--log-command" then
							set logCommand to true
						end if
					end if
				end repeat

				--	Meat.
				if logCommand then
					my logger(theScript)
				end if
				do shell script theScript

				my addFilesFromPathToGroup(_project, modelItr, humanGroupRef)
				if machineGroupRef is not null then
					my addFilesFromPathToGroup(_project, modelItr, machineGroupRef)
				end if
	end tell
end updateModel

on everyTargetWithBuildFilePath(_project, _buildFilePath)
	set theResult to {}
	tell application "Xcode"
		repeat with targetItr in (every target of _project)
			repeat with buildFileItr in build files of targetItr
				if full path of file reference of buildFileItr is _buildFilePath then set theResult to theResult & {(targetItr as anything)}
			end repeat
		end repeat
	end tell
	return theResult
end everyTargetWithBuildFilePath

on getModelInfo(modelFileUnixPath, theComments)
	set modelFilePosixRef to POSIX file modelFileUnixPath
	set modelFileAlias to modelFilePosixRef as alias
	set theOutputDir to ""
	set theHumanDir to ""
	set theMachineDir to ""

	-- check for any directory path options specified in the commments
	set theParagraphs to every paragraph of theComments
	repeat with theParagraph in theParagraphs
		set paragraphLength to (length of theParagraph)
		if (offset of "--human-dir " in theParagraph) = 1 then
			set remainderOffset to (length of "--human-dir ") + 1
			if remainderOffset > paragraphLength then
				set theHumanDir to ""
			else
				set theHumanDir to text remainderOffset thru -1 of theParagraph
			end if
		else if (offset of "--machine-dir " in theParagraph) = 1 then
			set remainderOffset to (length of "--machine-dir ") + 1
			if remainderOffset > paragraphLength then
				set theMachineDir to ""
			else
				set theMachineDir to text remainderOffset thru -1 of theParagraph
			end if
		else if (offset of "--output-dir " in theParagraph) = 1 then
			set remainderOffset to (length of "--output-dir ") + 1
			if remainderOffset > paragraphLength then
				set theOutputDir to ""
			else
				set theOutputDir to text remainderOffset thru -1 of theParagraph
			end if
		end if
	end repeat

	-- remove any leading/trailing spaces or tabs from the paths
	if (length of theHumanDir) > 0 then
		repeat while character 1 of theHumanDir = tab or character 1 of theHumanDir = space
			set theHumanDir to text 2 thru -1 of theHumanDir
		end repeat
		repeat while last character of theHumanDir = tab or last character of theHumanDir = space
			set theHumanDir to text 1 thru -2 of theHumanDir
		end repeat
	end if

	if (length of theMachineDir) > 0 then
		repeat while character 1 of theMachineDir = tab or character 1 of theMachineDir = space
			set theMachineDir to text 2 thru -1 of theMachineDir
		end repeat
		repeat while last character of theMachineDir = tab or last character of theMachineDir = space
			set theMachineDir to text 1 thru -2 of theMachineDir
		end repeat
	end if

	if (length of theOutputDir) > 0 then
		repeat while character 1 of theOutputDir = tab or character 1 of theOutputDir = space
			set theOutputDir to text 2 thru -1 of theOutputDir
		end repeat
		repeat while last character of theOutputDir = tab or last character of theOutputDir = space
			set theOutputDir to text 1 thru -2 of theOutputDir
		end repeat
	end if

	-- get actual the model info and modify value for theOutputDir if necessary
	tell application "Finder"
		set modelFileFolder to folder of modelFileAlias

		set isModelBundle to name of modelFileFolder ends with ".xcdatamodeld"
		if isModelBundle then
			set modelFileAlias to (folder of modelFileAlias) as alias -- it's a bundle, go up one folder
			set modelFileFolder to folder of modelFileAlias
		end if

		set modelFileName to name of modelFileAlias
		if isModelBundle then
			set extensionLength to length of ".xcdatamodeld"
		else
			set extensionLength to length of ".xcdatamodel"
		end if

		set modelName to text 1 thru -(extensionLength + 1) of modelFileName -- pull off the extension

		-- if we didn't find any directory specifiers in the comments, then make theOutputDir the folder containg the model file
		if ((length of theOutputDir) = 0) and ((length of theHumanDir) = 0) and ((length of theMachineDir) = 0) then
			if not (exists folder modelName of modelFileFolder) then
				make folder at modelFileFolder with properties {name:modelName}
			set modelSrcFolder to folder modelName of modelFileFolder
			set theOutputDir to text 1 thru -2 of (POSIX path of (modelSrcFolder as alias))
			-- otherwise, if theOutputDir isn't a full path itself already, set theOutputDir to a full path relative to the model file
		else if theOutputDir does not start with "/" then
			set modelFolderPath to text 1 thru -2 of (POSIX path of (modelFileFolder as alias))
			if (length of theOutputDir) = 0 then
				set theOutputDir to modelFolderPath
			else
				copy modelFolderPath & "/" & theOutputDir to theOutputDir
			end if
		end if

	end tell

	-- if theHumanDir is empty, use theOutputDir value instead, if theHumanDir not empty and it isn't a full path, then treat it as a relative path to theOutputDir
	if (length of theHumanDir) = 0 then
		set theHumanDir to theOutputDir
	else if theHumanDir does not start with "/" then
		copy theOutputDir & "/" & theHumanDir to theHumanDir
	end if

	-- if theMachineDir is empty, use theOutputDir value instead, if theMachineDir not empty and it isn't a full path, then treat it as a relative path to theOutputDir
	if (length of theMachineDir) = 0 then
		set theMachineDir to theOutputDir
	else if theMachineDir does not start with "/" then
		copy theOutputDir & "/" & theMachineDir to theMachineDir
	end if

	-- ensure the directories exist
	do shell script "mkdir -p '" & theHumanDir & "'"
	do shell script "mkdir -p '" & theMachineDir & "'"

	-- the following should resolve any symlinks, '..', or '~' in the paths
	tell application "System Events"
		set dirAlias to POSIX file theHumanDir as alias
		set theHumanDir to (POSIX path of (dirAlias as alias))
		set dirAlias to POSIX file theMachineDir as alias
		set theMachineDir to (POSIX path of (dirAlias as alias))
	end tell

	return {name:modelName, isBundle:isModelBundle, machineDirPath:theMachineDir, humanDirPath:theHumanDir}

end getModelInfo

on logger(msg)
	do shell script "logger '" & msg & "'"
end logger

on addFilesFromPathToGroup(_project, modelItr, groupRef)
	tell application "Xcode"
		-- get the full path of the directory that the groupRef represents
		set groupPath to full path of groupRef

		--	Build a list of source files in the group's directory
		tell application "System Events"
			set modelSrcDirAlias to POSIX file groupPath as alias
			set fileList to (every file of modelSrcDirAlias whose (name ends with ".m" or name ends with ".mm" or name ends with ".h"))
			set pathList to {}
			repeat with fileItem in fileList
				set pathList to pathList & POSIX path of fileItem
			end repeat
		end tell

		--	Add the source files to the group and the model's targets, but only if they don't exist already
		set targetList to null
		repeat with pathItr in pathList
			tell groupRef
				set fileRefs to (every file reference of _project whose full path is pathItr)
				if (count of fileRefs) = 0 then
					set modelSrcFileRef to make new file reference with properties {full path:pathItr, name:name of (info for POSIX file pathItr)}
					if targetList is null then
						set targetList to my everyTargetWithBuildFilePath(_project, full path of modelItr)
					end if
					repeat with targetIndex from 1 to (count of targetList)
						set targetItr to item targetIndex of targetList
						add modelSrcFileRef to targetItr
					end repeat
				end if
			end tell
		end repeat
	end tell
end addFilesFromPathToGroup


Update Scripts/reset-simulator.applescript

add ending new line

tell application "iPhone Simulator"
	activate
end tell

tell application "System Events"
	tell process "iPhone Simulator"
		tell menu bar 1
			tell menu bar item "iOs Simulator"
				tell menu "iOs Simulator"
					click menu item "Reset Content and Settings…"
				end tell
			end tell
		end tell
		tell window 1
			click button "Reset"
		end tell
	end tell
end tell


Break up slide creation

Break up slide creation

on newPresentation(themeName)
	tell application "Keynote"
		activate
		-- FIXME: Make this selectable?
		set targetWidth to 1024 -- 1440 <-- higher resolution
		set targetHeight to 768 -- 1080 <-- higher resolution

		set props to {document theme:theme (themeName as string), width:targetWidth, height:targetHeight}

		set thisDocument to make new document with properties props
		return name of thisDocument
	end tell
end newPresentation

on savePresentation(docname, posixPath)
	tell application "Keynote"
		save document named docname in POSIX file posixPath
	end tell
end savePresentation

on createSlide(docname, masterSlideName)
	try
		tell application "Keynote"
			tell document named docname
				set thisSlide to make new slide with properties {base slide:master slide masterSlideName}
			end tell
	  return thisSlide
		end tell
	on error
		return 0
	end try
end createSlide

on finalize(docname)
	tell application "Keynote"
		tell document named docname to delete slide 1
	end tell
end finalize

on themeMasters(docname)
	--	  tell application "Keynote" to get the name of every master slide of thisDocument
	tell application "Keynote"
		set names to the name of every master slide of document named docname
		return names
	end tell
end themeMasters

on addImage(docname, filepath)
  (*
	FIXME: Use slide ID to adress slide instead of "current slide"
  *)
  tell application "Keynote"
	tell document named docname
		tell the current slide

			-- TO REPLACE A PLACEHOLDER OR EXISTING IMAGE:
			set thisPlaceholderImageItem to image 1
			-- change the value of the “file name” property of the image to be an HFS file reference to the replacement image file
		set macPath to POSIX file filepath as Unicode text
			set file name of thisPlaceholderImageItem to ¬
				alias macPath

		end tell
	end tell
  end tell
end addImage

on addTitle(docname, thisSlideTitle)
  (*
	FIXME: Use slide ID to adress slide instead of "current slide"
  *)
	tell application "Keynote"
		tell document named docname
			tell the current slide
				if title showing is true then
					set the object text of the default title item to thisSlideTitle
				end if
			end tell
		end tell
	end tell
end addTitle

on addBody(docname, thisSlideBody)
  (*
	FIXME: Use slide ID to adress slide instead of "current slide"
  *)
	tell application "Keynote"
		tell document named docname
			tell the current slide
				if body showing is true then
					set the object text of the default body item to thisSlideBody
				end if
			end tell
		end tell
	end tell
end addTitle

on newPresentation(themeName)
	tell application "Keynote"
		activate
		-- FIXME: Make this selectable?
		set targetWidth to 1024 -- 1440 <-- higher resolution
		set targetHeight to 768 -- 1080 <-- higher resolution

		set props to {document theme:theme (themeName as string), width:targetWidth, height:targetHeight}

		set thisDocument to make new document with properties props
		return name of thisDocument
	end tell
end newPresentation

on savePresentation(docname, posixPath)
	tell application "Keynote"
		save document named docname in POSIX file posixPath
	end tell
end savePresentation

on createSlide(docname, masterSlideName, thisSlideTitle, thisSlideBody)
	try
		tell application "Keynote"
			tell document named docname
				set thisSlide to make new slide with properties {base slide:master slide masterSlideName}
				tell thisSlide
					-- set the transition properties to {transition effect:dissolve, transition duration:defaultTransitionDuration, transition delay:defaultTransitionDelay, automatic transition:defaultAutomaticTransition}
					if title showing is true then
						set the object text of the default title item to thisSlideTitle
					end if
					if body showing is true then
						set the object text of the default body item to thisSlideBody
					end if
				end tell
			end tell
	  return thisSlide
		end tell
	on error
		return 0
	end try
end createSlide

on finalize(docname)
	tell application "Keynote"
		tell document named docname to delete slide 1
	end tell
end finalize

on themeMasters(docname)
	--	  tell application "Keynote" to get the name of every master slide of thisDocument
	tell application "Keynote"
		set names to the name of every master slide of document named docname
		return names
	end tell
end themeMasters

on addImage(docname, filepath)
  (*
	FIXME: Use slide ID to adress slide instead of "current slide"
  *)
  tell application "Keynote"
	tell document named docname
		tell the current slide

			-- TO REPLACE A PLACEHOLDER OR EXISTING IMAGE:
			set thisPlaceholderImageItem to image 1
			-- change the value of the “file name” property of the image to be an HFS file reference to the replacement image file
		set macPath to POSIX file filepath as Unicode text
			set file name of thisPlaceholderImageItem to ¬
				alias macPath

		end tell
	end tell
  end tell
end addImage