From e81f4ab7deeb40308f240be5ea00091fc8786d7a Mon Sep 17 00:00:00 2001 From: AnastasiaSulyagina Date: Thu, 18 Aug 2016 22:11:42 +0300 Subject: init --- .../Homebrew/cask/developer/bin/cask-switch-https | 190 ++++++++++ .../Homebrew/cask/developer/bin/develop_brew_cask | 199 ++++++++++ .../cask/developer/bin/find_outdated_appcasts | 121 ++++++ .../cask/developer/bin/find_sparkle_appcast | 68 ++++ .../cask/developer/bin/fix_outdated_appcasts | 78 ++++ .../cask/developer/bin/generate_cask_token | 418 +++++++++++++++++++++ .../developer/bin/generate_issue_template_urls | 74 ++++ .../cask/developer/bin/irregular_cask_whitespace | 133 +++++++ .../Homebrew/cask/developer/bin/list_apps_in_pkg | 166 ++++++++ .../Homebrew/cask/developer/bin/list_id_in_kext | 93 +++++ .../Homebrew/cask/developer/bin/list_ids_in_app | 162 ++++++++ .../Homebrew/cask/developer/bin/list_ids_in_pkg | 115 ++++++ .../developer/bin/list_installed_launchjob_ids | 90 +++++ .../cask/developer/bin/list_loaded_kext_ids | 45 +++ .../cask/developer/bin/list_loaded_launchjob_ids | 84 +++++ .../cask/developer/bin/list_login_items_for_app | 64 ++++ .../cask/developer/bin/list_payload_in_pkg | 126 +++++++ .../cask/developer/bin/list_pkg_ids_by_regexp | 83 ++++ .../cask/developer/bin/list_recent_pkg_ids | 46 +++ .../cask/developer/bin/list_running_app_ids | 115 ++++++ .../cask/developer/bin/list_url_attributes_on_file | 84 +++++ .../cask/developer/bin/merge_outdated_appcasts | 99 +++++ .../cask/developer/bin/production_brew_cask | 1 + Library/Homebrew/cask/developer/bin/project_stats | 234 ++++++++++++ Library/Homebrew/cask/developer/bin/the_long_tail | 252 +++++++++++++ .../cask/developer/bin/update_issue_template_urls | 106 ++++++ .../cask/developer/examples/brewcask-doutdated.rb | 43 +++ .../cask/developer/examples/brewcask-dumpcask.rb | 16 + .../cask/developer/examples/brewcask-showargs | 16 + 29 files changed, 3321 insertions(+) create mode 100755 Library/Homebrew/cask/developer/bin/cask-switch-https create mode 100755 Library/Homebrew/cask/developer/bin/develop_brew_cask create mode 100755 Library/Homebrew/cask/developer/bin/find_outdated_appcasts create mode 100755 Library/Homebrew/cask/developer/bin/find_sparkle_appcast create mode 100755 Library/Homebrew/cask/developer/bin/fix_outdated_appcasts create mode 100755 Library/Homebrew/cask/developer/bin/generate_cask_token create mode 100755 Library/Homebrew/cask/developer/bin/generate_issue_template_urls create mode 100755 Library/Homebrew/cask/developer/bin/irregular_cask_whitespace create mode 100755 Library/Homebrew/cask/developer/bin/list_apps_in_pkg create mode 100755 Library/Homebrew/cask/developer/bin/list_id_in_kext create mode 100755 Library/Homebrew/cask/developer/bin/list_ids_in_app create mode 100755 Library/Homebrew/cask/developer/bin/list_ids_in_pkg create mode 100755 Library/Homebrew/cask/developer/bin/list_installed_launchjob_ids create mode 100755 Library/Homebrew/cask/developer/bin/list_loaded_kext_ids create mode 100755 Library/Homebrew/cask/developer/bin/list_loaded_launchjob_ids create mode 100755 Library/Homebrew/cask/developer/bin/list_login_items_for_app create mode 100755 Library/Homebrew/cask/developer/bin/list_payload_in_pkg create mode 100755 Library/Homebrew/cask/developer/bin/list_pkg_ids_by_regexp create mode 100755 Library/Homebrew/cask/developer/bin/list_recent_pkg_ids create mode 100755 Library/Homebrew/cask/developer/bin/list_running_app_ids create mode 100755 Library/Homebrew/cask/developer/bin/list_url_attributes_on_file create mode 100755 Library/Homebrew/cask/developer/bin/merge_outdated_appcasts create mode 120000 Library/Homebrew/cask/developer/bin/production_brew_cask create mode 100755 Library/Homebrew/cask/developer/bin/project_stats create mode 100755 Library/Homebrew/cask/developer/bin/the_long_tail create mode 100755 Library/Homebrew/cask/developer/bin/update_issue_template_urls create mode 100755 Library/Homebrew/cask/developer/examples/brewcask-doutdated.rb create mode 100755 Library/Homebrew/cask/developer/examples/brewcask-dumpcask.rb create mode 100755 Library/Homebrew/cask/developer/examples/brewcask-showargs (limited to 'Library/Homebrew/cask/developer') 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] [] + 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 '. 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|[^<]*||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} " + 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|[^<]*||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 '. 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 ] + +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 ... + +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 + + Find irregular whitespace in Cask files within + +" + 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{ + +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 + +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 + +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{/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 + +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{\s*Label\s*\s*([^<]+?)}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 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 + +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 + +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 + +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 ] + +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 ", 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 + +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 '. 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 [ ] + +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 diff --git a/Library/Homebrew/cask/developer/examples/brewcask-doutdated.rb b/Library/Homebrew/cask/developer/examples/brewcask-doutdated.rb new file mode 100755 index 000000000..de0234c2f --- /dev/null +++ b/Library/Homebrew/cask/developer/examples/brewcask-doutdated.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +# +# Generously contributed by Markus Doits +# https://github.com/doits +# (c) 2014 MIT license +# + +require "rubygems" + +class Hbc + def installed_version? + !installed_version.nil? + end + + def installed_version + # returns latest installed version if possible + + Pathname.glob(caskroom_path.join("*")).map(&:basename).sort do |x, y| + Gem::Version.new(x) <=> Gem::Version.new(y) # throws exception if invalid version is provided ... + end.last + rescue + nil + # ... return nil in this case + end + + def update_available? + Gem::Version.correct?(version) && # we have something to compare against in Cask file ... + installed_version? && # ... we can determine current installed version ... + Gem::Version.new(installed_version) < Gem::Version.new(version) # ... compare + end +end + +module Hbc::Scopes + module ClassMethods + def upgradable + Hbc.installed.select(&:update_available?) + end + end +end + +upgradable_casks = Hbc.upgradable + +puts upgradable_casks.empty? && "No outdated packages" || upgradable_casks diff --git a/Library/Homebrew/cask/developer/examples/brewcask-dumpcask.rb b/Library/Homebrew/cask/developer/examples/brewcask-dumpcask.rb new file mode 100755 index 000000000..8e209f941 --- /dev/null +++ b/Library/Homebrew/cask/developer/examples/brewcask-dumpcask.rb @@ -0,0 +1,16 @@ +# brewcask-dumpcask +# +# A trivial `brew cask` external command, implemented in Ruby. +# Loads a Cask definition, then dumps it in YAML format. +# Example usage: +# +# brew cask dumpcask google-chrome +# + +command_name = ARGV.shift +cask_token = ARGV.shift + +cask = Hbc.load(cask_token) + +Hbc.debug = true +cask.dumpcask diff --git a/Library/Homebrew/cask/developer/examples/brewcask-showargs b/Library/Homebrew/cask/developer/examples/brewcask-showargs new file mode 100755 index 000000000..9f9d06dcb --- /dev/null +++ b/Library/Homebrew/cask/developer/examples/brewcask-showargs @@ -0,0 +1,16 @@ +#!/bin/bash +# +# brewcask-showargs +# +# A trivial `brew cask` external command, implemented in bash. +# Displays the arguments passed to it. Example usage: +# +# brew cask showargs these were my args +# + +set -e; # exit on uncaught error +set +o histexpand; # don't expand history expressions + +echo "$@"; + +# -- cgit v1.2.3