aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/cask/developer/bin
diff options
context:
space:
mode:
Diffstat (limited to 'Library/Homebrew/cask/developer/bin')
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/cask-switch-https190
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/develop_brew_cask199
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/find_outdated_appcasts121
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/find_sparkle_appcast68
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/fix_outdated_appcasts78
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/generate_cask_token418
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/generate_issue_template_urls74
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/irregular_cask_whitespace133
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_apps_in_pkg166
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_id_in_kext93
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_ids_in_app162
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_ids_in_pkg115
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_installed_launchjob_ids90
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_loaded_kext_ids45
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_loaded_launchjob_ids84
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_login_items_for_app64
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_payload_in_pkg126
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_pkg_ids_by_regexp83
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_recent_pkg_ids46
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_running_app_ids115
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/list_url_attributes_on_file84
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/merge_outdated_appcasts99
l---------Library/Homebrew/cask/developer/bin/production_brew_cask1
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/project_stats234
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/the_long_tail252
-rwxr-xr-xLibrary/Homebrew/cask/developer/bin/update_issue_template_urls106
26 files changed, 3246 insertions, 0 deletions
diff --git a/Library/Homebrew/cask/developer/bin/cask-switch-https b/Library/Homebrew/cask/developer/bin/cask-switch-https
new file mode 100755
index 000000000..c7845f1eb
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/cask-switch-https
@@ -0,0 +1,190 @@
+#!/bin/bash
+
+set -o pipefail
+
+readonly program="$(basename "$0")"
+skip_curl_verify=0
+verbose=0
+
+syntax_error() {
+ echo "$program: $1" >&2
+ echo "Try \`$program --help\` for more information." >&2
+ exit 1
+}
+
+depends_on() {
+ formula="$1"
+ [[ "$#" -eq 2 ]] && cmd="$2" || cmd=$(basename "${formula}")
+
+ if [[ ! $(which ${cmd}) ]]; then
+ echo -e "$(tput setaf 1)
+ This script depends on '${cmd}'.
+ If you have [Homebrew](http://brew.sh), you can install it with 'brew install ${formula}'.
+ $(tput sgr0)" | sed -E 's/ {6}//' >&2
+ exit 1
+ fi
+}
+
+depends_on 'tsparber/tiny-scripts/curl-check-url'
+
+usage() {
+ echo "
+ This script changes the url, appcast and homepage stanzas to https
+
+ After changing to https a HTTP GET request is performed to verify if the url is reachable.
+ If the https url is not reachable it is reverted to the previous version.
+
+ Known Issues: If multiple url/appcast stanzas are present, all urls are changed but only
+ those for the current os are verified.
+
+ If no cask name is given the current work directory is scanned with the given options.
+
+ usage: $program [options] [<cask_name>]
+ options:
+ -s, --skip-verify Skip checking for a HTTP 200 Status Code using curl.
+ --verbose Show more verbose output.
+ -h, --help Show this help.
+
+ Based on: https://github.com/vitorgalvao/tiny-scripts/blob/master/cask-repair
+ " | sed -E 's/^ {4}//'
+}
+
+# available flags
+while [[ "$1" ]]; do
+ case "$1" in
+ -h | --help)
+ usage
+ exit 0
+ ;;
+ -s | --skip-verify)
+ skip_curl_verify=1
+ ;;
+ --verbose)
+ verbose=1
+ ;;
+ -*)
+ syntax_error "unrecognized option: $1"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+# define function to check if given URL exists and is reachable using HTTPS
+check_url_for_https() {
+ cask_url="$1"
+ verbose_option=""
+
+ [[ ${verbose} -ne 0 ]] && verbose_option="-v "
+
+ # check if the URL sends a 200 HTTP code, else abort
+ curl-check-url ${verbose_option} "${cask_url}" > /dev/null
+ exit_code=$?
+
+ if [[ exit_code -ne 0 ]]; then
+ echo "curl returned ${exit_code}: FAIL for ${cask_url}"
+ return 1
+ fi
+
+ return 0
+}
+
+# define function to modify part of stanza
+replace_protocol_of_stanza() {
+ cask_file="$1"
+ stanza="$2"
+ old_value="$3"
+ new_value="$4"
+
+ sed "s|${stanza} \(['\"]\)${old_value}://|${stanza} \1${new_value}://|g" "${cask_file}" > tmpfile
+ mv tmpfile "${cask_file}"
+}
+
+# define abort function, that will reset the state
+finish() {
+ # show message
+ if [[ "$1" == 'abort' ]]; then
+ echo -e "$(tput setaf 1)$2$(tput sgr0)\n"
+ [[ ! -z "${cask_file}" ]] && git checkout -- "${cask_file}"
+ exit 1
+ elif [[ "$1" == 'success' ]]; then
+ echo -e "$(tput setaf 2)Updated: ${cask_name} is now using HTTPS$(tput sgr0)\n"
+ exit 0
+ fi
+}
+
+# cleanup if aborted with ⌃C
+trap 'finish abort "You aborted"' SIGINT
+
+# exit if not inside a 'homebrew-*/Casks' directory
+casks_dir=$(pwd | perl -ne 'print m{homebrew-[^/]+/Casks}')
+if [[ -z "${casks_dir}" ]]; then
+ echo -e "\n$(tput setaf 1)You need to be inside a '/homebrew-*/Casks' directory$(tput sgr0)\n"
+ exit 1
+fi
+
+# exit if no argument was given: Run in current directory
+if [[ -z "$1" ]]; then
+ options=""
+ [[ ${skip_curl_verify} -ne 0 ]] && options+=" --skip-verify"
+ [[ ${verbose} -ne 0 ]] && options+=" --verbose"
+
+ for file in *.rb;
+ do
+ "$0" ${options} ${file}
+ done
+
+ exit 0
+fi
+
+# clean the cask's name, and check if it is valid
+cask_name="$1"
+[[ "${cask_name}" == *'.rb' ]] && cask_name=$(echo "${cask_name}" | sed 's|\.rb$||')
+cask_file="./${cask_name}.rb"
+[[ ! -f "${cask_file}" ]] && finish abort 'There is no such cask'
+
+# initial tasks
+git checkout -- "${cask_file}"
+
+# check if a http url exists
+cask_contains_http=$(grep "['\"]http://" "${cask_file}")
+if [[ -z ${cask_contains_http} ]]; then
+ echo -e "Skipped ${cask_name} no http found\n"
+ exit 0
+fi
+
+updated_stanzas=0
+for stanza in url appcast homepage; do
+ # Check if the stanza exists
+ stanza_contained=$(grep "${stanza} ['\"]" "${cask_file}")
+ [[ -z ${stanza_contained} ]] && continue
+
+ stanza_contains_https=$(grep "${stanza} ['\"]http://" "${cask_file}")
+ if [[ -z ${stanza_contains_https} ]]; then
+# echo "Skipped stanza ${stanza} in ${cask_name} no http url found"
+ continue
+ fi
+
+ replace_protocol_of_stanza ${cask_file} ${stanza} "http" "https"
+
+ if [[ ${skip_curl_verify} -eq 0 ]]; then
+ check_url_for_https $(brew cask _stanza ${stanza} "${cask_name}")
+ else
+ true
+ fi
+
+ if [[ $? -ne 0 ]]; then
+ echo "Restored original value for stanza ${stanza} as curl check failed"
+ replace_protocol_of_stanza ${cask_file} ${stanza} "https" "http"
+ else
+ updated_stanzas=$((updated_stanzas+1))
+ fi
+done
+
+if [[ ${updated_stanzas} -ne 0 ]]; then
+ finish success
+else
+ finish abort "no updated stanzas after verify for ${cask_name}"
+fi
diff --git a/Library/Homebrew/cask/developer/bin/develop_brew_cask b/Library/Homebrew/cask/developer/bin/develop_brew_cask
new file mode 100755
index 000000000..6723fc978
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/develop_brew_cask
@@ -0,0 +1,199 @@
+#!/bin/bash
+#
+# develop_brew_cask
+#
+# Called via symlink as:
+# production_brew_cask
+#
+
+called_as="$(basename "$0")"
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### configurable global variables
+###
+
+taps_subdir="Library/Taps"
+cask_tap_subdir="caskroom/homebrew-cask"
+dev_links=("cmd" "lib" "Casks")
+
+###
+### functions
+###
+
+warn () {
+ local message="$*"
+ message="${message//\\t/$'\011'}"
+ message="${message//\\n/$'\012'}"
+ message="${message%${message##*[![:space:]]}}"
+ printf "%s\n" "$message" 1>&2
+}
+
+die () {
+ warn "$@"
+ exit 1
+}
+
+cd_to_project_root () {
+ local script_dir git_root
+ script_dir="$(/usr/bin/dirname "$0")"
+ cd "$script_dir"
+ git_root="$(git rev-parse --show-toplevel)"
+ if [[ -z "$git_root" ]]; then
+ die "ERROR: Could not find git project root"
+ fi
+ cd "$git_root"
+}
+
+cd_to_tap_dir () {
+ local taps_dir="$1"
+ local tap_dir="$2"
+ if [[ ! -d "$tap_dir" ]]; then
+ die "ERROR: Could not find tap dir under $taps_dir/"
+ fi
+ cd "$tap_dir"
+}
+
+not_inside_homebrew () {
+ local tap_dir="$1"
+ local git_root="$2"
+ if [[ "$(/usr/bin/stat -L -f '%i' -- "$tap_dir")" -eq "$(/usr/bin/stat -L -f '%i' -- "$git_root")" ]]; then
+ die "\nERROR: Run this script in your private repo, not inside Homebrew.\n"
+ fi
+}
+
+remove_dev_links () {
+ for link_name in "${dev_links[@]}"; do
+ remove_dev_link "$link_name"
+ done
+ printf "brew-cask is now in production mode\n"
+ printf "It is safe to run 'brew update' if you are in production mode for all Caskroom repos.\n"
+}
+
+create_dev_links () {
+ local git_root="$1"
+ for link_name in "${dev_links[@]}"; do
+ create_dev_link "$git_root" "$link_name"
+ done
+ printf "brew-cask is now in development mode\n"
+ printf "Note: it is not safe to run 'brew update' while in development mode\n"
+}
+
+remove_dev_link () {
+ local link_name="$1"
+ /bin/rm -- "$link_name"
+ /bin/mv -- "production_$link_name" "$link_name"
+}
+
+create_dev_link () {
+ local git_root="$1"
+ local link_name="$2"
+ /bin/mv -- "$link_name" "production_$link_name"
+ /bin/ln -s -- "$git_root/$link_name" .
+}
+
+###
+### main
+###
+
+_develop_brew_cask_develop_action () {
+ die "brew-cask is already set up for development"
+}
+
+_develop_brew_cask_production_action () {
+ create_dev_links "$git_root"
+}
+
+_production_brew_cask_develop_action () {
+ remove_dev_links
+}
+
+_production_brew_cask_production_action () {
+ die "brew-cask is already set up for production"
+}
+
+_main () {
+ local git_root brew_repository taps_dir tap_dir
+
+ # initialization
+ cd_to_project_root
+ git_root="$(/bin/pwd)"
+ brew_repository="$(brew --repository)"
+ taps_dir="$brew_repository/$taps_subdir"
+ tap_dir="$taps_dir/$cask_tap_subdir"
+
+ # sanity check
+ not_inside_homebrew "$tap_dir" "$git_root"
+
+ # action
+ cd_to_tap_dir "$taps_dir" "$tap_dir"
+ if [[ -e "production_lib" ]]; then
+ eval "_${called_as}_develop_action"
+ else
+ eval "_${called_as}_production_action"
+ fi
+
+}
+
+_develop_brew_cask_usage () {
+
+ printf "develop_brew_cask
+
+Symlink private repo directories into Homebrew's Cellar, so
+that the 'brew cask' command will use code and Casks from
+the current development branch in your private repo.
+
+Saves the production Homebrew directories under new names.
+
+You can reverse this operation with 'production_brew_cask'.
+
+Note: it is not safe to run 'brew update' while development
+mode is in effect.
+
+"
+
+}
+
+_production_brew_cask_usage () {
+
+ printf "production_brew_cask
+
+Undo all symlinks created by 'develop_brew_cask' so that the
+'brew cask' command will use only released code and Casks
+within Homebrew.
+
+After running this command it is safe to run 'brew update',
+unless you are using similar scripts to create symlinks into
+other Caskroom development repos.
+
+"
+
+}
+
+# ensure we're called by a valid name
+case "${called_as}" in
+ develop_brew_cask) ;;
+ production_brew_cask) ;;
+ *)
+ die "ERROR: name ${called_as} not recognized"
+ ;;
+esac
+
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ ]]; then
+ eval "_${called_as}_usage"
+ exit
+fi
+
+# dispatch main
+_main "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/find_outdated_appcasts b/Library/Homebrew/cask/developer/bin/find_outdated_appcasts
new file mode 100755
index 000000000..d6ddcf4d5
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/find_outdated_appcasts
@@ -0,0 +1,121 @@
+#!/bin/bash
+
+readonly caskroom_online='https://github.com/caskroom'
+readonly caskroom_repos_dir='/tmp/caskroom_repos'
+readonly caskroom_repos=(homebrew-cask homebrew-versions homebrew-fonts homebrew-eid)
+readonly curl_flags=(--silent --location --header 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36')
+inaccessible_appcasts=()
+
+if [[ ! $(which 'ghi') ]] || ! security find-internet-password -s github.com -l 'ghi token' &> /dev/null; then
+ echo -e "$(tput setaf 1)
+ This script requires 'ghi' installed and configured.
+ If you have [Homebrew](http://brew.sh), you can install it with 'brew install ghi'.
+ To configure it, run 'ghi config --auth <username>'. Your Github password will be required, but is never stored.
+ $(tput sgr0)" | sed -E 's/ {4}//' >&2
+ exit 1
+fi
+
+function message {
+ echo "${1}"
+}
+
+function go_to_repos_dir {
+ [[ ! -d "${caskroom_repos_dir}" ]] && mkdir -p "${caskroom_repos_dir}"
+ cd "${caskroom_repos_dir}" || exit 1
+}
+
+function go_to_repo_and_update {
+ local repo_name repo_dir casks_dir
+
+ repo_name="${1}"
+ repo_dir="${caskroom_repos_dir}/${repo_name}"
+ casks_dir="${repo_dir}/Casks"
+
+ if [[ ! -d "${repo_dir}" ]]; then
+ go_to_repos_dir
+
+ message "Cloning ${repo_name}…"
+ git clone "https://github.com/caskroom/${repo_name}.git" --quiet
+
+ cd "${casks_dir}" || exit 1
+ else
+ cd "${casks_dir}" || exit 1
+
+ message "Updating ${repo_name}…"
+ git pull --rebase origin master --quiet
+ fi
+}
+
+function open_issue {
+ local repo_name cask_name cask_url version appcast_url issue_number
+
+ repo_name="${1}"
+ cask_name="${2}"
+ cask_url="${caskroom_online}/${repo_name}/blob/master/Casks/${cask_name}.rb"
+ version="${3}"
+ appcast_url="${4}"
+
+ message="$(echo "Outdated cask: ${cask_name}
+
+ Outdated cask: [\`${cask_name}\`](${cask_url}).
+
+ Info:
+ + version: \`${version}\`.
+ + appcast url: ${appcast_url}.
+ " | sed -E 's/^ {4}//')"
+
+ issue_number=$(ghi open --label 'outdated appcast' --message "${message}" | head -1 | perl -pe 's/^#(\d+): .*/\1/')
+ message "Opened issue: https://github.com/caskroom/${repo_name}/issues/${issue_number}."
+}
+
+function is_appcast_available {
+ local appcast_url
+
+ appcast_url="${1}"
+
+ http_status="$(curl "${curl_flags[@]}" --head --write-out '%{http_code}' "${appcast_url}" -o '/dev/null')"
+
+ [[ "${http_status}" == 200 ]]
+}
+
+function report_outdated_appcasts {
+ local repo_name cask_name appcast_url current_checkpoint new_checkpoint version
+
+ repo_name="${1}"
+
+ for cask_file in ./*; do
+ appcast_url="$(brew cask _stanza appcast "${cask_file}")"
+ [[ -z "${appcast_url}" ]] && continue # skip early if there is no appcast
+
+ cask_name="$(basename "${cask_file%.*}")"
+
+ message "Verifying appcast checkpoint for ${cask_name}…"
+
+ if is_appcast_available "${appcast_url}"; then
+ current_checkpoint="$(brew cask _stanza --yaml appcast "${cask_file}" | grep '^- :checkpoint' | awk '{print $3}')"
+ new_checkpoint="$(curl "${curl_flags[@]}" --compressed "${appcast_url}" | sed 's|<pubDate>[^<]*</pubDate>||g' | shasum --algorithm 256 | awk '{ print $1 }')"
+ else
+ message "There was an error checking the appcast for ${cask_name}."
+ inaccessible_appcasts+=("${repo_name}/${cask_name}")
+ continue
+ fi
+
+ if [[ "${current_checkpoint}" != "${new_checkpoint}" ]]; then
+ version="$(brew cask _stanza version "${cask_file}")"
+
+ message "${cask_name} is outdated. Opening issue in ${repo_name}…"
+ open_issue "${repo_name}" "${cask_name}" "${version}" "${appcast_url}"
+ fi
+ done
+}
+
+for repo in "${caskroom_repos[@]}"; do
+ go_to_repo_and_update "${repo}"
+ report_outdated_appcasts "${repo}"
+done
+
+if [[ ${#inaccessible_appcasts[@]} -gt 0 ]];then
+ echo # empty line
+ message 'Some casks have appcasts that errored out, and may need to be rechecked:'
+ printf '%s\n' "${inaccessible_appcasts[@]}"
+fi
diff --git a/Library/Homebrew/cask/developer/bin/find_sparkle_appcast b/Library/Homebrew/cask/developer/bin/find_sparkle_appcast
new file mode 100755
index 000000000..b3b854f39
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/find_sparkle_appcast
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+readonly user_agent=(--user-agent 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36')
+
+usage() {
+ local program exit_status
+
+ program="$(basename "$0")"
+ exit_status="$1"
+
+ echo "usage: ${program} <path_to_app>"
+ exit "${exit_status}"
+}
+
+absolute_path() {
+ echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
+}
+
+appcast_found_error() {
+ local error_reason="$1"
+
+ echo "An appcast was found pointing to ${appcast_url}, but it ${error_reason}. You should:
+
+ 1. Check your internet connection.
+ 2. Try again later.
+ 3. Contact the developer."
+
+ exit 1
+}
+
+# exit if no argument (or more than one) was given
+if [[ -z "$1" ]] || [[ -n "$2" ]]; then
+ usage 1
+fi
+
+# get plist
+path_to_app="$(absolute_path "$1")"
+path_to_plist="${path_to_app}/Contents/Info.plist"
+
+if [[ ! -f "${path_to_plist}" ]]; then
+ echo 'You need to use this on a .app bundle. Please verify your target.'
+ usage 1
+fi
+
+# get appcast
+appcast_url="$(defaults read "${path_to_plist}" 'SUFeedURL' 2>/dev/null)"
+
+if [[ -z "${appcast_url}" ]]; then
+ echo 'It appears this app does not have a Sparkle appcast'
+ exit 0
+fi
+
+# validate appcast
+appcast_http_response="$(curl --silent --head "${user_agent[@]}" --write-out '%{http_code}' "${appcast_url}" -o /dev/null)"
+[[ "${appcast_http_response}" != '200' ]] && appcast_found_error "returned a non-200 (OK) HTTP response code (${appcast_http_response})"
+
+appcast_checkpoint=$(curl --silent --compressed --location "${user_agent[@]}" "${appcast_url}" | sed 's|<pubDate>[^<]*</pubDate>||g' | shasum --algorithm 256 | awk '{ print $1 }')
+[[ "${appcast_checkpoint}" == 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' ]] && appcast_found_error 'seems to be empty'
+
+# output appcast
+echo "A Sparkle appcast was found. You should add it to your cask as
+
+ appcast '${appcast_url}',
+ checkpoint: '${appcast_checkpoint}'
+
+You should likely also add 'auto_updates true'"
+
+exit 0
diff --git a/Library/Homebrew/cask/developer/bin/fix_outdated_appcasts b/Library/Homebrew/cask/developer/bin/fix_outdated_appcasts
new file mode 100755
index 000000000..9915b5339
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/fix_outdated_appcasts
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+IFS=$'\n'
+
+readonly caskroom_repos_dir='/tmp/caskroom_repos'
+readonly caskroom_repos=(homebrew-cask homebrew-versions homebrew-fonts homebrew-eid)
+
+if [[ ! $(which 'ghi') ]] || ! security find-internet-password -s github.com -l 'ghi token' &> /dev/null; then
+ echo -e "$(tput setaf 1)
+ This script requires 'ghi' installed and configured.
+ If you have [Homebrew](http://brew.sh), you can install it with 'brew install ghi'.
+ To configure it, run 'ghi config --auth <username>'. Your Github password will be required, but is never stored.
+ $(tput sgr0)" | sed -E 's/ {4}//' >&2
+ exit 1
+fi
+
+if [[ ! $(which 'cask-repair') ]]; then
+ echo -e "$(tput setaf 1)
+ This script requires 'cask-repair'.
+ If you have [Homebrew](http://brew.sh), you can install it with 'brew install vitorgalvao/tiny-scripts/cask-repair'.
+ $(tput sgr0)" | sed -E 's/ {4}//' >&2
+ exit 1
+fi
+
+function message {
+ echo "${1}"
+}
+
+function go_to_repos_dir {
+ [[ ! -d "${caskroom_repos_dir}" ]] && mkdir -p "${caskroom_repos_dir}"
+ cd "${caskroom_repos_dir}" || exit 1
+}
+
+function go_to_repo_and_update {
+ local repo_name repo_dir casks_dir
+
+ repo_name="${1}"
+ repo_dir="${caskroom_repos_dir}/${repo_name}"
+ casks_dir="${repo_dir}/Casks"
+
+ if [[ ! -d "${repo_dir}" ]]; then
+ go_to_repos_dir
+
+ message "Cloning ${repo_name}…"
+ git clone "https://github.com/caskroom/${repo_name}.git" --quiet
+
+ cd "${casks_dir}" || exit 1
+ else
+ cd "${casks_dir}" || exit 1
+
+ message "Updating ${repo_name}…"
+ git pull --rebase origin master --quiet
+ fi
+}
+
+function fix_outdated_appcasts {
+ local issue_number cask_name pr_number
+
+ for line in $(ghi list --state open --no-pulls --label 'outdated appcast' --reverse | tail +2); do
+ [[ "${line}" == 'None.' ]] && break # exit early if there are no relevant issues in repo
+
+ issue_number="$(awk '{print $1}' <<< "${line}")"
+ cask_name="$(awk '{print $4}' <<< "${line}")"
+
+ cask-repair --pull origin --push origin --open-appcast --closes-issue "${issue_number}" --blind-submit "${cask_name}"
+
+ if [[ "$?" -eq 0 ]]; then
+ pr_number="$(ghi list --pulls --creator | sed -n 2p | awk '{print $1}')"
+ ghi edit --label 'outdated appcast' "${pr_number}" &>/dev/null
+ ghi comment --close --message "Closing in favour of #${pr_number}." "${issue_number}" &>/dev/null
+ fi
+ done
+}
+
+for repo in "${caskroom_repos[@]}"; do
+ go_to_repo_and_update "${repo}"
+ fix_outdated_appcasts
+done
diff --git a/Library/Homebrew/cask/developer/bin/generate_cask_token b/Library/Homebrew/cask/developer/bin/generate_cask_token
new file mode 100755
index 000000000..48e933136
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/generate_cask_token
@@ -0,0 +1,418 @@
+#!/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/ruby
+#
+# generate_cask_token
+#
+# todo:
+#
+# remove Ruby 2.0 dependency and change shebang line
+#
+# detect Cask files which differ only by the placement of hyphens.
+#
+# merge entirely into "brew cask create" command
+#
+
+###
+### dependencies
+###
+
+require "pathname"
+require "open3"
+
+begin
+ # not available by default
+ require "active_support/inflector"
+rescue LoadError
+end
+
+###
+### configurable constants
+###
+
+EXPANDED_SYMBOLS = {
+ "+" => "plus",
+ "@" => "at",
+ }.freeze
+
+CASK_FILE_EXTENSION = ".rb".freeze
+
+# Hardcode App names that cannot be transformed automatically.
+# Example: in "x48.app", "x48" is not a version number.
+# The value in the hash should be a valid Cask token.
+APP_EXCEPTION_PATS = {
+ # looks like a trailing version, but is not.
+ %r{\Aiterm\Z}i => "iterm2",
+ %r{\Aiterm2\Z}i => "iterm2",
+ %r{\Apgadmin3\Z}i => "pgadmin3",
+ %r{\Ax48\Z}i => "x48",
+ %r{\Avitamin-r[\s\d\.]*\Z}i => "vitamin-r",
+ %r{\Aimagealpha\Z}i => "imagealpha",
+ # upstream is in the midst of changing branding
+ %r{\Abitcoin-?qt\Z}i => "bitcoin-core",
+ # "mac" cannot be separated from the name because it is in an English phrase
+ %r{\Aplayonmac\Z}i => "playonmac",
+ %r{\Acleanmymac[\s\d\.]*\Z}i => "cleanmymac",
+ # arguably we should not have kept these two exceptions
+ %r{\Akismac\Z}i => "kismac",
+ %r{\Avoicemac\Z}i => "voicemac",
+ }.freeze
+
+# Preserve trailing patterns on App names that could be mistaken
+# for version numbers, etc
+PRESERVE_TRAILING_PATS = [
+ %r{id3}i,
+ %r{mp3}i,
+ %r{3[\s-]*d}i,
+ %r{diff3}i,
+ %r{\A[^\d]+\+\Z}i,
+ ].freeze
+
+# The code that employs these patterns against App names
+# - hacks a \b (word-break) between CamelCase and snake_case transitions
+# - anchors the pattern to end-of-string
+# - applies the patterns repeatedly until there is no match
+REMOVE_TRAILING_PATS = [
+ # spaces
+ %r{\s+}i,
+
+ # generic terms
+ %r{\bapp}i,
+ %r{\b(?:quick[\s-]*)?launcher}i,
+
+ # "mac", "for mac", "for OS X", "macOS", "for macOS".
+ %r{\b(?:for)?[\s-]*mac(?:intosh|OS)?}i,
+ %r{\b(?:for)?[\s-]*os[\s-]*x}i,
+
+ # hardware designations such as "for x86", "32-bit", "ppc"
+ %r{(?:\bfor\s*)?x.?86}i,
+ %r{(?:\bfor\s*)?\bppc}i,
+ %r{(?:\bfor\s*)?\d+.?bits?}i,
+
+ # frameworks
+ %r{\b(?:for)?[\s-]*(?:oracle|apple|sun)*[\s-]*(?:jvm|java|jre)}i,
+ %r{\bgtk}i,
+ %r{\bqt}i,
+ %r{\bwx}i,
+ %r{\bcocoa}i,
+
+ # localizations
+ %r{en\s*-\s*us}i,
+
+ # version numbers
+ %r{[^a-z0-9]+}i,
+ %r{\b(?:version|alpha|beta|gamma|release|release.?candidate)(?:[\s\.\d-]*\d[\s\.\d-]*)?}i,
+ %r{\b(?:v|ver|vsn|r|rc)[\s\.\d-]*\d[\s\.\d-]*}i,
+ %r{\d+(?:[a-z\.]\d+)*}i,
+ %r{\b\d+\s*[a-z]}i,
+ %r{\d+\s*[a-c]}i, # constrained to a-c b/c of false positives
+ ].freeze
+
+# Patterns which are permitted (undisturbed) following an interior version number
+AFTER_INTERIOR_VERSION_PATS = [
+ %r{ce}i,
+ %r{pro}i,
+ %r{professional}i,
+ %r{client}i,
+ %r{server}i,
+ %r{host}i,
+ %r{viewer}i,
+ %r{launcher}i,
+ %r{installer}i,
+ ].freeze
+
+###
+### classes
+###
+
+class AppName < String
+ def self.remove_trailing_pat
+ @@remove_trailing_pat ||= %r{(?<=.)(?:#{REMOVE_TRAILING_PATS.join('|')})\Z}i
+ end
+
+ def self.preserve_trailing_pat
+ @@preserve_trailing_pat ||= %r{(?:#{PRESERVE_TRAILING_PATS.join('|')})\Z}i
+ end
+
+ def self.after_interior_version_pat
+ @@after_interior_version_pat ||= %r{(?:#{AFTER_INTERIOR_VERSION_PATS.join('|')})}i
+ end
+
+ def english_from_app_bundle
+ return self if ascii_only?
+ return self unless File.exist?(self)
+
+ # check Info.plist CFBundleDisplayName
+ bundle_name = Open3.popen3(*%w[
+ /usr/libexec/PlistBuddy -c
+ ],
+ "Print CFBundleDisplayName",
+ Pathname.new(self).join("Contents", "Info.plist").to_s) do |_stdin, stdout, _stderr|
+ begin
+ stdout.gets.force_encoding("UTF-8").chomp
+ rescue
+ end
+ end
+ return AppName.new(bundle_name) if bundle_name && bundle_name.ascii_only?
+
+ # check Info.plist CFBundleName
+ bundle_name = Open3.popen3(*%w[
+ /usr/libexec/PlistBuddy -c
+ ],
+ "Print CFBundleName",
+ Pathname.new(self).join("Contents", "Info.plist").to_s) do |_stdin, stdout, _stderr|
+ begin
+ stdout.gets.force_encoding("UTF-8").chomp
+ rescue
+ end
+ end
+ return AppName.new(bundle_name) if bundle_name && bundle_name.ascii_only?
+
+ # check localization strings
+ local_strings_file = Pathname.new(self).join("Contents", "Resources", "en.lproj", "InfoPlist.strings")
+ local_strings_file = Pathname.new(self).join("Contents", "Resources", "English.lproj", "InfoPlist.strings") unless local_strings_file.exist?
+ if local_strings_file.exist?
+ bundle_name = File.open(local_strings_file, "r:UTF-16LE:UTF-8") do |fh|
+ %r{\ACFBundle(?:Display)?Name\s*=\s*"(.*)";\Z}.match(fh.readlines.grep(%r{^CFBundle(?:Display)?Name\s*=\s*}).first) do |match|
+ match.captures.first
+ end
+ end
+ return AppName.new(bundle_name) if bundle_name && bundle_name.ascii_only?
+ end
+
+ # check Info.plist CFBundleExecutable
+ bundle_name = Open3.popen3(*%w[
+ /usr/libexec/PlistBuddy -c
+ ],
+ "Print CFBundleExecutable",
+ Pathname.new(self).join("Contents", "Info.plist").to_s) do |_stdin, stdout, _stderr|
+ begin
+ stdout.gets.force_encoding("UTF-8").chomp
+ rescue
+ end
+ end
+ return AppName.new(bundle_name) if bundle_name && bundle_name.ascii_only?
+
+ self
+ end
+
+ def basename
+ if Pathname.new(self).exist?
+ AppName.new(Pathname.new(self).basename.to_s)
+ else
+ self
+ end
+ end
+
+ def remove_extension
+ sub(%r{\.app\Z}i, "")
+ end
+
+ def decompose_to_ascii
+ # crudely (and incorrectly) decompose extended latin characters to ASCII
+ return self if ascii_only?
+ return self unless respond_to?(:mb_chars)
+ AppName.new(mb_chars.normalize(:kd).each_char.select(&:ascii_only?).join)
+ end
+
+ def hardcoded_exception
+ APP_EXCEPTION_PATS.each do |regexp, exception|
+ return AppName.new(exception) if regexp.match(self)
+ end
+ nil
+ end
+
+ def insert_vertical_tabs_for_camel_case
+ app_name = AppName.new(self)
+ if app_name.sub!(%r{(#{self.class.preserve_trailing_pat})\Z}i, "")
+ trailing = Regexp.last_match(1)
+ end
+ app_name.gsub!(%r{([^A-Z])([A-Z])}, "\\1\v\\2")
+ app_name.sub!(%r{\Z}, trailing) if trailing
+ app_name
+ end
+
+ def insert_vertical_tabs_for_snake_case
+ gsub(%r{_}, "\v")
+ end
+
+ def clean_up_vertical_tabs
+ gsub(%r{\v}, "")
+ end
+
+ def remove_interior_versions!
+ # done separately from REMOVE_TRAILING_PATS because this
+ # requires a substitution with a backreference
+ sub!(%r{(?<=.)[\.\d]+(#{self.class.after_interior_version_pat})\Z}i, '\1')
+ sub!(%r{(?<=.)[\s\.\d-]*\d[\s\.\d-]*(#{self.class.after_interior_version_pat})\Z}i, '-\1')
+ end
+
+ def remove_trailing_strings_and_versions
+ app_name = insert_vertical_tabs_for_camel_case
+ .insert_vertical_tabs_for_snake_case
+ while self.class.remove_trailing_pat.match(app_name) &&
+ !self.class.preserve_trailing_pat.match(app_name)
+ app_name.sub!(self.class.remove_trailing_pat, "")
+ end
+ app_name.remove_interior_versions!
+ app_name.clean_up_vertical_tabs
+ end
+
+ def simplified
+ return @simplified if @simplified
+ @simplified = english_from_app_bundle
+ .basename
+ .decompose_to_ascii
+ .remove_extension
+ @simplified = @simplified.hardcoded_exception || @simplified.remove_trailing_strings_and_versions
+ @simplified
+ end
+end
+
+class CaskFileName < String
+ def spaces_to_hyphens
+ gsub(%r{ +}, "-")
+ end
+
+ def delete_invalid_chars
+ gsub(%r{[^a-z0-9-]+}, "")
+ end
+
+ def collapse_multiple_hyphens
+ gsub(%r{--+}, "-")
+ end
+
+ def delete_leading_hyphens
+ gsub(%r{^--+}, "")
+ end
+
+ def delete_hyphens_before_numbers
+ gsub(%r{-([0-9])}, '\1')
+ end
+
+ def spell_out_symbols
+ cask_file_name = self
+ EXPANDED_SYMBOLS.each do |k, v|
+ cask_file_name.gsub!(k, " #{v} ")
+ end
+ cask_file_name.sub(%r{ +\Z}, "")
+ end
+
+ def add_extension
+ sub(%r{(?:#{escaped_cask_file_extension})?\Z}i, CASK_FILE_EXTENSION)
+ end
+
+ def remove_extension
+ sub(%r{#{escaped_cask_file_extension}\Z}i, "")
+ end
+
+ def from_simplified_app_name
+ return @from_simplified_app_name if @from_simplified_app_name
+ @from_simplified_app_name = if APP_EXCEPTION_PATS.rassoc(remove_extension)
+ remove_extension
+ else
+ remove_extension
+ .downcase
+ .spell_out_symbols
+ .spaces_to_hyphens
+ .delete_invalid_chars
+ .collapse_multiple_hyphens
+ .delete_leading_hyphens
+ .delete_hyphens_before_numbers
+ end
+ raise "Could not determine Simplified App name" if @from_simplified_app_name.empty?
+ @from_simplified_app_name.add_extension
+ end
+end
+
+###
+### methods
+###
+
+def project_root
+ Dir.chdir File.dirname(File.expand_path(__FILE__))
+ @git_root ||= Open3.popen3(*%w[
+ git rev-parse --show-toplevel
+ ]) do |_stdin, stdout, _stderr|
+ begin
+ Pathname.new(stdout.gets.chomp)
+ rescue
+ raise "could not find project root"
+ end
+ end
+ raise "could not find project root" unless @git_root.exist?
+ @git_root
+end
+
+def escaped_cask_file_extension
+ @escaped_cask_file_extension ||= Regexp.escape(CASK_FILE_EXTENSION)
+end
+
+def simplified_app_name
+ @simplified_app_name ||= AppName.new(ARGV.first.dup.force_encoding("UTF-8")).simplified
+end
+
+def cask_file_name
+ @cask_file_name ||= CaskFileName.new(simplified_app_name).from_simplified_app_name
+end
+
+def cask_token
+ @cask_token ||= cask_file_name.remove_extension
+end
+
+def warnings
+ return @warnings if @warnings
+ @warnings = []
+ unless APP_EXCEPTION_PATS.rassoc(cask_token)
+ if %r{\d} =~ cask_token
+ @warnings.push "WARNING: '#{cask_token}' contains digits. Digits which are version numbers should be removed."
+ end
+ end
+ filename = project_root.join("Casks", cask_file_name)
+ if filename.exist?
+ @warnings.push "WARNING: the file '#{filename}' already exists. Prepend the vendor name if this is not a duplicate."
+ end
+ @warnings
+end
+
+def report
+ puts "Proposed Simplified App name: #{simplified_app_name}" if $debug
+ puts "Proposed token: #{cask_token}"
+ puts "Proposed file name: #{cask_file_name}"
+ puts "Cask Header Line: cask '#{cask_token}' do"
+ unless warnings.empty?
+ $stderr.puts "\n"
+ $stderr.puts warnings
+ $stderr.puts "\n"
+ exit 1
+ end
+end
+
+###
+### main
+###
+
+usage = <<-EOS
+Usage: generate_cask_token [ -debug ] <application.app>
+
+Given an Application name or a path to an Application, propose a
+Cask token, filename, and header line.
+
+With -debug, also provide the internal "Simplified App Name".
+
+EOS
+
+if ARGV.first =~ %r{^-+h(elp)?$}i
+ puts usage
+ exit 0
+end
+
+if ARGV.first =~ %r{^-+debug?$}i
+ $debug = 1
+ ARGV.shift
+end
+
+unless ARGV.length == 1
+ puts usage
+ exit 1
+end
+
+report
diff --git a/Library/Homebrew/cask/developer/bin/generate_issue_template_urls b/Library/Homebrew/cask/developer/bin/generate_issue_template_urls
new file mode 100755
index 000000000..916d84987
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/generate_issue_template_urls
@@ -0,0 +1,74 @@
+#!/usr/bin/env ruby
+#
+# generate_issue_template_urls
+#
+
+###
+### dependencies
+###
+
+require "erb"
+
+###
+### constants
+###
+
+BASE_URL = "https://github.com/caskroom/homebrew-cask/issues/new".freeze
+
+###
+### methods
+###
+
+def main(args)
+ args.each do |file|
+ File.read(file).scan(%r{(.*?)\n(.*)}m) do |title, body|
+ puts generate_url(title, body)
+ end
+ end
+end
+
+def generate_url(title, body)
+ encoded_title = url_encode(title)
+ encoded_body = url_encode(body)
+ if $debug
+ puts "Encoded title: #{encoded_title}"
+ puts "Encoded body: #{encoded_body}"
+ end
+ "#{BASE_URL}?title=#{encoded_title}&body=#{encoded_body}"
+end
+
+def url_encode(unencoded_str)
+ ERB::Util.url_encode(unencoded_str)
+end
+
+###
+### main
+###
+
+usage = <<-EOS
+Usage: generate_issue_template_urls <issue_template.md> ...
+
+Given one or more GitHub issue template files, generate encoded URLs for each
+and print, separated by newlines. The first line of a template file should be
+the issue title.
+
+With -debug, print out the encoded title and body individually as well.
+
+EOS
+
+if ARGV.first =~ %r{^-+h(elp)?$}i
+ puts usage
+ exit 0
+end
+
+if ARGV.first =~ %r{^-+debug?$}i
+ $debug = 1
+ ARGV.shift
+end
+
+if ARGV.empty?
+ puts usage
+ exit 1
+end
+
+main(ARGV)
diff --git a/Library/Homebrew/cask/developer/bin/irregular_cask_whitespace b/Library/Homebrew/cask/developer/bin/irregular_cask_whitespace
new file mode 100755
index 000000000..62a60ae73
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/irregular_cask_whitespace
@@ -0,0 +1,133 @@
+#!/bin/bash
+#
+# irregular_cask_whitespace
+#
+# find irregular whitespace in Cask files
+#
+# notes
+#
+# requires a recent-ish Perl with Unicode support, probably 5.14
+# or better.
+#
+# bugs
+#
+# todo
+#
+
+###
+### settings
+###
+
+set -e
+set -o pipefail
+set +o histexpand
+set -o nounset
+shopt -s nocasematch
+shopt -s nullglob
+
+###
+### functions
+###
+
+warn () {
+ local message="$@"
+ message="${message//\\t/$'\011'}"
+ message="${message//\\n/$'\012'}"
+ message="${message%"${message##*[![:space:]]}"}"
+ printf "%s\n" "$message" 1>&2
+}
+
+die () {
+ warn "$@"
+ exit 1
+}
+
+###
+### main
+###
+
+_irregular_cask_whitespace () {
+ local directory="$1"
+ cd "$directory" || die "Could not cd to '$directory'"
+
+ printf "# No trailing newline at EOF\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{[^\n]\z}s' -- ./*.rb
+
+ printf "\n# Extra trailing newline at EOF\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\n{2}\z}s' -- ./*.rb
+
+ printf "\n# Final 'end' indented\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{ end\s+\z}s' -- ./*.rb
+
+ printf "\n# Extra newline before final end\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\n\nend\s+\z}s' -- ./*.rb
+
+ printf "\n# Extra newline before header\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\n\ncask\s+:v\d\S*\s+=>}s' -- ./*.rb
+
+ printf "\n# Extra newline after header\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{(?:\A|\n)cask\s+:v\d\S*\s+=>[^\n]+\n\n\s*(\S+)}s and $1 ne "if"' -- ./*.rb
+
+ printf "\n# No empty line before uninstall\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\n[^\n]+\n +uninstall }s' -- ./*.rb
+
+ # todo?
+ # printf "\n# No empty line before caveats\n"
+ # perl -C32 -0777 -ne 'print " $ARGV\n" if m{\n[^\n]+\n +caveats }s' -- ./*.rb
+
+ printf "\n# Extra interior newlines\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\n{3,}}s' -- ./*.rb
+
+ printf "\n# Leading whitespace at BOF\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\A\s}s' -- ./*.rb
+
+ printf "\n# Trailing whitespace at EOL (includes Tab/CR)\n"
+ perl -C32 -ne 'print " $ARGV\n" if m{\s\n}s' -- ./*.rb | sort | uniq
+
+ printf "\n# Tabs\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\t}s' -- ./*.rb
+
+ printf "\n# Carriage Returns\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\r}s' -- ./*.rb
+
+ printf "\n# Misc Control Characters\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{[\x00-\x08\x0B-\x0C\x0E\x1F]}s' -- ./*.rb
+
+ printf "\n# First indent not 2\n"
+ perl -C32 -0777 -ne 's{\A(.*?\n)?cask\s+[^\n]+\n+}{}s; print " $ARGV\n" unless m{\A \S}s' -- ./*.rb
+
+ printf "\n# Indents not multiple of 2\n"
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{\n(?: ){0,} [a-z]}s' -- ./*.rb
+
+ printf "\n# Unicode Space Characters\n"
+ # \x{0085}\x{0088}\x{0089}
+ perl -C32 -0777 -ne 'print " $ARGV\n" if m{[\x{008a}\x{00a0}\x{1680}\x{180e}\x{2000}\x{2001}\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}\x{200a}\x{200b}\x{2028}\x{2029}\x{202f}\x{205f}\x{2060}\x{3000}\x{feff}\x{e0020}]}s' -- ./*.rb
+
+}
+
+###
+### argument processing
+###
+
+if [[ "${1:-}" =~ ^-+h(elp)?$ ]]; then
+ printf "irregular_cask_whitespace <dir>
+
+ Find irregular whitespace in Cask files within <dir>
+
+"
+ exit
+fi
+
+if [ "$#" -ne 1 ]; then
+ die "Single directory argument required"
+elif ! [ -d "$1" ]; then
+ die "No directory found at '$1'"
+fi
+
+###
+### dispatch
+###
+
+_irregular_cask_whitespace "${@:-}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_apps_in_pkg b/Library/Homebrew/cask/developer/bin/list_apps_in_pkg
new file mode 100755
index 000000000..9e0411322
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_apps_in_pkg
@@ -0,0 +1,166 @@
+#!/bin/bash
+#
+# list_apps_in_pkg
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+opt_lax=''
+opt_pkg=''
+pkgdir=''
+
+# prefer GNU xargs
+xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"
+
+###
+### functions
+###
+
+warn () {
+ local message="$@"
+ message="${message//\\t/$'\011'}"
+ message="${message//\\n/$'\012'}"
+ message="${message%"${message##*[![:space:]]}"}"
+ printf "%s\n" "$message" 1>&2
+}
+
+die () {
+ warn "$@"
+ exit 1
+}
+
+app_source_1 () {
+ /usr/bin/find "$pkgdir" -name PackageInfo -print0 | \
+ "$xargs" -0 /usr/bin/perl -0777 -ne \
+ 'while (m{<pkg-info[^\n]*install-location="/Applications".*?path\s*=\s*"([^"]+)"}sg) { my $p = $1; $p =~ s{\A.*/}{}; print "$p\n" }';
+}
+
+app_source_2 () {
+ /usr/bin/find "$pkgdir" -name PackageInfo -print0 | \
+ "$xargs" -0 /usr/bin/perl -0777 -ne \
+ 'while (m{<pkg-info[^\n]*install-location="/".*?path\s*=\s*"./Applications/([^"]+)"}sg) { my $p = $1; $p =~ s{\A.*/}{}; print "$p\n" }';
+}
+
+app_source_3 () {
+ /usr/bin/find "$pkgdir" -type d -name '*.app' | \
+ perl -pe 's{\A.*/}{}';
+}
+
+app_source_4 () {
+ /usr/bin/find "$pkgdir" -name PackageInfo -print0 | \
+ "$xargs" -0 /usr/bin/perl -0777 -ne \
+ 'while (m{path\s*=\s*"([^"]+\.app)"}sg) { my $p = $1; $p =~ s{\A.*/}{}; print "$p\n" }';
+}
+
+app_source_5 () {
+ /usr/bin/find "$pkgdir" -name Archive.pax.gz -print0 | \
+ "$xargs" -0 -n1 -I{} /bin/bash -c \
+ "/usr/bin/gunzip -c '{}' | /bin/pax | /usr/bin/egrep '\.app'" | \
+ /usr/bin/perl -pe 's{\A.*/([^/]+\.app).*\Z}{$1}';
+}
+
+merge_sources () {
+ /usr/bin/sort | /usr/bin/uniq
+}
+
+mark_up_sources () {
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 -I{} -n1 /bin/bash -c \
+ 'printf "{}"; /bin/test -n "$(/usr/bin/find /Applications -type d -maxdepth 3 -name "{}" -print0; /usr/bin/find ~/Applications -type d -maxdepth 3 -name "{}")" && printf " (+)"; printf "\n"'
+}
+
+process_args () {
+ local arg
+ if [[ "$#" -eq 0 ]]; then
+ die "ERROR: A file argument is required"
+ else
+ opt_pkg="${!#}" # last arg
+ fi
+ for arg in "$@"; do
+ if [[ $arg =~ ^-+h(elp)?$ ]]; then
+ printf "list_apps_in_pkg [ -lax ] <file.pkg>
+
+Given a package file, extract a list of candidate App names from
+inside the pkg, which may be useful for naming a Cask.
+
+The given package file need not be installed.
+
+If an App of the listed name is already installed in /Applications
+or ~/Applications, it will be followed by a plus symbol '(+)' in
+the output. This can be verified via 'ls' or the Finder.
+
+Arguments
+
+ -lax Be less selective in looking for App names. Generate
+ more, but less accurate, guesses.
+
+Bugs: This script is imperfect.
+ - It does not fully parse PackageInfo files
+ - An App can be hidden within a nested archive and not found
+ - Some pkg files simply don't contain any Apps
+
+See CONTRIBUTING.md and 'man pkgutil' for more information.
+
+"
+ exit
+ elif [[ $arg =~ ^-+lax$ ]]; then
+ opt_lax='true'
+ elif [[ "$arg" = "$opt_pkg" ]]; then
+ true
+ else
+ die "ERROR: Unknown argument '$arg'"
+ fi
+ done
+ if [[ -h "$opt_pkg" ]]; then
+ opt_pkg="$(/usr/bin/readlink "$opt_pkg")"
+ fi
+ if ! [[ -e "$opt_pkg" ]]; then
+ die "ERROR: No such pkg file: '$opt_pkg'"
+ fi
+}
+
+###
+### main
+###
+
+_list_apps_in_pkg () {
+
+ if [[ -d "$opt_pkg" ]]; then
+ pkgdir="$opt_pkg"
+ else
+ local tmpdir="$(/usr/bin/mktemp -d -t list_ids_in_pkg)"
+ trap "/bin/rm -rf -- '$tmpdir'" EXIT
+ pkgdir="$tmpdir/unpack"
+ /usr/sbin/pkgutil --expand "$opt_pkg" "$tmpdir/unpack" "$pkgdir"
+ fi
+
+ {
+ # strings that look like App names (Something.app)
+ app_source_1;
+ app_source_2;
+ app_source_3;
+ if [[ -n "$opt_lax" ]]; then
+ app_source_4;
+ app_source_5;
+ fi
+ } | \
+ merge_sources | \
+ mark_up_sources
+}
+
+process_args "${@}"
+
+# dispatch main
+_list_apps_in_pkg
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_id_in_kext b/Library/Homebrew/cask/developer/bin/list_id_in_kext
new file mode 100755
index 000000000..b1ee95a77
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_id_in_kext
@@ -0,0 +1,93 @@
+#!/bin/bash
+#
+# list_id_in_kext
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+kextdir=''
+
+# prefer GNU xargs
+xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"
+
+###
+### functions
+###
+
+bundle_id_source_1 () {
+ /usr/bin/find "$kextdir" -name Info.plist -print0 | \
+ "$xargs" -0 -I {} /usr/libexec/PlistBuddy {} -c 'Print :CFBundleIdentifier'
+}
+
+merge_sources () {
+ /usr/bin/sort | /usr/bin/uniq
+}
+
+clean_sources () {
+ /usr/bin/egrep -v '^com\.apple\.'
+}
+
+mark_up_sources () {
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 -I{} -n1 /bin/bash -c \
+ 'printf "{}"; /bin/test "$(/usr/sbin/kextstat -kl -b "{}" | /usr/bin/wc -l)" -gt 0 && printf " (+)"; printf "\n"'
+}
+
+###
+### main
+###
+
+_list_id_in_kext () {
+
+ kextdir="$1"
+ if [[ -h "$kextdir" ]]; then
+ kextdir="$(/usr/bin/readlink "$kextdir")"
+ fi
+
+ {
+ # emit strings that look like bundle ids
+ bundle_id_source_1;
+ } | \
+ clean_sources | \
+ merge_sources | \
+ mark_up_sources
+
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ || -z "$1" ]]; then
+ printf "list_id_in_kext <file.kext>
+
+Given a Kernel Extension (kext) bundle dir on disk, extract the
+associated kext Bundle ID, which may be useful in a Cask uninstall
+stanza, eg
+
+ uninstall :kext => 'kext.id.goes.here'
+
+The kext need not be loaded for this script to work.
+
+If a given kext is currently loaded, it will be followed by a plus
+symbol '(+)' in the output. This can be verified via the command
+
+ /usr/sbin/kextstat -kl -b 'kext.id.goes.here'
+
+See CONTRIBUTING.md and 'man kextstat' for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_id_in_kext "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_ids_in_app b/Library/Homebrew/cask/developer/bin/list_ids_in_app
new file mode 100755
index 000000000..89bb253a5
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_ids_in_app
@@ -0,0 +1,162 @@
+#!/bin/bash
+#
+# list_ids_in_app
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+appdir=''
+scriptdir="$(dirname "$0")"
+
+# prefer GNU xargs
+xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"
+
+###
+### functions
+###
+
+bundle_id_source_1 () {
+ /usr/bin/find "$appdir" -name Info.plist -print0 | \
+ "$xargs" -0 -I {} /usr/libexec/PlistBuddy {} -c 'Print :CFBundleIdentifier'
+}
+
+merge_sources () {
+ /usr/bin/sort | /usr/bin/uniq
+}
+
+clean_sources () {
+ /usr/bin/egrep -v '^com\.apple\.' | \
+ /usr/bin/egrep -vi '^org\.andymatuschak\.Sparkle' | \
+ /usr/bin/egrep -vi '^(SDL|SDL_net|TDParseKit)$' | \
+ /usr/bin/egrep -vi '^com\.growl\.(growlframework|WebKit|iCal|GrowlAction|GrowlLauncher|GrowlPlugins)' | \
+ /usr/bin/egrep -vi '^com\.adobe\.(ACE|AGM|AXE8SharedExpat|AdobeExtendScript|AdobeScCore|AdobeXMPCore|BIB|BIBUtils|CoolType|ESD\.AdobeUpdaterLibFramework|JP2K|adobe_caps|AcrobatPlugin|AdobeResourceSynchronizer|ICUConverter|ICUData|acrobat\.assert|acrobat\.pdfviewerNPAPI|adobepdf417pmp|ahclientframework|datamatrixpmp|eulaframework|framework|linguistic|qrcodepmp)' | \
+ /usr/bin/egrep -vi '^com\.microsoft\.(Automator|certificate\.framework|chart|converterlib|converters|excel\.pde|grammar|igx|mcp|merp|msls3|netlib|officeart|ole|oleo|powerplant|powerplantcore|powerpoint\.pde|speller|thesaurus|urlmon|wizard|wordforms|MSCommon|mbuinstrument_framework|mbukernel_framework|rdpkit)' | \
+ /usr/bin/egrep -vi '^com\.google\.(Chrome\.framework|Chrome\.helper|Keystone|BreakpadFramework|GDataFramework|Reporter)' | \
+ /usr/bin/egrep -vi '^atmo\.mac\.macho' | \
+ /usr/bin/egrep -vi '^com\.3dconnexion\.driver\.client' | \
+ /usr/bin/egrep -vi '^com\.Breakpad\.crash_report_sender' | \
+ /usr/bin/egrep -vi '^com\.Cycling74\.driver\.Soundflower' | \
+ /usr/bin/egrep -vi '^com\.HumbleDaisy\.HDCrashReporter' | \
+ /usr/bin/egrep -vi '^com\.amazon\.JSONKit' | \
+ /usr/bin/egrep -vi '^com\.bensyverson\..*dvmatte' | \
+ /usr/bin/egrep -vi '^com\.binarymethod\.BGHUDAppKit' | \
+ /usr/bin/egrep -vi '^com\.blacktree\.(QSCore|QSEffects|QSFoundation|QSInterface)' | \
+ /usr/bin/egrep -vi '^com\.brandonwalkin\.BWToolkitFramework' | \
+ /usr/bin/egrep -vi '^com\.cruzapp\.TDWebThumbnail' | \
+ /usr/bin/egrep -vi '^com\.cycling74\.QTExportTool' | \
+ /usr/bin/egrep -vi '^com\.fluidapp\.(BrowserBrowserPlugIn|FluidInstance|ThumbnailPlugIn)' | \
+ /usr/bin/egrep -vi '^com\.github\.ObjectiveGit' | \
+ /usr/bin/egrep -vi '^com\.heroku\.RedisAdapter' | \
+ /usr/bin/egrep -vi '^com\.instinctivecode\.MGScopeBar' | \
+ /usr/bin/egrep -vi '^com\.intel\.nw\.helper' | \
+ /usr/bin/egrep -vi '^com\.joshaber\.RockemSockem' | \
+ /usr/bin/egrep -vi '^com\.katidev\.KTUIKit' | \
+ /usr/bin/egrep -vi '^com\.kirin\.plugin\.adapter' | \
+ /usr/bin/egrep -vi '^com\.lextek\.onix' | \
+ /usr/bin/egrep -vi '^com\.macromedia\.(Flash Player\.authplaylib|PepperFlashPlayer)' | \
+ /usr/bin/egrep -vi '^com\.mainconcept\.mc\.enc\.avc' | \
+ /usr/bin/egrep -vi '^com\.netscape\.(DefaultPlugin|MRJPlugin)' | \
+ /usr/bin/egrep -vi '^com\.nxtbgthng\.JSONKit' | \
+ /usr/bin/egrep -vi '^com\.omnigroup\.(OmniAppKit|OmniInspector|framework)' | \
+ /usr/bin/egrep -vi '^com\.oracle\.java\..*\.jdk' | \
+ /usr/bin/egrep -vi '^com\.panic\.(PanicCore|automator|CodaScriptPlugIn)' | \
+ /usr/bin/egrep -vi '^com\.pixelespresso\.cocoafob' | \
+ /usr/bin/egrep -vi '^com\.positivespinmedia\.(PSMTabBarControlFramework|PSMTabBarFramework)' | \
+ /usr/bin/egrep -vi '^com\.softube\.(Amplifier|Cabinet)' | \
+ /usr/bin/egrep -vi '^com\.sonic\.(AS_Storage|AuthorScriptHDMV)' | \
+ /usr/bin/egrep -vi '^com\.soundcloud\.Share-on-SoundCloud' | \
+ /usr/bin/egrep -vi '^com\.stuffit\.(format|sdk|stuffitcore)' | \
+ /usr/bin/egrep -vi '^com\.sun\.Scintilla' | \
+ /usr/bin/egrep -vi '^com\.yourcompany' | \
+ /usr/bin/egrep -vi '^coop\.plausible\.(CrashReporter|PLWeakCompatibility)' | \
+ /usr/bin/egrep -vi '^de\.buzzworks\.Quincy' | \
+ /usr/bin/egrep -vi '^de\.dstoecker\.xadmaster' | \
+ /usr/bin/egrep -vi '^isao\.sonobe\.OgreKit' | \
+ /usr/bin/egrep -vi '^jp\.hmdt\.framework\.hmdtblkappkit' | \
+ /usr/bin/egrep -vi '^net\.hockeyapp\.sdk\.mac' | \
+ /usr/bin/egrep -vi '^net\.java\.openjdk\.jre' | \
+ /usr/bin/egrep -vi '^net\.liquidx\.EyeTunes' | \
+ /usr/bin/egrep -vi '^net\.sourceforge\.Log4Cocoa' | \
+ /usr/bin/egrep -vi '^net\.sourceforge\.munt\.MT32Emu' | \
+ /usr/bin/egrep -vi '^net\.sourceforge\.skim-app\.framework' | \
+ /usr/bin/egrep -vi '^net\.wafflesoftware\.ShortcutRecorder\.framework' | \
+ /usr/bin/egrep -vi '^org\.AFNetworking\.AFNetworking' | \
+ /usr/bin/egrep -vi '^org\.boredzo\.(LMX|ISO8601DateFormatter)' | \
+ /usr/bin/egrep -vi '^org\.dribin\.dave\.DDHidLib' | \
+ /usr/bin/egrep -vi '^org\.linkbackproject\.LinkBack' | \
+ /usr/bin/egrep -vi '^org\.mozilla\.(crashreporter|plugincontainer|universalchardet|updater)' | \
+ /usr/bin/egrep -vi '^org\.python\.(python|PythonLauncher|buildapplet)' | \
+ /usr/bin/egrep -vi '^org\.remotesensing\.libtiff' | \
+ /usr/bin/egrep -vi '^org\.vafer\.FeedbackReporter' | \
+ /usr/bin/egrep -vi '^org\.xiph\.(ogg|vorbis)' | \
+ /usr/bin/egrep -vi '^se\.propellerheads\..*\.library$'
+
+}
+
+mark_up_sources () {
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 -I{} -n1 /bin/bash -c \
+ "printf '{}'; $scriptdir/list_running_app_ids -t '{}' >/dev/null 2>&1 && printf ' (+)'; printf "\\\\n""
+}
+
+###
+### main
+###
+
+_list_ids_in_app () {
+
+ appdir="$1"
+ if [[ -h "$appdir" ]]; then
+ appdir="$(/usr/bin/readlink "$appdir")"
+ fi
+
+ {
+ # emit strings that look like bundle ids
+ bundle_id_source_1;
+ } | \
+ clean_sources | \
+ merge_sources | \
+ mark_up_sources
+
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ || -z "$1" ]]; then
+ printf "list_ids_in_app <path.app>
+
+Given a Application (app) bundle directory on disk, extract the
+associated app Bundle ID, which may be useful in a Cask uninstall
+stanza, eg
+
+ uninstall quit: 'app.id.goes.here'
+
+The app need not be running for this script to work.
+
+Bundle IDs attributed to Apple and common developer frameworks
+are excluded from the output.
+
+If a given app is currently running, it will be followed by a plus
+symbol '(+)' in the output. This can be verified via the command
+
+ list_running_app_ids | grep 'app.id.goes.here'
+
+See CONTRIBUTING.md for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_ids_in_app "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_ids_in_pkg b/Library/Homebrew/cask/developer/bin/list_ids_in_pkg
new file mode 100755
index 000000000..3b0dec4a6
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_ids_in_pkg
@@ -0,0 +1,115 @@
+#!/bin/bash
+#
+# list_ids_in_pkg
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+pkgdir=''
+
+# prefer GNU xargs
+xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"
+
+###
+### functions
+###
+
+bundle_id_source_1 () {
+ /usr/bin/find "$pkgdir" -name PackageInfo -print0 | \
+ "$xargs" -0 /usr/bin/perl -0777 -ne \
+ 'while (m{<pkg-info.*?\sid(?:entifier)?\s*=\s*"([^"]*?)"}sg) { print "$1\n" }'
+}
+
+bundle_id_source_2 () {
+ /usr/bin/find "$pkgdir" -name Info.plist -print0 | \
+ "$xargs" -0 -I {} /usr/libexec/PlistBuddy {} -c 'Print :CFBundleIdentifier'
+}
+
+merge_sources () {
+ /usr/bin/sort | /usr/bin/uniq
+}
+
+clean_sources () {
+ /usr/bin/egrep -v '^com\.apple\.' | \
+ /usr/bin/egrep -v 'sparkle\.finish-installation$'
+}
+
+mark_up_sources () {
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 -I{} -n1 /bin/bash -c \
+ 'printf "{}"; /usr/sbin/pkgutil --pkg-info "{}" >/dev/null 2>&1 && printf " (+)"; printf "\n"'
+}
+
+###
+### main
+###
+
+_list_ids_in_pkg () {
+
+ if [[ -d "$1" ]]; then
+ pkgdir="$1"
+ if [[ -h "$pkgdir" ]]; then
+ pkgdir="$(/usr/bin/readlink "$pkgdir")"
+ fi
+ else
+ local tmpdir="$(/usr/bin/mktemp -d -t list_ids_in_pkg)"
+ trap "/bin/rm -rf -- '$tmpdir'" EXIT
+ pkgdir="$tmpdir/unpack"
+ /usr/sbin/pkgutil --expand "$1" "$tmpdir/unpack" "$pkgdir"
+ fi
+
+ {
+ # emit strings that look like bundle ids
+ bundle_id_source_1;
+ bundle_id_source_2;
+ } | \
+ merge_sources | \
+ clean_sources | \
+ mark_up_sources
+
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ || -z "$1" ]]; then
+ printf "list_ids_in_pkg <file.pkg>
+
+Given a package file, extract a list of candidate Package IDs
+which may be useful in a Cask uninstall stanza, eg
+
+ uninstall pkgutil: 'package.id.goes.here'
+
+The given package file need not be installed.
+
+The output of this script should be overly inclusive -- not
+every candidate package id in the output will be needed at
+uninstall time.
+
+Package IDs designated by Apple or common development frameworks
+will be excluded from the output.
+
+If a package id is already installed, it will be followed by
+a plus symbol '(+)' in the output. This can be verified via
+the command
+
+ /usr/sbin/pkgutil --pkg-info 'package.id.goes.here'
+
+See CONTRIBUTING.md and 'man pkgutil' for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_ids_in_pkg "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_installed_launchjob_ids b/Library/Homebrew/cask/developer/bin/list_installed_launchjob_ids
new file mode 100755
index 000000000..82672c2c9
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_installed_launchjob_ids
@@ -0,0 +1,90 @@
+#!/bin/bash
+#
+# list_installed_launchjob_ids
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+# prefer GNU xargs
+xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"
+
+###
+### functions
+###
+
+launchjob_id_source_1 () {
+ /usr/bin/find ~/Library/LaunchAgents/ \
+ ~/Library/LaunchDaemons/ \
+ /Library/LaunchAgents/ \
+ /Library/LaunchDaemons/ \
+ -type f -print0 2>/dev/null | \
+ "$xargs" -0 /usr/bin/perl -0777 -ne \
+ 'while (m{<key>\s*Label\s*</key>\s*<string>([^<]+?)</string>}sg) { print "$1\n" }'
+}
+
+merge_sources () {
+ /usr/bin/sort | /usr/bin/uniq
+}
+
+clean_sources () {
+ /usr/bin/egrep -v '^com\.apple\.'
+}
+
+mark_up_sources () {
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 -I{} -n1 /bin/bash -c \
+ 'printf "{}"; /bin/launchctl list "{}" >/dev/null 2>&1 && printf " (+)"; printf "\n"'
+}
+
+###
+### main
+###
+
+_list_installed_launchjob_ids () {
+
+ {
+ launchjob_id_source_1;
+ } | \
+ merge_sources | \
+ clean_sources | \
+ mark_up_sources
+
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ ]]; then
+ printf "list_installed_launchjob_ids
+
+List all installed launchjob IDs, which may be useful
+in a Cask uninstall stanza, eg
+
+ uninstall launchctl: 'job.id.goes.here'
+
+Launchctl jobs attributed to Apple will be ommitted.
+
+If a launchctl job is currently loaded, and visible to the current
+user, it will be followed by a plus symbol '(+)' in the output.
+This can be verified via the command
+
+ /bin/launchctl list 'job.id.goes.here'
+
+See CONTRIBUTING.md and 'man launchctl' for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_installed_launchjob_ids "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_loaded_kext_ids b/Library/Homebrew/cask/developer/bin/list_loaded_kext_ids
new file mode 100755
index 000000000..19b47cb07
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_loaded_kext_ids
@@ -0,0 +1,45 @@
+#!/bin/bash
+#
+# list_loaded_kext_ids
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### main
+###
+
+_list_loaded_kext_ids () {
+ /usr/sbin/kextstat -kl | \
+ /usr/bin/cut -c53- | \
+ /usr/bin/cut -f1 -d' ' | \
+ /usr/bin/egrep -v '^com\.apple\.'
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ ]]; then
+ printf "list_loaded_kext_ids
+
+Print Bundle IDs for currently loaded Kernel Extensions (kexts)
+which may be useful in a Cask uninstall stanza, eg
+
+ uninstall kext: 'kext.bundle.id.goes.here'
+
+Kexts attributed to Apple are excluded from the output.
+
+See CONTRIBUTING.md for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_loaded_kext_ids "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_loaded_launchjob_ids b/Library/Homebrew/cask/developer/bin/list_loaded_launchjob_ids
new file mode 100755
index 000000000..4b1330b70
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_loaded_launchjob_ids
@@ -0,0 +1,84 @@
+#!/bin/bash
+#
+# list_loaded_launchjob_ids
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+###
+### functions
+###
+
+launchjob_id_source_1 () {
+ if [[ "$EUID" -ne 0 ]]; then
+ /usr/bin/sudo -p 'Optionally give your sudo password: ' -- /bin/launchctl list | /usr/bin/cut -f3
+ fi
+}
+
+launchjob_id_source_2 () {
+ /bin/launchctl list | /usr/bin/cut -f3
+}
+
+merge_sources () {
+ /usr/bin/sort | /usr/bin/uniq
+}
+
+clean_sources () {
+ /usr/bin/egrep -v '^Label$' | \
+ /usr/bin/egrep -v '^com\.apple\.' | \
+ /usr/bin/egrep -v '^0x[0-9a-f]+\.anonymous\.' | \
+ /usr/bin/egrep -v '^\[0x' | \
+ /usr/bin/egrep -v '\.[0-9]+$'
+}
+
+###
+### main
+###
+
+_list_loaded_launchjob_ids () {
+
+ {
+ launchjob_id_source_1;
+ launchjob_id_source_2;
+ } | \
+ clean_sources | \
+ merge_sources
+
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ ]]; then
+ printf "list_loaded_launchjob_ids
+
+List IDs for currently-loaded launchctl jobs, which may be useful
+in a Cask uninstall stanza, eg
+
+ uninstall launchctl: 'job.id.goes.here'
+
+If this command is not run as the superuser, you will be prompted
+for a password to run a subcommand using 'sudo'. The password is
+not required, but supplying it may reveal additional job ids. To
+skip using the password, press <return> repeatedly.
+
+Launchctl jobs attributed to Apple are excluded from the output.
+
+See CONTRIBUTING.md and 'man launchctl' for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_loaded_launchjob_ids "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_login_items_for_app b/Library/Homebrew/cask/developer/bin/list_login_items_for_app
new file mode 100755
index 000000000..f775347ef
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_login_items_for_app
@@ -0,0 +1,64 @@
+#!/usr/bin/env ruby
+#
+# list_login_items_for_app
+#
+
+###
+### dependencies
+###
+
+require "open3"
+
+###
+### methods
+###
+
+def usage
+ <<-EOS
+Usage: list_login_items_for_app <path.app>
+
+Given an Application (app) bundle directory on disk, find all
+login items associated with that app, which you can use in a
+Cask uninstall stanza, eg
+
+ uninstall login_item: 'login item name'
+
+Note that you will likely need to have opened the app at least
+once for any login items to be present.
+
+See CONTRIBUTING.md for more information.
+
+EOS
+end
+
+def process_args
+ if ARGV.first =~ %r{^-+h(?:elp)?$}
+ puts usage
+ exit 0
+ elsif ARGV.length == 1
+ $app_path = ARGV.first
+ else
+ puts usage
+ exit 1
+ end
+end
+
+def list_login_items_for_app(app_path)
+ out, err, status = Open3.capture3(
+ "/usr/bin/osascript", "-e",
+ "tell application \"System Events\" to get the name of every login item " \
+ "whose path contains \"#{File.basename(app_path)}\""
+ )
+ if status.exitstatus > 0
+ $stderr.puts err
+ exit status.exitstatus
+ end
+ puts out.gsub(", ", "\n")
+end
+
+###
+### main
+###
+
+process_args
+list_login_items_for_app $app_path
diff --git a/Library/Homebrew/cask/developer/bin/list_payload_in_pkg b/Library/Homebrew/cask/developer/bin/list_payload_in_pkg
new file mode 100755
index 000000000..cf71bb2aa
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_payload_in_pkg
@@ -0,0 +1,126 @@
+#!/bin/bash
+#
+# list_payload_in_pkg
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+pkg_arg=''
+tmp_boms=''
+
+# prefer GNU xargs
+xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"
+
+trap cleanup_tmp_boms EXIT
+
+###
+### functions
+###
+
+cleanup_tmp_boms () {
+ if [[ -n "$tmp_boms" ]]; then
+ # tmpfile ensures that rmdir -p is not too destructive
+ local tmpfile="/tmp/list_payload_in_pkg.$$";
+ /usr/bin/touch "$tmpfile";
+ echo "$tmp_boms" | \
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 /bin/rm -f --;
+ {
+ echo "$tmp_boms" | \
+ /usr/bin/perl -pe 's{[^/]+\n}{\000}sg' | \
+ "$xargs" -0 /bin/rmdir -p -- || true
+ } 2>/dev/null
+ /bin/rm -- "$tmpfile";
+ fi
+}
+
+bom_source_1 () {
+ /usr/bin/find "$pkg_arg" -iname '*.pkg' -print0 | \
+ "$xargs" -0 -I{} -n1 /usr/sbin/pkgutil --bom "{}" 2>/dev/null
+}
+
+bom_source_2 () {
+ /usr/bin/find "$pkg_arg" -name '*.bom'
+}
+
+expand_sources () {
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 lsbom --
+}
+
+merge_sources () {
+ /usr/bin/sort | /usr/bin/uniq
+}
+
+clean_sources () {
+ /usr/bin/cut -f1 | \
+ /usr/bin/perl -pe 's{\A\.}{}' | \
+ /usr/bin/egrep '.'
+}
+
+mark_up_sources () {
+ /usr/bin/perl -pe 's{\n}{\000}sg' | \
+ "$xargs" -0 -I{} -n1 /bin/bash -c \
+ 'printf "{}"; /bin/test -e "{}" >/dev/null 2>&1 && printf " (+)"; printf "\n"'
+}
+
+###
+### main
+###
+
+_list_payload_in_pkg () {
+
+ pkg_arg="$1"
+ if [[ -h "$pkg_arg" ]]; then
+ pkg_arg="$(/usr/bin/readlink "$pkg_arg")"
+ fi
+
+ tmp_boms="$(bom_source_1)";
+ {
+ # find BOM files
+ echo "$tmp_boms";
+ bom_source_2;
+ } | \
+ expand_sources | \
+ clean_sources | \
+ merge_sources | \
+ mark_up_sources
+
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ || -z "$1" ]]; then
+ printf "list_payload_in_pkg <file.pkg>
+
+Given a package file, show what files may be installed by that
+pkg, which may be useful when writing a Cask uninstall stanza.
+
+The given package file need not be installed.
+
+The output attempts to be overly inclusive. However, since
+pkg files are allowed to run arbitrary scripts, there can be
+no guarantee that the output is exact.
+
+If a given file is already installed, it will be followed by
+a plus symbol '(+)' in the output.
+
+See CONTRIBUTING.md and 'man pkgutil' for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_payload_in_pkg "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_pkg_ids_by_regexp b/Library/Homebrew/cask/developer/bin/list_pkg_ids_by_regexp
new file mode 100755
index 000000000..6c1f392c9
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_pkg_ids_by_regexp
@@ -0,0 +1,83 @@
+#!/bin/bash
+#
+# list_pkg_ids_by_regexp
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### functions
+###
+
+warn () {
+ local message="$@"
+ message="${message//\\t/$'\011'}"
+ message="${message//\\n/$'\012'}"
+ message="${message%"${message##*[![:space:]]}"}"
+ printf "%s\n" "$message" 1>&2
+}
+
+die () {
+ warn "$@"
+ exit 1
+}
+
+fail_informatively () {
+ local message="No match."
+ if ! [[ "$1" =~ '*' ]]; then
+ message="$message Suggestion: try '${1}.*'"
+ fi
+ die "$message"
+}
+
+analyze_regexp () {
+ if [[ "$1" =~ ^\^ ]]; then
+ warn "Note: pkgutil regular expressions are implicitly anchored with '^' at start"
+ fi
+ if [[ "$1" =~ \$$ ]]; then
+ warn "Note: pkgutil regular expressions are implicitly anchored with '$' at end"
+ fi
+}
+
+###
+### main
+###
+
+_list_pkg_ids_by_regexp () {
+ analyze_regexp "$1"
+ if ! /usr/sbin/pkgutil --pkgs="$1"; then
+ fail_informatively "$1"
+ fi
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ || $# -ne 1 ]]; then
+ printf "list_pkg_ids_by_regexp <regexp>
+
+Print pkg receipt IDs for installed packages matching a regular
+expression, which may be useful in a Cask uninstall stanza, eg
+
+ uninstall pkgutil: 'pkg.regexp.goes.here'
+
+Unlike most other scripts in this directory, package IDs attributed to
+Apple are NOT excluded from the output. This is to avoid uninstalling
+essential system files due to an exuberant regexp.
+
+For more information, see
+
+ https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/uninstall.md
+
+"
+ exit
+fi
+
+# dispatch main
+_list_pkg_ids_by_regexp "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_recent_pkg_ids b/Library/Homebrew/cask/developer/bin/list_recent_pkg_ids
new file mode 100755
index 000000000..0c203ec26
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_recent_pkg_ids
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# list_recent_pkg_ids
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### main
+###
+
+_list_recent_pkg_ids () {
+ /bin/ls -t /var/db/receipts | \
+ /usr/bin/egrep '\.plist$' | \
+ /usr/bin/perl -pe 's{\A([^/]+)\.plist\Z}{$1}sg' | \
+ /usr/bin/egrep -v '^com\.apple\.' | \
+ /usr/bin/head -10
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ ]]; then
+ printf "list_recent_pkg_ids
+
+Print pkg receipt IDs for the 10 most-recently-installed packages,
+which may be useful in a Cask uninstall stanza, eg
+
+ uninstall pkgutil: 'pkg.receipt.id.goes.here'
+
+Package IDs attributed to Apple are excluded from the output.
+
+See CONTRIBUTING.md for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_recent_pkg_ids "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/list_running_app_ids b/Library/Homebrew/cask/developer/bin/list_running_app_ids
new file mode 100755
index 000000000..136a578d4
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_running_app_ids
@@ -0,0 +1,115 @@
+#!/usr/bin/env ruby
+#
+# list_running_app_ids
+#
+
+###
+### dependencies
+###
+
+require "open3"
+require "set"
+
+###
+### globals
+###
+
+$opt_test = nil
+
+###
+### methods
+###
+
+def check_ruby
+ if RUBY_VERSION.to_f < 2.0
+ print "You are currently using Ruby ", RUBY_VERSION, ", but version 2.0 or above is required."
+ exit 1
+ end
+end
+
+def usage
+ <<-EOS
+list_running_app_ids [ -t <bundle-id> ]
+
+Print a list of currently running Applications and associated
+Bundle IDs, which may be useful in a Cask uninstall stanza, eg
+
+ uninstall quit: 'bundle.id.goes.here'
+
+Applications attributed to Apple are excluded from the output.
+
+With optional "-t <bundle-id>", silently test if a given app
+is running, exiting with an error code if not.
+
+See CONTRIBUTING.md for more information.
+
+EOS
+end
+
+def process_args
+ until ARGV.empty?
+ if ARGV.first =~ %r{^-+t(?:est)?$} && ARGV.length > 1
+ ARGV.shift
+ $opt_test = ARGV.shift
+ elsif ARGV.first =~ %r{^-+h(?:elp)?$}
+ puts usage
+ exit 0
+ else
+ puts usage
+ exit 1
+ end
+ end
+end
+
+def load_apps
+ out, err, status = Open3.capture3("/usr/bin/osascript", "-e", 'tell application "System Events" to get (name, bundle identifier, unix id) of every process')
+ if status.exitstatus > 0
+ puts err
+ exit status.exitstatus
+ end
+ out = out.split(", ")
+ one_third = out.length / 3
+ @app_names = out.shift(one_third)
+ @bundle_ids = out.shift(one_third)
+ @unix_ids = out.shift(one_third)
+end
+
+def test_app(bundle)
+ @bundle_ids.include?(bundle) ? 0 : 1
+end
+
+def excluded_bundle_id(bundle_id)
+ %r{^com\.apple\.}.match(bundle_id)
+end
+
+def excluded_app_name(app_name)
+ %r{^osascript$}.match(app_name) # this script itself
+end
+
+def report_apps
+ running = Set.new
+ @app_names.zip(@bundle_ids, @unix_ids).each do |app_name, bundle_id, _unix_id|
+ next if excluded_bundle_id bundle_id
+ next if excluded_app_name app_name
+ bundle_id.gsub!(%r{^(missing value)$}, '<\1>')
+ running.add "#{bundle_id}\t#{app_name}"
+ end
+
+ puts "bundle_id\tapp_name\n"
+ puts "--------------------------------------\n"
+ puts running.to_a.sort
+end
+
+###
+### main
+###
+
+check_ruby
+process_args
+load_apps
+
+if $opt_test
+ exit test_app($opt_test)
+else
+ report_apps
+end
diff --git a/Library/Homebrew/cask/developer/bin/list_url_attributes_on_file b/Library/Homebrew/cask/developer/bin/list_url_attributes_on_file
new file mode 100755
index 000000000..e59497656
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/list_url_attributes_on_file
@@ -0,0 +1,84 @@
+#!/bin/bash
+#
+# list_url_attributes_on_file
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### global variables
+###
+
+file_arg=''
+attribute_1='com.apple.metadata:kMDItemWhereFroms'
+
+###
+### functions
+###
+
+warn () {
+ local message="$@"
+ message="${message//\\t/$'\011'}"
+ message="${message//\\n/$'\012'}"
+ message="${message%"${message##*[![:space:]]}"}"
+ printf "%s\n" "$message" 1>&2
+}
+
+die () {
+ warn "$@"
+ exit 1
+}
+
+xml_source_1 () {
+ if /usr/bin/xattr -p "$attribute_1" "$file_arg" > /dev/null 2>&1; then
+ /usr/bin/xattr -p "$attribute_1" "$file_arg" | /usr/bin/xxd -r -p | /usr/bin/plutil -convert xml1 -o - - 2> /dev/null
+ fi
+}
+
+extract_string_elements () {
+ /usr/bin/perl -ne 'print "$1\n" if m{\A\s*<\s*string\s*>(.+?)<\s*/\s*string\s*>\Z}'
+}
+
+###
+### main
+###
+
+_list_url_attributes_on_file () {
+ file_arg="$1"
+ {
+ xml_source_1;
+ } | \
+ extract_string_elements
+}
+
+# process_args
+if [[ $1 =~ ^-+h(elp)?$ || -z "$1" ]]; then
+ printf "list_url_attributes_on_file <file>
+
+Given a downloaded file, extract possible sources from macOS extended
+attributes, which may be useful in a Cask url stanza.
+
+Currently the only attribute examined is
+
+ com.apple.metadata:kMDItemWhereFroms
+
+This attribute will typically be set if the file was downloaded via a
+browser, but not if the file was downloaded by a CLI utility such as
+curl.
+
+See CONTRIBUTING.md for more information.
+
+"
+ exit
+fi
+
+# dispatch main
+_list_url_attributes_on_file "${@}"
+
+#
diff --git a/Library/Homebrew/cask/developer/bin/merge_outdated_appcasts b/Library/Homebrew/cask/developer/bin/merge_outdated_appcasts
new file mode 100755
index 000000000..0c2b55a89
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/merge_outdated_appcasts
@@ -0,0 +1,99 @@
+#!/bin/bash
+
+IFS=$'\n'
+
+readonly caskroom_online='https://github.com/caskroom'
+readonly caskroom_repos_dir='/tmp/caskroom_repos'
+readonly caskroom_repos=(homebrew-cask homebrew-versions homebrew-fonts homebrew-eid)
+
+if [[ ! $(which 'ghi') ]] || ! security find-internet-password -s github.com -l 'ghi token' &> /dev/null; then
+ echo -e "$(tput setaf 1)
+ This script requires 'ghi' installed and configured.
+ If you have [Homebrew](http://brew.sh), you can install it with 'brew install ghi'.
+ To configure it, run 'ghi config --auth <username>'. Your Github password will be required, but is never stored.
+ $(tput sgr0)" | sed -E 's/ {4}//' >&2
+ exit 1
+fi
+
+if [[ ! $(which 'fastmerge') ]]; then
+ echo -e "$(tput setaf 1)
+ This script requires 'fastmerge'.
+ If you have [Homebrew](http://brew.sh), you can install it with 'brew install vitorgalvao/tiny-scripts/fastmerge'.
+ $(tput sgr0)" | sed -E 's/ {4}//' >&2
+ exit 1
+fi
+
+function message {
+ echo "${1}"
+}
+
+function go_to_repos_dir {
+ [[ ! -d "${caskroom_repos_dir}" ]] && mkdir -p "${caskroom_repos_dir}"
+ cd "${caskroom_repos_dir}" || exit 1
+}
+
+function go_to_repo_and_update {
+ local repo_name repo_dir casks_dir
+
+ repo_name="${1}"
+ repo_dir="${caskroom_repos_dir}/${repo_name}"
+ casks_dir="${repo_dir}/Casks"
+
+ if [[ ! -d "${repo_dir}" ]]; then
+ go_to_repos_dir
+
+ message "Cloning ${repo_name}…"
+ git clone "https://github.com/caskroom/${repo_name}.git" --quiet
+
+ cd "${casks_dir}" || exit 1
+ else
+ cd "${casks_dir}" || exit 1
+
+ message "Updating ${repo_name}…"
+ git pull --rebase origin master --quiet
+ fi
+}
+
+function delete_current_branch {
+ local current_branch
+
+ current_branch="$(git rev-parse --abbrev-ref HEAD)"
+ git checkout master --quiet
+ git branch -D "${current_branch}" --quiet
+}
+
+function delete_cask_repair_branches {
+ [[ $(ghi list --state open --pulls --label 'outdated appcast' | tail -1) == 'None.' ]] && cask-repair --push origin --delete-branches
+}
+
+function merge_outdated_appcasts {
+ local repo_name pr_number cask_name pr_url last_commit
+
+ repo_name="${1}"
+
+ for line in $(ghi list --state open --pulls --label 'outdated appcast' --reverse | tail +2); do
+ [[ "${line}" == 'None.' ]] && break # exit early if there are no relevant issues in repo
+
+ pr_number="$(awk '{print $1}' <<< "${line}")"
+ cask_name="$(awk '{print $3}' <<< "${line}")"
+ pr_url="${caskroom_online}/${repo_name}/pull/${pr_number}"
+
+ hub checkout "${pr_url}" &>/dev/null
+ last_commit="$(git log -n 1 --pretty=format:'%H')"
+ delete_current_branch
+
+ if [[ "$(hub ci-status "${last_commit}")" == 'success' ]]; then
+ message "Merging pull request for ${cask_name}…"
+ fastmerge --maintainer --remote origin "${pr_url}"
+ else
+ continue
+ fi
+ done
+}
+
+for repo in "${caskroom_repos[@]}"; do
+ go_to_repo_and_update "${repo}"
+ merge_outdated_appcasts "${repo}"
+ delete_cask_repair_branches
+ git gc
+done
diff --git a/Library/Homebrew/cask/developer/bin/production_brew_cask b/Library/Homebrew/cask/developer/bin/production_brew_cask
new file mode 120000
index 000000000..4afaf77a6
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/production_brew_cask
@@ -0,0 +1 @@
+develop_brew_cask \ No newline at end of file
diff --git a/Library/Homebrew/cask/developer/bin/project_stats b/Library/Homebrew/cask/developer/bin/project_stats
new file mode 100755
index 000000000..652b6d7b3
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/project_stats
@@ -0,0 +1,234 @@
+#!/bin/bash
+#
+# project_stats
+#
+# stats on project/release from git database
+#
+
+###
+### settings
+###
+
+set -e # exit on any uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### configurable global variables
+###
+
+# these paths relative to project root
+declare -a cask_paths=(Casks)
+declare -a code_paths=(bin developer lib spec test brew-cask.rb Rakefile Gemfile Gemfile.lock .travis.yml .gitignore)
+declare -a doc_paths=(doc LICENSE "*.md")
+end_object="HEAD"
+
+###
+### global variables
+###
+
+cask_authors=''
+
+# prefer GNU xargs
+xargs="$(/usr/bin/which gxargs || printf '/usr/bin/xargs')"
+
+###
+### functions
+###
+
+warn () {
+ local message="$@"
+ message="${message//\\t/$'\011'}"
+ message="${message//\\n/$'\012'}"
+ message="${message%"${message##*[![:space:]]}"}"
+ printf "%s\n" "$message" 1>&2
+}
+
+die () {
+ warn "$@"
+ exit 1
+}
+
+cd_to_project_root () {
+ local script_dir="$(/usr/bin/dirname "$0")"
+ cd "$script_dir"
+ local git_root="$(git rev-parse --show-toplevel)"
+ if [[ -z "$git_root" ]]; then
+ die "ERROR: Could not find git project root"
+ fi
+ cd "$git_root"
+}
+
+warn_if_off_branch () {
+ local wanted_branch='master'
+ if [[ -n "$1" ]]; then
+ wanted_branch="$1"
+ fi
+
+ local current_branch="$(git rev-parse --abbrev-ref HEAD)"
+ if ! [[ "$current_branch" = "$wanted_branch" ]]; then
+ warn "\nWARNING: you are running from branch '$current_branch', not '$wanted_branch'\n\n"
+ fi
+}
+
+verify_git_object () {
+ local object="$1"
+
+ if ! git rev-parse --verify "$object" -- >/dev/null 2>&1; then
+ die "\nERROR: No such commit object: '$object'\n\n"
+ fi
+}
+
+print_contributor_stats () {
+ local start_object="$1"
+ local initial_commit="$2"
+
+ printf "====================\n"
+ printf "Contributors\n"
+ printf "====================\n"
+
+ local -a git_log_cmd=("git" "log" "--no-merges" "--format='%ae'" "${start_object}..${end_object}")
+ printf "Unique contributors"
+ if ! [[ "$start_object" = "$initial_commit" ]]; then
+ printf " since %s" "${start_object#v}"
+ fi
+ printf "\n"
+ cask_authors="$("${git_log_cmd[@]}" -- "${cask_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)"
+ printf " Casks\t%s\n" "$cask_authors"
+ printf " code\t"
+ "${git_log_cmd[@]}" -- "${code_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l
+ printf " docs\t"
+ "${git_log_cmd[@]}" -- "${doc_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l
+ printf " any\t"
+ "${git_log_cmd[@]}" -- . | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l
+ if ! [[ "$start_object" = "$initial_commit" ]]; then
+ local alltime_contribs="$(git log --no-merges --format='%ae' "${initial_commit}".."${end_object}" -- . | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)"
+ local prior_contribs="$(git log --no-merges --format='%ae' "${initial_commit}".."${start_object}" -- . | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)"
+ # arithmetic removes whitespace
+ ((alltime_contribs += 0))
+ ((new_contribs = alltime_contribs - prior_contribs))
+ printf "\nAll-time contributors\t%s\n" "$alltime_contribs"
+ printf "New contributors since %s\t%s\n" "${start_object#v}" "$new_contribs"
+ fi
+ printf "\n"
+}
+
+print_commit_stats () {
+ local start_object="$1"
+ local initial_commit="$2"
+
+ printf "====================\n"
+ printf "Commits\n"
+ printf "====================\n"
+
+ local -a git_log_cmd=("git" "log" "--no-merges" "--format='%ae'" "${start_object}..${end_object}")
+ printf "Commit count"
+ if ! [[ "$start_object" = "$initial_commit" ]]; then
+ printf " since %s" "${start_object#v}"
+ fi
+ printf "\n"
+ printf " Casks\t"
+ "${git_log_cmd[@]}" -- "${cask_paths[@]}" | /usr/bin/wc -l
+ printf " code\t"
+ "${git_log_cmd[@]}" -- "${code_paths[@]}" | /usr/bin/wc -l
+ printf " docs\t"
+ "${git_log_cmd[@]}" -- "${doc_paths[@]}" | /usr/bin/wc -l
+ printf " any\t"
+ "${git_log_cmd[@]}" -- . | /usr/bin/wc -l
+ if ! [[ "$start_object" = "$initial_commit" ]]; then
+ printf "\nAll-time commits\t"
+ git log --no-merges --format='%ae' "${initial_commit}".."${end_object}" -- . | /usr/bin/wc -l
+ fi
+ printf "\n"
+}
+
+print_doc_stats () {
+ local start_object="$1"
+ local initial_commit="$2"
+
+ printf "====================\n"
+ printf "Docs\n"
+ printf "====================\n"
+
+ local -a git_log_cmd=("git" "log" "--no-merges" "--format='%ae'" "${start_object}..${end_object}")
+ printf "Doc contributors"
+ if ! [[ "$start_object" = "$initial_commit" ]]; then
+ printf " since %s" "${start_object#v}"
+ fi
+ printf "\n "
+ "${git_log_cmd[@]}" -- "${doc_paths[@]}" | /usr/bin/sort | /usr/bin/uniq | \
+ /usr/bin/egrep -v $'^\'(paul\\.t\\.hinze@gmail\\.com|fanquake@users\\.noreply\\.github\\.com|fanquake@gmail\\.com|info@vitorgalvao\\.com|calebcenter@live\\.com|hagins\\.josh@gmail\\.com|dragon\\.vctr@gmail\\.com|github@adityadalal\\.com|adityadalal924@users\\.noreply\\.github\\.com)\'$' | \
+ "$xargs" | /usr/bin/perl -pe 's{ }{, }g' # '
+ printf "\n"
+}
+
+print_cask_stats () {
+ local start_object="$1"
+ local initial_commit="$2"
+
+ printf "====================\n"
+ printf "Casks\n"
+ printf "====================\n"
+
+ if ! [[ "$start_object" = "$initial_commit" ]]; then
+ local new_casks="$(git diff --name-status "$start_object" "$end_object" -- "${cask_paths[@]}" | /usr/bin/grep '^A.*\.rb' | cut -f2 | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)"
+ local deleted_casks="$(git diff --name-status "$start_object" "$end_object" -- "${cask_paths[@]}" | /usr/bin/grep '^D.*\.rb' | cut -f2 | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)"
+ local updated_casks="$(git diff --name-status "$start_object" "$end_object" -- "${cask_paths[@]}" | /usr/bin/grep '^M.*\.rb' | cut -f2 | /usr/bin/sort | /usr/bin/uniq | /usr/bin/wc -l)"
+ # arithmetic removes whitespace
+ ((cask_authors += 0))
+ ((deleted_casks += 0))
+ ((new_casks -= deleted_casks))
+ ((updated_casks += 0))
+ printf "%s Casks added (%s updated) by %s contributors since %s\n" "$new_casks" "$updated_casks" "$cask_authors" "${start_object#v}"
+ fi
+
+ printf "Total current Casks in HEAD\t"
+ /usr/bin/find "${cask_paths[@]}" -name '*.rb' | /usr/bin/wc -l
+ printf "\n"
+}
+
+###
+### main
+###
+
+_project_stats () {
+ local arg_object="$1"
+
+ cd_to_project_root
+ warn_if_off_branch 'master'
+
+ local initial_commit="$(git log --pretty=format:%H -- | /usr/bin/tail -1)"
+ verify_git_object "$initial_commit"
+ local start_object="$initial_commit"
+
+ if [[ "$arg_object" = 'release' ]]; then
+ start_object="$(./developer/bin/get_release_tag)"
+ elif [[ -n "$arg_object" ]]; then
+ start_object="$arg_object"
+ fi
+ verify_git_object "$start_object"
+
+ print_contributor_stats "$start_object" "$initial_commit"
+ print_commit_stats "$start_object" "$initial_commit"
+ print_doc_stats "$start_object" "$initial_commit"
+ print_cask_stats "$start_object" "$initial_commit"
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ ]]; then
+ printf "project_stats [ <commit-object> ]
+
+With optional single argument, (eg a tag or commit-hash)
+show statistics since that commit object.
+
+Use the special argument 'release' to calculate since the
+most recent tag (usually the same as the last release).
+
+Without argument, show statistics since first commit.
+
+"
+ exit
+fi
+
+# dispatch main
+_project_stats "${@}"
diff --git a/Library/Homebrew/cask/developer/bin/the_long_tail b/Library/Homebrew/cask/developer/bin/the_long_tail
new file mode 100755
index 000000000..2a4cb8e7c
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/the_long_tail
@@ -0,0 +1,252 @@
+#!/usr/bin/env ruby
+#
+# the_long_tail
+#
+# A histogram view on contributor stats
+#
+# notes
+#
+# Since this script does not track file-renames in the git history, the
+# dependence of Casks upon occasional contributors/non-maintainers can
+# only be expressed as a range or lower bound.
+#
+
+###
+### dependencies
+###
+
+require "open3"
+require "set"
+
+###
+### configurable constants
+###
+
+BINS = [
+ (1..10).to_a,
+ 100,
+ 1000,
+ ].flatten
+
+OCCASIONAL_CUTOFF = 5
+
+CASK_PATH = "Casks".freeze
+
+# all maintainers, past and present
+MAINTAINERS = %w[
+ paul.t.hinze@gmail.com
+ fanquake@users.noreply.github.com
+ fanquake@gmail.com
+ kevin@suttle.io
+ leoj3n@gmail.com
+ nano@fdp.io
+ nanoid.xd@gmail.com
+ me@passcod.name
+ walker@pobox.com
+ info@vitorgalvao.com
+ calebcenter@live.com
+ ndr@qef.io
+ josh@joshbutts.com
+ goxberry@gmail.com
+ radek.simko@gmail.com
+ federicobond@gmail.com
+ claui@users.noreply.github.com
+ amorymeltzer@gmail.com
+ hagins.josh@gmail.com
+ dragon.vctr@gmail.com
+ mail@sebastianroeder.de
+ github@adityadalal.com
+ adityadalal924@users.noreply.github.com
+ ].freeze
+
+###
+### git methods
+###
+
+def cd_to_project_root
+ Dir.chdir File.dirname(File.expand_path(__FILE__))
+ @git_root ||= Open3.popen3(*%w[
+ git rev-parse --show-toplevel
+ ]) do |_stdin, stdout, _stderr|
+ begin
+ stdout.gets.chomp
+ rescue
+ end
+ end
+ Dir.chdir @git_root
+ @git_root
+end
+
+def authors
+ @authors ||= Open3.popen3(*%w[
+ git log --no-merges --format=%ae --
+ ]) do |_stdin, stdout, _stderr|
+ h = {}
+ stdout.each_line do |line|
+ line.chomp!
+ h[line] ||= 0
+ h[line] += 1
+ end
+ h
+ end
+end
+
+def casks_by_author
+ @casks_by_author ||= Open3.popen3(*%w[
+ git log --no-merges --name-only --format=%ae --
+ ],
+ CASK_PATH) do |_stdin, stdout, _stderr|
+ email = nil
+ h = {}
+ stdout.each_line.to_a.join("").split("\n\n").each do |paragraph|
+ if paragraph.include?("Casks/")
+ lines = paragraph.split("\n")
+ email = lines.pop
+ h[email] ||= Set.new
+ h[email].merge(lines.compact)
+ else
+ email = paragraph.chomp
+ end
+ end
+ h
+ end
+end
+
+###
+### filesystem methods
+###
+
+def all_casks
+ @all_casks ||= Open3.popen2("/usr/bin/find",
+ CASK_PATH,
+ *%w[-type f -name *.rb]) do |_stdin, stdout|
+ stdout.each_line.map(&:chomp)
+ end
+end
+
+###
+### analysis and report methods
+###
+
+def histogram
+ if @histogram.nil?
+ @histogram = Hash[*BINS.map { |elt| [elt, 0] }.flatten]
+ authors.each do |_name, num_commits|
+ bottom = 0
+ BINS.each do |top|
+ @histogram[bottom] += 1 if num_commits >= bottom && num_commits < top
+ bottom = top
+ end
+ end
+ end
+ @histogram
+end
+
+def historic_occasional_cask_set
+ @historic_occasional_cask_set = authors.each.collect do |name, num_commits|
+ if num_commits > OCCASIONAL_CUTOFF
+ nil
+ elsif !casks_by_author.key?(name)
+ nil
+ else
+ casks_by_author[name].to_a
+ end
+ end.flatten.compact.to_set
+end
+
+def extant_occasional_cask_count
+ # avoid double-counting renames by intersecting with extant Casks
+ historic_occasional_cask_set.intersection(all_casks).count
+end
+
+def historic_nonmaintainer_cask_set
+ @historic_nonmaintainer_cask_set = authors.each.collect do |name, _num_commits|
+ if MAINTAINERS.include?(name)
+ nil
+ else
+ casks_by_author[name].to_a
+ end
+ end.flatten.compact.to_set
+end
+
+def extant_nonmaintainer_cask_count
+ # avoid double-counting renames by intersecting with extant Casks
+ historic_nonmaintainer_cask_set.intersection(all_casks).count
+end
+
+def extant_occasional_cask_percentage
+ @extant_occasional_cask_percentage ||= (100 * extant_occasional_cask_count / all_casks.count).to_i
+end
+
+def historic_occasional_cask_percentage
+ @historic_occasional_cask_percentage ||= (100 * historic_occasional_cask_set.count / all_casks.count).to_i
+end
+
+def extant_nonmaintainer_cask_percentage
+ @extant_nonmaintainer_cask_percentage ||= (100 * extant_nonmaintainer_cask_count / all_casks.count).to_i
+end
+
+def historic_nonmaintainer_cask_percentage
+ # this is so large, it might cross 100%
+ @historic_nonmaintainer_cask_percentage ||= [100, (100 * historic_nonmaintainer_cask_set.count / all_casks.count).to_i].min
+end
+
+def onetime_author_percentage
+ @onetime_author_percentage ||= (100 *
+ histogram[1] /
+ authors.length).to_i
+end
+
+def occasional_author_percentage
+ # why is it so hard to slice a hash?
+ @occasional_author_percentage ||= (100 *
+ (1..OCCASIONAL_CUTOFF).to_a.collect { |bin| histogram[bin] }.reduce(:+) /
+ authors.length).to_i
+end
+
+def graph_width
+ if @graph_width.nil?
+ @graph_width = `/bin/stty size 2>/dev/null`.chomp.split(" ").last.to_i
+ @graph_width = 80 if @graph_width <= 0
+ @graph_width -= 20 if @graph_width > 20
+ end
+ @graph_width
+end
+
+def graph_normalization
+ @graph_normalization ||= histogram.values.max.to_f
+end
+
+def print_header
+ puts "Commits\tContributors"
+ puts "---------------------"
+end
+
+def print_table
+ BINS.each do |bin|
+ plural = (bin % 10) == 0 ? "'s" : ""
+ graph = "." * ((histogram[bin] / graph_normalization) * graph_width)
+ puts "#{bin}#{plural}\t#{histogram[bin]}\t#{graph}"
+ end
+end
+
+def print_footer
+ puts %Q{\n#{occasional_author_percentage}% of contributors are "occasional" (with <= #{OCCASIONAL_CUTOFF} commits)}
+ puts "\n#{onetime_author_percentage}% of contributors commit only once"
+ puts "\n#{extant_occasional_cask_percentage}% - #{historic_occasional_cask_percentage}% of Casks depend on an occasional contributor"
+ puts "\n#{extant_nonmaintainer_cask_percentage}% - #{historic_nonmaintainer_cask_percentage}% of Casks depend on a contributor who is not a maintainer"
+ puts "\n"
+end
+
+def generate_report
+ print_header
+ print_table
+ print_footer
+end
+
+###
+### main
+###
+
+cd_to_project_root
+generate_report
diff --git a/Library/Homebrew/cask/developer/bin/update_issue_template_urls b/Library/Homebrew/cask/developer/bin/update_issue_template_urls
new file mode 100755
index 000000000..8e61ff69e
--- /dev/null
+++ b/Library/Homebrew/cask/developer/bin/update_issue_template_urls
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+#
+# update_issue_template_urls
+#
+
+###
+### settings
+###
+
+set -e # exit on uncaught error
+set +o histexpand # don't expand history expressions
+shopt -s nocasematch # case-insensitive regular expressions
+
+###
+### constants
+###
+
+script_subdir='developer/bin'
+template_subdir='doc/issue_templates'
+generate_url_script='generate_issue_template_urls'
+files_to_update=('README.md')
+
+###
+### functions
+###
+
+warn () {
+ local message="$*"
+ message="${message//\\t/$'\011'}"
+ message="${message//\\n/$'\012'}"
+ message="${message%${message##*[![:space:]]}}"
+ printf "%s\n" "$message" 1>&2
+}
+
+die () {
+ warn "$@"
+ exit 1
+}
+
+usage () {
+ printf "update_issue_template_urls
+
+Regenerate issue template URLs and update them in relevant files.
+
+Note: docs using issue template URLs must use a specific format.
+If the template file is called 'bug_report.md', the URL must be
+referenced in the doc as follows:
+
+ [Some link text][bug_report_template]
+
+ ...
+
+ [bug_report_template]: (auto-generated-url)
+
+"
+}
+
+cd_to_project_root () {
+ local script_dir git_root
+ script_dir="$(/usr/bin/dirname "$0")"
+ cd "$script_dir"
+ git_root="$(git rev-parse --show-toplevel)"
+ if [[ -z "$git_root" ]]; then
+ die "ERROR: Could not find git project root"
+ fi
+ cd "$git_root"
+}
+
+generate_template_url () {
+ local template_file="$1"
+ "$script_subdir/$generate_url_script" "$template_file"
+}
+
+update_template_url () {
+ local template_name="$1"
+ local template_url="$2"
+ local escaped_template_url="${template_url/&/\\&}"
+
+ /usr/bin/sed -i '' \
+ -e "s|^\\(\\[${template_name}_template\\]: \\).*$|\\1${escaped_template_url}|g" \
+ -- "${files_to_update[@]}"
+}
+
+###
+### main
+###
+
+_update_issue_template_urls () {
+ local template_file template_name template_url
+ cd_to_project_root
+ for template_file in ./$template_subdir/*; do
+ template_name="${template_file##*/}"
+ template_name="${template_name%%.*}"
+ template_url="$(generate_template_url "$template_file")"
+ update_template_url "$template_name" "$template_url"
+ done
+}
+
+# process args
+if [[ $1 =~ ^-+h(elp)?$ ]]; then
+ usage
+ exit
+fi
+
+# dispatch main
+_update_issue_template_urls