├── .gitignore ├── LICENSE ├── README.md ├── bitrise └── download-recent-error-logs.sh ├── carthage ├── checkout ├── search-bcsymbolmap ├── update └── update-with-hack.sh ├── fabric └── download-ipa.sh ├── git ├── blame-word ├── brdmerged ├── brset ├── git-branch-by-author ├── replaceOriginWith.sh ├── take-ours └── take-theirs ├── github ├── add-collab ├── find-pull-requests ├── pr-approve ├── pr-check-milestone ├── pr-ready-for-review ├── pro └── update-comment ├── image ├── pixelSize.sh └── sips-assets.sh ├── ios └── resize-appicon.sh ├── media └── hls │ └── extract-master-playlist ├── net ├── url_decode └── url_encode ├── snippet └── gen │ ├── color-printf │ └── swift-json-decode ├── swiftpm ├── install.sh └── release.sh ├── tools └── sync-file.sh ├── twitter └── bearer └── xcode ├── build-time.sh ├── check-build-time.sh ├── check-expiring-mobileprovision.sh ├── find-error.sh ├── fix-framework-version.sh ├── latest-build-time.sh ├── open-device-support-dir.sh ├── pbpasteReplayArgs.sh ├── plugins ├── revert-xquick.scpt ├── xcode_toggleFlux_backward.scpt ├── xcode_toggleFlux_forward.scpt ├── xcode_toggleImplAndTests.scpt └── xquick.scpt ├── printUUIDofMobileprovision.sh ├── sort-Xcode-project-file ├── sort-xcpretty-by-time ├── sync-device-support-dir.sh ├── total-test-duration └── xquick.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Toshihiro Suzuki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scripts 2 | Handy scripts for productive iOS/tvOS/Mac developers! 3 | 4 | Also checkout my cousins and brothers. I've converted some useful but complex ShellScripts to simple Swift CLI tools. 5 | 6 | - hackscode: https://github.com/toshi0383/hackscode 7 | - GitHub at work: https://github.com/toshi0383/ghaw 8 | 9 | # Install 10 | ## Use cmdshelf 11 | 12 | ```bash 13 | cmdshelf add remote toshi0383 git@github.com:toshi0383/scripts.git 14 | cmdshelf run xcode/open-device-support-dir.sh tvos 15 | ``` 16 | 17 | https://github.com/toshi0383/cmdshelf 18 | 19 | # Contents 20 | 21 | # bitrise 22 | ## download-recent-error-logs.sh 23 | TBD 24 | 25 | # carthage 26 | ## carthage/checkout 27 | TBD 28 | 29 | ## carthage/search-bcsymbolmap 30 | TBD 31 | 32 | ## carthage/update 33 | TBD 34 | 35 | ## carthage/update-with-hack.sh 36 | TBD 37 | 38 | # git 39 | ## git/brset 40 | Set upstream remote branch to `origin/$current_branch`. 41 | 42 | ## git/git-branch-by-author 43 | TBD 44 | 45 | ## git/replaceOriginWith.sh 46 | Change `origin` to `upstream` and add `$1` as `origin`. 47 | Useful when you cloned somebody's repo, and later forked and want to push to your fork repo. 48 | 49 | ## git/take-ours 50 | Resolve merge conflict by removing everything from their change. 51 | 52 | ## git/take-theirs 53 | Resolve merge conflict by removing everything from our change. 54 | 55 | # github 56 | 57 | ## github/pr-approve 58 | Approve a pull-request number `$1`. 59 | 60 | ## github/pr-check-milestone 61 | TBD 62 | 63 | ## github/pr-ready-for-review 64 | List pull-requests which you haven't approved and not WIP. 65 | 66 | ## github/pro 67 | Open PR like `hub pull-request`, but also supports milestone and labels. 68 | 69 | ## github/update-comment 70 | Add or update PR/issue comment (like Danger). 71 | 72 | # image 73 | ## image/pixelSize.sh 74 | TBD 75 | 76 | ## image/sips-assets.sh 77 | Generate given size of @1x,@2x,@3x variants using `sips` utility. 78 | 79 | # ios 80 | ## ios/resize-appicon.sh 81 | Resizing image for App Icon image assets. 82 | 83 | # media/hls 84 | ## media/hls/extract-master-playlist 85 | TBD 86 | 87 | # net 88 | ## net/url_encode 89 | TBD 90 | 91 | # snippet/gen 92 | ## snippet/gen/color-printf 93 | TBD 94 | 95 | # swiftpm 96 | ## swiftpm/install.sh 97 | TBD 98 | 99 | ## swiftpm/release.sh 100 | TBD 101 | 102 | # twitter 103 | ## twitter/bearer 104 | TBD 105 | 106 | # xcode 107 | ## xcode/build-time.sh 108 | TBD 109 | 110 | ## xcode/latest-build-time.sh 111 | TBD 112 | 113 | ## xcode/check-build-time.sh 114 | TBD 115 | 116 | ## xcode/find-error.sh 117 | TBD 118 | 119 | ## xcode/fix-framework-version.sh 120 | TBD 121 | 122 | ## xcode/open-device-support-dir.sh 123 | TBD 124 | 125 | ## xcode/printUUIDofMobileprovision.sh 126 | TBD 127 | 128 | ## xcode/sort-Xcode-project-file 129 | TBD 130 | 131 | ## xcode/sort-xcpretty-by-time 132 | TBD 133 | 134 | ## xcode/sync-device-support-dir.sh 135 | TBD 136 | 137 | ## xcode/total-test-duration 138 | TBD 139 | 140 | # xcode/plugins 141 | 142 | ## xcode/plugins/xcode_toggleFlux_backword.scpt 143 | TBD 144 | 145 | ## xcode/plugins/xcode_toggleFlux_forward.scpt 146 | TBD 147 | 148 | -------------------------------------------------------------------------------- /bitrise/download-recent-error-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # description: 4 | # Download recent errored build logs into current directory. 5 | # 6 | # usage: 7 | # APP_SLUG=abcdefghijk download-recent-error-logs.sh 1 8 | # 9 | # parameter 1: fetch limit 10 | # 11 | LIMIT=${1:-1} 12 | for BUILD_SLUG in $(curl -sS -H "Authorization: token $BITRISE_API_TOKEN" "https://api.bitrise.io/v0.1/apps/${APP_SLUG:?}/builds?status=2&limit=${LIMIT}" | jq '.data | map(.slug)' | gsed -nr -e 's/.*"(.*)".*/\1/p') 13 | do 14 | LOG_URL=$(curl -sS -H "Authorization: token $BITRISE_API_TOKEN" "https://api.bitrise.io/v0.1/apps/${APP_SLUG}/builds/${BUILD_SLUG}/log" | jq .expiring_raw_log_url) 15 | echo "$LOG_URL" | xargs curl > ${BUILD_SLUG}.log 16 | done 17 | -------------------------------------------------------------------------------- /carthage/checkout: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | carthage checkout --use-ssh $@ 3 | -------------------------------------------------------------------------------- /carthage/search-bcsymbolmap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # search-bcsymbolmap 5 | # 6 | # description: 7 | # 引数として対象のライブラリ名を渡してください. 複数受け付けます. 8 | # 引数なしで実行された場合、Cartfile.resolvedにある全てのライブラリを検索します. 9 | # このファイル自体はどこにおいても構いませんが、 10 | # 通常carthageを使用するのと同様、プロジェクトのルートディレクトリから実行することを前提としています. 11 | # tvOSでは以下のように実行してください. 12 | # `PLATFORM=tvOS search-bcsymbolmap APIKit Himotoki` 13 | # 14 | # author: 15 | # 鈴木 俊裕 16 | # 17 | # Copyright © 2017年 Toshihiro Suzuki All rights reserved. 18 | 19 | PLATFORM=${PLATFORM:-iOS} 20 | 21 | printBcsymbolmapFiles() { 22 | tmp0=`mktemp` 23 | tmp1=`mktemp` 24 | 25 | ls Carthage/Build/${PLATFORM}/*map > $tmp0 26 | while read l 27 | do 28 | echo $l $(sed -n 's/.*-module-name \([A-Za-z_]*\) .*/\1/p' $l | head -1) >> $tmp1 29 | done < $tmp0 30 | sort -k2 $tmp1 31 | 32 | rm $tmp0 $tmp1 33 | } 34 | 35 | if [ $# -eq 0 ];then 36 | set -- $(awk '{print $2}' Cartfile.resolved | gsed -rn 's?".*/(.*)"$?\1?p') 37 | fi 38 | for module in $@;do 39 | if [ "${module}" == "NSObject-Rx" ]; then 40 | module=NSObject_Rx 41 | fi 42 | target=$(printBcsymbolmapFiles | grep $module) 43 | if [ 0 -eq $(echo $target | sed '/^$/d' | wc -l) ];then 44 | printf "\033[0;33m[waring] $module に対応するbcsymbolmapが見つかりませんでした.\033[0m\n" 45 | continue 46 | fi 47 | printBcsymbolmapFiles | grep $module 48 | done 49 | -------------------------------------------------------------------------------- /carthage/update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # update 5 | # 6 | # description: 7 | # carthage管理のライブラリを更新する. 8 | # 引数としてアップデート対象のライブラリ名を渡してください. 複数受け付けます. 9 | # 引数なしで実行された場合、全てのライブラリを更新します. 10 | # このファイル自体はどこにおいても構いませんが、 11 | # 通常carthageを使用するのと同様、プロジェクトのルートディレクトリから実行することを前提としています. 12 | # 例えば `scripts/carthage/update` に配置した場合、以下のように実行します. 13 | # e.g. `./scripts/carthage/update APIKit Himotoki` 14 | # tvOSでは以下のように実行してください. 15 | # `PLATFORM=tvOS ./scripts/carthage/update APIKit Himotoki` 16 | # 17 | # ライブラリのxcschemeのbuild設定のcodeCoverageEnabledがYESの場合はNOにしてbuildします 18 | # refs: https://github.com/Carthage/Carthage/issues/2056#issuecomment-314433811 19 | # https://qiita.com/appwatcher/items/fb93a386920cbeeec6aa 20 | # 21 | # author: 22 | # 鈴木 俊裕 23 | # 24 | # Copyright © 2017年 Toshihiro Suzuki All rights reserved. 25 | 26 | PLATFORM=${PLATFORM:-iOS} 27 | 28 | printBcsymbolmapFiles() { 29 | tmp0=`mktemp` 30 | tmp1=`mktemp` 31 | 32 | ls Carthage/Build/${PLATFORM}/*map > $tmp0 33 | while read l 34 | do 35 | echo $l $(sed -n 's/.*-module-name \([A-Za-z_]*\) .*/\1/p' $l | head -1) >> $tmp1 36 | done < $tmp0 37 | sort -k2 $tmp1 38 | 39 | rm $tmp0 $tmp1 40 | } 41 | 42 | resolveModuleName() { 43 | if [ "${1}" == "NSObject-Rx" ]; then 44 | moduleName=NSObject_Rx 45 | elif [ "${1}" == "SQLite.swift" ]; then 46 | moduleName=SQLite 47 | else 48 | moduleName=${1} 49 | fi 50 | echo ${moduleName} 51 | } 52 | 53 | echo "古いbcsymbolmapを削除しています..." 54 | if [ $# -gt 0 ];then 55 | for module in $@;do 56 | module=`resolveModuleName ${module}` 57 | 58 | target=$(printBcsymbolmapFiles | grep $module) 59 | if [ 0 -eq $(echo $target | sed '/^$/d' | wc -l) ];then 60 | printf "\033[0;33m[waring] $module のbcsymbolmapが見つかりませんでした。手動で見つけて削除してください。\033[0m\n" 61 | continue 62 | fi 63 | printBcsymbolmapFiles | grep $module 64 | printBcsymbolmapFiles | grep $module | awk '{print $1}' | xargs rm 2> /dev/null 65 | done 66 | else 67 | rm -rf Carthage/* 68 | fi 69 | 70 | carthage update --platform ${PLATFORM} --use-ssh --no-use-binaries $@ 71 | -------------------------------------------------------------------------------- /carthage/update-with-hack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # update-with-hack.sh 5 | # 6 | # description: 7 | # Shrink Cartfile, perform update, then revert to original state, 8 | # applying updated state to Cartfile.resolved. 9 | # This way it's faster without unwanted clone and fetch. 10 | # Do not use for installing a new library. (Only for updating) 11 | # 12 | # Cartfileを対象のみに絞ってからcarthage/updateを実行したのち 13 | # 更新された情報をresolvedに反映してCartfileも戻します. 14 | # こうした方が余計なclone/fetchが走らないため高速です. 15 | # 新規追加がある時は使えません. 16 | # 17 | # tvOS: PLATFORM=tvOS cmdshelf run carthage/update-with-hack.sh ${LIBRARY_NAME} 18 | # 19 | # author: 20 | # Toshihiro Suzuki 21 | # 22 | # since: 23 | # 2017-12-15 24 | # 25 | # dependencies: 26 | # cmdshelf 27 | # toshi0383/scripts 28 | # 29 | # copyright: 30 | # Copyright © 2018年 Toshihiro Suzuki, Inc. All rights reserved. 31 | # 32 | 33 | if [ $# -eq 0 ];then 34 | echo "パラメータにライブラリを1つ以上指定してください." 35 | exit 1 36 | fi 37 | 38 | if [ ! -f Cartfile.resolved ];then 39 | echo "Cartfile.resolvedがありません." 40 | exit 1 41 | fi 42 | 43 | MODULES=$@ 44 | 45 | for MODULE in $MODULES 46 | do 47 | if ! grep $MODULE Cartfile.resolved > /dev/null 48 | then 49 | echo ライブラリ名が不正なようです: $MODULE 50 | exit 1 51 | fi 52 | done 53 | 54 | TMP=$(mktemp) 55 | CARTFILE_TMP=$(mktemp) 56 | NEWLY_RESOLVED=$(mktemp) 57 | 58 | 59 | ## Cartfileを退避して対象のみに絞る 60 | 61 | cp Cartfile $CARTFILE_TMP 62 | 63 | for MODULE in $MODULES 64 | do 65 | grep $MODULE Cartfile >> $TMP 66 | done 67 | 68 | cat $TMP > Cartfile 69 | rm $TMP 70 | 71 | ## resolvedを一旦退避 72 | mv Cartfile.resolved $TMP 73 | 74 | ## carthage update 75 | cmdshelf run carthage/update $@ 76 | 77 | ## 新しいresolvedを一旦退避 78 | for MODULE in $MODULES 79 | do 80 | grep $MODULE Cartfile.resolved >> $NEWLY_RESOLVED 81 | done 82 | 83 | ## 退避しておいた古いresolvedを戻し、更新された分を反映. 84 | mv $TMP Cartfile.resolved 85 | 86 | while read LINE 87 | do 88 | MODULE=$(echo $LINE | awk '{print $2}' | sed 's/"//g') 89 | sed -i "" -e "s,.*${MODULE}.*,${LINE}," Cartfile.resolved 90 | done < $NEWLY_RESOLVED 91 | 92 | rm $NEWLY_RESOLVED 93 | 94 | ## 最後にCartfileを戻しておしまい 95 | mv $CARTFILE_TMP Cartfile 96 | 97 | -------------------------------------------------------------------------------- /fabric/download-ipa.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Usage: 4 | # 1. Copy URL to your paste board. (e.g. via hands-off api from iPhone) 5 | # 2. `cmdshelf run fabric/download-ipa.sh` 6 | # 7 | PLIST=`mktemp` 8 | DOWNLOAD_PATH=~/Downloads/downloaded.ipa 9 | 10 | if [ -e "$DOWNLOAD_PATH" ];then 11 | echo File already exists: "$DOWNLOAD_PATH" 12 | exit 1 13 | fi 14 | 15 | curl -L $(curl -s $(pbpaste | gsed -rn 's/.*=(https.*)/\1/p' | perl -MURI::Escape -ne 'print uri_escape($_)') -o $PLIST; /usr/libexec/PlistBuddy -c 'Print :items:0:assets:0:url' $PLIST; rm $PLIST) -o "${DOWNLOAD_PATH}" 16 | if [ $? -eq 0 ];then 17 | say 'Downloaded an IPA file successfully.' 18 | else 19 | say 'Download failed.' 20 | fi 21 | -------------------------------------------------------------------------------- /git/blame-word: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # - name: 4 | # blame-word 5 | # 6 | # - description: 7 | # Find files which contain specified keyword and show the latest log for each line. 8 | # 9 | # e.g. cmdshelf run git/blame-word Hello Sources 10 | # 11 | # - parameters: 12 | # 1: a keyword to search 13 | # 2(optional): a directory to search 14 | # 15 | 16 | WORD=${1:?} 17 | DIRECTORY=${2:-\.} 18 | 19 | (git grep -l "$WORD" "$DIRECTORY" \ 20 | | sort \ 21 | | uniq \ 22 | | while read f; do git blame -- "$f"; done) \ 23 | | grep "$WORD" \ 24 | | awk '{print $1}' \ 25 | | sort \ 26 | | uniq \ 27 | | while read c; do git log -1 --pretty=format:"%C(yellow)%h %C(green)%cd %C(blue)%an%C(red)%d %C(reset)%s%n" $c; done 28 | -------------------------------------------------------------------------------- /git/brdmerged: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git branch --merged | \ 3 | grep -v ' master$' | \ 4 | grep -v '^\*' | \ 5 | xargs git branch -d 6 | -------------------------------------------------------------------------------- /git/brset: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | b="${1:-$(git branch | gsed -rn 's/^\* (.*)/\1/p')}" 3 | git branch --set-upstream-to=origin/${b} ${b} 4 | -------------------------------------------------------------------------------- /git/git-branch-by-author: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | shopt -s nocasematch 3 | 4 | # Print branch names and last commit authors for branches in the origin remote 5 | # Authors specified are matched case insensitively 6 | # usage: 7 | # script-name 8 | # script-name 'author name' 9 | # script-name 'author name' 'other author' 10 | 11 | function print_name_match() 12 | { 13 | name=$1; shift 14 | for author in $*; do 15 | if [[ $author =~ $name ]]; then 16 | print_match $branch $name 17 | fi 18 | done 19 | } 20 | 21 | function print_match() 22 | { 23 | b=$1 24 | shift 25 | echo $b : $@ 26 | } 27 | 28 | git branch -r | grep -v -- '->' | grep origin | while read branch; do 29 | name=`git log --pretty=tformat:%an -1 $branch` 30 | if [ $# -eq 0 ]; then 31 | print_match $branch $name 32 | else 33 | print_name_match $name $* 34 | fi 35 | done 36 | -------------------------------------------------------------------------------- /git/replaceOriginWith.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ $# -ne 1 ];then 3 | echo Please specify new origin URL. 4 | exit 1 5 | fi 6 | new=$1 7 | current=`git remote -v | head -1 | awk '{print $2}'` 8 | git remote add upstream $current 9 | if [ $? -ne 0 ];then 10 | exit $? 11 | fi 12 | git remote remove origin 13 | git remote add origin $new 14 | git fetch --all --tag --prune 15 | git remote -v 16 | -------------------------------------------------------------------------------- /git/take-ours: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FILE_NAME=${1:?} 3 | gawk -i inplace '$1=="======="{a=1};$1==">>>>>>>"{a=0};a!=1{print}' "$FILE_NAME" && sed -i "" -e '/<<<<<<>>>>>>/d;' "$FILE_NAME" 4 | -------------------------------------------------------------------------------- /git/take-theirs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FILE_NAME=${1:?} 3 | gawk -i inplace '$1=="<<<<<<<"{a=1};$1=="======="{a=0};a!=1{print}' "$FILE_NAME" && sed -i "" -e '/<<<<<<>>>>>>/d;' "$FILE_NAME" 4 | -------------------------------------------------------------------------------- /github/add-collab: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | TOKEN=${GITHUB_ACCESS_TOKEN:?"GitHub Personal Access Token is required."} 5 | BASE_NAME=$(basename $0) 6 | USAGE=$(cat <] 9 | EOF 10 | ) 11 | 12 | if [ $# -eq 0 ];then 13 | echo $USAGE 14 | exit 1 15 | fi 16 | 17 | USER_REPO=${GITHUB_USER_REPO:-$(git remote -v | head -1 | sed 's,.*github.com[:/],,; s/ (fetch)$//; s/\.git$//')} 18 | 19 | for USER in $@; 20 | do 21 | curl -vX PUT -H "Authorization: token ${TOKEN}" \ 22 | https://api.github.com/repos/${USER_REPO}/collaborators/${USER} 23 | done 24 | -------------------------------------------------------------------------------- /github/find-pull-requests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # description: 4 | # Find pull-requests matching given filename. 5 | # 6 | # author: 7 | # Toshihiro Suzuki 8 | # 9 | # since: 10 | # 2018-07-20 11 | # 12 | # Copyright © 2019年 Toshihiro Suzuki All rights reserved. 13 | 14 | MATCH=${1:?} 15 | 16 | if which fd > /dev/null 2>&1 17 | then 18 | FIND_COMMAND=fd 19 | else 20 | FIND_COMMAND=find . -name 21 | fi 22 | 23 | USER_REPO=$(git remote -v | head -1 | sed -n 's/.*github.com.\(.*\)\.git.*/\1/p') 24 | 25 | PULL_URL=https://github.com/$USER_REPO/pull 26 | 27 | $FIND_COMMAND $MATCH | \ 28 | xargs git log --pretty=%H | \ 29 | awk '{print $1}' | \ 30 | sed -n 's/\(.*\)/git log --merges --oneline --reverse --ancestry-path \1...master | grep "Merge pull request #" | head -n 1/p' | \ 31 | xargs -0 bash -c | \ 32 | sed -n 's/.*#\([0-9][0-9]*\) .*/\1/p' | \ 33 | sort -nr | \ 34 | uniq | \ 35 | sed "s,^,$PULL_URL/," 36 | -------------------------------------------------------------------------------- /github/pr-approve: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | TOKEN=${GITHUB_ACCESS_TOKEN:?"GitHub Personal Access Token is required."} 3 | NUMBER=${1:?} 4 | MESSAGE=${2:-"👌"} 5 | USER_REPO=$(git remote -v | head -1 | sed 's,.*github.com[:/],,; s/ (fetch)$//; s/\.git$//') 6 | DATA=$(cat <&2 7 | echo "Deprecated: Use toshi0383/ghaw instead." 1>&2 8 | echo "=> https://github.com/toshi0383/ghaw" 1>&2 9 | echo "========================================" 1>&2 10 | 11 | TOKEN=${GITHUB_ACCESS_TOKEN:?"GitHub Personal Access Token is required."} 12 | USER_REPO=$(git remote -v | head -1 | sed 's,.*github.com[:/],,; s/ (fetch)$//; s/\.git$//') 13 | ME=${1:-$(git config --get user.name)} 14 | 15 | TMP_SWIFT=`mktemp` 16 | 17 | cat > $TMP_SWIFT << EOF 18 | import Foundation 19 | 20 | let stdin = FileHandle.standardInput 21 | let stdout = FileHandle.standardOutput 22 | let stderr = FileHandle.standardError 23 | 24 | do { 25 | let inputData = stdin.readDataToEndOfFile() 26 | 27 | let jsonObject = try JSONSerialization.jsonObject(with: inputData, 28 | options: []) 29 | as! [[String: Any]] 30 | 31 | let mapp = jsonObject.reduce([String]()) { (acc: [String], v: [String: Any]) in 32 | let n = v["number"] as! Int 33 | if let labels = v["labels"] as? [[String: Any]], 34 | labels.contains(where: { a in (a["name"] as! String) == "WIP" }) { 35 | return acc 36 | } else { 37 | let milestone = (v["milestone"] as? String) ?? "" 38 | return acc + ["\(n):\"\(milestone)\""] 39 | } 40 | } 41 | mapp.forEach { a in print(a) } 42 | } catch let error as NSError { 43 | let message = "error: \(error.localizedDescription)\n" 44 | stderr.write(message.data(using: .utf8)!) 45 | } 46 | EOF 47 | 48 | TMP_NUMBER_MILESTONES=`mktemp` 49 | curl -sS -H "Authorization: token ${TOKEN}" \ 50 | https://api.github.com/repos/${USER_REPO}/pulls?direction=desc \ 51 | | jq "map(select(.user.login != \"${ME}\") | {number, labels, milestone: .milestone.title})" \ 52 | | xcrun swift $TMP_SWIFT \ 53 | > $TMP_NUMBER_MILESTONES 54 | 55 | rm $TMP_SWIFT 56 | 57 | # echo $NUMBER_MILESTONES 58 | 59 | while read it 60 | do 61 | NUMBER=$(echo $it | awk -F: '{print $1}') 62 | MILESTONE="$(echo $it | awk -F: '{print $2}')" 63 | 64 | RESULT=$(curl -sS -H "Authorization: token ${TOKEN}" \ 65 | https://api.github.com/repos/${USER_REPO}/pulls/${NUMBER}/reviews \ 66 | | jq "map(select(.user.login == \"${ME}\"))" \ 67 | | jq 'map(.user.login + ":" + .state + ":" + .body |scan(".*APPROVED.*"))' \ 68 | | sed 's/\[//;s/\]//;/^$/d' 69 | ) 70 | if [ $(echo $RESULT | wc -c) -eq 1 ];then 71 | echo https://github.com/${USER_REPO}/pull/$NUMBER $MILESTONE 72 | fi 73 | done < $TMP_NUMBER_MILESTONES 74 | 75 | rm $TMP_NUMBER_MILESTONES 76 | -------------------------------------------------------------------------------- /github/pro: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | TOKEN=${GITHUB_ACCESS_TOKEN:?"GitHub Personal Access Token is required."} 5 | BASE_NAME=$(basename $0) 6 | USAGE=$(cat < <-m milestone> <-l label> <-t title> <-M message> 9 | EOF 10 | ) 11 | 12 | # Options 13 | 14 | while getopts b:l:m:t:M: OPT 15 | do 16 | case $OPT in 17 | b) BASE_BRANCH=$OPTARG 18 | ;; 19 | l) LABEL=$OPTARG 20 | ;; 21 | m) MILESTONE=$OPTARG 22 | ;; 23 | t) TITLE=$OPTARG 24 | ;; 25 | M) MESSAGE=$OPTARG 26 | ;; 27 | *) 28 | ;; 29 | esac 30 | done 31 | 32 | # Parameters check 33 | 34 | BASE_BRANCH=${BASE_BRANCH:?${USAGE}} 35 | 36 | if [[ -z $TITLE && ! -z $MESSAGE ]];then 37 | echo 'You must use -M with -t.' 38 | exit 1 39 | fi 40 | 41 | # Get title and messages from user 42 | 43 | if [ -z $TITLE ];then 44 | MESSAGE_FILE=$(mktemp) 45 | git log -1 HEAD | tail +6 > $MESSAGE_FILE 46 | $GIT_EDITOR $MESSAGE_FILE 47 | TITLE=$(head -1 $MESSAGE_FILE) 48 | MESSAGE=$(tail +3 $MESSAGE_FILE) 49 | fi 50 | 51 | # Go! 52 | 53 | USER_REPO=$(git remote -v | head -1 | sed 's,.*github.com[:/],,; s/ (fetch)$//; s/\.git$//') 54 | 55 | # POST a new pull-request 56 | 57 | HEAD_BRANCH=$(git branch | grep \* | cut -d ' ' -f2) 58 | echo Pushing current branch.. 59 | git push origin $(git rev-parse --abbrev-ref HEAD) 60 | echo Creating a new pull-request.. 61 | DATA=$(cat < /dev/null 51 | else 52 | # Update 53 | curl -sS -X PATCH $COMMENTS_EDIT_API/$EXISTING \ 54 | -H "$AUTH" \ 55 | --data "$DATA" > /dev/null 56 | fi 57 | -------------------------------------------------------------------------------- /image/pixelSize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | File=${1:?} 3 | h=`sips -g pixelHeight $File | grep pixelHeight | awk '{print $2}'` 4 | w=`sips -g pixelWidth $File | grep pixelWidth | awk '{print $2}'` 5 | echo "${w}x${h}" 6 | -------------------------------------------------------------------------------- /image/sips-assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # sips-assets.sh 5 | # 6 | # description: 7 | # Generate given size of @1x,@2x,@3x variants using `sips` utility. 8 | # Looks for *.png in current directory and generate all variants by default. 9 | # Make sure the original images are large enough to correctly generate the largest variant of resolution. 10 | # 11 | # svg2png: 12 | # http://svgtopng.com 13 | 14 | GIVEN_SIZE=${1:?Please specify target image size.} 15 | 16 | for line in `ls *.png` 17 | do 18 | line=${line} 19 | for n in `seq 1 3` 20 | do 21 | NEW_FILENAME=$(echo $line | gsed -rn "s/(.*)(\.png)/\1@${n}x\2/p") 22 | cp $line "$NEW_FILENAME" 23 | sips -Z $(expr $n \* $GIVEN_SIZE) $NEW_FILENAME 24 | done 25 | done 26 | -------------------------------------------------------------------------------- /ios/resize-appicon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Original script: 4 | # https://gist.github.com/AndyIbanez/7715e6849adaa8d27e0207d585dde139 5 | 6 | # Takes a 1024x1024 app icon and produces all app icon sizes based on it. 7 | # Requires imagemagick to work. 8 | # USAGE iair ORIGINAL_FILE PATH_TO_OUTPUT_DIR 9 | # Example: iair icon.png . 10 | 11 | if ! which convert > /dev/null 12 | then 13 | echo Please install imagemagick. e.g. \`brew install imagemagick\` 14 | exit 1 15 | fi 16 | 17 | original_file=$1 18 | output_dir=$2 19 | 20 | if [ ! -f $original_file ]; then 21 | echo 'File $original_file not found' 22 | exit 23 | fi 24 | 25 | if [ ! -e "$output_dir" ];then 26 | mkdir -p "$output_dir" 27 | fi 28 | 29 | #Array of all required sizes 30 | declare -a sizes=( 31 | "20.0" 32 | "29.0" 33 | "40.0" 34 | "60.0" 35 | "76.0" 36 | "83.5" 37 | "40.0" 38 | "58.0" 39 | "80.0" 40 | "120.0" 41 | "152.0" 42 | "167.0" 43 | "60.0" 44 | "87.0" 45 | "120.0" 46 | "180.0" 47 | "228.0" 48 | "250.5" 49 | "1024" 50 | ) 51 | 52 | for size in ${sizes[@]} 53 | do 54 | convert $original_file -resize $size'x'$size "$output_dir/Icon-$size.png" 55 | done 56 | -------------------------------------------------------------------------------- /media/hls/extract-master-playlist: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # extract-master-playlist 5 | # 6 | # description: 7 | # Extracts master playlist and prints first playlist found. 8 | # 9 | 10 | URL=${1:?URL is required} 11 | BLOB_BASE=$(echo $URL | gsed -r 's?(.*)/.*?\1?g') 12 | TIMEOUT="--connect-timeout 2" 13 | _PATH="$(curl -sSL ${TIMEOUT} "$URL" | grep -m 1 m3u8)" 14 | 15 | if [[ "$_PATH" =~ "~http" ]];then 16 | DST="${_PATH}" 17 | else 18 | DST="${BLOB_BASE}/${_PATH}" 19 | fi 20 | 21 | curl -sSL ${TIMEOUT} "${DST}" 22 | -------------------------------------------------------------------------------- /net/url_decode: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | perl -MURI::Escape -ne 'print uri_unescape($_)' 3 | -------------------------------------------------------------------------------- /net/url_encode: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | perl -MURI::Escape -ne 'print uri_escape($_)' 3 | -------------------------------------------------------------------------------- /snippet/gen/color-printf: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # color-printf 5 | # 6 | # description: 7 | # Echo colored printf function 8 | # 9 | COLOR=${1:?color is required} 10 | shift 11 | MESSAGE="$@" 12 | 13 | red='\e[1;31m' 14 | grn='\e[1;32m' 15 | yel='\e[1;33m' 16 | blu='\e[1;34m' 17 | mag='\e[1;35m' 18 | cyn='\e[1;36m' 19 | end='\e[0m' 20 | 21 | case $(echo $COLOR | perl -ne 'print lc') in 22 | red) 23 | echo "printf \"${red}${MESSAGE}${end}\n\"" 24 | ;; 25 | green) 26 | echo "printf \"${grn}${MESSAGE}${end}\n\"" 27 | ;; 28 | yellow) 29 | echo "printf \"${yel}${MESSAGE}${end}\n\"" 30 | ;; 31 | blue) 32 | echo "printf \"${blu}${MESSAGE}${end}\n\"" 33 | ;; 34 | magenta) 35 | echo "printf \"${mag}${MESSAGE}${end}\n\"" 36 | ;; 37 | cyan) 38 | echo "printf \"${cyn}${MESSAGE}${end\n}\"" 39 | ;; 40 | *) 41 | echo "printf \"${MESSAGE}\\n\"\n" 42 | ;; 43 | esac 44 | -------------------------------------------------------------------------------- /snippet/gen/swift-json-decode: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # swift-json-decode 5 | # 6 | # description: 7 | # Useful when you want to decode JSON ([String: Any]) but found out that 8 | # Swift's Decodable doesn't allow you to do that. 9 | # 10 | # e.g. `cat Person.swift | swift-json-decode` 11 | # 12 | 13 | TMP=$(mktemp) 14 | cat > $TMP 15 | 16 | echo 'init(from json: JSON) {' 17 | gsed -rn 's/let (.*): (.*)\?/ self.\1 = json["\1"] as? \2/p' $TMP 18 | gsed -rn 's/let (.*): (.*[^?])$/ self.\1 = json["\1"] as! \2/p' $TMP 19 | echo '}' 20 | 21 | rm $TMP 22 | -------------------------------------------------------------------------------- /swiftpm/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # install.sh 5 | # 6 | # description: 7 | # Download latest release from GitHub and install. 8 | # Uploaded zip file should be created with release.sh. 9 | # 10 | # parameters: 11 | # 1: "user/app" ... your github repository name. (e.g. toshi0383/cmdshelf) 12 | # 2: "version" ... version string to install (e.g. 0.9.1) 13 | # 14 | # author: 15 | # Toshihiro Suzuki 16 | # 17 | # since: 18 | # 2017-06-29 19 | # 20 | # copyright: 21 | # Copyright © 2018 Toshihiro Suzuki All rights reserved. 22 | # 23 | 24 | # e.g. toshi0383/cmdshelf 25 | REPO_NAME=${1:?} 26 | 27 | VERSION=${2} 28 | 29 | # Separate arguments by '/' 30 | OLD_IFS=$IFS; IFS=/; set -- $@; IFS=$OLD_IFS 31 | 32 | # e.g. toshi0383/cmdshelf => cmshelf 33 | APP_NAME=${2:?} 34 | 35 | TEMPORARY_FOLDER=/tmp/${APP_NAME}.dst 36 | DOWNLOAD_URLS=${TEMPORARY_FOLDER}/download.urls 37 | 38 | # GitHub API Client ID to get rid of rate limit 39 | CLIENT_ID=6da3e83e315e51292de6 40 | CLIENT_SECRET=a748acd67f2e95d6098ff29243f415133b055226 41 | 42 | # Cleanup 43 | rm -rf $TEMPORARY_FOLDER 44 | mkdir -p $TEMPORARY_FOLDER 2> /dev/null 45 | 46 | # Get binary URL via github api 47 | curl -s "https://api.github.com/repos/${REPO_NAME}/releases?client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}" \ 48 | | grep browser_download_url \ 49 | > $DOWNLOAD_URLS 50 | 51 | # Grep given VERSION otherwise get latest 52 | if [ ! -z $VERSION ];then 53 | BINARY_URL=$(grep $VERSION $DOWNLOAD_URLS | awk -F": \"" '{print $2}' | sed 's/\"//') 54 | else 55 | BINARY_URL=$(head -1 $DOWNLOAD_URLS | awk -F": \"" '{print $2}' | sed 's/\"//') 56 | fi 57 | 58 | echo $BINARY_URL 59 | 60 | # Download zip 61 | cd ${TEMPORARY_FOLDER} 62 | ZIP_NAME=${APP_NAME}.zip 63 | curl -sLk $BINARY_URL -o ${ZIP_NAME} 64 | 65 | # Install 66 | unzip ${ZIP_NAME} 67 | PREFIX=${PREFIX:-/usr/local} 68 | TARGETS="bin share lib etc" 69 | chmod +x bin/$APP_NAME 70 | 71 | # overwrite mint installed executable 72 | if [ -h $PREFIX/bin/$APP_NAME ];then 73 | rm $PREFIX/bin/$APP_NAME 74 | fi 75 | 76 | for target in $TARGETS 77 | do 78 | if [ -e $target ];then 79 | cp -Rf $target $PREFIX/ 80 | fi 81 | done 82 | -------------------------------------------------------------------------------- /swiftpm/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # release.sh 5 | # 6 | # description: 7 | # Archive your SwiftPM executable. 8 | # Generated zip file is intended to uploaded to GitHub releases page and installed by using install.sh. 9 | # Executable must be built with `-static-stdlib` option. 10 | # e.g. `swift build -c release -Xswiftc -static-stdlib` 11 | # 12 | # parameters: 13 | # 1: executable name ... e.g. cmdshelf 14 | # 15 | # author: 16 | # Toshihiro Suzuki 17 | # 18 | # since: 19 | # 2017-06-29 20 | # 21 | # copyright: 22 | # Copyright © 2017 Toshihiro Suzuki All rights reserved. 23 | # 24 | 25 | # executable 26 | TMPDIR=$(mktemp -d) 27 | APP_NAME=${1:?} 28 | 29 | RELEASE_DIR=.build/release 30 | EXECUTABLE=${RELEASE_DIR}/${APP_NAME} 31 | BIN=$TMPDIR/bin 32 | 33 | install_name_tool -delete_rpath `xcode-select -p`/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx $EXECUTABLE 34 | mkdir $BIN 35 | cp $EXECUTABLE $BIN/ 36 | 37 | # manual pages 38 | SHARE=$TMPDIR/share 39 | MAN_DIR=docs/man 40 | mkdir $SHARE 41 | cp -R $MAN_DIR $SHARE/ 42 | 43 | # resources 44 | LIB=$TMPDIR/lib 45 | RESOURCES= 46 | PACKAGE_RESOURCES=Package.resources 47 | 48 | if [ -f $PACKAGE_RESOURCES ];then 49 | RESOURCES=${SHARE}/${APP_NAME}/ 50 | mkdir -p $RESOURCES 51 | while read dir 52 | do 53 | cp -R $dir $RESOURCES/ 54 | done < $PACKAGE_RESOURCES 55 | fi 56 | 57 | # bash_completion script 58 | ETC=$TMPDIR/etc 59 | BASH_COMPLETION_D=$ETC/bash_completion.d 60 | mkdir -p $BASH_COMPLETION_D 61 | BASH_COMPLETION_SCRIPT=Sources/Scripts/${APP_NAME}-completion.bash 62 | cp $BASH_COMPLETION_SCRIPT $BASH_COMPLETION_D 63 | 64 | cd $TMPDIR 65 | zip -r ${APP_NAME}.zip bin share lib etc 66 | cd - 67 | 68 | cp ${TMPDIR}/${APP_NAME}.zip . 69 | 70 | rm -rf ${TMPDIR} 71 | -------------------------------------------------------------------------------- /tools/sync-file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # sync-file.sh 5 | # 6 | # description: 7 | # 指定されたディレクトリとカレントディレクトリの構成を、指定された1ファイルに対してのみ同期します. 8 | # 指定されたファイルパスが見つからない場合、ファイル名として扱い `find` をかけます. 9 | # ディレクトリ階層がカレントディレクトリになければ `mkdir` します. 10 | # 11 | # parameters: 12 | # 1: 同期したいディレクトリ 13 | # 2: 同期したいファイルパス 14 | # 15 | # author: 16 | # 鈴木 俊裕 17 | # 18 | # since: 19 | # 2017-06-23 20 | # 2019-11-27 copied 21 | # 22 | # copyright: 23 | # Copyright © 2017年 Abema TV, Inc. All rights reserved. 24 | # Copyright © 2019年 Toshihiro Suzuki All rights reserved. 25 | # 26 | ORIGIN_DIR=${1:?} 27 | FILE_PATH=${2:?} 28 | 29 | TARGET="${ORIGIN_DIR}/${FILE_PATH}" 30 | 31 | if [ -d "$TARGET" -o -f "$TARGET" ] 32 | then 33 | target_path="${TARGET}" 34 | destination="${FILE_PATH}" 35 | else 36 | target_path=$(find ${ORIGIN_DIR} -name ${FILE_PATH}) 37 | destination=$(echo ${target_path} | sed "s?${ORIGIN_DIR}/??") 38 | fi 39 | 40 | PARENT_DIR=$(dirname "${destination}") 41 | if [ ! -d "${PARENT_DIR}" ] 42 | then 43 | mkdir -p "${PARENT_DIR}" 44 | fi 45 | 46 | cp -R "${target_path}" "${PARENT_DIR}" 47 | -------------------------------------------------------------------------------- /twitter/bearer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Get bearer token for TwitterAPI 3 | # SeeAlso: https://dev.twitter.com/oauth/application-only 4 | CONSUMER_KEY=${1} 5 | CONSUMER_SECRET=${2} 6 | CONSUMER_KEY=$(echo -n $CONSUMER_KEY | cmdshelf run net/url_encode) 7 | CONSUMER_SECRET=$(echo -n $CONSUMER_SECRET | cmdshelf run net/url_encode) 8 | REQUEST=$(echo -n "${CONSUMER_KEY}:${CONSUMER_SECRET}" | base64) 9 | 10 | DATA=grant_type=client_credentials 11 | JSON=$(curl -sSL https://api.twitter.com/oauth2/token -d "${DATA}" \ 12 | -H "Authorization:Basic ${REQUEST}" \ 13 | -H "Content-Type:application/x-www-form-urlencoded;charset=UTF-8") 14 | 15 | TYPE=$(echo "$JSON" | jq .token_type | sed 's/"//g') 16 | if [ $TYPE != "bearer" ];then 17 | echo Response token_type was not bearer: $JSON 18 | exit 1 19 | fi 20 | echo -n $JSON | jq .access_token | sed 's/"//g' 21 | 22 | -------------------------------------------------------------------------------- /xcode/build-time.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # build-time.sh 5 | # 6 | # description: 7 | # Extracts build log and prints build duration per file. 8 | # Output is sorted in descending order of compile time. 9 | # 10 | # Make sure to build with these `OTHER_SWIFT_FLAGS`: 11 | # -Xfrontend -debug-time-function-bodies 12 | # 13 | # flags: 14 | # -d: Detailed mode. Prints build duration per functions, getter/setter or such. 15 | # 16 | # parameters: 17 | # - 1 (Optional): Log file name. Reads stdin if omitted. 18 | # 19 | # usage: 20 | # You can either use stdin or filename parameter. 21 | # e.g. 22 | # `echo $log_file | ./build-time.sh` 23 | # `./build-time.sh $log_file` 24 | # 25 | # You may want to filter output like this. 26 | # `./build-time.sh $log_file | head` 27 | # `./build-time.sh $log_file | grep YourSpecificFile.swift 28 | # 29 | # Invoke via cmdshelf 30 | # `cmdshelf run build-time.sh -d $log_file` 31 | # `make build | cmdshelf run build-time.sh -d` 32 | # 33 | # https://github.com/toshi0383/cmdshelf 34 | # 35 | # dependencies (Make sure you install them beforehand.) 36 | # - mac2unix 37 | # - gnu-sed 38 | # 39 | # author: 40 | # Toshihiro Suzuki 41 | # 42 | # since: 43 | # 2018-02-05 44 | # 45 | # copyright: 46 | # Copyright © 2018年 toshi0383 All rights reserved. 47 | # 48 | 49 | # default: per file 50 | SUMMARIZE_AWK='{if(o!=$1){i+=1;n[i]=$1}sum[i]+=$2;o=$1;}END{for(key in sum){print n[key]": "sum[key]}}' 51 | 52 | while getopts d OPT 53 | do 54 | case $OPT in 55 | 56 | # detailed: per function, closure, getter/setter or initializers 57 | d) 58 | SUMMARIZE_AWK='{s=$1;for(i=4;i<=NF;i++)s=s" "$i" ";if(o!=s){c+=1;n[c]=s;}sum[c]+=$2;o=s}END{for(key in sum){print n[key]": "sum[key]}}' 59 | ;; 60 | esac 61 | done 62 | 63 | shift $(($OPTIND - 1)) 64 | 65 | LOG_FILE=${1:-/dev/stdin} 66 | 67 | # Check dependencies 68 | if ! which mac2unix > /dev/null 2>&1 69 | then 70 | echo 'mac2unix is missing. Please install first. e.g. `brew install dos2unix`' 71 | exit 1 72 | fi 73 | 74 | if ! which gsed > /dev/null 2>&1 75 | then 76 | echo 'gnu-sed is missing. Please install first. e.g. `brew install gnu-sed`' 77 | exit 1 78 | fi 79 | 80 | EXTRACT_FILE_AND_DURATION='s?(^|.*")([0-9]+\.[0-9]+)ms\t.*/(.*\.swift)(.*)?\3 \2 \4,?p' 81 | 82 | cat $LOG_FILE \ 83 | | gzcat 2> /dev/null \ 84 | | mac2unix \ 85 | | gsed -nr "$EXTRACT_FILE_AND_DURATION" \ 86 | | sort -u \ 87 | | awk "$SUMMARIZE_AWK" \ 88 | | sort -k 2 -nr 89 | 90 | 91 | if [ ${PIPESTATUS[1]} -ne 0 ];then 92 | 93 | # Maybe it wasn't gzip input. 94 | # Try again as if it's redirect from xcodebuild's output. 95 | cat $LOG_FILE \ 96 | | mac2unix \ 97 | | gsed -nr "$EXTRACT_FILE_AND_DURATION" \ 98 | | sort -u \ 99 | | awk "$SUMMARIZE_AWK" \ 100 | | sort -k 2 -nr 101 | fi 102 | -------------------------------------------------------------------------------- /xcode/check-build-time.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # check-build-time.sh 5 | # 6 | # description: 7 | # Print build time of Swift files which you have changed in this branch. 8 | # Note that if you cancelled the last build, result would be empty. 9 | # 10 | # dependencies (Make sure you install them beforehand.) 11 | # - latest-build-time.sh 12 | # - build-time.sh 13 | # 14 | # options 15 | # 16 | # -b 17 | # Base branch to compare with. Defaults to master. 18 | # 19 | # -l 20 | # Use given log file instead of the automatically found latest log. 21 | # Latest build log is used if omitted. 22 | # 23 | # author: 24 | # Toshihiro Suzuki 25 | # 26 | # since: 27 | # 2018-02-07 28 | # 29 | # copyright: 30 | # Copyright © 2018年 toshi0383 All rights reserved. 31 | 32 | # Check dependencies 33 | 34 | WORKSPACE=$(cd $(dirname $0); pwd) 35 | LATEST_BUILD_TIME_SH=${WORKSPACE}/latest-build-time.sh 36 | BUILD_TIME_SH=${WORKSPACE}/build-time.sh 37 | 38 | if [ ! -f $LATEST_BUILD_TIME_SH ];then 39 | echo $LATEST_BUILD_TIME_SH is missing. 40 | exit 1 41 | fi 42 | 43 | if [ ! -f $BUILD_TIME_SH ];then 44 | echo $BUILD_TIME_SH is missing. 45 | exit 1 46 | fi 47 | 48 | # Options 49 | 50 | while getopts l:b: OPT 51 | do 52 | case $OPT in 53 | l) LOG_FILE=$OPTARG 54 | ;; 55 | b) BASE_BRANCH=$OPTARG 56 | ;; 57 | *) 58 | ;; 59 | esac 60 | done 61 | 62 | BASE_BRANCH=${BASE_BRANCH:-master} 63 | 64 | # Go! 65 | 66 | MODIFIED_FILE_NAMES=$(mktemp) 67 | 68 | # Get modified files 69 | git diff --name-status ${BASE_BRANCH}..HEAD \ 70 | | gsed -nr 's?.*/(.*.swift)$?\1?p' \ 71 | > $MODIFIED_FILE_NAMES 72 | 73 | # Filter by modified files 74 | if [ ! -z "$LOG_FILE" ]; then 75 | $BUILD_TIME_SH "$LOG_FILE" | grep -f $MODIFIED_FILE_NAMES 76 | else 77 | $LATEST_BUILD_TIME_SH | grep -f $MODIFIED_FILE_NAMES 78 | fi 79 | 80 | rm $MODIFIED_FILE_NAMES 81 | -------------------------------------------------------------------------------- /xcode/check-expiring-mobileprovision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # name: 4 | # check-expiring-mobileprovision.sh 5 | # 6 | # description: 7 | # Search for expiring mobileprovision file and prints them in console. 8 | # 9 | # parameter: 10 | # - files list 11 | # 12 | # author: 13 | # 鈴木 俊裕 14 | # 15 | # dependency: 16 | # - gdate: to format date string to unixtime 17 | # 18 | # copyright: 19 | # Copyright © 2018年 toshi0383 All rights reserved. 20 | # 21 | # license: 22 | # MIT 23 | # 24 | 25 | if [ $# -eq 0 ];then 26 | echo "no files given." 27 | echo " e.g." 28 | echo " $0 \$(ls certs/*mobileprovision)" 29 | exit 1 30 | fi 31 | 32 | for m in $@ 33 | do 34 | security cms -D -i "$m" > a 35 | 36 | d=$(/usr/libexec/PlistBuddy -c 'Print ExpirationDate' a) 37 | 38 | if [ $(gdate -d "$d" +%s) -lt $(expr $(date +%s) + 43200) ]; then 39 | # Expires in less than 30 days 40 | echo $m : $d 41 | STATUS=1 42 | fi 43 | 44 | rm a 45 | done 46 | 47 | exit $STATUS 48 | -------------------------------------------------------------------------------- /xcode/find-error.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # this script is work in progress 4 | # 5 | f=${1:-"/dev/fd/0"} 6 | cat $f | 7 | grep -v export | 8 | grep -v write-file | 9 | grep -v builtin- | 10 | grep -v CpHeader | 11 | grep -v cd\ | 12 | grep -v ^[0-9] | 13 | sed '/^$/d' 14 | -------------------------------------------------------------------------------- /xcode/fix-framework-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # name: 3 | # fix-framework-version.sh 4 | # 5 | # description: 6 | # 指定されたディレクトリ配下にあるInfo.plistの中身をAppStoreでエラーにならないように修正します. 7 | # 注意:面倒なのでバージョンはCFBundleVersionもCFBundleShortVersionStringも全部1 になります. 8 | # ライブラリのCFBundle...なんて大抵は関係ないでしょう. 9 | # 10 | dir=${1:?} 11 | if [ ! -d "$dir" ];then 12 | echo "Directory not found: ${dir}" 13 | exit 1 14 | fi 15 | 16 | prompt() { 17 | echo -n "${1}?:" 18 | read line 19 | echo $line 20 | } 21 | major=`prompt CFBundleVersion(major)` 22 | minor=`prompt CFBundleShortVersionString(minor)` 23 | 24 | for i in `find $dir -name "*.plist"`; do 25 | plutil -replace 'CFBundleShortVersionString' -string ${major} "$i" 26 | plutil -replace 'CFBundleVersion' -string ${minor} "$i" 27 | done 28 | 29 | -------------------------------------------------------------------------------- /xcode/latest-build-time.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # name: 3 | # latest-build-time.sh 4 | # 5 | # description: 6 | # Find the latest Xcode build log file and pass it to `build-time.sh`. 7 | # 8 | # dependencies (Make sure you install them beforehand.) 9 | # - build-time.sh 10 | # - gls 11 | # 12 | # author: 13 | # Toshihiro Suzuki 14 | # 15 | # since: 16 | # 2018-02-07 17 | # 18 | # copyright: 19 | # Copyright © 2018年 toshi0383 All rights reserved. 20 | 21 | # Check dependencies 22 | 23 | if ! which gls > /dev/null 2>&1 24 | then 25 | echo 'gls is missing. Please install first. e.g. `brew install coreutils`' 26 | exit 1 27 | fi 28 | 29 | BUILD_TIME_SH=$(cd $(dirname $0); pwd)/build-time.sh 30 | 31 | if [ ! -f $BUILD_TIME_SH ];then 32 | echo $BUILD_TIME_SH is missing. 33 | exit 1 34 | fi 35 | 36 | # Go! 37 | 38 | if [ -z ${DERIVED_DIR} ];then 39 | 40 | # `find` is slow. Let's use `ls` with wild-card by default. 41 | LATEST_LOG_FILE=$(gls --numeric-uid-gid --time-style=+%s ~/Library/Developer/Xcode/DerivedData/**/Logs/Build/*.xcactivitylog \ 42 | | sort -k 6 -n \ 43 | | tail -1 \ 44 | | awk '{print $7}') 45 | 46 | else 47 | # Fallback to `find` if DERIVED_DIR is specified. 48 | LATEST_LOG_FILE=$(find ${DERIVED_DIR} -name "*.xcactivitylog" \ 49 | | xargs gls --numeric-uid-gid --time-style=+%s \ 50 | | sort -k 6 -n \ 51 | | tail -1 \ 52 | | awk '{print $7}') 53 | 54 | fi 55 | 56 | if [ ! -f "$LATEST_LOG_FILE" ];then 57 | echo No such file: $LATEST_LOG_FILE 58 | exit 1 59 | fi 60 | 61 | $BUILD_TIME_SH $@ "$LATEST_LOG_FILE" 62 | -------------------------------------------------------------------------------- /xcode/open-device-support-dir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | OS=${1:-'[a-z]'} 3 | for xcode in `ls -d /Applications/Xcode*.app` 4 | do 5 | case $OS in 6 | i[oO][sS]) 7 | dirs="$dirs ${xcode}/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport" 8 | ;; 9 | tv[oO][sS]) 10 | dirs="$dirs ${xcode}/Contents/Developer/Platforms/AppleTVOS.platform/DeviceSupport" 11 | ;; 12 | watch) 13 | dirs="$dirs ${xcode}/Contents/Developer/Platforms/WatchOS.platform/DeviceSupport" 14 | ;; 15 | [mM]ac) 16 | dirs="$dirs ${xcode}/Contents/Developer/Platforms/MacOSX.platform/DeviceSupport" 17 | ;; 18 | *) 19 | echo invalid os: $OS 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | for dir in $dirs 25 | do 26 | open "${dir}" 27 | done 28 | -------------------------------------------------------------------------------- /xcode/pbpasteReplayArgs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pbpaste | awk '{print "let replayArgs = CheckerArguments(replay: (StdGen(" $1 ", " $2 "), " $5 "))"}' 3 | -------------------------------------------------------------------------------- /xcode/plugins/revert-xquick.scpt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript 2 | 3 | use AppleScript version "2.4" # Yosemite or later 4 | use scripting additions 5 | use framework "Foundation" 6 | 7 | on run 8 | tell application "Xcode" 9 | set projectPath to path of active workspace document 10 | set projectFolder to characters 1 thru -((offset of "/" in (reverse of items of projectPath as string)) + 1) of projectPath as string 11 | set sourceName to (get name of window 1) 12 | do shell script "hackscode xquick -r " & sourceName 13 | end tell 14 | end run 15 | 16 | -------------------------------------------------------------------------------- /xcode/plugins/xcode_toggleFlux_backward.scpt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript 2 | 3 | use AppleScript version "2.4" # Yosemite or later 4 | use scripting additions 5 | use framework "Foundation" 6 | 7 | on run 8 | tell application "Xcode" 9 | # Xcodeで表示中のファイルの情報を取得 10 | set projectPath to path of active workspace document 11 | set projectFolder to characters 1 thru -((offset of "/" in (reverse of items of projectPath as string)) + 1) of projectPath as string 12 | # display dialog projectFolder 13 | set sourceName to (get name of window 1) 14 | # display dialog sourceName 15 | 16 | # Hoge.swift ⇄ HogeTests.swift のトグル変換 17 | set w1 to "Store.swift" 18 | set w2 to "Dispatcher.swift" 19 | set w3 to "Action.swift" 20 | # Action => Dispatcher 21 | if sourceName contains w1 then 22 | set destinationName to (my replaceThis:(w1 & ".*") inString:sourceName usingThis:w2) 23 | # Dispatcher => Store 24 | else if sourceName contains w2 then 25 | set destinationName to (my replaceThis:(w2 & ".*") inString:sourceName usingThis:w3) 26 | # Store => Action 27 | else if sourceName contains w3 then 28 | set destinationName to (my replaceThis:(w3 & ".*") inString:sourceName usingThis:w1) 29 | end if 30 | # display dialog destinationName 31 | 32 | # ファイルパスを探して開く 33 | # display dialog command 34 | set command to "fd " & quoted form of destinationName & " " & quoted form of projectFolder & "| head -1" 35 | set destinationPath to do shell script command 36 | if length of destinationPath < 1 then 37 | set destinationName to (my replaceThis:(w1 & ".*") inString:sourceName usingThis:w3) 38 | set command to "find " & quoted form of projectFolder & " -name " & quoted form of destinationName 39 | set destinationPath to do shell script command 40 | end if 41 | if length of destinationPath > 0 then 42 | open destinationPath 43 | else 44 | display dialog "the file" & quoted form of destinationName & " not found." 45 | end if 46 | end tell 47 | end run 48 | 49 | # 正規表現置換 50 | on replaceThis:thePattern inString:theString usingThis:theTemplate 51 | set theNSString to current application's NSString's stringWithString:theString 52 | set theOptions to (current application's NSRegularExpressionDotMatchesLineSeparators as integer) + (current application's NSRegularExpressionAnchorsMatchLines as integer) 53 | set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value) 54 | set theResult to theRegEx's stringByReplacingMatchesInString:theNSString options:0 range:{location:0, |length|:theNSString's |length|()} withTemplate:theTemplate 55 | return theResult as text 56 | end replaceThis:inString:usingThis: 57 | -------------------------------------------------------------------------------- /xcode/plugins/xcode_toggleFlux_forward.scpt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript 2 | 3 | use AppleScript version "2.4" # Yosemite or later 4 | use scripting additions 5 | use framework "Foundation" 6 | 7 | on run 8 | tell application "Xcode" 9 | # Xcodeで表示中のファイルの情報を取得 10 | set projectPath to path of active workspace document 11 | set projectFolder to characters 1 thru -((offset of "/" in (reverse of items of projectPath as string)) + 1) of projectPath as string 12 | # display dialog projectFolder 13 | set sourceName to (get name of window 1) 14 | # display dialog sourceName 15 | 16 | # Hoge.swift ⇄ HogeTests.swift のトグル変換 17 | set w1 to "Action.swift" 18 | set w2 to "Dispatcher.swift" 19 | set w3 to "Store.swift" 20 | # Action => Dispatcher 21 | if sourceName contains w1 then 22 | set destinationName to (my replaceThis:(w1 & ".*") inString:sourceName usingThis:w2) 23 | # Dispatcher => Store 24 | else if sourceName contains w2 then 25 | set destinationName to (my replaceThis:(w2 & ".*") inString:sourceName usingThis:w3) 26 | # Store => Action 27 | else if sourceName contains w3 then 28 | set destinationName to (my replaceThis:(w3 & ".*") inString:sourceName usingThis:w1) 29 | end if 30 | # display dialog destinationName 31 | 32 | # ファイルパスを探して開く 33 | # display dialog command 34 | set command to "fd " & quoted form of destinationName & " " & quoted form of projectFolder & "| head -1" 35 | set destinationPath to do shell script command 36 | if length of destinationPath < 1 then 37 | set destinationName to (my replaceThis:(w1 & ".*") inString:sourceName usingThis:w3) 38 | set command to "find " & quoted form of projectFolder & " -name " & quoted form of destinationName 39 | set destinationPath to do shell script command 40 | end if 41 | if length of destinationPath > 0 then 42 | open destinationPath 43 | else 44 | display dialog "the file" & quoted form of destinationName & " not found." 45 | end if 46 | end tell 47 | end run 48 | 49 | # 正規表現置換 50 | on replaceThis:thePattern inString:theString usingThis:theTemplate 51 | set theNSString to current application's NSString's stringWithString:theString 52 | set theOptions to (current application's NSRegularExpressionDotMatchesLineSeparators as integer) + (current application's NSRegularExpressionAnchorsMatchLines as integer) 53 | set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value) 54 | set theResult to theRegEx's stringByReplacingMatchesInString:theNSString options:0 range:{location:0, |length|:theNSString's |length|()} withTemplate:theTemplate 55 | return theResult as text 56 | end replaceThis:inString:usingThis: 57 | -------------------------------------------------------------------------------- /xcode/plugins/xcode_toggleImplAndTests.scpt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript 2 | # 3 | # [2018-07-13] 4 | # Copied from https://github.com/takasek/XCJumpToTests under MIT license. 5 | 6 | use AppleScript version "2.4" # Yosemite or later 7 | use scripting additions 8 | use framework "Foundation" 9 | 10 | on run 11 | tell application "Xcode" 12 | # Xcodeで表示中のファイルの情報を取得 13 | set projectPath to path of active workspace document 14 | set projectFolder to characters 1 thru -((offset of "/" in (reverse of items of projectPath as string)) + 1) of projectPath as string 15 | # display dialog projectFolder 16 | set sourceName to (get name of window 1) 17 | # display dialog sourceName 18 | 19 | # Hoge.swift ⇄ HogeTests.swift のトグル変換 20 | set w1 to ".swift" 21 | set w2 to "Tests.swift" 22 | set w3 to "Spec.swift" 23 | if sourceName contains w2 then 24 | set destinationName to (my replaceThis:(w2 & ".*") inString:sourceName usingThis:w1) 25 | else if sourceName contains w3 then 26 | set destinationName to (my replaceThis:(w3 & ".*") inString:sourceName usingThis:w1) 27 | else 28 | set destinationName to (my replaceThis:(w1 & ".*") inString:sourceName usingThis:w2) 29 | end if 30 | # display dialog destinationName 31 | 32 | # ファイルパスを探して開く 33 | # display dialog command 34 | set command to "fd " & quoted form of destinationName & " " & quoted form of projectFolder 35 | set destinationPath to do shell script command 36 | if length of destinationPath < 1 then 37 | set destinationName to (my replaceThis:(w1 & ".*") inString:sourceName usingThis:w3) 38 | set command to "fd " & quoted form of destinationName & " " & quoted form of projectFolder 39 | set destinationPath to do shell script command 40 | end if 41 | if length of destinationPath > 0 then 42 | open destinationPath 43 | else 44 | display dialog "the file" & quoted form of destinationName & " not found." 45 | end if 46 | end tell 47 | end run 48 | 49 | # 正規表現置換 50 | on replaceThis:thePattern inString:theString usingThis:theTemplate 51 | set theNSString to current application's NSString's stringWithString:theString 52 | set theOptions to (current application's NSRegularExpressionDotMatchesLineSeparators as integer) + (current application's NSRegularExpressionAnchorsMatchLines as integer) 53 | set theRegEx to current application's NSRegularExpression's regularExpressionWithPattern:thePattern options:theOptions |error|:(missing value) 54 | set theResult to theRegEx's stringByReplacingMatchesInString:theNSString options:0 range:{location:0, |length|:theNSString's |length|()} withTemplate:theTemplate 55 | return theResult as text 56 | end replaceThis:inString:usingThis: 57 | -------------------------------------------------------------------------------- /xcode/plugins/xquick.scpt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript 2 | 3 | use AppleScript version "2.4" # Yosemite or later 4 | use scripting additions 5 | use framework "Foundation" 6 | 7 | on run 8 | tell application "Xcode" 9 | set projectPath to path of active workspace document 10 | set projectFolder to characters 1 thru -((offset of "/" in (reverse of items of projectPath as string)) + 1) of projectPath as string 11 | set sourceName to (get name of window 1) 12 | do shell script "hackscode xquick " & sourceName 13 | end tell 14 | end run 15 | 16 | -------------------------------------------------------------------------------- /xcode/printUUIDofMobileprovision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /usr/libexec/PlistBuddy -c 'Print UUID' /dev/stdin <<< $(security cms -D -i "${1}") 3 | -------------------------------------------------------------------------------- /xcode/sort-Xcode-project-file: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | 3 | # Copyright (C) 2017 Toshihiro Suzuki. All rights reserved. 4 | # - CHANGELOG: 5 | # 2017/07/04 sort inputPaths by name 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | # 26 | # Downloaded from WebKit: https://github.com/WebKit/webkit/blob/master/Tools/Scripts/sort-Xcode-project-file 27 | # 28 | 29 | # Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. 30 | # 31 | # Redistribution and use in source and binary forms, with or without 32 | # modification, are permitted provided that the following conditions 33 | # are met: 34 | # 35 | # 1. Redistributions of source code must retain the above copyright 36 | # notice, this list of conditions and the following disclaimer. 37 | # 2. Redistributions in binary form must reproduce the above copyright 38 | # notice, this list of conditions and the following disclaimer in the 39 | # documentation and/or other materials provided with the distribution. 40 | # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 41 | # its contributors may be used to endorse or promote products derived 42 | # from this software without specific prior written permission. 43 | # 44 | # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 45 | # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 46 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 47 | # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 48 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 49 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 50 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 51 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 53 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | 55 | # Script to sort "children" and "files" sections in Xcode project.pbxproj files 56 | 57 | use strict; 58 | 59 | use File::Basename; 60 | use File::Spec; 61 | use File::Temp qw(tempfile); 62 | use Getopt::Long; 63 | 64 | sub sortChildrenByFileName($$); 65 | sub sortFilesByFileName($$); 66 | 67 | # Files (or products) without extensions 68 | my %isFile = map { $_ => 1 } qw( 69 | create_hash_table 70 | jsc 71 | minidom 72 | testapi 73 | testjsglue 74 | ); 75 | 76 | my $printWarnings = 1; 77 | my $showHelp; 78 | 79 | my $getOptionsResult = GetOptions( 80 | 'h|help' => \$showHelp, 81 | 'w|warnings!' => \$printWarnings, 82 | ); 83 | 84 | if (scalar(@ARGV) == 0 && !$showHelp) { 85 | print STDERR "ERROR: No Xcode project files (project.pbxproj) listed on command-line.\n"; 86 | undef $getOptionsResult; 87 | } 88 | 89 | if (!$getOptionsResult || $showHelp) { 90 | print STDERR <<__END__; 91 | Usage: @{[ basename($0) ]} [options] path/to/project.pbxproj [path/to/project.pbxproj ...] 92 | -h|--help show this help message 93 | -w|--[no-]warnings show or suppress warnings (default: show warnings) 94 | __END__ 95 | exit 1; 96 | } 97 | 98 | for my $projectFile (@ARGV) { 99 | if (basename($projectFile) =~ /\.xcodeproj$/) { 100 | $projectFile = File::Spec->catfile($projectFile, "project.pbxproj"); 101 | } 102 | 103 | if (basename($projectFile) ne "project.pbxproj") { 104 | print STDERR "WARNING: Not an Xcode project file: $projectFile\n" if $printWarnings; 105 | next; 106 | } 107 | 108 | # Grab the mainGroup for the project file 109 | my $mainGroup = ""; 110 | open(IN, "< $projectFile") || die "Could not open $projectFile: $!"; 111 | while (my $line = ) { 112 | $mainGroup = $2 if $line =~ m#^(\s*)mainGroup = ([0-9A-F]{24} /\* .+ \*/);$#; 113 | } 114 | close(IN); 115 | 116 | my ($OUT, $tempFileName) = tempfile( 117 | basename($projectFile) . "-XXXXXXXX", 118 | DIR => dirname($projectFile), 119 | UNLINK => 0, 120 | ); 121 | 122 | # Clean up temp file in case of die() 123 | $SIG{__DIE__} = sub { 124 | close(IN); 125 | close($OUT); 126 | unlink($tempFileName); 127 | }; 128 | 129 | my @lastTwo = (); 130 | open(IN, "< $projectFile") || die "Could not open $projectFile: $!"; 131 | while (my $line = ) { 132 | if ($line =~ /^(\s*)files = \(\s*$/) { 133 | print $OUT $line; 134 | my $endMarker = $1 . ");"; 135 | my @files; 136 | while (my $fileLine = ) { 137 | if ($fileLine =~ /^\Q$endMarker\E\s*$/) { 138 | $endMarker = $fileLine; 139 | last; 140 | } 141 | push @files, $fileLine; 142 | } 143 | print $OUT sort sortFilesByFileName @files; 144 | print $OUT $endMarker; 145 | } elsif ($line =~ /^(\s*)inputPaths = \($/) { 146 | print $OUT $line; 147 | my $endMarker = $1 . ");"; 148 | my @files; 149 | while (my $inputPathLine = ) { 150 | if ($inputPathLine =~ /^\Q$endMarker\E\s*$/) { 151 | $endMarker = $inputPathLine; 152 | last; 153 | } 154 | push @files, $inputPathLine; 155 | } 156 | print $OUT sort sortInputPathsByFileName @files; 157 | print $OUT $endMarker; 158 | } elsif ($line =~ /^(\s*)children = \(\s*$/) { 159 | print $OUT $line; 160 | my $endMarker = $1 . ");"; 161 | my @children; 162 | while (my $childLine = ) { 163 | if ($childLine =~ /^\Q$endMarker\E\s*$/) { 164 | $endMarker = $childLine; 165 | last; 166 | } 167 | push @children, $childLine; 168 | } 169 | if ($lastTwo[0] =~ m#^\s+\Q$mainGroup\E = \{$#) { 170 | # Don't sort mainGroup 171 | print $OUT @children; 172 | } else { 173 | print $OUT sort sortChildrenByFileName @children; 174 | } 175 | print $OUT $endMarker; 176 | } else { 177 | print $OUT $line; 178 | } 179 | 180 | push @lastTwo, $line; 181 | shift @lastTwo if scalar(@lastTwo) > 2; 182 | } 183 | close(IN); 184 | close($OUT); 185 | 186 | unlink($projectFile) || die "Could not delete $projectFile: $!"; 187 | rename($tempFileName, $projectFile) || die "Could not rename $tempFileName to $projectFile: $!"; 188 | } 189 | 190 | exit 0; 191 | 192 | sub sortChildrenByFileName($$) 193 | { 194 | my ($a, $b) = @_; 195 | my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/; 196 | my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/; 197 | my $aSuffix = $1 if $aFileName =~ m/\.([^.]+)$/; 198 | my $bSuffix = $1 if $bFileName =~ m/\.([^.]+)$/; 199 | if ((!$aSuffix && !$isFile{$aFileName} && $bSuffix) || ($aSuffix && !$bSuffix && !$isFile{$bFileName})) { 200 | return !$aSuffix ? -1 : 1; 201 | } 202 | return lc($aFileName) cmp lc($bFileName); 203 | } 204 | 205 | sub sortFilesByFileName($$) 206 | { 207 | my ($a, $b) = @_; 208 | my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /; 209 | my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /; 210 | return lc($aFileName) cmp lc($bFileName); 211 | } 212 | 213 | sub sortInputPathsByFileName($$) 214 | { 215 | my ($a, $b) = @_; 216 | return $a cmp $b; 217 | } 218 | -------------------------------------------------------------------------------- /xcode/sort-xcpretty-by-time: -------------------------------------------------------------------------------- 1 | grep seconds | awk -F '[()]' '{print $2 " -> " $1}' | sort -rn 2 | -------------------------------------------------------------------------------- /xcode/sync-device-support-dir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # warning: never tested. Use this on your own risk. 4 | # 5 | PLATFORMS="AppleTVOS iPhoneOS WatchOS" 6 | 7 | for platform in $PLATFORMS 8 | do 9 | for origin in `ls -d /Applications/Xcode*.app/Contents/Developer/Platforms/${platform}.platform/DeviceSupport/` 10 | do 11 | for dest in $(ls -d /Applications/Xcode*.app/Contents/Developer/Platforms/${platform}.platform/DeviceSupport/ | grep -v $x) 12 | do 13 | rsync -r $origin/* $dest 14 | done 15 | done 16 | done 17 | 18 | -------------------------------------------------------------------------------- /xcode/total-test-duration: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | grep seconds | awk -F '[()]' '{print $2 " -> " $1}' | sort -rn | \ 3 | cut -d ' ' -f 1 | awk '{ sum += $1 } END { print sum }' 4 | -------------------------------------------------------------------------------- /xcode/xquick.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FILE=${1:?} 3 | if [ $# -eq 2 ]; then 4 | # Revert 5 | COMMAND='s, xdescribe(.*) // xquick, describe\1,;s, xcontext(.*) // xquick, context\1,;s, xit(.*) // xquick, it\1,;s, // xquick,,' 6 | else 7 | # Apply 8 | COMMAND='s, describe(.*), xdescribe\1 // xquick,;s, context(.*), xcontext\1 // xquick,;s, it(.*), xit\1 // xquick,' 9 | fi 10 | gsed -ri "" -e "$COMMAND" "$FILE" 11 | --------------------------------------------------------------------------------