Skip to main content

pour all my fish config into this repo

ID
25d9f81
date
2023-11-26 02:16:00+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
5f337c6
message
pour all my fish config into this repo
changed files
10 files, 389 additions, 38 deletions

Changed files

README.md (2876) → README.md (1493)

diff --git a/README.md b/README.md
index 0f87c44..030a51f 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,32 @@
 # scripts
 
-This is a collection of useful scripts and tools I keep in my [PATH].
+This is a collection of various scripts and tools I find useful.
 I use this Git repository to sync them across multiple computers.
 
-I use scripts over shell config for a couple of reasons:
+## Installation
 
--   Individual scripts are more portable.
-    I can send you a script and you can start using it immediately, even if you use a different shell.
+To set up this repo on a new computer, I run the following commands in a Fish shell:
 
--   I can use different languages.
-    I'm not restricted to whatever my shell uses.
+1.  Clone the repository:
 
--   It makes my shell start faster.
-    I use [fish], and I've noticed that if I write large fish config files, there's a noticeable delay between starting a shell and getting my first prompt.
-    Using scripts means I have smaller config files, and I get a first prompt faster.
+    ```console
+    $ git clone git@github.com:alexwlchan/scripts.git ~/repos/scripts
+    $ cd ~/repos/scripts
+    ```
 
-[PATH]: https://en.wikipedia.org/wiki/PATH_(variable)
-[fish]: https://fishshell.com/
+2.  Create a Python virtualenv and install dependencies:
 
-## Usage
+    ```console
+    $ python3 -m venv .venv
+    $ source .venv/bin/activate.fish
+    $ pip install -r requirements.txt
+    ```
+    
+3.  Install my Fish config, so Fish knows where to find all these scripts:
 
