Add new Zsh parser to replace Awk parser

This commit is contained in:
mattmc3 2025-04-12 17:39:57 -04:00
parent 839e7621b6
commit 112f982589
No known key found for this signature in database
GPG Key ID: B2B915D8A8706013
5 changed files with 323 additions and 35 deletions

View File

@ -3,39 +3,53 @@
### Parse antidote's bundle DSL.
#function __antidote_parse_bundles {
emulate -L zsh; setopt local_options $_adote_funcopts
$__adote_awkcmd '
BEGIN { RS="[\r\n]" }
# skip comments and empty lines
/^ *$/ || /^ *#/ {next}
# Declare vars
local bundle_str bundle_repr collected_input err lineno=0
local key val
local -a bundles
local -A bundle
# strip trailing comments
{ sub(/[ \t]#.*$/,"",$0) }
# Get piped/passed bundles
collected_input="$(__antidote_collect_input "$@")"
if [[ -n "$collected_input" ]]; then
bundles=( "${(@f)collected_input}" )
else
bundles=()
fi
if ! (( $#bundles )) ; then
print -ru2 -- "antidote: error: bundle argument expected"
return 1
fi
# escape leading $ variables
{ sub(/^\$/,"\\$",$0) }
for bundle_str in $bundles; do
(( lineno += 1 ))
# handle extension functionality (eg :use ohmyzsh)
$1~/^:/ {
sub(/^:/,"",$1)
printf "antidote-script-" $1
for (i=2; i<=NF; i++) {
printf " %s",$i
}
printf "\n"
next
}
# Parse the bundle.
bundle_repr=$(__antidote_parser "$bundle_str"); err=$?
if [[ -z "$bundle_repr" ]]; then
continue
elif [[ "$err" -ne 0 ]]; then
print -ru2 -- "antidote: Bundle parser error on line ${lineno}: '$bundle_str'"
return 1
fi
# Turn the typeset repr into the bundle assoc_arr
eval "$bundle_repr"
# move flags to front and call antidote-script
{
sub(/ #.*$/,"",$0)
printf "antidote-script"
for (i=2; i<=NF; i++) {
sub(/^/,"--",$i)
sub(/:/," ",$i)
printf " %s",$i
}
printf " %s\n",$1
}
' "$@"
print -rn -- "antidote-script"
for key in ${(ok)bundle}; do
[[ "$key" != name ]] && [[ "$key" != '_'* ]] || continue
val="${bundle[$key]}"
printf ' --%s %s' $key $val
done
# Escape leading $ variables
if [[ "${bundle[name]}" == '$'* ]]; then
printf ' \$%s\n' "${bundle[name]#\$}"
else
printf ' %s\n' "${bundle[name]}"
fi
done
#}

123
functions/__antidote_parser Normal file
View File

@ -0,0 +1,123 @@
#!/bin/zsh
### Parse antidote's bundle DSL to an associative array.
# Example:
# __antidote_parser 'foo/bar path:plugins/baz kind:fpath pre:myprecmd # comment'
# typeset -A bundle=( [kind]=fpath [path]=plugins/baz [pre]=myprecmd [name]=foo/bar )
#
# Notes:
# bundle_str : antidote DSL syntax
# bundle : assoc array representation
# bundle_repr : Zsh serialization of the bundle assoc arrary
#
# Metadata:
# _repodir : The clone destination dir
# _type : The type of bundle (url, repo, path, ?)
# _repo : The user/repo short form of the URL
# _url : The git repo URL
#
#function __antidote_parser {
emulate -L zsh; setopt local_options $_adote_funcopts
local MATCH MBEGIN MEND; local -a match mbegin mend # appease 'warn_create_global'
local bundle_str bundle_var bundle_repr gitsite str pair key value
local -a kvpairs parts
local -A bundle
bundle_str="$1"
bundle_var="${2:-bundle}"
# Allow the user to override the default git site if they really want to
zstyle -s ':antidote:gitremote' url 'gitsite' \
|| gitsite='https://github.com'
gitsite="${gitsite%/}"
# Remove anything after the first '#'
bundle_str=${bundle_str%%\#*}
# Trim spaces
bundle_str=${${bundle_str/#[[:space:]]#}/%[[:space:]]#}
# Skip empty bundle strings
[[ -z "$bundle_str" ]] && return 0
# 1st field gets a 'name:' prefix so we can treat everything as key:val pairs
bundle_str="name:${bundle_str}"
# Split line into key-value pairs with quoting
kvpairs=(${(Q)${(z)bundle_str}})
for pair in "${kvpairs[@]}"; do
key=${pair%%:*} # Extract key (before first ':')
if [[ "$pair" == *:* ]]; then
value=${pair#*:} # Extract value (after first ':')
else
value=
fi
bundle[$key]=$value
done
# Enhance the bundle with metadata fields. Metadata fields begin with an underscore
# since those will never be part of the DSL. Let's start with _type, which tells us
# whether the bundle is a URL, a user/repo, or a path
if [[ "$bundle[name]" == *://*/*/* || "$bundle[name]" == (ssh|git)@*:*/* ]]; then
if [[ "$bundle[name]" == *://*/*/*/* || "$bundle[name]" == *@*:*/*/* ]]; then
bundle[_type]="?"
else
bundle[_type]="url"
fi
elif [[ "$bundle[name]" == *('@'|':')* ]] ; then
bundle[_type]="?" # bad URLs
elif [[ "$bundle[name]" == ('~'|'$'|'.')* ]]; then
bundle[_type]="path"
elif [[ "$bundle[name]" == */* && "$bundle[name]" != */*/* ]]; then
bundle[_type]="repo"
elif [[ "$bundle[name]" == */* ]]; then
bundle[_type]="path"
else
bundle[_type]="?"
fi
# For git repos, we add a metadata field for the URL
if [[ "$bundle[_type]" == url ]]; then
str="$bundle[name]"
str=${str%.git}
str=${str:gs/\:/\/}
parts=( ${(ps./.)str} )
if [[ $#parts -gt 1 ]]; then
bundle[_repo]="${parts[-2]}/${parts[-1]}"
else
bundle[_repo]="$str"
fi
bundle[_url]="$bundle[name]"
elif [[ "$bundle[_type]" == repo ]]; then
bundle[_repo]="${bundle[name]}"
bundle[_url]="${gitsite}/${bundle[name]}"
fi
# If there's a git URL, we also need to set the _repodir
if [[ -v bundle[_url] ]]; then
# TODO: Remove for antidote 2.0
if zstyle -t ':antidote:compatibility-mode' 'antibody' || ! zstyle -t ':antidote:bundle' use-friendly-names; then
# sanitize URL for safe use as a dir name
# ex: $ANTIDOTE_HOME/https-COLON--SLASH--SLASH-github.com-SLASH-zsh-users-SLASH-zsh-autosuggestions
str="$bundle[_url]"
str=${str%.git}
str=${str:gs/\@/-AT-}
str=${str:gs/\:/-COLON-}
str=${str:gs/\//-SLASH-}
bundle[_repodir]="$str"
else
bundle[_repodir]="$bundle[_repo]"
fi
fi
# Print the parsed bundle assoc arr using whatever bundle_var the user wants
bundle_repr="$(declare -p bundle)"
bundle_repr="typeset -A ${bundle_var}=${bundle_repr#*=}"
# Sanity check that I probably don't need.
if [[ ! "$bundle_repr" =~ "^typeset\ -A\ ${bundle_var}=" ]]; then
print -ru2 -- "antidote: Unable to parse bundle string: '$bundle_str'."
return 1
fi
# Return/print the result.
typeset -g REPLY="$bundle_repr"
print -r -- "$REPLY"
#}

View File

@ -89,13 +89,13 @@ antidote-script foo/bar
```zsh
% echo 'https://github.com/foo/bar path:lib branch:dev' | __antidote_parse_bundles
antidote-script --path lib --branch dev https://github.com/foo/bar
antidote-script --branch dev --path lib https://github.com/foo/bar
% echo 'git@github.com:foo/bar.git kind:clone branch:main' | __antidote_parse_bundles
antidote-script --kind clone --branch main git@github.com:foo/bar.git
antidote-script --branch main --kind clone git@github.com:foo/bar.git
% echo 'foo/bar kind:fpath abc:xyz' | __antidote_parse_bundles
antidote-script --kind fpath --abc xyz foo/bar
antidote-script --abc xyz --kind fpath foo/bar
% echo 'foo/bar path:plugins/myplugin kind:path # trailing comment' | __antidote_parse_bundles
antidote-script --path plugins/myplugin --kind path foo/bar
antidote-script --kind path --path plugins/myplugin foo/bar
%
```
@ -111,7 +111,7 @@ antidote-script --kind path foo/bar
The bundle parser is an awk script that turns the bundle DSL into antidote-script statements.
```zsh
% __antidote_parse_bundles $ZDOTDIR/.zsh_plugins.txt
% __antidote_parse_bundles < $ZDOTDIR/.zsh_plugins.txt
antidote-script ~/foo/bar
antidote-script --path plugins/myplugin \$ZSH_CUSTOM
antidote-script foo/bar
@ -122,7 +122,7 @@ antidote-script --kind fpath foo/bar
antidote-script --kind path foo/bar
antidote-script --path lib ohmy/ohmy
antidote-script --path plugins/extract ohmy/ohmy
antidote-script --path plugins/magic-enter --kind defer ohmy/ohmy
antidote-script --kind defer --path plugins/magic-enter ohmy/ohmy
antidote-script --path custom/themes/pretty.zsh-theme ohmy/ohmy
%
```

View File

@ -14,6 +14,7 @@
```zsh
% antidote-script #=> --exit 1
% antidote-script
antidote: error: bundle argument expected
%
```

150
tests/test_parser.md Normal file
View File

@ -0,0 +1,150 @@
# antidote bundle parser tests
## Setup
```zsh
% source ./tests/__init__.zsh
% t_setup
% zstyle ':antidote:gitremote' url 'https://fakegitsite.com/'
%
```
## Test bundle parser associative arrays
The bundle parser takes the antidote bundle format and returns an associative array
from the results of `declare -p parsed_bundle`
Test empty:
```zsh
% __antidote_parser
% __antidote_parser '# This is a full line comment'
%
```
Test assoc array for repo
```zsh
% __antidote_parser 'foo/bar' | print_aarr
$assoc_arr : bundle
_repo : foo/bar
_repodir : foo/bar
_type : repo
_url : https://fakegitsite.com/foo/bar
name : foo/bar
%
```
Test assoc array for repo in compatibility mode
```zsh
% zstyle ':antidote:bundle' use-friendly-names off
% __antidote_parser 'foo/bar' | print_aarr
$assoc_arr : bundle
_repo : foo/bar
_repodir : https-COLON--SLASH--SLASH-fakegitsite.com-SLASH-foo-SLASH-bar
_type : repo
_url : https://fakegitsite.com/foo/bar
name : foo/bar
% zstyle ':antidote:bundle' use-friendly-names on
%
```
Test assoc array for path
```zsh
% __antidote_parser '$ZSH_CUSTOM/foo' 'mybundle' | print_aarr
$assoc_arr : mybundle
_type : path
name : $ZSH_CUSTOM/foo
%
```
Test assoc array for jibberish
```zsh
% __antidote_parser 'a b c d:e:f' | print_aarr
$assoc_arr : bundle
_type : ?
b :
c :
d : e:f
name : a
% __antidote_parser 'foo bar:baz' | print_aarr
$assoc_arr : bundle
_type : ?
bar : baz
name : foo
%
```
Test assoc array for everything
```zsh
% __antidote_parser 'foo/bar branch:baz kind:zsh path:plugins/baz pre:precmd post:"post cmd"' | print_aarr
$assoc_arr : bundle
_repo : foo/bar
_repodir : foo/bar
_type : repo
_url : https://fakegitsite.com/foo/bar
branch : baz
kind : zsh
name : foo/bar
path : plugins/baz
post : post cmd
pre : precmd
%
```
## Test specific keys have known values
Test name:
```zsh
% __antidote_parser 'foo/bar' | aarr_val name
foo/bar
%
```
Test \_type:
```zsh
% __antidote_parser 'foo/bar' | aarr_val _type
repo
% __antidote_parser 'https://github.com/foo/bar' | aarr_val _type
url
% __antidote_parser 'git@bitbucket.org:foo/bar' | aarr_val _type
url
% __antidote_parser '$foo/bar' | aarr_val _type
path
% __antidote_parser '$foo/bar/baz.zsh' | aarr_val _type
path
% __antidote_parser '~foo/bar' | aarr_val _type
path
% __antidote_parser '~/foo' | aarr_val _type
path
% __antidote_parser './foo.zsh' | aarr_val _type
path
% __antidote_parser '../foo.zsh' | aarr_val _type
path
% __antidote_parser 'foo/bar/' | aarr_val _type
path
% __antidote_parser 'foo:bar' | aarr_val _type
?
% __antidote_parser 'bad@gitsite.com/foo/bar' | aarr_val _type
?
% __antidote_parser 'http:/badsite.com/foo/bar' | aarr_val _type
?
% __antidote_parser 'https://badsite.com/foo/bar/baz' | aarr_val _type
?
% __antidote_parser 'https://badsite.com/foo' | aarr_val _type
?
%
```
## Teardown
```zsh
% t_teardown
%
```