diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..eac69ce --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +layout_poetry diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 46b1f9b..417c4a2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -31,6 +31,9 @@ jobs: with: go-version: 1.16 + - name: Install Poetry + run: curl -sSL https://install.python-poetry.org | python3 - + - name: Install dependencies run: make setup diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fb2ac01..167b31b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,5 @@ name: Main + on: push: branches: @@ -7,6 +8,7 @@ on: - '**' - '!v[0-9]+.[0-9]+.[0-9]+' pull_request: + jobs: test_build: name: Test and build @@ -14,11 +16,19 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + - name: Export GOBIN uses: actions/setup-go@v2 with: go-version: 1.16 + + - name: Install Poetry + run: curl -sSL https://install.python-poetry.org | python3 - + - name: Install dependencies run: make setup + - name: Run tests - run: make test + run: | + source $(poetry env info --path)/bin/activate + make test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b6f8792..cc69713 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,11 +25,16 @@ jobs: with: go-version: 1.16 + - name: Install Poetry + run: curl -sSL https://install.python-poetry.org | python3 - + - name: Install dependencies run: make setup - name: Run tests - run: make test + run: | + source $(poetry env info --path)/bin/activate + make test - name: Log in to the Container registry uses: docker/login-action@v1 @@ -53,3 +58,12 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 63ad616..e8efc28 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ out/ *.db config.yml +docs/ ### Go # Binaries for programs and plugins @@ -19,8 +20,154 @@ config.yml # Dependency directories (remove the comment below to include it) # vendor/ +### Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +### VisualStudioCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json *.code-workspace -# Build documentation +# Local History for Visual Studio Code +.history/ -docs/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7245332 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/semgrep-rules"] + path = tests/semgrep-rules + url = https://github.com/returntocorp/semgrep-rules diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..144bdc2 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,19 @@ +builds: + - id: pushbits + main: ./cmd/pushbits + goos: + - linux + goarch: + - amd64 + - 386 + - arm + - arm64 + +checksum: + algorithm: sha256 + +archives: + - id: pushbits + builds: + - pushbits + format: tar.gz diff --git a/Makefile b/Makefile index e610231..520967c 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,45 @@ +# References: +# [1] Needed so the Go files of semgrep-rules do not interfere with static analysis + +DOCS_DIR := ./docs +OUT_DIR := ./out +TESTS_DIR := ./tests + +SEMGREP_MODFILE := $(TESTS_DIR)/semgrep-rules/go.mod + .PHONY: build build: - mkdir -p ./out - go build -ldflags="-w -s" -o ./out/pushbits ./cmd/pushbits + mkdir -p $(OUT_DIR) + go build -ldflags="-w -s" -o $(OUT_DIR)/pushbits ./cmd/pushbits + +.PHONY: clean +clean: + rm -rf $(DOCS_DIR) + rm -rf $(OUT_DIR) + rm -rf $(SEMGREP_MODFILE) .PHONY: test test: - stdout=$$(gofmt -l . 2>&1); if [ "$$stdout" ]; then exit 1; fi + touch $(SEMGREP_MODFILE) # See [1]. + go fmt ./... go vet ./... - gocyclo -over 10 $(shell find . -iname '*.go' -type f) + gocyclo -over 10 $(shell find . -type f \( -iname '*.go' ! -path "./tests/semgrep-rules/*" \)) staticcheck ./... go test -v -cover ./... + gosec -exclude-dir=tests ./... + semgrep --lang=go --config=tests/semgrep-rules/go --metrics=off + rm -rf $(SEMGREP_MODFILE) # See [1]. @printf '\n%s\n' "> Test successful" .PHONY: setup setup: + git submodule update --init --recursive go install github.com/fzipp/gocyclo/cmd/gocyclo@latest + go install github.com/securego/gosec/v2/cmd/gosec@latest go install github.com/swaggo/swag/cmd/swag@latest go install honnef.co/go/tools/cmd/staticcheck@latest + poetry install .PHONY: swag swag: - swag init --parseDependency=true -d . -g cmd/pushbits/main.go + swag init --parseDependency=true --exclude $(TESTS_DIR) -g cmd/pushbits/main.go diff --git a/README.md b/README.md index 68af19d..b38cd80 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@

PushBits

-

- Receive your important notifications immediately, over Matrix. -

+

Receive your important notifications immediately, over Matrix.

PushBits enables you to send push notifications via a simple web API, and delivers them to your users.

