GeistHaus
log in · sign up

https://salferrarello.com/feed

rss
20 posts
Polling state
Status active
Last polled May 19, 2026 17:00 UTC
Next poll May 20, 2026 16:32 UTC
Poll interval 86400s
ETag "bfcdef294b6952c9f7328a20d8b590e9"
Last-Modified Tue, 24 Mar 2026 13:01:20 GMT

Posts

Add Element to Array if It Does Not Already Exist with jq
ComputingDev TipsProgrammingSolutionjq
This is how I use jq to conditionally add an element to an array only if it doesn't exist there already. In other words, add an element to an array only if it doesn't create a duplicate in the array. This is particularly helpful when I'm scripting a JSON transformation and want to make it idempotent.
Show full content

This is how I use jq to conditionally add an element to an array only if it doesn’t exist there already. In other words, add an element to an array only if it doesn’t create a duplicate in the array. This is particularly helpful when I’m scripting a JSON transformation and want to make it idempotent.

echo '{"values": ["a", "b"]}' | \
jq --arg newvalue 'c' \
  'if .values | index($newvalue)
    then .
    else .values += [$newvalue]
  end'
How It Works Output Object Unchanged
echo '{"values": ["a", "b"]}' | jq '.'
Add Value to Array inside Object
echo '{"values": ["a", "b"]}' | jq --arg newvalue 'c' '.values += [$newvalue]'
Duplicate

If we add a value that is already in the array, we end up having it twice.

echo '{"values": ["a", "b"]}' | jq --arg newvalue 'a' '.values += [$newvalue]'
Check If Already Exists

Conditional Check If Value Already Appears

echo '{"values": ["a", "b"]}' | jq --arg newvalue 'c' '.values | index($newvalue)'

null

echo '{"values": ["a", "b"]}' | jq --arg newvalue 'a' '.values | index($newvalue)'

0

Note: in jq the only falsy values are false and null (0 is NOT a falsy value)

echo '{"values": ["a", "b"]}' | jq --arg newvalue 'c' 'if .values | index($newvalue) then "exists" else "does not exist" end'

“does not exist”

echo '{"values": ["a", "b"]}' | jq --arg newvalue 'a' 'if .values | index($newvalue) then "exists" else "does not exist" end'

“exists”

Conditionally Add Value to Array
echo '{"values": ["a", "b"]}' | jq --arg newvalue 'a' 'if .values | index($newvalue) then . else .values += [$newvalue] end'

(unchanged)

echo '{"values": ["a", "b"]}' | jq --arg newvalue 'c' 'if .values | index($newvalue) then . else .values += [$newvalue] end'

{
  "values": [
    "a",
    "b",
    "c"
  ]
}
With Indentation
echo '{"values": ["a", "b"]}' | \
jq --arg newvalue 'c' \
  'if .values | index($newvalue)
    then .
    else .values += [$newvalue]
  end'
https://salferrarello.com/?p=10151
Extensions
Show Git Commit Message without Indentation
Dev TipsSolutionGitgit aliasgitconfig
Sometimes I want a Git commit message without the leading indentation added by "git show" (for example, if I want to copy it and paste it somewhere else). This command gives me this output.
Show full content

Sometimes I want a Git commit message without the leading indentation added by git show (for example, if I want to copy it and paste it somewhere else). This command gives me this output:

git show --no-patch --format='%B'

we can also optionally pass a reference to a Git commit (e.g. a branch, hash, or tag) just like we can with git show, e.g.

git show --no-patch --format='%B' HEAD~2
Alias

I have this command aliased to shnpfb (which is based on SHow No Patch Format B), though I typically type git shn<tab> so I can use tab completion and don’t have to think about all the letters.

You can add this alias to your global Git config file by running the following from the command line.

git config --global alias.shnpfb "show --no-patch --format='%B'"

Here is the commit where I added shnpfb to my Git config.

How it Works

The --no-patch flag excludes the actual code changes (a.k.a. the “patch”) from the output of git show

The --format flag for git show takes the same values as the Pretty Formats for Git Log, specifically we’re using %B which is

