diff --git a/.gitignore b/.gitignore index 717ae33934d1360293488519341c3cddb19a77ac..13bb7b5923d234f0509c1b4c9d3c410210cc529f 100644 --- a/.gitignore +++ b/.gitignore @@ -152,3 +152,4 @@ devenv.local.nix # pre-commit .pre-commit-config.yaml +/Session.vim diff --git a/.gitlab-ci.yaml b/.gitlab-ci.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e88d621f210edd8fc094a72ad27c6483077ad5ab --- /dev/null +++ b/.gitlab-ci.yaml @@ -0,0 +1,64 @@ +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: + - build + - 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 + +build: + stage: build + tags: + - nixos + script: + - devenv shell build-app + + cache: + - key: nixos + paths: + - /nix/store + + artifacts: + paths: + - dist + +deploy: + stage: deploy + tags: + - nixos + script: + - devenv shell -c deploy-app + + when: on_success + + cache: + - key: nixos + paths: + - /nix/store + + + artifacts: + paths: + - dist diff --git a/LICENSE b/LICENSE index f335543f280632ca05a6ffee71ac9a6a8286c792..8aa3080082f117124214e2d4c96b5f14d6fbf54c 100644 --- a/LICENSE +++ b/LICENSE @@ -10,4 +10,5 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License -along with this program. If not, see <https://www.gnu.org/licenses/>. \ No newline at end of file +along with this program. If not, see <https://www.gnu.org/licenses/>. + diff --git a/README.md b/README.md index 4333b476d3c9a5425ae3d06919977db07127cea6..c89e28fe64979f1d07a3df02f273ebe0cdc5adf1 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ func main() { var h configuration.EventHook h = &ChangeEventHandler{ callback: func(event configuration.ChangeEvent) { - log := event.Changlog + log := event.Changelog msg = fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To) fmt.Println(msg) closeChan <- true @@ -349,6 +349,33 @@ func main() { ``` +### Environment variables + +The configuration can also be loaded from environment variables. This is useful if you want to +define the configuration via environment variables. For example, if you want to use Docker. + +With the `InitFromEnv(prefix)` function, you can set the configuration from environment variables. +You must call this function manually. The definition of the environment variables must be +done in the following format: + +```go + config := struct { + Host string `env:"HOST"` + }{ + Host: "localhost", + } + + s := configuration.New(config) + + s.InitFromEnv("APP") + +``` + +In this example, the environment variable `APP_HOST` is used. The prefix is used to +to avoid collisions with other environment variables. You can also use an empty string +as a prefix. Then the environment variables are used directly. + + ### Error handling If an error occurs, it is returned by the function `Errors()`. The errors can be handled as usual. diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000000000000000000000000000000000000..7febe83e7b63269468299d999d7aac31118ef44f --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,48 @@ +# https://taskfile.dev + +version: '3' + +tasks: + default: + cmds: + - task --list-all + silent: true + test: + desc: Execute unit tests in Go. + cmds: + - echo "Execute unit tests in Go." + - go test -cover -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." + - addlicense -c "schukai GmbH" -s -l "AGPL-3.0" ./*.go + silent: true + + 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 + + build: + desc: Compile the application, + aliases: + - b + vars: + DEVENV_ROOT: + sh: echo "$DEVENV_ROOT" + + cmds: + - devenv shell build-app + sources: + - source/**/*.go + - source/**/*.mod + - dist/** diff --git a/api.go b/api.go index b2a340d02c15dc67e298f13bd4e31fbe23dacefa..b8638a53270667b0f2c1232a049161bc9dfc51bc 100644 --- a/api.go +++ b/api.go @@ -7,7 +7,7 @@ import ( "github.com/imdario/mergo" ) -// NewSetting creates a new configuration setting +// New NewSetting creates a new configuration setting // with the given defaults. func New[C any](defaults C) *Settings[C] { @@ -39,7 +39,7 @@ func New[C any](defaults C) *Settings[C] { return s } -// Set the mnemonic +// SetMnemonic Set the mnemonic // The mnemonic is used to identify the configuration in the configuration file func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] { @@ -58,7 +58,7 @@ func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] { return s } -// Config() returns the configuration +// Config returns the configuration // Remember that the configuration is a copy of the original configuration. // Changes to the configuration will not be reflected in the original configuration. func (s *Settings[C]) Config() C { diff --git a/change-handler.go b/change-handler.go index d6e66fcd0d06a074bbfabab8e1438f6c47d58aa2..721a745dcd992ff702fdbff31f0b9ce6046ee9d4 100644 --- a/change-handler.go +++ b/change-handler.go @@ -8,7 +8,7 @@ import ( ) type ChangeEvent struct { - Changlog diff.Changelog + Changelog diff.Changelog } type ChangeHook interface { @@ -44,7 +44,7 @@ func (s *Settings[C]) RemoveOnChangeHook(hook ChangeHook) *Settings[C] { func (s *Settings[C]) notifyChangeHooks(changelog diff.Changelog) *Settings[C] { for _, h := range s.hooks.change { - go h.Handle(ChangeEvent{Changlog: changelog}) + go h.Handle(ChangeEvent{Changelog: changelog}) } return s } diff --git a/change.go b/change.go index c3000e42ffdbf355b4462560201707f3ff04bf93..7027cc0b5218e8ad36fe70e8981a89782f1aab78 100644 --- a/change.go +++ b/change.go @@ -27,6 +27,7 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] { defer func() { if len(changelog) > 0 { + s.notifyPostprocessingHooks(changelog) s.notifyChangeHooks(changelog) } diff --git a/change_test.go b/change_test.go index 6dea9f6ff847e7bd7bd13abbf4cf8d9e6f5d268d..94d9368b925dca4f20e65b46ff0eab148b2de2ff 100644 --- a/change_test.go +++ b/change_test.go @@ -63,7 +63,7 @@ func TestReadmeExample(t *testing.T) { var h ChangeHook h = &ChangeEventHandler{ Callback: func(event ChangeEvent) { - log := event.Changlog + log := event.Changelog msg = fmt.Sprintf("Change from %s to %s", log[0].From, log[0].To) // for Readme //fmt.Println(msg) @@ -91,7 +91,7 @@ func TestReadmeExample(t *testing.T) { } -func TestCangeOnChange(t *testing.T) { +func TestChangeOnChange(t *testing.T) { defaults := ConfigStruct2{ A: "", diff --git a/configuration.iml b/configuration.iml index 2aabe1403a0edfad0e889e4ef2242811eb6dbf65..11dced775143a48129a88c078710d3bedeaaa89d 100644 --- a/configuration.iml +++ b/configuration.iml @@ -11,7 +11,9 @@ </component> <component name="NewModuleRootManager" inherit-compiler-output="true"> <exclude-output /> - <content url="file://$MODULE_DIR$" /> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/licenses" /> + </content> <orderEntry type="inheritedJdk" /> <orderEntry type="sourceFolder" forTests="false" /> </component> diff --git a/devenv.lock b/devenv.lock index 8262950770534d1c20c0fb05622cdb40a6efbc14..8993ca89c49f0009e157e0fa07774b143c214b2d 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,11 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1690831896, - "narHash": "sha256-k4Cb2/Yx2kL8TFuVejwEk4R0J+5sxbydAj2IZBKZg7o=", + "lastModified": 1692003204, + "narHash": "sha256-gO2DXwXuArjpywgtRTDb3aKscWMbnI7YwFaqvV46yv0=", "owner": "cachix", "repo": "devenv", - "rev": "e91205acb792f6a49ea53ab04c04fbc2a85039cd", + "rev": "ade3ae522baf366296598e232b7b063d81740bbb", "type": "github" }, "original": { @@ -74,16 +74,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1691188534, - "narHash": "sha256-oXjS9GrZar+sB8j2KYzWw3dW62jYFhnjOsnO5+D+B3s=", - "owner": "NixOS", + "lastModified": 1691950488, + "narHash": "sha256-iUNEeudc4dGjx+HsHccnGiuZUVE/nhjXuQ1DVCsHIUY=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "5767e7b931f2e6ee7f582d564b8665095c059f3b", + "rev": "720e61ed8de116eec48d6baea1d54469b536b985", "type": "github" }, "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", + "owner": "nixos", + "ref": "nixos-23.05", "repo": "nixpkgs", "type": "github" } @@ -104,6 +104,21 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1691950488, + "narHash": "sha256-iUNEeudc4dGjx+HsHccnGiuZUVE/nhjXuQ1DVCsHIUY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "720e61ed8de116eec48d6baea1d54469b536b985", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.05", + "type": "indirect" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -115,11 +130,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1691093055, - "narHash": "sha256-sjNWYpDHc6vx+/M0WbBZKltR0Avh2S43UiDbmYtfHt0=", + "lastModified": 1691747570, + "narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "ebb43bdacd1af8954d04869c77bc3b61fde515e4", + "rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a", "type": "github" }, "original": { @@ -132,7 +147,8 @@ "inputs": { "devenv": "devenv", "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks" + "pre-commit-hooks": "pre-commit-hooks", + "version": "version" } }, "systems": { @@ -149,6 +165,24 @@ "repo": "default", "type": "github" } + }, + "version": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1690668568, + "narHash": "sha256-jzixQKFFW4oxO0S4GYqbkFCXzhBd6com6Z9+MtVKakU=", + "ref": "refs/heads/master", + "rev": "3838f03165b726e47d586c04a1821749375e1001", + "revCount": 37, + "type": "git", + "url": "https://gitlab.schukai.com/oss/utilities/version.git" + }, + "original": { + "type": "git", + "url": "https://gitlab.schukai.com/oss/utilities/version.git" + } } }, "root": "root", diff --git a/devenv.nix b/devenv.nix index 9fc608ad6cea4bad996182a6d122b5ec65e6806a..cef457e62cf8fabfd66ff7e8895df4dde7164f9c 100644 --- a/devenv.nix +++ b/devenv.nix @@ -1,19 +1,161 @@ -{ pkgs, ... }: +{ pkgs, inputs, phps, lib, config, modulesPath,... }: { - # https://devenv.sh/basics/ + + env.APP_NAME = "configuration"; # https://devenv.sh/packages/ - packages = [ pkgs.git ]; + packages = [ + 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 + ]; + # https://devenv.sh/languages/ - languages.go.enable = true; + # languages.nix.enable = true; + languages = { + go = { enable = true; }; + }; + + difftastic.enable = true; + + + # This script is executed when the app is built + # You can use it to build the app + scripts.build-app.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 -f 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-app.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 + +## the version should be the same as in the build task +if ! version auto --git --verbose +then + echo "ERROR: Could not update version." + exit 1 +fi + +git --no-pager log --decorate=short --pretty=oneline + +gitVersion=v$(version predict) + + +git push -o ci.skip origin ''${CI_COMMIT_REF_NAME} --tags + +echo "done" - # https://devenv.sh/pre-commit-hooks/ - # pre-commit.hooks.shellcheck.enable = true; +''; - # https://devenv.sh/processes/ - # processes.ping.exec = "ping example.com"; - # See full reference at https://devenv.sh/reference/options/ } diff --git a/devenv.yaml b/devenv.yaml index c7cb5cedadc04e17a1703f298db573f43c79cad8..525a6f02f172bb9108a23c335a4ced3e80f78502 100644 --- a/devenv.yaml +++ b/devenv.yaml @@ -1,3 +1,7 @@ inputs: nixpkgs: - url: github:NixOS/nixpkgs/nixpkgs-unstable + url: github:nixos/nixpkgs/nixos-23.05 + + version: + url: git+https://gitlab.schukai.com/oss/utilities/version.git + flake: true diff --git a/error-handler.go b/error-handler.go index 5eb3752cd00c401595b2de66442a98b12ef7a699..fee0f7cd73e802faf16da24168876e1c2ffa67d0 100644 --- a/error-handler.go +++ b/error-handler.go @@ -10,13 +10,13 @@ type ErrorHook interface { Handle(event ErrorEvent) } -// OnChange registers a hook that is called when the configuration changes. +// OnError registers a hook that is called when the configuration changes. func (s *Settings[C]) OnError(hook ErrorHook) *Settings[C] { s.hooks.error = append(s.hooks.error, hook) return s } -// HasOnChangeHook returns true if there are registered hooks. +// HasOnErrorHook returns true if there are registered hooks. func (s *Settings[C]) HasOnErrorHook(hook ErrorHook) *Settings[C] { for _, h := range s.hooks.error { if h == hook { @@ -26,7 +26,7 @@ func (s *Settings[C]) HasOnErrorHook(hook ErrorHook) *Settings[C] { return s } -// RemoveOnChangeHook removes a change hook from the list of hooks. +// RemoveOnErrorHook removes a change hook from the list of hooks. func (s *Settings[C]) RemoveOnErrorHook(hook ErrorHook) *Settings[C] { for i, h := range s.hooks.error { if h == hook { diff --git a/error-handler_test.go b/error-handler_test.go index 88bb7980cf6b3d83fefcb07baaa95e64dc66df1e..4b63e8979a385abde61733a50be920d1f1778728 100644 --- a/error-handler_test.go +++ b/error-handler_test.go @@ -59,13 +59,13 @@ func TestAddRemoveErrorHook(t *testing.T) { s.OnError(h) if len(s.hooks.error) != 1 { - t.Error("Expected 1 got ", len(s.hooks.change)) + t.Error("Expected 1 got ", len(s.hooks.error)) } s.RemoveOnErrorHook(h) if len(s.hooks.error) != 0 { - t.Error("Expected 0 got ", len(s.hooks.change)) + t.Error("Expected 0 got ", len(s.hooks.error)) } } diff --git a/error.go b/error.go index cb7d68877c2d45c357557ccf42218c73a2ba3177..4ee39a6b7cb8e19fe700acfed43b7376df4d9526 100644 --- a/error.go +++ b/error.go @@ -69,14 +69,14 @@ func newUnsupportedTypeError(t reflect.Type) UnsupportedTypeError { return UnsupportedTypeError(errors.New("type " + t.String() + " is not supported")) } -// At the reflect level, some types are not supported +// UnsupportedReflectKindError At the reflect level, some types are not supported type UnsupportedReflectKindError error func newUnsupportedReflectKindError(t reflect.Type) UnsupportedReflectKindError { return UnsupportedReflectKindError(errors.New("type " + t.String() + " is not supported")) } -// This error indicates that the flag is not found +// FlagNotFoundError This error indicates that the flag is not found type FlagNotFoundError error func newFlagNotFoundError(name string) FlagNotFoundError { diff --git a/file.go b/file.go index 297efb2d84ca833750d81283c9cc5e6467fdf67e..b934f0e0edf4d28fc150c1190986f0aa43ea3f92 100644 --- a/file.go +++ b/file.go @@ -34,7 +34,7 @@ func (s *Settings[c]) HasFile(file string) bool { return false } -// AddFiles adds a file to the list of files to import +// AddFile adds a file to the list of files to import func (s *Settings[C]) AddFile(file string, format ...Format) *Settings[C] { var f Format @@ -96,12 +96,12 @@ func initFileBackend(files *fileBackend) { files.fs = fs.FS(internalFS{}) } -// Path returns the configuration directory +// Directories returns the list of directories to search for configuration files func (s *Settings[C]) Directories() []string { return s.files.directories } -// AddPath adds a directory to the configuration directory +// AddDirectory adds a directory to the list of directories to search for configuration files func (s *Settings[C]) AddDirectory(d string) *Settings[C] { s.Lock() defer s.Unlock() @@ -148,7 +148,7 @@ func (s *Settings[C]) sanitizeDirectories() { } -// Set all configuration directories +// SetDirectories sets the list of directories to search for configuration files func (s *Settings[C]) SetDirectories(d []string) *Settings[C] { s.Lock() defer s.Unlock() @@ -158,7 +158,7 @@ func (s *Settings[C]) SetDirectories(d []string) *Settings[C] { return s } -// Add the current working directory to the configuration directory +// AddWorkingDirectory adds the current working directory to the list of directories to search for configuration files func (s *Settings[C]) AddWorkingDirectory() *Settings[C] { s.Lock() defer s.Unlock() @@ -181,7 +181,7 @@ func (s *Settings[C]) AddWorkingDirectory() *Settings[C] { return s } -// Add the Unix etc directory to the configuration directory +// AddEtcDirectory adds the /etc directory to the list of directories to search for configuration files func (s *Settings[C]) AddEtcDirectory() *Settings[C] { s.Lock() defer s.Unlock() @@ -211,7 +211,7 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] { return s } -// Add the user configuration directory to the configuration directory +// AddUserConfigDirectory Add the user configuration directory to the configuration directory // The mnemonic must be set for this function func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] { @@ -242,7 +242,7 @@ func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] { return s } -// Add the current working directory, the user configuration directory +// SetDefaultDirectories Add the current working directory, the user configuration directory // and the Unix etc directory to the configuration directory func (s *Settings[C]) SetDefaultDirectories() *Settings[C] { s.AddWorkingDirectory(). @@ -269,7 +269,7 @@ func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] { return s } -// Set the file name without extension +// SetFileName sets the name of the configuration file func (s *Settings[C]) SetFileName(name string) *Settings[C] { errorCount := len(s.errors) diff --git a/go.mod b/go.mod index 768bc9d1b4f619cbeac1cb70a6023903370347a6..a4e546ef7d838c02d31431e9e257a0d2c12aa29a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module gitlab.schukai.com/oss/libraries/go/application/configuration -go 1.19 +go 1.20 require ( github.com/fsnotify/fsnotify v1.6.0 @@ -13,7 +13,7 @@ require ( gitlab.schukai.com/oss/libraries/go/application/xflags v1.9.0 gitlab.schukai.com/oss/libraries/go/network/http-negotiation v1.3.0 gitlab.schukai.com/oss/libraries/go/utilities/pathfinder v0.5.2 - golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b + golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index d5905a812c8c674d31139b2be43b572c6b24b3f3..2bcbd7a068e92963f98bde693dbc487753999a49 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,10 @@ golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKM golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU= +golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= diff --git a/handler.go b/handler.go index b5d5a1f813d8ea10b64ea6fba5b8037843cc3cb0..3c6937f3e05c99ae9a4ee9141e35e6c80fe22d38 100644 --- a/handler.go +++ b/handler.go @@ -18,3 +18,11 @@ type ErrorEventHandler struct { func (c *ErrorEventHandler) Handle(event ErrorEvent) { c.Callback(event) } + +type PostprocessingHandler struct { + Callback func(event PostprocessingEvent) +} + +func (c *PostprocessingHandler) Handle(event PostprocessingEvent) { + c.Callback(event) +} diff --git a/postprocessing-handler.go b/postprocessing-handler.go new file mode 100644 index 0000000000000000000000000000000000000000..912750a64c4efb81861c23b8af05996c2c7a612f --- /dev/null +++ b/postprocessing-handler.go @@ -0,0 +1,50 @@ +// Copyright 2022 schukai GmbH +// SPDX-License-Identifier: AGPL-3.0 + +package configuration + +import ( + "github.com/r3labs/diff/v3" +) + +type PostprocessingEvent struct { + Changelog diff.Changelog +} + +type PostprocessingHook interface { + Handle(event PostprocessingEvent) +} + +// OnPostprocessing registers a hook that is called when the configuration changes. +func (s *Settings[C]) OnPostprocessing(hook PostprocessingHook) *Settings[C] { + s.hooks.postprocessing = append(s.hooks.postprocessing, hook) + return s +} + +// HasOnPostprocessingHook returns true if there are registered hooks. +func (s *Settings[C]) HasOnPostprocessingHook(hook PostprocessingHook) *Settings[C] { + for _, h := range s.hooks.postprocessing { + if h == hook { + break + } + } + return s +} + +// RemoveOnPostprocessingHook removes a change hook from the list of hooks. +func (s *Settings[C]) RemoveOnPostprocessingHook(hook PostprocessingHook) *Settings[C] { + for i, h := range s.hooks.postprocessing { + if h == hook { + s.hooks.postprocessing = append(s.hooks.postprocessing[:i], s.hooks.postprocessing[i+1:]...) + break + } + } + return s +} + +func (s *Settings[C]) notifyPostprocessingHooks(changelog diff.Changelog) *Settings[C] { + for _, h := range s.hooks.postprocessing { + h.Handle(PostprocessingEvent{Changelog: changelog}) + } + return s +} diff --git a/postprocessing-handler_test.go b/postprocessing-handler_test.go new file mode 100644 index 0000000000000000000000000000000000000000..86e87e1e01e368f6246cd7c8464625091b4102ab --- /dev/null +++ b/postprocessing-handler_test.go @@ -0,0 +1,95 @@ +// Copyright 2022 schukai GmbH +// SPDX-License-Identifier: AGPL-3.0 + +package configuration + +import ( + "bytes" + "io" + "testing" +) + +type mockTestPostprocessingEventHandler struct { + ChangeHook +} + +func (m *mockTestPostprocessingEventHandler) Handle(event PostprocessingEvent) { + // do nothing + +} + +func TestAddRemovePostprocessingHook(t *testing.T) { + + config := struct { + Host string + }{ + Host: "localhost", + } + + s := New(config) + + var h PostprocessingHook + h = &mockTestPostprocessingEventHandler{} + s.OnPostprocessing(h) + + if len(s.hooks.postprocessing) != 1 { + t.Error("Expected 1 got ", len(s.hooks.postprocessing)) + } + + s.RemoveOnPostprocessingHook(h) + + if len(s.hooks.postprocessing) != 0 { + t.Error("Expected 0 got ", len(s.hooks.postprocessing)) + } + +} + +func TestPostprocessingOnChange(t *testing.T) { + + defaults := ConfigStruct2{ + A: "", + B: false, + C: nil, + D: nil, + F: 0, + G: "", + } + + s := New(defaults) + s.SetMnemonic("test") + + var buf bytes.Buffer + buf.WriteString("A: a\n") + r := (io.Reader)(&buf) + + s.AddReader(r, Yaml) + + s.Import() + + if s.HasErrors() { + t.Error("Expected not error", s.Errors()) + } + + c := s.Config() + if c.A != "a" { + t.Error("Expected \"a\" got ", c) + } + + counter := 0 + var p PostprocessingHook + p = &PostprocessingHandler{ + Callback: func(event PostprocessingEvent) { + counter++ + }, + } + + s.OnPostprocessing(p) + + c.A = "x" + s.SetConfig(c) + + if counter != 1 { + t.Error("Expected 1 got ", counter) + } + +} diff --git a/settings.go b/settings.go index 62a9d095cb54c61a7ab64a79c3ffbcf49058004a..a76b270811f4a5866a7acdafeddf92955b07c370 100644 --- a/settings.go +++ b/settings.go @@ -4,10 +4,11 @@ package configuration import ( - "github.com/fsnotify/fsnotify" "reflect" "strconv" "sync" + + "github.com/fsnotify/fsnotify" ) type fileWatch struct { @@ -28,8 +29,9 @@ type Settings[C any] struct { mnemonic string importCounter int hooks struct { - change []ChangeHook - error []ErrorHook + change []ChangeHook + error []ErrorHook + postprocessing []PostprocessingHook } fileWatch fileWatch diff --git a/stream.go b/stream.go index 4ad5621aa39a7ebabf1dc6596ae407036cd2ae37..7cde59c86e0c0cc914a9209bb65b7cccc9798b73 100644 --- a/stream.go +++ b/stream.go @@ -10,35 +10,13 @@ type reader struct { reader io.Reader } -//type writer struct { -// format Format -// reader io.Writer -//} - type streamBackend struct { readers []reader - //writers []writer } -//type StreamOption struct { -//} - -//func (s *Settings[C]) AddStream(stream io.ReadWriter, format Format) *Settings[C] { -// return s. -// AddReader(stream, format) -// //AddWriter(stream, format) -//} - func (s *Settings[C]) AddReader(r io.Reader, format Format) *Settings[C] { s.Lock() defer s.Unlock() s.stream.readers = append(s.stream.readers, reader{format, r}) return s } - -//func (s *Settings[C]) AddWriter(w io.Writer, format Format) *Settings[C] { -// s.Lock() -// defer s.Unlock() -// s.stream.writers = append(s.stream.writers, writer{format, w}) -// return s -//} diff --git a/stream_test.go b/stream_test.go index f6fe7243c3884c4404147d410074a4b9eb875c95..da9e215744273dbbd7974a82d12d8adf5d154267 100644 --- a/stream_test.go +++ b/stream_test.go @@ -8,7 +8,7 @@ import ( "io" "testing" ) - + func TestAddReader(t *testing.T) { var config ConfigStruct2 s := New(&config) diff --git a/watch_test.go b/watch_test.go index 494970bf736c336d8dfee0437b459cffd451634c..91e4bfff5a795f4af7bbe6d6d9fec939214060cf 100644 --- a/watch_test.go +++ b/watch_test.go @@ -73,8 +73,8 @@ func TestMultiChange(t *testing.T) { var h ChangeHook h = &ChangeEventHandler{ Callback: func(event ChangeEvent) { - result = append(result, event.Changlog[0].To.(string)) - signal <- event.Changlog[0].To.(string) + result = append(result, event.Changelog[0].To.(string)) + signal <- event.Changelog[0].To.(string) }, } @@ -167,8 +167,8 @@ func TestWatch(t *testing.T) { var h ChangeHook h = &ChangeEventHandler{ Callback: func(event ChangeEvent) { - assert.Equal(t, event.Changlog[0].From, "localhost") - assert.Equal(t, event.Changlog[0].To, "example.org") + assert.Equal(t, event.Changelog[0].From, "localhost") + assert.Equal(t, event.Changelog[0].To, "example.org") signal <- true }, }