scripts/change.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
set -euo pipefail
CHANGE_DIRECTORY=.changes
TYPES=(added changed deprecated removed fixed security maintenance)
SECTIONS=('Breaking Changes' Security Added Changed Deprecated Removed Fixed Maintenance)
BOLD=$'\033[1m' GREEN=$'\033[32m' RED=$'\033[31m' DIM=$'\033[2m' RESET=$'\033[0m'
[ -t 1 ] || BOLD='' GREEN='' RED='' DIM='' RESET=''
heading() { printf '\n %s%s%s\n' "$BOLD" "$*" "$RESET"; }
ok() { printf ' %s✔%s %s\n' "$GREEN" "$RESET" "$*"; }
die() {
printf ' %s✖%s %s\n' "$RED" "$RESET" "$*" >&2
exit 1
}
confirm() {
local answer
printf '\n %s [y/N] ' "$*"
read -r answer
[[ "$answer" =~ ^[Yy]$ ]]
}
cd "$(dirname "$0")/.."
trap 'tput cnorm 2> /dev/null || true' EXIT
main() {
read_arguments "$@"
if [ -n "$notes" ]; then
print_notes
return
fi
[ -n "$kind" ] && [ -n "$summary" ] || ask_questions
write_change
}
read_arguments() {
kind='' summary='' commit='' breaking='' notes='' details=()
while [ $# -gt 0 ]; do
case "$1" in
--notes) notes=true ;;
--breaking) breaking=true ;;
--type | --summary | --detail | --commit)
[ $# -ge 2 ] || die "$1 has no value, expected one"
case "$1" in
--type) kind="$2" ;;
--summary) summary="$2" ;;
--detail) details+=("$2") ;;
--commit) commit="$2" ;;
esac
shift
;;
*) die "'$1' is not an option, expected --type, --summary, --detail, --breaking, --commit or --notes" ;;
esac
shift
done
}
ask_questions() {
local selected detail
[ -t 0 ] || die "--type and --summary are required without a terminal"
if [ -z "$kind" ]; then
heading "Select the type"
selected="$(menu 0 "${TYPES[@]}")"
kind="${TYPES[$selected]}"
fi
if [ -z "$summary" ]; then
heading "Summary, one sentence with the heading"
printf ' '
read -r summary
fi
heading "Details, an empty line ends them"
while printf ' - ' && read -r detail && [ -n "$detail" ]; do
details+=("$detail")
done
if confirm "Breaking change?"; then
breaking=true
fi
printf '\n'
}
write_change() {
local path detail
printf '%s\n' "${TYPES[@]}" | grep -qx -- "$kind" || die "the type is '$kind', expected ${TYPES[*]}"
[ -n "$summary" ] || die "the summary is empty, expected one sentence"
if [ -n "$commit" ]; then
git rev-parse --verify --quiet "$commit^{commit}" > /dev/null || die "$commit is not a commit of this repository"
commit="$(git rev-parse --short "$commit")"
fi
mkdir -p "$CHANGE_DIRECTORY"
path="$CHANGE_DIRECTORY/$(file_name).md"
{
printf -- '---\ntype: %s\n' "$kind"
[ -z "$breaking" ] || printf 'breaking: true\n'
[ -z "$commit" ] || printf 'commit: %s\n' "$commit"
printf -- '---\n%s\n' "$summary"
[ "${#details[@]}" = 0 ] || printf '\n'
for detail in ${details[@]+"${details[@]}"}; do
printf -- '- %s\n' "$detail"
done
} > "$path"
ok "$path describes the $BOLD$kind$RESET change"
}
print_notes() {
local section entries
compgen -G "$CHANGE_DIRECTORY/*.md" > /dev/null || die "$CHANGE_DIRECTORY has no change files"
for section in "${SECTIONS[@]}"; do
entries="$(awk -v section="$section" '
FNR == 1 { fences = 0; type = ""; breaking = 0 }
fences < 2 {
if ($0 == "---") fences++
else if ($1 == "type:") type = $2
else if ($0 == "breaking: true") breaking = 1
next
}
NF && tolower(section) == (breaking ? "breaking changes" : type) {
prefix = /^- / ? " " : "- "
print prefix $0
}
' "$CHANGE_DIRECTORY"/*.md)"
[ -z "$entries" ] || printf '### %s\n\n%s\n\n' "$section" "$entries"
done
}
file_name() {
local stem name number=2
stem="$(printf '%s' "$summary" | LC_ALL=C tr -cs '[:alnum:]' '\n' | LC_ALL=C tr '[:upper:]' '[:lower:]' | awk 'NF && ++count <= 6' | paste -sd - -)"
name="$stem"
while [ -e "$CHANGE_DIRECTORY/$name.md" ]; do
name="$stem-$number"
number=$((number + 1))
done
echo "$name"
}
menu() {
local selected="$1" options key i
shift
options=("$@")
tput civis >&2 2> /dev/null || true
while true; do
for i in "${!options[@]}"; do
if [ "$i" = "$selected" ]; then
printf ' %s❯ %s%s\n' "$GREEN" "${options[$i]}" "$RESET" >&2
else
printf ' %s %s%s\n' "$DIM" "${options[$i]}" "$RESET" >&2
fi
done
IFS= read -rsn1 key
if [ "$key" = $'\x1b' ]; then
IFS= read -rsn2 key
if [ "$key" = '[A' ] && [ "$selected" -gt 0 ]; then
selected=$((selected - 1))
elif [ "$key" = '[B' ] && [ "$selected" -lt $((${#options[@]} - 1)) ]; then
selected=$((selected + 1))
fi
elif [ -z "$key" ]; then
break
fi
printf '\033[%dA\033[J' "${#options[@]}" >&2
done
tput cnorm >&2 2> /dev/null || true
echo "$selected"
}
main "$@"