Skip to content

Commit

Permalink
WIP: config-helper: improved strategy for multi instances
Browse files Browse the repository at this point in the history
- replace rg/sd/teip combo with new `echo-regexp` that uses deno
- use new `fs-diff` for showing diffs based on multi strategy
- add new styling
- enable deno in vscode

todos:

- fix tests
- remove lingering sd/rg/teip references
  • Loading branch information
balupton committed Sep 3, 2024
1 parent be4b3ce commit ada62e0
Show file tree
Hide file tree
Showing 12 changed files with 508 additions and 74 deletions.
3 changes: 2 additions & 1 deletion .vscode/workspace.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"Xonsh",
"Zsh",
"Bevry"
]
],
"deno.enable": true
}
}
98 changes: 53 additions & 45 deletions commands/config-helper
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function config_helper_test() (
sleep "$delay"
eval-tester --name='test-invalid-find-and-replace-arguments-01' --status=22 --ignore-stderr \
-- eval-no-color -- config-helper --no-quiet --file="$test_file" -- --replace='one'
# ^ fails completely
sleep "$delay"
eval-tester --name='test-invalid-find-and-replace-arguments-02' --status=22 --ignore-stderr \
-- eval-no-color -- config-helper --no-quiet --file="$test_file" -- --find='a' --replace='one' --replace='two'
Expand Down Expand Up @@ -181,7 +182,7 @@ function config_helper() (
}

# process
local item option_args=() option_file='' option_quiet
local item option_args=() option_file='' option_multiple='ok' option_quiet
option_quiet="$(echo-quiet-enabled -- "$@")"
while test "$#" -ne 0; do
item="$1"
Expand All @@ -190,6 +191,7 @@ function config_helper() (
'--help' | '-h') help ;;
'--file='*) option_file="${item#*=}" ;;
'--no-quiet'* | '--quiet'* | '--no-verbose'* | '--verbose'*) ;; # handled by echo-quiet-enabled
'--multiple=ok' | '--multiple=warn-skip' | '--multiple=warn-apply') option_multiple="${item#*=}" ;;
'--')
option_args+=("$@")
shift $#
Expand Down Expand Up @@ -225,11 +227,7 @@ function config_helper() (
# Dependencies

source "$DOROTHY/sources/ripgrep.bash"
setup-util-sd --quiet
setup-util-teip --quiet

local bin_gsed
bin_gsed="$(echo-gnu-command --install -- gsed)"
setup-util-deno --quiet

# =====================================
# Action
Expand All @@ -239,7 +237,7 @@ function config_helper() (

# cycle
function act {
local option_search option_columns='1' option_quote='yes' option_replace search_pattern replace_pattern get_value_pattern addition find field value content
local option_search option_columns='1' option_quote='yes' option_replace search_pattern replace_pattern remove_pattern get_value_pattern addition find field value content count temp_file
while test "$#" -ne 0; do
# extract arguments
option_search="$1"
Expand All @@ -263,6 +261,7 @@ function config_helper() (
# reset variables
search_pattern=''
replace_pattern=''
remove_pattern=''
get_value_pattern=''
addition=''
find='' # value of --find=<value>
Expand All @@ -278,6 +277,9 @@ function config_helper() (
# x ignore whitespace and allow line comments (starting with `#`)
# ^ x doesn't seem to work
# the (?m) prefix turns out not be necessary, for sed, nor ripgrep

# named capture groups: rust: (?P< javascript: (?<
# multiline mode: rust: (?m: javascript: m must be provided as a regexp instantiation flag
if [[ $option_search == '--string-find='* ]]; then
# raw string
find="${option_search#*=}"
Expand All @@ -294,11 +296,13 @@ function config_helper() (
find="${option_search#*=}"
search_pattern="^(?P<indent>[[:blank:]]*)(?P<comment>(?:#|[[:blank:]])*)(?P<value>$find)$"
get_value_pattern="^(?P<indent>[[:blank:]]*)(?P<value>$find)$" # ignore comments
remove_pattern='${indent}'
elif [[ $option_search == '--field='* ]]; then
# field that can be an array and value
field="${option_search#*=}"
search_pattern="^(?P<indent>[[:blank:]]*)(?P<comment>(?:#|[[:blank:]])*)$field *= *(?P<value>[(](?ms:.*?)[)]|[^\n]*)$"
get_value_pattern="^(?P<indent>[[:blank:]]*)$field *= *(?P<value>[(](?ms:.*?)[)]|[^\n]*)$" # ignore comments
remove_pattern='${indent}'
else
help "Invalid search argument [$option_search] it must be either --find=<pattern> or --field=<field>"
fi
Expand Down Expand Up @@ -332,7 +336,6 @@ function config_helper() (
# replacement, use spaces as the filler, as without echo-lines being aware of the content of ${indent}, there is no way for it to correctly align tabs, also use spaces as editors show tabs at variable widths
value=$'(\n'"$(echo-lines --indent=$'${indent}\t' --quoted --columns="${option_columns:-"1"}" --width=inputs --filler=space -- "${lines[@]}")"$'\n${indent})'
replace_pattern="\${indent}$field=$value"
set +x

# reset columns
option_columns='1'
Expand All @@ -355,7 +358,7 @@ function config_helper() (
# the replacement field was a find value, do not shift it, as we will use it in the next cycle
# instead, fetch the value, then continue to use the field as the next find
# --max-count=1 is to workaround: https://github.com/BurntSushi/ripgrep/issues/2095
rg --max-count=1 --multiline --only-matching --regexp="$get_value_pattern" --replace='${value}' "$option_file" || :
echo-regexp -omn --find="$get_value_pattern" --replace='${value}' <"$option_file" || :
continue
fi

Expand All @@ -364,48 +367,53 @@ function config_helper() (
shift
# then perform the replace

# ensure a trailing line, as otherwise the regexes get confused
# remove superflous trailing lines, to ensure there is only one, and at least one
content="$(cat "$option_file")"
content="${content%$'\n'}"$'\n'
content="${content%$'\n'}"

# do the replacement or addition
if printf '%s' "$content" | rg --quiet --multiline "$search_pattern"; then
# trim all but the first occurance
# https://github.com/BurntSushi/ripgrep/issues/2094
# https://github.com/chmln/sd/issues/105
# https://github.com/greymd/teip/issues/27
# https://github.com/greymd/teip/issues/27#issuecomment-1101065549

# we want to remove all secondary occurances
# keeping only the first occurance
#
# as teip lacks the ability to delete lines
# and as it requires line output
# we need to use sed and sd to do it
#
# we can't just do teip to sd, as teip to sd occassionally causes
# Error: Broken pipe (os error 32)
#
# as such, we need to use teip to sed to sd
# for the removal of the secondary occurances
#
# this will then leave the first occurance in the whole
# which we can use sd to perform our final intended replacement
# of the primary occurance
# printf '%s' "$content" | sd -n 1 "$search_pattern" 'REPLACE_THIS_LINE' | sd "$search_pattern" '' | sd $'(REPLACE_THIS_LINE\n)+' "$replace_pattern" >"$option_file" <-- this fails to handle replacement groups
printf '%s' "$content" |
teip -g "$search_pattern" -- teip -l 2- -- "$bin_gsed" 's/.*/REMOVE_THIS_LINE/' |
sd $'(REMOVE_THIS_LINE\n)+' '' |
sd "$search_pattern" "$replace_pattern" \
>"$option_file"
else
count="$(__print_values_print "$content" | echo-regexp -cm --find="$search_pattern" || :)"
if test -z "$count" || test "$count" -eq 0; then
# it wasn't found, so add manually if it's not empty
if test -n "$addition"; then
__print_line "$addition" >>"$option_file"
__print_values_print "$content" "$addition" >"$option_file"
UPDATED='yes' # a valid update occured, note for logging
fi
elif test "$count" -eq 1 -o "$option_multiple" = 'ok'; then
# first match replaced, subsequent matches removed
__print_values_print "$content" | echo-regexp -gm --find="$search_pattern" --replace="$replace_pattern" --replace="$remove_pattern" >"$option_file"
UPDATED='yes' # a valid update occured, note for logging
# notes:
# sd only supports replacing every match with the same replacement
# _print_lines "$content" | sd "$search_pattern" "$replace_pattern" >"$option_file"
# teip does not seem to behave correctly for multiline matches
else
temp_file="$(
fs-temp \
--directory='config-helper' \
--directory="$(basename "$option_file")" \
--file --no-touch
)"
if test "$option_multiple" = 'warn-skip'; then
# write update to temp file (for diff), do not update intended file
__print_values_print "$content" | echo-regexp -gm --find="$search_pattern" --replace="$replace_pattern" --replace="$remove_pattern" >"$temp_file"
{
echo-style --notice1='The configuration file ' --code-error1="$option_file" --error1=' was not saved' --notice1=' as multiple value instances exist within the configuration file, which may be computational conditions that our automation would be fraught to update.' $'\n' --info1='When you can' --notice1=' manually apply the requested changes to your configuration such that this automated update will not be attempted again.' $'\n' --notice1='Below is our unapplied automated attempt, saved to ' --code-notice1="$temp_file"
fs-diff -- "$option_file" "$temp_file" || :
echo-style --notice1=$'\n'
} >/dev/stderr
elif test "$option_multiple" = 'warn-apply'; then
# write backup to temp file, write update to intended file
__print_values_print "$content" >"$temp_file"
echo-regexp -gm --find="$search_pattern" --replace="$replace_pattern" --replace="$remove_pattern" <"$temp_file" >"$option_file"
UPDATED='yes' # a valid update occured, note for logging
{
echo-style --notice1='The configuration file ' --code-notice1="$option_file" --notice1=' ' --good1='was updated successfully.' $'\n' --info1='However,' --notice1=' only a single replacement is supported but multiple were performed. The changes that were made are a best effort upon evaluated values and may be incorrect if conditional values were used.' $'\n' --info1='Review the changes for correctness.' --notice1=' A backup has been made to ' --code-notice1="$temp_file"
fs-diff -- "$temp_file" "$option_file" || :
echo-style --notice1=$'\n'
} >/dev/stderr
fi
fi

# a valid update occured, note for logging
UPDATED='yes'
done
}

Expand Down
5 changes: 2 additions & 3 deletions commands/dorothy
Original file line number Diff line number Diff line change
Expand Up @@ -513,15 +513,14 @@ function dorothy_() (

# [github-download] sometimes requires [jq]
# [set-hostname] requires [sd]
# [config-helper] requires [rg], [sd], [teip]
if __command_missing jq rg sd sd teip; then
# [config-helper] requires [rg], [sd]
if __command_missing jq rg sd; then
if __command_exists apt-get; then
__try_sudo apt-get -qq update # -qq: quiet
fi
setup-util-jq --quiet
setup-util-ripgrep --quiet
setup-util-sd --quiet
setup-util-teip --quiet
fi

# ensure nushell always has the config files it needs
Expand Down
38 changes: 25 additions & 13 deletions commands/dorothy-config
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ function dorothy_config_test() (
curl
ripgrep
sd
teip
)
expected_stdout="$(__print_lines "${invented_packages[@]}")"
expected_stderr="$(
cat <<-EOF
Moved [curl] from [DOROTHY_CONFIG_TESTING] to [SETUP_UTILS] as [curl].
Moved [ripgrep] from [DOROTHY_CONFIG_TESTING] to [SETUP_UTILS] as [ripgrep].
Moved [sd] from [DOROTHY_CONFIG_TESTING] to [SETUP_UTILS] as [sd].
Moved [teip] from [DOROTHY_CONFIG_TESTING] to [SETUP_UTILS] as [teip].
Updated configuration file: $DOROTHY/user/config/setup.bash
EOF
)"
Expand Down Expand Up @@ -69,6 +67,9 @@ function dorothy_config() (
--prefer=<local|public>
If <local> prefer <user/config.local>, if <public> prefer <user/config>.
If there are multiple config files, prompt the user which one to use.
--reason=<reason>
The reason for the change, displays in prompts and whatnot.
EOF
if test "$#" -ne 0; then
echo-error "$@"
Expand All @@ -77,14 +78,15 @@ function dorothy_config() (
}

# process
local item option_packages_var='' option_filename='' option_prefer='' option_args=()
local item option_packages_var='' option_filename='' option_reason='' option_prefer='' option_args=()
while test "$#" -ne 0; do
item="$1"
shift
case "$item" in
'--help' | '-h') help ;;
'--packages-var='*) option_packages_var="${item#*=}" ;;
'--file='*) option_filename="${item#*=}" ;;
'--reason='*) option_reason="${item#*=}" ;;
'--prefer=local' | '--prefer=public' | '--prefer=') option_prefer="${item#*=}" ;;
'--')
option_args+=("$@")
Expand Down Expand Up @@ -122,7 +124,7 @@ function dorothy_config() (
# Action

function prune_utilities_from_packages {
local item reconfigure='no' outputs=() installer util
local item reconfigure='no' revised_items=() installer util reason

# SETUP_UTILS should have already been loaded, but let's create and load it if it hasn't
# we need to do it this way, otherwise we would wipe pre-existing custom configuration
Expand All @@ -140,30 +142,34 @@ function dorothy_config() (
if test -n "$installer"; then
if [[ $installer == 'setup-util-'* ]]; then
util="${installer#*setup-util-}"
echo-style --notice="Moved [$item] from [$option_packages_var] to [SETUP_UTILS] as [$util]." >/dev/stderr
reason+="$(
echo-style --reset --notice1='Relocate ' --code-notice1="$item" --notice1=' from ' --code-notice1="$option_packages_var" --notice1=' to ' --code-notice1="$util" --notice1=' in ' --code-notice1='SETUP_UTILS' $'\n'
)"
SETUP_UTILS+=("$util")
reconfigure='yes'
else
echo-style --notice="Skipping [$item] from [$option_packages_var], as it should be installed via [$installer]." >/dev/stderr
reason+="$(
echo-style --reset --notice1='Remove ' --code-notice1="$item" --notice1=' from ' --code-notice1="$option_packages_var" --notice1=' as it should be installed via ' --code-notice1="$installer" $'\n'
)"
reconfigure='yes'
fi
continue
else
echo "$item"
outputs+=("$item")
revised_items+=("$item")
fi
done

# update configuration if necessary
if test "$reconfigure" = 'yes'; then
dorothy-config "$option_filename" --prefer="$option_prefer" -- \
--field="$option_packages_var" --array="$(__print_lines "${outputs[@]}" | sort --ignore-case | uniq)" \
dorothy-config "$option_filename" --prefer="$option_prefer" --reason="$reason" -- \
--field="$option_packages_var" --array="$(__print_lines "${revised_items[@]}" | sort --ignore-case | uniq)" \
--field='SETUP_UTILS' --array="$(__print_lines "${SETUP_UTILS[@]}" | sort --ignore-case | uniq)"
fi
}

function update_configuration {
# check for existing
local user_filepath='' temp_filepath='' source_filepath='' default_filepath="$DOROTHY/config/$option_filename" local_filepath="$DOROTHY/user/config.local/$option_filename" public_filepath="$DOROTHY/user/config/$option_filename"
local user_filepath='' temp_filepath='' source_filepath='' default_filepath="$DOROTHY/config/$option_filename" local_filepath="$DOROTHY/user/config.local/$option_filename" public_filepath="$DOROTHY/user/config/$option_filename" displayed_reason='no'

# reset default filepath if it doesn't exist
if test ! -f "$default_filepath"; then
Expand All @@ -181,9 +187,10 @@ function dorothy_config() (
if test -f "$local_filepath" -a -f "$public_filepath"; then
user_filepath="$(
choose --linger --required \
--question="The [$option_filename] configuration file is pending updates, which one do you wish to update?" \
--question="$(echo-style --notice1='The configuration file ' --code-notice1="$option_filename" --notice1=' is pending updates. Select the specific configuration file to update.')"$'\n'"$option_reason" \
--default="$user_filepath" -- "$public_filepath" "$local_filepath"
)"
displayed_reason='yes'
elif test -f "$local_filepath"; then
user_filepath="$local_filepath"
elif test -f "$public_filepath"; then
Expand All @@ -192,6 +199,11 @@ function dorothy_config() (
user_filepath="$public_filepath"
fi

# show the reason
if test -n "$option_reason" -a "$displayed_reason" = 'no'; then
echo-style --notice1='The configuration file ' --code-notice1="$user_filepath" --notice1=' will be updated to:' $'\n' "$reason"
fi

# ensure filepath can be written
mkdir -p "$(dirname "$user_filepath")"

Expand Down Expand Up @@ -252,7 +264,7 @@ function dorothy_config() (

# now that the file definitely exists, update it if we have values to update it
if test "${#option_args[@]}" -ne 0; then
config-helper --file="$user_filepath" \
config-helper --file="$user_filepath" --multiple=warn-skip \
-- "${option_args[@]}"
fi
}
Expand Down
Loading

0 comments on commit ada62e0

Please sign in to comment.