raw body (unwrapped subject and body)

Copying to the Clipboard on a Mac

On my Mac, I use the pbcopy command to copy the commit message to the clipboard.

e.g.

git show --no-patch --format='%B' | pbcopy

or with my alias

git shnpfb | pbcopy
https://salferrarello.com/?p=10400
Extensions
Write Slack Posts in Markdown
ComputingRecommendationsslack
By default, Slack provides you with a Formatting toolbar and a WYSIWYG editor. I'm a big fan of writing in Markdown rather than WYSIWYG and use it everywhere I can. Fortunately, I can configure Slack to write in Markdown (or more accurately Slack's subset of Markdown).
Show full content

By default, Slack provides you with a Formatting toolbar and a WYSIWYG editor. I’m a big fan of writing in Markdown rather than WYSIWYG and use it everywhere I can. Fortunately, I can configure Slack to write in Markdown (or more accurately Slack’s subset of Markdown).

If you’re not already using Markdown, Slack probably isn’t the best place to get started but if you’re a Markdown fan like me I strongly suggest going to

  • Preferences (on a Mac, this is at Slack > Settings...)
  • Advanced
  • Input options
  • and checking Format messages with markup

The slack help article Format your messages in Slack with markup has more information including a list of what markdown formatting is supported.

Note: Slack refers to their subset of Markdown as “markup” (which I think is confusing).

https://salferrarello.com/?p=9871
Extensions
Filter Meta in WordPress Gutenberg Block Editor
ComputingDev TipsSolutionGutenbergWordPress
I work with WordPress sites that have lots of meta fields exposed through the REST API. Within Gutenberg, I'm often checking those values by querying the "meta" attribute in the data. Sometimes the larger number of meta fields makes it difficult to find the specific fields I'm interested in. To make my life easier, I've put together a code snippet that filters the keys in the meta object that is returned.
Show full content

I work with WordPress sites that have lots of meta fields exposed through the REST API. Within Gutenberg, I’m often checking those values by querying the meta attribute in the data. Sometimes the large number of meta fields makes it difficult to find the specific fields I’m interested in. To make my life easier, I’ve put together a code snippet that filters the keys in the meta object that is returned.

This is the command I run where mysubstring is the string I’m looking for in the meta keys.

((substring) => Object.fromEntries(
  Object.entries(
    wp.data.select('core/editor').getEditedPostAttribute('meta')
  ).filter(
    ([key, value]) => key.toLowerCase().includes(substring.toLowerCase()
  )
)))('mysubstring')
How It Works First Retrieve meta Attribute in Gutenberg Block Editor

Running this in the browser console while on our Gutenberg page, returns a list of the meta fields exposed in the REST API.

wp.data.select('core/editor').getEditedPostAttribute('meta');

e.g.

{
  my_color: 'red',
  mastodon_post: 'https://phpc.social/@salcode/115684016910550967',
  mastodon_server: 'https://phpc.social/',
}

If there are lots of fields this response could be unpleasant to wade through.

Convert Object to An Array of Arrays

We’re using Object.entries()) to convert our meta object from something like

{
  my_color: 'red',
  mastodon_post: 'https://phpc.social/@salcode/115684016910550967',
  mastodon_server: 'https://phpc.social/',
}

to an array of arrays like

[
  [ 'my_color',  'red' ],
  [ 'mastodon_post', 'https://phpc.social/@salcode/115684016910550967' ],
  [ 'mastodon_server': 'https://phpc.social/'],
]
Filter the Arrays

