├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .golangci.yaml
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── application.go
├── component.go
├── config.go
├── dependency.go
├── docs
├── .gitignore
├── Dockerfile
├── README.md
├── build.sh
├── docker-compose.yml
├── mkdocs.yml
├── mkdocs
│ ├── hooks
│ │ ├── navigation.py
│ │ └── social.py
│ └── theme
│ │ ├── assets
│ │ ├── images
│ │ │ ├── favicon.ico
│ │ │ ├── gopher-eyes.webp
│ │ │ ├── logo.png
│ │ │ └── social.png
│ │ └── stylesheets
│ │ │ ├── extra.css
│ │ │ └── home.css
│ │ ├── home.html
│ │ └── partials
│ │ ├── copyright.html
│ │ └── meta_social_tags.html
├── pages
│ ├── assets
│ │ └── images
│ │ │ ├── diagrams
│ │ │ └── componego-flow.svg
│ │ │ └── warnings
│ │ │ └── goroutine-leak.png
│ ├── contribution
│ │ └── guide.md
│ ├── get-started.md
│ ├── impl
│ │ ├── application.md
│ │ ├── component.md
│ │ ├── config.md
│ │ ├── dependency.md
│ │ ├── driver.md
│ │ ├── environment.md
│ │ ├── processor.md
│ │ └── runner.md
│ ├── index.md
│ ├── tests
│ │ ├── mock.md
│ │ └── runner.md
│ └── warnings
│ │ └── goroutine-leak.md
├── poetry.lock
└── pyproject.toml
├── environment.go
├── examples
├── Dockerfile
├── README.md
├── docker-compose.yml
├── hello-app
│ ├── cmd
│ │ └── application
│ │ │ ├── dev
│ │ │ └── main.go
│ │ │ └── main.go
│ ├── internal
│ │ └── application
│ │ │ └── application.go
│ └── tests
│ │ ├── basic_test.go
│ │ └── mocks
│ │ └── application.go
├── urfave-cli-integration
│ └── README.md
└── url-shortener-app
│ ├── .gitignore
│ ├── cmd
│ └── application
│ │ ├── dev
│ │ └── main.go
│ │ └── main.go
│ ├── config
│ └── config.json.example
│ ├── internal
│ ├── application
│ │ └── application.go
│ ├── domain
│ │ └── domain.go
│ ├── migration
│ │ └── migration.go
│ ├── repository
│ │ └── repository.go
│ ├── server
│ │ ├── handlers
│ │ │ ├── index.go
│ │ │ └── redirect.go
│ │ ├── json
│ │ │ └── json.go
│ │ └── server.go
│ └── utils
│ │ └── utils.go
│ ├── pkg
│ └── components
│ │ ├── database
│ │ ├── component.go
│ │ ├── examples
│ │ │ └── config
│ │ │ │ └── config.json
│ │ ├── internal
│ │ │ ├── config.go
│ │ │ └── provider.go
│ │ └── tests
│ │ │ ├── database_test.go
│ │ │ └── mocks
│ │ │ └── application.go
│ │ ├── server
│ │ ├── component.go
│ │ ├── examples
│ │ │ └── config
│ │ │ │ └── config.json
│ │ └── internal
│ │ │ ├── config.go
│ │ │ └── server.go
│ │ └── test-server
│ │ ├── component.go
│ │ └── internal
│ │ └── server.go
│ ├── tests
│ ├── integration_test.go
│ └── mocks
│ │ └── application.go
│ └── third_party
│ ├── Readme.md
│ ├── config-reader
│ └── reader.go
│ ├── db-driver
│ ├── driver.go
│ └── queries.go
│ ├── errgroup
│ └── errgroup.go
│ └── servermux
│ └── router.go
├── go.mod
├── go.sum
├── impl
├── application
│ ├── factory.go
│ ├── helpers.go
│ ├── io.go
│ └── tests
│ │ └── helpers_test.go
├── driver
│ ├── driver.go
│ ├── options.go
│ └── tests
│ │ ├── driver.go
│ │ └── driver_test.go
├── environment
│ ├── environment.go
│ ├── managers
│ │ ├── component
│ │ │ ├── factory.go
│ │ │ ├── manager.go
│ │ │ └── tests
│ │ │ │ ├── factory_test.go
│ │ │ │ ├── manager.go
│ │ │ │ └── manager_test.go
│ │ ├── config
│ │ │ ├── helper.go
│ │ │ └── manager.go
│ │ └── dependency
│ │ │ ├── container
│ │ │ ├── container.go
│ │ │ └── tests
│ │ │ │ ├── container.go
│ │ │ │ └── container_test.go
│ │ │ ├── helpers.go
│ │ │ ├── manager.go
│ │ │ └── tests
│ │ │ ├── helper_test.go
│ │ │ ├── manager.go
│ │ │ └── manager_test.go
│ └── tests
│ │ ├── environment.go
│ │ └── environment_test.go
├── processors
│ ├── processor.go
│ ├── tests
│ │ └── types_test.go
│ └── types.go
└── runner
│ ├── runner.go
│ └── unhandled-errors
│ ├── handlers
│ ├── default.go
│ └── vendor-proxy.go
│ └── render.go
├── internal
├── developer
│ ├── message.go
│ └── tests
│ │ └── message_test.go
├── system
│ ├── os.go
│ ├── runtime.go
│ └── tests
│ │ └── runtime_test.go
├── testing
│ ├── logger
│ │ ├── logger.go
│ │ └── tests
│ │ │ └── logger_test.go
│ ├── require
│ │ ├── call.go
│ │ ├── require.go
│ │ └── tests
│ │ │ └── call_test.go
│ ├── testing.go
│ └── types
│ │ └── types.go
└── utils
│ ├── context.go
│ ├── fprint.go
│ ├── reflect.go
│ ├── slice.go
│ └── tests
│ ├── context_test.go
│ ├── fprint_test.go
│ ├── reflect_test.go
│ └── slice_test.go
├── libs
├── color
│ ├── color.go
│ └── theme.go
├── debug
│ ├── stack.go
│ ├── tests
│ │ └── variable_test.go
│ └── variable.go
├── ordered-map
│ ├── map.go
│ └── tests
│ │ ├── map.go
│ │ └── map_test.go
├── type-cast
│ ├── bool.go
│ ├── float.go
│ ├── int.go
│ ├── string.go
│ └── tests
│ │ ├── bool_test.go
│ │ ├── float_test.go
│ │ ├── int_test.go
│ │ └── string_test.go
├── vendor-proxy
│ ├── proxy.go
│ └── tests
│ │ ├── proxy.go
│ │ └── proxy_test.go
└── xerrors
│ ├── option.go
│ ├── tests
│ ├── unwrap_test.go
│ ├── xerrors.go
│ └── xerrors_test.go
│ ├── unwrap.go
│ └── xerrors.go
├── processor.go
├── scripts
├── Dockerfile
└── make.py
├── tests
└── runner
│ ├── runner.go
│ └── tests
│ └── runner_test.go
└── tools
├── create-basic-app.sh
└── create-contributor-env.sh
/.dockerignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vs-code/
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [{*.yml,*.yaml}]
12 | indent_size = 2
13 |
14 | [{Makefile,go.mod,go.sum}]
15 | indent_style = tab
16 |
17 | [LICENSE]
18 | insert_final_newline = false
19 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | *.bat text eol=crlf
3 | *.cmd text eol=crlf
4 | *.ahk text eol=crlf
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .idea
3 | **/.idea
4 | .vs-code
5 | **/.vs-code
6 |
7 | # MacOS files
8 | .DS_STORE
9 | **/.DS_Store
10 |
11 | # Binaries for programs and plugins
12 | *.exe
13 | *.exe~
14 | *.dll
15 | *.so
16 | *.dylib
17 |
18 | # Test binary, built with `go test -c`
19 | *.test
20 |
21 | # Output of the go coverage tool, specifically when used with LiteIDE
22 | *.out
23 |
24 | # Dependency directories
25 | vendor/
26 |
27 | # Python environment
28 | venv/
29 | __pycache__
30 | **/__pycache__
31 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | run:
2 | timeout: 5m
3 | linters:
4 | enable:
5 | - asciicheck
6 | - copyloopvar
7 | - depguard
8 | - dogsled
9 | - durationcheck
10 | - errcheck
11 | - errorlint
12 | - gci
13 | - gofmt
14 | - goimports
15 | - gosec
16 | - gosimple
17 | - misspell
18 | - nakedret
19 | - nilerr
20 | - nolintlint
21 | - revive
22 | - staticcheck
23 | - unparam
24 | - unused
25 | - wastedassign
26 | issues:
27 | exclude-rules:
28 | - linters:
29 | - revive
30 | text: 'var-naming:'
31 | - linters:
32 | - goimports
33 | text: File is not `goimports`-ed
34 | linters-settings:
35 | depguard:
36 | rules:
37 | main:
38 | allow:
39 | - $gostd
40 | - github.com/unpleasantcam/componego
41 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: 'https://github.com/pre-commit/pre-commit-hooks'
3 | rev: v4.6.0
4 | hooks:
5 | - id: end-of-file-fixer
6 | - id: trailing-whitespace
7 | - id: check-added-large-files
8 | args:
9 | - '--maxkb=200'
10 | - repo: local
11 | hooks:
12 | - id: componego-framework-development
13 | name: Componego Framework Development Hook
14 | entry: 'python ./scripts/make.py commit:hook'
15 | language: python
16 | pass_filenames: false
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.2.0 (September 26, 2024)
2 |
3 | * Improved documentation and testing.
4 | * Fixed some bugs.
5 |
6 | ## 0.1.0 (July 27, 2024)
7 |
8 | * All interfaces are stable now and will no longer be changed.
9 | * Improved the graceful shutdown.
10 | * Fixed dependency closing order.
11 | * Increased GitHub Action speed.
12 |
13 | ## 0.0.3 (July 8, 2024)
14 |
15 | * The graceful shutdown component was replaced with various application launch functions.
16 | * Added the ability to conveniently close resources in dependencies.
17 | * Improved the xerrors package and added error codes.
18 | * Added strict code formatting.
19 |
20 | ## 0.0.2 (June 30, 2024)
21 |
22 | * Increased test coverage.
23 | * Improved dependency injection stability.
24 | * Improved several interfaces for flexibility.
25 | * Added linking of application skeleton creation to the last git tag.
26 |
27 | ## 0.0.1 (June 1, 2024)
28 |
29 | * Initial release.
30 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | fmt:
2 | python ./scripts/make.py fmt
3 |
4 | tests:
5 | python ./scripts/make.py tests
6 |
7 | tests-cover:
8 | python ./scripts/make.py tests:cover
9 |
10 | lint:
11 | python ./scripts/make.py lint
12 |
13 | security:
14 | python ./scripts/make.py security
15 |
16 | generate:
17 | python ./scripts/make.py generate
18 |
19 | all: fmt tests tests-cover lint security generate
20 |
21 | .NOTPARALLEL:
22 | .PHONY: all fmt tests tests-cover lint security generate
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ComponeGo Framework
2 |
3 | [](https://goreportcard.com/report/github.com/unpleasantcam/componego)
4 | [](https://github.com/unpleasantcam/componego/actions/workflows/tests.yml)
5 | [](https://codecov.io/gh/componego/componego)
6 | [](https://pkg.go.dev/github.com/unpleasantcam/componego)
7 | [](https://github.com/avelino/awesome-go)
8 |
9 | 
10 |
11 | It is a framework for building applications based on components. These components can be used in multiple applications and are interchangeable.
12 | This framework is used solely to initialize the application and does NOT affect the main loop of your application.
13 | You can still use your favorite frameworks and libraries. We allow you to wrap them in components.
14 |
15 | Components may depend on other components. They can be expanded or reduced based on your requirements.
16 | Components are not microservices; they are folders that contain different functionalities.
17 |
18 | The framework has very low coupling within its code. All entities are optional.
19 |
20 | We provide the ability to use dependency injection, configuration, and error handling.
21 | However, one of the framework's main features is that you can modify entities without changing the application code.
22 | This allows you to create mocks for any part of your application without changing the code.
23 |
24 | If your application is divided into components (modules), it further separates your code into different services and allows you to reuse it in other applications.
25 | Of course, you don’t need to make components too small.
26 |
27 | ### Documentation
28 |
29 | Introduction: [medium.com/@konstanchuk/25bfd16a97a9](https://medium.com/@konstanchuk/25bfd16a97a9).
30 |
31 | Visit our website to learn more: [componego.github.io](https://componego.github.io/).
32 |
33 | The documentation is up-to-date with the latest version of the framework.
34 | Please update your version to [the latest](https://github.com/unpleasantcam/componego/releases).
35 |
36 | ### Examples
37 |
38 | You can find some examples [here](./examples).
39 |
40 | A typical application of this framework looks like [this](./examples/url-shortener-app/internal/application/application.go).
41 |
42 | ### Skeleton
43 |
44 | You can quickly create a basic application in several ways:
45 | ```shell
46 | curl -sSL https://raw.githubusercontent.com/componego/componego/master/tools/create-basic-app.sh | sh
47 | ```
48 | or
49 | ```shell
50 | wget -O - https://raw.githubusercontent.com/componego/componego/master/tools/create-basic-app.sh | sh
51 | ```
52 | On Windows, you can run the commands above with Git Bash, which comes with [Git for Windows](https://git-scm.com/download/win).
53 |
54 | ### Contributing
55 |
56 | We are open to improvements and suggestions. Pull requests are welcome.
57 |
58 | ### License
59 |
60 | The source code of the repository is licensed under the [Apache 2.0 license](./LICENSE).
61 | The core of the framework does not depend on other packages.
62 |
--------------------------------------------------------------------------------
/component.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package componego
18 |
19 | // Component is an interface that describes the component.
20 | type Component interface {
21 | // ComponentIdentifier returns the component ID.
22 | // If the identifier in several components is the same, then the last component will be used.
23 | // This can be used to overwrite components.
24 | ComponentIdentifier() string
25 | // ComponentVersion returns the component version.
26 | ComponentVersion() string
27 | }
28 |
29 | // ComponentComponents is an interface that describes which components the current component depends on.
30 | type ComponentComponents interface {
31 | // Component belongs to the component.
32 | Component
33 | // ComponentComponents returns list of components that the component depends on.
34 | // They will be sorted based on the dependent components.
35 | ComponentComponents() ([]Component, error)
36 | }
37 |
38 | // ComponentDependencies is an interface that describes the dependencies of the component.
39 | type ComponentDependencies interface {
40 | // Component belongs to the component.
41 | Component
42 | // ComponentDependencies returns a list of dependencies that the component provides
43 | // This function must return an array of objects or functions that returns objects.
44 | ComponentDependencies() ([]Dependency, error)
45 | }
46 |
47 | // ComponentInit is an interface that describes the component initialization.
48 | type ComponentInit interface {
49 | // Component belongs to the component.
50 | Component
51 | // ComponentInit is called during component initialization.
52 | ComponentInit(env Environment) error
53 | }
54 |
55 | // ComponentStop is an interface that describes stopping the component.
56 | type ComponentStop interface {
57 | // Component belongs to the component.
58 | Component
59 | // ComponentStop is called when the component stops.
60 | // You can handle previous error (return a new or old error).
61 | ComponentStop(env Environment, prevErr error) error
62 | }
63 |
64 | // ComponentProvider is an interface that describes a list of active application components.
65 | // These components are sorted in order of dependencies between components.
66 | type ComponentProvider interface {
67 | // Components returns a list of sorted application components.
68 | Components() []Component
69 | }
70 |
--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package componego
18 |
19 | // ConfigProvider is an interface that describes configuration getter.
20 | type ConfigProvider interface {
21 | // ConfigValue returns the configuration by key.
22 | // The value can be modified by the Processor which validates or converts the value.
23 | ConfigValue(configKey string, processor Processor) (any, error)
24 | }
25 |
--------------------------------------------------------------------------------
/dependency.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package componego
18 |
19 | type Dependency any
20 |
21 | // DependencyInvoker is an interface that describes the functions that invoke dependencies for the application.
22 | type DependencyInvoker interface {
23 | // Invoke uses dependencies.
24 | // You can only pass a function as an argument.
25 | // If the passed function returns an error, then it will be returned.
26 | Invoke(function any) (any, error)
27 | // Populate is similar to Invoke, but it can only take objects that are a reference.
28 | // The passed object will be filled with dependencies.
29 | Populate(target any) error
30 | // PopulateFields fills the struct fields passed as an argument with dependencies.
31 | // Fields must have a special tag: `componego:"inject"`.
32 | // Fields without this tag are ignored.
33 | PopulateFields(target any) error
34 | }
35 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | .cache/
2 | build/
3 |
--------------------------------------------------------------------------------
/docs/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.10.13-slim
2 |
3 | WORKDIR /docs
4 |
5 | # mkdocs mkdocs-material mkdocs-minify-plugin mkdocs-material-extensions beautifulsoup4
6 | COPY pyproject.toml poetry.lock ./
7 |
8 | RUN pip install poetry==1.7.0 && poetry install --no-root --no-directory
9 |
10 | COPY . .
11 | RUN poetry install
12 |
13 | EXPOSE 8123
14 | ENTRYPOINT ["poetry", "run", "mkdocs", "serve", "--dev-addr=0.0.0.0:8123"]
15 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | Please visit the [website](https://componego.github.io) to see the documentation, or run the documentation locally using the command: '**docker-compose up componego-framework-docs**'.
2 |
3 | You can also use previous commits to run a local website with documentation for earlier versions of the framework.
4 |
--------------------------------------------------------------------------------
/docs/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -o errexit
4 | set -o nounset
5 |
6 | cd "$(dirname "$(realpath -- "$0")")";
7 |
8 | docker-compose run --entrypoint "poetry run mkdocs build --clean --site-dir=/docs/build" componego-framework-docs
9 |
10 | cp ../LICENSE ./build/LICENSE
11 | if [ -e "../NOTICE" ]; then
12 | cp ../NOTICE ./build/NOTICE
13 | fi
14 |
15 | cat > build/README.md << EOF
16 | Website available [here](https://componego.github.io/).
17 |
18 | ---
19 |
20 | These files are auto-generated files from the [main repository](https://github.com/unpleasantcam/componego)
21 | so you don't have to commit changes directly to this repository.
22 |
23 | The [license](./LICENSE) of this repository matches the license of the parent repository.
24 | EOF
25 |
26 | cat > build/.gitignore << EOF
27 | .idea
28 | **/.idea
29 | .vs-code
30 | **/.vs-code
31 | EOF
32 |
--------------------------------------------------------------------------------
/docs/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | services:
4 | componego-framework-docs:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | container_name: componego-framework-docs
9 | working_dir: /docs
10 | volumes:
11 | - .:/docs:cached
12 | ports:
13 | - "8123:8123"
14 |
--------------------------------------------------------------------------------
/docs/mkdocs/hooks/navigation.py:
--------------------------------------------------------------------------------
1 | """ Adds additional logic to improve navigation. """
2 |
3 | from mkdocs.plugins import event_priority
4 | from mkdocs.structure.pages import Page
5 | from mkdocs.config.defaults import MkDocsConfig
6 | from bs4 import BeautifulSoup
7 |
8 |
9 | @event_priority(50)
10 | def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
11 | """
12 | This hook changes the HTML tree by adding or removing some nodes or attributes.
13 | """
14 | parsed_html = BeautifulSoup(html, 'html.parser')
15 | items = parsed_html.select('ul.md-nav__list .md-nav__item--section')
16 | # Menu items "Built-in Components" and "Examples".
17 | for index in [2, 3]:
18 | # The necessary checks for the keys in the list are missing
19 | # because we expect an exception if such a key does not exist.
20 | section = items[index]
21 | section['class'].remove('md-nav__item--section')
22 | section['class'] += ['toggle-color']
23 | for toggle in section.select('.md-toggle--indeterminate'):
24 | toggle['class'].remove('md-toggle--indeterminate')
25 | for link in section.select('a'):
26 | if 'https://github.com' in link.get('href', ''):
27 | link['target'] = '_blank'
28 | for link in parsed_html.select('a[target="_blank"]'):
29 | if link.get('rel', None) is None:
30 | # noinspection SpellCheckingInspection
31 | link['rel'] = 'noopener'
32 | return str(parsed_html)
33 |
--------------------------------------------------------------------------------
/docs/mkdocs/hooks/social.py:
--------------------------------------------------------------------------------
1 | """ Adds social params for each page. """
2 |
3 | from mkdocs.plugins import event_priority
4 | from mkdocs.structure.pages import Page
5 | from mkdocs.config.defaults import MkDocsConfig
6 |
7 |
8 | @event_priority(50)
9 | def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
10 | """
11 | This hook adds social meta tags for each page.
12 | """
13 | try:
14 | title = page.meta['social_meta']['title']
15 | except KeyError:
16 | title = page.title
17 | try:
18 | description = page.meta['social_meta']['description']
19 | except KeyError:
20 | description = config.site_description
21 | social_tags = config.theme.get_env().get_template('partials/meta_social_tags.html').render({
22 | 'title': f'{config.theme["social_title_prefix"]} | {title}',
23 | 'description': description,
24 | 'url': page.canonical_url,
25 | 'image': f'{config["site_url"]}{config.theme["social_image"]}',
26 | })
27 | return html.replace('', f'{social_tags}', 1)
28 |
--------------------------------------------------------------------------------
/docs/mkdocs/theme/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unpleasantcam/componego/ca9559039e475ae00542874c1dc4fe462477df7d/docs/mkdocs/theme/assets/images/favicon.ico
--------------------------------------------------------------------------------
/docs/mkdocs/theme/assets/images/gopher-eyes.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unpleasantcam/componego/ca9559039e475ae00542874c1dc4fe462477df7d/docs/mkdocs/theme/assets/images/gopher-eyes.webp
--------------------------------------------------------------------------------
/docs/mkdocs/theme/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unpleasantcam/componego/ca9559039e475ae00542874c1dc4fe462477df7d/docs/mkdocs/theme/assets/images/logo.png
--------------------------------------------------------------------------------
/docs/mkdocs/theme/assets/images/social.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unpleasantcam/componego/ca9559039e475ae00542874c1dc4fe462477df7d/docs/mkdocs/theme/assets/images/social.png
--------------------------------------------------------------------------------
/docs/mkdocs/theme/assets/stylesheets/extra.css:
--------------------------------------------------------------------------------
1 | [data-md-color-primary=red] {
2 | --md-primary-fg-color: #dd2d58;
3 | }
4 |
5 | [data-md-color-scheme="default"] {
6 | --md-primary-bg-color: #fffdfa;
7 | --md-primary-bg-color--light: var(--md-primary-bg-color);
8 | background-color: var(--md-primary-bg-color);
9 | }
10 |
11 | html .md-footer-meta.md-typeset .site-generator {
12 | font-size: 9px;
13 | color: #44454b;
14 | }
15 |
16 | html .md-footer-meta.md-typeset .site-generator a {
17 | color: inherit;
18 | }
19 |
20 | body .md-content {
21 | counter-reset: h2;
22 | }
23 |
24 | .md-header__button.md-logo {
25 | margin: 0 0 0 0.2rem;
26 | padding: 0 0 0 0.4rem;
27 | }
28 |
29 | .md-header__button.md-logo > img {
30 | height: 1.7rem;
31 | }
32 |
33 | .md-progress {
34 | background-color: var(--md-accent-fg-color);
35 | height: 0.1rem;
36 | }
37 |
38 | .md-content .md-typeset a {
39 | color: var(--md-accent-fg-color);
40 | }
41 |
42 | .md-content .md-typeset a:hover {
43 | text-decoration: underline;
44 | }
45 |
46 | .md-content .md-typeset .componego-grid-cards {
47 | grid-gap: 0.4rem;
48 | display: grid;
49 | grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
50 | margin: 1em 0;
51 | }
52 |
53 | .md-content h2 {
54 | counter-reset: h3;
55 | }
56 |
57 | .md-content h2:before {
58 | counter-increment: h2;
59 | content: counter(h2) ". ";
60 | color: var(--md-primary-fg-color);
61 | font-weight: 700;
62 | }
63 |
64 | .md-content a.headerlink {
65 | text-decoration: none !important;
66 | color: var(--md-primary-fg-color) !important;
67 | }
68 |
69 | .md-content h3:before {
70 | counter-increment: h3;
71 | content: counter(h2) "." counter(h3) ". ";
72 | color: var(--md-primary-fg-color);
73 | font-weight: 700;
74 | }
75 |
76 | .md-typeset .componego-grid-cards .card {
77 | border: 0.05rem solid var(--md-primary-fg-color);
78 | border-radius: 0.1rem;
79 | display: block;
80 | margin: 0;
81 | padding: 0.8rem;
82 | font-size: 0.85rem;
83 | }
84 |
85 | .md-typeset .componego-grid-cards p {
86 | margin: 0;
87 | }
88 |
89 | .md-typeset .componego-grid-cards a {
90 | color: var(--md-primary-fg-color) !important;
91 | font-weight: 700;
92 | }
93 |
94 | .md-typeset .componego-grid-cards a:hover {
95 | text-decoration: none;
96 | }
97 |
98 | .md-typeset .componego-grid-cards .twemoji {
99 | margin-right: 10px;
100 | }
101 |
102 | .md-source .md-source__facts {
103 | display: none;
104 | }
105 |
106 |
107 | .md-content .twemoji.heart {
108 | color: var(--md-primary-fg-color);
109 | }
110 |
111 | .md-content ins {
112 | text-decoration: none;
113 | color: var(--md-primary-fg-color);
114 | }
115 |
116 | .md-content a.social-share-button {
117 | padding: .625em 1em;
118 | margin: 0.5em 0.5em 0.5em 0;
119 | }
120 |
121 | .md-content a.social-share-button:hover {
122 | text-decoration: none;
123 | }
124 |
125 | @media screen and (min-width:76.234375em) {
126 | .md-nav--primary .md-nav__item--nested.toggle-color > label {
127 | color: var(--md-default-fg-color--light);
128 | font-weight: 700;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/docs/mkdocs/theme/home.html:
--------------------------------------------------------------------------------
1 | {% extends "main.html" %}
2 |
3 | {% block tabs %}
4 | {{ super() }}
5 |
6 | {{ config.site_description }}Componego Framework
11 |
36 | import (
37 | _ "github.com/componego/meta-package/pre-init/vendor-proxy/for-app" // if you run the application.
38 | _ "github.com/componego/meta-package/pre-init/vendor-proxy/for-tests" // if you run the tests.
39 | )
40 |
41 | If that doesn't help, then you need to debug the code.
42 | `)
43 | return true
44 | }
45 |
--------------------------------------------------------------------------------
/impl/runner/unhandled-errors/render.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package unhandled_errors
18 |
19 | import (
20 | "fmt"
21 | "io"
22 | "runtime/debug"
23 | "strings"
24 |
25 | "github.com/unpleasantcam/componego"
26 | "github.com/unpleasantcam/componego/impl/runner/unhandled-errors/handlers"
27 | "github.com/unpleasantcam/componego/libs/ordered-map"
28 | )
29 |
30 | type handler = func(err error, writer io.Writer, appMode componego.ApplicationMode) bool
31 |
32 | func GetHandlers() ordered_map.Map[string, handler] {
33 | result := ordered_map.New[string, handler](3)
34 | result.Set("componego:vendor-proxy", handlers.VendorProxyHandler)
35 | return result
36 | }
37 |
38 | func ToString(err error, appMode componego.ApplicationMode, errHandlers ordered_map.Map[string, handler]) (message string) {
39 | defer func() {
40 | if r := recover(); r != nil {
41 | stack := string(debug.Stack())
42 | message = fmt.Sprintf("panic during rendering the original error: %v\n\nstack: %s\n", r, stack)
43 | }
44 | }()
45 | writer := &strings.Builder{}
46 | for _, fn := range errHandlers.Values() {
47 | if fn(err, writer, appMode) {
48 | return writer.String()
49 | }
50 | }
51 | handlers.DefaultHandler(err, writer, appMode)
52 | return writer.String()
53 | }
54 |
--------------------------------------------------------------------------------
/internal/developer/message.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package developer
18 |
19 | import (
20 | "io"
21 |
22 | "github.com/unpleasantcam/componego/internal/utils"
23 | "github.com/unpleasantcam/componego/libs/color"
24 | )
25 |
26 | func Warning(writer io.Writer, args ...any) {
27 | writer = color.NewColoredWriter(writer, color.WhiteColor, color.YellowBackground)
28 | utils.Fprint(writer, "[W] ")
29 | utils.Fprintln(writer, args...)
30 | }
31 |
--------------------------------------------------------------------------------
/internal/developer/tests/message_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "bytes"
21 | "testing"
22 |
23 | "github.com/unpleasantcam/componego/internal/developer"
24 | "github.com/unpleasantcam/componego/internal/testing/require"
25 | )
26 |
27 | func TestWarning(t *testing.T) {
28 | t.Run("basic test", func(t *testing.T) {
29 | buffer := &bytes.Buffer{}
30 | developer.Warning(buffer, "hello")
31 | require.Contains(t, buffer.String(), "hello")
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/internal/system/os.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package system
18 |
19 | import (
20 | "io"
21 | "os"
22 | )
23 |
24 | var (
25 | Stdin io.Reader = os.Stdin
26 | Stdout io.Writer = os.Stdout
27 | Stderr io.Writer = os.Stderr
28 | Exit = os.Exit
29 | )
30 |
--------------------------------------------------------------------------------
/internal/system/runtime.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package system
18 |
19 | import (
20 | "os"
21 | "os/signal"
22 | "runtime"
23 | "syscall"
24 | )
25 |
26 | func NumGoroutineBeforeExit() int {
27 | numGoroutine := runtime.NumGoroutine()
28 | if numGoroutine == 1 {
29 | return numGoroutine
30 | }
31 | // Signals start a goroutine that never stops.
32 | // We make sure that this goroutine does not influence the result.
33 | interruptChan := make(chan os.Signal, 1)
34 | signal.Notify(interruptChan, syscall.SIGTERM)
35 | signal.Stop(interruptChan)
36 | return runtime.NumGoroutine() - 1
37 | }
38 |
--------------------------------------------------------------------------------
/internal/system/tests/runtime_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "sync"
21 | "testing"
22 |
23 | "github.com/unpleasantcam/componego/internal/system"
24 | "github.com/unpleasantcam/componego/internal/testing/require"
25 | )
26 |
27 | func TestNumGoroutineBeforeExit(t *testing.T) {
28 | t.Run("basic test", func(t *testing.T) {
29 | require.True(t, system.NumGoroutineBeforeExit() >= 1)
30 | numGoroutine := 0
31 | wg := sync.WaitGroup{}
32 | wg.Add(1)
33 | go func() {
34 | defer wg.Done()
35 | numGoroutine = system.NumGoroutineBeforeExit()
36 | }()
37 | wg.Wait()
38 | require.True(t, numGoroutine > 1)
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/internal/testing/logger/logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package logger
18 |
19 | import (
20 | "fmt"
21 | "io"
22 | "reflect"
23 | "strings"
24 | )
25 |
26 | // LogData is a function that writes data to the log.
27 | func LogData(writer io.Writer, messages ...any) {
28 | if writer == nil {
29 | return
30 | }
31 | _, err := fmt.Fprintln(writer, messages...)
32 | if err != nil {
33 | panic(err)
34 | }
35 | }
36 |
37 | // ExpectedLogData is a function that returns formatted data.
38 | // You can use it with the LogData function to compare data in tests.
39 | func ExpectedLogData(lines ...any) string {
40 | var result strings.Builder
41 | for _, line := range lines {
42 | if line == nil {
43 | result.WriteString(fmt.Sprintln(line))
44 | continue
45 | }
46 | reflectValue := reflect.ValueOf(line)
47 | if reflectValue.Kind() != reflect.Slice && reflectValue.Kind() != reflect.Array {
48 | result.WriteString(fmt.Sprintln(line))
49 | continue
50 | }
51 | valueLen := reflectValue.Len()
52 | messages := make([]any, valueLen)
53 | for i := 0; i < valueLen; i++ {
54 | messages[i] = reflectValue.Index(i).Interface()
55 | }
56 | result.WriteString(fmt.Sprintln(messages...))
57 | }
58 | return result.String()
59 | }
60 |
--------------------------------------------------------------------------------
/internal/testing/logger/tests/logger_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "bytes"
21 | "fmt"
22 | "testing"
23 |
24 | "github.com/unpleasantcam/componego/internal/testing/logger"
25 | "github.com/unpleasantcam/componego/internal/testing/require"
26 | )
27 |
28 | func TestLogData(t *testing.T) {
29 | t.Run("nil writer", func(t *testing.T) {
30 | require.NotPanics(t, func() {
31 | logger.LogData(nil, "hello")
32 | })
33 | })
34 |
35 | t.Run("single message", func(t *testing.T) {
36 | var buffer bytes.Buffer
37 | logger.LogData(&buffer, "hello")
38 | expected := fmt.Sprintln("hello")
39 | require.Equal(t, expected, buffer.String())
40 | })
41 |
42 | t.Run("multiple messages", func(t *testing.T) {
43 | var buffer bytes.Buffer
44 | logger.LogData(&buffer, "hello", 123, true)
45 | expected := fmt.Sprintln("hello", 123, true)
46 | require.Equal(t, expected, buffer.String())
47 | })
48 |
49 | t.Run("empty messages", func(t *testing.T) {
50 | var buffer bytes.Buffer
51 | logger.LogData(&buffer)
52 | expected := fmt.Sprintln()
53 | require.Equal(t, expected, buffer.String())
54 | })
55 |
56 | t.Run("error on write", func(t *testing.T) {
57 | writer := &errorWriter{}
58 | require.Panics(t, func() {
59 | logger.LogData(writer, "hello")
60 | })
61 | })
62 | }
63 |
64 | func TestExpectedLogData(t *testing.T) {
65 | t.Run("nil value", func(t *testing.T) {
66 | result := logger.ExpectedLogData(nil)
67 | expected := fmt.Sprintln(nil)
68 | require.Equal(t, expected, result)
69 | })
70 |
71 | t.Run("non-slice value", func(t *testing.T) {
72 | result := logger.ExpectedLogData("hello")
73 | expected := fmt.Sprintln("hello")
74 | require.Equal(t, expected, result)
75 | })
76 |
77 | t.Run("slice of strings", func(t *testing.T) {
78 | result := logger.ExpectedLogData([]string{"a", "b", "c"})
79 | expected := fmt.Sprintln([]any{"a", "b", "c"}...)
80 | require.Equal(t, expected, result)
81 | })
82 |
83 | t.Run("array of integers", func(t *testing.T) {
84 | result := logger.ExpectedLogData([...]int{1, 2, 3})
85 | expected := fmt.Sprintln([]any{1, 2, 3}...)
86 | require.Equal(t, expected, result)
87 | })
88 |
89 | t.Run("mixed values", func(t *testing.T) {
90 | result := logger.ExpectedLogData(nil, "hello", []int{1, 2, 3}, [...]string{"x", "y"})
91 | expected := fmt.Sprintln(nil) + fmt.Sprintln("hello") +
92 | fmt.Sprintln([]any{1, 2, 3}...) + fmt.Sprintln([]any{"x", "y"}...)
93 | require.Equal(t, expected, result)
94 | })
95 |
96 | t.Run("empty slice", func(t *testing.T) {
97 | result := logger.ExpectedLogData([]int{})
98 | expected := fmt.Sprintln([]any{}...)
99 | require.Equal(t, expected, result)
100 | })
101 |
102 | t.Run("empty array", func(t *testing.T) {
103 | result := logger.ExpectedLogData([...]int{})
104 | expected := fmt.Sprintln([]any{}...)
105 | require.Equal(t, expected, result)
106 | })
107 | }
108 |
109 | // errorWriter is an io.Writer implementation that always returns an error.
110 | type errorWriter struct{}
111 |
112 | func (e *errorWriter) Write(_ []byte) (int, error) {
113 | return 0, fmt.Errorf("error")
114 | }
115 |
--------------------------------------------------------------------------------
/internal/testing/require/call.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package require
18 |
19 | import (
20 | "errors"
21 |
22 | "github.com/unpleasantcam/componego/internal/testing"
23 | "github.com/unpleasantcam/componego/libs/vendor-proxy"
24 | )
25 |
26 | func call(name string, t testing.T, args ...any) {
27 | if h, ok := t.(testing.THelper); ok {
28 | h.Helper() // Removes this function from the error stack.
29 | }
30 | var err error
31 | require := vendor_proxy.Get("testify/require")
32 | if len(args) == 0 {
33 | _, err = require.CallFunction(name, t)
34 | } else {
35 | newArgs := make([]any, 0, len(args)+1)
36 | newArgs = append(newArgs, t)
37 | newArgs = append(newArgs, args...)
38 | _, err = require.CallFunction(name, newArgs...)
39 | }
40 | if err == nil {
41 | return
42 | }
43 | message := "error: " + err.Error()
44 | if errors.Is(err, vendor_proxy.ErrFunctionNotExists) {
45 | message += messageOfIncorrectRun()
46 | }
47 | t.Errorf(message)
48 | t.FailNow()
49 | }
50 |
51 | func messageOfIncorrectRun() string {
52 | // noinspection SpellCheckingInspection
53 | return `
54 | Make sure you run tests using 'make tests' or 'make tests-cover'.
55 | However, if you are running tests in your application, then you need to add a new imports:
56 |
57 | import (
58 | _ "github.com/componego/meta-package/pre-init/vendor-proxy/for-app"
59 | _ "github.com/componego/meta-package/pre-init/vendor-proxy/for-tests"
60 | )
61 |
62 | `
63 | }
64 |
--------------------------------------------------------------------------------
/internal/testing/require/tests/call_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "testing"
21 |
22 | "github.com/unpleasantcam/componego/internal/testing/require"
23 | )
24 |
25 | func TestCallRequire(t *testing.T) {
26 | flag := true
27 | require.Panics(t, func() {
28 | flag = false
29 | panic("catch me if you can")
30 | })
31 | require.False(t, flag)
32 | if flag {
33 | t.Fatal("flag is true")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/internal/testing/testing.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package testing
18 |
19 | type T interface {
20 | Errorf(format string, args ...any)
21 | FailNow()
22 | Cleanup(fn func())
23 | }
24 |
25 | type THelper interface {
26 | T
27 | Helper()
28 | }
29 |
30 | type TParallel interface {
31 | T
32 | Parallel()
33 | }
34 |
35 | type TRun[I T] interface {
36 | T
37 | Run(name string, fn func(t I)) bool
38 | }
39 |
--------------------------------------------------------------------------------
/internal/testing/types/types.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package types
18 |
19 | // These are the internal types that are needed to test the framework.
20 |
21 | type CustomString string
22 |
23 | type AInterface interface {
24 | Method()
25 | }
26 |
27 | type AStruct struct {
28 | Value int
29 | }
30 |
31 | func (a *AStruct) Method() {}
32 |
33 | type BStruct struct {
34 | AStruct *AStruct
35 | }
36 |
37 | type CStruct struct {
38 | *AStruct `componego:"inject"`
39 | Value int
40 | privateField AInterface `componego:"inject"`
41 | PublicField1 *AStruct `componego:"inject,otherValue"`
42 | PublicField2 *BStruct
43 | IncorrectTag1 *AStruct `componego:"INJECT"`
44 | IncorrectTag2 *AStruct `COMPONEGO:"inject"`
45 | }
46 |
47 | type DStruct struct {
48 | PublicField2 AInterface `componego:"inject"`
49 | PublicField1 *CStruct `componego:"inject"`
50 | }
51 |
52 | func (c *CStruct) GetPrivateField() AInterface {
53 | return c.privateField
54 | }
55 |
56 | var _ AInterface = (*AStruct)(nil)
57 |
--------------------------------------------------------------------------------
/internal/utils/context.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package utils
18 |
19 | import (
20 | "context"
21 | "reflect"
22 | )
23 |
24 | const parentContextField = "Context"
25 |
26 | func init() {
27 | checkContextForUsedGoVersion()
28 | }
29 |
30 | func IsParentContext(parent context.Context, child context.Context) (ok bool) {
31 | if parent == nil || child == nil {
32 | return false
33 | }
34 | for {
35 | if parent == child {
36 | return true
37 | }
38 | reflectValue := reflect.Indirect(reflect.ValueOf(child))
39 | if reflectValue.Kind() != reflect.Struct {
40 | return false
41 | }
42 | contextField := reflectValue.FieldByName(parentContextField)
43 | if !contextField.IsValid() || contextField.IsNil() {
44 | return false
45 | }
46 | child, ok = contextField.Interface().(context.Context)
47 | if !ok {
48 | return false
49 | }
50 | }
51 | }
52 |
53 | func checkContextForUsedGoVersion() {
54 | errMessage := `
55 | It looks like you are using a GoLang version not supported by the framework version installed in your project.
56 | This incompatibility was not detected during project compilation.
57 | ---
58 | You need to update your framework to the latest version.
59 | If this does not solve the problem, please create an issue on GitHub -> https://github.com/unpleasantcam/componego/issues
60 | We will fix this problem as soon as possible.
61 | `
62 | parentCtx := context.Background()
63 | childCtx, cancel := context.WithCancel(parentCtx)
64 | defer cancel()
65 | reflectValue := reflect.Indirect(reflect.ValueOf(childCtx))
66 | if reflectValue.Kind() != reflect.Struct {
67 | panic(errMessage)
68 | }
69 | contextField := reflectValue.FieldByName(parentContextField)
70 | if !contextField.IsValid() {
71 | panic(errMessage)
72 | }
73 | internalCtx, ok := contextField.Interface().(context.Context)
74 | if !ok || internalCtx != parentCtx {
75 | panic(errMessage)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/internal/utils/fprint.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package utils
18 |
19 | import (
20 | "fmt"
21 | "io"
22 | )
23 |
24 | const Indent = " "
25 |
26 | // noinspection SpellCheckingInspection
27 | func Fprint(writer io.Writer, args ...any) {
28 | _, err := fmt.Fprint(writer, args...)
29 | if err != nil {
30 | panic(err)
31 | }
32 | }
33 |
34 | // noinspection SpellCheckingInspection
35 | func Fprintln(writer io.Writer, args ...any) {
36 | // fmt.Fprintln adds unnecessary spaces between arguments.
37 | // This method brings the output to a single format without spaces.
38 | if len(args) > 0 {
39 | Fprint(writer, args...)
40 | }
41 | Fprint(writer, "\n")
42 | }
43 |
44 | func Fprintf(writer io.Writer, format string, args ...any) {
45 | _, err := fmt.Fprintf(writer, format, args...)
46 | if err != nil {
47 | panic(err)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/internal/utils/reflect.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package utils
18 |
19 | import (
20 | "reflect"
21 | )
22 |
23 | var errorType = reflect.TypeOf((*error)(nil)).Elem()
24 |
25 | // IsErrorType returns true if the type is an error.
26 | func IsErrorType(reflectType reflect.Type) bool {
27 | return reflectType.Implements(errorType)
28 | }
29 |
30 | func Indirect(instance any) any {
31 | if instance == nil {
32 | return nil
33 | }
34 | if reflectType := reflect.TypeOf(instance); reflectType.Kind() != reflect.Pointer {
35 | return instance
36 | }
37 | reflectValue := reflect.ValueOf(instance)
38 | for reflectValue.Kind() == reflect.Pointer && !reflectValue.IsNil() {
39 | reflectValue = reflectValue.Elem()
40 | }
41 | return reflectValue.Interface()
42 | }
43 |
44 | func IsEmpty(instance any) bool {
45 | if instance == nil {
46 | return true
47 | }
48 | reflectValue := reflect.ValueOf(instance)
49 | switch reflectValue.Kind() {
50 | case reflect.Chan, reflect.Map, reflect.Slice:
51 | return reflectValue.Len() == 0
52 | case reflect.Pointer:
53 | if reflectValue.IsNil() {
54 | return true
55 | }
56 | return IsEmpty(reflectValue.Elem().Interface())
57 | }
58 | return reflect.DeepEqual(instance, reflect.Zero(reflectValue.Type()).Interface())
59 | }
60 |
--------------------------------------------------------------------------------
/internal/utils/slice.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package utils
18 |
19 | func Keys[K comparable, V any](items map[K]V) []K {
20 | result := make([]K, 0, len(items))
21 | for key := range items {
22 | result = append(result, key)
23 | }
24 | return result
25 | }
26 |
27 | func Values[K comparable, V any](items map[K]V) []V {
28 | result := make([]V, 0, len(items))
29 | for _, item := range items {
30 | result = append(result, item)
31 | }
32 | return result
33 | }
34 |
35 | func Contains[T comparable](items []T, value T) bool {
36 | for _, item := range items {
37 | if value == item {
38 | return true
39 | }
40 | }
41 | return false
42 | }
43 |
44 | func Reverse[T any](items []T) {
45 | for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
46 | items[i], items[j] = items[j], items[i]
47 | }
48 | }
49 |
50 | func Copy[T any](items []T) []T {
51 | result := make([]T, len(items))
52 | copy(result, items)
53 | return result
54 | }
55 |
--------------------------------------------------------------------------------
/internal/utils/tests/context_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "context"
21 | "testing"
22 | "time"
23 |
24 | "github.com/unpleasantcam/componego/internal/utils"
25 | )
26 |
27 | func TestIsParentContext(t *testing.T) {
28 | parentCtx := context.Background()
29 | childCtx1, cancel1 := context.WithCancel(parentCtx)
30 | defer cancel1()
31 | childCtx2, cancel2 := context.WithTimeout(childCtx1, time.Minute*60)
32 | defer cancel2()
33 | childCtx3, cancel3 := context.WithDeadline(childCtx1, time.Now().Add(time.Minute*60))
34 | defer cancel3()
35 | testCases := [...]struct {
36 | a context.Context
37 | b context.Context
38 | status bool
39 | }{
40 | {parentCtx, childCtx1, true}, // 1
41 | {childCtx1, parentCtx, false}, // 2
42 | //
43 | {parentCtx, childCtx2, true}, // 3
44 | {childCtx2, parentCtx, false}, // 4
45 | //
46 | {childCtx1, childCtx2, true}, // 5
47 | {childCtx2, childCtx1, false}, // 6
48 | //
49 | {childCtx1, childCtx3, true}, // 7
50 | {childCtx3, childCtx1, false}, // 8
51 | //
52 | {parentCtx, context.Background(), true}, // 9
53 | {context.Background(), context.Background(), true}, // 10
54 | {context.Background(), parentCtx, true}, // 11
55 | {context.Background(), childCtx1, true}, // 12
56 | {context.Background(), context.TODO(), false}, // 13
57 | {context.TODO(), context.Background(), false}, // 14
58 | }
59 | for i, testCase := range testCases {
60 | if utils.IsParentContext(testCase.a, testCase.b) != testCase.status {
61 | message := "Failed to check parent context (#%d). Make sure the framework is compatible with your version of Go."
62 | t.Fatalf(message, i+1)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/internal/utils/tests/fprint_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "bytes"
21 | "errors"
22 | "fmt"
23 | "io"
24 | "testing"
25 |
26 | "github.com/unpleasantcam/componego/internal/testing/require"
27 | "github.com/unpleasantcam/componego/internal/utils"
28 | )
29 |
30 | // noinspection SpellCheckingInspection
31 | func TestFprint(t *testing.T) {
32 | t.Run("single string", func(t *testing.T) {
33 | var buffer bytes.Buffer
34 | utils.Fprint(&buffer, "hello")
35 | require.Equal(t, "hello", buffer.String())
36 | })
37 |
38 | t.Run("different input types", func(t *testing.T) {
39 | var buffer bytes.Buffer
40 | utils.Fprint(&buffer, "hello", 123, " ", '1')
41 | require.Equal(t, "hello123 49", buffer.String())
42 | })
43 |
44 | t.Run("empty input", func(t *testing.T) {
45 | var buffer bytes.Buffer
46 | utils.Fprint(&buffer)
47 | require.Equal(t, buffer.Len(), 0)
48 | })
49 |
50 | t.Run("error case", func(t *testing.T) {
51 | require.Panics(t, func() {
52 | utils.Fprint(&errorWriter{}, "this will fail")
53 | })
54 | })
55 | }
56 |
57 | // noinspection SpellCheckingInspection
58 | func TestFprintln(t *testing.T) {
59 | t.Run("single string", func(t *testing.T) {
60 | var buffer bytes.Buffer
61 | utils.Fprintln(&buffer, "hello")
62 | require.Equal(t, fmt.Sprintln("hello"), buffer.String())
63 | })
64 |
65 | t.Run("different input types", func(t *testing.T) {
66 | var buffer bytes.Buffer
67 | utils.Fprintln(&buffer, "hello", 123, " ", '1')
68 | require.Equal(t, fmt.Sprintln("hello123 49"), buffer.String())
69 | })
70 |
71 | t.Run("empty input", func(t *testing.T) {
72 | var buffer bytes.Buffer
73 | utils.Fprintln(&buffer)
74 | require.Equal(t, fmt.Sprintln(), buffer.String())
75 | })
76 |
77 | t.Run("error case", func(t *testing.T) {
78 | require.Panics(t, func() {
79 | utils.Fprintln(&errorWriter{}, "this will fail")
80 | })
81 | })
82 | }
83 |
84 | func TestFprintf(t *testing.T) {
85 | t.Run("simple formatting", func(t *testing.T) {
86 | var buffer bytes.Buffer
87 | utils.Fprintf(&buffer, "hello %s", "world")
88 | require.Equal(t, "hello world", buffer.String())
89 | })
90 |
91 | t.Run("multiple format specifiers", func(t *testing.T) {
92 | var buffer bytes.Buffer
93 | utils.Fprintf(&buffer, "integer: %d, string: %s", 123, "test")
94 | require.Equal(t, "integer: 123, string: test", buffer.String())
95 | })
96 |
97 | t.Run("empty format string", func(t *testing.T) {
98 | var buffer bytes.Buffer
99 | utils.Fprintf(&buffer, "")
100 | require.Equal(t, buffer.Len(), 0)
101 | })
102 |
103 | t.Run("error case", func(t *testing.T) {
104 | require.Panics(t, func() {
105 | utils.Fprintf(&errorWriter{}, "this will fail")
106 | })
107 | })
108 | }
109 |
110 | // Custom io.Writer that always returns an error.
111 | type errorWriter struct{}
112 |
113 | func (e *errorWriter) Write(_ []byte) (n int, err error) {
114 | return 0, errors.New("write error")
115 | }
116 |
117 | var _ io.Writer = (*errorWriter)(nil)
118 |
--------------------------------------------------------------------------------
/libs/color/color.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package color
18 |
19 | import (
20 | "bytes"
21 | "io"
22 | "strconv"
23 | )
24 |
25 | type Color int
26 |
27 | const (
28 | BlackColor Color = 30
29 | BlueColor Color = 34
30 | CyanColor Color = 36
31 | GrayColor Color = 37
32 | GreenColor Color = 32
33 | PurpleColor Color = 35
34 | RedColor Color = 31
35 | WhiteColor Color = 97
36 | YellowColor Color = 33
37 |
38 | BlackBackground Color = 40
39 | BlueBackground Color = 44
40 | CyanBackground Color = 46
41 | GrayBackground Color = 47
42 | GreenBackground Color = 42
43 | PurpleBackground Color = 45
44 | RedBackground Color = 41
45 | WhiteBackground Color = 107
46 | YellowBackground Color = 43
47 |
48 | BoldText Color = 1
49 | UnderlineText Color = 4
50 |
51 | reset Color = 0
52 | )
53 |
54 | var _active = false // Colors are disabled by default
55 |
56 | func SetIsActive(active bool) {
57 | _active = active
58 | }
59 |
60 | func GetIsActive() bool {
61 | return _active
62 | }
63 |
64 | type coloredWriter struct {
65 | writer io.Writer
66 | colors []Color
67 | }
68 |
69 | func NewColoredWriter(writer io.Writer, colors ...Color) io.Writer {
70 | return &coloredWriter{
71 | writer: writer,
72 | colors: colors,
73 | }
74 | }
75 |
76 | func (s *coloredWriter) Write(data []byte) (int, error) {
77 | if !_active || len(s.colors) == 0 {
78 | return s.writer.Write(data)
79 | }
80 | data = normalize(data)
81 | if bytes.IndexByte(data, '\n') < 0 {
82 | newData := make([]byte, 0, len(data)+(len(s.colors)+1)*5) // 5 is the approximate size of one color in bytes.
83 | newData = appendColors(newData, data, s.colors)
84 | return s.writer.Write(newData)
85 | }
86 | // In some cases, colors may color a new line if there is a new line.
87 | // Colors will not be applied to the new line because it will look ugly.
88 | items := bytes.Split(data, []byte{'\n'})
89 | newData := make([]byte, 0, len(data)+(len(s.colors)+1)*len(items)*5) // 5 is the approximate size of one color in bytes.
90 | for i, data := range items {
91 | if i > 0 {
92 | newData = append(newData, '\n')
93 | }
94 | newData = appendColors(newData, data, s.colors)
95 | }
96 | return s.writer.Write(newData)
97 | }
98 |
99 | func normalize(data []byte) []byte {
100 | if bytes.IndexByte(data, '\r') < 0 {
101 | return data
102 | }
103 | data = bytes.ReplaceAll(data, []byte("\r\n"), []byte{'\n'})
104 | if bytes.IndexByte(data, '\r') < 0 {
105 | return data
106 | }
107 | return bytes.ReplaceAll(data, []byte{'\r'}, []byte{'\n'})
108 | }
109 |
110 | func appendColors(newData, data []byte, colors []Color) []byte {
111 | if len(data) == 0 {
112 | return newData
113 | }
114 | for _, color := range colors {
115 | newData = colorToBytes(newData, color)
116 | }
117 | newData = append(newData, data...)
118 | return colorToBytes(newData, reset)
119 | }
120 |
121 | func colorToBytes(data []byte, color Color) []byte {
122 | data = append(data, "\033["...)
123 | data = append(data, []byte(strconv.Itoa(int(color)))...)
124 | data = append(data, 'm')
125 | return data
126 | }
127 |
--------------------------------------------------------------------------------
/libs/color/theme.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package color
18 |
19 | import (
20 | "fmt"
21 | "io"
22 | )
23 |
24 | var themeFactories map[string]themeFactory
25 |
26 | func init() {
27 | themeFactories = make(map[string]themeFactory, 5)
28 | }
29 |
30 | type themeFactory = func(writer io.Writer) Theme
31 |
32 | type Theme interface {
33 | GetWriter(name string) io.Writer
34 | }
35 |
36 | type theme struct {
37 | defaultWriter io.Writer
38 | colors map[string]io.Writer
39 | }
40 |
41 | func NewTheme(writer io.Writer, colors map[string][]Color) Theme {
42 | theme := &theme{
43 | defaultWriter: writer,
44 | colors: make(map[string]io.Writer, len(colors)),
45 | }
46 | for name, colors := range colors {
47 | theme.colors[name] = NewColoredWriter(writer, colors...)
48 | }
49 | return theme
50 | }
51 |
52 | func (t *theme) GetWriter(name string) io.Writer {
53 | if writer, ok := t.colors[name]; ok {
54 | return writer
55 | }
56 | return t.defaultWriter
57 | }
58 |
59 | func AddTheme(name string, themeFactory themeFactory) {
60 | themeFactories[name] = themeFactory
61 | }
62 |
63 | func GetTheme(name string, writer io.Writer) Theme {
64 | if factory, ok := themeFactories[name]; ok {
65 | return factory(writer)
66 | }
67 | // We use panic here because we don't want you to handle an error when the theme doesn't exist.
68 | // The theme should always exist if you use it.
69 | panic(fmt.Sprintf("theme '%s' not found", name))
70 | }
71 |
72 | func HasTheme(name string) bool {
73 | _, ok := themeFactories[name]
74 | return ok
75 | }
76 |
--------------------------------------------------------------------------------
/libs/debug/stack.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package debug
18 |
19 | import (
20 | "fmt"
21 | "runtime"
22 | "strings"
23 | )
24 |
25 | type StackTrace []uintptr
26 |
27 | func GetStackTrace(skip int) *StackTrace {
28 | const depth = 32
29 | var pcs [depth]uintptr
30 | position := runtime.Callers(2+skip, pcs[:])
31 | var stackTrace StackTrace = pcs[0:position]
32 | return &stackTrace
33 | }
34 |
35 | func (s *StackTrace) String() string {
36 | var builder strings.Builder
37 | for _, pc := range *s {
38 | frame := StackFrame(pc)
39 | builder.WriteString(frame.String())
40 | builder.WriteString("\n")
41 | }
42 | return builder.String()
43 | }
44 |
45 | type StackFrame uintptr
46 |
47 | func (s StackFrame) PC() uintptr {
48 | return uintptr(s) - 1
49 | }
50 |
51 | func (s StackFrame) FileLine() (string, int) {
52 | fn := runtime.FuncForPC(s.PC())
53 | if fn == nil {
54 | return "unknown", 0
55 | }
56 | return fn.FileLine(s.PC())
57 | }
58 |
59 | func (s StackFrame) Name() string {
60 | fn := runtime.FuncForPC(s.PC())
61 | if fn == nil {
62 | return "unknown"
63 | }
64 | return fn.Name()
65 | }
66 |
67 | func (s StackFrame) String() string {
68 | file, line := s.FileLine()
69 | return fmt.Sprintf("%s: %s:%d", s.Name(), file, line)
70 | }
71 |
72 | var (
73 | _ fmt.Stringer = (*StackTrace)(nil)
74 | _ fmt.Stringer = (*StackFrame)(nil)
75 | )
76 |
--------------------------------------------------------------------------------
/libs/debug/tests/variable_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "reflect"
21 | "strings"
22 | "testing"
23 |
24 | "github.com/unpleasantcam/componego/internal/testing/require"
25 | "github.com/unpleasantcam/componego/libs/debug"
26 | )
27 |
28 | type testStruct1 struct {
29 | A float64 `json:"a"`
30 | B []any
31 | c bool
32 | D *testStruct2
33 | }
34 |
35 | type testStruct2 struct {
36 | A bool
37 | B []string
38 | }
39 |
40 | func TestRenderVariable(t *testing.T) {
41 | require.Equal(t, "nil", debug.RenderVariable(nil, nil))
42 | require.Equal(t, "int{ 1 }", debug.RenderVariable(1, nil))
43 | require.Equal(t, `string{ "1" }`, debug.RenderVariable("1", nil))
44 | require.Equal(t, "int", debug.RenderVariable(reflect.TypeOf(1), nil))
45 | require.Equal(t, "int", debug.RenderVariable(reflect.ValueOf(1), nil))
46 | require.Regexp(t, `func \S+\(float64, \.\.\.string\) error`, debug.RenderVariable(func(_ float64, _ ...string) error { return nil }, nil))
47 | require.Equal(t, "struct {}{[... no public data]}", debug.RenderVariable(struct{}{}, nil))
48 | structObj := &testStruct1{
49 | A: 0.1,
50 | B: []any{"1", 2, false},
51 | c: true,
52 | D: &testStruct2{
53 | A: false,
54 | B: []string{"text"},
55 | },
56 | }
57 | structOutput := `*tests.testStruct1{ "a": 0.1, "B": { 0: "1", 1: 2, 2: false, }, "D": { "A": false, "B": []string{[... read depth exceeded]}, }, }`
58 | require.Equal(t, structOutput, debug.RenderVariable(structObj, nil))
59 | structOutput = `*tests.testStruct1{
60 | "a": 0.1,
61 | "B": {
62 | 0: "1",
63 | 1: 2,
64 | 2: false,
65 | },
66 | "D": {
67 | "A": false,
68 | "B": []string{[... read depth exceeded]},
69 | },
70 | }`
71 | require.Equal(t, structOutput, debug.RenderVariable(structObj, &debug.VariableConfig{
72 | Indent: " ",
73 | UseNewLine: true,
74 | MaxDepth: 3,
75 | MaxItems: 10,
76 | }))
77 | structOutput = strings.ReplaceAll(strings.ReplaceAll(structOutput, " ", "@@"), "\n", " ")
78 | require.Equal(t, structOutput, debug.RenderVariable(structObj, &debug.VariableConfig{
79 | Indent: "@@",
80 | UseNewLine: false,
81 | MaxDepth: 3,
82 | MaxItems: 10,
83 | }))
84 | structOutput = `*tests.testStruct1{
85 | --"a": 0.1,
86 | --"B": []any{[... read depth exceeded]},
87 | --"D": *tests.testStruct2{[... read depth exceeded]},
88 | }`
89 | require.Equal(t, structOutput, debug.RenderVariable(structObj, &debug.VariableConfig{
90 | Indent: "--",
91 | UseNewLine: true,
92 | MaxDepth: 2,
93 | MaxItems: 2,
94 | }))
95 | structOutput = `*tests.testStruct1{
96 | "a": 0.1,
97 | "B": {
98 | 0: "1",
99 | [...and other 2 items],
100 | },
101 | "D": {
102 | "A": false,
103 | "B": {
104 | 0: "text",
105 | },
106 | },
107 | }`
108 | require.Equal(t, structOutput, debug.RenderVariable(structObj, &debug.VariableConfig{
109 | Indent: " ",
110 | UseNewLine: true,
111 | MaxDepth: 4,
112 | MaxItems: 1,
113 | }))
114 | }
115 |
--------------------------------------------------------------------------------
/libs/ordered-map/tests/map_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "testing"
21 |
22 | "github.com/unpleasantcam/componego/libs/ordered-map"
23 | )
24 |
25 | func TestOrderedMap(t *testing.T) {
26 | MapTester[*testing.T](t, ordered_map.New[int, float64])
27 | }
28 |
29 | func BenchmarkOrderedMap(b *testing.B) {
30 | b.Run("set", func(b *testing.B) {
31 | m := ordered_map.New[int, int](b.N)
32 | b.ReportAllocs()
33 | for n := 0; n < b.N; n++ {
34 | m.Set(n, n)
35 | }
36 | })
37 | b.Run("get", func(b *testing.B) {
38 | m := ordered_map.New[int, int](1)
39 | m.Set(0, 0)
40 | b.ReportAllocs()
41 | for n := 0; n < b.N; n++ {
42 | m.Get(0)
43 | }
44 | })
45 | b.Run("has", func(b *testing.B) {
46 | m := ordered_map.New[int, int](1)
47 | m.Set(0, 0)
48 | b.ReportAllocs()
49 | for n := 0; n < b.N; n++ {
50 | m.Has(0)
51 | }
52 | })
53 | b.Run("prepend", func(b *testing.B) {
54 | m := ordered_map.New[int, int](b.N)
55 | b.ReportAllocs()
56 | for n := 0; n < b.N; n++ {
57 | m.Prepend(n, n)
58 | }
59 | })
60 | b.Run("append", func(b *testing.B) {
61 | m := ordered_map.New[int, int](b.N)
62 | b.ReportAllocs()
63 | for n := 0; n < b.N; n++ {
64 | m.Append(n, n)
65 | }
66 | })
67 | b.Run("add before", func(b *testing.B) {
68 | m := ordered_map.New[int, int](b.N)
69 | b.ReportAllocs()
70 | for n := 0; n < b.N; n++ {
71 | m.AddBefore(n, n, n-1)
72 | }
73 | })
74 | b.Run("add after", func(b *testing.B) {
75 | m := ordered_map.New[int, int](b.N)
76 | b.ReportAllocs()
77 | for n := 0; n < b.N; n++ {
78 | m.AddAfter(n, n, n-1)
79 | }
80 | })
81 | }
82 |
--------------------------------------------------------------------------------
/libs/type-cast/bool.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package type_cast
18 |
19 | import (
20 | "fmt"
21 | "strconv"
22 |
23 | "github.com/unpleasantcam/componego/internal/utils"
24 | )
25 |
26 | func ToBool(value any) (bool, error) {
27 | value = utils.Indirect(value)
28 | switch castedValue := value.(type) {
29 | case nil:
30 | return false, nil
31 | case bool:
32 | return castedValue, nil
33 | case int:
34 | return castedValue != 0, nil
35 | case int8:
36 | return castedValue != 0, nil
37 | case int16:
38 | return castedValue != 0, nil
39 | case int32:
40 | return castedValue != 0, nil
41 | case int64:
42 | return castedValue != 0, nil
43 | case uint:
44 | return castedValue != 0, nil
45 | case uint8:
46 | return castedValue != 0, nil
47 | case uint16:
48 | return castedValue != 0, nil
49 | case uint32:
50 | return castedValue != 0, nil
51 | case uint64:
52 | return castedValue != 0, nil
53 | case float32:
54 | return castedValue != 0, nil
55 | case float64:
56 | return castedValue != 0, nil
57 | case string:
58 | return strconv.ParseBool(castedValue)
59 | }
60 | return false, fmt.Errorf("unable to cast %#v of type %T to bool", value, value)
61 | }
62 |
--------------------------------------------------------------------------------
/libs/type-cast/float.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package type_cast
18 |
19 | import (
20 | "fmt"
21 | "strconv"
22 |
23 | "github.com/unpleasantcam/componego/internal/utils"
24 | )
25 |
26 | func ToFloat64(value any) (float64, error) {
27 | value = utils.Indirect(value)
28 | switch castedValue := value.(type) {
29 | case nil:
30 | return 0, nil
31 | case bool:
32 | if castedValue {
33 | return 1, nil
34 | }
35 | return 0, nil
36 | case int:
37 | return float64(castedValue), nil
38 | case int8:
39 | return float64(castedValue), nil
40 | case int16:
41 | return float64(castedValue), nil
42 | case int32:
43 | return float64(castedValue), nil
44 | case int64:
45 | return float64(castedValue), nil
46 | case uint:
47 | return float64(castedValue), nil
48 | case uint8:
49 | return float64(castedValue), nil
50 | case uint16:
51 | return float64(castedValue), nil
52 | case uint32:
53 | return float64(castedValue), nil
54 | case uint64:
55 | return float64(castedValue), nil
56 | case float32:
57 | return float64(castedValue), nil
58 | case float64:
59 | return castedValue, nil
60 | case string:
61 | return strconv.ParseFloat(castedValue, 64)
62 | }
63 | return 0, fmt.Errorf("unable to cast %#v of type %T to float64", value, value)
64 | }
65 |
--------------------------------------------------------------------------------
/libs/type-cast/int.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package type_cast
18 |
19 | import (
20 | "fmt"
21 | "math"
22 | "strconv"
23 |
24 | "github.com/unpleasantcam/componego/internal/utils"
25 | )
26 |
27 | func ToInt64(value any) (int64, error) {
28 | value = utils.Indirect(value)
29 | switch castedValue := value.(type) {
30 | case nil:
31 | return 0, nil
32 | case bool:
33 | if castedValue {
34 | return 1, nil
35 | }
36 | return 0, nil
37 | case int:
38 | return int64(castedValue), nil
39 | case int8:
40 | return int64(castedValue), nil
41 | case int16:
42 | return int64(castedValue), nil
43 | case int32:
44 | return int64(castedValue), nil
45 | case int64:
46 | return castedValue, nil
47 | case uint:
48 | if castedValue > math.MaxInt64 {
49 | return 0, fmt.Errorf("unable to cast %#v of type %T to int64", value, value)
50 | }
51 | return int64(castedValue), nil
52 | case uint8:
53 | return int64(castedValue), nil
54 | case uint16:
55 | return int64(castedValue), nil
56 | case uint32:
57 | return int64(castedValue), nil
58 | case uint64:
59 | if castedValue > math.MaxInt64 {
60 | return 0, fmt.Errorf("unable to cast %#v of type %T to int64", value, value)
61 | }
62 | return int64(castedValue), nil
63 | case float32:
64 | return int64(castedValue), nil
65 | case float64:
66 | return int64(castedValue), nil
67 | case string:
68 | return strconv.ParseInt(castedValue, 0, 0)
69 | }
70 | return 0, fmt.Errorf("unable to cast %#v of type %T to int64", value, value)
71 | }
72 |
--------------------------------------------------------------------------------
/libs/type-cast/string.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package type_cast
18 |
19 | import (
20 | "fmt"
21 | "strconv"
22 |
23 | "github.com/unpleasantcam/componego/internal/utils"
24 | )
25 |
26 | func ToString(value any) (string, error) {
27 | value = utils.Indirect(value)
28 | switch castedValue := value.(type) {
29 | case nil:
30 | return "", nil
31 | case bool:
32 | return strconv.FormatBool(castedValue), nil
33 | case int:
34 | return strconv.Itoa(castedValue), nil
35 | case int8:
36 | return strconv.FormatInt(int64(castedValue), 10), nil
37 | case int16:
38 | return strconv.FormatInt(int64(castedValue), 10), nil
39 | case int32:
40 | return strconv.FormatInt(int64(castedValue), 10), nil
41 | case int64:
42 | return strconv.FormatInt(castedValue, 10), nil
43 | case uint:
44 | return strconv.FormatUint(uint64(castedValue), 10), nil
45 | case uint8:
46 | return strconv.FormatInt(int64(castedValue), 10), nil
47 | case uint16:
48 | return strconv.FormatInt(int64(castedValue), 10), nil
49 | case uint32:
50 | return strconv.FormatInt(int64(castedValue), 10), nil
51 | case uint64:
52 | return strconv.FormatUint(castedValue, 10), nil
53 | case float32:
54 | return strconv.FormatFloat(float64(castedValue), 'f', -1, 32), nil
55 | case float64:
56 | return strconv.FormatFloat(castedValue, 'f', -1, 64), nil
57 | case string:
58 | return castedValue, nil
59 | }
60 | return "", fmt.Errorf("unable to cast %#v of type %T to string", value, value)
61 | }
62 |
--------------------------------------------------------------------------------
/libs/type-cast/tests/float_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "testing"
21 |
22 | "github.com/unpleasantcam/componego/internal/testing/require"
23 | "github.com/unpleasantcam/componego/libs/type-cast"
24 | )
25 |
26 | func TestToFloat64(t *testing.T) {
27 | t.Run("nil value", func(t *testing.T) {
28 | result, err := type_cast.ToFloat64(nil)
29 | require.NoError(t, err)
30 | require.Equal(t, float64(0), result)
31 | })
32 |
33 | t.Run("bool value true", func(t *testing.T) {
34 | result, err := type_cast.ToFloat64(true)
35 | require.NoError(t, err)
36 | require.Equal(t, float64(1), result)
37 | })
38 |
39 | t.Run("bool value false", func(t *testing.T) {
40 | result, err := type_cast.ToFloat64(false)
41 | require.NoError(t, err)
42 | require.Equal(t, float64(0), result)
43 | })
44 |
45 | t.Run("int value", func(t *testing.T) {
46 | result, err := type_cast.ToFloat64(123)
47 | require.NoError(t, err)
48 | require.Equal(t, float64(123), result)
49 | })
50 |
51 | t.Run("int8 value", func(t *testing.T) {
52 | result, err := type_cast.ToFloat64(int8(123))
53 | require.NoError(t, err)
54 | require.Equal(t, float64(123), result)
55 | })
56 |
57 | t.Run("int16 value", func(t *testing.T) {
58 | result, err := type_cast.ToFloat64(int16(123))
59 | require.NoError(t, err)
60 | require.Equal(t, float64(123), result)
61 | })
62 |
63 | t.Run("int32 value", func(t *testing.T) {
64 | result, err := type_cast.ToFloat64(int32(123))
65 | require.NoError(t, err)
66 | require.Equal(t, float64(123), result)
67 | })
68 |
69 | t.Run("int64 value", func(t *testing.T) {
70 | result, err := type_cast.ToFloat64(int64(123))
71 | require.NoError(t, err)
72 | require.Equal(t, float64(123), result)
73 | })
74 |
75 | t.Run("uint value", func(t *testing.T) {
76 | result, err := type_cast.ToFloat64(uint(123))
77 | require.NoError(t, err)
78 | require.Equal(t, float64(123), result)
79 | })
80 |
81 | t.Run("uint8 value", func(t *testing.T) {
82 | result, err := type_cast.ToFloat64(uint8(123))
83 | require.NoError(t, err)
84 | require.Equal(t, float64(123), result)
85 | })
86 |
87 | t.Run("uint16 value", func(t *testing.T) {
88 | result, err := type_cast.ToFloat64(uint16(123))
89 | require.NoError(t, err)
90 | require.Equal(t, float64(123), result)
91 | })
92 |
93 | t.Run("uint32 value", func(t *testing.T) {
94 | result, err := type_cast.ToFloat64(uint32(123))
95 | require.NoError(t, err)
96 | require.Equal(t, float64(123), result)
97 | })
98 |
99 | t.Run("uint64 value", func(t *testing.T) {
100 | result, err := type_cast.ToFloat64(uint64(123))
101 | require.NoError(t, err)
102 | require.Equal(t, float64(123), result)
103 | })
104 |
105 | t.Run("float32 value", func(t *testing.T) {
106 | result, err := type_cast.ToFloat64(float32(123.123))
107 | require.NoError(t, err)
108 | // noinspection ALL
109 | require.InDelta(t, float64(123.123), result, 1e-5)
110 | })
111 |
112 | t.Run("float64 value", func(t *testing.T) {
113 | // noinspection ALL
114 | result, err := type_cast.ToFloat64(float64(123.123))
115 | require.NoError(t, err)
116 | // noinspection ALL
117 | require.Equal(t, float64(123.123), result)
118 | })
119 |
120 | t.Run("string value", func(t *testing.T) {
121 | result, err := type_cast.ToFloat64("123.123")
122 | require.NoError(t, err)
123 | // noinspection ALL
124 | require.Equal(t, float64(123.123), result)
125 | })
126 |
127 | t.Run("string value invalid", func(t *testing.T) {
128 | _, err := type_cast.ToFloat64("invalid")
129 | require.Error(t, err)
130 | })
131 |
132 | t.Run("struct value invalid", func(t *testing.T) {
133 | _, err := type_cast.ToFloat64(struct{}{})
134 | require.Error(t, err)
135 | })
136 | }
137 |
--------------------------------------------------------------------------------
/libs/type-cast/tests/int_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "math"
21 | "testing"
22 |
23 | "github.com/unpleasantcam/componego/internal/testing/require"
24 | "github.com/unpleasantcam/componego/libs/type-cast"
25 | )
26 |
27 | func TestToInt64(t *testing.T) {
28 | t.Run("nil value", func(t *testing.T) {
29 | result, err := type_cast.ToInt64(nil)
30 | require.NoError(t, err)
31 | require.Equal(t, int64(0), result)
32 | })
33 |
34 | t.Run("bool value true", func(t *testing.T) {
35 | result, err := type_cast.ToInt64(true)
36 | require.NoError(t, err)
37 | require.Equal(t, int64(1), result)
38 | })
39 |
40 | t.Run("bool value false", func(t *testing.T) {
41 | result, err := type_cast.ToInt64(false)
42 | require.NoError(t, err)
43 | require.Equal(t, int64(0), result)
44 | })
45 |
46 | t.Run("int value", func(t *testing.T) {
47 | result, err := type_cast.ToInt64(123)
48 | require.NoError(t, err)
49 | require.Equal(t, int64(123), result)
50 | })
51 |
52 | t.Run("int8 value", func(t *testing.T) {
53 | result, err := type_cast.ToInt64(int8(123))
54 | require.NoError(t, err)
55 | require.Equal(t, int64(123), result)
56 | })
57 |
58 | t.Run("int16 value", func(t *testing.T) {
59 | result, err := type_cast.ToInt64(int16(123))
60 | require.NoError(t, err)
61 | require.Equal(t, int64(123), result)
62 | })
63 |
64 | t.Run("int32 value", func(t *testing.T) {
65 | result, err := type_cast.ToInt64(int32(123))
66 | require.NoError(t, err)
67 | require.Equal(t, int64(123), result)
68 | })
69 |
70 | t.Run("int64 value", func(t *testing.T) {
71 | result, err := type_cast.ToInt64(int64(123))
72 | require.NoError(t, err)
73 | require.Equal(t, int64(123), result)
74 | })
75 |
76 | t.Run("uint value", func(t *testing.T) {
77 | result, err := type_cast.ToInt64(uint(123))
78 | require.NoError(t, err)
79 | require.Equal(t, int64(123), result)
80 | })
81 |
82 | t.Run("uint8 value", func(t *testing.T) {
83 | result, err := type_cast.ToInt64(uint8(123))
84 | require.NoError(t, err)
85 | require.Equal(t, int64(123), result)
86 | })
87 |
88 | t.Run("uint16 value", func(t *testing.T) {
89 | result, err := type_cast.ToInt64(uint16(123))
90 | require.NoError(t, err)
91 | require.Equal(t, int64(123), result)
92 | })
93 |
94 | t.Run("uint32 value", func(t *testing.T) {
95 | result, err := type_cast.ToInt64(uint32(123))
96 | require.NoError(t, err)
97 | require.Equal(t, int64(123), result)
98 | })
99 |
100 | t.Run("uint64 value", func(t *testing.T) {
101 | result, err := type_cast.ToInt64(uint64(123))
102 | require.NoError(t, err)
103 | require.Equal(t, int64(123), result)
104 | })
105 |
106 | t.Run("float32 value", func(t *testing.T) {
107 | result, err := type_cast.ToInt64(float32(123.123))
108 | require.NoError(t, err)
109 | require.Equal(t, int64(123), result)
110 | })
111 |
112 | t.Run("float64 value", func(t *testing.T) {
113 | // noinspection ALL
114 | result, err := type_cast.ToInt64(float64(123.123))
115 | require.NoError(t, err)
116 | require.Equal(t, int64(123), result)
117 | })
118 |
119 | t.Run("string value", func(t *testing.T) {
120 | result, err := type_cast.ToInt64("123")
121 | require.NoError(t, err)
122 | require.Equal(t, int64(123), result)
123 | })
124 |
125 | t.Run("string value invalid", func(t *testing.T) {
126 | _, err := type_cast.ToInt64("invalid")
127 | require.Error(t, err)
128 | })
129 |
130 | t.Run("struct value invalid", func(t *testing.T) {
131 | _, err := type_cast.ToInt64(struct{}{})
132 | require.Error(t, err)
133 | })
134 |
135 | t.Run("uint input above MaxInt64", func(t *testing.T) {
136 | result, err := type_cast.ToInt64(uint(math.MaxInt64 + 1))
137 | require.Error(t, err)
138 | require.Equal(t, int64(0), result)
139 | })
140 |
141 | t.Run("uint64 input below MaxInt64", func(t *testing.T) {
142 | result, err := type_cast.ToInt64(uint64(math.MaxInt64 + 1))
143 | require.Error(t, err)
144 | require.Equal(t, int64(0), result)
145 | })
146 | }
147 |
--------------------------------------------------------------------------------
/libs/type-cast/tests/string_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "testing"
21 |
22 | "github.com/unpleasantcam/componego/internal/testing/require"
23 | "github.com/unpleasantcam/componego/libs/type-cast"
24 | )
25 |
26 | func TestToString(t *testing.T) {
27 | t.Run("nil value", func(t *testing.T) {
28 | result, err := type_cast.ToString(nil)
29 | require.NoError(t, err)
30 | require.Equal(t, "", result)
31 | })
32 |
33 | t.Run("bool value", func(t *testing.T) {
34 | result, err := type_cast.ToString(true)
35 | require.NoError(t, err)
36 | require.Equal(t, "true", result)
37 | })
38 |
39 | t.Run("int value", func(t *testing.T) {
40 | result, err := type_cast.ToString(123)
41 | require.NoError(t, err)
42 | require.Equal(t, "123", result)
43 | })
44 |
45 | t.Run("int8 value", func(t *testing.T) {
46 | result, err := type_cast.ToString(int8(123))
47 | require.NoError(t, err)
48 | require.Equal(t, "123", result)
49 | })
50 |
51 | t.Run("int16 value", func(t *testing.T) {
52 | result, err := type_cast.ToString(int16(123))
53 | require.NoError(t, err)
54 | require.Equal(t, "123", result)
55 | })
56 |
57 | t.Run("int32 value", func(t *testing.T) {
58 | result, err := type_cast.ToString(int32(123))
59 | require.NoError(t, err)
60 | require.Equal(t, "123", result)
61 | })
62 |
63 | t.Run("int64 value", func(t *testing.T) {
64 | result, err := type_cast.ToString(int64(123))
65 | require.NoError(t, err)
66 | require.Equal(t, "123", result)
67 | })
68 |
69 | t.Run("uint value", func(t *testing.T) {
70 | result, err := type_cast.ToString(uint(123))
71 | require.NoError(t, err)
72 | require.Equal(t, "123", result)
73 | })
74 |
75 | t.Run("uint8 value", func(t *testing.T) {
76 | result, err := type_cast.ToString(uint8(123))
77 | require.NoError(t, err)
78 | require.Equal(t, "123", result)
79 | })
80 |
81 | t.Run("uint16 value", func(t *testing.T) {
82 | result, err := type_cast.ToString(uint16(123))
83 | require.NoError(t, err)
84 | require.Equal(t, "123", result)
85 | })
86 |
87 | t.Run("uint32 value", func(t *testing.T) {
88 | result, err := type_cast.ToString(uint32(123))
89 | require.NoError(t, err)
90 | require.Equal(t, "123", result)
91 | })
92 |
93 | t.Run("uint64 value", func(t *testing.T) {
94 | result, err := type_cast.ToString(uint64(123))
95 | require.NoError(t, err)
96 | require.Equal(t, "123", result)
97 | })
98 |
99 | t.Run("float32 value", func(t *testing.T) {
100 | result, err := type_cast.ToString(float32(123.123))
101 | require.NoError(t, err)
102 | require.Equal(t, "123.123", result)
103 | })
104 |
105 | t.Run("float64 value", func(t *testing.T) {
106 | // noinspection ALL
107 | result, err := type_cast.ToString(float64(123.123))
108 | require.NoError(t, err)
109 | require.Equal(t, "123.123", result)
110 | })
111 |
112 | t.Run("string value", func(t *testing.T) {
113 | result, err := type_cast.ToString("hello")
114 | require.NoError(t, err)
115 | require.Equal(t, "hello", result)
116 | })
117 |
118 | t.Run("unsupported value type", func(t *testing.T) {
119 | _, err := type_cast.ToString([]int{1, 2, 3})
120 | require.Error(t, err)
121 | })
122 | }
123 |
--------------------------------------------------------------------------------
/libs/vendor-proxy/tests/proxy_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "strconv"
21 | "testing"
22 |
23 | "github.com/unpleasantcam/componego/libs/vendor-proxy"
24 | )
25 |
26 | func TestVendorProxy(t *testing.T) {
27 | VendorProxyTester[*testing.T](t, createInstance)
28 | }
29 |
30 | func BenchmarkVendorProxy(b *testing.B) {
31 | instance := createInstance()
32 | _ = instance.AddFunction("reflectFunc", func(_ int, _ any, _ ...float64) (bool, error) {
33 | return true, nil
34 | })
35 | _ = instance.AddFunction("contextFunc", func(ctx vendor_proxy.Context, args ...any) (any, error) {
36 | _ = args[0].(int)
37 | _ = args[1]
38 | _ = args[2].([]float64)
39 | ctx.Validated()
40 | return true, nil
41 | })
42 | arg1 := 1
43 | arg2 := "text"
44 | arg3 := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
45 | b.Run("reflect call", func(b *testing.B) {
46 | b.ReportAllocs()
47 | for n := 0; n < b.N; n++ {
48 | _, _ = instance.CallFunction("reflectFunc", arg1, arg2, arg3)
49 | }
50 | })
51 | b.Run("context call", func(b *testing.B) {
52 | b.ReportAllocs()
53 | for n := 0; n < b.N; n++ {
54 | _, _ = instance.CallFunction("contextFunc", arg1, arg2, arg3)
55 | }
56 | })
57 | }
58 |
59 | func createInstance() vendor_proxy.Proxy {
60 | index := 0
61 | for {
62 | name := "temp-vendor-proxy-" + strconv.Itoa(index)
63 | if !vendor_proxy.Has(name) {
64 | return vendor_proxy.Get(name)
65 | }
66 | index++
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/libs/xerrors/option.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package xerrors
18 |
19 | type Option interface {
20 | Key() string
21 | Value() any
22 | }
23 |
24 | type option struct {
25 | key string
26 | value any
27 | }
28 |
29 | func NewOption(key string, value any) Option {
30 | return &option{
31 | key: key,
32 | value: value,
33 | }
34 | }
35 |
36 | func (o *option) Key() string {
37 | return o.key
38 | }
39 |
40 | func (o *option) Value() any {
41 | return o.value
42 | }
43 |
44 | type callableOption struct {
45 | key string
46 | getValue func() any
47 | }
48 |
49 | func NewCallableOption(key string, getValue func() any) Option {
50 | return &callableOption{
51 | key: key,
52 | getValue: getValue,
53 | }
54 | }
55 |
56 | func (c *callableOption) Key() string {
57 | return c.key
58 | }
59 |
60 | func (c *callableOption) Value() any {
61 | return c.getValue()
62 | }
63 |
--------------------------------------------------------------------------------
/libs/xerrors/tests/unwrap_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "errors"
21 | "testing"
22 |
23 | "github.com/unpleasantcam/componego/internal/testing/require"
24 | "github.com/unpleasantcam/componego/libs/xerrors"
25 | )
26 |
27 | func TestUnwrapAll(t *testing.T) {
28 | t.Run("single unwrap error", func(t *testing.T) {
29 | err1 := errors.New("error")
30 | err2 := &singleUnwrapError{message: "wrapped error", inner: err1}
31 | result := xerrors.UnwrapAll(err2)
32 | require.Len(t, result, 2)
33 | require.Same(t, err2, result[0])
34 | require.Same(t, err1, result[1])
35 | })
36 |
37 | t.Run("multiple unwrap error", func(t *testing.T) {
38 | err1 := errors.New("error 1")
39 | err2 := errors.New("error 2")
40 | err3 := &multipleUnwrapError{message: "wrapped error", inners: []error{err1, err2}}
41 | result := xerrors.UnwrapAll(err3)
42 | require.Len(t, result, 3)
43 | require.Same(t, err3, result[0])
44 | require.Same(t, err2, result[1])
45 | require.Same(t, err1, result[2])
46 | })
47 |
48 | t.Run("mixed unwrap error", func(t *testing.T) {
49 | err1 := errors.New("error")
50 | err2 := &singleUnwrapError{message: "wrapped error 1", inner: err1}
51 | err3 := &multipleUnwrapError{message: "wrapped error 2", inners: []error{err1, err2}}
52 | result := xerrors.UnwrapAll(err3)
53 | require.Len(t, result, 4)
54 | require.Same(t, err3, result[0])
55 | require.Same(t, err2, result[1])
56 | require.Same(t, err1, result[2])
57 | require.Same(t, err1, result[3])
58 | })
59 |
60 | t.Run("nil Error", func(t *testing.T) {
61 | var err error
62 | result := xerrors.UnwrapAll(err)
63 | require.Len(t, result, 1)
64 | require.Nil(t, result[0])
65 | })
66 | }
67 |
68 | type singleUnwrapError struct {
69 | message string
70 | inner error
71 | }
72 |
73 | func (e *singleUnwrapError) Error() string {
74 | return e.message
75 | }
76 |
77 | func (e *singleUnwrapError) Unwrap() error {
78 | return e.inner
79 | }
80 |
81 | type multipleUnwrapError struct {
82 | message string
83 | inners []error
84 | }
85 |
86 | func (e *multipleUnwrapError) Error() string {
87 | return e.message
88 | }
89 |
90 | func (e *multipleUnwrapError) Unwrap() []error {
91 | return e.inners
92 | }
93 |
94 | var (
95 | _ error = (*singleUnwrapError)(nil)
96 | _ interface{ Unwrap() error } = (*singleUnwrapError)(nil)
97 | _ error = (*multipleUnwrapError)(nil)
98 | _ interface{ Unwrap() []error } = (*multipleUnwrapError)(nil)
99 | )
100 |
--------------------------------------------------------------------------------
/libs/xerrors/tests/xerrors.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "errors"
21 | "strings"
22 |
23 | "github.com/unpleasantcam/componego/internal/testing"
24 | "github.com/unpleasantcam/componego/libs/xerrors"
25 | )
26 |
27 | func XErrorsTester[T testing.T](
28 | t testing.TRun[T],
29 | factory func(message string, code string, options ...xerrors.Option) xerrors.XError,
30 | ) {
31 | err1 := errors.New("error1")
32 | err1x := factory("error1", "E1X")
33 | err2 := errors.New("error2")
34 | err2x := factory("error2", "E2X")
35 | err3 := errors.New("error3")
36 | err3x := factory("error3", "E3X")
37 | err1S3 := errors.Join(err1, err3)
38 | err1xS3x := err1x.WithError(err3x, "E1XS3X")
39 | err1S2 := errors.Join(err1, err2)
40 | err1xS2 := err1x.WithError(err2, "E1XS2")
41 | err1S2S3 := errors.Join(err1S2, err3)
42 | err1xS2S3x := err1xS2.WithError(err3x, "E1XS2S3X")
43 | t.Run("comparison with the original error package", func(t T) {
44 | testCases := [...]struct {
45 | a1 error
46 | b1 error
47 | a2 error
48 | b2 error
49 | }{
50 | {err1, err1, err1x, err1x}, // 1
51 | {err1, err2, err1x, err2x}, // 2
52 | {err2, err1, err2x, err1x}, // 3
53 | {err1, err3, err1x, err3x}, // 4
54 | {err1, err1S3, err1x, err1xS3x}, // 5
55 | {err1S3, err1, err1xS3x, err1x}, // 6
56 | {err1S3, err2, err1xS3x, err2x}, // 7
57 | {err1S2, err2, err1xS2, err2}, // 8
58 | {err2, err1S2, err2, err1xS2}, // 9
59 | {err1S2S3, err3, err1xS2S3x, err3x}, // 10
60 | {err1S2, err3, err1xS2, err3x}, // 11
61 | }
62 | for i, testCase := range testCases {
63 | if errors.Is(testCase.a1, testCase.b1) != errors.Is(testCase.a2, testCase.b2) {
64 | t.Errorf("#%d failed", i+1)
65 | t.FailNow()
66 | }
67 | }
68 | })
69 | t.Run("comparison with the expected result", func(t T) {
70 | testCases := [...]struct {
71 | a error
72 | b error
73 | result bool
74 | }{
75 | {err1x, err1x, true}, // 1
76 | {err1x, err2x, false}, // 2
77 | {err1x, err1, false}, // 3
78 | {err1, err1x, false}, // 4
79 | {err1xS3x, err1x, true}, // 5
80 | {err1xS3x, err3x, true}, // 6
81 | {err1xS3x, err2x, false}, // 7
82 | {err1xS2, err2, true}, // 8
83 | {err2, err1xS2, false}, // 9
84 | {err1xS2, err2, true}, // 10
85 | {err1xS2S3x, err1x, true}, // 11
86 | {err1xS2S3x, err2, true}, // 12
87 | {err1xS2S3x, err3x, true}, // 13
88 | {err1xS2S3x, err3, false}, // 14
89 | {err3x, err1xS2S3x, false}, // 15
90 | }
91 | for i, testCase := range testCases {
92 | if errors.Is(testCase.a, testCase.b) != testCase.result {
93 | t.Errorf("#%d failed", i+1)
94 | t.FailNow()
95 | }
96 | }
97 | })
98 | t.Run("error text comparison", func(t T) {
99 | testCases2 := [...]struct {
100 | a error
101 | b error
102 | }{
103 | {err1, err1x}, // 1
104 | {err1S3, err1xS3x}, // 2
105 | {err1S2, err1xS2}, // 3
106 | {err1S2S3, err1xS2S3x}, // 4
107 | }
108 | for i, testCase := range testCases2 {
109 | if testCase.a.Error() != convertErrorMessage(testCase.b.Error()) {
110 | t.Errorf("#%d failed", i+1)
111 | t.FailNow()
112 | }
113 | }
114 | })
115 | }
116 |
117 | func GetOptionsByKey(t testing.T, err error, key string) any {
118 | for _, err = range xerrors.UnwrapAll(err) {
119 | // noinspection ALL
120 | xError, ok := err.(xerrors.XError) //nolint:errorlint
121 | if !ok {
122 | continue
123 | }
124 | for _, option := range xError.ErrorOptions() {
125 | if option.Key() == key {
126 | return option.Value()
127 | }
128 | }
129 | }
130 | t.Errorf("failed to get error option by key '%s'", key)
131 | t.FailNow()
132 | return nil
133 | }
134 |
135 | func convertErrorMessage(errMessage string) string {
136 | lines := strings.Split(errMessage, "\n")
137 | for i, line := range lines {
138 | index := strings.Index(line, " (")
139 | if index != -1 {
140 | lines[i] = line[:index]
141 | }
142 | }
143 | return strings.Join(lines, "\n")
144 | }
145 |
--------------------------------------------------------------------------------
/libs/xerrors/tests/xerrors_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package tests
18 |
19 | import (
20 | "testing"
21 |
22 | "github.com/unpleasantcam/componego/libs/xerrors"
23 | )
24 |
25 | func TestXErrors(t *testing.T) {
26 | XErrorsTester[*testing.T](t, xerrors.New)
27 | }
28 |
--------------------------------------------------------------------------------
/libs/xerrors/unwrap.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package xerrors
18 |
19 | func UnwrapAll(err error) []error {
20 | result := make([]error, 0, 5)
21 | errorStack := make([]error, 0, 5)
22 | errorStack = append(errorStack, err)
23 | for len(errorStack) > 0 {
24 | err = errorStack[len(errorStack)-1]
25 | result = append(result, err)
26 | errorStack = errorStack[:len(errorStack)-1]
27 | switch castedErr := err.(type) { //nolint:errorlint
28 | case interface{ Unwrap() error }:
29 | errorStack = append(errorStack, castedErr.Unwrap())
30 | case interface{ Unwrap() []error }:
31 | errorStack = append(errorStack, castedErr.Unwrap()...)
32 | }
33 | }
34 | return result
35 | }
36 |
--------------------------------------------------------------------------------
/libs/xerrors/xerrors.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package xerrors
18 |
19 | import (
20 | "errors"
21 | )
22 |
23 | type XError interface {
24 | error
25 | ErrorCode() string
26 | ErrorOptions() []Option
27 | WithMessage(message string, code string, options ...Option) XError
28 | WithError(err error, code string, options ...Option) XError
29 | WithOptions(code string, options ...Option) XError
30 | }
31 |
32 | type xError struct {
33 | parent XError
34 | current error
35 | asMessage bool
36 | code string
37 | options []Option
38 | }
39 |
40 | func New(message string, code string, options ...Option) XError {
41 | return &xError{
42 | parent: nil,
43 | current: errors.New(message),
44 | asMessage: true,
45 | code: code,
46 | options: options,
47 | }
48 | }
49 |
50 | func ToXError(err error, code string, options ...Option) XError {
51 | return &xError{
52 | parent: nil,
53 | current: err,
54 | asMessage: false,
55 | code: code,
56 | options: options,
57 | }
58 | }
59 |
60 | func (x *xError) Error() string {
61 | if x.parent == nil {
62 | if x.code == "" {
63 | return x.current.Error()
64 | }
65 | return x.current.Error() + " (" + x.code + ")"
66 | }
67 | if x.code == "" {
68 | return x.parent.Error() + "\n" + x.current.Error()
69 | }
70 | return x.parent.Error() + "\n" + x.current.Error() + " (" + x.code + ")"
71 | }
72 |
73 | func (x *xError) ErrorCode() string {
74 | return x.code
75 | }
76 |
77 | func (x *xError) ErrorOptions() []Option {
78 | return x.options
79 | }
80 |
81 | func (x *xError) WithMessage(message string, code string, options ...Option) XError {
82 | return &xError{
83 | parent: x,
84 | current: errors.New(message),
85 | asMessage: true,
86 | code: code,
87 | options: options,
88 | }
89 | }
90 |
91 | func (x *xError) WithError(err error, code string, options ...Option) XError {
92 | return &xError{
93 | parent: x,
94 | current: err,
95 | asMessage: false,
96 | code: code,
97 | options: options,
98 | }
99 | }
100 |
101 | func (x *xError) WithOptions(code string, options ...Option) XError {
102 | return &xError{
103 | parent: nil,
104 | current: x,
105 | asMessage: false,
106 | code: code,
107 | options: options,
108 | }
109 | }
110 |
111 | func (x *xError) Unwrap() []error {
112 | if x.parent == nil {
113 | if x.asMessage {
114 | return nil
115 | }
116 | return []error{x.current}
117 | }
118 | if x.asMessage {
119 | return []error{x.parent}
120 | }
121 | return []error{x.parent, x.current}
122 | }
123 |
124 | var (
125 | _ error = (*xError)(nil)
126 | _ XError = (*xError)(nil)
127 | _ interface{ Unwrap() []error } = (*xError)(nil)
128 | )
129 |
--------------------------------------------------------------------------------
/processor.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024-present Volodymyr Konstanchuk and contributors
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package componego
18 |
19 | // Processor is an interface that describes the validation or conversion of data to another format.
20 | type Processor interface {
21 | // ProcessData validates and converts data.
22 | // The method returns a new value or an error.
23 | ProcessData(value any) (any, error)
24 | }
25 |
--------------------------------------------------------------------------------
/scripts/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22-alpine
2 |
3 | ENV PIP_ROOT_USER_ACTION=ignore
4 |
5 | RUN apk add --update bash git make gcc libc-dev binutils-gold && \
6 | apk add --update --no-cache python3-dev~3.10 --repository=https://dl-cdn.alpinelinux.org/alpine/v3.17/main && \
7 | ln -sf python3 /usr/bin/python && \
8 | python3 -m ensurepip && \
9 | pip3 install --no-cache --upgrade pip setuptools pre-commit
10 |
11 | CMD ["sleep", "infinity"]
12 |
--------------------------------------------------------------------------------
/tools/create-contributor-env.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # Copyright 2024-present Volodymyr Konstanchuk and contributors
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 |
20 | check_directory() {
21 | if [ -e "$1" ]; then
22 | echo "ERROR: could not create a project in the current directory because we are inside another project: $1"
23 | exit 1
24 | fi
25 | }
26 |
27 | directory=$(pwd)
28 | while [ "$directory" != "/" ]; do
29 | check_directory "$directory/go.mod"
30 | check_directory "$directory/.git"
31 | directory=$(dirname "$directory")
32 | done
33 |
34 | mkdir -p "componego-contributor-env"
35 | cd "componego-contributor-env"
36 |
37 | cat >docker-compose.yml <