-Individual scripts have header comments explaining what they do.
-Download them and add them to your PATH.
-
-You can also clone this entire repo, then add it to your PATH, if you want to use all the scripts.
-
-For example, I have the following code in [my fishconfig](https://github.com/alexwlchan/fishconfig/blob/main/config.fish#L5-L22):
-
-```shell
-set --global --export PATH $PATH \
-  ~/repos/scripts \
-  ~/repos/scripts/aws \
-  ~/repos/scripts/git \
-  ~/repos/scripts/installers \
-  ~/repos/scripts/macos \
-  ~/repos/scripts/terraform
-```
+    ```console
+    $ ln -s ~/repos/scripts/config.fish ~/.config/fish/config.fish
+    ```
 
 ## Organisation
 
@@ -50,13 +41,3 @@ The script in this repo are pretty short – typically 50 lines or less (includi
 They're mostly stuff that I can write all in one go.
 
 If a script gets sufficiently large and complicated that it might benefit from its own documentation or change history, it "graduates" into a separate repo.
-
-## Contributors
-
-My scripts include code written by other people, and in those cases I've [attributed the commit that added the code/script to multiple authors][trailer].
-This is why several people who aren't me appear in then list of contributors, even though none of them have ever directly committed code.
-
-You can see all the (documented) instances of other people's code by searching for the [Co-authored-by line][search] in my commits.
-
-[trailer]: https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors#:~:text=In%20the%20text%20box%20below,Commit%20changes%20or%20Propose%20changes.
-[search]: https://github.com/search?q=repo%3Aalexwlchan%2Fscripts%20co-authored-by&type=commits

config.fish (0) → config.fish (2207)

diff --git a/config.fish b/config.fish
new file mode 100644
index 0000000..09bdc83
--- /dev/null
+++ b/config.fish
@@ -0,0 +1,64 @@
+# Don't show a greeting on startup.  In particular, don't show
+# this message:
+#
+#     Welcome to fish, the friendly interactive shell
+#     Type help for instructions on how to use fish
+#
+set -g -x fish_greeting ''
+
+# This tells fish to find functions in my "fish_functions" directory.
+#
+# See https://fishshell.com/docs/current/language.html#autoloading-functions
+set -x fish_function_path ~/repos/scripts/fish_functions $fish_function_path
+
+
+# Load macOS-specific utilities
+if [ (uname -s) = "Darwin" ]
+  # Provide a convenient alias for the front URL in both browsers
+  alias furl="safari url"
+  alias gurl="osascript -e 'tell application \"Google Chrome\" to tell front window to get URL of tab (active tab index)'"
+
+  # Get the URL of the frontmost GitHub page and clone it
+  function gh-clone
+      _ensure_ssh_key_loaded
+      github-clone (furl)
+  end
+
+end
+
+
+# Taken from https://gist.github.com/tommyip/cf9099fa6053e30247e5d0318de2fb9e
+#
+# This will automatically enable/disable my virtualenvs when I enter/leave directories.
+#
+# Based on https://gist.github.com/bastibe/c0950e463ffdfdfada7adf149ae77c6f
+# Changes:
+# * Instead of overriding cd, we detect directory change. This allows the script to work
+#   for other means of cd, such as z.
+# * Update syntax to work with new versions of fish.
+# * Handle virtualenvs that are not located in the root of a git directory.
+
+function __auto_source_venv --on-variable PWD --description "Activate/Deactivate virtualenv on directory change"
+  status --is-command-substitution; and return
+
+  # Check if we are inside a git directory
+  if git rev-parse --show-toplevel &>/dev/null
+    set gitdir (realpath (git rev-parse --show-toplevel))
+    set cwd (pwd)
+    # While we are still inside the git directory, find the closest
+    # virtualenv starting from the current directory.
+    while string match "$gitdir*" "$cwd" &>/dev/null
+      if test -e "$cwd/.venv/bin/activate.fish"
+        source "$cwd/.venv/bin/activate.fish" &>/dev/null
+        return
+      else
+        set cwd (path dirname "$cwd")
+      end
+    end
+  end
+
+  # If virtualenv activated but we are not in a git directory, deactivate.
+  if test -n "$VIRTUAL_ENV"
+    deactivate
+  end
+end

fish_functions/fish_prompt.fish (0) → fish_functions/fish_prompt.fish (4380)

diff --git a/fish_functions/fish_prompt.fish b/fish_functions/fish_prompt.fish
new file mode 100644
index 0000000..e8dde1b
--- /dev/null
+++ b/fish_functions/fish_prompt.fish
@@ -0,0 +1,157 @@
+###############################################################################
+# My fish prompt
+#
+# This has been inspired by various examples from other people, not all
+# of whom I kept notes of.
+###############################################################################
+
+
+function print_current_directory
+  set_color green
+  printf (echo -n (prompt_pwd))
+  set_color normal
+end
+
+
+# Print information about the current Git branch, if I'm in a Git repo.
+#
+# At one point I had similar functions for getting SVN and Mercurial information,
+# but at time of writing (Apr 2022), it's been 5+ years since I used a non-Git VCS.
+# It's not worth maintaining those alternatives or running them against every
+# shell prompt.
+function print_git_information
+  which git 2>&1 >/dev/null
+  if [ $status = "0" ]
+    set branch (git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
+    if [ -n "$branch" ]
+      set_color normal
+      printf " on git:"
+
+      if test (basename "$branch") = "main"
+        set_color cyan
+      else
+        set_color purple
+      end
+
+      printf "$branch"
+
+      # Print an asterisk to indicate uncommitted changes, if there are any
+      if ! git diff-index --quiet HEAD --
+        printf "*"
+      end
+
+      set_color normal
+    end
+  end
+end
+
+
+# Print information about the current virtualenv, if one is enabled.
+#
+# The VIRTUAL_ENV_DISABLE_PROMPT command disables the auto-prepending of the
+# venv into my prompt by the virtualenv itself; see
+# https://stackoverflow.com/a/63029769/1558022
+set -x VIRTUAL_ENV_DISABLE_PROMPT 1
+
+function print_venv_information
+  if [ -n "$VIRTUAL_ENV" ]
+    set_color normal
+    printf " using "
+
+    if test (basename "$VIRTUAL_ENV") = ".venv"
+      set_color cyan
+    else
+      set_color purple
+    end
+
+    printf (basename "$VIRTUAL_ENV")
+    set_color normal
+  end
+end
+
+
+# If I'm running over SSH, prepend the name of the remote host to
+# the context line.
+function print_ssh_information
+  if set -q SSH_CLIENT
+    printf "("
+    set_color purple
+    printf (echo -n (hostname))
+    set_color normal
+    printf ") "
+  end
+end
+
+
+# Allow me to prevent certain dangerous commands from ever
+# appearing in autocomplete.
+#
+# See https://alexwlchan.net/2023/forgetful-fish/
+# See https://github.com/fish-shell/fish-shell/issues/10066
+function forget_dangerous_history_commands
+    set last_typed_command (history --max 1)
+
+    if [ "$last_typed_command" = "git push origin (gcb) --force" ]
+        history delete --exact --case-sensitive "$last_typed_command"
+        history save
+    end
+end
+
+
+function fish_prompt --description 'Write out the prompt'
+  # forget_dangerous_history_commands
+
+  # Put a newline between new prompts for cleanliness, but not on the first run.
+  #
+  # This means the first prompt of a new session is right at the top of
+  # the terminal window, not with a newline above it.
+  #
+  # If we're in an SSH session, we always insert a newline, even on the first
+  # command -- to separate from the client session.  I avoid getting the
+  # 'Last login' message with `touch ~/.hushlogin`
+  if set -q SSH_CLIENT
+    echo ''
+  else
+    if test \( -f "/tmp/$TERM_SESSION_ID" -o -f "/tmp/$XDG_SESSION_ID" \)
+      echo ''
+    end
+
+    touch "/tmp/$TERM_SESSION_ID" 2>/dev/null
+    touch "/tmp/$XDG_SESSION_ID" 2>/dev/null
+  end
+
+  # Print some context about where I'm running this command.
+  #
+  # If I'm in my home directory, the context isn't very interesting (it's where
+  # new shells open, and it's not in Git), so skip the context line to reduce
+  # visual noise.
+  if [ (prompt_pwd) = "~" ]
+    if set -q SSH_CLIENT
+      print_ssh_information
+      echo ''
+    end
+    echo '$ '
+    return
+  end
+
+  print_ssh_information
+  print_current_directory
+  print_git_information
+  print_venv_information
+
+  # Print the shell prompt.
+  #
+  # I have a different prompt for when I'm running as root; admittedly this
+  # is extremely rare if I'm also using fish, but if I am I want a visual cue
+  # that this terminal is unusual.
+  #
+  # I print the prompt on a separate line to the context information so it's
+  # always in the same place: as I'm typing commands, I get the full width of
+  # the terminal to use, rather than a variable amount based on the context line.
+  set_color normal
+  if [ "$USER" = "root" ]
+    echo '' & echo '# '
+  else
+    echo '' & echo '$ '
+  end
+end

fish_functions/forget_last_command.fish (0) → fish_functions/forget_last_command.fish (521)

diff --git a/fish_functions/forget_last_command.fish b/fish_functions/forget_last_command.fish
new file mode 100644
index 0000000..3f2e225
--- /dev/null
+++ b/fish_functions/forget_last_command.fish
@@ -0,0 +1,13 @@
+# Removes the last-typed command from my fish history.
+#
+# This means that if I mistype a command and it starts appearing in
+# my suggested commands, I can type it one more time then purge it from
+# my history, to prevent it being suggested again.
+#
+# See https://alexwlchan.net/2023/forgetful-fish/
+# See https://github.com/fish-shell/fish-shell/issues/10066
+function forget_last_command
+    set last_typed_command (history --max 1)
+    history delete --exact --case-sensitive "$last_typed_command"
+    history save
+end

fish_functions/gh-add-remote.fish (0) → fish_functions/gh-add-remote.fish (333)

diff --git a/fish_functions/gh-add-remote.fish b/fish_functions/gh-add-remote.fish
new file mode 100644
index 0000000..298da80
--- /dev/null
+++ b/fish_functions/gh-add-remote.fish
@@ -0,0 +1,7 @@
+# Within a GitHub repository, create a remote named 'alex' for a GitHub fork
+# of the same name.  Useful if I cloned before forking.
+function gh-add-remote
+    set origin_url (git remote get-url origin)
+    set repo_name (string split "/" "$origin_url" | tail -n 1)
+    git remote add alex "git@github.com:alexwlchan/$repo_name"
+end

fish_functions/github-add-pr-branch.fish (0) → fish_functions/github-add-pr-branch.fish (1445)

diff --git a/fish_functions/github-add-pr-branch.fish b/fish_functions/github-add-pr-branch.fish
new file mode 100644
index 0000000..e2754e3
--- /dev/null
+++ b/fish_functions/github-add-pr-branch.fish
@@ -0,0 +1,41 @@
+# Given a GitHub pull request, create the repo and make sure the remote
+# of the PR owner is added as a remote.
+#
+#     $1 = URL of the pull request
+#
+# Sometimes when I'm looking at a pull request, it's useful to get the
+# branch locally and test/review/squash it as appropriate.  This makes
+# it easier to do so!
+#
+# Typically not called directly, but detected by 'github-open' and
+# switched to if looking at a pull request URL.
+function github-add-pr-branch
+    # First ensure we have a local clone of the repo
+    set url "$argv[1]"
+    github-clone (string split "pull/" "$url" | head -n 1)
+    if [ $status != 0 ]
+        return 1
+    end
+
+    # Get the identifiers for the repository.  A pull request URL is
+    # of the form
+    #
+    #     https://github.com/:owner/:repo/pull/:number#discussion_:comment
+    #
+    set components (string split "/" "$url")
+
+    if [ "$components[6]" != "pull" ]
+        echo "$url is not a GitHub pull request"
+        return 1
+    end
+
+    set owner $components[4]
+    set repo $components[5]
+    set number (echo $components[7] | tr '#' ' ' | awk '{print $1}')
+
+    set api_url "https://api.github.com/repos/$owner/$repo/pulls/$number"
+    set api_resp (curl -s -H "Accept: application/vnd.github.v3+json" "$api_url")
+    set pr_branch (echo $api_resp | jq '.head.repo.full_name' | tr '"' ' ' | awk '{print $1}')
+
+    git checkout (echo $api_resp | jq '.head.ref' | tr '"' ' ' | awk '{print $1}')
+end
\ No newline at end of file

fish_functions/github-clone.fish (0) → fish_functions/github-clone.fish (1983)

diff --git a/fish_functions/github-clone.fish b/fish_functions/github-clone.fish
new file mode 100644
index 0000000..22d51ac
--- /dev/null
+++ b/fish_functions/github-clone.fish
@@ -0,0 +1,64 @@
+# Clone a GitHub repo given its URL.
+#
+#     $1 = URL of the GitHub page
+#
+# Because switching to the repo homepage, clicking, copying the clone URL,
+# typing 'git clone', pasting, are all more effort than I care to do manually.
+function github-clone
+    set url "$argv[1]"
+
+    # Get the identifiers for the repository
+    set components (string split "/" "$url")
+
+    if [ "$components[3]" != "github.com" ]
+        echo "$url is not a GitHub repo"
+        return 1
+    end
+
+    set owner $components[4]
+    set repo $components[5]
+
+    if [ (count $components) -gt 5 ]
+        # Detect if this is a pull request, and divert
+        if [ $components[6] = "pull" ]
+            github-add-pr-branch "$url"
+            return $status
+        end
+    end
+
+    set repo_url "git@github.com:$owner/$repo.git"
+
+    mkdir -p ~/repos; cd ~/repos
+
+    if [ -d $repo ]
+        # If the repo already exists, check we have the selected fork
+        # as a remote.
+        cd $repo
+        git remote -v | grep "$owner" >/dev/null 2>&1
+        if [ $status != 0 ]
+            echo "git remote add $owner $repo_url"
+            git remote add $owner $repo_url
+        end
+        set remote (git remote -v | grep "$owner" | awk '{print $1}')
+        git fetch
+    else
+        # Otherwise, clone a fresh copy of the repo
+        echo "git clone $repo_url"
+        git clone $repo_url
+        cd $repo
+
+        # If this looks like a Python repository, create a virtualenv
+        # in the root of the repo.
+        if [ -f "requirements.txt" ]
+            echo "Creating virtualenv..."
+            new_venv
+        end
+
+        # I auto-populate .git/info/exclude with a few common entries to
+        # save having to do it later.  (I could use a global .gitignore,
+        # but this way it's managed programatically and all local to the
+        # repo, which I slightly prefer to a homefolder full of manually
+        # managed dotfiles.)
+        echo .DS_Store >> .git/info/exclude
+    end
+end
\ No newline at end of file

fish_functions/reload_fish_config.fish (0) → fish_functions/reload_fish_config.fish (63)

diff --git a/fish_functions/reload_fish_config.fish b/fish_functions/reload_fish_config.fish
new file mode 100644
index 0000000..e9e4b9d
--- /dev/null
+++ b/fish_functions/reload_fish_config.fish
@@ -0,0 +1,3 @@
+function reload_fish_config
+  . ~/.config/fish/config.fish
+end

fish_functions/tempdir.fish (0) → fish_functions/tempdir.fish (108)

diff --git a/fish_functions/tempdir.fish b/fish_functions/tempdir.fish
new file mode 100644
index 0000000..3067114
--- /dev/null
+++ b/fish_functions/tempdir.fish
@@ -0,0 +1,3 @@
+function tmpdir --description "Quickly create and switch into a temporary directory"
+    cd (mktemp -d)
+end

fish_functions/venv.fish (0) → fish_functions/venv.fish (485)

diff --git a/fish_functions/venv.fish b/fish_functions/venv.fish
new file mode 100644
index 0000000..9532010
--- /dev/null
+++ b/fish_functions/venv.fish
@@ -0,0 +1,18 @@
+# Create and activate a new virtualenv.
+#
+# This is to prevent me from making a very common mistake, which is
+# creating the venv and then immediately running "pip install" without
+# activating it first.
+#
+# I upgrade pip because otherwise I get warnings about it being
+# out-of-date, and that's annoying.
+function new_venv
+  python3 -m venv .venv
+  source .venv/bin/activate.fish
+
+  python3 -m pip install --upgrade pip
+
+  if [ -f .git ]
+    echo .venv >> .git/info/exclude
+  end
+end