Then .filter(([key, value]) => key.includes("mastodon") to filter to array to keep only the inner arrays that include our string (mastodon)

[
  [ 'mastodon_post', 'https://phpc.social/@salcode/115684016910550967' ],
  [ 'mastodon_server': 'https://phpc.social/'],
]
Convert the Array of Arrays Back to An Object

Finally we use Object.fromEntries() to convert the remaining entries back to an object

{
  mastodon_post: 'https://phpc.social/@salcode/115684016910550967',
  mastodon_server: 'https://phpc.social/',
}
https://salferrarello.com/?p=10566
Extensions
Vim Increment Number With Dash
ComputingSolutionneovimvim
In Neovim, I'm a big fan of CTRL-a to increment a number (and CTRL-x to decrement a number), however when I have dash before the number (e.g. "sprint-23"), this is interpreted as a negative number so incrementing the value goes to 22 (because -22 is one greater than -23). We can fix this.
Show full content

In Neovim, I’m a big fan of CTRL-a to increment a number (and CTRL-x to decrement a number), however when I have dash before the number (e.g. “sprint-23”), this is interpreted as a negative number so incrementing the value
goes to 22 (because -22 is one greater than -23). We can fix this.

The short answer is to add this to your Neovim configuration

 vim.opt.nrformats:append('blank')

or if you’re using Vim

set nrformats+=blank

Neovim has a number of algorithms for identifying numbers to increment/decrement and they are controlled by nrformats (see :help nrformats).

By adding blank to the nrformats, Neovim will

treat numbers as signed or unsigned based on
preceding whitespace.  If a number with a leading dash has its
dash immediately preceded by a non-whitespace character (i.e.,
not a tab or a " "), the negative sign won't be considered as
part of the number.  For example:
    Using CTRL-A on "14" in "Carbon-14" results in "Carbon-15"
    (without "blank" it would become "Carbon-13").
    Using CTRL-X on "8" in "Carbon -8" results in "Carbon -9"
    (because -8 is preceded by whitespace.  If "unsigned" was
    set, it would result in "Carbon -7").
How to Add blank to nrformats

We can add blank to nformats by running this command

:set nrformats+=blank

or we can update our Neovim configuration in our init.lua file with

vim.opt.nrformats:append('blank')
Vim Compatibility

Vim also supports blank as an nrformats value and it looks like this was originally an issue and improvement made in Vim that was ported over to Neovim.

See https://github.com/vim/vim/issues/15033

https://salferrarello.com/?p=10515
Extensions
Turn Off a Specific Linter in Neovim Ale
Solutionneovimvimplugin
I recently needed to disable one of the built-in linters in the ALE (Asynchronous Lint Engine) Vim plugin in my Neovim configuration. This is the line that disables the PHP linter "phpstan" in Neovim.
Show full content

I recently needed to disable one of the built-in linters in the ALE (Asynchronous Lint Engine) Vim plugin in my Neovim configuration.

This is the line that disables the PHP linter phpstan in Neovim.

vim.g.ale_linters_ignore = { "phpstan" }

I’m using the lazy.nvim plugin manager (not to be confused with the LazyVim pre-packaged configuration), so my line to load ALE is

return {
    "dense-analysis/ale",
}

which I updated to

return {
    "dense-analysis/ale",
    config = function()
        vim.g.ale_linters_ignore = { "phpstan" }
    end,
}

Here is the Pull Request Disable phpstan in ALE.

Related Resources
https://salferrarello.com/?p=10495
Extensions
xargs: warning: options –max-args and –replace/-I/-i are mutually exclusive, ignoring previous –max-args value
ComputingDev TipsSolutioncommand linexargs
This threw me for a loop so I'm writing this down to help others (and likely me in the future). I swear I've used "xargs" with both "-I" and "--max-args" in the past but now I'm getting this warning and things are behaving improperly. It turns out there are two version of xargs, (FreeBSD xargs and GNU xargs). FreeBSD xargs is installed on BSD systems (including MacOS) and GNU xargs is found on on Linux systems. This warning (and the incorrect result you also get) indicate you're running GNU xargs.
Show full content

This threw me for a loop so I’m writing this down to help others (and likely me in the future). I swear I’ve used xargs with both -I and --max-args in the past but now I’m getting this warning and things are behaving improperly. It turns out there are two version of xargs, FreeBSD xargs and GNU xargs. FreeBSD xargs is installed on BSD systems (including MacOS) and GNU xargs is found on on Linux systems. This warning (and the incorrect result you also get) indicate you’re running GNU xargs.

xargs: warning: options --max-args and --replace/-I/-i are mutually exclusive, ignoring previous --max-args value

Note: This warning also occurs when using -n the shortcut version of --max-args.

How I Triggered the “--max-args and --replace/-I/-i are mutually exclusive” Warning

I ran a command that returned space separated content (e.g. echo '1 2 3 4') and I wanted to use xargs to:

  • Run a separate command for each entry (--max-args=1)
  • Put the entry in the middle of my command using {} as a placeholder
    (-I {} echo a-{}-b)

e.g.

echo '1 2 3 4' | xargs --max-args=1 -I {} echo a-{}-b

hoping to get the output

a-1-b
a-2-b
a-3-b
a-4-b

but instead I got the warning and a-1 2 3 4-b

Here is a run on GNU xargs

echo '1 2 3 4' | xargs --max-args=1 -I {} echo a-{}-b
xargs: warning: options --max-args and --replace/-I/-i are mutually exclusive, ignoring previous --max-args value
a-1 2 3 4-b

(but FreeBSD xargs does give the desired output).

Solution for GNU xargs

The solution I found requires two things:

  1. Switching the order of --max-args=1 and -I {}, so -I {} comes first
  2. And adding | tr " " "\n" before | xargs ... to convert (a.k.a. to TRanslate) a space " " into a new line \n

This updated version works properly on GNU xargs (and FreeBSD xargs)

$ echo '1 2 3 4' | tr " " "\n" | xargs -I {} --max-args=1 echo a-{}-b
a-1-b
a-2-b
a-3-b
a-4-b
Is There a Better Way to Do This?

Using both --max-args=1 and -I {} seems like a pretty common use case, is there a better way to do this?

https://salferrarello.com/?p=10369
Extensions
Git Editor Roulette
ComputingDev TipsSolutioncommand lineGitgit log
A question came up about setting the Git editor to a bash function, which I could not figure out. I was, however, able to set the Git editor to a bash script. As a proof of concept, here is a bash script to randomly choose an editor (nano or vim) when using Git.
Show full content

A question came up about setting the Git editor to a bash function, which I could not figure out. I was, however, able to set the Git editor to a bash script. As a proof of concept, here is a bash script to randomly choose an editor (nano or vim) when using Git.

I saved this file as ~/my-random-git-editor-script

#!/bin/bash

if (( RANDOM % 2 )); then
    editor="nano"
else
    editor="vim"
fi

exec "$editor" "$@"

and made it executable by running

chmod +x ~/my-random-git-editor-script

and finally I updated my Git config to use the script by running

git config --global core.editor "~/my-random-git-editor-script"
https://salferrarello.com/?p=10338
Extensions
Git Squash Back To
ComputingDev TipsSolutioncommand lineGitgit aliasrebasevim
Often in my work, I'll create a number of commits before I craft my correct solution. Having these missteps in separate commits doesn't provide any real value and adds noise so I'll often squash these commits into a single commit. Historically, I've used a Git interactive rebase to do this but as I was reminded on Mastodon, it seems like there should be a quicker way, so I've created a Git alias, "git squashbackto", to do this.
Show full content

Often in my work, I’ll create a number of commits before I craft my correct solution. Having these missteps in separate commits doesn’t provide any real value and adds noise so I’ll often squash these commits into a single commit. Historically, I’ve used a Git interactive rebase to do this but as I was reminded on Mastodon, it seems like there should be a quicker way, so I’ve created a Git alias, git squashbackto, to do this.

My Solution

This is the command I came up with to squash the last 5 commits.

git -c sequence.editor="vim --clean -c 'silent 2,\$s/^pick/squash/|:wq'" rebase --interactive --keep-base HEAD~5
Git Alias git squashbackto

I’ve turned this solution into a Git alias, git squashbackto.

Running the following from the command line will add the git squashbackto alias to your Global Git Configuration file.

git config --global alias.squashbackto '!git -c sequence.editor="vim --clean -c '\''silent 2,\$s/^pick/squash/|:wq'\'' " rebase --interactive --keep-base'

The entry in your Global Git Configuration file will look like

[alias]
    squashbackto = !git -c sequence.editor=\"vim --clean -c 'silent 2,\\$s/^pick/squash/|:wq' \" rebase --interactive --keep-base
How to Use git squashbackto

We can squash the last five commits with

git squashbackto HEAD~5

or squash all of the commits added to our branch since we branched off main (i.e. all of the commits on our branch that don’t appear on main)

git squashbackto main
How I Tested This

In the process of testing this, I created an empty directory (I called mine squashcommits/) and cded into it. Once there I ran the following:

(rm -rf ./.git || true ) && 
(rm -f ./*.md || true ) && \
git init && \
echo '# My Project' > README.md && \
git add README.md && \
git commit -m 'Initial commit: add README.md' && \
git switch -c feat/myfeature && \
echo 'Feature (attempt 1)' > myfeature.md  && \
git add myfeature.md && \
git commit -m 'Add my feature (attempt 1)' && \
echo 'Feature (attempt 2)' > myfeature.md && \
git add myfeature.md && \
git commit -m 'Add my feature (attempt 2)' && \
echo 'Feature (attempt 3)' > myfeature.md && \
git add myfeature.md && \
git commit -m 'Add my feature (attempt 3)' && \
echo 'Feature (attempt 4)' > myfeature.md && \
git add myfeature.md && \
git commit -m 'Add my feature (attempt 4)' && \
echo 'Feature (attempt 5)' > myfeature.md && \
git add myfeature.md && \
git commit -m 'Add my feature (attempt 5)' && \
git log --oneline --graph --abbrev=4

This command does the following:

  1. delete any current repo files (so I can re-run this command)
  2. setup the repo the way I want for testing and
  3. display the Git history that is created

When the command finishes the git history of the repo looks something like this:

* 87c6 (HEAD -> feat/myfeature) Add my feature (attempt 5)
* 1816 Add my feature (attempt 4)
* 4b3f Add my feature (attempt 3)
* 3282 Add my feature (attempt 2)
* 070f Add my feature (attempt 1)
* 3e62 (main) Initial commit: add README.md

Now when I run

 git -c sequence.editor="vim --clean -c 'silent 2,\$s/^pick/squash/|:wq'" rebase --interactive --keep-base HEAD~5

The git interactive rebase screen (where I would change pick to squash) is skipped and we go directly to the screen where I have all 5 commit messages and I can combine them as desired. Once that is saved, running git log --oneline --graph --abbrev=4 looks like

* 9b4a (HEAD -> feat/myfeature) Add my feature
* 3e62 (main) Initial commit: add README.md

Success!

How this Works

At the heart of this command is

git rebase --interactive --keep-base HEAD~5

which drops you into your chosen editor with content that looks like

pick 070fb05 Add my feature (attempt 1)
pick 3282a75 Add my feature (attempt 2)
pick 4b3f39c Add my feature (attempt 3)
pick 18160b0 Add my feature (attempt 4)
pick 87c6d32 Add my feature (attempt 5)

To do this manually, we would update attempts 2 through 5 from pick to squash.

--keep-base

We include –keep-base on our git rebase call, so squash commits on our current branch without pulling in any new commits from our other branch (e.g. main). Typically, a git rebase -i main would both:

  1. Add any new commits that have been added to main since we last updated from there
  2. Open the editor with the interactive rebase screen

By using --keep-base, we skip the first step and only do the second.

Automating pick to squash

Git has lots of configuration values and one of the things you can configure is what editor to use. I started by modifying the editor via the core.editor configuration value, but unfortunately an interactive rebase opens the editor twice (once to update the commits from pick to something else and once to craft the commit message for your new commit created by the squash). In this case, I want to automate the first but NOT automate the second.

The good news is Git version 2.40 (released in 2023) added a new configuration value, sequence.editor, that impacts when the editor is opened to update commits from pick to something else but NOT when crafting the commit message (see Use a Different Editor for Git Interactive Rebase).

We want to update this sequence.editor value but we only want to do this when we run this command (we don’t want to impact other times a Git interactive rebase is run). We can modify our Git config values just for the current command by run by using the Git -c option to set sequence.editor just for this run.

We’re going to set Git to use Vim as the Git interactive rebase editor (but we’re also going to pass some configuration values to Vim).

vim --clean -c 'silent 2,\$s/^pick/squash/|:wq'
--clean

The --clean argument results in “initializations from files and environment variables is skipped”.

We don’t want any of our custom Vim configuration or plugins impacting this code.

See :help --clean

-c {command}

The {command} given will run after Vim has loaded the file. Multiple commands can be given separated by a vertical bar (|). See :help -c

In this case, our command is

silent 2,$s/^pick/squash/|:wq

The 2,$s/^pick/squash/ command replaces pick with squash starting on line 2 and going to the end of the file ($) (see :help substitute) – this only applies on lines where “pick” appears at the beginning of the line (^). We prefix this command with silent to avoid the execution halting with the message

Press ENTER or type command to continue

The :wq, writes and quits the file (see :help :wq).

Peek Under the Hood

You can omit |:wq from the command to pause after the substitution has completed. If you run this command, you’ll need to enter :wq manually to get things moving again.

git -c sequence.editor="vim --clean -c 'silent 2,\$s/^pick/squash/'" rebase --interactive --keep-base HEAD~5
Adding this to My Git Configuration

Here is the PR where I add the git squashbackto alias to my Git configuration.

Other Solutions

In looking for this solution I found a number of other articles about this same idea of squashing the last so many commits but they all seem to use one of these two approaches:

  1. A Git interactive rebase (without the additional automation in this post), which has the disadvantage of requiring you update pick to squash on lines you want to squash and then save this “sequence” file
  2. A Git reset to unstage the changes and then re-adding the changes and creating a new commit, which has the disadvantage of losing your previous commit messages
https://salferrarello.com/?p=9402
Extensions
Mac Maximize Window with Alfred
ComputingDev TipsDraftSolutionAlfred
One of the things that frustrates me about Apple Macs (macOS) is clicking the green sizing button in the upper-left hand corner makes a window full screen. I almost never want to go full screen. What I typically want is to maximize the window (fill up the entire area while leaving the toolbar visible). This can be done by holding down "Option" while you click the green button (but I find I mess that up often enough that I wanted another solution).
Show full content

One of the things that frustrates me about Apple Macs (macOS) is clicking the green sizing button in the upper-left hand corner makes a window full screen. I almost never want to go full screen. What I typically want is to maximize the window (fill up the entire area while leaving the toolbar visible). This can be done by holding down Option while you click the green button (but I find I mess that up often enough that I wanted another solution).

Update: This solution has a bug where it does not properly handle multiple displays. See Issue #1 Does not handle multiple displays on the GitHub repo.
I’ve started using the app Rectangle in the meantime as an alternative solution.

I’m a big user of Alfred (a Mac application for shortcuts, snippets, and workflows), so I created a workflow to maximize the current window.

While I could have bound this to a hot key combination, I’m typically not a big hot key combination person and instead created a keyword to trigger it.

Alfred window selecting the 'Maximize Current Window' workflow.

Here is the AppleScript that does the work.

on run argv
    tell application "Finder"
        set desktopSize to bounds of window of desktop
    end tell

    set screenWidth to item 3 of desktopSize
    set screenHeight to item 4 of desktopSize

    tell application "System Events"
        set frontAppName to name of first application process whose frontmost is true
        tell application process (name of first application process whose frontmost is true)
            try
                set position of front window to {0, 0}
                set size of front window to {screenWidth, screenHeight}
                return "Maximized \"" & frontAppName & "\""
            on error
                return "This application's window cannot be resized by AppleScript."
            end try
        end tell
    end tell
end run

Alfred Workflow Editor AppleScript to Maximize Current Window

Permissions

In order for this script to work, Alfred needs Accessibility access to your computer.

  1. Click the Apple Icon
  2. Select System Settings... from the dropdown
  3. Select Privacy & Security
  4. Select Accessibility
  5. Add Alfred (if necessary) and toggle on

macOS System Settings > Privacy & Security > Accessibility > Alfred enabled

Download

This workflow is available online at https://github.com/salcode/maximize-window-alfredworkflow

https://salferrarello.com/?p=10288
Extensions