<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Peter Benjamin (they/them)</title>
    <description>The latest articles on Forem by Peter Benjamin (they/them) (@pbnj).</description>
    <link>https://forem.com/pbnj</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F23548%2F7b007747-30bd-4391-85fa-39e56baafb12.jpeg</url>
      <title>Forem: Peter Benjamin (they/them)</title>
      <link>https://forem.com/pbnj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pbnj"/>
    <language>en</language>
    <item>
      <title>Tmux Toggle-able Terminals in Split Panes or Floating Windows</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Thu, 31 Aug 2023 19:50:59 +0000</pubDate>
      <link>https://forem.com/pbnj/tmux-toggle-able-terminals-in-split-panes-or-floating-windows-17pa</link>
      <guid>https://forem.com/pbnj/tmux-toggle-able-terminals-in-split-panes-or-floating-windows-17pa</guid>
      <description>&lt;h1&gt;
  
  
  Toggle-able Terminal in Tmux
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;For Vim, Neovim, Helix, or any terminal-based editor&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;(Neo)Vim users are likely familiar with the integrated &lt;code&gt;:terminal&lt;/code&gt;. Any time&lt;br&gt;
you need to compile a program or start a long running process, the&lt;br&gt;
&lt;code&gt;:terminal&lt;/code&gt; is always near-by.&lt;/p&gt;

&lt;p&gt;Popular plugins, like &lt;a href="https://github.com/voldikss/vim-floaterm"&gt;vim-floaterm&lt;/a&gt;&lt;br&gt;
and &lt;a href="https://github.com/akinsho/toggleterm.nvim"&gt;toggleterm.nvim&lt;/a&gt; add some nice&lt;br&gt;
ergonomics, like floating windows and key mappings for toggling the terminal.&lt;/p&gt;

&lt;p&gt;I have been a long-time user of the integrated terminal until I started&lt;br&gt;
encountering long text outputs, like URLs, that the integrated terminal&lt;br&gt;
hard-wraps them mid-word, instead of soft-wrap.&lt;/p&gt;

&lt;p&gt;This meant that what should have been a one-step task of clicking a URL&lt;br&gt;
or copy-pasting text into a Vim buffer has now become a multi-step process of&lt;br&gt;
fixing text by removing line returns. Doing this over-and-over, especially for&lt;br&gt;
large outputs (e.g. logs) gets very tedious very quickly.&lt;/p&gt;

&lt;p&gt;Let's explore how we can accomplish a similar experience as vim-floaterm and&lt;br&gt;
toggleterm.nvim, but leveraging &lt;code&gt;tmux&lt;/code&gt; instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Tmux is a powerful utility. You can configure it using &lt;code&gt;~/.tmux.conf&lt;/code&gt;&lt;br&gt;
configuration file and you can even drive it from within itself (see &lt;code&gt;man tmux&lt;/code&gt;&lt;br&gt;
for usage details). For example, &lt;code&gt;tmux split-window&lt;/code&gt; will split the current&lt;br&gt;
tmux window into 2 vertical panes.&lt;/p&gt;

&lt;p&gt;At a high-level, what we need to accomplish is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bind &lt;code&gt;ctrl-\&lt;/code&gt; to be the keyboard shortcut for toggling the terminal in tmux.&lt;/li&gt;
&lt;li&gt;If the current window has only 1 pane, pressing &lt;code&gt;ctrl-\&lt;/code&gt; should create a new
pane for the terminal.&lt;/li&gt;
&lt;li&gt;If the current window has 2 panes, pressing &lt;code&gt;ctrl-\&lt;/code&gt; should toggle the
terminal pane.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  First Iteration
&lt;/h3&gt;

