From 68263ce41040e46b7b67dd31ccbd632ea45963dd Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 18:15:00 -0500 Subject: [PATCH 01/16] Modernize Fish plugin layout and safety Refactor fishmarks to use fish-native parsing with safer bookmark handling, and package it with conf.d/functions/completions for Fisher compatibility while keeping legacy marks.fish loading. Add an automated Fish test suite and update install/docs to match modern Fish workflows. --- README.md | 43 ++++--- completions/delete_bookmark.fish | 1 + completions/go_to_bookmark.fish | 1 + completions/print_bookmark.fish | 1 + conf.d/fishmarks.fish | 29 +++++ functions/_check_help.fish | 17 +++ functions/_fishmarks_complete.fish | 6 + functions/_fishmarks_decode_path.fish | 15 +++ functions/_fishmarks_encode_path.fish | 4 + functions/_fishmarks_ensure_sdirs.fish | 10 ++ functions/_fishmarks_entries.fish | 14 +++ functions/_fishmarks_find_path.fish | 11 ++ functions/_fishmarks_print_error.fish | 5 + functions/_fishmarks_valid_name.fish | 3 + functions/_fishmarks_write_entries.fish | 12 ++ functions/_valid_bookmark.fish | 7 ++ functions/delete_bookmark.fish | 24 ++++ functions/go_to_bookmark.fish | 22 ++++ functions/list_bookmarks.fish | 11 ++ functions/print_bookmark.fish | 16 +++ functions/save_bookmark.fish | 24 ++++ install.fish | 50 ++++---- marks.fish | 155 ++++-------------------- tests/run.fish | 123 +++++++++++++++++++ 24 files changed, 438 insertions(+), 166 deletions(-) create mode 100644 completions/delete_bookmark.fish create mode 100644 completions/go_to_bookmark.fish create mode 100644 completions/print_bookmark.fish create mode 100644 conf.d/fishmarks.fish create mode 100644 functions/_check_help.fish create mode 100644 functions/_fishmarks_complete.fish create mode 100644 functions/_fishmarks_decode_path.fish create mode 100644 functions/_fishmarks_encode_path.fish create mode 100644 functions/_fishmarks_ensure_sdirs.fish create mode 100644 functions/_fishmarks_entries.fish create mode 100644 functions/_fishmarks_find_path.fish create mode 100644 functions/_fishmarks_print_error.fish create mode 100644 functions/_fishmarks_valid_name.fish create mode 100644 functions/_fishmarks_write_entries.fish create mode 100644 functions/_valid_bookmark.fish create mode 100644 functions/delete_bookmark.fish create mode 100644 functions/go_to_bookmark.fish create mode 100644 functions/list_bookmarks.fish create mode 100644 functions/print_bookmark.fish create mode 100644 functions/save_bookmark.fish create mode 100755 tests/run.fish diff --git a/README.md b/README.md index cf67a51..e53bb2f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # fishmarks Fishmarks is a clone of [bashmarks](https://github.com/huyng/bashmarks) for the -[Fish shell](http://fishshell.com/). Fishmarks is compatible with your existing +[Fish shell](https://fishshell.com/). Fishmarks is compatible with your existing bashmarks and bookmarks added using fishmarks are also available in bashmarks. ## Installation @@ -10,10 +10,18 @@ bashmarks and bookmarks added using fishmarks are also available in bashmarks. To install fishmarks automatically, paste the following in your terminal. ```fish -curl -L https://github.com/techwizrd/fishmarks/raw/master/install.fish | fish +curl -fsSL https://raw.githubusercontent.com/techwizrd/fishmarks/master/install.fish | fish ``` -Please note, however, that you should _never_ install things by piping untrusted "install" scripts downloaded through ``curl`` directly into your shell (be it ``bash`` or ``fish``). Even if you read through the install script and think you understand it, you could be prone to a [man-in-the-middle](http://en.wikipedia.org/wiki/Man-in-the-middle_attack) attack or any number of security vulnerabilities. While manual installations are tedious, they are recommended for any situations where security is a concern (and it should almost always be a concern). +Please note, however, that you should _never_ install things by piping untrusted "install" scripts downloaded through ``curl`` directly into your shell (be it ``bash`` or ``fish``). Even if you read through the install script and think you understand it, you could be prone to a [man-in-the-middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attack or any number of security vulnerabilities. While manual installations are tedious, they are recommended for any situations where security is a concern (and it should almost always be a concern). + +### Fisher Installation + +If you use [Fisher](https://github.com/jorgebucaran/fisher), install fishmarks with: + +```fish +fisher install techwizrd/fishmarks +``` ### Manual Installation @@ -22,15 +30,14 @@ To install fishmarks manually: 1. Clone fishmarks into `~/.fishmarks` ```fish -$ git clone http://github.com/techwizrd/fishmarks.git +$ git clone https://github.com/techwizrd/fishmarks.git ~/.fishmarks ``` -2. Source `fishmarks/marks.fish` in your `config.fish` by inserting the - following into your `~/.config/fish/config.fish` +2. Source `~/.fishmarks/marks.fish` from a file in `~/.config/fish/conf.d/` ```fish -# Load fishmarks (http://github.com/techwizrd/fishmarks) -source ~/.fishmarks/marks.fish +mkdir -p ~/.config/fish/conf.d +printf '# Load fishmarks (https://github.com/techwizrd/fishmarks)\nsource ~/.fishmarks/marks.fish\n' > ~/.config/fish/conf.d/fishmarks.fish ``` ### Update to the latest version @@ -46,10 +53,10 @@ cd ~/.fishmarks ```fish git fetch --all ``` -3. Remove any changes and force update to the latest version from the Git repository: +3. Pull the latest version: ```fish -git reset --hard origin/master +git pull --ff-only ``` @@ -66,7 +73,7 @@ l - Lists all available bookmarks' ``` ### Configuration Variables -All of these must be set before `virtual.fish` is sourced in your `~/.config/fish/config.fish`. +All of these must be set before `marks.fish` is sourced in your fish startup files. * `SDIRS` - (default: `~/.sdirs`) where all your bookmarks are kept. * `NO_FISHMARKS_COMPAT_ALIASES` - set this to turn off the bashmark-compatible aliases (e.g., `p` for `print_bookmark`) @@ -85,6 +92,14 @@ webfolder /var/www [/var/www]$ ``` +## Running tests + +Run the test suite with: + +```fish +fish tests/run.fish +``` + ### Contributing *Have you noticed any bugs or issues with fishmarks? Do you have any features you would like to see added?* @@ -95,8 +110,8 @@ webfolder /var/www I recommend the following guides on writing good commit messages: - [GIT Commit Good Practice](https://wiki.openstack.org/wiki/GitCommitMessages) -- [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) -- [Proper Git Commit Messages and an Elegant Git History](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) +- [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) +- [Proper Git Commit Messages and an Elegant Git History](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) ## License @@ -104,7 +119,7 @@ Copyright 2013 Kunal Sarkhel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the -License at `http://www.apache.org/licenses/LICENSE-2.0`. +License at `https://www.apache.org/licenses/LICENSE-2.0`. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR diff --git a/completions/delete_bookmark.fish b/completions/delete_bookmark.fish new file mode 100644 index 0000000..e0c6f60 --- /dev/null +++ b/completions/delete_bookmark.fish @@ -0,0 +1 @@ +complete -f -c delete_bookmark -a '(_fishmarks_complete)' diff --git a/completions/go_to_bookmark.fish b/completions/go_to_bookmark.fish new file mode 100644 index 0000000..b1f345b --- /dev/null +++ b/completions/go_to_bookmark.fish @@ -0,0 +1 @@ +complete -f -c go_to_bookmark -a '(_fishmarks_complete)' diff --git a/completions/print_bookmark.fish b/completions/print_bookmark.fish new file mode 100644 index 0000000..d57d0e6 --- /dev/null +++ b/completions/print_bookmark.fish @@ -0,0 +1 @@ +complete -f -c print_bookmark -a '(_fishmarks_complete)' diff --git a/conf.d/fishmarks.fish b/conf.d/fishmarks.fish new file mode 100644 index 0000000..8c66e01 --- /dev/null +++ b/conf.d/fishmarks.fish @@ -0,0 +1,29 @@ +if set -q __fishmarks_conf_loaded + return +end +set -g __fishmarks_conf_loaded 1 + +_fishmarks_ensure_sdirs + +complete -e -c print_bookmark +complete -e -c delete_bookmark +complete -e -c go_to_bookmark + +complete -c print_bookmark -a '(_fishmarks_complete)' -f +complete -c delete_bookmark -a '(_fishmarks_complete)' -f +complete -c go_to_bookmark -a '(_fishmarks_complete)' -f + +if not set -q NO_FISHMARKS_COMPAT_ALIASES + alias s save_bookmark + alias g go_to_bookmark + alias p print_bookmark + alias d delete_bookmark + alias l list_bookmarks + + complete -e -c p + complete -e -c d + complete -e -c g + complete -c p -w print_bookmark + complete -c d -w delete_bookmark + complete -c g -w go_to_bookmark +end diff --git a/functions/_check_help.fish b/functions/_check_help.fish new file mode 100644 index 0000000..60461ef --- /dev/null +++ b/functions/_check_help.fish @@ -0,0 +1,17 @@ +function _check_help + if test (count $argv) -lt 1 + return 1 + end + + if contains -- "$argv[1]" -h -help --help + echo '' + echo 's - Saves the current directory as "bookmark_name"' + echo 'g - Goes (cd) to the directory associated with "bookmark_name"' + echo 'p - Prints the directory associated with "bookmark_name"' + echo 'd - Deletes the bookmark' + echo 'l - Lists all available bookmarks' + echo '' + return 0 + end + return 1 +end diff --git a/functions/_fishmarks_complete.fish b/functions/_fishmarks_complete.fish new file mode 100644 index 0000000..3813373 --- /dev/null +++ b/functions/_fishmarks_complete.fish @@ -0,0 +1,6 @@ +function _fishmarks_complete + for entry in (_fishmarks_entries) + set -l parts (string split -m 1 '=' -- "$entry") + printf '%s\n' "$parts[1]" + end +end diff --git a/functions/_fishmarks_decode_path.fish b/functions/_fishmarks_decode_path.fish new file mode 100644 index 0000000..7136bf9 --- /dev/null +++ b/functions/_fishmarks_decode_path.fish @@ -0,0 +1,15 @@ +function _fishmarks_decode_path --argument-names raw_value + set -l value "$raw_value" + set -l quote_match (string match -r '^"(.*)"$' -- "$value") + if test (count $quote_match) -gt 1 + set value "$quote_match[2]" + else + set quote_match (string match -r "^'(.*)'" -- "$value") + if test (count $quote_match) -gt 1 + set value "$quote_match[2]" + end + end + + set value (string replace -a -- '\$HOME' "$HOME" "$value") + string replace -a -- '$HOME' "$HOME" "$value" +end diff --git a/functions/_fishmarks_encode_path.fish b/functions/_fishmarks_encode_path.fish new file mode 100644 index 0000000..b14794c --- /dev/null +++ b/functions/_fishmarks_encode_path.fish @@ -0,0 +1,4 @@ +function _fishmarks_encode_path --argument-names path_value + set -l escaped_home (string escape --style=regex -- "$HOME") + string replace -r -- "^$escaped_home" '\$HOME' "$path_value" +end diff --git a/functions/_fishmarks_ensure_sdirs.fish b/functions/_fishmarks_ensure_sdirs.fish new file mode 100644 index 0000000..9969eca --- /dev/null +++ b/functions/_fishmarks_ensure_sdirs.fish @@ -0,0 +1,10 @@ +function _fishmarks_ensure_sdirs + if not set -q SDIRS + set -gx SDIRS "$HOME/.sdirs" + end + + command mkdir -p -- (path dirname -- "$SDIRS") + if not test -e "$SDIRS" + command touch -- "$SDIRS" + end +end diff --git a/functions/_fishmarks_entries.fish b/functions/_fishmarks_entries.fish new file mode 100644 index 0000000..c985cd3 --- /dev/null +++ b/functions/_fishmarks_entries.fish @@ -0,0 +1,14 @@ +function _fishmarks_entries + _fishmarks_ensure_sdirs + + while read -l line + set -l entry (string match -r '^export[[:space:]]+DIR_([A-Za-z0-9_]+)=(.+)$' -- "$line") + if test (count $entry) -lt 3 + continue + end + + set -l name "$entry[2]" + set -l value (_fishmarks_decode_path "$entry[3]") + printf '%s=%s\n' "$name" "$value" + end < "$SDIRS" +end diff --git a/functions/_fishmarks_find_path.fish b/functions/_fishmarks_find_path.fish new file mode 100644 index 0000000..4b79d07 --- /dev/null +++ b/functions/_fishmarks_find_path.fish @@ -0,0 +1,11 @@ +function _fishmarks_find_path --argument-names bookmark_name + for entry in (_fishmarks_entries) + set -l parts (string split -m 1 '=' -- "$entry") + if test "$parts[1]" = "$bookmark_name" + printf '%s\n' "$parts[2]" + return 0 + end + end + + return 1 +end diff --git a/functions/_fishmarks_print_error.fish b/functions/_fishmarks_print_error.fish new file mode 100644 index 0000000..326da34 --- /dev/null +++ b/functions/_fishmarks_print_error.fish @@ -0,0 +1,5 @@ +function _fishmarks_print_error --argument-names message + set_color red >&2 + printf 'ERROR: %s\n' "$message" >&2 + set_color normal >&2 +end diff --git a/functions/_fishmarks_valid_name.fish b/functions/_fishmarks_valid_name.fish new file mode 100644 index 0000000..b029a6b --- /dev/null +++ b/functions/_fishmarks_valid_name.fish @@ -0,0 +1,3 @@ +function _fishmarks_valid_name --argument-names bookmark_name + string match -rq '^[A-Za-z0-9_]+$' -- "$bookmark_name" +end diff --git a/functions/_fishmarks_write_entries.fish b/functions/_fishmarks_write_entries.fish new file mode 100644 index 0000000..06e5de9 --- /dev/null +++ b/functions/_fishmarks_write_entries.fish @@ -0,0 +1,12 @@ +function _fishmarks_write_entries + _fishmarks_ensure_sdirs + + set -l tmp_file (command mktemp) + for entry in $argv + set -l parts (string split -m 1 '=' -- "$entry") + set -l encoded_path (_fishmarks_encode_path "$parts[2]") + printf 'export DIR_%s="%s"\n' "$parts[1]" "$encoded_path" + end > "$tmp_file" + + command mv -- "$tmp_file" "$SDIRS" +end diff --git a/functions/_valid_bookmark.fish b/functions/_valid_bookmark.fish new file mode 100644 index 0000000..afb887b --- /dev/null +++ b/functions/_valid_bookmark.fish @@ -0,0 +1,7 @@ +function _valid_bookmark + if test (count $argv) -lt 1 + return 1 + end + + _fishmarks_find_path "$argv[1]" >/dev/null +end diff --git a/functions/delete_bookmark.fish b/functions/delete_bookmark.fish new file mode 100644 index 0000000..c84b1f5 --- /dev/null +++ b/functions/delete_bookmark.fish @@ -0,0 +1,24 @@ +function delete_bookmark --description "Delete a bookmark" + if test (count $argv) -lt 1 + _fishmarks_print_error "bookmark name required" + return 1 + end + + set -l removed 0 + set -l updated_entries + for entry in (_fishmarks_entries) + set -l parts (string split -m 1 '=' -- "$entry") + if test "$parts[1]" = "$argv[1]" + set removed 1 + continue + end + set -a updated_entries "$entry" + end + + if test $removed -eq 0 + _fishmarks_print_error "bookmark '$argv[1]' does not exist" + return 1 + end + + _fishmarks_write_entries $updated_entries +end diff --git a/functions/go_to_bookmark.fish b/functions/go_to_bookmark.fish new file mode 100644 index 0000000..f214065 --- /dev/null +++ b/functions/go_to_bookmark.fish @@ -0,0 +1,22 @@ +function go_to_bookmark --description "Go to (cd) to the directory associated with a bookmark" + if test (count $argv) -lt 1 + _fishmarks_print_error "bookmark name required" + return 1 + end + + if not _check_help "$argv[1]" + set -l target (_fishmarks_find_path "$argv[1]") + if test -z "$target" + _fishmarks_print_error "'$argv[1]' bookmark does not exist" + return 1 + end + + if test -d "$target" + cd -- "$target" + return 0 + end + + _fishmarks_print_error "'$target' does not exist" + return 1 + end +end diff --git a/functions/list_bookmarks.fish b/functions/list_bookmarks.fish new file mode 100644 index 0000000..b5e916a --- /dev/null +++ b/functions/list_bookmarks.fish @@ -0,0 +1,11 @@ +function list_bookmarks --description "List all available bookmarks" + if not _check_help "$argv[1]" + for entry in (_fishmarks_entries) + set -l parts (string split -m 1 '=' -- "$entry") + set_color yellow + printf '%-20s' "$parts[1]" + set_color normal + printf ' %s\n' "$parts[2]" + end + end +end diff --git a/functions/print_bookmark.fish b/functions/print_bookmark.fish new file mode 100644 index 0000000..a3d505c --- /dev/null +++ b/functions/print_bookmark.fish @@ -0,0 +1,16 @@ +function print_bookmark --description "Print the directory associated with a bookmark" + if test (count $argv) -lt 1 + _fishmarks_print_error "bookmark name required" + return 1 + end + + if not _check_help "$argv[1]" + set -l target (_fishmarks_find_path "$argv[1]") + if test -z "$target" + _fishmarks_print_error "'$argv[1]' bookmark does not exist" + return 1 + end + + printf '%s\n' "$target" + end +end diff --git a/functions/save_bookmark.fish b/functions/save_bookmark.fish new file mode 100644 index 0000000..1a0ad9c --- /dev/null +++ b/functions/save_bookmark.fish @@ -0,0 +1,24 @@ +function save_bookmark --description "Save the current directory as a bookmark" + set -l bn "$argv[1]" + if test (count $argv) -lt 1 + set bn (string replace -ra -- '[^A-Za-z0-9]' '_' (path basename -- "$PWD")) + end + + if not _fishmarks_valid_name "$bn" + _fishmarks_print_error 'Bookmark names may only contain alphanumeric characters and underscores.' + return 1 + end + + set -l updated_entries + for entry in (_fishmarks_entries) + set -l parts (string split -m 1 '=' -- "$entry") + if test "$parts[1]" = "$bn" + continue + end + set -a updated_entries "$entry" + end + + set -a updated_entries "$bn=$PWD" + _fishmarks_write_entries $updated_entries + return 0 +end diff --git a/install.fish b/install.fish index 8faaa97..fc67431 100644 --- a/install.fish +++ b/install.fish @@ -1,28 +1,32 @@ #!/usr/bin/env fish -set -x required_version 2 -set -x fish_version (fish --version | cut -d ' ' -f 3 | cut -d . -f 1) -if [ $required_version -gt $fish_version ] - echo "Fish shell version $required_version is require for this script. You can obtain the latest version at http://fishshell.com/" +set -l required_major 3 +set -l fish_major (string split . -- (status fish-version))[1] + +if test "$fish_major" -lt "$required_major" + echo "Fish shell version $required_major or newer is required for this script. Get the latest version at https://fishshell.com/." exit 1 end -if [ -f "marks.fish" ] - set -x FISHMARKS (readlink -f 'marks.fish' | sed "s#^$HOME#\$HOME#g") -else if [ -f "fishmarks/marks.fish" ] - set -x FISHMARKS (readlink -f 'fishmarks/marks.fish' | sed "s#^$HOME#\$HOME#g") -else if not [ -f "$HOME/.fishmarks/marks.fish" ] - git clone http://github.com/techwizrd/fishmarks.git $HOME/.fishmarks - set -x FISHMARKS "\$HOME/.fishmarks/marks.fish" + +set -l source_file +if test -f "marks.fish" + set source_file (path resolve "marks.fish") +else if test -f "fishmarks/marks.fish" + set source_file (path resolve "fishmarks/marks.fish") +else if not test -f "$HOME/.fishmarks/marks.fish" + git clone https://github.com/techwizrd/fishmarks.git "$HOME/.fishmarks" + set source_file "$HOME/.fishmarks/marks.fish" else - cd $HOME/.fishmarks; and git pull - set -x FISHMARKS "\$HOME/.fishmarks/marks.fish" -end -mkdir -p ~/.config/fish -touch ~/.config/fish/config.fish -if grep -Fxq ". $FISHMARKS" $HOME/.config/fish/config.fish - echo "Fishmarks has already been installed" -else - echo '' >> $HOME/.config/fish/config.fish - echo "# Load fishmarks (http://github.com/techwizrd/fishmarks)" >> $HOME/.config/fish/config.fish - echo ". $FISHMARKS" >> $HOME/.config/fish/config.fish - echo "Fishmarks has been installed" + git -C "$HOME/.fishmarks" pull --ff-only + set source_file "$HOME/.fishmarks/marks.fish" end + +set -l conf_d_dir "$HOME/.config/fish/conf.d" +set -l conf_file "$conf_d_dir/fishmarks.fish" +set -l escaped_source (string replace -- "$HOME" '$HOME' "$source_file") + +command mkdir -p -- "$conf_d_dir" + +printf '# Load fishmarks (https://github.com/techwizrd/fishmarks)\n' > "$conf_file" +printf 'source %s\n' "$escaped_source" >> "$conf_file" + +echo "Fishmarks has been installed to $conf_file" diff --git a/marks.fish b/marks.fish index 0640587..8d8aa1c 100644 --- a/marks.fish +++ b/marks.fish @@ -12,139 +12,36 @@ # License for the specific language governing permissions and limitations under # the License. -# Fishmarks: -# Save and jump to commonly used directories -# -# Fishmarks is a a clone of bashmarks for the Fish shell. Fishmarks is -# compatible with your existing bashmarks and bookmarks added using fishmarks -# are also available in bashmarks. +# Fishmarks compatibility loader. -if not set -q SDIRS - set -gx SDIRS $HOME/.sdirs -end -touch $SDIRS +set -l fishmarks_root (path dirname -- (status filename)) -if not set -q NO_FISHMARKS_COMPAT_ALIASES - alias s save_bookmark - alias g go_to_bookmark - alias p print_bookmark - alias d delete_bookmark - alias l list_bookmarks +if test "$fishmarks_root" = '/'; and test -f './functions/save_bookmark.fish' + set fishmarks_root '.' end -function save_bookmark --description "Save the current directory as a bookmark" - set -l bn $argv[1] - if [ (count $argv) -lt 1 ] - set bn (string replace -r [^a-zA-Z0-9] _ (basename (pwd))) - end - if not echo $bn | grep -q "^[a-zA-Z0-9_]*\$"; - echo -e "\033[0;31mERROR: Bookmark names may only contain alphanumeric characters and underscores.\033[00m" - return 1 - end - if _valid_bookmark $bn; - sed -i='' "/DIR_$bn=/d" $SDIRS - end - set -l pwd (pwd | sed "s#^$HOME#\$HOME#g") - echo "export DIR_$bn=\"$pwd\"" >> $SDIRS - _update_completions +if not test -f "$fishmarks_root/functions/save_bookmark.fish" return 0 end -function go_to_bookmark --description "Go to (cd) to the directory associated with a bookmark" - if [ (count $argv) -lt 1 ] - echo -e "\033[0;31mERROR: '' bookmark does not exist\033[00m" - return 1 - end - if not _check_help $argv[1]; - cat $SDIRS | grep "^export DIR_" | sed "s/^export /set -x /" | sed "s/=/ /" | . - set -l target (env | grep "^DIR_$argv[1]=" | cut -f2 -d "=") - if [ ! -n "$target" ] - echo -e "\033[0;31mERROR: '$argv[1]' bookmark does not exist\033[00m" - return 1 - end - if [ -d "$target" ] - cd "$target" - return 0 - else - echo -e "\033[0;31mERROR: '$target' does not exist\033[00m" - return 1 - end - end -end - -function print_bookmark --description "Print the directory associated with a bookmark" - if [ (count $argv) -lt 1 ] - echo -e "\033[0;31mERROR: bookmark name required\033[00m" - return 1 - end - if not _check_help $argv[1]; - cat $SDIRS | grep "^export DIR_" | sed "s/^export /set -x /" | sed "s/=/ /" | . - env | grep "^DIR_$argv[1]=" | cut -f2 -d "=" - end -end - -function delete_bookmark --description "Delete a bookmark" - if [ (count $argv) -lt 1 ] - echo -e "\033[0;31mERROR: bookmark name required\033[00m" - return 1 - end - if not _valid_bookmark $argv[1]; - echo -e "\033[0;31mERROR: bookmark '$argv[1]' does not exist\033[00m" - return 1 - else - sed --follow-symlinks -i='' "/DIR_$argv[1]=/d" $SDIRS - _update_completions - end -end - -function list_bookmarks --description "List all available bookmarks" - if not _check_help $argv[1]; - cat $SDIRS | grep "^export DIR_" | sed "s/^export /set -x /" | sed "s/=/ /" | . - env | sort | awk '/DIR_.+/{split(substr($0,5),parts,"="); printf("\033[0;33m%-20s\033[0m %s\n", parts[1], parts[2]);}' - end -end - -function _check_help - if [ (count $argv) -lt 1 ] - return 1 - end - if begin; [ "-h" = $argv[1] ]; or [ "-help" = $argv[1] ]; or [ "--help" = $argv[1] ]; end - echo '' - echo 's - Saves the current directory as "bookmark_name"' - echo 'g - Goes (cd) to the directory associated with "bookmark_name"' - echo 'p - Prints the directory associated with "bookmark_name"' - echo 'd - Deletes the bookmark' - echo 'l - Lists all available bookmarks' - echo '' - return 0 - end - return 1 -end - -function _valid_bookmark - if begin; [ (count $argv) -lt 1 ]; or not [ -n $argv[1] ]; end - return 1 - else - cat $SDIRS | grep "^export DIR_" | sed "s/^export /set -x /" | sed "s/=/ /" | . - set -l bookmark (env | grep "^DIR_$argv[1]=" | cut -f1 -d "=" | cut -f2 -d "_" ) - if begin; not [ -n "$bookmark" ]; or not [ $bookmark=$argv[1] ]; end - return 1 - else - return 0 - end - end -end - -function _update_completions - cat $SDIRS | grep "^export DIR_" | sed "s/^export /set -x /" | sed "s/=/ /" | . - set -x _marks (env | grep "^DIR_" | sed "s/^DIR_//" | cut -f1 -d "=" | tr '\n' ' ') - complete -c print_bookmark -a $_marks -f - complete -c delete_bookmark -a $_marks -f - complete -c go_to_bookmark -a $_marks -f - if not set -q NO_FISHMARKS_COMPAT_ALIASES - complete -c p -a $_marks -f - complete -c d -a $_marks -f - complete -c g -a $_marks -f - end -end -_update_completions +for function_file in \ + _fishmarks_ensure_sdirs \ + _fishmarks_print_error \ + _fishmarks_encode_path \ + _fishmarks_decode_path \ + _fishmarks_entries \ + _fishmarks_find_path \ + _fishmarks_valid_name \ + _fishmarks_write_entries \ + _fishmarks_complete \ + _check_help \ + _valid_bookmark \ + save_bookmark \ + go_to_bookmark \ + print_bookmark \ + delete_bookmark \ + list_bookmarks + source "$fishmarks_root/functions/$function_file.fish" +end + +source "$fishmarks_root/conf.d/fishmarks.fish" diff --git a/tests/run.fish b/tests/run.fish new file mode 100755 index 0000000..14951b5 --- /dev/null +++ b/tests/run.fish @@ -0,0 +1,123 @@ +#!/usr/bin/env fish + +set -l test_root (command mktemp -d) +set -gx HOME "$test_root/home" +command mkdir -p -- "$HOME" +set -gx SDIRS "$HOME/.sdirs" +set -gx NO_FISHMARKS_COMPAT_ALIASES 1 +set -g repo_root (path resolve -- (path dirname -- (status filename))/..) + +source "$repo_root/marks.fish" + +set -g failures 0 +set -g assertions 0 + +function _assert_eq --argument-names expected actual message + set -g assertions (math $assertions + 1) + if test "$expected" != "$actual" + set_color red + printf 'FAIL: %s\n expected: %s\n actual: %s\n' "$message" "$expected" "$actual" + set_color normal + set -g failures (math $failures + 1) + end +end + +function _assert_status --argument-names expected actual message + _assert_eq "$expected" "$actual" "$message" +end + +function _prepare_dir --argument-names dir_path + command mkdir -p -- "$dir_path" + cd -- "$dir_path" +end + +function _test_save_and_print + _prepare_dir "$HOME/work/app" + save_bookmark app + _assert_status 0 $status 'save_bookmark succeeds' + + set -l location (print_bookmark app) + _assert_status 0 $status 'print_bookmark succeeds' + _assert_eq "$HOME/work/app" "$location" 'print_bookmark returns saved path' +end + +function _test_default_name_generation + _prepare_dir "$HOME/work/my-app.v2" + save_bookmark + _assert_status 0 $status 'save_bookmark without name succeeds' + + set -l location (print_bookmark my_app_v2) + _assert_status 0 $status 'default bookmark name is discoverable' + _assert_eq "$HOME/work/my-app.v2" "$location" 'default bookmark name is sanitized basename' +end + +function _test_go_to_and_delete + _prepare_dir "$HOME/projects/alpha" + save_bookmark alpha + _assert_status 0 $status 'save alpha bookmark succeeds' + + _prepare_dir "$HOME/projects/other" + go_to_bookmark alpha + _assert_status 0 $status 'go_to_bookmark succeeds for existing entry' + _assert_eq "$HOME/projects/alpha" "$PWD" 'go_to_bookmark changes current directory' + + delete_bookmark alpha + _assert_status 0 $status 'delete_bookmark succeeds for existing entry' + + print_bookmark alpha >/dev/null 2>/dev/null + _assert_status 1 $status 'deleted bookmark no longer resolves' +end + +function _test_legacy_file_compatibility + command mkdir -p -- "$HOME/legacy/location" + printf 'export DIR_legacy="\\$HOME/legacy/location"\n' > "$SDIRS" + printf 'export DIR_absolute="/tmp"\n' >> "$SDIRS" + printf 'not a bookmark line\n' >> "$SDIRS" + + set -l legacy_value (print_bookmark legacy) + _assert_status 0 $status 'legacy bookmark is parsed' + _assert_eq "$HOME/legacy/location" "$legacy_value" 'legacy value expands $HOME safely' + + set -l absolute_value (print_bookmark absolute) + _assert_status 0 $status 'absolute legacy bookmark is parsed' + _assert_eq '/tmp' "$absolute_value" 'absolute path is preserved' +end + +function _test_invalid_name_rejected + _prepare_dir "$HOME/work/invalid" + save_bookmark 'bad-name' >/dev/null 2>/dev/null + _assert_status 1 $status 'invalid bookmark names are rejected' +end + +function _test_conf_aliases + set -e NO_FISHMARKS_COMPAT_ALIASES + set -e __fishmarks_conf_loaded + source "$repo_root/conf.d/fishmarks.fish" + + type -q s + _assert_status 0 $status 'alias s is configured by conf.d script' + type -q g + _assert_status 0 $status 'alias g is configured by conf.d script' + type -q p + _assert_status 0 $status 'alias p is configured by conf.d script' + type -q d + _assert_status 0 $status 'alias d is configured by conf.d script' + type -q l + _assert_status 0 $status 'alias l is configured by conf.d script' +end + +_test_save_and_print +_test_default_name_generation +_test_go_to_and_delete +_test_legacy_file_compatibility +_test_invalid_name_rejected +_test_conf_aliases + +if test $failures -gt 0 + printf '\n%d of %d assertions failed\n' "$failures" "$assertions" + exit 1 +end + +set_color green +printf 'All %d assertions passed\n' "$assertions" +set_color normal From af01b27803c551f06932af9f1c3fd6f03e95b24b Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 18:44:00 -0500 Subject: [PATCH 02/16] Add CI and pre-commit quality checks Introduce a shared fish check runner, pre-commit/prek hooks, and a GitHub Actions workflow so local and remote validation stay aligned. Include an installer smoke test and document the development workflow in the README. --- .github/workflows/ci.yml | 30 +++++++++++++++++++++++++ .pre-commit-config.yaml | 21 +++++++++++++++++ README.md | 22 ++++++++++++++++++ functions/_fishmarks_entries.fish | 2 +- functions/_fishmarks_write_entries.fish | 2 +- install.fish | 4 ++-- marks.fish | 2 +- tests/check.fish | 28 +++++++++++++++++++++++ tests/run.fish | 10 ++++----- 9 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .pre-commit-config.yaml create mode 100755 tests/check.fish diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7d16ca4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install fish + run: | + sudo apt-get update + sudo apt-get install -y fish + + - name: Validate fish scripts + run: fish tests/check.fish + + - name: Run test suite + run: fish tests/run.fish + + - name: Smoke test installer + run: | + TEST_HOME=$(mktemp -d) + HOME="$TEST_HOME" fish install.fish + test -f "$TEST_HOME/.config/fish/conf.d/fishmarks.fish" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0bba78b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-added-large-files + + - repo: local + hooks: + - id: fish-check + name: fish syntax and formatting + entry: fish tests/check.fish + language: system + files: \.fish$ + - id: fish-tests + name: fishmarks behavior tests + entry: fish tests/run.fish + language: system + pass_filenames: false diff --git a/README.md b/README.md index e53bb2f..fd63251 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,28 @@ Run the test suite with: fish tests/run.fish ``` +Run syntax and formatting checks with: + +```fish +fish tests/check.fish +``` + +## Development workflow + +Install [prek](https://prek.j178.dev/latest/) (or pre-commit) hooks locally: + +```fish +prek install +``` + +Run all hooks on demand: + +```fish +prek run --all-files +``` + +GitHub Actions CI runs the same checks (`tests/check.fish`, `tests/run.fish`) and an installer smoke test on every push and pull request. + ### Contributing *Have you noticed any bugs or issues with fishmarks? Do you have any features you would like to see added?* diff --git a/functions/_fishmarks_entries.fish b/functions/_fishmarks_entries.fish index c985cd3..3a06d9d 100644 --- a/functions/_fishmarks_entries.fish +++ b/functions/_fishmarks_entries.fish @@ -10,5 +10,5 @@ function _fishmarks_entries set -l name "$entry[2]" set -l value (_fishmarks_decode_path "$entry[3]") printf '%s=%s\n' "$name" "$value" - end < "$SDIRS" + end <"$SDIRS" end diff --git a/functions/_fishmarks_write_entries.fish b/functions/_fishmarks_write_entries.fish index 06e5de9..56963bd 100644 --- a/functions/_fishmarks_write_entries.fish +++ b/functions/_fishmarks_write_entries.fish @@ -6,7 +6,7 @@ function _fishmarks_write_entries set -l parts (string split -m 1 '=' -- "$entry") set -l encoded_path (_fishmarks_encode_path "$parts[2]") printf 'export DIR_%s="%s"\n' "$parts[1]" "$encoded_path" - end > "$tmp_file" + end >"$tmp_file" command mv -- "$tmp_file" "$SDIRS" end diff --git a/install.fish b/install.fish index fc67431..3292dad 100644 --- a/install.fish +++ b/install.fish @@ -26,7 +26,7 @@ set -l escaped_source (string replace -- "$HOME" '$HOME' "$source_file") command mkdir -p -- "$conf_d_dir" -printf '# Load fishmarks (https://github.com/techwizrd/fishmarks)\n' > "$conf_file" -printf 'source %s\n' "$escaped_source" >> "$conf_file" +printf '# Load fishmarks (https://github.com/techwizrd/fishmarks)\n' >"$conf_file" +printf 'source %s\n' "$escaped_source" >>"$conf_file" echo "Fishmarks has been installed to $conf_file" diff --git a/marks.fish b/marks.fish index 8d8aa1c..e686f74 100644 --- a/marks.fish +++ b/marks.fish @@ -16,7 +16,7 @@ set -l fishmarks_root (path dirname -- (status filename)) -if test "$fishmarks_root" = '/'; and test -f './functions/save_bookmark.fish' +if test "$fishmarks_root" = /; and test -f './functions/save_bookmark.fish' set fishmarks_root '.' end diff --git a/tests/check.fish b/tests/check.fish new file mode 100755 index 0000000..9c51a70 --- /dev/null +++ b/tests/check.fish @@ -0,0 +1,28 @@ +#!/usr/bin/env fish + +set -l files + +if test (count $argv) -gt 0 + for file in $argv + if test -f "$file"; and string match -rq '\\.fish$' -- "$file" + set -a files "$file" + end + end +else + set files \ + marks.fish \ + install.fish \ + conf.d/*.fish \ + functions/*.fish \ + completions/*.fish \ + tests/*.fish +end + +if test (count $files) -eq 0 + exit 0 +end + +fish -n $files +or exit $status + +fish_indent --check $files diff --git a/tests/run.fish b/tests/run.fish index 14951b5..264eac8 100755 --- a/tests/run.fish +++ b/tests/run.fish @@ -70,9 +70,9 @@ end function _test_legacy_file_compatibility command mkdir -p -- "$HOME/legacy/location" - printf 'export DIR_legacy="\\$HOME/legacy/location"\n' > "$SDIRS" - printf 'export DIR_absolute="/tmp"\n' >> "$SDIRS" - printf 'not a bookmark line\n' >> "$SDIRS" + printf 'export DIR_legacy="\\$HOME/legacy/location"\n' >"$SDIRS" + printf 'export DIR_absolute="/tmp"\n' >>"$SDIRS" + printf 'not a bookmark line\n' >>"$SDIRS" set -l legacy_value (print_bookmark legacy) _assert_status 0 $status 'legacy bookmark is parsed' @@ -80,12 +80,12 @@ function _test_legacy_file_compatibility set -l absolute_value (print_bookmark absolute) _assert_status 0 $status 'absolute legacy bookmark is parsed' - _assert_eq '/tmp' "$absolute_value" 'absolute path is preserved' + _assert_eq /tmp "$absolute_value" 'absolute path is preserved' end function _test_invalid_name_rejected _prepare_dir "$HOME/work/invalid" - save_bookmark 'bad-name' >/dev/null 2>/dev/null + save_bookmark bad-name >/dev/null 2>/dev/null _assert_status 1 $status 'invalid bookmark names are rejected' end From c09deea68a12038a5cf23d9db797a525420b0786 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 19:09:00 -0500 Subject: [PATCH 03/16] Add contributor and security policy docs Document local contribution workflow, community conduct expectations, and a private vulnerability reporting process so contributors have clear standards and maintainers have a safer intake path. --- CODE_OF_CONDUCT.md | 29 +++++++++++++++++++++++++++++ CONTRIBUTING.md | 41 +++++++++++++++++++++++++++++++++++++++++ SECURITY.md | 23 +++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..12c4d80 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,29 @@ +# Code of Conduct + +## Our pledge + +We want fishmarks to be a welcoming, inclusive, and respectful community for everyone. + +## Our standards + +Examples of behavior that contributes to a positive environment: + +- Being respectful and considerate in communication +- Giving and accepting constructive feedback +- Focusing on what is best for the community and users + +Examples of unacceptable behavior: + +- Harassment or personal attacks +- Discriminatory language or conduct +- Trolling, insulting, or deliberately disruptive behavior + +## Scope + +This Code of Conduct applies in project spaces, including issues, pull requests, and discussions. + +## Enforcement + +Project maintainers are responsible for clarifying and enforcing this Code of Conduct and may remove, edit, or reject comments, commits, code, issues, and other contributions that are not aligned with it. + +To report concerns, open a private security advisory or contact the maintainers through repository administrators. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..efddca1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing to fishmarks + +Thanks for contributing. + +## Development setup + +1. Fork and clone the repository. +2. Create a feature branch from `master`. +3. Install Fish 3+. +4. Install local hooks: + +```fish +prek install +``` + +## Validate changes locally + +Run checks before committing: + +```fish +fish tests/check.fish +fish tests/run.fish +``` + +Or run all hooks: + +```fish +prek run --all-files +``` + +## Pull requests + +- Keep PRs focused and small when possible. +- Include tests for behavior changes. +- Update docs when usage or setup changes. +- Use clear commit messages that explain why the change is needed. + +## Compatibility policy + +- The project targets Fish 3+. +- Backward compatibility with existing bookmark storage (`~/.sdirs`) should be preserved unless a migration path is documented. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..625e016 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Supported versions + +Security fixes are provided for the latest release on `master`. + +## Reporting a vulnerability + +Please do not open public issues for security vulnerabilities. + +Instead: + +1. Use GitHub's private vulnerability reporting for this repository, or +2. Contact the maintainers privately through repository administrators. + +Include as much detail as possible: + +- A clear description of the issue +- Reproduction steps or proof of concept +- Potential impact +- Suggested mitigation (if known) + +We will acknowledge reports promptly and coordinate a fix and disclosure timeline. From bd19b47513533763ed8689a756f8e32bceca7168 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 19:19:00 -0500 Subject: [PATCH 04/16] Add issue and PR templates Provide structured bug report, feature request, and pull request templates to improve triage quality and keep validation expectations consistent across contributions. --- .github/ISSUE_TEMPLATE/bug_report.yml | 41 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++ .github/ISSUE_TEMPLATE/feature_request.yml | 32 +++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 22 ++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..a4a72ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,41 @@ +name: Bug report +description: Report a reproducible problem in fishmarks +title: "bug: " +labels: + - bug +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug. + - type: input + id: fish-version + attributes: + label: Fish version + placeholder: fish, version 4.0.6 + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Provide exact commands and inputs. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual behavior + validations: + required: true + - type: textarea + id: additional + attributes: + label: Additional context + description: Logs, screenshots, and environment details. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..51c918f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability report + url: https://github.com/techwizrd/fishmarks/security/advisories/new + about: Please report security issues privately. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..b42c76b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,32 @@ +name: Feature request +description: Propose an improvement for fishmarks +title: "feat: " +labels: + - enhancement +body: + - type: markdown + attributes: + value: | + Thanks for the idea. + - type: textarea + id: problem + attributes: + label: Problem statement + description: What user problem does this solve? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: Describe expected behavior and UX. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: textarea + id: context + attributes: + label: Additional context diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..dfe95c9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## Summary + +- + +## Why + +- + +## Changes + +- + +## Validation + +- [ ] `fish tests/check.fish` +- [ ] `fish tests/run.fish` + +## Checklist + +- [ ] Tests added or updated for behavior changes +- [ ] Docs updated when setup or usage changed +- [ ] No breaking changes, or migration path documented From 13cbdef9c52db92ec1f3aa870325b32608ae66ac Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 19:53:00 -0500 Subject: [PATCH 05/16] Add semver policy and changelog Introduce a Keep-a-Changelog file and document Semantic Versioning expectations in contributor and user docs so release impact is explicit and traceable. --- CHANGELOG.md | 20 ++++++++++++++++++++ CONTRIBUTING.md | 5 +++++ README.md | 4 ++++ 3 files changed, 29 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..89ac509 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Fish plugin packaging with `functions/`, `conf.d/`, and `completions/` layout. +- Automated test suite (`tests/run.fish`) and style/syntax checks (`tests/check.fish`). +- GitHub Actions CI workflow and pre-commit/prek hook configuration. +- Contributor policy docs and GitHub issue/PR templates. + +### Changed + +- Core bookmark handling refactored to fish-native parsing and safer file processing. +- Installer updated for Fish 3+ and modern startup integration via `conf.d`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index efddca1..59766cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,3 +39,8 @@ prek run --all-files - The project targets Fish 3+. - Backward compatibility with existing bookmark storage (`~/.sdirs`) should be preserved unless a migration path is documented. + +## Versioning and changelog + +- Releases follow Semantic Versioning. +- User-visible changes should be added to `CHANGELOG.md` under `Unreleased`. diff --git a/README.md b/README.md index fd63251..0d39fd3 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,10 @@ prek run --all-files GitHub Actions CI runs the same checks (`tests/check.fish`, `tests/run.fish`) and an installer smoke test on every push and pull request. +## Versioning + +fishmarks follows [Semantic Versioning](https://semver.org/). Notable changes are tracked in `CHANGELOG.md`. + ### Contributing *Have you noticed any bugs or issues with fishmarks? Do you have any features you would like to see added?* From c9892bc0170ffaba919bb2ca192a5060303d87ba Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 20:18:00 -0500 Subject: [PATCH 06/16] Add tag-driven release workflow Publish GitHub Releases automatically for version tags and attach a source tarball so users can install from a stable release artifact. --- .github/workflows/release.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ccb6202 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create source archive + run: | + git archive --format=tar.gz --output="fishmarks-${GITHUB_REF_NAME}.tar.gz" HEAD + + - name: Publish GitHub release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: fishmarks-${{ github.ref_name }}.tar.gz From 814b5299dd30241ab2aac73ec89baccd6f9cce4d Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 20:39:00 -0500 Subject: [PATCH 07/16] Run CI across multiple fish versions Execute checks and tests in a matrix of official fish container images to catch regressions across supported shell versions while preserving the installer smoke test. --- .github/workflows/ci.yml | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d16ca4..5241b33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,24 +7,43 @@ on: jobs: test: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + fish_image: + - ghcr.io/fish-shell/fish-shell:3.7.1 + - ghcr.io/fish-shell/fish-shell:4.0.6 steps: - name: Checkout uses: actions/checkout@v4 - - name: Install fish - run: | - sudo apt-get update - sudo apt-get install -y fish + - name: Show fish version + run: docker run --rm ${{ matrix.fish_image }} fish --version - name: Validate fish scripts - run: fish tests/check.fish + run: | + docker run --rm \ + -v "$PWD:/workspace" \ + -w /workspace \ + ${{ matrix.fish_image }} \ + fish tests/check.fish - name: Run test suite - run: fish tests/run.fish + run: | + docker run --rm \ + -v "$PWD:/workspace" \ + -w /workspace \ + ${{ matrix.fish_image }} \ + fish tests/run.fish - name: Smoke test installer run: | TEST_HOME=$(mktemp -d) - HOME="$TEST_HOME" fish install.fish + docker run --rm \ + -v "$PWD:/workspace" \ + -v "$TEST_HOME:/test-home" \ + -w /workspace \ + ${{ matrix.fish_image }} \ + env HOME=/test-home fish install.fish test -f "$TEST_HOME/.config/fish/conf.d/fishmarks.fish" From 6b5740ae230471a8e2fc6e112fa997a7c1670887 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 21:05:00 -0500 Subject: [PATCH 08/16] Add Dependabot for CI workflow updates Automatically track GitHub Actions dependency updates so workflow security patches and improvements are proposed regularly. --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1e2fc69 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + labels: + - dependencies + - ci From 114f1bdb1c8c056f9e802180ed259a7197f6f370 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 21:32:00 -0500 Subject: [PATCH 09/16] Add repository policy defaults Define branch protection defaults in a GitHub Settings policy file and document how to apply the same rules manually when the Settings app is unavailable. --- .github/settings.yml | 18 ++++++++++++++++++ CONTRIBUTING.md | 5 +++++ 2 files changed, 23 insertions(+) create mode 100644 .github/settings.yml diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..f286354 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,18 @@ +repository: + default_branch: master + delete_branch_on_merge: true + +branches: + - name: master + protection: + enforce_admins: true + required_pull_request_reviews: + required_approving_review_count: 1 + dismiss_stale_reviews: true + require_code_owner_reviews: false + required_status_checks: + strict: true + contexts: + - test (ghcr.io/fish-shell/fish-shell:3.7.1) + - test (ghcr.io/fish-shell/fish-shell:4.0.6) + restrictions: null diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59766cb..4a3071c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,3 +44,8 @@ prek run --all-files - Releases follow Semantic Versioning. - User-visible changes should be added to `CHANGELOG.md` under `Unreleased`. + +## Repository automation + +- `.github/settings.yml` defines branch protection defaults for repositories using the GitHub Settings app. +- If Settings app is not installed, apply equivalent branch protection rules manually in repository settings. From 0d72b65aef3b63b57e85f79855041870ec4f26bd Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 21:55:00 -0500 Subject: [PATCH 10/16] Harden bookmark escaping for bashmarks compatibility Escape and unescape shell-sensitive path characters when writing and reading ~/.sdirs so fishmarks remains interoperable with bashmarks for complex directory names. Add regression tests for special-character round-tripping and stored export line encoding. --- functions/_fishmarks_decode_path.fish | 6 +++++- functions/_fishmarks_encode_path.fish | 7 ++++++- tests/run.fish | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/functions/_fishmarks_decode_path.fish b/functions/_fishmarks_decode_path.fish index 7136bf9..fa5e4eb 100644 --- a/functions/_fishmarks_decode_path.fish +++ b/functions/_fishmarks_decode_path.fish @@ -11,5 +11,9 @@ function _fishmarks_decode_path --argument-names raw_value end set value (string replace -a -- '\$HOME' "$HOME" "$value") - string replace -a -- '$HOME' "$HOME" "$value" + set value (string replace -a -- '$HOME' "$HOME" "$value") + set value (string replace -a -- '\`' '`' "$value") + set value (string replace -a -- '\$' '$' "$value") + set value (string replace -a -- '\"' '"' "$value") + string replace -a -- "\\\\" "\\" "$value" end diff --git a/functions/_fishmarks_encode_path.fish b/functions/_fishmarks_encode_path.fish index b14794c..baf6939 100644 --- a/functions/_fishmarks_encode_path.fish +++ b/functions/_fishmarks_encode_path.fish @@ -1,4 +1,9 @@ function _fishmarks_encode_path --argument-names path_value set -l escaped_home (string escape --style=regex -- "$HOME") - string replace -r -- "^$escaped_home" '\$HOME' "$path_value" + set -l value (string replace -r -- "^$escaped_home" '__FISHMARKS_HOME_PREFIX__' "$path_value") + set value (string replace -a -- '\\' '\\\\' "$value") + set value (string replace -a -- '"' '\\"' "$value") + set value (string replace -a -- '$' '\\$' "$value") + set value (string replace -a -- '`' '\\`' "$value") + string replace -a -- __FISHMARKS_HOME_PREFIX__ '\$HOME' "$value" end diff --git a/tests/run.fish b/tests/run.fish index 264eac8..e5da546 100755 --- a/tests/run.fish +++ b/tests/run.fish @@ -89,6 +89,29 @@ function _test_invalid_name_rejected _assert_status 1 $status 'invalid bookmark names are rejected' end +function _test_shell_escaped_paths + set -l special_path "$HOME/work/special \"q\" \$d\\b" + _prepare_dir "$special_path" + save_bookmark specialchars + _assert_status 0 $status 'save_bookmark supports special shell characters' + + set -l location (print_bookmark specialchars) + _assert_status 0 $status 'print_bookmark resolves special shell characters' + _assert_eq "$special_path" "$location" 'special shell characters round-trip correctly' + + set -l stored_line + while read -l line + if string match -rq '^export DIR_specialchars=' -- "$line" + set stored_line "$line" + break + end + end <"$SDIRS" + + set -l expected_encoded (_fishmarks_encode_path "$special_path") + set -l expected_line "export DIR_specialchars=\"$expected_encoded\"" + _assert_eq "$expected_line" "$stored_line" 'bookmark file stores escaped shell-safe path' +end + function _test_conf_aliases set -e NO_FISHMARKS_COMPAT_ALIASES set -e __fishmarks_conf_loaded @@ -111,6 +134,7 @@ _test_default_name_generation _test_go_to_and_delete _test_legacy_file_compatibility _test_invalid_name_rejected +_test_shell_escaped_paths _test_conf_aliases if test $failures -gt 0 From 0cf7bda8c1ecfc5a354bd3f8d95b51937de4bf17 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 22:10:00 -0500 Subject: [PATCH 11/16] Tighten install, parsing, and docs hygiene Fix installer source-path quoting and harden decode behavior for safer /home/kunal handling, then strengthen escape regression assertions to avoid implementation-coupled expectations. Clean README quality issues, defer contribution details to CONTRIBUTING.md, and remove the unused GitHub Settings app policy file. --- .github/settings.yml | 18 ------------------ CONTRIBUTING.md | 5 ----- README.md | 18 +++++++----------- functions/_fishmarks_decode_path.fish | 4 ++-- install.fish | 3 ++- tests/run.fish | 21 ++++++++++++++++++--- 6 files changed, 29 insertions(+), 40 deletions(-) delete mode 100644 .github/settings.yml diff --git a/.github/settings.yml b/.github/settings.yml deleted file mode 100644 index f286354..0000000 --- a/.github/settings.yml +++ /dev/null @@ -1,18 +0,0 @@ -repository: - default_branch: master - delete_branch_on_merge: true - -branches: - - name: master - protection: - enforce_admins: true - required_pull_request_reviews: - required_approving_review_count: 1 - dismiss_stale_reviews: true - require_code_owner_reviews: false - required_status_checks: - strict: true - contexts: - - test (ghcr.io/fish-shell/fish-shell:3.7.1) - - test (ghcr.io/fish-shell/fish-shell:4.0.6) - restrictions: null diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4a3071c..59766cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,8 +44,3 @@ prek run --all-files - Releases follow Semantic Versioning. - User-visible changes should be added to `CHANGELOG.md` under `Unreleased`. - -## Repository automation - -- `.github/settings.yml` defines branch protection defaults for repositories using the GitHub Settings app. -- If Settings app is not installed, apply equivalent branch protection rules manually in repository settings. diff --git a/README.md b/README.md index 0d39fd3..ecba7e5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # fishmarks +[![CI](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml) +[![Latest Release](https://img.shields.io/github/v/release/techwizrd/fishmarks)](https://github.com/techwizrd/fishmarks/releases) +[![License](https://img.shields.io/github/license/techwizrd/fishmarks)](LICENSE) +[![Fish 3+](https://img.shields.io/badge/fish-3%2B-4AAE46)](https://fishshell.com/) + Fishmarks is a clone of [bashmarks](https://github.com/huyng/bashmarks) for the [Fish shell](https://fishshell.com/). Fishmarks is compatible with your existing bashmarks and bookmarks added using fishmarks are also available in bashmarks. @@ -69,7 +74,7 @@ s - Saves the current directory as "bookmark_name" g - Goes (cd) to the directory associated with "bookmark_name" p - Prints the directory associated with "bookmark_name" d - Deletes the bookmark -l - Lists all available bookmarks' +l - Lists all available bookmarks ``` ### Configuration Variables @@ -128,16 +133,7 @@ fishmarks follows [Semantic Versioning](https://semver.org/). Notable changes ar ### Contributing -*Have you noticed any bugs or issues with fishmarks? Do you have any features you would like to see added?* - -1. [Check on Github](https://github.com/techwizrd/fishmarks/issues?state=open) to see whether anyone else has encountered the same issue or has the same feature request. If someone someone has encountered the same issue or has the same feature request, comment to let me know that it affects you too. -2. If no one has encountered the same issue, [file an issue](https://github.com/techwizrd/fishmarks/issues?state=open) on Github with the "bug" label, your operating system version, fish shell version, clear steps describing how to reproduce the error. If no one has requested the same feature, [file an issue](https://github.com/techwizrd/fishmarks/issues?state=open) on Github with the "enhancement" label and a brief, clear description of your feature and why it would make a great addition to fishmarks. -3. Once you have filed the issue, if you would like to fix the issue or add the feature yourself, assign the issue to yourself on Github and fork the repository. Clone your fork and make your changes, commit them, and push them to Github. After you have pushed all your changes to Github, send me a merge request and comment on the issue to let me know that your merge request fixes the bug or adds the requested feature. Please make sure to write good commit messages and keep your history clean and understandable. Good commit messages and clean history make it easier for me to merge your changes and keep the history nice and useful. - -I recommend the following guides on writing good commit messages: -- [GIT Commit Good Practice](https://wiki.openstack.org/wiki/GitCommitMessages) -- [A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) -- [Proper Git Commit Messages and an Elegant Git History](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) +See `CONTRIBUTING.md` for setup, validation commands, and pull request guidelines. ## License diff --git a/functions/_fishmarks_decode_path.fish b/functions/_fishmarks_decode_path.fish index fa5e4eb..edc8522 100644 --- a/functions/_fishmarks_decode_path.fish +++ b/functions/_fishmarks_decode_path.fish @@ -4,14 +4,14 @@ function _fishmarks_decode_path --argument-names raw_value if test (count $quote_match) -gt 1 set value "$quote_match[2]" else - set quote_match (string match -r "^'(.*)'" -- "$value") + set quote_match (string match -r "^'(.*)'\$" -- "$value") if test (count $quote_match) -gt 1 set value "$quote_match[2]" end end set value (string replace -a -- '\$HOME' "$HOME" "$value") - set value (string replace -a -- '$HOME' "$HOME" "$value") + set value (string replace -r -- '^\$HOME' "$HOME" "$value") set value (string replace -a -- '\`' '`' "$value") set value (string replace -a -- '\$' '$' "$value") set value (string replace -a -- '\"' '"' "$value") diff --git a/install.fish b/install.fish index 3292dad..8b5630b 100644 --- a/install.fish +++ b/install.fish @@ -23,10 +23,11 @@ end set -l conf_d_dir "$HOME/.config/fish/conf.d" set -l conf_file "$conf_d_dir/fishmarks.fish" set -l escaped_source (string replace -- "$HOME" '$HOME' "$source_file") +set -l source_token (string escape --style=script -- "$escaped_source") command mkdir -p -- "$conf_d_dir" printf '# Load fishmarks (https://github.com/techwizrd/fishmarks)\n' >"$conf_file" -printf 'source %s\n' "$escaped_source" >>"$conf_file" +printf 'source %s\n' "$source_token" >>"$conf_file" echo "Fishmarks has been installed to $conf_file" diff --git a/tests/run.fish b/tests/run.fish index e5da546..3f46821 100755 --- a/tests/run.fish +++ b/tests/run.fish @@ -26,6 +26,10 @@ function _assert_status --argument-names expected actual message _assert_eq "$expected" "$actual" "$message" end +function _assert_true --argument-names status_value message + _assert_status 0 "$status_value" "$message" +end + function _prepare_dir --argument-names dir_path command mkdir -p -- "$dir_path" cd -- "$dir_path" @@ -107,9 +111,20 @@ function _test_shell_escaped_paths end end <"$SDIRS" - set -l expected_encoded (_fishmarks_encode_path "$special_path") - set -l expected_line "export DIR_specialchars=\"$expected_encoded\"" - _assert_eq "$expected_line" "$stored_line" 'bookmark file stores escaped shell-safe path' + string match -q 'export DIR_specialchars="*"' -- "$stored_line" + _assert_true $status 'bookmark line uses export DIR_="..." format' + + string match -q '*\$HOME/work/special*' -- "$stored_line" + _assert_true $status 'bookmark line preserves $HOME prefix' + + string match -q '*\\"q\\"*' -- "$stored_line" + _assert_true $status 'bookmark line escapes double quotes' + + string match -q '*\\$d*' -- "$stored_line" + _assert_true $status 'bookmark line escapes dollar signs' + + string match -q '*\\\\b*' -- "$stored_line" + _assert_true $status 'bookmark line escapes backslashes' end function _test_conf_aliases From 51932fbf35ebfb8c7c90333971f97d46f7850717 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 22:34:00 -0500 Subject: [PATCH 12/16] Update copyright year range Use a 2013-present copyright range in the loader header and README license section to reflect continued maintenance. --- README.md | 2 +- marks.fish | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ecba7e5..18e6dcc 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ See `CONTRIBUTING.md` for setup, validation commands, and pull request guideline ## License -Copyright 2013 Kunal Sarkhel +Copyright 2013-present Kunal Sarkhel Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the diff --git a/marks.fish b/marks.fish index e686f74..1466e45 100644 --- a/marks.fish +++ b/marks.fish @@ -1,4 +1,4 @@ -# Copyright 2013 Kunal Sarkhel +# Copyright 2013-present Kunal Sarkhel # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy From c3a86acfd494653dad036b9779f421aab0376723 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 22:58:00 -0500 Subject: [PATCH 13/16] Add version command and tighten path safety Introduce a fishmarks_version command and document the plugin-first layout while keeping marks.fish compatibility loading. Reject unsupported newline/carriage-return paths in save_bookmark, add regression coverage, and add make-based check/test shortcuts for contributors. --- CHANGELOG.md | 2 ++ CONTRIBUTING.md | 6 ++++++ Makefile | 7 +++++++ README.md | 16 ++++++++++++++++ functions/_fishmarks_path_supported.fish | 11 +++++++++++ functions/fishmarks_version.fish | 3 +++ functions/save_bookmark.fish | 5 +++++ marks.fish | 2 ++ tests/run.fish | 19 +++++++++++++++++++ 9 files changed, 71 insertions(+) create mode 100644 Makefile create mode 100644 functions/_fishmarks_path_supported.fish create mode 100644 functions/fishmarks_version.fish diff --git a/CHANGELOG.md b/CHANGELOG.md index 89ac509..2cac49e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,10 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht - Automated test suite (`tests/run.fish`) and style/syntax checks (`tests/check.fish`). - GitHub Actions CI workflow and pre-commit/prek hook configuration. - Contributor policy docs and GitHub issue/PR templates. +- `fishmarks_version` command to report plugin version. ### Changed - Core bookmark handling refactored to fish-native parsing and safer file processing. - Installer updated for Fish 3+ and modern startup integration via `conf.d`. +- `save_bookmark` now rejects paths containing newline or carriage-return characters. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59766cb..3a2208a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,12 @@ fish tests/check.fish fish tests/run.fish ``` +Shortcut: + +```sh +make test +``` + Or run all hooks: ```fish diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..057875a --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +.PHONY: check test + +check: + fish tests/check.fish + +test: check + fish tests/run.fish diff --git a/README.md b/README.md index 18e6dcc..38001b8 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,9 @@ If you use [Fisher](https://github.com/jorgebucaran/fisher), install fishmarks w fisher install techwizrd/fishmarks ``` +Fish plugin managers load fishmarks from `functions/`, `conf.d/`, and `completions/`. +The top-level `marks.fish` file remains as a compatibility loader for existing manual installs. + ### Manual Installation To install fishmarks manually: @@ -75,6 +78,7 @@ g - Goes (cd) to the directory associated with "bookmark_name" p - Prints the directory associated with "bookmark_name" d - Deletes the bookmark l - Lists all available bookmarks +fishmarks_version - Prints the installed fishmarks version ``` ### Configuration Variables @@ -111,6 +115,18 @@ Run syntax and formatting checks with: fish tests/check.fish ``` +Or with make: + +```sh +make check +``` + +Run all checks and tests with: + +```sh +make test +``` + ## Development workflow Install [prek](https://prek.j178.dev/latest/) (or pre-commit) hooks locally: diff --git a/functions/_fishmarks_path_supported.fish b/functions/_fishmarks_path_supported.fish new file mode 100644 index 0000000..202467d --- /dev/null +++ b/functions/_fishmarks_path_supported.fish @@ -0,0 +1,11 @@ +function _fishmarks_path_supported --argument-names path_value + if string match -q "*\n*" -- "$path_value" + return 1 + end + + if string match -q "*\r*" -- "$path_value" + return 1 + end + + return 0 +end diff --git a/functions/fishmarks_version.fish b/functions/fishmarks_version.fish new file mode 100644 index 0000000..5d19468 --- /dev/null +++ b/functions/fishmarks_version.fish @@ -0,0 +1,3 @@ +function fishmarks_version --description "Print fishmarks version" + printf '%s\n' '0.2.0-dev' +end diff --git a/functions/save_bookmark.fish b/functions/save_bookmark.fish index 1a0ad9c..4380267 100644 --- a/functions/save_bookmark.fish +++ b/functions/save_bookmark.fish @@ -9,6 +9,11 @@ function save_bookmark --description "Save the current directory as a bookmark" return 1 end + if not _fishmarks_path_supported "$PWD" + _fishmarks_print_error 'Current directory path contains unsupported newline or carriage-return characters.' + return 1 + end + set -l updated_entries for entry in (_fishmarks_entries) set -l parts (string split -m 1 '=' -- "$entry") diff --git a/marks.fish b/marks.fish index 1466e45..e8377ba 100644 --- a/marks.fish +++ b/marks.fish @@ -32,8 +32,10 @@ for function_file in \ _fishmarks_entries \ _fishmarks_find_path \ _fishmarks_valid_name \ + _fishmarks_path_supported \ _fishmarks_write_entries \ _fishmarks_complete \ + fishmarks_version \ _check_help \ _valid_bookmark \ save_bookmark \ diff --git a/tests/run.fish b/tests/run.fish index 3f46821..613ef2d 100755 --- a/tests/run.fish +++ b/tests/run.fish @@ -127,6 +127,23 @@ function _test_shell_escaped_paths _assert_true $status 'bookmark line escapes backslashes' end +function _test_rejects_newline_paths + set -l unsupported_path "$HOME/work/bad\nname" + _prepare_dir "$unsupported_path" + save_bookmark newlinepath >/dev/null 2>/dev/null + _assert_status 1 $status 'save_bookmark rejects directories containing newlines' + + print_bookmark newlinepath >/dev/null 2>/dev/null + _assert_status 1 $status 'rejected newline path bookmark is not written' +end + +function _test_version_command + set -l version_output (fishmarks_version) + _assert_status 0 $status 'fishmarks_version command succeeds' + string match -rq '^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?$' -- "$version_output" + _assert_true $status 'fishmarks_version returns semver-like output' +end + function _test_conf_aliases set -e NO_FISHMARKS_COMPAT_ALIASES set -e __fishmarks_conf_loaded @@ -150,6 +167,8 @@ _test_go_to_and_delete _test_legacy_file_compatibility _test_invalid_name_rejected _test_shell_escaped_paths +_test_rejects_newline_paths +_test_version_command _test_conf_aliases if test $failures -gt 0 From 216bff67fc4efc38a1ba29d187b428b91c7d1628 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 23:38:03 -0500 Subject: [PATCH 14/16] Use fish-actions and fisher smoke test in CI Replace container-based fish execution with GitHub runner best-practice setup using fish-actions/install-fish, run checks on ubuntu and macOS, and add a fisher installation smoke test to verify plugin-manager compatibility. --- .github/workflows/ci.yml | 58 ++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5241b33..e31c497 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,44 +6,56 @@ on: jobs: test: - runs-on: ubuntu-latest + name: test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - fish_image: - - ghcr.io/fish-shell/fish-shell:3.7.1 - - ghcr.io/fish-shell/fish-shell:4.0.6 + os: + - ubuntu-latest + - macos-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Show fish version - run: docker run --rm ${{ matrix.fish_image }} fish --version + uses: fish-actions/install-fish@v1 + + - name: Print fish version + run: fish --version - name: Validate fish scripts - run: | - docker run --rm \ - -v "$PWD:/workspace" \ - -w /workspace \ - ${{ matrix.fish_image }} \ - fish tests/check.fish + run: fish tests/check.fish - name: Run test suite - run: | - docker run --rm \ - -v "$PWD:/workspace" \ - -w /workspace \ - ${{ matrix.fish_image }} \ - fish tests/run.fish + run: fish tests/run.fish - name: Smoke test installer + if: matrix.os == 'ubuntu-latest' run: | TEST_HOME=$(mktemp -d) - docker run --rm \ - -v "$PWD:/workspace" \ - -v "$TEST_HOME:/test-home" \ - -w /workspace \ - ${{ matrix.fish_image }} \ - env HOME=/test-home fish install.fish + HOME="$TEST_HOME" fish install.fish test -f "$TEST_HOME/.config/fish/conf.d/fishmarks.fish" + + fisher-smoke: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install fish + uses: fish-actions/install-fish@v1 + + - name: Install plugin with fisher + uses: fish-actions/fisher@v1 + with: + plugins: $GITHUB_WORKSPACE + + - name: Verify plugin commands load + run: | + type -q save_bookmark + type -q go_to_bookmark + type -q fishmarks_version + shell: fish {0} From b1cf20d8cf73fc29e30ff2a993935f44ac387f0b Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 23:53:32 -0500 Subject: [PATCH 15/16] Fix README badges for current branch Point the CI badge at the active branch status and replace unstable release/license badges with reliable semver and Apache 2.0 badges. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 38001b8..70d14be 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # fishmarks -[![CI](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml) -[![Latest Release](https://img.shields.io/github/v/release/techwizrd/fishmarks)](https://github.com/techwizrd/fishmarks/releases) -[![License](https://img.shields.io/github/license/techwizrd/fishmarks)](LICENSE) +[![CI](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml/badge.svg?branch=fish-modernization)](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml?query=branch%3Afish-modernization) +[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) +[![SemVer](https://img.shields.io/badge/versioning-semver-3f4551)](https://semver.org/) [![Fish 3+](https://img.shields.io/badge/fish-3%2B-4AAE46)](https://fishshell.com/) Fishmarks is a clone of [bashmarks](https://github.com/huyng/bashmarks) for the From f81416dbb60032c3b1d628f579b27d48f39aa074 Mon Sep 17 00:00:00 2001 From: "Kunal K. Sarkhel" Date: Wed, 4 Mar 2026 23:56:00 -0500 Subject: [PATCH 16/16] Track default-branch CI badge Point the README CI badge back to master so it reflects default-branch workflow health instead of the feature branch. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70d14be..432e96f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # fishmarks -[![CI](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml/badge.svg?branch=fish-modernization)](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml?query=branch%3Afish-modernization) +[![CI](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/techwizrd/fishmarks/actions/workflows/ci.yml?query=branch%3Amaster) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![SemVer](https://img.shields.io/badge/versioning-semver-3f4551)](https://semver.org/) [![Fish 3+](https://img.shields.io/badge/fish-3%2B-4AAE46)](https://fishshell.com/)