-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a script to apply any config to any hunk in a config file. (#16)
* add a script to apply a single theme to a single file * add FORMAT and NAMES markers constants * add the COMMENT_SYMBOL environment variable * add TEMP_MARKER to make sure trailing whitespaces are used * add better name for error code 5 * refactor into functions and echo errors to stderr * add the possibility to modify hunks with the hunk=$palette syntax * check if the format exist in the palette hunks * refactor theme application to hunk into function * use return instead of custom terminate to propagate error signals * add some missing documentation * remove the obligation to pass more than one argument * add slightly bette apply message * exit the code when the file does not exist * add colors and log statements for prettier stdout * move the config from markers to separate config file * rename the config file * add header to a2n-s-apply-theme * add more general names and separate name and values * udpate the header * use key:value maps to map values to the right fields * check the key:value format of input/config * add ERROR_CODEs everywhere it was missing * add more general variable and configuration names * do not check the sizes thanks to the mappings Now that the arguments are not positional inside the lists of keys and values, the code does not need to check whether the sizes do match. The input values are seen as a bank of values that the config file demands to use. The code then only picks the values it needs to apply to the hunks of the current config file. * use jq's array expansion to read the mapping * add an example config file Co-authored-by: a2n-s <[email protected]>
- Loading branch information
Showing
2 changed files
with
366 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"cava": | ||
{ | ||
"file": "~/.config/cava/config", | ||
"hunks": | ||
{ | ||
"palette": | ||
{ | ||
"format": "{key} = '{value}'", | ||
"mapping": | ||
[ | ||
"gradient_color_1:g1", | ||
"gradient_color_2:g2", | ||
"gradient_color_3:g3", | ||
"gradient_color_4:g4", | ||
"gradient_color_5:g5", | ||
"gradient_color_6:g6", | ||
"gradient_color_7:g7", | ||
"gradient_color_8:g8" | ||
] | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,342 @@ | ||
#!/usr/bin/env bash | ||
# ___ personal page: https://a2n-s.github.io/ | ||
# __ _|_ )_ _ ___ ___ github page: https://github.com/a2n-s | ||
# / _` |/ /| ' \___(_-< my dotfiles: https://github.com/a2n-s/dotfiles | ||
# \__,_/___|_||_| /__/ | ||
# ___ _ _ _ _ | ||
# __ _|_ )_ _ ___ ______ __ _ _ __ _ __| |_ _ ___| |_ ___ ___| |_ _ _ _ _ | |__ ___ | ||
# / _` |/ /| ' \___(_-<___/ _` | '_ \ '_ \ | || |___| _/ _ \___| ' \ || | ' \| / /(_-< | ||
# \__,_/___|_||_| /__/ \__,_| .__/ .__/_|\_, | \__\___/ |_||_\_,_|_||_|_\_\/__/ | ||
# |_| |_| |__/ | ||
# Description: TODO. | ||
# Dependencies: TODO. | ||
# License: https://github.com/a2n-s/scripts/blob/main/LICENSE | ||
# Contributors: Stevan Antoine | ||
|
||
|
||
OPTIONS=$(getopt -o h --long help -n 'a2n-s-apply-to-hunks' -- "$@") | ||
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi | ||
eval set -- "$OPTIONS" | ||
|
||
# environment variables. | ||
[ -z "$COMMENT_SYMBOL" ] && COMMENT_SYMBOL="#" | ||
[ -z "$CONFIG_FILE" ] && CONFIG_FILE="$HOME/.config/a2n-s/hunks.json" | ||
|
||
# some constants. | ||
BEGIN_MARKER="$COMMENT_SYMBOL+ BEGIN" | ||
END_MARKER="$COMMENT_SYMBOL+ END" | ||
TEMP_MARKER="%%%" # a temporary marker to ensure blank characters are taken into account. | ||
|
||
SUCCESS_ERROR_CODE=0 | ||
MISSING_MARKER_ERROR_CODE=1 | ||
MISPLACED_MARKERS_ERROR_CODE=2 | ||
MISSING_FIELD_ERROR_CODE=3 | ||
BAD_ARGUMENTS_ERROR_CODE=4 | ||
ARRAY_SIZE_MISMATCH_ERROR_CODE=5 | ||
WRONG_MAPPING_ERROR_CODE=6 | ||
|
||
RED="$(tput setaf 1)" | ||
GRN="$(tput setaf 2)" | ||
YLW="$(tput setaf 3)" | ||
BLU="$(tput setaf 4)" | ||
MGT="$(tput setaf 5)" | ||
CYN="$(tput setaf 6)" | ||
OFF="$(tput sgr0)" | ||
|
||
|
||
log_err () { | ||
echo "${RED}[ERROR]${OFF} $1" > /dev/stderr; | ||
} | ||
|
||
|
||
log_warning () { | ||
echo "${YLW}[WARNING]${OFF} $1" > /dev/stderr; | ||
} | ||
|
||
|
||
log_debug () { | ||
echo "${MGT}[DEBUG]${OFF} $1" > /dev/stderr; | ||
} | ||
|
||
|
||
log_info () { | ||
echo "${CYN}[INFO]${OFF} $1" > /dev/stderr; | ||
} | ||
|
||
|
||
log_ok () { | ||
echo "${GRN}[OK]${OFF} $1" > /dev/stderr; | ||
} | ||
|
||
|
||
assert_file_exist () { | ||
[ ! -f "$1" ] && { | ||
log_err "$0: '$1': No such file or directory" | ||
return "$BAD_ARGUMENTS_ERROR_CODE"; | ||
} | ||
return "$SUCCESS_ERROR_CODE" | ||
} | ||
|
||
|
||
expand_user () { | ||
sed "s|~|$HOME|g; s/\"//g" | ||
} | ||
|
||
|
||
get_file_from_json () { | ||
jq ".$1.file" "$CONFIG_FILE" | expand_user | ||
} | ||
|
||
|
||
check_markers () { | ||
local file=$1 | ||
local hunk=$2 | ||
# assert the hunk's markers exist. | ||
local begin_markers=($(grep "^$BEGIN_MARKER:$hunk$" "$file" -n | awk -F: '{print $1}')) | ||
local end_markers=($(grep "^$END_MARKER:$hunk$" "$file" -n | awk -F: '{print $1}')) | ||
[ "${#begin_markers[@]}" -eq 0 ] && { | ||
log_err "'$BEGIN_MARKER:$hunk' marker could not be found in '$file'." | ||
return "$MISSING_MARKER_ERROR_CODE"; | ||
} | ||
[ "${#begin_markers[@]}" -gt 1 ] && { | ||
log_err "There should be exactly 1 '$BEGIN_MARKER:$hunk' marker in '$file'." | ||
log_err "Got ${#begin_markers[@]} markers on lines ${begin_markers[*]}." | ||
return "$MISSING_MARKER_ERROR_CODE"; | ||
} | ||
[ "${#end_markers[@]}" -eq 0 ] && { | ||
log_err "'$END_MARKER:$hunk' marker could not be found in '$file'." | ||
return "$MISSING_MARKER_ERROR_CODE"; | ||
} | ||
[ "${#end_markers[@]}" -gt 1 ] && { | ||
log_err "There should be exactly 1 '$END_MARKER:$hunk' marker in '$file'." | ||
log_err "Got ${#end_markers[@]} markers on lines ${end_markers[*]}." | ||
return "$MISSING_MARKER_ERROR_CODE"; | ||
} | ||
|
||
# assert the hunk's markers are placed correctly. | ||
local line_begin_marker=${begin_markers[0]} | ||
local line_end_marker=${end_markers[0]} | ||
[ "$line_begin_marker" -gt "$line_end_marker" ] && { | ||
log_err "The '$END_MARKER:$hunk' marker should come after '$BEGIN_MARKER:$hunk'." | ||
log_err "'$END_MARKER:$hunk' found on line $line_end_marker" | ||
log_err "'$BEGIN_MARKER:$hunk' found on line $line_begin_marker" | ||
return "$MISPLACED_MARKERS_ERROR_CODE"; | ||
} | ||
|
||
echo "$line_begin_marker" "$line_end_marker" | ||
} | ||
|
||
|
||
get_format_from_json () { | ||
local format=$(jq ".$1.hunks.$2.format" "$CONFIG_FILE") | ||
|
||
[ "$format" == "null" ] && { | ||
log_err "No format found in $CONFIG_FILE.$1.hunks.$2" | ||
return "$MISSING_FIELD_ERROR_CODE"; | ||
} | ||
[ -z "$(echo "$format" | grep "{key}" )" ] && { | ||
log_err "missing 'key' placeholder in $CONFIG_FILE.$1.hunks.$2" | ||
return "$MISSING_FIELD_ERROR_CODE"; | ||
} | ||
[ -z "$(echo "$format" | grep "{value}" )" ] && { | ||
log_err "missing 'value' placeholder in $CONFIG_FILE.$1.hunks.$2" | ||
return "$MISSING_FIELD_ERROR_CODE"; | ||
} | ||
|
||
echo "$format" | sed 's/^"//g; s/"$//g; s/\\"/"/g' | ||
} | ||
|
||
|
||
get_mapping_from_json () { | ||
local mapping=$(jq ".$1.hunks.$2.mapping" "$CONFIG_FILE") | ||
[ "$mapping" == "null" ] && { | ||
log_err "No mapping found between lines $CONFIG_FILE.$1.hunks.$2" | ||
return "$MISSING_FIELD_ERROR_CODE"; | ||
} | ||
|
||
echo "$mapping" | jq '.[]' | sed 's/^"\(.*\)"$/\1/g' | ||
} | ||
|
||
|
||
apply_values_to_hunk () { | ||
config=$1 | ||
hunk=$2 | ||
shift 2 | ||
values=($@) | ||
|
||
file="$(get_file_from_json "$config")" | ||
|
||
res=$(check_markers "$file" "$hunk") | ||
code=$? | ||
[ $code -ne "$SUCCESS_ERROR_CODE" ] && return "$code" | ||
line_begin_marker=$(echo "$res" | cut -d' ' -f1) | ||
line_end_marker=$(echo "$res" | cut -d' ' -f2) | ||
|
||
format=$(get_format_from_json "$config" "$hunk") | ||
code=$? | ||
[ $code -ne "$SUCCESS_ERROR_CODE" ] && return "$code" | ||
mapping=($(get_mapping_from_json "$config" "$hunk")) | ||
code=$? | ||
[ $code -ne "$SUCCESS_ERROR_CODE" ] && return "$code" | ||
|
||
# generate the final maps of values. | ||
declare -A keys_map; | ||
error="false" | ||
for ((i=0; i<"${#mapping[@]}"; i++)); | ||
do | ||
keys_key=$(echo "${mapping[$i]}" | cut -d: -f1) | ||
keys_value=$(echo "${mapping[$i]}" | cut -d: -f2) | ||
[ "$keys_key:$keys_value" != "${mapping[$i]}" ] && { | ||
log_err "Error parsing $CONFIG_FILE.$config.hunks.$hunk.mapping..."; | ||
log_info "Please make sure all the array elements are of the form 'key:value'."; | ||
error="true"; | ||
} | ||
keys_map["$keys_key"]=$keys_value | ||
done | ||
[ "$error" == "true" ] && return "$WRONG_MAPPING_ERROR_CODE"; | ||
|
||
declare -A values_map; | ||
error="false" | ||
for ((i=0; i<"${#values[@]}"; i++)); | ||
do | ||
values_key=$(echo "${values[$i]}" | cut -d: -f1) | ||
values_value=$(echo "${values[$i]}" | cut -d: -f2) | ||
[ "$values_key:$values_value" != "${values[$i]}" ] && { | ||
log_err "Error parsing the input values..."; | ||
log_info "Please make sure all the input elements are of the form 'key:value',"; | ||
log_info "got '${values[*]}'." | ||
error="true"; | ||
} | ||
values_map["$values_key"]=$values_value | ||
done | ||
[ "$error" == "true" ] && return "$WRONG_MAPPING_ERROR_CODE"; | ||
|
||
# check that the map from the config file uses keys provided as arguments. | ||
for key in "${!keys_map[@]}"; | ||
do | ||
value_key=${keys_map[$key]} | ||
value=${values_map[$value_key]} | ||
[ -z "$value" ] && { | ||
log_err "The '$value_key' value was not given as an argument but is required by '$key'..."; | ||
log_info "See $CONFIG_FILE.$config.hunks.$hunk.mapping to fix it." | ||
log_info "The list of provided keys was '${!values_map[*]}'." | ||
return "$WRONG_MAPPING_ERROR_CODE"; | ||
} | ||
done | ||
|
||
log_info "${BLU}[$file]${OFF} apply ${BLU}'${values[*]}'${OFF} to '${BLU}$config.$hunk${OFF}'" | ||
# remove the values inbetween the markers. | ||
new_file=$(sed -e "/^$BEGIN_MARKER:$hunk$/,/^$END_MARKER:$hunk$/{//!d}" "$file") | ||
# add the new values back. | ||
for key in "${!keys_map[@]}"; | ||
do | ||
value=${values_map[${keys_map[$key]}]} | ||
line=$(echo "$format" | sed -e "s/{key}/$key/g" -e "s/{value}/$value/g") | ||
new_file=$(echo "$new_file" | sed "/^$BEGIN_MARKER:$hunk/a $TEMP_MARKER$line" | sed "s/^$TEMP_MARKER//") | ||
done | ||
echo "$new_file" > "$file" | ||
log_ok "${GRN}[$file]${OFF} config successfully applied to hunk '${GRN}$config.$hunk${OFF}'" | ||
} | ||
|
||
|
||
apply_to_hunks() { | ||
config=$1 | ||
shift 1 | ||
|
||
[ ! -f "$CONFIG_FILE" ] && { | ||
log_err "CONFIG_FILE: '$CONFIG_FILE': No such file or directory."; | ||
exit 1; | ||
} | ||
|
||
[ "$(jq ".$config" "$CONFIG_FILE")" == "null" ] && { | ||
log_err "'$config' was not found in '$CONFIG_FILE'..."; | ||
exit 1; | ||
} | ||
|
||
assert_file_exist "$(get_file_from_json "$config")" | ||
code=$? | ||
[ $code -ne "$SUCCESS_ERROR_CODE" ] && return "$code" | ||
|
||
[ $(($# % 2)) -ne 0 ] && { | ||
log_err "There should be the same number of hunk names and hunks values to apply..."; | ||
exit 1; | ||
} | ||
|
||
# compute the associative array of named hunks and their respective values. | ||
declare -A all_values | ||
while [ "$#" -gt 0 ]; | ||
do | ||
hunk=$1; | ||
value=$2; | ||
shift 2; | ||
all_values["$hunk"]="$value" | ||
done | ||
|
||
# apply the values to the hunks. | ||
for hunk in "${!all_values[@]}"; | ||
do | ||
apply_values_to_hunk "$config" "$hunk" "${all_values[$hunk]}" | ||
done | ||
} | ||
|
||
|
||
usage () { | ||
# | ||
# the usage function. | ||
# | ||
echo "Usage: a2n-s-apply-to-hunks [-h] CONFIG_NAME HUNK1 VALUES1 HUNK2 VALUES2 ..." | ||
echo "Type -h or --help for the full help." | ||
} | ||
|
||
|
||
help () { | ||
# | ||
# the help function. | ||
# | ||
echo "a2n-s-apply-to-hunks:" | ||
echo " This script allows the user to easily apply some configuration to a config file." | ||
echo " Do not forget to put it in your PATH." | ||
echo "" | ||
echo "Usage:" | ||
echo " a2n-s-apply-to-hunks [-h] CONFIG_NAME HUNK1 VALUES1 HUNK2 VALUES2 ..." | ||
echo "" | ||
echo " - CONFIG_NAME is the name of the config in the json config file of this script" | ||
echo " - HUNK is the name of the hunk to apply the values to" | ||
echo " - VALUES is the list of key:value pairs, as defined in the config file below, to apply to the hunk" | ||
echo "" | ||
echo "Switches:" | ||
echo " -h/--help shows this help." | ||
echo "" | ||
echo "Environment variables:" | ||
echo " COMMENT_SYMBOL the symbol used to begin comment in the config file (defaults to '#', e.g. for \`bash\` or \`python\`)" | ||
echo " CONFIG_FILE the config file for this script (defaults to '\$HOME/.config/a2n-s/hunks.json')" | ||
exit 0 | ||
} | ||
|
||
|
||
main () { | ||
# | ||
# TODO. | ||
# | ||
while [[ $# -gt 0 ]]; do | ||
case "$1" in | ||
-h | --help ) help ;; | ||
-- ) shift; break ;; | ||
* ) break ;; | ||
esac | ||
done | ||
|
||
[ -z "$1" ] && { | ||
log_warning "no file to edit given as first positional argument..."; | ||
log_warning "Terminating..."; | ||
usage; | ||
return "$BAD_ARGUMENTS_ERROR_CODE"; | ||
} | ||
|
||
config=$1 | ||
shift 1 | ||
apply_to_hunks "$config" "$@" | ||
} | ||
|
||
|
||
main "$@" |