├── .rubocop.yml ├── gemrc ├── guard.rb ├── yarnrc.yml ├── ripgreprc ├── xrayconfig ├── config ├── bat │ └── config ├── direnv │ └── direnv.toml ├── mise │ └── config.toml ├── xbar │ └── settings.json.erb ├── git │ └── ignore └── pgcli │ └── config ├── jscsrc ├── sublime ├── Git blame.sublime-settings ├── Git Commit.sublime-settings ├── Ruby.sublime-settings ├── JsPrettier.sublime-settings ├── ruby-initialize.sublime-snippet ├── ruby-metaclass.sublime-snippet ├── co-authored-by.sublime-snippet ├── ColorHighlighter.sublime-settings ├── Default (OSX).sublime-keymap └── Preferences.sublime-settings ├── bashrc.d ├── shopt.bash ├── prompt.bash ├── man.bash ├── history.bash ├── android.bash ├── java.bash ├── completion.bash ├── env.bash ├── aliases.bash └── ruby.bash ├── bash_profile ├── bin ├── subl ├── brew-install ├── node-install ├── ruby-install ├── release-notes ├── cci ├── git-ppr ├── every ├── rails_generate_and_open ├── sweep ├── git-pluck ├── defaults-install ├── sup ├── sci ├── bucket ├── sp ├── gemm ├── git-trim ├── outdated-projects └── git-squash ├── railsrc ├── default-npm-packages ├── psqlrc ├── default-gems ├── .gitignore ├── inputrc ├── bundle └── config.erb ├── bashrc ├── liquidpromptrc ├── completions └── pgcli ├── Brewfile.erb ├── terminalizer └── config.yml ├── gitconfig.erb ├── README.md ├── Rakefile └── extras └── Matt.terminal /.rubocop.yml: -------------------------------------------------------------------------------- 1 | rubocop.yml -------------------------------------------------------------------------------- /gemrc: -------------------------------------------------------------------------------- 1 | gem: --no-document 2 | -------------------------------------------------------------------------------- /guard.rb: -------------------------------------------------------------------------------- 1 | clearing :on 2 | -------------------------------------------------------------------------------- /yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableTelemetry: 0 2 | -------------------------------------------------------------------------------- /ripgreprc: -------------------------------------------------------------------------------- 1 | -. 2 | --glob=!.git/ 3 | -------------------------------------------------------------------------------- /xrayconfig: -------------------------------------------------------------------------------- 1 | --- 2 | :editor: "subl" 3 | -------------------------------------------------------------------------------- /config/bat/config: -------------------------------------------------------------------------------- 1 | --theme="Dracula" 2 | -------------------------------------------------------------------------------- /jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "airbnb" 3 | } 4 | -------------------------------------------------------------------------------- /sublime/Git blame.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /bashrc.d/shopt.bash: -------------------------------------------------------------------------------- 1 | shopt -s cdspell 2 | shopt -s checkhash 3 | -------------------------------------------------------------------------------- /config/direnv/direnv.toml: -------------------------------------------------------------------------------- 1 | [global] 2 | load_dotenv=false 3 | -------------------------------------------------------------------------------- /bash_profile: -------------------------------------------------------------------------------- 1 | if [ -f ~/.bashrc ]; then 2 | source ~/.bashrc 3 | fi 4 | -------------------------------------------------------------------------------- /bin/subl: -------------------------------------------------------------------------------- 1 | /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl -------------------------------------------------------------------------------- /railsrc: -------------------------------------------------------------------------------- 1 | -d postgresql 2 | --skip-action-mailbox 3 | --skip-action-text 4 | -------------------------------------------------------------------------------- /default-npm-packages: -------------------------------------------------------------------------------- 1 | @antfu/ni 2 | eslint 3 | prettier 4 | react-devtools 5 | yarn 6 | -------------------------------------------------------------------------------- /bin/brew-install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | brew update 6 | brew bundle --global --no-upgrade 7 | brew cleanup --prune=30 8 | brew upgrade 9 | -------------------------------------------------------------------------------- /sublime/Git Commit.sublime-settings: -------------------------------------------------------------------------------- 1 | // These settings override both User and Default settings for the Git Commit syntax 2 | { 3 | "rulers": [50, 72], 4 | } 5 | -------------------------------------------------------------------------------- /bashrc.d/prompt.bash: -------------------------------------------------------------------------------- 1 | # Use https://github.com/nojhan/liquidprompt 2 | if [ -f $BREW_PREFIX/share/liquidprompt ]; then 3 | . $BREW_PREFIX/share/liquidprompt 4 | fi 5 | -------------------------------------------------------------------------------- /sublime/Ruby.sublime-settings: -------------------------------------------------------------------------------- 1 | // These settings override both User and Default settings for the Ruby syntax 2 | { 3 | "word_separators": "./\\()\"'-:,.;<>~@#$%^&*|+=[]{}`~", 4 | } 5 | -------------------------------------------------------------------------------- /psqlrc: -------------------------------------------------------------------------------- 1 | \set PROMPT1 '%n@%/\n%R%x%# ' 2 | \set PROMPT2 '%R%x%# ' 3 | \set COMP_KEYWORD_CASE upper 4 | \pset border 1 5 | \pset format wrapped 6 | \pset linestyle unicode 7 | \pset null NULL 8 | -------------------------------------------------------------------------------- /bashrc.d/man.bash: -------------------------------------------------------------------------------- 1 | # Use Apple's man page viewer if we are on a local console 2 | if [ "$TERM_PROGRAM" == "Apple_Terminal" ]; then 3 | function man { 4 | open x-man-page://$1 5 | } 6 | fi 7 | -------------------------------------------------------------------------------- /sublime/JsPrettier.sublime-settings: -------------------------------------------------------------------------------- 1 | // Settings in here override those in "/JsPrettier/JsPrettier.sublime-settings", 2 | 3 | { 4 | "prettier_cli_path": "~/.local/share/mise/shims/prettier", 5 | } 6 | -------------------------------------------------------------------------------- /config/mise/config.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | node = "22.15.0" 3 | ruby = "3.4.3" 4 | 5 | [settings] 6 | idiomatic_version_file_enable_tools = ["ruby", "node"] 7 | 8 | [settings.ruby] 9 | verbose_install = true 10 | -------------------------------------------------------------------------------- /config/xbar/settings.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "covid": { 3 | "api_key": <%= prompt("COVID Act Now API key").inspect %>, 4 | "county_fips": <%= prompt("US County FIPS code (for COVID stats)").inspect %> 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /bashrc.d/history.bash: -------------------------------------------------------------------------------- 1 | HISTSIZE=500000 2 | HISTFILESIZE=100000 3 | export HISTIGNORE="&:ls:l:la:ll:exit" 4 | 5 | # Rebind ctrl-r with to show smart history search 6 | export MCFLY_RESULTS=16 7 | eval "$(mcfly init bash)" 8 | -------------------------------------------------------------------------------- /sublime/ruby-initialize.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 7 | init 8 | source.ruby 9 | 10 | -------------------------------------------------------------------------------- /sublime/ruby-metaclass.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 7 | meta 8 | source.ruby 9 | 10 | -------------------------------------------------------------------------------- /default-gems: -------------------------------------------------------------------------------- 1 | bundle_update_interactive 2 | bundler 3 | bundleup 4 | highline 5 | irb 6 | overcommit 7 | rake 8 | retest 9 | rubocop 10 | rubocop-minitest 11 | rubocop-performance 12 | rubocop-rails 13 | solargraph 14 | tomo 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Database of previously entered template values 2 | /.db 3 | 4 | # Brew packages to ignore on this machine 5 | /.brewignore 6 | 7 | # Local Sublime state 8 | /sublime/Package Control* 9 | /sublime/Color Highlighter/ 10 | -------------------------------------------------------------------------------- /sublime/co-authored-by.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | ]]> 5 | co- 6 | text.git.commit 7 | 8 | -------------------------------------------------------------------------------- /bashrc.d/android.bash: -------------------------------------------------------------------------------- 1 | if [ -d ~/Library/Android/sdk ]; then 2 | export ANDROID_HOME=~/Library/Android/sdk 3 | export PATH=$PATH:$ANDROID_HOME/platform-tools 4 | export PATH=$PATH:$ANDROID_HOME/tools 5 | export PATH=$PATH:$ANDROID_HOME/tools/bin 6 | fi 7 | -------------------------------------------------------------------------------- /bin/node-install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mise install "node@$1" 6 | echo -n "Installed: " 7 | mise exec "node@$1" -- node -v 8 | 9 | read -r -p "Press enter to make $1 the global default Node, or ^C to cancel. " 10 | mise use --global "node@$1" 11 | -------------------------------------------------------------------------------- /bashrc.d/java.bash: -------------------------------------------------------------------------------- 1 | if [ -d /System/Library/Frameworks/JavaVM.framework/Versions/1.6 ]; then 2 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home 3 | export MAVEN_OPTS="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError" 4 | fi 5 | 6 | alias gw="./gradlew --daemon" 7 | -------------------------------------------------------------------------------- /inputrc: -------------------------------------------------------------------------------- 1 | "\e[A": history-search-backward 2 | "\e[B": history-search-forward 3 | $if Bash 4 | Space: magic-space 5 | $endif 6 | set completion-ignore-case on 7 | set completion-map-case on 8 | set match-hidden-files off 9 | set show-all-if-ambiguous on 10 | set colored-stats on 11 | -------------------------------------------------------------------------------- /bin/ruby-install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | CFLAGS=-O3 mise install "ruby@$1" 6 | mise exec "ruby@$1" -- bundle plugin install bundler-why 7 | echo -n "Installed: " 8 | mise exec "ruby@$1" -- ruby -v 9 | 10 | read -r -p "Press enter to make $1 the global default Ruby, or ^C to cancel. " 11 | 12 | mise use --global "ruby@$1" 13 | -------------------------------------------------------------------------------- /bundle/config.erb: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_IGNORE_MESSAGES__HTTPARTY: "true" 3 | BUNDLE_GEM__COC: "true" 4 | BUNDLE_GEM__MIT: "true" 5 | BUNDLE_GEM__TEST: "minitest" 6 | BUNDLE_RETRY: "3" 7 | BUNDLE_GITHUB__HTTPS: "true" 8 | BUNDLE_BUILD__MYSQL2: "--with-ldflags=-L<%= brew_prefix %>/opt/zstd/lib --with-opt-dir=<%= brew_prefix %>/opt/openssl" 9 | BUNDLE_GEM__RUBOCOP: "true" 10 | BUNDLE_AUTO_INSTALL: "true" 11 | -------------------------------------------------------------------------------- /bin/release-notes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | release = `gh release list`.lines.first[/\s(v[\d.]+)/, 1] 4 | note = `gh release view #{release}` 5 | 6 | regexp = /- Update rubocop.* requirement from .* \((#\d+)\) @\[?dependabot\b.*\n/ 7 | prs = note.scan(regexp).flatten.sort 8 | consolidated = "- Update rubocop gems (#{prs.join(", ")}) @dependabot\n" 9 | 10 | edited = note.gsub(regexp) do |match| 11 | match.include?(prs.first) ? consolidated : "" 12 | end 13 | 14 | puts edited 15 | -------------------------------------------------------------------------------- /bashrc.d/completion.bash: -------------------------------------------------------------------------------- 1 | if [[ $- == *i* ]] 2 | then 3 | if [ -f $BREW_PREFIX/etc/bash_completion ]; then 4 | . $BREW_PREFIX/etc/bash_completion 5 | fi 6 | # Follow these instructions to enable Heroku CLI Autocomplete 7 | # https://devcenter.heroku.com/articles/heroku-cli-autocomplete 8 | CLI_ENGINE_AC_BASH_SETUP_PATH=~/Library/Caches/heroku/completions/bash_setup 9 | if [ -f $CLI_ENGINE_AC_BASH_SETUP_PATH ]; then 10 | . $CLI_ENGINE_AC_BASH_SETUP_PATH 11 | fi 12 | fi 13 | 14 | # Enable "s" alias to auto-complete just like "git switch" 15 | __git_complete s _git_switch 16 | -------------------------------------------------------------------------------- /sublime/ColorHighlighter.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "search_colors_in": { 3 | "all_content": { 4 | "enabled": true, 5 | "color_highlighters": { 6 | "color_scheme": { 7 | "enabled": true, 8 | "highlight_style": "filled" 9 | }, 10 | "gutter_icons": { 11 | "enabled": false, 12 | }, 13 | "phantoms": { 14 | "enabled": false, 15 | } 16 | } 17 | }, 18 | "selection": { 19 | "enabled": false, 20 | }, 21 | "hover": { 22 | "enabled": false, 23 | }, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bashrc.d/env.bash: -------------------------------------------------------------------------------- 1 | export CLICOLOR=1 2 | export LSCOLORS=Dxxxxxxxxxxxxxxxxxxxxx 3 | export PAGER=/usr/bin/less 4 | export PSQL_PAGER="/usr/bin/less -S" 5 | export LESS=-RFX 6 | export HOMEBREW_INSTALL_BADGE=🍵 7 | export HOMEBREW_NO_ANALYTICS=1 8 | export PATH=$BREW_PREFIX/opt/grep/libexec/gnubin:$BREW_PREFIX/sbin:$PATH 9 | export RIPGREP_CONFIG_PATH=$HOME/.ripgreprc 10 | export NEXT_TELEMETRY_DISABLED=1 11 | export CUCUMBER_PUBLISH_QUIET=true 12 | export THOR_DIFF="colordiff -u" 13 | 14 | if [ $TERM_PROGRAM == 'Apple_Terminal' ]; then 15 | export EDITOR="subl -w" 16 | export BUNDLER_EDITOR=subl 17 | export GEM_EDITOR=subl 18 | else 19 | export EDITOR=vim 20 | fi 21 | -------------------------------------------------------------------------------- /bashrc: -------------------------------------------------------------------------------- 1 | if [ -x /opt/homebrew/bin/brew ]; then 2 | BREW_PREFIX=/opt/homebrew 3 | else 4 | BREW_PREFIX=/usr/local 5 | fi 6 | 7 | export PATH=$BREW_PREFIX/bin:$PATH 8 | 9 | if [ -x $BREW_PREFIX/bin/mise ]; then 10 | eval "$(mise activate bash)" 11 | fi 12 | 13 | # Custom bashrc sources are stored in ~/.bashrc.d 14 | if [[ -d $HOME/.bashrc.d ]] ; then 15 | for config in "$HOME"/.bashrc.d/*.bash ; do 16 | . "$config" 17 | done 18 | fi 19 | unset -v config 20 | 21 | # Custom binaries are stored in ~/.bin 22 | if [ -d ~/.bin ]; then 23 | export PATH=~/.bin:$PATH 24 | fi 25 | 26 | if [ -x "$BREW_PREFIX/bin/zoxide" ]; then 27 | eval "$(zoxide init --cmd j bash)" 28 | fi 29 | 30 | unset -v BREW_PREFIX 31 | -------------------------------------------------------------------------------- /liquidpromptrc: -------------------------------------------------------------------------------- 1 | LP_USER_ALWAYS=0 2 | 3 | LP_ENABLE_AWS_PROFILE=0 4 | LP_ENABLE_BATT=0 5 | LP_ENABLE_BZR=0 6 | LP_ENABLE_FOSSIL=0 7 | LP_ENABLE_FQDN=0 8 | LP_ENABLE_GIT=1 9 | LP_ENABLE_HG=0 10 | LP_ENABLE_JOBS=0 11 | LP_ENABLE_LOAD=0 12 | LP_ENABLE_PERM=0 13 | LP_ENABLE_PROXY=0 14 | LP_ENABLE_RUBY_VENV=0 15 | LP_ENABLE_RUNTIME=0 16 | LP_ENABLE_RUNTIME_BELL=0 17 | LP_ENABLE_SCREEN_TITLE=0 18 | LP_ENABLE_SHORTEN_PATH=1 19 | LP_ENABLE_SSH_COLORS=0 20 | LP_ENABLE_SUDO=0 21 | LP_ENABLE_SVN=0 22 | LP_ENABLE_TEMP=0 23 | LP_ENABLE_TIME=0 24 | LP_ENABLE_TITLE=0 25 | LP_ENABLE_VCS_ROOT=0 26 | LP_MARK_SHORTEN_PATH=… 27 | LP_PATH_KEEP=1 28 | LP_PATH_LENGTH=35 29 | LP_PATH_METHOD=truncate_chars_from_path_left 30 | LP_PATH_VCS_ROOT=1 31 | -------------------------------------------------------------------------------- /bin/cci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "cgi" 4 | require "open3" 5 | 6 | def open_circle_ci 7 | origin = capture("git remote get-url origin").chomp 8 | brand = origin.include?("bitbucket.org") ? "bitbucket" : "github" 9 | repo = origin[%r{(:|/)(.*?/.*?)\.git$}, 2] 10 | branch = capture("git branch --show-current").chomp 11 | 12 | url = "https://app.circleci.com/pipelines/#{brand}/#{repo}?branch=#{CGI.escape(branch)}" 13 | system "open", url 14 | end 15 | 16 | def capture(command) 17 | out_err, status = Open3.capture2e(command) 18 | return out_err if status.success? 19 | 20 | $stderr.puts 21 | $stderr.puts(out_err || "Could not execute #{command}") 22 | exit(1) 23 | end 24 | 25 | open_circle_ci 26 | -------------------------------------------------------------------------------- /bin/git-ppr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "pathname" 4 | require "shellwords" 5 | 6 | return unless system "git push -u origin head" 7 | 8 | project_root = Pathname.new(`git rev-parse --show-toplevel`.chomp) 9 | pr_template_path = project_root.join(".github/PULL_REQUEST_TEMPLATE.md") 10 | pr_body = pr_template_path.read if pr_template_path.exist? 11 | 12 | git_branch = `git branch --show-current`.chomp 13 | tracker_id = git_branch[/\d{8,}/] 14 | 15 | pr_body.sub!(%r{(pivotaltracker\.com/story/show/)\d+}, '\1' + tracker_id) if pr_body && tracker_id 16 | 17 | gh_command = +"gh pr create --web" 18 | gh_command << " --body #{pr_body.shellescape}" if pr_body 19 | 20 | exit Process.last_status.exitstatus unless system gh_command 21 | -------------------------------------------------------------------------------- /config/git/ignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.class 4 | *.o 5 | *.pyc 6 | *.so 7 | .sass-cache 8 | 9 | # Logs and databases # 10 | ###################### 11 | *.log 12 | *.sqlite 13 | *.sqlite3 14 | 15 | # OS generated files # 16 | ###################### 17 | .DS_Store 18 | 19 | # Swap files # 20 | ############## 21 | *.swp 22 | 23 | # Backups # 24 | ########### 25 | *.orig 26 | 27 | # Repositories # 28 | ################ 29 | .hg 30 | .svn 31 | 32 | # Projects # 33 | ############ 34 | *.tmproj 35 | *.pbxuser 36 | *.mode1v3 37 | __MACOSX 38 | .tm_properties 39 | .bundle/ 40 | .pairs 41 | .powder 42 | *.sublime-project 43 | *.sublime-workspace 44 | .idea/ 45 | .byebug_history 46 | .vscode/ 47 | .envrc 48 | mise.local.toml 49 | .venv/ 50 | -------------------------------------------------------------------------------- /bin/every: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | puts(<<~USAGE) & exit if (%w[-h --help] & ARGV).any? 4 | 5 | Usage: every INTERVAL COMMAND... 6 | 7 | Run the given COMMAND forever (or until it fails), waiting INTERVAL between 8 | each execution. INTERVAL must be a number with unit, e.g.: 30s, 5m, 1h. 9 | 10 | Examples: 11 | 12 | # Print "hello" every five seconds 13 | every 5s echo hello 14 | 15 | # Fetch a web page every minute 16 | every 1m curl https://www.githubstatus.com/ 17 | 18 | USAGE 19 | 20 | interval, *command = ARGV 21 | 22 | seconds = case interval 23 | when /\A\d+s\z/ then interval[/\d+/].to_i 24 | when /\A\d+m\z/ then interval[/\d+/].to_i * 60 25 | when /\A\d+h\z/ then interval[/\d+/].to_i * 60 * 60 26 | else raise ArgumentError, "First arg must be an interval (e.g. 30s)" 27 | end 28 | 29 | sleep seconds while system(*command) 30 | -------------------------------------------------------------------------------- /bin/rails_generate_and_open: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Passes arguments to `rails generate` and opens the resulting files 4 | # in Sublime. Take from here: 5 | # http://www.davidverhasselt.com/auto-open-rails-generator-files/ 6 | 7 | require "pty" 8 | 9 | def extract_created_files(lines) 10 | lines.map do |line| 11 | command, file = colorless(line).split 12 | file if command == "create" 13 | end.compact.reverse 14 | end 15 | 16 | def colorless(str) 17 | str.gsub(/\033\[\d+m/, "") 18 | end 19 | 20 | command = %w[bin/rails generate] + ARGV 21 | lines = [] 22 | 23 | # Use PTY to force Thor to output colored text 24 | PTY.spawn(*command) do |r, _, _| 25 | while (line = r.gets) 26 | puts line 27 | lines << line 28 | end 29 | end 30 | 31 | files = extract_created_files(lines) 32 | 33 | if files.any? 34 | puts "\nOpening #{files.length} file#{files.empty? ? "" : "s"}…" 35 | exec("subl", *files) 36 | end 37 | -------------------------------------------------------------------------------- /bin/sweep: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "date" 4 | THIRTY_DAYS_AGO = Date.today - 30 5 | 6 | def list_files 7 | Dir.entries(".").reject { |file| file.start_with?(".") } 8 | end 9 | 10 | def last_access_date(file) 11 | return File.ctime(file).to_date if File.exist?(file) && (File.file?(file) || File.symlink?(file)) 12 | return nil unless File.directory?(file) 13 | 14 | Dir.chdir(file) do 15 | access_dates = list_files.each_with_object([]) do |child, dates| 16 | dates << last_access_date(child) unless child.start_with?(".") 17 | end 18 | access_dates.compact.max 19 | end 20 | end 21 | 22 | ARGV << "." if ARGV.empty? 23 | 24 | ARGV.each do |dir| 25 | Dir.chdir(dir) do 26 | list_files.each do |file| 27 | next if file.start_with?(".") 28 | 29 | date = last_access_date(file) 30 | next if date.nil? || date > THIRTY_DAYS_AGO 31 | 32 | puts "Trashing #{file}" 33 | system "trash", file 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /bashrc.d/aliases.bash: -------------------------------------------------------------------------------- 1 | alias b='bundle exec' 2 | alias bup='bundleup' 3 | alias cat='bat' 4 | # Change to the root level directory the current git repository 5 | alias cdg='cd $(git rev-parse --show-toplevel || pwd)' 6 | alias diff=colordiff 7 | alias get='git' 8 | alias hl='heroku local -p 3000 $(test -f Procfile.dev && echo "-f Procfile.dev")' 9 | alias httpserve='ruby -run -e httpd -- --port=8888' 10 | alias l='eza' 11 | alias la='eza -la --git --icons' 12 | alias ll='eza -l --git --icons' 13 | alias ls='eza' 14 | alias n='npx --no-install' 15 | alias ras='bin/rails server -b 0.0.0.0 -p 3000' 16 | alias s="git sw" 17 | alias secret="ruby -rsecurerandom -e 'puts SecureRandom.hex(64)'" 18 | alias top='top -s 5 -o cpu -stats pid,user,command,cpu,rsize,vsize,threads,state' 19 | alias uuid="ruby -rsecurerandom -e 'puts SecureRandom.uuid'" 20 | alias yi="yarn install --check-files" 21 | alias ys="yarn start" 22 | alias yup="yarn upgrade-interactive" 23 | 24 | function mcdir() { 25 | mkdir -p $1 && cd $1 26 | } 27 | 28 | function gb() { 29 | gh pr view --web $1 > /dev/null 2>&1 || gh repo view --web 30 | } 31 | -------------------------------------------------------------------------------- /sublime/Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { "keys": ["super+\\"], "command": "toggle_side_bar" }, 3 | { "keys": ["super+shift+\\"], "command": "reveal_in_side_bar"}, 4 | { "keys": ["super+shift+w"], "command": "close_other_tabs" }, 5 | { "keys": ["super+backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Line.sublime-macro"} }, 6 | { "keys": ["super+shift+r"], "command": "goto_symbol_in_project" }, 7 | { "keys": ["super+b"], "command": "goto_definition" }, 8 | { "keys": ["super+shift+t"], "command": "reopen_last_file" }, 9 | 10 | // DashDoc package 11 | { 12 | "keys": ["ctrl+h"], 13 | "command": "dash_doc" 14 | }, 15 | 16 | // Disable shortcut that conflicts with Dash macOS app 17 | { 18 | "keys": ["super+shift+space"], 19 | "command": "pass" 20 | }, 21 | 22 | // Make paste_and_indent the default behavior 23 | { "keys": ["super+shift+v"], "command": "paste" }, 24 | { "keys": ["super+v"], "command": "paste_and_indent" }, 25 | 26 | // Doc-Block package 27 | { "keys": ["super+shift+j"], "command": "jsdocs_join", "context": 28 | [ 29 | { "key": "selector", "operator": "equal", "operand": "comment.block" } 30 | ] 31 | }, 32 | { "keys": ["super+shift+j"], "command": "jsdocs_join", "context": 33 | [ 34 | { "key": "selector", "operator": "equal", "operand": "comment.line" } 35 | ] 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /bin/git-pluck: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "shellwords" 3 | 4 | # Colorization from http://stackoverflow.com/a/11482430 5 | class String 6 | def colorize(color_code) 7 | "\e[#{color_code}m#{self}\e[0m" 8 | end 9 | 10 | def red 11 | colorize(31) 12 | end 13 | 14 | def cyan 15 | colorize(36) 16 | end 17 | end 18 | 19 | def pluck(git_dir, sha) 20 | commands = [ 21 | "git fetch --quiet --no-tags #{git_dir.shellescape}", 22 | "git cherry-pick #{sha.shellescape}" 23 | ] 24 | 25 | commands.each do |cmd| 26 | puts cmd.cyan 27 | system(cmd) || fail 28 | end 29 | end 30 | 31 | def usage_and_exit 32 | puts <<~USAGE 33 | git pluck 34 | 35 | Cherry-picks a commit identified by SHA from another Git repository 36 | into the current repository. 37 | 38 | Example: 39 | 40 | git pluck ../rails-starter ab1df43 41 | USAGE 42 | 43 | exit(2) 44 | end 45 | 46 | def fail(reason = nil) 47 | puts(reason.red) unless reason.nil? 48 | exit(1) 49 | end 50 | 51 | def find_repository(path) 52 | git_repo = [File.join(path, ".git"), path].find { |d| Dir.exist?(d) } 53 | 54 | git_repo || fail("#{path} does not exist") 55 | end 56 | 57 | if $PROGRAM_NAME == __FILE__ 58 | usage_and_exit if ARGV.length != 2 || (%w[-h --help] & ARGV).any? 59 | 60 | repo = find_repository(ARGV[0]) 61 | sha = ARGV[1] 62 | 63 | pluck(repo, sha) 64 | end 65 | -------------------------------------------------------------------------------- /bin/defaults-install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Dock 6 | defaults write com.apple.dock autohide -bool true 7 | defaults write com.apple.dock launchanim -bool false 8 | defaults write com.apple.dock orientation right 9 | defaults write com.apple.dock show-recents -bool false 10 | defaults write com.apple.dock tilesize -int 64 11 | 12 | # Screenshots 13 | defaults write com.apple.screencapture location ~/Downloads/ 14 | defaults write com.apple.screencapture disable-shadow -bool true 15 | defaults write com.apple.screencapture show-thumbnail -bool false 16 | 17 | # Finder 18 | defaults write NSGlobalDomain AppleShowAllExtensions -bool true 19 | defaults write com.apple.finder FXPreferredViewStyle -string clmv 20 | defaults write com.apple.finder _FXSortFoldersFirst -bool true 21 | defaults write com.apple.finder FXRemoveOldTrashItems -bool true 22 | defaults write com.apple.finder FXEnableExtensionChangeWarning -bool false 23 | defaults write com.apple.finder ShowExternalHardDrivesOnDesktop -bool false 24 | defaults write com.apple.finder ShowRemovableMediaOnDesktop -bool false 25 | 26 | # Enable ability to inspect WebKit views 27 | # https://blog.jim-nielsen.com/2022/inspecting-web-views-in-macos/ 28 | defaults write NSGlobalDomain WebKitDeveloperExtras -bool true 29 | defaults write -g WebKitDeveloperExtras -bool YES 30 | 31 | # Menu bar 32 | defaults -currentHost write -globalDomain NSStatusItemSpacing -int 8 33 | 34 | killall Dock 35 | killall Finder 36 | killall SystemUIServer 37 | -------------------------------------------------------------------------------- /bin/sup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "highline" 3 | HighLine.colorize_strings 4 | 5 | def run(host, cmd, exit_on_failure: true) 6 | ssh_cmd = ["ssh", config, with_user(host), cmd].compact.join(" ") 7 | puts ssh_cmd.yellow 8 | system(ssh_cmd).tap do |success| 9 | exit(1) if exit_on_failure && !success 10 | end 11 | end 12 | 13 | def with_user(host) 14 | return host if host.include?("@") 15 | 16 | "root@#{host}" 17 | end 18 | 19 | def config 20 | "-F .ssh/config" if File.file?(".ssh/config") 21 | end 22 | 23 | def ubuntu16?(host) 24 | !run(host, "type aptitude 2>/dev/null", exit_on_failure: false) 25 | end 26 | 27 | completed = [] 28 | 29 | ARGV.each_with_index do |host, i| 30 | if i > 0 31 | puts "Next up: #{host}".red 32 | exit unless HighLine.new.agree("Proceed (y/n)? ".blue) 33 | end 34 | 35 | if ubuntu16?(host) 36 | run host, "sudo apt-get -y autoremove" 37 | run host, "sudo apt-get -y autoclean" 38 | run host, "sudo DEBIAN_FRONTEND=noninteractive apt-get -q -q -y update" 39 | run host, 40 | "sudo DEBIAN_FRONTEND=noninteractive apt-get -q -q " \ 41 | '-o DPkg::options::="--force-confdef" ' \ 42 | '-o DPkg::options::="--force-confold" dist-upgrade' 43 | else 44 | run host, "DEBIAN_FRONTEND=noninteractive sudo aptitude -q -q -y update" 45 | run host, "DEBIAN_FRONTEND=noninteractive sudo aptitude -q -q safe-upgrade" 46 | end 47 | 48 | run host, "sudo checkrestart -v" 49 | 50 | completed << host 51 | completed.each { |h| puts "✔ #{h}".green } 52 | end 53 | -------------------------------------------------------------------------------- /bashrc.d/ruby.bash: -------------------------------------------------------------------------------- 1 | export PATH=".git/safe/../../bin:$PATH" 2 | export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$BREW_PREFIX/opt/openssl@3" 3 | 4 | export TESTOPTS="--pride" 5 | 6 | # OS X has its own way of setting LANG, but only at the console. 7 | # By declaring here in .bashrc, daemons like Pow will also pick it up. 8 | export LANG=en_US.UTF-8 9 | 10 | # Use Homebrew's terminal-notifier, which is much faster than Ruby's. 11 | if [ -x $BREW_PREFIX/bin/terminal-notifier ]; then 12 | export TERMINAL_NOTIFIER_BIN=$BREW_PREFIX/bin/terminal-notifier 13 | fi 14 | 15 | # Shortcut for running `rails` or `rake`, based on a simple heuristic 16 | # to determine which command is appropriate. 17 | function r() { 18 | if [ $# -eq 0 ] || [ ! -x bin/rails ]; then 19 | if [ -x bin/rake ]; then 20 | bin/rake "$@" 21 | elif [ -f Gemfile.lock ]; then 22 | bundle exec rake "$@" 23 | else 24 | rake "$@" 25 | fi 26 | else 27 | bin/rails "$@" 28 | fi 29 | } 30 | 31 | # Shortcut to start the test watcher appropriate for the project 32 | function w() { 33 | if [ -x bin/mt ]; then 34 | bin/mt --watch "$@" 35 | else 36 | retest --notify "$@" 37 | fi 38 | } 39 | 40 | # Search all bundler dependencies for a given pattern 41 | # Courtesy of https://everydayrails.com/2018/06/11/bundler-shortcuts.html 42 | function bs() { 43 | rg "$1" $(bundle list --paths) 44 | } 45 | 46 | alias rgen="~/.bin/rails_generate_and_open" 47 | alias routes="bin/rails routes -g '^/(?!rails/active_storage|rails/action_mailbox|rails/conductor|(recede|resume|refresh)_historical_location).*$'" 48 | 49 | eval "$(tomo completion-script)" 50 | -------------------------------------------------------------------------------- /bin/sci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "json" 4 | require "open3" 5 | 6 | def open_semaphore_ci 7 | url = if (workflow_url = find_workflow_url_based_on_pr_check) 8 | workflow_url.sub(%r{/summary}, "").sub(/&report_id=[^&+]/, "") 9 | else 10 | subdomain, project = find_project_based_on_git_origin 11 | project_name = project.dig("metadata", "name") 12 | "https://#{subdomain}.semaphoreci.com/projects/#{project_name}" 13 | end 14 | 15 | system "open", url 16 | end 17 | 18 | def find_workflow_url_based_on_pr_check 19 | `gh pr checks 2> /dev/null`[/(https:\S+semaphoreci\.com\S+)/, 1] 20 | end 21 | 22 | def find_project_based_on_git_origin 23 | origin = capture("git remote get-url origin").chomp 24 | contexts_with_current_first.each_with_index do |context, index| 25 | system("sem context #{context}", exception: true) unless index.zero? 26 | projects = capture_json("sem get projects --verbose") 27 | project = projects.find { _1.dig("spec", "repository", "url") == origin } 28 | 29 | return [context.sub(/_semaphoreci_com$/, ""), project] unless project.nil? 30 | end 31 | 32 | raise "Couldn’t find Semaphore project for #{origin}" 33 | end 34 | 35 | def contexts_with_current_first 36 | capture("sem context").lines.sort.reverse.map { _1[/(\S+)$/, 1] } 37 | end 38 | 39 | def capture_json(command) 40 | raw_json = capture(command)[%r(^[\d/]+ [\d:]+ (\[\{.*?)$), 1] 41 | JSON.parse(raw_json) 42 | end 43 | 44 | def capture(command) 45 | out_err, status = Open3.capture2e(command) 46 | return out_err if status.success? 47 | 48 | $stderr.puts 49 | $stderr.puts(out_err || "Could not execute #{command}") 50 | exit(1) 51 | end 52 | 53 | open_semaphore_ci 54 | -------------------------------------------------------------------------------- /completions/pgcli: -------------------------------------------------------------------------------- 1 | _pg_databases() 2 | { 3 | # -w was introduced in 8.4, https://launchpad.net/bugs/164772 4 | # "Access privileges" in output may contain linefeeds, hence the NF > 1 5 | COMPREPLY=( $( compgen -W "$( psql -AtqwlF $'\t' 2>/dev/null | \ 6 | awk 'NF > 1 { print $1 }' )" -- "$cur" ) ) 7 | } 8 | 9 | _pg_users() 10 | { 11 | # -w was introduced in 8.4, https://launchpad.net/bugs/164772 12 | COMPREPLY=( $( compgen -W "$( psql -Atqwc 'select usename from pg_user' \ 13 | template1 2>/dev/null )" -- "$cur" ) ) 14 | [[ ${#COMPREPLY[@]} -eq 0 ]] && COMPREPLY=( $( compgen -u -- "$cur" ) ) 15 | } 16 | 17 | __pg_init_completion() 18 | { 19 | COMPREPLY=() 20 | _get_comp_words_by_ref cur prev words cword 21 | } 22 | 23 | _pgcli() 24 | { 25 | local cur prev words cword 26 | if declare -F _init_completions >/dev/null 2>&1; then 27 | _init_completion 28 | else 29 | __pg_init_completion 30 | fi 31 | 32 | case $prev in 33 | -h|--host) 34 | _known_hosts_real "$cur" 35 | return 0 36 | ;; 37 | -U|--user) 38 | _pg_users 39 | return 0 40 | ;; 41 | -d|--dbname) 42 | _pg_databases 43 | return 0 44 | ;; 45 | --help|-v|--version|-p|--port|-R|--row-limit) 46 | # all other arguments are noop with these 47 | return 0 48 | ;; 49 | esac 50 | 51 | case "$cur" in 52 | --*) 53 | # return list of available options 54 | COMPREPLY=( $( compgen -W '--host --port --user --password --no-password 55 | --single-connection --version --dbname --pgclirc --dsn 56 | --row-limit --help' -- "$cur" ) ) 57 | [[ $COMPREPLY == *= ]] && compopt -o nospace 58 | return 0 59 | ;; 60 | -) 61 | # only complete long options 62 | compopt -o nospace 63 | COMPREPLY=( -- ) 64 | return 0 65 | ;; 66 | *) 67 | # return list of available databases 68 | _pg_databases 69 | esac 70 | } && 71 | complete -F _pgcli pgcli 72 | -------------------------------------------------------------------------------- /bin/bucket: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "cgi" 3 | require "open3" 4 | 5 | # Colorization from http://stackoverflow.com/a/11482430 6 | class String 7 | def colorize(color_code) 8 | "\e[#{color_code}m#{self}\e[0m" 9 | end 10 | 11 | def red 12 | colorize(31) 13 | end 14 | end 15 | 16 | def usage_and_exit 17 | puts <<~USAGE 18 | bucket operates on the Bitbucket-hosted git repository that is in your 19 | working directory. It assumes that you have a remote named "origin" that 20 | points to Bitbucket. 21 | 22 | Usage: 23 | 24 | bucket show # Opens the Bitbucket website for this repository 25 | bucket # Shortcut for `bucket show` 26 | bucket compare # Opens the "compare" view for the current branch 27 | bucket pull # Creates a new pull request for the current branch 28 | bucket branches # Shows all un-merged feature branches 29 | USAGE 30 | 31 | exit(2) 32 | end 33 | 34 | def git(*args) 35 | out, err, status = Open3.capture3("git", *args) 36 | fail(err) unless status.success? 37 | 38 | out 39 | end 40 | 41 | def pull_url 42 | source = [repository, current_sha, current_branch].join(":") 43 | "#{show_url}/pull-request/new?source=#{CGI.escape(source)}" 44 | end 45 | 46 | def compare_url 47 | "#{show_url}/branch/#{CGI.escape(current_branch)}#diff" 48 | end 49 | 50 | def branches_url 51 | "#{show_url}/commits/featurebranches" 52 | end 53 | 54 | def show_url 55 | "https://bitbucket.org/#{repository}" 56 | end 57 | 58 | def repository 59 | @repo ||= git("remote", "-v")[%r{bitbucket.org(/|:)(.*)\.git \(push\)}, 2] 60 | @repo || fail("This doesn’t seem to be a Bitbucket repository.") 61 | end 62 | 63 | def current_branch 64 | @current_branch ||= git("branch")[/\s*\*\s*(.*)/, 1] 65 | end 66 | 67 | def current_sha 68 | @current_sha ||= git("rev-parse", current_branch)[/^\S{12}/] 69 | end 70 | 71 | def fail(reason = nil) 72 | puts reason.to_s.red unless reason.nil? 73 | exit(1) 74 | end 75 | 76 | if $PROGRAM_NAME == __FILE__ 77 | usage_and_exit if (ARGV & %w[-h --help help]).any? 78 | 79 | command = ARGV.first || "show" 80 | url = send("#{command}_url") 81 | 82 | system("open", url) 83 | end 84 | -------------------------------------------------------------------------------- /Brewfile.erb: -------------------------------------------------------------------------------- 1 | tap "heroku/brew" 2 | 3 | brew "bash" 4 | brew "bash-completion" 5 | brew "bat" 6 | brew "bison" 7 | brew "circleci" 8 | brew "cloc" 9 | brew "colordiff" 10 | brew "diff-so-fancy" 11 | brew "doggo" 12 | brew "eza" 13 | brew "fd" 14 | brew "gh" 15 | brew "git" 16 | brew "git-extras" 17 | brew "git-lfs" 18 | brew "git-who" 19 | brew "gmp" 20 | brew "heroku/brew/heroku" 21 | brew "httpie" 22 | brew "imagemagick" 23 | brew "jq" 24 | brew "libffi" 25 | brew "libsodium" 26 | brew "libxml2" 27 | brew "libxslt" 28 | brew "libyaml" 29 | brew "liquidprompt" 30 | brew "magic-wormhole" 31 | brew "mas" 32 | brew "mcfly" 33 | brew "mise" 34 | brew "mongosh" 35 | brew "mmv" 36 | brew "openssl" 37 | brew "openssl@3" 38 | brew "pgcli" 39 | brew "php" 40 | brew "postgresql@17", restart_service: true, link: true 41 | brew "readline" 42 | brew "redis", restart_service: true 43 | brew "ripgrep" 44 | brew "ruby-build" 45 | brew "rust" 46 | brew "shellcheck" 47 | brew "trash" 48 | brew "wget" 49 | brew "zoxide" 50 | 51 | cask "alfred" 52 | cask "bruno" 53 | cask "dash" 54 | cask "disk-arbitrator" 55 | cask "docker" 56 | cask "ente-auth" 57 | cask "figma" 58 | cask "flux" 59 | cask "font-inter" 60 | cask "font-meslo-lg-nerd-font" 61 | cask "gitify" 62 | cask "google-chrome" 63 | cask "hot" 64 | cask "iina" 65 | cask "logitech-camera-settings" 66 | cask "mitmproxy" 67 | cask "monitorcontrol" 68 | cask "moom" 69 | cask "ngrok" 70 | cask "notunes" 71 | cask "pika" 72 | cask "pixelsnap" 73 | cask "pop" 74 | cask "slack" 75 | cask "sublime-text" 76 | cask "visual-studio-code" 77 | cask "xbar" 78 | cask "zoom" 79 | 80 | mas "Amphetamine", id: 937984704 81 | mas "Aware", id: 1082170746 82 | mas "Barbee", id: 1548711022 83 | mas "BreakTime", id: 427475982 84 | mas "Fantastical 2", id: 975937182 85 | mas "Gifski", id: 1351639930 86 | mas "Ka-Block!", id: 1335413823 87 | mas "Keynote", id: 409183694 88 | mas "Marked 2", id: 890031187 89 | mas "Numbers", id: 409203825 90 | mas "Pages", id: 409201541 91 | mas "Parcel - Delivery Tracking", id: 639968404 92 | mas "Pixelmator Pro", id: 1289583905 93 | mas "Reeder 5.", id: 1529448980 94 | mas "The Unarchiver", id: 425424353 95 | mas "Things3", id: 904280696 96 | mas "Timery for Toggle", id: 1425368544 97 | mas "Trello", id: 1278508951 98 | mas "Xcode", id: 497799835 99 | -------------------------------------------------------------------------------- /bin/sp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if ARGV.length > 1 || ARGV.grep(/(-h|--help)/).any? 4 | puts <<~USAGE 5 | Sublime Text open as project 6 | 7 | Usage: sp [DIRECTORY] 8 | 9 | Opens DIRECTORY in Sublime Text as a project, creating a new .sublime-project 10 | descriptor if one does not already exist. 11 | 12 | If a .sublime-project file already exists in DIRECTORY, uses that as the 13 | project configuration by passing it to `subl --project`. 14 | 15 | Otherwise creates a new, empty .sublime-project file in DIRECTORY and names it 16 | the same as DIRECTORY. For example, `sp my-project` will create the file 17 | my-project/my-project.sublime-project and open it in Sublime Text. 18 | 19 | If DIRECTORY is omitted, the current and all parent directories will be 20 | searched for an existing .sublime-project. If one can't be found, a new 21 | project will be created in the current directory. 22 | 23 | The `subl` command must be installed and available in the PATH. 24 | USAGE 25 | exit 26 | end 27 | 28 | def find_project_directory 29 | curr = start = Dir.pwd 30 | until Dir["*.sublime-project"].any? || curr.nil? 31 | Dir.chdir("..") 32 | curr = curr == Dir.pwd ? nil : Dir.pwd 33 | end 34 | curr 35 | ensure 36 | Dir.chdir(start) 37 | end 38 | 39 | directory = File.expand_path(ARGV.first || find_project_directory || ".", ".") 40 | name = File.basename(directory) 41 | name = "root" if name =~ %r{/} 42 | project = File.join(directory, "#{name}.sublime-project") 43 | 44 | unless File.exist?(project) 45 | if (glob = Dir[File.join(directory, "*.sublime-project")]).any? 46 | project = glob.first 47 | else 48 | File.write(project, <<~JSON) 49 | { 50 | "folders": [ 51 | { 52 | "path": ".", 53 | "folder_exclude_patterns": ["build", "public/assets", "public/packs"], 54 | "file_exclude_patterns": ["yarn.lock", "yarn-error.log"] 55 | } 56 | ], 57 | "settings": { 58 | "js_prettier": { 59 | "auto_format_on_save": true, 60 | "auto_format_on_save_excludes": ["*.md"] 61 | }, 62 | "SublimeLinter.linters.scss.@disable": true, 63 | "SublimeLinter.linters.rubocop.use_bundle_exec": false 64 | } 65 | } 66 | JSON 67 | end 68 | end 69 | 70 | puts "Opening #{project}" 71 | exec("subl", "--project", project) 72 | -------------------------------------------------------------------------------- /bin/gemm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "open3" 4 | 5 | def run(command) 6 | out_err, status = Open3.capture2e(command) 7 | return out_err if status.success? 8 | 9 | $stderr.puts 10 | $stderr.puts(out_err || "Could not execute #{command}") 11 | exit(1) 12 | end 13 | 14 | def choose(question, choices) 15 | puts 16 | choices.each_with_index do |choice, i| 17 | puts "#{i + 1}. #{choice}" 18 | end 19 | print "#{question} [1-#{choices.length}]? " 20 | index = [$stdin.gets.to_i, 1].max 21 | choices[[index, choices.length].min - 1] 22 | end 23 | 24 | def gemfile_groups 25 | File.read("Gemfile").scan(/^group\s*\(?(.*)\)?\s+do\s*$/).map(&:first) 26 | end 27 | 28 | def version_specs(version) 29 | specs = [version] 30 | parts = version.split(".") 31 | parts.length.downto(2).each do |limit| 32 | specs << "~> #{parts[0...limit].join(".")}" 33 | end 34 | specs.reverse 35 | end 36 | 37 | ARGV.each do |gem| 38 | if File.read("Gemfile") =~ /^gem "#{gem}"/ 39 | puts "#{gem} is already in the Gemfile!" 40 | exit 1 41 | end 42 | 43 | print "Looking for #{gem}..." 44 | version = Gem.latest_version_for(gem).to_s 45 | raise "Could not find #{gem}" if version.empty? 46 | 47 | puts "\rLooking for #{gem}... Found it! (#{version})" 48 | group = gemfile_groups.any? ? choose("Where do you want it", [""] + gemfile_groups) : "" 49 | spec = choose("Version specification", [""] + version_specs(version)) 50 | 51 | insert = %(gem "#{gem}") 52 | insert << %(, "#{spec}") unless spec == "" 53 | insert << "\n" 54 | insert = " #{insert}" unless group == "" 55 | 56 | puts 57 | ok = [] 58 | ok << "group #{group} do" unless group == "" 59 | ok << insert.chomp 60 | ok << "end" unless group == "" 61 | puts ok.join("\n") 62 | print "Does this look okay? [Yn]? " 63 | exit unless $stdin.gets =~ /^($|y)/i 64 | 65 | in_group = nil 66 | lines = (File.readlines("Gemfile") + [""]).each_cons(2).with_object([]) do |(curr, succ), out| 67 | out << curr 68 | in_group = "" if !in_group && succ =~ /^#?\s*(gem\s|group)/ 69 | in_group = $1 if curr =~ /^group\s*\(?(.*)\)?\s+do\s*$/ 70 | match = succ =~ /^\s*gem\s/ && insert && insert.tr("-_", "") < succ.tr("-_", "") 71 | bottom = (out.length > 1 && succ.strip.empty?) || (in_group != "" && succ =~ /^end/) 72 | 73 | next unless in_group == group && insert && (match || bottom) 74 | 75 | out << insert 76 | insert = nil 77 | end 78 | File.write("Gemfile", lines.join) 79 | 80 | puts 81 | print "Updating Gemfile..." 82 | 83 | run("bundle install") 84 | 85 | puts "\rUpdating Gemfile... DONE" 86 | end 87 | -------------------------------------------------------------------------------- /bin/git-trim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | RESERVED_BRANCHES = [ 4 | /^acceptance$/, 5 | /^develop$/, 6 | /^development$/, 7 | /^gh-pages$/, 8 | /^HEAD$/, 9 | /^hotfix.*$/, 10 | /^main$/, 11 | /^master$/, 12 | /^.*production$/, 13 | /^staging$/, 14 | /^release.*$/, 15 | /^stable.*$/, 16 | /^trunk$/ 17 | ].freeze 18 | 19 | require "shellwords" 20 | 21 | def trim 22 | git "fetch --prune --quiet" 23 | 24 | merged_local_branches.each { |b| git "branch -D #{b.shellescape}" } 25 | orphaned_branches.each { |b| git "branch -D #{b.shellescape}" } 26 | 27 | branches = merged_remote_branches 28 | if branches.any? && confirm_delete("Is it okay to delete the following merged #{"remote".yellow} branches?", branches) 29 | branches.each { |b| git "push origin :#{b.shellescape}" } 30 | end 31 | puts 32 | puts "All done! ✨".green 33 | puts "The unmerged and reserved branches listed below have been left as is." 34 | puts 35 | system "git branch -vv" 36 | puts 37 | end 38 | 39 | def merged_local_branches 40 | branches = git("branch --format '%(refname:short)' --merged HEAD", echo: false).lines(chomp: true) 41 | excluding_reserved(branches) 42 | end 43 | 44 | def orphaned_branches 45 | git("for-each-ref --format '%(refname:short) %(upstream:track)' refs/heads", echo: false) 46 | .scan(/^(.+) \[gone\]$/) 47 | .flatten 48 | end 49 | 50 | def merged_remote_branches 51 | branches = git("branch -r --format '%(refname:short)' --merged HEAD", echo: false) 52 | .scan(%r{^origin/(.+)}) 53 | .flatten 54 | excluding_reserved(branches) 55 | end 56 | 57 | def excluding_reserved(branches) 58 | @current_branch ||= git("branch --show-current", echo: false).chomp 59 | branches.reject do |branch| 60 | branch == @current_branch || RESERVED_BRANCHES.any? { |pattern| branch.match?(pattern) } 61 | end 62 | end 63 | 64 | def confirm_delete(message, branches) 65 | puts 66 | puts message 67 | puts 68 | branches.each { |branch| puts " origin/#{branch.blue}" } 69 | puts 70 | print 'Really delete (type "yes" to confirm)? '.yellow 71 | gets.chomp == "yes" 72 | end 73 | 74 | def git(command, echo: true) 75 | command = "git #{command}" 76 | puts ">>>> #{command}".grey if echo 77 | result = `#{command}` 78 | return result if Process.last_status.success? 79 | 80 | warn "Failed: #{command}".red 81 | exit 1 82 | end 83 | 84 | class String 85 | def colorize(color_code) 86 | "\e[#{color_code}m#{self}\e[0m" 87 | end 88 | 89 | def blue 90 | colorize(34) 91 | end 92 | 93 | def red 94 | colorize(31) 95 | end 96 | 97 | def green 98 | colorize(32) 99 | end 100 | 101 | def grey 102 | colorize(90) 103 | end 104 | 105 | def yellow 106 | colorize(33) 107 | end 108 | end 109 | 110 | trim if $PROGRAM_NAME == __FILE__ 111 | -------------------------------------------------------------------------------- /terminalizer/config.yml: -------------------------------------------------------------------------------- 1 | # Specify a command to be executed 2 | # like `/bin/bash -l`, `ls`, or any other commands 3 | # the default is bash for Linux 4 | # or powershell.exe for Windows 5 | command: null 6 | 7 | # Specify the current working directory path 8 | # the default is the current working directory path 9 | cwd: null 10 | 11 | # Export additional ENV variables 12 | env: 13 | recording: true 14 | 15 | # Explicitly set the number of columns 16 | # or use `auto` to take the current 17 | # number of columns of your shell 18 | cols: 80 19 | 20 | # Explicitly set the number of rows 21 | # or use `auto` to take the current 22 | # number of rows of your shell 23 | rows: 30 24 | 25 | # Amount of times to repeat GIF 26 | # If value is -1, play once 27 | # If value is 0, loop indefinitely 28 | # If value is a positive number, loop n times 29 | repeat: 0 30 | 31 | # Quality 32 | # 1 - 100 33 | quality: 100 34 | 35 | # Delay between frames in ms 36 | # If the value is `auto` use the actual recording delays 37 | frameDelay: auto 38 | 39 | # Maximum delay between frames in ms 40 | # Ignored if the `frameDelay` isn't set to `auto` 41 | # Set to `auto` to prevent limiting the max idle time 42 | maxIdleTime: 2000 43 | 44 | # The surrounding frame box 45 | # The `type` can be null, window, floating, or solid` 46 | # To hide the title use the value null 47 | # Don't forget to add a backgroundColor style with a null as type 48 | frameBox: 49 | type: solid 50 | title: null 51 | style: 52 | backgroundColor: "#323232" 53 | border: 0px black solid 54 | # boxShadow: none 55 | # margin: 0px 56 | 57 | # Add a watermark image to the rendered gif 58 | # You need to specify an absolute path for 59 | # the image on your machine or a URL, and you can also 60 | # add your own CSS styles 61 | watermark: 62 | imagePath: null 63 | style: 64 | position: absolute 65 | right: 15px 66 | bottom: 15px 67 | width: 100px 68 | opacity: 0.9 69 | 70 | # Cursor style can be one of 71 | # `block`, `underline`, or `bar` 72 | cursorStyle: block 73 | 74 | # Font family 75 | # You can use any font that is installed on your machine 76 | # in CSS-like syntax 77 | fontFamily: "Menlo, Monaco, Lucida Console, Ubuntu Mono, Monospace" 78 | 79 | # The size of the font 80 | fontSize: 18 81 | 82 | # The height of lines 83 | lineHeight: 1 84 | 85 | # The spacing between letters 86 | letterSpacing: 0 87 | 88 | # Theme 89 | theme: 90 | background: "transparent" 91 | foreground: "#fafafa" 92 | cursor: "#c7c7c7" 93 | black: "#2f2f2f" 94 | red: "#d24938" 95 | green: "#8dcb3c" 96 | yellow: "#d5c45a" 97 | blue: "#95c1e1" 98 | magenta: "#d24d96" 99 | cyan: "#3fbbc3" 100 | white: "#e2ded1" 101 | brightBlack: "#595959" 102 | brightRed: "#e65c4b" 103 | brightGreen: "#a3dd49" 104 | brightYellow: "#ebdc70" 105 | brightBlue: "#b4d9f4" 106 | brightMagenta: "#e768ae" 107 | brightCyan: "#58c5cd" 108 | brightWhite: "#f9f7ec" 109 | -------------------------------------------------------------------------------- /sublime/Preferences.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "ignored_packages": 3 | [ 4 | "Vintage", 5 | ], 6 | "theme": "Adaptive.sublime-theme", 7 | "color_scheme": "Packages/Dracula Color Scheme/Dracula.tmTheme", 8 | "auto_complete_commit_on_tab": true, 9 | "caret_extra_bottom": 0, 10 | "caret_extra_top": 3, 11 | "caret_style": "smooth", 12 | "control_character_style": "names", 13 | "enable_telemetry": false, 14 | "ensure_newline_at_eof_on_save": true, 15 | "file_exclude_patterns": 16 | [ 17 | ".gitkeep", 18 | ".keep", 19 | "*.pyc", 20 | "*.pyo", 21 | "*.exe", 22 | "*.dll", 23 | "*.obj", 24 | "*.o", 25 | "*.a", 26 | "*.lib", 27 | "*.so", 28 | "*.dylib", 29 | "*.ncb", 30 | "*.sdf", 31 | "*.suo", 32 | "*.pdb", 33 | "*.idb", 34 | ".DS_Store", 35 | "*.class", 36 | "*.psd", 37 | "*.db", 38 | "*.sublime-workspace", 39 | ".byebug_history" 40 | ], 41 | "folder_exclude_patterns": 42 | [ 43 | ".svn", 44 | ".git", 45 | ".hg", 46 | "CVS", 47 | "tmp", 48 | ".bundle", 49 | "log", 50 | ".sass-cache", 51 | "coverage", 52 | "node_modules", 53 | ".Trash", 54 | ".Trash-*", 55 | ".ruby-lsp" 56 | ], 57 | "font_face": "Menlo", 58 | "font_options": 59 | [ 60 | "ss07" 61 | ], 62 | "font_size": 14, 63 | "hide_new_tab_button": true, 64 | "highlight_line": true, 65 | "highlight_modified_tabs": true, 66 | "line_padding_top": 4, 67 | "line_padding_bottom": 1, 68 | "rulers": 69 | [ 70 | 120 71 | ], 72 | "save_on_focus_lost": true, 73 | "spell_check": true, 74 | "tab_size": 2, 75 | "translate_tabs_to_spaces": true, 76 | "trim_trailing_white_space_on_save": "all", 77 | "added_words": 78 | [ 79 | "frontmatter", 80 | "rubocop", 81 | "Bundler", 82 | "binstub", 83 | "bundler", 84 | "binstubs", 85 | "rubygems", 86 | "Gemfile", 87 | "localhost", 88 | "http", 89 | "capybara", 90 | "rspec", 91 | "selectable", 92 | "Minitest", 93 | "Nextgen", 94 | "initializers", 95 | "serializer", 96 | "initializer", 97 | "subclasses", 98 | "codebases", 99 | "accessor", 100 | "Influencer", 101 | "Brictson", 102 | "middleware", 103 | "monorepo", 104 | "inlined", 105 | "Uncomment", 106 | "unhandled", 107 | "interdependencies", 108 | ], 109 | "ignored_words": 110 | [ 111 | "cjs", 112 | "css", 113 | "env", 114 | "fileutils", 115 | "frontend", 116 | "init", 117 | "npm", 118 | "open3", 119 | "rb", 120 | "securerandom", 121 | "shellwords", 122 | "stylelint", 123 | "stylelintrc", 124 | "stylesheets", 125 | "tmp", 126 | "tmpdir", 127 | "usr", 128 | ], 129 | "index_files": true, 130 | } 131 | -------------------------------------------------------------------------------- /gitconfig.erb: -------------------------------------------------------------------------------- 1 | [advice] 2 | skippedCherryPicks = false 3 | 4 | [alias] 5 | amend = commit --amend --reuse-message=HEAD 6 | bl = blame -C 7 | br = branch 8 | branches = for-each-ref --sort=-committerdate --format=\"%(color:blue)%(authordate:relative)\t%(color:red)%(authorname)\t%(color:white)%(color:bold)%(refname:short)\" refs/remotes 9 | chp = cherry-pick 10 | ci = commit 11 | co = checkout 12 | di = diff 13 | dci = duet-commit 14 | drv = duet-revert 15 | dmg = duet-merge 16 | drb = rebase -i --exec 'git duet-commit --amend' 17 | fixup = commit --amend -c HEAD 18 | hist = log --pretty=format:\"%C(yellow bold)%h%Creset %C(red)%ad%Creset | %s%C(green bold)%d%Creset %C(blue)[%an]%Creset\" --graph --date=short 19 | ignored-files = ls-files --others -i --exclude-standard 20 | l = log --oneline --decorate --graph 21 | ll = log --pretty=medium --decorate --graph --stat 22 | ls = log --name-only --oneline 23 | modified-files = ls-files -m 24 | mru = for-each-ref --sort=-committerdate --count=10 refs/heads/ --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) - %(color:red)%(objectname:short)%(color:reset) - %(contents:subject) - %(authorname) (%(color:green)%(committerdate:relative)%(color:reset))' 25 | oops = reset --hard 'HEAD@{1}' 26 | patch = !git --no-pager diff --no-color 27 | pt = !"git pull && git trim" 28 | pushf = push --force-with-lease 29 | pushu = push -u origin head 30 | ru = reset --hard '@{u}' 31 | st = status -s -b 32 | sw = switch 33 | timeline = log --graph --branches --pretty=oneline --decorate 34 | trust = "!sh -c 'mkdir -p .git/safe && hash -r'" 35 | unmerged = branch --sort=committerdate --no-merged 36 | unstage = reset HEAD 37 | untracked-files = ls-files -o --exclude-standard 38 | 39 | [branch] 40 | sort = -committerdate 41 | 42 | [color] 43 | status = auto 44 | diff = auto 45 | ui = true 46 | 47 | [color "branch"] 48 | current = red reverse 49 | local = blue 50 | remote = green 51 | 52 | [color "diff"] 53 | meta = 227 54 | frag = magenta bold 55 | old = red bold 56 | new = green bold 57 | plain = white 58 | commit = 227 bold 59 | whitespace = red reverse 60 | 61 | [color "diff-highlight"] 62 | oldNormal = red bold 63 | oldHighlight = red bold 52 64 | newNormal = green bold 65 | newHighlight = green bold 22 66 | 67 | [color "status"] 68 | added = yellow 69 | changed = green 70 | untracked = cyan 71 | 72 | [commit] 73 | cleanup = scissors 74 | gpgsign = true 75 | verbose = true 76 | 77 | [core] 78 | safecrlf = warn 79 | autocrlf = input 80 | pager = diff-so-fancy | less --tabs=4 -RFX 81 | 82 | [diff] 83 | algorithm = histogram 84 | noprefix = true 85 | renames = true 86 | 87 | [gpg] 88 | format = ssh 89 | 90 | [help] 91 | autocorrect = prompt 92 | 93 | [init] 94 | defaultBranch = main 95 | 96 | [log] 97 | follow = true 98 | 99 | [merge] 100 | tool = opendiff 101 | 102 | [pull] 103 | ff = only 104 | 105 | [push] 106 | default = simple 107 | 108 | [rebase] 109 | autostash = true 110 | instructionFormat = %s [%an] 111 | updateRefs = true 112 | 113 | [rerere] 114 | enabled = true 115 | autoupdate = true 116 | 117 | [tag] 118 | sort = version:refname 119 | 120 | [url "git@github.com:"] 121 | insteadOf = https://github.com/ 122 | insteadOf = git://github.com/ 123 | 124 | [user] 125 | name = <%= prompt("Your Full Name") %> 126 | email = <%= prompt("Your Email") %> 127 | signingkey = <%= prompt("SSH Public Key") %> 128 | -------------------------------------------------------------------------------- /bin/outdated-projects: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/inline" 4 | require "date" 5 | require "json" 6 | require "pstore" 7 | require "time" 8 | 9 | gemfile do 10 | source "https://rubygems.org" 11 | gem "chronic" 12 | gem "faraday" 13 | gem "faraday-net_http_persistent" 14 | gem "faraday-retry" 15 | gem "nokogiri" 16 | gem "tty-progressbar" 17 | end 18 | 19 | PSTORE = PStore.new(File.expand_path("~/.cache/outdated-projects")) 20 | 21 | Faraday.default_connection = Faraday.new do |config| 22 | retry_options = { 23 | max: 5, 24 | interval: 0.5, 25 | interval_randomness: 0.5, 26 | backoff_factor: 2, 27 | retry_statuses: [429], 28 | methods: [:get] 29 | } 30 | config.response :json 31 | config.response :raise_error 32 | config.request :retry, retry_options 33 | config.adapter :net_http_persistent 34 | end 35 | 36 | class Project 37 | class << self 38 | def all 39 | gems = Faraday.get("https://rubygems.org/api/v1/owners/mattbrictson/gems.json").body 40 | gems.map { |g| new(g) } 41 | end 42 | end 43 | 44 | attr_reader :name 45 | 46 | def initialize(gem_data) 47 | @name = gem_data["name"] 48 | @gem_data = gem_data 49 | end 50 | 51 | def days_since_release 52 | release_date && [Date.today - release_date, 0].max.to_i 53 | end 54 | 55 | def needs_release? 56 | return true if days_since_release.nil? 57 | 58 | stale = version.start_with?("0") ? 7 : 28 59 | days_since_release >= stale && new_commits? 60 | end 61 | 62 | def release_date 63 | local_date = latest_release_html.css("local-time").first&.public_send(:[], "datetime") 64 | relative_date = latest_release_html.css("relative-time").first&.public_send(:[], "datetime") 65 | date = relative_date || local_date 66 | date && Chronic.parse(date).to_date 67 | end 68 | 69 | def releases_url 70 | "https://github.com/#{github_repo}/releases" 71 | end 72 | 73 | def snooze! 74 | PSTORE.transaction { PSTORE[name] = latest_unreleased_commit_at&.to_s } 75 | end 76 | 77 | private 78 | 79 | attr_reader :gem_data 80 | 81 | def version 82 | gem_data["version"] 83 | end 84 | 85 | def new_commits? 86 | return false if latest_unreleased_commit_at.nil? 87 | 88 | snooze_date.nil? || latest_unreleased_commit_at > snooze_date 89 | end 90 | 91 | def snooze_date 92 | date = PSTORE.transaction { PSTORE[name] } 93 | date && Date.parse(date) 94 | end 95 | 96 | def latest_release_html 97 | @_latest_release_html ||= Nokogiri::HTML(Faraday.get(releases_url).body) 98 | end 99 | 100 | def latest_unreleased_commit_at 101 | return nil if unreleased_commits_html.nil? 102 | 103 | date = unreleased_commits_html.text[/commits on (.*)/i, 1] 104 | Chronic.parse(date).to_date 105 | end 106 | 107 | def unreleased_commits_html 108 | @_unreleased_commits_html ||= begin 109 | url = "https://github.com/#{github_repo}/compare/v#{version}...#{default_branch}" 110 | doc = Nokogiri::HTML(Faraday.get(url).body) 111 | doc.css("#commits_bucket .TimelineItem.pb-2").last 112 | end 113 | end 114 | 115 | def default_branch 116 | @_default_branch ||= begin 117 | branches = Faraday.get("https://github.com/#{github_repo}/branches.json").body 118 | branches.dig("payload", "branches", "default", "name") 119 | end 120 | end 121 | 122 | def github_repo 123 | keys = %w[source_code_uri homepage_uri changelog_uri] 124 | urls = gem_data.values_at(*keys).compact 125 | repos = urls.map { |u| u[%r{github.com/((?:[^/]+)/(?:[^/]+))}, 1] } 126 | repos.compact.first 127 | end 128 | end 129 | 130 | if $PROGRAM_NAME == __FILE__ 131 | all_projects = Project.all 132 | needing_release = [] 133 | 134 | bar = TTY::ProgressBar.new("Checking projects… [:bar]", total: all_projects.size) 135 | all_projects.each do |proj| 136 | needing_release << proj if proj.needs_release? 137 | bar.advance(1) 138 | end 139 | 140 | if needing_release.empty? 141 | puts "All projects are up to date!" 142 | else 143 | puts "The following projects need releasing:" 144 | needing_release.each do |proj| 145 | puts [proj.name.ljust(28), proj.days_since_release || "n/a", "days old"].join(" ") 146 | system("open", proj.releases_url) 147 | print "Snooze? (y/N) " 148 | proj.snooze! if $stdin.gets.match?(/\Ay/i) 149 | end 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /bin/git-squash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "English" 4 | require "shellwords" 5 | require "tempfile" 6 | 7 | def usage_and_exit(status = 0) 8 | out = status == 0 ? $stdout : $stderr 9 | 10 | out.puts <<~USAGE.gsub(/^/, " ") 11 | 12 | Usage: git squash BASE_BRANCH 13 | 14 | Rewrites the history of the current feature branch by squashing it down to a 15 | single commit. Before squashing, BASE_BRANCH will be merged into this branch 16 | in order to bring it up to date. If there are merge conflicts, the squash 17 | will be aborted. 18 | 19 | The resulting squashed commit will have a nice commit message based on the 20 | feature branch name and the messages of the commits being squashed. You will 21 | be given a chance to revise the message before it is committed. 22 | 23 | Example: 24 | 25 | Let's say we are working in a branch `features/improve-webpacker-support` 26 | that was branched off of `main` and has the following commits: 27 | 28 | * 1393233 Document the new yarn:install task 29 | * f165c7a Allow `yarn install` flags to be customized 30 | * ea35e0f Explicitly run `yarn install` per webpacker docs 31 | * 6c46003 Include public/packs in linked_dirs by default 32 | 33 | If we run: 34 | 35 | $ git squash main 36 | 37 | This rewrites the history of our `features/improve-webpacker-support` branch 38 | to now be a single commit: 39 | 40 | * 932084e Feature: improve webpacker support 41 | 42 | And the commit message is: 43 | 44 | Feature: improve webpacker support 45 | 46 | * Include public/packs in linked_dirs by default 47 | * Explicitly run `yarn install` per webpacker docs 48 | * Allow `yarn install` flags to be customized 49 | * Document the new yarn:install task 50 | 51 | If we don't like the results of the squash, we can always "undo": 52 | 53 | $ git reset --hard 1393233 54 | 55 | USAGE 56 | 57 | exit(status) 58 | end 59 | 60 | def squash(base_branch, force: false) 61 | require_feature_branch! unless force 62 | head_sha = capture!("git rev-parse --verify --short HEAD").chomp 63 | 64 | clean_merge!(base_branch) 65 | msg_path = write_commit_message_to_temp_file(base_branch) 66 | 67 | sh! "git reset --soft #{base_branch.shellescape}" 68 | sh! "git commit -F - -e < #{msg_path.shellescape}", "OVERCOMMIT_DISABLE" => "1" 69 | 70 | puts 71 | puts "Done! Now up to date with #{base_branch} and squashed down to 1 commit." 72 | puts "To undo the squash, you can run:" 73 | puts 74 | puts " git reset --hard #{head_sha}" 75 | puts 76 | end 77 | 78 | def require_feature_branch! 79 | return unless %w[main master trunk develop development acceptance].include?(current_branch) 80 | 81 | $stderr.puts <<~ERROR.gsub(/^/, " ") 82 | 83 | ERROR: git squash is meant to be used within feature branches only! 84 | 85 | If you really want to rewrite the history of #{current_branch} by squashing 86 | it 1 commit (this is not recommended!), then run: 87 | 88 | $ git squash --force #{current_branch.shellescape} 89 | 90 | More likely you meant to run this command within your feature branch. 91 | The intended usage is: 92 | 93 | $ git switch FEATURE_BRANCH 94 | $ git squash #{current_branch.shellescape} 95 | 96 | ERROR 97 | exit 1 98 | end 99 | 100 | def clean_merge!(base_branch) 101 | sh! "git merge --no-edit #{base_branch.shellescape}" 102 | return if capture!("git diff --name-only --diff-filter=U").strip.empty? 103 | 104 | sh! "git merge --abort" 105 | $stderr.puts <<~ERROR.gsub(/^/, " ") 106 | 107 | ERROR: There are conflicts with #{base_branch}. 108 | Resolve them with a rebase or merge before running `git squash`. 109 | 110 | ERROR 111 | exit 1 112 | end 113 | 114 | def write_commit_message_to_temp_file(base_branch) 115 | file = Tempfile.new 116 | file.puts squash_summary 117 | file.puts 118 | file.puts commit_messages(base_branch) 119 | file.close 120 | file.path 121 | end 122 | 123 | def squash_summary 124 | current_branch.sub(%r{^bugs?/}i, "Bug fix: ") 125 | .sub(%r{^docs?/}i, "Doc: ") 126 | .sub(%r{^features?/}i, "Feature: ") 127 | .sub(%r{^chores?/}i, "Chore: ") 128 | .gsub(/[-_]/, " ") 129 | end 130 | 131 | def commit_messages(base_branch) 132 | log_options = "--no-merges --reverse --format='* %B%n'" 133 | message = capture! "git log #{base_branch.shellescape}..HEAD #{log_options}" 134 | message.gsub(/\n\n+/, "\n\n") 135 | end 136 | 137 | def current_branch 138 | @_current_branch ||= capture!("git branch --show-current").chomp 139 | end 140 | 141 | def sh!(command, env = {}) 142 | puts ">>>> #{command}" 143 | exit 1 unless system(env, command) 144 | end 145 | 146 | def capture!(command) 147 | result = `#{command}` 148 | return result if $CHILD_STATUS.success? 149 | 150 | $stderr.puts "Failed: #{command}" 151 | exit 1 152 | end 153 | 154 | if $PROGRAM_NAME == __FILE__ 155 | force = ARGV.delete("--force") 156 | usage_and_exit if (ARGV & %w[-h --help help]).any? 157 | usage_and_exit(2) unless ARGV.length == 1 158 | 159 | squash(ARGV.first, force: force) 160 | end 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mattbrictson’s dotfiles 2 | 3 | This project contains the dotfiles and custom shell scripts that I use on my Mac. 4 | 5 | Why keep them at GitHub? It’s a way to share advanced shell tips with other developers, and more practically, a way to back up my configuration. When I get a new Mac or (knock on wood) need to recover from lost data or a corrupt OS, I can simply clone this repository and immediately be back in business. 6 | 7 | Thank you [Ryan Bates](https://github.com/ryanb/dotfiles), [thoughtbot](http://github.com/thoughtbot/dotfiles) and [Tom Ryder](https://github.com/tejr/dotfiles) for the inspiration. 8 | 9 | ## Requirements 10 | 11 | Before using these dotfiles there are a few things you'll need to install manually: 12 | 13 | 1. Your Mac needs git and Apple’s command-line developer tools. 14 | 2. Homebrew. 15 | 16 | My blog post here has a walkthrough: https://mattbrictson.com/rails-osx-setup-guide 17 | 18 | ## Installation 19 | 20 | Choose a place to store the dotfiles, like `~/Code/dotfiles`. 21 | 22 | ``` 23 | git clone git://github.com/mattbrictson/dotfiles ~/Code/dotfiles 24 | cd ~/Code/dotfiles 25 | ``` 26 | 27 | Run one of the three installation options: 28 | 29 | ``` 30 | rake install:dotfiles # Install the dotfiles and scripts 31 | rake install:packages # Install homebrew packages and Mac defaults 32 | rake install # Install all of the above (recommended) 33 | ``` 34 | 35 | ### Changing your bash version 36 | 37 | Some features of the dotfiles only work with bash v5+. After installing bash via homebrew (which `rake install:packages` does for you), enable it as your shell as follows: 38 | 39 | 1. Add `/usr/local/bin/bash` to `/etc/shells` 40 | 2. Change your shell to `/usr/local/bin/bash` by running `chsh` 41 | 42 | ### Package Control 43 | 44 | The Sublime Text packages configured in these dotfiles are not installed automatically. You need to manually install [Package Control](https://packagecontrol.io): 45 | 46 | 1. Press SHIFT CMD P to bring up the command palette 47 | 2. Type `install` 48 | 3. Select the option to install Package Control 49 | 50 | Then install the packages you wish. 51 | 52 | ## What’s included? 53 | 54 | ### Sublime Text settings and packages 55 | 56 | My Sublime Text settings are stored in the `sublime` directory of this repo. To ensure that Sublime Text can find this directory, I symlink `~/Library/Application Support/Sublime Text/Packages/User` to it. The installation rake task takes care of setting this up for you. Here's what my dotfiles specify for Sublime: 57 | 58 | - Settings optimized for Rails development 59 | - Better auto-complete behavior 60 | - Custom key bindings 61 | 62 | #### Packages 63 | 64 | - A File Icon 65 | - AdvancedNewFile 66 | - AutoFileName 67 | - CloseOtherWindows 68 | - Color Highlighter 69 | - DashDoc 70 | - DocBlockr 71 | - Dracula Color Scheme 72 | - GitGutter 73 | - JsPrettier 74 | - MarkdownPreview 75 | - Package Control 76 | - Pretty JSON 77 | - Spec Finder 78 | - SublimeLinter 79 | - SublimeLinter-annotations 80 | - SublimeLinter-contrib-erblint 81 | - SublimeLinter-eslint 82 | - SublimeLinter-json 83 | - SublimeLinter-rubocop 84 | - SublimeLinter-ruby 85 | - SublimeLinter-shellcheck 86 | - SublimeLinter-stylelint 87 | - TOML 88 | - YamlPipelines 89 | 90 | ### Handy scripts 91 | 92 | These scripts will be installed to `~/.bin` and added to your `$PATH`: 93 | 94 | - `brew-install` installs and updates my standard suite of homebrew recipes. Run it on a new machine after installing homebrew to get all the recipes needed for Rails development, node, PostgreSQL, etc. 95 | - `bucket` is a simple command-line interface for Bitbucket, most notably providing a way to create pull requests. Run `bucket --help` for details. 96 | - `defaults-install` uses `defaults write` on OS X to change system default behavior to my liking: e.g. don't include drop-shadows on screenshots. 97 | - `git-pluck` adds the `pluck` command to git, which is a trick for cherry-picking a commit from another repository into the current one: `git pluck ../other-repo SHA`. 98 | - `git-trim` adds the `trim` command to git, which deletes local and remote branches that have already been merged and thus are no longer needed. 99 | - `node-install` is a convenient wrapper around `mise install` that does some easy-to-forget housekeeping before and after installation. Usage: `node-install 22.14.0`. 100 | - `ruby-install` is a convenient wrapper around `mise install` that does some easy-to-forget housekeeping before and after installation. Usage: `ruby-install 3.4.2`. 101 | - `sup` uses SSH to update packages on one or more Ubuntu servers (assuming you have root access to them). In other words, `sup SERVER1 SERVER2` will SSH as root into both servers and run the appropriate `aptitude` commands to safely update all packages. It will also report whether any daemons need to be restarted for the updates to take effect. 102 | 103 | ### Shell enhancements (bash) 104 | 105 | - Customizes the shell prompt with current directory and git status: e.g. `~/Work/dotfiles (main *%)$`. 106 | - Replaces `diff` with `colordiff`. 107 | - Prettifies `ls` output and adds `l`, `la`, and `ll` shortcuts. 108 | - Improves default `top` settings. 109 | - Allows bash command history to be navigated with up and down arrow keys. 110 | - Makes bash auto-completion case-insensitive. 111 | - Sets up necessary homebrew, mise, and python virtualenv shell variables. 112 | - Specifies `less` as the default pager and Sublime Text (`subl`) as the default editor. 113 | - Sets better defaults for the `psql` command. 114 | 115 | ### Git configuration 116 | 117 | - Sets up a reasonable global gitignore file to ignore things like `.DS_Store`, `Icon?`, and `*sublime-project`. 118 | - Enables color output and line-ending checks. 119 | - Shortens common commands: `di`, `co`, `ci`, `br`, `l`, `sw`. 120 | - Defines somes useful aliases: 121 | - `git hist` 122 | - `git ignored-files` 123 | - `git untracked-files` 124 | - `git unstage` 125 | 126 | In addition, during installation (see below), you will be prompted for your full name and email address, which are automatically added to the git config file. 127 | 128 | ### Ruby/rails stuff 129 | 130 | - Adds an `r` command that serves as a shortcut for running `bin/rake` or `bin/rails`. It's pretty smart, so `r s` will expand to `bin/rails server`, and `r db` will expand to `bin/rake db:console`. No more mistakes of typing `rails` vs `rake`! 131 | - Disables gem documentation generation so that `gem install` runs much faster. 132 | - Configures the `xray` gem to use Sublime Text. 133 | - Enables command history (use up and down arrows) in `irb`. 134 | - Defines a list of useful gems that are installed by default whenever a new version of ruby is installed via `mise install`. 135 | -------------------------------------------------------------------------------- /config/pgcli/config: -------------------------------------------------------------------------------- 1 | # vi: ft=dosini 2 | [main] 3 | 4 | # Enables context sensitive auto-completion. If this is disabled the all 5 | # possible completions will be listed. 6 | smart_completion = True 7 | 8 | # Display the completions in several columns. (More completions will be 9 | # visible.) 10 | wider_completion_menu = False 11 | 12 | # Multi-line mode allows breaking up the sql statements into multiple lines. If 13 | # this is set to True, then the end of the statements must have a semi-colon. 14 | # If this is set to False then sql statements can't be split into multiple 15 | # lines. End of line (return) is considered as the end of the statement. 16 | multi_line = False 17 | 18 | # If multi_line_mode is set to "psql", in multi-line mode, [Enter] will execute 19 | # the current input if the input ends in a semicolon. 20 | # If multi_line_mode is set to "safe", in multi-line mode, [Enter] will always 21 | # insert a newline, and [Esc] [Enter] or [Alt]-[Enter] must be used to execute 22 | # a command. 23 | multi_line_mode = psql 24 | 25 | # Destructive warning mode will alert you before executing a sql statement 26 | # that may cause harm to the database such as "drop table", "drop database" 27 | # or "shutdown". 28 | destructive_warning = True 29 | 30 | # Enables expand mode, which is similar to `\x` in psql. 31 | expand = False 32 | 33 | # Enables auto expand mode, which is similar to `\x auto` in psql. 34 | auto_expand = False 35 | 36 | # If set to True, table suggestions will include a table alias 37 | generate_aliases = False 38 | 39 | # log_file location. 40 | # In Unix/Linux: ~/.config/pgcli/log 41 | # In Windows: %USERPROFILE%\AppData\Local\dbcli\pgcli\log 42 | # %USERPROFILE% is typically C:\Users\{username} 43 | log_file = default 44 | 45 | # keyword casing preference. Possible values "lower", "upper", "auto" 46 | keyword_casing = auto 47 | 48 | # casing_file location. 49 | # In Unix/Linux: ~/.config/pgcli/casing 50 | # In Windows: %USERPROFILE%\AppData\Local\dbcli\pgcli\casing 51 | # %USERPROFILE% is typically C:\Users\{username} 52 | casing_file = default 53 | 54 | # If generate_casing_file is set to True and there is no file in the above 55 | # location, one will be generated based on usage in SQL/PLPGSQL functions. 56 | generate_casing_file = False 57 | 58 | # Casing of column headers based on the casing_file described above 59 | case_column_headers = True 60 | 61 | # history_file location. 62 | # In Unix/Linux: ~/.config/pgcli/history 63 | # In Windows: %USERPROFILE%\AppData\Local\dbcli\pgcli\history 64 | # %USERPROFILE% is typically C:\Users\{username} 65 | history_file = default 66 | 67 | # Default log level. Possible values: "CRITICAL", "ERROR", "WARNING", "INFO" 68 | # and "DEBUG". "NONE" disables logging. 69 | log_level = INFO 70 | 71 | # Order of columns when expanding * to column list 72 | # Possible values: "table_order" and "alphabetic" 73 | asterisk_column_order = table_order 74 | 75 | # Whether to qualify with table alias/name when suggesting columns 76 | # Possible values: "always", never" and "if_more_than_one_table" 77 | qualify_columns = if_more_than_one_table 78 | 79 | # When no schema is entered, only suggest objects in search_path 80 | search_path_filter = False 81 | 82 | # Default pager. 83 | # By default 'PAGER' environment variable is used 84 | pager = less -SRXF 85 | 86 | # Timing of sql statments and table rendering. 87 | timing = True 88 | 89 | # Table format. Possible values: psql, plain, simple, grid, fancy_grid, pipe, 90 | # ascii, double, github, orgtbl, rst, mediawiki, html, latex, latex_booktabs, 91 | # textile, moinmoin, jira, vertical, tsv, csv. 92 | # Recommended: psql, fancy_grid and grid. 93 | table_format = psql 94 | 95 | # Syntax Style. Possible values: manni, igor, xcode, vim, autumn, vs, rrt, 96 | # native, perldoc, borland, tango, emacs, friendly, monokai, paraiso-dark, 97 | # colorful, murphy, bw, pastie, paraiso-light, trac, default, fruity 98 | syntax_style = default 99 | 100 | # Keybindings: 101 | # When Vi mode is enabled you can use modal editing features offered by Vi in the REPL. 102 | # When Vi mode is disabled emacs keybindings such as Ctrl-A for home and Ctrl-E 103 | # for end are available in the REPL. 104 | vi = False 105 | 106 | # Error handling 107 | # When one of multiple SQL statements causes an error, choose to either 108 | # continue executing the remaining statements, or stopping 109 | # Possible values "STOP" or "RESUME" 110 | on_error = STOP 111 | 112 | # Set threshold for row limit prompt. Use 0 to disable prompt. 113 | row_limit = 1000 114 | 115 | # Skip intro on startup and goodbye on exit 116 | less_chatty = True 117 | 118 | # Postgres prompt 119 | # \t - Current date and time 120 | # \u - Username 121 | # \h - Short hostname of the server (up to first '.') 122 | # \H - Hostname of the server 123 | # \d - Database name 124 | # \p - Database port 125 | # \i - Postgres PID 126 | # \# - "@" sign if logged in as superuser, '>' in other case 127 | # \n - Newline 128 | # \dsn_alias - name of dsn alias if -D option is used (empty otherwise) 129 | prompt = '\u@\h:\d> ' 130 | 131 | # Number of lines to reserve for the suggestion menu 132 | min_num_menu_lines = 4 133 | 134 | # Character used to left pad multi-line queries to match the prompt size. 135 | multiline_continuation_char = '' 136 | 137 | # The string used in place of a null value. 138 | null_string = '' 139 | 140 | # manage pager on startup 141 | enable_pager = True 142 | 143 | # Use keyring to automatically save and load password in a secure manner 144 | keyring = True 145 | 146 | # Custom colors for the completion menu, toolbar, etc. 147 | [colors] 148 | completion-menu.completion.current = 'bg:#ffffff #000000' 149 | completion-menu.completion = 'bg:#008888 #ffffff' 150 | completion-menu.meta.completion.current = 'bg:#44aaaa #000000' 151 | completion-menu.meta.completion = 'bg:#448888 #ffffff' 152 | completion-menu.multi-column-meta = 'bg:#aaffff #000000' 153 | scrollbar.arrow = 'bg:#003333' 154 | scrollbar = 'bg:#00aaaa' 155 | selected = '#ffffff bg:#6666aa' 156 | search = '#ffffff bg:#4444aa' 157 | search.current = '#ffffff bg:#44aa44' 158 | bottom-toolbar = 'bg:#222222 #aaaaaa' 159 | bottom-toolbar.off = 'bg:#222222 #888888' 160 | bottom-toolbar.on = 'bg:#222222 #ffffff' 161 | search-toolbar = 'noinherit bold' 162 | search-toolbar.text = 'nobold' 163 | system-toolbar = 'noinherit bold' 164 | arg-toolbar = 'noinherit bold' 165 | arg-toolbar.text = 'nobold' 166 | bottom-toolbar.transaction.valid = 'bg:#222222 #00ff5f bold' 167 | bottom-toolbar.transaction.failed = 'bg:#222222 #ff005f bold' 168 | 169 | # style classes for colored table output 170 | output.header = "#00ff5f bold" 171 | output.odd-row = "" 172 | output.even-row = "" 173 | 174 | # Named queries are queries you can execute by name. 175 | [named queries] 176 | 177 | # DSN to call by -D option 178 | [alias_dsn] 179 | # example_dsn = postgresql://[user[:password]@][netloc][:port][/dbname] 180 | 181 | # Format for number representation 182 | # for decimal "d" - 12345678, ",d" - 12,345,678 183 | # for float "g" - 123456.78, ",g" - 123,456.78 184 | [data_formats] 185 | decimal = "" 186 | float = "" 187 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/ryanb/dotfiles/ 2 | 3 | require "erb" 4 | require "fileutils" 5 | require "pstore" 6 | require "rake" 7 | require "shellwords" 8 | 9 | IGNORE = %w[ 10 | dotfiles.sublime-project 11 | dotfiles.sublime-workspace 12 | completions 13 | extras 14 | LICENSE 15 | Rakefile 16 | README.md 17 | sublime 18 | ].freeze 19 | 20 | def brew_prefix 21 | @brew_prefix ||= File.executable?("/opt/homebrew/bin/brew") ? "/opt/homebrew" : "/usr/local" 22 | end 23 | 24 | task default: "install" 25 | 26 | desc "Install packages and dotfiles" 27 | task install: %w[install:dotfiles install:completions install:packages] 28 | 29 | desc "Warn if git origin is newer" 30 | task :check do 31 | next unless system("git fetch origin") 32 | next if `git diff HEAD origin/main`.strip.empty? 33 | 34 | log(:yellow, "warning Working copy is out of date; consider `git pull`") 35 | end 36 | 37 | namespace :install do 38 | desc "Install homebrew, etc. packages" 39 | task packages: :check do 40 | %w[brew defaults].each do |type| 41 | log(:blue, "executing bin/#{type}-install …") 42 | system("bin/#{type}-install") 43 | end 44 | end 45 | 46 | desc "Install dotfiles into user’s home directory" 47 | task dotfiles: %i[link_sublime link_xbar check] do 48 | always_replace = false 49 | 50 | Dotfile.each do |dotfile| 51 | case dotfile.status 52 | when :identical 53 | log(:green, "identical #{dotfile}") 54 | when :missing 55 | dotfile.link! 56 | when :different 57 | if always_replace 58 | dotfile.replace! 59 | elsif (answer = ask(:red, "overwrite? #{dotfile}")) 60 | always_replace = true if answer == :always 61 | dotfile.replace! 62 | else 63 | log(:gray, "skipping #{dotfile}") 64 | end 65 | end 66 | end 67 | 68 | Rake::Task["install:prune_brewfile"].invoke 69 | end 70 | 71 | task :prune_brewfile do 72 | ignores = File.exist?(".brewignore") ? File.readlines(".brewignore", chomp: true) : [] 73 | brewfile_path = File.expand_path("~/.Brewfile") 74 | brewfile = File.readlines(brewfile_path) 75 | 76 | ignores.each do |ignore| 77 | next if ignore.empty? 78 | 79 | brewfile.reject! { |line| line.include?(ignore) } 80 | end 81 | 82 | File.write(brewfile_path, brewfile.join) 83 | end 84 | 85 | desc "Install bash completion scripts" 86 | task :completions do 87 | FileUtils.mkdir_p "#{brew_prefix}/etc/bash_completion.d" 88 | Dir[File.expand_path("completions/*", __dir__)].each do |script| 89 | basename = File.basename(script) 90 | target = "#{brew_prefix}/etc/bash_completion.d/#{basename}" 91 | log(:blue, "linking completions/#{basename}") 92 | FileUtils.rm_rf(target) 93 | FileUtils.ln_s(script, target) 94 | end 95 | end 96 | 97 | desc "Symlink the Sublime Packages/User directory" 98 | task :link_sublime do 99 | sublime = File.expand_path("sublime", __dir__) 100 | user_packages = File.expand_path("~/Library/Application Support/Sublime Text/Packages/User") 101 | unless File.exist?(user_packages) 102 | log(:magenta, "mkdir Library/Application Support/Sublime Text/Packages/User") 103 | FileUtils.mkdir_p(user_packages) 104 | end 105 | if File.directory?(user_packages) && !File.symlink?(user_packages) 106 | log(:blue, "copy Library/Application Support/Sublime Text/Packages/User/*") 107 | FileUtils.cp_r(Dir.glob(user_packages.shellescape + "/*"), sublime) 108 | log(:magenta, "rm Library/Application Support/Sublime Text/Packages/User") 109 | FileUtils.rm_rf(user_packages) 110 | log(:blue, "linking Library/Application Support/Sublime Text/Packages/User") 111 | FileUtils.ln_s(sublime, user_packages) 112 | end 113 | end 114 | 115 | desc "Symlink the xbar plugins directory" 116 | task :link_xbar do 117 | custom_plugins = File.expand_path("../xbar/plugins", __dir__) 118 | break unless File.directory?(custom_plugins) 119 | 120 | xbar_support = File.expand_path("~/Library/Application Support/xbar/plugins") 121 | unless File.symlink?(xbar_support) 122 | log(:magenta, "rm ~/Library/Application Support/xbar/plugins") 123 | FileUtils.rm_rf(xbar_support) 124 | log(:blue, "linking ~/Library/Application Support/xbar/plugins") 125 | FileUtils.ln_s(custom_plugins, xbar_support) 126 | end 127 | end 128 | end 129 | 130 | def log(color, message) 131 | begin 132 | require "highline" 133 | rescue LoadError 134 | end 135 | 136 | first, rest = message.split(" ", 2) 137 | first = first.ljust(10) 138 | 139 | if defined?(HighLine::String) 140 | first = HighLine::String.new(first).public_send(color) 141 | end 142 | 143 | line = [first, rest].join(" ") 144 | 145 | if line.end_with?(" ") 146 | print(line) 147 | else 148 | puts(line) 149 | end 150 | end 151 | 152 | def ask(color, question) 153 | log(color, "#{question} [yNaq]? ") 154 | 155 | case $stdin.gets.chomp 156 | when "a" 157 | :always 158 | when "y" 159 | true 160 | when "q" 161 | exit 162 | else 163 | false 164 | end 165 | end 166 | 167 | class Dotfile 168 | def self.each 169 | `git ls-files -z`.split("\x0").each do |file| 170 | next if file =~ %r{^\.|/\.} 171 | next if IGNORE.include?(file.split("/").first) 172 | 173 | yield(new(file)) 174 | end 175 | end 176 | 177 | attr_reader :file 178 | 179 | def initialize(file) 180 | @file = file 181 | end 182 | 183 | def erb? 184 | file =~ /\.erb\z/i 185 | end 186 | 187 | def name 188 | ".#{file.sub(/\.erb\z/i, "")}" 189 | end 190 | alias :to_s :name 191 | 192 | def target 193 | File.expand_path("~/#{name}") 194 | end 195 | 196 | def status 197 | if File.identical?(file, target) 198 | :identical 199 | elsif File.exist?(target) || File.symlink?(target) 200 | :different 201 | else 202 | :missing 203 | end 204 | end 205 | 206 | def link!(delete_first: false) 207 | ensure_target_directory 208 | 209 | if erb? 210 | log(:yellow, "generating #{self}") 211 | contents = ERB.new(File.read(file)).result(binding) 212 | 213 | log(:blue, "writing #{self}") 214 | FileUtils.rm_rf(target) if delete_first 215 | File.open(target, "w") do |out| 216 | out << contents 217 | end 218 | else 219 | log(:blue, "linking #{self}") 220 | FileUtils.rm_rf(target) if delete_first 221 | FileUtils.ln_s(File.absolute_path(file), target) 222 | end 223 | end 224 | 225 | def replace! 226 | link!(delete_first: true) 227 | end 228 | 229 | def ensure_target_directory 230 | directory = File.dirname(target) 231 | return if File.directory?(directory) 232 | 233 | log(:magenta, "mkdir #{File.dirname(name)}") 234 | FileUtils.mkdir_p(directory) 235 | end 236 | 237 | def prompt(label) 238 | default = pstore.transaction { pstore[label] } 239 | print default ? "#{label} (#{default}): " : "#{label}: " 240 | $stdout.flush 241 | 242 | entry = $stdin.gets.chomp 243 | result = entry.empty? && default ? default : entry 244 | 245 | pstore.transaction { pstore[label] = result } 246 | result 247 | end 248 | 249 | def pstore 250 | @_pstore ||= PStore.new(pstore_path) 251 | end 252 | 253 | def pstore_path 254 | File.join(__dir__, ".db") 255 | end 256 | end 257 | -------------------------------------------------------------------------------- /extras/Matt.terminal: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ANSIBlackColor 6 | 7 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 8 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 9 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAmMC4xMzg3NjA3MDA4IDAuMTM4ODkyODg5 10 | IDAuMTM4NzUzOTM1NwAQAYAC0hQVFhdaJGNsYXNzbmFtZVgkY2xhc3Nlc1dOU0NvbG9y 11 | ohYYWE5TT2JqZWN0CBEaJCkyN0lMUVNXXWRqd36nqauwu8TMzwAAAAAAAAEBAAAAAAAA 12 | ABkAAAAAAAAAAAAAAAAAAADY 13 | 14 | ANSIBlueColor 15 | 16 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 17 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 18 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC41MTg3NDQ4MjYzIDAuNzAxNzgwNzk2 19 | MSAwLjg1NDAyMzQ1NjYAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 20 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 21 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 22 | 23 | ANSIBrightBlackColor 24 | 25 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 26 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 27 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC4yNzczMjIwODM3IDAuMjc3NTEyODc4 28 | MiAwLjI3NzMzODk1MTgAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 29 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 30 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 31 | 32 | ANSIBrightBlueColor 33 | 34 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 35 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 36 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC42NTE5NzIyMzQyIDAuODE0ODM5MzAz 37 | NSAwLjk0Mzk0ODYyNjUAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 38 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 39 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 40 | 41 | ANSIBrightCyanColor 42 | 43 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 44 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 45 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAmMC4yOTM0NzYyNTM3IDAuNzI2NDk3NjUw 46 | MSAwLjc2MTUyNjUyNQAQAYAC0hQVFhdaJGNsYXNzbmFtZVgkY2xhc3Nlc1dOU0NvbG9y 47 | ohYYWE5TT2JqZWN0CBEaJCkyN0lMUVNXXWRqd36nqauwu8TMzwAAAAAAAAEBAAAAAAAA 48 | ABkAAAAAAAAAAAAAAAAAAADY 49 | 50 | ANSIBrightGreenColor 51 | 52 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 53 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 54 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAmMC41ODQyMzAwNjUzIDAuODU2NjI5MzEy 55 | IDAuMjI3Mzk5MjAwMgAQAYAC0hQVFhdaJGNsYXNzbmFtZVgkY2xhc3Nlc1dOU0NvbG9y 56 | ohYYWE5TT2JqZWN0CBEaJCkyN0lMUVNXXWRqd36nqauwu8TMzwAAAAAAAAEBAAAAAAAA 57 | ABkAAAAAAAAAAAAAAAAAAADY 58 | 59 | ANSIBrightMagentaColor 60 | 61 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 62 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 63 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC44NzQxODI4Nzk5IDAuMzAxNzk1MDY1 64 | NCAwLjYyMjI4Nzk4ODcAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 65 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 66 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 67 | 68 | ANSIBrightRedColor 69 | 70 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 71 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 72 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC44Njk1ODIxMTY2IDAuMjcxMzI0ODEz 73 | NCAwLjIzMjA3MDM4NjQAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 74 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 75 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 76 | 77 | ANSIBrightWhiteColor 78 | 79 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 80 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 81 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC45NzAzNzE0ODQ4IDAuOTYxOTgxMTE3 82 | NyAwLjkwNTkzMDkzNjMAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 83 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 84 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 85 | 86 | ANSIBrightYellowColor 87 | 88 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 89 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 90 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC45MDA1MDY2MTU2IDAuODQxNDI4NTE4 91 | MyAwLjM2ODQ1MzUwMjcAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 92 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 93 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 94 | 95 | ANSICyanColor 96 | 97 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 98 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 99 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC4yMDk4ODEwOTcxIDAuNjgxNTE1NTc0 100 | NSAwLjcxNDEwOTU5OTYAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 101 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 102 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 103 | 104 | ANSIGreenColor 105 | 106 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 107 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 108 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAmMC40ODkxMDI0MjMyIDAuNzcwMTQ5MjMx 109 | IDAuMTgyMjQ4NDg4MQAQAYAC0hQVFhdaJGNsYXNzbmFtZVgkY2xhc3Nlc1dOU0NvbG9y 110 | ohYYWE5TT2JqZWN0CBEaJCkyN0lMUVNXXWRqd36nqauwu8TMzwAAAAAAAAEBAAAAAAAA 111 | ABkAAAAAAAAAAAAAAAAAAADY 112 | 113 | ANSIMagentaColor 114 | 115 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 116 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 117 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC43NzQ4NjM1NDExIDAuMTk5ODM5NjUx 118 | NiAwLjUxNTUxNTIwODIAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 119 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 120 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 121 | 122 | ANSIRedColor 123 | 124 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 125 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 126 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC43NzMxMzQzNTA4IDAuMjAwOTkzMzQ0 127 | MiAwLjE2ODU5NTIzOTUAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 128 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 129 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 130 | 131 | ANSIWhiteColor 132 | 133 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 134 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 135 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAlMC44NTY3MTYzOTQ0IDAuODQxODIzMTAx 136 | IDAuNzc2ODMwMjU2ABABgALSFBUWF1okY2xhc3NuYW1lWCRjbGFzc2VzV05TQ29sb3Ki 137 | FhhYTlNPYmplY3QIERokKTI3SUxRU1ddZGp3fqaoqq+6w8vOAAAAAAAAAQEAAAAAAAAA 138 | GQAAAAAAAAAAAAAAAAAAANc= 139 | 140 | ANSIYellowColor 141 | 142 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 143 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 144 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAnMC43OTcwMTQ5NTE3IDAuNzMwMjM4NjE2 145 | NSAwLjI4Mzc4MDYwNDYAEAGAAtIUFRYXWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xv 146 | cqIWGFhOU09iamVjdAgRGiQpMjdJTFFTV11kand+qKqssbzFzdAAAAAAAAABAQAAAAAA 147 | AAAZAAAAAAAAAAAAAAAAAAAA2Q== 148 | 149 | BackgroundBlur 150 | 0.0 151 | BackgroundBlurInactive 152 | 0.20270330255681818 153 | BackgroundColor 154 | 155 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 156 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 157 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAsMC4xMDAzOTQ4NzA5IDAuMTAwMzk0ODcw 158 | OSAwLjEwMDM5NDg3MDkgMC44NQAQAYAC0hQVFhdaJGNsYXNzbmFtZVgkY2xhc3Nlc1dO 159 | U0NvbG9yohYYWE5TT2JqZWN0CBEaJCkyN0lMUVNXXWRqd36tr7G2wcrS1QAAAAAAAAEB 160 | AAAAAAAAABkAAAAAAAAAAAAAAAAAAADe 161 | 162 | BackgroundSettingsForInactiveWindows 163 | 164 | Bell 165 | 166 | BlinkText 167 | 168 | CursorType 169 | 0 170 | DisableANSIColor 171 | 172 | EscapeNonASCIICharacters 173 | 174 | Font 175 | 176 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 177 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGkCwwVFlUkbnVsbNQNDg8QERIT 178 | FFZOU1NpemVYTlNmRmxhZ3NWTlNOYW1lViRjbGFzcyNALAAAAAAAABAQgAKAA18QIE1l 179 | c2xvTEdTTmVyZEZvbnRDb21wbGV0ZS1SZWd1bGFy0hcYGRpaJGNsYXNzbmFtZVgkY2xh 180 | c3Nlc1ZOU0ZvbnSiGRtYTlNPYmplY3QIERokKTI3SUxRU1heZ253foWOkJKUt7zH0Nfa 181 | AAAAAAAAAQEAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAOM= 182 | 183 | FontAntialias 184 | 185 | FontWidthSpacing 186 | 1.004032258064516 187 | Linewrap 188 | 189 | ProfileCurrentVersion 190 | 2.0699999999999998 191 | SelectionColor 192 | 193 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 194 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 195 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzTxAsMC45MTc3ODE2NTEgMC41NzM5MDIwNzA1 196 | IDAuMDMxOTUxODU5NTkgMC43NQAQAYAC0hQVFhdaJGNsYXNzbmFtZVgkY2xhc3Nlc1dO 197 | U0NvbG9yohYYWE5TT2JqZWN0CBEaJCkyN0lMUVNXXWRqd36tr7G2wcrS1QAAAAAAAAEB 198 | AAAAAAAAABkAAAAAAAAAAAAAAAAAAADe 199 | 200 | ShowDimensionsInTitle 201 | 202 | TerminalType 203 | xterm-256color 204 | TextBoldColor 205 | 206 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 207 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxAREldO 208 | U1doaXRlXE5TQ29sb3JTcGFjZVYkY2xhc3NCMQAQA4AC0hQVFhdaJGNsYXNzbmFtZVgk 209 | Y2xhc3Nlc1dOU0NvbG9yohYYWE5TT2JqZWN0CBEaJCkyN0lMUVNXXWRseYCDhYeMl6Co 210 | qwAAAAAAAAEBAAAAAAAAABkAAAAAAAAAAAAAAAAAAAC0 211 | 212 | TextColor 213 | 214 | YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMS 215 | AAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGjCwwTVSRudWxs0w0ODxARElVO 216 | U1JHQlxOU0NvbG9yU3BhY2VWJGNsYXNzRjEgMSAxABABgALSFBUWF1okY2xhc3NuYW1l 217 | WCRjbGFzc2VzV05TQ29sb3KiFhhYTlNPYmplY3QIERokKTI3SUxRU1ddZGp3foWHiY6Z 218 | oqqtAAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY= 219 | 220 | UseBoldFonts 221 | 222 | UseBrightBold 223 | 224 | VisualBell 225 | 226 | VisualBellOnlyWhenMuted 227 | 228 | columnCount 229 | 100 230 | name 231 | Matt 232 | shellExitAction 233 | 1 234 | type 235 | Window Settings 236 | useOptionAsMetaKey 237 | 238 | 239 | 240 | --------------------------------------------------------------------------------