+"
echo " $(basename "$0") --print-dictionary"
}
make_absolute ()
{
local arg
arg="$1"
case "$arg" in
/*)
;;
*)
arg=$(pwd -P)/"$arg"
;;
esac
echo "$arg"
}
parse_args ()
{
local files
files=$(mktemp)
trap 'rm -f "$files"' EXIT
if [ $# -eq 1 ] && [ "$1" = "--print-dictionary" ]; then
print_dictionary=true
return
fi
while true; do
case " $1 " in
" --check ")
check=true
shift
;;
*)
break
;;
esac
done
if [ $# -eq -0 ]; then
usage
exit 1
fi
local arg
for arg in "$@"; do
if [ -f "$arg" ]; then
arg=$(make_absolute "$arg")
readlink -e "$arg" \
>> "$files"
elif [ -d "$arg" ]; then
arg=$(make_absolute "$arg")
local f
find "$arg" -type f -exec readlink -e {} \; \
>> "$files"
else
echo "Not a file or directory: $arg"
exit 1
fi
done
mapfile -t unique_files \
< <(sort -u "$files" \
| grep -v ChangeLog)
rm -f "$files"
trap "" EXIT
}
get_dictionary ()
{
if [ -f "$dictionary" ]; then
return
fi
local webpage
webpage=$(mktemp)
trap 'rm -f "$webpage"' EXIT
# Download web page containing table.
wget $url -O "$webpage"
# Extract table from web page.
awk '//,/<\/pre>/' "$webpage" \
| sed 's///;s/<\/pre>//' \
| grep -E -v "^$" \
> "$dictionary"
rm -f "$webpage"
trap "" EXIT
}
output_local_dictionary ()
{
# Filter out comments and empty lines.
grep -E -v \
"^#|^$" \
"$local_dictionary"
}
output_dictionaries ()
{
(
output_local_dictionary
cat "$dictionary"
) | grep -E -v "[A-Z]"
}
parse_dictionary ()
{
# Parse dictionary.
mapfile -t words \
< <(awk -F '->' '{print $1}' <(output_dictionaries))
mapfile -t replacements \
< <(awk -F '->' '{print $2}' <(output_dictionaries))
local words_done
declare -A words_done
local i word replacement
i=0
for word in "${words[@]}"; do
replacement=${replacements[i]}
# Skip words that are already handled. This ensures that the local
# dictionary overrides the wiki dictionary.
if [ "${words_done[$word]}" == 1 ]; then
words[i]=""
replacements[i]=""
i=$((i + 1))
continue
fi
words_done[$word]=1
# Skip identity rules.
if [ "$word" = "$replacement" ]; then
words[i]=""
replacements[i]=""
fi
i=$((i + 1))
done
}
print_dictionary ()
{
local i word replacement
i=0
for word in "${words[@]}"; do
replacement=${replacements[i]}
i=$((i + 1))
if [ "$word" == "" ]; then
continue
fi
echo "$word -> $replacement"
done
}
find_files_matching_words ()
{
local cache_id
cache_id=$(cat "$local_dictionary" "$dictionary" "$this_script" \
| md5sum \
| awk '{print $1}')
local patfile
patfile="$cache_dir/$cache_file2".$cache_id
local pat
if [ -f "$patfile" ]; then
pat=$(cat "$patfile")
else
rm -f "$cache_dir/$cache_file2".*
declare -a re_words
mapfile -t re_words \
< <(for f in "${words[@]}"; do
if [ "$f" = "" ]; then
continue
fi
echo "$f"
done \
| sed "s/^\(.\)/[\u\1\1]/")
pat=$(grep_join "${re_words[@]}")
local before after
before=$(grep_join \
"${grep_pre[@]}" \
"${grep_separators[@]}")
after=$(grep_join \
"${grep_separators[@]}" \
"${grep_post[@]}")
pat="$before$pat$after"
echo "$pat" \
> "$patfile"
fi
grep -E \
-l \
"$pat" \
"$@"
}
find_files_matching_word ()
{
local pat
pat="$1"
shift
local before after
before=$(grep_join \
"${grep_pre[@]}" \
"${grep_separators[@]}")
after=$(grep_join \
"${grep_separators[@]}" \
"${grep_post[@]}")
if bash_version_at_least 5 1; then
patc=${pat@u}
else
# shellcheck disable=SC2001
patc=$(echo "$pat" | sed 's/^\(.\)/\u\1/')
fi
pat="($patc|$pat)"
pat="$before$pat$after"
grep -E \
-l \
"$pat" \
"$@"
}
replace_word_in_file ()
{
local word
word="$1"
local replacement
replacement="$2"
local file
file="$3"
local before after
before=$(sed_join \
"${sed_pre[@]}" \
"${sed_separators[@]}")
after=$(sed_join \
"${sed_separators[@]}" \
"${sed_post[@]}")
if bash_version_at_least 5 1; then
wordc=${word@u}
replacementc=${replacement@u}
else
# shellcheck disable=SC2001
wordc=$(echo "$word" | sed 's/^\(.\)/\u\1/')
# shellcheck disable=SC2001
replacementc=$(echo "$replacement" | sed 's/^\(.\)/\u\1/')
fi
local repl1
local repl2
repl1="s%$before$word$after%\1$replacement\2%g"
repl2="s%$before$wordc$after%\1$replacementc\2%g"
sed -i \
"$repl1;$repl2" \
"$file"
}
replace_word_in_files ()
{
local word
word="$1"
local replacement
replacement="$2"
shift 2
local id
id="$word -> $replacement"
# Reduce set of files for sed to operate on.
local files_matching_word
declare -a files_matching_word
mapfile -t files_matching_word \
< <(find_files_matching_word "$word" "$@")
if [ ${#files_matching_word[@]} -eq 0 ]; then
return
fi
if echo "$replacement"| grep -q ","; then
echo "TODO: $id"
return
fi
declare -A md5sums
local changed f before after
changed=false
for f in "${files_matching_word[@]}"; do
if [ "${md5sums[$f]}" = "" ]; then
md5sums[$f]=$(md5sum "$f")
fi
before="${md5sums[$f]}"
replace_word_in_file \
"$word" \
"$replacement" \
"$f"
after=$(md5sum "$f")
if [ "$after" != "$before" ]; then
md5sums[$f]="$after"
changed=true
fi
done
if $changed; then
echo "$id"
fi
find_files_matching_word "$word" "${files_matching_word[@]}" \
| awk "{ printf \"TODO: $id: replacement failed: %s\n\", \$0}"
}
main ()
{
declare -a unique_files
check=false
print_dictionary=false
parse_args "$@"
get_dictionary
declare -a words
declare -a replacements
parse_dictionary
if $print_dictionary; then
print_dictionary
exit 0
fi
# Reduce set of files for sed to operate on.
local files_matching_words
declare -a files_matching_words
mapfile -t files_matching_words \
< <(find_files_matching_words "${unique_files[@]}")
if [ ${#files_matching_words[@]} -eq 0 ]; then
return
fi
if $check; then
exit 1
fi
local i word replacement
i=0
for word in "${words[@]}"; do
replacement=${replacements[i]}
i=$((i + 1))
if [ "$word" = "" ]; then
continue
fi
replace_word_in_files \
"$word" \
"$replacement" \
"${files_matching_words[@]}"
done
}
main "$@"