&lt;p&gt;Let's set up the key binding to either create a new split or to toggle based on&lt;br&gt;
the current number of panes, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bind-key -n 'C-\' if-shell '[ "$(tmux list-panes | wc -l | bc)" = 1 ]' {
  split-window
} {
  resize-pane -Z
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bind-key -n&lt;/code&gt;: this allows us to define keybindings that do not require the
tmux prefix or modifier key (&lt;code&gt;ctrl-b&lt;/code&gt; by default)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if-shell '&amp;lt;condition&amp;gt;' { true } { false }&lt;/code&gt;: this allows us to evaluate some
condition and execute the 1st-block if true, otherwise execute the 2nd block.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[ "$(tmux list-panes | wc -l | bc)" = 1 ]&lt;/code&gt;: we list the current panes, count
the lines, pipe it through a calculator. If we only have 1 pane, then we run
&lt;code&gt;split-window&lt;/code&gt; to create a new pane, otherwise we leverage the zoom feature
to maximize the current pane (i.e. the pane that has the cursor) to fill the
entire tmux window (thus hiding the other pane).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is already a great start. For some, this might be all that you need.&lt;/p&gt;

&lt;p&gt;The experience looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Toggle the terminal with &lt;code&gt;ctrl-\&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Move the cursor to the next pane with &lt;code&gt;ctrl-b&lt;/code&gt; + &lt;code&gt;o&lt;/code&gt;, or &lt;code&gt;ctrl-b&lt;/code&gt; + &lt;code&gt;&amp;lt;down&amp;gt;&lt;/code&gt;
arrows, or even focus the pane with the mouse (if you configure &lt;code&gt;set-option
-g mouse on&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;When done with the terminal pane, focus the main pane and hit &lt;code&gt;ctrl-\&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Having to manually focus tmux panes introduces a bit of friction.&lt;/p&gt;

&lt;p&gt;Let's improve this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second Iteration
&lt;/h3&gt;

&lt;p&gt;For an experience closer to toggleterm.nvim, where &lt;code&gt;ctrl-\&lt;/code&gt; toggles &lt;strong&gt;and&lt;br&gt;
focuses the terminal&lt;/strong&gt;, then &lt;code&gt;ctrl-\&lt;/code&gt; again hides the terminal and returns the&lt;br&gt;
cursor to the main pane, we need to tweak the second branch (i.e. the&lt;br&gt;
&lt;code&gt;resize-pane&lt;/code&gt; logic) to make it a little smarter, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;bind-key -n 'C-\' if-shell '[ "$(tmux list-panes | wc -l | bc)" = 1 ]' {
&lt;/span&gt;&lt;span class="gd"&gt;- split-window
&lt;/span&gt;&lt;span class="gi"&gt;+ split-window -c '#{pane_current_path}'
&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt; {
&lt;span class="gd"&gt;- resize-pane -Z
&lt;/span&gt;&lt;span class="gi"&gt;+ if-shell '[ -n "$(tmux list-panes -F ''#F'' | grep Z)" ]' {
+   select-pane -t:.-
+ } {
+   resize-pane -Z -t1
+ }
&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tmux list-panes -F '#F'&lt;/code&gt;: this lists panes in a custom format. The &lt;code&gt;#F&lt;/code&gt;
tells tmux to print the "pane flags". Zoomed panes have a &lt;code&gt;Z&lt;/code&gt; flag.&lt;/li&gt;
&lt;li&gt;If there is a zoomed pane (i.e. the terminal pane is hidden), then we switch
focus (via &lt;code&gt;select-pane&lt;/code&gt;) to the previous one (i.e. &lt;code&gt;-t:.-&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Otherwise, there is no zoomed pane (i.e. the terminal split pane is shown),
so we zoom into pane 1 (via &lt;code&gt;resize-pane -Z -t1&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is it. We have implemented the core functionality to be able to toggle&lt;br&gt;
terminals with a few lines of tmux configuration.&lt;/p&gt;

&lt;p&gt;But, there is still room for improvement. For those who prefer floating&lt;br&gt;
terminal windows, this is for you.&lt;/p&gt;
&lt;h3&gt;
  
  
  Third Iteration
&lt;/h3&gt;

&lt;p&gt;Let's toggle the terminal in a floating window. Because of the added bit of&lt;br&gt;
complexity here, let's abstract the functionality into a reusable shell script&lt;br&gt;
that we can extend further as needed.&lt;/p&gt;

&lt;p&gt;Tmux has a &lt;code&gt;popup&lt;/code&gt; feature. In the most basic scenario, you can run &lt;code&gt;tmux&lt;br&gt;
popup&lt;/code&gt; inside a tmux session (or &lt;code&gt;ctrl-b&lt;/code&gt; + &lt;code&gt;:&lt;/code&gt;, then type &lt;code&gt;popup&lt;/code&gt; and hit&lt;br&gt;
&lt;code&gt;&amp;lt;ENTER&amp;gt;&lt;/code&gt;) and a floating window will appear with a terminal shell. However, I&lt;br&gt;
was not able to find a way to toggle this popup window. So, we will combine&lt;br&gt;
pop-ups with tmux sessions so we can detach and attach as the toggle mechanism.&lt;/p&gt;

&lt;p&gt;Create a file called it &lt;code&gt;tmux-toggle-term&lt;/code&gt; somewhere in your &lt;code&gt;$PATH&lt;/code&gt;&lt;br&gt;
(e.g. &lt;code&gt;~/.local/bin&lt;/code&gt;) with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-uo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;FLOAT_TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;LIST_PANES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;tmux list-panes &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'#F'&lt;/span&gt; &lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PANE_ZOOMED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIST_PANES&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;Z&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PANE_COUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LIST_PANES&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; | bc&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FLOAT_TERM&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;tmux display-message &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"#{session_name}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"popup"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then
    &lt;/span&gt;tmux detach-client
  &lt;span class="k"&gt;else
    &lt;/span&gt;tmux popup &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'#{pane_current_path}'&lt;/span&gt; &lt;span class="nt"&gt;-xC&lt;/span&gt; &lt;span class="nt"&gt;-yC&lt;/span&gt; &lt;span class="nt"&gt;-w90&lt;/span&gt;% &lt;span class="nt"&gt;-h80&lt;/span&gt;% &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"tmux attach -t popup || tmux new -s popup"&lt;/span&gt;
  &lt;span class="k"&gt;fi
else
  if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PANE_COUNT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;tmux split-window &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"#{pane_current_path}"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PANE_ZOOMED&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-pane&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt;:.-
  &lt;span class="k"&gt;else
    &lt;/span&gt;tmux resize-pane &lt;span class="nt"&gt;-Z&lt;/span&gt; &lt;span class="nt"&gt;-t1&lt;/span&gt;
  &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We expose the floating window behind a &lt;code&gt;FLOAT_TERM&lt;/code&gt; flag&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;FLOAT_TERM&lt;/code&gt; string is not empty, then we check the session name. If
session name of &lt;code&gt;popup&lt;/code&gt; exists, then we detach, otherwise we attempt to
attach. If attach fails, then we create a new session.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;FLOAT_TERM&lt;/code&gt; string is empty, then we fallback to split panes with the
same logic as before.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, update your &lt;code&gt;~/.tmux.conf&lt;/code&gt; to replace the previous config from the 1st or&lt;br&gt;
2nd iterations with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# for splits
bind-key -n 'C-\' run-shell -b "${HOME}/path/to/tmux-toggle-term"

# or, for floats
bind-key -n 'C-\' run-shell -b "${HOME}/path/to/tmux-toggle-term float"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; (Neo)Vim users who enjoy using the integrated terminal with buffer&lt;br&gt;
completion in insert-mode to avoid copy/paste can still accomplish a similar&lt;br&gt;
with &lt;a href="https://github.com/wellle/tmux-complete.vim"&gt;https://github.com/wellle/tmux-complete.vim&lt;/a&gt; or&lt;br&gt;
&lt;a href="https://github.com/andersevenrud/cmp-tmux"&gt;https://github.com/andersevenrud/cmp-tmux&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we have seen how we can accomplish so much with so little.&lt;/p&gt;

&lt;p&gt;I hope this inspired you to find ways to make your workflows more efficient and&lt;br&gt;
productive.&lt;/p&gt;

</description>
      <category>tmux</category>
      <category>vim</category>
      <category>neovim</category>
      <category>helix</category>
    </item>
    <item>
      <title>FZF + JQ = Interactive JQ (+ a vim bonus)</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Sat, 20 May 2023 03:09:38 +0000</pubDate>
      <link>https://forem.com/pbnj/fzf-jq-interactive-jq-15no</link>
      <guid>https://forem.com/pbnj/fzf-jq-interactive-jq-15no</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm99mcp7dzz14wcf2e0om.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm99mcp7dzz14wcf2e0om.png" alt="fzf + jq"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Do you frequently query an API and get back a large JSON payload, like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;curl https://jsonplaceholder.typicode.com/todos
&lt;span class="o"&gt;[&lt;/span&gt;
  &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"userId"&lt;/span&gt;: 1,
    &lt;span class="s2"&gt;"id"&lt;/span&gt;: 1,
    &lt;span class="s2"&gt;"title"&lt;/span&gt;: &lt;span class="s2"&gt;"delectus aut autem"&lt;/span&gt;,
    &lt;span class="s2"&gt;"completed"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  ...
&lt;span class="o"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So you pipe it to &lt;code&gt;jq&lt;/code&gt;, but wish you could interactively query the data?&lt;/p&gt;

&lt;p&gt;You may be familiar with some utilities that provide this interactivity, like &lt;a href="https://github.com/noahgorstein/jqp" rel="noopener noreferrer"&gt;&lt;code&gt;jqp&lt;/code&gt;&lt;/a&gt;, but these come with their own downsides, namely subtle bugs due to the re-implementation of the original JQ in language X, but also they are additional dependencies you have to track and install for your platform architecture and OS/distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Here is a hack (err... ✨ &lt;strong&gt;pro-tip&lt;/strong&gt; ✨) to get JQ interactivity for “free” (as long as you have &lt;code&gt;fzf&lt;/code&gt; on your machine):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; | fzf &lt;span class="nt"&gt;--print-query&lt;/span&gt; &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s2"&gt;"cat &lt;/span&gt;&lt;span class="nv"&gt;$JSON_FILE_ON_DISK&lt;/span&gt;&lt;span class="s2"&gt; | jq {q}"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; | fzf &lt;span class="nt"&gt;--print-query&lt;/span&gt; &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s2"&gt;"cat &amp;lt;(curl &lt;/span&gt;&lt;span class="nv"&gt;$API_URL&lt;/span&gt;&lt;span class="s2"&gt;) | jq {q}"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  How does this work?
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

echo '' | fzf --print-query --preview "cat $JSON_FILE_ON_DISK | jq {q}"
^^^^^^^   ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [1]          [2]                          [3]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When running &lt;code&gt;fzf&lt;/code&gt;, by default it will prompt you to fuzzy search through a list of files in your current working directory. So, we suppress this by &lt;code&gt;echo&lt;/code&gt;ing an empty string into &lt;code&gt;fzf&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--print-query&lt;/code&gt; is an optional flag that will print out the query we typed upon hitting Enter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--preview&lt;/code&gt; flag is what enables all of this. We are using fzf's previewing capability to &lt;code&gt;cat&lt;/code&gt; out JSON content, pipe it to &lt;code&gt;jq&lt;/code&gt;, injecting our query as &lt;code&gt;jq&lt;/code&gt; arguments via &lt;code&gt;{q}&lt;/code&gt; placeholder, and we see the results in fzf's preview window.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Further Improvements
&lt;/h3&gt;

&lt;p&gt;We can improve this further by creating a shell script in our global &lt;code&gt;$PATH&lt;/code&gt; to turn it into a quick handy utility.&lt;/p&gt;

&lt;p&gt;For example, assuming this script is named &lt;code&gt;fjq&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; | fzf-tmux &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'80%'&lt;/span&gt; &lt;span class="nt"&gt;--print-query&lt;/span&gt; &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s2"&gt;"cat &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | jq {q}"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can now use this like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# interactive jq on a single JSON file&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;fjq file.json

&lt;span class="c"&gt;# interactive jq on a directory with multiple JSON files&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;fjq /path/to/&lt;span class="k"&gt;*&lt;/span&gt;.json

&lt;span class="c"&gt;# interactive jq on-the-fly&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;fjq &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;curl https://jsonplaceholder.typicode.com/todos&lt;span class="o"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Voila! Interactive json querying with just &lt;code&gt;jq&lt;/code&gt; and &lt;code&gt;fzf&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vim Tip
&lt;/h3&gt;

&lt;p&gt;A semi-interactive experience can be achieved (without FZF) by leveraging Vim's &lt;code&gt;:!&lt;/code&gt; and &lt;code&gt;:help filter&lt;/code&gt; to filter contents of Vim buffers through an external program and write the output back into Vim's buffers, like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ curl https://jsonplaceholder.typicode.com/todos | vim -

# inside vim ...

:%! jq .[].completed
:%! sort | uniq -c


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can even use our previous script for full JQ interactivity:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;

&lt;span class="p"&gt;:!&lt;/span&gt; fjq %
&lt;span class="c"&gt;" or&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;%&lt;span class="p"&gt;!&lt;/span&gt; fjq %
&lt;span class="c"&gt;" or &lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt; fjq %


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We've seen how terminal programs and shell utilities can be composed to create powerful workflows.&lt;/p&gt;

&lt;p&gt;We took 2-3 tools that already exist on our system and achieved 80% of what other tools try to accomplish by reimplementing &lt;code&gt;jq&lt;/code&gt; in Go, Ruby, Rust, Python, Node.js ...etc.&lt;/p&gt;

&lt;p&gt;This is not to say you should not reach for these extra tools, but, in many cases, learning how to leverage existing tools enables you to solve more problems than what these tools were created for.&lt;/p&gt;

&lt;p&gt;Happy hacking! 😄&lt;/p&gt;

</description>
      <category>fzf</category>
      <category>jq</category>
      <category>vim</category>
      <category>protip</category>
    </item>
    <item>
      <title>Open Local Files and Line Numbers in GitHub and GitLab From Shell or Vim</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Wed, 25 Aug 2021 08:17:20 +0000</pubDate>
      <link>https://forem.com/pbnj/open-local-files-and-line-numbers-in-github-and-gitlab-26m6</link>
      <guid>https://forem.com/pbnj/open-local-files-and-line-numbers-in-github-and-gitlab-26m6</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Update&lt;/li&gt;
&lt;li&gt;Problem&lt;/li&gt;
&lt;li&gt;
Solution

&lt;ul&gt;
&lt;li&gt;Git Subcommand&lt;/li&gt;
&lt;li&gt;Vim Command&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Update
&lt;/h2&gt;

&lt;p&gt;I have contributed this solution to &lt;a href="https://github.com/tj/git-extras/pull/981"&gt;&lt;code&gt;git-extras&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Now you can enjoy this feature without having to implement &amp;amp; maintain a custom script.&lt;/p&gt;

&lt;p&gt;To integrate this new feature with Vim, add the following snippet to your vimrc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="c"&gt;" GitBrowse takes a dictionary and opens files in the browser at the remote URL.&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; GitBrowse&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; abort
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;filename &lt;span class="p"&gt;==&lt;/span&gt;# &lt;span class="s1"&gt;''&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="k"&gt;endif&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;remote &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'git config branch.'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;branch&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'.remote || echo "origin" '&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;cmd &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'git browse '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;remote &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;filename
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;cmd &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'git browse '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;remote &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;filename &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;line1 &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;line2
  &lt;span class="k"&gt;endif&lt;/span&gt;
  &lt;span class="nb"&gt;execute&lt;/span&gt; &lt;span class="s1"&gt;'silent ! '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;cmd &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;

command&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt; GB &lt;span class="k"&gt;call&lt;/span&gt; GitBrowse&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="se"&gt;      \&lt;/span&gt; &lt;span class="s1"&gt;'branch'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'git rev-parse --abbrev-ref HEAD 2&amp;gt;/dev/null'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="se"&gt;      \&lt;/span&gt; &lt;span class="s1"&gt;'filename'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'git ls-files --full-name '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;expand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
&lt;span class="se"&gt;      \&lt;/span&gt; &lt;span class="s1"&gt;'range'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
&lt;span class="se"&gt;      \&lt;/span&gt; &lt;span class="s1"&gt;'line1'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;line1&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
&lt;span class="se"&gt;      \&lt;/span&gt; &lt;span class="s1"&gt;'line2'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;line2&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
&lt;span class="se"&gt;      \&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I frequently need to share links and URLs to files in GitHub/GitLab repositories with colleagues.&lt;/p&gt;

&lt;p&gt;Traditionally, I did this manually by launching a browser, navigating to the repository and file, then selecting the line numbers in question, and finally copying the permalink or the link of the current branch.&lt;/p&gt;

&lt;p&gt;Because this process takes several minutes, it was enough to cause significant context switching and breaks me out of the flow, causing me to lose my train of thought (relevant &lt;a href="https://heeris.id.au/2013/this-is-why-you-shouldnt-interrupt-a-programmer/"&gt;comic&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;One feature I appreciated in &lt;a href="https://github.com/tpope/vim-fugitive"&gt;vim-fugitive&lt;/a&gt; was the &lt;code&gt;:Gbrowse&lt;/code&gt; command on a buffer or a range (i.e. &lt;code&gt;:Gbrowse&lt;/code&gt; on a visual selection) that would open the current file and line number directly on GitHub.&lt;/p&gt;

&lt;p&gt;I wanted to implement this feature in my minimal vim setup, but I also wanted to be able to use this in a shell without vim.&lt;/p&gt;

&lt;p&gt;In this blog post, I will walk you through the process of implementing this solution one building block at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;I had an idea of the experience I wanted.&lt;/p&gt;

&lt;p&gt;I wanted to be able to call &lt;code&gt;git&lt;/code&gt; commands from the shell, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# open local repo in GitHub (GH) / GitLab (GL)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git browse

&lt;span class="c"&gt;# open a file from local repo in GH/GL&lt;/span&gt;
&lt;span class="c"&gt;# $ git browse &amp;lt;filename&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git browse README.md

&lt;span class="c"&gt;# open a file &amp;amp; line number from local repo in GH/GL&lt;/span&gt;
&lt;span class="c"&gt;# $ git browse &amp;lt;filename&amp;gt; &amp;lt;line_number&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git browse README.md 1

&lt;span class="c"&gt;# open a file &amp;amp; select range of lines from local repo in GH/GL&lt;/span&gt;
&lt;span class="c"&gt;# $ git browse &amp;lt;filename&amp;gt; &amp;lt;line_from&amp;gt; &amp;lt;line_to&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git browse README.md 1 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I wanted to be able to call the same functionality within vim, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="c"&gt;" open current file &amp;amp; line under cursor in GH/GL &lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;Gbrowse

&lt;span class="c"&gt;" open line range from vim in GH/GL&lt;/span&gt;
&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;Gbrowse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that rough specification in mind, let's dive into the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Subcommand
&lt;/h3&gt;

&lt;p&gt;If you didn't know, &lt;code&gt;git&lt;/code&gt; allows you to define your own custom subcommands as long as the file name starts with &lt;code&gt;git-&lt;/code&gt; prefix and the file is located in your &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With this information in mind, we can start by creating a file called &lt;code&gt;git-browse&lt;/code&gt; located in &lt;code&gt;${HOME}/bin&lt;/code&gt; (assuming &lt;code&gt;${HOME}/bin&lt;/code&gt; is in your &lt;code&gt;$PATH&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git-browse&lt;/code&gt; file will be a straight forward shell script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define shebang and expected inputs:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

   &lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
   &lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
   &lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Get some additional data we will need, like branch name,  remote name, and remote-url:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD 2&amp;gt; /dev/null&lt;span class="si"&gt;)&lt;/span&gt;
   &lt;span class="nv"&gt;remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git config branch.&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;.remote &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"origin"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
   &lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git remote get-url &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;remote&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;I always use HTTPS for git URLs, but if you use SSH, then you would want to convert &lt;code&gt;git@*&lt;/code&gt; to &lt;code&gt;https://*&lt;/code&gt;. Also, remove the trailing &lt;code&gt;.git&lt;/code&gt; in the HTTP URL:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$giturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; git@&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
     &lt;/span&gt;&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$giturl&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/:/\//'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/\.git$//'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/.*@(.*)/http:\/\/\1/'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;fi

   &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="p"&gt;%.git&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Now we craft the URL with filenames and line numbers based on the domain (GH &amp;amp; GL have slight nuances):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;# GitLab Example: https://gitlab.example.com/group/repo/-/blob/commit_or_branch/path/to/filename#L1-2&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ gitlab &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
     if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
       &lt;/span&gt;&lt;span class="nv"&gt;commit_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"%H"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
       &lt;span class="c"&gt;# append '/-/blob' + commit hash or branch name + '/&amp;lt;filename&amp;gt;'&lt;/span&gt;
       &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/-/blob/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;commit_hash&lt;/span&gt;&lt;span class="k"&gt;:-${&lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
         &lt;span class="c"&gt;# append '#L&amp;lt;LINE1&amp;gt;'&lt;/span&gt;
         &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#L&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
           &lt;span class="c"&gt;# append '-&amp;lt;LINE2&amp;gt;'&lt;/span&gt;
       &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
     &lt;span class="k"&gt;fi
       fi
     fi
   fi&lt;/span&gt;

   &lt;span class="c"&gt;# GitHub Example: https://github.example.com/org/repo/blob/commit_or_branch/path/to/filename#L1-L2&lt;/span&gt;
   &lt;span class="c"&gt;# Mostly the same as above, except:&lt;/span&gt;
   &lt;span class="c"&gt;#   - no '/-/'&lt;/span&gt;
   &lt;span class="c"&gt;#   - '#L1-L2' instead of '#L1-2'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Lastly, open the crafted URL:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;hash &lt;/span&gt;open 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; open &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
   &lt;span class="nb"&gt;hash &lt;/span&gt;xdg-open 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; xdg-open &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Don't forget to &lt;code&gt;chmod +x ${HOME}/bin/git-browse&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, these commands should work&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git browse
&lt;span class="nv"&gt;$ &lt;/span&gt;git browse README.md
&lt;span class="nv"&gt;$ &lt;/span&gt;git browse README.md 1
&lt;span class="nv"&gt;$ &lt;/span&gt;git browse README.md 1 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is half the battle!&lt;/p&gt;

&lt;h4&gt;
  
  
  TLDR
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; HEAD 2&amp;gt; /dev/null&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;remote&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git config branch.&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;.remote &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"origin"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git remote get-url &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;remote&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# base url&lt;/span&gt;
&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="p"&gt;%.git&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# craft gitlab URLs&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ gitlab &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;commit_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"%H"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/-/blob/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;commit_hash&lt;/span&gt;&lt;span class="k"&gt;:-${&lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#L&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
                &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;fi
        fi
    fi&lt;/span&gt;

&lt;span class="c"&gt;# craft github URLs&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ github &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;commit_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="nt"&gt;-n1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s2"&gt;"%H"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;giturl&lt;/span&gt;&lt;span class="p"&gt;%.*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blob/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;commit_hash&lt;/span&gt;&lt;span class="k"&gt;:-${&lt;/span&gt;&lt;span class="nv"&gt;branch&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#L&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
                &lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-L&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;line2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;fi
        fi
    fi
fi

&lt;/span&gt;&lt;span class="nb"&gt;hash &lt;/span&gt;open 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; open &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;hash &lt;/span&gt;xdg-open 2&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; xdg-open &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vim Command
&lt;/h3&gt;

&lt;p&gt;I never imaged I would be saying this, but this is the &lt;strong&gt;easy&lt;/strong&gt; part!&lt;/p&gt;

&lt;p&gt;All we need is a Vim command that accepts a range and executes the correct shell command that we implemented above, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;command&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt; Gbrowse &lt;span class="nb"&gt;execute&lt;/span&gt; &lt;span class="s1"&gt;'silent ! git browse '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;expand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;line1&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;line2&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;checktime&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;command! -range&lt;/code&gt; allows us to pass a line range, like &lt;code&gt;:1,5Gbrowse&lt;/code&gt;, or visual selection. See &lt;code&gt;:help :command-range&lt;/code&gt; for more info.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Gbrowse&lt;/code&gt; is the name of the command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;execute '...'&lt;/code&gt; is going to execute whatever string we pass. See &lt;code&gt;:help :execute&lt;/code&gt; for more info.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;silent&lt;/code&gt; prevents vim from prompting &lt;code&gt;Hit ENTER to continue&lt;/code&gt;. See &lt;code&gt;:help :silent&lt;/code&gt; for more info.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;!git browse&lt;/code&gt; is calling the external git subcommand we defined above.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;. expand('%')&lt;/code&gt; concatenates the current buffer path &amp;amp; name to the previous string. See &lt;code&gt;:help expand()&lt;/code&gt; for more info.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;' ' . &amp;lt;line1&amp;gt; . ' ' . &amp;lt;line2&amp;gt;&lt;/code&gt; concatenates start of range and end of range to the previous string. For example, &lt;code&gt;:1,5Gbrowse&lt;/code&gt; translates to &lt;code&gt;!git browse &amp;lt;filename&amp;gt; 1 5&lt;/code&gt;. See &lt;code&gt;:help &amp;lt;line1&amp;gt;&lt;/code&gt; and &lt;code&gt;:help &amp;lt;line2&amp;gt;&lt;/code&gt; for more info.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;| checktime | redraw!&lt;/code&gt; refreshes the screen and redraws the buffer, otherwise after silently executing an external command, you may see a blank screen in vim.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;For a demonstration of this in action:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/MyfYEAG2VNs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What I absolutely love about this is the demonstration of the Unix Philosphy in action. This post illustrates how disparate tools can be composed and used together to create powerful and ergonomic workflows with very little effort.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this post and found some of it useful for your workflows and needs.&lt;/p&gt;

&lt;p&gt;If you liked this guide, you may find more useful/interesting things in my &lt;a href="https://github.com/pbnj/dotfiles/blob/main/vim/.vim/vimrc"&gt;&lt;code&gt;vimrc&lt;/code&gt;&lt;/a&gt; and/or in my &lt;a href="https://github.com/pbnj/dotfiles/tree/main/git/bin"&gt;custom git subcommands&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For more interesting git subcommands, checkout &lt;a href="https://github.com/tj/git-extras"&gt;git-extras&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As always, happy hacking! 🤘🏽&lt;/p&gt;

</description>
      <category>git</category>
      <category>vim</category>
      <category>bash</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Print Git Status in Your Tmux Statusbar</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Wed, 18 Aug 2021 04:14:15 +0000</pubDate>
      <link>https://forem.com/pbnj/print-git-status-in-your-tmux-statusbar-232h</link>
      <guid>https://forem.com/pbnj/print-git-status-in-your-tmux-statusbar-232h</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Problem&lt;/li&gt;
&lt;li&gt;
Solution

&lt;ul&gt;
&lt;li&gt;Bash Function&lt;/li&gt;
&lt;li&gt;Git Alias&lt;/li&gt;
&lt;li&gt;Tmux&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I recently wanted to show git branch and status information of the project I am wokring on in tmux statusbar. &lt;/p&gt;

&lt;p&gt;We can use one of the many open source projects to achieve this (see the Conclusion for honorable mentions), but I prefer a minimalist approach for simplicity and portability.&lt;/p&gt;

&lt;p&gt;Naturally, I know I can invoke any shell command from my &lt;code&gt;.tmux.conf&lt;/code&gt; using the &lt;code&gt;#(&amp;lt;shell command&amp;gt;)&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;set -g status-right '#(echo "hello world")'&lt;/code&gt; will print &lt;code&gt;hello world&lt;/code&gt; in the right corner of your tmux statusbar, which is located at &lt;code&gt;bottom&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;But, doing something like &lt;code&gt;set -g status-right '#(git branch)'&lt;/code&gt; didn't work. This is because the command is running in a different shell context, like running &lt;code&gt;sh -c "git branch"&lt;/code&gt;. The current directory is simply not passed to the sub-shell command.&lt;/p&gt;

&lt;p&gt;Furthermore, &lt;code&gt;git branch&lt;/code&gt; only prints the branch name. We need a custom bash function to print symbols indicating if there are modified files, staged files, stashes, and/or untracked files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Similar to how you can print any information in a &lt;a href="https://github.com/pbnj/dotfiles/blob/5dbc2c665db081a4ffe07dea63902e7cc62f9fd3/bash/.bash_prompt"&gt;&lt;code&gt;.bash_prompt&lt;/code&gt;&lt;/a&gt; via custom bash functions, so too can we implement a function that is invoked as a git sub-command via &lt;a href="https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases"&gt;aliases&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bash Function
&lt;/h3&gt;

&lt;p&gt;The function we will use for this is borrowed from &lt;a href="https://github.com/jessfraz/dotfiles/blob/master/.bash_prompt"&gt;jessfraz/dotfiles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If we simply join all the lines and escape the double-quotes, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt_git() { local s=''; local branchName=''; if [ \"$(git rev-parse --is-inside-work-tree &amp;amp;&amp;gt;/dev/null; echo \"${?}\")\" == '0' ]; then if [ \"$(git rev-parse --is-inside-git-dir 2&amp;gt; /dev/null)\" == 'false' ]; then if [[ -O \"$(git rev-parse --show-toplevel)/.git/index\" ]]; then git update-index --really-refresh -q &amp;amp;&amp;gt; /dev/null; fi; if ! git diff --quiet --ignore-submodules --cached; then s+='+'; fi; if ! git diff-files --quiet --ignore-submodules --; then s+='!'; fi; if [ -n \"$(git ls-files --others --exclude-standard)\" ]; then s+='?'; fi; if git rev-parse --verify refs/stash &amp;amp;&amp;gt; /dev/null; then s+='$'; fi; fi; branchName=\"$(git symbolic-ref --quiet --short HEAD 2&amp;gt; /dev/null || git rev-parse --short HEAD 2&amp;gt; /dev/null || echo '(unknown)')\"; echo \"(${1}${branchName} ${s})\"; else return; fi; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Git Alias
&lt;/h3&gt;

&lt;p&gt;Now, you can put this one-liner into a git alias in your &lt;code&gt;.gitconfig&lt;/code&gt; or &lt;code&gt;~/.config/git/config&lt;/code&gt;, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[alias]
    prompt = !"prompt_git() { ... }; prompt_git"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip: alternatively, you can copy/paste the bash_prompt function into a shell script file prefixed with &lt;code&gt;git-&lt;/code&gt;, like &lt;code&gt;git-prompt&lt;/code&gt;, placed somewhere in your &lt;code&gt;$PATH&lt;/code&gt;. This allows you to call it as if it were a standard git subcommand as well, like &lt;code&gt;git prompt&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Note that we invoke/execute the function after defining it.&lt;/p&gt;

&lt;p&gt;To test this, simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should return the branch name and indicators based on your status, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(branch +!$?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tmux
&lt;/h3&gt;

&lt;p&gt;Now, we can easily call a git command to show this information, but we also need to pass a &lt;code&gt;-C&lt;/code&gt; flag with a path to the repo in question. Fortunately, tmux gives us the path of the current pane in a special variable called &lt;code&gt;#{pane_current_path}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, our status bar configuration in tmux may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; status-right &lt;span class="s1"&gt;'#(git -C #{pane_current_path} prompt)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Bonus Tip
&lt;/h4&gt;

&lt;p&gt;Did you know tmux has 2 different "status bars"? &lt;/p&gt;

&lt;p&gt;I like to put any global information, or information that does not change between panes, in my &lt;code&gt;status-right&lt;/code&gt;. This includes date, time, cpu, and memory.&lt;/p&gt;

&lt;p&gt;Any pane-specific information, I prefer to put it in my &lt;code&gt;pane-border-format&lt;/code&gt;. This includes current working directory and git status information.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -g status-right-length 200
# show [prefix] when activated/pressed + mem/cpu load + date/timestamp
set -g status-right "#{?client_prefix,#[reverse][prefix]#[noreverse],}  #(tmux-mem-cpu-load --interval 1) [%A %Y-%m-%d %l:%M %p]"

set -g pane-border-status top
set -g pane-border-format ' #{pane_current_path} #(git -C #{pane_current_path} prompt) '
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you are a minimalist like me, I hope this helps you configure and customize your tmux to best suit your needs, workflow, and productivity.&lt;/p&gt;

&lt;p&gt;If you were hoping to discover tools or utilities to conveniently configure tmux, then I would like to leave you with some honorable mentions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/arl/gitmux"&gt;&lt;code&gt;gitmux&lt;/code&gt;&lt;/a&gt;: a binary that you can simply download, configure, and execute in your &lt;code&gt;.tmux.conf&lt;/code&gt;. No runtime dependencies needed.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/powerline/powerline"&gt;&lt;code&gt;powerline&lt;/code&gt;&lt;/a&gt;: a fully-featured statusline utility for vim, tmux, and more. Requires python.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shuber/tmux-git"&gt;&lt;code&gt;tmux-git&lt;/code&gt;&lt;/a&gt;: a tmux plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more inspiration, feel free to browse my &lt;a href="https://github.com/pbnj/dotfiles"&gt;dotfiles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

&lt;p&gt;Happing hacking!&lt;/p&gt;

</description>
      <category>linux</category>
      <category>git</category>
      <category>tmux</category>
      <category>minimal</category>
    </item>
    <item>
      <title>How To Get Make Target Tab Completion in Vim</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Sun, 08 Aug 2021 04:26:09 +0000</pubDate>
      <link>https://forem.com/pbnj/how-to-get-make-target-tab-completion-in-vim-4mj1</link>
      <guid>https://forem.com/pbnj/how-to-get-make-target-tab-completion-in-vim-4mj1</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Problem&lt;/li&gt;
&lt;li&gt;Solution&lt;/li&gt;
&lt;li&gt;Improved&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;If you use &lt;code&gt;make&lt;/code&gt; frequently, you may be aware that you can tab complete targets in bash. For example, &lt;code&gt;$ make cl&amp;lt;TAB&amp;gt; ins&amp;lt;TAB&amp;gt;&lt;/code&gt; may autocomplete to &lt;code&gt;clean&lt;/code&gt; then &lt;code&gt;install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In vim, however, if you type &lt;code&gt;:make &amp;lt;TAB&amp;gt;&lt;/code&gt;, by default, vim attempts to autocomplete file names.&lt;/p&gt;

&lt;p&gt;This blog post will show you how you can get vim to autocomplete &lt;code&gt;&amp;lt;TAB&amp;gt;&lt;/code&gt; with target names from your &lt;code&gt;Makefile&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;I recently came across &lt;a href="https://unix.stackexchange.com/questions/230047/how-to-list-all-targets-in-make"&gt;this&lt;/a&gt; StackOverflow question, &lt;em&gt;How to list all targets in make?&lt;/em&gt;, in which the answer is bash's completion logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nt"&gt;-qp&lt;/span&gt; |
    &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt; &lt;span class="s1"&gt;'/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'&lt;/span&gt; |
    &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will print out a newline-delimited list of possible targets from the Makefile in your current working directory.&lt;/p&gt;

&lt;p&gt;With this set of commands, we can write a custom vimscript function that executes them and returns the output as completion candidates for a vim command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; MakeCompletion&lt;span class="p"&gt;()&lt;/span&gt; abort
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;systemlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'make -qp | awk -F'':'' ''/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'' | grep -v Makefile | sort -u'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;

command&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;nargs&lt;span class="p"&gt;=&lt;/span&gt;* &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;customlist&lt;span class="p"&gt;,&lt;/span&gt;MakeCompletion Make &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;make&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;args&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This alone is a great accomplishment. If you type &lt;code&gt;:Make &amp;lt;TAB&amp;gt;&lt;/code&gt;, you can cycle through all the possible make targets!&lt;/p&gt;

&lt;p&gt;However, you may notice that if you type part of a target name and hit &lt;code&gt;&amp;lt;TAB&amp;gt;&lt;/code&gt;, vim will ignore the partial input and cycle through from the beginning of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved
&lt;/h2&gt;

&lt;p&gt;If you read &lt;code&gt;:help :command-completion-customlist&lt;/code&gt;, you will discover that the function can take 3 arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The function arguments are:
    ArgLead     the leading portion of the argument currently being completed on
    CmdLine     the entire command line
    CursorPos   the cursor position in it (byte index)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's modify our &lt;code&gt;MakeCompletion()&lt;/code&gt; function to support filtering results based on &lt;code&gt;ArgLead&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; MakeCompletion&lt;span class="p"&gt;(&lt;/span&gt;A&lt;span class="p"&gt;,&lt;/span&gt;L&lt;span class="p"&gt;,&lt;/span&gt;P&lt;span class="p"&gt;)&lt;/span&gt; abort
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;targets &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;systemlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'make -qp | awk -F'':'' ''/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'' | grep -v Makefile | sort -u'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;targets&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'v:val =~ "^'&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;a:A&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;

command&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;nargs&lt;span class="p"&gt;=&lt;/span&gt;* &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;customlist&lt;span class="p"&gt;,&lt;/span&gt;MakeCompletion Make &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;make&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;args&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when you type &lt;code&gt;:Make cl&amp;lt;TAB&amp;gt; ins&amp;lt;TAB&amp;gt;&lt;/code&gt;, it will successfully auto-complete to &lt;code&gt;:Make clean install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I hope you find this short vim tip helpful.&lt;/p&gt;

&lt;p&gt;For more vim goodies, check out my &lt;a href="https://github.com/pbnj/dotfiles/blob/main/vim/.vim/vimrc"&gt;vimrc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy hacking! &lt;/p&gt;

</description>
      <category>vim</category>
      <category>make</category>
      <category>linux</category>
    </item>
    <item>
      <title>A Workflow for Cloning and Launching Git Repositories in New tmux Windows</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Sun, 25 Jul 2021 16:02:28 +0000</pubDate>
      <link>https://forem.com/pbnj/clone-and-launch-git-repositories-in-new-tmux-windows-3bdh</link>
      <guid>https://forem.com/pbnj/clone-and-launch-git-repositories-in-new-tmux-windows-3bdh</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;The Setup&lt;/li&gt;
&lt;li&gt;Bonus&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;One thing I really like about Visual Studio Code is the ability to clone a repo and open it in the editor with little-to-no friction in 3 quick steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Command Palette, select: &lt;code&gt;&amp;gt; Git: clone&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enter the repo to clone&lt;/li&gt;
&lt;li&gt;Select the directory to clone into&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then Visual Studio Code launches a new editor in the repo and I'm ready to go.&lt;/p&gt;

&lt;p&gt;I want to have a similar experience in my terminal-based development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;At a high-level, my current solution is to invoke a bash function that prompts the user for the repo to clone and directory to clone it into, then run vim on the repo in a new tmux window.&lt;/p&gt;

&lt;p&gt;Here is the implementation as a bash script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git-clone-tmux&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;

    &lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Repo: "&lt;/span&gt; repo
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Directory: "&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Work"&lt;/span&gt; directory

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^github &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GH_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        gh repo clone &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^gitlab &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GL_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        glab repo clone &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;directory&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        git clone &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi

    &lt;/span&gt;tmux new-window &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;clone_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; vim &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens when you run &lt;code&gt;$ git-clone-tmux&lt;/code&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[ -z "${repo}" ] &amp;amp;&amp;amp; read -e -p "Repo: " repo&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;${repo}&lt;/code&gt; is empty, then prompt user interactively&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[ -z "${directory}" ] &amp;amp;&amp;amp; read -e -p "Directory: " -i "${HOME}/Work" directory&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;${directory}&lt;/code&gt; is empty, then prompt user interactively&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if [[ "${repo}" =~ ^github ]]&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;${repo}&lt;/code&gt; starts with &lt;code&gt;github&lt;/code&gt;, then use &lt;a href="https://github.com/cli/cli"&gt;&lt;code&gt;gh&lt;/code&gt;&lt;/a&gt; to clone the repo&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if [[ "${repo}" =~ ^gitlab ]]&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;${repo}&lt;/code&gt; starts with &lt;code&gt;gitlab&lt;/code&gt;, then use &lt;a href="https://github.com/profclems/glab"&gt;&lt;code&gt;glab&lt;/code&gt;&lt;/a&gt; to clone the repo&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;otherwise, assume &lt;code&gt;${repo}&lt;/code&gt; starts with &lt;code&gt;https://&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tmux new-window -c "${clone_path}" -n "${clone_path}" vim .&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Launch new tmux window, setting the working directory to the newly cloned repo, and run &lt;code&gt;vim .&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;You can create a git alias so that you can call this function as if it were a git subcommand.&lt;/p&gt;

&lt;p&gt;Just run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; alias.clone-tmux &lt;span class="s1"&gt;'!/usr/bin/env bash -ic git-clone-tmux'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or add the following to your git config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[alias]
    clone-tmux = !/usr/bin/env bash -ic git-clone-tmux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can run &lt;code&gt;git clone-tmux &amp;lt;repo&amp;gt; &amp;lt;dir&amp;gt;&lt;/code&gt; or &lt;code&gt;git clone-tmux&lt;/code&gt; for interactive prompting. &lt;/p&gt;

&lt;p&gt;Extra bonus: shell tab completions work on git aliases too!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This is, by no means, the most elegant script, but it works for me needs.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed reading this article and I hope it inspires you to find ways to streamline and improve your workflow and productivity!&lt;/p&gt;

&lt;p&gt;For more git alias goodies, check out my &lt;a href="https://github.com/pbnj/dotfiles/blob/master/git/.config/git/config"&gt;&lt;code&gt;.config/git/config&lt;/code&gt;&lt;/a&gt; dotfile.&lt;/p&gt;

&lt;p&gt;For more bash function goodies, check out my &lt;a href="https://github.com/pbnj/dotfiles/blob/master/bash/.functions"&gt;&lt;code&gt;.functions&lt;/code&gt;&lt;/a&gt; dotfile.&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>tmux</category>
      <category>git</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Interactive Fuzzy Finding in Vim without Plugins</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Sun, 25 Jul 2021 03:11:02 +0000</pubDate>
      <link>https://forem.com/pbnj/interactive-fuzzy-finding-in-vim-without-plugins-4kkj</link>
      <guid>https://forem.com/pbnj/interactive-fuzzy-finding-in-vim-without-plugins-4kkj</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;The Setup&lt;/li&gt;
&lt;li&gt;Bonus&lt;/li&gt;
&lt;li&gt;Caveat&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/junegunn/fzf"&gt;FZF&lt;/a&gt; is a great command-line fuzzy-finder and there is &lt;a href="https://github.com/junegunn/fzf.vim"&gt;fzf.vim&lt;/a&gt; plugin that integrates with Vim to provide features, like &lt;a href="https://github.com/junegunn/fzf.vim/blob/e34f6c129d39b90db44df1107c8b7dfacfd18946/doc/fzf-vim.txt#L118"&gt;&lt;code&gt;:Files&lt;/code&gt;&lt;/a&gt; to fuzzy search over files and &lt;a href="https://github.com/junegunn/fzf.vim/blob/e34f6c129d39b90db44df1107c8b7dfacfd18946/doc/fzf-vim.txt#L124"&gt;&lt;code&gt;:Rg&lt;/code&gt;&lt;/a&gt; to fuzzy search over text using &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;&lt;code&gt;ripgrep&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently, however, I have been experimenting with a plugin-free Vim setup and while &lt;a href="http://vimdoc.sourceforge.net/htmldoc/editing.html#:find"&gt;&lt;code&gt;:find&lt;/code&gt;&lt;/a&gt; is sufficient for some use-cases, I found myself quitting vim and running &lt;code&gt;fzf&lt;/code&gt; to find deeply nested files in new or large projects. &lt;/p&gt;

&lt;p&gt;There has to be a simple way to integrate a command-line program with a command-line editor, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;This setup is simple and it leverages a feature in vim called &lt;a href="http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix"&gt;quickfix&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;fzf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Format the output to make it compatible with &lt;a href="http://vimdoc.sourceforge.net/htmldoc/quickfix.html#errorformats"&gt;&lt;code&gt;errorformat&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Write the results to a temporary file&lt;/li&gt;
&lt;li&gt;Load the results into Vim's quickfix list with &lt;a href="http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:cfile"&gt;&lt;code&gt;:cfile&lt;/code&gt;&lt;/a&gt; or &lt;a href="http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:cgetfile"&gt;&lt;code&gt;:cgetfile&lt;/code&gt;&lt;/a&gt;, so that we can navigate through the results with &lt;a href="http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:cnext"&gt;&lt;code&gt;:cnext&lt;/code&gt;&lt;/a&gt;/&lt;code&gt;:cprevious&lt;/code&gt; or &lt;a href="http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:copen"&gt;&lt;code&gt;:copen&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Clean up temporary file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is what the final vimscript looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; FZF&lt;span class="p"&gt;()&lt;/span&gt; abort
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tempname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;" fzf | awk '{ print $1":1:0" }' &amp;gt; file&lt;/span&gt;
    &lt;span class="nb"&gt;execute&lt;/span&gt; &lt;span class="s1"&gt;'silent !fzf --multi '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'| awk ''{ print $1":1:0" }'' &amp;gt; '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;fnameescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="nb"&gt;execute&lt;/span&gt; &lt;span class="s1"&gt;'cfile '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt;
        &lt;span class="k"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;endtry&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;

&lt;span class="c"&gt;" :Files&lt;/span&gt;
command&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;nargs&lt;span class="p"&gt;=&lt;/span&gt;* Files &lt;span class="k"&gt;call&lt;/span&gt; FZF&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;" \ff&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;leader&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;ff&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;Files&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;let l:tempname = tempname()&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Generate a path to a temporary file and store it in a variable.&lt;/li&gt;
&lt;li&gt;See &lt;a href="http://vimdoc.sourceforge.net/htmldoc/eval.html#tempname()"&gt;&lt;code&gt;:h tempname()&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;execute 'silent !fzf --multi ' . '| awk ''{ print $1":1:0" }'' &amp;gt; ' . fnameescape(l:tempname)&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Call &lt;code&gt;fzf&lt;/code&gt; with &lt;code&gt;--multi&lt;/code&gt; to allow for selecting multiple files&lt;/li&gt;
&lt;li&gt;Pipe to &lt;code&gt;awk&lt;/code&gt; to append &lt;code&gt;:1:0&lt;/code&gt; to fzf results to make them &lt;code&gt;errorformat&lt;/code&gt;-compatible.&lt;/li&gt;
&lt;li&gt;Note: you can drop this &lt;code&gt;awk&lt;/code&gt; command if you &lt;code&gt;set errorformat+=%f&lt;/code&gt; in your vimrc, but I found &lt;code&gt;%f&lt;/code&gt; to capture a lot of false-positives from other programs' outputs and therefore &lt;code&gt;:cnext&lt;/code&gt;/&lt;code&gt;:cprevious&lt;/code&gt; don't function on these false-positive results.&lt;/li&gt;
&lt;li&gt;Finally, direct the results into the temp file&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;execute 'cfile ' . l:tempname&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Load results from temp file into quickfix list and jump to the 1st result.&lt;/li&gt;
&lt;li&gt;Note #1: you may use &lt;code&gt;:cgetfile&lt;/code&gt; to only load results into quickfix list without jumping to the 1st result.&lt;/li&gt;
&lt;li&gt;Note #2: you may replace &lt;code&gt;:cfile&lt;/code&gt;/&lt;code&gt;:cgetfile&lt;/code&gt; with &lt;code&gt;:lfile&lt;/code&gt;/&lt;code&gt;:lgetfile&lt;/code&gt; to use location list instead of quickfix list. Location lists are window-specific, whereas quickfix lists are global. So if you prefer to have different set of results per vim window, then use &lt;code&gt;:lfile&lt;/code&gt;/&lt;code&gt;:lgetfile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call delete(l:tempname)&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Clean up by deleting the temp file&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command! -nargs=* Files call FZF()&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Invoke &lt;code&gt;FZF()&lt;/code&gt; function when we call &lt;code&gt;:Files&lt;/code&gt; in vim&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nnoremap &amp;lt;leader&amp;gt;ff :Files&amp;lt;cr&amp;gt;&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Normal-mode mapping so that we can trigger this flow with &lt;code&gt;&amp;lt;leader&amp;gt;ff&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;:set grepprg=rg\ --vimgrep&lt;/code&gt; is again sufficient for most of my use-cases, those who have used &lt;code&gt;:Rg&lt;/code&gt; in fzf.vim will appreciate the interactive fuzzy grepping experience and the ability to preview results before opening files in vim.&lt;/p&gt;

&lt;p&gt;Well, here is a similar experience with pure vim (&lt;em&gt;obviously, &lt;code&gt;fzf&lt;/code&gt; and &lt;code&gt;rg&lt;/code&gt; binaries are still required&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; RG&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; abort
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tempname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;pattern &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a:args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;pattern &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;a:args&lt;/span&gt;
    &lt;span class="k"&gt;endif&lt;/span&gt;
    &lt;span class="c"&gt;" rg --vimgrep &amp;lt;pattern&amp;gt; | fzf -m &amp;gt; file&lt;/span&gt;
    &lt;span class="nb"&gt;execute&lt;/span&gt; &lt;span class="s1"&gt;'silent !rg --vimgrep '''&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;pattern &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;''' | fzf -m &amp;gt; '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;fnameescape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="nb"&gt;execute&lt;/span&gt; &lt;span class="s1"&gt;'cfile '&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt;
        &lt;span class="k"&gt;redraw&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;tempname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;endtry&lt;/span&gt;
&lt;span class="k"&gt;endfunction&lt;/span&gt;

&lt;span class="c"&gt;" :Rg [pattern]&lt;/span&gt;
command&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;nargs&lt;span class="p"&gt;=&lt;/span&gt;* Rg &lt;span class="k"&gt;call&lt;/span&gt; RG&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;q&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="k"&gt;args&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;

&lt;span class="c"&gt;" \fs&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;leader&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;fs&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;Rg&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;cr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This offers the same experience where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;:Rg&lt;/code&gt; without arguments will load all text into vim and allow users to interactively type and preview results before selecting files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:Rg [pattern]&lt;/code&gt; will pre-filter results to just ones that match &lt;code&gt;[pattern]&lt;/code&gt; before passing them to &lt;code&gt;fzf&lt;/code&gt; for further fuzzy searching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Caveat
&lt;/h2&gt;

&lt;p&gt;In my testing, I found one major caveat that did not impact me too much, but it is still worth calling out here:&lt;/p&gt;

&lt;p&gt;Executing shell commands in vim with bangs, like &lt;code&gt;:!fzf&lt;/code&gt;, is not meant to be interactive (at least, not out of the box). This could be a problem in GVim/MacVim. The vim &lt;a href="http://vimdoc.sourceforge.net/htmldoc/various.html#:!"&gt;docs&lt;/a&gt; mention the following workaround:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On Unix the command normally runs in a non-interactive shell.  If you want an interactive shell to be used (to use aliases) set 'shellcmdflag' to "-ic".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Setting &lt;code&gt;shellcmdflag=-ic&lt;/code&gt; could incur a time penalty, depending on your shell startup/initialization times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Vim is extremely versatile and customizable. &lt;/p&gt;

&lt;p&gt;With some knowledge of vim concepts (e.g. quickfix, &lt;code&gt;:cfile&lt;/code&gt;/&lt;code&gt;:lfile&lt;/code&gt;) and a little bit of (vim &amp;amp; bash) scripting, you can achieve a richer experience and pleasant integrations to enhance your productivity in a way that suits you and your workflow.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this post and I hope it inspires you to develop and share your productivity tips and tricks in vim.&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>vim</category>
      <category>terminal</category>
      <category>fzf</category>
    </item>
    <item>
      <title>Search For GIFs Without Leaving Your Terminal</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Thu, 31 Dec 2020 20:48:55 +0000</pubDate>
      <link>https://forem.com/pbnj/search-for-gifs-without-leaving-your-terminal-2kha</link>
      <guid>https://forem.com/pbnj/search-for-gifs-without-leaving-your-terminal-2kha</guid>
      <description>&lt;p&gt;&lt;a href="https://i.giphy.com/media/Lny6Rw04nsOOc/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/Lny6Rw04nsOOc/giphy.gif" alt="terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Have you ever wanted to search for GIFs from the terminal?&lt;/p&gt;

&lt;p&gt;Perhaps it's just me, but I do a lot in the terminal, from writing code and debugging programs to searching and browsing the web (via &lt;a href="https://github.com/jarun/ddgr"&gt;&lt;code&gt;ddgr&lt;/code&gt;&lt;/a&gt; + &lt;a href="https://duckduckgo.com/bang?q="&gt;!bangs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;When I am reviewing Pull Requests (via &lt;a href="https://github.com/cli/cli"&gt;&lt;code&gt;gh&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/profclems/glab"&gt;&lt;code&gt;glab&lt;/code&gt;&lt;/a&gt;), I often like to post a funny GIF as a comment when I'm approving the PR (hopefully to leave the collaborator with a smile).&lt;/p&gt;

&lt;p&gt;Initially, I did this by going to a site, like &lt;a href="https://giphy.com"&gt;giphy.com&lt;/a&gt;, then searching for a GIF, then several clicks later, I got a GIF URL that I can copy/paste into the terminal. This obviously broke my workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;The second iteration of this used &lt;code&gt;ddgr&lt;/code&gt;, a command-line interface for DuckDuckGo. Using the &lt;code&gt;!gif&lt;/code&gt; or &lt;code&gt;!giphy&lt;/code&gt; &lt;a href="https://duckduckgo.com/bang?"&gt;bang&lt;/a&gt; (e.g. &lt;code&gt;ddgr --no-prompt '!gif yay'&lt;/code&gt;) did save me a few steps as it takes me right to Giphy's search results where I can get a GIF URL with a few clicks. However, because this still requires a browser, it doesn't work for all the times I work on remote development servers (e.g. &lt;a href="https://cloud.google.com/free"&gt;GCP Always Free Compute Engine&lt;/a&gt;, Raspberry Pi ...etc).&lt;/p&gt;

&lt;p&gt;I needed a different solution that was completely in the terminal.&lt;/p&gt;

&lt;p&gt;I couldn't find a dependency-free&lt;sup&gt;*&lt;/sup&gt; CLI utility that really suit my needs. So, I decided to write a dependency-free CLI utility that allows you to search for GIFs from giphy.com and return a URL.&lt;/p&gt;

&lt;p&gt;And it fits in &lt;a href="https://twitter.com/petermbenjamin/status/1344680036167688192?s=20"&gt;a tweet&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;giphy&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;1 &lt;span class="o"&gt;||&lt;/span&gt; curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://giphy.com/search/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; | rg &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s2"&gt;"(?&amp;lt;=gifs: ).*?(?=,&lt;/span&gt;&lt;span class="nv"&gt;$)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="s2"&gt;".[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="nv"&gt;$RANDOM&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt; &lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] | .images.original.url"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[ -z "${1}" ] &amp;amp;&amp;amp; return 1 || ...&lt;/code&gt; : this is kind of a ternary operation in Bash. If the 1st argument is empty, then return non-zero exit code, otherwise ...&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;curl -fsSL https://giphy.com/search/${1}&lt;/code&gt;: this is pretty straight-forward. We use &lt;code&gt;curl&lt;/code&gt; to query the site.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rg -oP "(?&amp;lt;=gifs: ).*?(?=,$)"&lt;/code&gt;: this uses Positive Lookbehind (&lt;code&gt;?&amp;lt;=...&lt;/code&gt;) and Positive Lookahead (&lt;code&gt;?=...&lt;/code&gt;) Regular Expressions (see &lt;a href="https://caspar.bgsu.edu/~courses/Stats/Labs/Handouts/grepadvanced.htm"&gt;this&lt;/a&gt; for more info) to match the text that is between &lt;code&gt;gifs:&lt;/code&gt; and &lt;code&gt;,&lt;/code&gt; at the end of the same line in the HTML response. This is where we have a JSON list of 25 GIFs from the 1st results page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jq ".[$(echo $(( $RANDOM % 25 )) )] | .images.original.url"&lt;/code&gt;: here we use &lt;code&gt;jq ".[] | .images.original.url"&lt;/code&gt; to parse the JSON list and get at the URL we need. The &lt;code&gt;$(echo $(( $RANDOM % 25 )) )&lt;/code&gt; is a Bash way of picking a random number between 0 and 25. Another way we could do this is &lt;code&gt;shuf -i 0-25 -n 1&lt;/code&gt; (see &lt;a href="https://manpage.me/index.cgi?apropos=0&amp;amp;q=shuf&amp;amp;sektion=0&amp;amp;manpath=CentOS+7.1&amp;amp;arch=default&amp;amp;format=html"&gt;&lt;code&gt;shuf&lt;/code&gt;&lt;/a&gt; manual pages for more info).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tr -d '"'&lt;/code&gt;: finally, we trim the double-quotes from the URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Now, we can compose this utility with other tools to spread smiles, like &lt;code&gt;$ gh pr review --approve --comment -b "![yay]($(giphy yay))"&lt;/code&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is why I love the terminal. It gives me the freedom and flexibility to creatively solve a problem and re-use it in composable ways. Check out this &lt;a href="https://www.expressionsofchange.org/reification-of-interaction/"&gt;blog post&lt;/a&gt; for additional reading on the merits of CLIs.&lt;/p&gt;

&lt;p&gt;I am, by no means, a Bash or Regular Expressions expert and I am sure there are many opportunities for improvement here. So, feel free to leave your ideas, thoughts, suggestions for improvements, or GIFs in the comments section below!&lt;/p&gt;

&lt;p&gt;Also, contributions are welcome on the GitHub repo: &lt;a href="https://github.com/pbnj/giphy-cli"&gt;https://github.com/pbnj/giphy-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/xT9IgG50Fb7Mi0prBC/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/xT9IgG50Fb7Mi0prBC/giphy.gif" alt="wave"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt; My Bash one-liner is not 100% dependency-free. It requires &lt;code&gt;curl&lt;/code&gt;, &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;&lt;code&gt;rg&lt;/code&gt;&lt;/a&gt; for a consistent grep utility between macOS &amp;amp; Linux, &amp;amp; &lt;a href="https://stedolan.github.io/jq/"&gt;&lt;code&gt;jq&lt;/code&gt;&lt;/a&gt; for command-line JSON parsing. Most GIF CLI utilities I found required system dependencies and entire development toolchains (e.g. &lt;code&gt;ffmpeg&lt;/code&gt;, &lt;code&gt;python3&lt;/code&gt;, &lt;code&gt;dotnet&lt;/code&gt;, ...etc).&lt;/p&gt;

</description>
      <category>bash</category>
      <category>cli</category>
      <category>gif</category>
      <category>linux</category>
    </item>
    <item>
      <title>Unit Test Your Configuration Files</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Thu, 20 Feb 2020 10:32:59 +0000</pubDate>
      <link>https://forem.com/pbnj/unit-test-your-configuration-files-3mnf</link>
      <guid>https://forem.com/pbnj/unit-test-your-configuration-files-3mnf</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dy-ACYW2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images.unsplash.com/photo-1516992654410-9309d4587e94%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D2700%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dy-ACYW2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://images.unsplash.com/photo-1516992654410-9309d4587e94%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D2700%26q%3D80" alt="pile of paper garbage" width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Infrastructure as Code. Photo courtesy: &lt;a href="https://unsplash.com/@mediavormgever"&gt;@Bass Emmen&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://pbnj.dev"&gt;https://pbnj.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;





&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;
Getting Started

&lt;ul&gt;
&lt;li&gt;Dockerfile&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Next Steps&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The era of Infrastructure-as-Code (IaC) has unlocked tremendous developer&lt;br&gt;
productivity and agility features. Now, as an Engineer, we can declare our&lt;br&gt;
infrastructure and environments as structured data in configuration files, such&lt;br&gt;
as Terraform templates, Dockerfiles, and Kubernetes manifests.&lt;/p&gt;

&lt;p&gt;However, this agility and speed of provisioning and configuring infrastructure&lt;br&gt;
comes with a high risk of bugs in the form of misconfigurations.&lt;/p&gt;

&lt;p&gt;Fortunately, we can solve this problem just as we can solve for other bugs in&lt;br&gt;
our products, by writing &lt;strong&gt;unit tests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One such tool that can help us unit test our configuration files is&lt;br&gt;
&lt;a href="https://github.com/instrumenta/conftest"&gt;&lt;code&gt;conftest&lt;/code&gt;&lt;/a&gt;. What is unique about&lt;br&gt;
&lt;code&gt;conftest&lt;/code&gt; is that it uses&lt;br&gt;
&lt;a href="https://github.com/open-policy-agent/opa"&gt;Open-Policy-Agent&lt;/a&gt; (OPA) and a policy&lt;br&gt;
language, called&lt;br&gt;
&lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/"&gt;Rego&lt;/a&gt; to&lt;br&gt;
accomplish this.&lt;/p&gt;

&lt;p&gt;This might appear difficult at first, but it will start to make sense.&lt;/p&gt;

&lt;p&gt;Let's explore 2 use-cases where we can test our configurations!&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;First, some prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/instrumenta/conftest"&gt;&lt;code&gt;conftest&lt;/code&gt;&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;brew install instrumenta/instrumenta/conftest&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;(Optional)&lt;/strong&gt; &lt;a href="https://github.com/open-policy-agent/opa"&gt;&lt;code&gt;opa&lt;/code&gt;&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;brew install opa&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;Let's say we want to prevent some images and/or tags (e.g. &lt;code&gt;latest&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We need to create a simple Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; kalilinux/kali-linux-docker:latest&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["echo"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we need to create our first unit test file, let's call it &lt;code&gt;test.rego&lt;/code&gt;, and&lt;br&gt;
place it in a directory, let's call it &lt;code&gt;policy&lt;/code&gt; (this is configurable).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;&lt;span class="ow"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="n"&gt;disallowed_tags&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;disallowed_images&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"kalilinux/kali-linux-docker"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"from"&lt;/span&gt;
        &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;
        &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disallowed_tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[%s] tag is not allowed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"from"&lt;/span&gt;
        &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disallowed_images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[%s] image is not allowed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assuming we are in the right directory, we can test our Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls
&lt;/span&gt;Dockerfile      policy/

&lt;span class="nv"&gt;$ &lt;/span&gt;conftest &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; Dockerfile ./Dockerfile
FAIL - ./Dockerfile - &lt;span class="o"&gt;[&lt;/span&gt;latest] tag is not allowed
FAIL - ./Dockerfile - &lt;span class="o"&gt;[&lt;/span&gt;kalilinux/kali-linux-docker] image is not allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just to be sure, let's change this Dockerfile to pass the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# FROM kalilinux/kali-linux-docker:latest&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; debian:buster&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["echo"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls
&lt;/span&gt;Dockerfile      policy/

&lt;span class="nv"&gt;$ &lt;/span&gt;conftest &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; Dockerfile ./Dockerfile
PASS - ./Dockerfile - data.main.deny
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"It works! But I don't understand how," I hear you thinking to yourself.&lt;/p&gt;

&lt;p&gt;Let's break the Rego syntax down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package main&lt;/code&gt; is a way for us to put some rules that belong together in a
namespace. In this case, we named it &lt;code&gt;main&lt;/code&gt; because &lt;code&gt;conftest&lt;/code&gt; defaults to it,
but we can easily do something like &lt;code&gt;package docker&lt;/code&gt; and then run
&lt;code&gt;conftest test -i Dockerfile --namespace docker ./Dockerfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disallowed_tags&lt;/code&gt; &amp;amp; &lt;code&gt;disallowed_images&lt;/code&gt; are just simple variables that hold an
array of strings&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deny[msg] { ... }&lt;/code&gt; is the start of the deny rule and it means that the
Dockerfile should be rejected and the user should be given an error message
&lt;code&gt;msg&lt;/code&gt; if the conditions in the body (i.e. &lt;code&gt;{ ... }&lt;/code&gt;) are true&lt;/li&gt;
&lt;li&gt;Expressions in the body of the deny rule are treated as logical AND. For
example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rego"&gt;&lt;code&gt;  &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;                    &lt;span class="c1"&gt;# IF 1 is equal to 1&lt;/span&gt;
  &lt;span class="n"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foobar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# AND "foobar" contains "foo"&lt;/span&gt;
                            &lt;span class="c1"&gt;# This would trigger the deny rule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;input[i].Cmd == "from"&lt;/code&gt; checks if the Docker command is &lt;code&gt;FROM&lt;/code&gt;. &lt;code&gt;input[i]&lt;/code&gt;
means we can have multiple Dockerfiles being tested at once. This will iterate
over them&lt;/li&gt;
&lt;li&gt;The next 2 lines are assignments just to split a string and store some data in
variables&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contains(tag, disallowed_tags[_])&lt;/code&gt; will return true if the &lt;code&gt;tag&lt;/code&gt; we obtained
from the Dockerfile contains one of the &lt;code&gt;disallowed_tags&lt;/code&gt;. &lt;code&gt;array[_]&lt;/code&gt; syntax
means iterate over values&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;msg := sprinf(...)&lt;/code&gt; creates the message we want to tell our user if this deny
rule is triggered&lt;/li&gt;
&lt;li&gt;The second &lt;code&gt;deny[msg]&lt;/code&gt; rule checks that the image itself is not on the
blocklist.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kubernetes
&lt;/h3&gt;

&lt;p&gt;Let's say we want to ensure that all pods are running as a non-root user.&lt;/p&gt;

&lt;p&gt;We need to create our deployment&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; kubernetes
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt;./kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we need to create our unit test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./kubernetes/policy
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt;./kubernetes/policy/test.rego
package main

name := input.metadata.name

deny[msg] {
  input.kind == "Deployment"
  not input.spec.template.spec.securityContext.runAsNonRoot

  msg = sprintf("Containers must run as non root in Deployment %s. See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", [name])
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, let's run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;conftest &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; yaml ./kubernetes/deployment.yaml
FAIL - ./kubernetes/deployment.yaml - Containers must run as non root &lt;span class="k"&gt;in &lt;/span&gt;Deployment nginx-deployment. See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a bit more straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the &lt;code&gt;metadata.name&lt;/code&gt; from the &lt;code&gt;input&lt;/code&gt; (which is the Kubernetes Deployment
yaml file)&lt;/li&gt;
&lt;li&gt;Create a deny rule that is triggered if:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;input.kind&lt;/code&gt; is &lt;code&gt;Deployment&lt;/code&gt; and&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;securityContext.runAsNonRoot&lt;/code&gt; is not set&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Return an error message to the user that containers must run as non-root and
point them to the docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;So, where to go from here?&lt;/p&gt;

&lt;p&gt;The Rego language is vast and it can take a bit to wrap your head around how it&lt;br&gt;
works. You can even send and receive HTTP requests inside Rego.&lt;/p&gt;

&lt;p&gt;I recommend reading the docs to learn more about Rego's capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.openpolicyagent.org/docs/latest/policy-reference/"&gt;Reference sheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openpolicyagent.org/docs/latest/policy-cheatsheet/"&gt;Cheat sheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also barely scratched the surface with &lt;code&gt;conftest&lt;/code&gt; in this blog post. The&lt;br&gt;
repository has a nice list of&lt;br&gt;
&lt;a href="https://github.com/instrumenta/conftest#examples"&gt;examples&lt;/a&gt; that you should&lt;br&gt;
peruse at your leisure. &lt;code&gt;conftest&lt;/code&gt; even supports sharing policies via uploading&lt;br&gt;
OPA bundles to OCI-compliant registries, e.g. &lt;code&gt;conftest push ...&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;conftest pull ...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, if you have any questions, the OPA community is friendly and welcoming.&lt;br&gt;
Feel free to join the &lt;code&gt;#conftest&lt;/code&gt; channel in&lt;br&gt;
&lt;a href="https://slack.openpolicyagent.org/"&gt;OPA Slack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>openpolicyagent</category>
      <category>conftest</category>
      <category>unittest</category>
      <category>devops</category>
    </item>
    <item>
      <title>Effective Security Programs</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Wed, 01 Jan 2020 08:36:33 +0000</pubDate>
      <link>https://forem.com/pbnj/effective-security-programs-2aea</link>
      <guid>https://forem.com/pbnj/effective-security-programs-2aea</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://pbnj.dev/blog/effective-security-programs.html"&gt;https://pbnj.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;
Culture

&lt;ul&gt;
&lt;li&gt;Listen to Your Users&lt;/li&gt;
&lt;li&gt;Hold Office Hours, Community Meetings, Events&lt;/li&gt;
&lt;li&gt;Create a Security Champion Program&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Engineering

&lt;ul&gt;
&lt;li&gt;Invest in Tooling, APIs, Services&lt;/li&gt;
&lt;li&gt;Contribute to Engineering&lt;/li&gt;
&lt;li&gt;Practice What You Preach&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;TLDR&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;As we approach the end of a year and a decade, I decided to write down my reflections on my experiences in the Information Security (InfoSec) field.&lt;/p&gt;

&lt;p&gt;This is also, in part, coming from someone (me) who has struggled with how security gets done at most places and nearly quit the field (on more than 1 occassion) due to so many bad (at best) and toxic (at worst) security teams and cultures out there.&lt;/p&gt;

&lt;p&gt;It all boils down to this: &lt;strong&gt;Security is a service &amp;amp; you are a service provider.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To unravel this, let's explore what effective and successfull Security programs look like from a culture and engineering perspectives.&lt;/p&gt;

&lt;p&gt;If you want the &lt;em&gt;Too-Long; Didn't Read&lt;/em&gt; version, skip to the TLDR section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culture
&lt;/h2&gt;

&lt;p&gt;Information Security is not only a technical problem, it is also a people problem, specifically, security people. Our perceptions and reputations preceed us, and they're not the good ones either.&lt;/p&gt;

&lt;p&gt;We often come off as an&lt;a href="https://securityboulevard.com/2019/12/seven-toxic-information-security-personalities/"&gt;isolated, egotistical, self-absorbed bunch with a god complex&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, how can we fix our perception and reputation?&lt;/p&gt;

&lt;h3&gt;
  
  
  Listen to Your Users
&lt;/h3&gt;

&lt;p&gt;Our job is to enable developers and operations teams to do their work securely and effectively.&lt;/p&gt;

&lt;p&gt;As such, how do we expect to help them without listening to them, without understanding their business needs, without asking them how we can help them better?&lt;/p&gt;

&lt;p&gt;Before you walk into the office, thinking you're a hero about to save the day, stop and check your ego at the door:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask your users what is their biggest problem or challenge when it comes to
security?

&lt;ul&gt;
&lt;li&gt;If the answer is knowledge, then invest in &lt;strong&gt;education&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If the answer is management, then work with project management and
leadership to demonstrate the &lt;strong&gt;business value&lt;/strong&gt; of paying down technical
debt and improving software quality (&lt;em&gt;hint: security is a subset of
quality&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;If the answer is technology, then invest in &lt;strong&gt;developer-friendly tooling&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If the answer is culture, then &lt;strong&gt;look in the mirror&lt;/strong&gt; and try to see why your users avoid you like the plague.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Ask your users for their feedback on your work. What is working well? What is not working well? How would they suggest/recommend you improve? You'll be surprised by what they say, one way or another.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hold Office Hours, Community Meetings, Events
&lt;/h3&gt;

&lt;p&gt;Most engineers love to learn new topics and concepts, so they could share with their teams or improve their work.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hold weekly, bi-weekly, or monthly lunch-and-learn sessions or office hours to dive into a topic of their choosing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put on a quarterly or bi-annually Capture-The-Flag (CTF) competition or a hack-a-thon around enabling, improving, or automating security engineering (who knows, you might even find a few engineers with a knack or a passion for security who might want to join your team?).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This doubles as a means to build or improve relationships with your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Security Champion Program
&lt;/h3&gt;

&lt;p&gt;Not only is security a niche field, it is also a thankless job. Yet, I have always noticed 1 or 2 engineers on every product development team that always go above and beyond their work to improve security in their projects, however small of an improvement it is.&lt;/p&gt;

&lt;p&gt;Create a &lt;a href="https://www.owasp.org/index.php/Security_Champions"&gt;Security Champion&lt;/a&gt; program to designate engineers as primariy security points of contact, to recognize them for their work, and to form a support group of sorts for engineers who have to carry this mantle. Give them the space to share their challenges and their learnings with each other and with you. It's not easy being under constant pressure from project management to deliver on customers' priorities within tight timelines and do extra-curricular activities.&lt;/p&gt;

&lt;p&gt;This Security Champion program also doubles as a bi-directional channel of communication between Security and Product Development teams that reinforces the first point I made, i.e. &lt;strong&gt;listening to your users&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering
&lt;/h2&gt;

&lt;p&gt;As mentioned before, product developers are under project management pressures to deliver within tight timelines and deadlines. If all we are doing is piling on, we are failing.&lt;/p&gt;

&lt;p&gt;Engineers, on product development teams or operations teams, are often closest to the product than InfoSec teams are. They likely know more about potential security concerns or vulnerabilities in their products than we do.&lt;/p&gt;

&lt;p&gt;You giving them a security report generated from a vulnerability scanner is the least helpful thing you can do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invest in Tooling, APIs, Services
&lt;/h3&gt;

&lt;p&gt;It is our job to enable our users to do their work securely and effectively.&lt;/p&gt;

&lt;p&gt;This means we should be making their lives easier, not harder.&lt;/p&gt;

&lt;p&gt;There is absolutely no reason we should be requiring our users to fill out forms and submit tickets to get their work done. Instead, we should be enabling them to be self-sufficient.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you require a threat model, provide tooling to automate generating threat models. If no tooling exists, or if existing tooling is insufficient, build the right tooling for your business.&lt;/li&gt;
&lt;li&gt;If you require security standards or benchmarks to be followed, provide tooling to automate the scanning or analysis of possible misconfigurations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are some folks who are much smarter than I on the subject:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/ryanhuber/status/1204492600541171717"&gt;Ryan Huber&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/dinodaizovi/status/1204758128253898753"&gt;Dino A. Dai Zovi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contribute to Engineering
&lt;/h3&gt;

&lt;p&gt;Remember that security report generated from a vulnerability scanner?&lt;/p&gt;

&lt;p&gt;Here are some crazy ideas, what if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate the finding(s), weed out the false-positives, measure and assess the risk of each finding against the realities of the product and business needs, then work with the respective team(s) to remediate the issue, or even contribute the fix yourself?&lt;/li&gt;
&lt;li&gt;Contribute security unit tests to prevent that bug from recurring?&lt;/li&gt;
&lt;li&gt;Offer to peform security code reviews if engineers are working on sensitive/critical parts of the system (e.g. user input, certificate management)?&lt;/li&gt;
&lt;li&gt;Contribute additional security checks in CI pipelines?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, most of these tasks would require you to operate closely with your users... As if you're actually one of them; an engineer on their team, but with a security background.&lt;/p&gt;

&lt;p&gt;Now, you finally understand what &lt;strong&gt;DevSecOps&lt;/strong&gt; and &lt;strong&gt;Shift Left&lt;/strong&gt; truly entail!&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice What You Preach
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If you require threat models for every service, then start with your own.&lt;/li&gt;
&lt;li&gt;If you require security bugs to be remediated within an SLA, then start with your own bugs.&lt;/li&gt;
&lt;li&gt;If you don't or you can't, then you are in no position to tell engineers to do that work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plain and simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;The short version of my rambling is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security is a Service. You are a Service Provider.&lt;/li&gt;
&lt;li&gt;Fix your culture

&lt;ul&gt;
&lt;li&gt;Listen to your users. Ask them what they need. Ask them for their feedback on your work.&lt;/li&gt;
&lt;li&gt;Hold office hours, community meetings, events to build/improve relationships with your users.&lt;/li&gt;
&lt;li&gt;Create a Security Champion program to designate engineers as security points of contact, to recognize their efforts, and to allow them to share knowledge with each other and with the security team (going back to listening to your users).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Enable engineers to do their work securely and effectively

&lt;ul&gt;
&lt;li&gt;Automate manual processes. Don't introduce friction.&lt;/li&gt;
&lt;li&gt;Contribute directly to engineering teams.&lt;/li&gt;
&lt;li&gt;Practice what you preach.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>devrel</category>
    </item>
    <item>
      <title>Demystifying STRIDE Threat Models</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Sat, 08 Dec 2018 08:40:38 +0000</pubDate>
      <link>https://forem.com/pbnj/demystifying-stride-threat-models-230m</link>
      <guid>https://forem.com/pbnj/demystifying-stride-threat-models-230m</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;What is a Threat Model?&lt;/li&gt;
&lt;li&gt;
What is STRIDE?

&lt;ul&gt;
&lt;li&gt;Spoofing&lt;/li&gt;
&lt;li&gt;Tampering&lt;/li&gt;
&lt;li&gt;Repudiation&lt;/li&gt;
&lt;li&gt;Information Disclosure&lt;/li&gt;
&lt;li&gt;Denial of Service&lt;/li&gt;
&lt;li&gt;Elevation of Privileges&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Summary&lt;/li&gt;

&lt;li&gt;Additional Resources&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Software is eating the world. As a result, the repercussions of software failure is costly and, at times, can be catastrophic. This can be seen today in a wide variety of incidents, from data leak incidents caused by misconfigured &lt;a href="https://github.com/petermbenjamin/yas3bl" rel="noopener noreferrer"&gt;AWS S3 buckets&lt;/a&gt; to Facebook data breach incidents due to &lt;a href="https://techcrunch.com/2018/06/28/facepalm-2/" rel="noopener noreferrer"&gt;lax API limitations&lt;/a&gt; to the Equifax incident due to the use of &lt;a href="https://www.synopsys.com/blogs/software-security/equifax-apache-struts-cve-2017-5638-vulnerability/" rel="noopener noreferrer"&gt;an old Apache Struts version with a known critical vulnerability&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Application Security advocates encourage developers and engineers to adopt security practices as early in the Software Development Life Cycle (SDLC) as possible &lt;sup&gt;1&lt;/sup&gt;. One such security practice is &lt;strong&gt;Threat Modeling&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I offer a high-level introduction to one methodology, called STRIDE, and in a future article, I will demonstrate this process using an existing open-source application as an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Threat Model?
&lt;/h2&gt;

&lt;p&gt;Here is the obligatory &lt;a href="https://en.wikipedia.org/wiki/Threat_model" rel="noopener noreferrer"&gt;Wikipedia definition&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Threat modeling is a process by which potential threats, such as structural vulnerabilities, can be identified, enumerated, and prioritized – all from a hypothetical attacker’s point of view. The purpose of threat modeling is to provide defenders with a systematic analysis of the probable attacker’s profile, the most likely attack vectors, and the assets most desired by an attacker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With that out of the way, the simplest explanation in English is this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Threat Models are a systematic and structured way to identify and mitigate security risks in our software&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;There are various ways and methodologies of doing threat models, one of which is a process popularized by Microsoft, called &lt;em&gt;STRIDE&lt;/em&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is STRIDE?
&lt;/h2&gt;

&lt;p&gt;STRIDE is an acronym that stands for 6 categories of security risks: Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privileges.&lt;/p&gt;

&lt;p&gt;Each category of risk aims to address one aspect of security.&lt;/p&gt;

&lt;p&gt;Let's dive into each of these categories.&lt;/p&gt;




&lt;h3&gt;
  
  
  Spoofing
&lt;/h3&gt;

&lt;p&gt;Spoofing refers to the act of posing as someone else (i.e. spoofing a user) or claiming a false identity (i.e. spoofing a process).&lt;/p&gt;

&lt;p&gt;This category is concerned with &lt;strong&gt;authenticity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One user spoofs the identify of another user by brute-forcing username/password credentials. &lt;/li&gt;
&lt;li&gt;A malicious, phishing host is set up in an attempt to trick users into divulging their credentials. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You would typically mitigate these risks with proper &lt;a href="https://www.owasp.org/index.php/Authentication_Cheat_Sheet" rel="noopener noreferrer"&gt;authentication&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tampering
&lt;/h3&gt;

&lt;p&gt;Tampering refers to malicious modification of data or processes. Tampering may occur on data in transit, on data at rest, or on processes.&lt;/p&gt;

&lt;p&gt;This category is concerned with &lt;strong&gt;integrity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user performs &lt;a href="https://en.wikipedia.org/wiki/Bit-flipping_attack" rel="noopener noreferrer"&gt;bit-flipping attacks&lt;/a&gt; on data in transit.&lt;/li&gt;
&lt;li&gt;A user modifies data at rest/on disk.&lt;/li&gt;
&lt;li&gt;A user performs &lt;a href="https://en.wikipedia.org/wiki/Code_injection" rel="noopener noreferrer"&gt;injection attacks&lt;/a&gt; on the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You would typically mitigate these risks with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proper validation of users' inputs and proper encoding of outputs.&lt;/li&gt;
&lt;li&gt;Use prepared SQL statements or stored procedures to mitigate SQL injections.&lt;/li&gt;
&lt;li&gt;Integrate with security static code analysis tools to identify security bugs.&lt;/li&gt;
&lt;li&gt;Integrate with composition analysis tools (e.g. &lt;code&gt;snyk&lt;/code&gt;, &lt;code&gt;npm audit&lt;/code&gt;, BlackDuck ...etc) to identify 3rd party libraries/dependencies with known security vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Repudiation
&lt;/h3&gt;

&lt;p&gt;Repudiation refers to the ability of denying that an action or an event has occurred.&lt;/p&gt;

&lt;p&gt;This category is concerned with &lt;strong&gt;non-repudiation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user denies performing a destructive action (e.g. deleting all records from a database).&lt;/li&gt;
&lt;li&gt;Attackers commonly erase or truncate log files as a technique for hiding their tracks.&lt;/li&gt;
&lt;li&gt;Administrators unable to determine if a container has started to behave suspiciously/erratically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You would typically mitigate these risks with proper &lt;a href="https://www.computerweekly.com/tip/Best-practices-for-audit-log-review-for-IT-security-investigations" rel="noopener noreferrer"&gt;audit logging&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Information Disclosure
&lt;/h3&gt;

&lt;p&gt;Information Disclosure refers to data leaks or data breaches. This could occur on data in transit, data at rest, or even to a process.&lt;/p&gt;

&lt;p&gt;This category is concerned with &lt;strong&gt;confidentiality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user is able to eavesdrop, sniff, or read traffic in clear-text.&lt;/li&gt;
&lt;li&gt;A user is able to read data on disk in clear-text.&lt;/li&gt;
&lt;li&gt;A user attacks an application protected by TLS but is able to steal x.509 (SSL/TLS certificate) decryption keys and other sensitive information. &lt;a href="https://en.wikipedia.org/wiki/Heartbleed" rel="noopener noreferrer"&gt;Yes, this happened&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A user is able to read sensitive data in a database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You would typically mitigate these risks by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing proper &lt;a href="https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet" rel="noopener noreferrer"&gt;encryption&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Avoiding self-signed certificates. Use a valid, trusted Certificate Authority (CA).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Denial of Service
&lt;/h3&gt;

&lt;p&gt;Denial of Service refers to causing a service or a network resource to be unavailable to its intended users.&lt;/p&gt;

&lt;p&gt;This category is concerned with &lt;strong&gt;availability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user performs &lt;a href="https://en.wikipedia.org/wiki/SYN_flood" rel="noopener noreferrer"&gt;SYN flood&lt;/a&gt; attack.&lt;/li&gt;
&lt;li&gt;The storage (i.e. disk, drive) becomes too full.&lt;/li&gt;
&lt;li&gt;A Kubernetes dashboard is left exposed on the Internet, allowing anyone to deploy containers on your company's infrastructure to mine cryptocurrency and starve your legitimate applications of CPU. &lt;a href="https://redlock.io/blog/cryptojacking-tesla" rel="noopener noreferrer"&gt;Yes, that happened too&lt;/a&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mitigating this class of security risks is tricky because solutions are highly dependent on a lot of factors. &lt;/p&gt;

&lt;p&gt;For the Kubernetes example, you would mitigate resource consumption with &lt;a href="https://kubernetes.io/docs/concepts/policy/resource-quotas/" rel="noopener noreferrer"&gt;resource quotas&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a storage example, you would mitigate this with proper log rotation and monitoring/alerting when disk is nearing capacity.&lt;/p&gt;




&lt;h3&gt;
  
  
  Elevation of Privileges
&lt;/h3&gt;

&lt;p&gt;Elevation of Privileges refers to gaining access that one should not have.&lt;/p&gt;

&lt;p&gt;This category is concerned with &lt;strong&gt;authorization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user takes advantage of a Buffer Overflow to gain root-level privileges on a system.&lt;/li&gt;
&lt;li&gt;A user with limited to no permissions to Kubernetes can elevate their privileges by sending a specially crafted request to a container with the Kubernetes API server's TLS credentials. &lt;a href="https://github.com/kubernetes/kubernetes/issues/71411" rel="noopener noreferrer"&gt;Yes, this was possible&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mitigating these risks would require a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proper authorization mechanism (e.g. role-based access control).&lt;/li&gt;
&lt;li&gt;Security static code analysis to ensure your code has little to no security bugs.&lt;/li&gt;
&lt;li&gt;Compositional analysis (aka dependency checking/scanning), like &lt;a href="https://snyk.io" rel="noopener noreferrer"&gt;&lt;code&gt;snyk&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://docs.npmjs.com/cli/audit" rel="noopener noreferrer"&gt;&lt;code&gt;npm audit&lt;/code&gt;&lt;/a&gt;, to ensure that you're not relying on known-vulnerable 3rd party dependencies.&lt;/li&gt;
&lt;li&gt;Generally practicing least privilege principle, like running your web server as a non-root user.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;So, STRIDE is a threat model methodology that should help you systematically examine and address gaps in the security posture of your applications.&lt;/p&gt;

&lt;p&gt;In a future article, we'll take an application and go through this process so you can get a feel for how this works.&lt;/p&gt;

&lt;p&gt;If you would like to propose an application for me to threat model next, feel free to drop suggestions in the comments below. &lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/STRIDE_(security)" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/STRIDE_(security)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.webtrends.com/blog/2015/04/threat-modeling-with-stride/" rel="noopener noreferrer"&gt;https://www.webtrends.com/blog/2015/04/threat-modeling-with-stride/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.oreilly.com/library/view/threat-modeling-designing/9781118810057/9781118810057c03.xhtml" rel="noopener noreferrer"&gt;https://www.oreilly.com/library/view/threat-modeling-designing/9781118810057/9781118810057c03.xhtml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;1&lt;/a&gt;: &lt;a href="https://www.youtube.com/watch?v=YGJqpQy79no&amp;amp;WT.mc_id=shehackspurple-blog-tajanca" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=YGJqpQy79no&amp;amp;WT.mc_id=shehackspurple-blog-tajanca&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Updates
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add more mitigations against tampering.&lt;/li&gt;
&lt;li&gt;Add more mitigations against information disclosure.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>security</category>
      <category>learning</category>
      <category>beginners</category>
      <category>threatmodel</category>
    </item>
    <item>
      <title>What Genres Do You Listen To?</title>
      <dc:creator>Peter Benjamin (they/them)</dc:creator>
      <pubDate>Wed, 21 Nov 2018 06:11:56 +0000</pubDate>
      <link>https://forem.com/pbnj/what-genres-do-you-listen-to-2h68</link>
      <guid>https://forem.com/pbnj/what-genres-do-you-listen-to-2h68</guid>
      <description>&lt;p&gt;I enjoyed reading the following article by a fellow Dev.to'er:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/hus_hmd" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F53925%2F3882a6ff-be8f-46cf-bd7b-49e74cf5447e.jpg" alt="hus_hmd"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/hus_hmd/favourite-albums-to-listen-to-while-coding-25fb" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Favourite albums to listen to while coding&lt;/h2&gt;
      &lt;h3&gt;Hussein Al Hammad ・ Nov 18 '18&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#music&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;And I wanted to share with the community what I listen to.&lt;/p&gt;




&lt;p&gt;I have different modes for when I'm coding, hacking (e.g. bug bounties), working (e.g. writing emails), or just doing mind-numbing chores.&lt;/p&gt;

&lt;p&gt;The following is what I jam to for each of those modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coding
&lt;/h2&gt;

&lt;p&gt;No distractions. No lyrics. Soft tracks are ideal, but not too soft that I would fall asleep during late-night coding sessions. &lt;/p&gt;

&lt;p&gt;Genres that meet these criteria: &lt;strong&gt;Chill Step&lt;/strong&gt;, &lt;strong&gt;Deep House&lt;/strong&gt;, &lt;strong&gt;Liquid Trap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spotify playlists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/kent1337/playlist/6IjDl5eRczFdgZkKYXhuHZ?si=O6XpAa_BQJitIQuMmAKcgA" rel="noopener noreferrer"&gt;Chill Step&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/22d4ad36edipx6lzornmjz3ha/playlist/0OLd6eJuk2KDrbY8FC7rOz?si=iDGn--FOTdW5qyaEqvMwKw" rel="noopener noreferrer"&gt;Chill Step / Deep House&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX2TRYkJECvfC?si=nq0l1hZkSkulLXJOreN01w" rel="noopener noreferrer"&gt;Deep House Relax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX0r3x8OtiwEM?si=XzrwuO4JQXu2ZAW6aQ1sHA" rel="noopener noreferrer"&gt;Lowkey Tech&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hacking
&lt;/h2&gt;

&lt;p&gt;Get pumped and get in the mood 😈 with upbeat jams. Fast, electronic beats with sick bass drops are ideal. Lyrics are fine too.&lt;/p&gt;

&lt;p&gt;Genres that meet these criteria: &lt;strong&gt;Drum and Bass&lt;/strong&gt;, &lt;strong&gt;EDM&lt;/strong&gt;, &lt;strong&gt;Trap&lt;/strong&gt;, &lt;strong&gt;Dubstep&lt;/strong&gt;, &lt;strong&gt;Nightcore&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spotify playlists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/1228601559/playlist/6oQWvdr5iAxqzqhQdYvnP1?si=yctnoHAySDyojOSpqmhc0w" rel="noopener noreferrer"&gt;Hacking - EDM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/1129342760/playlist/4tdTQ7Ka4z9886LN96bCG1?si=dJqpK8-9SZud5mnBZXtTGQ" rel="noopener noreferrer"&gt;Drum and Bass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/sonymusicentertainment/playlist/7CEEEMKV41x0RlFCXWdvKF?si=Vx9AuTTJS1G8eSV_Ne_z8w" rel="noopener noreferrer"&gt;EDM Trap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/rsjoerts/playlist/2Dc8G0mO5JvrewxJQSSRwN?si=5bhzo3ZVThOO5D3kGfanHg" rel="noopener noreferrer"&gt;Japanese EDM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DZ06evO4d9PxK?si=vIXGp4Y2Ta6DQZCfU62g9Q" rel="noopener noreferrer"&gt;This is Nightcore&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Working
&lt;/h2&gt;

&lt;p&gt;Upbeat, poppy, but not-too-fast tracks hits the spot for this category.&lt;/p&gt;

&lt;p&gt;Genres that meet this criteria: &lt;strong&gt;ePop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spotify playlists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/1228601559/playlist/61XsDOKsMb6olcfyiC3ZMY?si=b81C_Y-wSRm4BpvKzMqLbQ" rel="noopener noreferrer"&gt;Hacking - ePop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/11130330168/playlist/2pmYGEq3BfMvtdBX42faF6?si=2vjTSuFKSyGStRVLRdqtzQ" rel="noopener noreferrer"&gt;Glitch Hop Gaming Mix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX8AliSIsGeKd?si=BOzykIe5RMyzc3Pl6_R8Pg" rel="noopener noreferrer"&gt;Electronic Rising&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX8tZsk68tuDw?si=sTK9PCrPSxyPpYNg1imgjw" rel="noopener noreferrer"&gt;Dance Rising&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/nicolasluc/playlist/0IfKZZ870Oc9t7RaTniViw?si=dgkrwu6pRnGlh1Tm9ZU5Gg" rel="noopener noreferrer"&gt;NoCopyRightSounds&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chores
&lt;/h2&gt;

&lt;p&gt;Podcasts, actually. Word? 🤓 &lt;/p&gt;

&lt;p&gt;Wait. Hear me out. &lt;br&gt;
Because of the inherent nature of chores, I don't need focus on the minutiae of the task. Instead, I take these opportunities to catch up on the many technical/coding/engineering podcasts.&lt;/p&gt;

&lt;p&gt;Genres that meet these criteria: &lt;strong&gt;Podcasts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spotify actually has podcasts you can listen to, but I will shamelessly plug my collection of &lt;a href="https://github.com/petermbenjamin/awesome-podcasts" rel="noopener noreferrer"&gt;awesome-podcasts&lt;/a&gt; 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Miscellaneous
&lt;/h2&gt;

&lt;p&gt;Every once in a while, I get a craving for something entirely new and different, but they still tend to fall within broader categories.&lt;/p&gt;

&lt;p&gt;Genres that meet these criteria: &lt;strong&gt;Synthpop&lt;/strong&gt;, &lt;strong&gt;Remixes&lt;/strong&gt;,  &lt;strong&gt;Hip Hop&lt;/strong&gt;, but with a &lt;em&gt;twist&lt;/em&gt; 👩‍🎤👨‍🎤&lt;/p&gt;

&lt;p&gt;Spotify playlists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DXcZXC7Soywtq?si=jqyHr051QhyOZNbI8VJTMw" rel="noopener noreferrer"&gt;Women Wa Bas!&lt;/a&gt; - Arab women in one playlist&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DXd43GfSFAeHA?si=PuIQzEI3TPCLntjUpLsAvg" rel="noopener noreferrer"&gt;Arab Hip-Hop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DWWkrGNlIHxPl?si=BfnvLsc0TAiIsZssuMkORw" rel="noopener noreferrer"&gt;Arab X&lt;/a&gt; - Global cross-overs with/by Arab artists&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DXdfYLmYuBPaf?si=VLjrMY_WTQuYjlnrzS9qaQ" rel="noopener noreferrer"&gt;Arab Raggae&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/1228601559/playlist/2DamE58lTRRrb2pZaqahkW?si=lfBLN0WJRgiuPCfErzEKNw" rel="noopener noreferrer"&gt;Hacking - Synthpop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/strangewaysradio/playlist/2en136LymJ1BzU2GwwtYCp?si=wG1EPFVeTPuDqZl3ETYt5A" rel="noopener noreferrer"&gt;Synthpop / Electronic / Dark Wave&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/123700021/playlist/6LACc57WQAjwvZb2fPw91t?si=4zXbBfosT96ytj56KmX7gw" rel="noopener noreferrer"&gt;Pop / Rock Remix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/1221226272/playlist/5w03d22KCKSUHKOg2oDLDU?si=_BBrgLJDSnapZzcWyNrxdg" rel="noopener noreferrer"&gt;Remixes Of Popular Songs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX9tPFwDMOaN1?si=BVqOECOhRTmsetNIV_D4bA" rel="noopener noreferrer"&gt;K-Pop Daebak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/spotify/playlist/37i9dQZF1DX4FcAKI5Nhzq?si=HKliHyHcTfmxx2YN56iVaA" rel="noopener noreferrer"&gt;K-Pop Rising&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/user/11158710263/playlist/10mkFxeRBWcwPJE3QKVRQz?si=S8Hr3T2jTpe5Z2YRpNxS7A" rel="noopener noreferrer"&gt;JPop / Anime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I love getting exposed to new music, so what do you listen to?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>discuss</category>
      <category>music</category>
    </item>
  </channel>
</rss>
