├── assets ├── no-as.scpt ├── as.scpt ├── as2.scpt ├── js.scpt ├── asdbg.scpt ├── js.javascript ├── osa-logo.png ├── as.applescript ├── as2.applescript ├── js-hdr.javascript ├── as-hdr.applescript ├── asdbg.applescript ├── asdbg-hdr.applescript └── usage.txt ├── .ok ├── LICENSE ├── README.md ├── osagetlang.sh ├── setup.sh ├── osagitfilter.sh └── test.sh /assets/no-as.scpt: -------------------------------------------------------------------------------- 1 | This is a text-file, NOT an AppleScript file. 2 | -------------------------------------------------------------------------------- /assets/as.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doekman/osagitfilter/HEAD/assets/as.scpt -------------------------------------------------------------------------------- /assets/as2.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doekman/osagitfilter/HEAD/assets/as2.scpt -------------------------------------------------------------------------------- /assets/js.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doekman/osagitfilter/HEAD/assets/js.scpt -------------------------------------------------------------------------------- /assets/asdbg.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doekman/osagitfilter/HEAD/assets/asdbg.scpt -------------------------------------------------------------------------------- /assets/js.javascript: -------------------------------------------------------------------------------- 1 | function add(a,b) 2 | { 3 | return a+b; 4 | } 5 | 6 | add(2,3); 7 | -------------------------------------------------------------------------------- /assets/osa-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doekman/osagitfilter/HEAD/assets/osa-logo.png -------------------------------------------------------------------------------- /assets/as.applescript: -------------------------------------------------------------------------------- 1 | on run (arg) 2 | -- Some unicode test in Dutch 3 | log "Skiën ⛷ kost veel €" 4 | end run 5 | -------------------------------------------------------------------------------- /assets/as2.applescript: -------------------------------------------------------------------------------- 1 | #@osa-lang:AppleScript 2 | on run (arg) 3 | log "Doesn't end with an empty line" 4 | end run -------------------------------------------------------------------------------- /assets/js-hdr.javascript: -------------------------------------------------------------------------------- 1 | //@osa-lang:JavaScript 2 | function add(a,b) 3 | { 4 | return a+b; 5 | } 6 | 7 | add(2,3); 8 | -------------------------------------------------------------------------------- /assets/as-hdr.applescript: -------------------------------------------------------------------------------- 1 | #@osa-lang:AppleScript 2 | on run (arg) 3 | -- Some unicode test in Dutch 4 | log "Skiën ⛷ kost veel €" 5 | end run 6 | -------------------------------------------------------------------------------- /.ok: -------------------------------------------------------------------------------- 1 | # osagitfilter tests 2 | ./test.sh --list # List all test cases 3 | ./test.sh "$@" # Provide test-nr to only run that one 4 | ./test.sh --group-run # Run most extensive tests 5 | 6 | # 7 | ./setup.sh # Show installation status 8 | -------------------------------------------------------------------------------- /assets/asdbg.applescript: -------------------------------------------------------------------------------- 1 | use AppleScript version "2.4" 2 | use framework "Foundation" 3 | use framework "AppKit" 4 | use framework "OSAKit" 5 | use scripting additions 6 | 7 | «event asDBDBid» "CF0107E8-55C4-42E0-80BF-2C20409AF636" 8 | tell me to «event asDBLine» {1, 0, {}} 9 | set availableLanguages to current application's OSALanguage's availableLanguages() 10 | tell me to «event asDBLine» {2, 0, {}} 11 | set langList to {} 12 | tell me to «event asDBLine» {3, 0, {}} 13 | repeat with lang in availableLanguages 14 | tell me to «event asDBLine» {4, 0, {"lang", {lang, 0} as «class bst»}} 15 | set end of langList to lang's |name|() as text 16 | end repeat 17 | tell me to «event asDBLine» {5, 0, {}} 18 | langList 19 | -------------------------------------------------------------------------------- /assets/asdbg-hdr.applescript: -------------------------------------------------------------------------------- 1 | #@osa-lang:AppleScript Debugger 2 | use AppleScript version "2.4" 3 | use framework "Foundation" 4 | use framework "AppKit" 5 | use framework "OSAKit" 6 | use scripting additions 7 | 8 | «event asDBDBid» "CF0107E8-55C4-42E0-80BF-2C20409AF636" 9 | tell me to «event asDBLine» {1, 0, {}} 10 | set availableLanguages to current application's OSALanguage's availableLanguages() 11 | tell me to «event asDBLine» {2, 0, {}} 12 | set langList to {} 13 | tell me to «event asDBLine» {3, 0, {}} 14 | repeat with lang in availableLanguages 15 | tell me to «event asDBLine» {4, 0, {"lang", {lang, 0} as «class bst»}} 16 | set end of langList to lang's |name|() as text 17 | end repeat 18 | tell me to «event asDBLine» {5, 0, {}} 19 | langList 20 | -------------------------------------------------------------------------------- /assets/usage.txt: -------------------------------------------------------------------------------- 1 | usage: osagitfilter command [options] [FILE] 2 | 3 | command (use one): 4 | clean Translates OSA script to text, to be put into git 5 | smudge Translates text stored in git to OSA script 6 | 7 | arguments (all optional): 8 | -f, --forbidden Provide forbidden languages. '-' for empty list, defaults to 'AppleScript Debugger' 9 | -n, --no-header Don't write a OSA-lang header for the default language (AppleScript) 10 | -d, --debug Write debug info to stderr 11 | -l, --log Write debug info to '/Users/doekman/Library/Logs/Catsdeep//osagitfilter.log' 12 | -h, -?, --help Show this help message and exit 13 | -v, --version Show program's version number and exit 14 | FILE Filename of current stream. Useful for debugging/logging only 15 | 16 | This script translates input from stdin to stdout only. The options '--forbidden' and '--no-header' 17 | are only used with the 'clean' command. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2020 Doeke Zanstra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | osagitfilter 2 | ============ 3 | 4 | Filter to put [OSA][] languages into a `git`-repository. So you can put your `.scpt`-file (AppleScript, JavaScript) into your git-repository, and get full textual diff support. 5 | 6 | 7 | Installation 8 | ------------ 9 | 10 | Either clone [this repository](https://github.com/doekman/osagitfilter), or download the [latest release](https://github.com/doekman/osagitfilter/releases/latest) and unzip it to a folder where you want to install it. 11 | 12 | Configure the filter by running the following command: 13 | 14 | ./setup.sh configure 15 | 16 | For every reporistory you want to use it, put the line `*.scpt filter=osa` in the [gitattributes][] of your repository. Do this by running the command below in the root of your repository: 17 | 18 | echo "*.scpt filter=osa" >> .gitattributes 19 | 20 | 21 | Extra's 22 | ------- 23 | 24 | If you want to add your own git configuration, use the following configure command: 25 | 26 | ./setup.sh configure --no-git 27 | 28 | To reset the configuration, run this command: 29 | 30 | ./setup.sh reset 31 | 32 | If you have trouble with the script, switch on logging with: 33 | 34 | ./setup.sh configure --git-log 35 | 36 | Logging can be found in `~/Library/Logs/Catsdeep/osagitfilter.log` and can be easy inspected with `Console.app`. 37 | 38 | Some git-clients, like GitHub Desktop, can be quite chatty so log files grow quite fast. With the following command you can create a new log file, while preserving the old ones: 39 | 40 | ./setup.sh rotate 41 | 42 | Default, it prevents from accidently committing AppleScript files with Debugging Mode (from [AppleScript Debugger][asdbg]) switched on. Run `osagitfilter --help` to see more options. 43 | 44 | I've setup a [demo repository][demo] with different `.scpt`-files. 45 | 46 | 47 | Problems 48 | -------- 49 | 50 | If you want to (re-)apply osagitfilter to a repository (for example, if you already added your binary `.scpt` file to git, but want to use osagitfilter), execute the following statement: 51 | 52 | git add --renormalize . 53 | 54 | 55 | Credits 56 | ------- 57 | 58 | Based on [this answer by Daniel Trebbien][so-ascr-in-git] on stackoverflow and help from [guys on the Script Debugger Forum][asdbg-forum]. 59 | 60 | 61 | 62 | [OSA]: https://developer.apple.com/library/content/documentation/AppleScript/Conceptual/AppleScriptX/Concepts/osa.html "Apple's Open Scripting Architecture" 63 | [asdbg]: http://latenightsw.com 64 | [so-ascr-in-git]: https://stackoverflow.com/a/14425009/56 65 | [asdbg-forum]: http://forum.latenightsw.com/t/cross-play-between-script-debugger-and-script-editor/834/5 66 | [gitconfig]: https://git-scm.com/docs/git-config 67 | [gitattributes]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes 68 | [demo]: https://github.com/doekman/osagitfilter-demo 69 | -------------------------------------------------------------------------------- /osagetlang.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Unofficial Bash Strict Mode 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | function show_usage { 7 | echo "usage: $(basename "$0" .sh) [--options] path/to/script-file.scpt" 8 | echo "options:" 9 | echo " -o, --osa-kit Determine file-type via OSAKit (default)" 10 | echo " -H, --header Determine file-type via headers (quicker)" 11 | echo " -? -h, --help Show this usage" 12 | if (( $# > 0 )); then 13 | echo 14 | echo "ERROR: $1" 15 | exit 1 16 | fi 17 | } 18 | 19 | function abs_file_path { 20 | if [[ -f "$1" ]]; then 21 | pushd "$(dirname "$1")" >/dev/null 22 | echo "$(pwd)/$(basename "$1")" 23 | popd >/dev/null 24 | else 25 | echo "File '$1' does not exist!" >&2 26 | return 127 27 | fi 28 | } 29 | 30 | function osagetlang_via_osakit { 31 | local script_posix_path="$1" 32 | osascript - "$script_posix_path" <<'END_OF_APPLESCRIPT' 33 | use AppleScript version "2.4" 34 | use scripting additions 35 | use framework "Foundation" 36 | use framework "OSAKit" 37 | 38 | on run {scpt_path} 39 | local source_nsurl, the_script, osa_lang 40 | # Make sure it's an absolute path (since 'read' below gives the following warning on stderr otherwise) 41 | # ...CFURLGetFSRef was passed an URL which has no scheme (the URL will not work with other CFURL routines) 42 | set source_nsurl to current application's |NSURL|'s fileURLWithPath:scpt_path 43 | try 44 | set the_script to current application's OSAScript's alloc()'s initWithContentsOfURL:source_nsurl |error|:(missing value) 45 | on error number -10000 46 | # macOS Mojave, when running in shell context, blocks loading Script Debugger component. Assume it's AppleScript Debugger: 47 | # see: https://forum.latenightsw.com/t/mojave-changes-make-osadecompile-fail-on-applescript-debugger-files/1854 48 | # OSAScript also writes an error message to stderr. 49 | return "AppleScript Debugger" 50 | end try 51 | if the_script is missing value then 52 | return "AppleScript Debugger" # also macOS Mojave, but when ran in GUI context 53 | end 54 | return the_script's language()'s |name| as text 55 | end run 56 | END_OF_APPLESCRIPT 57 | } 58 | 59 | function osagetlang_via_header { 60 | local script_posix_path="$1" 61 | local max_header_length=16 62 | local script_content 63 | script_content=$(head -c "$max_header_length" "$script_posix_path") 64 | if [[ ${script_content:0:16} == "FasdUAS 1.101.10" ]]; then 65 | echo "AppleScript" 66 | elif [[ ${script_content:0:8} == "MarY3.00" ]]; then 67 | echo "AppleScript Debugger" 68 | elif [[ ${script_content:0:16} == "JsOsaDAS1.001.00" ]]; then 69 | echo "JavaScript" 70 | else 71 | echo "-" #Output for non-OSA files 72 | fi 73 | } 74 | 75 | method=osakit 76 | script_path= 77 | while (( $# > 0 )) ; do 78 | case "$1" in 79 | -o | --osa-kit) method=osakit;; 80 | -H | --header) method=header;; 81 | "-?" | -h | --help) show_usage; exit 0;; 82 | *) if (( $# > 1 )); then show_usage "path/to/script-file.scpt argument should be last"; fi; script_path="$1";break;; 83 | esac 84 | shift 85 | done 86 | 87 | if [[ -z $script_path ]]; then 88 | show_usage "You should at least provide a path to a script file" 89 | fi 90 | 91 | script_path=$(abs_file_path "$script_path") 92 | if [[ $method == osakit ]]; then 93 | osalang=$(osagetlang_via_osakit "$script_path" 2> /dev/null) 94 | if [[ "$osalang" == "AppleScript" ]]; then 95 | # osakit thinks even a JPEG is AppleScript, so check header 96 | osalang=$(osagetlang_via_header "$script_path") 97 | fi 98 | else 99 | osalang=$(osagetlang_via_header "$script_path") 100 | fi 101 | echo "$osalang" 102 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Unofficial Bash Strict Mode 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | function abspath { 7 | if [[ -d "$1" ]]; then 8 | pushd "$1" >/dev/null 9 | pwd 10 | popd >/dev/null 11 | elif [[ -e $1 ]]; then 12 | pushd "$(dirname "$1")" >/dev/null 13 | echo "$(pwd)/$(basename "$1")" 14 | popd >/dev/null 15 | else 16 | echo "$1" does not exist! >&2 17 | return 127 18 | fi 19 | } 20 | 21 | function root_check { 22 | if [[ $EUID -ne 0 ]]; then 23 | >&2 echo "Please run script with 'sudo $(basename "$0") $*', exiting..." 24 | exit 1 25 | fi 26 | } 27 | 28 | function os_check { 29 | VERSION=$(sw_vers | grep "ProductVersion" | sed -E 's/[^.0-9]+//g') 30 | #From "10.11.12" (sw_vers) get "11" 31 | #From "12.0.1" (sw_vers) get "12" 32 | MAJOR=$(echo "$VERSION" | awk -F . '{ if ($1 >= 11) print $1; else print $2; }') 33 | MIN_MAJOR_VER=10 34 | MIN_OS_NAME=Yosemite 35 | if [[ $MAJOR -lt $MIN_MAJOR_VER ]]; then 36 | >&2 echo "osagitfilter needs at least macOS 10.$MIN_MAJOR_VER ($MIN_OS_NAME) but you have macOS ${VERSION}, exiting..." 37 | exit 1 38 | elif [[ -n $1 ]]; then 39 | if [[ $VERBOSE == 1 ]]; then 40 | echo "osagitfilter needs at least macOS 10.$MIN_MAJOR_VER.* ($MIN_OS_NAME) but you have macOS ${VERSION}, so that's OK..." 41 | fi 42 | fi 43 | } 44 | 45 | SCRIPT_NAME=$0 46 | BASE_DIR=$(dirname "$(abspath "$0")") 47 | INSTALL_INTO=/usr/local/bin 48 | COMMANDS=$'osagetlang\tosagitfilter' 49 | LOG_PATH=~/Library/Logs/Catsdeep/ 50 | VERBOSE=0 51 | GIT_OPTION="" 52 | C=unknown 53 | 54 | while [[ $# -gt 0 ]]; do 55 | case $1 in 56 | configure | reset | rotate | create_local_bin) C=$1;; 57 | -v | --verbose) VERBOSE=1;; 58 | --no-git | --git-log) GIT_OPTION=$1;; 59 | *) echo "Unknown command/option '$1'";; 60 | esac 61 | shift; 62 | done 63 | 64 | if [[ $C = configure ]]; then 65 | echo "Trying to install the osagitfilter-commands" 66 | os_check $VERBOSE 67 | if [[ ! -d $INSTALL_INTO ]]; then 68 | echo "! ERROR: the folder $INSTALL_INTO doesn't exist on your system." 69 | echo 70 | echo "Create it with: sudo ${SCRIPT_NAME} create_local_bin" 71 | exit 1 72 | fi 73 | for CMD in $COMMANDS; do 74 | if [[ -f $INSTALL_INTO/$CMD ]]; then 75 | echo "- WARNING: the command '$CMD' is already exists in '$INSTALL_INTO'." 76 | else 77 | if ln -s "$BASE_DIR/$CMD.sh" "$INSTALL_INTO/$CMD"; then 78 | echo "- '$CMD' installed" 79 | else 80 | echo "- ERROR: couldn't create symbolic link ($?)" 81 | fi 82 | fi 83 | done 84 | if [[ $(which git) ]]; then 85 | if [[ $GIT_OPTION == "--no-git" ]]; then 86 | echo "Skipping git config" 87 | elif [[ $GIT_OPTION == "--git-log" ]]; then 88 | echo "Configuring git with logging options switched on" 89 | git config --global filter.osa.clean "$INSTALL_INTO/osagitfilter clean --log %f" 90 | git config --global filter.osa.smudge "$INSTALL_INTO/osagitfilter smudge --log %f" 91 | git config --global filter.osa.required "true" 92 | else 93 | echo "Configuring git" 94 | git config --global filter.osa.clean "$INSTALL_INTO/osagitfilter clean %f" 95 | git config --global filter.osa.smudge "$INSTALL_INTO/osagitfilter smudge %f" 96 | git config --global filter.osa.required "true" 97 | fi 98 | else 99 | echo "git is not found; filter is not configured." 100 | fi 101 | elif [[ $C = reset ]]; then 102 | for CMD in $COMMANDS; do 103 | if [[ -h $INSTALL_INTO/$CMD ]]; then 104 | if rm -f "$INSTALL_INTO/$CMD"; then 105 | echo "- '$CMD' reset" 106 | else 107 | echo "- ERROR: couldn't remove '$CMD' ($?)" 108 | fi 109 | else 110 | echo "- WARNING: the command '$CMD' is not installed in '$INSTALL_INTO' as symbolic link." 111 | fi 112 | done 113 | if [[ $(which git) ]]; then 114 | echo "Removing git configuration" 115 | git config --global --remove-section filter.osa || echo "- WARNING: git exitted with code $?" 116 | else 117 | echo "WARNING: git is not found; filter is not removed." 118 | fi 119 | elif [[ $C = rotate ]]; then 120 | LOG_NAME=osagitfilter 121 | ROTATE_STAMP=$(date "+%Y-%m-%dT%H-%M-%S") 122 | if [[ -d $LOG_PATH ]]; then 123 | echo "Rotating log (if any) in $LOG_PATH:" 124 | if [[ -f $LOG_PATH/$LOG_NAME.log ]]; then 125 | echo "- renaming $LOG_NAME.log -> ${ROTATE_STAMP}_$LOG_NAME.log" 126 | mv $LOG_PATH/$LOG_NAME.log "$LOG_PATH/${ROTATE_STAMP}_$LOG_NAME.log" 127 | fi 128 | else 129 | echo "Creating log directory, nothing to rotate" 130 | mkdir -p $LOG_PATH 131 | fi 132 | touch $LOG_PATH/$LOG_NAME.log 133 | elif [[ $C = create_local_bin ]]; then 134 | root_check "$@" 135 | if [[ ! -d $INSTALL_INTO ]]; then 136 | echo "Creating $INSTALL_INTO" 137 | mkdir $INSTALL_INTO 138 | fi 139 | if [[ $(stat -f '%u' $INSTALL_INTO) != $(id -u "${SUDO_USER}") ]]; then 140 | echo "Change ownership to ${SUDO_USER}:admin" 141 | chown -R "${SUDO_USER}:admin" $INSTALL_INTO 142 | fi 143 | if [[ ":$PATH:" == *":$INSTALL_INTO:"* ]]; then 144 | echo "The folder $INSTALL_INTO is created successfully." 145 | echo "Next you can run: $SCRIPT_NAME configure" 146 | else 147 | echo "You should add $INSTALL_INTO to your PATH variable. This should be the case on every macOS install." 148 | echo "You should solve this yourself." 149 | exit 1 150 | fi 151 | else 152 | echo "usage: $SCRIPT_NAME (configure|reset|rotate) [options] [-v|--verbose]" 153 | echo 154 | echo "configure: create symlinks in '$INSTALL_INTO' and add git config ('--no-git' to skip git config, or '--git-log' for logging)" 155 | echo " reset: remove those symlinks and reset git configuration" 156 | echo " rotate: rename any old logs in '$LOG_PATH' to something with a timestamp" 157 | #hidden option: when needed, this is suggested by command 'configure' 158 | #echo "create_local_bin: must be run with 'sudo'. Makes sure $INSTALL_INTO is created correctly." 159 | echo 160 | echo "Installation status:" 161 | for CMD in $COMMANDS; do 162 | if [[ -h $INSTALL_INTO/$CMD ]]; then 163 | echo "- '$CMD' is currently installed" 164 | else 165 | echo "- '$CMD' is currently NOT installed" 166 | fi 167 | done 168 | echo 169 | if [[ $VERBOSE == 1 ]]; then 170 | echo "script location: $BASE_DIR" 171 | echo " install into: $INSTALL_INTO" 172 | echo " log path: $LOG_PATH" 173 | echo 174 | fi 175 | os_check $VERBOSE 176 | fi 177 | -------------------------------------------------------------------------------- /osagitfilter.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Unofficial Bash Strict Mode 4 | set -euo pipefail 5 | IFS=$'\n\t' 6 | 7 | SCRIPT_VER=0.7.1 8 | SCRIPT_NAME=$(basename $0 .sh) 9 | CALLED_WITH="$0 $@" 10 | LOG_PATH=~/Library/Logs/Catsdeep/ 11 | OSA_GET_LANG_CMD=osagetlang 12 | DEFAULT_OSA_LANG=AppleScript 13 | OSA_LANG=$DEFAULT_OSA_LANG 14 | FORBIDDEN_TEXT="AppleScript Debugger" #colon seperated list 15 | FORBIDDEN=() 16 | DO_LOG=0 17 | FILE= 18 | SCRATCH= 19 | 20 | function finish { 21 | #SCRATCH is initialized to a temp directory only at the moment it's needed 22 | if [[ $SCRATCH ]]; then 23 | if [[ $DO_LOG = 0 ]]; then 24 | rm -Rf "$SCRATCH" 25 | else #leave the temporary files when in debug or logging mode 26 | log_line "stopped: $(date '+%Y-%m-%d %H:%M:%S') (temporary files have been left for inspection)" 27 | #make the log operation "atomic" 28 | cat $SCRATCH/tmp.log >> $LOG_PATH/$SCRIPT_NAME.log 29 | fi 30 | fi 31 | } 32 | trap finish EXIT 33 | ################ 34 | 35 | function usage { 36 | show_version 37 | echo 38 | echo "usage: $SCRIPT_NAME command [options] [FILE]" 39 | echo 40 | echo "command (use one):" 41 | echo " clean Translates OSA script to text, to be put into git" 42 | echo " smudge Translates text stored in git to OSA script" 43 | echo 44 | echo "arguments (all optional):" 45 | echo " -f, --forbidden Provide (colon-seperated) forbidden languages. Use '-' for empty list; defaults to 'AppleScript Debugger'" 46 | echo " -l, --log Write debug info to '$LOG_PATH/$SCRIPT_NAME.log'" 47 | echo " -h, -?, --help Show this help message and exit" 48 | echo " -v, --version Show program's version number and exit" 49 | echo " FILE Filename of current stream; not actually used but comes in handy when debugging" 50 | echo 51 | echo "This script translates input from stdin to stdout only. The option '--forbidden' " 52 | echo "are only used with the 'clean' command." 53 | if [[ $# > 0 ]]; then 54 | ERROR 1 "$@" 55 | fi 56 | exit 0 57 | } 58 | 59 | function show_version { 60 | echo "$SCRIPT_NAME v$SCRIPT_VER" 61 | } 62 | 63 | function log_line { 64 | if [[ $DO_LOG = 1 ]]; then 65 | printf "%s\n" "$@">>$SCRATCH/tmp.log 66 | fi 67 | } 68 | 69 | function ERROR { 70 | ERR_NR=$1 71 | shift 72 | >&2 echo "ERROR: $@" 73 | log_line "ERROR($ERR_NR): $@" 74 | exit $ERR_NR 75 | } 76 | 77 | if [[ $# > 0 ]]; then 78 | case $1 in 79 | clean | smudge) CMD=$1;; 80 | -h | -\? | --help) usage;; 81 | -v | --version) show_version;exit 0;; 82 | *) usage "unknown command '$1'";; 83 | esac 84 | shift 85 | else 86 | usage "missing command" 87 | fi 88 | while (( $# > 0 )) ; do 89 | case $1 in 90 | -f | --forbidden) [[ $# > 1 ]] || usage "FORBIDDEN argument expected after $1"; FORBIDDEN_TEXT=$2; shift;; 91 | -l | --log) DO_LOG=1;; 92 | -h | -\? | --help) usage;; 93 | -v | --version) show_version;exit 0;; 94 | -*) usage "Unrecognized switch '$1'";; 95 | *) FILE=$1;; 96 | esac 97 | shift 98 | done 99 | IFS=: 100 | if [[ $FORBIDDEN_TEXT = "-" ]]; then 101 | FORBIDDEN=() 102 | else 103 | FORBIDDEN=($FORBIDDEN_TEXT) 104 | fi 105 | IFS=$'\n\t' 106 | 107 | [[ $CMD ]] || usage "No command supplied" 108 | [[ -d "$LOG_PATH" ]] || mkdir -p "$LOG_PATH" 109 | SCRATCH=$(mktemp -d -t osagitfilter.tmp) 110 | 111 | log_line "---=[ $(show_version) ]=----------------------------------------------" 112 | log_line "command: $CMD" 113 | log_line "started: $(date '+%Y-%m-%d %H:%M:%S')" 114 | log_line "sw_vers: $(sw_vers | tr -d '\t' | tr '\n' ';')" 115 | log_line "call: $CALLED_WITH" 116 | log_line "caller: $(ps -o args= $PPID)" #see: https://stackoverflow.com/a/26985984/56 117 | log_line "scratch: $SCRATCH" 118 | log_line "pwd: $(pwd)" 119 | log_line "filename: '$FILE'" 120 | 121 | if [[ $CMD = clean ]]; then 122 | 123 | #Create a temporary file from the stdin, because osadecompile expects a file, and $FILE might not exist on disk 124 | CLEAN_SCPT_FILE=$SCRATCH/tmp_clean_stdin.scpt 125 | cat - > $CLEAN_SCPT_FILE 126 | 127 | #determine osa-language of input file 128 | OSA_LANG=$($OSA_GET_LANG_CMD $CLEAN_SCPT_FILE) 129 | log_line "OSA lang: $OSA_LANG" 130 | 131 | if [[ $OSA_LANG = "-" ]]; then 132 | #Not an OSA file, just let it pass through 133 | cat $CLEAN_SCPT_FILE 134 | else 135 | #check if the osa-lang is forbidden 136 | log_line "forbidden langs: ${FORBIDDEN[@]:-}" 137 | for BL in "${FORBIDDEN[@]:-}"; do 138 | if [[ $BL = $OSA_LANG ]]; then 139 | ERROR 1 "OSA language '$BL' is forbidden by $SCRIPT_NAME" 140 | fi 141 | done 142 | 143 | #write header 144 | if [[ $OSA_LANG = "JavaScript" ]]; then 145 | comment="//" 146 | else 147 | comment="#" 148 | fi 149 | # Possible lines that can be written (22-31 bytes, excluding newline): 150 | # "#@osa-lang:AppleScript" 151 | # "#@osa-lang:AppleScript Debugger" 152 | # "//@osa-lang:JavaScript" 153 | echo "$comment@osa-lang:$OSA_LANG" 154 | 155 | #decompile to text, and strip tailing whitespace and finally remove last line if it's empty 156 | log_line "Starting osadecompile, strip trailing whitespace and remove last line if it's empty (which is added by osacompile)" 157 | osadecompile $CLEAN_SCPT_FILE | sed -E 's/[[:space:]]*$//' | perl -pe 'chomp if eof' 158 | fi 159 | 160 | elif [[ $CMD = smudge ]]; then 161 | #Create a temporary file, for "random access" of stdin 162 | SMUDGE_TXT_FILE=$SCRATCH/tmp_smudge_stdin.txt 163 | cat - > $SMUDGE_TXT_FILE 164 | 165 | # First get first 42 bytes (more than the max osa header) to make fail safe for binary files 166 | # then take the first line (if there are more) and only return the header value 167 | FIRST_LINE=$(head -c 42 < $SMUDGE_TXT_FILE | head -n 1) 168 | OSA_HDR_RX=$'^(//|#)@osa-lang:(.+)$' 169 | if [[ $FIRST_LINE =~ $OSA_HDR_RX ]]; then 170 | OSA_LANG=${BASH_REMATCH[2]} 171 | log_line "osa-lang header: '$OSA_LANG'" 172 | #Create a temporary file, for storing output of osacompile 173 | SMUDGE_SCPT_FILE=$SCRATCH/tmp_smudge_stdout.scpt 174 | # Remove header (first line) and perform the compilation 175 | log_line "Starting osacompilation" 176 | # perl -pe'1..1and$_=""' 177 | # sed '1d' 178 | perl -pe'1..1and$_=""' < $SMUDGE_TXT_FILE | osacompile -l "$OSA_LANG" -o $SMUDGE_SCPT_FILE 179 | # Osacompile always outputs to file puts the output on a file, so cat that file... 180 | cat $SMUDGE_SCPT_FILE 181 | else 182 | # no header, so not an OSA file, just let it pass through 183 | cat $SMUDGE_TXT_FILE 184 | fi 185 | else 186 | usage "unexpected command (SHOULD NOT HAPPEN)" 187 | fi 188 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This file + terminal output is best viewed with the Menlo-font (because of |≣≡=-) 4 | 5 | function abspath { 6 | if [[ -d "$1" ]] 7 | then 8 | pushd "$1" >/dev/null 9 | pwd 10 | popd >/dev/null 11 | elif [[ -e $1 ]] 12 | then 13 | pushd "$(dirname "$1")" >/dev/null 14 | echo "$(pwd)/$(basename "$1")" 15 | popd >/dev/null 16 | else 17 | echo "$1" does not exist! >&2 18 | return 127 19 | fi 20 | } 21 | 22 | function lang_test { 23 | ((TEST_NR+=1)) 24 | if (( RUN_TEST != 0 && TEST_NR != RUN_TEST )); then 25 | ((TESTS_SKIPPED+=1)) 26 | return 27 | fi 28 | #input arguments 29 | if [[ ${1:0:1} == ";" ]]; then 30 | # if first argument starts with ";", then use it completely as command without the ";" 31 | local CMD="${1:1}" 32 | else 33 | # Otherwise, the argument it the file to get the language of 34 | local CMD="osagetlang $TEST_FILES_DIR/$1" 35 | fi 36 | local EXPECTED_LANG=$2 37 | local EXPECTED_EXIT_CODE=$3 38 | local TEST_DESCRIPTION=$4 39 | local HAS_CAPABILITIES=${5:-1} 40 | # 41 | local TEST_LOG=$TEST_DIR/$TEST_NR.stderr.log 42 | 43 | echo "|≣≡=- $(printf '%2s' $TEST_NR), running lang-test: $TEST_DESCRIPTION" 44 | if [[ $HAS_CAPABILITIES != "1" ]]; then 45 | echo "- Test skipped because of capabilities" 46 | ((TESTS_SKIPPED+=1)) 47 | return 48 | fi 49 | if (( LIST_ONLY == 1 )); then 50 | ((TESTS_SKIPPED+=1)) 51 | return 52 | fi 53 | echo "---===[ $CMD ]=========---------" > $TEST_LOG 54 | RESULT=$(bash -c "$CMD" 2>>$TEST_LOG) 55 | EXIT_CODE=$? 56 | echo "-=> EXIT_CODE:$EXIT_CODE" >> $TEST_LOG 57 | if (( $EXIT_CODE != $EXPECTED_EXIT_CODE )); then 58 | ((TESTS_NOK+=1)) 59 | echo "# Test failed, exit code $EXPECTED_EXIT_CODE expected; was $EXIT_CODE (output was '$RESULT')" 60 | return 61 | fi 62 | if (( $EXIT_CODE == 0 )); then 63 | if [[ $RESULT != $EXPECTED_LANG ]]; then 64 | ((TESTS_NOK+=1)) 65 | echo "# Output '$EXPECTED_LANG' expected, but actually got '$RESULT'" 66 | return 67 | fi 68 | fi 69 | ((TESTS_OK+=1)) 70 | } 71 | 72 | 73 | function filter_test { 74 | ((TEST_NR+=1)) 75 | if (( RUN_TEST != 0 && TEST_NR != RUN_TEST )); then 76 | ((TESTS_SKIPPED+=1)) 77 | return 78 | fi 79 | #input arguments 80 | #local CMD="$1" #Hmm, command substitution... 81 | local INPUT_FILE="$TEST_FILES_DIR/$2" 82 | if [[ $3 = "-" ]]; then 83 | local EXPECTED_FILE="" 84 | else 85 | local EXPECTED_FILE="$TEST_FILES_DIR/$3" 86 | fi 87 | local EXPECTED_EXIT_CODE="$4" 88 | local TEST_DESCRIPTION="$5" 89 | local HAS_CAPABILITIES=${6:-1} 90 | local NO_DIFF=${7:-0} 91 | # 92 | local EXPECTED_FILENAME=$(basename "$EXPECTED_FILE") 93 | local EXPECTED_EXT="${EXPECTED_FILENAME##*.}" 94 | local ACTUAL_FILE="$TEST_DIR/$TEST_NR.actual.$EXPECTED_EXT" 95 | local TEST_LOG=$TEST_DIR/$TEST_NR.stderr.log 96 | local TEST_CMD="cat $INPUT_FILE | $CMD > $ACTUAL_FILE" 97 | # 98 | echo "|≣≡=- $(printf '%2s' $TEST_NR), running filter-test: $TEST_DESCRIPTION" 99 | if [[ $HAS_CAPABILITIES != "1" && $HAS_CAPABILITIES != "2" ]]; then 100 | echo "- Test skipped because of capabilities" 101 | ((TESTS_SKIPPED+=1)) 102 | return 103 | fi 104 | if (( LIST_ONLY == 1 )); then 105 | ((TESTS_SKIPPED+=1)) 106 | return 107 | fi 108 | echo "---===[ cat $INPUT_FILE | $1 > $ACTUAL_FILE }=========---------" > $TEST_LOG 109 | bash -c "cat $INPUT_FILE | $1 > $ACTUAL_FILE 2>> $TEST_LOG" 110 | EXIT_CODE=$? 111 | echo "-=> EXIT_CODE:$EXIT_CODE" >> $TEST_LOG 112 | if (( $EXIT_CODE != $EXPECTED_EXIT_CODE )); then 113 | ((TESTS_NOK+=1)) 114 | echo "# Test failed, exit code $EXPECTED_EXIT_CODE expected; was $EXIT_CODE" 115 | return 116 | fi 117 | if (( $EXIT_CODE == 0 )); then 118 | if [[ $EXPECTED_FILE ]]; then 119 | echo "---===[ diff $EXPECTED_FILE $ACTUAL_FILE ]=========---------" >> $TEST_LOG 120 | diff $EXPECTED_FILE $ACTUAL_FILE >> $TEST_LOG 2>&1 121 | EXIT_CODE=$? 122 | if [[ $NO_DIFF == 1 ]]; then 123 | echo "- Diff skipped because of reasons (please check manually to be sure)" 124 | echo "-=> EXIT_CODE:$EXIT_CODE but NO_DIFF specified" >> $TEST_LOG 125 | else 126 | echo "-=> EXIT_CODE:$EXIT_CODE" >> $TEST_LOG 127 | if (( $EXIT_CODE != 0 )); then 128 | ((TESTS_NOK+=1)) 129 | echo "# diff failed, see $TEST_NR.stderr.log" 130 | if [[ $EXPECTED_EXT != scpt ]]; then #non-binary file 131 | echo "- COMPARE: opendiff $EXPECTED_FILE $ACTUAL_FILE" 132 | fi 133 | return 134 | fi 135 | fi 136 | fi 137 | fi 138 | ((TESTS_OK+=1)) 139 | } 140 | 141 | function clean_up { 142 | if (( TESTS_NOK == 0 )); then 143 | rm -Rf $TEST_DIR 144 | else 145 | echo -n "$TEST_DIR" | pbcopy 146 | echo "CLEANUP YOURSELF: rm -Rf $TEST_DIR # (test dir copied on to clipboard)" 147 | fi 148 | } 149 | 150 | #testing pre-requisites 151 | if [[ -z $(which osagetlang) || -z $(which osagitfilter) ]]; then 152 | >&2 echo "Test-subjects 'osagetlang' and/or 'osagitfilter' not found in the PATH." 153 | >&2 echo "Maybe you should run './setup.sh configure' first?" 154 | exit 1 155 | fi 156 | 157 | #statistics 158 | ((TEST_NR=0)) 159 | ((TESTS_OK=0)) 160 | ((TESTS_NOK=0)) 161 | ((TESTS_SKIPPED=0)) 162 | #Other 163 | ((RUN_TEST=0)) 164 | ((LIST_ONLY=0)) 165 | # 166 | ((TEST_ERROR=0)) 167 | TEST_DIR="$(mktemp -d -t osagitfilter.test.tmp)" 168 | SCRIPT_DIR="$(abspath $(dirname $0))" 169 | TEST_FILES_DIR="$SCRIPT_DIR/assets" 170 | trap clean_up EXIT INT HUP TERM 171 | 172 | if [[ -n `osalang | grep "Debugger"` ]]; then 173 | HAS_ASDBG=1 174 | else 175 | HAS_ASDBG=0 176 | fi 177 | GROUP_RUN=0 178 | 179 | while [[ $# -gt 0 ]]; do 180 | if [[ $1 =~ -?[0-9]+ ]]; then 181 | if (( $1 == -1 )); then 182 | ((TEST_ERROR=1)) 183 | ((RUN_TEST=0)) 184 | elif (( $1 == -2 )); then 185 | ((TEST_ERROR=2)) 186 | ((RUN_TEST=0)) 187 | elif (( $1 < 0 )); then 188 | ((RUN_TEST=${1:1})) 189 | else 190 | ((RUN_TEST=${1})) 191 | fi 192 | elif [[ $1 == "-l" || $1 == "--list" ]]; then 193 | ((LIST_ONLY=1)) 194 | elif [[ $1 == "-gr" || $1 == "--group-run" ]]; then 195 | GROUP_RUN=1 196 | elif [[ $1 == "-na" || $1 == "--no-asdbg" ]]; then 197 | HAS_ASDBG=0 198 | fi 199 | shift 200 | done 201 | 202 | echo "Starting tests... " 203 | echo " ---8<-------------------------------------------------------------------" 204 | echo " To run one test: '$(basename $0) TEST_NR'" 205 | echo " To show all tests: '$(basename $0) --list' (or '-l')" 206 | echo " Force no AppleScript Debugger tests: '$(basename $0) --no-asdbg (or '-na')" 207 | echo " Use -1 (one) to force failure on lang-tests, -2 for filter-tests" 208 | echo " Force grouped run: '$(basename $0) --group-run' (or '-gr')" 209 | echo " ---8<-------------------------------------------------------------------" 210 | echo "started: $(date '+%Y-%m-%d %H:%M:%S')" 211 | echo 212 | 213 | #--| Determine OSA language tests 214 | lang_test "as.scpt" "AppleScript" 0 "Get language of an AppleScript file" 215 | lang_test "asdbg.scpt" "AppleScript Debugger" 0 "Get language of an AppleScript Debugger file" $HAS_ASDBG 216 | lang_test "js.scpt" "JavaScript" 0 "Get language of an JavaScript file" 217 | lang_test "perl.scpt" "Perl" 127 "Get language of non-existing file" 218 | cmd=';osagetlang' 219 | lang_test "$cmd" "" 1 "Get language without arguments" 220 | lang_test "no-as.scpt" "-" 0 "Get language of a non-AppleScript file" 221 | cmd=';cd '$TEST_FILES_DIR'; osagetlang as.scpt' 222 | lang_test "$cmd" "AppleScript" 0 "Get language of an AppleScript file (relative path)" 223 | 224 | #--| Instrumentarium to generate a test-error. 225 | if (( TEST_ERROR == 1 )); then 226 | lang_test "as.scpt" "AdobeScript" 0 "They would like that (test-case designed to fail)" 227 | fi 228 | 229 | 230 | if (( TESTS_NOK != 0 )); then 231 | echo "$ Tests for osagetlang failed, no use to continue further tests" 232 | else 233 | 234 | #--| (Wrong) argument tests 235 | filter_test "osagitfilter" "as.scpt" "-" 1 "No arguments" 236 | filter_test "osagitfilter -h" "as.scpt" "-" 0 "Show usage screen (--help)" 237 | 238 | CMD_CLEAN="osagitfilter clean --log" 239 | CMD_CLEAN_2="$CMD_CLEAN | $CMD_CLEAN" 240 | CMD_CLEAN_ALL="osagitfilter clean --forbidden - --log" #Don't deny any OSA language 241 | CMD_CLEAN_APPLE="osagitfilter clean --forbidden 'JavaScript:AppleScript Debugger' --log" #Deny Javascript AND AppleScript Debugger 242 | CMD_SMUDGE="osagitfilter smudge --log" 243 | CMD_BOTH="osagitfilter clean --log | osagitfilter smudge --log" 244 | CMD_BOTH_ALL="$CMD_CLEAN_ALL | $CMD_SMUDGE" 245 | 246 | for current_run in with_logging no_logging; do 247 | echo "-=≡≣[ Grouped run: $current_run ]≣≡=----------------------------------------------------------" 248 | 249 | #--| Plain AppleScript tests 250 | filter_test "$CMD_CLEAN" "as.scpt" "as-hdr.applescript" 0 "Clean AppleScript" 251 | filter_test "$CMD_CLEAN_2" "as.scpt" "as-hdr.applescript" 0 "Twice clean AppleScript" 252 | filter_test "$CMD_CLEAN_APPLE" "as.scpt" "as-hdr.applescript" 0 "Deny non-Apple languages: AppleScript" 253 | filter_test "$CMD_CLEAN" "as2.scpt" "as2.applescript" 0 "Clean AppleScript; file not ending with empty line" 254 | filter_test "$CMD_SMUDGE" "as-hdr.applescript" "as.scpt" 0 "Smudge AppleScript" 255 | filter_test "$CMD_SMUDGE" "as.applescript" "as.applescript" 0 "Smudge AppleScript (passthrough, because no header)" 256 | filter_test "$CMD_SMUDGE" "as2.applescript" "as2.scpt" 0 "Smudge AppleScript; file not ending with empty line" 257 | filter_test "$CMD_BOTH" "as.scpt" "as.scpt" 0 "round: Pass through AppleScript" 258 | filter_test "$CMD_BOTH" "as2.scpt" "as2.scpt" 0 "round: Pass through AppleScript; file not ending with empty line" 259 | 260 | #--| Non-AppleScript files test 261 | #issue 2: doesn't work yet completely 262 | filter_test "$CMD_CLEAN" "no-as.scpt" "no-as.scpt" 0 "Clean AppleScript: Non-AppleScript (ASCII) file" 263 | filter_test "$CMD_CLEAN" "osa-logo.png" "osa-logo.png" 0 "Clean AppleScript: Non-AppleScript (binary) file" 264 | filter_test "$CMD_CLEAN_2" "osa-logo.png" "osa-logo.png" 0 "Twice clean AppleScript: Non-AppleScript (binary) file" 265 | 266 | #--| ScriptDebugger tests 267 | filter_test "$CMD_CLEAN" "asdbg.scpt" "asdbg-hdr.applescript" 1 "Default Deny: forbidden Debugging Mode switched on" $HAS_ASDBG 268 | #--| ALSO: switched off, because Mojave doesn't allow AppleScript Debugger to be ran from command line or stuff? 269 | ##filter_test "$CMD_CLEAN_ALL" "asdbg.scpt" "asdbg-hdr.applescript" 0 "Allow Debugging Mode switched on" $HAS_ASDBG 270 | filter_test "$CMD_CLEAN_APPLE" "asdbg.scpt" "asdbg-hdr.applescript" 1 "Deny non-Apple languages: AppleScript Debugger" $HAS_ASDBG 271 | #--| This can't be tested completely, because .scpt files always are different, even if they look the same (so no diffing) 272 | #--| ALSO: switched off, because Mojave doesn't allow AppleScript Debugger to be ran from command line or stuff? 273 | ##filter_test "$CMD_SMUDGE" "asdbg-hdr.applescript" "asdbg.scpt" 0 "Smudge AppleScript Debugger" $HAS_ASDBG 1 274 | ##filter_test "$CMD_BOTH_ALL" "asdbg.scpt" "asdbg.scpt" 0 "Pass through AppleScript Debugger" $HAS_ASDBG 1 275 | 276 | #--| JavaScript tests 277 | filter_test "$CMD_CLEAN" "js.scpt" "js-hdr.javascript" 0 "Clean JavaScript" 278 | filter_test "$CMD_CLEAN_APPLE" "js.scpt" "js-hdr.javascript" 1 "Deny non-Apple languages: JavaScript" 279 | filter_test "$CMD_SMUDGE" "js-hdr.javascript" "js.scpt" 0 "Smudge JavaScript" 280 | filter_test "$CMD_SMUDGE" "js.javascript" "js.javascript" 0 "Smudge JavaScript (passthrough, because no header)" 281 | filter_test "$CMD_BOTH" "js.scpt" "js.scpt" 0 "round: Pass through JavaScript" 282 | 283 | if [[ $GROUP_RUN = 0 ]]; then 284 | echo "Skipped grouped run" 285 | break 286 | fi 287 | # Remove log-flags from commands 288 | CMD_CLEAN=${CMD_CLEAN// --log/} 289 | CMD_CLEAN_ALL=${CMD_CLEAN_ALL// --log/} 290 | CMD_CLEAN_APPLE=${CMD_CLEAN_APPLE// --log/} 291 | CMD_SMUDGE=${CMD_SMUDGE// --log/} 292 | CMD_BOTH=${CMD_BOTH// --log/} 293 | done 294 | 295 | #--| Instrumentarium to generate a test-error. 296 | if (( TEST_ERROR == 2 )); then 297 | filter_test "$CMD_BOTH" "js.scpt" "as.scpt" 0 "AppleScript != Javascript (test-case designed to fail)" 298 | fi 299 | 300 | fi 301 | 302 | echo 303 | echo "$TEST_NR tests have ran, $TESTS_OK tested OK, $TESTS_NOK tested NOK and $TESTS_SKIPPED have been skipped." 304 | echo "ended: $(date '+%Y-%m-%d %H:%M:%S')" 305 | --------------------------------------------------------------------------------