From 57ce4b62c27a7213048b1ea366d02dab22f30935 Mon Sep 17 00:00:00 2001 From: plastikfan Date: Sat, 24 Aug 2024 22:16:25 +0100 Subject: [PATCH] ref(locale): define i18n translatable content (#115) --- .github/workflows/ci-workflow.yml | 10 +- .github/workflows/release-workflow.yml | 2 +- .golangci.yml | 6 +- .vscode/settings.json | 9 + .vscode/snippets/README-SNIPPETS.md | 54 ++ .vscode/snippets/go.json | 176 +++++++ Taskfile.yml | 51 +- collections/errors.go | 11 +- collections/stack.go | 6 +- collections/stack_test.go | 2 +- core/core-defs.go | 2 +- core/core-suite_test.go | 13 + core/errors.go | 205 ++++++-- core/errors_test.go | 181 +++++++ cycle/cycle-defs.go | 2 +- go.mod | 7 +- go.sum | 8 +- internal/feat/hiber/hibernation-defs.go | 2 +- internal/feat/refine/filter-plugin.go | 2 +- internal/feat/refine/new-filter.go | 14 +- internal/feat/resume/resume-defs.go | 2 +- internal/feat/resume/strategy-fastward.go | 4 +- internal/feat/sampling/sampling-defs.go | 2 +- internal/helpers/directory-tree-builder.go | 4 +- internal/helpers/test-utilities.go | 9 +- internal/kernel/guardian.go | 4 +- internal/kernel/kernel-defs.go | 2 +- internal/kernel/kernel-suite_test.go | 10 +- internal/kernel/navigator-sample_test.go | 6 +- internal/level/periscope.go | 6 +- internal/override/actions.go | 2 +- internal/services/broker.go | 2 +- internal/third/bus/broker.go | 13 +- internal/third/bus/broker_test.go | 11 +- internal/types/definitions.go | 2 +- locale/default/traverse.active.en-GB.json | 22 - locale/deploy/traverse.active.en-US.json | 27 - locale/i18n-messages_test.go | 50 -- locale/locale-defs.go | 4 +- ...18n_suite_test.go => locale-suite_test.go} | 4 +- locale/messages-errors.go | 480 +++++++++++++++++- locale/messages-errors_test.go | 117 +++++ locale/messages-general.go | 2 +- locale/out/active.en-GB.json | 22 - locale/out/active.en-US.json | 1 - .../out/en-US/traverse.translate.en-US.json | 0 locale/out/translate.en-US.json | 27 - locale/pending-errors.go | 17 - measure/measure-defs.go | 2 +- nfs/ensure-path-at.go | 6 +- nfs/nfs-defs.go | 2 +- pref/errors.go | 14 - pref/options.go | 3 +- pref/using.go | 21 +- synchronise.go | 3 +- tapable/tapable-defs.go | 2 +- traverse-api.go | 4 +- 57 files changed, 1332 insertions(+), 340 deletions(-) create mode 100644 .vscode/snippets/README-SNIPPETS.md create mode 100644 .vscode/snippets/go.json create mode 100644 core/core-suite_test.go create mode 100644 core/errors_test.go delete mode 100644 locale/default/traverse.active.en-GB.json delete mode 100644 locale/deploy/traverse.active.en-US.json delete mode 100644 locale/i18n-messages_test.go rename locale/{i18n_suite_test.go => locale-suite_test.go} (76%) create mode 100644 locale/messages-errors_test.go delete mode 100644 locale/out/active.en-GB.json delete mode 100644 locale/out/active.en-US.json delete mode 100644 locale/out/en-US/traverse.translate.en-US.json delete mode 100644 locale/out/translate.en-US.json delete mode 100644 locale/pending-errors.go delete mode 100644 pref/errors.go diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1663775..050e532 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -7,27 +7,27 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v3 + - uses: actions/setup-go@v4 with: - go-version: 1.22 + go-version: 1.23 - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.56.2 + version: v1.60.3 args: --verbose test: strategy: matrix: - go-version: [1.22] + go-version: [1.23] platform: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/release-workflow.yml b/.github/workflows/release-workflow.yml index 7e122b5..92bd794 100644 --- a/.github/workflows/release-workflow.yml +++ b/.github/workflows/release-workflow.yml @@ -23,7 +23,7 @@ jobs: - name: Setup uses: actions/setup-go@v3 with: - go-version: '>=1.22' + go-version: '>=1.23' cache: true - name: Generate Changelog Only uses: goreleaser/goreleaser-action@v4 diff --git a/.golangci.yml b/.golangci.yml index 7ab8a5f..7ac922c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,7 +12,7 @@ linters-settings: - performance - style govet: - check-shadowing: true + shadow: true disable: - fieldalignment # too strict @@ -28,22 +28,22 @@ linters: # it can be re-enabled. # https://github.com/OpenPeeDeeP/depguard#example-configs # - depguard + - copyloopvar - dogsled # - dupl - errcheck - - exportloopref - exhaustive - goconst - gocritic - gofmt - goimports - - gomnd - gocyclo - gosec - gosimple - govet - ineffassign - misspell + - mnd - nolintlint - nakedret - prealloc diff --git a/.vscode/settings.json b/.vscode/settings.json index dd5f7fe..3ab73b7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,7 @@ "Clonable", "cmds", "Cobrass", + "copyloopvar", "coverpkg", "coverprofile", "cubiest", @@ -36,6 +37,7 @@ "fortytw", "fsys", "Fugazi", + "Ginko", "goconst", "gocritic", "gocyclo", @@ -50,6 +52,7 @@ "goveralls", "govet", "graffico", + "gverrt", "hiber", "icase", "ineffassign", @@ -67,6 +70,7 @@ "Mutables", "nakedret", "navi", + "nicksnyder", "nolint", "nolintlint", "onsi", @@ -84,8 +88,10 @@ "samber", "shogo", "sidewalk", + "snivilisation", "snivilised", "sortables", + "Sprintf", "staticcheck", "struct", "structcheck", @@ -93,8 +99,10 @@ "tapable", "tapcore", "Taskfile", + "tcore", "testcache", "thelper", + "toplevel", "tparallel", "trimprefix", "Turan", @@ -103,6 +111,7 @@ "unlambda", "unparam", "varcheck", + "verr", "watchv", "watchvc", "watchvi", diff --git a/.vscode/snippets/README-SNIPPETS.md b/.vscode/snippets/README-SNIPPETS.md new file mode 100644 index 0000000..d960d84 --- /dev/null +++ b/.vscode/snippets/README-SNIPPETS.md @@ -0,0 +1,54 @@ +# ๐Ÿท Snippets + +For further help with snippets see: + +- [How To Create A VSCode Snippet (webdevsimplified)](https://blog.webdevsimplified.com/2022-03/vscode-snippet/) + +## i18n Field + +Creates a simple text field. The field is a key value pair, where the key is static text subject to translation. + +- prefix: f18 +- $1: the name of the field +- $2: the name of the source package from which to import the field +- example: locale.PatternFieldTemplData + +## i18n Field and Variable i18n Error + +Creates a variable i18n error (an error with state). This snippet uses a copy of `Simple i18n Field` to implement the variable part; ie the native variable error contains within it a field that implements the variable. + +- prefix: v18e +- $1: the core name of the error (do not include Err or Error in name) +... + +## Ginko Variable Native Error + +Creates Ginkgo unit tests corresponding to `Variable Native Error`. + +- prefix: gverrt +- $1: the core name of the error (do not include Err or Error in name) +- $2: the name of the source package from which to import the error +- example: core.errors_test + +Creates the following test case: + +- check content of variable error +- affirmative case; check that an error matches the target error being defined +- negative case; check that a different error does not match the target error being defined + +## Simple i18n Error + +Creates a simple i18n error message (ie a message without any variables) + +- prefix: s18e +- $1: the core name of the error (do not include Err or Error in name) +- $2: the `TemplData` prefix, eg ___traverse___ to create `traverseTemplData` +- example: locale.FilterIsNilErrorTemplData + +## Variable Native Error + +Creates a native error (un-translated error, not meant for the end user). Note that by default it creates a single variable `value` of type string in the New function. This needs to be changed to be the state required for this message, or indeed, further state can be defined. + +- prefix: verr +- $1: the core name of the error (do not include Err or Error in name) +- example: core.NewInvalidNotificationMuteRequestedError diff --git a/.vscode/snippets/go.json b/.vscode/snippets/go.json new file mode 100644 index 0000000..1a9fe60 --- /dev/null +++ b/.vscode/snippets/go.json @@ -0,0 +1,176 @@ +{ + "Simple i18n Field": { + "description": "Creates a i18n key/value pair text field", + "scope": "go", + "prefix": "kv18", + "body": [ + "// ๐Ÿ€ $1", + "", + "// $1TemplData", + "type $1FieldTemplData struct {", + " $2TemplData", + " $1 string", + "}", + "", + "// Message", + "func (td $1FieldTemplData) Message() *i18n.Message {", + " return &i18n.Message{", + " ID: \"$1.field\",", + " Description: \"$1\",", + " Other: \"$1: {{.$1}}\",", + " }", + "}", + "$0" + ] + }, + + "Simple Field with Variable i18n Error": { + "scope": "go", + "prefix": "fav18e", + "body": [ + "// ๐Ÿ€ $1", + "", + "// $1TemplData", + "type $1FieldTemplData struct {", + " $2TemplData", + " $1 string", + "}", + "", + "// Message", + "func (td $1FieldTemplData) Message() *i18n.Message {", + " return &i18n.Message{", + " ID: \"$1.field\",", + " Description: \"$1\",", + " Other: \"$1: {{.$1}}\",", + " }", + "}", + "", + "// โŒ $3 error", + "", + "// New$3Error creates an untranslated error to", + "// indicate tbd...", + "func New$3Error(value string) error {", + " return errors.Wrap(", + " err$3,", + " fmt.Sprintf(\"value: %v\", value),", + " )", + "}", + "", + "// Is$3Error uses errors.Is to check", + "// if the err's error tree contains the core error:", + "// $3Error", + "func Is$3Error(err error) bool {", + " return errors.Is(err, err$3)", + "}", + "", + "var err$3 = errors.New(", + " \"tbd...\",", + ")", + "", + "$0" + ] + }, + + "Variable Native Error": { + "scope": "go", + "prefix": "verr", + "body": [ + "// โŒ $1 error", + "", + "// New$1Error creates an untranslated error to", + "// indicate tbd...", + "func New$1Error(value string) error {", + " return errors.Wrap(", + " err$1,", + " fmt.Sprintf(\"value: %v\", value),", + " )", + "}", + "", + "// Is$1Error uses errors.Is to check", + "// if the err's error tree contains the core error:", + "// $1Error", + "func Is$1Error(err error) bool {", + " return errors.Is(err, err$1)", + "}", + "", + "var err$1 = errors.New(", + " \"tbd...\",", + ")", + "" + ] + }, + + "Ginko Variable Native Error": { + "scope": "go", + "prefix": "gverrt", + "body": [ + "Context(\"$1 error\", func() {", + "When(\"variant error created\", func() {", + " It(\"should: render translated content\", func() {", + " const (", + " expected = \"wiz...\"", + " )", + " text := locale.New$1Error(", + " \"wiz\",", + " ).Error()", + " Expect(text).To(Equal(expected))", + " })", + "})", + " When(\"given: matching error\", func() {", + " It(\"๐Ÿงช should: affirm\", func() {", + " err := $2.New$1Error(\"bar\")", + " Expect(", + " $2.Is$1Error(err),", + " ).To(BeTrue(),", + " \"error does not match $1\",", + " )", + " })", + " })", + "", + " When(\"given: non matching error\", func() {", + " It(\"๐Ÿงช should: reject\", func() {", + " err := errors.New(\"fake\")", + " Expect(", + " $2.Is$1Error(err),", + " ).To(BeFalse(),", + " \"not matching error should not match $1\",", + " )", + " })", + " })", + "})", + "" + ] + }, + + "Simple i18n Error": { + "scope": "go", + "prefix": "s18e", + "body": [ + "// โŒ $1", + "", + "// $1TemplData", + "type $1ErrorTemplData struct {", + " $2TemplData", + "}", + "", + "// Message", + "func (td $1ErrorTemplData) Message() *i18n.Message {", + " return &i18n.Message{", + " ID: \".error\",", + " Description: \"description\",", + " Other: \"content\",", + " }", + "}", + "", + "type $1Error struct {", + " li18ngo.LocalisableError", + "}", + "", + "var Err$1 = $1Error{", + " LocalisableError: li18ngo.LocalisableError{", + " Data: $1ErrorTemplData{},", + " },", + "}" + ] + } +} diff --git a/Taskfile.yml b/Taskfile.yml index 7a5720a..01487b0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -11,6 +11,22 @@ silent: true dotenv: [".env"] +vars: + FORMAT: json + BINARY_NAME: traverse + DEPLOY_DIR: ./locale/deploy + OUT_DIR: ./locale/out + L10N_DIR: ./locale/out/l10n + # + SOURCE_LANG: en-GB + SOURCE_ACTIVE: "active.{{.SOURCE_LANG}}.{{.FORMAT}}" + # + LANGUAGE_US: en-US + US_OUT_DIR: "{{.OUT_DIR}}/{{.LANGUAGE_US}}" + ACTIVE_US: "{{.BINARY_NAME}}.active.en-US.{{.FORMAT}}" + TRANSLATE_US: "{{.BINARY_NAME}}.translate.en-US.{{.FORMAT}}" + TRANSLATE_US_FILEPATH: "{{.US_OUT_DIR}}/{{.TRANSLATE_US}}" + tasks: # === build ================================================ @@ -52,10 +68,18 @@ tasks: cmds: - go test ./i18n + tl: + cmds: + - go test ./locale + tn: cmds: - go test ./nfs + tcore: + cmds: + - go test ./core + toc: cmds: - go test ./collections @@ -155,44 +179,53 @@ tasks: clear: cmds: - - rm ./i18n/out/* --recursive + - rm -rf {{.OUT_DIR}}/* --recursive # extract i18m messages extract: cmds: + - mkdir -p ./locale/out/l10n - goi18n extract -format json -sourceLanguage "en-GB" - -outdir ./i18n/out/l10n + -outdir ./locale/out/l10n # new translation + # where is the default? locale/default/li18ngo.active.en-GB.json not populated + # ! creates: locale/out/l10n/active.en-GB.json => extracted output + # ! creates: locale/out/l10n/translate.en-US.json => empty newt: deps: [extract] cmds: - - touch ./i18n/out/l10n/translate.en-US.json + - touch ./locale/out/l10n/translate.en-US.json # derive a translation from the default + # ! the default active file does not contain hashes, just the extracted content + # ! the foreign translate file contains the hashes + # ! the active foreign file (locale/out/l10n/active.en-US.json) is empty + # ! pass the translate file(locale/out/l10n/translate.en-US.json) to your translator + # merge: cmds: - goi18n merge -format json -sourceLanguage "en-GB" - -outdir ./i18n/out - ./i18n/out/active.en-GB.json ./i18n/out/l10n/translate.en-US.json + -outdir ./locale/out/l10n + ./locale/out/l10n/active.en-GB.json ./locale/out/l10n/translate.en-US.json # update existing translations # after running this task, the translation file generated will # contain only the new translations. Update the active file, # with the new translations. Also, need to copy the default - # file (active.en-GB.json) back into ./i18n/default + # file (active.en-GB.json) back into ./locale/default update: deps: [extract] cmds: - goi18n merge -format json -sourceLanguage "en-GB" - -outdir ./i18n/out - ./i18n/out/active.en-GB.json ./i18n/deploy/active.en-US.json + -outdir ./locale/out + ./locale/out/active.en-GB.json ./i18n/deploy/active.en-US.json # run this after manual translation has occurred to integrate it # back into the translation file. Unfortunately, this task doesn't @@ -205,4 +238,4 @@ tasks: -format json -sourceLanguage "en-US" -outdir ./i18n/temp - ./i18n/out/translate.en-US.json ./i18n/deploy/active.en-US.json + ./locale/out/translate.en-US.json ./i18n/deploy/active.en-US.json diff --git a/collections/errors.go b/collections/errors.go index 702ab07..60dc3ac 100644 --- a/collections/errors.go +++ b/collections/errors.go @@ -1,13 +1,8 @@ package collections -import ( - "fmt" -) +import "errors" // โŒ Stack Is Empty (internal error) -// NewStackIsEmptyNativeError creates an untranslated error to -// indicate stack is empty (internal error) -func NewStackIsEmptyNativeError() error { - return fmt.Errorf("internal: stack is empty") -} +// ErrStackIsEmpty indicates stack is empty (internal error) +var ErrStackIsEmpty = errors.New("internal: stack is empty") diff --git a/collections/stack.go b/collections/stack.go index 186346c..cb93b53 100644 --- a/collections/stack.go +++ b/collections/stack.go @@ -27,7 +27,7 @@ func (s *Stack[T]) Push(item T) { func (s *Stack[T]) Pop() (T, error) { if s.IsEmpty() { var zero T - return zero, NewStackIsEmptyNativeError() + return zero, ErrStackIsEmpty } item := s.pop() @@ -38,7 +38,7 @@ func (s *Stack[T]) Pop() (T, error) { // MustPop func (s *Stack[T]) MustPop() T { if s.IsEmpty() { - panic(NewStackIsEmptyNativeError()) + panic(ErrStackIsEmpty) } return s.pop() @@ -48,7 +48,7 @@ func (s *Stack[T]) MustPop() T { func (s *Stack[T]) Current() (T, error) { if s.IsEmpty() { var zero T - return zero, NewStackIsEmptyNativeError() + return zero, ErrStackIsEmpty } return s.content[s.top()], nil diff --git a/collections/stack_test.go b/collections/stack_test.go index 174f8b4..e98aa14 100644 --- a/collections/stack_test.go +++ b/collections/stack_test.go @@ -78,7 +78,7 @@ var _ = Describe("Stack", func() { Expect(func() { stack.MustPop() - }).To(PanicWith(collections.NewStackIsEmptyNativeError())) + }).To(PanicWith(collections.ErrStackIsEmpty)) }) }) }) diff --git a/core/core-defs.go b/core/core-defs.go index 69b0a2b..146f56d 100644 --- a/core/core-defs.go +++ b/core/core-defs.go @@ -6,7 +6,7 @@ import ( "github.com/snivilised/traverse/measure" ) -// ๐Ÿ“š package: core contains universal definitions and handles user facing cross +// ๐Ÿ“ฆ pkg: core - contains universal definitions and handles user facing cross // cutting concerns try to keep to a minimum to reduce rippling changes. type ( diff --git a/core/core-suite_test.go b/core/core-suite_test.go new file mode 100644 index 0000000..c0f8a35 --- /dev/null +++ b/core/core-suite_test.go @@ -0,0 +1,13 @@ +package core_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" //nolint:revive // ok + . "github.com/onsi/gomega" //nolint:revive // ok +) + +func TestCore(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Core Suite") +} diff --git a/core/errors.go b/core/errors.go index aa72d59..fd28c22 100644 --- a/core/errors.go +++ b/core/errors.go @@ -1,65 +1,200 @@ package core import ( - "errors" "fmt" + + "github.com/pkg/errors" ) // Errors defined here are internal errors that are of no value to end // users (hence not l10n). There are usually programming errors which -// means they only have meaning for client developers. +// means they only have meaning for client developers as opposed to the +// end user. +// +// A Variable error is any error that contains extra state that +// is used to decorate the underlying core error. So for example +// 'InvalidNotificationMuteRequestedError' has a 'notification' value +// associated with it, that is required when creating this error. The +// core error (unexported), is decorated by the value to create a custom +// version of the core. But because of the way wrapping works, when we +// display the error via the Error method, the information unfortunately +// comes out backwards. So creating an instance of +// 'InvalidNotificationMuteRequestedError', with a notification value +// 'OnBegin' is displayed as: +// notification: OnBegin: invalid notification mute requested +// So what this is saying is that the underlying error is +// 'invalid notification mute requested', but the detail is that the +// notification value is 'OnBegin'. Intuitively, we would expect to see +// this arranged the other way around, but this is not possible, without +// an unjustifiable amount of re-work. +// +// Any non variable errors, eg 'ErrGuardianCantDecorateItemSealed' do not +// have an IsXXX function defined, because the client can invoke errors.Is +// on the error directly themselves; +// ie: errors.Is(err, ErrGuardianCantDecorateItemSealed) +// +// These errors are generated from snippet "Variable Native Error" (verr) +// -// โŒ Invalid Notification Mute Requested +// โŒ InvalidNotificationMuteRequested error -// NewInvalidNotificationMuteRequestedNativeError creates an untranslated error to +// NewInvalidNotificationMuteRequestedError creates an untranslated error to // indicate invalid notification mute requested (internal error) -func NewInvalidNotificationMuteRequestedNativeError(value string) error { - return fmt.Errorf("internal: invalid notification mute requested (%v)", value) +func NewInvalidNotificationMuteRequestedError(notification string) error { + return errors.Wrap( + errInvalidNotificationMuteRequested, + fmt.Sprintf("notification: %v", notification), + ) } -// โŒ Invalid Resume State Transition Detected +// IsInvalidNotificationMuteRequestedError uses errors.Is to check +// if the err's error tree contains the core error: +// InvalidNotificationMuteRequestedError +func IsInvalidNotificationMuteRequestedError(err error) bool { + return errors.Is(err, errInvalidNotificationMuteRequested) +} + +var errInvalidNotificationMuteRequested = errors.New( + "invalid notification mute requested", +) + +// โŒ InvalidResumeStateTransition error -// NewItemAlreadyExtendedNativeError creates an untranslated error to -// indicate invalid resume state transition occurred (internal error) -func NewInvalidResumeStateTransitionNativeError(state string) error { - return fmt.Errorf("internal: invalid resume state transition detected (%v)", state) +// NewInvalidResumeStateTransitionError creates an untranslated error to +// indicate in invalid resume state transition +func NewInvalidResumeStateTransitionError(state string) error { + return errors.Wrap( + errInvalidResumeStateTransition, + fmt.Sprintf("state: %v", state), + ) } -// โŒ Item already extended +// IsInvalidResumeStateTransitionError uses errors.Is to check +// if the err's error tree contains the core error: +// InvalidResumeStateTransitionNativeError +func IsInvalidResumeStateTransitionError(err error) bool { + return errors.Is(err, errInvalidResumeStateTransition) +} + +var errInvalidResumeStateTransition = errors.New( + "invalid resume state transition detected", +) + +// โŒ NewItemAlreadyExtended error -// NewItemAlreadyExtendedNativeError creates an untranslated error to -// indicate traverse-item already extended (internal error) -func NewItemAlreadyExtendedNativeError(path string) error { - return fmt.Errorf("internal: item already extended for item at: '%v'", path) +// NewItemAlreadyExtendedError creates an untranslated error to +// indicate the node has already been extended +func NewItemAlreadyExtendedError(path string) error { + return errors.Wrap( + errNewItemAlreadyExtended, + fmt.Sprintf("path: %v", path), + ) } -// โŒ Missing listen detacher function +// IsNewItemAlreadyExtendedError uses errors.Is to check +// if the err's error tree contains the core error: +// NewItemAlreadyExtendedError +func IsNewItemAlreadyExtendedError(err error) bool { + return errors.Is(err, errNewItemAlreadyExtended) +} + +var errNewItemAlreadyExtended = errors.New( + "item already extended for item", +) + +// โŒ MissingHibernationDetacherFunction error -// NewMissingListenDetacherFunctionNativeError creates an untranslated error to -// indicate invalid resume state transition occurred (internal error) -func NewMissingListenDetacherFunctionNativeError(state string) error { - return fmt.Errorf("internal: missing listen detacher function (%v)", state) +// NewMissingHibernationDetacherFunctionError creates an untranslated error to +// indicate hibernation detacher function nt defined +func NewMissingHibernationDetacherFunctionError(state string) error { + return errors.Wrap( + errMissingListenDetacherFunction, + fmt.Sprintf("state: %v", state), + ) } -// โŒ Invalid Periscope Root Path +// IsMissingHibernationDetacherFunctionError uses errors.Is to check +// if the err's error tree contains the core error: +// MissingListenDetacherFunctionError +func IsMissingHibernationDetacherFunctionError(err error) bool { + return errors.Is(err, errMissingListenDetacherFunction) +} + +var errMissingListenDetacherFunction = errors.New( + "missing listen detacher function", +) -// NewInvalidPeriscopeRootPathNativeError creates an untranslated error to -// indicate invalid resume state transition occurred (internal error) -func NewInvalidPeriscopeRootPathNativeError(root, current string) error { - return fmt.Errorf("internal: root path '%v' can't be longer than current '%v'", root, current) +// โŒ InvalidPeriscopeRootPath error + +// NewInvalidPeriscopeRootPathError creates an untranslated error to +// indicate invalid periscope root path, ie the current path is +// not a child directory relative to the root path. +func NewInvalidPeriscopeRootPathError(root, current string) error { + return errors.Wrap( + errInvalidPeriscopeRootPath, + fmt.Sprintf("root: '%v', current: '%v'", root, current), + ) } -// โŒ Resume controller not set +// IsInvalidPeriscopeRootPathError uses errors.Is to check +// if the err's error tree contains the core error: +// InvalidPeriscopeRootPathError +func IsInvalidPeriscopeRootPathError(err error) bool { + return errors.Is(err, errInvalidPeriscopeRootPath) +} + +var errInvalidPeriscopeRootPath = errors.New( + "root path can't be longer than current", +) + +// โŒ ResumeControllerNotSet error -// NewResumeControllerNotSetNativeError creates an untranslated error to -// indicate resume controller not set (internal error) -func NewResumeControllerNotSetNativeError(from string) error { - return fmt.Errorf("internal: resume controller not set (from: '%v')", from) +// NewResumeControllerNotSetError creates an untranslated error to +// indicate resume controller not set +func NewResumeControllerNotSetError(from string) error { + return errors.Wrap( + errResumeControllerNotSet, + fmt.Sprintf("from: %v", from), + ) } -var ( - ErrNotImpl = errors.New("NOT-IMPL") - ErrUndefinedSubscriptionType = errors.New( - "undefined subscription type; please set in traverse options (/Options.Store.Subscription)", +// IsResumeControllerNotSetError uses errors.Is to check +// if the err's error tree contains the core error: +// ResumeControllerNotSetError +func IsResumeControllerNotSetError(err error) bool { + return errors.Is(err, errResumeControllerNotSet) +} + +var errResumeControllerNotSet = errors.New( + "resume controller not set", +) + +// โŒ GuardianCantDecorateItemSealed error + +// ErrGuardianCantDecorateItemSealedError creates an untranslated error to +// indicate last item is sealed +var ErrGuardianCantDecorateItemSealed = errors.New( + "can't decorate, last item is sealed", +) + +// โŒ BrokerTopicNotFound error + +// NewBrokerTopicNotFoundError creates an untranslated error to +// indicate the topic requested was not found; ie it wasn't registered. +func NewBrokerTopicNotFoundError(topic string) error { + return errors.Wrap( + errBrokerTopicNotFound, + fmt.Sprintf("topic: %v", topic), ) +} + +// IsBrokerTopicNotFoundError uses errors.Is to check +// if the err's error tree contains the core error: +// BrokerTopicNotFoundError +func IsBrokerTopicNotFoundError(err error) bool { + return errors.Is(err, errBrokerTopicNotFound) +} + +var errBrokerTopicNotFound = errors.New( + "broker topic not found", ) diff --git a/core/errors_test.go b/core/errors_test.go new file mode 100644 index 0000000..74b191d --- /dev/null +++ b/core/errors_test.go @@ -0,0 +1,181 @@ +package core_test + +import ( + . "github.com/onsi/ginkgo/v2" //nolint:revive // ok + . "github.com/onsi/gomega" //nolint:revive // ok + "github.com/pkg/errors" + + "github.com/snivilised/traverse/core" +) + +var _ = Describe("Variable untranslated Errors", func() { + // These tests generated using snippet: "Ginko Variable Native Error" (gverrt) + // + Context("InvalidNotificationMuteRequested error", func() { + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := core.NewInvalidNotificationMuteRequestedError("OnBegin") + Expect( + core.IsInvalidNotificationMuteRequestedError(err), + ).To(BeTrue(), + "error does not match InvalidNotificationMuteRequested", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + core.IsInvalidNotificationMuteRequestedError(err), + ).To(BeFalse(), + "not matching error should not match InvalidNotificationMuteRequested", + ) + }) + }) + }) + + Context("InvalidResumeStateTransition error", func() { + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := core.NewInvalidResumeStateTransitionError("bar") + Expect( + core.IsInvalidResumeStateTransitionError(err), + ).To(BeTrue(), + "error does not match InvalidResumeStateTransition", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + core.IsInvalidResumeStateTransitionError(err), + ).To(BeFalse(), + "not matching error should not match InvalidResumeStateTransition", + ) + }) + }) + }) + + Context("NewItemAlreadyExtended error", func() { + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := core.NewItemAlreadyExtendedError("/some-path") + Expect( + core.IsNewItemAlreadyExtendedError(err), + ).To(BeTrue(), + "error does not match NewItemAlreadyExtended", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + core.IsNewItemAlreadyExtendedError(err), + ).To(BeFalse(), + "not matching error should not match NewItemAlreadyExtended", + ) + }) + }) + }) + + Context("MissingHibernationDetacherFunction error", func() { + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := core.NewMissingHibernationDetacherFunctionError("bar") + Expect( + core.IsMissingHibernationDetacherFunctionError(err), + ).To(BeTrue(), + "error does not match MissingHibernationDetacherFunction", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + core.IsMissingHibernationDetacherFunctionError(err), + ).To(BeFalse(), + "not matching error should not match MissingHibernationDetacherFunction", + ) + }) + }) + }) + + Context("InvalidPeriscopeRootPath error", func() { + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := core.NewInvalidPeriscopeRootPathError("/some-root", "/come-current") + Expect( + core.IsInvalidPeriscopeRootPathError(err), + ).To(BeTrue(), + "error does not match InvalidPeriscopeRootPath", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + core.IsInvalidPeriscopeRootPathError(err), + ).To(BeFalse(), + "not matching error should not match InvalidPeriscopeRootPath", + ) + }) + }) + }) + + Context("ResumeControllerNotSet error", func() { + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := core.NewResumeControllerNotSetError("bar") + Expect( + core.IsResumeControllerNotSetError(err), + ).To(BeTrue(), + "error does not match ResumeControllerNotSet", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + core.IsResumeControllerNotSetError(err), + ).To(BeFalse(), + "not matching error should not match ResumeControllerNotSet", + ) + }) + }) + }) + + Context("BrokerTopicNotFound error", func() { + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := core.NewBrokerTopicNotFoundError("/foo/bar") + Expect( + core.IsBrokerTopicNotFoundError(err), + ).To(BeTrue(), + "error does not match BrokerTopicNotFound", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + core.IsBrokerTopicNotFoundError(err), + ).To(BeFalse(), + "not matching error should not match BrokerTopicNotFound", + ) + }) + }) + }) +}) diff --git a/cycle/cycle-defs.go b/cycle/cycle-defs.go index 60b1d5d..ad90d31 100644 --- a/cycle/cycle-defs.go +++ b/cycle/cycle-defs.go @@ -4,7 +4,7 @@ import ( "github.com/snivilised/traverse/core" ) -// ๐Ÿ“š package: cycle represents life cycle events; can't use prefs +// ๐Ÿ“ฆ pkg: cycle - represents life cycle events; can't use prefs // beforeX // afterX diff --git a/go.mod b/go.mod index 00bc5b5..53477e1 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,10 @@ module github.com/snivilised/traverse -go 1.22.0 +go 1.23.0 require ( github.com/onsi/ginkgo/v2 v2.20.0 github.com/onsi/gomega v1.34.1 - github.com/snivilised/extendio v0.7.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 ) @@ -18,16 +17,16 @@ require ( ) require ( - github.com/avfs/avfs v0.33.0 // indirect github.com/fortytw2/leaktest v1.3.0 github.com/go-logr/logr v1.4.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect - github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect + github.com/nicksnyder/go-i18n/v2 v2.4.0 github.com/pkg/errors v0.9.1 github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/samber/lo v1.46.0 // indirect + github.com/snivilised/li18ngo v0.1.2 golang.org/x/net v0.28.0 // indirect golang.org/x/sys v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/go.sum b/go.sum index b623626..d30adb5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/avfs/avfs v0.33.0 h1:5WQXbUbr6VS7aani39ZN2Vrd/s3wLnyih1Sc4ExWTxs= -github.com/avfs/avfs v0.33.0/go.mod h1:Q59flcFRYe9KYkNMfrLUJney3yeKGQpcWRyxsDBW7vI= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -20,8 +18,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= @@ -36,8 +32,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/snivilised/extendio v0.7.0 h1:MY6w9qCK5wVEvP2WpMT5ywJwpDJe97WHDGuwrsTLpek= -github.com/snivilised/extendio v0.7.0/go.mod h1:l8MwJOy9ojMQYJrSKRbQS3WfDylevnRtBp/zwAmFEKc= +github.com/snivilised/li18ngo v0.1.2 h1:xiOZnAcIkeHfEVuTy/FBwA1u9IrAfYSjO/DYMC77z/I= +github.com/snivilised/li18ngo v0.1.2/go.mod h1:Or3qUhpR6AM1X51i82RtyCvORWy2/hrxY9lg1i1gFTE= github.com/snivilised/lorax v0.6.1 h1:U3OnSsGjDdsQfHyxB3TMGX/zwxepafM6YnPXMCPJS9E= github.com/snivilised/lorax v0.6.1/go.mod h1:pEvn+FDo7DkUwhDQq3XAqajSHdbjgC/6H9mcBI67NsU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= diff --git a/internal/feat/hiber/hibernation-defs.go b/internal/feat/hiber/hibernation-defs.go index c1edb9c..acd402f 100644 --- a/internal/feat/hiber/hibernation-defs.go +++ b/internal/feat/hiber/hibernation-defs.go @@ -4,7 +4,7 @@ const ( badge = "badge: hibernator" ) -// ๐Ÿ“š package: hiber represents the facility to be able to start navigation in +// ๐Ÿ“ฆ pkg: hiber - represents the facility to be able to start navigation in // hibernated state, ie we navigate but dont invoke a client action, // until a certain condition occurs, specified by a node matching a // filter. This is what used to be called listening in extendio. We diff --git a/internal/feat/refine/filter-plugin.go b/internal/feat/refine/filter-plugin.go index 8b50976..b76da51 100644 --- a/internal/feat/refine/filter-plugin.go +++ b/internal/feat/refine/filter-plugin.go @@ -1,6 +1,6 @@ package refine -// ๐Ÿ“š package: refine defines filters +// ๐Ÿ“ฆ pkg: refine - defines filters import ( "github.com/snivilised/traverse/core" diff --git a/internal/feat/refine/new-filter.go b/internal/feat/refine/new-filter.go index 3f467e1..d4aa41a 100644 --- a/internal/feat/refine/new-filter.go +++ b/internal/feat/refine/new-filter.go @@ -1,11 +1,9 @@ package refine import ( - "errors" "slices" "strings" - xi18n "github.com/snivilised/extendio/i18n" "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/third/lo" @@ -16,7 +14,7 @@ import ( func fromExtendedGlobPattern(pattern string) (segments, suffixes []string, err error) { if !strings.Contains(pattern, "|") { return []string{}, []string{}, - errors.New("invalid extended glob filter definition; pattern is missing separator") + locale.NewInvalidExtGlobFilterMissingSeparatorError(pattern) } segments = strings.Split(pattern, "|") @@ -97,9 +95,7 @@ func newNodeFilter(def *core.FilterDef, case enums.FilterTypeCustom: if fo.Custom == nil { - return nil, xi18n.NewMissingCustomFilterDefinitionError( - "Options.Filter.Custom", - ) + return nil, locale.ErrMissingCustomFilterDefinition } filter = fo.Custom @@ -184,7 +180,7 @@ func newChildFilter(def *core.ChildFilterDef) (core.ChildTraverseFilter, error) ) if segments, suffixes, err = fromExtendedGlobPattern(def.Pattern); err != nil { - return nil, locale.ErrInvalidIncaseFilterDef + return nil, locale.NewInvalidIncaseFilterDefError(def.Pattern) } base, exclusion := splitGlob(segments[0]) @@ -260,11 +256,11 @@ func newSampleFilter(def *core.SampleFilterDef, } if base.scope.IsFile() && so.NoOf.Files == 0 { - return nil, locale.ErrInvalidFileSamplingSpecification + return nil, locale.ErrInvalidFileSamplingSpecMissingFiles } if base.scope.IsFolder() && so.NoOf.Folders == 0 { - return nil, locale.ErrInvalidFolderSamplingSpecification + return nil, locale.ErrInvalidFolderSamplingSpecMissingFolders } switch def.Type { diff --git a/internal/feat/resume/resume-defs.go b/internal/feat/resume/resume-defs.go index 1c06979..b35ecbb 100644 --- a/internal/feat/resume/resume-defs.go +++ b/internal/feat/resume/resume-defs.go @@ -8,7 +8,7 @@ import ( "github.com/snivilised/traverse/pref" ) -// ๐Ÿ“š package: resume depends on hiber, refine and persist. +// ๐Ÿ“ฆ pkg: resume - depends on hiber, refine and persist. // refine should also contain persistence concerns (actually // these may be internal modules, eg internal/serial/JSON). diff --git a/internal/feat/resume/strategy-fastward.go b/internal/feat/resume/strategy-fastward.go index ec501f8..20c3d46 100644 --- a/internal/feat/resume/strategy-fastward.go +++ b/internal/feat/resume/strategy-fastward.go @@ -3,7 +3,7 @@ package resume import ( "context" - "github.com/pkg/errors" + "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/types" ) @@ -13,7 +13,7 @@ type fastwardGuardianSealer struct { func (g *fastwardGuardianSealer) Seal(top types.Link) error { if top.Role() == enums.RoleFastward { - return errors.New("can't decorate, last item is sealed (fastward)") + return core.ErrGuardianCantDecorateItemSealed } return nil } diff --git a/internal/feat/sampling/sampling-defs.go b/internal/feat/sampling/sampling-defs.go index 7d7e716..2162319 100644 --- a/internal/feat/sampling/sampling-defs.go +++ b/internal/feat/sampling/sampling-defs.go @@ -1,4 +1,4 @@ package sampling -// ๐Ÿ“š package: sampling refers to the ability to just take a sample from each directory +// ๐Ÿ“ฆ pkg: sampling - refers to the ability to just take a sample from each directory // encountered during traversal. diff --git a/internal/helpers/directory-tree-builder.go b/internal/helpers/directory-tree-builder.go index de8be78..d402849 100644 --- a/internal/helpers/directory-tree-builder.go +++ b/internal/helpers/directory-tree-builder.go @@ -36,7 +36,7 @@ func Musico(verbose bool, portions ...string) (msys fstest.MapFS, root string) { } func Provision(provider *IOProvider, verbose bool, portions ...string) (root string) { - repo := Repo(filepath.Join("..", "..", "test", "data", "MUSICO")) + repo := Repo(filepath.Join("test", "data", "MUSICO")) if ensure(repo, provider, verbose) != nil { return "" @@ -53,7 +53,7 @@ func Provision(provider *IOProvider, verbose bool, portions ...string) (root str // ensure func ensure(root string, provider *IOProvider, verbose bool) error { - repo := Repo(filepath.Join("..", "..")) + repo := Repo("") index := Path(repo, "test/data/musico-index.xml") parent, _ := nfs.SplitParent(root) builder := directoryTreeBuilder{ diff --git a/internal/helpers/test-utilities.go b/internal/helpers/test-utilities.go index 45ee10e..530c64d 100644 --- a/internal/helpers/test-utilities.go +++ b/internal/helpers/test-utilities.go @@ -3,8 +3,8 @@ package helpers import ( "fmt" "os" + "os/exec" "path/filepath" - "runtime" "strings" ) @@ -53,8 +53,11 @@ func Root() string { } func Repo(relative string) string { - _, filename, _, _ := runtime.Caller(0) //nolint:dogsled // ignore - return Path(filepath.Dir(filename), relative) + cmd := exec.Command("git", "rev-parse", "--show-toplevel") + output, _ := cmd.Output() + repo := strings.TrimSpace(string(output)) + + return Path(repo, relative) } func Log() string { diff --git a/internal/kernel/guardian.go b/internal/kernel/guardian.go index 176d95f..f1facff 100644 --- a/internal/kernel/guardian.go +++ b/internal/kernel/guardian.go @@ -1,8 +1,6 @@ package kernel import ( - "errors" - "github.com/snivilised/traverse/collections" "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" @@ -99,7 +97,7 @@ func (g *guardian) Decorate(link types.Link) error { top := g.container.chain[g.container.positions.Items()[0]] if g.master.IsSealed(top) { - return errors.New("can't decorate, last item is sealed") + return core.ErrGuardianCantDecorateItemSealed } role := link.Role() diff --git a/internal/kernel/kernel-defs.go b/internal/kernel/kernel-defs.go index 2a4c83f..82f4101 100644 --- a/internal/kernel/kernel-defs.go +++ b/internal/kernel/kernel-defs.go @@ -9,7 +9,7 @@ import ( "github.com/snivilised/traverse/internal/types" ) -// ๐Ÿ“š package: kernel contains the core traversal functionality. Kernel +// ๐Ÿ“ฆ pkg: kernel - contains the core traversal functionality. Kernel // is concerned only with the core task of navigation. Supplementary // functionality is implemented externally in plugins. diff --git a/internal/kernel/kernel-suite_test.go b/internal/kernel/kernel-suite_test.go index 46b7f64..516d002 100644 --- a/internal/kernel/kernel-suite_test.go +++ b/internal/kernel/kernel-suite_test.go @@ -302,16 +302,16 @@ func assertMetrics(entry *naviTE, to *testOptions) { Expect(to.result.Metrics().Count(enums.MetricNoFilesInvoked)).To( Equal(entry.expectedNoOf.files), helpers.BecauseQuantity("Incorrect no of files", - int(entry.expectedNoOf.files), - int(to.result.Metrics().Count(enums.MetricNoFilesInvoked)), + int(entry.expectedNoOf.files), //nolint:gosec // ok + int(to.result.Metrics().Count(enums.MetricNoFilesInvoked)), //nolint:gosec // ok ), ) Expect(to.result.Metrics().Count(enums.MetricNoFoldersInvoked)).To( Equal(entry.expectedNoOf.folders), helpers.BecauseQuantity("Incorrect no of folders", - int(entry.expectedNoOf.folders), - int(to.result.Metrics().Count(enums.MetricNoFoldersInvoked)), + int(entry.expectedNoOf.folders), //nolint:gosec // ok + int(to.result.Metrics().Count(enums.MetricNoFoldersInvoked)), //nolint:gosec // ok ), ) @@ -321,7 +321,7 @@ func assertMetrics(entry *naviTE, to *testOptions) { Equal(uint(sum)), helpers.BecauseQuantity("Incorrect total no of child files", sum, - int(to.result.Metrics().Count(enums.MetricNoChildFilesFound)), + int(to.result.Metrics().Count(enums.MetricNoChildFilesFound)), //nolint:gosec // ok ), ) } diff --git a/internal/kernel/navigator-sample_test.go b/internal/kernel/navigator-sample_test.go index bfe080f..150bd90 100644 --- a/internal/kernel/navigator-sample_test.go +++ b/internal/kernel/navigator-sample_test.go @@ -14,7 +14,7 @@ import ( "github.com/snivilised/traverse/internal/helpers" "github.com/snivilised/traverse/internal/services" "github.com/snivilised/traverse/internal/third/lo" - i18n "github.com/snivilised/traverse/locale" + "github.com/snivilised/traverse/locale" "github.com/snivilised/traverse/pref" ) @@ -748,7 +748,7 @@ var _ = Describe("Sampling", Ordered, func() { expectedNoOf: quantities{ files: 24, }, - expectedErr: i18n.ErrInvalidFolderSamplingSpecification, + expectedErr: locale.ErrInvalidFolderSamplingSpecMissingFolders, }, filter: &filterTE{ // ๐Ÿง„ description: "glob: items with .flac suffix", @@ -772,7 +772,7 @@ var _ = Describe("Sampling", Ordered, func() { expectedNoOf: quantities{ files: 24, }, - expectedErr: i18n.ErrInvalidFileSamplingSpecification, + expectedErr: locale.ErrInvalidFileSamplingSpecMissingFiles, }, filter: &filterTE{ // ๐Ÿง„ description: "glob: items with .flac suffix", diff --git a/internal/level/periscope.go b/internal/level/periscope.go index 2aa0888..79d898e 100644 --- a/internal/level/periscope.go +++ b/internal/level/periscope.go @@ -8,7 +8,7 @@ import ( "github.com/snivilised/traverse/enums" ) -// ๐Ÿ“š package: level contains functionality concerned only with depth +// ๐Ÿ“ฆ pkg: level - contains functionality concerned only with depth // management. // Periscope: depth and scope manager @@ -63,7 +63,7 @@ func (p *Periscope) Delta(root, current string) (err error) { currentSize := len(strings.Split(current, string(filepath.Separator))) if rootSize > currentSize { - return core.NewInvalidPeriscopeRootPathNativeError(root, current) + return core.NewInvalidPeriscopeRootPathError(root, current) } p.offset = currentSize - rootSize @@ -72,7 +72,7 @@ func (p *Periscope) Delta(root, current string) (err error) { } func (p *Periscope) Descend(maximum uint) bool { - if maximum > 0 && p.depth > int(maximum) { + if maximum > 0 && p.depth > int(maximum) { //nolint:gosec // ok return false } diff --git a/internal/override/actions.go b/internal/override/actions.go index a753e2a..bcee016 100644 --- a/internal/override/actions.go +++ b/internal/override/actions.go @@ -6,7 +6,7 @@ import ( "github.com/snivilised/traverse/tapable" ) -// ๐Ÿ“š package: override provides a similar function to tapable except we +// ๐Ÿ“ฆ pkg: override - provides a similar function to tapable except we // use the name action to replace hook. The difference between the // two are that hooks allow for the client to customise core internal // behaviour, where as an action allows for internal behaviour to diff --git a/internal/services/broker.go b/internal/services/broker.go index 80929c8..10973c6 100644 --- a/internal/services/broker.go +++ b/internal/services/broker.go @@ -4,7 +4,7 @@ import ( "github.com/snivilised/traverse/internal/third/bus" ) -// ๐Ÿ“š package: services can contain anything which is a cross cutting concern +// ๐Ÿ“ฆ pkg: services - can contain anything which is a cross cutting concern // so much so that explicitly passing them around just results in // tighter coupling. diff --git a/internal/third/bus/broker.go b/internal/third/bus/broker.go index a36b6d5..10e4ebe 100644 --- a/internal/third/bus/broker.go +++ b/internal/third/bus/broker.go @@ -9,6 +9,9 @@ import ( "fmt" "regexp" "time" + + "github.com/snivilised/traverse/core" + "github.com/snivilised/traverse/locale" ) type ( @@ -71,7 +74,7 @@ const ( // New inits a new bus func New(g IDGenerator) (*Broker, error) { if g == nil { - return nil, fmt.Errorf("bus: Next() id generator func can't be nil") // TODO: i18n + return nil, locale.ErrIDGeneratorFuncCantBeNil } return &Broker{ @@ -125,9 +128,7 @@ func (b *Broker) Emit(ctx context.Context, handlers, ok := b.topics[topic] if !ok { - return &BrokerError{ - message: fmt.Sprintf("topics(%s) not found", topic), // TODO: i18n - } + return core.NewBrokerTopicNotFoundError(topic) } source, _ := ctx.Value(CtxKeySource).(string) @@ -161,9 +162,7 @@ func (b *Broker) EmitWithOpts(ctx context.Context, handlers, ok := b.topics[topic] if !ok { - return &BrokerError{ - message: fmt.Sprintf("topics(%s) not found", topic), // TODO: i18n - } + return core.NewBrokerTopicNotFoundError(topic) } e := Message{Topic: topic, Data: data} diff --git a/internal/third/bus/broker_test.go b/internal/third/bus/broker_test.go index aee1f99..9996757 100644 --- a/internal/third/bus/broker_test.go +++ b/internal/third/bus/broker_test.go @@ -12,6 +12,7 @@ import ( . "github.com/onsi/ginkgo/v2" //nolint:revive // ok . "github.com/onsi/gomega" //nolint:revive // ok + "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/internal/third/bus" ) @@ -107,12 +108,14 @@ var _ = Describe("Bus", func() { }) When("with unknown topic", func() { - It("๐Ÿงช should: tbd", func() { + It("๐Ÿงช should: return broker topic not found error", func() { ctx := context.Background() err := b.Emit(ctx, topicCommentUpdated, "my comment") Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(Equal("topics(comment.updated) not found")) + Expect(core.IsBrokerTopicNotFoundError(err)).To(BeTrue(), + "error is not BrokerTopicNotFound", + ) }) }) }) @@ -162,7 +165,9 @@ var _ = Describe("Bus", func() { err := b.EmitWithOpts(ctx, topicCommentUpdated, "my comment") Expect(err).NotTo(Succeed()) - Expect(err.Error()).To(Equal("topics(comment.updated) not found")) + Expect(core.IsBrokerTopicNotFoundError(err)).To(BeTrue(), + "error is not BrokerTopicNotFound", + ) }) }) }) diff --git a/internal/types/definitions.go b/internal/types/definitions.go index 7f6b5c3..fc85563 100644 --- a/internal/types/definitions.go +++ b/internal/types/definitions.go @@ -12,7 +12,7 @@ import ( "github.com/snivilised/traverse/pref" ) -// ๐Ÿ“š package: types package defines internal types +// ๐Ÿ“ฆ pkg: types - defines internal types type ( // Link represents a single decorator in the chain diff --git a/locale/default/traverse.active.en-GB.json b/locale/default/traverse.active.en-GB.json deleted file mode 100644 index bb88f60..0000000 --- a/locale/default/traverse.active.en-GB.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "root-command-config-file-usage": { - "description": "root command config flag usage", - "other": "config file (default is $HOME/{{.ConfigFileName}}.yml)" - }, - "root-command-language-usage": { - "description": "root command lang usage", - "other": "'lang' defines the language according to IETF BCP 47" - }, - "root-command-long-description": { - "description": "long description for the root command", - "other": "A longer description that spans multiple lines and likely contains\n\t\texamples and usage of using your application. For example:\n\t\t\n\t\tCobra is a CLI library for Go that empowers applications.\n\t\tThis application is a tool to generate the needed files\n\t\tto quickly create a Cobra application." - }, - "root-command-short-description": { - "description": "short description for the root command", - "other": "A brief description of your application" - }, - "using-config-file": { - "description": "Message to indicate which config is being used", - "other": "Using config file: {{.ConfigFileName}}" - } -} \ No newline at end of file diff --git a/locale/deploy/traverse.active.en-US.json b/locale/deploy/traverse.active.en-US.json deleted file mode 100644 index 1bdc419..0000000 --- a/locale/deploy/traverse.active.en-US.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "root-command-config-file-usage": { - "description": "root command config flag usage", - "hash": "sha1-60ec091d99ab2286a4bb4a1e1733c155bca95a9f", - "other": "config file (default is $HOME/{{.ConfigFileName}}.yml)" - }, - "root-command-language-usage": { - "description": "root command lang usage", - "hash": "sha1-13eee706e037f33844c76f1dcdac5786b4089a00", - "other": "'lang' defines the language according to IETF BCP 47" - }, - "root-command-long-description": { - "description": "long description for the root command", - "hash": "sha1-9122dfb1f95d2d00501ca67449df1dcf695f2cc2", - "other": "A longer description that spans multiple lines and likely contains\n\t\texamples and usage of using your application. For example:\n\t\t\n\t\tCobra is a CLI library for Go that empowers applications.\n\t\tThis application is a tool to generate the needed files\n\t\tto quickly create a Cobra application." - }, - "root-command-short-description": { - "description": "short description for the root command", - "hash": "sha1-a88cc9526d5e47f670ce9b089390e65415ab26cc", - "other": "A brief description of your application" - }, - "using-config-file": { - "description": "Message to indicate which config is being used", - "hash": "sha1-06fd9528f8226d1501ea09bc5d629f4922b4f3c6", - "other": "Using config file: {{.ConfigFileName}}" - } -} \ No newline at end of file diff --git a/locale/i18n-messages_test.go b/locale/i18n-messages_test.go deleted file mode 100644 index acf1b3c..0000000 --- a/locale/i18n-messages_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package locale_test - -import ( - "github.com/snivilised/extendio/i18n" -) - -const ( - GrafficoSourceID = "github.com/snivilised/graffico" -) - -type GrafficoData struct{} - -func (td GrafficoData) SourceID() string { - return GrafficoSourceID -} - -// ๐ŸงŠ Pavement Graffiti Report - -// PavementGraffitiReportTemplData -type PavementGraffitiReportTemplData struct { - GrafficoData - Primary string -} - -func (td PavementGraffitiReportTemplData) Message() *i18n.Message { - return &i18n.Message{ - ID: "pavement-graffiti-report.graffico.unit-test", - Description: "Report of graffiti found on a pavement", - Other: "Found graffiti on pavement; primary colour: '{{.Primary}}'", - } -} - -// โ˜ข๏ธ Wrong Source Id - -// WrongSourceIDTemplData -type WrongSourceIDTemplData struct { - GrafficoData -} - -func (td WrongSourceIDTemplData) SourceID() string { - return "FOO-BAR" -} - -func (td WrongSourceIDTemplData) Message() *i18n.Message { - return &i18n.Message{ - ID: "wrong-source-id.graffico.unit-test", - Description: "Incorrect Source Id for which doesn't match the one n the localizer", - Other: "Message with wrong id", - } -} diff --git a/locale/locale-defs.go b/locale/locale-defs.go index ff1ab2a..c9baff1 100644 --- a/locale/locale-defs.go +++ b/locale/locale-defs.go @@ -1,11 +1,11 @@ package locale const ( - TraverseSourceID = "github.com/snivilised/traverse" + SourceID = "github.com/snivilised/traverse" ) type traverseTemplData struct{} func (td traverseTemplData) SourceID() string { - return TraverseSourceID + return SourceID } diff --git a/locale/i18n_suite_test.go b/locale/locale-suite_test.go similarity index 76% rename from locale/i18n_suite_test.go rename to locale/locale-suite_test.go index fae3007..23aeb9d 100644 --- a/locale/i18n_suite_test.go +++ b/locale/locale-suite_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/gomega" //nolint:revive // gomega ok ) -func TestI18n(t *testing.T) { +func TestLocale(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "I18n Suite") + RunSpecs(t, "Locale Suite") } diff --git a/locale/messages-errors.go b/locale/messages-errors.go index 2fb52a9..fd36235 100644 --- a/locale/messages-errors.go +++ b/locale/messages-errors.go @@ -1,14 +1,486 @@ package locale import ( - "github.com/snivilised/extendio/i18n" + "github.com/pkg/errors" + + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/snivilised/li18ngo" ) +// โŒ FilterIsNil + +// FilterIsNilTemplData +type FilterIsNilErrorTemplData struct { + traverseTemplData +} + +// Message +func (td FilterIsNilErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "filter-is-nil.error", + Description: "filter is nil error", + Other: "filter is nil", + } +} + +type FilterIsNilError struct { + li18ngo.LocalisableError +} + +var ErrFilterIsNil = FilterIsNilError{ + LocalisableError: li18ngo.LocalisableError{ + Data: FilterIsNilErrorTemplData{}, + }, +} + +// โŒ FilterMissingType + +// FilterMissingTypeTemplData +type FilterMissingTypeErrorTemplData struct { + traverseTemplData +} + +// Message +func (td FilterMissingTypeErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "filter-missing-type.error", + Description: "filter missing type", + Other: "filter missing type", + } +} + +type FilterMissingTypeError struct { + li18ngo.LocalisableError +} + +var ErrFilterMissingType = FilterMissingTypeError{ + LocalisableError: li18ngo.LocalisableError{ + Data: FilterMissingTypeErrorTemplData{}, + }, +} + +// โŒ FilterCustomNotSupported + +// FilterCustomNotSupportedTemplData +type FilterCustomNotSupportedErrorTemplData struct { + traverseTemplData +} + +// Message +func (td FilterCustomNotSupportedErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "custom-filter-not-supported-for-children.error", + Description: "custom filter not supported for children", + Other: "custom filter not supported for children", + } +} + +type FilterCustomNotSupportedError struct { + li18ngo.LocalisableError +} + +var ErrFilterCustomNotSupported = FilterCustomNotSupportedError{ + LocalisableError: li18ngo.LocalisableError{ + Data: FilterCustomNotSupportedErrorTemplData{}, + }, +} + +// โŒ FilterUndefined + +// FilterUndefinedTemplData +type FilterUndefinedErrorTemplData struct { + traverseTemplData +} + +// Message +func (td FilterUndefinedErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "filter-is-undefined.error", + Description: "filter is undefined error", + Other: "filter is undefined", + } +} + +type FilterUndefinedError struct { + li18ngo.LocalisableError +} + +var ErrFilterUndefined = FilterUndefinedError{ + LocalisableError: li18ngo.LocalisableError{ + Data: FilterUndefinedErrorTemplData{}, + }, +} + +// โŒ InternalFailedToGetNavigatorDriver + +// InternalFailedToGetNavigatorDriverTemplData +type InternalFailedToGetNavigatorDriverErrorTemplData struct { + traverseTemplData +} + +// Message +func (td InternalFailedToGetNavigatorDriverErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "failed-to-get-navigator-driver.error", + Description: "failed to get navigator driver", + Other: "failed to get navigator driver", + } +} + +type InternalFailedToGetNavigatorDriverError struct { + li18ngo.LocalisableError +} + +var ErrInternalFailedToGetNavigatorDriver = InternalFailedToGetNavigatorDriverError{ + LocalisableError: li18ngo.LocalisableError{ + Data: InternalFailedToGetNavigatorDriverErrorTemplData{}, + }, +} + +// โŒ InvalidIncaseFilterDef error + +// InvalidIncaseFilterDefTemplData +type InvalidIncaseFilterDefErrorTemplData struct { + traverseTemplData +} + +// Message +func (td InvalidIncaseFilterDefErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "invalid-incase-filter-definition.error", + Description: "invalid incase filter definition; pattern is missing separator", + Other: "invalid incase filter definition; pattern is missing separator", + } +} + +type InvalidIncaseFilterDefError struct { + li18ngo.LocalisableError +} + +// IsInvalidIncaseFilterDefError uses errors.Is to check +// if the err's error tree contains the core error: +// InvalidIncaseFilterDefError +func IsInvalidIncaseFilterDefError(err error) bool { + return errors.Is(err, errInvalidIncaseFilterDef) +} + +func NewInvalidIncaseFilterDefError(pattern string) error { + return errors.Wrap( + errInvalidIncaseFilterDef, + li18ngo.Text(PatternFieldTemplData{ + Pattern: pattern, + }), + ) +} + +var errInvalidIncaseFilterDef = InvalidIncaseFilterDefError{ + LocalisableError: li18ngo.LocalisableError{ + Data: InvalidIncaseFilterDefErrorTemplData{}, + }, +} + +// โŒ WorkerPoolCreationFailed + +// WorkerPoolCreationFailedTemplData +type WorkerPoolCreationFailedErrorTemplData struct { + traverseTemplData +} + +// Message +func (td WorkerPoolCreationFailedErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "failed-to-create-worker-pool.error", + Description: "failed to create worker pool", + Other: "failed to create worker pool", + } +} + +type WorkerPoolCreationFailedError struct { + li18ngo.LocalisableError +} + +var ErrWorkerPoolCreationFailed = WorkerPoolCreationFailedError{ + LocalisableError: li18ngo.LocalisableError{ + Data: WorkerPoolCreationFailedErrorTemplData{}, + }, +} + +// โŒ InvalidFileSamplingSpecMissingFilesError + +// InvalidFileSamplingSpecMissingFilesErrorTemplData +type InvalidFileSamplingSpecMissingFilesErrorTemplData struct { + traverseTemplData +} + +// Message +func (td InvalidFileSamplingSpecMissingFilesErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "invalid-file-sampling-spec-missing-files.error", + Description: "invalid file sampling specification, missing no of files", + Other: "invalid file sampling specification, missing no of files", + } +} + +type InvalidFileSamplingSpecificationError struct { + li18ngo.LocalisableError +} + +var ErrInvalidFileSamplingSpecMissingFiles = InvalidFileSamplingSpecificationError{ + LocalisableError: li18ngo.LocalisableError{ + Data: InvalidFileSamplingSpecMissingFilesErrorTemplData{}, + }, +} + +// โŒ InvalidFolderSamplingSpecMissingFolders + +// InvalidFolderSamplingSpecMissingFoldersTemplData +type InvalidFolderSamplingSpecMissingFoldersErrorTemplData struct { + traverseTemplData +} + +// Message +func (td InvalidFolderSamplingSpecMissingFoldersErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "invalid-file-sampling-spec-missing-folders.error", + Description: "invalid file sampling specification, missing no of folders", + Other: "invalid file sampling specification, missing no of folders", + } +} + +type InvalidFolderSamplingSpecMissingFoldersError struct { + li18ngo.LocalisableError +} + +var ErrInvalidFolderSamplingSpecMissingFolders = InvalidFolderSamplingSpecMissingFoldersError{ + LocalisableError: li18ngo.LocalisableError{ + Data: InvalidFolderSamplingSpecMissingFoldersErrorTemplData{}, + }, +} + +// โŒ MissingCustomFilterDefinition + +// MissingCustomFilterDefinitionTemplData +type MissingCustomFilterDefinitionErrorTemplData struct { + traverseTemplData +} + +// Message +func (td MissingCustomFilterDefinitionErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "missing-custom-filter-definition.error", + Description: "config error missing-custom-filter-definition", + Other: "missing custom filter definition (config error)", + } +} + +type MissingCustomFilterDefinitionError struct { + li18ngo.LocalisableError +} + +var ErrMissingCustomFilterDefinition = MissingCustomFilterDefinitionError{ + LocalisableError: li18ngo.LocalisableError{ + Data: MissingCustomFilterDefinitionErrorTemplData{}, + }, +} + +// to define variable error with simple field - "Field and Variable error/fv18e" +// "Simple i18n Field" +// followed by + +// ๐Ÿ€ Pattern + +// PatternTemplData +type PatternFieldTemplData struct { + traverseTemplData + Pattern string +} + +// Message +func (td PatternFieldTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "pattern.field", + Description: "pattern", + Other: "pattern: {{.Pattern}}", + } +} + +// โŒ InvalidExtGlobFilterMissingSeparator + +// InvalidExtGlobFilterMissingSeparatorTemplData +type InvalidExtGlobFilterMissingSeparatorErrorTemplData struct { + traverseTemplData +} + +// Message +func (td InvalidExtGlobFilterMissingSeparatorErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "invalid-extended-glob-filter-missing-separator.error", + Description: "invalid extended glob filter definition; pattern is missing separator", + Other: "invalid extended glob filter definition; pattern is missing separator", + } +} + +type InvalidExtGlobFilterMissingSeparatorError struct { + li18ngo.LocalisableError +} + +// IsInvalidExtGlobFilterMissingSeparatorError uses errors.Is to check +// if the err's error tree contains the core error: +// InvalidExtGlobFilterMissingSeparatorError +func IsInvalidExtGlobFilterMissingSeparatorError(err error) bool { + return errors.Is(err, errInvalidExtGlobFilterMissingSeparator) +} + +func NewInvalidExtGlobFilterMissingSeparatorError(pattern string) error { + return errors.Wrap( + errInvalidExtGlobFilterMissingSeparator, + li18ngo.Text(PatternFieldTemplData{ + Pattern: pattern, + }), + ) +} + +var errInvalidExtGlobFilterMissingSeparator = InvalidExtGlobFilterMissingSeparatorError{ + LocalisableError: li18ngo.LocalisableError{ + Data: InvalidExtGlobFilterMissingSeparatorErrorTemplData{}, + }, +} + +// โŒ UsageMissingRootPath + +// UsageMissingRootPathTemplData +type UsageMissingRootPathErrorTemplData struct { + traverseTemplData +} + +// Message +func (td UsageMissingRootPathErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "usage-missing-root-path.error", + Description: "usage missing root path", + Other: "usage missing root path", + } +} + +type UsageMissingRootPathError struct { + li18ngo.LocalisableError +} + +var ErrUsageMissingRootPath = UsageMissingRootPathError{ + LocalisableError: li18ngo.LocalisableError{ + Data: UsageMissingRootPathErrorTemplData{}, + }, +} + +// โŒ UsageMissingRestorePath + +// UsageMissingRestorePathTemplData +type UsageMissingRestorePathErrorTemplData struct { + traverseTemplData +} + +// Message +func (td UsageMissingRestorePathErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "usage-missing-restore-path.error", + Description: "usage missing restore path", + Other: "usage missing restore path", + } +} + +type UsageMissingRestorePathError struct { + li18ngo.LocalisableError +} + +var ErrUsageMissingRestorePath = UsageMissingRestorePathError{ + LocalisableError: li18ngo.LocalisableError{ + Data: UsageMissingRestorePathErrorTemplData{}, + }, +} + +// โŒ UsageMissingSubscription + +// UsageMissingSubscriptionTemplData +type UsageMissingSubscriptionErrorTemplData struct { + traverseTemplData +} + +// Message +func (td UsageMissingSubscriptionErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "usage-missing-subscription.error", + Description: "usage missing subscription", + Other: "usage missing subscription", + } +} + +type UsageMissingSubscriptionError struct { + li18ngo.LocalisableError +} + +var ErrUsageMissingSubscription = UsageMissingSubscriptionError{ + LocalisableError: li18ngo.LocalisableError{ + Data: UsageMissingSubscriptionErrorTemplData{}, + }, +} + +// โŒ UsageMissingHandler + +// UsageMissingHandlerTemplData +type UsageMissingHandlerErrorTemplData struct { + traverseTemplData +} + +// Message +func (td UsageMissingHandlerErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "usage-missing-handler.error", + Description: "usage missing handler", + Other: "usage missing handler", + } +} + +type UsageMissingHandlerError struct { + li18ngo.LocalisableError +} + +var ErrUsageMissingHandler = UsageMissingHandlerError{ + LocalisableError: li18ngo.LocalisableError{ + Data: UsageMissingHandlerErrorTemplData{}, + }, +} + +// โŒ IDGeneratorFuncCantBeNil + +// IDGeneratorFuncCantBeNilTemplData +type IDGeneratorFuncCantBeNilErrorTemplData struct { + traverseTemplData +} + +// Message +func (td IDGeneratorFuncCantBeNilErrorTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "id-generator-func-cant-be-nil.error", + Description: "id generator func is nil, should be defined", + Other: "id generator func can't be nil", + } +} + +type IDGeneratorFuncCantBeNilError struct { + li18ngo.LocalisableError +} + +var ErrIDGeneratorFuncCantBeNil = IDGeneratorFuncCantBeNilError{ + LocalisableError: li18ngo.LocalisableError{ + Data: IDGeneratorFuncCantBeNilErrorTemplData{}, + }, +} + // โŒ FooBar // FooBarTemplData - TODO: this is a none existent error that should be // replaced by the client. Its just defined here to illustrate the pattern -// that should be used to implement i18n with extendio. Also note, +// that should be used to implement i18n with li18ngo. Also note, // that this message has been removed from the translation files, so // it is not useable at run time. type FooBarTemplData struct { @@ -34,7 +506,7 @@ type FooBarErrorBehaviourQuery interface { } type FooBarError struct { - i18n.LocalisableError + li18ngo.LocalisableError } // FooBar enables the client to check if error is FooBarError @@ -46,7 +518,7 @@ func (e FooBarError) FooBar() bool { // NewFooBarError creates a FooBarError func NewFooBarError(path string, reason error) FooBarError { return FooBarError{ - LocalisableError: i18n.LocalisableError{ + LocalisableError: li18ngo.LocalisableError{ Data: FooBarTemplData{ Path: path, Reason: reason, diff --git a/locale/messages-errors_test.go b/locale/messages-errors_test.go new file mode 100644 index 0000000..4ed94c4 --- /dev/null +++ b/locale/messages-errors_test.go @@ -0,0 +1,117 @@ +package locale_test + +import ( + "errors" + "fmt" + "os" + + . "github.com/onsi/ginkgo/v2" //nolint:revive // ok + . "github.com/onsi/gomega" //nolint:revive // ok + + "github.com/snivilised/li18ngo" + "github.com/snivilised/traverse/internal/helpers" + "github.com/snivilised/traverse/locale" +) + +var _ = Describe("error messages", Ordered, func() { + var ( + repo string + l10nPath string + testTranslationFile li18ngo.TranslationFiles + ) + + BeforeAll(func() { + repo = helpers.Repo("") + l10nPath = helpers.Path(repo, "test/data/l10n") + + _, err := os.Stat(l10nPath) + Expect(err).To(Succeed(), + fmt.Sprintf("l10n '%v' path does not exist", l10nPath), + ) + + testTranslationFile = li18ngo.TranslationFiles{ + locale.SourceID: li18ngo.TranslationSource{Name: "foo"}, + } + }) + + BeforeEach(func() { + if err := li18ngo.Use(func(o *li18ngo.UseOptions) { + o.Tag = li18ngo.DefaultLanguage + o.From.Sources = testTranslationFile + }); err != nil { + Fail(err.Error()) + } + }) + + Context("InvalidExtGlobFilterMissingSeparator error", func() { + When("variant error created", func() { + It("should: render translated content", func() { + const ( + expected = "pattern: foo: invalid extended glob filter definition; pattern is missing separator" + ) + text := locale.NewInvalidExtGlobFilterMissingSeparatorError( + "foo", + ).Error() + Expect(text).To(Equal(expected)) + }) + }) + + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := locale.NewInvalidExtGlobFilterMissingSeparatorError("bar") + Expect( + locale.IsInvalidExtGlobFilterMissingSeparatorError(err), + ).To(BeTrue(), + "error does not match InvalidExtGlobFilterMissingSeparator", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + locale.IsInvalidExtGlobFilterMissingSeparatorError(err), + ).To(BeFalse(), + "not matching error should not match InvalidExtGlobFilterMissingSeparator", + ) + }) + }) + }) + + Context("InvalidIncaseFilterDef error", func() { + When("variant error created", func() { + It("should: render translated content", func() { + const ( + expected = "pattern: foo: invalid incase filter definition; pattern is missing separator" + ) + text := locale.NewInvalidIncaseFilterDefError( + "foo", + ).Error() + Expect(text).To(Equal(expected)) + }) + }) + + When("given: matching error", func() { + It("๐Ÿงช should: affirm", func() { + err := locale.NewInvalidIncaseFilterDefError("bar") + Expect( + locale.IsInvalidIncaseFilterDefError(err), + ).To(BeTrue(), + "error does not match InvalidIncaseFilterDef", + ) + }) + }) + + When("given: non matching error", func() { + It("๐Ÿงช should: reject", func() { + err := errors.New("fake") + Expect( + locale.IsInvalidIncaseFilterDefError(err), + ).To(BeFalse(), + "not matching error should not match InvalidIncaseFilterDef", + ) + }) + }) + }) +}) diff --git a/locale/messages-general.go b/locale/messages-general.go index e8c5522..382d13b 100644 --- a/locale/messages-general.go +++ b/locale/messages-general.go @@ -1,7 +1,7 @@ package locale import ( - "github.com/snivilised/extendio/i18n" + "github.com/nicksnyder/go-i18n/v2/i18n" ) type UsingConfigFileTemplData struct { diff --git a/locale/out/active.en-GB.json b/locale/out/active.en-GB.json deleted file mode 100644 index bb88f60..0000000 --- a/locale/out/active.en-GB.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "root-command-config-file-usage": { - "description": "root command config flag usage", - "other": "config file (default is $HOME/{{.ConfigFileName}}.yml)" - }, - "root-command-language-usage": { - "description": "root command lang usage", - "other": "'lang' defines the language according to IETF BCP 47" - }, - "root-command-long-description": { - "description": "long description for the root command", - "other": "A longer description that spans multiple lines and likely contains\n\t\texamples and usage of using your application. For example:\n\t\t\n\t\tCobra is a CLI library for Go that empowers applications.\n\t\tThis application is a tool to generate the needed files\n\t\tto quickly create a Cobra application." - }, - "root-command-short-description": { - "description": "short description for the root command", - "other": "A brief description of your application" - }, - "using-config-file": { - "description": "Message to indicate which config is being used", - "other": "Using config file: {{.ConfigFileName}}" - } -} \ No newline at end of file diff --git a/locale/out/active.en-US.json b/locale/out/active.en-US.json deleted file mode 100644 index 0967ef4..0000000 --- a/locale/out/active.en-US.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/locale/out/en-US/traverse.translate.en-US.json b/locale/out/en-US/traverse.translate.en-US.json deleted file mode 100644 index e69de29..0000000 diff --git a/locale/out/translate.en-US.json b/locale/out/translate.en-US.json deleted file mode 100644 index 1bdc419..0000000 --- a/locale/out/translate.en-US.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "root-command-config-file-usage": { - "description": "root command config flag usage", - "hash": "sha1-60ec091d99ab2286a4bb4a1e1733c155bca95a9f", - "other": "config file (default is $HOME/{{.ConfigFileName}}.yml)" - }, - "root-command-language-usage": { - "description": "root command lang usage", - "hash": "sha1-13eee706e037f33844c76f1dcdac5786b4089a00", - "other": "'lang' defines the language according to IETF BCP 47" - }, - "root-command-long-description": { - "description": "long description for the root command", - "hash": "sha1-9122dfb1f95d2d00501ca67449df1dcf695f2cc2", - "other": "A longer description that spans multiple lines and likely contains\n\t\texamples and usage of using your application. For example:\n\t\t\n\t\tCobra is a CLI library for Go that empowers applications.\n\t\tThis application is a tool to generate the needed files\n\t\tto quickly create a Cobra application." - }, - "root-command-short-description": { - "description": "short description for the root command", - "hash": "sha1-a88cc9526d5e47f670ce9b089390e65415ab26cc", - "other": "A brief description of your application" - }, - "using-config-file": { - "description": "Message to indicate which config is being used", - "hash": "sha1-06fd9528f8226d1501ea09bc5d629f4922b4f3c6", - "other": "Using config file: {{.ConfigFileName}}" - } -} \ No newline at end of file diff --git a/locale/pending-errors.go b/locale/pending-errors.go deleted file mode 100644 index 4789452..0000000 --- a/locale/pending-errors.go +++ /dev/null @@ -1,17 +0,0 @@ -package locale - -import "errors" - -// these errors are to be converted into proper i18n errors - -var ( - ErrFilterIsNil = errors.New("filter is nil") - ErrFilterMissingType = errors.New("filter missing type") - ErrFilterCustomNotSupported = errors.New("custom filter not supported for children") - ErrFilterUndefined = errors.New("filter is undefined") - ErrInternalFailedToGetNavigatorDriver = errors.New("failed to get navigator driver (internal)") - ErrInvalidIncaseFilterDef = errors.New("invalid incase filter definition; pattern is missing separator") - ErrWorkerPoolCreationFailed = errors.New("failed to create worker pool") - ErrInvalidFileSamplingSpecification = errors.New("invalid file sampling specification, missing no of files") - ErrInvalidFolderSamplingSpecification = errors.New("invalid folder sampling specification, missing no of folders") -) diff --git a/measure/measure-defs.go b/measure/measure-defs.go index 3b96ef8..6dd8fd2 100644 --- a/measure/measure-defs.go +++ b/measure/measure-defs.go @@ -4,7 +4,7 @@ import ( "github.com/snivilised/traverse/enums" ) -// ๐Ÿ“š package: measure package defines facilities for counting things +// ๐Ÿ“ฆ pkg: measure - defines facilities for counting things // represented by metrics. type ( diff --git a/nfs/ensure-path-at.go b/nfs/ensure-path-at.go index 7226626..00605b3 100644 --- a/nfs/ensure-path-at.go +++ b/nfs/ensure-path-at.go @@ -22,7 +22,7 @@ import ( // If vfs is not provided, then the path is ensured directly on the native file // system. -func EnsurePathAt(path, defaultFilename string, perm int, +func EnsurePathAt(path, defaultFilename string, perm os.FileMode, vfs ...MkDirAllFS, ) (at string, err error) { var ( @@ -38,10 +38,10 @@ func EnsurePathAt(path, defaultFilename string, perm int, if len(vfs) > 0 { if !vfs[0].DirectoryExists(directory) { - err = vfs[0].MkDirAll(directory, os.FileMode(perm)) + err = vfs[0].MkDirAll(directory, perm) } } else { - err = os.MkdirAll(directory, os.FileMode(perm)) + err = os.MkdirAll(directory, perm) } return filepath.Clean(filepath.Join(directory, file)), err diff --git a/nfs/nfs-defs.go b/nfs/nfs-defs.go index d9b8dd3..d231ac7 100644 --- a/nfs/nfs-defs.go +++ b/nfs/nfs-defs.go @@ -5,7 +5,7 @@ import ( "os" ) -// ๐Ÿ“š package: nfs contains file system abstractions for navigation. Since +// ๐Ÿ“ฆ pkg: nfs - contains file system abstractions for navigation. Since // there are no standard write-able file system interfaces, // we need to define proprietary ones here in this package. // This is a low level package that should not use anything else in diff --git a/pref/errors.go b/pref/errors.go deleted file mode 100644 index a2e2911..0000000 --- a/pref/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package pref - -import ( - "fmt" -) - -type UsageError struct { - message string -} - -func (e UsageError) Error() string { - // TODO: i18n - return fmt.Sprintf("usage error: %v", e.message) -} diff --git a/pref/options.go b/pref/options.go index 432d1b0..189a611 100644 --- a/pref/options.go +++ b/pref/options.go @@ -11,7 +11,8 @@ import ( "github.com/snivilised/traverse/tapable" ) -// ๐Ÿ“š package: pref contains user option definitions; do not use anything in kernel (cyclic) +// ๐Ÿ“ฆ pkg: pref - contains user option definitions; do not use anything +// in kernel (cyclic). const ( badge = "badge: option-requester" diff --git a/pref/using.go b/pref/using.go index d780048..56346d9 100644 --- a/pref/using.go +++ b/pref/using.go @@ -5,6 +5,7 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" + "github.com/snivilised/traverse/locale" ) // Using contains essential properties required for a traversal. If @@ -45,9 +46,7 @@ type Using struct { // Validate checks that the properties on Using are all valid. func (u Using) Validate() error { if u.Root == "" { - return UsageError{ - message: "missing root path", - } + return locale.ErrUsageMissingRootPath } return validate(&u) @@ -70,15 +69,11 @@ type Was struct { // Validate checks that the properties on Using and Was are all valid. func (a Was) Validate() error { //nolint:gocritic // heavy, so what, low frequency if a.From == "" { - return UsageError{ - message: "missing restore from path", - } + return locale.ErrUsageMissingRestorePath } if a.Strategy == enums.ResumeStrategyUndefined { - return UsageError{ - message: "missing subscription", - } + return locale.ErrUsageMissingSubscription } return validate(&a.Using) @@ -86,15 +81,11 @@ func (a Was) Validate() error { //nolint:gocritic // heavy, so what, low frequen func validate(using *Using) error { if using.Subscription == enums.SubscribeUndefined { - return UsageError{ - message: "missing subscription", - } + return locale.ErrUsageMissingSubscription } if using.Handler == nil { - return UsageError{ - message: "missing handler", - } + return locale.ErrUsageMissingHandler } return nil diff --git a/synchronise.go b/synchronise.go index d93b9e1..91cc205 100644 --- a/synchronise.go +++ b/synchronise.go @@ -93,8 +93,7 @@ func (c *concurrent) Navigate(ctx context.Context) (core.TraverseResult, error) ) if c.err != nil { - err := errors.Wrapf(c.err, locale.ErrWorkerPoolCreationFailed.Error()) - + err := errors.Wrap(c.err, locale.ErrWorkerPoolCreationFailed.Error()) return c.kc.Result(ctx, err), err } c.open(ctx) diff --git a/tapable/tapable-defs.go b/tapable/tapable-defs.go index 652a5a9..3204a0b 100644 --- a/tapable/tapable-defs.go +++ b/tapable/tapable-defs.go @@ -1,6 +1,6 @@ package tapable -// ๐Ÿ“š package: tapable enables entities to expose hooks +// ๐Ÿ“ฆ pkg: tapable - enables entities to expose hooks type ( // Invokable diff --git a/traverse-api.go b/traverse-api.go index 1a1d209..01b49fe 100644 --- a/traverse-api.go +++ b/traverse-api.go @@ -10,12 +10,12 @@ import ( "github.com/snivilised/traverse/pref" ) -// ๐Ÿ“š package: traverse is the front line user facing interface to this module. It sits +// ๐Ÿ“ฆ pkg: traverse - is the front line user facing interface to this module. It sits // on the top of the code stack and is allowed to use anything, but nothing // else can depend on definitions here, except unit tests. -// Director type ( + // Director Director interface { // Extent represents the magnitude of the traversal; ie we can // perform a full Prime run, or Resume from a previously