@@ -31,21 +29,7 @@ It enables you to send notifications via a simple web API, and delivers them to This is similar to what [Pushover](https://pushover.net/) and [Gotify](https://gotify.net/) offer, but it does not require an additional app. The vision is to have compatibility with Gotify on the sending side, while on the receiving side an established service is used. -This has the advantages that -- sending plugins written for Gotify (like those for [Watchtower](https://containrrr.dev/watchtower/) and [Jellyfin](https://jellyfin.org/)) as well as -- receiving clients written for Matrix -can be reused. - -### Why Matrix instead of X? - -This project totally would've used Signal if it would offer a proper API. -Sadly, neither [Signal](https://signal.org/) nor [WhatsApp](https://www.whatsapp.com/) come with an API (at the time of writing) through which PushBits could interact. - -In [Telegram](https://telegram.org/) there is an API to run bots, but these are limited in that they cannot create chats by themselves. -If you insist on going with Telegram, have a look at [webhook2telegram](https://github.com/muety/webhook2telegram). - -The idea of a federated, synchronized but yet end-to-end encrypted protocol is awesome, but its clients simply aren't really there yet. -Still, if you haven't tried it yet, we'd encourage you to check it out. +This has the advantages that we need to maintain neither plugins (like those for [Watchtower](https://containrrr.dev/watchtower/) and [Jellyfin](https://jellyfin.org/)) nor clients. ## 🤘 Features @@ -57,18 +41,38 @@ Still, if you haven't tried it yet, we'd encourage you to check it out. - [ ] Two-factor authentication, [issue](https://github.com/pushbits/server/issues/19) - [ ] Bi-directional key verification, [issue](https://github.com/pushbits/server/issues/20) -## 👮 Acknowledgments +## 👮 License and Acknowledgments -The idea for this software and most parts of the initial source are heavily inspired by [Gotify](https://gotify.net/). -Many thanks to [jmattheis](https://jmattheis.de/) for his well-structured code. +Please refer to [the LICENSE file](LICENSE) to learn more about the license of this code. +It applies only where not specified differently. -## 💻 Development +The idea for this software was inspired by [Gotify](https://gotify.net/). -The source code is located on [GitHub](https://github.com/pushbits/server). -You can retrieve it by checking out the repository as follows. +## 💻 Development and Contributions +The source code is located [on GitHub](https://github.com/pushbits/server). +You can retrieve it by checking out the repository as follows: ```bash git clone https://github.com/pushbits/server.git ``` -[![Stargazers over time](https://starchart.cc/pushbits/server.svg)](https://starchart.cc/pushbits/server) +:wrench: **Want to contribute?** +Before moving forward, please refer to [our contribution guidelines](CONTRIBUTING.md). + +:mailbox: **Found a security vulnerability?** +Check [this document](SECURITY.md) for information on how you can bring it to our attention. + +:star: **Like fancy graphs?** See [our stargazers over time](https://starchart.cc/pushbits/server). + +## ❓ Frequently Asked Questions (FAQ) + +### Why Matrix instead of X? + +This project totally would've used Signal if it would offer a proper API. +Sadly, neither [Signal](https://signal.org/) nor [WhatsApp](https://www.whatsapp.com/) come with an API (at the time of writing) through which PushBits could interact. + +In [Telegram](https://telegram.org/) there is an API to run bots, but these are limited in that they cannot create chats by themselves. +If you insist on going with Telegram, have a look at [webhook2telegram](https://github.com/muety/webhook2telegram). + +The idea of a federated, synchronized but yet end-to-end encrypted protocol is awesome, but its clients simply aren't really there yet. +Still, if you haven't tried it yet, we'd encourage you to check it out. diff --git a/SECURITY.md b/SECURITY.md index 22ef60a..2d399ac 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -Only the latest version is supported. +Because we are a small team, only the latest version is supported. ## Reporting a Vulnerability diff --git a/cmd/pushbits/main.go b/cmd/pushbits/main.go index 254065e..30d9c50 100644 --- a/cmd/pushbits/main.go +++ b/cmd/pushbits/main.go @@ -77,5 +77,8 @@ func main() { engine := router.Create(c.Debug, cm, db, dp) - runner.Run(engine, c.HTTP.ListenAddress, c.HTTP.Port) + err = runner.Run(engine, c.HTTP.ListenAddress, c.HTTP.Port) + if err != nil { + log.Fatal(err) + } } diff --git a/internal/api/application.go b/internal/api/application.go index 9914924..341b588 100644 --- a/internal/api/application.go +++ b/internal/api/application.go @@ -35,7 +35,11 @@ func (h *ApplicationHandler) registerApplication(ctx *gin.Context, a *model.Appl } a.MatrixID = channelID - h.DB.UpdateApplication(a) + + err = h.DB.UpdateApplication(a) + if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + return err + } return nil } @@ -55,7 +59,6 @@ func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User, if err := h.registerApplication(ctx, &application, u); err != nil { err := h.DB.DeleteApplication(&application) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { log.Printf("Cannot delete application with ID %d.", application.ID) } diff --git a/internal/api/notification.go b/internal/api/notification.go index c04df4d..4198880 100644 --- a/internal/api/notification.go +++ b/internal/api/notification.go @@ -4,7 +4,6 @@ import ( "log" "net/http" "net/url" - "strings" "time" "github.com/pushbits/server/internal/authentication" @@ -45,23 +44,17 @@ type NotificationHandler struct { // @Failure 500,404,403 "" // @Router /message [post] func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { - var notification model.Notification + application := authentication.GetApplication(ctx) + log.Printf("Sending notification for application %s.", application.Name) + var notification model.Notification if err := ctx.Bind(¬ification); err != nil { return } - application := authentication.GetApplication(ctx) - log.Printf("Sending notification for application %s.", application.Name) - - notification.ApplicationID = application.ID - if strings.TrimSpace(notification.Title) == "" { - notification.Title = application.Name - } - notification.Date = time.Now() + notification.Sanitize(application) messageID, err := h.DP.SendNotification(application, ¬ification) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { return } @@ -86,8 +79,9 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { // @Router /message/{message_id} [DELETE] func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) { application := authentication.GetApplication(ctx) - id, err := getMessageID(ctx) + log.Printf("Deleting notification for application %s.", application.Name) + id, err := getMessageID(ctx) if success := successOrAbort(ctx, http.StatusUnprocessableEntity, err); !success { return } diff --git a/internal/api/user.go b/internal/api/user.go index 385c443..3dca53f 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -44,6 +44,8 @@ func (h *UserHandler) deleteApplications(ctx *gin.Context, u *model.User) error } for _, application := range applications { + application := application // See https://stackoverflow.com/a/68247837 + if err := h.AH.deleteApplication(ctx, &application, u); err != nil { return err } @@ -59,6 +61,8 @@ func (h *UserHandler) updateChannels(ctx *gin.Context, u *model.User, matrixID s } for _, application := range applications { + application := application // See https://stackoverflow.com/a/68247837 + err := h.DP.DeregisterApplication(&application, u) if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { return err @@ -68,6 +72,8 @@ func (h *UserHandler) updateChannels(ctx *gin.Context, u *model.User, matrixID s u.MatrixID = matrixID for _, application := range applications { + application := application // See https://stackoverflow.com/a/68247837 + err := h.AH.registerApplication(ctx, &application, u) if err != nil { return err @@ -169,10 +175,10 @@ func (h *UserHandler) GetUsers(ctx *gin.Context) { return } - var externalUsers []*model.ExternalUser + externalUsers := make([]*model.ExternalUser, len(users)) - for _, user := range users { - externalUsers = append(externalUsers, user.IntoExternalUser()) + for i, user := range users { + externalUsers[i] = user.IntoExternalUser() } ctx.JSON(http.StatusOK, &externalUsers) diff --git a/internal/authentication/credentials/hibp.go b/internal/authentication/credentials/hibp.go index 81680a8..8522497 100644 --- a/internal/authentication/credentials/hibp.go +++ b/internal/authentication/credentials/hibp.go @@ -1,7 +1,7 @@ package credentials import ( - "crypto/sha1" + "crypto/sha1" //#nosec G505 -- False positive, see the use below. "fmt" "io/ioutil" "log" @@ -21,7 +21,8 @@ func IsPasswordPwned(password string) (bool, error) { return true, nil } - hash := sha1.Sum([]byte(password)) + // nosemgrep: tests.semgrep-rules.go.lang.security.audit.crypto.insecure-module-used, tests.semgrep-rules.go.lang.security.audit.crypto.use-of-sha1 + hash := sha1.Sum([]byte(password)) //#nosec G401 -- False positive, only the first 5 bytes are transmitted. hashStr := fmt.Sprintf("%X", hash) lookup := hashStr[0:5] match := hashStr[5:] diff --git a/internal/database/database.go b/internal/database/database.go index c7f52ff..6ea7b81 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -24,8 +24,11 @@ type Database struct { } func createFileDir(file string) { - if _, err := os.Stat(filepath.Dir(file)); os.IsNotExist(err) { - if err := os.MkdirAll(filepath.Dir(file), 0775); err != nil { + dir := filepath.Dir(file) + + if _, err := os.Stat(dir); os.IsNotExist(err) { + // nosemgrep: tests.semgrep-rules.go.lang.correctness.permissions.incorrect-default-permission + if err := os.MkdirAll(dir, 0750); err != nil { panic(err) } } @@ -67,14 +70,20 @@ func Create(cm *credentials.Manager, dialect, connection string) (*Database, err sql.SetConnMaxLifetime(9 * time.Minute) } - db.AutoMigrate(&model.User{}, &model.Application{}) + err = db.AutoMigrate(&model.User{}, &model.Application{}) + if err != nil { + return nil, err + } return &Database{gormdb: db, sqldb: sql, credentialsManager: cm}, nil } // Close closes the database connection. func (d *Database) Close() { - d.sqldb.Close() + err := d.sqldb.Close() + if err != nil { + log.Printf("Error while closing database: %s", err) + } } // Populate fills the database with initial information like the admin user. @@ -111,12 +120,16 @@ func (d *Database) RepairChannels(dp Dispatcher) error { } for _, user := range users { + user := user // See https://stackoverflow.com/a/68247837 + applications, err := d.GetApplications(&user) if err != nil { return err } for _, application := range applications { + application := application // See https://stackoverflow.com/a/68247837 + if err := dp.UpdateApplication(&application); err != nil { return err } diff --git a/internal/dispatcher/dispatcher.go b/internal/dispatcher/dispatcher.go index 25d5df5..528ba09 100644 --- a/internal/dispatcher/dispatcher.go +++ b/internal/dispatcher/dispatcher.go @@ -40,8 +40,12 @@ func Create(homeserver, username, password string, formatting configuration.Form // Close closes the dispatcher connection. func (d *Dispatcher) Close() { log.Printf("Logging out.") + + _, err := d.mautrixClient.Logout() + if err != nil { + log.Printf("Error while logging out: %s", err) + } - d.mautrixClient.Logout() d.mautrixClient.ClearCredentials() log.Printf("Successfully logged out.") diff --git a/internal/model/notification.go b/internal/model/notification.go index 6239236..3fbb44f 100644 --- a/internal/model/notification.go +++ b/internal/model/notification.go @@ -1,6 +1,7 @@ package model import ( + "strings" "time" ) @@ -16,6 +17,17 @@ type Notification struct { Date time.Time `json:"date"` } +// Sanitize sets explicit defaults for a notification. +func (n *Notification) Sanitize(application *Application) { + n.ID = "" + n.UrlEncodedID = "" + n.ApplicationID = application.ID + if strings.TrimSpace(n.Title) == "" { + n.Title = application.Name + } + n.Date = time.Now() +} + // DeleteNotification holds information like the message ID of a deletion notification. type DeleteNotification struct { ID string `json:"id" form:"id"` diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 2043179..911f778 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -7,6 +7,11 @@ import ( ) // Run starts the Gin engine. -func Run(engine *gin.Engine, address string, port int) { - engine.Run(fmt.Sprintf("%s:%d", address, port)) +func Run(engine *gin.Engine, address string, port int) error { + err := engine.Run(fmt.Sprintf("%s:%d", address, port)) + if err != nil { + return err + } + + return nil } diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..bbdf101 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,439 @@ +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "bracex" +version = "2.2.1" +description = "Bash style brace expander." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "click-option-group" +version = "0.5.3" +description = "Option groups missing in Click" +category = "dev" +optional = false +python-versions = ">=3.6,<4" + +[package.dependencies] +Click = ">=7.0,<9" + +[package.extras] +docs = ["sphinx (>=2.3,<3)", "pallets-sphinx-themes", "m2r"] +tests = ["coverage (<6)", "pytest", "pytest-cov", "coveralls"] + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.11.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + +[[package]] +name = "importlib-resources" +version = "5.4.0" +description = "Read resources from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[[package]] +name = "jsonschema" +version = "4.4.0" +description = "An implementation of JSON Schema validation for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=17.4.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format_nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "peewee" +version = "3.14.8" +description = "a little orm" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyrsistent" +version = "0.18.1" +description = "Persistent/Functional/Immutable data structures" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "ruamel.yaml" +version = "0.17.21" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +category = "dev" +optional = false +python-versions = ">=3" + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel.yaml.clib" +version = "0.2.6" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "semgrep" +version = "0.82.0" +description = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.3.0" +click = ">=8.0.1" +click-option-group = ">=0.5.3" +colorama = ">=0.4.3" +jsonschema = ">=3.2.0,<5" +packaging = ">=20.4" +peewee = ">=3.14.4,<3.15.0" +requests = ">=2.22.0" +"ruamel.yaml" = ">=0.16.0,<0.18" +tqdm = ">=4.46.1" +wcmatch = "8.3" + +[[package]] +name = "tqdm" +version = "4.62.3" +description = "Fast, Extensible Progress Meter" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +telegram = ["requests"] + +[[package]] +name = "typing-extensions" +version = "4.1.0" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "urllib3" +version = "1.26.8" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wcmatch" +version = "8.3" +description = "Wildcard/glob file name matcher." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +bracex = ">=2.1.1" + +[[package]] +name = "zipp" +version = "3.7.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "792ec3aa3a1641a70a6cd82c2399211c85c8a5cad5522f260c86ef407f8482ce" + +[metadata.files] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +bracex = [ + {file = "bracex-2.2.1-py3-none-any.whl", hash = "sha256:096c4b788bf492f7af4e90ef8b5bcbfb99759ae3415ea1b83c9d29a5ed8f9a94"}, + {file = "bracex-2.2.1.tar.gz", hash = "sha256:1c8d1296e00ad9a91030ccb4c291f9e4dc7c054f12c707ba3c5ff3e9a81bcd21"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] +click = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +click-option-group = [ + {file = "click-option-group-0.5.3.tar.gz", hash = "sha256:a6e924f3c46b657feb5b72679f7e930f8e5b224b766ab35c91ae4019b4e0615e"}, + {file = "click_option_group-0.5.3-py3-none-any.whl", hash = "sha256:9653a2297357335d7325a1827e71ac1245d91c97d959346a7decabd4a52d5354"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.11.0-py3-none-any.whl", hash = "sha256:6affcdb3aec542dd98df8211e730bba6c5f2bec8288d47bacacde898f548c9ad"}, + {file = "importlib_metadata-4.11.0.tar.gz", hash = "sha256:9e5e553bbba1843cb4a00823014b907616be46ee503d2b9ba001d214a8da218f"}, +] +importlib-resources = [ + {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, + {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, +] +jsonschema = [ + {file = "jsonschema-4.4.0-py3-none-any.whl", hash = "sha256:77281a1f71684953ee8b3d488371b162419767973789272434bbc3f29d9c8823"}, + {file = "jsonschema-4.4.0.tar.gz", hash = "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +peewee = [ + {file = "peewee-3.14.8.tar.gz", hash = "sha256:01bd7f734defb08d7a3346a0c0ca7011bc8d0d685934ec0e001b3371d522ec53"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pyrsistent = [ + {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"}, + {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"}, + {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"}, + {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +"ruamel.yaml" = [ + {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, + {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, +] +"ruamel.yaml.clib" = [ + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, + {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, +] +semgrep = [ + {file = "semgrep-0.82.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl", hash = "sha256:1750f2b755ffc7db4690e6c5990e03e03729e6de4d4c4edf3f39435020244989"}, + {file = "semgrep-0.82.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl", hash = "sha256:037124db822a0c9235ea6cfbc637f06771ca2882d181dfdbd460c430bf51d7ec"}, + {file = "semgrep-0.82.0.tar.gz", hash = "sha256:aa6c57d08602ffde3bf36159c0325d6d72f70dc634cd237f30ff46f7d6b78be3"}, +] +tqdm = [ + {file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"}, + {file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"}, +] +typing-extensions = [ + {file = "typing_extensions-4.1.0-py3-none-any.whl", hash = "sha256:c13180fbaa7cd97065a4915ceba012bdb31dc34743e63ddee16360161d358414"}, + {file = "typing_extensions-4.1.0.tar.gz", hash = "sha256:ba97c5143e5bb067b57793c726dd857b1671d4b02ced273ca0538e71ff009095"}, +] +urllib3 = [ + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, +] +wcmatch = [ + {file = "wcmatch-8.3-py3-none-any.whl", hash = "sha256:7141d2c85314253f16b38cb3d6cc0fb612918d407e1df3ccc2be7c86cc259c22"}, + {file = "wcmatch-8.3.tar.gz", hash = "sha256:371072912398af61d1e4e78609e18801c6faecd3cb36c54c82556a60abc965db"}, +] +zipp = [ + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b830e65 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[tool.poetry] +name = "pushbits" +version = "0.0.0" +description = "" +authors = [] + +[tool.poetry.dependencies] +python = "^3.7" + +[tool.poetry.dev-dependencies] +semgrep = "^0.82.0" diff --git a/tests/semgrep-rules b/tests/semgrep-rules new file mode 160000 index 0000000..46e040f --- /dev/null +++ b/tests/semgrep-rules @@ -0,0 +1 @@ +Subproject commit 46e040f844c2b1c40599c8d4f1cdf277b197329e