Skip to content

Commit

Permalink
feat: add first time setup
Browse files Browse the repository at this point in the history
  • Loading branch information
bonjourmauko committed Oct 23, 2024
1 parent 45c6725 commit 1fa2646
Show file tree
Hide file tree
Showing 24 changed files with 1,175 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,11 @@ tasks:
- poetry run yamllint --strict {{.YAML}}
- poetry run mdformat --wrap 79 --number --check {{.MD}}
- poetry run shellcheck {{.SH}}

test:
desc: Test the library.
vars:
SH_TEST:
ref: .SH_TEST | splitLines | join " "
cmds:
- lib/bashunit --parallel --simple {{.SH_TEST}}
35 changes: 35 additions & 0 deletions src/first_time_setup/checks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash
# @name checks
# @deps utils/boolean
# @brief A package for checks and validations.

set -euo pipefail

source 'src/first_time_setup/utils/boolean.sh'

# @description Check if the script should run in non-interactive mode.
# @arg $1 The jurisdiction name.
# @arg $2 The repository URL.
is::interactive() {
[[ -z ${1:-} || -z ${2:-} ]] && echo true || echo false
}

# @description Check if the script is running in a CI environment.
# @arg $1 The CI environment variable.
is::ci() {
is::true "${1:-}"
}

# @description Check if the repository exists.
is::repo() {
git rev-parse --is-inside-work-tree &>/dev/null && echo true || echo false
}

# @description Check if the setup should persevere.
# @arg $1 If we are in a CI environment.
# @arg $2 If the repository exists.
setup::persevere() {
local -r is_ci=$(is::true "${1:-}")
local -r is_repo=$(is::true "${2:-}")
if ! "${is_ci}" && "${is_repo}"; then echo false; else echo true; fi
}
46 changes: 46 additions & 0 deletions src/first_time_setup/envvars.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/bash
# @name envvars
# @deps none
# @brief A package for setting the environment variables.

set -euo pipefail

if [[ -z ${JURISDICTION_NAME+A} ]]; then
readonly JURISDICTION_NAME="${JURISDICTION_NAME:-}"
fi

if [[ -z ${REPOSITORY_URL+A} ]]; then
readonly REPOSITORY_URL="${REPOSITORY_URL:-}"
fi

if [[ -z ${CI+A} ]]; then
readonly CI="${CI:-}"
fi

if [[ -z ${DRY_MODE+A} ]]; then
readonly DRY_MODE="${DRY_MODE:-}"
fi

if [[ -z ${SCRIPT_PATH+A} ]]; then
readonly SCRIPT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
fi

if [[ -z ${DEV_MODE+A} ]]; then
if [[ -f "${SCRIPT_PATH}/README.md" ]]; then
readonly DEV_MODE='false'
else
readonly DEV_MODE='true'
fi
fi

if [[ -z ${ROOT_PATH+A} ]]; then
if ${DEV_MODE}; then
readonly ROOT_PATH=$(cd "${SCRIPT_PATH}/../.." && pwd)
else
readonly ROOT_PATH=${SCRIPT_PATH}
fi
fi

