From 1d4d811718db4e5131924a79cc7ad681f02ac83d Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Sat, 16 Sep 2023 17:17:30 +0200 Subject: [PATCH] fix: setting the paths does not always work smoothly #9 --- .gitlab-ci.yml | 9 +- Taskfile.yml | 44 +- configuration.iml | 15 +- devenv.nix | 731 +++++++++++++++--- go.mod | 2 +- go.sum | 20 + import.go | 122 ++- import_test.go | 85 +- integration_test.go | 2 - issue-7_test.go | 12 +- .../github.com/pelletier/go-toml/v2/LICENSE | 3 +- 11 files changed, 813 insertions(+), 232 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b2885e..4339280 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,15 @@ + +# THIS FILE IS AUTOGENERATED BY THE DEVENVSHELL +# DO NOT EDIT THIS FILE MANUALLY +# INSTEAD EDIT THE DEVENVSHELL CONFIGURATION FILE devenv.nix +# AND OPEN A SHELL WITH THE COMMAND devenv shell +# + image: docker-registry.schukai.com:443/nixos-ci-devenv:latest services: - docker:dind - variables: # The repo name as used in # https://github.com/nix-community/NUR/blob/master/repos.json @@ -13,7 +19,6 @@ variables: DOCKER_DRIVER: overlay2 GIT_DEPTH: 10 - stages: - test - deploy diff --git a/Taskfile.yml b/Taskfile.yml index 8fd6c58..b7313f4 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,3 +1,10 @@ + +# THIS FILE IS AUTOGENERATED BY THE DEVENVSHELL +# DO NOT EDIT THIS FILE MANUALLY +# INSTEAD EDIT THE DEVENVSHELL CONFIGURATION FILE devenv.nix +# AND OPEN A SHELL WITH THE COMMAND devenv shell +# +# Information about the task runner can be found here: # https://taskfile.dev version: '3' @@ -5,29 +12,36 @@ version: '3' tasks: default: cmds: - - task --list-all + - task --list silent: true - + test: desc: Execute unit tests in Go. - cmds: + cmds: - echo "Execute unit tests in Go." - go test -cover -v ./... - - go test -bench . - - go test -race . + - go test -bench -v ./... + - go test -race -v ./... test-fuzz: desc: Conduct fuzzing tests.# cmds: - echo "Conduct fuzzing tests." - - go test -v -fuzztime=10s -fuzz=Fuzz ./... + - go test -v -fuzztime=30s -fuzz=Fuzz ./... add-licenses: desc: Attach license headers to Go files. cmds: - echo "Attach license headers to Go files." + - go install github.com/google/addlicense@latest - addlicense -c "schukai GmbH" -s -l "AGPL-3.0" ./*.go silent: true + + check-licenses: + desc: Check license headers of Go files. + silent: true + cmds: + - go-licenses save "$(get-go-default-packages)" --ignore "gitlab.schukai.com" --force --save_path ${DEVENV_ROOT}/licenses/ check: desc: Confirm repository status. @@ -35,17 +49,11 @@ tasks: - git diff-index --quiet HEAD || (echo "There are uncommitted changes after running make. Please commit or stash them before running make."; exit 1) silent: true - build: - desc: Compile the application, + commit: + desc: Commit changes to the repository. aliases: - - b - vars: - DEVENV_ROOT: - sh: echo "$DEVENV_ROOT" - + - c + - ci + - git-commit cmds: - - devenv shell build-app - sources: - - source/**/*.go - - source/**/*.mod - - dist/** + - do-git-commit diff --git a/configuration.iml b/configuration.iml index 11dced7..5f86599 100644 --- a/configuration.iml +++ b/configuration.iml @@ -1,18 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> -<module type="WEB_MODULE" version="4"> - <component name="Go" enabled="true"> - <buildTags> - <option name="customFlags"> - <array> - <option value="" /> - </array> - </option> - </buildTags> - </component> +<module type="JAVA_MODULE" version="4"> + <component name="Go" enabled="true" /> <component name="NewModuleRootManager" inherit-compiler-output="true"> <exclude-output /> <content url="file://$MODULE_DIR$"> - <excludeFolder url="file://$MODULE_DIR$/licenses" /> + <sourceFolder url="file://$MODULE_DIR$/.devenv/state/go/pkg/mod/github.com/google/addlicense@v1.1.1/testdata/expected" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/.devenv/state/go/pkg/mod/github.com/google/addlicense@v1.1.1/testdata/initial" isTestSource="false" /> </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> diff --git a/devenv.nix b/devenv.nix index 0eb76b3..efd4d9b 100644 --- a/devenv.nix +++ b/devenv.nix @@ -1,149 +1,638 @@ -{ pkgs, inputs, phps, lib, config, modulesPath,... }: +{ pkgs, inputs, phps, lib, config, modulesPath, ... }: { - # https://devenv.sh/packages/ packages = with pkgs; [ inputs.version.defaultPackage."${builtins.currentSystem}" - git - gcc12 - go-task + appimage-run blackbox blackbox-terminal - jq + coreutils-full + dbeaver delve + dialog + drill + exa + fd + fd + gcc12 gdlv - wget + git glab - unixtools.xxd - libffi - zlib - procps - php81Extensions.xdebug - ranger - meld - gnused - coreutils-full gnugrep gnumake - util-linux + gnused + go-licenses + go-task + gum httpie - netcat + hurl + jq + libffi + logrotate + meld memcached - fd + netcat + nixfmt + procps + ranger + unixtools.xxd + unzip + util-linux + wget + zlib ]; - # https://devenv.sh/languages/ # languages.nix.enable = true; - languages = { - go = { enable = true; }; - }; - - difftastic.enable = true; + languages = { go = { enable = true; }; }; + + difftastic.enable = true; + + scripts.get-go-default-packages.exec = '' + #!${pkgs.bash}/bin/bash + echo $(awk -F ' ' '/^module / { print $2 }' go.mod) + ''; - # This script is executed when the app is built # You can use it to build the app - scripts.test-lib.exec = '' -#!${pkgs.bash}/bin/bash -#set -euo pipefail -set -x - -PATH="''${PATH}":${pkgs.coreutils}/bin -PATH="''${PATH}":${pkgs.findutils}/bin -PATH="''${PATH}":${pkgs.jq}/bin/ -PATH="''${PATH}":${pkgs.rsync}/bin/ -PATH="''${PATH}":${pkgs.bash}/bin/ -PATH="''${PATH}":${pkgs.curl}/bin/ -PATH="''${PATH}":${pkgs.moreutils}/bin/ -PATH="''${PATH}":${pkgs.gnutar}/bin -PATH="''${PATH}":${pkgs.gzip}/bin/ -PATH="''${PATH}":${pkgs.procps}/bin/ -PATH="''${PATH}":${pkgs.exa}/bin/ -PATH="''${PATH}":${pkgs.git}/bin/ -PATH="''${PATH}":${pkgs.gnugrep}/bin/ -PATH="''${PATH}":${inputs.version.defaultPackage."${builtins.currentSystem}"}/bin/ - -export PATH - -task test - -''; + scripts.test-lib.exec = '' + #!${pkgs.bash}/bin/bash + #set -euo pipefail + set -x + + PATH="''${PATH}":${pkgs.coreutils}/bin + PATH="''${PATH}":${pkgs.findutils}/bin + PATH="''${PATH}":${pkgs.jq}/bin/ + PATH="''${PATH}":${pkgs.rsync}/bin/ + PATH="''${PATH}":${pkgs.bash}/bin/ + PATH="''${PATH}":${pkgs.curl}/bin/ + PATH="''${PATH}":${pkgs.moreutils}/bin/ + PATH="''${PATH}":${pkgs.gnutar}/bin + PATH="''${PATH}":${pkgs.gzip}/bin/ + PATH="''${PATH}":${pkgs.procps}/bin/ + PATH="''${PATH}":${pkgs.exa}/bin/ + PATH="''${PATH}":${pkgs.git}/bin/ + PATH="''${PATH}":${pkgs.gnugrep}/bin/ + PATH="''${PATH}":${ + inputs.version.defaultPackage."${builtins.currentSystem}" + }/bin/ + + export PATH + + task test + + ''; # This scritp is used to deploy the app to the gitlab registry # It is used by the gitlab-ci.yml file # The environment variables are set in the gitlab project settings - scripts.deploy-lib.exec = '' -#!${pkgs.bash}/bin/bash - -PATH="''${PATH}":${pkgs.coreutils}/bin -PATH="''${PATH}":${pkgs.jq}/bin/ -PATH="''${PATH}":${pkgs.curl}/bin/ -PATH="''${PATH}":${pkgs.moreutils}/bin/ -PATH="''${PATH}":${pkgs.gnutar}/bin -PATH="''${PATH}":${pkgs.gzip}/bin/ -PATH="''${PATH}":${pkgs.exa}/bin/ -PATH="''${PATH}":${pkgs.git}/bin/ -PATH="''${PATH}":${inputs.version.defaultPackage."${builtins.currentSystem}"}/bin/ - -export PATH - -if [[ -f .env-gitlab-ci ]]; then - source .env-gitlab-ci - rm .env-gitlab-ci -fi - -set -x -## if $HOME not set, set it to current directory -if [[ -z "''${HOME}" ]]; then - HOME=$(pwd) -fi - -export HOME - -git config user.email "''${GITLAB_USER_EMAIL}" -git config user.name "''${GITLAB_USER_NAME:-"Gitlab CI"}" -git config pull.rebase true -git config http.sslVerify "false" -git remote set-url origin https://pad:''${GITLAB_TOKEN}@''${CI_REPOSITORY_URL#*@} - -git fetch --all --tags --unshallow -git reset --hard origin/master -git checkout $CI_COMMIT_REF_NAME -git pull origin $CI_COMMIT_REF_NAME - -if [ ! -z "''${CI_PROJECT_DIR}" ]; then - echo "CI_PROJECT_DIR is set, using it as project root." - project_root=$(realpath "''${CI_PROJECT_DIR}")/ -elif [ ! -z "''${DEVENV_ROOT}" ]; then - echo "DEVENV_ROOT is set, using it as project root." - project_root=$(realpath "''${DEVENV_ROOT}")/ -else - echo "Error: DEVENV_ROOT or CI_PROJECT_DIR environment variables are not set." - exit 1 -fi - -if [ ! -d "''${project_root}" ]; then - echo "Error: Project root directory does not seem to be valid." - echo "Check the DEVENV_ROOT or CI_PROJECT_DIR environment variables." - exit 1 -fi - -if [ -z "'CI_JOB_TOKEN" ]; then - echo "Error: CI_JOB_TOKEN variable is not set." - exit 1 -fi - -git --no-pager log --decorate=short --pretty=oneline -gitVersion=v$(version predict) -git tag -a $gitVersion -m"chore: bump version" -git --no-pager log --decorate=short --pretty=oneline -git push -o ci.skip origin ''${CI_COMMIT_REF_NAME} --tags - -echo "done" - -''; + scripts.deploy-lib.exec = '' + #!${pkgs.bash}/bin/bash + + PATH="''${PATH}":${pkgs.coreutils}/bin + PATH="''${PATH}":${pkgs.jq}/bin/ + PATH="''${PATH}":${pkgs.curl}/bin/ + PATH="''${PATH}":${pkgs.moreutils}/bin/ + PATH="''${PATH}":${pkgs.gnutar}/bin + PATH="''${PATH}":${pkgs.gzip}/bin/ + PATH="''${PATH}":${pkgs.exa}/bin/ + PATH="''${PATH}":${pkgs.git}/bin/ + PATH="''${PATH}":${ + inputs.version.defaultPackage."${builtins.currentSystem}" + }/bin/ + + export PATH + + if [[ -f .env-gitlab-ci ]]; then + source .env-gitlab-ci + rm .env-gitlab-ci + fi + + set -x + ## if $HOME not set, set it to current directory + if [[ -z "''${HOME}" ]]; then + HOME=$(pwd) + fi + + export HOME + + git config user.email "''${GITLAB_USER_EMAIL}" + git config user.name "''${GITLAB_USER_NAME:-"Gitlab CI"}" + git config pull.rebase true + git config http.sslVerify "false" + git remote set-url origin https://pad:''${GITLAB_TOKEN}@''${CI_REPOSITORY_URL#*@} + + git fetch --all --tags --unshallow + git reset --hard origin/master + git checkout $CI_COMMIT_REF_NAME + git pull origin $CI_COMMIT_REF_NAME + + if [ ! -z "''${CI_PROJECT_DIR}" ]; then + echo "CI_PROJECT_DIR is set, using it as project root." + project_root=$(realpath "''${CI_PROJECT_DIR}")/ + elif [ ! -z "''${DEVENV_ROOT}" ]; then + echo "DEVENV_ROOT is set, using it as project root." + project_root=$(realpath "''${DEVENV_ROOT}")/ + else + echo "Error: DEVENV_ROOT or CI_PROJECT_DIR environment variables are not set." + exit 1 + fi + + if [ ! -d "''${project_root}" ]; then + echo "Error: Project root directory does not seem to be valid." + echo "Check the DEVENV_ROOT or CI_PROJECT_DIR environment variables." + exit 1 + fi + + if [ -z "'CI_JOB_TOKEN" ]; then + echo "Error: CI_JOB_TOKEN variable is not set." + exit 1 + fi + + git --no-pager log --decorate=short --pretty=oneline + gitVersion=v$(version predict) + git tag -a $gitVersion -m"chore: bump version" + git --no-pager log --decorate=short --pretty=oneline + git push -o ci.skip origin ''${CI_COMMIT_REF_NAME} --tags + + echo "done" + + ''; + + enterShell = '' + + cat <<'EOF' > Taskfile.yml + + # THIS FILE IS AUTOGENERATED BY THE DEVENVSHELL + # DO NOT EDIT THIS FILE MANUALLY + # INSTEAD EDIT THE DEVENVSHELL CONFIGURATION FILE devenv.nix + # AND OPEN A SHELL WITH THE COMMAND devenv shell + # + # Information about the task runner can be found here: + # https://taskfile.dev + + version: '3' + + tasks: + default: + cmds: + - task --list + silent: true + + test: + desc: Execute unit tests in Go. + cmds: + - echo "Execute unit tests in Go." + - go test -cover -v ./... + - go test -bench -v ./... + - go test -race -v ./... + + test-fuzz: + desc: Conduct fuzzing tests.# + cmds: + - echo "Conduct fuzzing tests." + - go test -v -fuzztime=30s -fuzz=Fuzz ./... + + add-licenses: + desc: Attach license headers to Go files. + cmds: + - echo "Attach license headers to Go files." + - go install github.com/google/addlicense@latest + - addlicense -c "schukai GmbH" -s -l "AGPL-3.0" ./*.go + silent: true + + check-licenses: + desc: Check license headers of Go files. + silent: true + cmds: + - go-licenses save "$(get-go-default-packages)" --ignore "gitlab.schukai.com" --force --save_path ''${DEVENV_ROOT}/licenses/ + + check: + desc: Confirm repository status. + cmds: + - git diff-index --quiet HEAD || (echo "There are uncommitted changes after running make. Please commit or stash them before running make."; exit 1) + silent: true + + commit: + desc: Commit changes to the repository. + aliases: + - c + - ci + - git-commit + cmds: + - do-git-commit + EOF + + cat <<'EOF' > .gitlab-ci.yml + + # THIS FILE IS AUTOGENERATED BY THE DEVENVSHELL + # DO NOT EDIT THIS FILE MANUALLY + # INSTEAD EDIT THE DEVENVSHELL CONFIGURATION FILE devenv.nix + # AND OPEN A SHELL WITH THE COMMAND devenv shell + # + + image: docker-registry.schukai.com:443/nixos-ci-devenv:latest + + services: + - docker:dind + + variables: + # The repo name as used in + # https://github.com/nix-community/NUR/blob/master/repos.json + NIXOS_VERSION: "23.05" + NIXPKGS_ALLOW_UNFREE: "1" + NIXPKGS_ALLOW_INSECURE: "1" + DOCKER_DRIVER: overlay2 + GIT_DEPTH: 10 + + stages: + - test + - deploy + + before_script: + - nix shell nixpkgs#coreutils-full -c mkdir -p /certs/client/ + - nix shell nixpkgs#coreutils-full -c ln -fs /etc/ssl/certs/ca-bundle.crt /certs/client/ca.pem + - echo > .env-gitlab-ci + - variables=("HOME=$HOME" "CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME" "CI_REPOSITORY_URL=$CI_REPOSITORY_URL" "GITLAB_TOKEN=$GITLAB_TOKEN" "CI_JOB_TOKEN=$CI_JOB_TOKEN" "GITLAB_USER_EMAIL=$GITLAB_USER_EMAIL" "GITLAB_USER_NAME=\"$GITLAB_USER_NAME\"" "CI_REGISTRY_USER=$CI_REGISTRY_USER" "CI_PROJECT_ID=$CI_PROJECT_ID" "CI_PROJECT_DIR=$CI_PROJECT_DIR" "CI_API_V4_URL=$CI_API_V4_URL" "CI_PROJECT_NAME=$CI_PROJECT_NAME" "CI_COMMIT_SHORT_SHA=$CI_COMMIT_SHORT_SHA"); for var in "''${variables[@]}"; do echo "$var" >> .env-gitlab-ci; done + - cat .env-gitlab-ci + + after_script: + - if [ -f .env-gitlab-ci ]; then rm .env-gitlab-ci; fi + + test: + stage: test + tags: + - nixos + script: + - devenv shell test-lib + + cache: + - key: nixos + paths: + - /nix/store + + artifacts: + paths: + - dist + + deploy: + stage: deploy + tags: + - nixos + script: + - devenv shell -c deploy-lib + + when: on_success + + cache: + - key: nixos + paths: + - /nix/store + + + artifacts: + paths: + - dist + EOF + + + + ''; + + scripts.do-git-commit.exec = '' + #!/usr/bin/env bash + + # Define colors if the terminal supports it + if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + RESET='\033[0m' + BOLD='\033[1m' + else + RED="" + GREEN="" + RESET="" + fi + + step=1 + + reset + clear + + # create random log file + LOGFILE="$(mktemp)" + if [ $? -ne 0 ]; then + echo -e "''${RED}✖ Could not create temporary log file. Exiting.''${RESET}" + exit 1 + fi + + log_and_display() { + echo -e "''${GREEN}==> $step. $1''${RESET}" | tee -a $LOGFILE + step=$((step + 1)) + } + + log_error_and_display() { + echo -e "''${RED}==> $step. $1''${RESET}" | tee -a $LOGFILE + } + + printLogfileAndExit() { + exit_code=$1 + echo -e "\n\n========================================\n\n\n" + + echo -e "\n\n''${BOLD}Git and GitLab Automation Script''${RESET}\n\nI have performed the following actions:\n\n" + cat "$LOGFILE" + + # Optional: Remove log file + rm -f "$LOGFILE" + + if [ $exit_code -eq 0 ]; then + echo -e "\n''${GREEN}✔''${RESET} All actions were successful" | tee -a $LOGFILE + elif [ $exit_code -eq -1 ]; then + echo -e "\n''${RED}✖''${RESET} The script was manually cancelled" | tee -a $LOGFILE + exit_code=0 + else + echo -e "\n''${RED}✖''${RESET} Some actions failed" | tee -a $LOGFILE + fi + + exit $exit_code + } + + print_headline() { + local title=$1 + local underline=$(printf '─%.0s' $(seq 1 ''${#title})) + echo -e "\n\n''${BOLD}''${title}\n''${underline}''${RESET}\n" + } + + do_cancel() { + echo -e "''${RED}==> ✖ Cancelled.''${RESET}" | tee -a $LOGFILE + printLogfileAndExit -1 + } + + # Function for unified logging and display + log_action() { + if [ $? -eq 0 ]; then + echo -e " ''${GREEN}✔''${RESET} $1: Successful" | tee -a $LOGFILE + else + echo -e " ''${RED}✖''${RESET} $1: Failed" | tee -a $LOGFILE + printLogfileAndExit 1 + fi + } + + print_what_to_do() { + echo -e "\n\nWhat do you want to do?\n" + } + + git_status=$(git status --porcelain) + if [[ -z "$git_status" ]]; then + log_error_and_display "No changes to commit. Exiting." + printLogfileAndExit 0 + fi + + print_headline "Choose commit type" + selection=$(gum choose "feat: (new feature for the user, not a new feature for build script)" "fix: (bug fix for the user, not a fix to a build script)" "chore: (updating grunt tasks etc.; no production code change)" "docs: (changes to the documentation)" "style: (formatting, missing semi colons, etc; no production code change)" "refactor: (refactoring production code, eg. renaming a variable)" "test: (adding missing tests, refactoring tests; no production code change)" "Cancel") + + commit_type=$(echo "$selection" | awk -F':' '{print $1}') + + if [[ "$commit_type" == "Cancel" ]]; then + do_cancel + fi + + log_and_display "You chose the commit type: $commit_type" + + # NEXT STEP ISSUE HANDLING ############################################################################################################ + #log_and_display "Issue handling" + + gitlabIssues=() + while IFS= read -r line; do + if [[ $line =~ ^# ]]; then + id=$(echo "$line" | awk '{print substr($1, 2)}') + title=$(echo "$line" | awk -F'about' '{print $1}' | awk '{$1=$2=""; print substr($0, 3)}') + gitlabIssues+=("$id > $title") + fi + done < <(gum spin --spinner dot --show-output --title "Ask gitlab ..." -- glab issue list --output-format=details) + + ## if issues are available, ask if user wants to use an existing issue or create a new one + createOption="Create new issue" + existingOption="Use existing issue" + cancelOption="Cancel" + + print_headline "Choose issue handling" + if [ ''${#gitlabIssues[@]} -eq 0 ]; then + log_and_display "There are no issues available." + + print_what_to_do + choice=$(gum choose "$createOption" "$cancelOption") + + else + log_and_display "There are ''${#gitlabIssues[@]} issues available." + print_what_to_do + choice=$(gum choose "$createOption" "$existingOption" "$cancelOption") + fi + + if [[ "$choice" == "$cancelOption" ]]; then + do_cancel + fi + + ## array of issue ids + work_on_issue_ids=() + + issue_text="" + + if [[ "$choice" == "$createOption" ]]; then + print_headline "Create new issue" + issue_text=$(gum input --placeholder "Enter issue title") + echo -e "Enter issue description. ''${RED}End with Ctrl+D''${RESET}\n" + issue_description=$(gum write --placeholder "Enter issue description. End with Ctrl+D") + + if [[ -z "$issue_text" ]]; then + log_error_and_display "Issue title is empty. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "You entered the issue title: $issue_text" + log_and_display "You entered the issue description: $issue_description" + echo -e "\n" + + gum confirm "Do you want to create this issue?" + # gum confirm exits with status 0 if confirmed and status 1 if cancelled. + if [ $? -eq 1 ]; then + do_cancel + fi + + issue_output=$(glab issue create -t"$issue_text" --no-editor --description "$issue_description") + issue_id=$(echo "$issue_output" | grep -oP '(?<=/issues/)\d+') + + work_on_issue_ids+=("$issue_id") + + log_action "glab issue with id $issue_id created" + + else + + print_headline "Use existing issue" + echo -e "Select issue with arrow keys and press tab or space to select. Press enter to confirm.\n" + issue_ids=$(gum choose --no-limit "''${gitlabIssues[@]}") + + # assign issue_ids to work_on_issue_ids. iterate over lines and take integer from beginning of line + while IFS= read -r line; do + work_on_issue_ids+=($(echo "$line" | grep -oP '^\d+')) + done <<<"$issue_ids" + + fi + + if [ ''${#work_on_issue_ids[@]} -eq 0 ]; then + log_and_display "No issue selected. Exiting." + printLogfileAndExit 0 + fi + + # NEXT STEP COMMIT MESSAGE ############################################################################################################ + # print work_on_issue_ids + work_on_issue_ids_string="" + for i in "''${work_on_issue_ids[@]}"; do + work_on_issue_ids_string+="#$i " + done + + log_and_display "You chose to work on the following issues: ''${work_on_issue_ids_string}" + + + print_headline "Check for changes to commit" + + # ' ' = unmodified + # M = modified + # T = file type changed (regular file, symbolic link or submodule) + # A = added + # D = deleted + # R = renamed + # C = copied (if config option status.renames is set to "copies") + # U = updated but unmerged + # https://man.freebsd.org/cgi/man.cgi?query=git-status&sektion=1&manpath=freebsd-release-ports + + count_all_changes=$(echo "$git_status" | wc -l) + count_staged_changes=$(echo "$git_status" | grep -c '^M') + count_new_staged_files=$(echo "$git_status" | grep -c '^A') + count_staged_changes=$((count_staged_changes + count_new_staged_files)) + + + + git_options_all="All $count_all_changes changes" + git_options_staged="Only the $count_staged_changes staged changes" + git_options_select_files="Select files" + git_options_cancel="Cancel" + + git_options_array=() + if [[ $count_all_changes -gt 0 ]]; then + git_options_array+=("$git_options_all") + fi + + if [[ $count_staged_changes -gt 0 ]]; then + git_options_array+=("$git_options_staged") + fi + + git_options_array+=( "$git_options_select_files" ) + git_options_array+=( "$git_options_cancel" ) + + + selection=$(gum choose "''${git_options_array[@]}") + if [[ "$selection" == "$git_options_cancel" ]]; then + do_cancel + fi + + if [[ "$selection" == "$git_options_all" ]]; then + git add -A + echo "1" + elif [[ "$selection" == "$git_options_select_files" ]]; then + + files=() + while IFS= read -r line; do + files+=("$line") + done <<<"$git_status" + + selected_files=$(gum choose --no-limit "''${files[@]}") + + # no files selected + if [[ -z "$selected_files" ]]; then + log_and_display "No files selected. Exiting." + printLogfileAndExit 0 + fi + + # add selected files + while IFS= read -r line; do + ## git proclimne could have letter, ? or ! at the beginning of the line + file=$(echo "$line" | awk '{print $2}') + if [[ -z "$file" || ! -f "$file" ]]; then + log_and_display "No file found in line: $line" + continue + fi + + git add "$file" + done <<<"$selected_files" + + fi + + ## count staged changes again and print + count_staged_changes=$(echo "$git_status" | grep -c '^M') + count_new_staged_files=$(echo "$git_status" | grep -c '^A') + count_staged_changes=$((count_staged_changes + count_new_staged_files)) + + log_and_display "You have $count_staged_changes staged changes to commit." + + # NEXT STEP COMMIT MESSAGE ############################################################################################################ + + print_headline "Enter commit message" + commit_message=$(gum input --placeholder "Enter commit message" --value "$commit_type: $issue_text $work_on_issue_ids_string") + + if [[ -z "$commit_message" ]]; then + log_error_and_display "Commit message is empty. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "You entered the commit message: $commit_message" + + gum confirm "Do you want to commit with this message?" + if [ $? -eq 1 ]; then + do_cancel + fi + + # NEXT STEP COMMIT #################################################################################################################### + print_headline "Committing changes" + + if ! git commit -m "$commit_message" ; then + log_error_and_display "Commit failed. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "Commit successful." + + # NEXT STEP PUSH ###################################################################################################################### + + print_headline "Pushing changes" + + if ! git push ; then + log_error_and_display "Push failed. Exiting." + printLogfileAndExit 1 + fi + + log_and_display "Push successful." + + # Close issue ###################################################################################################################### + + print_headline "Closing issues" + + for issue_id in "''${work_on_issue_ids[@]}"; do + + gum confirm "Do you want to close issue #$issue_id?" + if [ $? -eq 1 ]; then + continue + fi + + if ! glab issue close "$issue_id" ; then + log_error_and_display "Closing issue $issue_id failed. Exiting." + else + log_and_display "Closing issue $issue_id successful." + fi + done + printLogfileAndExit 0 + ''; } diff --git a/go.mod b/go.mod index 8b36c5e..caac104 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/stretchr/testify v1.8.4 gitlab.schukai.com/oss/libraries/go/application/xflags v1.9.0 gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.3.1 - gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.5.2 + gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.9.1 gitlab.schukai.com/oss/libraries/go/utilities/watch v0.3.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index a24095f..30ff20c 100644 --- a/go.sum +++ b/go.sum @@ -66,6 +66,26 @@ gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.3.1 h1:oyElaqEiyr2Xg gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.3.1/go.mod h1:UvdD4NAf3gLKYafabJD7e9ZCOetzM9JZ9y4GkZukPVU= gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.5.2 h1:R+dL2NJCM+AQNPK4DPDmfvx1eomi1Xb1dl0XKEFj7Ek= gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.5.2/go.mod h1:UvdD4NAf3gLKYafabJD7e9ZCOetzM9JZ9y4GkZukPVU= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.5.3 h1:pl5XnGS6lxBCI98pnqNU0D0qt19X7g7/Qf/ycM4Do44= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.5.3/go.mod h1:0vlu9GwsHHuFTm49FSOs60UN3KDgcrcfDeCxsG3Ygyg= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.6.0 h1:1edcW7HB2VvTqzFUEGnnJih316sJvBvwGxBwMP8m3ho= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.6.0/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.7.0 h1:ReofKz+Ys4d2l4y4Whwfbtj7CQPEL1ew46QTf1kPW7A= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.7.0/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.7.1 h1:NRlSHy8DlWhkmZkHzjjdPj7hQMrpL8K8V9an9JW9UAk= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.7.1/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.7.2 h1:LF9b1UiWpShxld9Re4VnvkRt3oi3KqTqpSaU21F9tKg= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.7.2/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.8.0 h1:jG/kDUsBgaqIHLoMiF4cJA627neqf9BwaoKFMaMMB6U= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.8.0/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.8.1 h1:A3KvLvu4rV3OstgEn6xHulhQaXawVvzFzbafYHWHUfs= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.8.1/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.8.2 h1:Gs06CcP/pvywyO86dAsv2fmJfwPoJrTjN9fX7CqT/54= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.8.2/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.9.0 h1:YJHX4IDO+1c+AufidfYBcfZA8OlvLsqs8K9Hq4R8icY= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.9.0/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.9.1 h1:WpM6PcFqQZWPBGDEuMILWHq98dPVBLAxi8ae9QMWWbM= +gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.9.1/go.mod h1:MqCBFv7DXKoBE2rZDc51LGvl2QI7Kz0D+XkQ0izj+ws= gitlab.schukai.com/oss/libraries/go/utilities/watch v0.1.0 h1:FAKHmf9p3NKyzuM0cIXYBxmhdQ7zJ+6wj5qqeoIMbGc= gitlab.schukai.com/oss/libraries/go/utilities/watch v0.1.0/go.mod h1:tMFl68peRKHgFQLltrTN3JLredofMqvGi3C0SEAj73Y= gitlab.schukai.com/oss/libraries/go/utilities/watch v0.2.0 h1:tLjN9Wyv+LJhtiiQDzdzaDelEq2LVCDP3Ndo7ZPIWfQ= diff --git a/import.go b/import.go index 8fddd9f..3139764 100644 --- a/import.go +++ b/import.go @@ -6,15 +6,16 @@ package configuration import ( "bytes" "encoding/json" - "reflect" - + "fmt" "github.com/imdario/mergo" "github.com/magiconair/properties" "github.com/pelletier/go-toml/v2" + "gitlab.schukai.com/oss/libraries/go/utilities/pathfinder" "gopkg.in/yaml.v3" "io" "os" "path" + "reflect" ) func importJson[C any](config *C, reader io.Reader) error { @@ -94,87 +95,65 @@ func (s *Settings[C]) importStreams() { } } -func replacePath(p string, c interface{}) { - cValue := reflect.ValueOf(c) +var typeOfPathValue PathValue - // If c is of type PathValue (which implements pathInterface), modify the path - if cValue.Type() == reflect.TypeOf(PathValue("")) { - pathVal := cValue.Interface().(PathValue) - if !path.IsAbs(pathVal.String()) { - newPath := PathValue(path.Join(p, pathVal.String())) - if cValue.CanSet() { - cValue.Set(reflect.ValueOf(newPath)) - } - } - return - } +// replacePath replaces all relative paths with absolute paths +func replacePath[C any](basePath string, c C) error { - // If c is not a struct, simply return - if cValue.Kind() != reflect.Ptr || cValue.Elem().Kind() != reflect.Struct { - return - } + paths := []string{} - fields := reflect.VisibleFields(cValue.Elem().Type()) - for _, field := range fields { - r := cValue.Elem().FieldByName(field.Name) - handleField(p, r) - } -} + top := reflect.TypeOf(typeOfPathValue) + toc := reflect.ValueOf(c) + ptp := &paths -func handleField(p string, r reflect.Value) { - switch r.Kind() { - case reflect.Struct: - if r.CanAddr() { - replacePath(p, r.Addr().Interface()) + pathfinder.FindPaths(toc, top, []string{}, ptp) + + for _, px := range paths { + + p, err := pathfinder.GetValue[C](c, px) + if err != nil { + return err } - case reflect.Slice: - for i := 0; i < r.Len(); i++ { - elem := r.Index(i) - if elem.Type() == reflect.TypeOf(PathValue("")) { - pathVal := elem.Interface().(PathValue) - if !path.IsAbs(pathVal.String()) { - newPath := PathValue(path.Join(p, pathVal.String())) - if elem.CanSet() { - elem.Set(reflect.ValueOf(newPath)) - } - } - } else if elem.CanAddr() { - replacePath(p, elem.Addr().Interface()) + + switch p.(type) { + case PathValue: + tp, ok := p.(PathValue) + if !ok { + return fmt.Errorf("type assertion failed") } - } - case reflect.Map: - for _, k := range r.MapKeys() { - elem := r.MapIndex(k) - if elem.Type() == reflect.TypeOf(PathValue("")) { - pathVal := elem.Interface().(PathValue) - if !path.IsAbs(pathVal.String()) { - newPath := PathValue(path.Join(p, pathVal.String())) - r.SetMapIndex(k, reflect.ValueOf(newPath)) + + if !path.IsAbs(p.(PathValue).String()) { + newPath := PathValue(path.Join(basePath, tp.String())) + + switch reflect.TypeOf(p).Kind() { + case reflect.Ptr: + err = pathfinder.SetValue[C](c, px, &newPath) + + case reflect.String: + if px == "G.I.key1GI.A" { + } + + err = pathfinder.SetValue[C](c, px, newPath.String()) + + default: + err = pathfinder.SetValue[C](c, px, newPath) } - } else if elem.CanAddr() { - replacePath(p, elem.Addr().Interface()) - } - } - default: - // Check for pathInterface - if v, ok := r.Interface().(pathInterface); ok { - // Check if r is nil - if r.Kind() == reflect.Ptr && r.IsNil() { - return - } - currentPath := v.String() - if r.CanSet() && !path.IsAbs(currentPath) { - if r.Type() == reflect.TypeOf(PathValue("")) { - r.Set(reflect.ValueOf(PathValue(path.Join(p, currentPath)))) + + if err != nil { + return err } } } + } + + return nil + } func (s *Settings[C]) importFiles() { - defer func() { + defer func() { s.notifyErrorHooks() }() @@ -190,7 +169,7 @@ func (s *Settings[C]) importFiles() { r := (io.Reader)(f) s.importStream(reader{s.files.format, r}, func(c *C) { - replacePath(d, c) + replacePath[*C](d, c) }) _ = f.Close() } @@ -206,7 +185,10 @@ func (s *Settings[C]) importFiles() { s.importStream(reader{f.format, r}, func(c *C) { d := path.Dir(f.path) - replacePath(d, c) + err := replacePath[*C](d, c) + if err != nil { + return + } }) _ = r.Close() diff --git a/import_test.go b/import_test.go index 490d2e7..98fbd03 100644 --- a/import_test.go +++ b/import_test.go @@ -1,10 +1,11 @@ -// Copyright 2022 schukai GmbH +// Copyright 2023 schukai GmbH // SPDX-License-Identifier: AGPL-3.0 package configuration import ( "github.com/stretchr/testify/assert" + "path/filepath" "testing" ) @@ -27,3 +28,85 @@ func TestReadExample3(t *testing.T) { assert.Equal(t, c.Config().Host, "localhost") } + +type TestStruct struct { + Path1 PathValue + Path2 string + Path3 []PathValue +} + +func TestReplacePath2(t *testing.T) { + testData := TestStruct{ + Path1: "relative/path1", + Path2: "relative/path2", + Path3: []PathValue{"relative/path3", "relative/path4"}, + } + basePath := "/base" + + err := replacePath[*TestStruct](basePath, &testData) + assert.Nil(t, err) + + expectedPath1 := PathValue(filepath.Join(basePath, "relative/path1")) + if testData.Path1 != expectedPath1 { + t.Errorf("Expected %s, got %s", expectedPath1, testData.Path1) + } + + // no pathValue + expectedPath2 := "relative/path2" + if testData.Path2 != expectedPath2 { + t.Errorf("Expected %s, got %s", expectedPath2, testData.Path2) + } + + expectedPath3 := []PathValue{PathValue(filepath.Join(basePath, "relative/path3")), PathValue(filepath.Join(basePath, "relative/path4"))} + if testData.Path3[0] != expectedPath3[0] { + t.Errorf("Expected %s, got %s", expectedPath3[0], testData.Path3[0]) + } + + if testData.Path3[1] != expectedPath3[1] { + t.Errorf("Expected %s, got %s", expectedPath3[1], testData.Path3[1]) + } +} + +type SubTestSubPaths struct { + Template PathValue + Definitions []PathValue +} + +type SubTest2Def struct { + Paths SubTestSubPaths +} + +type SubTestStruct1 map[string]SubTest2Def +type MainTestStruct struct { + Sub SubTestStruct1 +} + +func TestReplacePathForConfig(t *testing.T) { + config := MainTestStruct{ + Sub: SubTestStruct1{ + "Default": SubTest2Def{ + Paths: SubTestSubPaths{ + Template: "../../../default.html", + Definitions: []PathValue{"../../../legacy.yaml"}, + }, + }, + }, + } + + basePath := "/base/1/2/3/" + + err := replacePath[*MainTestStruct](basePath, &config) + if err != nil { + t.Error(err) + } + + expectedTemplatePath := PathValue(filepath.Join(basePath, "../../../default.html")) + if config.Sub["Default"].Paths.Template != expectedTemplatePath { + t.Errorf("Expected %s, got %s", expectedTemplatePath, config.Sub["Default"].Paths.Template) + } + + expectedDefinitionPath := PathValue(filepath.Join(basePath, "../../../legacy.yaml")) + if config.Sub["Default"].Paths.Definitions[0] != expectedDefinitionPath { + t.Errorf("Expected %s, got %s", expectedDefinitionPath, config.Sub["Default"].Paths.Definitions[0]) + } +} diff --git a/integration_test.go b/integration_test.go index c5f1b92..baa0748 100644 --- a/integration_test.go +++ b/integration_test.go @@ -4,7 +4,6 @@ package configuration import ( - "fmt" "os" "testing" ) @@ -21,7 +20,6 @@ func Fuzz1Test(f *testing.F) { s := New(config) - fmt.Println("A:", a, "B:", b, "F:", f) if s == nil { t.Error("Expected not nil") diff --git a/issue-7_test.go b/issue-7_test.go index 711714f..33d6437 100644 --- a/issue-7_test.go +++ b/issue-7_test.go @@ -5,7 +5,6 @@ package configuration import ( "github.com/stretchr/testify/assert" - "io/ioutil" "os" "path" "path/filepath" @@ -26,7 +25,7 @@ type Issue7Config struct { } func createIssue7TempFile(content string) (string, error) { - file, err := ioutil.TempFile("", "tempfile") + file, err := os.CreateTemp("", "tempfile") if err != nil { return "", err } @@ -100,7 +99,8 @@ func TestIssue7ReplacePath(t *testing.T) { C: 42, } - replacePath(basePath, &ts1) + err := replacePath[*Issue7TestStruct1](basePath, &ts1) + assert.Nil(t, err) if ts1.A != PathValue(path.Join(basePath, "relative/path")) { t.Errorf("Expected '%s', got '%s'", path.Join(basePath, "relative/path"), ts1.A) @@ -118,7 +118,8 @@ func TestIssue7ReplacePath(t *testing.T) { E: "justastring", } - replacePath(basePath, &ts2) + err = replacePath[*Issue7TestStruct2](basePath, &ts2) + assert.Nil(t, err) if ts2.A != PathValue(path.Join(basePath, "another/relative/path")) { t.Errorf("Expected '%s', got '%s'", path.Join(basePath, "another/relative/path"), ts2.A) @@ -220,7 +221,8 @@ func TestReplacePath(t *testing.T) { // Copy the struct to compare later original := *s - replacePath(basePath, s) + err := replacePath[*MyStruct](basePath, s) + assert.Nil(t, err) // Checking each field to ensure replacePath works as expected if s.A != original.A && !path.IsAbs(s.A.String()) { diff --git a/licenses/github.com/pelletier/go-toml/v2/LICENSE b/licenses/github.com/pelletier/go-toml/v2/LICENSE index 6839d51..991e2ae 100644 --- a/licenses/github.com/pelletier/go-toml/v2/LICENSE +++ b/licenses/github.com/pelletier/go-toml/v2/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2013 - 2022 Thomas Pelletier, Eric Anderton +go-toml v2 +Copyright (c) 2021 - 2023 Thomas Pelletier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal -- GitLab