#!/usr/bin/env bash # Author: Lucas Frérot # Affiliation: # Sorbonne Université, CNRS, Institut Jean Le Rond d'Alembert, # F-75005 Paris, France # # Copyright © 2024 Lucas Frérot # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Sane bash options set -o errexit set -o nounset set -o pipefail # Colors readonly RED='\033[0;31m' readonly ORANGE='\033[0;33m' readonly GREEN='\033[0;32m' readonly BLUE='\033[0;34m' readonly CYAN='\033[0;36m' readonly NC='\033[0m' # Gitea readonly ENDPOINT="https://git.dalembert.upmc.fr/api/v1" # Lab info readonly AFFILIATION="Sorbonne Université, CNRS, Institut Jean Le Rond d'Alembert, F-75005 Paris, France" # Files readonly MANDATORY_FILES=("README.md" "AUTHORS" "COPYING" "make_all_figures" "tests/run_all_tests") # ----------------- Logging commands ----------------------- # Print error and exit error() { printf "${RED}error${NC}: %b\\n" "$@" 1>&2 exit 1 } # Print warning warning() { printf "${ORANGE}warning${NC}: %b\\n" "$@" 1>&2 } # Print info info() { printf "${GREEN}info${NC}: %b\\n" "$@" 1>&2 } # Enter value enter() { printf "${BLUE}input${NC}: %b" "$@" 1>&2 local input_var='' read input_var printf "${input_var}" } # Test if a variable name is set is_set() { local var_name="$1" eval "! [[ -z \"\${${var_name}+x}\" ]]" return $? } # Set var to first value if unset alt_var() { local value_if_unset="$1" local variable="$2" if is_set "${variable}"; then printf "%s" "$(eval "printf \"%s\" \"\${${variable}}\"")" else printf "%s" "${value_if_unset}" fi } # Enter value if unset cond_enter() { local input="$1" local var_name="$2" local value="$(alt_var "" "${var_name}")" if [[ "${value}" == "" ]]; then value="$(enter "${input}")" fi printf "%s" "${value}" } # Check that command exists has_command() { command -v "$1" >/dev/null 2>&1 } # Check curl and jq to process API calls to gitea check_api_prerequisites() { if ! has_command curl; then error "curl not found, please install" fi if ! has_command jq; then error "jq not found, please install" fi } # ----------------- Gitea API commands ----------------------- get_gitea_token(){ if [[ -f token ]]; then read TOKEN < token else TOKEN="$(enter "gitea token: ")" fi } gitea() { check_api_prerequisites if ! is_set TOKEN; then get_gitea_token fi readonly TOKEN local method="$1" local request="$2" local data="" if [[ $# > 2 ]]; then data="$3" fi \curl -s -X "${request}" \ -H "Content-Type: application/json" \ -H "Authorization: token ${TOKEN}" \ -d "${data}" \ "${ENDPOINT}/${method}" } # ----------------- Git commands ----------------------- # Set value of git config parameter set_git_config() { local param="$1" local value='' while ! [[ -n "${value}" ]]; do value="$(enter "new value for ${param}: ")" done \git config "${param}" "${value}" info "setting new value for ${param}: '$(\git config "${param}")'" printf "${value}" } # Get value of git config parameter, set if unset get_git_config() { local param="$1" local value="$(\git config "${param}")" if ! [[ -n "${value}" ]]; then warning "git ${param} is unset" value="$(set_git_config "${param}")" fi printf "${value}" } # Check git configuration and correct if necessary check_git_config() { if ! has_command git; then error "git not found, please install" fi readonly USER="$(alt_var "$(get_git_config user.name)" USER)" readonly EMAIL="$(alt_var "$(get_git_config user.email)" EMAIL)" info "found git credentials:\\n - user.name: '${USER}'\\n - user.email: '${EMAIL}'" } # Initialize git repository init_repo() { check_git_config info "$(\git init)" } # ----------------- Tree commands ----------------------- # Make bash script stub script_stub() { local script_name="$1" local start_phrase="$2" if ! [[ -f "${script_name}" ]]; then mkdir -p "$(dirname "${script_name}")" cat << STUB > "${script_name}" #!/usr/bin/env bash set -euo pipefail main() { printf "${start_phrase}\\n" 1>&2 # put code here } main "\$@" STUB chmod +x "${script_name}" fi info "wrote '${script_name}'" } # Fetch licence fetch_licence() { local licence="COPYING" if ! [[ -f "${licence}" ]]; then if has_command curl; then info "setting licence to GPL by default, see https://www.gnu.org/licenses for more options" \curl -s "https://www.gnu.org/licenses/gpl-3.0.txt" > "${licence}" info "wrote '${licence}'" else warning "please choose a free software licence, see https://www.gnu.org/licenses" fi fi } # Create README create_readme() { local readme="${MANDATORY_FILES[0]}" if ! [[ -f "${readme}" ]]; then local project_name="$(cond_enter "project name: " PROJECT_NAME)" local project_desc="$(cond_enter "project short description: " PROJECT_DESC)" cat << READMEMSG > "${readme}" # ${project_name} ${project_desc} ## Dependencies Here are the dependencies to build and run the code: - ## Running the code Here is how to run the code: \`\`\` ./make_all_figures \`\`\` ## Tests Here is how to run the tests: \`\`\` ./tests/run_all_tests \`\`\` READMEMSG info "wrote '${readme}'" fi } create_authors_file() { if ! [[ -f AUTHORS ]]; then printf "%s\\n" "${USER} <${EMAIL}> ${AFFILIATION}" > AUTHORS fi info "wrote 'AUTHORS'" } # Create required files create_tenet_file_tree() { create_readme create_authors_file fetch_licence script_stub tests/run_all_tests "Running all tests..." script_stub make_all_figures "Generating figures..." } # Check tree structure check-tree() { local directory="$1" local check_failed=0 pushd "${directory}" >/dev/null # Test directory is a git repo if ! \git status > /dev/null 2>&1; then warning "git failed: $PWD is not a repo" check_failed=1 fi # Test config values if ! ( [[ "$(\git config user.name)" != "" ]] \ && [[ "$(\git config user.email)" != "" ]] ); then warning "git config failed" check_failed=1 fi # Test file structure for file in "${MANDATORY_FILES[@]}"; do if ! [[ -f "${file}" ]]; then warning "file ${file} is missing" check_failed=1 fi done popd >/dev/null return ${check_failed} } # ----------------- Gitea commands ----------------------- get_gitea_owner() { gitea /user GET | jq -r '.login' } # Check if repository exists get_gitea_repo() { local owner="$1" local repo_name="$2" gitea "/repos/${owner}/${repo_name}" GET } # Create a repository on gitea create_gitea_repo() { local owner="$1" local repo_name="$2" local get_response="$(get_gitea_repo "${owner}" "${repo_name}")" # Check if repo already exits if [[ "$(jq '.id' <<< "${get_response}")" == "null" ]]; then info "creating repository '${repo_name}' on gitea" gitea /user/repos POST "{ \"name\": \"${repo_name}\", \"private\": true }" else info "found repository '${repo_name}' on gitea" printf "%s" "${get_response}" fi } # API call to setup the software heritage webhook setup_software_heritage_hook() { local owner="$1" local repo_name="$2" local remote_url="$3" #gitea "/repos/${owner}/${repo_name}/hooks" GET gitea "/repos/${owner}/${repo_name}/hooks" POST \ "{ \"type\": \"gitea\", \"config\": { \"content_type\": \"json\", \"url\": \"https://archive.softwareheritage.org/api/1/origin/save/git/url/${remote_url}\" }, \"events\": [\"release\"], \"active\": true }" info "created Software Hertiage webhook for releases in gitea" } # Setup remote setup_dalembert_gitea() { local repo_name="$(basename <<< "${PWD}")" local owner="$(get_gitea_owner)" local repo_json="$(create_gitea_repo "${repo_name}")" local remote_ssh="$(jq '.ssh_url' <<< "${repo_json}")" local remote_http="$(jq '.clone_url' <<< "${repo_json}")" if [[ "$(\git remote | \grep -c '^dalembert$' || true)" == "0" ]]; then \git remote add dalembert "${remote_ssh}" info "created remote dalembert with '${remote_ssh}" else \git remote set-url dalembert "${remote_ssh}" info "set remote dalembert with '${remote_ssh}" fi setup_software_heritage_hook "${owner}" "${repo_name}" "${remote_http}" } # ----------------- Script subcommands commands ----------------------- init-tree() { declare desc="usage: rtenets init-tree " local directory="$1" ( cd "${directory}" init_repo create_tenet_file_tree ) } init-gitea() { declare desc="usage: rtenets init-gitea " } init-workflow() { declare desc="usage: rtenets init-workflow " } # Create git repository create() { declare desc="usage: rtenets create " local repo_name="$1" info "recursively creating directory '${repo_name}'" mkdir -p "${repo_name}" ( info "initializing repository '${repo_name}'" cd "${repo_name}" init_repo create_tenet_file_tree ) } # Print usage and exit usage() { cat </dev/null ) && true || main "$@"