if [[ -z ${ROOT_DIR+A} ]]; then
readonly ROOT_DIR=${ROOT_PATH##*/}
fi
218 changes: 218 additions & 0 deletions src/first_time_setup/main.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/bin/bash
# @name main
# @deps checks envvars messages setup status utils/*
# @brief Setup a new OpenFisca extension package.

source 'src/first_time_setup/utils/boolean.sh'
source 'src/first_time_setup/utils/colours.sh'
source 'src/first_time_setup/utils/file.sh'
source 'src/first_time_setup/utils/string.sh'
source 'src/first_time_setup/utils/subshell.sh'
source 'src/first_time_setup/utils/url.sh'
source 'src/first_time_setup/checks.sh'
source 'src/first_time_setup/envvars.sh'
source 'src/first_time_setup/messages.sh'
source 'src/first_time_setup/setup.sh'
source 'src/first_time_setup/status.sh'

# @description User interruption.
# @internal
main.trap.interrupted() {
local -r exit_code=$?
trap - SIGINT
echo ''
echo -e "$(colour::warn 'Interrupted')" >&2
exit "${exit_code}"
}

# @description Cleanup on exit.
# @internal
main.trap.cleanup() {
local exit_code=$?
trap - EXIT
echo -e "$(colour::info 'Exiting, bye!')"
exit "${exit_code}"
}

# @description Generic error message.
# @arg $1 The message to display.
# @internal
main.error() {
local -r message="${1}"
echo -e "$(colour::fail "${message}")" >&2
}

# @description Main function to drive the script.
main() {
# Exit immediately if a command exits with a non-zero status.
set -o errexit
# Ensure that the ERR trap is inherited by shell functions.
set -o errtrace
# More verbosity when something within a function fails.
set -o functrace
# Treat unset variables as an error and exit immediately.
set -o nounset
# Prevent errors in a pipeline from being masked.
set -o pipefail
# Make word splitting happen only on newlines and tab characters.
IFS=$'\n\t'

# Define a cleanup functions to be called on script exit or interruption.
trap main.trap.interrupted SIGINT
trap main.trap.cleanup EXIT

# Make sure we are not being sourced. Exit if it is the case.
if "$(is::sourced)"; then
main.error 'This script should not be sourced but executed directly'
exit 1
fi

# Support being called from anywhere on the file system.
cd "${ROOT_PATH}"

# Define the variables we will use.
local name="${JURISDICTION_NAME}"
local url="${REPOSITORY_URL}"
local -r interactive=$(is::interactive "${name}" "${url}")
local -r ci=$(is::ci "${CI}")
local -r repo=$(is::repo)
local -r persevere="$(setup::persevere "${ci}" "${repo}")"
local -r dry=$(is::true "${DRY_MODE}")
local -r module='openfisca_extension_template'
local -r first_commit='Initial import from OpenFisca-Extension_Template'
local -r second_commit='Customise extension_template through script'

# Check if we can continue.
colour::info "$(msg::welcome)"
status::gather_info "${name}" "${url}" "${interactive}"
status::check_continue "${ci}" "${repo}" "${persevere}" "${dry}"
if ! "${persevere}" && ! "${dry}"; then
echo ''
main.error "$(msg::stop)"
echo ''
exit 2
fi

echo ''
colour::task 'We will now start setting up your new package'

# Process the jurisdiction name.
if [[ -z ${name} ]]; then echo ''; fi
while [[ -z ${name} ]]; do
echo -e -n "$(colour::user "$(msg::prompt_name)")"
IFS= read -r -p ' ' name
done
readonly name
local -r label=$(setup::name_label "${name}")
local -r snake=$(setup::name_snake "${label}")

# Process the repository URL.
if [[ -z ${url} ]]; then echo ''; fi
while [[ -z ${url} ]]; do
echo -e -n "$(colour::user "$(msg::prompt_url)")"
IFS= read -r -p ' ' url
done
readonly url
local -r folder=$(setup::repository_folder "${url}")

status::pre_summary "${name}" "${snake}" "${url}"

# Shall we proceed?
echo ''
local continue=$(is::false "${interactive}")
local prompt=$(colour::user "$(msg::prompt_continue)")
if "${continue}"; then echo -e "${prompt} Y"; fi
while ! "${continue}"; do
echo -e -n "${prompt}"
IFS= read -r -p ' ' continue
continue=$(is::true "${continue}")
if ! "${continue}"; then break; fi
done
unset prompt
readonly continue
echo -e "$(colour::logs "${continue}")"
if ! "${continue}"; then echo '' && exit 3; fi

echo ''
colour::task 'Now we can proceed with the setup'

local -r package="openfisca_${snake}"
local -r lineno_readme=$(setup::readme_lineno)
if [[ ${lineno_readme} -eq -1 ]]; then
echo ''
main.error 'Could not find the last line number of the README.md section'
echo ''
exit 4
fi
local -r lineno_changelog=$(setup::changelog_lineno)
if [[ ${lineno_changelog} -eq -1 ]]; then
echo ''
main.error 'Could not find the last line number of the CHANGELOG.md section'
echo ''
exit 5
fi

# Initialise the repository.
if ! "${ci}" || "${dry}"; then
echo ''
colour::pass 'Initialise git repository...'
if ! "${dry}"; then
setup::init_repository "${ROOT_DIR}" "${label}" "${first_commit}"
else
colour::warn 'Skipping git repository initialisation because of dry run'
fi
colour::pass "Commit made to 'main' with message:"
colour::logs "${first_commit}"
fi

# And go on...
colour::pass 'Replace default extension_template references'
local -r files=$(git ls-files "src/${module}")
setup::replace_references "${label}" "${snake}" "${name}" "${files}"
colour::pass 'Remove bootstrap instructions'
setup::remove_bootstrap_instructions "${lineno_readme}"
colour::pass 'Prepare README.md and CONTRIBUTING.md'
setup::prepare_readme_contributing "${url}"
colour::pass 'Prepare CHANGELOG.md'
setup::prepare_changelog "${lineno_changelog}"
colour::pass 'Prepare pyproject.toml'
setup::prepare_pyproject "${url}" "${folder}"
colour::pass 'Rename package to:'
colour::logs "${package}"
if ! "${dry}"; then
setup::rename_package "${package}"
else
colour::warn 'Skipping renaming of package because of dry run'
fi
colour::pass 'Remove single use first time setup files'
if ! "${dry}"; then
setup::remove_files
else
colour::warn 'Skipping removal of first time setup files because of dry run'
fi

# Committing and tagging take directly place in the GitHub Actions workflow.
if "${ci}"; then
echo ''
colour::done "$(msg::goodbye)"
echo ''
exit 0
fi

# Second commit and first tag.
colour::pass 'Committing and tagging...'
if ! "${dry}"; then
setup::second_commit "${second_commit}"
else
colour::warn 'Skipping committing and tagging because of dry run'
fi
colour::pass "Second commit and first tag made on 'main' branch:"
colour::logs "${second_commit}"

# And finish! :)
echo ''
colour::done "$(msg::byebye "${label}")"
echo ''
}

main "$@"
78 changes: 78 additions & 0 deletions src/first_time_setup/messages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/bash
# @name messages
# @deps envvars
# @brief A package for setting the messages used by the setup script.

set -euo pipefail

source 'src/first_time_setup/envvars.sh'

# @internal
msg.version() {
grep '^version =' pyproject.toml | cut -d '"' -f 2
}

# @description Define the welcome message.
msg::welcome() {
local version
version=$(msg.version)
cat <<MSG
Welcome to the OpenFisca Extension Template setup script v${version}!
This script will guide you through the process of setting up a new OpenFisca
jurisdiction from start to finish. We will begin now...
MSG
}

# @description Define the ci/repo validation message.
msg::stop() {
cat <<MSG
It seems you cloned this repository, or already initialised it. Refusing to go
further as you might lose work. If you are certain this is a new repository,
run 'cd "${ROOT_PATH}" && rm -rf .git' to erase the history.
MSG
}

# @description Define the prompt for the jurisdiction name.
msg::prompt_name() {
cat <<MSG
The name of the jurisdiction (usually a city or a region, e.g. Île-d'Yeu,
Val-d'Isère...) you will model the rules of:
MSG
}

# @description Define the prompt for the url of the repository.
msg::prompt_url() {
cat <<MSG
Your Git repository URL (i.e.
https://githost.example/organisation/openfisca-jurisdiction):
MSG
}

# @description Define the message whether to continue or not.
msg::prompt_continue() {
cat <<MSG
Would you like to continue (type Y for yes, N for no):
MSG
}

# @description Define the good bye message.
msg::goodbye() {
cat <<MSG
The setup script has finished. You can now start writing your legislation with
OpenFisca 🎉. Happy rules-as-coding!
MSG
}

# @description Define the byebye message.
# @arg $1 The jurisdiction label.
msg::byebye() {
cat <<MSG
Bootstrap complete, you can now push this codebase to your remote repository.
First, set up the remote with 'git remote add origin <SSH repository URL>'.
You can then 'git push origin main' and refer to the README.md.
The parent directory name has been changed, you can use
'cd ../openfisca-${1} to navigate to it.
MSG
}
Loading

0 comments on commit 1fa2646

Please sign in to comment.