From d526b058b63582e20f0be855b190b8a5e327042b Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Tue, 12 Sep 2023 14:43:51 +0200 Subject: [PATCH] fix: arrays and slices dont work #3 --- .gitlab-ci.yml | 9 +- .idea/.gitignore | 8 + Taskfile.yml | 39 ++-- devenv.nix | 551 ++++++++++++++++++++++++++++++++++++++++++++--- get.go | 17 +- issue_2_test.go | 47 ++++ 6 files changed, 622 insertions(+), 49 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 issue_2_test.go 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/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Taskfile.yml b/Taskfile.yml index 7febe83..9a4f089 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,13 +12,16 @@ 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 . test-fuzz: desc: Conduct fuzzing tests.# @@ -23,8 +33,15 @@ tasks: 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 "$(get-go-default-packages)" --force --save_path ${DEVENV_ROOT}/licenses/ check: desc: Confirm repository status. @@ -32,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/devenv.nix b/devenv.nix index 2568f0c..94e6d0e 100644 --- a/devenv.nix +++ b/devenv.nix @@ -1,36 +1,46 @@ { pkgs, inputs, phps, lib, config, modulesPath,... }: { - # https://devenv.sh/packages/ - packages = [ + packages = with pkgs; [ inputs.version.defaultPackage."${builtins.currentSystem}" - pkgs.git - pkgs.gcc12 - pkgs.go-task - pkgs.blackbox - pkgs.blackbox-terminal - pkgs.jq - pkgs.delve - pkgs.gdlv - pkgs.wget - pkgs.glab - pkgs.unixtools.xxd - pkgs.libffi - pkgs.zlib - pkgs.procps - pkgs.php81Extensions.xdebug - pkgs.ranger - pkgs.meld - pkgs.gnused - pkgs.coreutils-full - pkgs.gnugrep - pkgs.gnumake - pkgs.util-linux - pkgs.httpie - pkgs.netcat - pkgs.memcached - pkgs.fd + appimage-run + blackbox + blackbox-terminal + coreutils-full + dbeaver + delve + dialog + drill + exa + fd + fd + gcc12 + gdlv + git + glab + gnugrep + gnumake + gnused + go-licenses + go-task + gum + httpie + hurl + jq + libffi + logrotate + meld + memcached + netcat + nixfmt + procps + ranger + unixtools.xxd + unzip + util-linux + wget + zlib ]; @@ -41,8 +51,12 @@ }; 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 = '' @@ -65,7 +79,7 @@ PATH="''${PATH}":${pkgs.git}/bin/ PATH="''${PATH}":${pkgs.gnugrep}/bin/ PATH="''${PATH}":${inputs.version.defaultPackage."${builtins.currentSystem}"}/bin/ -export -f PATH +export PATH task test @@ -143,6 +157,481 @@ 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 . + - go test -race . + + 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 "$(get-go-default-packages)" --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/get.go b/get.go index 79789c4..c8f6603 100644 --- a/get.go +++ b/get.go @@ -16,8 +16,12 @@ func GetValue[D any](obj D, keyWithDots string) (any, error) { for _, key := range keySlice[0:len(keySlice)] { + if !v.IsValid() { + return nil, newInvalidPathError(keyWithDots) + } + switch v.Kind() { - case reflect.Ptr, reflect.Slice, reflect.Array, reflect.Interface: + case reflect.Ptr, reflect.Interface: v = v.Elem() } @@ -33,6 +37,10 @@ func GetValue[D any](obj D, keyWithDots string) (any, error) { if err != nil { return nil, newInvalidPathError(keyWithDots) } + // check if index is in range + if index >= v.Len() { + return nil, newInvalidPathError(keyWithDots) + } v = v.Index(index) case reflect.Struct: v = v.FieldByName(key) @@ -52,7 +60,12 @@ func GetValue[D any](obj D, keyWithDots string) (any, error) { for v.Kind() == reflect.Ptr { v = v.Elem() } - + + // check if v can interface + if !v.CanInterface() { + return nil, newInvalidPathError(keyWithDots) + } + return v.Interface(), nil } diff --git a/issue_2_test.go b/issue_2_test.go new file mode 100644 index 0000000..4be4bec --- /dev/null +++ b/issue_2_test.go @@ -0,0 +1,47 @@ +// Copyright 2023 schukai GmbH +// SPDX-License-Identifier: AGPL-3.0 + +package pathfinder + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +type Issue2SubSubStruct struct { + Issue2SubSubField []string +} + +type Issue2SubStruct struct { + Issue2SubField Issue2SubSubStruct +} + +type Issue2Struct struct { + Issue1Sub1 Issue2SubStruct +} + +func TestGetValueWithArray(t *testing.T) { + invalidValue := Issue2Struct{ + Issue1Sub1: Issue2SubStruct{ + Issue2SubField: Issue2SubSubStruct{ + Issue2SubSubField: []string{"test0", "test1", "test2"}, + }, + }, + } + + // iterate from 0 to 2 + for i := 0; i < 2; i++ { + result, err := GetValue(invalidValue, "Issue1Sub1.Issue2SubField.Issue2SubSubField."+fmt.Sprintf("%d", i)) + + assert.Nil(t, err) + assert.Equal(t, result, "test"+fmt.Sprintf("%d", i)) + } + + i := 3 + result, err := GetValue(invalidValue, "Issue1Sub1.Issue2SubField.Issue2SubSubField."+fmt.Sprintf("%d", i)) + + assert.NotNil(t, err) + assert.Nil(t, result) + +} -- GitLab