From 40768b171222521095d1af7b083fac4961ab5dbc Mon Sep 17 00:00:00 2001 From: Marcus Young Date: Fri, 12 Aug 2022 15:12:33 -0500 Subject: [PATCH 1/2] Refactor to go --- .claire.yml | 6 +- .github/workflows/deploy.yml | 28 +- .github/workflows/release.yml | 99 +- .github/workflows/sast.yml | 23 +- .github/workflows/tests.yml | 37 +- .gitignore | 3 + .pre-commit-config.yaml | 21 +- .pylintrc | 451 - .tool-versions | 2 +- CHANGELOG | 6 + Dockerfile | 27 +- Makefile | 24 +- README.md | 66 +- SECURITY.md | 2 +- _examples/example_config.json | 15 + _examples/example_config.toml | 12 + ibeacon.png => assets/ibeacon.png | Bin assets/influxdb.png | Bin 111313 -> 0 bytes cli/args.go | 24 + datadog.png | Bin 12673 -> 0 bytes emitters/datadog.go | 52 + emitters/datadog_test.go | 34 + emitters/models.go | 9 + emitters/sqlite.go | 59 + emitters/sqlite_test.go | 30 + emitters/webhook.go | 88 + emitters/webhook_test.go | 119 + entrypoint.sh | 2 - go.mod | 43 + go.sum | 535 + goodcheck.yml | 6 - main.go | 113 + poetry.lock | 1475 - pyproject.toml | 34 - requirements-dev.txt | 76 - requirements.txt | 34 - setup.py | 38 - sider.yml | 5 - tests/mock_config_parser.py | 58 - tests/mock_config_parser_mac.py | 29 - tests/mock_keys.py | 0 tests/test_blescan.py | 16 - tests/test_cli.py | 95 - tests/test_common.py | 14 - tests/test_datadog.py | 43 - tests/test_google.py | 87 - tests/test_influxdb.py | 49 - tests/test_prometheus.py | 40 - tests/test_sqlite.py | 30 - tests/test_stdout.py | 17 - tests/test_tilt_device.py | 13 - tests/test_tilty.py | 262 - tests/test_webhook.py | 221 - tilt/config.go | 69 + tilt/device.go | 24 + tilt/logger.go | 38 + tilty/__init__.py | 0 tilty/blescan.py | 77 - tilty/cli.py | 159 - tilty/common.py | 19 - tilty/constants.py | 15 - tilty/emitters/__init__.py | 0 tilty/emitters/datadog.py | 56 - tilty/emitters/google.py | 87 - tilty/emitters/influxdb.py | 72 - tilty/emitters/prometheus.py | 76 - tilty/emitters/sqlite.py | 52 - tilty/emitters/stdout.py | 29 - tilty/emitters/webhook.py | 98 - tilty/exceptions.py | 10 - tilty/tilt_device.py | 70 - tilty/tilty.py | 54 - tox.ini | 19 - .../DataDog/datadog-go/v5/LICENSE.txt | 19 + .../DataDog/datadog-go/v5/statsd/README.md | 4 + .../datadog-go/v5/statsd/aggregator.go | 289 + .../DataDog/datadog-go/v5/statsd/buffer.go | 195 + .../datadog-go/v5/statsd/buffer_pool.go | 40 + .../v5/statsd/buffered_metric_context.go | 82 + .../DataDog/datadog-go/v5/statsd/container.go | 82 + .../DataDog/datadog-go/v5/statsd/event.go | 75 + .../DataDog/datadog-go/v5/statsd/fnv1a.go | 39 + .../DataDog/datadog-go/v5/statsd/format.go | 272 + .../DataDog/datadog-go/v5/statsd/metrics.go | 181 + .../DataDog/datadog-go/v5/statsd/noop.go | 96 + .../DataDog/datadog-go/v5/statsd/options.go | 348 + .../DataDog/datadog-go/v5/statsd/pipe.go | 13 + .../datadog-go/v5/statsd/pipe_windows.go | 75 + .../DataDog/datadog-go/v5/statsd/sender.go | 111 + .../datadog-go/v5/statsd/service_check.go | 57 + .../DataDog/datadog-go/v5/statsd/statsd.go | 736 + .../DataDog/datadog-go/v5/statsd/telemetry.go | 274 + .../DataDog/datadog-go/v5/statsd/udp.go | 34 + .../DataDog/datadog-go/v5/statsd/uds.go | 88 + .../datadog-go/v5/statsd/uds_windows.go | 14 + .../DataDog/datadog-go/v5/statsd/utils.go | 32 + .../DataDog/datadog-go/v5/statsd/worker.go | 150 + .../github.com/Microsoft/go-winio/.gitignore | 1 + .../github.com/Microsoft/go-winio/CODEOWNERS | 1 + vendor/github.com/Microsoft/go-winio/LICENSE | 22 + .../github.com/Microsoft/go-winio/README.md | 22 + .../github.com/Microsoft/go-winio/backup.go | 280 + vendor/github.com/Microsoft/go-winio/ea.go | 137 + vendor/github.com/Microsoft/go-winio/file.go | 323 + .../github.com/Microsoft/go-winio/fileinfo.go | 73 + .../github.com/Microsoft/go-winio/hvsock.go | 307 + vendor/github.com/Microsoft/go-winio/pipe.go | 517 + .../Microsoft/go-winio/pkg/guid/guid.go | 237 + .../Microsoft/go-winio/privilege.go | 203 + .../github.com/Microsoft/go-winio/reparse.go | 128 + vendor/github.com/Microsoft/go-winio/sd.go | 98 + .../github.com/Microsoft/go-winio/syscall.go | 3 + .../Microsoft/go-winio/zsyscall_windows.go | 427 + .../github.com/akamensky/argparse/.gitignore | 16 + .../github.com/akamensky/argparse/.travis.yml | 9 + vendor/github.com/akamensky/argparse/LICENSE | 21 + .../github.com/akamensky/argparse/README.md | 230 + .../github.com/akamensky/argparse/argparse.go | 788 + .../github.com/akamensky/argparse/argument.go | 580 + .../github.com/akamensky/argparse/command.go | 238 + .../github.com/akamensky/argparse/errors.go | 14 + .../github.com/akamensky/argparse/extras.go | 25 + vendor/github.com/akamensky/argparse/misc.go | 13 + vendor/github.com/davecgh/go-spew/LICENSE | 15 + .../github.com/davecgh/go-spew/spew/bypass.go | 145 + .../davecgh/go-spew/spew/bypasssafe.go | 38 + .../github.com/davecgh/go-spew/spew/common.go | 341 + .../github.com/davecgh/go-spew/spew/config.go | 306 + vendor/github.com/davecgh/go-spew/spew/doc.go | 211 + .../github.com/davecgh/go-spew/spew/dump.go | 509 + .../github.com/davecgh/go-spew/spew/format.go | 419 + .../github.com/davecgh/go-spew/spew/spew.go | 148 + .../fsnotify/fsnotify/.editorconfig | 12 + .../fsnotify/fsnotify/.gitattributes | 1 + .../github.com/fsnotify/fsnotify/.gitignore | 6 + vendor/github.com/fsnotify/fsnotify/.mailmap | 2 + vendor/github.com/fsnotify/fsnotify/AUTHORS | 62 + .../github.com/fsnotify/fsnotify/CHANGELOG.md | 357 + .../fsnotify/fsnotify/CONTRIBUTING.md | 60 + vendor/github.com/fsnotify/fsnotify/LICENSE | 28 + vendor/github.com/fsnotify/fsnotify/README.md | 120 + vendor/github.com/fsnotify/fsnotify/fen.go | 38 + .../github.com/fsnotify/fsnotify/fsnotify.go | 69 + .../fsnotify/fsnotify/fsnotify_unsupported.go | 36 + .../github.com/fsnotify/fsnotify/inotify.go | 351 + .../fsnotify/fsnotify/inotify_poller.go | 187 + vendor/github.com/fsnotify/fsnotify/kqueue.go | 535 + .../fsnotify/fsnotify/open_mode_bsd.go | 12 + .../fsnotify/fsnotify/open_mode_darwin.go | 13 + .../github.com/fsnotify/fsnotify/windows.go | 586 + vendor/github.com/go-kit/kit/LICENSE | 22 + vendor/github.com/go-kit/kit/log/README.md | 160 + vendor/github.com/go-kit/kit/log/doc.go | 118 + .../github.com/go-kit/kit/log/json_logger.go | 15 + vendor/github.com/go-kit/kit/log/level/doc.go | 25 + .../github.com/go-kit/kit/log/level/level.go | 120 + vendor/github.com/go-kit/kit/log/log.go | 51 + .../go-kit/kit/log/logfmt_logger.go | 15 + .../github.com/go-kit/kit/log/nop_logger.go | 8 + vendor/github.com/go-kit/kit/log/stdlib.go | 54 + vendor/github.com/go-kit/kit/log/sync.go | 37 + vendor/github.com/go-kit/kit/log/value.go | 52 + vendor/github.com/go-kit/log/.gitignore | 15 + vendor/github.com/go-kit/log/LICENSE | 21 + vendor/github.com/go-kit/log/README.md | 151 + vendor/github.com/go-kit/log/doc.go | 116 + vendor/github.com/go-kit/log/json_logger.go | 91 + vendor/github.com/go-kit/log/level/doc.go | 22 + vendor/github.com/go-kit/log/level/level.go | 205 + vendor/github.com/go-kit/log/log.go | 179 + vendor/github.com/go-kit/log/logfmt_logger.go | 62 + vendor/github.com/go-kit/log/nop_logger.go | 8 + vendor/github.com/go-kit/log/stdlib.go | 151 + vendor/github.com/go-kit/log/sync.go | 113 + vendor/github.com/go-kit/log/value.go | 110 + vendor/github.com/go-logfmt/logfmt/.gitignore | 1 + .../github.com/go-logfmt/logfmt/CHANGELOG.md | 48 + vendor/github.com/go-logfmt/logfmt/LICENSE | 22 + vendor/github.com/go-logfmt/logfmt/README.md | 33 + vendor/github.com/go-logfmt/logfmt/decode.go | 237 + vendor/github.com/go-logfmt/logfmt/doc.go | 6 + vendor/github.com/go-logfmt/logfmt/encode.go | 322 + .../github.com/go-logfmt/logfmt/jsonstring.go | 277 + .../go-playground/locales/.gitignore | 24 + .../go-playground/locales/.travis.yml | 26 + .../github.com/go-playground/locales/LICENSE | 21 + .../go-playground/locales/README.md | 172 + .../locales/currency/currency.go | 311 + .../github.com/go-playground/locales/logo.png | Bin 0 -> 37360 bytes .../github.com/go-playground/locales/rules.go | 293 + .../universal-translator/.gitignore | 25 + .../universal-translator/.travis.yml | 27 + .../universal-translator/LICENSE | 21 + .../universal-translator/Makefile | 18 + .../universal-translator/README.md | 89 + .../universal-translator/errors.go | 148 + .../universal-translator/import_export.go | 276 + .../universal-translator/logo.png | Bin 0 -> 16598 bytes .../universal-translator/translator.go | 420 + .../universal_translator.go | 113 + .../go-playground/validator/v10/.gitignore | 30 + .../go-playground/validator/v10/LICENSE | 22 + .../validator/v10/MAINTAINERS.md | 16 + .../go-playground/validator/v10/Makefile | 18 + .../go-playground/validator/v10/README.md | 338 + .../go-playground/validator/v10/baked_in.go | 2521 + .../go-playground/validator/v10/cache.go | 327 + .../validator/v10/country_codes.go | 1132 + .../validator/v10/currency_codes.go | 79 + .../go-playground/validator/v10/doc.go | 1401 + .../go-playground/validator/v10/errors.go | 275 + .../validator/v10/field_level.go | 120 + .../go-playground/validator/v10/logo.png | Bin 0 -> 13443 bytes .../validator/v10/postcode_regexes.go | 173 + .../go-playground/validator/v10/regexes.go | 131 + .../validator/v10/struct_level.go | 175 + .../validator/v10/translations.go | 11 + .../go-playground/validator/v10/util.go | 288 + .../go-playground/validator/v10/validator.go | 486 + .../validator/v10/validator_instance.go | 699 + vendor/github.com/hashicorp/hcl/.gitignore | 9 + vendor/github.com/hashicorp/hcl/.travis.yml | 13 + vendor/github.com/hashicorp/hcl/LICENSE | 354 + vendor/github.com/hashicorp/hcl/Makefile | 18 + vendor/github.com/hashicorp/hcl/README.md | 125 + vendor/github.com/hashicorp/hcl/appveyor.yml | 19 + vendor/github.com/hashicorp/hcl/decoder.go | 729 + vendor/github.com/hashicorp/hcl/hcl.go | 11 + .../github.com/hashicorp/hcl/hcl/ast/ast.go | 219 + .../github.com/hashicorp/hcl/hcl/ast/walk.go | 52 + .../hashicorp/hcl/hcl/parser/error.go | 17 + .../hashicorp/hcl/hcl/parser/parser.go | 532 + .../hashicorp/hcl/hcl/printer/nodes.go | 789 + .../hashicorp/hcl/hcl/printer/printer.go | 66 + .../hashicorp/hcl/hcl/scanner/scanner.go | 652 + .../hashicorp/hcl/hcl/strconv/quote.go | 241 + .../hashicorp/hcl/hcl/token/position.go | 46 + .../hashicorp/hcl/hcl/token/token.go | 219 + .../hashicorp/hcl/json/parser/flatten.go | 117 + .../hashicorp/hcl/json/parser/parser.go | 313 + .../hashicorp/hcl/json/scanner/scanner.go | 451 + .../hashicorp/hcl/json/token/position.go | 46 + .../hashicorp/hcl/json/token/token.go | 118 + vendor/github.com/hashicorp/hcl/lex.go | 38 + vendor/github.com/hashicorp/hcl/parse.go | 39 + vendor/github.com/jarcoal/httpmock/.gitignore | 22 + vendor/github.com/jarcoal/httpmock/LICENSE | 20 + vendor/github.com/jarcoal/httpmock/README.md | 296 + vendor/github.com/jarcoal/httpmock/any.go | 6 + vendor/github.com/jarcoal/httpmock/doc.go | 81 + vendor/github.com/jarcoal/httpmock/env.go | 13 + vendor/github.com/jarcoal/httpmock/file.go | 57 + .../jarcoal/httpmock/internal/error.go | 35 + .../jarcoal/httpmock/internal/route_key.go | 15 + .../jarcoal/httpmock/internal/stack_tracer.go | 91 + .../jarcoal/httpmock/internal/submatches.go | 22 + .../github.com/jarcoal/httpmock/response.go | 560 + .../github.com/jarcoal/httpmock/transport.go | 1313 + vendor/github.com/leodido/go-urn/.gitignore | 11 + vendor/github.com/leodido/go-urn/.travis.yml | 16 + vendor/github.com/leodido/go-urn/LICENSE | 21 + vendor/github.com/leodido/go-urn/README.md | 55 + vendor/github.com/leodido/go-urn/machine.go | 1691 + .../github.com/leodido/go-urn/machine.go.rl | 159 + vendor/github.com/leodido/go-urn/makefile | 39 + vendor/github.com/leodido/go-urn/urn.go | 86 + .../magiconair/properties/.gitignore | 6 + .../magiconair/properties/.travis.yml | 17 + .../magiconair/properties/CHANGELOG.md | 160 + .../magiconair/properties/LICENSE.md | 24 + .../magiconair/properties/README.md | 128 + .../magiconair/properties/decode.go | 289 + .../github.com/magiconair/properties/doc.go | 156 + .../magiconair/properties/integrate.go | 34 + .../github.com/magiconair/properties/lex.go | 395 + .../github.com/magiconair/properties/load.go | 293 + .../magiconair/properties/parser.go | 86 + .../magiconair/properties/properties.go | 853 + .../magiconair/properties/rangecheck.go | 31 + .../github.com/mattn/go-sqlite3/.codecov.yml | 4 + vendor/github.com/mattn/go-sqlite3/.gitignore | 14 + vendor/github.com/mattn/go-sqlite3/LICENSE | 21 + vendor/github.com/mattn/go-sqlite3/README.md | 591 + vendor/github.com/mattn/go-sqlite3/backup.go | 85 + .../github.com/mattn/go-sqlite3/callback.go | 411 + vendor/github.com/mattn/go-sqlite3/convert.go | 299 + vendor/github.com/mattn/go-sqlite3/doc.go | 135 + vendor/github.com/mattn/go-sqlite3/error.go | 150 + .../mattn/go-sqlite3/sqlite3-binding.c | 241979 +++++++++++++++ .../mattn/go-sqlite3/sqlite3-binding.h | 12937 + vendor/github.com/mattn/go-sqlite3/sqlite3.go | 2266 + .../mattn/go-sqlite3/sqlite3_context.go | 103 + .../mattn/go-sqlite3/sqlite3_func_crypt.go | 120 + .../mattn/go-sqlite3/sqlite3_go18.go | 70 + .../mattn/go-sqlite3/sqlite3_libsqlite3.go | 19 + .../go-sqlite3/sqlite3_load_extension.go | 84 + .../go-sqlite3/sqlite3_load_extension_omit.go | 24 + .../sqlite3_opt_allow_uri_authority.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_app_armor.go | 16 + .../go-sqlite3/sqlite3_opt_column_metadata.go | 21 + .../go-sqlite3/sqlite3_opt_foreign_keys.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_fts5.go | 14 + .../mattn/go-sqlite3/sqlite3_opt_icu.go | 17 + .../go-sqlite3/sqlite3_opt_introspect.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_preupdate.go | 20 + .../go-sqlite3/sqlite3_opt_preupdate_hook.go | 112 + .../go-sqlite3/sqlite3_opt_preupdate_omit.go | 21 + .../go-sqlite3/sqlite3_opt_secure_delete.go | 15 + .../sqlite3_opt_secure_delete_fast.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_stat4.go | 15 + .../go-sqlite3/sqlite3_opt_unlock_notify.c | 85 + .../go-sqlite3/sqlite3_opt_unlock_notify.go | 93 + .../mattn/go-sqlite3/sqlite3_opt_userauth.go | 289 + .../go-sqlite3/sqlite3_opt_userauth_omit.go | 152 + .../go-sqlite3/sqlite3_opt_vacuum_full.go | 15 + .../go-sqlite3/sqlite3_opt_vacuum_incr.go | 15 + .../mattn/go-sqlite3/sqlite3_opt_vtable.go | 720 + .../mattn/go-sqlite3/sqlite3_other.go | 17 + .../mattn/go-sqlite3/sqlite3_solaris.go | 14 + .../mattn/go-sqlite3/sqlite3_trace.go | 287 + .../mattn/go-sqlite3/sqlite3_type.go | 108 + .../go-sqlite3/sqlite3_usleep_windows.go | 41 + .../mattn/go-sqlite3/sqlite3_windows.go | 18 + .../github.com/mattn/go-sqlite3/sqlite3ext.h | 710 + .../mattn/go-sqlite3/static_mock.go | 37 + .../mitchellh/mapstructure/CHANGELOG.md | 96 + .../github.com/mitchellh/mapstructure/LICENSE | 21 + .../mitchellh/mapstructure/README.md | 46 + .../mitchellh/mapstructure/decode_hooks.go | 279 + .../mitchellh/mapstructure/error.go | 50 + .../mitchellh/mapstructure/mapstructure.go | 1540 + vendor/github.com/myoung34/gatt/.gitignore | 3 + vendor/github.com/myoung34/gatt/LICENSE.md | 27 + vendor/github.com/myoung34/gatt/adv.go | 232 + vendor/github.com/myoung34/gatt/attr.go | 160 + vendor/github.com/myoung34/gatt/central.go | 152 + .../myoung34/gatt/central_darwin.go | 70 + .../github.com/myoung34/gatt/central_linux.go | 446 + vendor/github.com/myoung34/gatt/common.go | 399 + vendor/github.com/myoung34/gatt/const.go | 153 + vendor/github.com/myoung34/gatt/device.go | 158 + .../github.com/myoung34/gatt/device_darwin.go | 505 + .../github.com/myoung34/gatt/device_linux.go | 238 + vendor/github.com/myoung34/gatt/doc.go | 88 + .../myoung34/gatt/examples/option/doc.go | 3 + .../gatt/examples/option/option_darwin.go | 11 + .../gatt/examples/option/option_linux.go | 21 + vendor/github.com/myoung34/gatt/known_uuid.go | 122 + .../myoung34/gatt/l2cap_writer_linux.go | 156 + .../github.com/myoung34/gatt/linux/cmd/cmd.go | 995 + .../github.com/myoung34/gatt/linux/const.go | 21 + .../github.com/myoung34/gatt/linux/device.go | 109 + .../github.com/myoung34/gatt/linux/devices.go | 58 + vendor/github.com/myoung34/gatt/linux/doc.go | 5 + .../github.com/myoung34/gatt/linux/evt/evt.go | 382 + .../myoung34/gatt/linux/gioctl/LICENSE.md | 22 + .../myoung34/gatt/linux/gioctl/README.md | 12 + .../myoung34/gatt/linux/gioctl/ioctl.go | 57 + vendor/github.com/myoung34/gatt/linux/hci.go | 397 + .../github.com/myoung34/gatt/linux/l2cap.go | 174 + .../myoung34/gatt/linux/socket/asm.s | 8 + .../gatt/linux/socket/asm_linux_386.s | 33 + .../myoung34/gatt/linux/socket/socket.go | 121 + .../gatt/linux/socket/socket_common.go | 24 + .../gatt/linux/socket/socket_darwin.go | 6 + .../gatt/linux/socket/socket_linux.go | 7 + .../gatt/linux/socket/socket_linux_386.go | 31 + .../myoung34/gatt/linux/util/util.go | 16 + .../github.com/myoung34/gatt/option_darwin.go | 15 + .../github.com/myoung34/gatt/option_linux.go | 87 + vendor/github.com/myoung34/gatt/peripheral.go | 102 + .../myoung34/gatt/peripheral_darwin.go | 277 + .../myoung34/gatt/peripheral_linux.go | 445 + vendor/github.com/myoung34/gatt/readme.md | 115 + vendor/github.com/myoung34/gatt/uuid.go | 86 + vendor/github.com/myoung34/gatt/xpc/LICENSE | 21 + vendor/github.com/myoung34/gatt/xpc/doc.go | 8 + .../myoung34/gatt/xpc/xpc_darwin.go | 350 + .../myoung34/gatt/xpc/xpc_wrapper_darwin.c | 85 + .../myoung34/gatt/xpc/xpc_wrapper_darwin.h | 32 + .../pelletier/go-toml/.dockerignore | 2 + .../github.com/pelletier/go-toml/.gitignore | 5 + .../pelletier/go-toml/CONTRIBUTING.md | 132 + .../github.com/pelletier/go-toml/Dockerfile | 11 + vendor/github.com/pelletier/go-toml/LICENSE | 247 + vendor/github.com/pelletier/go-toml/Makefile | 29 + .../go-toml/PULL_REQUEST_TEMPLATE.md | 5 + vendor/github.com/pelletier/go-toml/README.md | 176 + .../github.com/pelletier/go-toml/SECURITY.md | 19 + .../pelletier/go-toml/azure-pipelines.yml | 188 + .../github.com/pelletier/go-toml/benchmark.sh | 35 + vendor/github.com/pelletier/go-toml/doc.go | 23 + .../pelletier/go-toml/example-crlf.toml | 30 + .../github.com/pelletier/go-toml/example.toml | 30 + vendor/github.com/pelletier/go-toml/fuzz.go | 31 + vendor/github.com/pelletier/go-toml/fuzz.sh | 15 + .../pelletier/go-toml/keysparsing.go | 112 + vendor/github.com/pelletier/go-toml/lexer.go | 1031 + .../github.com/pelletier/go-toml/localtime.go | 287 + .../github.com/pelletier/go-toml/marshal.go | 1308 + .../go-toml/marshal_OrderPreserve_test.toml | 39 + .../pelletier/go-toml/marshal_test.toml | 39 + vendor/github.com/pelletier/go-toml/parser.go | 507 + .../github.com/pelletier/go-toml/position.go | 29 + vendor/github.com/pelletier/go-toml/token.go | 136 + vendor/github.com/pelletier/go-toml/toml.go | 533 + .../github.com/pelletier/go-toml/tomlpub.go | 71 + .../pelletier/go-toml/tomltree_create.go | 155 + .../pelletier/go-toml/tomltree_write.go | 552 + .../pelletier/go-toml/tomltree_writepub.go | 6 + .../pelletier/go-toml/v2/.dockerignore | 2 + .../pelletier/go-toml/v2/.gitattributes | 4 + .../pelletier/go-toml/v2/.gitignore | 6 + .../pelletier/go-toml/v2/.golangci.toml | 84 + .../pelletier/go-toml/v2/.goreleaser.yaml | 111 + .../pelletier/go-toml/v2/CONTRIBUTING.md | 196 + .../pelletier/go-toml/v2/Dockerfile | 5 + .../github.com/pelletier/go-toml/v2/LICENSE | 21 + .../github.com/pelletier/go-toml/v2/README.md | 552 + .../pelletier/go-toml/v2/SECURITY.md | 19 + vendor/github.com/pelletier/go-toml/v2/ci.sh | 279 + .../github.com/pelletier/go-toml/v2/decode.go | 544 + vendor/github.com/pelletier/go-toml/v2/doc.go | 2 + .../github.com/pelletier/go-toml/v2/errors.go | 269 + .../pelletier/go-toml/v2/internal/ast/ast.go | 144 + .../go-toml/v2/internal/ast/builder.go | 51 + .../pelletier/go-toml/v2/internal/ast/kind.go | 69 + .../go-toml/v2/internal/danger/danger.go | 65 + .../go-toml/v2/internal/danger/typeid.go | 23 + .../go-toml/v2/internal/tracker/key.go | 50 + .../go-toml/v2/internal/tracker/seen.go | 356 + .../go-toml/v2/internal/tracker/tracker.go | 1 + .../pelletier/go-toml/v2/localtime.go | 120 + .../pelletier/go-toml/v2/marshaler.go | 962 + .../github.com/pelletier/go-toml/v2/parser.go | 1086 + .../pelletier/go-toml/v2/scanner.go | 269 + .../github.com/pelletier/go-toml/v2/strict.go | 107 + .../github.com/pelletier/go-toml/v2/toml.abnf | 243 + .../github.com/pelletier/go-toml/v2/types.go | 14 + .../pelletier/go-toml/v2/unmarshaler.go | 1205 + .../github.com/pelletier/go-toml/v2/utf8.go | 240 + vendor/github.com/pmezard/go-difflib/LICENSE | 27 + .../pmezard/go-difflib/difflib/difflib.go | 772 + vendor/github.com/spf13/afero/.gitignore | 2 + vendor/github.com/spf13/afero/.travis.yml | 26 + vendor/github.com/spf13/afero/LICENSE.txt | 174 + vendor/github.com/spf13/afero/README.md | 442 + vendor/github.com/spf13/afero/afero.go | 111 + vendor/github.com/spf13/afero/appveyor.yml | 15 + vendor/github.com/spf13/afero/basepath.go | 211 + .../github.com/spf13/afero/cacheOnReadFs.go | 315 + vendor/github.com/spf13/afero/const_bsds.go | 22 + .../github.com/spf13/afero/const_win_unix.go | 26 + .../github.com/spf13/afero/copyOnWriteFs.go | 326 + vendor/github.com/spf13/afero/httpFs.go | 114 + vendor/github.com/spf13/afero/iofs.go | 288 + vendor/github.com/spf13/afero/ioutil.go | 240 + vendor/github.com/spf13/afero/lstater.go | 27 + vendor/github.com/spf13/afero/match.go | 110 + vendor/github.com/spf13/afero/mem/dir.go | 37 + vendor/github.com/spf13/afero/mem/dirmap.go | 43 + vendor/github.com/spf13/afero/mem/file.go | 338 + vendor/github.com/spf13/afero/memmap.go | 404 + vendor/github.com/spf13/afero/os.go | 113 + vendor/github.com/spf13/afero/path.go | 106 + vendor/github.com/spf13/afero/readonlyfs.go | 96 + vendor/github.com/spf13/afero/regexpfs.go | 224 + vendor/github.com/spf13/afero/symlink.go | 55 + vendor/github.com/spf13/afero/unionFile.go | 331 + vendor/github.com/spf13/afero/util.go | 330 + vendor/github.com/spf13/cast/.gitignore | 25 + vendor/github.com/spf13/cast/LICENSE | 21 + vendor/github.com/spf13/cast/Makefile | 40 + vendor/github.com/spf13/cast/README.md | 75 + vendor/github.com/spf13/cast/cast.go | 176 + vendor/github.com/spf13/cast/caste.go | 1476 + .../spf13/cast/timeformattype_string.go | 27 + .../spf13/jwalterweatherman/.gitignore | 24 + .../spf13/jwalterweatherman/LICENSE | 21 + .../spf13/jwalterweatherman/README.md | 148 + .../jwalterweatherman/default_notepad.go | 111 + .../spf13/jwalterweatherman/log_counter.go | 46 + .../spf13/jwalterweatherman/notepad.go | 225 + vendor/github.com/spf13/pflag/.gitignore | 2 + vendor/github.com/spf13/pflag/.travis.yml | 22 + vendor/github.com/spf13/pflag/LICENSE | 28 + vendor/github.com/spf13/pflag/README.md | 296 + vendor/github.com/spf13/pflag/bool.go | 94 + vendor/github.com/spf13/pflag/bool_slice.go | 185 + vendor/github.com/spf13/pflag/bytes.go | 209 + vendor/github.com/spf13/pflag/count.go | 96 + vendor/github.com/spf13/pflag/duration.go | 86 + .../github.com/spf13/pflag/duration_slice.go | 166 + vendor/github.com/spf13/pflag/flag.go | 1239 + vendor/github.com/spf13/pflag/float32.go | 88 + .../github.com/spf13/pflag/float32_slice.go | 174 + vendor/github.com/spf13/pflag/float64.go | 84 + .../github.com/spf13/pflag/float64_slice.go | 166 + vendor/github.com/spf13/pflag/golangflag.go | 105 + vendor/github.com/spf13/pflag/int.go | 84 + vendor/github.com/spf13/pflag/int16.go | 88 + vendor/github.com/spf13/pflag/int32.go | 88 + vendor/github.com/spf13/pflag/int32_slice.go | 174 + vendor/github.com/spf13/pflag/int64.go | 84 + vendor/github.com/spf13/pflag/int64_slice.go | 166 + vendor/github.com/spf13/pflag/int8.go | 88 + vendor/github.com/spf13/pflag/int_slice.go | 158 + vendor/github.com/spf13/pflag/ip.go | 94 + vendor/github.com/spf13/pflag/ip_slice.go | 186 + vendor/github.com/spf13/pflag/ipmask.go | 122 + vendor/github.com/spf13/pflag/ipnet.go | 98 + vendor/github.com/spf13/pflag/string.go | 80 + vendor/github.com/spf13/pflag/string_array.go | 129 + vendor/github.com/spf13/pflag/string_slice.go | 163 + .../github.com/spf13/pflag/string_to_int.go | 149 + .../github.com/spf13/pflag/string_to_int64.go | 149 + .../spf13/pflag/string_to_string.go | 160 + vendor/github.com/spf13/pflag/uint.go | 88 + vendor/github.com/spf13/pflag/uint16.go | 88 + vendor/github.com/spf13/pflag/uint32.go | 88 + vendor/github.com/spf13/pflag/uint64.go | 88 + vendor/github.com/spf13/pflag/uint8.go | 88 + vendor/github.com/spf13/pflag/uint_slice.go | 168 + vendor/github.com/spf13/viper/.editorconfig | 15 + vendor/github.com/spf13/viper/.gitignore | 5 + vendor/github.com/spf13/viper/.golangci.yaml | 96 + vendor/github.com/spf13/viper/LICENSE | 21 + vendor/github.com/spf13/viper/Makefile | 76 + vendor/github.com/spf13/viper/README.md | 874 + .../github.com/spf13/viper/TROUBLESHOOTING.md | 32 + .../spf13/viper/experimental_logger.go | 11 + vendor/github.com/spf13/viper/flags.go | 57 + vendor/github.com/spf13/viper/fs.go | 65 + .../spf13/viper/internal/encoding/decoder.go | 61 + .../viper/internal/encoding/dotenv/codec.go | 61 + .../internal/encoding/dotenv/map_utils.go | 41 + .../spf13/viper/internal/encoding/encoder.go | 60 + .../spf13/viper/internal/encoding/error.go | 7 + .../viper/internal/encoding/hcl/codec.go | 40 + .../viper/internal/encoding/ini/codec.go | 99 + .../viper/internal/encoding/ini/map_utils.go | 74 + .../internal/encoding/javaproperties/codec.go | 86 + .../encoding/javaproperties/map_utils.go | 74 + .../viper/internal/encoding/json/codec.go | 17 + .../viper/internal/encoding/toml/codec.go | 39 + .../viper/internal/encoding/toml/codec2.go | 19 + .../viper/internal/encoding/yaml/codec.go | 14 + .../viper/internal/encoding/yaml/yaml2.go | 14 + .../viper/internal/encoding/yaml/yaml3.go | 14 + vendor/github.com/spf13/viper/logger.go | 77 + vendor/github.com/spf13/viper/util.go | 204 + vendor/github.com/spf13/viper/viper.go | 2124 + vendor/github.com/spf13/viper/viper_go1_15.go | 57 + vendor/github.com/spf13/viper/viper_go1_16.go | 32 + vendor/github.com/spf13/viper/watch.go | 12 + vendor/github.com/spf13/viper/watch_wasm.go | 30 + vendor/github.com/stretchr/testify/LICENSE | 21 + .../testify/assert/assertion_compare.go | 436 + .../assert/assertion_compare_can_convert.go | 16 + .../assert/assertion_compare_legacy.go | 16 + .../testify/assert/assertion_format.go | 753 + .../testify/assert/assertion_format.go.tmpl | 5 + .../testify/assert/assertion_forward.go | 1494 + .../testify/assert/assertion_forward.go.tmpl | 5 + .../testify/assert/assertion_order.go | 81 + .../stretchr/testify/assert/assertions.go | 1810 + .../github.com/stretchr/testify/assert/doc.go | 45 + .../stretchr/testify/assert/errors.go | 10 + .../testify/assert/forward_assertions.go | 16 + .../testify/assert/http_assertions.go | 162 + vendor/github.com/subosito/gotenv/.env | 1 + .../github.com/subosito/gotenv/.env.invalid | 1 + vendor/github.com/subosito/gotenv/.gitignore | 3 + .../github.com/subosito/gotenv/CHANGELOG.md | 58 + vendor/github.com/subosito/gotenv/LICENSE | 21 + vendor/github.com/subosito/gotenv/README.md | 130 + vendor/github.com/subosito/gotenv/gotenv.go | 308 + vendor/golang.org/x/crypto/AUTHORS | 3 + vendor/golang.org/x/crypto/CONTRIBUTORS | 3 + vendor/golang.org/x/crypto/LICENSE | 27 + vendor/golang.org/x/crypto/PATENTS | 22 + vendor/golang.org/x/crypto/sha3/doc.go | 62 + vendor/golang.org/x/crypto/sha3/hashes.go | 97 + .../x/crypto/sha3/hashes_generic.go | 28 + vendor/golang.org/x/crypto/sha3/keccakf.go | 413 + .../golang.org/x/crypto/sha3/keccakf_amd64.go | 14 + .../golang.org/x/crypto/sha3/keccakf_amd64.s | 391 + vendor/golang.org/x/crypto/sha3/register.go | 19 + vendor/golang.org/x/crypto/sha3/sha3.go | 193 + vendor/golang.org/x/crypto/sha3/sha3_s390x.go | 287 + vendor/golang.org/x/crypto/sha3/sha3_s390x.s | 34 + vendor/golang.org/x/crypto/sha3/shake.go | 173 + .../golang.org/x/crypto/sha3/shake_generic.go | 20 + vendor/golang.org/x/crypto/sha3/xor.go | 24 + .../golang.org/x/crypto/sha3/xor_generic.go | 28 + .../golang.org/x/crypto/sha3/xor_unaligned.go | 68 + vendor/golang.org/x/sys/AUTHORS | 3 + vendor/golang.org/x/sys/CONTRIBUTORS | 3 + vendor/golang.org/x/sys/LICENSE | 27 + vendor/golang.org/x/sys/PATENTS | 22 + vendor/golang.org/x/sys/cpu/asm_aix_ppc64.s | 18 + vendor/golang.org/x/sys/cpu/byteorder.go | 66 + vendor/golang.org/x/sys/cpu/cpu.go | 287 + vendor/golang.org/x/sys/cpu/cpu_aix.go | 34 + vendor/golang.org/x/sys/cpu/cpu_arm.go | 73 + vendor/golang.org/x/sys/cpu/cpu_arm64.go | 172 + vendor/golang.org/x/sys/cpu/cpu_arm64.s | 32 + vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go | 12 + vendor/golang.org/x/sys/cpu/cpu_gc_s390x.go | 22 + vendor/golang.org/x/sys/cpu/cpu_gc_x86.go | 17 + .../golang.org/x/sys/cpu/cpu_gccgo_arm64.go | 12 + .../golang.org/x/sys/cpu/cpu_gccgo_s390x.go | 23 + vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.c | 43 + vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.go | 33 + vendor/golang.org/x/sys/cpu/cpu_linux.go | 16 + vendor/golang.org/x/sys/cpu/cpu_linux_arm.go | 39 + .../golang.org/x/sys/cpu/cpu_linux_arm64.go | 71 + .../golang.org/x/sys/cpu/cpu_linux_mips64x.go | 24 + .../golang.org/x/sys/cpu/cpu_linux_noinit.go | 10 + .../golang.org/x/sys/cpu/cpu_linux_ppc64x.go | 32 + .../golang.org/x/sys/cpu/cpu_linux_s390x.go | 40 + vendor/golang.org/x/sys/cpu/cpu_loong64.go | 13 + vendor/golang.org/x/sys/cpu/cpu_mips64x.go | 16 + vendor/golang.org/x/sys/cpu/cpu_mipsx.go | 12 + .../golang.org/x/sys/cpu/cpu_netbsd_arm64.go | 173 + vendor/golang.org/x/sys/cpu/cpu_other_arm.go | 10 + .../golang.org/x/sys/cpu/cpu_other_arm64.go | 10 + .../golang.org/x/sys/cpu/cpu_other_mips64x.go | 13 + vendor/golang.org/x/sys/cpu/cpu_ppc64x.go | 17 + vendor/golang.org/x/sys/cpu/cpu_riscv64.go | 12 + vendor/golang.org/x/sys/cpu/cpu_s390x.go | 172 + vendor/golang.org/x/sys/cpu/cpu_s390x.s | 58 + vendor/golang.org/x/sys/cpu/cpu_wasm.go | 18 + vendor/golang.org/x/sys/cpu/cpu_x86.go | 145 + vendor/golang.org/x/sys/cpu/cpu_x86.s | 28 + vendor/golang.org/x/sys/cpu/cpu_zos.go | 10 + vendor/golang.org/x/sys/cpu/cpu_zos_s390x.go | 25 + vendor/golang.org/x/sys/cpu/hwcap_linux.go | 56 + .../golang.org/x/sys/cpu/syscall_aix_gccgo.go | 27 + .../x/sys/cpu/syscall_aix_ppc64_gc.go | 36 + .../sys/internal/unsafeheader/unsafeheader.go | 30 + vendor/golang.org/x/sys/unix/.gitignore | 2 + vendor/golang.org/x/sys/unix/README.md | 184 + .../golang.org/x/sys/unix/affinity_linux.go | 86 + vendor/golang.org/x/sys/unix/aliases.go | 15 + vendor/golang.org/x/sys/unix/asm_aix_ppc64.s | 18 + vendor/golang.org/x/sys/unix/asm_bsd_386.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_amd64.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_arm.s | 29 + vendor/golang.org/x/sys/unix/asm_bsd_arm64.s | 29 + vendor/golang.org/x/sys/unix/asm_linux_386.s | 66 + .../golang.org/x/sys/unix/asm_linux_amd64.s | 58 + vendor/golang.org/x/sys/unix/asm_linux_arm.s | 57 + .../golang.org/x/sys/unix/asm_linux_arm64.s | 53 + .../golang.org/x/sys/unix/asm_linux_loong64.s | 54 + .../golang.org/x/sys/unix/asm_linux_mips64x.s | 57 + .../golang.org/x/sys/unix/asm_linux_mipsx.s | 55 + .../golang.org/x/sys/unix/asm_linux_ppc64x.s | 45 + .../golang.org/x/sys/unix/asm_linux_riscv64.s | 49 + .../golang.org/x/sys/unix/asm_linux_s390x.s | 57 + .../x/sys/unix/asm_openbsd_mips64.s | 30 + .../golang.org/x/sys/unix/asm_solaris_amd64.s | 18 + vendor/golang.org/x/sys/unix/asm_zos_s390x.s | 426 + .../golang.org/x/sys/unix/bluetooth_linux.go | 36 + vendor/golang.org/x/sys/unix/cap_freebsd.go | 196 + vendor/golang.org/x/sys/unix/constants.go | 14 + vendor/golang.org/x/sys/unix/dev_aix_ppc.go | 27 + vendor/golang.org/x/sys/unix/dev_aix_ppc64.go | 29 + vendor/golang.org/x/sys/unix/dev_darwin.go | 24 + vendor/golang.org/x/sys/unix/dev_dragonfly.go | 30 + vendor/golang.org/x/sys/unix/dev_freebsd.go | 30 + vendor/golang.org/x/sys/unix/dev_linux.go | 42 + vendor/golang.org/x/sys/unix/dev_netbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_openbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_zos.go | 29 + vendor/golang.org/x/sys/unix/dirent.go | 103 + vendor/golang.org/x/sys/unix/endian_big.go | 10 + vendor/golang.org/x/sys/unix/endian_little.go | 10 + vendor/golang.org/x/sys/unix/env_unix.go | 32 + vendor/golang.org/x/sys/unix/epoll_zos.go | 221 + .../x/sys/unix/errors_freebsd_386.go | 233 + .../x/sys/unix/errors_freebsd_amd64.go | 233 + .../x/sys/unix/errors_freebsd_arm.go | 226 + .../x/sys/unix/errors_freebsd_arm64.go | 17 + vendor/golang.org/x/sys/unix/fcntl.go | 37 + vendor/golang.org/x/sys/unix/fcntl_darwin.go | 24 + .../x/sys/unix/fcntl_linux_32bit.go | 14 + vendor/golang.org/x/sys/unix/fdset.go | 30 + vendor/golang.org/x/sys/unix/fstatfs_zos.go | 164 + vendor/golang.org/x/sys/unix/gccgo.go | 60 + vendor/golang.org/x/sys/unix/gccgo_c.c | 45 + .../x/sys/unix/gccgo_linux_amd64.go | 21 + vendor/golang.org/x/sys/unix/ifreq_linux.go | 142 + vendor/golang.org/x/sys/unix/ioctl.go | 75 + vendor/golang.org/x/sys/unix/ioctl_linux.go | 219 + vendor/golang.org/x/sys/unix/ioctl_zos.go | 74 + vendor/golang.org/x/sys/unix/mkall.sh | 231 + vendor/golang.org/x/sys/unix/mkerrors.sh | 772 + vendor/golang.org/x/sys/unix/pagesize_unix.go | 16 + .../golang.org/x/sys/unix/pledge_openbsd.go | 163 + vendor/golang.org/x/sys/unix/ptrace_darwin.go | 12 + vendor/golang.org/x/sys/unix/ptrace_ios.go | 12 + vendor/golang.org/x/sys/unix/race.go | 31 + vendor/golang.org/x/sys/unix/race0.go | 26 + .../x/sys/unix/readdirent_getdents.go | 13 + .../x/sys/unix/readdirent_getdirentries.go | 20 + .../x/sys/unix/sockcmsg_dragonfly.go | 16 + .../golang.org/x/sys/unix/sockcmsg_linux.go | 85 + vendor/golang.org/x/sys/unix/sockcmsg_unix.go | 93 + .../x/sys/unix/sockcmsg_unix_other.go | 47 + vendor/golang.org/x/sys/unix/str.go | 27 + vendor/golang.org/x/sys/unix/syscall.go | 95 + vendor/golang.org/x/sys/unix/syscall_aix.go | 551 + .../golang.org/x/sys/unix/syscall_aix_ppc.go | 54 + .../x/sys/unix/syscall_aix_ppc64.go | 85 + vendor/golang.org/x/sys/unix/syscall_bsd.go | 625 + .../x/sys/unix/syscall_darwin.1_12.go | 32 + .../x/sys/unix/syscall_darwin.1_13.go | 108 + .../golang.org/x/sys/unix/syscall_darwin.go | 733 + .../x/sys/unix/syscall_darwin_amd64.go | 51 + .../x/sys/unix/syscall_darwin_arm64.go | 51 + .../x/sys/unix/syscall_darwin_libSystem.go | 27 + .../x/sys/unix/syscall_dragonfly.go | 544 + .../x/sys/unix/syscall_dragonfly_amd64.go | 57 + .../golang.org/x/sys/unix/syscall_freebsd.go | 869 + .../x/sys/unix/syscall_freebsd_386.go | 67 + .../x/sys/unix/syscall_freebsd_amd64.go | 67 + .../x/sys/unix/syscall_freebsd_arm.go | 63 + .../x/sys/unix/syscall_freebsd_arm64.go | 63 + .../golang.org/x/sys/unix/syscall_illumos.go | 186 + vendor/golang.org/x/sys/unix/syscall_linux.go | 2456 + .../x/sys/unix/syscall_linux_386.go | 346 + .../x/sys/unix/syscall_linux_alarm.go | 14 + .../x/sys/unix/syscall_linux_amd64.go | 151 + .../x/sys/unix/syscall_linux_amd64_gc.go | 13 + .../x/sys/unix/syscall_linux_arm.go | 248 + .../x/sys/unix/syscall_linux_arm64.go | 199 + .../golang.org/x/sys/unix/syscall_linux_gc.go | 15 + .../x/sys/unix/syscall_linux_gc_386.go | 17 + .../x/sys/unix/syscall_linux_gc_arm.go | 14 + .../x/sys/unix/syscall_linux_gccgo_386.go | 31 + .../x/sys/unix/syscall_linux_gccgo_arm.go | 21 + .../x/sys/unix/syscall_linux_loong64.go | 191 + .../x/sys/unix/syscall_linux_mips64x.go | 195 + .../x/sys/unix/syscall_linux_mipsx.go | 207 + .../x/sys/unix/syscall_linux_ppc.go | 236 + .../x/sys/unix/syscall_linux_ppc64x.go | 122 + .../x/sys/unix/syscall_linux_riscv64.go | 183 + .../x/sys/unix/syscall_linux_s390x.go | 302 + .../x/sys/unix/syscall_linux_sparc64.go | 118 + .../golang.org/x/sys/unix/syscall_netbsd.go | 609 + .../x/sys/unix/syscall_netbsd_386.go | 38 + .../x/sys/unix/syscall_netbsd_amd64.go | 38 + .../x/sys/unix/syscall_netbsd_arm.go | 38 + .../x/sys/unix/syscall_netbsd_arm64.go | 38 + .../golang.org/x/sys/unix/syscall_openbsd.go | 389 + .../x/sys/unix/syscall_openbsd_386.go | 42 + .../x/sys/unix/syscall_openbsd_amd64.go | 42 + .../x/sys/unix/syscall_openbsd_arm.go | 42 + .../x/sys/unix/syscall_openbsd_arm64.go | 42 + .../x/sys/unix/syscall_openbsd_mips64.go | 35 + .../golang.org/x/sys/unix/syscall_solaris.go | 1004 + .../x/sys/unix/syscall_solaris_amd64.go | 28 + vendor/golang.org/x/sys/unix/syscall_unix.go | 486 + .../golang.org/x/sys/unix/syscall_unix_gc.go | 18 + .../x/sys/unix/syscall_unix_gc_ppc64x.go | 25 + .../x/sys/unix/syscall_zos_s390x.go | 1823 + vendor/golang.org/x/sys/unix/sysvshm_linux.go | 21 + vendor/golang.org/x/sys/unix/sysvshm_unix.go | 61 + .../x/sys/unix/sysvshm_unix_other.go | 14 + vendor/golang.org/x/sys/unix/timestruct.go | 77 + .../golang.org/x/sys/unix/unveil_openbsd.go | 42 + vendor/golang.org/x/sys/unix/xattr_bsd.go | 241 + .../golang.org/x/sys/unix/zerrors_aix_ppc.go | 1385 + .../x/sys/unix/zerrors_aix_ppc64.go | 1386 + .../x/sys/unix/zerrors_darwin_amd64.go | 1892 + .../x/sys/unix/zerrors_darwin_arm64.go | 1892 + .../x/sys/unix/zerrors_dragonfly_amd64.go | 1738 + .../x/sys/unix/zerrors_freebsd_386.go | 1948 + .../x/sys/unix/zerrors_freebsd_amd64.go | 1947 + .../x/sys/unix/zerrors_freebsd_arm.go | 1846 + .../x/sys/unix/zerrors_freebsd_arm64.go | 1948 + vendor/golang.org/x/sys/unix/zerrors_linux.go | 3084 + .../x/sys/unix/zerrors_linux_386.go | 826 + .../x/sys/unix/zerrors_linux_amd64.go | 826 + .../x/sys/unix/zerrors_linux_arm.go | 832 + .../x/sys/unix/zerrors_linux_arm64.go | 823 + .../x/sys/unix/zerrors_linux_loong64.go | 818 + .../x/sys/unix/zerrors_linux_mips.go | 833 + .../x/sys/unix/zerrors_linux_mips64.go | 833 + .../x/sys/unix/zerrors_linux_mips64le.go | 833 + .../x/sys/unix/zerrors_linux_mipsle.go | 833 + .../x/sys/unix/zerrors_linux_ppc.go | 885 + .../x/sys/unix/zerrors_linux_ppc64.go | 889 + .../x/sys/unix/zerrors_linux_ppc64le.go | 889 + .../x/sys/unix/zerrors_linux_riscv64.go | 813 + .../x/sys/unix/zerrors_linux_s390x.go | 888 + .../x/sys/unix/zerrors_linux_sparc64.go | 883 + .../x/sys/unix/zerrors_netbsd_386.go | 1780 + .../x/sys/unix/zerrors_netbsd_amd64.go | 1770 + .../x/sys/unix/zerrors_netbsd_arm.go | 1759 + .../x/sys/unix/zerrors_netbsd_arm64.go | 1770 + .../x/sys/unix/zerrors_openbsd_386.go | 1668 + .../x/sys/unix/zerrors_openbsd_amd64.go | 1775 + .../x/sys/unix/zerrors_openbsd_arm.go | 1670 + .../x/sys/unix/zerrors_openbsd_arm64.go | 1798 + .../x/sys/unix/zerrors_openbsd_mips64.go | 1863 + .../x/sys/unix/zerrors_solaris_amd64.go | 1557 + .../x/sys/unix/zerrors_zos_s390x.go | 860 + .../x/sys/unix/zptrace_armnn_linux.go | 42 + .../x/sys/unix/zptrace_linux_arm64.go | 17 + .../x/sys/unix/zptrace_mipsnn_linux.go | 51 + .../x/sys/unix/zptrace_mipsnnle_linux.go | 51 + .../x/sys/unix/zptrace_x86_linux.go | 81 + .../golang.org/x/sys/unix/zsyscall_aix_ppc.go | 1485 + .../x/sys/unix/zsyscall_aix_ppc64.go | 1443 + .../x/sys/unix/zsyscall_aix_ppc64_gc.go | 1192 + .../x/sys/unix/zsyscall_aix_ppc64_gccgo.go | 1070 + .../x/sys/unix/zsyscall_darwin_amd64.1_13.go | 40 + .../x/sys/unix/zsyscall_darwin_amd64.1_13.s | 25 + .../x/sys/unix/zsyscall_darwin_amd64.go | 2519 + .../x/sys/unix/zsyscall_darwin_amd64.s | 889 + .../x/sys/unix/zsyscall_darwin_arm64.1_13.go | 40 + .../x/sys/unix/zsyscall_darwin_arm64.1_13.s | 25 + .../x/sys/unix/zsyscall_darwin_arm64.go | 2519 + .../x/sys/unix/zsyscall_darwin_arm64.s | 889 + .../x/sys/unix/zsyscall_dragonfly_amd64.go | 1679 + .../x/sys/unix/zsyscall_freebsd_386.go | 2016 + .../x/sys/unix/zsyscall_freebsd_amd64.go | 2016 + .../x/sys/unix/zsyscall_freebsd_arm.go | 2016 + .../x/sys/unix/zsyscall_freebsd_arm64.go | 2016 + .../x/sys/unix/zsyscall_illumos_amd64.go | 128 + .../golang.org/x/sys/unix/zsyscall_linux.go | 2153 + .../x/sys/unix/zsyscall_linux_386.go | 537 + .../x/sys/unix/zsyscall_linux_amd64.go | 704 + .../x/sys/unix/zsyscall_linux_arm.go | 652 + .../x/sys/unix/zsyscall_linux_arm64.go | 603 + .../x/sys/unix/zsyscall_linux_loong64.go | 552 + .../x/sys/unix/zsyscall_linux_mips.go | 704 + .../x/sys/unix/zsyscall_linux_mips64.go | 698 + .../x/sys/unix/zsyscall_linux_mips64le.go | 687 + .../x/sys/unix/zsyscall_linux_mipsle.go | 704 + .../x/sys/unix/zsyscall_linux_ppc.go | 709 + .../x/sys/unix/zsyscall_linux_ppc64.go | 755 + .../x/sys/unix/zsyscall_linux_ppc64le.go | 755 + .../x/sys/unix/zsyscall_linux_riscv64.go | 572 + .../x/sys/unix/zsyscall_linux_s390x.go | 546 + .../x/sys/unix/zsyscall_linux_sparc64.go | 699 + .../x/sys/unix/zsyscall_netbsd_386.go | 1850 + .../x/sys/unix/zsyscall_netbsd_amd64.go | 1850 + .../x/sys/unix/zsyscall_netbsd_arm.go | 1850 + .../x/sys/unix/zsyscall_netbsd_arm64.go | 1850 + .../x/sys/unix/zsyscall_openbsd_386.go | 1693 + .../x/sys/unix/zsyscall_openbsd_amd64.go | 1693 + .../x/sys/unix/zsyscall_openbsd_arm.go | 1693 + .../x/sys/unix/zsyscall_openbsd_arm64.go | 1693 + .../x/sys/unix/zsyscall_openbsd_mips64.go | 1693 + .../x/sys/unix/zsyscall_solaris_amd64.go | 2053 + .../x/sys/unix/zsyscall_zos_s390x.go | 1255 + .../x/sys/unix/zsysctl_openbsd_386.go | 274 + .../x/sys/unix/zsysctl_openbsd_amd64.go | 272 + .../x/sys/unix/zsysctl_openbsd_arm.go | 274 + .../x/sys/unix/zsysctl_openbsd_arm64.go | 276 + .../x/sys/unix/zsysctl_openbsd_mips64.go | 280 + .../x/sys/unix/zsysnum_darwin_amd64.go | 440 + .../x/sys/unix/zsysnum_darwin_arm64.go | 438 + .../x/sys/unix/zsysnum_dragonfly_amd64.go | 317 + .../x/sys/unix/zsysnum_freebsd_386.go | 397 + .../x/sys/unix/zsysnum_freebsd_amd64.go | 397 + .../x/sys/unix/zsysnum_freebsd_arm.go | 397 + .../x/sys/unix/zsysnum_freebsd_arm64.go | 397 + .../x/sys/unix/zsysnum_linux_386.go | 450 + .../x/sys/unix/zsysnum_linux_amd64.go | 372 + .../x/sys/unix/zsysnum_linux_arm.go | 414 + .../x/sys/unix/zsysnum_linux_arm64.go | 317 + .../x/sys/unix/zsysnum_linux_loong64.go | 313 + .../x/sys/unix/zsysnum_linux_mips.go | 434 + .../x/sys/unix/zsysnum_linux_mips64.go | 364 + .../x/sys/unix/zsysnum_linux_mips64le.go | 364 + .../x/sys/unix/zsysnum_linux_mipsle.go | 434 + .../x/sys/unix/zsysnum_linux_ppc.go | 441 + .../x/sys/unix/zsysnum_linux_ppc64.go | 413 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 413 + .../x/sys/unix/zsysnum_linux_riscv64.go | 315 + .../x/sys/unix/zsysnum_linux_s390x.go | 378 + .../x/sys/unix/zsysnum_linux_sparc64.go | 392 + .../x/sys/unix/zsysnum_netbsd_386.go | 275 + .../x/sys/unix/zsysnum_netbsd_amd64.go | 275 + .../x/sys/unix/zsysnum_netbsd_arm.go | 275 + .../x/sys/unix/zsysnum_netbsd_arm64.go | 275 + .../x/sys/unix/zsysnum_openbsd_386.go | 219 + .../x/sys/unix/zsysnum_openbsd_amd64.go | 219 + .../x/sys/unix/zsysnum_openbsd_arm.go | 219 + .../x/sys/unix/zsysnum_openbsd_arm64.go | 218 + .../x/sys/unix/zsysnum_openbsd_mips64.go | 221 + .../x/sys/unix/zsysnum_zos_s390x.go | 2670 + .../golang.org/x/sys/unix/ztypes_aix_ppc.go | 354 + .../golang.org/x/sys/unix/ztypes_aix_ppc64.go | 358 + .../x/sys/unix/ztypes_darwin_amd64.go | 768 + .../x/sys/unix/ztypes_darwin_arm64.go | 768 + .../x/sys/unix/ztypes_dragonfly_amd64.go | 474 + .../x/sys/unix/ztypes_freebsd_386.go | 723 + .../x/sys/unix/ztypes_freebsd_amd64.go | 726 + .../x/sys/unix/ztypes_freebsd_arm.go | 707 + .../x/sys/unix/ztypes_freebsd_arm64.go | 704 + .../x/sys/unix/ztypes_illumos_amd64.go | 42 + vendor/golang.org/x/sys/unix/ztypes_linux.go | 5590 + .../golang.org/x/sys/unix/ztypes_linux_386.go | 683 + .../x/sys/unix/ztypes_linux_amd64.go | 699 + .../golang.org/x/sys/unix/ztypes_linux_arm.go | 678 + .../x/sys/unix/ztypes_linux_arm64.go | 678 + .../x/sys/unix/ztypes_linux_loong64.go | 679 + .../x/sys/unix/ztypes_linux_mips.go | 683 + .../x/sys/unix/ztypes_linux_mips64.go | 681 + .../x/sys/unix/ztypes_linux_mips64le.go | 681 + .../x/sys/unix/ztypes_linux_mipsle.go | 683 + .../golang.org/x/sys/unix/ztypes_linux_ppc.go | 691 + .../x/sys/unix/ztypes_linux_ppc64.go | 687 + .../x/sys/unix/ztypes_linux_ppc64le.go | 687 + .../x/sys/unix/ztypes_linux_riscv64.go | 706 + .../x/sys/unix/ztypes_linux_s390x.go | 701 + .../x/sys/unix/ztypes_linux_sparc64.go | 682 + .../x/sys/unix/ztypes_netbsd_386.go | 502 + .../x/sys/unix/ztypes_netbsd_amd64.go | 510 + .../x/sys/unix/ztypes_netbsd_arm.go | 507 + .../x/sys/unix/ztypes_netbsd_arm64.go | 510 + .../x/sys/unix/ztypes_openbsd_386.go | 574 + .../x/sys/unix/ztypes_openbsd_amd64.go | 574 + .../x/sys/unix/ztypes_openbsd_arm.go | 575 + .../x/sys/unix/ztypes_openbsd_arm64.go | 568 + .../x/sys/unix/ztypes_openbsd_mips64.go | 568 + .../x/sys/unix/ztypes_solaris_amd64.go | 482 + .../golang.org/x/sys/unix/ztypes_zos_s390x.go | 406 + vendor/golang.org/x/sys/windows/aliases.go | 13 + .../golang.org/x/sys/windows/dll_windows.go | 416 + vendor/golang.org/x/sys/windows/empty.s | 9 + .../golang.org/x/sys/windows/env_windows.go | 54 + vendor/golang.org/x/sys/windows/eventlog.go | 21 + .../golang.org/x/sys/windows/exec_windows.go | 178 + .../x/sys/windows/memory_windows.go | 48 + vendor/golang.org/x/sys/windows/mkerrors.bash | 70 + .../x/sys/windows/mkknownfolderids.bash | 27 + vendor/golang.org/x/sys/windows/mksyscall.go | 10 + vendor/golang.org/x/sys/windows/race.go | 31 + vendor/golang.org/x/sys/windows/race0.go | 26 + .../x/sys/windows/security_windows.go | 1444 + vendor/golang.org/x/sys/windows/service.go | 247 + .../x/sys/windows/setupapi_windows.go | 1425 + vendor/golang.org/x/sys/windows/str.go | 23 + vendor/golang.org/x/sys/windows/syscall.go | 113 + .../x/sys/windows/syscall_windows.go | 1700 + .../golang.org/x/sys/windows/types_windows.go | 3176 + .../x/sys/windows/types_windows_386.go | 35 + .../x/sys/windows/types_windows_amd64.go | 34 + .../x/sys/windows/types_windows_arm.go | 35 + .../x/sys/windows/types_windows_arm64.go | 34 + .../x/sys/windows/zerrors_windows.go | 9468 + .../x/sys/windows/zknownfolderids_windows.go | 149 + .../x/sys/windows/zsyscall_windows.go | 4196 + vendor/golang.org/x/text/AUTHORS | 3 + vendor/golang.org/x/text/CONTRIBUTORS | 3 + vendor/golang.org/x/text/LICENSE | 27 + vendor/golang.org/x/text/PATENTS | 22 + .../x/text/internal/language/common.go | 16 + .../x/text/internal/language/compact.go | 29 + .../text/internal/language/compact/compact.go | 61 + .../internal/language/compact/language.go | 260 + .../text/internal/language/compact/parents.go | 120 + .../text/internal/language/compact/tables.go | 1015 + .../x/text/internal/language/compact/tags.go | 91 + .../x/text/internal/language/compose.go | 167 + .../x/text/internal/language/coverage.go | 28 + .../x/text/internal/language/language.go | 627 + .../x/text/internal/language/lookup.go | 412 + .../x/text/internal/language/match.go | 226 + .../x/text/internal/language/parse.go | 604 + .../x/text/internal/language/tables.go | 3464 + .../x/text/internal/language/tags.go | 48 + vendor/golang.org/x/text/internal/tag/tag.go | 100 + vendor/golang.org/x/text/language/coverage.go | 187 + vendor/golang.org/x/text/language/doc.go | 102 + vendor/golang.org/x/text/language/go1_1.go | 39 + vendor/golang.org/x/text/language/go1_2.go | 12 + vendor/golang.org/x/text/language/language.go | 605 + vendor/golang.org/x/text/language/match.go | 735 + vendor/golang.org/x/text/language/parse.go | 250 + vendor/golang.org/x/text/language/tables.go | 298 + vendor/golang.org/x/text/language/tags.go | 145 + .../golang.org/x/text/transform/transform.go | 709 + .../x/text/unicode/norm/composition.go | 512 + .../x/text/unicode/norm/forminfo.go | 278 + .../golang.org/x/text/unicode/norm/input.go | 109 + vendor/golang.org/x/text/unicode/norm/iter.go | 458 + .../x/text/unicode/norm/normalize.go | 609 + .../x/text/unicode/norm/readwriter.go | 125 + .../x/text/unicode/norm/tables10.0.0.go | 7658 + .../x/text/unicode/norm/tables11.0.0.go | 7694 + .../x/text/unicode/norm/tables12.0.0.go | 7711 + .../x/text/unicode/norm/tables13.0.0.go | 7761 + .../x/text/unicode/norm/tables9.0.0.go | 7638 + .../x/text/unicode/norm/transform.go | 88 + vendor/golang.org/x/text/unicode/norm/trie.go | 54 + vendor/gopkg.in/ini.v1/.editorconfig | 12 + vendor/gopkg.in/ini.v1/.gitignore | 7 + vendor/gopkg.in/ini.v1/.golangci.yml | 21 + vendor/gopkg.in/ini.v1/LICENSE | 191 + vendor/gopkg.in/ini.v1/Makefile | 15 + vendor/gopkg.in/ini.v1/README.md | 43 + vendor/gopkg.in/ini.v1/codecov.yml | 9 + vendor/gopkg.in/ini.v1/data_source.go | 76 + vendor/gopkg.in/ini.v1/deprecated.go | 25 + vendor/gopkg.in/ini.v1/error.go | 34 + vendor/gopkg.in/ini.v1/file.go | 541 + vendor/gopkg.in/ini.v1/helper.go | 24 + vendor/gopkg.in/ini.v1/ini.go | 176 + vendor/gopkg.in/ini.v1/key.go | 837 + vendor/gopkg.in/ini.v1/parser.go | 513 + vendor/gopkg.in/ini.v1/section.go | 256 + vendor/gopkg.in/ini.v1/struct.go | 747 + vendor/gopkg.in/yaml.v2/.travis.yml | 17 + vendor/gopkg.in/yaml.v2/LICENSE | 201 + vendor/gopkg.in/yaml.v2/LICENSE.libyaml | 31 + vendor/gopkg.in/yaml.v2/NOTICE | 13 + vendor/gopkg.in/yaml.v2/README.md | 133 + vendor/gopkg.in/yaml.v2/apic.go | 744 + vendor/gopkg.in/yaml.v2/decode.go | 815 + vendor/gopkg.in/yaml.v2/emitterc.go | 1685 + vendor/gopkg.in/yaml.v2/encode.go | 390 + vendor/gopkg.in/yaml.v2/parserc.go | 1095 + vendor/gopkg.in/yaml.v2/readerc.go | 412 + vendor/gopkg.in/yaml.v2/resolve.go | 258 + vendor/gopkg.in/yaml.v2/scannerc.go | 2711 + vendor/gopkg.in/yaml.v2/sorter.go | 113 + vendor/gopkg.in/yaml.v2/writerc.go | 26 + vendor/gopkg.in/yaml.v2/yaml.go | 478 + vendor/gopkg.in/yaml.v2/yamlh.go | 739 + vendor/gopkg.in/yaml.v2/yamlprivateh.go | 173 + vendor/gopkg.in/yaml.v3/LICENSE | 50 + vendor/gopkg.in/yaml.v3/NOTICE | 13 + vendor/gopkg.in/yaml.v3/README.md | 150 + vendor/gopkg.in/yaml.v3/apic.go | 747 + vendor/gopkg.in/yaml.v3/decode.go | 1000 + vendor/gopkg.in/yaml.v3/emitterc.go | 2020 + vendor/gopkg.in/yaml.v3/encode.go | 577 + vendor/gopkg.in/yaml.v3/parserc.go | 1249 + vendor/gopkg.in/yaml.v3/readerc.go | 434 + vendor/gopkg.in/yaml.v3/resolve.go | 326 + vendor/gopkg.in/yaml.v3/scannerc.go | 3038 + vendor/gopkg.in/yaml.v3/sorter.go | 134 + vendor/gopkg.in/yaml.v3/writerc.go | 48 + vendor/gopkg.in/yaml.v3/yaml.go | 698 + vendor/gopkg.in/yaml.v3/yamlh.go | 807 + vendor/gopkg.in/yaml.v3/yamlprivateh.go | 198 + vendor/modules.txt | 144 + 1054 files changed, 616045 insertions(+), 4244 deletions(-) delete mode 100644 .pylintrc create mode 100644 _examples/example_config.json create mode 100644 _examples/example_config.toml rename ibeacon.png => assets/ibeacon.png (100%) delete mode 100644 assets/influxdb.png create mode 100644 cli/args.go delete mode 100755 datadog.png create mode 100644 emitters/datadog.go create mode 100644 emitters/datadog_test.go create mode 100644 emitters/models.go create mode 100644 emitters/sqlite.go create mode 100644 emitters/sqlite_test.go create mode 100644 emitters/webhook.go create mode 100644 emitters/webhook_test.go delete mode 100644 entrypoint.sh create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 goodcheck.yml create mode 100644 main.go delete mode 100644 poetry.lock delete mode 100644 pyproject.toml delete mode 100644 requirements-dev.txt delete mode 100644 requirements.txt delete mode 100644 setup.py delete mode 100644 sider.yml delete mode 100644 tests/mock_config_parser.py delete mode 100644 tests/mock_config_parser_mac.py delete mode 100644 tests/mock_keys.py delete mode 100644 tests/test_blescan.py delete mode 100644 tests/test_cli.py delete mode 100644 tests/test_common.py delete mode 100644 tests/test_datadog.py delete mode 100644 tests/test_google.py delete mode 100644 tests/test_influxdb.py delete mode 100644 tests/test_prometheus.py delete mode 100644 tests/test_sqlite.py delete mode 100644 tests/test_stdout.py delete mode 100644 tests/test_tilt_device.py delete mode 100644 tests/test_tilty.py delete mode 100644 tests/test_webhook.py create mode 100644 tilt/config.go create mode 100644 tilt/device.go create mode 100644 tilt/logger.go delete mode 100644 tilty/__init__.py delete mode 100644 tilty/blescan.py delete mode 100644 tilty/cli.py delete mode 100644 tilty/common.py delete mode 100644 tilty/constants.py delete mode 100644 tilty/emitters/__init__.py delete mode 100644 tilty/emitters/datadog.py delete mode 100644 tilty/emitters/google.py delete mode 100644 tilty/emitters/influxdb.py delete mode 100644 tilty/emitters/prometheus.py delete mode 100644 tilty/emitters/sqlite.py delete mode 100644 tilty/emitters/stdout.py delete mode 100644 tilty/emitters/webhook.py delete mode 100644 tilty/exceptions.py delete mode 100644 tilty/tilt_device.py delete mode 100644 tilty/tilty.py delete mode 100644 tox.ini create mode 100644 vendor/github.com/DataDog/datadog-go/v5/LICENSE.txt create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/README.md create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/aggregator.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/buffer.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/buffer_pool.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/buffered_metric_context.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/container.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/event.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/fnv1a.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/format.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/metrics.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/noop.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/options.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/pipe.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/pipe_windows.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/sender.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/service_check.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/statsd.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/telemetry.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/udp.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/uds.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/uds_windows.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/utils.go create mode 100644 vendor/github.com/DataDog/datadog-go/v5/statsd/worker.go create mode 100644 vendor/github.com/Microsoft/go-winio/.gitignore create mode 100644 vendor/github.com/Microsoft/go-winio/CODEOWNERS create mode 100644 vendor/github.com/Microsoft/go-winio/LICENSE create mode 100644 vendor/github.com/Microsoft/go-winio/README.md create mode 100644 vendor/github.com/Microsoft/go-winio/backup.go create mode 100644 vendor/github.com/Microsoft/go-winio/ea.go create mode 100644 vendor/github.com/Microsoft/go-winio/file.go create mode 100644 vendor/github.com/Microsoft/go-winio/fileinfo.go create mode 100644 vendor/github.com/Microsoft/go-winio/hvsock.go create mode 100644 vendor/github.com/Microsoft/go-winio/pipe.go create mode 100644 vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go create mode 100644 vendor/github.com/Microsoft/go-winio/privilege.go create mode 100644 vendor/github.com/Microsoft/go-winio/reparse.go create mode 100644 vendor/github.com/Microsoft/go-winio/sd.go create mode 100644 vendor/github.com/Microsoft/go-winio/syscall.go create mode 100644 vendor/github.com/Microsoft/go-winio/zsyscall_windows.go create mode 100644 vendor/github.com/akamensky/argparse/.gitignore create mode 100644 vendor/github.com/akamensky/argparse/.travis.yml create mode 100644 vendor/github.com/akamensky/argparse/LICENSE create mode 100644 vendor/github.com/akamensky/argparse/README.md create mode 100644 vendor/github.com/akamensky/argparse/argparse.go create mode 100644 vendor/github.com/akamensky/argparse/argument.go create mode 100644 vendor/github.com/akamensky/argparse/command.go create mode 100644 vendor/github.com/akamensky/argparse/errors.go create mode 100644 vendor/github.com/akamensky/argparse/extras.go create mode 100644 vendor/github.com/akamensky/argparse/misc.go create mode 100644 vendor/github.com/davecgh/go-spew/LICENSE create mode 100644 vendor/github.com/davecgh/go-spew/spew/bypass.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/bypasssafe.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/common.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/config.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/doc.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/dump.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/format.go create mode 100644 vendor/github.com/davecgh/go-spew/spew/spew.go create mode 100644 vendor/github.com/fsnotify/fsnotify/.editorconfig create mode 100644 vendor/github.com/fsnotify/fsnotify/.gitattributes create mode 100644 vendor/github.com/fsnotify/fsnotify/.gitignore create mode 100644 vendor/github.com/fsnotify/fsnotify/.mailmap create mode 100644 vendor/github.com/fsnotify/fsnotify/AUTHORS create mode 100644 vendor/github.com/fsnotify/fsnotify/CHANGELOG.md create mode 100644 vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md create mode 100644 vendor/github.com/fsnotify/fsnotify/LICENSE create mode 100644 vendor/github.com/fsnotify/fsnotify/README.md create mode 100644 vendor/github.com/fsnotify/fsnotify/fen.go create mode 100644 vendor/github.com/fsnotify/fsnotify/fsnotify.go create mode 100644 vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go create mode 100644 vendor/github.com/fsnotify/fsnotify/inotify.go create mode 100644 vendor/github.com/fsnotify/fsnotify/inotify_poller.go create mode 100644 vendor/github.com/fsnotify/fsnotify/kqueue.go create mode 100644 vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go create mode 100644 vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go create mode 100644 vendor/github.com/fsnotify/fsnotify/windows.go create mode 100644 vendor/github.com/go-kit/kit/LICENSE create mode 100644 vendor/github.com/go-kit/kit/log/README.md create mode 100644 vendor/github.com/go-kit/kit/log/doc.go create mode 100644 vendor/github.com/go-kit/kit/log/json_logger.go create mode 100644 vendor/github.com/go-kit/kit/log/level/doc.go create mode 100644 vendor/github.com/go-kit/kit/log/level/level.go create mode 100644 vendor/github.com/go-kit/kit/log/log.go create mode 100644 vendor/github.com/go-kit/kit/log/logfmt_logger.go create mode 100644 vendor/github.com/go-kit/kit/log/nop_logger.go create mode 100644 vendor/github.com/go-kit/kit/log/stdlib.go create mode 100644 vendor/github.com/go-kit/kit/log/sync.go create mode 100644 vendor/github.com/go-kit/kit/log/value.go create mode 100644 vendor/github.com/go-kit/log/.gitignore create mode 100644 vendor/github.com/go-kit/log/LICENSE create mode 100644 vendor/github.com/go-kit/log/README.md create mode 100644 vendor/github.com/go-kit/log/doc.go create mode 100644 vendor/github.com/go-kit/log/json_logger.go create mode 100644 vendor/github.com/go-kit/log/level/doc.go create mode 100644 vendor/github.com/go-kit/log/level/level.go create mode 100644 vendor/github.com/go-kit/log/log.go create mode 100644 vendor/github.com/go-kit/log/logfmt_logger.go create mode 100644 vendor/github.com/go-kit/log/nop_logger.go create mode 100644 vendor/github.com/go-kit/log/stdlib.go create mode 100644 vendor/github.com/go-kit/log/sync.go create mode 100644 vendor/github.com/go-kit/log/value.go create mode 100644 vendor/github.com/go-logfmt/logfmt/.gitignore create mode 100644 vendor/github.com/go-logfmt/logfmt/CHANGELOG.md create mode 100644 vendor/github.com/go-logfmt/logfmt/LICENSE create mode 100644 vendor/github.com/go-logfmt/logfmt/README.md create mode 100644 vendor/github.com/go-logfmt/logfmt/decode.go create mode 100644 vendor/github.com/go-logfmt/logfmt/doc.go create mode 100644 vendor/github.com/go-logfmt/logfmt/encode.go create mode 100644 vendor/github.com/go-logfmt/logfmt/jsonstring.go create mode 100644 vendor/github.com/go-playground/locales/.gitignore create mode 100644 vendor/github.com/go-playground/locales/.travis.yml create mode 100644 vendor/github.com/go-playground/locales/LICENSE create mode 100644 vendor/github.com/go-playground/locales/README.md create mode 100644 vendor/github.com/go-playground/locales/currency/currency.go create mode 100644 vendor/github.com/go-playground/locales/logo.png create mode 100644 vendor/github.com/go-playground/locales/rules.go create mode 100644 vendor/github.com/go-playground/universal-translator/.gitignore create mode 100644 vendor/github.com/go-playground/universal-translator/.travis.yml create mode 100644 vendor/github.com/go-playground/universal-translator/LICENSE create mode 100644 vendor/github.com/go-playground/universal-translator/Makefile create mode 100644 vendor/github.com/go-playground/universal-translator/README.md create mode 100644 vendor/github.com/go-playground/universal-translator/errors.go create mode 100644 vendor/github.com/go-playground/universal-translator/import_export.go create mode 100644 vendor/github.com/go-playground/universal-translator/logo.png create mode 100644 vendor/github.com/go-playground/universal-translator/translator.go create mode 100644 vendor/github.com/go-playground/universal-translator/universal_translator.go create mode 100644 vendor/github.com/go-playground/validator/v10/.gitignore create mode 100644 vendor/github.com/go-playground/validator/v10/LICENSE create mode 100644 vendor/github.com/go-playground/validator/v10/MAINTAINERS.md create mode 100644 vendor/github.com/go-playground/validator/v10/Makefile create mode 100644 vendor/github.com/go-playground/validator/v10/README.md create mode 100644 vendor/github.com/go-playground/validator/v10/baked_in.go create mode 100644 vendor/github.com/go-playground/validator/v10/cache.go create mode 100644 vendor/github.com/go-playground/validator/v10/country_codes.go create mode 100644 vendor/github.com/go-playground/validator/v10/currency_codes.go create mode 100644 vendor/github.com/go-playground/validator/v10/doc.go create mode 100644 vendor/github.com/go-playground/validator/v10/errors.go create mode 100644 vendor/github.com/go-playground/validator/v10/field_level.go create mode 100644 vendor/github.com/go-playground/validator/v10/logo.png create mode 100644 vendor/github.com/go-playground/validator/v10/postcode_regexes.go create mode 100644 vendor/github.com/go-playground/validator/v10/regexes.go create mode 100644 vendor/github.com/go-playground/validator/v10/struct_level.go create mode 100644 vendor/github.com/go-playground/validator/v10/translations.go create mode 100644 vendor/github.com/go-playground/validator/v10/util.go create mode 100644 vendor/github.com/go-playground/validator/v10/validator.go create mode 100644 vendor/github.com/go-playground/validator/v10/validator_instance.go create mode 100644 vendor/github.com/hashicorp/hcl/.gitignore create mode 100644 vendor/github.com/hashicorp/hcl/.travis.yml create mode 100644 vendor/github.com/hashicorp/hcl/LICENSE create mode 100644 vendor/github.com/hashicorp/hcl/Makefile create mode 100644 vendor/github.com/hashicorp/hcl/README.md create mode 100644 vendor/github.com/hashicorp/hcl/appveyor.yml create mode 100644 vendor/github.com/hashicorp/hcl/decoder.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/ast/ast.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/ast/walk.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/parser/error.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/parser/parser.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/printer/nodes.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/printer/printer.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/scanner/scanner.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/strconv/quote.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/token/position.go create mode 100644 vendor/github.com/hashicorp/hcl/hcl/token/token.go create mode 100644 vendor/github.com/hashicorp/hcl/json/parser/flatten.go create mode 100644 vendor/github.com/hashicorp/hcl/json/parser/parser.go create mode 100644 vendor/github.com/hashicorp/hcl/json/scanner/scanner.go create mode 100644 vendor/github.com/hashicorp/hcl/json/token/position.go create mode 100644 vendor/github.com/hashicorp/hcl/json/token/token.go create mode 100644 vendor/github.com/hashicorp/hcl/lex.go create mode 100644 vendor/github.com/hashicorp/hcl/parse.go create mode 100644 vendor/github.com/jarcoal/httpmock/.gitignore create mode 100644 vendor/github.com/jarcoal/httpmock/LICENSE create mode 100644 vendor/github.com/jarcoal/httpmock/README.md create mode 100644 vendor/github.com/jarcoal/httpmock/any.go create mode 100644 vendor/github.com/jarcoal/httpmock/doc.go create mode 100644 vendor/github.com/jarcoal/httpmock/env.go create mode 100644 vendor/github.com/jarcoal/httpmock/file.go create mode 100644 vendor/github.com/jarcoal/httpmock/internal/error.go create mode 100644 vendor/github.com/jarcoal/httpmock/internal/route_key.go create mode 100644 vendor/github.com/jarcoal/httpmock/internal/stack_tracer.go create mode 100644 vendor/github.com/jarcoal/httpmock/internal/submatches.go create mode 100644 vendor/github.com/jarcoal/httpmock/response.go create mode 100644 vendor/github.com/jarcoal/httpmock/transport.go create mode 100644 vendor/github.com/leodido/go-urn/.gitignore create mode 100644 vendor/github.com/leodido/go-urn/.travis.yml create mode 100644 vendor/github.com/leodido/go-urn/LICENSE create mode 100644 vendor/github.com/leodido/go-urn/README.md create mode 100644 vendor/github.com/leodido/go-urn/machine.go create mode 100644 vendor/github.com/leodido/go-urn/machine.go.rl create mode 100644 vendor/github.com/leodido/go-urn/makefile create mode 100644 vendor/github.com/leodido/go-urn/urn.go create mode 100644 vendor/github.com/magiconair/properties/.gitignore create mode 100644 vendor/github.com/magiconair/properties/.travis.yml create mode 100644 vendor/github.com/magiconair/properties/CHANGELOG.md create mode 100644 vendor/github.com/magiconair/properties/LICENSE.md create mode 100644 vendor/github.com/magiconair/properties/README.md create mode 100644 vendor/github.com/magiconair/properties/decode.go create mode 100644 vendor/github.com/magiconair/properties/doc.go create mode 100644 vendor/github.com/magiconair/properties/integrate.go create mode 100644 vendor/github.com/magiconair/properties/lex.go create mode 100644 vendor/github.com/magiconair/properties/load.go create mode 100644 vendor/github.com/magiconair/properties/parser.go create mode 100644 vendor/github.com/magiconair/properties/properties.go create mode 100644 vendor/github.com/magiconair/properties/rangecheck.go create mode 100644 vendor/github.com/mattn/go-sqlite3/.codecov.yml create mode 100644 vendor/github.com/mattn/go-sqlite3/.gitignore create mode 100644 vendor/github.com/mattn/go-sqlite3/LICENSE create mode 100644 vendor/github.com/mattn/go-sqlite3/README.md create mode 100644 vendor/github.com/mattn/go-sqlite3/backup.go create mode 100644 vendor/github.com/mattn/go-sqlite3/callback.go create mode 100644 vendor/github.com/mattn/go-sqlite3/convert.go create mode 100644 vendor/github.com/mattn/go-sqlite3/doc.go create mode 100644 vendor/github.com/mattn/go-sqlite3/error.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_context.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_func_crypt.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension_omit.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_allow_uri_authority.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_app_armor.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_column_metadata.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_foreign_keys.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_fts5.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_icu.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_introspect.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate_hook.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_preupdate_omit.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete_fast.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_stat4.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_unlock_notify.c create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_unlock_notify.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth_omit.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_full.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_incr.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_other.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_solaris.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_type.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_usleep_windows.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go create mode 100644 vendor/github.com/mattn/go-sqlite3/sqlite3ext.h create mode 100644 vendor/github.com/mattn/go-sqlite3/static_mock.go create mode 100644 vendor/github.com/mitchellh/mapstructure/CHANGELOG.md create mode 100644 vendor/github.com/mitchellh/mapstructure/LICENSE create mode 100644 vendor/github.com/mitchellh/mapstructure/README.md create mode 100644 vendor/github.com/mitchellh/mapstructure/decode_hooks.go create mode 100644 vendor/github.com/mitchellh/mapstructure/error.go create mode 100644 vendor/github.com/mitchellh/mapstructure/mapstructure.go create mode 100644 vendor/github.com/myoung34/gatt/.gitignore create mode 100644 vendor/github.com/myoung34/gatt/LICENSE.md create mode 100644 vendor/github.com/myoung34/gatt/adv.go create mode 100644 vendor/github.com/myoung34/gatt/attr.go create mode 100644 vendor/github.com/myoung34/gatt/central.go create mode 100644 vendor/github.com/myoung34/gatt/central_darwin.go create mode 100644 vendor/github.com/myoung34/gatt/central_linux.go create mode 100644 vendor/github.com/myoung34/gatt/common.go create mode 100644 vendor/github.com/myoung34/gatt/const.go create mode 100644 vendor/github.com/myoung34/gatt/device.go create mode 100644 vendor/github.com/myoung34/gatt/device_darwin.go create mode 100644 vendor/github.com/myoung34/gatt/device_linux.go create mode 100644 vendor/github.com/myoung34/gatt/doc.go create mode 100644 vendor/github.com/myoung34/gatt/examples/option/doc.go create mode 100644 vendor/github.com/myoung34/gatt/examples/option/option_darwin.go create mode 100644 vendor/github.com/myoung34/gatt/examples/option/option_linux.go create mode 100644 vendor/github.com/myoung34/gatt/known_uuid.go create mode 100644 vendor/github.com/myoung34/gatt/l2cap_writer_linux.go create mode 100644 vendor/github.com/myoung34/gatt/linux/cmd/cmd.go create mode 100644 vendor/github.com/myoung34/gatt/linux/const.go create mode 100644 vendor/github.com/myoung34/gatt/linux/device.go create mode 100644 vendor/github.com/myoung34/gatt/linux/devices.go create mode 100644 vendor/github.com/myoung34/gatt/linux/doc.go create mode 100644 vendor/github.com/myoung34/gatt/linux/evt/evt.go create mode 100644 vendor/github.com/myoung34/gatt/linux/gioctl/LICENSE.md create mode 100644 vendor/github.com/myoung34/gatt/linux/gioctl/README.md create mode 100644 vendor/github.com/myoung34/gatt/linux/gioctl/ioctl.go create mode 100644 vendor/github.com/myoung34/gatt/linux/hci.go create mode 100644 vendor/github.com/myoung34/gatt/linux/l2cap.go create mode 100644 vendor/github.com/myoung34/gatt/linux/socket/asm.s create mode 100644 vendor/github.com/myoung34/gatt/linux/socket/asm_linux_386.s create mode 100644 vendor/github.com/myoung34/gatt/linux/socket/socket.go create mode 100644 vendor/github.com/myoung34/gatt/linux/socket/socket_common.go create mode 100644 vendor/github.com/myoung34/gatt/linux/socket/socket_darwin.go create mode 100644 vendor/github.com/myoung34/gatt/linux/socket/socket_linux.go create mode 100644 vendor/github.com/myoung34/gatt/linux/socket/socket_linux_386.go create mode 100644 vendor/github.com/myoung34/gatt/linux/util/util.go create mode 100644 vendor/github.com/myoung34/gatt/option_darwin.go create mode 100644 vendor/github.com/myoung34/gatt/option_linux.go create mode 100644 vendor/github.com/myoung34/gatt/peripheral.go create mode 100644 vendor/github.com/myoung34/gatt/peripheral_darwin.go create mode 100644 vendor/github.com/myoung34/gatt/peripheral_linux.go create mode 100644 vendor/github.com/myoung34/gatt/readme.md create mode 100644 vendor/github.com/myoung34/gatt/uuid.go create mode 100644 vendor/github.com/myoung34/gatt/xpc/LICENSE create mode 100644 vendor/github.com/myoung34/gatt/xpc/doc.go create mode 100644 vendor/github.com/myoung34/gatt/xpc/xpc_darwin.go create mode 100644 vendor/github.com/myoung34/gatt/xpc/xpc_wrapper_darwin.c create mode 100644 vendor/github.com/myoung34/gatt/xpc/xpc_wrapper_darwin.h create mode 100644 vendor/github.com/pelletier/go-toml/.dockerignore create mode 100644 vendor/github.com/pelletier/go-toml/.gitignore create mode 100644 vendor/github.com/pelletier/go-toml/CONTRIBUTING.md create mode 100644 vendor/github.com/pelletier/go-toml/Dockerfile create mode 100644 vendor/github.com/pelletier/go-toml/LICENSE create mode 100644 vendor/github.com/pelletier/go-toml/Makefile create mode 100644 vendor/github.com/pelletier/go-toml/PULL_REQUEST_TEMPLATE.md create mode 100644 vendor/github.com/pelletier/go-toml/README.md create mode 100644 vendor/github.com/pelletier/go-toml/SECURITY.md create mode 100644 vendor/github.com/pelletier/go-toml/azure-pipelines.yml create mode 100644 vendor/github.com/pelletier/go-toml/benchmark.sh create mode 100644 vendor/github.com/pelletier/go-toml/doc.go create mode 100644 vendor/github.com/pelletier/go-toml/example-crlf.toml create mode 100644 vendor/github.com/pelletier/go-toml/example.toml create mode 100644 vendor/github.com/pelletier/go-toml/fuzz.go create mode 100644 vendor/github.com/pelletier/go-toml/fuzz.sh create mode 100644 vendor/github.com/pelletier/go-toml/keysparsing.go create mode 100644 vendor/github.com/pelletier/go-toml/lexer.go create mode 100644 vendor/github.com/pelletier/go-toml/localtime.go create mode 100644 vendor/github.com/pelletier/go-toml/marshal.go create mode 100644 vendor/github.com/pelletier/go-toml/marshal_OrderPreserve_test.toml create mode 100644 vendor/github.com/pelletier/go-toml/marshal_test.toml create mode 100644 vendor/github.com/pelletier/go-toml/parser.go create mode 100644 vendor/github.com/pelletier/go-toml/position.go create mode 100644 vendor/github.com/pelletier/go-toml/token.go create mode 100644 vendor/github.com/pelletier/go-toml/toml.go create mode 100644 vendor/github.com/pelletier/go-toml/tomlpub.go create mode 100644 vendor/github.com/pelletier/go-toml/tomltree_create.go create mode 100644 vendor/github.com/pelletier/go-toml/tomltree_write.go create mode 100644 vendor/github.com/pelletier/go-toml/tomltree_writepub.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/.dockerignore create mode 100644 vendor/github.com/pelletier/go-toml/v2/.gitattributes create mode 100644 vendor/github.com/pelletier/go-toml/v2/.gitignore create mode 100644 vendor/github.com/pelletier/go-toml/v2/.golangci.toml create mode 100644 vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml create mode 100644 vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md create mode 100644 vendor/github.com/pelletier/go-toml/v2/Dockerfile create mode 100644 vendor/github.com/pelletier/go-toml/v2/LICENSE create mode 100644 vendor/github.com/pelletier/go-toml/v2/README.md create mode 100644 vendor/github.com/pelletier/go-toml/v2/SECURITY.md create mode 100644 vendor/github.com/pelletier/go-toml/v2/ci.sh create mode 100644 vendor/github.com/pelletier/go-toml/v2/decode.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/doc.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/errors.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/ast/ast.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/ast/builder.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/ast/kind.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/danger/danger.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/danger/typeid.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/localtime.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/marshaler.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/parser.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/scanner.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/strict.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/toml.abnf create mode 100644 vendor/github.com/pelletier/go-toml/v2/types.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unmarshaler.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/utf8.go create mode 100644 vendor/github.com/pmezard/go-difflib/LICENSE create mode 100644 vendor/github.com/pmezard/go-difflib/difflib/difflib.go create mode 100644 vendor/github.com/spf13/afero/.gitignore create mode 100644 vendor/github.com/spf13/afero/.travis.yml create mode 100644 vendor/github.com/spf13/afero/LICENSE.txt create mode 100644 vendor/github.com/spf13/afero/README.md create mode 100644 vendor/github.com/spf13/afero/afero.go create mode 100644 vendor/github.com/spf13/afero/appveyor.yml create mode 100644 vendor/github.com/spf13/afero/basepath.go create mode 100644 vendor/github.com/spf13/afero/cacheOnReadFs.go create mode 100644 vendor/github.com/spf13/afero/const_bsds.go create mode 100644 vendor/github.com/spf13/afero/const_win_unix.go create mode 100644 vendor/github.com/spf13/afero/copyOnWriteFs.go create mode 100644 vendor/github.com/spf13/afero/httpFs.go create mode 100644 vendor/github.com/spf13/afero/iofs.go create mode 100644 vendor/github.com/spf13/afero/ioutil.go create mode 100644 vendor/github.com/spf13/afero/lstater.go create mode 100644 vendor/github.com/spf13/afero/match.go create mode 100644 vendor/github.com/spf13/afero/mem/dir.go create mode 100644 vendor/github.com/spf13/afero/mem/dirmap.go create mode 100644 vendor/github.com/spf13/afero/mem/file.go create mode 100644 vendor/github.com/spf13/afero/memmap.go create mode 100644 vendor/github.com/spf13/afero/os.go create mode 100644 vendor/github.com/spf13/afero/path.go create mode 100644 vendor/github.com/spf13/afero/readonlyfs.go create mode 100644 vendor/github.com/spf13/afero/regexpfs.go create mode 100644 vendor/github.com/spf13/afero/symlink.go create mode 100644 vendor/github.com/spf13/afero/unionFile.go create mode 100644 vendor/github.com/spf13/afero/util.go create mode 100644 vendor/github.com/spf13/cast/.gitignore create mode 100644 vendor/github.com/spf13/cast/LICENSE create mode 100644 vendor/github.com/spf13/cast/Makefile create mode 100644 vendor/github.com/spf13/cast/README.md create mode 100644 vendor/github.com/spf13/cast/cast.go create mode 100644 vendor/github.com/spf13/cast/caste.go create mode 100644 vendor/github.com/spf13/cast/timeformattype_string.go create mode 100644 vendor/github.com/spf13/jwalterweatherman/.gitignore create mode 100644 vendor/github.com/spf13/jwalterweatherman/LICENSE create mode 100644 vendor/github.com/spf13/jwalterweatherman/README.md create mode 100644 vendor/github.com/spf13/jwalterweatherman/default_notepad.go create mode 100644 vendor/github.com/spf13/jwalterweatherman/log_counter.go create mode 100644 vendor/github.com/spf13/jwalterweatherman/notepad.go create mode 100644 vendor/github.com/spf13/pflag/.gitignore create mode 100644 vendor/github.com/spf13/pflag/.travis.yml create mode 100644 vendor/github.com/spf13/pflag/LICENSE create mode 100644 vendor/github.com/spf13/pflag/README.md create mode 100644 vendor/github.com/spf13/pflag/bool.go create mode 100644 vendor/github.com/spf13/pflag/bool_slice.go create mode 100644 vendor/github.com/spf13/pflag/bytes.go create mode 100644 vendor/github.com/spf13/pflag/count.go create mode 100644 vendor/github.com/spf13/pflag/duration.go create mode 100644 vendor/github.com/spf13/pflag/duration_slice.go create mode 100644 vendor/github.com/spf13/pflag/flag.go create mode 100644 vendor/github.com/spf13/pflag/float32.go create mode 100644 vendor/github.com/spf13/pflag/float32_slice.go create mode 100644 vendor/github.com/spf13/pflag/float64.go create mode 100644 vendor/github.com/spf13/pflag/float64_slice.go create mode 100644 vendor/github.com/spf13/pflag/golangflag.go create mode 100644 vendor/github.com/spf13/pflag/int.go create mode 100644 vendor/github.com/spf13/pflag/int16.go create mode 100644 vendor/github.com/spf13/pflag/int32.go create mode 100644 vendor/github.com/spf13/pflag/int32_slice.go create mode 100644 vendor/github.com/spf13/pflag/int64.go create mode 100644 vendor/github.com/spf13/pflag/int64_slice.go create mode 100644 vendor/github.com/spf13/pflag/int8.go create mode 100644 vendor/github.com/spf13/pflag/int_slice.go create mode 100644 vendor/github.com/spf13/pflag/ip.go create mode 100644 vendor/github.com/spf13/pflag/ip_slice.go create mode 100644 vendor/github.com/spf13/pflag/ipmask.go create mode 100644 vendor/github.com/spf13/pflag/ipnet.go create mode 100644 vendor/github.com/spf13/pflag/string.go create mode 100644 vendor/github.com/spf13/pflag/string_array.go create mode 100644 vendor/github.com/spf13/pflag/string_slice.go create mode 100644 vendor/github.com/spf13/pflag/string_to_int.go create mode 100644 vendor/github.com/spf13/pflag/string_to_int64.go create mode 100644 vendor/github.com/spf13/pflag/string_to_string.go create mode 100644 vendor/github.com/spf13/pflag/uint.go create mode 100644 vendor/github.com/spf13/pflag/uint16.go create mode 100644 vendor/github.com/spf13/pflag/uint32.go create mode 100644 vendor/github.com/spf13/pflag/uint64.go create mode 100644 vendor/github.com/spf13/pflag/uint8.go create mode 100644 vendor/github.com/spf13/pflag/uint_slice.go create mode 100644 vendor/github.com/spf13/viper/.editorconfig create mode 100644 vendor/github.com/spf13/viper/.gitignore create mode 100644 vendor/github.com/spf13/viper/.golangci.yaml create mode 100644 vendor/github.com/spf13/viper/LICENSE create mode 100644 vendor/github.com/spf13/viper/Makefile create mode 100644 vendor/github.com/spf13/viper/README.md create mode 100644 vendor/github.com/spf13/viper/TROUBLESHOOTING.md create mode 100644 vendor/github.com/spf13/viper/experimental_logger.go create mode 100644 vendor/github.com/spf13/viper/flags.go create mode 100644 vendor/github.com/spf13/viper/fs.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/decoder.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/dotenv/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/dotenv/map_utils.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/encoder.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/error.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/hcl/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/ini/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/ini/map_utils.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/javaproperties/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/javaproperties/map_utils.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/json/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/toml/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/toml/codec2.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/yaml/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/yaml/yaml2.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/yaml/yaml3.go create mode 100644 vendor/github.com/spf13/viper/logger.go create mode 100644 vendor/github.com/spf13/viper/util.go create mode 100644 vendor/github.com/spf13/viper/viper.go create mode 100644 vendor/github.com/spf13/viper/viper_go1_15.go create mode 100644 vendor/github.com/spf13/viper/viper_go1_16.go create mode 100644 vendor/github.com/spf13/viper/watch.go create mode 100644 vendor/github.com/spf13/viper/watch_wasm.go create mode 100644 vendor/github.com/stretchr/testify/LICENSE create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_compare.go create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_compare_legacy.go create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_format.go create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_forward.go create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_order.go create mode 100644 vendor/github.com/stretchr/testify/assert/assertions.go create mode 100644 vendor/github.com/stretchr/testify/assert/doc.go create mode 100644 vendor/github.com/stretchr/testify/assert/errors.go create mode 100644 vendor/github.com/stretchr/testify/assert/forward_assertions.go create mode 100644 vendor/github.com/stretchr/testify/assert/http_assertions.go create mode 100644 vendor/github.com/subosito/gotenv/.env create mode 100644 vendor/github.com/subosito/gotenv/.env.invalid create mode 100644 vendor/github.com/subosito/gotenv/.gitignore create mode 100644 vendor/github.com/subosito/gotenv/CHANGELOG.md create mode 100644 vendor/github.com/subosito/gotenv/LICENSE create mode 100644 vendor/github.com/subosito/gotenv/README.md create mode 100644 vendor/github.com/subosito/gotenv/gotenv.go create mode 100644 vendor/golang.org/x/crypto/AUTHORS create mode 100644 vendor/golang.org/x/crypto/CONTRIBUTORS create mode 100644 vendor/golang.org/x/crypto/LICENSE create mode 100644 vendor/golang.org/x/crypto/PATENTS create mode 100644 vendor/golang.org/x/crypto/sha3/doc.go create mode 100644 vendor/golang.org/x/crypto/sha3/hashes.go create mode 100644 vendor/golang.org/x/crypto/sha3/hashes_generic.go create mode 100644 vendor/golang.org/x/crypto/sha3/keccakf.go create mode 100644 vendor/golang.org/x/crypto/sha3/keccakf_amd64.go create mode 100644 vendor/golang.org/x/crypto/sha3/keccakf_amd64.s create mode 100644 vendor/golang.org/x/crypto/sha3/register.go create mode 100644 vendor/golang.org/x/crypto/sha3/sha3.go create mode 100644 vendor/golang.org/x/crypto/sha3/sha3_s390x.go create mode 100644 vendor/golang.org/x/crypto/sha3/sha3_s390x.s create mode 100644 vendor/golang.org/x/crypto/sha3/shake.go create mode 100644 vendor/golang.org/x/crypto/sha3/shake_generic.go create mode 100644 vendor/golang.org/x/crypto/sha3/xor.go create mode 100644 vendor/golang.org/x/crypto/sha3/xor_generic.go create mode 100644 vendor/golang.org/x/crypto/sha3/xor_unaligned.go create mode 100644 vendor/golang.org/x/sys/AUTHORS create mode 100644 vendor/golang.org/x/sys/CONTRIBUTORS create mode 100644 vendor/golang.org/x/sys/LICENSE create mode 100644 vendor/golang.org/x/sys/PATENTS create mode 100644 vendor/golang.org/x/sys/cpu/asm_aix_ppc64.s create mode 100644 vendor/golang.org/x/sys/cpu/byteorder.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_aix.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_arm.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_arm64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_arm64.s create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gc_s390x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gc_x86.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gccgo_s390x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.c create mode 100644 vendor/golang.org/x/sys/cpu/cpu_gccgo_x86.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_arm.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_mips64x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_ppc64x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_loong64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_mips64x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_mipsx.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_other_arm.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_other_arm64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_other_mips64x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_ppc64x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_riscv64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_s390x.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_s390x.s create mode 100644 vendor/golang.org/x/sys/cpu/cpu_wasm.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_x86.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_x86.s create mode 100644 vendor/golang.org/x/sys/cpu/cpu_zos.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/cpu/hwcap_linux.go create mode 100644 vendor/golang.org/x/sys/cpu/syscall_aix_gccgo.go create mode 100644 vendor/golang.org/x/sys/cpu/syscall_aix_ppc64_gc.go create mode 100644 vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go create mode 100644 vendor/golang.org/x/sys/unix/.gitignore create mode 100644 vendor/golang.org/x/sys/unix/README.md create mode 100644 vendor/golang.org/x/sys/unix/affinity_linux.go create mode 100644 vendor/golang.org/x/sys/unix/aliases.go create mode 100644 vendor/golang.org/x/sys/unix/asm_aix_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_loong64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mips64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mipsx.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_openbsd_mips64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_solaris_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_zos_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/bluetooth_linux.go create mode 100644 vendor/golang.org/x/sys/unix/cap_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/constants.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/dev_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/dev_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/dev_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_linux.go create mode 100644 vendor/golang.org/x/sys/unix/dev_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_zos.go create mode 100644 vendor/golang.org/x/sys/unix/dirent.go create mode 100644 vendor/golang.org/x/sys/unix/endian_big.go create mode 100644 vendor/golang.org/x/sys/unix/endian_little.go create mode 100644 vendor/golang.org/x/sys/unix/env_unix.go create mode 100644 vendor/golang.org/x/sys/unix/epoll_zos.go create mode 100644 vendor/golang.org/x/sys/unix/errors_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/errors_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/errors_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/errors_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_linux_32bit.go create mode 100644 vendor/golang.org/x/sys/unix/fdset.go create mode 100644 vendor/golang.org/x/sys/unix/fstatfs_zos.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo_c.c create mode 100644 vendor/golang.org/x/sys/unix/gccgo_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ifreq_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_zos.go create mode 100644 vendor/golang.org/x/sys/unix/mkall.sh create mode 100644 vendor/golang.org/x/sys/unix/mkerrors.sh create mode 100644 vendor/golang.org/x/sys/unix/pagesize_unix.go create mode 100644 vendor/golang.org/x/sys/unix/pledge_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_ios.go create mode 100644 vendor/golang.org/x/sys/unix/race.go create mode 100644 vendor/golang.org/x/sys/unix/race0.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdents.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdirentries.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/str.go create mode 100644 vendor/golang.org/x/sys/unix/syscall.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin.1_12.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin.1_13.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_illumos.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_alarm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/timestruct.go create mode 100644 vendor/golang.org/x/sys/unix/unveil_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/xattr_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_armnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnnle_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_x86_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.1_13.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.1_13.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.1_13.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.1_13.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_illumos_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/windows/aliases.go create mode 100644 vendor/golang.org/x/sys/windows/dll_windows.go create mode 100644 vendor/golang.org/x/sys/windows/empty.s create mode 100644 vendor/golang.org/x/sys/windows/env_windows.go create mode 100644 vendor/golang.org/x/sys/windows/eventlog.go create mode 100644 vendor/golang.org/x/sys/windows/exec_windows.go create mode 100644 vendor/golang.org/x/sys/windows/memory_windows.go create mode 100644 vendor/golang.org/x/sys/windows/mkerrors.bash create mode 100644 vendor/golang.org/x/sys/windows/mkknownfolderids.bash create mode 100644 vendor/golang.org/x/sys/windows/mksyscall.go create mode 100644 vendor/golang.org/x/sys/windows/race.go create mode 100644 vendor/golang.org/x/sys/windows/race0.go create mode 100644 vendor/golang.org/x/sys/windows/security_windows.go create mode 100644 vendor/golang.org/x/sys/windows/service.go create mode 100644 vendor/golang.org/x/sys/windows/setupapi_windows.go create mode 100644 vendor/golang.org/x/sys/windows/str.go create mode 100644 vendor/golang.org/x/sys/windows/syscall.go create mode 100644 vendor/golang.org/x/sys/windows/syscall_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_386.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_amd64.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm64.go create mode 100644 vendor/golang.org/x/sys/windows/zerrors_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zknownfolderids_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zsyscall_windows.go create mode 100644 vendor/golang.org/x/text/AUTHORS create mode 100644 vendor/golang.org/x/text/CONTRIBUTORS create mode 100644 vendor/golang.org/x/text/LICENSE create mode 100644 vendor/golang.org/x/text/PATENTS create mode 100644 vendor/golang.org/x/text/internal/language/common.go create mode 100644 vendor/golang.org/x/text/internal/language/compact.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/compact.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/language.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/parents.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/tables.go create mode 100644 vendor/golang.org/x/text/internal/language/compact/tags.go create mode 100644 vendor/golang.org/x/text/internal/language/compose.go create mode 100644 vendor/golang.org/x/text/internal/language/coverage.go create mode 100644 vendor/golang.org/x/text/internal/language/language.go create mode 100644 vendor/golang.org/x/text/internal/language/lookup.go create mode 100644 vendor/golang.org/x/text/internal/language/match.go create mode 100644 vendor/golang.org/x/text/internal/language/parse.go create mode 100644 vendor/golang.org/x/text/internal/language/tables.go create mode 100644 vendor/golang.org/x/text/internal/language/tags.go create mode 100644 vendor/golang.org/x/text/internal/tag/tag.go create mode 100644 vendor/golang.org/x/text/language/coverage.go create mode 100644 vendor/golang.org/x/text/language/doc.go create mode 100644 vendor/golang.org/x/text/language/go1_1.go create mode 100644 vendor/golang.org/x/text/language/go1_2.go create mode 100644 vendor/golang.org/x/text/language/language.go create mode 100644 vendor/golang.org/x/text/language/match.go create mode 100644 vendor/golang.org/x/text/language/parse.go create mode 100644 vendor/golang.org/x/text/language/tables.go create mode 100644 vendor/golang.org/x/text/language/tags.go create mode 100644 vendor/golang.org/x/text/transform/transform.go create mode 100644 vendor/golang.org/x/text/unicode/norm/composition.go create mode 100644 vendor/golang.org/x/text/unicode/norm/forminfo.go create mode 100644 vendor/golang.org/x/text/unicode/norm/input.go create mode 100644 vendor/golang.org/x/text/unicode/norm/iter.go create mode 100644 vendor/golang.org/x/text/unicode/norm/normalize.go create mode 100644 vendor/golang.org/x/text/unicode/norm/readwriter.go create mode 100644 vendor/golang.org/x/text/unicode/norm/tables10.0.0.go create mode 100644 vendor/golang.org/x/text/unicode/norm/tables11.0.0.go create mode 100644 vendor/golang.org/x/text/unicode/norm/tables12.0.0.go create mode 100644 vendor/golang.org/x/text/unicode/norm/tables13.0.0.go create mode 100644 vendor/golang.org/x/text/unicode/norm/tables9.0.0.go create mode 100644 vendor/golang.org/x/text/unicode/norm/transform.go create mode 100644 vendor/golang.org/x/text/unicode/norm/trie.go create mode 100644 vendor/gopkg.in/ini.v1/.editorconfig create mode 100644 vendor/gopkg.in/ini.v1/.gitignore create mode 100644 vendor/gopkg.in/ini.v1/.golangci.yml create mode 100644 vendor/gopkg.in/ini.v1/LICENSE create mode 100644 vendor/gopkg.in/ini.v1/Makefile create mode 100644 vendor/gopkg.in/ini.v1/README.md create mode 100644 vendor/gopkg.in/ini.v1/codecov.yml create mode 100644 vendor/gopkg.in/ini.v1/data_source.go create mode 100644 vendor/gopkg.in/ini.v1/deprecated.go create mode 100644 vendor/gopkg.in/ini.v1/error.go create mode 100644 vendor/gopkg.in/ini.v1/file.go create mode 100644 vendor/gopkg.in/ini.v1/helper.go create mode 100644 vendor/gopkg.in/ini.v1/ini.go create mode 100644 vendor/gopkg.in/ini.v1/key.go create mode 100644 vendor/gopkg.in/ini.v1/parser.go create mode 100644 vendor/gopkg.in/ini.v1/section.go create mode 100644 vendor/gopkg.in/ini.v1/struct.go create mode 100644 vendor/gopkg.in/yaml.v2/.travis.yml create mode 100644 vendor/gopkg.in/yaml.v2/LICENSE create mode 100644 vendor/gopkg.in/yaml.v2/LICENSE.libyaml create mode 100644 vendor/gopkg.in/yaml.v2/NOTICE create mode 100644 vendor/gopkg.in/yaml.v2/README.md create mode 100644 vendor/gopkg.in/yaml.v2/apic.go create mode 100644 vendor/gopkg.in/yaml.v2/decode.go create mode 100644 vendor/gopkg.in/yaml.v2/emitterc.go create mode 100644 vendor/gopkg.in/yaml.v2/encode.go create mode 100644 vendor/gopkg.in/yaml.v2/parserc.go create mode 100644 vendor/gopkg.in/yaml.v2/readerc.go create mode 100644 vendor/gopkg.in/yaml.v2/resolve.go create mode 100644 vendor/gopkg.in/yaml.v2/scannerc.go create mode 100644 vendor/gopkg.in/yaml.v2/sorter.go create mode 100644 vendor/gopkg.in/yaml.v2/writerc.go create mode 100644 vendor/gopkg.in/yaml.v2/yaml.go create mode 100644 vendor/gopkg.in/yaml.v2/yamlh.go create mode 100644 vendor/gopkg.in/yaml.v2/yamlprivateh.go create mode 100644 vendor/gopkg.in/yaml.v3/LICENSE create mode 100644 vendor/gopkg.in/yaml.v3/NOTICE create mode 100644 vendor/gopkg.in/yaml.v3/README.md create mode 100644 vendor/gopkg.in/yaml.v3/apic.go create mode 100644 vendor/gopkg.in/yaml.v3/decode.go create mode 100644 vendor/gopkg.in/yaml.v3/emitterc.go create mode 100644 vendor/gopkg.in/yaml.v3/encode.go create mode 100644 vendor/gopkg.in/yaml.v3/parserc.go create mode 100644 vendor/gopkg.in/yaml.v3/readerc.go create mode 100644 vendor/gopkg.in/yaml.v3/resolve.go create mode 100644 vendor/gopkg.in/yaml.v3/scannerc.go create mode 100644 vendor/gopkg.in/yaml.v3/sorter.go create mode 100644 vendor/gopkg.in/yaml.v3/writerc.go create mode 100644 vendor/gopkg.in/yaml.v3/yaml.go create mode 100644 vendor/gopkg.in/yaml.v3/yamlh.go create mode 100644 vendor/gopkg.in/yaml.v3/yamlprivateh.go create mode 100644 vendor/modules.txt diff --git a/.claire.yml b/.claire.yml index acc130b..05f4e49 100644 --- a/.claire.yml +++ b/.claire.yml @@ -1,5 +1 @@ -generalwhitelist: - # musl 1.2.2 resolves -> A buffer overflow (CVE-2020-28928) in wcsnrtombs has been fixed with the function essentially rewritten - CVE-2020-28928: musl - CVE-2021-30139: apk-tools - CVE-2021-36159: apk-tools +generalwhitelist: {} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index efc46ef..9621526 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,32 +7,16 @@ on: jobs: test: - runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10' ] + go-version: [1.19.x] + runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup python - uses: actions/setup-python@v1 + - uses: actions/setup-go@v3 with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - name: Get short Python version - id: full-python-version - shell: bash - run: echo ::set-output name=version::$(python -c "import sys; print(f'py{sys.version_info.major}{sys.version_info.minor}')") - - name: install pre-reqs - run: sudo apt-get update && sudo apt-get install libbluetooth-dev && pip install poetry pre-commit - - name: lint - run: pre-commit run --all-files - - name: Configure poetry - run: poetry config virtualenvs.in-project true - - name: Install dependencies - run: poetry install - - name: Run tox - run: poetry run tox -e ${{ steps.full-python-version.outputs.version }} + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - run: go test -coverprofile coverage.out -v ./... latest_deploy: runs-on: ubuntu-latest needs: [test] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1fcbea9..a7ee1eb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,86 +5,23 @@ on: - '*' jobs: - test: + goreleaser: runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10' ] steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup python - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - name: Get short Python version - id: full-python-version - shell: bash - run: echo ::set-output name=version::$(python -c "import sys; print(f'py{sys.version_info.major}{sys.version_info.minor}')") - - name: install pre-reqs - run: sudo apt-get update && sudo apt-get install libbluetooth-dev && pip install poetry pre-commit - - name: lint - run: pre-commit run --all-files - - name: Configure poetry - run: poetry config virtualenvs.in-project true - - name: Install dependencies - run: poetry install - - name: Run tox - run: poetry run tox -e ${{ steps.full-python-version.outputs.version }} - create-release: - name: Create Release - needs: [test] - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - upload_to_pypi: - runs-on: ubuntu-latest - needs: [create-release] - steps: - - name: Copy Repo Files - uses: actions/checkout@v2 - - name: Setup python - uses: actions/setup-python@v1 - with: - python-version: 3.7 - architecture: x64 - - name: pre-reqs - run: pip install twine wheel - - name: Build - run: python setup.py sdist bdist_wheel - - name: upload - run: python -m twine upload dist/* --verbose - env: - TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} - TWINE_PASSWORD : ${{ secrets.TWINE_PASSWORD }} - ubuntu_tag_deploy: - runs-on: ubuntu-latest - needs: [create-release] - steps: - - name: Copy Repo Files - uses: actions/checkout@v2 - - name: get version - run: echo 'TAG='$(echo ${GITHUB_REF} | sed -e "s/refs\/tags\///g") >> $GITHUB_ENV - - name: Set up Docker Buildx - id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 - with: - buildx-version: latest - - name: Available platforms - run: echo ${{ steps.buildx.outputs.platforms }} - - name: Login - run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_TOKEN }} - - name: Build - run: docker buildx build -t myoung34/tilty:${TAG} --output "type=image,push=true" --platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 . + - + name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v3 + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v3 + with: + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sast.yml b/.github/workflows/sast.yml index fca38d9..615ef77 100644 --- a/.github/workflows/sast.yml +++ b/.github/workflows/sast.yml @@ -17,6 +17,8 @@ jobs: run: sudo apt-get update && sudo apt-get install libbluetooth-dev - name: Initialize CodeQL uses: github/codeql-action/init@v2 + with: + languages: go - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis @@ -33,24 +35,3 @@ jobs: run: docker build -t myoung34/tilty:latest . - name: Test run: ./clair-scanner -w .claire.yml --ip $(ip -f inet addr show eth0 | grep -Po 'inet \K[\d.]+') myoung34/tilty:latest - bandit: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10' ] - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup python - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - name: install pre-reqs - run: sudo apt-get update && sudo apt-get install libbluetooth-dev && pip install -U pip && pip install poetry pre-commit - - name: Configure poetry - run: poetry config virtualenvs.in-project true - - name: Install dependencies - run: poetry install - - name: Run bandit - run: poetry run bandit -r tilty diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f54b47c..58c8be7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,40 +1,21 @@ -name: Test - on: push: branches-ignore: - 'master' - 'refs/tags/*' + pull_request: + branches: [ main ] +name: Test jobs: test: - runs-on: ubuntu-latest strategy: matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10' ] + go-version: [1.19.x] + runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup python - uses: actions/setup-python@v1 + - uses: actions/setup-go@v3 with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - name: Get short Python version - id: full-python-version - shell: bash - run: echo ::set-output name=version::$(python -c "import sys; print(f'py{sys.version_info.major}{sys.version_info.minor}')") - - name: install pre-reqs - run: sudo apt-get update && sudo apt-get install libbluetooth-dev && pip install poetry pre-commit - - name: lint - run: pre-commit run --all-files - - name: Configure poetry - run: poetry config virtualenvs.in-project true - - name: Install dependencies - run: poetry install - - name: Run tox - run: poetry run tox -e ${{ steps.full-python-version.outputs.version }} - - name: coveralls - run: poetry run coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - run: go test -coverprofile coverage.out -v ./... diff --git a/.gitignore b/.gitignore index 8497caf..6f6c0ec 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ __pycache__ .coverage *.sw[o-p] config.ini +coverage.out +.idea +*.db diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c33b719..0d74804 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,23 +1,18 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v1.4.0 + rev: v4.3.0 hooks: - id: check-yaml - id: end-of-file-fixer + exclude: ^vendor/ - id: trailing-whitespace + exclude: ^vendor/ - id: check-case-conflict + exclude: ^vendor/ - id: check-merge-conflict - - id: check-executables-have-shebangs - - id: check-ast - - id: flake8 - args: ['--config=setup.cfg'] - - id: fix-encoding-pragma + exclude: ^vendor/ - id: detect-private-key -- repo: https://github.com/detailyang/pre-commit-shell - rev: 1.0.2 +- repo: https://github.com/hadolint/hadolint + rev: v2.10.0 hooks: - - id: shell-lint -- repo: https://github.com/stratasan/hadolint-pre-commit - rev: cdefcb096e520a6daa9552b1d4636f5f1e1729cd - hooks: - - id: hadolint + - id: hadolint diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index f4b55a3..0000000 --- a/.pylintrc +++ /dev/null @@ -1,451 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. (PAB) -#load-plugins=pylint.extensions.mccabe - -# Maximum threshold for cyclomatic complexity (PAB) -max-complexity=10 - -# (PAB) -consider-iterating-dictionary=yes - -# Use multiple processes to speed up Pylint. -jobs=1 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code (PAB) -#extension-pkg-whitelist= - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. This option is deprecated -# and it will be removed in Pylint 2.0. -optimize-ast=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=locally-disabled -disable=import-star-module-level, - old-octal-literal, - oct-method, - bad-continuation, - print-statement, - unpacking-in-except, - parameter-unpacking, - backtick, - old-raise-syntax, - old-ne-operator, - long-suffix, - dict-view-method, - dict-iter-method, - metaclass-assignment, - next-method-called, - raising-string, - indexing-exception, - raw_input-builtin, - long-builtin, - file-builtin, - execfile-builtin, - coerce-builtin, - cmp-builtin, - buffer-builtin, - basestring-builtin, - apply-builtin, - filter-builtin-not-iterating, - using-cmp-argument, - useless-suppression, - range-builtin-not-iterating, - suppressed-message, - no-absolute-import, - old-division, - cmp-method, - reload-builtin, - zip-builtin-not-iterating, - intern-builtin, - unichr-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method,getslice-method, - setslice-method, - input-builtin, - round-builtin, - hex-method, - nonzero-method, - map-builtin-not-iterating,W0201, - duplicate-code, - ungrouped-imports - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=parseable -# output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no - -# Tells whether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,m,n,ex, Run, _, x, y, z, u, v, w, a, b, c, f, g, h, p, q, r, s, t - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -#include-naming-hint=no -include-naming-hint=yes - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct dummy-variable names (PAB) -dummy-variables-rgx=^_ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=bluetooth - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,Resource - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant,ipa,built - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/.tool-versions b/.tool-versions index 12d587b..1799f52 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -python 3.7.8 +golang 1.19 diff --git a/CHANGELOG b/CHANGELOG index a2258cd..10a9c85 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +## 1.0.0 + +IMPROVEMENTS: + + * Major rewrite into golang because I'm over the python packaging ecosystem + ## 0.12.0 IMPROVEMENTS: diff --git a/Dockerfile b/Dockerfile index a9a1af9..3fb402b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,19 @@ -FROM alpine:3.16 +FROM golang:alpine AS builder +ENV CGO_ENABLED=0 +ENV CGO_CHECK=0 +RUN apk update && \ + apk add --no-cache git=2.36.2-r0 +WORKDIR $GOPATH/src/mypackage/myapp/ +COPY . . +RUN go build -o /usr/local/bin/tilty main.go -LABEL maintainer="3vilpenguin@gmail.com" -RUN apk add -U --no-cache python3 bluez-dev && \ - apk add --no-cache --virtual .build-deps py3-setuptools py3-pip python3-dev alpine-sdk && \ - pip3 --no-cache-dir install -U setuptools pip +FROM alpine:3.16 +LABEL maintainer="3vilpenguin@gmail.com" -COPY . /src -WORKDIR /src -RUN python3 setup.py install && \ - apk del .build-deps +RUN apk add -U --no-cache bluez -COPY entrypoint.sh / -RUN chmod +x /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] +COPY --from=builder /usr/local/bin/tilty /usr/local/bin/tilty VOLUME "/etc/tilty" -CMD ["-r", "--config-file", "/etc/tilty/config.ini"] +ENTRYPOINT ["/usr/local/bin/tilty"] +CMD ["--config-file", "/etc/tilty/config.ini"] diff --git a/Makefile b/Makefile index 24f6271..252ca05 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,17 @@ -setup: - poetry install -bump_version: - sed -i.bak "s/version = \".*\"/version = \"$(VERSION)\"/g" pyproject.toml - sed -i.bak "s/version='.*'/version='$(VERSION)'/g" setup.py - rm pyproject.toml.bak setup.py.bak +test: + go test -coverprofile coverage.out ./... + go tool cover -func=coverage.out -gen_requirements: - poetry export --without-hashes -f requirements.txt | grep -vIE '^Warning:' >requirements.txt 2>/dev/null -gen_requirements_dev: - poetry export --without-hashes --dev -f requirements.txt | grep -vIE '^Warning:' >requirements-dev.txt 2>/dev/null +build: + GODEBUG=cgocheck=0 go build -o dist/tilty -test: - poetry run tox +run: + sudo ./dist/tilty -c test.ini -.PHONY: build test -build: +build-docker: docker-compose build -run: build +run-docker: build-docker docker-compose run tilty diff --git a/README.md b/README.md index f996e7d..f9084a7 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,11 @@ Tilty ===== [![Coverage Status](https://coveralls.io/repos/github/myoung34/tilty/badge.svg)](https://coveralls.io/github/myoung34/tilty) -[![PyPI version](https://img.shields.io/pypi/v/tilty.svg)](https://pypi.python.org/pypi/Tilty/) [![Docker Pulls](https://img.shields.io/docker/pulls/myoung34/tilty.svg)](https://hub.docker.com/r/myoung34/tilty) ![](assets/datadog.png) -![](assets/influxdb.png) -A python module and CLI to capture and emit events from your [tilt hydrometer](https://tilthydrometer.com/) +A CLI to capture and emit events from your [tilt hydrometer](https://tilthydrometer.com/) I've been unhappy with the quality/inconsistency of what I've seen out there in terms of random scripts that capture. No tests, no pluggable emitters, hard to find, etc. @@ -27,18 +25,15 @@ The Tilt supports writing to a google doc which you could use with something lik * Generic (Send to any endpoint with any type) * Brewstat.us (Example below) * BrewersFriend (Example below) -* InfluxDB (1.8+) * Datadog (dogstatsd) * SQLite -* Google Sheets (experimental/advanced) -* Prometheus (via pushgateway) ## Usage ## ### Generate Config ### ``` -$ cat <config.ini +$ cat <config.toml [general] sleep_interval = 2 # defaults to 1 gravity_offset = -0.001 # subtract 0.001 gravity @@ -49,13 +44,6 @@ logfile = /var/log/foo.log # defaults to stdout # stdout example [stdout] -[google] -# This is advanced. TODO: write up how to provide an access/refresh token -refresh_token = 11111111111111111111111111 -client_id = 111111-1111.apps.googleusercontent.com -client_secret = 1111111111111111 -spreadsheet_id = 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms - # SQLite example [sqlite] file = /etc/tilty/tilt.sqlite @@ -64,82 +52,48 @@ file = /etc/tilty/tilt.sqlite [webhook] url = http://www.foo.com headers = {"Content-Type": "application/json"} -payload_template = {"color": "{{ color }}", "gravity": {{ gravity }}, "mac": "{{ mac }}", "temp": {{ temp }}, "timestamp": "{{ timestamp }}"} +template = {"color": "{{ color }}", "gravity": {{ gravity }}, "mac": "{{ mac }}", "temp": {{ temp }}, "timestamp": "{{ timestamp }}"} method = POST -delay_minutes = 1 # cause a minimum delay between webhook emits # Brewstat.us example [webhook] url = https://www.brewstat.us/tilt/0yjRbGd2/log headers = {"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"} -payload_template = {"Color": "{{ color }}", "SG": {{ gravity }}, "Temp": {{ temp }}, "Timepoint": "{{ timestamp }}"} +template = {"Color": "{{ color }}", "SG": {{ gravity }}, "Temp": {{ temp }}, "Timepoint": "{{ timestamp }}"} method = POST # Brewers Friend example [webhook] url = https://log.brewersfriend.com/tilt/3009ec67c6d81276185c90824951bd32bg headers = {"Content-Type": "application/x-www-form-urlencoded"} -payload_template = {"SG": {{ gravity }}, "Temp": {{ temp }}, "Color": "{{ color }}"} +template = {"SG": {{ gravity }}, "Temp": {{ temp }}, "Color": "{{ color }}"} method = POST # Brewfather custom stream example [webhook] url = https://log.brewfather.net/stream?id=aTHF9WlXKrAb1C headers = {"Content-Type": "application/json"} -payload_template = {"name": "Tilt {{ color }}", "gravity": {{ gravity }}, "gravity_unit": "G", "temp": {{ temp }}, "temp_unit": "F"} +template = {"name": "Tilt {{ color }}", "gravity": {{ gravity }}, "gravity_unit": "G", "temp": {{ temp }}, "temp_unit": "F"} method = POST -[influxdb] -url = http://localhost:8086 -verify_ssl = True # defaults to False, only used if url is https:// -bucket = tilty -org = Mine -token = mytoken # if using influx cloud -token = myuser:password # if using self hosted -gravity_payload_template = gravity,color={{ color }},mac={{ mac }} sg={{ gravity }} -temperature_payload_template = temperature,color={{ color }},mac={{ mac }} temp={{ temp }} - [datadog] # Note: make sure that the dd agent has DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true host = statsdhost.corp.com port = 8125 -[prometheus] -url = localhost:80 -gravity_gauge_name = tilty_gravity_g -temp_gauge_name = tilty_temperature_f -labels = {"color": "{{ color }}", "mac": "{{ mac }}"} -job_name = tilty -EOF ``` ### Run ### ``` $ tilty -$ # Or from docker ( generate config into $cwd/config/config.ini ) +$ # Or from docker ( generate config into $cwd/config/config.toml ) $ docker run -it \ -v $(pwd)/config:/etc/tilty \ + --privileged \ --net=host \ myoung34/tilty:latest \ - -r --config-file /etc/tilty/config.ini -``` - -## Installation ## - -``` -$ git clone https://github.com/myoung34/tilty -$ pip install -e . -``` - -## Development ## - -``` -$ docker run -it -v $(pwd):/src -w /src --entrypoint /bin/sh python:3.7-alpine -$ apk add -U openssl-dev alpine-sdk libffi-dev python3-dev py3-bluez bluez-dev -$ pip3 install poetry -$ poetry install -$ poetry run tox + -r --config-file /etc/tilty/config.toml ``` ### Functional Development ### @@ -150,4 +104,4 @@ To test locally (and without using my tilty): I use iBeacon on android and set: * Major to a temperature in F * Minor to an SG*1000 -![](ibeacon.png) +![](assets/ibeacon.png) diff --git a/SECURITY.md b/SECURITY.md index 39f791e..b990838 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ If you believe you have found a security vulnerability, please report it to me a ## Reporting Security Issues -**Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to me directly at [myoung34@my.apsu.edu](mailto:myoung34@my.apsu.edu). +**Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to me directly at [myoung34@my.apsu.edu](mailto:myoung34@my.apsu.edu). If you'd like to communicate securely, my keybase is [here](https://keybase.io/3vilpenguin) diff --git a/_examples/example_config.json b/_examples/example_config.json new file mode 100644 index 0000000..ad46db2 --- /dev/null +++ b/_examples/example_config.json @@ -0,0 +1,15 @@ +{ + "general": { + "logging_level": "DEBUG", + "gravity_offset": -0.001, + "temperature_offset": 3, + "logfile": "/var/log/foo.log" + }, + "webhook": { + "enabled": true, + "url": "http://www.foo.com", + "headers": "{\"Content-Type\": \"application/json\"}", + "payload_template": "{\"color\": \"{{ color }}\", \"gravity\": {{ gravity }}, \"mac\": \"{{ mac }}\", \"temp\": {{ temp }}, \"timestamp\": \"{{ timestamp }}\"}", + "method": "POST" + } +} diff --git a/_examples/example_config.toml b/_examples/example_config.toml new file mode 100644 index 0000000..4e7cbef --- /dev/null +++ b/_examples/example_config.toml @@ -0,0 +1,12 @@ +[general] +logging_level = "DEBUG" +gravity_offset = -0.001 # subtract 0.001 gravity +temperature_offset = 3 # add 3 degrees +logfile = "/var/log/foo.log" # defaults to stdout + +[webhook] +enabled = true +url = "http://192.168.2.133:8000" +headers = "{\"Content-Type\": \"application/json\", \"Foo\": \"bar\"}" +template = "{\"color\": \"{{ color }}\", \"gravity\": {{ gravity }}, \"mac\": \"{{ mac }}\", \"temp\": {{ temp }}, \"timestamp\": \"{{ timestamp }}\"}" +method = "POST" diff --git a/ibeacon.png b/assets/ibeacon.png similarity index 100% rename from ibeacon.png rename to assets/ibeacon.png diff --git a/assets/influxdb.png b/assets/influxdb.png deleted file mode 100644 index 0290c895e46aed326407ccb482d92089702c7429..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111313 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 z 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≦u%uVKrqUz9wX`TFF67) zzFgXOA3{UO8hM^*MBzD#g3-V>M`H}kc;JrluV~0`;V%BZ%+H`~j%&E)Y8qZFj1qI_ zmGlIYdh!FfXLm!Wtxm0Toyf{+R!>>Ki#T0z&y%NdJ$#0m(h-sV)MoN6!RKY?;K%FA z63o@bs75*3XM^6UD`f301tC{g<~=grb&_z60e+$Ndv~VtNg4rb%Tk# zzj+1NC01?TMetf~k2);XPS;3_4Z5w?X3*OUC=Ae=_=Sc~3R&VczGR?66==#byohHd z1l?w6WRxpxz8U=R5t0$kf=NgAu1-AdsUqQKndE6?K=boF2#X>kwx^I&vUAkju4^_H z7?XR&!lsMl(71>I4156{;pftt@Mv_st}9~j@+J-$Txe8#A=;#34##|@CG^YHp-;+D zfrLH|*@EqDe&Spf({gg&3oIdAq!d@D)aQ570uSYHej+RUw#Fk#*kVha#!gp%`B+}w zK5edsUaE99S$2j}*-4Yieb&*$C(6wW;=1S|=7S|TT1nQZK=j%lmje}=L-`kAc7cPfYqSeGGGo>KSg^h$) zO-*dL?^VuC1qyq2UlipSDwJ{L&Bh6v*pKv|dsfxQd!`29fgb7#eBxF|M@R9_Q~@ok zS{9tiiII{S%4-K&GKG&}Cj|(!Jomz~14SSYr%Gzak=z&PQx-u}S8vgyVtTSXyo418 z7pTQ`9Z~L84*OM?vWYe*ZQQOiKFwd>bT29fF{VG(foNBBC2aImS4ikh?3y{OBv>%} z`LJ4C2SdacPr+oCJ{Kbld+`=Tou*J#+uOHsI~RtsR5h3$mo~-b{V2|Y-u|w-V;(nX zDJTb9_`<|HGgi1O?FP+W1OK`JW-uNBXvnzMN4mk7-PAidMcRU-o z<-rd6!2Ix%`%&!k90&?$=rf(pBk$pOV1sR-=?MeQ$D^mu0m3@4(1!<`D8N)XY?M6F z`8Bdb`C?3gdh^!N{}h9!tI;uoF&%B4YJ9(J+(A#+C**$qu}NB>->pH(d?kdSkFn<= z7bRaDn!>{aw2O+Bj*)fbV)eY9&S7C*I?#TJla>v&!(ksmK2XI*1v?OAm(3TIL3=n9 zVRjnG(oK7;;x%>EgV|gJz1OH=Q`-#i;|r)JLeH5jr*JWq1Q>|xCP$| zZ5GIWQ;mU2QGft^&(fU*gWrbb=GuO} z_!9Tp?x<+C(M(;hA}c%W8z3bkx4lsQX|^NAkc+{I@y_EARCnY;(HMKc{HU>Hu{UcE5xj>Ebyh4da>3D>igT s>UOa6UcdF`hAaQ?#%TV>;rV)#M_FA~vDXZ-Cj)-BRn(P>6fOP!1Ju|zH2?qr diff --git a/cli/args.go b/cli/args.go new file mode 100644 index 0000000..176f0da --- /dev/null +++ b/cli/args.go @@ -0,0 +1,24 @@ +package cli + +import ( + "fmt" + "github.com/akamensky/argparse" + "github.com/myoung34/tilty/tilt" + "os" +) + +func ParseArgs() tilt.Config { + parser := argparse.NewParser("tilty", "A pluggable system to receive and transmit bluetooth events from the Tilt Hydrometer") + + configFile := parser.String("c", "config", &argparse.Options{ + Required: true, + Help: "Configuration file location", + }) + + if err := parser.Parse(os.Args); err != nil { + fmt.Println(parser.Usage(err)) + os.Exit(1) + } + + return tilt.ParseConfig(*configFile) +} diff --git a/datadog.png b/datadog.png deleted file mode 100755 index cd4d2f882b2c7d4ebda241e70ec11850e89fb55a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12673 zcmeHt2UL?;*KQcpSOboXpoC%@K&gU22oS6|N)-j91&N4Clh8{DjE#;;2}lWw6hVl9 z^b$tu5Q>5jdK3&X2@oL(C6MI4apwE(|F2vBb?;jDe(T<>MM6%J^X{|H-p})#z28_X z3)4M25AFnkKzn|>c*KQ7LU{Br$L~K zU8q-r z4c<$b^!PEBt+FLH7;faW1y}W8^(%*5@i~{Hj#{{7daxyTZ+qY^pWwdbXUn-iwk;h^ zc)H~o=+K9kN=KfU%=~d8dTDiK2;Mv~G}NrUp;?;AZ*jTC8lPIsEy^9^2N8>kR`=}y z%<6uQ2A)ADi=e=BRIduKD3GE*eze~6aBR$v$HkWQc1MMXjH49MH_(dg|L_ZkJ>AGE zY9_EO_YoGJ?lm*iTY`##K&qYhNYS-OGvDUw`q12jbG+D`0aRB~aHQL(cK>|?dqJR9 zx82oH{mpiw+Mtf_q0r62l%d~B4=%q(T*eD~w$dxB9S8e^vouch(UqoiZoLal0u1ap z=m5KSdq867OPA36=~*K0&g@CHxiuuh6ZKsGdr)xD;>A@$^Whx6Qc*#QvH$t7O)ECF zK#xDV1jXbY0fAgA9@#>g_@9V)6)*(BZ%ich>Yaz(qo;Rj*zB}TLuBQ z{SBJvh}Bf_$5_8hS{mthn&v?V>Y!^yiFdGVO|dlA<e6UOyV9U$5De|SI zmd>>dD4hhXb&}U;=AfTG{4R2Te04GU^M_L0t2Vg6KyG|_EMP5@!c^NbezMxjFSBm` zQ=pD>unjL=oV9q-4j<{j<2C!P_D<9+%Fut^G>VJArEiQ4Qm9ZJiH=_*|srR^4Dc zt8%GBGjtCYVmwHn^eQ$@Y7qxVD9wj3>%GEjM_ce3R3J*<+Q**&*9cj2Il|DR<@xl6 ztU(9B_mSi4gPj3fJAn>HQ1VEQk6X+be}NzO!&d7Wp+A4&aBT)Img)tvT3N!e^Yql3 z6j@^|isu!Y+S=%j`UiKz3@N!2k+6HK3uuOOSGa7tH(P>VZSL1uqFHU zWpsb^L#ZunwQYyb!#;2byOn|py2wIJ4BsD{VJwtbE%0`xNsc~KhjSAGxa{FOQ)Bfj zlL3Rs>9nBqp_OS|zf0ihh7H$M^e{)hL(>_O-CRzO*uu7~Z=(A)-QW# zed+o%r_2`ZDu3e*A1wK#&1d7$;Lg_|W$-~wNAEIW}5JhEY$wSjuP2~-4(u!LnUN}_=-`8M)wlGiCa+w!s9w{OqYYW^z}dR;mKLk)l*1Q{B9S> zRbfgi$0B@)sOM$wC+CkZ%F}B*0X{r@Ca>2mKKddeUJV?V^NrC+??~Es52S@D_O4v8 znvD~dD#y3tEB}#Qi@*>|$#&VOJb&koh3bW`wdvxAAnioO`AD*m&-q?!aaoB^*%&3j_>>y2*4UJnyBf`R%pYzYpY%rS65?lsD+4kC z7i7N9;k|Zm5afJj>lF@^8>nG0{LjMQTxX_t4XTv(U$XP{*$95b-Ax1n1OSeULro7~ z;pc-4X@-Yg^?$54wkCku%0@n)UKe(-iq3);nK_74YIDDkn!Xq0uHS#940aHteQEO~ zT*NNDP8m}1n9k4*%u&8)FfoETf`%Y%)TBhR0Qgn?1>>M!tTkkCNno1ug*+5hSd4GC#Rjs17k&amy>UaMM z&Jw{F5atbN#iVVc;ij`qK$uq*9|0eK>@)E76T%M?W^Uw4OAA5{9@NC% zm;Y;SRtTU3K>N?ao`mv3SyQX=IRhcV4i_|*Cfh|5yp`#R?gd~3^dEk~e~S=lxswI| zPA&BSYzpem{;vtC|L{kh-R+*?alm}7R~%N*pT|8UUJ)CxcDzp#xt7=n5}WR;FqSjY zyk_g6?r#mzy!5M*($GFxyjd`%-gxTXR3Z?T8T$c=7PVCIhT1Bu_weB5c;dZ#VG{EU z)$_7cP`;Z`AWm=aB;v}OHW{7mP4<9-m60IXLLz(?k}?k~Cdt=f$zM31OMQ!WVU4x4 zF@5RCz;t=(@Sy^-j|FpjepCNun>x5=n(fuV>FAf(!|PMn`Z#wqJCc^$oP+N)^&|R* z9+P8i8sZ|8{YIItT`SU!aZz~0^6fBs2X1J^8$QTu)D7s1cNYUieXLcbwvNmB_DO-v zUn(opD8imE{huNsMS-JK&a&HL6sT zY&*rE{j)LAx`6CjrzH}C3wzaqu0CF0{N{Rr2vrXH#>yPOBj@U@_6Az)FwOmV^Sj)v z@~QnHNXMNxYM6O$H~RCRUHIo?OGN*LhW=0V{2e8xJ2pShIo?W-Cjonx?|r=|T7SuP zehslX4$o0(?#Tzw+ziHUgy7=?P z2VbnNkHKl1V{)@~h$>Q_u^yLRwOYRs>Vs8BIbZdy6^t?oxalsd@Wc+!V)R!NA&EH1 zm0_?;sV1v|fr!F#T3wKl`C|RXbY_mAQZeLPk%Tmb;`R_YgI{qbPlVNlcqmm>9ut2l}x7<@tiEAcTu^)EE!9q zfwYZ5{-n+%xp3kx{1YO*tiM#)^n^Uxq>e;_tJkTs*-WirUw@FE3-Q*iBI0x99n;2! z{0%u|DXC)>NTS=0BgwzC{|TN*RX9<%Nyp@7+IUb%zIq6$YQlqWqGROK$Rq zsRbR+(wL~c4;zpxKix-werq~`oQi%kNto{X)GW{I^VRL<6&p)8lgAnbjf5%+dq@X+ zfsCOy6O5AUYlf!EHkKC%Pe1VWWm&V?ge(Eh*AeV*T-eVVm>jrMTZ7IsB($D@2NyU- z-g6`yisWfRi@OSVq**`w%zOPadF86x4(~JFzbyP%6r9_WQ|d-5_QwqO&L8baviEk` zNU~!==HKj92OpH5op0>xOI8UwT2#c%q7(*WdihSAX$uyQg@jx!3LIcHsh8AFIY4w& z9bc_%+V(Dn)HXrxlCK%}7j}JMH)PVNS>!(ylT{YOJ;M0Lr;4#}6n{3|10BUGE>ME= zDh$7iI9~=sx&nFNFF$&!%Kr-A?h!Kl`*0>*xYU3wq(p2^^dFmh9U}eCe~se=|JGq) zN4>W`4!Jv&WH-=A&~^ygX_^PU-7qlRH3BDw8*#EIUMr{HFj*XlNi_9n{ag*{AEIqhZ5 zkZhR9+WqX(TI)%|_~y(qmzgUF?I&MkvL}Bn_xCqwT#;!&ird67bDRsgr)*trJt0ZF zgA_{PyhF`~*$2(;G$BuMvF7nHagWfwHNO znV0T-KLWNb@09aQpAzuFgMs8oLeXyJeQdV_fS@!?xC)b;iHTO;*3u-P_Dfyr0R*pd zW_GrjrB}qsDGZGl&=#n-KbD1Z5D|4}S9hBDI~&N4lhmWrFwF}hu>{(E|9=bR0y8Q4 z{|||JQt4NhiIj7HJ&sZ$X2=Sg2LCLPSVR6Qmc-nYr$+r^ANM3&R*3k1+G8i^jjCvCokQ1$>6t~?kC~PF z6(9GKr85V%b;qiMf$O~qm^GZ!2#3AJ3IzdE!30ZI6Wd~meQCm~7-0-r-Y*NL=Cx!b zKC}y3ycAsSY!^}HtK9x>&A6rCy`B`EW;`cuGF+FuepU?(fv#N(`}zeR82Nlfu)!)a z&(iJllKG@3l_UpGU#zE1S4`b2Mzka&1o}pt&u#QRJ<9ROZHfvN)^C|<&U8UI)JO7gJy5G-VY+O|u(h_+rB6PChly%KgpG zIW*89V(nf<1bv-`yr?&Q&xXe(V5c%qGHZ!^Z$D>YSYLHVU^_Vn9$X0bA?DXLOV(n^ z4UI)DEtx)z+?AP@md)jU0Gc;{^&YD%(BFNskUyj+JT`@;h@ZC+el%4m2n?Q1lgDr> zW(+R*Da^FAV@ix$?t>)?NgB;f%q2taCmU^&ZO3eUSG|Yj#v^Ia>>=Hs<@sYM=lkJa zs_mSIF~M>rkALapwh#C!L^iIqWV@)7Z`o5@V-K>k%JZQ##OC+5cYd>-ZjqCHrP3Cu_ z4duyN*kV&iyW&->^o{a84-4@qlSb~yM4VW;)R5cTaK@iSaGM6XQEZ;fXE!B8Ic=eq zq8=BGmfYjFL3Ro>Z}<|fSGX`yD=m&SeBoYk?_1f~4%bjl_s5qnlG_fSRduA8#4;1Q zIE#&}6~h+Zz@hR|1N+Az5md!Jt!rVt*XA8-QnK5qAQ#Buu4~P?7rhiyOJpV8$94BV zx(0c$@6;TbaHM^WUu7EUlEBwW>Kf&Jvat;QMZyoM!CUF*sFTNGW)t<|nCmNy_&9e| zqhifgH~1GC0vmA46z0QH5DK#Bf{jH>(`#UEdE%T(5%*%NE8MAbbzG@B^G?$W!*!83iy z%D>K3m|IxvUsZ~WW|>bB>+pWq*+^u>$AUYo>E;e9=(cUrI;(j#lB{o7juLd^#1Bnt z6+;rV);d8*$os%0NGFfGDkTdpSX2GVz^E^s1LTN};Ns7EWwfJISy;pJkO+4%vDT0YNig02@hAV-1)Qv}lQzvD{vXTtsi)Sw-q74wn-R2s+%Zh* zG>8qfL0tJc>N+(;q2q4gZh4v(R<$R3^iXnVbHwM0ea-+l9S2X!^p-NjJtiZ)^sjwny5NX*DlY3xLtNaSn9(wux~39-^E-x9i6R~krA|K z(&C3YiHvQhojf+1ppRv2weO-LZ#N`m*d|f9siwztrYMDXxN`G_6TAHCM_(iz*@ap! zoKV-W^C?X|fBu@+&CV`)Lft0Z2)E0;R)Ny>F2ip4D&$k}+H8Gn{+5!}Dl?2TNV5#e zo@M#Kw&7;kZfU4cuE8vgD#Px22~`g6o_*VLyD@*5fW1Y$3aO)hMwUoq`)!yjabgpn zj4H?eQSY_MOg<79W&f+LtD7sq=iMg-B%gL=GC2f%NcZqdzc~K`srd6-T$|cmF+-~( zy}L)<)7_4|&|xSYHQf8xTK40!2)QTn%IN}KbI02-D{RWkl1bZGW=Wjhjv3U`3U6!q zk5Rdy4t?a%RYIfM65=WCB!p@r6VNMRPTp&}wbMgDGx*bYi0}P; zC<5m1#e@R#a6l_Zc6_3`|P{hFffbYpJm@;kmpp60~%qnv7ebDw+ zSKH+b#3!_(#daCi+b%^pahu&&`MOdwTVRy>nG!e6gwxf~4{_0)6?j~V*huguAYbst z^ba3{cWlg6*z?>RY2ChXUJ|{m#6vTTtFUvoamdYIe)iXK3jB zB4$1WY>)n-8`%@ZhEH9ypJ?`@t{V62zjlpK{Y*oIzpI<7VKp6O3+)Tfjw9O=9-_>=O2;JH0>}dE7^- zCVHkt-r$o<59sUNV?X4A+dYS1TEh%-sojHtdP1*#At@fV+lfk)EYTfX!Z_JArC9v} zAM*?PvaG#Pk)l9q_r7pQl4a0NJP}8rnm}tQoQmINBvJPgCt^1wJ@zO4QQ1-<`Ou`v zU-G^m_vQs|m$Vxwxk{!hu=B>Vf@OmqwlqjH*R3@v@gaU%_}NPLX9h!d=lz4XVE0~j zZq_hk5^~*{aRU){yp~Gf*S@ss3UilL&YXd8`X_92ffKp?60U5GnGP zH%V=Egr(8h}Ic|%( zb0V}?;V$g$r=I9i=;&oFs|)@}xm2c`RpOp!Jd=pS9gQbG3dnj9AXk}3*CDFa9SQ4g zS2grOu2N;+(2CPyF$1h?Lh|>BcKGh4^)_pRFBb3ke%&`CI3Um?Z>OHUrS5S~E z^i4PL^+PdjE3`7R*YGk|!RJ!E#wH+0AmOE3GY5sUs ztW`514EFYk7PcPHq%zdQf(j(i9uK-)k-nh@KYfQnU3wAKJbZh@$3?f^`!D53NQ zD!F&>)1`|;_QtoLms|8psZbcVUun z{?fEI)Bs5~ym4f~Z$g#tR4_|k;`f&wioH#WJn<{3#a~^~7wf?z%@0Qar#lweR z0>4jh1+MSs5-k#g5en`m*LSwRTh11@mN^G>(*N@n+-O7rW#>zoH4fbaP+rC$O&Q>n z^5JeONh~VuYbKvn$nqM;8P^7xifW&7jfU_*;Q`+sv8cV@zxYOG8Lxex4Y;|S&lv$L z6!KZ}mkA1)W{uOiX7FjAqBmxi@Ag5iOLH2K@NG-WFOWr9wM|c3(5!w=BgK3Ai+mA4 zZ+LW6g(TLi9J3PD-Tk%1W2K0cJD%0TZ_nlQb^SW{5JAZiP>wVX*(pSv;t%s12kjQe zGW#8bXx{q<=^+}n@(6!bfpt;Ht?{NO`-UFEFg6^A?s(R`O7RU*FKZCs=!l3_T;l!t zOEG&ME&;B!KM1BEau*}lt$10Fk(Y^xYB?(fZQ<7u={WB=FcJfoGg`^&FK}H~2FvWK zxNj{KKVJE48DGBP&TE#21`%DKJU8}EzEBW3=D4~j;w}q(m^Dd~%@vox9AF=sRS5N53-b~M1aBb=7 zX_-@^07^rH@|>lFwZ7rnIsU@(neDA%2P92}&J}l%2sI;5s)5O1Y!%Yv8#5zErULYc< zb4S}xGW@1Nwk3d`v&2VU5M^2!we@4Z>G^Z#Dtq!$#Z~pFQ3(#&dQna{0a$-3uEn49 z?>`Z_4YVy^geW&{AgL7OJKjuYD5!N3aLAL#Z+fC771W9adg7qG8~}Qj-_8JTaHRuy zq$u&XLqN-U|HQL@jiE#U-NRSU@qi3|)i>a+9+^Z?Dl@3>-bzDe04EZTKd9_NH8dt)94I$w;{OXYN*AwSE70~S52#7TvLS{6$j9tE@FWn-k+oC z2G8|2@Jc*y&2<6y6#&RT_&m#JQB|wiY|CHZA1afDR_uvF7YTsak7J=|#N(Hul*LO* zsI8B*)X;GUcfpnQIO&NS0X9wtf34et*%Qh;v<>Ppcu zF&AwQq>8daQ{3A12>MG)ll^Xa=ebeut9}xqXUm{{7oM__zwafwt@$GtjAN|!erGcZ7 z<(5QIdFSibn_6<9j4V;4oGPK3T$B3A4jE`}y~PR0{C7ZFXkq?+bOUNi`KP2N|C@zJ zi!H$ai0Wo&%o#;vV@V_!XnH9i)DItic5R!eGwG0shDo}&*3+`}*jm5ewU$g32MAb% z8aop1!mJQPlRJIpdIM(h`P;LBThc_(^A~lSZn-LS{38<^Y3tW+A|;MD<|nv004UVz z3OoV1s3ww5Kd$h@r~evI^MBgZw=a95_b~NdrP$^;eFcC_rEp=|=;_F0fd3LEhE6pE zq}QyiNSLvEYg+%tKe#_As{Ty|`m-p>5~J1*zLbn{%X5I73zZZDZPT`(nn;OBm$lT% zY@AJ4x84r|@yjyMd65rH>Q7r)*zE?jZYJ8mBz+~@KSl#kSRv;+503f+V4MgZM?0=< z*MzR*KLe(EuqV1O@X+DImCv+`f4K%}fAN624LCc-3@Yl%^c&HdW@rsX_{gyNN}y5~ z@aHj{PJc=PiQKYM-AS(usOPUt43*VRkLz2et0VagJJ}-AAa(GvhIz8liv4Vpb#Gp0 z8MHXE{q1Y$Z3pRmd6ZzeJp-{R!^SCfZhq;?Kx92t1;3Pv3Et&)KaZE$RteqC{pm(F z(8^aWbF3|A?`uPM{9+f-g0ipirR2JVe0kV!xtrvUa~@ewx>3Ke*l5?jc0~_xNQ@RS z;)r6+u9u23!JV7b8@&p~z_m2zB=A~T-6Pi_4@N=C16EEgHlkEFL+LjooEet_+vT}d^u@KRQlkloql zarQ>&sFjSd?9M)|IgMN`Y^V(T@jwRA5I7`x^x_lIu3JW}?B#W(9t}Ktzx?)vW`wTP zb5t+V@SisQ^#cUZkq6y8Bf>;05mkvOA1q=t(M>17P(sEsLOoR&_Bw2YdXkGJNqL~u zX&nGGui=tB1nbwehwOR|WZ}-#Tabjet&w(5dBTn)d?|YtR*dvHwke%ZAVsLRM^J^b z1VS65>4PsOeZY~=#a9)CsgaAw_^j*>PfeHjVKR#A>+@=%+M%By;Fp!t{6f`EfFCKj zh!2y`@T-S723m6i16TMt^LiJk{TYae%Up+YO9DaKKa(0F-a8Re#8FXlb`nIO=5WZ_oQ$htD10ml+0*4&FEH3+&aC1+ z9`URvUujM!MXML$9v|0M8?k;f!1L4{@ip`PnMpA0eH$U~Hin++T=mAuo$87H5=ZIc zvFhg7 zN09xf7PM>k46aVF%Y~1E;dtn!GdfY+yS&-)m9D&U@A^WTnny*Xn^Bs$^_>q~DUuQY z(~$Oky94*_Yi~5CGs&4%HC!pICpc6sozxMSO5mTy=UeANWy%YQv7MU*r``}#?kBj{ z$8h&CRYvTF^O!2>+HM4HDveoA%#K4E-&>8GgaBm%34prP2YHN z$hE@#5g%yE(trUuSM{W3He}XHhsJfvESYsU^78jndl=r#a`ibJ3uK2cJ*|P*nJV7) z3nBKQpe)|qZA|kU3zJkr3@W#F^4boPkR5X98uqs=7b`6UM8W{j z59R6AUiL?*U)0<;X9h|H?;jP4sg4Aj#ZzaJ&taTL$bPDD2$4fNHgf(=EyEx&@VYbW zPzV8vV$#Tx83@4l@8WxE$p3+hHtCEl|1+)C%CdD2{p#>Pi?f9u9z-i>WsJtHb1ui z^<+0Mf!dVRxM$fRDbTYQS!a){uqYvP@jNS>9xpkya~KBdWDkr{GRgBg0671>9^e05 zC-A@dtuGJKL?Yy*{GEd&u=x-d=$v-nF1(|PKM}7E{=2JsvngLxixEBkH0%-k>%XVH l|9|lRA%_?cErbf%ci*Tpg* \x02\x01x03\x01w\t \xbc\xd0W\xef\x1e\x02\x01\x04\x1a\xffL\x00\x02\x15 \xa4\x95\xbb0\xc5\xb1KD\xb5\x12\x13p\xf0-t\xde \x00B \x03\xf7 \xc5\xa7' # noqa + // | | | | | | | | | # noqa + // | preamble+header | PDU | # noqa + // | 3 bytes | x bytes (plen) | # noqa + // | | | mac addr | uuid | unused data | major| minor | tx | # noqa + // | | | | | + if len(data) < 25 || binary.BigEndian.Uint32(data) != 0x4c000215 { + return tilt.TiltPayload{}, errors.New("not an iBeacon") + } + return tilt.TiltPayload{ + Id: strings.ToLower(strings.Replace(strings.ToUpper(hex.EncodeToString(data[4:8])+"-"+hex.EncodeToString(data[8:10])+"-"+hex.EncodeToString(data[10:12])+"-"+hex.EncodeToString(data[12:14])+"-"+hex.EncodeToString(data[14:20])), "-", "", -1)), + Major: binary.BigEndian.Uint16(data[20:22]), + Minor: binary.BigEndian.Uint16(data[22:24]), + }, nil +} + +func OnPeripheralDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) { + _tilt, err := NewTilt(a.ManufacturerData) + if err == nil { + payload := tilt.TiltPayload{ + Id: _tilt.Id, + Mac: p.ID(), + Color: tilt.TiltMap[_tilt.Id], + Major: _tilt.Major, + Minor: _tilt.Minor, + Rssi: rssi, + Timestamp: time.Now().String(), + } + err = validate.Struct(payload) + if err == nil { + level.Info(tilt.Logger).Log("main.OnPeripheralDiscovered", fmt.Sprintf("%s [%s] temp: %d gravity: %d rssi: %d", payload.Id, payload.Color, payload.Major, payload.Minor, rssi)) + returnStr, _ := callEmitter(fmt.Sprintf("%s.emit", config.EnabledEmitter), payload, config.ConfigData.Get(config.EnabledEmitter)) + level.Info(tilt.Logger).Log("main.OnPeripheralDiscovered", returnStr) + } + + } +} + +func callEmitter(funcName string, payload tilt.TiltPayload, emitterConfig interface{}) (result interface{}, err error) { + level.Info(tilt.Logger).Log("main.callEmitter", fmt.Sprintf("Attempting to call %+v", funcName)) + _, ok := EmittersMap[funcName] + if !ok { + panic(fmt.Sprintf("Emitter '%s' not found'", strings.Split(funcName, ".")[0])) + } + f := reflect.ValueOf(EmittersMap[funcName]) + in := make([]reflect.Value, 2) + in[0] = reflect.ValueOf(payload) + in[1] = reflect.ValueOf(emitterConfig) + var res []reflect.Value + res = f.Call(in) + result = res[0].Interface() + return +} diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 85517f5..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1475 +0,0 @@ -[[package]] -name = "astroid" -version = "2.11.7" -description = "An abstract syntax tree for Python with inference support." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} -typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} -wrapt = ">=1.11,<2" - -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "attrs" -version = "22.1.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "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)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] - -[[package]] -name = "bandit" -version = "1.7.4" -description = "Security oriented static analyser for python code." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -GitPython = ">=1.0.1" -PyYAML = ">=5.3.1" -stevedore = ">=1.20.0" - -[package.extras] -test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"] -toml = ["toml"] -yaml = ["pyyaml"] - -[[package]] -name = "cachetools" -version = "4.2.4" -description = "Extensible memoizing collections and decorators" -category = "main" -optional = false -python-versions = "~=3.5" - -[[package]] -name = "certifi" -version = "2022.6.15" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "charset-normalizer" -version = "2.1.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode_backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "7.1.2" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "colorama" -version = "0.4.5" -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 = "coverage" -version = "4.5.4" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" - -[[package]] -name = "coveralls" -version = "1.11.1" -description = "Show coverage stats online via coveralls.io" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -coverage = ">=3.6,<6.0" -docopt = ">=0.6.1" -requests = ">=1.0.0" - -[package.extras] -yaml = ["PyYAML (>=3.10,<5.3)"] - -[[package]] -name = "datadog" -version = "0.34.1" -description = "The Datadog Python library" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -decorator = ">=3.3.2" -requests = ">=2.6.0" - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "dill" -version = "0.3.5.1" -description = "serialize all of python" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" - -[package.extras] -graph = ["objgraph (>=1.7.2)"] - -[[package]] -name = "distlib" -version = "0.3.5" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "docopt" -version = "0.6.2" -description = "Pythonic argument parser, that will make you smile" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "filelock" -version = "3.7.1" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] - -[[package]] -name = "flake8" -version = "3.9.2" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" - -[[package]] -name = "gitdb" -version = "4.0.9" -description = "Git Object Database" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.27" -description = "GitPython is a python library used to interact with Git repositories" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -gitdb = ">=4.0.1,<5" -typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} - -[[package]] -name = "google-api-core" -version = "1.20.1" -description = "Google API client core library" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" - -[package.dependencies] -google-auth = ">=1.14.0,<2.0dev" -googleapis-common-protos = ">=1.6.0,<2.0dev" -protobuf = ">=3.12.0" -pytz = "*" -requests = ">=2.18.0,<3.0.0dev" -six = ">=1.10.0" - -[package.extras] -grpc = ["grpcio (>=1.29.0,<2.0dev)"] -grpcgcp = ["grpcio-gcp (>=0.2.2)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2)"] - -[[package]] -name = "google-api-python-client" -version = "1.11.0" -description = "Google API Client Library for Python" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" - -[package.dependencies] -google-api-core = ">=1.18.0,<2dev" -google-auth = ">=1.16.0" -google-auth-httplib2 = ">=0.0.3" -httplib2 = ">=0.9.2,<1dev" -six = ">=1.6.1,<2dev" -uritemplate = ">=3.0.0,<4dev" - -[[package]] -name = "google-auth" -version = "1.35.0" -description = "Google Authentication Library" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" - -[package.dependencies] -cachetools = ">=2.0.0,<5.0" -pyasn1-modules = ">=0.2.1" -rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} -six = ">=1.9.0" - -[package.extras] -aiohttp = ["requests (>=2.20.0,<3.0.0dev)", "aiohttp (>=3.6.2,<4.0.0dev)"] -pyopenssl = ["pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] - -[[package]] -name = "google-auth-httplib2" -version = "0.0.4" -description = "Google Authentication Library: httplib2 transport" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -google-auth = "*" -httplib2 = ">=0.9.1" -six = "*" - -[[package]] -name = "google-auth-oauthlib" -version = "0.4.6" -description = "Google Authentication Library" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -google-auth = ">=1.0.0" -requests-oauthlib = ">=0.7.0" - -[package.extras] -tool = ["click (>=6.0.0)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.56.4" -description = "Common protobufs used in Google APIs" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -protobuf = ">=3.15.0,<5.0.0dev" - -[package.extras] -grpc = ["grpcio (>=1.0.0,<2.0.0dev)"] - -[[package]] -name = "httplib2" -version = "0.20.4" -description = "A comprehensive HTTP client library." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} - -[[package]] -name = "idna" -version = "3.3" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "importlib-metadata" -version = "4.12.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 (>=9)", "rst.linker (>=1.9)"] -perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] - -[[package]] -name = "influxdb-client" -version = "1.31.0" -description = "InfluxDB 2.0 Python client library" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -certifi = ">=14.05.14" -python-dateutil = ">=2.5.3" -rx = ">=3.0.1" -urllib3 = ">=1.26.0" - -[package.extras] -async = ["aiohttp (>=3.8.1)"] -ciso = ["ciso8601 (>=2.1.1)"] -extra = ["pandas (>=0.25.3)", "numpy"] -test = ["coverage (>=4.0.3)", "nose (>=1.3.7)", "pluggy (>=0.3.1)", "py (>=1.4.31)", "randomize (>=0.13)", "pytest (>=5.0.0)", "httpretty (==1.0.5)", "psutil (>=5.6.3)", "aioresponses (>=0.7.3)"] - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "isort" -version = "4.3.21" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -pipfile = ["pipreqs", "requirementslib"] -pyproject = ["toml"] -requirements = ["pipreqs", "pip-api"] -xdg_home = ["appdirs (>=1.4.0)"] - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lazy-object-proxy" -version = "1.7.1" -description = "A fast and thorough lazy object proxy." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "mypy" -version = "0.782" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" -typing-extensions = ">=3.7.4" - -[package.extras] -dmypy = ["psutil (>=4.0)"] - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "oauth2client" -version = "4.1.3" -description = "OAuth 2.0 client library" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -httplib2 = ">=0.9.1" -pyasn1 = ">=0.1.7" -pyasn1-modules = ">=0.0.5" -rsa = ">=3.1.4" -six = ">=1.6.1" - -[[package]] -name = "oauthlib" -version = "3.2.0" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - -[[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 = "pbr" -version = "5.9.0" -description = "Python Build Reasonableness" -category = "dev" -optional = false -python-versions = ">=2.6" - -[[package]] -name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -testing = ["pytest-benchmark", "pytest"] -dev = ["tox", "pre-commit"] - -[[package]] -name = "prometheus-client" -version = "0.8.0" -description = "Python client for the Prometheus monitoring system." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -twisted = ["twisted"] - -[[package]] -name = "protobuf" -version = "4.21.4" -description = "" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pyasn1" -version = "0.4.8" -description = "ASN.1 types and codecs" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyasn1-modules" -version = "0.2.8" -description = "A collection of ASN.1-based protocols modules." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.5.0" - -[[package]] -name = "PyBluez" -version = "0.30" -description = "" -category = "main" -optional = false -python-versions = ">=3.5" -develop = false - -[package.extras] -ble = ["gattlib"] - -[package.source] -type = "git" -url = "https://github.com/tonyfettes/pybluez.git" -reference = "bluez-use-bytes" -resolved_reference = "6c720085fb30aaf5d4331caab367a786c4180f97" - -[[package]] -name = "pycodestyle" -version = "2.7.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyflakes" -version = "2.3.1" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pylint" -version = "2.13.9" -description = "python code static checker" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -astroid = ">=2.11.5,<=2.12.0-dev0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -dill = ">=0.2" -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -testutil = ["gitpython (>3)"] - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["railroad-diagrams", "jinja2"] - -[[package]] -name = "pytest" -version = "6.2.5" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "2.10.1" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -coverage = ">=4.4" -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2022.1" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pyyaml" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-oauthlib" -version = "1.3.1" -description = "OAuthlib authentication support for Requests." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -category = "main" -optional = false -python-versions = ">=3.6,<4" - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "rx" -version = "3.2.0" -description = "Reactive Extensions (Rx) for Python" -category = "main" -optional = false -python-versions = ">=3.6.0" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "smmap" -version = "5.0.0" -description = "A pure Python implementation of a sliding window memory map manager" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "stevedore" -version = "3.5.0" -description = "Manage dynamic plugins for Python applications" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} -pbr = ">=2.0.0,<2.1.0 || >2.1.0" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tox" -version = "3.25.1" -description = "tox is a generic virtualenv management and test command line tool" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -toml = ">=0.9.4" -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] - -[[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "4.3.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "uritemplate" -version = "3.0.1" -description = "URI templates" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "urllib3" -version = "1.26.11" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "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 = "virtualenv" -version = "20.16.2" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -platformdirs = ">=2,<3" - -[package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] - -[[package]] -name = "wrapt" -version = "1.14.1" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "zipp" -version = "3.8.1" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = ">=3.7,<4" -content-hash = "439e452d8224d331a2e92f5f53ac222d89fe3f2f37361d43607c4c1f29f5a1f3" - -[metadata.files] -astroid = [ - {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, - {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -bandit = [ - {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"}, - {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, -] -cachetools = [ - {file = "cachetools-4.2.4-py3-none-any.whl", hash = "sha256:92971d3cb7d2a97efff7c7bb1657f21a8f5fb309a37530537c71b1774189f2d1"}, - {file = "cachetools-4.2.4.tar.gz", hash = "sha256:89ea6f1b638d5a73a4f9226be57ac5e4f399d22770b92355f92dcb0f7f001693"}, -] -certifi = [ - {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, - {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, - {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, -] -click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -coverage = [ - {file = "coverage-4.5.4-cp26-cp26m-macosx_10_12_x86_64.whl", hash = "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28"}, - {file = "coverage-4.5.4-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c"}, - {file = "coverage-4.5.4-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce"}, - {file = "coverage-4.5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe"}, - {file = "coverage-4.5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888"}, - {file = "coverage-4.5.4-cp27-cp27m-win32.whl", hash = "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc"}, - {file = "coverage-4.5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24"}, - {file = "coverage-4.5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437"}, - {file = "coverage-4.5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6"}, - {file = "coverage-4.5.4-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5"}, - {file = "coverage-4.5.4-cp34-cp34m-macosx_10_12_x86_64.whl", hash = "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef"}, - {file = "coverage-4.5.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e"}, - {file = "coverage-4.5.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca"}, - {file = "coverage-4.5.4-cp34-cp34m-win32.whl", hash = "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0"}, - {file = "coverage-4.5.4-cp34-cp34m-win_amd64.whl", hash = "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1"}, - {file = "coverage-4.5.4-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7"}, - {file = "coverage-4.5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47"}, - {file = "coverage-4.5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"}, - {file = "coverage-4.5.4-cp35-cp35m-win32.whl", hash = "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e"}, - {file = "coverage-4.5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d"}, - {file = "coverage-4.5.4-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9"}, - {file = "coverage-4.5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755"}, - {file = "coverage-4.5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9"}, - {file = "coverage-4.5.4-cp36-cp36m-win32.whl", hash = "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f"}, - {file = "coverage-4.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5"}, - {file = "coverage-4.5.4-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca"}, - {file = "coverage-4.5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650"}, - {file = "coverage-4.5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2"}, - {file = "coverage-4.5.4-cp37-cp37m-win32.whl", hash = "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5"}, - {file = "coverage-4.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351"}, - {file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"}, - {file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"}, -] -coveralls = [ - {file = "coveralls-1.11.1-py2.py3-none-any.whl", hash = "sha256:4b6bfc2a2a77b890f556bc631e35ba1ac21193c356393b66c84465c06218e135"}, - {file = "coveralls-1.11.1.tar.gz", hash = "sha256:67188c7ec630c5f708c31552f2bcdac4580e172219897c4136504f14b823132f"}, -] -datadog = [ - {file = "datadog-0.34.1-py2.py3-none-any.whl", hash = "sha256:186b25a51e160e4d6ee599c647d83dca60d6889f852e07e552fdad18b0d0b6f5"}, - {file = "datadog-0.34.1.tar.gz", hash = "sha256:3bd8cc3d6915c6ac74c68093068b903de3fae22b8dd3d31480bfc2092a1f51d7"}, -] -decorator = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] -dill = [ - {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, - {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, -] -distlib = [ - {file = "distlib-0.3.5-py2.py3-none-any.whl", hash = "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c"}, - {file = "distlib-0.3.5.tar.gz", hash = "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe"}, -] -docopt = [ - {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, -] -filelock = [ - {file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"}, - {file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"}, -] -flake8 = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, -] -gitdb = [ - {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, - {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, -] -gitpython = [ - {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, - {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, -] -google-api-core = [ - {file = "google-api-core-1.20.1.tar.gz", hash = "sha256:6b757736bbc699db858794e9b71e2bbf17996075773a40551ef5e6b0fad2a2f9"}, - {file = "google_api_core-1.20.1-py2.py3-none-any.whl", hash = "sha256:b310709c325f5f9acee8feb2344c76d23577a07f4c6f4a0272c5e39d09850827"}, -] -google-api-python-client = [ - {file = "google-api-python-client-1.11.0.tar.gz", hash = "sha256:caf4015800ef1a18d06d117f47f0219c0c0641f21978f6b1bb5ede7912fab97b"}, - {file = "google_api_python_client-1.11.0-py2.py3-none-any.whl", hash = "sha256:4f596894f702736da84cf89490a810b55ca02a81f0cddeacb3022e2900b11ec6"}, -] -google-auth = [ - {file = "google-auth-1.35.0.tar.gz", hash = "sha256:b7033be9028c188ee30200b204ea00ed82ea1162e8ac1df4aa6ded19a191d88e"}, - {file = "google_auth-1.35.0-py2.py3-none-any.whl", hash = "sha256:997516b42ecb5b63e8d80f5632c1a61dddf41d2a4c2748057837e06e00014258"}, -] -google-auth-httplib2 = [ - {file = "google-auth-httplib2-0.0.4.tar.gz", hash = "sha256:8d092cc60fb16517b12057ec0bba9185a96e3b7169d86ae12eae98e645b7bc39"}, - {file = "google_auth_httplib2-0.0.4-py2.py3-none-any.whl", hash = "sha256:aeaff501738b289717fac1980db9711d77908a6c227f60e4aa1923410b43e2ee"}, -] -google-auth-oauthlib = [ - {file = "google-auth-oauthlib-0.4.6.tar.gz", hash = "sha256:a90a072f6993f2c327067bf65270046384cda5a8ecb20b94ea9a687f1f233a7a"}, - {file = "google_auth_oauthlib-0.4.6-py2.py3-none-any.whl", hash = "sha256:3f2a6e802eebbb6fb736a370fbf3b055edcb6b52878bf2f26330b5e041316c73"}, -] -googleapis-common-protos = [ - {file = "googleapis-common-protos-1.56.4.tar.gz", hash = "sha256:c25873c47279387cfdcbdafa36149887901d36202cb645a0e4f29686bf6e4417"}, - {file = "googleapis_common_protos-1.56.4-py2.py3-none-any.whl", hash = "sha256:8eb2cbc91b69feaf23e32452a7ae60e791e09967d81d4fcc7fc388182d1bd394"}, -] -httplib2 = [ - {file = "httplib2-0.20.4-py3-none-any.whl", hash = "sha256:8b6a905cb1c79eefd03f8669fd993c36dc341f7c558f056cb5a33b5c2f458543"}, - {file = "httplib2-0.20.4.tar.gz", hash = "sha256:58a98e45b4b1a48273073f905d2961666ecf0fbac4250ea5b47aef259eb5c585"}, -] -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.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, -] -influxdb-client = [ - {file = "influxdb_client-1.31.0-py3-none-any.whl", hash = "sha256:c65e1efe2e361a65f54238d0630280c217ca7b0502a1942de1e5d541635b719f"}, - {file = "influxdb_client-1.31.0.tar.gz", hash = "sha256:adf6dfdf35f7c39cf543b243359d2c0ae79fc462f1ef57a09d0f1623f181796d"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -isort = [ - {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, - {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, -] -jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -lazy-object-proxy = [ - {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, - {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, - {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, - {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, - {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, - {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, - {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, -] -markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -mypy = [ - {file = "mypy-0.782-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c"}, - {file = "mypy-0.782-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e"}, - {file = "mypy-0.782-cp35-cp35m-win_amd64.whl", hash = "sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d"}, - {file = "mypy-0.782-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd"}, - {file = "mypy-0.782-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a"}, - {file = "mypy-0.782-cp36-cp36m-win_amd64.whl", hash = "sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406"}, - {file = "mypy-0.782-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86"}, - {file = "mypy-0.782-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707"}, - {file = "mypy-0.782-cp37-cp37m-win_amd64.whl", hash = "sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308"}, - {file = "mypy-0.782-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc"}, - {file = "mypy-0.782-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea"}, - {file = "mypy-0.782-cp38-cp38-win_amd64.whl", hash = "sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b"}, - {file = "mypy-0.782-py3-none-any.whl", hash = "sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d"}, - {file = "mypy-0.782.tar.gz", hash = "sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -oauth2client = [ - {file = "oauth2client-4.1.3-py2.py3-none-any.whl", hash = "sha256:b8a81cc5d60e2d364f0b1b98f958dbd472887acaf1a5b05e21c28c31a2d6d3ac"}, - {file = "oauth2client-4.1.3.tar.gz", hash = "sha256:d486741e451287f69568a4d26d70d9acd73a2bbfa275746c535b4209891cccc6"}, -] -oauthlib = [ - {file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"}, - {file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pbr = [ - {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"}, - {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -prometheus-client = [ - {file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"}, - {file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"}, -] -protobuf = [ - {file = "protobuf-4.21.4-cp310-abi3-win32.whl", hash = "sha256:e113f3d1629cebc911b107ce704f1a17d7e1589efef5c498e202bd47df223955"}, - {file = "protobuf-4.21.4-cp310-abi3-win_amd64.whl", hash = "sha256:cb50d93ef748671b7e2537658869e00aaa8175d717d8e73a23fcd58842883229"}, - {file = "protobuf-4.21.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:142ef5d73d6cd1bd8ab539d7d73c3722f31d33e64914e01bb91439cfcef11a9f"}, - {file = "protobuf-4.21.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:47b7cf3e542fd50a3a7c24d0da13451bc362a32c0a9b905714942ea8cf35fa11"}, - {file = "protobuf-4.21.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:adeccfbffbf4c9d1e77da86dc995d76c837d01387e412066cc803ad037000892"}, - {file = "protobuf-4.21.4-cp37-cp37m-win32.whl", hash = "sha256:5e47947fbfefd5a1bdc7c28eea1d197ea6dba5812789c2429667831a55ef71b7"}, - {file = "protobuf-4.21.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d9b0398ff68017015ec2a37fb0ab390363a654362b15ca2e4543d3c82587768f"}, - {file = "protobuf-4.21.4-cp38-cp38-win32.whl", hash = "sha256:2ea8c841cc6422aea07d0f4f71f0e5e6e130de9a4b6c31a53b9d2a41a75f2d54"}, - {file = "protobuf-4.21.4-cp38-cp38-win_amd64.whl", hash = "sha256:a8119c029c60cf29b7eea5a9f56648482388e874611243f41cd10aff0a0e5461"}, - {file = "protobuf-4.21.4-cp39-cp39-win32.whl", hash = "sha256:0275902f8292039d4a022319d3f86e8b231ac4c51d7be4cb797890fb78c16b85"}, - {file = "protobuf-4.21.4-cp39-cp39-win_amd64.whl", hash = "sha256:5b95c5f515334dd3a811762e3c588b469bf39d4ee7b7f47ac1e0c41dc73809f7"}, - {file = "protobuf-4.21.4-py2.py3-none-any.whl", hash = "sha256:fd62b6eda64e199b5da651d6be42af2aa8e30805961af1fc5f70292affca78e3"}, - {file = "protobuf-4.21.4-py3-none-any.whl", hash = "sha256:7e51f6244e53e936abadf624ab3a0f06dc106b27473997374fbb34e6b2eb1e60"}, - {file = "protobuf-4.21.4.tar.gz", hash = "sha256:5783dc0d6edae631145337fabb18503b4f77274f94cdd22a4b26b9fe5029e718"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pyasn1 = [ - {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, - {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, - {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, - {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, - {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, - {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, - {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, - {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, - {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, - {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, - {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, - {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, - {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, -] -pyasn1-modules = [ - {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, - {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"}, - {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"}, - {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"}, - {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"}, - {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, - {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"}, - {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"}, - {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"}, - {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"}, - {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"}, - {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"}, - {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, -] -PyBluez = [] -pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, -] -pyflakes = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, -] -pylint = [ - {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, - {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] -pytest-cov = [ - {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, - {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -pytz = [ - {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, - {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -requests-oauthlib = [ - {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, - {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, -] -rsa = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] -rx = [ - {file = "Rx-3.2.0-py3-none-any.whl", hash = "sha256:922c5f4edb3aa1beaa47bf61d65d5380011ff6adcd527f26377d05cb73ed8ec8"}, - {file = "Rx-3.2.0.tar.gz", hash = "sha256:b657ca2b45aa485da2f7dcfd09fac2e554f7ac51ff3c2f8f2ff962ecd963d91c"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -smmap = [ - {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, - {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, -] -stevedore = [ - {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, - {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -tox = [ - {file = "tox-3.25.1-py2.py3-none-any.whl", hash = "sha256:c38e15f4733683a9cc0129fba078633e07eb0961f550a010ada879e95fb32632"}, - {file = "tox-3.25.1.tar.gz", hash = "sha256:c138327815f53bc6da4fe56baec5f25f00622ae69ef3fe4e1e385720e22486f9"}, -] -typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, -] -typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] -uritemplate = [ - {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, - {file = "uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"}, -] -urllib3 = [ - {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, - {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, -] -virtualenv = [ - {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, - {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, -] -wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, -] -zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, -] diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index ac7b6a0..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,34 +0,0 @@ -[tool.poetry] -name = "Tilty" -version = "0.12.0" -description = "A pluggable system to receive and transmit bluetooth events from the Tilt Hydrometer" -authors = ["Marcus Young <3vilpenguin@gmail.com>"] -license = "MIT" - -[tool.poetry.dependencies] -python = ">=3.7,<4" -urllib3 = ">=1.26.5" -click = "^7.0" -pybluez = { git = "https://github.com/tonyfettes/pybluez.git", branch = "bluez-use-bytes" } -requests = "^2.22" -jinja2 = ">=2.11.3" -influxdb-client = "^1.12.0" -datadog = "^0.34.1" -google-api-core = "1.20.1" -google-api-python-client = "^1.10.0" -google-auth-httplib2 = "^0.0.4" -google-auth-oauthlib = "^0.4.1" -oauth2client = "^4.1.3" -prometheus_client = "^0.8.0" - -[tool.poetry.dev-dependencies] -mypy = "^0.782" -coverage = "^4.4.2" -coveralls = "^1.11.1" -flake8 = "^3.7" -pytest = "^6.2.5" -pylint = "^2.4" -pytest-cov = "^2.8" -isort = "^4.3" -tox = "^3.14" -bandit = "^1.6.2" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 67be18d..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,76 +0,0 @@ -astroid==2.11.5; python_full_version >= "3.6.2" -atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") -attrs==21.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -bandit==1.7.4; python_version >= "3.7" -cachetools==4.2.4; python_version >= "3.5" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -certifi==2022.5.18.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -charset-normalizer==2.0.12; python_full_version >= "3.6.0" and python_version >= "3" -click==7.1.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -colorama==0.4.4; sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.6.2" and (python_version >= "2.7" and python_full_version < "3.0.0" and platform_system == "Windows" or python_full_version >= "3.5.0" and platform_system == "Windows") and (python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.5.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") and python_full_version >= "3.5.0") -coverage==4.5.4; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0" and python_version < "4") -coveralls==1.11.1 -datadog==0.34.1 -decorator==5.1.1; python_version >= "3.5" -dill==0.3.5.1; python_full_version >= "3.7.0" -distlib==0.3.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -docopt==0.6.2 -filelock==3.7.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" -flake8==3.9.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -gitdb==4.0.9; python_version >= "3.7" -gitpython==3.1.27; python_version >= "3.7" -google-api-core==1.20.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -google-api-python-client==1.11.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -google-auth-httplib2==0.0.4 -google-auth-oauthlib==0.4.6; python_version >= "3.6" -google-auth==1.35.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -googleapis-common-protos==1.56.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -httplib2==0.20.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -idna==3.3; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.5" -importlib-metadata==4.11.4; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.8" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.7" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") -influxdb-client==1.29.1; python_version >= "3.6" -iniconfig==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -isort==4.3.21; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -jinja2==3.1.2; python_version >= "3.7" -lazy-object-proxy==1.7.1; python_version >= "3.6" and python_full_version >= "3.6.2" -markupsafe==2.1.1; python_version >= "3.7" -mccabe==0.6.1; python_full_version >= "3.6.2" -mypy-extensions==0.4.3; python_version >= "3.5" -mypy==0.782; python_version >= "3.5" -oauth2client==4.1.3 -oauthlib==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -packaging==21.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pbr==5.9.0; python_version >= "3.7" -platformdirs==2.5.2; python_version >= "3.7" and python_full_version >= "3.6.2" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") -pluggy==1.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -prometheus-client==0.8.0 -protobuf==4.21.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7" -py==1.11.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pyasn1-modules==0.2.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -pyasn1==0.4.8; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_full_version >= "3.6.0" and python_version >= "3.6" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -pybluez==0.22 -pycodestyle==2.7.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pylint==2.13.9; python_full_version >= "3.6.2" -pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.6" -pytest-cov==2.10.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -pytest==6.2.5; python_version >= "3.6" -python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -pytz==2022.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -pyyaml==6.0; python_version >= "3.7" -requests-oauthlib==1.3.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") -rsa==4.8; python_version >= "3.6" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -rx==3.2.0; python_full_version >= "3.6.0" and python_version >= "3.6" -six==1.16.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -smmap==5.0.0; python_version >= "3.7" -stevedore==3.5.0; python_version >= "3.7" -toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -tomli==2.0.1; python_version < "3.11" and python_full_version >= "3.6.2" and python_version >= "3.7" -tox==3.25.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -typed-ast==1.4.3; implementation_name == "cpython" and python_version < "3.8" and python_full_version >= "3.6.2" and python_version >= "3.5" -typing-extensions==4.2.0; python_version < "3.8" and python_full_version >= "3.6.2" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.7") -uritemplate==3.0.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -urllib3==1.26.9; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") -virtualenv==20.14.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -wrapt==1.14.1; python_full_version >= "3.6.2" -zipp==3.8.0; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.7" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 06d0e82..0000000 --- a/requirements.txt +++ /dev/null @@ -1,34 +0,0 @@ -cachetools==4.2.4; python_version >= "3.5" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -certifi==2022.5.18.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -charset-normalizer==2.0.12; python_full_version >= "3.6.0" and python_version >= "3" -click==7.1.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -datadog==0.34.1 -decorator==5.1.1; python_version >= "3.5" -google-api-core==1.20.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -google-api-python-client==1.11.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -google-auth-httplib2==0.0.4 -google-auth-oauthlib==0.4.6; python_version >= "3.6" -google-auth==1.35.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -googleapis-common-protos==1.56.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -httplib2==0.20.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -idna==3.3; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.5" -influxdb-client==1.29.1; python_version >= "3.6" -jinja2==3.1.2; python_version >= "3.7" -markupsafe==2.1.1; python_version >= "3.7" -oauth2client==4.1.3 -oauthlib==3.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -prometheus-client==0.8.0 -protobuf==4.21.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7" -pyasn1-modules==0.2.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -pyasn1==0.4.8; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_full_version >= "3.6.0" and python_version >= "3.6" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -pybluez==0.22 -pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version > "3.0" -python-dateutil==2.8.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -pytz==2022.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -requests-oauthlib==1.3.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") -rsa==4.8; python_version >= "3.6" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -rx==3.2.0; python_full_version >= "3.6.0" and python_version >= "3.6" -six==1.16.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -uritemplate==3.0.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -urllib3==1.26.9; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") diff --git a/setup.py b/setup.py deleted file mode 100644 index 5f9fdcc..0000000 --- a/setup.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -"""Setup file for the package""" -from setuptools import find_packages, setup - -with open("README.md", "r") as fh: - long_description = fh.read() - -setup( - name='Tilty', - description='A pluggable system to receive and transmit bluetooth events from the Tilt Hydrometer', # noqa - author='Marcus Young', - author_email='3vilpenguin@gmail.com', - long_description=long_description, - long_description_content_type="text/markdown", - py_modules=['tilty', 'blescan'], - version='0.12.0', - packages=find_packages(exclude=['tests*']), - install_requires=[ - 'click>=7.0,<8.0', - 'datadog>=0.34.1,<0.35.0', - 'google-api-core==1.20.1', - 'google-api-python-client>=1.10.0,<2.0.0', - 'google-auth-httplib2>=0.0.4,<0.0.5', - 'google-auth-oauthlib>=0.4.1,<0.5.0', - 'influxdb-client>=1.12.0,<2.0.0', - 'jinja2>=2.11.3', - 'oauth2client>=4.1.3,<5.0.0', - 'prometheus_client>=0.8.0,<0.9.0', - 'pybluez @ git+https://github.com/tonyfettes/pybluez.git@bluez-use-bytes', - 'requests>=2.22,<3.0', - 'urllib3>=1.26.5', - ], - entry_points={ - 'console_scripts': [ - 'tilty=tilty.cli:run', - ], - }, -) diff --git a/sider.yml b/sider.yml deleted file mode 100644 index d3b388a..0000000 --- a/sider.yml +++ /dev/null @@ -1,5 +0,0 @@ -linter: - flake8: - plugins: - - flake8-builtins==1.4.1 - - flake8-mypy>=17.3.3 diff --git a/tests/mock_config_parser.py b/tests/mock_config_parser.py deleted file mode 100644 index 768c445..0000000 --- a/tests/mock_config_parser.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -class MockConfigParser: - def __init__( - self, - section, - return_empty=False, - include_extra_section=False, - include_invalid_section=False, - ): - self.section = section - self.include_extra_section = include_extra_section - self.return_empty = return_empty - self.include_invalid_section = include_invalid_section - - def __getitem__(self, key): - if self.section == 'google': - return { - "client_id": "1111111111", - "client_secret": "222222222", - "spreadsheet_id": "333333333333", - "refresh_token": "5555555555555555", - } - if self.section == 'sqlite': - return { - 'file': '/foo.sqlite', - } - if self.section == 'webhook': - return { - 'url': 'http://www.google.com', - 'headers': '{"Content-Type": "application/json"}', - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "timestamp": "{{ timestamp }}"}', # noqa - 'method': 'GET' - } - if self.section == 'influxdb': - return { - 'url': 'http://www.google.com', - 'database': 'foo', - 'gravity_payload_template': 'gravity,color={{ color }} value={{ gravity }} {{timestamp}}', # noqa - 'temperature_payload_template': 'temperature,scale=fahrenheit,color={{ color }} value={{ temp }} {{timestamp}}', # noqa - } - if self.section == 'datadog': - return { - 'host': 'http://api.datadog.com', - 'port': '8120', - } - return {} - - def sections(self, *args, **kwargs): - if self.include_extra_section: - return ['general', self.section] - if self.include_invalid_section: - return ['general', 'fake'] - if self.return_empty: - return [] - return ['general'] - - def has_section(self, *args, **kwargs): - return self.section in args diff --git a/tests/mock_config_parser_mac.py b/tests/mock_config_parser_mac.py deleted file mode 100644 index 1857a32..0000000 --- a/tests/mock_config_parser_mac.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -class MockConfigParserMac: - def __init__(self, section): - self.section = section - - def __getitem__(self, key): - if self.section == 'webhook': - return { - 'url': 'http://www.google.com', - 'headers': '{"Content-Type": "application/json"}', - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "mac": "{{ mac }}", "timestamp": "{{ timestamp }}"}', # noqa - 'method': 'GET' - } - if self.section == 'influxdb': - return { - 'url': 'http://www.google.com', - 'database': 'foo', - 'gravity_payload_template': 'gravity,mac={{ mac }} color={{ color }} value={{ gravity }} {{timestamp}}', # noqa - 'temperature_payload_template': 'temperature,scale=fahrenheit,mac={{ mac }} color={{ color }} value={{ temp }} {{timestamp}}', # noqa - } - if self.section == 'datadog': - return { - 'host': 'http://api.datadog.com', - 'port': '8120', - } - return None - - def has_section(self, *args, **kwargs): - return self.section in args diff --git a/tests/mock_keys.py b/tests/mock_keys.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_blescan.py b/tests/test_blescan.py deleted file mode 100644 index 1c74537..0000000 --- a/tests/test_blescan.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -from tilty import blescan - - -def test_string_packet(): - assert blescan.string_packet(b'\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') == 'fe000000000000000000000000000000' # noqa - assert blescan.string_packet(b'\x93\xe2\xfdD\x1b\xafhOH\xef>mn\x91\xcb\x14') == '93e2fd441baf684f48ef3e6d6e91cb14' # noqa - - -def test_packed_bdaddr_to_string(): - assert blescan.packed_bdaddr_to_string(b'ID\x8b\xea&b') == '62:26:ea:8b:44:49' # noqa - - -def test_parse_packet(): - assert blescan.parse_packet(b'\x04>+\x02\x01\x03\x01r\xed\x08S\x84=\x1f\x1e\xff\x06\x00\x01\t \x02)\xa7\x93\xe2\xfdD\x1b\xafhOH\xef>mn\x91\xcb\x14\x02$\x98\xc7\xef\xb3') == {'mac': 'ed:72:01:03:01:02', 'uuid': '93e2fd441baf684f48ef3e6d6e91cb14', 'major': 548, 'minor': 39111} # noqa - assert blescan.parse_packet(b'\x04>*\x02\x01\x03\x01w\t\xbc\xd0W\xef\x1e\x02\x01\x04\x1a\xffL\x00\x02\x15\xa4\x95\xbb0\xc5\xb1KD\xb5\x12\x13p\xf0-t\xde\x00B\x03\xf7\xc5\xa7') == {'mac': '09:77:01:03:01:02', 'uuid': 'a495bb30c5b14b44b5121370f02d74de', 'major': 66, 'minor': 1015} # noqa diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index c6ef34b..0000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -import pytest -from click.testing import CliRunner - -from tilty import cli -from tilty.exceptions import ConfigurationFileNotFoundException - - -@mock.patch('tilty.tilt_device') -@mock.patch('tilty.cli.sys') -def test_terminate_process( - mock_tilt_device, - mock_sys, -): - cli.terminate_process(mock_tilt_device, None, None) - assert mock_tilt_device.mock_calls == [ - mock.call.stop(), - mock.call.exit() - ] - - -def test_cli_config_dne(): - with pytest.raises(ConfigurationFileNotFoundException): - runner = CliRunner() - result = runner.invoke( - cli.run, - ["--config-file", "/foo"], - catch_exceptions=False - ) - assert result.exit_code == 1 - - -def test_cli_invalid_params(): - runner = CliRunner() - result = runner.invoke(cli.run, ["--foo"]) - assert result.exit_code == 2 - assert result.output == 'Usage: run [OPTIONS]\nTry \'run --help\' for help.\n\nError: no such option: --foo\n' # noqa - - -@mock.patch('tilty.cli.parse_config', return_value={}) -@mock.patch('tilty.cli.pathlib.Path.exists', return_value=True) -@mock.patch('tilty.blescan.get_events', return_value=[{'uuid': 'foo', 'major': 78, 'minor': 1833}]) # noqa -@mock.patch('tilty.blescan.hci_le_set_scan_parameters') # noqa -@mock.patch('tilty.blescan.hci_enable_le_scan') # noqa -def test_cli_no_params_no_valid_data( - bt_enable_scan, - bt_set_scan, - bt_events, - mock_pathlib, - mock_parse_config, -): - runner = CliRunner() - result = runner.invoke(cli.run, []) - assert result.exit_code == 0 - assert result.output == 'Scanning for Tilt data...\n' - - -@mock.patch('tilty.cli.parse_config', return_value={}) -@mock.patch('tilty.cli.pathlib.Path.exists', return_value=True) -@mock.patch('tilty.blescan.get_events', return_value=[]) # noqa -@mock.patch('tilty.blescan.hci_le_set_scan_parameters') # noqa -@mock.patch('tilty.blescan.hci_enable_le_scan') # noqa -def test_cli_no_params_no_data( - bt_enable_scan, - bt_set_scan, - bt_events, - mock_pathlib, - mock_parse_config, -): - runner = CliRunner() - result = runner.invoke(cli.run, []) - assert result.exit_code == 0 - assert result.output == 'Scanning for Tilt data...\n' - - -@mock.patch('tilty.cli.parse_config', return_value={}) -@mock.patch('tilty.cli.pathlib.Path.exists', return_value=True) -@mock.patch('tilty.blescan.get_events', return_value=[{'mac': '00:0a:95:9d:68:16', 'uuid': 'a495bb30c5b14b44b5121370f02d74de', 'major': 60, 'minor': 1053}]) # noqa -@mock.patch('tilty.blescan.hci_le_set_scan_parameters') # noqa -@mock.patch('tilty.blescan.hci_enable_le_scan') # noqa -def test_cli_no_params_success( - bt_enable_scan, - bt_set_scan, - bt_events, - mock_pathlib, - mock_parse_config, -): - runner = CliRunner() - result = runner.invoke(cli.run, []) - assert result.exit_code == 0 - # For some reason logger.info is different in python36 vs python37/38 and I dont care about this test enough to fix that difference # noqa - # assert "Scanning for Tilt data...\n{'color': 'Black', 'gravity': 1.053, 'temp': 60, 'mac': '00:0a:95:9d:68:16', 'timestamp': " in result.output # noqa - assert "Scanning for Tilt data...\n" in result.output diff --git a/tests/test_common.py b/tests/test_common.py deleted file mode 100644 index 2221ef9..0000000 --- a/tests/test_common.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -from tilty import common - - -def test_safe_get_key_no_fallback(): - assert common.safe_get_key({}, 'foo') is None - - -def test_safe_get_key_fallback(): - assert common.safe_get_key({}, 'foo', 'wut') == 'wut' - - -def test_safe_get_key_valid(): - assert common.safe_get_key({'foo': 'asdf'}, 'foo', 'wut') == 'asdf' diff --git a/tests/test_datadog.py b/tests/test_datadog.py deleted file mode 100644 index e68c6eb..0000000 --- a/tests/test_datadog.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -from tilty.emitters import datadog - - -def test_datadog_type( -): - assert datadog.__type__() == 'Datadog' - - -@mock.patch('tilty.emitters.datadog.statsd') -@mock.patch('tilty.emitters.datadog.initialize') -def test_datadog( - mock_statsd_init, - mock_statsd_client, -): - config = { - 'host': 'http://statsd.google.com', - 'port': '8130', - } - tilt_data = { - 'temp': '55', - 'gravity': '1054', - 'color': 'black', - 'mac': '00:0a:95:9d:68:16', - } - datadog.Datadog(config=config).emit(tilt_data) - mock_statsd_init.mock_calls == [ - mock.call(statsd_host='http://statsd.google.com', statsd_port='8130') - ] - assert mock_statsd_client.mock_calls == [ - mock.call.gauge( - 'tilty.temperature', - '55', - tags=['color:black', 'mac:00:0a:95:9d:68:16'] - ), - mock.call.gauge( - 'tilty.gravity', - '1054', - tags=['color:black', 'mac:00:0a:95:9d:68:16'] - ), - ] diff --git a/tests/test_google.py b/tests/test_google.py deleted file mode 100644 index 10bc296..0000000 --- a/tests/test_google.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -from tilty.emitters import google - - -def test_google_type( -): - assert google.__type__() == 'Google' - - -@mock.patch('tilty.emitters.google.httplib2') -@mock.patch('tilty.emitters.google.discovery') -@mock.patch('tilty.emitters.google.client') -@mock.patch( - 'tilty.emitters.google.GOOGLE_REVOKE_URI', - return_value='http://revoke.com' -) -@mock.patch( - 'tilty.emitters.google.GOOGLE_TOKEN_URI', - return_value='http://token.com' -) -def test_google( - mock_token_uri, - mock_revoke_uri, - mock_oauth_client, - mock_googleapi_client, - mock_httplib, -): - config = { - "client_id": "11111111-111111111111111111111", - "client_secret": "xxxxxxxxx-fffffffff-wwwww", - "token_uri": "https://oauth2.googleapis.com/token", - "spreadsheet_id": "xxxxxxxxxxxxxxxx-yyyyyyyyyyyyyyyy", - "access_token": "yyyy.cccc-dddddddddd-eeeeee-ffffffffffffffff", - "refresh_token": "yyyy.cccc-dddddddddd-eeeeee-ffffffffffffffff", - } - - google.Google(config=config).emit(tilt_data={ - 'color': 'black', - 'mac': '00:0a:95:9d:68:16', - 'gravity': 1000, - 'temp': 80, - 'timestamp': 155558888, - }) - assert mock_oauth_client.mock_calls == [ - mock.call.OAuth2Credentials( - access_token='yyyy.cccc-dddddddddd-eeeeee-ffffffffffffffff', - client_id='11111111-111111111111111111111', - client_secret='xxxxxxxxx-fffffffff-wwwww', - refresh_token='yyyy.cccc-dddddddddd-eeeeee-ffffffffffffffff', - revoke_uri=mock.ANY, - token_expiry=None, - token_uri=mock.ANY, - user_agent=None - ), - mock.call.OAuth2Credentials().access_token_expired.__bool__(), - mock.call.OAuth2Credentials().authorize(mock.ANY), - mock.call.OAuth2Credentials().refresh(mock.ANY), - mock.call.OAuth2Credentials().access_token_expired.__bool__(), - mock.call.OAuth2Credentials().authorize(mock.ANY), - mock.call.OAuth2Credentials().refresh(mock.ANY), - ] - - assert mock_googleapi_client.mock_calls == [ - mock.call.build('sheets', 'v4', credentials=mock.ANY), - mock.call.build().spreadsheets(), - mock.call.build().spreadsheets().values(), - mock.call.build().spreadsheets().values().append( - body={ - 'majorDimension': 'ROWS', - 'values': [[ - 155558888, - 'TODO', - 1000, - 80, - 'BLACK', - '00:0a:95:9d:68:16' - ]] - }, - range='Data!A:F', - spreadsheetId='xxxxxxxxxxxxxxxx-yyyyyyyyyyyyyyyy', - valueInputOption='USER_ENTERED' - ), - mock.call.build().spreadsheets().values().append().execute(), - ] - assert mock_httplib.mock_calls == [mock.call.Http(), mock.call.Http()] diff --git a/tests/test_influxdb.py b/tests/test_influxdb.py deleted file mode 100644 index be41171..0000000 --- a/tests/test_influxdb.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -from tilty.emitters import influxdb - - -def test_influxdb_type( -): - assert influxdb.__type__() == 'InfluxDB' - - -@mock.patch('tilty.emitters.influxdb.InfluxDBClient') -def test_influxdb( - mock_influx_client, -): - config = { - 'url': 'http://www.google.com', - 'org': 'foo', - 'bucket': 'wat', - 'token': 'somelongtoken', - 'gravity_payload_template': '{"measurement": "gravity", "tags": {"color": "{{ color }}"}, "fields": {"value": {{ gravity }}}}', # noqa - 'temperature_payload_template': '{"measurement": "temperature", "tags": {"color": "{{ color }}"}, "fields": {"value": {{ temp }}}}', # noqa - } - influxdb.InfluxDB(config=config).emit({ - 'temp': 80, - 'color': 'black', - 'gravity': 1.054, - 'timestamp': 155558888, - 'mac': 'foo', - }) - assert mock_influx_client.mock_calls == [ - mock.call( - org='foo', - token='somelongtoken', - url='http://www.google.com', - verify_ssl=False - ), - mock.call().write_api(write_options=mock.ANY), - mock.call().write_api().write( - bucket='wat', - org='foo', - record='{"measurement": "temperature", "tags": {"color": "black"}, "fields": {"value": 80}}' # noqa - ), - mock.call().write_api().write( - bucket='wat', - org='foo', - record='{"measurement": "gravity", "tags": {"color": "black"}, "fields": {"value": 1.054}}' # noqa - ), - ] diff --git a/tests/test_prometheus.py b/tests/test_prometheus.py deleted file mode 100644 index 61713e7..0000000 --- a/tests/test_prometheus.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -from tilty.emitters import prometheus - - -def test_prometheus_type( -): - assert prometheus.__type__() == 'Prometheus' - - -@mock.patch('tilty.emitters.prometheus.push_to_gateway') -def test_prometheus( - mock_prometheus_client, -): - config = { - 'url': 'localhost:8000', - 'gravity_gauge_name': 'gravity_g', - 'temp_gauge_name': 'temp_f', - 'labels': '{"color": "{{ color }}"}' - } - prometheus.Prometheus(config=config).emit({ - 'temp': 80, - 'color': 'black', - 'gravity': 1.054, - 'timestamp': 155558888, - 'mac': 'foo', - }) - assert mock_prometheus_client.call_count == 1 - assert mock_prometheus_client.call_args[0][0] == 'localhost:8000' - assert mock_prometheus_client.call_args[1]['job'] == 'tilty' - registry = mock_prometheus_client.call_args[1]['registry'] - for i, metric in enumerate(registry.collect()): - assert metric.name in ['gravity_g', 'temp_f'] - if metric.name == 'gravity_g': - assert metric.samples[0].value == 1.054 - else: - assert metric.samples[0].value == 80 - assert metric.samples[0].labels['color'] == 'black' - assert i == 1 # => 2 iterations diff --git a/tests/test_sqlite.py b/tests/test_sqlite.py deleted file mode 100644 index 462d1db..0000000 --- a/tests/test_sqlite.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -from tilty.emitters import sqlite - - -def test_sqlite_type( -): - assert sqlite.__type__() == 'SQLite' - - -@mock.patch('tilty.emitters.sqlite.sqlite3') -def test_sqlite( - mock_sqlite_client, -): - config = { - 'file': '/etc/tilty/tilt.sqlite', - } - sqlite.SQLite(config=config).emit(tilt_data={ - 'color': 'black', - 'mac': '00:0a:95:9d:68:16', - 'gravity': 1000, - 'temp': 80, - }) - assert mock_sqlite_client.mock_calls == [ - mock.call.connect('/etc/tilty/tilt.sqlite'), - mock.call.connect().execute('\n CREATE TABLE IF NOT EXISTS data(\n id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n gravity INTEGER,\n temp INTEGER,\n color VARCHAR(16),\n mac VARCHAR(17),\n timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n '), # noqa - mock.call.connect().execute('insert into data (gravity,temp,color,mac) values (?,?,?,?)', (1000, 80, 'black', '00:0a:95:9d:68:16')), # noqa - mock.call.connect().commit(), - ] diff --git a/tests/test_stdout.py b/tests/test_stdout.py deleted file mode 100644 index 247804a..0000000 --- a/tests/test_stdout.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -from tilty.emitters import stdout - - -def test_stdout_type( -): - assert stdout.__type__() == 'Stdout' - - -def test_stdout(): - config = {} - stdout.Stdout(config=config).emit(tilt_data={ - 'color': 'black', - 'mac': '00:0a:95:9d:68:16', - 'gravity': 1000, - 'temp': 80, - }) diff --git a/tests/test_tilt_device.py b/tests/test_tilt_device.py deleted file mode 100644 index b67814c..0000000 --- a/tests/test_tilt_device.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -from tilty import tilt_device - - -@mock.patch('tilty.blescan.hci_disable_le_scan') -def test_scan_for_tilt_data( - mock_disable_le_scan, -): - t = tilt_device.TiltDevice() - t.stop() - mock_disable_le_scan.assert_called() diff --git a/tests/test_tilty.py b/tests/test_tilty.py deleted file mode 100644 index 319cdee..0000000 --- a/tests/test_tilty.py +++ /dev/null @@ -1,262 +0,0 @@ -# -*- coding: utf-8 -*- -from unittest import mock - -import pytest - -from mock_config_parser import MockConfigParser -from mock_config_parser_mac import MockConfigParserMac -from tilty import tilt_device, tilty -from tilty.emitters import datadog, influxdb, sqlite, webhook -from tilty.exceptions import ConfigurationFileEmptyException -from tilty.tilty import parse_config - - -@mock.patch('tilty.emitters.sqlite.sqlite3') -def test_parse_config( - mock_sqlite, -): - config = MockConfigParser('sqlite', include_extra_section=True) - emitters = parse_config(config) - assert len(emitters) == 1 - assert str(type(emitters[0])) == "" - - -def test_parse_config_empty(): - with pytest.raises(ConfigurationFileEmptyException): - config = MockConfigParser('', return_empty=True) - emitters = parse_config(config) - assert not emitters - - -def test_parse_config_invalid_emitter(): - with pytest.raises(ModuleNotFoundError): - config = MockConfigParser('', include_extra_section=True) - emitters = parse_config(config) - assert not emitters - - - -@mock.patch('tilty.blescan.get_events', return_value=[{'mac': '00:0a:95:9d:68:16', 'uuid': 'a495bb30c5b14b44b5121370f02d74de', 'major': 2, 'minor': 1}]) # noqa -def test_scan_for_tilt_data( - bt_events, -): - t = tilt_device.TiltDevice() - tilt_data = t.scan_for_tilt_data() - bt_events.assert_called() - assert tilt_data == [{ - 'color': 'Black', - 'gravity': 0.001, - 'temp': 2.0, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': mock.ANY, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de' - }] - - assert t.scan_for_tilt_data( - temperature_offset=10, - gravity_offset=-0.05, - ) == [{ - 'color': 'Black', - 'gravity': -0.049, - 'temp': 12, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': mock.ANY, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de' - }] - - assert t.scan_for_tilt_data( - temperature_offset=-5, - gravity_offset=0.001, - ) == [{ - 'color': 'Black', - 'gravity': 0.002, - 'temp': -3.0, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': mock.ANY, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de' - }] - - -@mock.patch('tilty.emitters.sqlite.sqlite3') -def test_scan_for_tilt_data_parse_sqlite( - mock_sqlite, -): - config = MockConfigParser('sqlite')[0] - emitter = sqlite.SQLite(config=config) - tilty.emit( - emitters=[emitter], - tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - } - ) - assert mock_sqlite.mock_calls == [ - mock.call.connect('/foo.sqlite'), - mock.call.connect().execute('\n CREATE TABLE IF NOT EXISTS data(\n id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n gravity INTEGER,\n temp INTEGER,\n color VARCHAR(16),\n mac VARCHAR(17),\n timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)\n '), # noqa - mock.call.connect().execute('insert into data (gravity,temp,color,mac) values (?,?,?,?)', (1, 32, 'black', '00:0a:95:9d:68:16')), # noqa - mock.call.connect().commit() - ] - - -@mock.patch('tilty.emitters.webhook.Webhook') -def test_scan_for_tilt_data_parse_webhook( - mock_webhook, -): - config = MockConfigParser('webhook')[0] - emitter = webhook.Webhook(config=config) - tilty.emit( - emitters=[emitter], - tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - } - ) - assert mock_webhook.mock_calls == [ - mock.call(config={ - 'url': 'http://www.google.com', - 'headers': '{"Content-Type": "application/json"}', - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "timestamp": "{{ timestamp }}"}', # noqa - 'method': 'GET' - }), - mock.call().emit(tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - }) - ] - - -@mock.patch('tilty.emitters.webhook.Webhook') -def test_scan_for_tilt_data_parse_webhook_with_mac( - mock_webhook, -): - config = MockConfigParserMac('webhook')[0] - emitter = webhook.Webhook(config=config) - tilty.emit( - emitters=[emitter], - tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - } - ) - assert mock_webhook.mock_calls == [ - mock.call(config={ - 'url': 'http://www.google.com', - 'headers': '{"Content-Type": "application/json"}', 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "mac": "{{ mac }}", "timestamp": "{{ timestamp }}"}', # noqa - 'method': 'GET' - }), - mock.call().emit(tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - }) - ] - - -@mock.patch('tilty.emitters.influxdb.InfluxDB') -def test_scan_for_tilt_data_parse_influxdb( - mock_influxdb, -): - config = MockConfigParser('influxdb')[0] - emitter = influxdb.InfluxDB(config=config) - tilty.emit( - emitters=[emitter], - tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - } - ) - assert mock_influxdb.mock_calls == [ - mock.call(config={ - 'url': 'http://www.google.com', - 'database': 'foo', - 'gravity_payload_template': 'gravity,color={{ color }} value={{ gravity }} {{timestamp}}', # noqa - 'temperature_payload_template': 'temperature,scale=fahrenheit,color={{ color }} value={{ temp }} {{timestamp}}' # noqa - }), - mock.call().emit(tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - }) - ] - - -@mock.patch('tilty.emitters.influxdb.InfluxDB') -def test_scan_for_tilt_data_parse_influxdb_with_mac( - mock_influxdb, -): - config = MockConfigParserMac('influxdb')[0] - emitter = influxdb.InfluxDB(config=config) - tilty.emit( - emitters=[emitter], - tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - } - ) - assert mock_influxdb.mock_calls == [ - mock.call(config={ - 'url': 'http://www.google.com', - 'database': 'foo', - 'gravity_payload_template': 'gravity,mac={{ mac }} color={{ color }} value={{ gravity }} {{timestamp}}', # noqa - 'temperature_payload_template': 'temperature,scale=fahrenheit,mac={{ mac }} color={{ color }} value={{ temp }} {{timestamp}}' # noqa - }), - mock.call().emit(tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - }) - ] - - -@mock.patch('tilty.emitters.datadog.Datadog') -def test_scan_for_tilt_data_parse_datadog( - mock_dd, -): - config = MockConfigParser('datadog')[0] - emitter = datadog.Datadog(config=config) - tilty.emit( - emitters=[emitter], - tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - } - ) - assert mock_dd.mock_calls == [ - mock.call(config={ - 'host': 'http://api.datadog.com', - 'port': '8120' - }), - mock.call().emit(tilt_data={ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888 - }) - ] diff --git a/tests/test_webhook.py b/tests/test_webhook.py deleted file mode 100644 index 95130f3..0000000 --- a/tests/test_webhook.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from unittest import mock - -import pytest - -from tilty.emitters import webhook - - -def test_webhook_type( -): - assert webhook.__type__() == 'Webhook' - - -@mock.patch('tilty.emitters.webhook.METHODS') -def test_webhook_get( - mock_requests, -): - config = { - 'url': 'http://www.google.com', - 'headers': '{"Content-Type": "application/json"}', - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}}', # noqa - 'method': 'GET', - } - webhook.Webhook(config=config).emit({ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de' - }) - assert mock_requests.mock_calls == [ - mock.call.get('GET'), - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'black', 'gravity': 1, 'temp': 32}, url='http://www.google.com') # noqa - ] - - -@mock.patch('tilty.emitters.webhook.METHODS') -def test_webhook_post_json( - mock_requests, -): - config = { - 'url': 'http://www.google.com', - 'headers': '{"Content-Type": "application/json"}', - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}}', # noqa - 'method': 'POST', - } - webhook.Webhook(config=config).emit({ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de' - }) - assert mock_requests.mock_calls == [ - mock.call.get('POST'), - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'black', 'gravity': 1, 'temp': 32}, - url='http://www.google.com' - ) - ] - - -@mock.patch('tilty.emitters.webhook.METHODS') -def test_webhook_post_data( - mock_requests, -): - config = { - 'url': 'http://www.google.com', - 'headers': '{"Content-Type": "text/plain"}', - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "timestamp": "{{ timestamp }}"}', # noqa - 'method': 'POST', - } - webhook.Webhook(config=config).emit({ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de' - }) - assert mock_requests.mock_calls == [ - mock.call.get('POST'), - mock.ANY, - mock.call.get()( - data={'color': 'black', 'gravity': 1, 'temp': 32, 'timestamp': '155558888'}, # noqa - headers={'Content-Type': 'text/plain'}, - url='http://www.google.com' - ), - mock.call.get()().raise_for_status(), - ] - - -def test_webhook_invalid_method(): - config = { - 'url': 'http://www.google.com', - 'headers': {'Content-Type': 'application/json'}, - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "timestamp": "{{ timestamp }}"}', # noqa - 'method': 'FOO', - } - with pytest.raises(KeyError): - webhook.Webhook(config=config).emit({ - 'color': 'black', - 'gravity': 1, - 'temp': 32, - 'mac': '00:0a:95:9d:68:16', - 'timestamp': 155558888, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de' - }) - - -@mock.patch('tilty.emitters.webhook.METHODS') -def test_webhook_delay_minutes( - mock_requests, -): - config = { - 'url': 'http://example.com', - 'headers': '{"Content-Type": "application/json"}', - 'delay_minutes': '3', - 'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}}', # noqa - 'method': 'GET', - } - - wh = webhook.Webhook(config=config) - # On init, we load delay_minutes from config - assert wh.delay_minutes == 3 - # delay_until is unset until emitting calling emit once - delay_until = wh.delay_until.get('black') - assert delay_until is None - wh.emit({ - 'color': 'black', - 'gravity': 1, - 'mac': '00:0a:95:9d:68:16', - 'temp': 32, - 'timestamp': 155558888, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de', - }) - wh.emit({ - 'color': 'black', - 'gravity': 2, - 'mac': '00:0a:95:9d:68:16', - 'temp': 33, - 'timestamp': 155558899, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de', - }) - now = datetime.datetime.now(datetime.timezone.utc) - assert wh.delay_minutes == 3 - # delay_until should be set for about 3 minutes from now - delay_until = wh.delay_until.get('black') - assert delay_until is not None and delay_until >= now - # emitted twice, but the second returned before actually sending a request. - assert mock_requests.mock_calls == [ - mock.call.get('GET'), - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'black', 'gravity': 1, 'temp': 32}, url='http://example.com') # noqa - ] - - # enxure that the blue tilt can send while the black one is waiting - delay_until = wh.delay_until.get('blue') - assert delay_until is None - wh.emit({ - 'color': 'blue', - 'gravity': 99, - 'mac': '00:0a:95:9d:68:17', - 'temp': 99, - 'timestamp': 155559999, - 'uuid': 'a495bb60c5b14b44b5121370f02d74de', - }) - delay_until = wh.delay_until.get('blue') - assert delay_until is not None and delay_until >= now - assert mock_requests.mock_calls == [ - mock.call.get('GET'), - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'black', 'gravity': 1, 'temp': 32}, url='http://example.com'), # noqa - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'blue', 'gravity': 99, 'temp': 99}, url='http://example.com') # noqa - ] - - # move the clock forward by setting delay_until to the past, which should - # allow a request to process again - wh.delay_until['black'] = now - datetime.timedelta(minutes=1) - wh.emit({ - 'color': 'black', - 'gravity': 3, - 'mac': '00:0a:95:9d:68:16', - 'temp': 34, - 'timestamp': 155558899, - 'uuid': 'a495bb30c5b14b44b5121370f02d74de', - }) - # delay_until is once again about 3 minutes in the future - delay_until = wh.delay_until.get('black') - assert delay_until is not None and delay_until >= now - # we now see the request that was made after the delay timeout - assert mock_requests.mock_calls == [ - mock.call.get('GET'), - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'black', 'gravity': 1, 'temp': 32}, url='http://example.com'), # noqa - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'blue', 'gravity': 99, 'temp': 99}, url='http://example.com'), # noqa - mock.ANY, - mock.call.get()( - headers={'Content-Type': 'application/json'}, - json={'color': 'black', 'gravity': 3, 'temp': 34}, url='http://example.com') # noqa - ] diff --git a/tilt/config.go b/tilt/config.go new file mode 100644 index 0000000..3339999 --- /dev/null +++ b/tilt/config.go @@ -0,0 +1,69 @@ +package tilt + +import ( + "errors" + "fmt" + "github.com/go-kit/kit/log/level" + "github.com/spf13/viper" + "os" + "path/filepath" + "strings" +) + +type Config struct { + ConfigFile string + ConfigData *viper.Viper + EnabledEmitter string +} + +func ParseConfig(configFile string) Config { + _viper := viper.New() + + // Set Some Defaults + _viper.SetDefault("general.logging_level", "INFO") + _viper.SetDefault("general.logfile", "/dev/stdout") + _viper.SetDefault("general.gravity_offset", 0.0) + _viper.SetDefault("general.temperature_offset", 0) + + if _, err := os.Stat(configFile); errors.Is(err, os.ErrNotExist) { + level.Debug(Logger).Log( + "config.ParseConfig", + fmt.Sprintf("Config file %s does not exist. Using all default values.", configFile), + ) + _viper.SetDefault("stdout.enabled", true) + } else { + level.Debug(Logger).Log("config.ParseConfig", fmt.Sprintf("Using config file: %s", configFile)) + _viper.SetConfigType(filepath.Ext(configFile)[1:]) + _viper.SetConfigName(filepath.Base(configFile)) + _viper.AddConfigPath(filepath.Dir(configFile)) + + err := _viper.ReadInConfig() + if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("fatal error config file: %w", err)) + } + } + level.Debug(Logger).Log("config.ParseConfig", fmt.Sprintf("Log Level: %s", _viper.Get("general.logging_level"))) + level.Debug(Logger).Log("config.ParseConfig", fmt.Sprintf("Log File: %s", _viper.Get("general.logfile"))) + level.Debug(Logger).Log("config.ParseConfig", fmt.Sprintf("Gravity Offset: %f", _viper.Get("general.gravity_offset"))) + level.Debug(Logger).Log("config.ParseConfig", fmt.Sprintf("Temperature Offset: %d", _viper.Get("general.temperature_offset"))) + + enabledEmitter := "" + for _, emitter := range _viper.AllKeys() { + _emitterPair := strings.Split(emitter, ".") + if _emitterPair[1] == "enabled" { + enabledEmitter = _emitterPair[0] + break + } + } + + if len(enabledEmitter) == 0 { + level.Debug(Logger).Log("config.ParseConfig", "No enabled emitters in configuration. Using 'stdout'") + enabledEmitter = "stdout" + } + + return Config{ + ConfigFile: configFile, + ConfigData: _viper, + EnabledEmitter: enabledEmitter, + } +} diff --git a/tilt/device.go b/tilt/device.go new file mode 100644 index 0000000..4c09b3a --- /dev/null +++ b/tilt/device.go @@ -0,0 +1,24 @@ +package tilt + +var TiltMap = map[string]string{ + "a495bb30c5b14b44b5121370f02d74de": "BLACK", + "a495bb60c5b14b44b5121370f02d74de": "BLUE", + "a495bb20c5b14b44b5121370f02d74de": "GREEN", + "a495bb50c5b14b44b5121370f02d74de": "ORANGE", + "a495bb80c5b14b44b5121370f02d74de": "PINK", + "a495bb40c5b14b44b5121370f02d74de": "PURPLE", + "a495bb10c5b14b44b5121370f02d74de": "RED", + "a495bb70c5b14b44b5121370f02d74de": "YELLOW", + "a495bb90c5b14b44b5121370f02d74de": "TEST", + "25cc0b60914de76ead903f903bfd5e53": "MIGHTY", +} + +type TiltPayload struct { + Id string + Mac string + Color string `validate:"required"` + Major uint16 + Minor uint16 + Rssi int + Timestamp string +} diff --git a/tilt/logger.go b/tilt/logger.go new file mode 100644 index 0000000..0227ef9 --- /dev/null +++ b/tilt/logger.go @@ -0,0 +1,38 @@ +package tilt + +import ( + "fmt" + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "os" + "strings" +) + +var Logger log.Logger + +func EnableLogging() { + Logger = log.NewLogfmtLogger(os.Stderr) + Logger = level.NewFilter(Logger, level.AllowDebug()) + Logger = log.With(Logger, "ts", log.DefaultTimestampUTC) +} + +func SetLogging(logLevel string) { + Logger = log.NewLogfmtLogger(os.Stderr) + + switch strings.ToLower(logLevel) { + case "all": + Logger = level.NewFilter(Logger, level.AllowAll()) + case "debug": + Logger = level.NewFilter(Logger, level.AllowDebug()) + case "info": + Logger = level.NewFilter(Logger, level.AllowInfo()) + case "none": + Logger = level.NewFilter(Logger, level.AllowNone()) + case "warn": + Logger = level.NewFilter(Logger, level.AllowWarn()) + default: + Logger = level.NewFilter(Logger, level.AllowInfo()) + } + Logger = log.With(Logger, "ts", log.DefaultTimestampUTC) + level.Info(Logger).Log("logger.SetLogging", fmt.Sprintf("Setting log level to %s", logLevel)) +} diff --git a/tilty/__init__.py b/tilty/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tilty/blescan.py b/tilty/blescan.py deleted file mode 100644 index 8700adc..0000000 --- a/tilty/blescan.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=line-too-long,missing-function-docstring -""" This Module parses iBeacon events for the tilt hydrometer """ -import struct - -import bluetooth._bluetooth as bluez - - -def get_socket(device_id): - return bluez.hci_open_dev(device_id) - - -def string_packet(pkt): - # UUID is 16 Bytes - # b'\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - # so len() is 16 - # loop over each byte, get it to hex, build up the string (uuid is 32 chars, 16bytes) # noqa - return ''.join(["%02x" % int.from_bytes(pkt[i:i+1], "big") for i in range(len(pkt))]) # noqa # pylint: disable=consider-using-f-string - - -def packed_bdaddr_to_string(bdaddr_packed): - # iBeacon packets have the mac byte-reversed, reverse with bdaddr_packed[::-1] # noqa - # b'ID\x8b\xea&b' -> b'b&\xea\x8bDI' - # decode to int -> (98, 38, 234, 139, 68, 73) , join by : as hex -> '62:26:ea:8b:44:49' # noqa - return ':'.join('%02x' % i for i in struct.unpack("* \x02\x01x03\x01w\t \xbc\xd0W\xef\x1e\x02\x01\x04\x1a\xffL\x00\x02\x15 \xa4\x95\xbb0\xc5\xb1KD\xb5\x12\x13p\xf0-t\xde \x00B \x03\xf7 \xc5\xa7' # noqa - # | | | | | | | | | # noqa - # | preamble+header | PDU | # noqa - # | 3 bytes | x bytes (plen) | # noqa - # | | | mac addr | uuid | unused data | major| minor | tx | # noqa - # | | | | | | temp | gravity | | # noqa - ptype, event, plen = struct.unpack("BBB", pkt[:3]) # b'\x04>+' -> (4, 62, 40) # pylint:disable=unused-variable # noqa - if event == 0x3e: # 62 -> 0x3e -> HCI Event: LE Meta Event (0x3e) plen 44 - subevent, = struct.unpack("B", pkt[3:4]) # b'\x02' -> (2,) - if subevent == 0x02: # if 0x02 (2) -> all iBeacons use this - return { - 'mac': packed_bdaddr_to_string(pkt[3:9]), # mac -> 6 bytes -> b'\x02\x01\x03\x01w\t' # noqa - 'uuid': string_packet(pkt[-22:-6]), # uuid -> 16bytes -> b'\xa4\x95\xbb0\xc5\xb1KD\xb5\x12\x13p\xf0-t\xde' # noqa - 'major': int.from_bytes(pkt[-6:-4], "big"), # major -> 2 bytes -> b'\x00B' # noqa - 'minor': int.from_bytes(pkt[-4:-2], "big"), # minor -> 2 bytes -> b'\x03\xf7' # noqa - } - return {} diff --git a/tilty/cli.py b/tilty/cli.py deleted file mode 100644 index c84f3b0..0000000 --- a/tilty/cli.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -""" Main Click methods """ - -import configparser -import logging -import pathlib -import signal -import sys -import threading -import traceback -from functools import partial -from time import sleep -from typing import List - -import click - -from tilty import tilt_device -from tilty.common import safe_get_key -from tilty.exceptions import ConfigurationFileNotFoundException -from tilty.tilty import LOGGER, emit, parse_config - -CONFIG = configparser.ConfigParser() - - -def terminate_process( - device: tilt_device.TiltDevice, - signal_number: int, - frame: None -): # noqa # pylint: disable=unused-argument - """ handle SIGTERM - - Args: - device (TiltDevice): The bluetooth device to operate on. - signal_number (int): The signal to operate on - frame (TODO): The TODO - - """ - device.stop() - sys.exit() - - -def scan_and_emit( - device: tilt_device.TiltDevice, - emitters: List[dict], - gravity_offset: float, - temperature_offset: float -): - """ Scans and emits the data via the loaded emitters. - - Args: - device (TiltDevice): The bluetooth device to operate on. - emitters ([dict]): The emitters to use. - """ - - LOGGER.debug('Starting device scan') - tilt_data = device.scan_for_tilt_data( - gravity_offset=gravity_offset, - temperature_offset=temperature_offset, - ) - if tilt_data: - for event in tilt_data: - LOGGER.debug('tilt data retrieved') - LOGGER.info(event) - emit(emitters=emitters, tilt_data=event) - else: - LOGGER.debug('No tilt data') - - -def scan_and_emit_thread( - device: tilt_device.TiltDevice, - config: configparser.ConfigParser, - keep_running: bool = False -) -> None: - """ method that calls the needful - - Args: - device (TiltDevice): The bluetooth device to operate on. - config (dict): The parsed configuration - keep_running (bool): Whether or not to keep running. Default: False - """ - emitters = parse_config(config) - click.echo('Scanning for Tilt data...') - - gravity_offset = float( - safe_get_key(CONFIG, 'general', {}).get('gravity_offset', '0') - ) - LOGGER.debug('Gravity offset: %f', gravity_offset) - temperature_offset = float( - safe_get_key(CONFIG, 'general', {}).get('temperature_offset', '0') - ) - LOGGER.debug('Temperature offset: %f', temperature_offset) - - scan_and_emit(device, emitters, gravity_offset, temperature_offset) - while keep_running: - LOGGER.debug('Scanning for Tilt data...') - try: - scan_and_emit(device, emitters, gravity_offset, temperature_offset) - except Exception as exception: # pylint: disable=broad-except - LOGGER.error( - "%s\n%s", - str(exception), - traceback.format_tb(exception.__traceback__) - ) - sleep_time = int(CONFIG['general'].get('sleep_interval', '1')) - LOGGER.debug('Sleeping for %s....', sleep_time) - sleep(sleep_time) - - -@click.command() -@click.option( - '--keep-running', - '-r', - is_flag=True, - help="Keep running until SIGTERM", -) -@click.option( - '--config-file', - '-c', - default='config.ini', - help="configuration file path", -) -def run( - keep_running: bool, - config_file: str = 'config.ini', -): - """ - main cli entrypoint - - Args: - keep_running (bool): Whether or not to keep running. Default: False - config_file (str): The configuration file location to load. - """ - file = pathlib.Path(config_file) - if not file.exists(): - raise ConfigurationFileNotFoundException() - - CONFIG.read(config_file) - - handler = logging.StreamHandler(sys.stdout) - logging_level = 'INFO' - try: - logging_level = CONFIG['general'].get('logging_level', 'INFO') - logfile = CONFIG['general'].get('logfile', None) - if logfile: - handler = logging.FileHandler(filename=logfile) - except KeyError: - pass - LOGGER.setLevel(logging.getLevelName(logging_level)) - handler.setLevel(logging_level) - LOGGER.addHandler(handler) - - device = tilt_device.TiltDevice() - signal.signal(signal.SIGINT, partial(terminate_process, device)) - device.start() - threading.Thread( - target=scan_and_emit_thread, - name='tilty_daemon', - args=(device, CONFIG, keep_running) - ).start() diff --git a/tilty/common.py b/tilty/common.py deleted file mode 100644 index cc26d39..0000000 --- a/tilty/common.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -""" Common methods """ - -from typing import Any, Optional - - -def safe_get_key(config, key: str, fallback: Optional[Any] = None): - """ Class to safely pull key from config or a fallback value - - Args: - config (dict): The configuration (dict) to try and pull from - key (str): The config key to try and get. - fallback (TODO): TODO - """ - try: - return config[key] - except KeyError: - pass - return fallback diff --git a/tilty/constants.py b/tilty/constants.py deleted file mode 100644 index 01dc2a4..0000000 --- a/tilty/constants.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -""" Constant variables """ - - -TILT_DEVICES = { - 'a495bb30c5b14b44b5121370f02d74de': 'Black', - 'a495bb60c5b14b44b5121370f02d74de': 'Blue', - 'a495bb20c5b14b44b5121370f02d74de': 'Green', - 'a495bb50c5b14b44b5121370f02d74de': 'Orange', - 'a495bb80c5b14b44b5121370f02d74de': 'Pink', - 'a495bb40c5b14b44b5121370f02d74de': 'Purple', - 'a495bb10c5b14b44b5121370f02d74de': 'Red', - 'a495bb70c5b14b44b5121370f02d74de': 'Yellow', - 'a495bb90c5b14b44b5121370f02d74de': 'Test', -} diff --git a/tilty/emitters/__init__.py b/tilty/emitters/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tilty/emitters/datadog.py b/tilty/emitters/datadog.py deleted file mode 100644 index 19fc830..0000000 --- a/tilty/emitters/datadog.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" DataDog emitter """ -import logging - -from datadog import initialize, statsd - -from tilty.common import safe_get_key - -LOGGER = logging.getLogger() - - -def __type__() -> str: - return 'Datadog' - - -class Datadog: # pylint: disable=too-few-public-methods - """ Class to represent the actual device """ - def __init__(self, config: dict) -> None: - """ Initializer - - Args: - config: (dict) represents the configuration for the emitter - """ - # [datadog] - # host = 'host' - # port = 'port' - options = { - 'statsd_host': config['host'], - 'statsd_port': safe_get_key(config, 'port', 8125), - } - initialize(**options) - - def emit(self, tilt_data: dict) -> None: # pylint:disable=no-self-use - """ Initializer - - Args: - tilt_data (dict): data returned from valid tilt device scan - """ - LOGGER.info('[datadog] posting temperature data') - tags = [f"color:{tilt_data['color']}"] - if tilt_data['mac']: - tags = [ - f"color:{tilt_data['color']}", - f"mac:{tilt_data['mac']}", - ] - statsd.gauge( - 'tilty.temperature', - tilt_data['temp'], - tags=tags, - ) - LOGGER.info('[datadog] posting gravity data') - statsd.gauge( - 'tilty.gravity', - tilt_data['gravity'], - tags=tags, - ) diff --git a/tilty/emitters/google.py b/tilty/emitters/google.py deleted file mode 100644 index 2f3d249..0000000 --- a/tilty/emitters/google.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -""" Google Sheets emitter """ -import logging -from typing import Any, Dict - -import httplib2 -from googleapiclient import discovery -from oauth2client import GOOGLE_REVOKE_URI, GOOGLE_TOKEN_URI, client - -LOGGER = logging.getLogger() - - -def __type__() -> str: - return 'Google' - - -class Google: # pylint: disable=too-few-public-methods - """ Google wrapper class """ - - def __init__(self, config: dict) -> None: - """ Initializer - - Args: - config: (dict) represents the configuration for the emitter - """ - # - # [google] - # access_token = 11111111111111111111111111 - # client_id = 111111-1111.apps.googleusercontent.com - # client_secret = 1111111111111111 - # refresh_token = 11111111111111111111111111 - # spreadsheet_id = 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms - - self.access_token: Any = config.get('access_token') - self.credentials: client.OAuth2Credentials = client.OAuth2Credentials( - access_token=self.access_token, - client_id=config['client_id'], - client_secret=config['client_secret'], - refresh_token=config['refresh_token'], - token_expiry=None, - token_uri=GOOGLE_TOKEN_URI, - user_agent=None, - revoke_uri=GOOGLE_REVOKE_URI, - ) - self.refresh_if_needed() - self.spreadsheet_id: str = config['spreadsheet_id'] - - def refresh_if_needed(self) -> None: - """ OAuth Refresh helper - - Args: - """ - if not self.access_token or self.credentials.access_token_expired: - LOGGER.info('[google] refreshing access token') - http = self.credentials.authorize(httplib2.Http()) - self.credentials.refresh(http) - - def emit(self, tilt_data: dict) -> None: - """ Emitter - - Args: - tilt_data (dict): data returned from valid tilt device scan - """ - self.refresh_if_needed() - service: discovery.Resource = discovery.build( - 'sheets', - 'v4', - credentials=self.credentials - ) - resource: Dict[str, Any] = { - "majorDimension": "ROWS", - "values": [[ - tilt_data['timestamp'], - 'TODO', - tilt_data['gravity'], - tilt_data['temp'], - tilt_data['color'].upper(), - tilt_data['mac'], - ]] - } - LOGGER.info('[google] inserting sheet data') - service.spreadsheets().values().append( - spreadsheetId=self.spreadsheet_id, - range='Data!A:F', - body=resource, - valueInputOption="USER_ENTERED" - ).execute() diff --git a/tilty/emitters/influxdb.py b/tilty/emitters/influxdb.py deleted file mode 100644 index 6bb072e..0000000 --- a/tilty/emitters/influxdb.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -""" InfluxDB emitter """ -import logging -from distutils import util as distutil # noqa # pylint: disable=line-too-long,deprecated-module,fixme # TODO: fix this - -from influxdb_client import InfluxDBClient -from influxdb_client.client.write_api import SYNCHRONOUS -from jinja2 import Template - -from tilty.common import safe_get_key - -LOGGER = logging.getLogger() - - -def __type__() -> str: - return 'InfluxDB' - - -class InfluxDB: # pylint: disable=too-few-public-methods - """ Class to represent the actual device """ - def __init__(self, config: dict) -> None: - """ Initializer - - Args: - config: (dict) represents the configuration for the emitter - """ - self.gravity_template = Template(config['gravity_payload_template']) # noqa - self.temperature_template = Template(config['temperature_payload_template']) # noqa - self.bucket = safe_get_key(config, 'bucket') - - verify_ssl = bool(distutil.strtobool( - safe_get_key(config, 'verify_ssl', 'False') - )) - self.org = safe_get_key(config, 'org') - client = InfluxDBClient( - url=config['url'], - org=self.org, - token=safe_get_key(config, 'token'), - verify_ssl=verify_ssl - ) - self.write_api = client.write_api(write_options=SYNCHRONOUS) - - def emit(self, tilt_data: dict) -> None: - """ Initializer - - Args: - tilt_data (dict): data returned from valid tilt device scan - """ - temperature_payload = self.temperature_template.render( - color=tilt_data['color'], - gravity=tilt_data['gravity'], - mac=tilt_data['mac'], - temp=tilt_data['temp'], - ) - gravity_payload = self.gravity_template.render( - color=tilt_data['color'], - gravity=tilt_data['gravity'], - mac=tilt_data['mac'], - temp=tilt_data['temp'], - ) - LOGGER.info('[influxdb] posting temperature data') - self.write_api.write( - bucket=self.bucket, - org=self.org, - record=temperature_payload - ) - LOGGER.info('[influxdb] posting gravity data') - self.write_api.write( - bucket=self.bucket, - org=self.org, - record=gravity_payload - ) diff --git a/tilty/emitters/prometheus.py b/tilty/emitters/prometheus.py deleted file mode 100644 index 7427b83..0000000 --- a/tilty/emitters/prometheus.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -""" Prometheus emitter """ -import json -import logging - -from jinja2 import Template -from prometheus_client import CollectorRegistry, Gauge, push_to_gateway - -from tilty.common import safe_get_key - -LOGGER = logging.getLogger() - - -def __type__() -> str: - return "Prometheus" - - -class Prometheus: # pylint: disable=too-few-public-methods - """ Class to represent the actual device """ - def __init__(self, config: dict) -> None: - """ Initializer - - Args: - config: (dict) represents the configuration for the emitter - """ - # [prometheus] - # url = localhost:80 - # gravity_gauge_name = tilty_gravity_g - # temp_gauge_name = tilty_temperature_f - # labels = {"color": "{{ color }}", "mac": "{{ mac }}"} - # job_name = tilty - self.job_name = safe_get_key(config, 'job_name', 'tilty') - self.label_template = Template(config['labels']) - self.url = config['url'] - - self.registry = CollectorRegistry() - label_dict = json.loads(self.label_template.render()) - self.gravity_gauge = Gauge( - safe_get_key(config, 'gravity_gauge_name', 'tilty_gravity_g'), - 'The currently measured gravity', - label_dict.keys(), - registry=self.registry, - ) - self.temp_gauge = Gauge( - safe_get_key(config, 'temp_gauge_name', 'tilty_temperature_f'), - 'The currently measured temperature', - label_dict.keys(), - registry=self.registry, - ) - - def emit(self, tilt_data: dict) -> None: - """ Initializer - - Args: - tilt_data (dict): data returned from valid tilt device scan - """ - - label_payload = self.label_template.render( - color=tilt_data['color'], - gravity=tilt_data['gravity'], - mac=tilt_data['mac'], - temp=tilt_data['temp'], - timestamp=tilt_data['timestamp'], - ) - - label_payload = json.loads(label_payload) - - self.gravity_gauge.labels( - **label_payload - ).set(tilt_data['gravity']) - self.temp_gauge.labels( - **label_payload - ).set(tilt_data['temp']) - - LOGGER.info('[prometheus] posting data') - push_to_gateway(self.url, job=self.job_name, registry=self.registry) diff --git a/tilty/emitters/sqlite.py b/tilty/emitters/sqlite.py deleted file mode 100644 index 004b29a..0000000 --- a/tilty/emitters/sqlite.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -""" SQLite emitter """ -import logging -import sqlite3 - -LOGGER = logging.getLogger() - - -def __type__() -> str: - return 'SQLite' - - -class SQLite: # pylint: disable=too-few-public-methods - """ SQLite wrapper class """ - - def __init__(self, config: dict) -> None: - """ Initializer - - Args: - config: (dict) represents the configuration for the emitter - """ - # - # [sqlite] - # file = /etc/tilty/tilt.sqlite - self.conn = sqlite3.connect(config['file']) - self.conn.execute(''' - CREATE TABLE IF NOT EXISTS data( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - gravity INTEGER, - temp INTEGER, - color VARCHAR(16), - mac VARCHAR(17), - timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL) - ''') - - def emit(self, tilt_data: dict) -> None: - """ Initializer - - Args: - tilt_data (dict): data returned from valid tilt device scan - """ - LOGGER.info('[sqlite] creating row') - self.conn.execute( - "insert into data (gravity,temp,color,mac) values (?,?,?,?)", - ( - tilt_data['gravity'], - tilt_data['temp'], - tilt_data['color'], - tilt_data['mac'] - ) - ) - self.conn.commit() diff --git a/tilty/emitters/stdout.py b/tilty/emitters/stdout.py deleted file mode 100644 index 2076850..0000000 --- a/tilty/emitters/stdout.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -""" stdout emitter """ -import logging - -LOGGER = logging.getLogger() - - -def __type__() -> str: - return 'Stdout' - - -class Stdout: # pylint: disable=too-few-public-methods - """ Stdout wrapper class """ - - def __init__(self, config: dict) -> None: - """ Initializer - - Args: - config: (dict) represents the configuration for the emitter - """ - # - # [stdout] - - def emit(self, tilt_data: dict) -> None: - """ Initializer - - Args: - tilt_data (dict): data returned from valid tilt device scan - """ diff --git a/tilty/emitters/webhook.py b/tilty/emitters/webhook.py deleted file mode 100644 index 7990622..0000000 --- a/tilty/emitters/webhook.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -""" Webhook emitter """ -import datetime -import json -import logging -from typing import Callable, Dict, Union - -import requests -from jinja2 import Template - -LOGGER = logging.getLogger() - -METHODS: Dict[str, Callable] = { - "GET": requests.get, - "POST": requests.post, -} - - -def __type__() -> str: - return 'Webhook' - - -class Webhook: # pylint: disable=too-few-public-methods - """ Class to represent the actual device """ - def __init__(self, config: dict) -> None: - """ Initializer - - Args: - config: (dict) represents the configuration for the emitter - """ - # [webhook] - # url = http://www.foo.com - # self.headers = {"Content-Type": "application/json"} - # payload_template = {"color": "{{ color }}", "gravity"... - # method = GET - self.url: str = config['url'] - self.method = METHODS.get(config['method']) - if self.method is None: - raise KeyError - delay_minutes = config.get('delay_minutes') - if delay_minutes: - delay_minutes = int(delay_minutes) - self.delay_minutes: Union[int, None] = delay_minutes - self.headers: dict = json.loads(config['headers']) - self.template: Template = Template(config['payload_template']) - self.delay_until_identifier: str = config.get( - 'delay_until_identifier', - 'color' - ) - self.delay_until: Dict[str, datetime.datetime] = {} - - def emit(self, tilt_data: dict) -> None: - """ Initializer - - Args: - tilt_data (dict): data returned from valid tilt device scan - """ - - delay_until_identifier = str(tilt_data[self.delay_until_identifier]) - now = datetime.datetime.now(datetime.timezone.utc) - delay_until = self.delay_until.get(delay_until_identifier) - if delay_until and now < delay_until: - return None - - payload: dict = json.loads(self.template.render( - color=tilt_data['color'], - gravity=tilt_data['gravity'], - mac=tilt_data['mac'], - temp=tilt_data['temp'], - timestamp=tilt_data['timestamp'], - )) - - LOGGER.debug( - '[webhook] %s to %s with %s', - self.method.__str__().split(' ')[1], - self.url, - payload, - ) - - if self.delay_minutes: - timedelta = datetime.timedelta(minutes=self.delay_minutes) - self.delay_until[delay_until_identifier] = now + timedelta - - if self.headers and 'json' in self.headers.get('Content-Type', {}): - LOGGER.debug('[webhook] sending as json') - return self.method( # type: ignore - url=self.url, - headers=self.headers, - json=payload, - ) - LOGGER.debug('[webhook] sending as non-json') - response = self.method( # type: ignore - url=self.url, - headers=self.headers, - data=payload, - ) - response.raise_for_status() - return None diff --git a/tilty/exceptions.py b/tilty/exceptions.py deleted file mode 100644 index a76024b..0000000 --- a/tilty/exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" Custom Exceptions """ - - -class ConfigurationFileNotFoundException(Exception): - """ Raised when the configuration file is not found """ - - -class ConfigurationFileEmptyException(Exception): - """ Raised when the configuration file is completely empty """ diff --git a/tilty/tilt_device.py b/tilty/tilt_device.py deleted file mode 100644 index 68aa203..0000000 --- a/tilty/tilt_device.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -""" Class to represent the actual device """ - -from datetime import datetime - -import bluetooth._bluetooth as bluez - -from tilty import blescan, constants -from tilty.tilty import LOGGER - - -class TiltDevice: # pylint: disable=too-few-public-methods - """ Class to represent the actual device """ - def __init__(self, device_id: int = 0) -> None: - """ Initializer - - Args: - device_id: (int) represents the device id for HCI - sock: the socket to open - """ - LOGGER.debug('Opening device socket') - self.sock = bluez.hci_open_dev(device_id) - - def start(self) -> None: - """ Start scanning and device - - Args: - """ - LOGGER.debug('Setting scan parameters and enabling LE scan') - blescan.hci_le_set_scan_parameters(self.sock) - blescan.hci_enable_le_scan(self.sock) - - def stop(self) -> None: - """ Stop scanning and device - - Args: - """ - LOGGER.debug('Stopping device socket') - blescan.hci_disable_le_scan(self.sock) - - def scan_for_tilt_data( - self, - temperature_offset: float = 0, - gravity_offset: float = 0 - ) -> list: - """ scan for tilt and return data if found """ - - data = [] - LOGGER.debug('Looking for events') - for beacon in blescan.get_events(self.sock): - uuid = beacon.get('uuid') - if uuid is None: - continue - color = constants.TILT_DEVICES.get(uuid) - if color: - data.append({ - 'color': color, - 'gravity': (float(beacon['minor']/1000) + gravity_offset), - 'temp': float(beacon['major'] + temperature_offset), - 'mac': beacon['mac'], - 'timestamp': datetime.now().isoformat(), - 'uuid': uuid - }) - else: - LOGGER.debug( - "Beacon UUID is not a tilt device: %s", - uuid - ) - - return data diff --git a/tilty/tilty.py b/tilty/tilty.py deleted file mode 100644 index 345a382..0000000 --- a/tilty/tilty.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -""" Class to encapsulate all the emitter logic """ -import configparser -import logging -from typing import Any, List - -from tilty.exceptions import ConfigurationFileEmptyException - -LOGGER = logging.getLogger() - - -def parse_config(config: configparser.ConfigParser) -> List[dict]: - """ Parse the config - - Args: - config (dict): configuration file loaded from disk - """ - emitters = [] - if not [section for section in config.sections() if section != 'general']: - raise ConfigurationFileEmptyException - for emitter in config.sections(): - if emitter == 'general': - continue - LOGGER.info( - "Loading emitter: %s (%s)", - emitter, - f"{__name__.split('.', maxsplit=1)[0]}.emitters.{emitter}" - ) - _emitter = __import__( - f"{__name__.split('.', maxsplit=1)[0]}.emitters.{emitter}", - fromlist=[''] - ) - _config: dict = {} - for config_key, config_val in config[emitter].items(): - _config.setdefault(config_key, config_val) - - emitters.append( - getattr(_emitter, _emitter.__type__())(config=_config) - ) - - return emitters - - -def emit(emitters: List[Any], tilt_data: dict) -> None: - """ Find and call emitters from config - - Args: - general_config (dict): general section from the configuration file - loaded from disk - emitters (obj[]): dynamically loaded emitters from parse_config() - tilt_data (dict): data returned from valid tilt device scan - """ - for emitter in emitters: - emitter.emit(tilt_data=tilt_data) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 0020e75..0000000 --- a/tox.ini +++ /dev/null @@ -1,19 +0,0 @@ -[tox] -isolated_build = True -envlist = py38 -skipsdist = True -toxworkdir=.tox -usedevelop=True - -[testenv] -setenv = PYTHONPATH = {toxinidir} -commands = - isort -c -rc tilty -sp {toxinidir} - mypy --ignore-missing-imports tilty/ - pylint --rcfile {toxinidir}/.pylintrc -r n tilty - py.test --cov-config .coveragerc --cov tilty --cov-report term-missing --cov-report xml --junitxml junit.xml tests {posargs} -whitelist_externals = test - pylint - py.test - isort - mypy diff --git a/vendor/github.com/DataDog/datadog-go/v5/LICENSE.txt b/vendor/github.com/DataDog/datadog-go/v5/LICENSE.txt new file mode 100644 index 0000000..97cd06d --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 Datadog, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/README.md b/vendor/github.com/DataDog/datadog-go/v5/statsd/README.md new file mode 100644 index 0000000..2fc8996 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/README.md @@ -0,0 +1,4 @@ +## Overview + +Package `statsd` provides a Go [dogstatsd](http://docs.datadoghq.com/guides/dogstatsd/) client. Dogstatsd extends Statsd, adding tags +and histograms. diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/aggregator.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/aggregator.go new file mode 100644 index 0000000..65c050e --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/aggregator.go @@ -0,0 +1,289 @@ +package statsd + +import ( + "strings" + "sync" + "sync/atomic" + "time" +) + +type ( + countsMap map[string]*countMetric + gaugesMap map[string]*gaugeMetric + setsMap map[string]*setMetric + bufferedMetricMap map[string]*bufferedMetric +) + +type aggregator struct { + nbContextGauge uint64 + nbContextCount uint64 + nbContextSet uint64 + + countsM sync.RWMutex + gaugesM sync.RWMutex + setsM sync.RWMutex + + gauges gaugesMap + counts countsMap + sets setsMap + histograms bufferedMetricContexts + distributions bufferedMetricContexts + timings bufferedMetricContexts + + closed chan struct{} + + client *Client + + // aggregator implements channelMode mechanism to receive histograms, + // distributions and timings. Since they need sampling they need to + // lock for random. When using both channelMode and ExtendedAggregation + // we don't want goroutine to fight over the lock. + inputMetrics chan metric + stopChannelMode chan struct{} + wg sync.WaitGroup +} + +func newAggregator(c *Client) *aggregator { + return &aggregator{ + client: c, + counts: countsMap{}, + gauges: gaugesMap{}, + sets: setsMap{}, + histograms: newBufferedContexts(newHistogramMetric), + distributions: newBufferedContexts(newDistributionMetric), + timings: newBufferedContexts(newTimingMetric), + closed: make(chan struct{}), + stopChannelMode: make(chan struct{}), + } +} + +func (a *aggregator) start(flushInterval time.Duration) { + ticker := time.NewTicker(flushInterval) + + go func() { + for { + select { + case <-ticker.C: + a.flush() + case <-a.closed: + return + } + } + }() +} + +func (a *aggregator) startReceivingMetric(bufferSize int, nbWorkers int) { + a.inputMetrics = make(chan metric, bufferSize) + for i := 0; i < nbWorkers; i++ { + a.wg.Add(1) + go a.pullMetric() + } +} + +func (a *aggregator) stopReceivingMetric() { + close(a.stopChannelMode) + a.wg.Wait() +} + +func (a *aggregator) stop() { + a.closed <- struct{}{} +} + +func (a *aggregator) pullMetric() { + for { + select { + case m := <-a.inputMetrics: + switch m.metricType { + case histogram: + a.histogram(m.name, m.fvalue, m.tags, m.rate) + case distribution: + a.distribution(m.name, m.fvalue, m.tags, m.rate) + case timing: + a.timing(m.name, m.fvalue, m.tags, m.rate) + } + case <-a.stopChannelMode: + a.wg.Done() + return + } + } +} + +func (a *aggregator) flush() { + for _, m := range a.flushMetrics() { + a.client.sendBlocking(m) + } +} + +func (a *aggregator) flushTelemetryMetrics(t *Telemetry) { + if a == nil { + // aggregation is disabled + return + } + + t.AggregationNbContextGauge = atomic.LoadUint64(&a.nbContextGauge) + t.AggregationNbContextCount = atomic.LoadUint64(&a.nbContextCount) + t.AggregationNbContextSet = atomic.LoadUint64(&a.nbContextSet) + t.AggregationNbContextHistogram = a.histograms.getNbContext() + t.AggregationNbContextDistribution = a.distributions.getNbContext() + t.AggregationNbContextTiming = a.timings.getNbContext() +} + +func (a *aggregator) flushMetrics() []metric { + metrics := []metric{} + + // We reset the values to avoid sending 'zero' values for metrics not + // sampled during this flush interval + + a.setsM.Lock() + sets := a.sets + a.sets = setsMap{} + a.setsM.Unlock() + + for _, s := range sets { + metrics = append(metrics, s.flushUnsafe()...) + } + + a.gaugesM.Lock() + gauges := a.gauges + a.gauges = gaugesMap{} + a.gaugesM.Unlock() + + for _, g := range gauges { + metrics = append(metrics, g.flushUnsafe()) + } + + a.countsM.Lock() + counts := a.counts + a.counts = countsMap{} + a.countsM.Unlock() + + for _, c := range counts { + metrics = append(metrics, c.flushUnsafe()) + } + + metrics = a.histograms.flush(metrics) + metrics = a.distributions.flush(metrics) + metrics = a.timings.flush(metrics) + + atomic.AddUint64(&a.nbContextCount, uint64(len(counts))) + atomic.AddUint64(&a.nbContextGauge, uint64(len(gauges))) + atomic.AddUint64(&a.nbContextSet, uint64(len(sets))) + return metrics +} + +func getContext(name string, tags []string) string { + c, _ := getContextAndTags(name, tags) + return c +} + +func getContextAndTags(name string, tags []string) (string, string) { + if len(tags) == 0 { + return name + nameSeparatorSymbol, "" + } + n := len(name) + len(nameSeparatorSymbol) + len(tagSeparatorSymbol)*(len(tags)-1) + for _, s := range tags { + n += len(s) + } + + var sb strings.Builder + sb.Grow(n) + sb.WriteString(name) + sb.WriteString(nameSeparatorSymbol) + sb.WriteString(tags[0]) + for _, s := range tags[1:] { + sb.WriteString(tagSeparatorSymbol) + sb.WriteString(s) + } + + s := sb.String() + + return s, s[len(name)+len(nameSeparatorSymbol):] +} + +func (a *aggregator) count(name string, value int64, tags []string) error { + context := getContext(name, tags) + a.countsM.RLock() + if count, found := a.counts[context]; found { + count.sample(value) + a.countsM.RUnlock() + return nil + } + a.countsM.RUnlock() + + a.countsM.Lock() + // Check if another goroutines hasn't created the value betwen the RUnlock and 'Lock' + if count, found := a.counts[context]; found { + count.sample(value) + a.countsM.Unlock() + return nil + } + + a.counts[context] = newCountMetric(name, value, tags) + a.countsM.Unlock() + return nil +} + +func (a *aggregator) gauge(name string, value float64, tags []string) error { + context := getContext(name, tags) + a.gaugesM.RLock() + if gauge, found := a.gauges[context]; found { + gauge.sample(value) + a.gaugesM.RUnlock() + return nil + } + a.gaugesM.RUnlock() + + gauge := newGaugeMetric(name, value, tags) + + a.gaugesM.Lock() + // Check if another goroutines hasn't created the value betwen the 'RUnlock' and 'Lock' + if gauge, found := a.gauges[context]; found { + gauge.sample(value) + a.gaugesM.Unlock() + return nil + } + a.gauges[context] = gauge + a.gaugesM.Unlock() + return nil +} + +func (a *aggregator) set(name string, value string, tags []string) error { + context := getContext(name, tags) + a.setsM.RLock() + if set, found := a.sets[context]; found { + set.sample(value) + a.setsM.RUnlock() + return nil + } + a.setsM.RUnlock() + + a.setsM.Lock() + // Check if another goroutines hasn't created the value betwen the 'RUnlock' and 'Lock' + if set, found := a.sets[context]; found { + set.sample(value) + a.setsM.Unlock() + return nil + } + a.sets[context] = newSetMetric(name, value, tags) + a.setsM.Unlock() + return nil +} + +// Only histograms, distributions and timings are sampled with a rate since we +// only pack them in on message instead of aggregating them. Discarding the +// sample rate will have impacts on the CPU and memory usage of the Agent. + +// type alias for Client.sendToAggregator +type bufferedMetricSampleFunc func(name string, value float64, tags []string, rate float64) error + +func (a *aggregator) histogram(name string, value float64, tags []string, rate float64) error { + return a.histograms.sample(name, value, tags, rate) +} + +func (a *aggregator) distribution(name string, value float64, tags []string, rate float64) error { + return a.distributions.sample(name, value, tags, rate) +} + +func (a *aggregator) timing(name string, value float64, tags []string, rate float64) error { + return a.timings.sample(name, value, tags, rate) +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/buffer.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/buffer.go new file mode 100644 index 0000000..0e4ea2b --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/buffer.go @@ -0,0 +1,195 @@ +package statsd + +import ( + "strconv" +) + +// MessageTooLongError is an error returned when a sample, event or service check is too large once serialized. See +// WithMaxBytesPerPayload option for more details. +type MessageTooLongError struct{} + +func (e MessageTooLongError) Error() string { + return "message too long. See 'WithMaxBytesPerPayload' documentation." +} + +var errBufferFull = MessageTooLongError{} + +type partialWriteError string + +func (e partialWriteError) Error() string { return string(e) } + +const errPartialWrite = partialWriteError("value partially written") + +const metricOverhead = 512 + +// statsdBuffer is a buffer containing statsd messages +// this struct methods are NOT safe for concurent use +type statsdBuffer struct { + buffer []byte + maxSize int + maxElements int + elementCount int +} + +func newStatsdBuffer(maxSize, maxElements int) *statsdBuffer { + return &statsdBuffer{ + buffer: make([]byte, 0, maxSize+metricOverhead), // pre-allocate the needed size + metricOverhead to avoid having Go re-allocate on it's own if an element does not fit + maxSize: maxSize, + maxElements: maxElements, + } +} + +func (b *statsdBuffer) writeGauge(namespace string, globalTags []string, name string, value float64, tags []string, rate float64) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendGauge(b.buffer, namespace, globalTags, name, value, tags, rate) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +func (b *statsdBuffer) writeCount(namespace string, globalTags []string, name string, value int64, tags []string, rate float64) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendCount(b.buffer, namespace, globalTags, name, value, tags, rate) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +func (b *statsdBuffer) writeHistogram(namespace string, globalTags []string, name string, value float64, tags []string, rate float64) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendHistogram(b.buffer, namespace, globalTags, name, value, tags, rate) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +// writeAggregated serialized as many values as possible in the current buffer and return the position in values where it stopped. +func (b *statsdBuffer) writeAggregated(metricSymbol []byte, namespace string, globalTags []string, name string, values []float64, tags string, tagSize int, precision int) (int, error) { + if b.elementCount >= b.maxElements { + return 0, errBufferFull + } + + originalBuffer := b.buffer + b.buffer = appendHeader(b.buffer, namespace, name) + + // buffer already full + if len(b.buffer)+tagSize > b.maxSize { + b.buffer = originalBuffer + return 0, errBufferFull + } + + // We add as many value as possible + var position int + for idx, v := range values { + previousBuffer := b.buffer + if idx != 0 { + b.buffer = append(b.buffer, ':') + } + + b.buffer = strconv.AppendFloat(b.buffer, v, 'f', precision, 64) + + // Should we stop serializing and switch to another buffer + if len(b.buffer)+tagSize > b.maxSize { + b.buffer = previousBuffer + break + } + position = idx + 1 + } + + // we could not add a single value + if position == 0 { + b.buffer = originalBuffer + return 0, errBufferFull + } + + b.buffer = append(b.buffer, '|') + b.buffer = append(b.buffer, metricSymbol...) + b.buffer = appendTagsAggregated(b.buffer, globalTags, tags) + b.buffer = appendContainerID(b.buffer) + b.writeSeparator() + b.elementCount++ + + if position != len(values) { + return position, errPartialWrite + } + return position, nil + +} + +func (b *statsdBuffer) writeDistribution(namespace string, globalTags []string, name string, value float64, tags []string, rate float64) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendDistribution(b.buffer, namespace, globalTags, name, value, tags, rate) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +func (b *statsdBuffer) writeSet(namespace string, globalTags []string, name string, value string, tags []string, rate float64) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendSet(b.buffer, namespace, globalTags, name, value, tags, rate) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +func (b *statsdBuffer) writeTiming(namespace string, globalTags []string, name string, value float64, tags []string, rate float64) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendTiming(b.buffer, namespace, globalTags, name, value, tags, rate) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +func (b *statsdBuffer) writeEvent(event *Event, globalTags []string) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendEvent(b.buffer, event, globalTags) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +func (b *statsdBuffer) writeServiceCheck(serviceCheck *ServiceCheck, globalTags []string) error { + if b.elementCount >= b.maxElements { + return errBufferFull + } + originalBuffer := b.buffer + b.buffer = appendServiceCheck(b.buffer, serviceCheck, globalTags) + b.writeSeparator() + return b.validateNewElement(originalBuffer) +} + +func (b *statsdBuffer) validateNewElement(originalBuffer []byte) error { + if len(b.buffer) > b.maxSize { + b.buffer = originalBuffer + return errBufferFull + } + b.elementCount++ + return nil +} + +func (b *statsdBuffer) writeSeparator() { + b.buffer = append(b.buffer, '\n') +} + +func (b *statsdBuffer) reset() { + b.buffer = b.buffer[:0] + b.elementCount = 0 +} + +func (b *statsdBuffer) bytes() []byte { + return b.buffer +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/buffer_pool.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/buffer_pool.go new file mode 100644 index 0000000..7a3e3c9 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/buffer_pool.go @@ -0,0 +1,40 @@ +package statsd + +type bufferPool struct { + pool chan *statsdBuffer + bufferMaxSize int + bufferMaxElements int +} + +func newBufferPool(poolSize, bufferMaxSize, bufferMaxElements int) *bufferPool { + p := &bufferPool{ + pool: make(chan *statsdBuffer, poolSize), + bufferMaxSize: bufferMaxSize, + bufferMaxElements: bufferMaxElements, + } + for i := 0; i < poolSize; i++ { + p.addNewBuffer() + } + return p +} + +func (p *bufferPool) addNewBuffer() { + p.pool <- newStatsdBuffer(p.bufferMaxSize, p.bufferMaxElements) +} + +func (p *bufferPool) borrowBuffer() *statsdBuffer { + select { + case b := <-p.pool: + return b + default: + return newStatsdBuffer(p.bufferMaxSize, p.bufferMaxElements) + } +} + +func (p *bufferPool) returnBuffer(buffer *statsdBuffer) { + buffer.reset() + select { + case p.pool <- buffer: + default: + } +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/buffered_metric_context.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/buffered_metric_context.go new file mode 100644 index 0000000..41404d9 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/buffered_metric_context.go @@ -0,0 +1,82 @@ +package statsd + +import ( + "math/rand" + "sync" + "sync/atomic" + "time" +) + +// bufferedMetricContexts represent the contexts for Histograms, Distributions +// and Timing. Since those 3 metric types behave the same way and are sampled +// with the same type they're represented by the same class. +type bufferedMetricContexts struct { + nbContext uint64 + mutex sync.RWMutex + values bufferedMetricMap + newMetric func(string, float64, string) *bufferedMetric + + // Each bufferedMetricContexts uses its own random source and random + // lock to prevent goroutines from contending for the lock on the + // "math/rand" package-global random source (e.g. calls like + // "rand.Float64()" must acquire a shared lock to get the next + // pseudorandom number). + random *rand.Rand + randomLock sync.Mutex +} + +func newBufferedContexts(newMetric func(string, float64, string) *bufferedMetric) bufferedMetricContexts { + return bufferedMetricContexts{ + values: bufferedMetricMap{}, + newMetric: newMetric, + // Note that calling "time.Now().UnixNano()" repeatedly quickly may return + // very similar values. That's fine for seeding the worker-specific random + // source because we just need an evenly distributed stream of float values. + // Do not use this random source for cryptographic randomness. + random: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +func (bc *bufferedMetricContexts) flush(metrics []metric) []metric { + bc.mutex.Lock() + values := bc.values + bc.values = bufferedMetricMap{} + bc.mutex.Unlock() + + for _, d := range values { + metrics = append(metrics, d.flushUnsafe()) + } + atomic.AddUint64(&bc.nbContext, uint64(len(values))) + return metrics +} + +func (bc *bufferedMetricContexts) sample(name string, value float64, tags []string, rate float64) error { + if !shouldSample(rate, bc.random, &bc.randomLock) { + return nil + } + + context, stringTags := getContextAndTags(name, tags) + + bc.mutex.RLock() + if v, found := bc.values[context]; found { + v.sample(value) + bc.mutex.RUnlock() + return nil + } + bc.mutex.RUnlock() + + bc.mutex.Lock() + // Check if another goroutines hasn't created the value betwen the 'RUnlock' and 'Lock' + if v, found := bc.values[context]; found { + v.sample(value) + bc.mutex.Unlock() + return nil + } + bc.values[context] = bc.newMetric(name, value, stringTags) + bc.mutex.Unlock() + return nil +} + +func (bc *bufferedMetricContexts) getNbContext() uint64 { + return atomic.LoadUint64(&bc.nbContext) +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/container.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/container.go new file mode 100644 index 0000000..b2331e8 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/container.go @@ -0,0 +1,82 @@ +package statsd + +import ( + "bufio" + "fmt" + "io" + "os" + "regexp" + "sync" +) + +const ( + // cgroupPath is the path to the cgroup file where we can find the container id if one exists. + cgroupPath = "/proc/self/cgroup" +) + +const ( + uuidSource = "[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}" + containerSource = "[0-9a-f]{64}" + taskSource = "[0-9a-f]{32}-\\d+" +) + +var ( + // expLine matches a line in the /proc/self/cgroup file. It has a submatch for the last element (path), which contains the container ID. + expLine = regexp.MustCompile(`^\d+:[^:]*:(.+)$`) + + // expContainerID matches contained IDs and sources. Source: https://github.com/Qard/container-info/blob/master/index.js + expContainerID = regexp.MustCompile(fmt.Sprintf(`(%s|%s|%s)(?:.scope)?$`, uuidSource, containerSource, taskSource)) + + // containerID holds the container ID. + containerID = "" +) + +// parseContainerID finds the first container ID reading from r and returns it. +func parseContainerID(r io.Reader) string { + scn := bufio.NewScanner(r) + for scn.Scan() { + path := expLine.FindStringSubmatch(scn.Text()) + if len(path) != 2 { + // invalid entry, continue + continue + } + if parts := expContainerID.FindStringSubmatch(path[1]); len(parts) == 2 { + return parts[1] + } + } + return "" +} + +// readContainerID attempts to return the container ID from the provided file path or empty on failure. +func readContainerID(fpath string) string { + f, err := os.Open(fpath) + if err != nil { + return "" + } + defer f.Close() + return parseContainerID(f) +} + +// getContainerID returns the container ID configured at the client creation +// It can either be auto-discovered with origin detection or provided by the user. +// User-defined container ID is prioritized. +func getContainerID() string { + return containerID +} + +var initOnce sync.Once + +// initContainerID initializes the container ID. +// It can either be provided by the user or read from cgroups. +func initContainerID(userProvidedID string, cgroupFallback bool) { + initOnce.Do(func() { + if userProvidedID != "" { + containerID = userProvidedID + return + } + + if cgroupFallback { + containerID = readContainerID(cgroupPath) + } + }) +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/event.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/event.go new file mode 100644 index 0000000..a2ca4fa --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/event.go @@ -0,0 +1,75 @@ +package statsd + +import ( + "fmt" + "time" +) + +// Events support +// EventAlertType and EventAlertPriority became exported types after this issue was submitted: https://github.com/DataDog/datadog-go/issues/41 +// The reason why they got exported is so that client code can directly use the types. + +// EventAlertType is the alert type for events +type EventAlertType string + +const ( + // Info is the "info" AlertType for events + Info EventAlertType = "info" + // Error is the "error" AlertType for events + Error EventAlertType = "error" + // Warning is the "warning" AlertType for events + Warning EventAlertType = "warning" + // Success is the "success" AlertType for events + Success EventAlertType = "success" +) + +// EventPriority is the event priority for events +type EventPriority string + +const ( + // Normal is the "normal" Priority for events + Normal EventPriority = "normal" + // Low is the "low" Priority for events + Low EventPriority = "low" +) + +// An Event is an object that can be posted to your DataDog event stream. +type Event struct { + // Title of the event. Required. + Title string + // Text is the description of the event. + Text string + // Timestamp is a timestamp for the event. If not provided, the dogstatsd + // server will set this to the current time. + Timestamp time.Time + // Hostname for the event. + Hostname string + // AggregationKey groups this event with others of the same key. + AggregationKey string + // Priority of the event. Can be statsd.Low or statsd.Normal. + Priority EventPriority + // SourceTypeName is a source type for the event. + SourceTypeName string + // AlertType can be statsd.Info, statsd.Error, statsd.Warning, or statsd.Success. + // If absent, the default value applied by the dogstatsd server is Info. + AlertType EventAlertType + // Tags for the event. + Tags []string +} + +// NewEvent creates a new event with the given title and text. Error checking +// against these values is done at send-time, or upon running e.Check. +func NewEvent(title, text string) *Event { + return &Event{ + Title: title, + Text: text, + } +} + +// Check verifies that an event is valid. +func (e *Event) Check() error { + if len(e.Title) == 0 { + return fmt.Errorf("statsd.Event title is required") + } + return nil +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/fnv1a.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/fnv1a.go new file mode 100644 index 0000000..03dc8a0 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/fnv1a.go @@ -0,0 +1,39 @@ +package statsd + +const ( + // FNV-1a + offset32 = uint32(2166136261) + prime32 = uint32(16777619) + + // init32 is what 32 bits hash values should be initialized with. + init32 = offset32 +) + +// HashString32 returns the hash of s. +func hashString32(s string) uint32 { + return addString32(init32, s) +} + +// AddString32 adds the hash of s to the precomputed hash value h. +func addString32(h uint32, s string) uint32 { + i := 0 + n := (len(s) / 8) * 8 + + for i != n { + h = (h ^ uint32(s[i])) * prime32 + h = (h ^ uint32(s[i+1])) * prime32 + h = (h ^ uint32(s[i+2])) * prime32 + h = (h ^ uint32(s[i+3])) * prime32 + h = (h ^ uint32(s[i+4])) * prime32 + h = (h ^ uint32(s[i+5])) * prime32 + h = (h ^ uint32(s[i+6])) * prime32 + h = (h ^ uint32(s[i+7])) * prime32 + i += 8 + } + + for _, c := range s[i:] { + h = (h ^ uint32(c)) * prime32 + } + + return h +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/format.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/format.go new file mode 100644 index 0000000..6e05ad5 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/format.go @@ -0,0 +1,272 @@ +package statsd + +import ( + "strconv" + "strings" +) + +var ( + gaugeSymbol = []byte("g") + countSymbol = []byte("c") + histogramSymbol = []byte("h") + distributionSymbol = []byte("d") + setSymbol = []byte("s") + timingSymbol = []byte("ms") + tagSeparatorSymbol = "," + nameSeparatorSymbol = ":" +) + +func appendHeader(buffer []byte, namespace string, name string) []byte { + if namespace != "" { + buffer = append(buffer, namespace...) + } + buffer = append(buffer, name...) + buffer = append(buffer, ':') + return buffer +} + +func appendRate(buffer []byte, rate float64) []byte { + if rate < 1 { + buffer = append(buffer, "|@"...) + buffer = strconv.AppendFloat(buffer, rate, 'f', -1, 64) + } + return buffer +} + +func appendWithoutNewlines(buffer []byte, s string) []byte { + // fastpath for strings without newlines + if strings.IndexByte(s, '\n') == -1 { + return append(buffer, s...) + } + + for _, b := range []byte(s) { + if b != '\n' { + buffer = append(buffer, b) + } + } + return buffer +} + +func appendTags(buffer []byte, globalTags []string, tags []string) []byte { + if len(globalTags) == 0 && len(tags) == 0 { + return buffer + } + buffer = append(buffer, "|#"...) + firstTag := true + + for _, tag := range globalTags { + if !firstTag { + buffer = append(buffer, tagSeparatorSymbol...) + } + buffer = appendWithoutNewlines(buffer, tag) + firstTag = false + } + for _, tag := range tags { + if !firstTag { + buffer = append(buffer, tagSeparatorSymbol...) + } + buffer = appendWithoutNewlines(buffer, tag) + firstTag = false + } + return buffer +} + +func appendTagsAggregated(buffer []byte, globalTags []string, tags string) []byte { + if len(globalTags) == 0 && tags == "" { + return buffer + } + + buffer = append(buffer, "|#"...) + firstTag := true + + for _, tag := range globalTags { + if !firstTag { + buffer = append(buffer, tagSeparatorSymbol...) + } + buffer = appendWithoutNewlines(buffer, tag) + firstTag = false + } + if tags != "" { + if !firstTag { + buffer = append(buffer, tagSeparatorSymbol...) + } + buffer = appendWithoutNewlines(buffer, tags) + } + return buffer +} + +func appendFloatMetric(buffer []byte, typeSymbol []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64, precision int) []byte { + buffer = appendHeader(buffer, namespace, name) + buffer = strconv.AppendFloat(buffer, value, 'f', precision, 64) + buffer = append(buffer, '|') + buffer = append(buffer, typeSymbol...) + buffer = appendRate(buffer, rate) + buffer = appendTags(buffer, globalTags, tags) + buffer = appendContainerID(buffer) + return buffer +} + +func appendIntegerMetric(buffer []byte, typeSymbol []byte, namespace string, globalTags []string, name string, value int64, tags []string, rate float64) []byte { + buffer = appendHeader(buffer, namespace, name) + buffer = strconv.AppendInt(buffer, value, 10) + buffer = append(buffer, '|') + buffer = append(buffer, typeSymbol...) + buffer = appendRate(buffer, rate) + buffer = appendTags(buffer, globalTags, tags) + buffer = appendContainerID(buffer) + return buffer +} + +func appendStringMetric(buffer []byte, typeSymbol []byte, namespace string, globalTags []string, name string, value string, tags []string, rate float64) []byte { + buffer = appendHeader(buffer, namespace, name) + buffer = append(buffer, value...) + buffer = append(buffer, '|') + buffer = append(buffer, typeSymbol...) + buffer = appendRate(buffer, rate) + buffer = appendTags(buffer, globalTags, tags) + buffer = appendContainerID(buffer) + return buffer +} + +func appendGauge(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte { + return appendFloatMetric(buffer, gaugeSymbol, namespace, globalTags, name, value, tags, rate, -1) +} + +func appendCount(buffer []byte, namespace string, globalTags []string, name string, value int64, tags []string, rate float64) []byte { + return appendIntegerMetric(buffer, countSymbol, namespace, globalTags, name, value, tags, rate) +} + +func appendHistogram(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte { + return appendFloatMetric(buffer, histogramSymbol, namespace, globalTags, name, value, tags, rate, -1) +} + +func appendDistribution(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte { + return appendFloatMetric(buffer, distributionSymbol, namespace, globalTags, name, value, tags, rate, -1) +} + +func appendSet(buffer []byte, namespace string, globalTags []string, name string, value string, tags []string, rate float64) []byte { + return appendStringMetric(buffer, setSymbol, namespace, globalTags, name, value, tags, rate) +} + +func appendTiming(buffer []byte, namespace string, globalTags []string, name string, value float64, tags []string, rate float64) []byte { + return appendFloatMetric(buffer, timingSymbol, namespace, globalTags, name, value, tags, rate, 6) +} + +func escapedEventTextLen(text string) int { + return len(text) + strings.Count(text, "\n") +} + +func appendEscapedEventText(buffer []byte, text string) []byte { + for _, b := range []byte(text) { + if b != '\n' { + buffer = append(buffer, b) + } else { + buffer = append(buffer, "\\n"...) + } + } + return buffer +} + +func appendEvent(buffer []byte, event *Event, globalTags []string) []byte { + escapedTextLen := escapedEventTextLen(event.Text) + + buffer = append(buffer, "_e{"...) + buffer = strconv.AppendInt(buffer, int64(len(event.Title)), 10) + buffer = append(buffer, tagSeparatorSymbol...) + buffer = strconv.AppendInt(buffer, int64(escapedTextLen), 10) + buffer = append(buffer, "}:"...) + buffer = append(buffer, event.Title...) + buffer = append(buffer, '|') + if escapedTextLen != len(event.Text) { + buffer = appendEscapedEventText(buffer, event.Text) + } else { + buffer = append(buffer, event.Text...) + } + + if !event.Timestamp.IsZero() { + buffer = append(buffer, "|d:"...) + buffer = strconv.AppendInt(buffer, int64(event.Timestamp.Unix()), 10) + } + + if len(event.Hostname) != 0 { + buffer = append(buffer, "|h:"...) + buffer = append(buffer, event.Hostname...) + } + + if len(event.AggregationKey) != 0 { + buffer = append(buffer, "|k:"...) + buffer = append(buffer, event.AggregationKey...) + } + + if len(event.Priority) != 0 { + buffer = append(buffer, "|p:"...) + buffer = append(buffer, event.Priority...) + } + + if len(event.SourceTypeName) != 0 { + buffer = append(buffer, "|s:"...) + buffer = append(buffer, event.SourceTypeName...) + } + + if len(event.AlertType) != 0 { + buffer = append(buffer, "|t:"...) + buffer = append(buffer, string(event.AlertType)...) + } + + buffer = appendTags(buffer, globalTags, event.Tags) + buffer = appendContainerID(buffer) + return buffer +} + +func appendEscapedServiceCheckText(buffer []byte, text string) []byte { + for i := 0; i < len(text); i++ { + if text[i] == '\n' { + buffer = append(buffer, "\\n"...) + } else if text[i] == 'm' && i+1 < len(text) && text[i+1] == ':' { + buffer = append(buffer, "m\\:"...) + i++ + } else { + buffer = append(buffer, text[i]) + } + } + return buffer +} + +func appendServiceCheck(buffer []byte, serviceCheck *ServiceCheck, globalTags []string) []byte { + buffer = append(buffer, "_sc|"...) + buffer = append(buffer, serviceCheck.Name...) + buffer = append(buffer, '|') + buffer = strconv.AppendInt(buffer, int64(serviceCheck.Status), 10) + + if !serviceCheck.Timestamp.IsZero() { + buffer = append(buffer, "|d:"...) + buffer = strconv.AppendInt(buffer, int64(serviceCheck.Timestamp.Unix()), 10) + } + + if len(serviceCheck.Hostname) != 0 { + buffer = append(buffer, "|h:"...) + buffer = append(buffer, serviceCheck.Hostname...) + } + + buffer = appendTags(buffer, globalTags, serviceCheck.Tags) + + if len(serviceCheck.Message) != 0 { + buffer = append(buffer, "|m:"...) + buffer = appendEscapedServiceCheckText(buffer, serviceCheck.Message) + } + + buffer = appendContainerID(buffer) + return buffer +} + +func appendSeparator(buffer []byte) []byte { + return append(buffer, '\n') +} + +func appendContainerID(buffer []byte) []byte { + if containerID := getContainerID(); len(containerID) > 0 { + buffer = append(buffer, "|c:"...) + buffer = append(buffer, containerID...) + } + return buffer +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/metrics.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/metrics.go new file mode 100644 index 0000000..82f11ac --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/metrics.go @@ -0,0 +1,181 @@ +package statsd + +import ( + "math" + "sync" + "sync/atomic" +) + +/* +Those are metrics type that can be aggregated on the client side: + - Gauge + - Count + - Set +*/ + +type countMetric struct { + value int64 + name string + tags []string +} + +func newCountMetric(name string, value int64, tags []string) *countMetric { + return &countMetric{ + value: value, + name: name, + tags: copySlice(tags), + } +} + +func (c *countMetric) sample(v int64) { + atomic.AddInt64(&c.value, v) +} + +func (c *countMetric) flushUnsafe() metric { + return metric{ + metricType: count, + name: c.name, + tags: c.tags, + rate: 1, + ivalue: c.value, + } +} + +// Gauge + +type gaugeMetric struct { + value uint64 + name string + tags []string +} + +func newGaugeMetric(name string, value float64, tags []string) *gaugeMetric { + return &gaugeMetric{ + value: math.Float64bits(value), + name: name, + tags: copySlice(tags), + } +} + +func (g *gaugeMetric) sample(v float64) { + atomic.StoreUint64(&g.value, math.Float64bits(v)) +} + +func (g *gaugeMetric) flushUnsafe() metric { + return metric{ + metricType: gauge, + name: g.name, + tags: g.tags, + rate: 1, + fvalue: math.Float64frombits(g.value), + } +} + +// Set + +type setMetric struct { + data map[string]struct{} + name string + tags []string + sync.Mutex +} + +func newSetMetric(name string, value string, tags []string) *setMetric { + set := &setMetric{ + data: map[string]struct{}{}, + name: name, + tags: copySlice(tags), + } + set.data[value] = struct{}{} + return set +} + +func (s *setMetric) sample(v string) { + s.Lock() + defer s.Unlock() + s.data[v] = struct{}{} +} + +// Sets are aggregated on the agent side too. We flush the keys so a set from +// multiple application can be correctly aggregated on the agent side. +func (s *setMetric) flushUnsafe() []metric { + if len(s.data) == 0 { + return nil + } + + metrics := make([]metric, len(s.data)) + i := 0 + for value := range s.data { + metrics[i] = metric{ + metricType: set, + name: s.name, + tags: s.tags, + rate: 1, + svalue: value, + } + i++ + } + return metrics +} + +// Histograms, Distributions and Timings + +type bufferedMetric struct { + sync.Mutex + + data []float64 + name string + // Histograms and Distributions store tags as one string since we need + // to compute its size multiple time when serializing. + tags string + mtype metricType +} + +func (s *bufferedMetric) sample(v float64) { + s.Lock() + defer s.Unlock() + s.data = append(s.data, v) +} + +func (s *bufferedMetric) flushUnsafe() metric { + return metric{ + metricType: s.mtype, + name: s.name, + stags: s.tags, + rate: 1, + fvalues: s.data, + } +} + +type histogramMetric = bufferedMetric + +func newHistogramMetric(name string, value float64, stringTags string) *histogramMetric { + return &histogramMetric{ + data: []float64{value}, + name: name, + tags: stringTags, + mtype: histogramAggregated, + } +} + +type distributionMetric = bufferedMetric + +func newDistributionMetric(name string, value float64, stringTags string) *distributionMetric { + return &distributionMetric{ + data: []float64{value}, + name: name, + tags: stringTags, + mtype: distributionAggregated, + } +} + +type timingMetric = bufferedMetric + +func newTimingMetric(name string, value float64, stringTags string) *timingMetric { + return &timingMetric{ + data: []float64{value}, + name: name, + tags: stringTags, + mtype: timingAggregated, + } +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/noop.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/noop.go new file mode 100644 index 0000000..5c09398 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/noop.go @@ -0,0 +1,96 @@ +package statsd + +import "time" + +// NoOpClient is a statsd client that does nothing. Can be useful in testing +// situations for library users. +type NoOpClient struct{} + +// Gauge does nothing and returns nil +func (n *NoOpClient) Gauge(name string, value float64, tags []string, rate float64) error { + return nil +} + +// Count does nothing and returns nil +func (n *NoOpClient) Count(name string, value int64, tags []string, rate float64) error { + return nil +} + +// Histogram does nothing and returns nil +func (n *NoOpClient) Histogram(name string, value float64, tags []string, rate float64) error { + return nil +} + +// Distribution does nothing and returns nil +func (n *NoOpClient) Distribution(name string, value float64, tags []string, rate float64) error { + return nil +} + +// Decr does nothing and returns nil +func (n *NoOpClient) Decr(name string, tags []string, rate float64) error { + return nil +} + +// Incr does nothing and returns nil +func (n *NoOpClient) Incr(name string, tags []string, rate float64) error { + return nil +} + +// Set does nothing and returns nil +func (n *NoOpClient) Set(name string, value string, tags []string, rate float64) error { + return nil +} + +// Timing does nothing and returns nil +func (n *NoOpClient) Timing(name string, value time.Duration, tags []string, rate float64) error { + return nil +} + +// TimeInMilliseconds does nothing and returns nil +func (n *NoOpClient) TimeInMilliseconds(name string, value float64, tags []string, rate float64) error { + return nil +} + +// Event does nothing and returns nil +func (n *NoOpClient) Event(e *Event) error { + return nil +} + +// SimpleEvent does nothing and returns nil +func (n *NoOpClient) SimpleEvent(title, text string) error { + return nil +} + +// ServiceCheck does nothing and returns nil +func (n *NoOpClient) ServiceCheck(sc *ServiceCheck) error { + return nil +} + +// SimpleServiceCheck does nothing and returns nil +func (n *NoOpClient) SimpleServiceCheck(name string, status ServiceCheckStatus) error { + return nil +} + +// Close does nothing and returns nil +func (n *NoOpClient) Close() error { + return nil +} + +// Flush does nothing and returns nil +func (n *NoOpClient) Flush() error { + return nil +} + +// IsClosed does nothing and return false +func (n *NoOpClient) IsClosed() bool { + return false +} + +// GetTelemetry does nothing and returns an empty Telemetry +func (n *NoOpClient) GetTelemetry() Telemetry { + return Telemetry{} +} + +// Verify that NoOpClient implements the ClientInterface. +// https://golang.org/doc/faq#guarantee_satisfies_interface +var _ ClientInterface = &NoOpClient{} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/options.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/options.go new file mode 100644 index 0000000..0728a97 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/options.go @@ -0,0 +1,348 @@ +package statsd + +import ( + "fmt" + "math" + "strings" + "time" +) + +var ( + defaultNamespace = "" + defaultTags = []string{} + defaultMaxBytesPerPayload = 0 + defaultMaxMessagesPerPayload = math.MaxInt32 + defaultBufferPoolSize = 0 + defaultBufferFlushInterval = 100 * time.Millisecond + defaultWorkerCount = 32 + defaultSenderQueueSize = 0 + defaultWriteTimeout = 100 * time.Millisecond + defaultTelemetry = true + defaultReceivingMode = mutexMode + defaultChannelModeBufferSize = 4096 + defaultAggregationFlushInterval = 2 * time.Second + defaultAggregation = true + defaultExtendedAggregation = false + defaultOriginDetection = true +) + +// Options contains the configuration options for a client. +type Options struct { + namespace string + tags []string + maxBytesPerPayload int + maxMessagesPerPayload int + bufferPoolSize int + bufferFlushInterval time.Duration + workersCount int + senderQueueSize int + writeTimeout time.Duration + telemetry bool + receiveMode receivingMode + channelModeBufferSize int + aggregationFlushInterval time.Duration + aggregation bool + extendedAggregation bool + telemetryAddr string + originDetection bool + containerID string +} + +func resolveOptions(options []Option) (*Options, error) { + o := &Options{ + namespace: defaultNamespace, + tags: defaultTags, + maxBytesPerPayload: defaultMaxBytesPerPayload, + maxMessagesPerPayload: defaultMaxMessagesPerPayload, + bufferPoolSize: defaultBufferPoolSize, + bufferFlushInterval: defaultBufferFlushInterval, + workersCount: defaultWorkerCount, + senderQueueSize: defaultSenderQueueSize, + writeTimeout: defaultWriteTimeout, + telemetry: defaultTelemetry, + receiveMode: defaultReceivingMode, + channelModeBufferSize: defaultChannelModeBufferSize, + aggregationFlushInterval: defaultAggregationFlushInterval, + aggregation: defaultAggregation, + extendedAggregation: defaultExtendedAggregation, + originDetection: defaultOriginDetection, + } + + for _, option := range options { + err := option(o) + if err != nil { + return nil, err + } + } + + return o, nil +} + +// Option is a client option. Can return an error if validation fails. +type Option func(*Options) error + +// WithNamespace sets a string to be prepend to all metrics, events and service checks name. +// +// A '.' will automatically be added after the namespace if needed. For example a metrics 'test' with a namespace 'prod' +// will produce a final metric named 'prod.test'. +func WithNamespace(namespace string) Option { + return func(o *Options) error { + if strings.HasSuffix(namespace, ".") { + o.namespace = namespace + } else { + o.namespace = namespace + "." + } + return nil + } +} + +// WithTags sets global tags to be applied to every metrics, events and service checks. +func WithTags(tags []string) Option { + return func(o *Options) error { + o.tags = tags + return nil + } +} + +// WithMaxMessagesPerPayload sets the maximum number of metrics, events and/or service checks that a single payload can +// contain. +// +// The default is 'math.MaxInt32' which will most likely let the WithMaxBytesPerPayload option take precedence. This +// option can be set to `1` to create an unbuffered client (each metrics/event/service check will be send in its own +// payload to the agent). +func WithMaxMessagesPerPayload(maxMessagesPerPayload int) Option { + return func(o *Options) error { + o.maxMessagesPerPayload = maxMessagesPerPayload + return nil + } +} + +// WithMaxBytesPerPayload sets the maximum number of bytes a single payload can contain. Each sample, even and service +// check must be lower than this value once serialized or an `MessageTooLongError` is returned. +// +// The default value 0 which will set the option to the optimal size for the transport protocol used: 1432 for UDP and +// named pipe and 8192 for UDS. Those values offer the best performances. +// Be careful when changing this option, see +// https://docs.datadoghq.com/developers/dogstatsd/high_throughput/#ensure-proper-packet-sizes. +func WithMaxBytesPerPayload(MaxBytesPerPayload int) Option { + return func(o *Options) error { + o.maxBytesPerPayload = MaxBytesPerPayload + return nil + } +} + +// WithBufferPoolSize sets the size of the pool of buffers used to serialized metrics, events and service_checks. +// +// The default, 0, will set the option to the optimal size for the transport protocol used: 2048 for UDP and named pipe +// and 512 for UDS. +func WithBufferPoolSize(bufferPoolSize int) Option { + return func(o *Options) error { + o.bufferPoolSize = bufferPoolSize + return nil + } +} + +// WithBufferFlushInterval sets the interval after which the current buffer is flushed. +// +// A buffers are used to serialized data, they're flushed either when full (see WithMaxBytesPerPayload) or when it's +// been open for longer than this interval. +// +// With apps sending a high number of metrics/events/service_checks the interval rarely timeout. But with slow sending +// apps increasing this value will reduce the number of payload sent on the wire as more data is serialized in the same +// payload. +// +// Default is 100ms +func WithBufferFlushInterval(bufferFlushInterval time.Duration) Option { + return func(o *Options) error { + o.bufferFlushInterval = bufferFlushInterval + return nil + } +} + +// WithWorkersCount sets the number of workers that will be used to serialized data. +// +// Those workers allow the use of multiple buffers at the same time (see WithBufferPoolSize) to reduce lock contention. +// +// Default is 32. +func WithWorkersCount(workersCount int) Option { + return func(o *Options) error { + if workersCount < 1 { + return fmt.Errorf("workersCount must be a positive integer") + } + o.workersCount = workersCount + return nil + } +} + +// WithSenderQueueSize sets the size of the sender queue in number of buffers. +// +// After data has been serialized in a buffer they're pushed to a queue that the sender will consume and then each one +// ot the agent. +// +// The default value 0 will set the option to the optimal size for the transport protocol used: 2048 for UDP and named +// pipe and 512 for UDS. +func WithSenderQueueSize(senderQueueSize int) Option { + return func(o *Options) error { + o.senderQueueSize = senderQueueSize + return nil + } +} + +// WithWriteTimeout sets the timeout for network communication with the Agent, after this interval a payload is +// dropped. This is only used for UDS and named pipes connection. +func WithWriteTimeout(writeTimeout time.Duration) Option { + return func(o *Options) error { + o.writeTimeout = writeTimeout + return nil + } +} + +// WithChannelMode make the client use channels to receive metrics +// +// This determines how the client receive metrics from the app (for example when calling the `Gauge()` method). +// The client will either drop the metrics if its buffers are full (WithChannelMode option) or block the caller until the +// metric can be handled (WithMutexMode option). By default the client use mutexes. +// +// WithChannelMode uses a channel (see WithChannelModeBufferSize to configure its size) to receive metrics and drops metrics if +// the channel is full. Sending metrics in this mode is much slower that WithMutexMode (because of the channel), but will not +// block the application. This mode is made for application using many goroutines, sending the same metrics, at a very +// high volume. The goal is to not slow down the application at the cost of dropping metrics and having a lower max +// throughput. +func WithChannelMode() Option { + return func(o *Options) error { + o.receiveMode = channelMode + return nil + } +} + +// WithMutexMode will use mutex to receive metrics from the app throught the API. +// +// This determines how the client receive metrics from the app (for example when calling the `Gauge()` method). +// The client will either drop the metrics if its buffers are full (WithChannelMode option) or block the caller until the +// metric can be handled (WithMutexMode option). By default the client use mutexes. +// +// WithMutexMode uses mutexes to receive metrics which is much faster than channels but can cause some lock contention +// when used with a high number of goroutines sendint the same metrics. Mutexes are sharded based on the metrics name +// which limit mutex contention when multiple goroutines send different metrics (see WithWorkersCount). This is the +// default behavior which will produce the best throughput. +func WithMutexMode() Option { + return func(o *Options) error { + o.receiveMode = mutexMode + return nil + } +} + +// WithChannelModeBufferSize sets the size of the channel holding incoming metrics when WithChannelMode is used. +func WithChannelModeBufferSize(bufferSize int) Option { + return func(o *Options) error { + o.channelModeBufferSize = bufferSize + return nil + } +} + +// WithAggregationInterval sets the interval at which aggregated metrics are flushed. See WithClientSideAggregation and +// WithExtendedClientSideAggregation for more. +// +// The default interval is 2s. The interval must divide the Agent reporting period (default=10s) evenly to reduce "aliasing" +// that can cause values to appear irregular/spiky. +// +// For example a 3s aggregation interval will create spikes in the final graph: a application sending a count metric +// that increments at a constant 1000 time per second will appear noisy with an interval of 3s. This is because +// client-side aggregation would report every 3 seconds, while the agent is reporting every 10 seconds. This means in +// each agent bucket, the values are: 9000, 9000, 12000. +func WithAggregationInterval(interval time.Duration) Option { + return func(o *Options) error { + o.aggregationFlushInterval = interval + return nil + } +} + +// WithClientSideAggregation enables client side aggregation for Gauges, Counts and Sets. +func WithClientSideAggregation() Option { + return func(o *Options) error { + o.aggregation = true + return nil + } +} + +// WithoutClientSideAggregation disables client side aggregation. +func WithoutClientSideAggregation() Option { + return func(o *Options) error { + o.aggregation = false + o.extendedAggregation = false + return nil + } +} + +// WithExtendedClientSideAggregation enables client side aggregation for all types. This feature is only compatible with +// Agent's version >=6.25.0 && <7.0.0 or Agent's versions >=7.25.0. +func WithExtendedClientSideAggregation() Option { + return func(o *Options) error { + o.aggregation = true + o.extendedAggregation = true + return nil + } +} + +// WithoutTelemetry disables the client telemetry. +// +// More on this here: https://docs.datadoghq.com/developers/dogstatsd/high_throughput/#client-side-telemetry +func WithoutTelemetry() Option { + return func(o *Options) error { + o.telemetry = false + return nil + } +} + +// WithTelemetryAddr sets a different address for telemetry metrics. By default the same address as the client is used +// for telemetry. +// +// More on this here: https://docs.datadoghq.com/developers/dogstatsd/high_throughput/#client-side-telemetry +func WithTelemetryAddr(addr string) Option { + return func(o *Options) error { + o.telemetryAddr = addr + return nil + } +} + +// WithoutOriginDetection disables the client origin detection. +// When enabled, the client tries to discover its container ID and sends it to the Agent +// to enrich the metrics with container tags. +// Origin detection can also be disabled by configuring the environment variabe DD_ORIGIN_DETECTION_ENABLED=false +// The client tries to read the container ID by parsing the file /proc/self/cgroup, this is not supported on Windows. +// The client prioritizes the value passed via DD_ENTITY_ID (if set) over the container ID. +// +// More on this here: https://docs.datadoghq.com/developers/dogstatsd/?tab=kubernetes#origin-detection-over-udp +func WithoutOriginDetection() Option { + return func(o *Options) error { + o.originDetection = false + return nil + } +} + +// WithOriginDetection enables the client origin detection. +// This feature requires Datadog Agent version >=6.35.0 && <7.0.0 or Agent versions >=7.35.0. +// When enabled, the client tries to discover its container ID and sends it to the Agent +// to enrich the metrics with container tags. +// Origin detection can be disabled by configuring the environment variabe DD_ORIGIN_DETECTION_ENABLED=false +// The client tries to read the container ID by parsing the file /proc/self/cgroup, this is not supported on Windows. +// The client prioritizes the value passed via DD_ENTITY_ID (if set) over the container ID. +// +// More on this here: https://docs.datadoghq.com/developers/dogstatsd/?tab=kubernetes#origin-detection-over-udp +func WithOriginDetection() Option { + return func(o *Options) error { + o.originDetection = true + return nil + } +} + +// WithContainerID allows passing the container ID, this will be used by the Agent to enrich metrics with container tags. +// This feature requires Datadog Agent version >=6.35.0 && <7.0.0 or Agent versions >=7.35.0. +// When configured, the provided container ID is prioritized over the container ID discovered via Origin Detection. +// The client prioritizes the value passed via DD_ENTITY_ID (if set) over the container ID. +func WithContainerID(id string) Option { + return func(o *Options) error { + o.containerID = id + return nil + } +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/pipe.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/pipe.go new file mode 100644 index 0000000..84c38e9 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/pipe.go @@ -0,0 +1,13 @@ +// +build !windows + +package statsd + +import ( + "errors" + "io" + "time" +) + +func newWindowsPipeWriter(pipepath string, writeTimeout time.Duration) (io.WriteCloser, error) { + return nil, errors.New("Windows Named Pipes are only supported on Windows") +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/pipe_windows.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/pipe_windows.go new file mode 100644 index 0000000..5ab60f0 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/pipe_windows.go @@ -0,0 +1,75 @@ +// +build windows + +package statsd + +import ( + "net" + "sync" + "time" + + "github.com/Microsoft/go-winio" +) + +type pipeWriter struct { + mu sync.RWMutex + conn net.Conn + timeout time.Duration + pipepath string +} + +func (p *pipeWriter) Write(data []byte) (n int, err error) { + conn, err := p.ensureConnection() + if err != nil { + return 0, err + } + + p.mu.RLock() + conn.SetWriteDeadline(time.Now().Add(p.timeout)) + p.mu.RUnlock() + + n, err = conn.Write(data) + if err != nil { + if e, ok := err.(net.Error); !ok || !e.Temporary() { + // disconnected; retry again on next attempt + p.mu.Lock() + p.conn = nil + p.mu.Unlock() + } + } + return n, err +} + +func (p *pipeWriter) ensureConnection() (net.Conn, error) { + p.mu.RLock() + conn := p.conn + p.mu.RUnlock() + if conn != nil { + return conn, nil + } + + // looks like we might need to connect - try again with write locking. + p.mu.Lock() + defer p.mu.Unlock() + if p.conn != nil { + return p.conn, nil + } + newconn, err := winio.DialPipe(p.pipepath, nil) + if err != nil { + return nil, err + } + p.conn = newconn + return newconn, nil +} + +func (p *pipeWriter) Close() error { + return p.conn.Close() +} + +func newWindowsPipeWriter(pipepath string, writeTimeout time.Duration) (*pipeWriter, error) { + // Defer connection establishment to first write + return &pipeWriter{ + conn: nil, + timeout: writeTimeout, + pipepath: pipepath, + }, nil +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/sender.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/sender.go new file mode 100644 index 0000000..500d53c --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/sender.go @@ -0,0 +1,111 @@ +package statsd + +import ( + "io" + "sync/atomic" +) + +// senderTelemetry contains telemetry about the health of the sender +type senderTelemetry struct { + totalPayloadsSent uint64 + totalPayloadsDroppedQueueFull uint64 + totalPayloadsDroppedWriter uint64 + totalBytesSent uint64 + totalBytesDroppedQueueFull uint64 + totalBytesDroppedWriter uint64 +} + +type sender struct { + transport io.WriteCloser + pool *bufferPool + queue chan *statsdBuffer + telemetry *senderTelemetry + stop chan struct{} + flushSignal chan struct{} +} + +func newSender(transport io.WriteCloser, queueSize int, pool *bufferPool) *sender { + sender := &sender{ + transport: transport, + pool: pool, + queue: make(chan *statsdBuffer, queueSize), + telemetry: &senderTelemetry{}, + stop: make(chan struct{}), + flushSignal: make(chan struct{}), + } + + go sender.sendLoop() + return sender +} + +func (s *sender) send(buffer *statsdBuffer) { + select { + case s.queue <- buffer: + default: + atomic.AddUint64(&s.telemetry.totalPayloadsDroppedQueueFull, 1) + atomic.AddUint64(&s.telemetry.totalBytesDroppedQueueFull, uint64(len(buffer.bytes()))) + s.pool.returnBuffer(buffer) + } +} + +func (s *sender) write(buffer *statsdBuffer) { + _, err := s.transport.Write(buffer.bytes()) + if err != nil { + atomic.AddUint64(&s.telemetry.totalPayloadsDroppedWriter, 1) + atomic.AddUint64(&s.telemetry.totalBytesDroppedWriter, uint64(len(buffer.bytes()))) + } else { + atomic.AddUint64(&s.telemetry.totalPayloadsSent, 1) + atomic.AddUint64(&s.telemetry.totalBytesSent, uint64(len(buffer.bytes()))) + } + s.pool.returnBuffer(buffer) +} + +func (s *sender) flushTelemetryMetrics(t *Telemetry) { + t.TotalPayloadsSent = atomic.LoadUint64(&s.telemetry.totalPayloadsSent) + t.TotalPayloadsDroppedQueueFull = atomic.LoadUint64(&s.telemetry.totalPayloadsDroppedQueueFull) + t.TotalPayloadsDroppedWriter = atomic.LoadUint64(&s.telemetry.totalPayloadsDroppedWriter) + + t.TotalBytesSent = atomic.LoadUint64(&s.telemetry.totalBytesSent) + t.TotalBytesDroppedQueueFull = atomic.LoadUint64(&s.telemetry.totalBytesDroppedQueueFull) + t.TotalBytesDroppedWriter = atomic.LoadUint64(&s.telemetry.totalBytesDroppedWriter) +} + +func (s *sender) sendLoop() { + defer close(s.stop) + for { + select { + case buffer := <-s.queue: + s.write(buffer) + case <-s.stop: + return + case <-s.flushSignal: + // At that point we know that the workers are paused (the statsd client + // will pause them before calling sender.flush()). + // So we can fully flush the input queue + s.flushInputQueue() + s.flushSignal <- struct{}{} + } + } +} + +func (s *sender) flushInputQueue() { + for { + select { + case buffer := <-s.queue: + s.write(buffer) + default: + return + } + } +} +func (s *sender) flush() { + s.flushSignal <- struct{}{} + <-s.flushSignal +} + +func (s *sender) close() error { + s.stop <- struct{}{} + <-s.stop + s.flushInputQueue() + return s.transport.Close() +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/service_check.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/service_check.go new file mode 100644 index 0000000..e285046 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/service_check.go @@ -0,0 +1,57 @@ +package statsd + +import ( + "fmt" + "time" +) + +// ServiceCheckStatus support +type ServiceCheckStatus byte + +const ( + // Ok is the "ok" ServiceCheck status + Ok ServiceCheckStatus = 0 + // Warn is the "warning" ServiceCheck status + Warn ServiceCheckStatus = 1 + // Critical is the "critical" ServiceCheck status + Critical ServiceCheckStatus = 2 + // Unknown is the "unknown" ServiceCheck status + Unknown ServiceCheckStatus = 3 +) + +// A ServiceCheck is an object that contains status of DataDog service check. +type ServiceCheck struct { + // Name of the service check. Required. + Name string + // Status of service check. Required. + Status ServiceCheckStatus + // Timestamp is a timestamp for the serviceCheck. If not provided, the dogstatsd + // server will set this to the current time. + Timestamp time.Time + // Hostname for the serviceCheck. + Hostname string + // A message describing the current state of the serviceCheck. + Message string + // Tags for the serviceCheck. + Tags []string +} + +// NewServiceCheck creates a new serviceCheck with the given name and status. Error checking +// against these values is done at send-time, or upon running sc.Check. +func NewServiceCheck(name string, status ServiceCheckStatus) *ServiceCheck { + return &ServiceCheck{ + Name: name, + Status: status, + } +} + +// Check verifies that a service check is valid. +func (sc *ServiceCheck) Check() error { + if len(sc.Name) == 0 { + return fmt.Errorf("statsd.ServiceCheck name is required") + } + if byte(sc.Status) < 0 || byte(sc.Status) > 3 { + return fmt.Errorf("statsd.ServiceCheck status has invalid value") + } + return nil +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/statsd.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/statsd.go new file mode 100644 index 0000000..b1bcce0 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/statsd.go @@ -0,0 +1,736 @@ +// Copyright 2013 Ooyala, Inc. + +/* +Package statsd provides a Go dogstatsd client. Dogstatsd extends the popular statsd, +adding tags and histograms and pushing upstream to Datadog. + +Refer to http://docs.datadoghq.com/guides/dogstatsd/ for information about DogStatsD. + +statsd is based on go-statsd-client. +*/ +package statsd + +//go:generate mockgen -source=statsd.go -destination=mocks/statsd.go + +import ( + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +/* +OptimalUDPPayloadSize defines the optimal payload size for a UDP datagram, 1432 bytes +is optimal for regular networks with an MTU of 1500 so datagrams don't get +fragmented. It's generally recommended not to fragment UDP datagrams as losing +a single fragment will cause the entire datagram to be lost. +*/ +const OptimalUDPPayloadSize = 1432 + +/* +MaxUDPPayloadSize defines the maximum payload size for a UDP datagram. +Its value comes from the calculation: 65535 bytes Max UDP datagram size - +8byte UDP header - 60byte max IP headers +any number greater than that will see frames being cut out. +*/ +const MaxUDPPayloadSize = 65467 + +// DefaultUDPBufferPoolSize is the default size of the buffer pool for UDP clients. +const DefaultUDPBufferPoolSize = 2048 + +// DefaultUDSBufferPoolSize is the default size of the buffer pool for UDS clients. +const DefaultUDSBufferPoolSize = 512 + +/* +DefaultMaxAgentPayloadSize is the default maximum payload size the agent +can receive. This can be adjusted by changing dogstatsd_buffer_size in the +agent configuration file datadog.yaml. This is also used as the optimal payload size +for UDS datagrams. +*/ +const DefaultMaxAgentPayloadSize = 8192 + +/* +UnixAddressPrefix holds the prefix to use to enable Unix Domain Socket +traffic instead of UDP. +*/ +const UnixAddressPrefix = "unix://" + +/* +WindowsPipeAddressPrefix holds the prefix to use to enable Windows Named Pipes +traffic instead of UDP. +*/ +const WindowsPipeAddressPrefix = `\\.\pipe\` + +const ( + agentHostEnvVarName = "DD_AGENT_HOST" + agentPortEnvVarName = "DD_DOGSTATSD_PORT" + defaultUDPPort = "8125" +) + +const ( + // ddEntityID specifies client-side user-specified entity ID injection. + // This env var can be set to the Pod UID on Kubernetes via the downward API. + // Docs: https://docs.datadoghq.com/developers/dogstatsd/?tab=kubernetes#origin-detection-over-udp + ddEntityID = "DD_ENTITY_ID" + + // ddEntityIDTag specifies the tag name for the client-side entity ID injection + // The Agent expects this tag to contain a non-prefixed Kubernetes Pod UID. + ddEntityIDTag = "dd.internal.entity_id" + + // originDetectionEnabled specifies the env var to enable/disable sending the container ID field. + originDetectionEnabled = "DD_ORIGIN_DETECTION_ENABLED" +) + +/* +ddEnvTagsMapping is a mapping of each "DD_" prefixed environment variable +to a specific tag name. We use a slice to keep the order and simplify tests. +*/ +var ddEnvTagsMapping = []struct{ envName, tagName string }{ + {ddEntityID, ddEntityIDTag}, // Client-side entity ID injection for container tagging. + {"DD_ENV", "env"}, // The name of the env in which the service runs. + {"DD_SERVICE", "service"}, // The name of the running service. + {"DD_VERSION", "version"}, // The current version of the running service. +} + +type metricType int + +const ( + gauge metricType = iota + count + histogram + histogramAggregated + distribution + distributionAggregated + set + timing + timingAggregated + event + serviceCheck +) + +type receivingMode int + +const ( + mutexMode receivingMode = iota + channelMode +) + +const ( + writerNameUDP string = "udp" + writerNameUDS string = "uds" + writerWindowsPipe string = "pipe" +) + +type metric struct { + metricType metricType + namespace string + globalTags []string + name string + fvalue float64 + fvalues []float64 + ivalue int64 + svalue string + evalue *Event + scvalue *ServiceCheck + tags []string + stags string + rate float64 +} + +type noClientErr string + +// ErrNoClient is returned if statsd reporting methods are invoked on +// a nil client. +const ErrNoClient = noClientErr("statsd client is nil") + +func (e noClientErr) Error() string { + return string(e) +} + +// ClientInterface is an interface that exposes the common client functions for the +// purpose of being able to provide a no-op client or even mocking. This can aid +// downstream users' with their testing. +type ClientInterface interface { + // Gauge measures the value of a metric at a particular time. + Gauge(name string, value float64, tags []string, rate float64) error + + // Count tracks how many times something happened per second. + Count(name string, value int64, tags []string, rate float64) error + + // Histogram tracks the statistical distribution of a set of values on each host. + Histogram(name string, value float64, tags []string, rate float64) error + + // Distribution tracks the statistical distribution of a set of values across your infrastructure. + Distribution(name string, value float64, tags []string, rate float64) error + + // Decr is just Count of -1 + Decr(name string, tags []string, rate float64) error + + // Incr is just Count of 1 + Incr(name string, tags []string, rate float64) error + + // Set counts the number of unique elements in a group. + Set(name string, value string, tags []string, rate float64) error + + // Timing sends timing information, it is an alias for TimeInMilliseconds + Timing(name string, value time.Duration, tags []string, rate float64) error + + // TimeInMilliseconds sends timing information in milliseconds. + // It is flushed by statsd with percentiles, mean and other info (https://github.com/etsy/statsd/blob/master/docs/metric_types.md#timing) + TimeInMilliseconds(name string, value float64, tags []string, rate float64) error + + // Event sends the provided Event. + Event(e *Event) error + + // SimpleEvent sends an event with the provided title and text. + SimpleEvent(title, text string) error + + // ServiceCheck sends the provided ServiceCheck. + ServiceCheck(sc *ServiceCheck) error + + // SimpleServiceCheck sends an serviceCheck with the provided name and status. + SimpleServiceCheck(name string, status ServiceCheckStatus) error + + // Close the client connection. + Close() error + + // Flush forces a flush of all the queued dogstatsd payloads. + Flush() error + + // IsClosed returns if the client has been closed. + IsClosed() bool + + // GetTelemetry return the telemetry metrics for the client since it started. + GetTelemetry() Telemetry +} + +// A Client is a handle for sending messages to dogstatsd. It is safe to +// use one Client from multiple goroutines simultaneously. +type Client struct { + // Sender handles the underlying networking protocol + sender *sender + // namespace to prepend to all statsd calls + namespace string + // tags are global tags to be added to every statsd call + tags []string + flushTime time.Duration + telemetry *statsdTelemetry + telemetryClient *telemetryClient + stop chan struct{} + wg sync.WaitGroup + workers []*worker + closerLock sync.Mutex + workersMode receivingMode + aggregatorMode receivingMode + agg *aggregator + aggExtended *aggregator + options []Option + addrOption string + isClosed bool +} + +// statsdTelemetry contains telemetry metrics about the client +type statsdTelemetry struct { + totalMetricsGauge uint64 + totalMetricsCount uint64 + totalMetricsHistogram uint64 + totalMetricsDistribution uint64 + totalMetricsSet uint64 + totalMetricsTiming uint64 + totalEvents uint64 + totalServiceChecks uint64 + totalDroppedOnReceive uint64 +} + +// Verify that Client implements the ClientInterface. +// https://golang.org/doc/faq#guarantee_satisfies_interface +var _ ClientInterface = &Client{} + +func resolveAddr(addr string) string { + envPort := "" + if addr == "" { + addr = os.Getenv(agentHostEnvVarName) + envPort = os.Getenv(agentPortEnvVarName) + } + + if addr == "" { + return "" + } + + if !strings.HasPrefix(addr, WindowsPipeAddressPrefix) && !strings.HasPrefix(addr, UnixAddressPrefix) { + if !strings.Contains(addr, ":") { + if envPort != "" { + addr = fmt.Sprintf("%s:%s", addr, envPort) + } else { + addr = fmt.Sprintf("%s:%s", addr, defaultUDPPort) + } + } + } + return addr +} + +func createWriter(addr string, writeTimeout time.Duration) (io.WriteCloser, string, error) { + addr = resolveAddr(addr) + if addr == "" { + return nil, "", errors.New("No address passed and autodetection from environment failed") + } + + switch { + case strings.HasPrefix(addr, WindowsPipeAddressPrefix): + w, err := newWindowsPipeWriter(addr, writeTimeout) + return w, writerWindowsPipe, err + case strings.HasPrefix(addr, UnixAddressPrefix): + w, err := newUDSWriter(addr[len(UnixAddressPrefix):], writeTimeout) + return w, writerNameUDS, err + default: + w, err := newUDPWriter(addr, writeTimeout) + return w, writerNameUDP, err + } +} + +// New returns a pointer to a new Client given an addr in the format "hostname:port" for UDP, +// "unix:///path/to/socket" for UDS or "\\.\pipe\path\to\pipe" for Windows Named Pipes. +func New(addr string, options ...Option) (*Client, error) { + o, err := resolveOptions(options) + if err != nil { + return nil, err + } + + w, writerType, err := createWriter(addr, o.writeTimeout) + if err != nil { + return nil, err + } + + client, err := newWithWriter(w, o, writerType) + if err == nil { + client.options = append(client.options, options...) + client.addrOption = addr + } + return client, err +} + +// NewWithWriter creates a new Client with given writer. Writer is a +// io.WriteCloser +func NewWithWriter(w io.WriteCloser, options ...Option) (*Client, error) { + o, err := resolveOptions(options) + if err != nil { + return nil, err + } + return newWithWriter(w, o, "custom") +} + +// CloneWithExtraOptions create a new Client with extra options +func CloneWithExtraOptions(c *Client, options ...Option) (*Client, error) { + if c == nil { + return nil, ErrNoClient + } + + if c.addrOption == "" { + return nil, fmt.Errorf("can't clone client with no addrOption") + } + opt := append(c.options, options...) + return New(c.addrOption, opt...) +} + +func newWithWriter(w io.WriteCloser, o *Options, writerName string) (*Client, error) { + c := Client{ + namespace: o.namespace, + tags: o.tags, + telemetry: &statsdTelemetry{}, + } + + hasEntityID := false + // Inject values of DD_* environment variables as global tags. + for _, mapping := range ddEnvTagsMapping { + if value := os.Getenv(mapping.envName); value != "" { + if mapping.envName == ddEntityID { + hasEntityID = true + } + c.tags = append(c.tags, fmt.Sprintf("%s:%s", mapping.tagName, value)) + } + } + + if !hasEntityID { + initContainerID(o.containerID, isOriginDetectionEnabled(o, hasEntityID)) + } + + if o.maxBytesPerPayload == 0 { + if writerName == writerNameUDS { + o.maxBytesPerPayload = DefaultMaxAgentPayloadSize + } else { + o.maxBytesPerPayload = OptimalUDPPayloadSize + } + } + if o.bufferPoolSize == 0 { + if writerName == writerNameUDS { + o.bufferPoolSize = DefaultUDSBufferPoolSize + } else { + o.bufferPoolSize = DefaultUDPBufferPoolSize + } + } + if o.senderQueueSize == 0 { + if writerName == writerNameUDS { + o.senderQueueSize = DefaultUDSBufferPoolSize + } else { + o.senderQueueSize = DefaultUDPBufferPoolSize + } + } + + bufferPool := newBufferPool(o.bufferPoolSize, o.maxBytesPerPayload, o.maxMessagesPerPayload) + c.sender = newSender(w, o.senderQueueSize, bufferPool) + c.aggregatorMode = o.receiveMode + + c.workersMode = o.receiveMode + // channelMode mode at the worker level is not enabled when + // ExtendedAggregation is since the user app will not directly + // use the worker (the aggregator sit between the app and the + // workers). + if o.extendedAggregation { + c.workersMode = mutexMode + } + + if o.aggregation || o.extendedAggregation { + c.agg = newAggregator(&c) + c.agg.start(o.aggregationFlushInterval) + + if o.extendedAggregation { + c.aggExtended = c.agg + + if c.aggregatorMode == channelMode { + c.agg.startReceivingMetric(o.channelModeBufferSize, o.workersCount) + } + } + } + + for i := 0; i < o.workersCount; i++ { + w := newWorker(bufferPool, c.sender) + c.workers = append(c.workers, w) + + if c.workersMode == channelMode { + w.startReceivingMetric(o.channelModeBufferSize) + } + } + + c.flushTime = o.bufferFlushInterval + c.stop = make(chan struct{}, 1) + + c.wg.Add(1) + go func() { + defer c.wg.Done() + c.watch() + }() + + if o.telemetry { + if o.telemetryAddr == "" { + c.telemetryClient = newTelemetryClient(&c, writerName, c.agg != nil) + } else { + var err error + c.telemetryClient, err = newTelemetryClientWithCustomAddr(&c, writerName, o.telemetryAddr, c.agg != nil, bufferPool, o.writeTimeout) + if err != nil { + return nil, err + } + } + c.telemetryClient.run(&c.wg, c.stop) + } + + return &c, nil +} + +func (c *Client) watch() { + ticker := time.NewTicker(c.flushTime) + + for { + select { + case <-ticker.C: + for _, w := range c.workers { + w.flush() + } + case <-c.stop: + ticker.Stop() + return + } + } +} + +// Flush forces a flush of all the queued dogstatsd payloads This method is +// blocking and will not return until everything is sent through the network. +// In mutexMode, this will also block sampling new data to the client while the +// workers and sender are flushed. +func (c *Client) Flush() error { + if c == nil { + return ErrNoClient + } + if c.agg != nil { + c.agg.flush() + } + for _, w := range c.workers { + w.pause() + defer w.unpause() + w.flushUnsafe() + } + // Now that the worker are pause the sender can flush the queue between + // worker and senders + c.sender.flush() + return nil +} + +// IsClosed returns if the client has been closed. +func (c *Client) IsClosed() bool { + c.closerLock.Lock() + defer c.closerLock.Unlock() + return c.isClosed +} + +func (c *Client) flushTelemetryMetrics(t *Telemetry) { + t.TotalMetricsGauge = atomic.LoadUint64(&c.telemetry.totalMetricsGauge) + t.TotalMetricsCount = atomic.LoadUint64(&c.telemetry.totalMetricsCount) + t.TotalMetricsSet = atomic.LoadUint64(&c.telemetry.totalMetricsSet) + t.TotalMetricsHistogram = atomic.LoadUint64(&c.telemetry.totalMetricsHistogram) + t.TotalMetricsDistribution = atomic.LoadUint64(&c.telemetry.totalMetricsDistribution) + t.TotalMetricsTiming = atomic.LoadUint64(&c.telemetry.totalMetricsTiming) + t.TotalEvents = atomic.LoadUint64(&c.telemetry.totalEvents) + t.TotalServiceChecks = atomic.LoadUint64(&c.telemetry.totalServiceChecks) + t.TotalDroppedOnReceive = atomic.LoadUint64(&c.telemetry.totalDroppedOnReceive) +} + +// GetTelemetry return the telemetry metrics for the client since it started. +func (c *Client) GetTelemetry() Telemetry { + return c.telemetryClient.getTelemetry() +} + +func (c *Client) send(m metric) error { + h := hashString32(m.name) + worker := c.workers[h%uint32(len(c.workers))] + + if c.workersMode == channelMode { + select { + case worker.inputMetrics <- m: + default: + atomic.AddUint64(&c.telemetry.totalDroppedOnReceive, 1) + } + return nil + } + return worker.processMetric(m) +} + +// sendBlocking is used by the aggregator to inject aggregated metrics. +func (c *Client) sendBlocking(m metric) error { + m.globalTags = c.tags + m.namespace = c.namespace + + h := hashString32(m.name) + worker := c.workers[h%uint32(len(c.workers))] + return worker.processMetric(m) +} + +func (c *Client) sendToAggregator(mType metricType, name string, value float64, tags []string, rate float64, f bufferedMetricSampleFunc) error { + if c.aggregatorMode == channelMode { + select { + case c.aggExtended.inputMetrics <- metric{metricType: mType, name: name, fvalue: value, tags: tags, rate: rate}: + default: + atomic.AddUint64(&c.telemetry.totalDroppedOnReceive, 1) + } + return nil + } + return f(name, value, tags, rate) +} + +// Gauge measures the value of a metric at a particular time. +func (c *Client) Gauge(name string, value float64, tags []string, rate float64) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalMetricsGauge, 1) + if c.agg != nil { + return c.agg.gauge(name, value, tags) + } + return c.send(metric{metricType: gauge, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace}) +} + +// Count tracks how many times something happened per second. +func (c *Client) Count(name string, value int64, tags []string, rate float64) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalMetricsCount, 1) + if c.agg != nil { + return c.agg.count(name, value, tags) + } + return c.send(metric{metricType: count, name: name, ivalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace}) +} + +// Histogram tracks the statistical distribution of a set of values on each host. +func (c *Client) Histogram(name string, value float64, tags []string, rate float64) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalMetricsHistogram, 1) + if c.aggExtended != nil { + return c.sendToAggregator(histogram, name, value, tags, rate, c.aggExtended.histogram) + } + return c.send(metric{metricType: histogram, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace}) +} + +// Distribution tracks the statistical distribution of a set of values across your infrastructure. +func (c *Client) Distribution(name string, value float64, tags []string, rate float64) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalMetricsDistribution, 1) + if c.aggExtended != nil { + return c.sendToAggregator(distribution, name, value, tags, rate, c.aggExtended.distribution) + } + return c.send(metric{metricType: distribution, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace}) +} + +// Decr is just Count of -1 +func (c *Client) Decr(name string, tags []string, rate float64) error { + return c.Count(name, -1, tags, rate) +} + +// Incr is just Count of 1 +func (c *Client) Incr(name string, tags []string, rate float64) error { + return c.Count(name, 1, tags, rate) +} + +// Set counts the number of unique elements in a group. +func (c *Client) Set(name string, value string, tags []string, rate float64) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalMetricsSet, 1) + if c.agg != nil { + return c.agg.set(name, value, tags) + } + return c.send(metric{metricType: set, name: name, svalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace}) +} + +// Timing sends timing information, it is an alias for TimeInMilliseconds +func (c *Client) Timing(name string, value time.Duration, tags []string, rate float64) error { + return c.TimeInMilliseconds(name, value.Seconds()*1000, tags, rate) +} + +// TimeInMilliseconds sends timing information in milliseconds. +// It is flushed by statsd with percentiles, mean and other info (https://github.com/etsy/statsd/blob/master/docs/metric_types.md#timing) +func (c *Client) TimeInMilliseconds(name string, value float64, tags []string, rate float64) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalMetricsTiming, 1) + if c.aggExtended != nil { + return c.sendToAggregator(timing, name, value, tags, rate, c.aggExtended.timing) + } + return c.send(metric{metricType: timing, name: name, fvalue: value, tags: tags, rate: rate, globalTags: c.tags, namespace: c.namespace}) +} + +// Event sends the provided Event. +func (c *Client) Event(e *Event) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalEvents, 1) + return c.send(metric{metricType: event, evalue: e, rate: 1, globalTags: c.tags, namespace: c.namespace}) +} + +// SimpleEvent sends an event with the provided title and text. +func (c *Client) SimpleEvent(title, text string) error { + e := NewEvent(title, text) + return c.Event(e) +} + +// ServiceCheck sends the provided ServiceCheck. +func (c *Client) ServiceCheck(sc *ServiceCheck) error { + if c == nil { + return ErrNoClient + } + atomic.AddUint64(&c.telemetry.totalServiceChecks, 1) + return c.send(metric{metricType: serviceCheck, scvalue: sc, rate: 1, globalTags: c.tags, namespace: c.namespace}) +} + +// SimpleServiceCheck sends an serviceCheck with the provided name and status. +func (c *Client) SimpleServiceCheck(name string, status ServiceCheckStatus) error { + sc := NewServiceCheck(name, status) + return c.ServiceCheck(sc) +} + +// Close the client connection. +func (c *Client) Close() error { + if c == nil { + return ErrNoClient + } + + // Acquire closer lock to ensure only one thread can close the stop channel + c.closerLock.Lock() + defer c.closerLock.Unlock() + + if c.isClosed { + return nil + } + + // Notify all other threads that they should stop + select { + case <-c.stop: + return nil + default: + } + close(c.stop) + + if c.workersMode == channelMode { + for _, w := range c.workers { + w.stopReceivingMetric() + } + } + + // flush the aggregator first + if c.agg != nil { + if c.aggExtended != nil && c.aggregatorMode == channelMode { + c.agg.stopReceivingMetric() + } + c.agg.stop() + } + + // Wait for the threads to stop + c.wg.Wait() + + c.Flush() + + c.isClosed = true + return c.sender.close() +} + +// isOriginDetectionEnabled returns whether the clients should fill the container field. +// +// If DD_ENTITY_ID is set, we don't send the container ID +// If a user-defined container ID is provided, we don't ignore origin detection +// as dd.internal.entity_id is prioritized over the container field for backward compatibility. +// If DD_ENTITY_ID is not set, we try to fill the container field automatically unless +// DD_ORIGIN_DETECTION_ENABLED is explicitly set to false. +func isOriginDetectionEnabled(o *Options, hasEntityID bool) bool { + if !o.originDetection || hasEntityID || o.containerID != "" { + // originDetection is explicitly disabled + // or DD_ENTITY_ID was found + // or a user-defined container ID was provided + return false + } + + envVarValue := os.Getenv(originDetectionEnabled) + if envVarValue == "" { + // DD_ORIGIN_DETECTION_ENABLED is not set + // default to true + return true + } + + enabled, err := strconv.ParseBool(envVarValue) + if err != nil { + // Error due to an unsupported DD_ORIGIN_DETECTION_ENABLED value + // default to true + return true + } + + return enabled +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/telemetry.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/telemetry.go new file mode 100644 index 0000000..b3825e8 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/telemetry.go @@ -0,0 +1,274 @@ +package statsd + +import ( + "fmt" + "sync" + "time" +) + +/* +telemetryInterval is the interval at which telemetry will be sent by the client. +*/ +const telemetryInterval = 10 * time.Second + +/* +clientTelemetryTag is a tag identifying this specific client. +*/ +var clientTelemetryTag = "client:go" + +/* +clientVersionTelemetryTag is a tag identifying this specific client version. +*/ +var clientVersionTelemetryTag = "client_version:5.1.1" + +// Telemetry represents internal metrics about the client behavior since it started. +type Telemetry struct { + // + // Those are produced by the 'Client' + // + + // TotalMetrics is the total number of metrics sent by the client before aggregation and sampling. + TotalMetrics uint64 + // TotalMetricsGauge is the total number of gauges sent by the client before aggregation and sampling. + TotalMetricsGauge uint64 + // TotalMetricsCount is the total number of counts sent by the client before aggregation and sampling. + TotalMetricsCount uint64 + // TotalMetricsHistogram is the total number of histograms sent by the client before aggregation and sampling. + TotalMetricsHistogram uint64 + // TotalMetricsDistribution is the total number of distributions sent by the client before aggregation and + // sampling. + TotalMetricsDistribution uint64 + // TotalMetricsSet is the total number of sets sent by the client before aggregation and sampling. + TotalMetricsSet uint64 + // TotalMetricsTiming is the total number of timings sent by the client before aggregation and sampling. + TotalMetricsTiming uint64 + // TotalEvents is the total number of events sent by the client before aggregation and sampling. + TotalEvents uint64 + // TotalServiceChecks is the total number of service_checks sent by the client before aggregation and sampling. + TotalServiceChecks uint64 + + // TotalDroppedOnReceive is the total number metrics/event/service_checks dropped when using ChannelMode (see + // WithChannelMode option). + TotalDroppedOnReceive uint64 + + // + // Those are produced by the 'sender' + // + + // TotalPayloadsSent is the total number of payload (packet on the network) succesfully sent by the client. When + // using UDP we don't know if packet dropped or not, so all packet are considered as succesfully sent. + TotalPayloadsSent uint64 + // TotalPayloadsDropped is the total number of payload dropped by the client. This includes all cause of dropped + // (TotalPayloadsDroppedQueueFull and TotalPayloadsDroppedWriter). When using UDP This won't includes the + // network dropped. + TotalPayloadsDropped uint64 + // TotalPayloadsDroppedWriter is the total number of payload dropped by the writer (when using UDS or named + // pipe) due to network timeout or error. + TotalPayloadsDroppedWriter uint64 + // TotalPayloadsDroppedQueueFull is the total number of payload dropped internally because the queue of payloads + // waiting to be sent on the wire is full. This means the client is generating more metrics than can be sent on + // the wire. If your app sends metrics in batch look at WithSenderQueueSize option to increase the queue size. + TotalPayloadsDroppedQueueFull uint64 + + // TotalBytesSent is the total number of bytes succesfully sent by the client. When using UDP we don't know if + // packet dropped or not, so all packet are considered as succesfully sent. + TotalBytesSent uint64 + // TotalBytesDropped is the total number of bytes dropped by the client. This includes all cause of dropped + // (TotalBytesDroppedQueueFull and TotalBytesDroppedWriter). When using UDP This + // won't includes the network dropped. + TotalBytesDropped uint64 + // TotalBytesDroppedWriter is the total number of bytes dropped by the writer (when using UDS or named pipe) due + // to network timeout or error. + TotalBytesDroppedWriter uint64 + // TotalBytesDroppedQueueFull is the total number of bytes dropped internally because the queue of payloads + // waiting to be sent on the wire is full. This means the client is generating more metrics than can be sent on + // the wire. If your app sends metrics in batch look at WithSenderQueueSize option to increase the queue size. + TotalBytesDroppedQueueFull uint64 + + // + // Those are produced by the 'aggregator' + // + + // AggregationNbContext is the total number of contexts flushed by the aggregator when either + // WithClientSideAggregation or WithExtendedClientSideAggregation options are enabled. + AggregationNbContext uint64 + // AggregationNbContextGauge is the total number of contexts for gauges flushed by the aggregator when either + // WithClientSideAggregation or WithExtendedClientSideAggregation options are enabled. + AggregationNbContextGauge uint64 + // AggregationNbContextCount is the total number of contexts for counts flushed by the aggregator when either + // WithClientSideAggregation or WithExtendedClientSideAggregation options are enabled. + AggregationNbContextCount uint64 + // AggregationNbContextSet is the total number of contexts for sets flushed by the aggregator when either + // WithClientSideAggregation or WithExtendedClientSideAggregation options are enabled. + AggregationNbContextSet uint64 + // AggregationNbContextHistogram is the total number of contexts for histograms flushed by the aggregator when either + // WithClientSideAggregation or WithExtendedClientSideAggregation options are enabled. + AggregationNbContextHistogram uint64 + // AggregationNbContextDistribution is the total number of contexts for distributions flushed by the aggregator when either + // WithClientSideAggregation or WithExtendedClientSideAggregation options are enabled. + AggregationNbContextDistribution uint64 + // AggregationNbContextTiming is the total number of contexts for timings flushed by the aggregator when either + // WithClientSideAggregation or WithExtendedClientSideAggregation options are enabled. + AggregationNbContextTiming uint64 +} + +type telemetryClient struct { + c *Client + tags []string + aggEnabled bool // is aggregation enabled and should we sent aggregation telemetry. + tagsByType map[metricType][]string + sender *sender + worker *worker + lastSample Telemetry // The previous sample of telemetry sent +} + +func newTelemetryClient(c *Client, transport string, aggregationEnabled bool) *telemetryClient { + t := &telemetryClient{ + c: c, + tags: append(c.tags, clientTelemetryTag, clientVersionTelemetryTag, "client_transport:"+transport), + aggEnabled: aggregationEnabled, + tagsByType: map[metricType][]string{}, + } + + t.tagsByType[gauge] = append(append([]string{}, t.tags...), "metrics_type:gauge") + t.tagsByType[count] = append(append([]string{}, t.tags...), "metrics_type:count") + t.tagsByType[set] = append(append([]string{}, t.tags...), "metrics_type:set") + t.tagsByType[timing] = append(append([]string{}, t.tags...), "metrics_type:timing") + t.tagsByType[histogram] = append(append([]string{}, t.tags...), "metrics_type:histogram") + t.tagsByType[distribution] = append(append([]string{}, t.tags...), "metrics_type:distribution") + return t +} + +func newTelemetryClientWithCustomAddr(c *Client, transport string, telemetryAddr string, aggregationEnabled bool, pool *bufferPool, writeTimeout time.Duration) (*telemetryClient, error) { + telemetryWriter, _, err := createWriter(telemetryAddr, writeTimeout) + if err != nil { + return nil, fmt.Errorf("Could not resolve telemetry address: %v", err) + } + + t := newTelemetryClient(c, transport, aggregationEnabled) + + // Creating a custom sender/worker with 1 worker in mutex mode for the + // telemetry that share the same bufferPool. + // FIXME due to performance pitfall, we're always using UDP defaults + // even for UDS. + t.sender = newSender(telemetryWriter, DefaultUDPBufferPoolSize, pool) + t.worker = newWorker(pool, t.sender) + return t, nil +} + +func (t *telemetryClient) run(wg *sync.WaitGroup, stop chan struct{}) { + wg.Add(1) + go func() { + defer wg.Done() + ticker := time.NewTicker(telemetryInterval) + for { + select { + case <-ticker.C: + t.sendTelemetry() + case <-stop: + ticker.Stop() + if t.sender != nil { + t.sender.close() + } + return + } + } + }() +} + +func (t *telemetryClient) sendTelemetry() { + for _, m := range t.flush() { + if t.worker != nil { + t.worker.processMetric(m) + } else { + t.c.send(m) + } + } + + if t.worker != nil { + t.worker.flush() + } +} + +func (t *telemetryClient) getTelemetry() Telemetry { + if t == nil { + // telemetry was disabled through the WithoutTelemetry option + return Telemetry{} + } + + tlm := Telemetry{} + t.c.flushTelemetryMetrics(&tlm) + t.c.sender.flushTelemetryMetrics(&tlm) + t.c.agg.flushTelemetryMetrics(&tlm) + + tlm.TotalMetrics = tlm.TotalMetricsGauge + + tlm.TotalMetricsCount + + tlm.TotalMetricsSet + + tlm.TotalMetricsHistogram + + tlm.TotalMetricsDistribution + + tlm.TotalMetricsTiming + + tlm.TotalPayloadsDropped = tlm.TotalPayloadsDroppedQueueFull + tlm.TotalPayloadsDroppedWriter + tlm.TotalBytesDropped = tlm.TotalBytesDroppedQueueFull + tlm.TotalBytesDroppedWriter + + if t.aggEnabled { + tlm.AggregationNbContext = tlm.AggregationNbContextGauge + + tlm.AggregationNbContextCount + + tlm.AggregationNbContextSet + + tlm.AggregationNbContextHistogram + + tlm.AggregationNbContextDistribution + + tlm.AggregationNbContextTiming + } + return tlm +} + +// flushTelemetry returns Telemetry metrics to be flushed. It's its own function to ease testing. +func (t *telemetryClient) flush() []metric { + m := []metric{} + + // same as Count but without global namespace + telemetryCount := func(name string, value int64, tags []string) { + m = append(m, metric{metricType: count, name: name, ivalue: value, tags: tags, rate: 1}) + } + + tlm := t.getTelemetry() + + // We send the diff between now and the previous telemetry flush. This keep the same telemetry behavior from V4 + // so users dashboard's aren't broken when upgrading to V5. It also allow to graph on the same dashboard a mix + // of V4 and V5 apps. + telemetryCount("datadog.dogstatsd.client.metrics", int64(tlm.TotalMetrics-t.lastSample.TotalMetrics), t.tags) + telemetryCount("datadog.dogstatsd.client.metrics_by_type", int64(tlm.TotalMetricsGauge-t.lastSample.TotalMetricsGauge), t.tagsByType[gauge]) + telemetryCount("datadog.dogstatsd.client.metrics_by_type", int64(tlm.TotalMetricsCount-t.lastSample.TotalMetricsCount), t.tagsByType[count]) + telemetryCount("datadog.dogstatsd.client.metrics_by_type", int64(tlm.TotalMetricsHistogram-t.lastSample.TotalMetricsHistogram), t.tagsByType[histogram]) + telemetryCount("datadog.dogstatsd.client.metrics_by_type", int64(tlm.TotalMetricsDistribution-t.lastSample.TotalMetricsDistribution), t.tagsByType[distribution]) + telemetryCount("datadog.dogstatsd.client.metrics_by_type", int64(tlm.TotalMetricsSet-t.lastSample.TotalMetricsSet), t.tagsByType[set]) + telemetryCount("datadog.dogstatsd.client.metrics_by_type", int64(tlm.TotalMetricsTiming-t.lastSample.TotalMetricsTiming), t.tagsByType[timing]) + telemetryCount("datadog.dogstatsd.client.events", int64(tlm.TotalEvents-t.lastSample.TotalEvents), t.tags) + telemetryCount("datadog.dogstatsd.client.service_checks", int64(tlm.TotalServiceChecks-t.lastSample.TotalServiceChecks), t.tags) + + telemetryCount("datadog.dogstatsd.client.metric_dropped_on_receive", int64(tlm.TotalDroppedOnReceive-t.lastSample.TotalDroppedOnReceive), t.tags) + + telemetryCount("datadog.dogstatsd.client.packets_sent", int64(tlm.TotalPayloadsSent-t.lastSample.TotalPayloadsSent), t.tags) + telemetryCount("datadog.dogstatsd.client.packets_dropped", int64(tlm.TotalPayloadsDropped-t.lastSample.TotalPayloadsDropped), t.tags) + telemetryCount("datadog.dogstatsd.client.packets_dropped_queue", int64(tlm.TotalPayloadsDroppedQueueFull-t.lastSample.TotalPayloadsDroppedQueueFull), t.tags) + telemetryCount("datadog.dogstatsd.client.packets_dropped_writer", int64(tlm.TotalPayloadsDroppedWriter-t.lastSample.TotalPayloadsDroppedWriter), t.tags) + + telemetryCount("datadog.dogstatsd.client.bytes_dropped", int64(tlm.TotalBytesDropped-t.lastSample.TotalBytesDropped), t.tags) + telemetryCount("datadog.dogstatsd.client.bytes_sent", int64(tlm.TotalBytesSent-t.lastSample.TotalBytesSent), t.tags) + telemetryCount("datadog.dogstatsd.client.bytes_dropped_queue", int64(tlm.TotalBytesDroppedQueueFull-t.lastSample.TotalBytesDroppedQueueFull), t.tags) + telemetryCount("datadog.dogstatsd.client.bytes_dropped_writer", int64(tlm.TotalBytesDroppedWriter-t.lastSample.TotalBytesDroppedWriter), t.tags) + + if t.aggEnabled { + telemetryCount("datadog.dogstatsd.client.aggregated_context", int64(tlm.AggregationNbContext-t.lastSample.AggregationNbContext), t.tags) + telemetryCount("datadog.dogstatsd.client.aggregated_context_by_type", int64(tlm.AggregationNbContextGauge-t.lastSample.AggregationNbContextGauge), t.tagsByType[gauge]) + telemetryCount("datadog.dogstatsd.client.aggregated_context_by_type", int64(tlm.AggregationNbContextSet-t.lastSample.AggregationNbContextSet), t.tagsByType[set]) + telemetryCount("datadog.dogstatsd.client.aggregated_context_by_type", int64(tlm.AggregationNbContextCount-t.lastSample.AggregationNbContextCount), t.tagsByType[count]) + telemetryCount("datadog.dogstatsd.client.aggregated_context_by_type", int64(tlm.AggregationNbContextHistogram-t.lastSample.AggregationNbContextHistogram), t.tagsByType[histogram]) + telemetryCount("datadog.dogstatsd.client.aggregated_context_by_type", int64(tlm.AggregationNbContextDistribution-t.lastSample.AggregationNbContextDistribution), t.tagsByType[distribution]) + telemetryCount("datadog.dogstatsd.client.aggregated_context_by_type", int64(tlm.AggregationNbContextTiming-t.lastSample.AggregationNbContextTiming), t.tagsByType[timing]) + } + + t.lastSample = tlm + + return m +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/udp.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/udp.go new file mode 100644 index 0000000..e2922a9 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/udp.go @@ -0,0 +1,34 @@ +package statsd + +import ( + "net" + "time" +) + +// udpWriter is an internal class wrapping around management of UDP connection +type udpWriter struct { + conn net.Conn +} + +// New returns a pointer to a new udpWriter given an addr in the format "hostname:port". +func newUDPWriter(addr string, _ time.Duration) (*udpWriter, error) { + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + conn, err := net.DialUDP("udp", nil, udpAddr) + if err != nil { + return nil, err + } + writer := &udpWriter{conn: conn} + return writer, nil +} + +// Write data to the UDP connection with no error handling +func (w *udpWriter) Write(data []byte) (int, error) { + return w.conn.Write(data) +} + +func (w *udpWriter) Close() error { + return w.conn.Close() +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/uds.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/uds.go new file mode 100644 index 0000000..fa5f591 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/uds.go @@ -0,0 +1,88 @@ +// +build !windows + +package statsd + +import ( + "net" + "sync" + "time" +) + +// udsWriter is an internal class wrapping around management of UDS connection +type udsWriter struct { + // Address to send metrics to, needed to allow reconnection on error + addr net.Addr + // Established connection object, or nil if not connected yet + conn net.Conn + // write timeout + writeTimeout time.Duration + sync.RWMutex // used to lock conn / writer can replace it +} + +// newUDSWriter returns a pointer to a new udsWriter given a socket file path as addr. +func newUDSWriter(addr string, writeTimeout time.Duration) (*udsWriter, error) { + udsAddr, err := net.ResolveUnixAddr("unixgram", addr) + if err != nil { + return nil, err + } + // Defer connection to first Write + writer := &udsWriter{addr: udsAddr, conn: nil, writeTimeout: writeTimeout} + return writer, nil +} + +// Write data to the UDS connection with write timeout and minimal error handling: +// create the connection if nil, and destroy it if the statsd server has disconnected +func (w *udsWriter) Write(data []byte) (int, error) { + conn, err := w.ensureConnection() + if err != nil { + return 0, err + } + + conn.SetWriteDeadline(time.Now().Add(w.writeTimeout)) + n, e := conn.Write(data) + + if err, isNetworkErr := e.(net.Error); err != nil && (!isNetworkErr || !err.Temporary()) { + // Statsd server disconnected, retry connecting at next packet + w.unsetConnection() + return 0, e + } + return n, e +} + +func (w *udsWriter) Close() error { + if w.conn != nil { + return w.conn.Close() + } + return nil +} + +func (w *udsWriter) ensureConnection() (net.Conn, error) { + // Check if we've already got a socket we can use + w.RLock() + currentConn := w.conn + w.RUnlock() + + if currentConn != nil { + return currentConn, nil + } + + // Looks like we might need to connect - try again with write locking. + w.Lock() + defer w.Unlock() + if w.conn != nil { + return w.conn, nil + } + + newConn, err := net.Dial(w.addr.Network(), w.addr.String()) + if err != nil { + return nil, err + } + w.conn = newConn + return newConn, nil +} + +func (w *udsWriter) unsetConnection() { + w.Lock() + defer w.Unlock() + w.conn = nil +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/uds_windows.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/uds_windows.go new file mode 100644 index 0000000..077894a --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/uds_windows.go @@ -0,0 +1,14 @@ +// +build windows + +package statsd + +import ( + "fmt" + "io" + "time" +) + +// newUDSWriter is disabled on Windows as Unix sockets are not available. +func newUDSWriter(_ string, _ time.Duration) (io.WriteCloser, error) { + return nil, fmt.Errorf("Unix socket is not available on Windows") +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/utils.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/utils.go new file mode 100644 index 0000000..8c3ac84 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/utils.go @@ -0,0 +1,32 @@ +package statsd + +import ( + "math/rand" + "sync" +) + +func shouldSample(rate float64, r *rand.Rand, lock *sync.Mutex) bool { + if rate >= 1 { + return true + } + // sources created by rand.NewSource() (ie. w.random) are not thread safe. + // TODO: use defer once the lowest Go version we support is 1.14 (defer + // has an overhead before that). + lock.Lock() + if r.Float64() > rate { + lock.Unlock() + return false + } + lock.Unlock() + return true +} + +func copySlice(src []string) []string { + if src == nil { + return nil + } + + c := make([]string, len(src)) + copy(c, src) + return c +} diff --git a/vendor/github.com/DataDog/datadog-go/v5/statsd/worker.go b/vendor/github.com/DataDog/datadog-go/v5/statsd/worker.go new file mode 100644 index 0000000..5446d50 --- /dev/null +++ b/vendor/github.com/DataDog/datadog-go/v5/statsd/worker.go @@ -0,0 +1,150 @@ +package statsd + +import ( + "math/rand" + "sync" + "time" +) + +type worker struct { + pool *bufferPool + buffer *statsdBuffer + sender *sender + random *rand.Rand + randomLock sync.Mutex + sync.Mutex + + inputMetrics chan metric + stop chan struct{} +} + +func newWorker(pool *bufferPool, sender *sender) *worker { + // Each worker uses its own random source and random lock to prevent + // workers in separate goroutines from contending for the lock on the + // "math/rand" package-global random source (e.g. calls like + // "rand.Float64()" must acquire a shared lock to get the next + // pseudorandom number). + // Note that calling "time.Now().UnixNano()" repeatedly quickly may return + // very similar values. That's fine for seeding the worker-specific random + // source because we just need an evenly distributed stream of float values. + // Do not use this random source for cryptographic randomness. + random := rand.New(rand.NewSource(time.Now().UnixNano())) + return &worker{ + pool: pool, + sender: sender, + buffer: pool.borrowBuffer(), + random: random, + stop: make(chan struct{}), + } +} + +func (w *worker) startReceivingMetric(bufferSize int) { + w.inputMetrics = make(chan metric, bufferSize) + go w.pullMetric() +} + +func (w *worker) stopReceivingMetric() { + w.stop <- struct{}{} +} + +func (w *worker) pullMetric() { + for { + select { + case m := <-w.inputMetrics: + w.processMetric(m) + case <-w.stop: + return + } + } +} + +func (w *worker) processMetric(m metric) error { + if !shouldSample(m.rate, w.random, &w.randomLock) { + return nil + } + w.Lock() + var err error + if err = w.writeMetricUnsafe(m); err == errBufferFull { + w.flushUnsafe() + err = w.writeMetricUnsafe(m) + } + w.Unlock() + return err +} + +func (w *worker) writeAggregatedMetricUnsafe(m metric, metricSymbol []byte, precision int) error { + globalPos := 0 + + // first check how much data we can write to the buffer: + // +3 + len(metricSymbol) because the message will include '||#' before the tags + // +1 for the potential line break at the start of the metric + tagsSize := len(m.stags) + 4 + len(metricSymbol) + for _, t := range m.globalTags { + tagsSize += len(t) + 1 + } + + for { + pos, err := w.buffer.writeAggregated(metricSymbol, m.namespace, m.globalTags, m.name, m.fvalues[globalPos:], m.stags, tagsSize, precision) + if err == errPartialWrite { + // We successfully wrote part of the histogram metrics. + // We flush the current buffer and finish the histogram + // in a new one. + w.flushUnsafe() + globalPos += pos + } else { + return err + } + } +} + +func (w *worker) writeMetricUnsafe(m metric) error { + switch m.metricType { + case gauge: + return w.buffer.writeGauge(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate) + case count: + return w.buffer.writeCount(m.namespace, m.globalTags, m.name, m.ivalue, m.tags, m.rate) + case histogram: + return w.buffer.writeHistogram(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate) + case distribution: + return w.buffer.writeDistribution(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate) + case set: + return w.buffer.writeSet(m.namespace, m.globalTags, m.name, m.svalue, m.tags, m.rate) + case timing: + return w.buffer.writeTiming(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate) + case event: + return w.buffer.writeEvent(m.evalue, m.globalTags) + case serviceCheck: + return w.buffer.writeServiceCheck(m.scvalue, m.globalTags) + case histogramAggregated: + return w.writeAggregatedMetricUnsafe(m, histogramSymbol, -1) + case distributionAggregated: + return w.writeAggregatedMetricUnsafe(m, distributionSymbol, -1) + case timingAggregated: + return w.writeAggregatedMetricUnsafe(m, timingSymbol, 6) + default: + return nil + } +} + +func (w *worker) flush() { + w.Lock() + w.flushUnsafe() + w.Unlock() +} + +func (w *worker) pause() { + w.Lock() +} + +func (w *worker) unpause() { + w.Unlock() +} + +// flush the current buffer. Lock must be held by caller. +// flushed buffer written to the network asynchronously. +func (w *worker) flushUnsafe() { + if len(w.buffer.bytes()) > 0 { + w.sender.send(w.buffer) + w.buffer = w.pool.borrowBuffer() + } +} diff --git a/vendor/github.com/Microsoft/go-winio/.gitignore b/vendor/github.com/Microsoft/go-winio/.gitignore new file mode 100644 index 0000000..b883f1f --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/.gitignore @@ -0,0 +1 @@ +*.exe diff --git a/vendor/github.com/Microsoft/go-winio/CODEOWNERS b/vendor/github.com/Microsoft/go-winio/CODEOWNERS new file mode 100644 index 0000000..ae1b494 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/CODEOWNERS @@ -0,0 +1 @@ + * @microsoft/containerplat diff --git a/vendor/github.com/Microsoft/go-winio/LICENSE b/vendor/github.com/Microsoft/go-winio/LICENSE new file mode 100644 index 0000000..b8b569d --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/Microsoft/go-winio/README.md b/vendor/github.com/Microsoft/go-winio/README.md new file mode 100644 index 0000000..60c93fe --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/README.md @@ -0,0 +1,22 @@ +# go-winio [![Build Status](https://github.com/microsoft/go-winio/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/go-winio/actions/workflows/ci.yml) + +This repository contains utilities for efficiently performing Win32 IO operations in +Go. Currently, this is focused on accessing named pipes and other file handles, and +for using named pipes as a net transport. + +This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go +to reuse the thread to schedule another goroutine. This limits support to Windows Vista and +newer operating systems. This is similar to the implementation of network sockets in Go's net +package. + +Please see the LICENSE file for licensing information. + +This project has adopted the [Microsoft Open Source Code of +Conduct](https://opensource.microsoft.com/codeofconduct/). For more information +see the [Code of Conduct +FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact +[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional +questions or comments. + +Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe +for another named pipe implementation. diff --git a/vendor/github.com/Microsoft/go-winio/backup.go b/vendor/github.com/Microsoft/go-winio/backup.go new file mode 100644 index 0000000..2be34af --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/backup.go @@ -0,0 +1,280 @@ +// +build windows + +package winio + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "runtime" + "syscall" + "unicode/utf16" +) + +//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead +//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite + +const ( + BackupData = uint32(iota + 1) + BackupEaData + BackupSecurity + BackupAlternateData + BackupLink + BackupPropertyData + BackupObjectId + BackupReparseData + BackupSparseBlock + BackupTxfsData +) + +const ( + StreamSparseAttributes = uint32(8) +) + +const ( + WRITE_DAC = 0x40000 + WRITE_OWNER = 0x80000 + ACCESS_SYSTEM_SECURITY = 0x1000000 +) + +// BackupHeader represents a backup stream of a file. +type BackupHeader struct { + Id uint32 // The backup stream ID + Attributes uint32 // Stream attributes + Size int64 // The size of the stream in bytes + Name string // The name of the stream (for BackupAlternateData only). + Offset int64 // The offset of the stream in the file (for BackupSparseBlock only). +} + +type win32StreamId struct { + StreamId uint32 + Attributes uint32 + Size uint64 + NameSize uint32 +} + +// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series +// of BackupHeader values. +type BackupStreamReader struct { + r io.Reader + bytesLeft int64 +} + +// NewBackupStreamReader produces a BackupStreamReader from any io.Reader. +func NewBackupStreamReader(r io.Reader) *BackupStreamReader { + return &BackupStreamReader{r, 0} +} + +// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if +// it was not completely read. +func (r *BackupStreamReader) Next() (*BackupHeader, error) { + if r.bytesLeft > 0 { + if s, ok := r.r.(io.Seeker); ok { + // Make sure Seek on io.SeekCurrent sometimes succeeds + // before trying the actual seek. + if _, err := s.Seek(0, io.SeekCurrent); err == nil { + if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil { + return nil, err + } + r.bytesLeft = 0 + } + } + if _, err := io.Copy(ioutil.Discard, r); err != nil { + return nil, err + } + } + var wsi win32StreamId + if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil { + return nil, err + } + hdr := &BackupHeader{ + Id: wsi.StreamId, + Attributes: wsi.Attributes, + Size: int64(wsi.Size), + } + if wsi.NameSize != 0 { + name := make([]uint16, int(wsi.NameSize/2)) + if err := binary.Read(r.r, binary.LittleEndian, name); err != nil { + return nil, err + } + hdr.Name = syscall.UTF16ToString(name) + } + if wsi.StreamId == BackupSparseBlock { + if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil { + return nil, err + } + hdr.Size -= 8 + } + r.bytesLeft = hdr.Size + return hdr, nil +} + +// Read reads from the current backup stream. +func (r *BackupStreamReader) Read(b []byte) (int, error) { + if r.bytesLeft == 0 { + return 0, io.EOF + } + if int64(len(b)) > r.bytesLeft { + b = b[:r.bytesLeft] + } + n, err := r.r.Read(b) + r.bytesLeft -= int64(n) + if err == io.EOF { + err = io.ErrUnexpectedEOF + } else if r.bytesLeft == 0 && err == nil { + err = io.EOF + } + return n, err +} + +// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API. +type BackupStreamWriter struct { + w io.Writer + bytesLeft int64 +} + +// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer. +func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter { + return &BackupStreamWriter{w, 0} +} + +// WriteHeader writes the next backup stream header and prepares for calls to Write(). +func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error { + if w.bytesLeft != 0 { + return fmt.Errorf("missing %d bytes", w.bytesLeft) + } + name := utf16.Encode([]rune(hdr.Name)) + wsi := win32StreamId{ + StreamId: hdr.Id, + Attributes: hdr.Attributes, + Size: uint64(hdr.Size), + NameSize: uint32(len(name) * 2), + } + if hdr.Id == BackupSparseBlock { + // Include space for the int64 block offset + wsi.Size += 8 + } + if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil { + return err + } + if len(name) != 0 { + if err := binary.Write(w.w, binary.LittleEndian, name); err != nil { + return err + } + } + if hdr.Id == BackupSparseBlock { + if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil { + return err + } + } + w.bytesLeft = hdr.Size + return nil +} + +// Write writes to the current backup stream. +func (w *BackupStreamWriter) Write(b []byte) (int, error) { + if w.bytesLeft < int64(len(b)) { + return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft) + } + n, err := w.w.Write(b) + w.bytesLeft -= int64(n) + return n, err +} + +// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API. +type BackupFileReader struct { + f *os.File + includeSecurity bool + ctx uintptr +} + +// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true, +// Read will attempt to read the security descriptor of the file. +func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader { + r := &BackupFileReader{f, includeSecurity, 0} + return r +} + +// Read reads a backup stream from the file by calling the Win32 API BackupRead(). +func (r *BackupFileReader) Read(b []byte) (int, error) { + var bytesRead uint32 + err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx) + if err != nil { + return 0, &os.PathError{"BackupRead", r.f.Name(), err} + } + runtime.KeepAlive(r.f) + if bytesRead == 0 { + return 0, io.EOF + } + return int(bytesRead), nil +} + +// Close frees Win32 resources associated with the BackupFileReader. It does not close +// the underlying file. +func (r *BackupFileReader) Close() error { + if r.ctx != 0 { + backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) + runtime.KeepAlive(r.f) + r.ctx = 0 + } + return nil +} + +// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API. +type BackupFileWriter struct { + f *os.File + includeSecurity bool + ctx uintptr +} + +// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true, +// Write() will attempt to restore the security descriptor from the stream. +func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter { + w := &BackupFileWriter{f, includeSecurity, 0} + return w +} + +// Write restores a portion of the file using the provided backup stream. +func (w *BackupFileWriter) Write(b []byte) (int, error) { + var bytesWritten uint32 + err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx) + if err != nil { + return 0, &os.PathError{"BackupWrite", w.f.Name(), err} + } + runtime.KeepAlive(w.f) + if int(bytesWritten) != len(b) { + return int(bytesWritten), errors.New("not all bytes could be written") + } + return len(b), nil +} + +// Close frees Win32 resources associated with the BackupFileWriter. It does not +// close the underlying file. +func (w *BackupFileWriter) Close() error { + if w.ctx != 0 { + backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) + runtime.KeepAlive(w.f) + w.ctx = 0 + } + return nil +} + +// OpenForBackup opens a file or directory, potentially skipping access checks if the backup +// or restore privileges have been acquired. +// +// If the file opened was a directory, it cannot be used with Readdir(). +func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) { + winPath, err := syscall.UTF16FromString(path) + if err != nil { + return nil, err + } + h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0) + if err != nil { + err = &os.PathError{Op: "open", Path: path, Err: err} + return nil, err + } + return os.NewFile(uintptr(h), path), nil +} diff --git a/vendor/github.com/Microsoft/go-winio/ea.go b/vendor/github.com/Microsoft/go-winio/ea.go new file mode 100644 index 0000000..4051c1b --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/ea.go @@ -0,0 +1,137 @@ +package winio + +import ( + "bytes" + "encoding/binary" + "errors" +) + +type fileFullEaInformation struct { + NextEntryOffset uint32 + Flags uint8 + NameLength uint8 + ValueLength uint16 +} + +var ( + fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) + + errInvalidEaBuffer = errors.New("invalid extended attribute buffer") + errEaNameTooLarge = errors.New("extended attribute name too large") + errEaValueTooLarge = errors.New("extended attribute value too large") +) + +// ExtendedAttribute represents a single Windows EA. +type ExtendedAttribute struct { + Name string + Value []byte + Flags uint8 +} + +func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { + var info fileFullEaInformation + err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) + if err != nil { + err = errInvalidEaBuffer + return + } + + nameOffset := fileFullEaInformationSize + nameLen := int(info.NameLength) + valueOffset := nameOffset + int(info.NameLength) + 1 + valueLen := int(info.ValueLength) + nextOffset := int(info.NextEntryOffset) + if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { + err = errInvalidEaBuffer + return + } + + ea.Name = string(b[nameOffset : nameOffset+nameLen]) + ea.Value = b[valueOffset : valueOffset+valueLen] + ea.Flags = info.Flags + if info.NextEntryOffset != 0 { + nb = b[info.NextEntryOffset:] + } + return +} + +// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION +// buffer retrieved from BackupRead, ZwQueryEaFile, etc. +func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { + for len(b) != 0 { + ea, nb, err := parseEa(b) + if err != nil { + return nil, err + } + + eas = append(eas, ea) + b = nb + } + return +} + +func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { + if int(uint8(len(ea.Name))) != len(ea.Name) { + return errEaNameTooLarge + } + if int(uint16(len(ea.Value))) != len(ea.Value) { + return errEaValueTooLarge + } + entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) + withPadding := (entrySize + 3) &^ 3 + nextOffset := uint32(0) + if !last { + nextOffset = withPadding + } + info := fileFullEaInformation{ + NextEntryOffset: nextOffset, + Flags: ea.Flags, + NameLength: uint8(len(ea.Name)), + ValueLength: uint16(len(ea.Value)), + } + + err := binary.Write(buf, binary.LittleEndian, &info) + if err != nil { + return err + } + + _, err = buf.Write([]byte(ea.Name)) + if err != nil { + return err + } + + err = buf.WriteByte(0) + if err != nil { + return err + } + + _, err = buf.Write(ea.Value) + if err != nil { + return err + } + + _, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) + if err != nil { + return err + } + + return nil +} + +// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION +// buffer for use with BackupWrite, ZwSetEaFile, etc. +func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { + var buf bytes.Buffer + for i := range eas { + last := false + if i == len(eas)-1 { + last = true + } + + err := writeEa(&buf, &eas[i], last) + if err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} diff --git a/vendor/github.com/Microsoft/go-winio/file.go b/vendor/github.com/Microsoft/go-winio/file.go new file mode 100644 index 0000000..0385e41 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/file.go @@ -0,0 +1,323 @@ +// +build windows + +package winio + +import ( + "errors" + "io" + "runtime" + "sync" + "sync/atomic" + "syscall" + "time" +) + +//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx +//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort +//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus +//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes +//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult + +type atomicBool int32 + +func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 } +func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) } +func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) } +func (b *atomicBool) swap(new bool) bool { + var newInt int32 + if new { + newInt = 1 + } + return atomic.SwapInt32((*int32)(b), newInt) == 1 +} + +const ( + cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1 + cFILE_SKIP_SET_EVENT_ON_HANDLE = 2 +) + +var ( + ErrFileClosed = errors.New("file has already been closed") + ErrTimeout = &timeoutError{} +) + +type timeoutError struct{} + +func (e *timeoutError) Error() string { return "i/o timeout" } +func (e *timeoutError) Timeout() bool { return true } +func (e *timeoutError) Temporary() bool { return true } + +type timeoutChan chan struct{} + +var ioInitOnce sync.Once +var ioCompletionPort syscall.Handle + +// ioResult contains the result of an asynchronous IO operation +type ioResult struct { + bytes uint32 + err error +} + +// ioOperation represents an outstanding asynchronous Win32 IO +type ioOperation struct { + o syscall.Overlapped + ch chan ioResult +} + +func initIo() { + h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff) + if err != nil { + panic(err) + } + ioCompletionPort = h + go ioCompletionProcessor(h) +} + +// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall. +// It takes ownership of this handle and will close it if it is garbage collected. +type win32File struct { + handle syscall.Handle + wg sync.WaitGroup + wgLock sync.RWMutex + closing atomicBool + socket bool + readDeadline deadlineHandler + writeDeadline deadlineHandler +} + +type deadlineHandler struct { + setLock sync.Mutex + channel timeoutChan + channelLock sync.RWMutex + timer *time.Timer + timedout atomicBool +} + +// makeWin32File makes a new win32File from an existing file handle +func makeWin32File(h syscall.Handle) (*win32File, error) { + f := &win32File{handle: h} + ioInitOnce.Do(initIo) + _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff) + if err != nil { + return nil, err + } + err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE) + if err != nil { + return nil, err + } + f.readDeadline.channel = make(timeoutChan) + f.writeDeadline.channel = make(timeoutChan) + return f, nil +} + +func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) { + // If we return the result of makeWin32File directly, it can result in an + // interface-wrapped nil, rather than a nil interface value. + f, err := makeWin32File(h) + if err != nil { + return nil, err + } + return f, nil +} + +// closeHandle closes the resources associated with a Win32 handle +func (f *win32File) closeHandle() { + f.wgLock.Lock() + // Atomically set that we are closing, releasing the resources only once. + if !f.closing.swap(true) { + f.wgLock.Unlock() + // cancel all IO and wait for it to complete + cancelIoEx(f.handle, nil) + f.wg.Wait() + // at this point, no new IO can start + syscall.Close(f.handle) + f.handle = 0 + } else { + f.wgLock.Unlock() + } +} + +// Close closes a win32File. +func (f *win32File) Close() error { + f.closeHandle() + return nil +} + +// prepareIo prepares for a new IO operation. +// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. +func (f *win32File) prepareIo() (*ioOperation, error) { + f.wgLock.RLock() + if f.closing.isSet() { + f.wgLock.RUnlock() + return nil, ErrFileClosed + } + f.wg.Add(1) + f.wgLock.RUnlock() + c := &ioOperation{} + c.ch = make(chan ioResult) + return c, nil +} + +// ioCompletionProcessor processes completed async IOs forever +func ioCompletionProcessor(h syscall.Handle) { + for { + var bytes uint32 + var key uintptr + var op *ioOperation + err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE) + if op == nil { + panic(err) + } + op.ch <- ioResult{bytes, err} + } +} + +// asyncIo processes the return value from ReadFile or WriteFile, blocking until +// the operation has actually completed. +func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { + if err != syscall.ERROR_IO_PENDING { + return int(bytes), err + } + + if f.closing.isSet() { + cancelIoEx(f.handle, &c.o) + } + + var timeout timeoutChan + if d != nil { + d.channelLock.Lock() + timeout = d.channel + d.channelLock.Unlock() + } + + var r ioResult + select { + case r = <-c.ch: + err = r.err + if err == syscall.ERROR_OPERATION_ABORTED { + if f.closing.isSet() { + err = ErrFileClosed + } + } else if err != nil && f.socket { + // err is from Win32. Query the overlapped structure to get the winsock error. + var bytes, flags uint32 + err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) + } + case <-timeout: + cancelIoEx(f.handle, &c.o) + r = <-c.ch + err = r.err + if err == syscall.ERROR_OPERATION_ABORTED { + err = ErrTimeout + } + } + + // runtime.KeepAlive is needed, as c is passed via native + // code to ioCompletionProcessor, c must remain alive + // until the channel read is complete. + runtime.KeepAlive(c) + return int(r.bytes), err +} + +// Read reads from a file handle. +func (f *win32File) Read(b []byte) (int, error) { + c, err := f.prepareIo() + if err != nil { + return 0, err + } + defer f.wg.Done() + + if f.readDeadline.timedout.isSet() { + return 0, ErrTimeout + } + + var bytes uint32 + err = syscall.ReadFile(f.handle, b, &bytes, &c.o) + n, err := f.asyncIo(c, &f.readDeadline, bytes, err) + runtime.KeepAlive(b) + + // Handle EOF conditions. + if err == nil && n == 0 && len(b) != 0 { + return 0, io.EOF + } else if err == syscall.ERROR_BROKEN_PIPE { + return 0, io.EOF + } else { + return n, err + } +} + +// Write writes to a file handle. +func (f *win32File) Write(b []byte) (int, error) { + c, err := f.prepareIo() + if err != nil { + return 0, err + } + defer f.wg.Done() + + if f.writeDeadline.timedout.isSet() { + return 0, ErrTimeout + } + + var bytes uint32 + err = syscall.WriteFile(f.handle, b, &bytes, &c.o) + n, err := f.asyncIo(c, &f.writeDeadline, bytes, err) + runtime.KeepAlive(b) + return n, err +} + +func (f *win32File) SetReadDeadline(deadline time.Time) error { + return f.readDeadline.set(deadline) +} + +func (f *win32File) SetWriteDeadline(deadline time.Time) error { + return f.writeDeadline.set(deadline) +} + +func (f *win32File) Flush() error { + return syscall.FlushFileBuffers(f.handle) +} + +func (f *win32File) Fd() uintptr { + return uintptr(f.handle) +} + +func (d *deadlineHandler) set(deadline time.Time) error { + d.setLock.Lock() + defer d.setLock.Unlock() + + if d.timer != nil { + if !d.timer.Stop() { + <-d.channel + } + d.timer = nil + } + d.timedout.setFalse() + + select { + case <-d.channel: + d.channelLock.Lock() + d.channel = make(chan struct{}) + d.channelLock.Unlock() + default: + } + + if deadline.IsZero() { + return nil + } + + timeoutIO := func() { + d.timedout.setTrue() + close(d.channel) + } + + now := time.Now() + duration := deadline.Sub(now) + if deadline.After(now) { + // Deadline is in the future, set a timer to wait + d.timer = time.AfterFunc(duration, timeoutIO) + } else { + // Deadline is in the past. Cancel all pending IO now. + timeoutIO() + } + return nil +} diff --git a/vendor/github.com/Microsoft/go-winio/fileinfo.go b/vendor/github.com/Microsoft/go-winio/fileinfo.go new file mode 100644 index 0000000..3ab6bff --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/fileinfo.go @@ -0,0 +1,73 @@ +// +build windows + +package winio + +import ( + "os" + "runtime" + "unsafe" + + "golang.org/x/sys/windows" +) + +// FileBasicInfo contains file access time and file attributes information. +type FileBasicInfo struct { + CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime + FileAttributes uint32 + pad uint32 // padding +} + +// GetFileBasicInfo retrieves times and attributes for a file. +func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { + bi := &FileBasicInfo{} + if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} + } + runtime.KeepAlive(f) + return bi, nil +} + +// SetFileBasicInfo sets times and attributes for a file. +func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { + if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} + } + runtime.KeepAlive(f) + return nil +} + +// FileStandardInfo contains extended information for the file. +// FILE_STANDARD_INFO in WinBase.h +// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info +type FileStandardInfo struct { + AllocationSize, EndOfFile int64 + NumberOfLinks uint32 + DeletePending, Directory bool +} + +// GetFileStandardInfo retrieves ended information for the file. +func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) { + si := &FileStandardInfo{} + if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil { + return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} + } + runtime.KeepAlive(f) + return si, nil +} + +// FileIDInfo contains the volume serial number and file ID for a file. This pair should be +// unique on a system. +type FileIDInfo struct { + VolumeSerialNumber uint64 + FileID [16]byte +} + +// GetFileID retrieves the unique (volume, file ID) pair for a file. +func GetFileID(f *os.File) (*FileIDInfo, error) { + fileID := &FileIDInfo{} + if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil { + return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} + } + runtime.KeepAlive(f) + return fileID, nil +} diff --git a/vendor/github.com/Microsoft/go-winio/hvsock.go b/vendor/github.com/Microsoft/go-winio/hvsock.go new file mode 100644 index 0000000..b632f8f --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/hvsock.go @@ -0,0 +1,307 @@ +// +build windows + +package winio + +import ( + "fmt" + "io" + "net" + "os" + "syscall" + "time" + "unsafe" + + "github.com/Microsoft/go-winio/pkg/guid" +) + +//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind + +const ( + afHvSock = 34 // AF_HYPERV + + socketError = ^uintptr(0) +) + +// An HvsockAddr is an address for a AF_HYPERV socket. +type HvsockAddr struct { + VMID guid.GUID + ServiceID guid.GUID +} + +type rawHvsockAddr struct { + Family uint16 + _ uint16 + VMID guid.GUID + ServiceID guid.GUID +} + +// Network returns the address's network name, "hvsock". +func (addr *HvsockAddr) Network() string { + return "hvsock" +} + +func (addr *HvsockAddr) String() string { + return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID) +} + +// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port. +func VsockServiceID(port uint32) guid.GUID { + g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3") + g.Data1 = port + return g +} + +func (addr *HvsockAddr) raw() rawHvsockAddr { + return rawHvsockAddr{ + Family: afHvSock, + VMID: addr.VMID, + ServiceID: addr.ServiceID, + } +} + +func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { + addr.VMID = raw.VMID + addr.ServiceID = raw.ServiceID +} + +// HvsockListener is a socket listener for the AF_HYPERV address family. +type HvsockListener struct { + sock *win32File + addr HvsockAddr +} + +// HvsockConn is a connected socket of the AF_HYPERV address family. +type HvsockConn struct { + sock *win32File + local, remote HvsockAddr +} + +func newHvSocket() (*win32File, error) { + fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1) + if err != nil { + return nil, os.NewSyscallError("socket", err) + } + f, err := makeWin32File(fd) + if err != nil { + syscall.Close(fd) + return nil, err + } + f.socket = true + return f, nil +} + +// ListenHvsock listens for connections on the specified hvsock address. +func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { + l := &HvsockListener{addr: *addr} + sock, err := newHvSocket() + if err != nil { + return nil, l.opErr("listen", err) + } + sa := addr.raw() + err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa))) + if err != nil { + return nil, l.opErr("listen", os.NewSyscallError("socket", err)) + } + err = syscall.Listen(sock.handle, 16) + if err != nil { + return nil, l.opErr("listen", os.NewSyscallError("listen", err)) + } + return &HvsockListener{sock: sock, addr: *addr}, nil +} + +func (l *HvsockListener) opErr(op string, err error) error { + return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err} +} + +// Addr returns the listener's network address. +func (l *HvsockListener) Addr() net.Addr { + return &l.addr +} + +// Accept waits for the next connection and returns it. +func (l *HvsockListener) Accept() (_ net.Conn, err error) { + sock, err := newHvSocket() + if err != nil { + return nil, l.opErr("accept", err) + } + defer func() { + if sock != nil { + sock.Close() + } + }() + c, err := l.sock.prepareIo() + if err != nil { + return nil, l.opErr("accept", err) + } + defer l.sock.wg.Done() + + // AcceptEx, per documentation, requires an extra 16 bytes per address. + const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) + var addrbuf [addrlen * 2]byte + + var bytes uint32 + err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o) + _, err = l.sock.asyncIo(c, nil, bytes, err) + if err != nil { + return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) + } + conn := &HvsockConn{ + sock: sock, + } + conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) + conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) + sock = nil + return conn, nil +} + +// Close closes the listener, causing any pending Accept calls to fail. +func (l *HvsockListener) Close() error { + return l.sock.Close() +} + +/* Need to finish ConnectEx handling +func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) { + sock, err := newHvSocket() + if err != nil { + return nil, err + } + defer func() { + if sock != nil { + sock.Close() + } + }() + c, err := sock.prepareIo() + if err != nil { + return nil, err + } + defer sock.wg.Done() + var bytes uint32 + err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o) + _, err = sock.asyncIo(ctx, c, nil, bytes, err) + if err != nil { + return nil, err + } + conn := &HvsockConn{ + sock: sock, + remote: *addr, + } + sock = nil + return conn, nil +} +*/ + +func (conn *HvsockConn) opErr(op string, err error) error { + return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} +} + +func (conn *HvsockConn) Read(b []byte) (int, error) { + c, err := conn.sock.prepareIo() + if err != nil { + return 0, conn.opErr("read", err) + } + defer conn.sock.wg.Done() + buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} + var flags, bytes uint32 + err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) + n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err) + if err != nil { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsarecv", err) + } + return 0, conn.opErr("read", err) + } else if n == 0 { + err = io.EOF + } + return n, err +} + +func (conn *HvsockConn) Write(b []byte) (int, error) { + t := 0 + for len(b) != 0 { + n, err := conn.write(b) + if err != nil { + return t + n, err + } + t += n + b = b[n:] + } + return t, nil +} + +func (conn *HvsockConn) write(b []byte) (int, error) { + c, err := conn.sock.prepareIo() + if err != nil { + return 0, conn.opErr("write", err) + } + defer conn.sock.wg.Done() + buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} + var bytes uint32 + err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) + n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err) + if err != nil { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsasend", err) + } + return 0, conn.opErr("write", err) + } + return n, err +} + +// Close closes the socket connection, failing any pending read or write calls. +func (conn *HvsockConn) Close() error { + return conn.sock.Close() +} + +func (conn *HvsockConn) shutdown(how int) error { + err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD) + if err != nil { + return os.NewSyscallError("shutdown", err) + } + return nil +} + +// CloseRead shuts down the read end of the socket. +func (conn *HvsockConn) CloseRead() error { + err := conn.shutdown(syscall.SHUT_RD) + if err != nil { + return conn.opErr("close", err) + } + return nil +} + +// CloseWrite shuts down the write end of the socket, notifying the other endpoint that +// no more data will be written. +func (conn *HvsockConn) CloseWrite() error { + err := conn.shutdown(syscall.SHUT_WR) + if err != nil { + return conn.opErr("close", err) + } + return nil +} + +// LocalAddr returns the local address of the connection. +func (conn *HvsockConn) LocalAddr() net.Addr { + return &conn.local +} + +// RemoteAddr returns the remote address of the connection. +func (conn *HvsockConn) RemoteAddr() net.Addr { + return &conn.remote +} + +// SetDeadline implements the net.Conn SetDeadline method. +func (conn *HvsockConn) SetDeadline(t time.Time) error { + conn.SetReadDeadline(t) + conn.SetWriteDeadline(t) + return nil +} + +// SetReadDeadline implements the net.Conn SetReadDeadline method. +func (conn *HvsockConn) SetReadDeadline(t time.Time) error { + return conn.sock.SetReadDeadline(t) +} + +// SetWriteDeadline implements the net.Conn SetWriteDeadline method. +func (conn *HvsockConn) SetWriteDeadline(t time.Time) error { + return conn.sock.SetWriteDeadline(t) +} diff --git a/vendor/github.com/Microsoft/go-winio/pipe.go b/vendor/github.com/Microsoft/go-winio/pipe.go new file mode 100644 index 0000000..96700a7 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pipe.go @@ -0,0 +1,517 @@ +// +build windows + +package winio + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + "runtime" + "syscall" + "time" + "unsafe" +) + +//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe +//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW +//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW +//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo +//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW +//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc +//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile +//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb +//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U +//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl + +type ioStatusBlock struct { + Status, Information uintptr +} + +type objectAttributes struct { + Length uintptr + RootDirectory uintptr + ObjectName *unicodeString + Attributes uintptr + SecurityDescriptor *securityDescriptor + SecurityQoS uintptr +} + +type unicodeString struct { + Length uint16 + MaximumLength uint16 + Buffer uintptr +} + +type securityDescriptor struct { + Revision byte + Sbz1 byte + Control uint16 + Owner uintptr + Group uintptr + Sacl uintptr + Dacl uintptr +} + +type ntstatus int32 + +func (status ntstatus) Err() error { + if status >= 0 { + return nil + } + return rtlNtStatusToDosError(status) +} + +const ( + cERROR_PIPE_BUSY = syscall.Errno(231) + cERROR_NO_DATA = syscall.Errno(232) + cERROR_PIPE_CONNECTED = syscall.Errno(535) + cERROR_SEM_TIMEOUT = syscall.Errno(121) + + cSECURITY_SQOS_PRESENT = 0x100000 + cSECURITY_ANONYMOUS = 0 + + cPIPE_TYPE_MESSAGE = 4 + + cPIPE_READMODE_MESSAGE = 2 + + cFILE_OPEN = 1 + cFILE_CREATE = 2 + + cFILE_PIPE_MESSAGE_TYPE = 1 + cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2 + + cSE_DACL_PRESENT = 4 +) + +var ( + // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. + // This error should match net.errClosing since docker takes a dependency on its text. + ErrPipeListenerClosed = errors.New("use of closed network connection") + + errPipeWriteClosed = errors.New("pipe has been closed for write") +) + +type win32Pipe struct { + *win32File + path string +} + +type win32MessageBytePipe struct { + win32Pipe + writeClosed bool + readEOF bool +} + +type pipeAddress string + +func (f *win32Pipe) LocalAddr() net.Addr { + return pipeAddress(f.path) +} + +func (f *win32Pipe) RemoteAddr() net.Addr { + return pipeAddress(f.path) +} + +func (f *win32Pipe) SetDeadline(t time.Time) error { + f.SetReadDeadline(t) + f.SetWriteDeadline(t) + return nil +} + +// CloseWrite closes the write side of a message pipe in byte mode. +func (f *win32MessageBytePipe) CloseWrite() error { + if f.writeClosed { + return errPipeWriteClosed + } + err := f.win32File.Flush() + if err != nil { + return err + } + _, err = f.win32File.Write(nil) + if err != nil { + return err + } + f.writeClosed = true + return nil +} + +// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since +// they are used to implement CloseWrite(). +func (f *win32MessageBytePipe) Write(b []byte) (int, error) { + if f.writeClosed { + return 0, errPipeWriteClosed + } + if len(b) == 0 { + return 0, nil + } + return f.win32File.Write(b) +} + +// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message +// mode pipe will return io.EOF, as will all subsequent reads. +func (f *win32MessageBytePipe) Read(b []byte) (int, error) { + if f.readEOF { + return 0, io.EOF + } + n, err := f.win32File.Read(b) + if err == io.EOF { + // If this was the result of a zero-byte read, then + // it is possible that the read was due to a zero-size + // message. Since we are simulating CloseWrite with a + // zero-byte message, ensure that all future Read() calls + // also return EOF. + f.readEOF = true + } else if err == syscall.ERROR_MORE_DATA { + // ERROR_MORE_DATA indicates that the pipe's read mode is message mode + // and the message still has more bytes. Treat this as a success, since + // this package presents all named pipes as byte streams. + err = nil + } + return n, err +} + +func (s pipeAddress) Network() string { + return "pipe" +} + +func (s pipeAddress) String() string { + return string(s) +} + +// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. +func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) { + for { + + select { + case <-ctx.Done(): + return syscall.Handle(0), ctx.Err() + default: + h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) + if err == nil { + return h, nil + } + if err != cERROR_PIPE_BUSY { + return h, &os.PathError{Err: err, Op: "open", Path: *path} + } + // Wait 10 msec and try again. This is a rather simplistic + // view, as we always try each 10 milliseconds. + time.Sleep(10 * time.Millisecond) + } + } +} + +// DialPipe connects to a named pipe by path, timing out if the connection +// takes longer than the specified duration. If timeout is nil, then we use +// a default timeout of 2 seconds. (We do not use WaitNamedPipe.) +func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { + var absTimeout time.Time + if timeout != nil { + absTimeout = time.Now().Add(*timeout) + } else { + absTimeout = time.Now().Add(2 * time.Second) + } + ctx, _ := context.WithDeadline(context.Background(), absTimeout) + conn, err := DialPipeContext(ctx, path) + if err == context.DeadlineExceeded { + return nil, ErrTimeout + } + return conn, err +} + +// DialPipeContext attempts to connect to a named pipe by `path` until `ctx` +// cancellation or timeout. +func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { + return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE) +} + +// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx` +// cancellation or timeout. +func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) { + var err error + var h syscall.Handle + h, err = tryDialPipe(ctx, &path, access) + if err != nil { + return nil, err + } + + var flags uint32 + err = getNamedPipeInfo(h, &flags, nil, nil, nil) + if err != nil { + return nil, err + } + + f, err := makeWin32File(h) + if err != nil { + syscall.Close(h) + return nil, err + } + + // If the pipe is in message mode, return a message byte pipe, which + // supports CloseWrite(). + if flags&cPIPE_TYPE_MESSAGE != 0 { + return &win32MessageBytePipe{ + win32Pipe: win32Pipe{win32File: f, path: path}, + }, nil + } + return &win32Pipe{win32File: f, path: path}, nil +} + +type acceptResponse struct { + f *win32File + err error +} + +type win32PipeListener struct { + firstHandle syscall.Handle + path string + config PipeConfig + acceptCh chan (chan acceptResponse) + closeCh chan int + doneCh chan int +} + +func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) { + path16, err := syscall.UTF16FromString(path) + if err != nil { + return 0, &os.PathError{Op: "open", Path: path, Err: err} + } + + var oa objectAttributes + oa.Length = unsafe.Sizeof(oa) + + var ntPath unicodeString + if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil { + return 0, &os.PathError{Op: "open", Path: path, Err: err} + } + defer localFree(ntPath.Buffer) + oa.ObjectName = &ntPath + + // The security descriptor is only needed for the first pipe. + if first { + if sd != nil { + len := uint32(len(sd)) + sdb := localAlloc(0, len) + defer localFree(sdb) + copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) + oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) + } else { + // Construct the default named pipe security descriptor. + var dacl uintptr + if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { + return 0, fmt.Errorf("getting default named pipe ACL: %s", err) + } + defer localFree(dacl) + + sdb := &securityDescriptor{ + Revision: 1, + Control: cSE_DACL_PRESENT, + Dacl: dacl, + } + oa.SecurityDescriptor = sdb + } + } + + typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS) + if c.MessageMode { + typ |= cFILE_PIPE_MESSAGE_TYPE + } + + disposition := uint32(cFILE_OPEN) + access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE) + if first { + disposition = cFILE_CREATE + // By not asking for read or write access, the named pipe file system + // will put this pipe into an initially disconnected state, blocking + // client connections until the next call with first == false. + access = syscall.SYNCHRONIZE + } + + timeout := int64(-50 * 10000) // 50ms + + var ( + h syscall.Handle + iosb ioStatusBlock + ) + err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err() + if err != nil { + return 0, &os.PathError{Op: "open", Path: path, Err: err} + } + + runtime.KeepAlive(ntPath) + return h, nil +} + +func (l *win32PipeListener) makeServerPipe() (*win32File, error) { + h, err := makeServerPipeHandle(l.path, nil, &l.config, false) + if err != nil { + return nil, err + } + f, err := makeWin32File(h) + if err != nil { + syscall.Close(h) + return nil, err + } + return f, nil +} + +func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) { + p, err := l.makeServerPipe() + if err != nil { + return nil, err + } + + // Wait for the client to connect. + ch := make(chan error) + go func(p *win32File) { + ch <- connectPipe(p) + }(p) + + select { + case err = <-ch: + if err != nil { + p.Close() + p = nil + } + case <-l.closeCh: + // Abort the connect request by closing the handle. + p.Close() + p = nil + err = <-ch + if err == nil || err == ErrFileClosed { + err = ErrPipeListenerClosed + } + } + return p, err +} + +func (l *win32PipeListener) listenerRoutine() { + closed := false + for !closed { + select { + case <-l.closeCh: + closed = true + case responseCh := <-l.acceptCh: + var ( + p *win32File + err error + ) + for { + p, err = l.makeConnectedServerPipe() + // If the connection was immediately closed by the client, try + // again. + if err != cERROR_NO_DATA { + break + } + } + responseCh <- acceptResponse{p, err} + closed = err == ErrPipeListenerClosed + } + } + syscall.Close(l.firstHandle) + l.firstHandle = 0 + // Notify Close() and Accept() callers that the handle has been closed. + close(l.doneCh) +} + +// PipeConfig contain configuration for the pipe listener. +type PipeConfig struct { + // SecurityDescriptor contains a Windows security descriptor in SDDL format. + SecurityDescriptor string + + // MessageMode determines whether the pipe is in byte or message mode. In either + // case the pipe is read in byte mode by default. The only practical difference in + // this implementation is that CloseWrite() is only supported for message mode pipes; + // CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only + // transferred to the reader (and returned as io.EOF in this implementation) + // when the pipe is in message mode. + MessageMode bool + + // InputBufferSize specifies the size of the input buffer, in bytes. + InputBufferSize int32 + + // OutputBufferSize specifies the size of the output buffer, in bytes. + OutputBufferSize int32 +} + +// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. +// The pipe must not already exist. +func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { + var ( + sd []byte + err error + ) + if c == nil { + c = &PipeConfig{} + } + if c.SecurityDescriptor != "" { + sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor) + if err != nil { + return nil, err + } + } + h, err := makeServerPipeHandle(path, sd, c, true) + if err != nil { + return nil, err + } + l := &win32PipeListener{ + firstHandle: h, + path: path, + config: *c, + acceptCh: make(chan (chan acceptResponse)), + closeCh: make(chan int), + doneCh: make(chan int), + } + go l.listenerRoutine() + return l, nil +} + +func connectPipe(p *win32File) error { + c, err := p.prepareIo() + if err != nil { + return err + } + defer p.wg.Done() + + err = connectNamedPipe(p.handle, &c.o) + _, err = p.asyncIo(c, nil, 0, err) + if err != nil && err != cERROR_PIPE_CONNECTED { + return err + } + return nil +} + +func (l *win32PipeListener) Accept() (net.Conn, error) { + ch := make(chan acceptResponse) + select { + case l.acceptCh <- ch: + response := <-ch + err := response.err + if err != nil { + return nil, err + } + if l.config.MessageMode { + return &win32MessageBytePipe{ + win32Pipe: win32Pipe{win32File: response.f, path: l.path}, + }, nil + } + return &win32Pipe{win32File: response.f, path: l.path}, nil + case <-l.doneCh: + return nil, ErrPipeListenerClosed + } +} + +func (l *win32PipeListener) Close() error { + select { + case l.closeCh <- 1: + <-l.doneCh + case <-l.doneCh: + } + return nil +} + +func (l *win32PipeListener) Addr() net.Addr { + return pipeAddress(l.path) +} diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go new file mode 100644 index 0000000..f497c0e --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go @@ -0,0 +1,237 @@ +// +build windows + +// Package guid provides a GUID type. The backing structure for a GUID is +// identical to that used by the golang.org/x/sys/windows GUID type. +// There are two main binary encodings used for a GUID, the big-endian encoding, +// and the Windows (mixed-endian) encoding. See here for details: +// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding +package guid + +import ( + "crypto/rand" + "crypto/sha1" + "encoding" + "encoding/binary" + "fmt" + "strconv" + + "golang.org/x/sys/windows" +) + +// Variant specifies which GUID variant (or "type") of the GUID. It determines +// how the entirety of the rest of the GUID is interpreted. +type Variant uint8 + +// The variants specified by RFC 4122. +const ( + // VariantUnknown specifies a GUID variant which does not conform to one of + // the variant encodings specified in RFC 4122. + VariantUnknown Variant = iota + VariantNCS + VariantRFC4122 + VariantMicrosoft + VariantFuture +) + +// Version specifies how the bits in the GUID were generated. For instance, a +// version 4 GUID is randomly generated, and a version 5 is generated from the +// hash of an input string. +type Version uint8 + +var _ = (encoding.TextMarshaler)(GUID{}) +var _ = (encoding.TextUnmarshaler)(&GUID{}) + +// GUID represents a GUID/UUID. It has the same structure as +// golang.org/x/sys/windows.GUID so that it can be used with functions expecting +// that type. It is defined as its own type so that stringification and +// marshaling can be supported. The representation matches that used by native +// Windows code. +type GUID windows.GUID + +// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. +func NewV4() (GUID, error) { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return GUID{}, err + } + + g := FromArray(b) + g.setVersion(4) // Version 4 means randomly generated. + g.setVariant(VariantRFC4122) + + return g, nil +} + +// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) +// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, +// and the sample code treats it as a series of bytes, so we do the same here. +// +// Some implementations, such as those found on Windows, treat the name as a +// big-endian UTF16 stream of bytes. If that is desired, the string can be +// encoded as such before being passed to this function. +func NewV5(namespace GUID, name []byte) (GUID, error) { + b := sha1.New() + namespaceBytes := namespace.ToArray() + b.Write(namespaceBytes[:]) + b.Write(name) + + a := [16]byte{} + copy(a[:], b.Sum(nil)) + + g := FromArray(a) + g.setVersion(5) // Version 5 means generated from a string. + g.setVariant(VariantRFC4122) + + return g, nil +} + +func fromArray(b [16]byte, order binary.ByteOrder) GUID { + var g GUID + g.Data1 = order.Uint32(b[0:4]) + g.Data2 = order.Uint16(b[4:6]) + g.Data3 = order.Uint16(b[6:8]) + copy(g.Data4[:], b[8:16]) + return g +} + +func (g GUID) toArray(order binary.ByteOrder) [16]byte { + b := [16]byte{} + order.PutUint32(b[0:4], g.Data1) + order.PutUint16(b[4:6], g.Data2) + order.PutUint16(b[6:8], g.Data3) + copy(b[8:16], g.Data4[:]) + return b +} + +// FromArray constructs a GUID from a big-endian encoding array of 16 bytes. +func FromArray(b [16]byte) GUID { + return fromArray(b, binary.BigEndian) +} + +// ToArray returns an array of 16 bytes representing the GUID in big-endian +// encoding. +func (g GUID) ToArray() [16]byte { + return g.toArray(binary.BigEndian) +} + +// FromWindowsArray constructs a GUID from a Windows encoding array of bytes. +func FromWindowsArray(b [16]byte) GUID { + return fromArray(b, binary.LittleEndian) +} + +// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows +// encoding. +func (g GUID) ToWindowsArray() [16]byte { + return g.toArray(binary.LittleEndian) +} + +func (g GUID) String() string { + return fmt.Sprintf( + "%08x-%04x-%04x-%04x-%012x", + g.Data1, + g.Data2, + g.Data3, + g.Data4[:2], + g.Data4[2:]) +} + +// FromString parses a string containing a GUID and returns the GUID. The only +// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +// format. +func FromString(s string) (GUID, error) { + if len(s) != 36 { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + var g GUID + + data1, err := strconv.ParseUint(s[0:8], 16, 32) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data1 = uint32(data1) + + data2, err := strconv.ParseUint(s[9:13], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data2 = uint16(data2) + + data3, err := strconv.ParseUint(s[14:18], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data3 = uint16(data3) + + for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { + v, err := strconv.ParseUint(s[x:x+2], 16, 8) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data4[i] = uint8(v) + } + + return g, nil +} + +func (g *GUID) setVariant(v Variant) { + d := g.Data4[0] + switch v { + case VariantNCS: + d = (d & 0x7f) + case VariantRFC4122: + d = (d & 0x3f) | 0x80 + case VariantMicrosoft: + d = (d & 0x1f) | 0xc0 + case VariantFuture: + d = (d & 0x0f) | 0xe0 + case VariantUnknown: + fallthrough + default: + panic(fmt.Sprintf("invalid variant: %d", v)) + } + g.Data4[0] = d +} + +// Variant returns the GUID variant, as defined in RFC 4122. +func (g GUID) Variant() Variant { + b := g.Data4[0] + if b&0x80 == 0 { + return VariantNCS + } else if b&0xc0 == 0x80 { + return VariantRFC4122 + } else if b&0xe0 == 0xc0 { + return VariantMicrosoft + } else if b&0xe0 == 0xe0 { + return VariantFuture + } + return VariantUnknown +} + +func (g *GUID) setVersion(v Version) { + g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) +} + +// Version returns the GUID version, as defined in RFC 4122. +func (g GUID) Version() Version { + return Version((g.Data3 & 0xF000) >> 12) +} + +// MarshalText returns the textual representation of the GUID. +func (g GUID) MarshalText() ([]byte, error) { + return []byte(g.String()), nil +} + +// UnmarshalText takes the textual representation of a GUID, and unmarhals it +// into this GUID. +func (g *GUID) UnmarshalText(text []byte) error { + g2, err := FromString(string(text)) + if err != nil { + return err + } + *g = g2 + return nil +} diff --git a/vendor/github.com/Microsoft/go-winio/privilege.go b/vendor/github.com/Microsoft/go-winio/privilege.go new file mode 100644 index 0000000..c3dd7c2 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/privilege.go @@ -0,0 +1,203 @@ +// +build windows + +package winio + +import ( + "bytes" + "encoding/binary" + "fmt" + "runtime" + "sync" + "syscall" + "unicode/utf16" + + "golang.org/x/sys/windows" +) + +//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges +//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf +//sys revertToSelf() (err error) = advapi32.RevertToSelf +//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken +//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread +//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW +//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW +//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW + +const ( + SE_PRIVILEGE_ENABLED = 2 + + ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300 + + SeBackupPrivilege = "SeBackupPrivilege" + SeRestorePrivilege = "SeRestorePrivilege" + SeSecurityPrivilege = "SeSecurityPrivilege" +) + +const ( + securityAnonymous = iota + securityIdentification + securityImpersonation + securityDelegation +) + +var ( + privNames = make(map[string]uint64) + privNameMutex sync.Mutex +) + +// PrivilegeError represents an error enabling privileges. +type PrivilegeError struct { + privileges []uint64 +} + +func (e *PrivilegeError) Error() string { + s := "" + if len(e.privileges) > 1 { + s = "Could not enable privileges " + } else { + s = "Could not enable privilege " + } + for i, p := range e.privileges { + if i != 0 { + s += ", " + } + s += `"` + s += getPrivilegeName(p) + s += `"` + } + return s +} + +// RunWithPrivilege enables a single privilege for a function call. +func RunWithPrivilege(name string, fn func() error) error { + return RunWithPrivileges([]string{name}, fn) +} + +// RunWithPrivileges enables privileges for a function call. +func RunWithPrivileges(names []string, fn func() error) error { + privileges, err := mapPrivileges(names) + if err != nil { + return err + } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + token, err := newThreadToken() + if err != nil { + return err + } + defer releaseThreadToken(token) + err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED) + if err != nil { + return err + } + return fn() +} + +func mapPrivileges(names []string) ([]uint64, error) { + var privileges []uint64 + privNameMutex.Lock() + defer privNameMutex.Unlock() + for _, name := range names { + p, ok := privNames[name] + if !ok { + err := lookupPrivilegeValue("", name, &p) + if err != nil { + return nil, err + } + privNames[name] = p + } + privileges = append(privileges, p) + } + return privileges, nil +} + +// EnableProcessPrivileges enables privileges globally for the process. +func EnableProcessPrivileges(names []string) error { + return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED) +} + +// DisableProcessPrivileges disables privileges globally for the process. +func DisableProcessPrivileges(names []string) error { + return enableDisableProcessPrivilege(names, 0) +} + +func enableDisableProcessPrivilege(names []string, action uint32) error { + privileges, err := mapPrivileges(names) + if err != nil { + return err + } + + p, _ := windows.GetCurrentProcess() + var token windows.Token + err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) + if err != nil { + return err + } + + defer token.Close() + return adjustPrivileges(token, privileges, action) +} + +func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error { + var b bytes.Buffer + binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) + for _, p := range privileges { + binary.Write(&b, binary.LittleEndian, p) + binary.Write(&b, binary.LittleEndian, action) + } + prevState := make([]byte, b.Len()) + reqSize := uint32(0) + success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize) + if !success { + return err + } + if err == ERROR_NOT_ALL_ASSIGNED { + return &PrivilegeError{privileges} + } + return nil +} + +func getPrivilegeName(luid uint64) string { + var nameBuffer [256]uint16 + bufSize := uint32(len(nameBuffer)) + err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize) + if err != nil { + return fmt.Sprintf("", luid) + } + + var displayNameBuffer [256]uint16 + displayBufSize := uint32(len(displayNameBuffer)) + var langID uint32 + err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID) + if err != nil { + return fmt.Sprintf("", string(utf16.Decode(nameBuffer[:bufSize]))) + } + + return string(utf16.Decode(displayNameBuffer[:displayBufSize])) +} + +func newThreadToken() (windows.Token, error) { + err := impersonateSelf(securityImpersonation) + if err != nil { + return 0, err + } + + var token windows.Token + err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token) + if err != nil { + rerr := revertToSelf() + if rerr != nil { + panic(rerr) + } + return 0, err + } + return token, nil +} + +func releaseThreadToken(h windows.Token) { + err := revertToSelf() + if err != nil { + panic(err) + } + h.Close() +} diff --git a/vendor/github.com/Microsoft/go-winio/reparse.go b/vendor/github.com/Microsoft/go-winio/reparse.go new file mode 100644 index 0000000..fc1ee4d --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/reparse.go @@ -0,0 +1,128 @@ +package winio + +import ( + "bytes" + "encoding/binary" + "fmt" + "strings" + "unicode/utf16" + "unsafe" +) + +const ( + reparseTagMountPoint = 0xA0000003 + reparseTagSymlink = 0xA000000C +) + +type reparseDataBuffer struct { + ReparseTag uint32 + ReparseDataLength uint16 + Reserved uint16 + SubstituteNameOffset uint16 + SubstituteNameLength uint16 + PrintNameOffset uint16 + PrintNameLength uint16 +} + +// ReparsePoint describes a Win32 symlink or mount point. +type ReparsePoint struct { + Target string + IsMountPoint bool +} + +// UnsupportedReparsePointError is returned when trying to decode a non-symlink or +// mount point reparse point. +type UnsupportedReparsePointError struct { + Tag uint32 +} + +func (e *UnsupportedReparsePointError) Error() string { + return fmt.Sprintf("unsupported reparse point %x", e.Tag) +} + +// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink +// or a mount point. +func DecodeReparsePoint(b []byte) (*ReparsePoint, error) { + tag := binary.LittleEndian.Uint32(b[0:4]) + return DecodeReparsePointData(tag, b[8:]) +} + +func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) { + isMountPoint := false + switch tag { + case reparseTagMountPoint: + isMountPoint = true + case reparseTagSymlink: + default: + return nil, &UnsupportedReparsePointError{tag} + } + nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6]) + if !isMountPoint { + nameOffset += 4 + } + nameLength := binary.LittleEndian.Uint16(b[6:8]) + name := make([]uint16, nameLength/2) + err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name) + if err != nil { + return nil, err + } + return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil +} + +func isDriveLetter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or +// mount point. +func EncodeReparsePoint(rp *ReparsePoint) []byte { + // Generate an NT path and determine if this is a relative path. + var ntTarget string + relative := false + if strings.HasPrefix(rp.Target, `\\?\`) { + ntTarget = `\??\` + rp.Target[4:] + } else if strings.HasPrefix(rp.Target, `\\`) { + ntTarget = `\??\UNC\` + rp.Target[2:] + } else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' { + ntTarget = `\??\` + rp.Target + } else { + ntTarget = rp.Target + relative = true + } + + // The paths must be NUL-terminated even though they are counted strings. + target16 := utf16.Encode([]rune(rp.Target + "\x00")) + ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00")) + + size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8 + size += len(ntTarget16)*2 + len(target16)*2 + + tag := uint32(reparseTagMountPoint) + if !rp.IsMountPoint { + tag = reparseTagSymlink + size += 4 // Add room for symlink flags + } + + data := reparseDataBuffer{ + ReparseTag: tag, + ReparseDataLength: uint16(size), + SubstituteNameOffset: 0, + SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2), + PrintNameOffset: uint16(len(ntTarget16) * 2), + PrintNameLength: uint16((len(target16) - 1) * 2), + } + + var b bytes.Buffer + binary.Write(&b, binary.LittleEndian, &data) + if !rp.IsMountPoint { + flags := uint32(0) + if relative { + flags |= 1 + } + binary.Write(&b, binary.LittleEndian, flags) + } + + binary.Write(&b, binary.LittleEndian, ntTarget16) + binary.Write(&b, binary.LittleEndian, target16) + return b.Bytes() +} diff --git a/vendor/github.com/Microsoft/go-winio/sd.go b/vendor/github.com/Microsoft/go-winio/sd.go new file mode 100644 index 0000000..db1b370 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/sd.go @@ -0,0 +1,98 @@ +// +build windows + +package winio + +import ( + "syscall" + "unsafe" +) + +//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW +//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW +//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW +//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW +//sys localFree(mem uintptr) = LocalFree +//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength + +const ( + cERROR_NONE_MAPPED = syscall.Errno(1332) +) + +type AccountLookupError struct { + Name string + Err error +} + +func (e *AccountLookupError) Error() string { + if e.Name == "" { + return "lookup account: empty account name specified" + } + var s string + switch e.Err { + case cERROR_NONE_MAPPED: + s = "not found" + default: + s = e.Err.Error() + } + return "lookup account " + e.Name + ": " + s +} + +type SddlConversionError struct { + Sddl string + Err error +} + +func (e *SddlConversionError) Error() string { + return "convert " + e.Sddl + ": " + e.Err.Error() +} + +// LookupSidByName looks up the SID of an account by name +func LookupSidByName(name string) (sid string, err error) { + if name == "" { + return "", &AccountLookupError{name, cERROR_NONE_MAPPED} + } + + var sidSize, sidNameUse, refDomainSize uint32 + err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse) + if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER { + return "", &AccountLookupError{name, err} + } + sidBuffer := make([]byte, sidSize) + refDomainBuffer := make([]uint16, refDomainSize) + err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse) + if err != nil { + return "", &AccountLookupError{name, err} + } + var strBuffer *uint16 + err = convertSidToStringSid(&sidBuffer[0], &strBuffer) + if err != nil { + return "", &AccountLookupError{name, err} + } + sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:]) + localFree(uintptr(unsafe.Pointer(strBuffer))) + return sid, nil +} + +func SddlToSecurityDescriptor(sddl string) ([]byte, error) { + var sdBuffer uintptr + err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil) + if err != nil { + return nil, &SddlConversionError{sddl, err} + } + defer localFree(sdBuffer) + sd := make([]byte, getSecurityDescriptorLength(sdBuffer)) + copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)]) + return sd, nil +} + +func SecurityDescriptorToSddl(sd []byte) (string, error) { + var sddl *uint16 + // The returned string length seems to including an aribtrary number of terminating NULs. + // Don't use it. + err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil) + if err != nil { + return "", err + } + defer localFree(uintptr(unsafe.Pointer(sddl))) + return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil +} diff --git a/vendor/github.com/Microsoft/go-winio/syscall.go b/vendor/github.com/Microsoft/go-winio/syscall.go new file mode 100644 index 0000000..5955c99 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/syscall.go @@ -0,0 +1,3 @@ +package winio + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go diff --git a/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go new file mode 100644 index 0000000..176ff75 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go @@ -0,0 +1,427 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package winio + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + modntdll = windows.NewLazySystemDLL("ntdll.dll") + modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") + + procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges") + procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW") + procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") + procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW") + procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength") + procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") + procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") + procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW") + procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") + procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") + procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") + procRevertToSelf = modadvapi32.NewProc("RevertToSelf") + procBackupRead = modkernel32.NewProc("BackupRead") + procBackupWrite = modkernel32.NewProc("BackupWrite") + procCancelIoEx = modkernel32.NewProc("CancelIoEx") + procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") + procCreateFileW = modkernel32.NewProc("CreateFileW") + procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") + procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") + procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") + procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") + procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") + procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") + procLocalAlloc = modkernel32.NewProc("LocalAlloc") + procLocalFree = modkernel32.NewProc("LocalFree") + procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") + procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") + procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") + procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") + procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") + procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") + procbind = modws2_32.NewProc("bind") +) + +func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { + var _p0 uint32 + if releaseAll { + _p0 = 1 + } + r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize))) + success = r0 != 0 + if true { + err = errnoErr(e1) + } + return +} + +func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func convertSidToStringSid(sid *byte, str **uint16) (err error) { + r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(str) + if err != nil { + return + } + return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size) +} + +func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func getSecurityDescriptorLength(sd uintptr) (len uint32) { + r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0) + len = uint32(r0) + return +} + +func impersonateSelf(level uint32) (err error) { + r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(accountName) + if err != nil { + return + } + return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse) +} + +func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(systemName) + if err != nil { + return + } + return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId) +} + +func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(systemName) + if err != nil { + return + } + return _lookupPrivilegeName(_p0, luid, buffer, size) +} + +func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(systemName) + if err != nil { + return + } + var _p1 *uint16 + _p1, err = syscall.UTF16PtrFromString(name) + if err != nil { + return + } + return _lookupPrivilegeValue(_p0, _p1, luid) +} + +func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) { + r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) { + var _p0 uint32 + if openAsSelf { + _p0 = 1 + } + r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func revertToSelf() (err error) { + r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { + var _p0 *byte + if len(b) > 0 { + _p0 = &b[0] + } + var _p1 uint32 + if abort { + _p1 = 1 + } + var _p2 uint32 + if processSecurity { + _p2 = 1 + } + r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) { + var _p0 *byte + if len(b) > 0 { + _p0 = &b[0] + } + var _p1 uint32 + if abort { + _p1 = 1 + } + var _p2 uint32 + if processSecurity { + _p2 = 1 + } + r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) { + r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) { + r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(name) + if err != nil { + return + } + return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile) +} + +func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0) + handle = syscall.Handle(r0) + if handle == syscall.InvalidHandle { + err = errnoErr(e1) + } + return +} + +func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0) + newport = syscall.Handle(r0) + if newport == 0 { + err = errnoErr(e1) + } + return +} + +func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) { + var _p0 *uint16 + _p0, err = syscall.UTF16PtrFromString(name) + if err != nil { + return + } + return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa) +} + +func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) { + r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0) + handle = syscall.Handle(r0) + if handle == syscall.InvalidHandle { + err = errnoErr(e1) + } + return +} + +func getCurrentThread() (h syscall.Handle) { + r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0) + h = syscall.Handle(r0) + return +} + +func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func localAlloc(uFlags uint32, length uint32) (ptr uintptr) { + r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0) + ptr = uintptr(r0) + return +} + +func localFree(mem uintptr) { + syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0) + return +} + +func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) { + r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) { + r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0) + status = ntstatus(r0) + return +} + +func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) { + r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0) + status = ntstatus(r0) + return +} + +func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) { + r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0) + status = ntstatus(r0) + return +} + +func rtlNtStatusToDosError(status ntstatus) (winerr error) { + r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0) + if r0 != 0 { + winerr = syscall.Errno(r0) + } + return +} + +func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) { + var _p0 uint32 + if wait { + _p0 = 1 + } + r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) { + r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) + if r1 == socketError { + err = errnoErr(e1) + } + return +} diff --git a/vendor/github.com/akamensky/argparse/.gitignore b/vendor/github.com/akamensky/argparse/.gitignore new file mode 100644 index 0000000..f479089 --- /dev/null +++ b/vendor/github.com/akamensky/argparse/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +.idea/ \ No newline at end of file diff --git a/vendor/github.com/akamensky/argparse/.travis.yml b/vendor/github.com/akamensky/argparse/.travis.yml new file mode 100644 index 0000000..8436a98 --- /dev/null +++ b/vendor/github.com/akamensky/argparse/.travis.yml @@ -0,0 +1,9 @@ +language: go +sudo: false +go: + - "1.x" +before_install: + - go install github.com/mattn/goveralls@latest +script: + - go test -v . + - $GOPATH/bin/goveralls -service=travis-ci diff --git a/vendor/github.com/akamensky/argparse/LICENSE b/vendor/github.com/akamensky/argparse/LICENSE new file mode 100644 index 0000000..f1831c5 --- /dev/null +++ b/vendor/github.com/akamensky/argparse/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Alexey Kamenskiy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/akamensky/argparse/README.md b/vendor/github.com/akamensky/argparse/README.md new file mode 100644 index 0000000..db72f70 --- /dev/null +++ b/vendor/github.com/akamensky/argparse/README.md @@ -0,0 +1,230 @@ +# Golang argparse + +[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/akamensky) [![GoDoc](https://godoc.org/github.com/akamensky/argparse?status.svg)](https://godoc.org/github.com/akamensky/argparse) [![Go Report Card](https://goreportcard.com/badge/github.com/akamensky/argparse)](https://goreportcard.com/report/github.com/akamensky/argparse) [![Coverage Status](https://coveralls.io/repos/github/akamensky/argparse/badge.svg?branch=master)](https://coveralls.io/github/akamensky/argparse?branch=master) [![Build Status](https://travis-ci.org/akamensky/argparse.svg?branch=master)](https://travis-ci.org/akamensky/argparse) + +Let's be honest -- Go's standard command line arguments parser `flag` terribly sucks. +It cannot come anywhere close to the Python's `argparse` module. This is why this project exists. + +The goal of this project is to bring ease of use and flexibility of `argparse` to Go. +Which is where the name of this package comes from. + +#### Installation + +To install and start using argparse simply do: + +``` +$ go get -u -v github.com/akamensky/argparse +``` + +You are good to go to write your first command line tool! +See Usage and Examples sections for information how you can use it + +#### Usage + +To start using argparse in Go see above instructions on how to install. +From here on you can start writing your first program. +Please check out examples from `examples/` directory to see how to use it in various ways. + +Here is basic example of print command (from `examples/print/` directory): +```go +package main + +import ( + "fmt" + "github.com/akamensky/argparse" + "os" +) + +func main() { + // Create new parser object + parser := argparse.NewParser("print", "Prints provided string to stdout") + // Create string flag + s := parser.String("s", "string", &argparse.Options{Required: true, Help: "String to print"}) + // Parse input + err := parser.Parse(os.Args) + if err != nil { + // In case of error print error and print usage + // This can also be done by passing -h or --help flags + fmt.Print(parser.Usage(err)) + } + // Finally print the collected string + fmt.Println(*s) +} +``` + +#### Basic options + +Create your parser instance and pass it program name and program description. +Program name if empty will be taken from `os.Args[0]` (which is okay in most cases). +Description can be as long as you wish and will be used in `--help` output +```go +parser := argparse.NewParser("progname", "Description of my awesome program. It can be as long as I wish it to be") +``` + +String will allow you to get a string from arguments, such as `$ progname --string "String content"` +```go +var myString *string = parser.String("s", "string", ...) +``` + +Positional arguments can be used like this `$ progname value1`. +See [Basic Option Structure](#basic-option-structure) and [Positionals](#positionals). +```go +var myString *string = parser.StringPositional(nil) +var myString *string = parser.FilePositional(nil) +var myString *string = parser.FloatPositional(nil) +var myString *string = parser.IntPositional(nil) +var myString *string = parser.SelectorPositional([]string{"a", "b"}, nil) +var myString1 *string = parser.StringPositional(Options{Default: "beep"}) +``` + +Selector works same as a string, except that it will only allow specific values. +For example like this `$ progname --debug-level WARN` +```go +var mySelector *string = parser.Selector("d", "debug-level", []string{"INFO", "DEBUG", "WARN"}, ...) +``` + +StringList allows to collect multiple string values into the slice of strings by repeating same flag multiple times. +Such as `$ progname --string hostname1 --string hostname2 -s hostname3` +```go +var myStringList *[]string = parser.StringList("s", "string", ...) +``` + +List allows to collect multiple values into the slice of strings by repeating same flag multiple times +(at fact - it is an Alias of StringList). +Such as `$ progname --host hostname1 --host hostname2 -H hostname3` +```go +var myList *[]string = parser.List("H", "hostname", ...) +``` + +Flag will tell you if a simple flag was set on command line (true is set, false is not). +For example `$ progname --force` +```go +var myFlag *bool = parser.Flag("f", "force", ...) +``` + +FlagCounter will tell you the number of times that simple flag was set on command line +(integer greater than or equal to 1 or 0 if not set). +For example `$ progname -vv --verbose` +```go +var myFlagCounter *int = parser.FlagCounter("v", "verbose", ...) +``` + +Int will allow you to get a decimal integer from arguments, such as `$ progname --integer "42"` +```go +var myInteger *int = parser.Int("i", "integer", ...) +``` + +IntList allows to collect multiple decimal integer values into the slice of integers by repeating same flag multiple times. +Such as `$ progname --integer 42 --integer +51 -i -1` +```go +var myIntegerList *[]int = parser.IntList("i", "integer", ...) +``` + +Float will allow you to get a floating point number from arguments, such as `$ progname --float "37.2"` +```go +var myFloat *float64 = parser.Float("f", "float", ...) +``` + +FloatList allows to collect multiple floating point number values into the slice of floats by repeating same flag multiple times. +Such as `$ progname --float 42 --float +37.2 -f -1.0` +```go +var myFloatList *[]float64 = parser.FloatList("f", "float", ...) +``` + +File will validate that file exists and will attempt to open it with provided privileges. +To be used like this `$ progname --log-file /path/to/file.log` +```go +var myLogFile *os.File = parser.File("l", "log-file", os.O_RDWR, 0600, ...) +``` + +FileList allows to collect files into the slice of files by repeating same flag multiple times. +FileList will validate that files exists and will attempt to open them with provided privileges. +To be used like this `$ progname --log-file /path/to/file.log --log-file /path/to/file_cpy.log -l /another/path/to/file.log` +```go +var myLogFiles *[]os.File = parser.FileList("l", "log-file", os.O_RDWR, 0600, ...) +``` + +You can implement sub-commands in your CLI using `parser.NewCommand()` or go even deeper with `command.NewCommand()`. +Addition of a sub-command implies that a subcommand is required. +Sub-commands are always parsed before arguments. +If a command has `Positional` arguments and sub-commands then sub-commands take precedence. +Since parser inherits from command, every command supports exactly same options as parser itself, +thus allowing to add arguments specific to that command or more global arguments added on parser itself! + +You can also dynamically retrieve argument values and if they were parsed: +``` +var myInteger *int = parser.Int("i", "integer", ...) +parser.Parse() +fmt.Printf("%d", *parser.GetArgs()[0].GetResult().(*int)) +fmt.Printf("%v", *parser.GetArgs()[0].GetParsed()) +``` + +#### Basic Option Structure + +The `Option` structure is declared at `argparse.go`: +```go +type Options struct { + Required bool + Validate func(args []string) error + Help string + Default interface{} +} +``` + +You can set `Required` to let it know if it should ask for arguments. +Or you can set `Validate` as a lambda function to make it know while value is valid. +Or you can set `Help` for your beautiful help document. +Or you can set `Default` will set the default value if user does not provide a value. + +Example: +``` +dirpath := parser.String("d", "dirpath", + &argparse.Options{ + Required: false, + Help: "the input files' folder path", + Default: "input", + }) +``` + +#### Caveats + +There are a few caveats (or more like design choices) to know about: +* Shorthand arguments MUST be a single character. Shorthand arguments are prepended with single dash `"-"` +* If not convenient shorthand argument can be completely skipped by passing empty string `""` as first argument +* Shorthand arguments ONLY for `parser.Flag()` and `parser.FlagCounter()` can be combined into single argument same as `ps -aux`, `rm -rf` or `lspci -vvk` +* Long arguments must be specified and cannot be empty. They are prepended with double dash `"--"` +* You cannot define two same arguments. Only first one will be used. For example doing `parser.Flag("t", "test", nil)` followed by `parser.String("t", "test2", nil)` will not work as second `String` argument will be ignored (note that both have `"t"` as shorthand argument). However since it is case-sensitive library, you can work arounf it by capitalizing one of the arguments +* There is a pre-defined argument for `-h|--help`, so from above attempting to define any argument using `h` as shorthand will fail +* `parser.Parse()` returns error in case of something going wrong, but it is not expected to cover ALL cases +* Any arguments that left un-parsed will be regarded as error + +##### Positionals +* `Positional` args have a set of effects and conditions: + * Always parsed after subcommands and non-positional args + * Always set Required=False + * Default is only used if the command or subcommand owning the arg `Happened` + * Parsed in Command root->leaf left->right order (breadth-first) + * Top level cmd consumes as many positionals as it can, from left to right + * Then in a descendeding loop for any command which `Happened` it repeats + * Positionals which are not satisfied (due to lack of input args) are not errors + +#### Contributing + +Can you write in Go? Then this projects needs your help! + +Take a look at open issues, specially the ones tagged as `help-wanted`. +If you have any improvements to offer, please open an issue first to ensure this improvement is discussed. + +There are following tasks to be done: +* Add more examples +* Improve code quality (it is messy right now and could use a major revamp to improve gocyclo report) +* Add more argument options (such as numbers parsing) +* Improve test coverage +* Write a wiki for this project + +However note that the logic outlined in method comments must be preserved +as the the library must stick with backward compatibility promise! + +#### Acknowledgments + +Thanks to Python developers for making a great `argparse` which inspired this package to match for greatness of Go diff --git a/vendor/github.com/akamensky/argparse/argparse.go b/vendor/github.com/akamensky/argparse/argparse.go new file mode 100644 index 0000000..cb2cf28 --- /dev/null +++ b/vendor/github.com/akamensky/argparse/argparse.go @@ -0,0 +1,788 @@ +// Package argparse provides users with more flexible and configurable option for command line arguments parsing. +package argparse + +import ( + "errors" + "fmt" + "os" + "strings" +) + +// DisableDescription can be assigned as a command or arguments description to hide it from the Usage output +const DisableDescription = "DISABLEDDESCRIPTIONWILLNOTSHOWUP" + +// Positional Prefix +// This must not overlap with any other arguments given or library +// will panic. +const positionalArgName = "_positionalArg_%s_%d" + +//disable help can be invoked from the parse and then needs to be propogated to subcommands +var disableHelp = false + +// Command is a basic type for this package. It represents top level Parser as well as any commands and sub-commands +// Command MUST NOT ever be created manually. Instead one should call NewCommand method of Parser or Command, +// which will setup appropriate fields and call methods that have to be called when creating new command. +type Command struct { + name string + description string + args []*arg + commands []*Command + parsed bool + happened bool + parent *Command + HelpFunc func(c *Command, msg interface{}) string + exitOnHelp bool +} + +// GetName exposes Command's name field +func (o Command) GetName() string { + return o.name +} + +// GetDescription exposes Command's description field +func (o Command) GetDescription() string { + return o.description +} + +// GetArgs exposes Command's args field +func (o Command) GetArgs() (args []Arg) { + for _, arg := range o.args { + args = append(args, arg) + } + return +} + +// GetCommands exposes Command's commands field +func (o Command) GetCommands() []*Command { + return o.commands +} + +// GetParent exposes Command's parent field +func (o Command) GetParent() *Command { + return o.parent +} + +// Help calls the overriddable Command.HelpFunc on itself, +// called when the help argument strings are passed via CLI +func (o *Command) Help(msg interface{}) string { + tempC := o + for tempC.HelpFunc == nil { + if tempC.parent == nil { + return "" + } + tempC = tempC.parent + } + return tempC.HelpFunc(o, msg) +} + +// Parser is a top level object of argparse. It MUST NOT ever be created manually. Instead one should use +// argparse.NewParser() method that will create new parser, propagate necessary private fields and call needed +// functions. +type Parser struct { + Command +} + +// Options are specific options for every argument. They can be provided if necessary. +// Possible fields are: +// +// Options.positional - tells Parser that the argument is positional (implies Required). Set to true by using *Positional functions. +// Positional arguments must not have arg name preceding them and must come in a specific order. +// Positionals are parsed breadth-first (left->right from Command tree root to leaf) +// Positional sets Shortname="", ignores Required +// Positionals which are not satisfied will be nil but no error will be thrown +// Defaults are only set for unparsed positionals on commands which happened +// Use arg.GetParsed() to detect if arg was satisfied or not +// +// Options.Required - tells Parser that this argument is required to be provided. +// useful when specific Command requires some data provided. +// +// Options.Validate - is a validation function. Using this field anyone can implement a custom validation for argument. +// If provided and argument is present, then function is called. If argument also consumes any following values +// (e.g. as String does), then these are provided as args to function. If validation fails the error must be returned, +// which will be the output of `Parser.Parse` method. +// +// Options.Help - A help message to be displayed in Usage output. Can be of any length as the message will be +// formatted to fit max screen width of 100 characters. +// +// Options.Default - A default value for an argument. This value will be assigned to the argument at the end of parsing +// in case if this argument was not supplied on command line. File default value is a string which it will be open with +// provided options. In case if provided value type does not match expected, the error will be returned on run-time. +type Options struct { + Required bool + Validate func(args []string) error + Help string + Default interface{} + + // Private modifiers + positional bool +} + +// NewParser creates new Parser object that will allow to add arguments for parsing +// It takes program name and description which will be used as part of Usage output +// Returns pointer to Parser object +func NewParser(name string, description string) *Parser { + p := &Parser{} + + p.name = name + p.description = description + + p.args = make([]*arg, 0) + p.commands = make([]*Command, 0) + + p.help("h", "help") + p.exitOnHelp = true + p.HelpFunc = (*Command).Usage + + return p +} + +// NewCommand will create a sub-command and propagate all necessary fields. +// All commands are always at the beginning of the arguments. +// Parser can have commands and those commands can have sub-commands, +// which allows for very flexible workflow. +// All commands are considered as required and all commands can have their own argument set. +// Commands are processed Parser -> Command -> sub-Command. +// Arguments will be processed in order of sub-Command -> Command -> Parser. +func (o *Command) NewCommand(name string, description string) *Command { + c := new(Command) + c.name = name + c.description = description + c.parsed = false + c.parent = o + if !disableHelp { + c.help("h", "help") + c.exitOnHelp = true + c.HelpFunc = (*Command).Usage + } + + if o.commands == nil { + o.commands = make([]*Command, 0) + } + + o.commands = append(o.commands, c) + + return c +} + +// DisableHelp removes any help arguments from the commands list of arguments +// This prevents prevents help from being parsed or invoked from the argument list +func (o *Parser) DisableHelp() { + disableHelp = true + for i, arg := range o.args { + if _, ok := arg.result.(*help); ok { + o.args = append(o.args[:i], o.args[i+1:]...) + } + } + for _, com := range o.commands { + for i, comArg := range com.args { + if _, ok := comArg.result.(*help); ok { + com.args = append(com.args[:i], com.args[i+1:]...) + } + } + } +} + +// ExitOnHelp sets the exitOnHelp variable of Parser +func (o *Command) ExitOnHelp(b bool) { + o.exitOnHelp = b + for _, c := range o.commands { + c.ExitOnHelp(b) + } +} + +// SetHelp removes the previous help argument, and creates a new one with the desired sname/lname +func (o *Parser) SetHelp(sname, lname string) { + o.DisableHelp() + o.help(sname, lname) +} + +// Flag Creates new flag type of argument, which is boolean value showing if argument was provided or not. +// Takes short name, long name and pointer to options (optional). +// Short name must be single character, but can be omitted by giving empty string. +// Long name is required. +// Returns pointer to boolean with starting value `false`. If Parser finds the flag +// provided on Command line arguments, then the value is changed to true. +// Set of Flag and FlagCounter shorthand arguments can be combined together such as `tar -cvaf foo.tar foo` +func (o *Command) Flag(short string, long string, opts *Options) *bool { + var result bool + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 1, + opts: opts, + unique: true, + argType: Flag, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add Flag: %s", err.Error())) + } + + return &result +} + +// FlagCounter Creates new flagCounter type of argument, which is integer value showing the number of times the argument has been provided. +// Takes short name, long name and pointer to options (optional). +// Short name must be single character, but can be omitted by giving empty string. +// Long name is required. +// Returns pointer to integer with starting value `0`. Each time Parser finds the flag +// provided on Command line arguments, the value is incremented by 1. +// Set of FlagCounter and Flag shorthand arguments can be combined together such as `tar -cvaf foo.tar foo` +func (o *Command) FlagCounter(short string, long string, opts *Options) *int { + var result int + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 1, + opts: opts, + unique: false, + argType: FlagCounter, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add FlagCounter: %s", err.Error())) + } + + return &result +} + +// String creates new string argument, which will return whatever follows the argument on CLI. +// Takes as arguments short name (must be single character or an empty string) +// long name and (optional) options +func (o *Command) String(short string, long string, opts *Options) *string { + var result string + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: true, + argType: String, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add String: %s", err.Error())) + } + + return &result +} + +// See func String documentation +func (o *Command) StringPositional(opts *Options) *string { + if opts == nil { + opts = &Options{} + } + opts.positional = true + + // We supply a long name for documentation and internal logic + name := fmt.Sprintf(positionalArgName, o.name, len(o.args)) + return o.String("", name, opts) +} + +// Int creates new int argument, which will attempt to parse following argument as int. +// Takes as arguments short name (must be single character or an empty string) +// long name and (optional) options. +// If parsing fails parser.Parse() will return an error. +func (o *Command) Int(short string, long string, opts *Options) *int { + var result int + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: true, + argType: Int, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add Int: %s", err.Error())) + } + + return &result +} + +// See func Int documentation +func (o *Command) IntPositional(opts *Options) *int { + if opts == nil { + opts = &Options{} + } + opts.positional = true + + // We supply a long name for documentation and internal logic + name := fmt.Sprintf(positionalArgName, o.name, len(o.args)) + return o.Int("", name, opts) +} + +// Float creates new float argument, which will attempt to parse following argument as float64. +// Takes as arguments short name (must be single character or an empty string) +// long name and (optional) options. +// If parsing fails parser.Parse() will return an error. +func (o *Command) Float(short string, long string, opts *Options) *float64 { + var result float64 + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: true, + argType: Float, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add Float: %s", err.Error())) + } + + return &result +} + +// See func Float documentation +func (o *Command) FloatPositional(opts *Options) *float64 { + if opts == nil { + opts = &Options{} + } + opts.positional = true + + // We supply a long name for documentation and internal logic + name := fmt.Sprintf(positionalArgName, o.name, len(o.args)) + return o.Float("", name, opts) +} + +// File creates new file argument, which is when provided will check if file exists or attempt to create it +// depending on provided flags (same as for os.OpenFile). +// It takes same as all other arguments short and long names, additionally it takes flags that specify +// in which mode the file should be open (see os.OpenFile for details on that), file permissions that +// will be applied to a file and argument options. +// Returns a pointer to os.File which will be set to opened file on success. On error the Parser.Parse +// will return error and the pointer might be nil. +func (o *Command) File(short string, long string, flag int, perm os.FileMode, opts *Options) *os.File { + var result os.File + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: true, + fileFlag: flag, + filePerm: perm, + argType: File, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add File: %s", err.Error())) + } + + return &result +} + +// See func File documentation +func (o *Command) FilePositional(flag int, perm os.FileMode, opts *Options) *os.File { + if opts == nil { + opts = &Options{} + } + opts.positional = true + + // We supply a long name for documentation and internal logic + name := fmt.Sprintf(positionalArgName, o.name, len(o.args)) + return o.File("", name, flag, perm, opts) +} + +// List creates new list argument. This is the argument that is allowed to be present multiple times on CLI. +// All appearances of this argument on CLI will be collected into the list of default type values which is strings. +// If no argument provided, then the list is empty. +// Takes same parameters as String. +// Returns a pointer the list of strings. +func (o *Command) List(short string, long string, opts *Options) *[]string { + return o.StringList(short, long, opts) +} + +// StringList creates new string list argument. This is the argument that is allowed to be present multiple times on CLI. +// All appearances of this argument on CLI will be collected into the list of strings. If no argument +// provided, then the list is empty. Takes same parameters as String +// Returns a pointer the list of strings. +func (o *Command) StringList(short string, long string, opts *Options) *[]string { + result := make([]string, 0) + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: false, + argType: StringList, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add StringList: %s", err.Error())) + } + + return &result +} + +// IntList creates new integer list argument. This is the argument that is allowed to be present multiple times on CLI. +// All appearances of this argument on CLI will be collected into the list of integers. If no argument +// provided, then the list is empty. Takes same parameters as Int +// Returns a pointer the list of integers. +func (o *Command) IntList(short string, long string, opts *Options) *[]int { + result := make([]int, 0) + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: false, + argType: IntList, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add IntList: %s", err.Error())) + } + + return &result +} + +// FloatList creates new float list argument. This is the argument that is allowed to be present multiple times on CLI. +// All appearances of this argument on CLI will be collected into the list of float64 values. If no argument +// provided, then the list is empty. Takes same parameters as Float +// Returns a pointer the list of float64 values. +func (o *Command) FloatList(short string, long string, opts *Options) *[]float64 { + result := make([]float64, 0) + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: false, + argType: FloatList, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add FloatList: %s", err.Error())) + } + + return &result +} + +// FileList creates new file list argument. This is the argument that is allowed to be present multiple times on CLI. +// All appearances of this argument on CLI will be collected into the list of os.File values. If no argument +// provided, then the list is empty. Takes same parameters as File +// Returns a pointer the list of os.File values. +func (o *Command) FileList(short string, long string, flag int, perm os.FileMode, opts *Options) *[]os.File { + result := make([]os.File, 0) + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: false, + fileFlag: flag, + filePerm: perm, + argType: FileList, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add FileList: %s", err.Error())) + } + + return &result +} + +// Selector creates a selector argument. Selector argument works in the same way as String argument, with +// the difference that the string value must be from the list of options provided by the program. +// Takes short and long names, argument options and a slice of strings which are allowed values +// for CLI argument. +// Returns a pointer to a string. If argument is not required (as in argparse.Options.Required), +// and argument was not provided, then the string is empty. +func (o *Command) Selector(short string, long string, options []string, opts *Options) *string { + var result string + + a := &arg{ + result: &result, + sname: short, + lname: long, + size: 2, + opts: opts, + unique: true, + selector: &options, + argType: Selector, + } + + if err := o.addArg(a); err != nil { + panic(fmt.Errorf("unable to add Selector: %s", err.Error())) + } + + return &result +} + +// See func Selector documentation +func (o *Command) SelectorPositional(allowed []string, opts *Options) *string { + if opts == nil { + opts = &Options{} + } + opts.positional = true + + // We supply a long name for documentation and internal logic + name := fmt.Sprintf(positionalArgName, o.name, len(o.args)) + return o.Selector("", name, allowed, opts) +} + +// message2String puts msg in result string +// done boolean indicates if result is ready to be returned +// Accepts an interface that can be error, string or fmt.Stringer that will be prepended to a message. +// All other interface types will be ignored +func message2String(msg interface{}) (string, bool) { + var result string + if msg != nil { + switch msg.(type) { + case subCommandError: + result = fmt.Sprintf("%s\n", msg.(error).Error()) + if msg.(subCommandError).cmd != nil { + result += msg.(subCommandError).cmd.Usage(nil) + } + return result, true + case error: + result = fmt.Sprintf("%s\n", msg.(error).Error()) + case string: + result = fmt.Sprintf("%s\n", msg.(string)) + case fmt.Stringer: + result = fmt.Sprintf("%s\n", msg.(fmt.Stringer).String()) + } + } + return result, false +} + +// getPrecedingCommands - collects info on command chain from root to current (o *Command) and all arguments in this chain +func (o *Command) getPrecedingCommands(chain *[]string, arguments *[]*arg) { + current := o + // Also add arguments + // Get line of commands until root + for current != nil { + *chain = append(*chain, current.name) + if current.args != nil { + *arguments = append(*arguments, current.args...) + } + current = current.parent + } + + // Reverse the slice + last := len(*chain) - 1 + for i := 0; i < len(*chain)/2; i++ { + (*chain)[i], (*chain)[last-i] = (*chain)[last-i], (*chain)[i] + } +} + +// getSubCommands - collects info on subcommands of current command +func (o *Command) getSubCommands(chain *[]string) []Command { + commands := make([]Command, 0) + if o.commands != nil && len(o.commands) > 0 { + *chain = append(*chain, "") + for _, v := range o.commands { + // Skip hidden commands + if v.description == DisableDescription { + continue + } + commands = append(commands, *v) + } + } + return commands +} + +// precedingCommands2Result - puts info about command chain from root to current (o *Command) into result string buffer +func (o *Command) precedingCommands2Result(result string, chain []string, arguments []*arg, maxWidth int) string { + usedHelp := false + leftPadding := len("usage: " + chain[0] + "") + // Add preceding commands + for _, v := range chain { + result = addToLastLine(result, v, maxWidth, leftPadding, true) + } + // Add arguments from this and all preceding commands + for _, v := range arguments { + // Skip arguments that are hidden + if v.opts.Help == DisableDescription { + continue + } + if v.lname == "help" && usedHelp { + } else { + result = addToLastLine(result, v.usage(), maxWidth, leftPadding, true) + } + if v.lname == "help" || v.sname == "h" { + usedHelp = true + } + } + // Add program/Command description to the result + result = result + "\n\n" + strings.Repeat(" ", leftPadding) + result = addToLastLine(result, o.description, maxWidth, leftPadding, true) + result = result + "\n\n" + + return result +} + +// subCommands2Result - puts info about subcommands of current command into result string buffer +func subCommands2Result(result string, commands []Command, maxWidth int) string { + // Add list of sub-commands to the result + if len(commands) > 0 { + cmdContent := "Commands:\n\n" + // Get biggest padding + var cmdPadding int + for _, com := range commands { + if com.description == DisableDescription { + continue + } + if len(" "+com.name+" ") > cmdPadding { + cmdPadding = len(" " + com.name + " ") + } + } + // Now add commands with known padding + for _, com := range commands { + if com.description == DisableDescription { + continue + } + cmd := " " + com.name + cmd = cmd + strings.Repeat(" ", cmdPadding-len(cmd)-1) + cmd = addToLastLine(cmd, com.description, maxWidth, cmdPadding, true) + cmdContent = cmdContent + cmd + "\n" + } + result = result + cmdContent + "\n" + } + return result +} + +// arguments2Result - puts info about all arguments of current command into result string buffer +func arguments2Result(result string, arguments []*arg, maxWidth int) string { + usedHelp := false + if len(arguments) > 0 { + argContent := "Arguments:\n\n" + // Get biggest padding + var argPadding int + // Find biggest padding + for _, argument := range arguments { + if argument.opts.Help == DisableDescription { + continue + } + if len(argument.lname)+9 > argPadding { + argPadding = len(argument.lname) + 9 + } + } + // Now add args with padding + for _, argument := range arguments { + if argument.opts.Help == DisableDescription { + continue + } + if argument.lname == "help" && usedHelp { + } else { + arg := " " + if argument.sname != "" { + arg = arg + "-" + argument.sname + " " + } else { + arg = arg + " " + } + arg = arg + "--" + argument.lname + arg = arg + strings.Repeat(" ", argPadding-len(arg)) + if argument.opts != nil && argument.opts.Help != "" { + arg = addToLastLine(arg, argument.getHelpMessage(), maxWidth, argPadding, true) + } + argContent = argContent + arg + "\n" + } + if argument.lname == "help" || argument.sname == "h" { + usedHelp = true + } + } + result = result + argContent + "\n" + } + return result +} + +// Happened shows whether Command was specified on CLI arguments or not. If Command did not "happen", then +// all its descendant commands and arguments are not parsed. Returns a boolean value. +func (o *Command) Happened() bool { + return o.happened +} + +// Usage returns a multiline string that is the same as a help message for this Parser or Command. +// Since Parser is a Command as well, they work in exactly same way. Meaning that usage string +// can be retrieved for any level of commands. It will only include information about this Command, +// its sub-commands, current Command arguments and arguments of all preceding commands (if any) +// +// Accepts an interface that can be error, string or fmt.Stringer that will be prepended to a message. +// All other interface types will be ignored +func (o *Command) Usage(msg interface{}) string { + for _, cmd := range o.commands { + if cmd.Happened() { + return cmd.Usage(msg) + } + } + + // Stay classy + maxWidth := 80 + // List of arguments from all preceding commands + arguments := make([]*arg, 0) + // Line of commands until root + var chain []string + + // Put message in result + result, done := message2String(msg) + if done { + return result + } + + //collect info about Preceding Commands into chain and arguments + o.getPrecedingCommands(&chain, &arguments) + // If this Command has sub-commands we need their list + commands := o.getSubCommands(&chain) + + // Build usage description from description of preceding commands chain and each of subcommands + result += "usage:" + result = o.precedingCommands2Result(result, chain, arguments, maxWidth) + result = subCommands2Result(result, commands, maxWidth) + // Add list of arguments to the result + result = arguments2Result(result, arguments, maxWidth) + + return result +} + +// Parse method can be applied only on Parser. It takes a slice of strings (as in os.Args) +// and it will process this slice as arguments of CLI (the original slice is not modified). +// Returns error on any failure. In case of failure recommended course of action is to +// print received error alongside with usage information (might want to check which Command +// was active when error happened and print that specific Command usage). +// In case no error returned all arguments should be safe to use. Safety of using arguments +// before Parse operation is complete is not guaranteed. +func (o *Parser) Parse(args []string) error { + subargs := make([]string, len(args)) + copy(subargs, args) + + result := o.parse(&subargs) + if result == nil { + result = o.parsePositionals(&subargs) + } + unparsed := make([]string, 0) + for _, v := range subargs { + if v != "" { + unparsed = append(unparsed, v) + } + } + + if result == nil && len(unparsed) > 0 { + return errors.New("unknown arguments " + strings.Join(unparsed, " ")) + } + + return result +} diff --git a/vendor/github.com/akamensky/argparse/argument.go b/vendor/github.com/akamensky/argparse/argument.go new file mode 100644 index 0000000..d0c52eb --- /dev/null +++ b/vendor/github.com/akamensky/argparse/argument.go @@ -0,0 +1,580 @@ +package argparse + +import ( + "fmt" + "os" + "reflect" + "strconv" + "strings" +) + +type arg struct { + result interface{} // Pointer to the resulting value + opts *Options // Options + sname string // Short name (in Parser will start with "-" + lname string // Long name (in Parser will start with "--" + size int // Size defines how many args after match will need to be consumed + unique bool // Specifies whether flag should be present only once + parsed bool // Specifies whether flag has been parsed already + fileFlag int // File mode to open file with + filePerm os.FileMode // File permissions to set a file + selector *[]string // Used in Selector type to allow to choose only one from list of options + parent *Command // Used to get access to specific Command + eqChar bool // This is used if the command is passed in with an equals char as a seperator + argType ArgumentType // Used to determine which argument type this is +} + +// enum used to determine the argument type +type ArgumentType int + +const ( + Flag ArgumentType = 0 + FlagCounter = 1 + String = 2 + Int = 3 + Float = 4 + File = 5 + StringList = 6 + IntList = 7 + FloatList = 8 + FileList = 9 + Selector = 10 +) + +// Arg interface provides exporting of arg structure, while exposing it +type Arg interface { + GetOpts() *Options + GetSname() string + GetLname() string + GetResult() interface{} + GetPositional() bool + GetParsed() bool +} + +func (o arg) GetPositional() bool { + if o.opts != nil { + return o.opts.positional + } + return false +} + +func (o arg) GetParsed() bool { + return o.parsed +} + +func (o arg) GetOpts() *Options { + return o.opts +} + +func (o arg) GetSname() string { + return o.sname +} + +func (o arg) GetLname() string { + return o.lname +} + +// getResult returns the interface{} to the *(type) containing the argument's result value +// Will contain the empty/default value if argument value was not given +func (o arg) GetResult() interface{} { + return o.result +} + +type help struct{} + +// checkLongName if long argumet present. +// checkLongName - returns the argumet's long name number of occurrences and error. +// For long name return value is 0 or 1. +func (o *arg) checkLongName(argument string) int { + // Check for long name only if not empty + if o.lname != "" { + // If argument begins with "--" and next is not "-" then it is a long name + if len(argument) > 2 && strings.HasPrefix(argument, "--") && argument[2] != '-' { + if argument[2:] == o.lname { + return 1 + } + } + } + + return 0 +} + +// checkShortName if argument present. +// checkShortName - returns the argumet's short name number of occurrences and error. +// For shorthand argument - 0 if there is no occurrences, or count of occurrences. +// Shorthand argument with parametr, mast be the only or last in the argument string. +func (o *arg) checkShortName(argument string) (int, error) { + // Check for short name only if not empty + if o.sname != "" { + + // If argument begins with "-" and next is not "-" then it is a short name + if len(argument) > 1 && strings.HasPrefix(argument, "-") && argument[1] != '-' { + count := strings.Count(argument[1:], o.sname) + switch { + // For args with size 1 (Flag,FlagCounter) multiple shorthand in one argument are allowed + case o.size == 1: + return count, nil + // For args with o.size > 1, shorthand argument is allowed only to complete the sequence of arguments combined into one + case o.size > 1: + if count > 1 { + return count, fmt.Errorf("[%s] argument: The parameter must follow", o.name()) + } + if strings.HasSuffix(argument[1:], o.sname) { + return count, nil + } + //if o.size < 1 - it is an error + default: + return 0, fmt.Errorf("Argument's size < 1 is not allowed") + } + } + } + + return 0, nil +} + +// check if argument present. +// check - returns the argument's number of occurrences and error. +// For long name return value is 0 or 1. +// For shorthand argument - 0 if there is no occurrences, or count of occurrences. +// Shorthand argument with parametr, mast be the only or last in the argument string. +func (o *arg) check(argument string) (int, error) { + rez := o.checkLongName(argument) + if rez > 0 { + return rez, nil + } + + return o.checkShortName(argument) +} + +func (o *arg) reducePositional(position int, args *[]string) { + (*args)[position] = "" +} + +func (o *arg) reduceLongName(position int, args *[]string) { + argument := (*args)[position] + // Check for long name only if not empty + if o.lname != "" { + // If argument begins with "--" and next is not "-" then it is a long name + if len(argument) > 2 && strings.HasPrefix(argument, "--") && argument[2] != '-' { + if o.eqChar { + splitInd := strings.LastIndex(argument, "=") + equalArg := []string{argument[:splitInd], argument[splitInd+1:]} + argument = equalArg[0] + } + if argument[2:] == o.lname { + for i := position; i < position+o.size; i++ { + (*args)[i] = "" + } + } + } + } +} + +func (o *arg) reduceShortName(position int, args *[]string) { + argument := (*args)[position] + // Check for short name only if not empty + if o.sname != "" { + // If argument begins with "-" and next is not "-" then it is a short name + if len(argument) > 1 && strings.HasPrefix(argument, "-") && argument[1] != '-' { + // For args with size 1 (Flag,FlagCounter) we allow multiple shorthand in one + if o.size == 1 { + if strings.Contains(argument[1:], o.sname) { + (*args)[position] = strings.Replace(argument, o.sname, "", -1) + if (*args)[position] == "-" { + (*args)[position] = "" + } + if o.eqChar { + (*args)[position] = "" + } + } + // For all other types it must be separate argument + } else { + if argument[1:] == o.sname { + for i := position; i < position+o.size; i++ { + (*args)[i] = "" + } + } + } + } + } +} + +// clear out already used argument from args at position +func (o *arg) reduce(position int, args *[]string) { + if o.GetPositional() { + o.reducePositional(position, args) + } else { + o.reduceLongName(position, args) + o.reduceShortName(position, args) + } +} + +func (o *arg) parseInt(args []string, argCount int) error { + //data of integer type is for + switch { + //FlagCounter argument + case len(args) < 1: + if o.size > 1 { + return fmt.Errorf("[%s] must be followed by an integer", o.name()) + } + *o.result.(*int) += argCount + case len(args) > 1: + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + //or Int argument with one integer parameter + default: + val, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("[%s] bad integer value [%s]", o.name(), args[0]) + } + *o.result.(*int) = val + } + o.parsed = true + return nil +} + +func (o *arg) parseBool(args []string) error { + //data of bool type is for Flag argument + *o.result.(*bool) = true + o.parsed = true + return nil +} + +func (o *arg) parseFloat(args []string) error { + //data of float64 type is for Float argument with one float parameter + if len(args) < 1 { + return fmt.Errorf("[%s] must be followed by a floating point number", o.name()) + } + if len(args) > 1 { + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + } + + val, err := strconv.ParseFloat(args[0], 64) + if err != nil { + return fmt.Errorf("[%s] bad floating point value [%s]", o.name(), args[0]) + } + + *o.result.(*float64) = val + o.parsed = true + return nil +} + +func (o *arg) parseString(args []string) error { + //data of string type is for String argument with one string parameter + if len(args) < 1 { + return fmt.Errorf("[%s] must be followed by a string", o.name()) + } + if len(args) > 1 { + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + } + + // Selector case + if o.selector != nil { + match := false + for _, v := range *o.selector { + if args[0] == v { + match = true + } + } + if !match { + return fmt.Errorf("bad value for [%s]. Allowed values are %v", o.name(), *o.selector) + } + } + + *o.result.(*string) = args[0] + o.parsed = true + return nil +} + +func (o *arg) parseFile(args []string) error { + //data of os.File type is for File argument with one file name parameter + if len(args) < 1 { + return fmt.Errorf("[%s] must be followed by a path to file", o.name()) + } + if len(args) > 1 { + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + } + + f, err := os.OpenFile(args[0], o.fileFlag, o.filePerm) + if err != nil { + return err + } + + *o.result.(*os.File) = *f + o.parsed = true + return nil +} + +func (o *arg) parseStringList(args []string) error { + //data of []string type is for List and StringList argument with set of string parameters + if len(args) < 1 { + return fmt.Errorf("[%s] must be followed by a string", o.name()) + } + if len(args) > 1 { + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + } + + *o.result.(*[]string) = append(*o.result.(*[]string), args[0]) + o.parsed = true + return nil +} + +func (o *arg) parseIntList(args []string) error { + //data of []int type is for IntList argument with set of int parameters + switch { + case len(args) < 1: + return fmt.Errorf("[%s] must be followed by an integer", o.name()) + case len(args) > 1: + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + } + + val, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("[%s] bad integer value [%s]", o.name(), args[0]) + } + *o.result.(*[]int) = append(*o.result.(*[]int), val) + o.parsed = true + return nil +} + +func (o *arg) parseFloatList(args []string) error { + //data of []float64 type is for FloatList argument with set of int parameters + switch { + case len(args) < 1: + return fmt.Errorf("[%s] must be followed by a floating point number", o.name()) + case len(args) > 1: + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + } + + val, err := strconv.ParseFloat(args[0], 64) + if err != nil { + return fmt.Errorf("[%s] bad floating point value [%s]", o.name(), args[0]) + } + *o.result.(*[]float64) = append(*o.result.(*[]float64), val) + o.parsed = true + return nil +} + +func (o *arg) parseFileList(args []string) error { + //data of []os.File type is for FileList argument with set of int parameters + switch { + case len(args) < 1: + return fmt.Errorf("[%s] must be followed by a path to file", o.name()) + case len(args) > 1: + return fmt.Errorf("[%s] followed by too many arguments", o.name()) + } + f, err := os.OpenFile(args[0], o.fileFlag, o.filePerm) + if err != nil { + //if one of FileList's file opening have been failed, close all other in this list + errs := make([]string, 0, len(*o.result.(*[]os.File))) + for _, f := range *o.result.(*[]os.File) { + if err := f.Close(); err != nil { + //almost unreal, but what if another process closed this file + errs = append(errs, err.Error()) + } + } + if len(errs) > 0 { + err = fmt.Errorf("while handling error: %v, other errors occured: %#v", err.Error(), errs) + } + *o.result.(*[]os.File) = []os.File{} + return err + } + *o.result.(*[]os.File) = append(*o.result.(*[]os.File), *f) + o.parsed = true + return nil +} + +// To overwrite while testing +// Possibly extend to allow user overriding +var exit func(int) = os.Exit +var print func(...interface{}) (int, error) = fmt.Println + +func (o *arg) parseSomeType(args []string, argCount int) error { + var err error + switch o.result.(type) { + case *help: + print(o.parent.Help(nil)) + if o.parent.exitOnHelp { + exit(0) + } + //data of bool type is for Flag argument + case *bool: + err = o.parseBool(args) + case *int: + err = o.parseInt(args, argCount) + case *float64: + err = o.parseFloat(args) + case *string: + err = o.parseString(args) + case *os.File: + err = o.parseFile(args) + case *[]string: + err = o.parseStringList(args) + case *[]int: + err = o.parseIntList(args) + case *[]float64: + err = o.parseFloatList(args) + case *[]os.File: + err = o.parseFileList(args) + default: + err = fmt.Errorf("unsupported type [%t]", o.result) + } + return err +} + +func (o *arg) parsePositional(arg string) error { + if err := o.parse([]string{arg}, 1); err != nil { + return err + } + + return nil +} + +func (o *arg) parse(args []string, argCount int) error { + // If unique do not allow more than one time + if o.unique && (o.parsed || argCount > 1) { + return fmt.Errorf("[%s] can only be present once", o.name()) + } + + // If validation function provided -- execute, on error return immediately + if o.opts != nil && o.opts.Validate != nil { + err := o.opts.Validate(args) + if err != nil { + return fmt.Errorf("[%s] %w", o.name(), err) + } + } + return o.parseSomeType(args, argCount) +} + +func (o *arg) name() string { + if o.GetPositional() { + return o.lname + } + var name string + if o.lname == "" { + name = "-" + o.sname + } else if o.sname == "" { + name = "--" + o.lname + } else { + name = "-" + o.sname + "|" + "--" + o.lname + } + return name +} + +func (o *arg) usage() string { + var result string + result = o.name() + switch o.result.(type) { + case *bool: + break + case *int: + isFlagCounter := !o.unique && o.size == 1 + if !isFlagCounter { + result = result + " " + } + case *float64: + result = result + " " + case *string: + if o.selector != nil { + result = result + " (" + strings.Join(*o.selector, "|") + ")" + } else { + result = result + " \"\"" + } + case *os.File: + result = result + " " + case *[]string: + result = result + " \"\"" + " [" + result + " \"\" ...]" + default: + break + } + if o.opts == nil || o.opts.Required == false { + result = "[" + result + "]" + } + return result +} + +func (o *arg) getHelpMessage() string { + message := "" + if len(o.opts.Help) > 0 { + message += o.opts.Help + if !o.opts.Required && o.opts.Default != nil { + message += fmt.Sprintf(". Default: %v", o.opts.Default) + } + } + return message +} + +// setDefaultFile - gets default os.File object based on provided default filename string +func (o *arg) setDefaultFile() error { + // In case of File we should get string as default value + if v, ok := o.opts.Default.(string); ok { + f, err := os.OpenFile(v, o.fileFlag, o.filePerm) + if err != nil { + return err + } + *o.result.(*os.File) = *f + } else { + return fmt.Errorf("cannot use default type [%T] as value of pointer with type [*string]", o.opts.Default) + } + return nil +} + +// setDefaultFiles - gets list of default os.File objects based on provided list of default filenames strings +func (o *arg) setDefaultFiles() error { + // In case of FileList we should get []string as default value + var files []os.File + if fileNames, ok := o.opts.Default.([]string); ok { + files = make([]os.File, 0, len(fileNames)) + for _, v := range fileNames { + f, err := os.OpenFile(v, o.fileFlag, o.filePerm) + if err != nil { + //if one of FileList's file opening have been failed, close all other in this list + errs := make([]string, 0, len(*o.result.(*[]os.File))) + for _, f := range *o.result.(*[]os.File) { + if err := f.Close(); err != nil { + //almost unreal, but what if another process closed this file + errs = append(errs, err.Error()) + } + } + if len(errs) > 0 { + err = fmt.Errorf("while handling error: %v, other errors occured: %#v", err.Error(), errs) + } + *o.result.(*[]os.File) = []os.File{} + return err + } + files = append(files, *f) + } + } else { + return fmt.Errorf("cannot use default type [%T] as value of pointer with type [*[]string]", o.opts.Default) + } + *o.result.(*[]os.File) = files + return nil +} + +// setDefault - if no value getted for specific argument, set default value, if provided +func (o *arg) setDefault() error { + // Only set default if it was not parsed, and default value was defined + if !o.parsed && o.opts != nil && o.opts.Default != nil { + switch o.result.(type) { + case *bool, *int, *float64, *string, *[]bool, *[]int, *[]float64, *[]string: + if reflect.TypeOf(o.result) != reflect.PtrTo(reflect.TypeOf(o.opts.Default)) { + return fmt.Errorf("cannot use default type [%T] as value of pointer with type [%T]", o.opts.Default, o.result) + } + defaultValue := o.opts.Default + if o.argType == Flag && defaultValue == true { + defaultValue = false + } + reflect.ValueOf(o.result).Elem().Set(reflect.ValueOf(defaultValue)) + + case *os.File: + if err := o.setDefaultFile(); err != nil { + return err + } + case *[]os.File: + if err := o.setDefaultFiles(); err != nil { + return err + } + } + } + + return nil +} diff --git a/vendor/github.com/akamensky/argparse/command.go b/vendor/github.com/akamensky/argparse/command.go new file mode 100644 index 0000000..f87d411 --- /dev/null +++ b/vendor/github.com/akamensky/argparse/command.go @@ -0,0 +1,238 @@ +package argparse + +import ( + "fmt" + "strings" +) + +func (o *Command) help(sname, lname string) { + result := &help{} + + if lname == "" { + sname, lname = "h", "help" + } + + a := &arg{ + result: result, + sname: sname, + lname: lname, + size: 1, + opts: &Options{Help: "Print help information"}, + unique: true, + } + + o.addArg(a) +} + +func (o *Command) addArg(a *arg) error { + // long name should be provided + if a.lname == "" { + return fmt.Errorf("long name should be provided") + } + // short name could be provided and must not exceed 1 character + if len(a.sname) > 1 { + return fmt.Errorf("short name must not exceed 1 character") + } + // Search parents for overlapping commands and fail if any + current := o + for current != nil { + if current.args != nil { + for _, v := range current.args { + if a.lname != "help" || a.sname != "h" { + if a.sname != "" && a.sname == v.sname { + return fmt.Errorf("short name %s occurs more than once", a.sname) + } + if a.lname == v.lname { + return fmt.Errorf("long name %s occurs more than once", a.lname) + } + } + } + } + current = current.parent + } + a.parent = o + + if a.GetPositional() { + switch a.argType { // Secondary guard + case Flag, FlagCounter, StringList, IntList, FloatList, FileList: + return fmt.Errorf("argument type cannot be positional") + } + a.sname = "" + a.opts.Required = false + a.size = 1 // We could allow other sizes in the future + } + o.args = append(o.args, a) + + return nil +} + +//parseSubCommands - Parses subcommands if any +func (o *Command) parseSubCommands(args *[]string) error { + if o.commands != nil && len(o.commands) > 0 { + // If we have subcommands and 0 args left + // that is an error of SubCommandError type + if len(*args) < 1 { + return newSubCommandError(o) + } + for _, v := range o.commands { + err := v.parse(args) + if err != nil { + return err + } + if v.happened { + return nil + } + } + // If we got here, there were subcommands to parse, + // but none were found, so return an error + return newSubCommandError(o) + } + return nil +} + +// Breadth-first parse style for positionals +// Each command proceeds left to right consuming as many +// positionals as it needs before beginning sub-command parsing +// All flags must have been parsed and reduced prior to calling this +// Positionals will consume any remaining values, +// disregarding if they have dashes or equals signs or other "delims". +func (o *Command) parsePositionals(inputArgs *[]string) error { + for _, oarg := range o.args { + // Two-stage parsing, this is the second stage + if !oarg.GetPositional() { + continue + } + for j := 0; j < len(*inputArgs); j++ { + arg := (*inputArgs)[j] + if arg == "" { + continue + } + if err := oarg.parsePositional(arg); err != nil { + return err + } + oarg.reduce(j, inputArgs) + break // Positionals can only occur once + } + // positional was unsatisfiable, use the default + if !oarg.parsed { + err := oarg.setDefault() + if err != nil { + return err + } + } + } + for _, c := range o.commands { + if c.happened { // presumption of only one sub-command happening + return c.parsePositionals(inputArgs) + } + } + return nil +} + +//parseArguments - Parses arguments +func (o *Command) parseArguments(inputArgs *[]string) error { + // Iterate over the args + for _, oarg := range o.args { + if oarg.GetPositional() { // Two-stage parsing, this is the first stage + continue + } + for j := 0; j < len(*inputArgs); j++ { + arg := (*inputArgs)[j] + if arg == "" { + continue + } + if strings.Contains(arg, "=") { + splitInd := strings.LastIndex(arg, "=") + equalArg := []string{arg[:splitInd], arg[splitInd+1:]} + if cnt, err := oarg.check(equalArg[0]); err != nil { + return err + } else if cnt > 0 { // No args implies we supply default + if equalArg[1] == "" { + return fmt.Errorf("not enough arguments for %s", oarg.name()) + } + oarg.eqChar = true + oarg.size = 1 + currArg := []string{equalArg[1]} + err := oarg.parse(currArg, cnt) + if err != nil { + return err + } + oarg.reduce(j, inputArgs) + continue + } + } + if cnt, err := oarg.check(arg); err != nil { + return err + } else if cnt > 0 { + if len(*inputArgs) < j+oarg.size { + return fmt.Errorf("not enough arguments for %s", oarg.name()) + } + err := oarg.parse((*inputArgs)[j+1:j+oarg.size], cnt) + if err != nil { + return err + } + oarg.reduce(j, inputArgs) + continue + } + } + + // Check if arg is required and not provided + if oarg.opts != nil && oarg.opts.Required && !oarg.parsed { + return fmt.Errorf("[%s] is required", oarg.name()) + } else if oarg.opts != nil && oarg.opts.Default != nil && !oarg.parsed { + // Check for argument default value and if provided try to type cast and assign + err := oarg.setDefault() + if err != nil { + return err + } + } + } + return nil +} + +// Will parse provided list of arguments +// common usage would be to pass directly os.Args +// Depth-first parsing: We will reach the deepest +// node of the command tree and then parse arguments, +// stepping back up only after each node is satisfied. +func (o *Command) parse(args *[]string) error { + // If already been parsed do nothing + if o.parsed { + return nil + } + + // If no arguments left to parse do nothing + if len(*args) < 1 { + return nil + } + + // Parse only matching commands + // But we always have to parse top level + if o.name == "" { + o.name = (*args)[0] + } else { + if o.name != (*args)[0] && o.parent != nil { + return nil + } + } + + // Set happened status to true when command happened + o.happened = true + + // Reduce arguments by removing Command name + *args = (*args)[1:] + + // Parse subcommands if any + if err := o.parseSubCommands(args); err != nil { + return err + } + + // Parse arguments if any + if err := o.parseArguments(args); err != nil { + return err + } + + // Set parsed status to true and return quietly + o.parsed = true + return nil +} diff --git a/vendor/github.com/akamensky/argparse/errors.go b/vendor/github.com/akamensky/argparse/errors.go new file mode 100644 index 0000000..3f5d461 --- /dev/null +++ b/vendor/github.com/akamensky/argparse/errors.go @@ -0,0 +1,14 @@ +package argparse + +type subCommandError struct { + error + cmd *Command +} + +func (e subCommandError) Error() string { + return "[sub]Command required" +} + +func newSubCommandError(cmd *Command) error { + return subCommandError{cmd: cmd} +} diff --git a/vendor/github.com/akamensky/argparse/extras.go b/vendor/github.com/akamensky/argparse/extras.go new file mode 100644 index 0000000..fbc7d5a --- /dev/null +++ b/vendor/github.com/akamensky/argparse/extras.go @@ -0,0 +1,25 @@ +package argparse + +import "strings" + +func getLastLine(input string) string { + slice := strings.Split(input, "\n") + return slice[len(slice)-1] +} + +func addToLastLine(base string, add string, width int, padding int, canSplit bool) string { + // If last line has less than 10% space left, do not try to fill in by splitting else just try to split + hasTen := (width - len(getLastLine(base))) > width/10 + if len(getLastLine(base)+" "+add) >= width { + if hasTen && canSplit { + adds := strings.Split(add, " ") + for _, v := range adds { + base = addToLastLine(base, v, width, padding, false) + } + return base + } + base = base + "\n" + strings.Repeat(" ", padding) + } + base = base + " " + add + return base +} diff --git a/vendor/github.com/akamensky/argparse/misc.go b/vendor/github.com/akamensky/argparse/misc.go new file mode 100644 index 0000000..bc9af6d --- /dev/null +++ b/vendor/github.com/akamensky/argparse/misc.go @@ -0,0 +1,13 @@ +package argparse + +import ( + "os" + "reflect" +) + +// IsNilFile allows to test whether returned `*os.File` has been initialized with data passed on CLI. +// Returns true if `fd == &{nil}`, which means `*os.File` was not initialized, false if `fd` is +// a fully initialized `*os.File` or if `fd == nil`. +func IsNilFile(fd *os.File) bool { + return reflect.DeepEqual(fd, &os.File{}) +} diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE new file mode 100644 index 0000000..bc52e96 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2012-2016 Dave Collins + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go new file mode 100644 index 0000000..7929947 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -0,0 +1,145 @@ +// Copyright (c) 2015-2016 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when the code is not running on Google App Engine, compiled by GopherJS, and +// "-tags safe" is not added to the go build command line. The "disableunsafe" +// tag is deprecated and thus should not be used. +// Go versions prior to 1.4 are disabled because they use a different layout +// for interfaces which make the implementation of unsafeReflectValue more complex. +// +build !js,!appengine,!safe,!disableunsafe,go1.4 + +package spew + +import ( + "reflect" + "unsafe" +) + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = false + + // ptrSize is the size of a pointer on the current arch. + ptrSize = unsafe.Sizeof((*byte)(nil)) +) + +type flag uintptr + +var ( + // flagRO indicates whether the value field of a reflect.Value + // is read-only. + flagRO flag + + // flagAddr indicates whether the address of the reflect.Value's + // value may be taken. + flagAddr flag +) + +// flagKindMask holds the bits that make up the kind +// part of the flags field. In all the supported versions, +// it is in the lower 5 bits. +const flagKindMask = flag(0x1f) + +// Different versions of Go have used different +// bit layouts for the flags type. This table +// records the known combinations. +var okFlags = []struct { + ro, addr flag +}{{ + // From Go 1.4 to 1.5 + ro: 1 << 5, + addr: 1 << 7, +}, { + // Up to Go tip. + ro: 1<<5 | 1<<6, + addr: 1 << 8, +}} + +var flagValOffset = func() uintptr { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") + } + return field.Offset +}() + +// flagField returns a pointer to the flag field of a reflect.Value. +func flagField(v *reflect.Value) *flag { + return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset)) +} + +// unsafeReflectValue converts the passed reflect.Value into a one that bypasses +// the typical safety restrictions preventing access to unaddressable and +// unexported data. It works by digging the raw pointer to the underlying +// value out of the protected value and generating a new unprotected (unsafe) +// reflect.Value to it. +// +// This allows us to check for implementations of the Stringer and error +// interfaces to be used for pretty printing ordinarily unaddressable and +// inaccessible values such as unexported struct fields. +func unsafeReflectValue(v reflect.Value) reflect.Value { + if !v.IsValid() || (v.CanInterface() && v.CanAddr()) { + return v + } + flagFieldPtr := flagField(&v) + *flagFieldPtr &^= flagRO + *flagFieldPtr |= flagAddr + return v +} + +// Sanity checks against future reflect package changes +// to the type or semantics of the Value.flag field. +func init() { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") + } + if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() { + panic("reflect.Value flag field has changed kind") + } + type t0 int + var t struct { + A t0 + // t0 will have flagEmbedRO set. + t0 + // a will have flagStickyRO set + a t0 + } + vA := reflect.ValueOf(t).FieldByName("A") + va := reflect.ValueOf(t).FieldByName("a") + vt0 := reflect.ValueOf(t).FieldByName("t0") + + // Infer flagRO from the difference between the flags + // for the (otherwise identical) fields in t. + flagPublic := *flagField(&vA) + flagWithRO := *flagField(&va) | *flagField(&vt0) + flagRO = flagPublic ^ flagWithRO + + // Infer flagAddr from the difference between a value + // taken from a pointer and not. + vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A") + flagNoPtr := *flagField(&vA) + flagPtr := *flagField(&vPtrA) + flagAddr = flagNoPtr ^ flagPtr + + // Check that the inferred flags tally with one of the known versions. + for _, f := range okFlags { + if flagRO == f.ro && flagAddr == f.addr { + return + } + } + panic("reflect.Value read-only flag has changed semantics") +} diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go new file mode 100644 index 0000000..205c28d --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go @@ -0,0 +1,38 @@ +// Copyright (c) 2015-2016 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when the code is running on Google App Engine, compiled by GopherJS, or +// "-tags safe" is added to the go build command line. The "disableunsafe" +// tag is deprecated and thus should not be used. +// +build js appengine safe disableunsafe !go1.4 + +package spew + +import "reflect" + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = true +) + +// unsafeReflectValue typically converts the passed reflect.Value into a one +// that bypasses the typical safety restrictions preventing access to +// unaddressable and unexported data. However, doing this relies on access to +// the unsafe package. This is a stub version which simply returns the passed +// reflect.Value when the unsafe package is not available. +func unsafeReflectValue(v reflect.Value) reflect.Value { + return v +} diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go new file mode 100644 index 0000000..1be8ce9 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "reflect" + "sort" + "strconv" +) + +// Some constants in the form of bytes to avoid string overhead. This mirrors +// the technique used in the fmt package. +var ( + panicBytes = []byte("(PANIC=") + plusBytes = []byte("+") + iBytes = []byte("i") + trueBytes = []byte("true") + falseBytes = []byte("false") + interfaceBytes = []byte("(interface {})") + commaNewlineBytes = []byte(",\n") + newlineBytes = []byte("\n") + openBraceBytes = []byte("{") + openBraceNewlineBytes = []byte("{\n") + closeBraceBytes = []byte("}") + asteriskBytes = []byte("*") + colonBytes = []byte(":") + colonSpaceBytes = []byte(": ") + openParenBytes = []byte("(") + closeParenBytes = []byte(")") + spaceBytes = []byte(" ") + pointerChainBytes = []byte("->") + nilAngleBytes = []byte("") + maxNewlineBytes = []byte("\n") + maxShortBytes = []byte("") + circularBytes = []byte("") + circularShortBytes = []byte("") + invalidAngleBytes = []byte("") + openBracketBytes = []byte("[") + closeBracketBytes = []byte("]") + percentBytes = []byte("%") + precisionBytes = []byte(".") + openAngleBytes = []byte("<") + closeAngleBytes = []byte(">") + openMapBytes = []byte("map[") + closeMapBytes = []byte("]") + lenEqualsBytes = []byte("len=") + capEqualsBytes = []byte("cap=") +) + +// hexDigits is used to map a decimal value to a hex digit. +var hexDigits = "0123456789abcdef" + +// catchPanic handles any panics that might occur during the handleMethods +// calls. +func catchPanic(w io.Writer, v reflect.Value) { + if err := recover(); err != nil { + w.Write(panicBytes) + fmt.Fprintf(w, "%v", err) + w.Write(closeParenBytes) + } +} + +// handleMethods attempts to call the Error and String methods on the underlying +// type the passed reflect.Value represents and outputes the result to Writer w. +// +// It handles panics in any called methods by catching and displaying the error +// as the formatted value. +func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { + // We need an interface to check if the type implements the error or + // Stringer interface. However, the reflect package won't give us an + // interface on certain things like unexported struct fields in order + // to enforce visibility rules. We use unsafe, when it's available, + // to bypass these restrictions since this package does not mutate the + // values. + if !v.CanInterface() { + if UnsafeDisabled { + return false + } + + v = unsafeReflectValue(v) + } + + // Choose whether or not to do error and Stringer interface lookups against + // the base type or a pointer to the base type depending on settings. + // Technically calling one of these methods with a pointer receiver can + // mutate the value, however, types which choose to satisify an error or + // Stringer interface with a pointer receiver should not be mutating their + // state inside these interface methods. + if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { + v = unsafeReflectValue(v) + } + if v.CanAddr() { + v = v.Addr() + } + + // Is it an error or Stringer? + switch iface := v.Interface().(type) { + case error: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.Error())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + + w.Write([]byte(iface.Error())) + return true + + case fmt.Stringer: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.String())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + w.Write([]byte(iface.String())) + return true + } + return false +} + +// printBool outputs a boolean value as true or false to Writer w. +func printBool(w io.Writer, val bool) { + if val { + w.Write(trueBytes) + } else { + w.Write(falseBytes) + } +} + +// printInt outputs a signed integer value to Writer w. +func printInt(w io.Writer, val int64, base int) { + w.Write([]byte(strconv.FormatInt(val, base))) +} + +// printUint outputs an unsigned integer value to Writer w. +func printUint(w io.Writer, val uint64, base int) { + w.Write([]byte(strconv.FormatUint(val, base))) +} + +// printFloat outputs a floating point value using the specified precision, +// which is expected to be 32 or 64bit, to Writer w. +func printFloat(w io.Writer, val float64, precision int) { + w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) +} + +// printComplex outputs a complex value using the specified float precision +// for the real and imaginary parts to Writer w. +func printComplex(w io.Writer, c complex128, floatPrecision int) { + r := real(c) + w.Write(openParenBytes) + w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) + i := imag(c) + if i >= 0 { + w.Write(plusBytes) + } + w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) + w.Write(iBytes) + w.Write(closeParenBytes) +} + +// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' +// prefix to Writer w. +func printHexPtr(w io.Writer, p uintptr) { + // Null pointer. + num := uint64(p) + if num == 0 { + w.Write(nilAngleBytes) + return + } + + // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix + buf := make([]byte, 18) + + // It's simpler to construct the hex string right to left. + base := uint64(16) + i := len(buf) - 1 + for num >= base { + buf[i] = hexDigits[num%base] + num /= base + i-- + } + buf[i] = hexDigits[num] + + // Add '0x' prefix. + i-- + buf[i] = 'x' + i-- + buf[i] = '0' + + // Strip unused leading bytes. + buf = buf[i:] + w.Write(buf) +} + +// valuesSorter implements sort.Interface to allow a slice of reflect.Value +// elements to be sorted. +type valuesSorter struct { + values []reflect.Value + strings []string // either nil or same len and values + cs *ConfigState +} + +// newValuesSorter initializes a valuesSorter instance, which holds a set of +// surrogate keys on which the data should be sorted. It uses flags in +// ConfigState to decide if and how to populate those surrogate keys. +func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { + vs := &valuesSorter{values: values, cs: cs} + if canSortSimply(vs.values[0].Kind()) { + return vs + } + if !cs.DisableMethods { + vs.strings = make([]string, len(values)) + for i := range vs.values { + b := bytes.Buffer{} + if !handleMethods(cs, &b, vs.values[i]) { + vs.strings = nil + break + } + vs.strings[i] = b.String() + } + } + if vs.strings == nil && cs.SpewKeys { + vs.strings = make([]string, len(values)) + for i := range vs.values { + vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) + } + } + return vs +} + +// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted +// directly, or whether it should be considered for sorting by surrogate keys +// (if the ConfigState allows it). +func canSortSimply(kind reflect.Kind) bool { + // This switch parallels valueSortLess, except for the default case. + switch kind { + case reflect.Bool: + return true + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return true + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.String: + return true + case reflect.Uintptr: + return true + case reflect.Array: + return true + } + return false +} + +// Len returns the number of values in the slice. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Len() int { + return len(s.values) +} + +// Swap swaps the values at the passed indices. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Swap(i, j int) { + s.values[i], s.values[j] = s.values[j], s.values[i] + if s.strings != nil { + s.strings[i], s.strings[j] = s.strings[j], s.strings[i] + } +} + +// valueSortLess returns whether the first value should sort before the second +// value. It is used by valueSorter.Less as part of the sort.Interface +// implementation. +func valueSortLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Bool: + return !a.Bool() && b.Bool() + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return a.Int() < b.Int() + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return a.Uint() < b.Uint() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.String: + return a.String() < b.String() + case reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Array: + // Compare the contents of both arrays. + l := a.Len() + for i := 0; i < l; i++ { + av := a.Index(i) + bv := b.Index(i) + if av.Interface() == bv.Interface() { + continue + } + return valueSortLess(av, bv) + } + } + return a.String() < b.String() +} + +// Less returns whether the value at index i should sort before the +// value at index j. It is part of the sort.Interface implementation. +func (s *valuesSorter) Less(i, j int) bool { + if s.strings == nil { + return valueSortLess(s.values[i], s.values[j]) + } + return s.strings[i] < s.strings[j] +} + +// sortValues is a sort function that handles both native types and any type that +// can be converted to error or Stringer. Other inputs are sorted according to +// their Value.String() value to ensure display stability. +func sortValues(values []reflect.Value, cs *ConfigState) { + if len(values) == 0 { + return + } + sort.Sort(newValuesSorter(values, cs)) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/config.go b/vendor/github.com/davecgh/go-spew/spew/config.go new file mode 100644 index 0000000..2e3d22f --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/config.go @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "os" +) + +// ConfigState houses the configuration options used by spew to format and +// display values. There is a global instance, Config, that is used to control +// all top-level Formatter and Dump functionality. Each ConfigState instance +// provides methods equivalent to the top-level functions. +// +// The zero value for ConfigState provides no indentation. You would typically +// want to set it to a space or a tab. +// +// Alternatively, you can use NewDefaultConfig to get a ConfigState instance +// with default settings. See the documentation of NewDefaultConfig for default +// values. +type ConfigState struct { + // Indent specifies the string to use for each indentation level. The + // global config instance that all top-level functions use set this to a + // single space by default. If you would like more indentation, you might + // set this to a tab with "\t" or perhaps two spaces with " ". + Indent string + + // MaxDepth controls the maximum number of levels to descend into nested + // data structures. The default, 0, means there is no limit. + // + // NOTE: Circular data structures are properly detected, so it is not + // necessary to set this value unless you specifically want to limit deeply + // nested data structures. + MaxDepth int + + // DisableMethods specifies whether or not error and Stringer interfaces are + // invoked for types that implement them. + DisableMethods bool + + // DisablePointerMethods specifies whether or not to check for and invoke + // error and Stringer interfaces on types which only accept a pointer + // receiver when the current type is not a pointer. + // + // NOTE: This might be an unsafe action since calling one of these methods + // with a pointer receiver could technically mutate the value, however, + // in practice, types which choose to satisify an error or Stringer + // interface with a pointer receiver should not be mutating their state + // inside these interface methods. As a result, this option relies on + // access to the unsafe package, so it will not have any effect when + // running in environments without access to the unsafe package such as + // Google App Engine or with the "safe" build tag specified. + DisablePointerMethods bool + + // DisablePointerAddresses specifies whether to disable the printing of + // pointer addresses. This is useful when diffing data structures in tests. + DisablePointerAddresses bool + + // DisableCapacities specifies whether to disable the printing of capacities + // for arrays, slices, maps and channels. This is useful when diffing + // data structures in tests. + DisableCapacities bool + + // ContinueOnMethod specifies whether or not recursion should continue once + // a custom error or Stringer interface is invoked. The default, false, + // means it will print the results of invoking the custom error or Stringer + // interface and return immediately instead of continuing to recurse into + // the internals of the data type. + // + // NOTE: This flag does not have any effect if method invocation is disabled + // via the DisableMethods or DisablePointerMethods options. + ContinueOnMethod bool + + // SortKeys specifies map keys should be sorted before being printed. Use + // this to have a more deterministic, diffable output. Note that only + // native types (bool, int, uint, floats, uintptr and string) and types + // that support the error or Stringer interfaces (if methods are + // enabled) are supported, with other types sorted according to the + // reflect.Value.String() output which guarantees display stability. + SortKeys bool + + // SpewKeys specifies that, as a last resort attempt, map keys should + // be spewed to strings and sorted by those strings. This is only + // considered if SortKeys is true. + SpewKeys bool +} + +// Config is the active configuration of the top-level functions. +// The configuration can be changed by modifying the contents of spew.Config. +var Config = ConfigState{Indent: " "} + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the formatted string as a value that satisfies error. See NewFormatter +// for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, c.convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, c.convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, c.convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a Formatter interface returned by c.NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, c.convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Print(a ...interface{}) (n int, err error) { + return fmt.Print(c.convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, c.convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Println(a ...interface{}) (n int, err error) { + return fmt.Println(c.convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprint(a ...interface{}) string { + return fmt.Sprint(c.convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, c.convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a Formatter interface returned by c.NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintln(a ...interface{}) string { + return fmt.Sprintln(c.convertArgs(a)...) +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +c.Printf, c.Println, or c.Printf. +*/ +func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(c, v) +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) { + fdump(c, w, a...) +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by modifying the public members +of c. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func (c *ConfigState) Dump(a ...interface{}) { + fdump(c, os.Stdout, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func (c *ConfigState) Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(c, &buf, a...) + return buf.String() +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a spew Formatter interface using +// the ConfigState associated with s. +func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = newFormatter(c, arg) + } + return formatters +} + +// NewDefaultConfig returns a ConfigState with the following default settings. +// +// Indent: " " +// MaxDepth: 0 +// DisableMethods: false +// DisablePointerMethods: false +// ContinueOnMethod: false +// SortKeys: false +func NewDefaultConfig() *ConfigState { + return &ConfigState{Indent: " "} +} diff --git a/vendor/github.com/davecgh/go-spew/spew/doc.go b/vendor/github.com/davecgh/go-spew/spew/doc.go new file mode 100644 index 0000000..aacaac6 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/doc.go @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Package spew implements a deep pretty printer for Go data structures to aid in +debugging. + +A quick overview of the additional features spew provides over the built-in +printing facilities for Go data types are as follows: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output (only when using + Dump style) + +There are two different approaches spew allows for dumping Go data structures: + + * Dump style which prints with newlines, customizable indentation, + and additional debug information such as types and all pointer addresses + used to indirect to the final value + * A custom Formatter interface that integrates cleanly with the standard fmt + package and replaces %v, %+v, %#v, and %#+v to provide inline printing + similar to the default %v while providing the additional functionality + outlined above and passing unsupported format verbs such as %x and %q + along to fmt + +Quick Start + +This section demonstrates how to quickly get started with spew. See the +sections below for further details on formatting and configuration options. + +To dump a variable with full newlines, indentation, type, and pointer +information use Dump, Fdump, or Sdump: + spew.Dump(myVar1, myVar2, ...) + spew.Fdump(someWriter, myVar1, myVar2, ...) + str := spew.Sdump(myVar1, myVar2, ...) + +Alternatively, if you would prefer to use format strings with a compacted inline +printing style, use the convenience wrappers Printf, Fprintf, etc with +%v (most compact), %+v (adds pointer addresses), %#v (adds types), or +%#+v (adds types and pointer addresses): + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +Configuration Options + +Configuration of spew is handled by fields in the ConfigState type. For +convenience, all of the top-level functions use a global state available +via the spew.Config global. + +It is also possible to create a ConfigState instance that provides methods +equivalent to the top-level functions. This allows concurrent configuration +options. See the ConfigState documentation for more details. + +The following configuration options are available: + * Indent + String to use for each indentation level for Dump functions. + It is a single space by default. A popular alternative is "\t". + + * MaxDepth + Maximum number of levels to descend into nested data structures. + There is no limit by default. + + * DisableMethods + Disables invocation of error and Stringer interface methods. + Method invocation is enabled by default. + + * DisablePointerMethods + Disables invocation of error and Stringer interface methods on types + which only accept pointer receivers from non-pointer variables. + Pointer method invocation is enabled by default. + + * DisablePointerAddresses + DisablePointerAddresses specifies whether to disable the printing of + pointer addresses. This is useful when diffing data structures in tests. + + * DisableCapacities + DisableCapacities specifies whether to disable the printing of + capacities for arrays, slices, maps and channels. This is useful when + diffing data structures in tests. + + * ContinueOnMethod + Enables recursion into types after invoking error and Stringer interface + methods. Recursion after method invocation is disabled by default. + + * SortKeys + Specifies map keys should be sorted before being printed. Use + this to have a more deterministic, diffable output. Note that + only native types (bool, int, uint, floats, uintptr and string) + and types which implement error or Stringer interfaces are + supported with other types sorted according to the + reflect.Value.String() output which guarantees display + stability. Natural map order is used by default. + + * SpewKeys + Specifies that, as a last resort attempt, map keys should be + spewed to strings and sorted by those strings. This is only + considered if SortKeys is true. + +Dump Usage + +Simply call spew.Dump with a list of variables you want to dump: + + spew.Dump(myVar1, myVar2, ...) + +You may also call spew.Fdump if you would prefer to output to an arbitrary +io.Writer. For example, to dump to standard error: + + spew.Fdump(os.Stderr, myVar1, myVar2, ...) + +A third option is to call spew.Sdump to get the formatted output as a string: + + str := spew.Sdump(myVar1, myVar2, ...) + +Sample Dump Output + +See the Dump example for details on the setup of the types and variables being +shown here. + + (main.Foo) { + unexportedField: (*main.Bar)(0xf84002e210)({ + flag: (main.Flag) flagTwo, + data: (uintptr) + }), + ExportedField: (map[interface {}]interface {}) (len=1) { + (string) (len=3) "one": (bool) true + } + } + +Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C +command as shown. + ([]uint8) (len=32 cap=32) { + 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | + 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| + 00000020 31 32 |12| + } + +Custom Formatter + +Spew provides a custom formatter that implements the fmt.Formatter interface +so that it integrates cleanly with standard fmt package printing functions. The +formatter is useful for inline printing of smaller data types similar to the +standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Custom Formatter Usage + +The simplest way to make use of the spew custom formatter is to call one of the +convenience functions such as spew.Printf, spew.Println, or spew.Printf. The +functions have syntax you are most likely already familiar with: + + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Println(myVar, myVar2) + spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +See the Index for the full list convenience functions. + +Sample Formatter Output + +Double pointer to a uint8: + %v: <**>5 + %+v: <**>(0xf8400420d0->0xf8400420c8)5 + %#v: (**uint8)5 + %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 + +Pointer to circular struct with a uint8 field and a pointer to itself: + %v: <*>{1 <*>} + %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} + %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} + %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} + +See the Printf example for details on the setup of variables being shown +here. + +Errors + +Since it is possible for custom Stringer/error interfaces to panic, spew +detects them and handles them internally by printing the panic information +inline with the output. Since spew is intended to provide deep pretty printing +capabilities on structures, it intentionally does not return any errors. +*/ +package spew diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go new file mode 100644 index 0000000..f78d89f --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "os" + "reflect" + "regexp" + "strconv" + "strings" +) + +var ( + // uint8Type is a reflect.Type representing a uint8. It is used to + // convert cgo types to uint8 slices for hexdumping. + uint8Type = reflect.TypeOf(uint8(0)) + + // cCharRE is a regular expression that matches a cgo char. + // It is used to detect character arrays to hexdump them. + cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) + + // cUnsignedCharRE is a regular expression that matches a cgo unsigned + // char. It is used to detect unsigned character arrays to hexdump + // them. + cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) + + // cUint8tCharRE is a regular expression that matches a cgo uint8_t. + // It is used to detect uint8_t arrays to hexdump them. + cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) +) + +// dumpState contains information about the state of a dump operation. +type dumpState struct { + w io.Writer + depth int + pointers map[uintptr]int + ignoreNextType bool + ignoreNextIndent bool + cs *ConfigState +} + +// indent performs indentation according to the depth level and cs.Indent +// option. +func (d *dumpState) indent() { + if d.ignoreNextIndent { + d.ignoreNextIndent = false + return + } + d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) +} + +// unpackValue returns values inside of non-nil interfaces when possible. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } + return v +} + +// dumpPtr handles formatting of pointers by indirecting them as necessary. +func (d *dumpState) dumpPtr(v reflect.Value) { + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range d.pointers { + if depth >= d.depth { + delete(d.pointers, k) + } + } + + // Keep list of all dereferenced pointers to show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by dereferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := d.pointers[addr]; ok && pd < d.depth { + cycleFound = true + indirects-- + break + } + d.pointers[addr] = d.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type information. + d.w.Write(openParenBytes) + d.w.Write(bytes.Repeat(asteriskBytes, indirects)) + d.w.Write([]byte(ve.Type().String())) + d.w.Write(closeParenBytes) + + // Display pointer information. + if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 { + d.w.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + d.w.Write(pointerChainBytes) + } + printHexPtr(d.w, addr) + } + d.w.Write(closeParenBytes) + } + + // Display dereferenced value. + d.w.Write(openParenBytes) + switch { + case nilFound: + d.w.Write(nilAngleBytes) + + case cycleFound: + d.w.Write(circularBytes) + + default: + d.ignoreNextType = true + d.dump(ve) + } + d.w.Write(closeParenBytes) +} + +// dumpSlice handles formatting of arrays and slices. Byte (uint8 under +// reflection) arrays and slices are dumped in hexdump -C fashion. +func (d *dumpState) dumpSlice(v reflect.Value) { + // Determine whether this type should be hex dumped or not. Also, + // for types which should be hexdumped, try to use the underlying data + // first, then fall back to trying to convert them to a uint8 slice. + var buf []uint8 + doConvert := false + doHexDump := false + numEntries := v.Len() + if numEntries > 0 { + vt := v.Index(0).Type() + vts := vt.String() + switch { + // C types that need to be converted. + case cCharRE.MatchString(vts): + fallthrough + case cUnsignedCharRE.MatchString(vts): + fallthrough + case cUint8tCharRE.MatchString(vts): + doConvert = true + + // Try to use existing uint8 slices and fall back to converting + // and copying if that fails. + case vt.Kind() == reflect.Uint8: + // We need an addressable interface to convert the type + // to a byte slice. However, the reflect package won't + // give us an interface on certain things like + // unexported struct fields in order to enforce + // visibility rules. We use unsafe, when available, to + // bypass these restrictions since this package does not + // mutate the values. + vs := v + if !vs.CanInterface() || !vs.CanAddr() { + vs = unsafeReflectValue(vs) + } + if !UnsafeDisabled { + vs = vs.Slice(0, numEntries) + + // Use the existing uint8 slice if it can be + // type asserted. + iface := vs.Interface() + if slice, ok := iface.([]uint8); ok { + buf = slice + doHexDump = true + break + } + } + + // The underlying data needs to be converted if it can't + // be type asserted to a uint8 slice. + doConvert = true + } + + // Copy and convert the underlying type if needed. + if doConvert && vt.ConvertibleTo(uint8Type) { + // Convert and copy each element into a uint8 byte + // slice. + buf = make([]uint8, numEntries) + for i := 0; i < numEntries; i++ { + vv := v.Index(i) + buf[i] = uint8(vv.Convert(uint8Type).Uint()) + } + doHexDump = true + } + } + + // Hexdump the entire slice as needed. + if doHexDump { + indent := strings.Repeat(d.cs.Indent, d.depth) + str := indent + hex.Dump(buf) + str = strings.Replace(str, "\n", "\n"+indent, -1) + str = strings.TrimRight(str, d.cs.Indent) + d.w.Write([]byte(str)) + return + } + + // Recursively call dump for each item. + for i := 0; i < numEntries; i++ { + d.dump(d.unpackValue(v.Index(i))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } +} + +// dump is the main workhorse for dumping a value. It uses the passed reflect +// value to figure out what kind of object we are dealing with and formats it +// appropriately. It is a recursive function, however circular data structures +// are detected and handled properly. +func (d *dumpState) dump(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + d.w.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + d.indent() + d.dumpPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !d.ignoreNextType { + d.indent() + d.w.Write(openParenBytes) + d.w.Write([]byte(v.Type().String())) + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + d.ignoreNextType = false + + // Display length and capacity if the built-in len and cap functions + // work with the value's kind and the len/cap itself is non-zero. + valueLen, valueCap := 0, 0 + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.Chan: + valueLen, valueCap = v.Len(), v.Cap() + case reflect.Map, reflect.String: + valueLen = v.Len() + } + if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 { + d.w.Write(openParenBytes) + if valueLen != 0 { + d.w.Write(lenEqualsBytes) + printInt(d.w, int64(valueLen), 10) + } + if !d.cs.DisableCapacities && valueCap != 0 { + if valueLen != 0 { + d.w.Write(spaceBytes) + } + d.w.Write(capEqualsBytes) + printInt(d.w, int64(valueCap), 10) + } + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + + // Call Stringer/error interfaces if they exist and the handle methods flag + // is enabled + if !d.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(d.cs, d.w, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(d.w, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(d.w, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(d.w, v.Uint(), 10) + + case reflect.Float32: + printFloat(d.w, v.Float(), 32) + + case reflect.Float64: + printFloat(d.w, v.Float(), 64) + + case reflect.Complex64: + printComplex(d.w, v.Complex(), 32) + + case reflect.Complex128: + printComplex(d.w, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + d.dumpSlice(v) + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.String: + d.w.Write([]byte(strconv.Quote(v.String()))) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + d.w.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + numEntries := v.Len() + keys := v.MapKeys() + if d.cs.SortKeys { + sortValues(keys, d.cs) + } + for i, key := range keys { + d.dump(d.unpackValue(key)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.MapIndex(key))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Struct: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + vt := v.Type() + numFields := v.NumField() + for i := 0; i < numFields; i++ { + d.indent() + vtf := vt.Field(i) + d.w.Write([]byte(vtf.Name)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.Field(i))) + if i < (numFields - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(d.w, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(d.w, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it in case any new + // types are added. + default: + if v.CanInterface() { + fmt.Fprintf(d.w, "%v", v.Interface()) + } else { + fmt.Fprintf(d.w, "%v", v.String()) + } + } +} + +// fdump is a helper function to consolidate the logic from the various public +// methods which take varying writers and config states. +func fdump(cs *ConfigState, w io.Writer, a ...interface{}) { + for _, arg := range a { + if arg == nil { + w.Write(interfaceBytes) + w.Write(spaceBytes) + w.Write(nilAngleBytes) + w.Write(newlineBytes) + continue + } + + d := dumpState{w: w, cs: cs} + d.pointers = make(map[uintptr]int) + d.dump(reflect.ValueOf(arg)) + d.w.Write(newlineBytes) + } +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func Fdump(w io.Writer, a ...interface{}) { + fdump(&Config, w, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(&Config, &buf, a...) + return buf.String() +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by an exported package global, +spew.Config. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func Dump(a ...interface{}) { + fdump(&Config, os.Stdout, a...) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go new file mode 100644 index 0000000..b04edb7 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" +) + +// supportedFlags is a list of all the character flags supported by fmt package. +const supportedFlags = "0-+# " + +// formatState implements the fmt.Formatter interface and contains information +// about the state of a formatting operation. The NewFormatter function can +// be used to get a new Formatter which can be used directly as arguments +// in standard fmt package printing calls. +type formatState struct { + value interface{} + fs fmt.State + depth int + pointers map[uintptr]int + ignoreNextType bool + cs *ConfigState +} + +// buildDefaultFormat recreates the original format string without precision +// and width information to pass in to fmt.Sprintf in the case of an +// unrecognized type. Unless new types are added to the language, this +// function won't ever be called. +func (f *formatState) buildDefaultFormat() (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + buf.WriteRune('v') + + format = buf.String() + return format +} + +// constructOrigFormat recreates the original format string including precision +// and width information to pass along to the standard fmt package. This allows +// automatic deferral of all format strings this package doesn't support. +func (f *formatState) constructOrigFormat(verb rune) (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + if width, ok := f.fs.Width(); ok { + buf.WriteString(strconv.Itoa(width)) + } + + if precision, ok := f.fs.Precision(); ok { + buf.Write(precisionBytes) + buf.WriteString(strconv.Itoa(precision)) + } + + buf.WriteRune(verb) + + format = buf.String() + return format +} + +// unpackValue returns values inside of non-nil interfaces when possible and +// ensures that types for values which have been unpacked from an interface +// are displayed when the show types flag is also set. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (f *formatState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface { + f.ignoreNextType = false + if !v.IsNil() { + v = v.Elem() + } + } + return v +} + +// formatPtr handles formatting of pointers by indirecting them as necessary. +func (f *formatState) formatPtr(v reflect.Value) { + // Display nil if top level pointer is nil. + showTypes := f.fs.Flag('#') + if v.IsNil() && (!showTypes || f.ignoreNextType) { + f.fs.Write(nilAngleBytes) + return + } + + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range f.pointers { + if depth >= f.depth { + delete(f.pointers, k) + } + } + + // Keep list of all dereferenced pointers to possibly show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by derferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := f.pointers[addr]; ok && pd < f.depth { + cycleFound = true + indirects-- + break + } + f.pointers[addr] = f.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type or indirection level depending on flags. + if showTypes && !f.ignoreNextType { + f.fs.Write(openParenBytes) + f.fs.Write(bytes.Repeat(asteriskBytes, indirects)) + f.fs.Write([]byte(ve.Type().String())) + f.fs.Write(closeParenBytes) + } else { + if nilFound || cycleFound { + indirects += strings.Count(ve.Type().String(), "*") + } + f.fs.Write(openAngleBytes) + f.fs.Write([]byte(strings.Repeat("*", indirects))) + f.fs.Write(closeAngleBytes) + } + + // Display pointer information depending on flags. + if f.fs.Flag('+') && (len(pointerChain) > 0) { + f.fs.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + f.fs.Write(pointerChainBytes) + } + printHexPtr(f.fs, addr) + } + f.fs.Write(closeParenBytes) + } + + // Display dereferenced value. + switch { + case nilFound: + f.fs.Write(nilAngleBytes) + + case cycleFound: + f.fs.Write(circularShortBytes) + + default: + f.ignoreNextType = true + f.format(ve) + } +} + +// format is the main workhorse for providing the Formatter interface. It +// uses the passed reflect value to figure out what kind of object we are +// dealing with and formats it appropriately. It is a recursive function, +// however circular data structures are detected and handled properly. +func (f *formatState) format(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + f.fs.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + f.formatPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !f.ignoreNextType && f.fs.Flag('#') { + f.fs.Write(openParenBytes) + f.fs.Write([]byte(v.Type().String())) + f.fs.Write(closeParenBytes) + } + f.ignoreNextType = false + + // Call Stringer/error interfaces if they exist and the handle methods + // flag is enabled. + if !f.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(f.cs, f.fs, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(f.fs, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(f.fs, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(f.fs, v.Uint(), 10) + + case reflect.Float32: + printFloat(f.fs, v.Float(), 32) + + case reflect.Float64: + printFloat(f.fs, v.Float(), 64) + + case reflect.Complex64: + printComplex(f.fs, v.Complex(), 32) + + case reflect.Complex128: + printComplex(f.fs, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + f.fs.Write(openBracketBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + numEntries := v.Len() + for i := 0; i < numEntries; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(v.Index(i))) + } + } + f.depth-- + f.fs.Write(closeBracketBytes) + + case reflect.String: + f.fs.Write([]byte(v.String())) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + f.fs.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + + f.fs.Write(openMapBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + keys := v.MapKeys() + if f.cs.SortKeys { + sortValues(keys, f.cs) + } + for i, key := range keys { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(key)) + f.fs.Write(colonBytes) + f.ignoreNextType = true + f.format(f.unpackValue(v.MapIndex(key))) + } + } + f.depth-- + f.fs.Write(closeMapBytes) + + case reflect.Struct: + numFields := v.NumField() + f.fs.Write(openBraceBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + vt := v.Type() + for i := 0; i < numFields; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + vtf := vt.Field(i) + if f.fs.Flag('+') || f.fs.Flag('#') { + f.fs.Write([]byte(vtf.Name)) + f.fs.Write(colonBytes) + } + f.format(f.unpackValue(v.Field(i))) + } + } + f.depth-- + f.fs.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(f.fs, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(f.fs, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it if any get added. + default: + format := f.buildDefaultFormat() + if v.CanInterface() { + fmt.Fprintf(f.fs, format, v.Interface()) + } else { + fmt.Fprintf(f.fs, format, v.String()) + } + } +} + +// Format satisfies the fmt.Formatter interface. See NewFormatter for usage +// details. +func (f *formatState) Format(fs fmt.State, verb rune) { + f.fs = fs + + // Use standard formatting for verbs that are not v. + if verb != 'v' { + format := f.constructOrigFormat(verb) + fmt.Fprintf(fs, format, f.value) + return + } + + if f.value == nil { + if fs.Flag('#') { + fs.Write(interfaceBytes) + } + fs.Write(nilAngleBytes) + return + } + + f.format(reflect.ValueOf(f.value)) +} + +// newFormatter is a helper function to consolidate the logic from the various +// public methods which take varying config states. +func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter { + fs := &formatState{value: v, cs: cs} + fs.pointers = make(map[uintptr]int) + return fs +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +Printf, Println, or Fprintf. +*/ +func NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(&Config, v) +} diff --git a/vendor/github.com/davecgh/go-spew/spew/spew.go b/vendor/github.com/davecgh/go-spew/spew/spew.go new file mode 100644 index 0000000..32c0e33 --- /dev/null +++ b/vendor/github.com/davecgh/go-spew/spew/spew.go @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2013-2016 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "fmt" + "io" +) + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the formatted string as a value that satisfies error. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a default Formatter interface returned by NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) +func Print(a ...interface{}) (n int, err error) { + return fmt.Print(convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) +func Println(a ...interface{}) (n int, err error) { + return fmt.Println(convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprint(a ...interface{}) string { + return fmt.Sprint(convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintln(a ...interface{}) string { + return fmt.Sprintln(convertArgs(a)...) +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a default spew Formatter interface. +func convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = NewFormatter(arg) + } + return formatters +} diff --git a/vendor/github.com/fsnotify/fsnotify/.editorconfig b/vendor/github.com/fsnotify/fsnotify/.editorconfig new file mode 100644 index 0000000..fad8958 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.go] +indent_style = tab +indent_size = 4 +insert_final_newline = true + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/github.com/fsnotify/fsnotify/.gitattributes b/vendor/github.com/fsnotify/fsnotify/.gitattributes new file mode 100644 index 0000000..32f1001 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitattributes @@ -0,0 +1 @@ +go.sum linguist-generated diff --git a/vendor/github.com/fsnotify/fsnotify/.gitignore b/vendor/github.com/fsnotify/fsnotify/.gitignore new file mode 100644 index 0000000..4cd0cba --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.gitignore @@ -0,0 +1,6 @@ +# Setup a Global .gitignore for OS and editor generated files: +# https://help.github.com/articles/ignoring-files +# git config --global core.excludesfile ~/.gitignore_global + +.vagrant +*.sublime-project diff --git a/vendor/github.com/fsnotify/fsnotify/.mailmap b/vendor/github.com/fsnotify/fsnotify/.mailmap new file mode 100644 index 0000000..a04f290 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/.mailmap @@ -0,0 +1,2 @@ +Chris Howey +Nathan Youngman <4566+nathany@users.noreply.github.com> diff --git a/vendor/github.com/fsnotify/fsnotify/AUTHORS b/vendor/github.com/fsnotify/fsnotify/AUTHORS new file mode 100644 index 0000000..6cbabe5 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/AUTHORS @@ -0,0 +1,62 @@ +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# You can update this list using the following command: +# +# $ (head -n10 AUTHORS && git shortlog -se | sed -E 's/^\s+[0-9]+\t//') | tee AUTHORS + +# Please keep the list sorted. + +Aaron L +Adrien Bustany +Alexey Kazakov +Amit Krishnan +Anmol Sethi +Bjørn Erik Pedersen +Brian Goff +Bruno Bigras +Caleb Spare +Case Nelson +Chris Howey +Christoffer Buchholz +Daniel Wagner-Hall +Dave Cheney +Eric Lin +Evan Phoenix +Francisco Souza +Gautam Dey +Hari haran +Ichinose Shogo +Johannes Ebke +John C Barstow +Kelvin Fo +Ken-ichirou MATSUZAWA +Matt Layher +Matthias Stone +Nathan Youngman +Nickolai Zeldovich +Oliver Bristow +Patrick +Paul Hammond +Pawel Knap +Pieter Droogendijk +Pratik Shinde +Pursuit92 +Riku Voipio +Rob Figueiredo +Rodrigo Chiossi +Slawek Ligus +Soge Zhang +Tiffany Jernigan +Tilak Sharma +Tobias Klauser +Tom Payne +Travis Cline +Tudor Golubenco +Vahe Khachikyan +Yukang +bronze1man +debrando +henrikedwards +铁哥 diff --git a/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md new file mode 100644 index 0000000..cc01c08 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CHANGELOG.md @@ -0,0 +1,357 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.5.4] - 2022-04-25 + +* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447) +* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444) +* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443) + +## [1.5.3] - 2022-04-22 + +* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445) + +## [1.5.2] - 2022-04-21 + +* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374) +* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361) +* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424) +* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406) +* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416) + +## [1.5.1] - 2021-08-24 + +* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394) + +## [1.5.0] - 2021-08-20 + +* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381) +* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298) +* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289) +* CI: Use GitHub Actions for CI and cover go 1.12-1.17 + [#378](https://github.com/fsnotify/fsnotify/pull/378) + [#381](https://github.com/fsnotify/fsnotify/pull/381) + [#385](https://github.com/fsnotify/fsnotify/pull/385) +* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325) + +## [1.4.7] - 2018-01-09 + +* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine) +* Tests: Fix missing verb on format string (thanks @rchiossi) +* Linux: Fix deadlock in Remove (thanks @aarondl) +* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne) +* Docs: Moved FAQ into the README (thanks @vahe) +* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich) +* Docs: replace references to OS X with macOS + +## [1.4.2] - 2016-10-10 + +* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack) + +## [1.4.1] - 2016-10-04 + +* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack) + +## [1.4.0] - 2016-10-01 + +* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie) + +## [1.3.1] - 2016-06-28 + +* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc) + +## [1.3.0] - 2016-04-19 + +* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135) + +## [1.2.10] - 2016-03-02 + +* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj) + +## [1.2.9] - 2016-01-13 + +kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep) + +## [1.2.8] - 2015-12-17 + +* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test) +* inotify: fix race in test +* enable race detection for continuous integration (Linux, Mac, Windows) + +## [1.2.5] - 2015-10-17 + +* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki) +* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken) +* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie) +* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion) + +## [1.2.1] - 2015-10-14 + +* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx) + +## [1.2.0] - 2015-02-08 + +* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD) +* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD) +* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59) + +## [1.1.1] - 2015-02-05 + +* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD) + +## [1.1.0] - 2014-12-12 + +* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43) + * add low-level functions + * only need to store flags on directories + * less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13) + * done can be an unbuffered channel + * remove calls to os.NewSyscallError +* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher) +* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48) +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## [1.0.4] - 2014-09-07 + +* kqueue: add dragonfly to the build tags. +* Rename source code files, rearrange code so exported APIs are at the top. +* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang) + +## [1.0.3] - 2014-08-19 + +* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36) + +## [1.0.2] - 2014-08-17 + +* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) +* [Fix] Make ./path and path equivalent. (thanks @zhsso) + +## [1.0.0] - 2014-08-15 + +* [API] Remove AddWatch on Windows, use Add. +* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30) +* Minor updates based on feedback from golint. + +## dev / 2014-07-09 + +* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify). +* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) + +## dev / 2014-07-04 + +* kqueue: fix incorrect mutex used in Close() +* Update example to demonstrate usage of Op. + +## dev / 2014-06-28 + +* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4) +* Fix for String() method on Event (thanks Alex Brainman) +* Don't build on Plan 9 or Solaris (thanks @4ad) + +## dev / 2014-06-21 + +* Events channel of type Event rather than *Event. +* [internal] use syscall constants directly for inotify and kqueue. +* [internal] kqueue: rename events to kevents and fileEvent to event. + +## dev / 2014-06-19 + +* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally). +* [internal] remove cookie from Event struct (unused). +* [internal] Event struct has the same definition across every OS. +* [internal] remove internal watch and removeWatch methods. + +## dev / 2014-06-12 + +* [API] Renamed Watch() to Add() and RemoveWatch() to Remove(). +* [API] Pluralized channel names: Events and Errors. +* [API] Renamed FileEvent struct to Event. +* [API] Op constants replace methods like IsCreate(). + +## dev / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## dev / 2014-05-23 + +* [API] Remove current implementation of WatchFlags. + * current implementation doesn't take advantage of OS for efficiency + * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes + * no tests for the current implementation + * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) + +## [0.9.3] - 2014-12-31 + +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## [0.9.2] - 2014-08-17 + +* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) + +## [0.9.1] - 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## [0.9.0] - 2014-01-17 + +* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany) +* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare) +* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library. + +## [0.8.12] - 2013-11-13 + +* [API] Remove FD_SET and friends from Linux adapter + +## [0.8.11] - 2013-11-02 + +* [Doc] Add Changelog [#72][] (thanks @nathany) +* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond) + +## [0.8.10] - 2013-10-19 + +* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott) +* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer) +* [Doc] specify OS-specific limits in README (thanks @debrando) + +## [0.8.9] - 2013-09-08 + +* [Doc] Contributing (thanks @nathany) +* [Doc] update package path in example code [#63][] (thanks @paulhammond) +* [Doc] GoCI badge in README (Linux only) [#60][] +* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany) + +## [0.8.8] - 2013-06-17 + +* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie) + +## [0.8.7] - 2013-06-03 + +* [API] Make syscall flags internal +* [Fix] inotify: ignore event changes +* [Fix] race in symlink test [#45][] (reported by @srid) +* [Fix] tests on Windows +* lower case error messages + +## [0.8.6] - 2013-05-23 + +* kqueue: Use EVT_ONLY flag on Darwin +* [Doc] Update README with full example + +## [0.8.5] - 2013-05-09 + +* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg) + +## [0.8.4] - 2013-04-07 + +* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz) + +## [0.8.3] - 2013-03-13 + +* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin) +* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin) + +## [0.8.2] - 2013-02-07 + +* [Doc] add Authors +* [Fix] fix data races for map access [#29][] (thanks @fsouza) + +## [0.8.1] - 2013-01-09 + +* [Fix] Windows path separators +* [Doc] BSD License + +## [0.8.0] - 2012-11-09 + +* kqueue: directory watching improvements (thanks @vmirage) +* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto) +* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr) + +## [0.7.4] - 2012-10-09 + +* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji) +* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig) +* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig) +* [Fix] kqueue: modify after recreation of file + +## [0.7.3] - 2012-09-27 + +* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage) +* [Fix] kqueue: no longer get duplicate CREATE events + +## [0.7.2] - 2012-09-01 + +* kqueue: events for created directories + +## [0.7.1] - 2012-07-14 + +* [Fix] for renaming files + +## [0.7.0] - 2012-07-02 + +* [Feature] FSNotify flags +* [Fix] inotify: Added file name back to event path + +## [0.6.0] - 2012-06-06 + +* kqueue: watch files after directory created (thanks @tmc) + +## [0.5.1] - 2012-05-22 + +* [Fix] inotify: remove all watches before Close() + +## [0.5.0] - 2012-05-03 + +* [API] kqueue: return errors during watch instead of sending over channel +* kqueue: match symlink behavior on Linux +* inotify: add `DELETE_SELF` (requested by @taralx) +* [Fix] kqueue: handle EINTR (reported by @robfig) +* [Doc] Godoc example [#1][] (thanks @davecheney) + +## [0.4.0] - 2012-03-30 + +* Go 1 released: build with go tool +* [Feature] Windows support using winfsnotify +* Windows does not have attribute change notifications +* Roll attribute notifications into IsModify + +## [0.3.0] - 2012-02-19 + +* kqueue: add files when watch directory + +## [0.2.0] - 2011-12-30 + +* update to latest Go weekly code + +## [0.1.0] - 2011-10-19 + +* kqueue: add watch on file creation to match inotify +* kqueue: create file event +* inotify: ignore `IN_IGNORED` events +* event String() +* linux: common FileEvent functions +* initial commit + +[#79]: https://github.com/howeyc/fsnotify/pull/79 +[#77]: https://github.com/howeyc/fsnotify/pull/77 +[#72]: https://github.com/howeyc/fsnotify/issues/72 +[#71]: https://github.com/howeyc/fsnotify/issues/71 +[#70]: https://github.com/howeyc/fsnotify/issues/70 +[#63]: https://github.com/howeyc/fsnotify/issues/63 +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#60]: https://github.com/howeyc/fsnotify/issues/60 +[#59]: https://github.com/howeyc/fsnotify/issues/59 +[#49]: https://github.com/howeyc/fsnotify/issues/49 +[#45]: https://github.com/howeyc/fsnotify/issues/45 +[#40]: https://github.com/howeyc/fsnotify/issues/40 +[#36]: https://github.com/howeyc/fsnotify/issues/36 +[#33]: https://github.com/howeyc/fsnotify/issues/33 +[#29]: https://github.com/howeyc/fsnotify/issues/29 +[#25]: https://github.com/howeyc/fsnotify/issues/25 +[#24]: https://github.com/howeyc/fsnotify/issues/24 +[#21]: https://github.com/howeyc/fsnotify/issues/21 diff --git a/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md new file mode 100644 index 0000000..8a64256 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# Contributing + +## Issues + +* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues). +* Please indicate the platform you are using fsnotify on. +* A code example to reproduce the problem is appreciated. + +## Pull Requests + +### Contributor License Agreement + +fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). + +Please indicate that you have signed the CLA in your pull request. + +### How fsnotify is Developed + +* Development is done on feature branches. +* Tests are run on BSD, Linux, macOS and Windows. +* Pull requests are reviewed and [applied to master][am] using [hub][]. + * Maintainers may modify or squash commits rather than asking contributors to. +* To issue a new release, the maintainers will: + * Update the CHANGELOG + * Tag a version, which will become available through gopkg.in. + +### How to Fork + +For smooth sailing, always use the original import path. Installing with `go get` makes this easy. + +1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Ensure everything works and the tests pass (see below) +4. Commit your changes (`git commit -am 'Add some feature'`) + +Contribute upstream: + +1. Fork fsnotify on GitHub +2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) +3. Push to the branch (`git push fork my-new-feature`) +4. Create a new Pull Request on GitHub + +This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/). + +### Testing + +fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows. + +Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. + +### Maintainers + +Help maintaining fsnotify is welcome. To be a maintainer: + +* Submit a pull request and sign the CLA as above. +* You must be able to run the test suite on Mac, Windows, Linux and BSD. + +All code changes should be internal pull requests. + +Releases are tagged using [Semantic Versioning](http://semver.org/). diff --git a/vendor/github.com/fsnotify/fsnotify/LICENSE b/vendor/github.com/fsnotify/fsnotify/LICENSE new file mode 100644 index 0000000..e180c8f --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2012-2019 fsnotify Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/fsnotify/fsnotify/README.md b/vendor/github.com/fsnotify/fsnotify/README.md new file mode 100644 index 0000000..0731c5e --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/README.md @@ -0,0 +1,120 @@ +# File system notifications for Go + +[![Go Reference](https://pkg.go.dev/badge/github.com/fsnotify/fsnotify.svg)](https://pkg.go.dev/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [![Maintainers Wanted](https://img.shields.io/badge/maintainers-wanted-red.svg)](https://github.com/fsnotify/fsnotify/issues/413) + +fsnotify utilizes [`golang.org/x/sys`](https://pkg.go.dev/golang.org/x/sys) rather than [`syscall`](https://pkg.go.dev/syscall) from the standard library. + +Cross platform: Windows, Linux, BSD and macOS. + +| Adapter | OS | Status | +| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| inotify | Linux 2.6.27 or later, Android\* | Supported | +| kqueue | BSD, macOS, iOS\* | Supported | +| ReadDirectoryChangesW | Windows | Supported | +| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | +| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) | +| fanotify | Linux 2.6.37+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) | +| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | +| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | + +\* Android and iOS are untested. + +Please see [the documentation](https://pkg.go.dev/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information. + +## API stability + +fsnotify is a fork of [howeyc/fsnotify](https://github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA). + +All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). + +## Usage + +```go +package main + +import ( + "log" + + "github.com/fsnotify/fsnotify" +) + +func main() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + log.Println("event:", event) + if event.Op&fsnotify.Write == fsnotify.Write { + log.Println("modified file:", event.Name) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("error:", err) + } + } + }() + + err = watcher.Add("/tmp/foo") + if err != nil { + log.Fatal(err) + } + <-done +} +``` + +## Contributing + +Please refer to [CONTRIBUTING][] before opening an issue or pull request. + +## FAQ + +**When a file is moved to another directory is it still being watched?** + +No (it shouldn't be, unless you are watching where it was moved to). + +**When I watch a directory, are all subdirectories watched as well?** + +No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]). + +**Do I have to watch the Error and Event channels in a separate goroutine?** + +As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7]) + +**Why am I receiving multiple events for the same file on OS X?** + +Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]). + +**How many files can be watched at once?** + +There are OS-specific limits as to how many watches can be created: +* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error. +* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error. + +**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?** + +fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications. + +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#18]: https://github.com/fsnotify/fsnotify/issues/18 +[#11]: https://github.com/fsnotify/fsnotify/issues/11 +[#7]: https://github.com/howeyc/fsnotify/issues/7 + +[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md + +## Related Projects + +* [notify](https://github.com/rjeczalik/notify) +* [fsevents](https://github.com/fsnotify/fsevents) + diff --git a/vendor/github.com/fsnotify/fsnotify/fen.go b/vendor/github.com/fsnotify/fsnotify/fen.go new file mode 100644 index 0000000..b3ac3d8 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fen.go @@ -0,0 +1,38 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build solaris +// +build solaris + +package fsnotify + +import ( + "errors" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + return nil, errors.New("FEN based watcher not yet supported for fsnotify\n") +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + return nil +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + return nil +} diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify.go b/vendor/github.com/fsnotify/fsnotify/fsnotify.go new file mode 100644 index 0000000..0f4ee52 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fsnotify.go @@ -0,0 +1,69 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 +// +build !plan9 + +// Package fsnotify provides a platform-independent interface for file system notifications. +package fsnotify + +import ( + "bytes" + "errors" + "fmt" +) + +// Event represents a single file system notification. +type Event struct { + Name string // Relative path to the file or directory. + Op Op // File operation that triggered the event. +} + +// Op describes a set of file operations. +type Op uint32 + +// These are the generalized file operations that can trigger a notification. +const ( + Create Op = 1 << iota + Write + Remove + Rename + Chmod +) + +func (op Op) String() string { + // Use a buffer for efficient string concatenation + var buffer bytes.Buffer + + if op&Create == Create { + buffer.WriteString("|CREATE") + } + if op&Remove == Remove { + buffer.WriteString("|REMOVE") + } + if op&Write == Write { + buffer.WriteString("|WRITE") + } + if op&Rename == Rename { + buffer.WriteString("|RENAME") + } + if op&Chmod == Chmod { + buffer.WriteString("|CHMOD") + } + if buffer.Len() == 0 { + return "" + } + return buffer.String()[1:] // Strip leading pipe +} + +// String returns a string representation of the event in the form +// "file: REMOVE|WRITE|..." +func (e Event) String() string { + return fmt.Sprintf("%q: %s", e.Name, e.Op.String()) +} + +// Common errors that can be reported by a watcher +var ( + ErrEventOverflow = errors.New("fsnotify queue overflow") +) diff --git a/vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go b/vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go new file mode 100644 index 0000000..5968855 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go @@ -0,0 +1,36 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows +// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows + +package fsnotify + +import ( + "fmt" + "runtime" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct{} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS) +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + return nil +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + return nil +} diff --git a/vendor/github.com/fsnotify/fsnotify/inotify.go b/vendor/github.com/fsnotify/fsnotify/inotify.go new file mode 100644 index 0000000..a6d0e0e --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/inotify.go @@ -0,0 +1,351 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux +// +build linux + +package fsnotify + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + "unsafe" + + "golang.org/x/sys/unix" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + mu sync.Mutex // Map access + fd int + poller *fdPoller + watches map[string]*watch // Map of inotify watches (key: path) + paths map[int]string // Map of watched paths (key: watch descriptor) + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + doneResp chan struct{} // Channel to respond to Close +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + // Create inotify fd + fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) + if fd == -1 { + return nil, errno + } + // Create epoll + poller, err := newFdPoller(fd) + if err != nil { + unix.Close(fd) + return nil, err + } + w := &Watcher{ + fd: fd, + poller: poller, + watches: make(map[string]*watch), + paths: make(map[int]string), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + doneResp: make(chan struct{}), + } + + go w.readEvents() + return w, nil +} + +func (w *Watcher) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + if w.isClosed() { + return nil + } + + // Send 'close' signal to goroutine, and set the Watcher to closed. + close(w.done) + + // Wake up goroutine + w.poller.wake() + + // Wait for goroutine to close + <-w.doneResp + + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + name = filepath.Clean(name) + if w.isClosed() { + return errors.New("inotify instance already closed") + } + + const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM | + unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY | + unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF + + var flags uint32 = agnosticEvents + + w.mu.Lock() + defer w.mu.Unlock() + watchEntry := w.watches[name] + if watchEntry != nil { + flags |= watchEntry.flags | unix.IN_MASK_ADD + } + wd, errno := unix.InotifyAddWatch(w.fd, name, flags) + if wd == -1 { + return errno + } + + if watchEntry == nil { + w.watches[name] = &watch{wd: uint32(wd), flags: flags} + w.paths[wd] = name + } else { + watchEntry.wd = uint32(wd) + watchEntry.flags = flags + } + + return nil +} + +// Remove stops watching the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + name = filepath.Clean(name) + + // Fetch the watch. + w.mu.Lock() + defer w.mu.Unlock() + watch, ok := w.watches[name] + + // Remove it from inotify. + if !ok { + return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) + } + + // We successfully removed the watch if InotifyRmWatch doesn't return an + // error, we need to clean up our internal state to ensure it matches + // inotify's kernel state. + delete(w.paths, int(watch.wd)) + delete(w.watches, name) + + // inotify_rm_watch will return EINVAL if the file has been deleted; + // the inotify will already have been removed. + // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously + // by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE + // so that EINVAL means that the wd is being rm_watch()ed or its file removed + // by another thread and we have not received IN_IGNORE event. + success, errno := unix.InotifyRmWatch(w.fd, watch.wd) + if success == -1 { + // TODO: Perhaps it's not helpful to return an error here in every case. + // the only two possible errors are: + // EBADF, which happens when w.fd is not a valid file descriptor of any kind. + // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. + // Watch descriptors are invalidated when they are removed explicitly or implicitly; + // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. + return errno + } + + return nil +} + +// WatchList returns the directories and files that are being monitered. +func (w *Watcher) WatchList() []string { + w.mu.Lock() + defer w.mu.Unlock() + + entries := make([]string, 0, len(w.watches)) + for pathname := range w.watches { + entries = append(entries, pathname) + } + + return entries +} + +type watch struct { + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) +} + +// readEvents reads from the inotify file descriptor, converts the +// received events into Event objects and sends them via the Events channel +func (w *Watcher) readEvents() { + var ( + buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events + n int // Number of bytes read with read() + errno error // Syscall errno + ok bool // For poller.wait + ) + + defer close(w.doneResp) + defer close(w.Errors) + defer close(w.Events) + defer unix.Close(w.fd) + defer w.poller.close() + + for { + // See if we have been closed. + if w.isClosed() { + return + } + + ok, errno = w.poller.wait() + if errno != nil { + select { + case w.Errors <- errno: + case <-w.done: + return + } + continue + } + + if !ok { + continue + } + + n, errno = unix.Read(w.fd, buf[:]) + // If a signal interrupted execution, see if we've been asked to close, and try again. + // http://man7.org/linux/man-pages/man7/signal.7.html : + // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" + if errno == unix.EINTR { + continue + } + + // unix.Read might have been woken up by Close. If so, we're done. + if w.isClosed() { + return + } + + if n < unix.SizeofInotifyEvent { + var err error + if n == 0 { + // If EOF is received. This should really never happen. + err = io.EOF + } else if n < 0 { + // If an error occurred while reading. + err = errno + } else { + // Read was too short. + err = errors.New("notify: short read in readEvents()") + } + select { + case w.Errors <- err: + case <-w.done: + return + } + continue + } + + var offset uint32 + // We don't know how many events we just read into the buffer + // While the offset points to at least one whole event... + for offset <= uint32(n-unix.SizeofInotifyEvent) { + // Point "raw" to the event in the buffer + raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) + + mask := uint32(raw.Mask) + nameLen := uint32(raw.Len) + + if mask&unix.IN_Q_OVERFLOW != 0 { + select { + case w.Errors <- ErrEventOverflow: + case <-w.done: + return + } + } + + // If the event happened to the watched directory or the watched file, the kernel + // doesn't append the filename to the event, but we would like to always fill the + // the "Name" field with a valid filename. We retrieve the path of the watch from + // the "paths" map. + w.mu.Lock() + name, ok := w.paths[int(raw.Wd)] + // IN_DELETE_SELF occurs when the file/directory being watched is removed. + // This is a sign to clean up the maps, otherwise we are no longer in sync + // with the inotify kernel state which has already deleted the watch + // automatically. + if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { + delete(w.paths, int(raw.Wd)) + delete(w.watches, name) + } + w.mu.Unlock() + + if nameLen > 0 { + // Point "bytes" at the first byte of the filename + bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen] + // The filename is padded with NULL bytes. TrimRight() gets rid of those. + name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") + } + + event := newEvent(name, mask) + + // Send the events that are not ignored on the events channel + if !event.ignoreLinux(mask) { + select { + case w.Events <- event: + case <-w.done: + return + } + } + + // Move to the next event in the buffer + offset += unix.SizeofInotifyEvent + nameLen + } + } +} + +// Certain types of events can be "ignored" and not sent over the Events +// channel. Such as events marked ignore by the kernel, or MODIFY events +// against files that do not exist. +func (e *Event) ignoreLinux(mask uint32) bool { + // Ignore anything the inotify API says to ignore + if mask&unix.IN_IGNORED == unix.IN_IGNORED { + return true + } + + // If the event is not a DELETE or RENAME, the file must exist. + // Otherwise the event is ignored. + // *Note*: this was put in place because it was seen that a MODIFY + // event was sent after the DELETE. This ignores that MODIFY and + // assumes a DELETE will come or has come if the file doesn't exist. + if !(e.Op&Remove == Remove || e.Op&Rename == Rename) { + _, statErr := os.Lstat(e.Name) + return os.IsNotExist(statErr) + } + return false +} + +// newEvent returns an platform-independent Event based on an inotify mask. +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { + e.Op |= Create + } + if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { + e.Op |= Remove + } + if mask&unix.IN_MODIFY == unix.IN_MODIFY { + e.Op |= Write + } + if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { + e.Op |= Rename + } + if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { + e.Op |= Chmod + } + return e +} diff --git a/vendor/github.com/fsnotify/fsnotify/inotify_poller.go b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go new file mode 100644 index 0000000..b572a37 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/inotify_poller.go @@ -0,0 +1,187 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux +// +build linux + +package fsnotify + +import ( + "errors" + + "golang.org/x/sys/unix" +) + +type fdPoller struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + epfd int // Epoll file descriptor + pipe [2]int // Pipe for waking up +} + +func emptyPoller(fd int) *fdPoller { + poller := new(fdPoller) + poller.fd = fd + poller.epfd = -1 + poller.pipe[0] = -1 + poller.pipe[1] = -1 + return poller +} + +// Create a new inotify poller. +// This creates an inotify handler, and an epoll handler. +func newFdPoller(fd int) (*fdPoller, error) { + var errno error + poller := emptyPoller(fd) + defer func() { + if errno != nil { + poller.close() + } + }() + + // Create epoll fd + poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC) + if poller.epfd == -1 { + return nil, errno + } + // Create pipe; pipe[0] is the read end, pipe[1] the write end. + errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC) + if errno != nil { + return nil, errno + } + + // Register inotify fd with epoll + event := unix.EpollEvent{ + Fd: int32(poller.fd), + Events: unix.EPOLLIN, + } + errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event) + if errno != nil { + return nil, errno + } + + // Register pipe fd with epoll + event = unix.EpollEvent{ + Fd: int32(poller.pipe[0]), + Events: unix.EPOLLIN, + } + errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event) + if errno != nil { + return nil, errno + } + + return poller, nil +} + +// Wait using epoll. +// Returns true if something is ready to be read, +// false if there is not. +func (poller *fdPoller) wait() (bool, error) { + // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. + // I don't know whether epoll_wait returns the number of events returned, + // or the total number of events ready. + // I decided to catch both by making the buffer one larger than the maximum. + events := make([]unix.EpollEvent, 7) + for { + n, errno := unix.EpollWait(poller.epfd, events, -1) + if n == -1 { + if errno == unix.EINTR { + continue + } + return false, errno + } + if n == 0 { + // If there are no events, try again. + continue + } + if n > 6 { + // This should never happen. More events were returned than should be possible. + return false, errors.New("epoll_wait returned more events than I know what to do with") + } + ready := events[:n] + epollhup := false + epollerr := false + epollin := false + for _, event := range ready { + if event.Fd == int32(poller.fd) { + if event.Events&unix.EPOLLHUP != 0 { + // This should not happen, but if it does, treat it as a wakeup. + epollhup = true + } + if event.Events&unix.EPOLLERR != 0 { + // If an error is waiting on the file descriptor, we should pretend + // something is ready to read, and let unix.Read pick up the error. + epollerr = true + } + if event.Events&unix.EPOLLIN != 0 { + // There is data to read. + epollin = true + } + } + if event.Fd == int32(poller.pipe[0]) { + if event.Events&unix.EPOLLHUP != 0 { + // Write pipe descriptor was closed, by us. This means we're closing down the + // watcher, and we should wake up. + } + if event.Events&unix.EPOLLERR != 0 { + // If an error is waiting on the pipe file descriptor. + // This is an absolute mystery, and should never ever happen. + return false, errors.New("Error on the pipe descriptor.") + } + if event.Events&unix.EPOLLIN != 0 { + // This is a regular wakeup, so we have to clear the buffer. + err := poller.clearWake() + if err != nil { + return false, err + } + } + } + } + + if epollhup || epollerr || epollin { + return true, nil + } + return false, nil + } +} + +// Close the write end of the poller. +func (poller *fdPoller) wake() error { + buf := make([]byte, 1) + n, errno := unix.Write(poller.pipe[1], buf) + if n == -1 { + if errno == unix.EAGAIN { + // Buffer is full, poller will wake. + return nil + } + return errno + } + return nil +} + +func (poller *fdPoller) clearWake() error { + // You have to be woken up a LOT in order to get to 100! + buf := make([]byte, 100) + n, errno := unix.Read(poller.pipe[0], buf) + if n == -1 { + if errno == unix.EAGAIN { + // Buffer is empty, someone else cleared our wake. + return nil + } + return errno + } + return nil +} + +// Close all poller file descriptors, but not the one passed to it. +func (poller *fdPoller) close() { + if poller.pipe[1] != -1 { + unix.Close(poller.pipe[1]) + } + if poller.pipe[0] != -1 { + unix.Close(poller.pipe[0]) + } + if poller.epfd != -1 { + unix.Close(poller.epfd) + } +} diff --git a/vendor/github.com/fsnotify/fsnotify/kqueue.go b/vendor/github.com/fsnotify/fsnotify/kqueue.go new file mode 100644 index 0000000..6fb8d85 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/kqueue.go @@ -0,0 +1,535 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build freebsd || openbsd || netbsd || dragonfly || darwin +// +build freebsd openbsd netbsd dragonfly darwin + +package fsnotify + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sync" + "time" + + "golang.org/x/sys/unix" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + + kq int // File descriptor (as returned by the kqueue() syscall). + + mu sync.Mutex // Protects access to watcher data + watches map[string]int // Map of watched file descriptors (key: path). + externalWatches map[string]bool // Map of watches added by user of the library. + dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. + paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. + fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). + isClosed bool // Set to true when Close() is first called +} + +type pathInfo struct { + name string + isDir bool +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + kq, err := kqueue() + if err != nil { + return nil, err + } + + w := &Watcher{ + kq: kq, + watches: make(map[string]int), + dirFlags: make(map[string]uint32), + paths: make(map[int]pathInfo), + fileExists: make(map[string]bool), + externalWatches: make(map[string]bool), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + } + + go w.readEvents() + return w, nil +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() + return nil + } + w.isClosed = true + + // copy paths to remove while locked + var pathsToRemove = make([]string, 0, len(w.watches)) + for name := range w.watches { + pathsToRemove = append(pathsToRemove, name) + } + w.mu.Unlock() + // unlock before calling Remove, which also locks + + for _, name := range pathsToRemove { + w.Remove(name) + } + + // send a "quit" message to the reader goroutine + close(w.done) + + return nil +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + w.mu.Lock() + w.externalWatches[name] = true + w.mu.Unlock() + _, err := w.addWatch(name, noteAllEvents) + return err +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + name = filepath.Clean(name) + w.mu.Lock() + watchfd, ok := w.watches[name] + w.mu.Unlock() + if !ok { + return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) + } + + const registerRemove = unix.EV_DELETE + if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { + return err + } + + unix.Close(watchfd) + + w.mu.Lock() + isDir := w.paths[watchfd].isDir + delete(w.watches, name) + delete(w.paths, watchfd) + delete(w.dirFlags, name) + w.mu.Unlock() + + // Find all watched paths that are in this directory that are not external. + if isDir { + var pathsToRemove []string + w.mu.Lock() + for _, path := range w.paths { + wdir, _ := filepath.Split(path.name) + if filepath.Clean(wdir) == name { + if !w.externalWatches[path.name] { + pathsToRemove = append(pathsToRemove, path.name) + } + } + } + w.mu.Unlock() + for _, name := range pathsToRemove { + // Since these are internal, not much sense in propagating error + // to the user, as that will just confuse them with an error about + // a path they did not explicitly watch themselves. + w.Remove(name) + } + } + + return nil +} + +// WatchList returns the directories and files that are being monitered. +func (w *Watcher) WatchList() []string { + w.mu.Lock() + defer w.mu.Unlock() + + entries := make([]string, 0, len(w.watches)) + for pathname := range w.watches { + entries = append(entries, pathname) + } + + return entries +} + +// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) +const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME + +// keventWaitTime to block on each read from kevent +var keventWaitTime = durationToTimespec(100 * time.Millisecond) + +// addWatch adds name to the watched file set. +// The flags are interpreted as described in kevent(2). +// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks. +func (w *Watcher) addWatch(name string, flags uint32) (string, error) { + var isDir bool + // Make ./name and name equivalent + name = filepath.Clean(name) + + w.mu.Lock() + if w.isClosed { + w.mu.Unlock() + return "", errors.New("kevent instance already closed") + } + watchfd, alreadyWatching := w.watches[name] + // We already have a watch, but we can still override flags. + if alreadyWatching { + isDir = w.paths[watchfd].isDir + } + w.mu.Unlock() + + if !alreadyWatching { + fi, err := os.Lstat(name) + if err != nil { + return "", err + } + + // Don't watch sockets. + if fi.Mode()&os.ModeSocket == os.ModeSocket { + return "", nil + } + + // Don't watch named pipes. + if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { + return "", nil + } + + // Follow Symlinks + // Unfortunately, Linux can add bogus symlinks to watch list without + // issue, and Windows can't do symlinks period (AFAIK). To maintain + // consistency, we will act like everything is fine. There will simply + // be no file events for broken symlinks. + // Hence the returns of nil on errors. + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + name, err = filepath.EvalSymlinks(name) + if err != nil { + return "", nil + } + + w.mu.Lock() + _, alreadyWatching = w.watches[name] + w.mu.Unlock() + + if alreadyWatching { + return name, nil + } + + fi, err = os.Lstat(name) + if err != nil { + return "", nil + } + } + + watchfd, err = unix.Open(name, openMode, 0700) + if watchfd == -1 { + return "", err + } + + isDir = fi.IsDir() + } + + const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE + if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { + unix.Close(watchfd) + return "", err + } + + if !alreadyWatching { + w.mu.Lock() + w.watches[name] = watchfd + w.paths[watchfd] = pathInfo{name: name, isDir: isDir} + w.mu.Unlock() + } + + if isDir { + // Watch the directory if it has not been watched before, + // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) + w.mu.Lock() + + watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && + (!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE) + // Store flags so this watch can be updated later + w.dirFlags[name] = flags + w.mu.Unlock() + + if watchDir { + if err := w.watchDirectoryFiles(name); err != nil { + return "", err + } + } + } + return name, nil +} + +// readEvents reads from kqueue and converts the received kevents into +// Event values that it sends down the Events channel. +func (w *Watcher) readEvents() { + eventBuffer := make([]unix.Kevent_t, 10) + +loop: + for { + // See if there is a message on the "done" channel + select { + case <-w.done: + break loop + default: + } + + // Get new events + kevents, err := read(w.kq, eventBuffer, &keventWaitTime) + // EINTR is okay, the syscall was interrupted before timeout expired. + if err != nil && err != unix.EINTR { + select { + case w.Errors <- err: + case <-w.done: + break loop + } + continue + } + + // Flush the events we received to the Events channel + for len(kevents) > 0 { + kevent := &kevents[0] + watchfd := int(kevent.Ident) + mask := uint32(kevent.Fflags) + w.mu.Lock() + path := w.paths[watchfd] + w.mu.Unlock() + event := newEvent(path.name, mask) + + if path.isDir && !(event.Op&Remove == Remove) { + // Double check to make sure the directory exists. This can happen when + // we do a rm -fr on a recursively watched folders and we receive a + // modification event first but the folder has been deleted and later + // receive the delete event + if _, err := os.Lstat(event.Name); os.IsNotExist(err) { + // mark is as delete event + event.Op |= Remove + } + } + + if event.Op&Rename == Rename || event.Op&Remove == Remove { + w.Remove(event.Name) + w.mu.Lock() + delete(w.fileExists, event.Name) + w.mu.Unlock() + } + + if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { + w.sendDirectoryChangeEvents(event.Name) + } else { + // Send the event on the Events channel. + select { + case w.Events <- event: + case <-w.done: + break loop + } + } + + if event.Op&Remove == Remove { + // Look for a file that may have overwritten this. + // For example, mv f1 f2 will delete f2, then create f2. + if path.isDir { + fileDir := filepath.Clean(event.Name) + w.mu.Lock() + _, found := w.watches[fileDir] + w.mu.Unlock() + if found { + // make sure the directory exists before we watch for changes. When we + // do a recursive watch and perform rm -fr, the parent directory might + // have gone missing, ignore the missing directory and let the + // upcoming delete event remove the watch from the parent directory. + if _, err := os.Lstat(fileDir); err == nil { + w.sendDirectoryChangeEvents(fileDir) + } + } + } else { + filePath := filepath.Clean(event.Name) + if fileInfo, err := os.Lstat(filePath); err == nil { + w.sendFileCreatedEventIfNew(filePath, fileInfo) + } + } + } + + // Move to next event + kevents = kevents[1:] + } + } + + // cleanup + err := unix.Close(w.kq) + if err != nil { + // only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors. + select { + case w.Errors <- err: + default: + } + } + close(w.Events) + close(w.Errors) +} + +// newEvent returns an platform-independent Event based on kqueue Fflags. +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { + e.Op |= Remove + } + if mask&unix.NOTE_WRITE == unix.NOTE_WRITE { + e.Op |= Write + } + if mask&unix.NOTE_RENAME == unix.NOTE_RENAME { + e.Op |= Rename + } + if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB { + e.Op |= Chmod + } + return e +} + +func newCreateEvent(name string) Event { + return Event{Name: name, Op: Create} +} + +// watchDirectoryFiles to mimic inotify when adding a watch on a directory +func (w *Watcher) watchDirectoryFiles(dirPath string) error { + // Get all files + files, err := ioutil.ReadDir(dirPath) + if err != nil { + return err + } + + for _, fileInfo := range files { + filePath := filepath.Join(dirPath, fileInfo.Name()) + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { + return err + } + + w.mu.Lock() + w.fileExists[filePath] = true + w.mu.Unlock() + } + + return nil +} + +// sendDirectoryEvents searches the directory for newly created files +// and sends them over the event channel. This functionality is to have +// the BSD version of fsnotify match Linux inotify which provides a +// create event for files created in a watched directory. +func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { + // Get all files + files, err := ioutil.ReadDir(dirPath) + if err != nil { + select { + case w.Errors <- err: + case <-w.done: + return + } + } + + // Search for new files + for _, fileInfo := range files { + filePath := filepath.Join(dirPath, fileInfo.Name()) + err := w.sendFileCreatedEventIfNew(filePath, fileInfo) + + if err != nil { + return + } + } +} + +// sendFileCreatedEvent sends a create event if the file isn't already being tracked. +func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) { + w.mu.Lock() + _, doesExist := w.fileExists[filePath] + w.mu.Unlock() + if !doesExist { + // Send create event + select { + case w.Events <- newCreateEvent(filePath): + case <-w.done: + return + } + } + + // like watchDirectoryFiles (but without doing another ReadDir) + filePath, err = w.internalWatch(filePath, fileInfo) + if err != nil { + return err + } + + w.mu.Lock() + w.fileExists[filePath] = true + w.mu.Unlock() + + return nil +} + +func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) { + if fileInfo.IsDir() { + // mimic Linux providing delete events for subdirectories + // but preserve the flags used if currently watching subdirectory + w.mu.Lock() + flags := w.dirFlags[name] + w.mu.Unlock() + + flags |= unix.NOTE_DELETE | unix.NOTE_RENAME + return w.addWatch(name, flags) + } + + // watch file to mimic Linux inotify + return w.addWatch(name, noteAllEvents) +} + +// kqueue creates a new kernel event queue and returns a descriptor. +func kqueue() (kq int, err error) { + kq, err = unix.Kqueue() + if kq == -1 { + return kq, err + } + return kq, nil +} + +// register events with the queue +func register(kq int, fds []int, flags int, fflags uint32) error { + changes := make([]unix.Kevent_t, len(fds)) + + for i, fd := range fds { + // SetKevent converts int to the platform-specific types: + unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags) + changes[i].Fflags = fflags + } + + // register the events + success, err := unix.Kevent(kq, changes, nil, nil) + if success == -1 { + return err + } + return nil +} + +// read retrieves pending events, or waits until an event occurs. +// A timeout of nil blocks indefinitely, while 0 polls the queue. +func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) { + n, err := unix.Kevent(kq, nil, events, timeout) + if err != nil { + return nil, err + } + return events[0:n], nil +} + +// durationToTimespec prepares a timeout value +func durationToTimespec(d time.Duration) unix.Timespec { + return unix.NsecToTimespec(d.Nanoseconds()) +} diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go new file mode 100644 index 0000000..36cc384 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go @@ -0,0 +1,12 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build freebsd || openbsd || netbsd || dragonfly +// +build freebsd openbsd netbsd dragonfly + +package fsnotify + +import "golang.org/x/sys/unix" + +const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go new file mode 100644 index 0000000..98cd847 --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go @@ -0,0 +1,13 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin +// +build darwin + +package fsnotify + +import "golang.org/x/sys/unix" + +// note: this constant is not defined on BSD +const openMode = unix.O_EVTONLY | unix.O_CLOEXEC diff --git a/vendor/github.com/fsnotify/fsnotify/windows.go b/vendor/github.com/fsnotify/fsnotify/windows.go new file mode 100644 index 0000000..02ce7de --- /dev/null +++ b/vendor/github.com/fsnotify/fsnotify/windows.go @@ -0,0 +1,586 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build windows +// +build windows + +package fsnotify + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "sync" + "syscall" + "unsafe" +) + +// Watcher watches a set of files, delivering events to a channel. +type Watcher struct { + Events chan Event + Errors chan error + isClosed bool // Set to true when Close() is first called + mu sync.Mutex // Map access + port syscall.Handle // Handle to completion port + watches watchMap // Map of watches (key: i-number) + input chan *input // Inputs to the reader are sent on this channel + quit chan chan<- error +} + +// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. +func NewWatcher() (*Watcher, error) { + port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) + if e != nil { + return nil, os.NewSyscallError("CreateIoCompletionPort", e) + } + w := &Watcher{ + port: port, + watches: make(watchMap), + input: make(chan *input, 1), + Events: make(chan Event, 50), + Errors: make(chan error), + quit: make(chan chan<- error, 1), + } + go w.readEvents() + return w, nil +} + +// Close removes all watches and closes the events channel. +func (w *Watcher) Close() error { + if w.isClosed { + return nil + } + w.isClosed = true + + // Send "quit" message to the reader goroutine + ch := make(chan error) + w.quit <- ch + if err := w.wakeupReader(); err != nil { + return err + } + return <-ch +} + +// Add starts watching the named file or directory (non-recursively). +func (w *Watcher) Add(name string) error { + if w.isClosed { + return errors.New("watcher already closed") + } + in := &input{ + op: opAddWatch, + path: filepath.Clean(name), + flags: sysFSALLEVENTS, + reply: make(chan error), + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +// Remove stops watching the the named file or directory (non-recursively). +func (w *Watcher) Remove(name string) error { + in := &input{ + op: opRemoveWatch, + path: filepath.Clean(name), + reply: make(chan error), + } + w.input <- in + if err := w.wakeupReader(); err != nil { + return err + } + return <-in.reply +} + +// WatchList returns the directories and files that are being monitered. +func (w *Watcher) WatchList() []string { + w.mu.Lock() + defer w.mu.Unlock() + + entries := make([]string, 0, len(w.watches)) + for _, entry := range w.watches { + for _, watchEntry := range entry { + entries = append(entries, watchEntry.path) + } + } + + return entries +} + +const ( + // Options for AddWatch + sysFSONESHOT = 0x80000000 + sysFSONLYDIR = 0x1000000 + + // Events + sysFSACCESS = 0x1 + sysFSALLEVENTS = 0xfff + sysFSATTRIB = 0x4 + sysFSCLOSE = 0x18 + sysFSCREATE = 0x100 + sysFSDELETE = 0x200 + sysFSDELETESELF = 0x400 + sysFSMODIFY = 0x2 + sysFSMOVE = 0xc0 + sysFSMOVEDFROM = 0x40 + sysFSMOVEDTO = 0x80 + sysFSMOVESELF = 0x800 + + // Special events + sysFSIGNORED = 0x8000 + sysFSQOVERFLOW = 0x4000 +) + +func newEvent(name string, mask uint32) Event { + e := Event{Name: name} + if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { + e.Op |= Create + } + if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF { + e.Op |= Remove + } + if mask&sysFSMODIFY == sysFSMODIFY { + e.Op |= Write + } + if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM { + e.Op |= Rename + } + if mask&sysFSATTRIB == sysFSATTRIB { + e.Op |= Chmod + } + return e +} + +const ( + opAddWatch = iota + opRemoveWatch +) + +const ( + provisional uint64 = 1 << (32 + iota) +) + +type input struct { + op int + path string + flags uint32 + reply chan error +} + +type inode struct { + handle syscall.Handle + volume uint32 + index uint64 +} + +type watch struct { + ov syscall.Overlapped + ino *inode // i-number + path string // Directory path + mask uint64 // Directory itself is being watched with these notify flags + names map[string]uint64 // Map of names being watched and their notify flags + rename string // Remembers the old name while renaming a file + buf [4096]byte +} + +type indexMap map[uint64]*watch +type watchMap map[uint32]indexMap + +func (w *Watcher) wakeupReader() error { + e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) + if e != nil { + return os.NewSyscallError("PostQueuedCompletionStatus", e) + } + return nil +} + +func getDir(pathname string) (dir string, err error) { + attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) + if e != nil { + return "", os.NewSyscallError("GetFileAttributes", e) + } + if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { + dir = pathname + } else { + dir, _ = filepath.Split(pathname) + dir = filepath.Clean(dir) + } + return +} + +func getIno(path string) (ino *inode, err error) { + h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), + syscall.FILE_LIST_DIRECTORY, + syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, + nil, syscall.OPEN_EXISTING, + syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) + if e != nil { + return nil, os.NewSyscallError("CreateFile", e) + } + var fi syscall.ByHandleFileInformation + if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { + syscall.CloseHandle(h) + return nil, os.NewSyscallError("GetFileInformationByHandle", e) + } + ino = &inode{ + handle: h, + volume: fi.VolumeSerialNumber, + index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), + } + return ino, nil +} + +// Must run within the I/O thread. +func (m watchMap) get(ino *inode) *watch { + if i := m[ino.volume]; i != nil { + return i[ino.index] + } + return nil +} + +// Must run within the I/O thread. +func (m watchMap) set(ino *inode, watch *watch) { + i := m[ino.volume] + if i == nil { + i = make(indexMap) + m[ino.volume] = i + } + i[ino.index] = watch +} + +// Must run within the I/O thread. +func (w *Watcher) addWatch(pathname string, flags uint64) error { + dir, err := getDir(pathname) + if err != nil { + return err + } + if flags&sysFSONLYDIR != 0 && pathname != dir { + return nil + } + ino, err := getIno(dir) + if err != nil { + return err + } + w.mu.Lock() + watchEntry := w.watches.get(ino) + w.mu.Unlock() + if watchEntry == nil { + if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { + syscall.CloseHandle(ino.handle) + return os.NewSyscallError("CreateIoCompletionPort", e) + } + watchEntry = &watch{ + ino: ino, + path: dir, + names: make(map[string]uint64), + } + w.mu.Lock() + w.watches.set(ino, watchEntry) + w.mu.Unlock() + flags |= provisional + } else { + syscall.CloseHandle(ino.handle) + } + if pathname == dir { + watchEntry.mask |= flags + } else { + watchEntry.names[filepath.Base(pathname)] |= flags + } + if err = w.startRead(watchEntry); err != nil { + return err + } + if pathname == dir { + watchEntry.mask &= ^provisional + } else { + watchEntry.names[filepath.Base(pathname)] &= ^provisional + } + return nil +} + +// Must run within the I/O thread. +func (w *Watcher) remWatch(pathname string) error { + dir, err := getDir(pathname) + if err != nil { + return err + } + ino, err := getIno(dir) + if err != nil { + return err + } + w.mu.Lock() + watch := w.watches.get(ino) + w.mu.Unlock() + if watch == nil { + return fmt.Errorf("can't remove non-existent watch for: %s", pathname) + } + if pathname == dir { + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) + watch.mask = 0 + } else { + name := filepath.Base(pathname) + w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + return w.startRead(watch) +} + +// Must run within the I/O thread. +func (w *Watcher) deleteWatch(watch *watch) { + for name, mask := range watch.names { + if mask&provisional == 0 { + w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED) + } + delete(watch.names, name) + } + if watch.mask != 0 { + if watch.mask&provisional == 0 { + w.sendEvent(watch.path, watch.mask&sysFSIGNORED) + } + watch.mask = 0 + } +} + +// Must run within the I/O thread. +func (w *Watcher) startRead(watch *watch) error { + if e := syscall.CancelIo(watch.ino.handle); e != nil { + w.Errors <- os.NewSyscallError("CancelIo", e) + w.deleteWatch(watch) + } + mask := toWindowsFlags(watch.mask) + for _, m := range watch.names { + mask |= toWindowsFlags(m) + } + if mask == 0 { + if e := syscall.CloseHandle(watch.ino.handle); e != nil { + w.Errors <- os.NewSyscallError("CloseHandle", e) + } + w.mu.Lock() + delete(w.watches[watch.ino.volume], watch.ino.index) + w.mu.Unlock() + return nil + } + e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], + uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) + if e != nil { + err := os.NewSyscallError("ReadDirectoryChanges", e) + if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { + // Watched directory was probably removed + if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) { + if watch.mask&sysFSONESHOT != 0 { + watch.mask = 0 + } + } + err = nil + } + w.deleteWatch(watch) + w.startRead(watch) + return err + } + return nil +} + +// readEvents reads from the I/O completion port, converts the +// received events into Event objects and sends them via the Events channel. +// Entry point to the I/O thread. +func (w *Watcher) readEvents() { + var ( + n, key uint32 + ov *syscall.Overlapped + ) + runtime.LockOSThread() + + for { + e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) + watch := (*watch)(unsafe.Pointer(ov)) + + if watch == nil { + select { + case ch := <-w.quit: + w.mu.Lock() + var indexes []indexMap + for _, index := range w.watches { + indexes = append(indexes, index) + } + w.mu.Unlock() + for _, index := range indexes { + for _, watch := range index { + w.deleteWatch(watch) + w.startRead(watch) + } + } + var err error + if e := syscall.CloseHandle(w.port); e != nil { + err = os.NewSyscallError("CloseHandle", e) + } + close(w.Events) + close(w.Errors) + ch <- err + return + case in := <-w.input: + switch in.op { + case opAddWatch: + in.reply <- w.addWatch(in.path, uint64(in.flags)) + case opRemoveWatch: + in.reply <- w.remWatch(in.path) + } + default: + } + continue + } + + switch e { + case syscall.ERROR_MORE_DATA: + if watch == nil { + w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") + } else { + // The i/o succeeded but the buffer is full. + // In theory we should be building up a full packet. + // In practice we can get away with just carrying on. + n = uint32(unsafe.Sizeof(watch.buf)) + } + case syscall.ERROR_ACCESS_DENIED: + // Watched directory was probably removed + w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) + w.deleteWatch(watch) + w.startRead(watch) + continue + case syscall.ERROR_OPERATION_ABORTED: + // CancelIo was called on this handle + continue + default: + w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e) + continue + case nil: + } + + var offset uint32 + for { + if n == 0 { + w.Events <- newEvent("", sysFSQOVERFLOW) + w.Errors <- errors.New("short read in readEvents()") + break + } + + // Point "raw" to the event in the buffer + raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) + // TODO: Consider using unsafe.Slice that is available from go1.17 + // https://stackoverflow.com/questions/51187973/how-to-create-an-array-or-a-slice-from-an-array-unsafe-pointer-in-golang + // instead of using a fixed syscall.MAX_PATH buf, we create a buf that is the size of the path name + size := int(raw.FileNameLength / 2) + var buf []uint16 + sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) + sh.Data = uintptr(unsafe.Pointer(&raw.FileName)) + sh.Len = size + sh.Cap = size + name := syscall.UTF16ToString(buf) + fullname := filepath.Join(watch.path, name) + + var mask uint64 + switch raw.Action { + case syscall.FILE_ACTION_REMOVED: + mask = sysFSDELETESELF + case syscall.FILE_ACTION_MODIFIED: + mask = sysFSMODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + watch.rename = name + case syscall.FILE_ACTION_RENAMED_NEW_NAME: + if watch.names[watch.rename] != 0 { + watch.names[name] |= watch.names[watch.rename] + delete(watch.names, watch.rename) + mask = sysFSMOVESELF + } + } + + sendNameEvent := func() { + if w.sendEvent(fullname, watch.names[name]&mask) { + if watch.names[name]&sysFSONESHOT != 0 { + delete(watch.names, name) + } + } + } + if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { + sendNameEvent() + } + if raw.Action == syscall.FILE_ACTION_REMOVED { + w.sendEvent(fullname, watch.names[name]&sysFSIGNORED) + delete(watch.names, name) + } + if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { + if watch.mask&sysFSONESHOT != 0 { + watch.mask = 0 + } + } + if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { + fullname = filepath.Join(watch.path, watch.rename) + sendNameEvent() + } + + // Move to the next event in the buffer + if raw.NextEntryOffset == 0 { + break + } + offset += raw.NextEntryOffset + + // Error! + if offset >= n { + w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") + break + } + } + + if err := w.startRead(watch); err != nil { + w.Errors <- err + } + } +} + +func (w *Watcher) sendEvent(name string, mask uint64) bool { + if mask == 0 { + return false + } + event := newEvent(name, uint32(mask)) + select { + case ch := <-w.quit: + w.quit <- ch + case w.Events <- event: + } + return true +} + +func toWindowsFlags(mask uint64) uint32 { + var m uint32 + if mask&sysFSACCESS != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS + } + if mask&sysFSMODIFY != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE + } + if mask&sysFSATTRIB != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES + } + if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 { + m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME + } + return m +} + +func toFSnotifyFlags(action uint32) uint64 { + switch action { + case syscall.FILE_ACTION_ADDED: + return sysFSCREATE + case syscall.FILE_ACTION_REMOVED: + return sysFSDELETE + case syscall.FILE_ACTION_MODIFIED: + return sysFSMODIFY + case syscall.FILE_ACTION_RENAMED_OLD_NAME: + return sysFSMOVEDFROM + case syscall.FILE_ACTION_RENAMED_NEW_NAME: + return sysFSMOVEDTO + } + return 0 +} diff --git a/vendor/github.com/go-kit/kit/LICENSE b/vendor/github.com/go-kit/kit/LICENSE new file mode 100644 index 0000000..9d83342 --- /dev/null +++ b/vendor/github.com/go-kit/kit/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Peter Bourgon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/go-kit/kit/log/README.md b/vendor/github.com/go-kit/kit/log/README.md new file mode 100644 index 0000000..5492dd9 --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/README.md @@ -0,0 +1,160 @@ +# package log + +**Deprecation notice:** The core Go kit log packages (log, log/level, log/term, and +log/syslog) have been moved to their own repository at github.com/go-kit/log. +The corresponding packages in this directory remain for backwards compatibility. +Their types alias the types and their functions call the functions provided by +the new repository. Using either import path should be equivalent. Prefer the +new import path when practical. + +______ + +`package log` provides a minimal interface for structured logging in services. +It may be wrapped to encode conventions, enforce type-safety, provide leveled +logging, and so on. It can be used for both typical application log events, +and log-structured data streams. + +## Structured logging + +Structured logging is, basically, conceding to the reality that logs are +_data_, and warrant some level of schematic rigor. Using a stricter, +key/value-oriented message format for our logs, containing contextual and +semantic information, makes it much easier to get insight into the +operational activity of the systems we build. Consequently, `package log` is +of the strong belief that "[the benefits of structured logging outweigh the +minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". + +Migrating from unstructured to structured logging is probably a lot easier +than you'd expect. + +```go +// Unstructured +log.Printf("HTTP server listening on %s", addr) + +// Structured +logger.Log("transport", "HTTP", "addr", addr, "msg", "listening") +``` + +## Usage + +### Typical application logging + +```go +w := log.NewSyncWriter(os.Stderr) +logger := log.NewLogfmtLogger(w) +logger.Log("question", "what is the meaning of life?", "answer", 42) + +// Output: +// question="what is the meaning of life?" answer=42 +``` + +### Contextual Loggers + +```go +func main() { + var logger log.Logger + logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) + logger = log.With(logger, "instance_id", 123) + + logger.Log("msg", "starting") + NewWorker(log.With(logger, "component", "worker")).Run() + NewSlacker(log.With(logger, "component", "slacker")).Run() +} + +// Output: +// instance_id=123 msg=starting +// instance_id=123 component=worker msg=running +// instance_id=123 component=slacker msg=running +``` + +### Interact with stdlib logger + +Redirect stdlib logger to Go kit logger. + +```go +import ( + "os" + stdlog "log" + kitlog "github.com/go-kit/kit/log" +) + +func main() { + logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout)) + stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) + stdlog.Print("I sure like pie") +} + +// Output: +// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} +``` + +Or, if, for legacy reasons, you need to pipe all of your logging through the +stdlib log package, you can redirect Go kit logger to the stdlib logger. + +```go +logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) +logger.Log("legacy", true, "msg", "at least it's something") + +// Output: +// 2016/01/01 12:34:56 legacy=true msg="at least it's something" +``` + +### Timestamps and callers + +```go +var logger log.Logger +logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) +logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) + +logger.Log("msg", "hello") + +// Output: +// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello +``` + +## Levels + +Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/kit/log/level). + +## Supported output formats + +- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write)) +- JSON + +## Enhancements + +`package log` is centered on the one-method Logger interface. + +```go +type Logger interface { + Log(keyvals ...interface{}) error +} +``` + +This interface, and its supporting code like is the product of much iteration +and evaluation. For more details on the evolution of the Logger interface, +see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), +a talk by [Chris Hines](https://github.com/ChrisHines). +Also, please see +[#63](https://github.com/go-kit/kit/issues/63), +[#76](https://github.com/go-kit/kit/pull/76), +[#131](https://github.com/go-kit/kit/issues/131), +[#157](https://github.com/go-kit/kit/pull/157), +[#164](https://github.com/go-kit/kit/issues/164), and +[#252](https://github.com/go-kit/kit/pull/252) +to review historical conversations about package log and the Logger interface. + +Value-add packages and suggestions, +like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level), +are of course welcome. Good proposals should + +- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With), +- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and +- Be friendly to packages that accept only an unadorned log.Logger. + +## Benchmarks & comparisons + +There are a few Go logging benchmarks and comparisons that include Go kit's package log. + +- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log +- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log diff --git a/vendor/github.com/go-kit/kit/log/doc.go b/vendor/github.com/go-kit/kit/log/doc.go new file mode 100644 index 0000000..c9873f4 --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/doc.go @@ -0,0 +1,118 @@ +// Package log provides a structured logger. +// +// Deprecated: Use github.com/go-kit/log instead. +// +// Structured logging produces logs easily consumed later by humans or +// machines. Humans might be interested in debugging errors, or tracing +// specific requests. Machines might be interested in counting interesting +// events, or aggregating information for off-line processing. In both cases, +// it is important that the log messages are structured and actionable. +// Package log is designed to encourage both of these best practices. +// +// Basic Usage +// +// The fundamental interface is Logger. Loggers create log events from +// key/value data. The Logger interface has a single method, Log, which +// accepts a sequence of alternating key/value pairs, which this package names +// keyvals. +// +// type Logger interface { +// Log(keyvals ...interface{}) error +// } +// +// Here is an example of a function using a Logger to create log events. +// +// func RunTask(task Task, logger log.Logger) string { +// logger.Log("taskID", task.ID, "event", "starting task") +// ... +// logger.Log("taskID", task.ID, "event", "task complete") +// } +// +// The keys in the above example are "taskID" and "event". The values are +// task.ID, "starting task", and "task complete". Every key is followed +// immediately by its value. +// +// Keys are usually plain strings. Values may be any type that has a sensible +// encoding in the chosen log format. With structured logging it is a good +// idea to log simple values without formatting them. This practice allows +// the chosen logger to encode values in the most appropriate way. +// +// Contextual Loggers +// +// A contextual logger stores keyvals that it includes in all log events. +// Building appropriate contextual loggers reduces repetition and aids +// consistency in the resulting log output. With, WithPrefix, and WithSuffix +// add context to a logger. We can use With to improve the RunTask example. +// +// func RunTask(task Task, logger log.Logger) string { +// logger = log.With(logger, "taskID", task.ID) +// logger.Log("event", "starting task") +// ... +// taskHelper(task.Cmd, logger) +// ... +// logger.Log("event", "task complete") +// } +// +// The improved version emits the same log events as the original for the +// first and last calls to Log. Passing the contextual logger to taskHelper +// enables each log event created by taskHelper to include the task.ID even +// though taskHelper does not have access to that value. Using contextual +// loggers this way simplifies producing log output that enables tracing the +// life cycle of individual tasks. (See the Contextual example for the full +// code of the above snippet.) +// +// Dynamic Contextual Values +// +// A Valuer function stored in a contextual logger generates a new value each +// time an event is logged. The Valuer example demonstrates how this feature +// works. +// +// Valuers provide the basis for consistently logging timestamps and source +// code location. The log package defines several valuers for that purpose. +// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and +// DefaultCaller. A common logger initialization sequence that ensures all log +// entries contain a timestamp and source location looks like this: +// +// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) +// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) +// +// Concurrent Safety +// +// Applications with multiple goroutines want each log event written to the +// same logger to remain separate from other log events. Package log provides +// two simple solutions for concurrent safe logging. +// +// NewSyncWriter wraps an io.Writer and serializes each call to its Write +// method. Using a SyncWriter has the benefit that the smallest practical +// portion of the logging logic is performed within a mutex, but it requires +// the formatting Logger to make only one call to Write per log event. +// +// NewSyncLogger wraps any Logger and serializes each call to its Log method. +// Using a SyncLogger has the benefit that it guarantees each log event is +// handled atomically within the wrapped logger, but it typically serializes +// both the formatting and output logic. Use a SyncLogger if the formatting +// logger may perform multiple writes per log event. +// +// Error Handling +// +// This package relies on the practice of wrapping or decorating loggers with +// other loggers to provide composable pieces of functionality. It also means +// that Logger.Log must return an error because some +// implementations—especially those that output log data to an io.Writer—may +// encounter errors that cannot be handled locally. This in turn means that +// Loggers that wrap other loggers should return errors from the wrapped +// logger up the stack. +// +// Fortunately, the decorator pattern also provides a way to avoid the +// necessity to check for errors every time an application calls Logger.Log. +// An application required to panic whenever its Logger encounters +// an error could initialize its logger as follows. +// +// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) +// logger := log.LoggerFunc(func(keyvals ...interface{}) error { +// if err := fmtlogger.Log(keyvals...); err != nil { +// panic(err) +// } +// return nil +// }) +package log diff --git a/vendor/github.com/go-kit/kit/log/json_logger.go b/vendor/github.com/go-kit/kit/log/json_logger.go new file mode 100644 index 0000000..edfde2f --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/json_logger.go @@ -0,0 +1,15 @@ +package log + +import ( + "io" + + "github.com/go-kit/log" +) + +// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a +// single JSON object. Each log event produces no more than one call to +// w.Write. The passed Writer must be safe for concurrent use by multiple +// goroutines if the returned Logger will be used concurrently. +func NewJSONLogger(w io.Writer) Logger { + return log.NewJSONLogger(w) +} diff --git a/vendor/github.com/go-kit/kit/log/level/doc.go b/vendor/github.com/go-kit/kit/log/level/doc.go new file mode 100644 index 0000000..7baf870 --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/level/doc.go @@ -0,0 +1,25 @@ +// Package level implements leveled logging on top of Go kit's log package. +// +// Deprecated: Use github.com/go-kit/log/level instead. +// +// To use the level package, create a logger as per normal in your func main, +// and wrap it with level.NewFilter. +// +// var logger log.Logger +// logger = log.NewLogfmtLogger(os.Stderr) +// logger = level.NewFilter(logger, level.AllowInfo()) // <-- +// logger = log.With(logger, "ts", log.DefaultTimestampUTC) +// +// Then, at the callsites, use one of the level.Debug, Info, Warn, or Error +// helper methods to emit leveled log events. +// +// logger.Log("foo", "bar") // as normal, no level +// level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get()) +// if value > 100 { +// level.Error(logger).Log("value", value) +// } +// +// NewFilter allows precise control over what happens when a log event is +// emitted without a level key, or if a squelched level is used. Check the +// Option functions for details. +package level diff --git a/vendor/github.com/go-kit/kit/log/level/level.go b/vendor/github.com/go-kit/kit/log/level/level.go new file mode 100644 index 0000000..803e8b9 --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/level/level.go @@ -0,0 +1,120 @@ +package level + +import ( + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// Error returns a logger that includes a Key/ErrorValue pair. +func Error(logger log.Logger) log.Logger { + return level.Error(logger) +} + +// Warn returns a logger that includes a Key/WarnValue pair. +func Warn(logger log.Logger) log.Logger { + return level.Warn(logger) +} + +// Info returns a logger that includes a Key/InfoValue pair. +func Info(logger log.Logger) log.Logger { + return level.Info(logger) +} + +// Debug returns a logger that includes a Key/DebugValue pair. +func Debug(logger log.Logger) log.Logger { + return level.Debug(logger) +} + +// NewFilter wraps next and implements level filtering. See the commentary on +// the Option functions for a detailed description of how to configure levels. +// If no options are provided, all leveled log events created with Debug, +// Info, Warn or Error helper methods are squelched and non-leveled log +// events are passed to next unmodified. +func NewFilter(next log.Logger, options ...Option) log.Logger { + return level.NewFilter(next, options...) +} + +// Option sets a parameter for the leveled logger. +type Option = level.Option + +// AllowAll is an alias for AllowDebug. +func AllowAll() Option { + return level.AllowAll() +} + +// AllowDebug allows error, warn, info and debug level log events to pass. +func AllowDebug() Option { + return level.AllowDebug() +} + +// AllowInfo allows error, warn and info level log events to pass. +func AllowInfo() Option { + return level.AllowInfo() +} + +// AllowWarn allows error and warn level log events to pass. +func AllowWarn() Option { + return level.AllowWarn() +} + +// AllowError allows only error level log events to pass. +func AllowError() Option { + return level.AllowError() +} + +// AllowNone allows no leveled log events to pass. +func AllowNone() Option { + return level.AllowNone() +} + +// ErrNotAllowed sets the error to return from Log when it squelches a log +// event disallowed by the configured Allow[Level] option. By default, +// ErrNotAllowed is nil; in this case the log event is squelched with no +// error. +func ErrNotAllowed(err error) Option { + return level.ErrNotAllowed(err) +} + +// SquelchNoLevel instructs Log to squelch log events with no level, so that +// they don't proceed through to the wrapped logger. If SquelchNoLevel is set +// to true and a log event is squelched in this way, the error value +// configured with ErrNoLevel is returned to the caller. +func SquelchNoLevel(squelch bool) Option { + return level.SquelchNoLevel(squelch) +} + +// ErrNoLevel sets the error to return from Log when it squelches a log event +// with no level. By default, ErrNoLevel is nil; in this case the log event is +// squelched with no error. +func ErrNoLevel(err error) Option { + return level.ErrNoLevel(err) +} + +// NewInjector wraps next and returns a logger that adds a Key/level pair to +// the beginning of log events that don't already contain a level. In effect, +// this gives a default level to logs without a level. +func NewInjector(next log.Logger, lvl Value) log.Logger { + return level.NewInjector(next, lvl) +} + +// Value is the interface that each of the canonical level values implement. +// It contains unexported methods that prevent types from other packages from +// implementing it and guaranteeing that NewFilter can distinguish the levels +// defined in this package from all other values. +type Value = level.Value + +// Key returns the unique key added to log events by the loggers in this +// package. +func Key() interface{} { return level.Key() } + +// ErrorValue returns the unique value added to log events by Error. +func ErrorValue() Value { return level.ErrorValue() } + +// WarnValue returns the unique value added to log events by Warn. +func WarnValue() Value { return level.WarnValue() } + +// InfoValue returns the unique value added to log events by Info. +func InfoValue() Value { return level.InfoValue() } + +// DebugValue returns the unique value added to log events by Debug. +func DebugValue() Value { return level.DebugValue() } diff --git a/vendor/github.com/go-kit/kit/log/log.go b/vendor/github.com/go-kit/kit/log/log.go new file mode 100644 index 0000000..164a4f9 --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/log.go @@ -0,0 +1,51 @@ +package log + +import ( + "github.com/go-kit/log" +) + +// Logger is the fundamental interface for all log operations. Log creates a +// log event from keyvals, a variadic sequence of alternating keys and values. +// Implementations must be safe for concurrent use by multiple goroutines. In +// particular, any implementation of Logger that appends to keyvals or +// modifies or retains any of its elements must make a copy first. +type Logger = log.Logger + +// ErrMissingValue is appended to keyvals slices with odd length to substitute +// the missing value. +var ErrMissingValue = log.ErrMissingValue + +// With returns a new contextual logger with keyvals prepended to those passed +// to calls to Log. If logger is also a contextual logger created by With, +// WithPrefix, or WithSuffix, keyvals is appended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func With(logger Logger, keyvals ...interface{}) Logger { + return log.With(logger, keyvals...) +} + +// WithPrefix returns a new contextual logger with keyvals prepended to those +// passed to calls to Log. If logger is also a contextual logger created by +// With, WithPrefix, or WithSuffix, keyvals is prepended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func WithPrefix(logger Logger, keyvals ...interface{}) Logger { + return log.WithPrefix(logger, keyvals...) +} + +// WithSuffix returns a new contextual logger with keyvals appended to those +// passed to calls to Log. If logger is also a contextual logger created by +// With, WithPrefix, or WithSuffix, keyvals is appended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func WithSuffix(logger Logger, keyvals ...interface{}) Logger { + return log.WithSuffix(logger, keyvals...) +} + +// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If +// f is a function with the appropriate signature, LoggerFunc(f) is a Logger +// object that calls f. +type LoggerFunc = log.LoggerFunc diff --git a/vendor/github.com/go-kit/kit/log/logfmt_logger.go b/vendor/github.com/go-kit/kit/log/logfmt_logger.go new file mode 100644 index 0000000..51cde2c --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/logfmt_logger.go @@ -0,0 +1,15 @@ +package log + +import ( + "io" + + "github.com/go-kit/log" +) + +// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in +// logfmt format. Each log event produces no more than one call to w.Write. +// The passed Writer must be safe for concurrent use by multiple goroutines if +// the returned Logger will be used concurrently. +func NewLogfmtLogger(w io.Writer) Logger { + return log.NewLogfmtLogger(w) +} diff --git a/vendor/github.com/go-kit/kit/log/nop_logger.go b/vendor/github.com/go-kit/kit/log/nop_logger.go new file mode 100644 index 0000000..b02c686 --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/nop_logger.go @@ -0,0 +1,8 @@ +package log + +import "github.com/go-kit/log" + +// NewNopLogger returns a logger that doesn't do anything. +func NewNopLogger() Logger { + return log.NewNopLogger() +} diff --git a/vendor/github.com/go-kit/kit/log/stdlib.go b/vendor/github.com/go-kit/kit/log/stdlib.go new file mode 100644 index 0000000..cb604a7 --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/stdlib.go @@ -0,0 +1,54 @@ +package log + +import ( + "io" + + "github.com/go-kit/log" +) + +// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's +// designed to be passed to a Go kit logger as the writer, for cases where +// it's necessary to redirect all Go kit log output to the stdlib logger. +// +// If you have any choice in the matter, you shouldn't use this. Prefer to +// redirect the stdlib log to the Go kit logger via NewStdlibAdapter. +type StdlibWriter = log.StdlibWriter + +// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib +// logger's SetOutput. It will extract date/timestamps, filenames, and +// messages, and place them under relevant keys. +type StdlibAdapter = log.StdlibAdapter + +// StdlibAdapterOption sets a parameter for the StdlibAdapter. +type StdlibAdapterOption = log.StdlibAdapterOption + +// TimestampKey sets the key for the timestamp field. By default, it's "ts". +func TimestampKey(key string) StdlibAdapterOption { + return log.TimestampKey(key) +} + +// FileKey sets the key for the file and line field. By default, it's "caller". +func FileKey(key string) StdlibAdapterOption { + return log.FileKey(key) +} + +// MessageKey sets the key for the actual log message. By default, it's "msg". +func MessageKey(key string) StdlibAdapterOption { + return log.MessageKey(key) +} + +// Prefix configures the adapter to parse a prefix from stdlib log events. If +// you provide a non-empty prefix to the stdlib logger, then your should provide +// that same prefix to the adapter via this option. +// +// By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to +// true if you want to include the parsed prefix in the msg. +func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption { + return log.Prefix(prefix, joinPrefixToMsg) +} + +// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed +// logger. It's designed to be passed to log.SetOutput. +func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { + return log.NewStdlibAdapter(logger, options...) +} diff --git a/vendor/github.com/go-kit/kit/log/sync.go b/vendor/github.com/go-kit/kit/log/sync.go new file mode 100644 index 0000000..bcfee2b --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/sync.go @@ -0,0 +1,37 @@ +package log + +import ( + "io" + + "github.com/go-kit/log" +) + +// SwapLogger wraps another logger that may be safely replaced while other +// goroutines use the SwapLogger concurrently. The zero value for a SwapLogger +// will discard all log events without error. +// +// SwapLogger serves well as a package global logger that can be changed by +// importers. +type SwapLogger = log.SwapLogger + +// NewSyncWriter returns a new writer that is safe for concurrent use by +// multiple goroutines. Writes to the returned writer are passed on to w. If +// another write is already in progress, the calling goroutine blocks until +// the writer is available. +// +// If w implements the following interface, so does the returned writer. +// +// interface { +// Fd() uintptr +// } +func NewSyncWriter(w io.Writer) io.Writer { + return log.NewSyncWriter(w) +} + +// NewSyncLogger returns a logger that synchronizes concurrent use of the +// wrapped logger. When multiple goroutines use the SyncLogger concurrently +// only one goroutine will be allowed to log to the wrapped logger at a time. +// The other goroutines will block until the logger is available. +func NewSyncLogger(logger Logger) Logger { + return log.NewSyncLogger(logger) +} diff --git a/vendor/github.com/go-kit/kit/log/value.go b/vendor/github.com/go-kit/kit/log/value.go new file mode 100644 index 0000000..96d783b --- /dev/null +++ b/vendor/github.com/go-kit/kit/log/value.go @@ -0,0 +1,52 @@ +package log + +import ( + "time" + + "github.com/go-kit/log" +) + +// A Valuer generates a log value. When passed to With, WithPrefix, or +// WithSuffix in a value element (odd indexes), it represents a dynamic +// value which is re-evaluated with each log event. +type Valuer = log.Valuer + +// Timestamp returns a timestamp Valuer. It invokes the t function to get the +// time; unless you are doing something tricky, pass time.Now. +// +// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which +// are TimestampFormats that use the RFC3339Nano format. +func Timestamp(t func() time.Time) Valuer { + return log.Timestamp(t) +} + +// TimestampFormat returns a timestamp Valuer with a custom time format. It +// invokes the t function to get the time to format; unless you are doing +// something tricky, pass time.Now. The layout string is passed to +// Time.Format. +// +// Most users will want to use DefaultTimestamp or DefaultTimestampUTC, which +// are TimestampFormats that use the RFC3339Nano format. +func TimestampFormat(t func() time.Time, layout string) Valuer { + return log.TimestampFormat(t, layout) +} + +// Caller returns a Valuer that returns a file and line from a specified depth +// in the callstack. Users will probably want to use DefaultCaller. +func Caller(depth int) Valuer { + return log.Caller(depth) +} + +var ( + // DefaultTimestamp is a Valuer that returns the current wallclock time, + // respecting time zones, when bound. + DefaultTimestamp = log.DefaultTimestamp + + // DefaultTimestampUTC is a Valuer that returns the current time in UTC + // when bound. + DefaultTimestampUTC = log.DefaultTimestampUTC + + // DefaultCaller is a Valuer that returns the file and line where the Log + // method was invoked. It can only be used with log.With. + DefaultCaller = log.DefaultCaller +) diff --git a/vendor/github.com/go-kit/log/.gitignore b/vendor/github.com/go-kit/log/.gitignore new file mode 100644 index 0000000..66fd13c --- /dev/null +++ b/vendor/github.com/go-kit/log/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/vendor/github.com/go-kit/log/LICENSE b/vendor/github.com/go-kit/log/LICENSE new file mode 100644 index 0000000..bb5bdb9 --- /dev/null +++ b/vendor/github.com/go-kit/log/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Go kit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/go-kit/log/README.md b/vendor/github.com/go-kit/log/README.md new file mode 100644 index 0000000..a093195 --- /dev/null +++ b/vendor/github.com/go-kit/log/README.md @@ -0,0 +1,151 @@ +# package log + +`package log` provides a minimal interface for structured logging in services. +It may be wrapped to encode conventions, enforce type-safety, provide leveled +logging, and so on. It can be used for both typical application log events, +and log-structured data streams. + +## Structured logging + +Structured logging is, basically, conceding to the reality that logs are +_data_, and warrant some level of schematic rigor. Using a stricter, +key/value-oriented message format for our logs, containing contextual and +semantic information, makes it much easier to get insight into the +operational activity of the systems we build. Consequently, `package log` is +of the strong belief that "[the benefits of structured logging outweigh the +minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". + +Migrating from unstructured to structured logging is probably a lot easier +than you'd expect. + +```go +// Unstructured +log.Printf("HTTP server listening on %s", addr) + +// Structured +logger.Log("transport", "HTTP", "addr", addr, "msg", "listening") +``` + +## Usage + +### Typical application logging + +```go +w := log.NewSyncWriter(os.Stderr) +logger := log.NewLogfmtLogger(w) +logger.Log("question", "what is the meaning of life?", "answer", 42) + +// Output: +// question="what is the meaning of life?" answer=42 +``` + +### Contextual Loggers + +```go +func main() { + var logger log.Logger + logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) + logger = log.With(logger, "instance_id", 123) + + logger.Log("msg", "starting") + NewWorker(log.With(logger, "component", "worker")).Run() + NewSlacker(log.With(logger, "component", "slacker")).Run() +} + +// Output: +// instance_id=123 msg=starting +// instance_id=123 component=worker msg=running +// instance_id=123 component=slacker msg=running +``` + +### Interact with stdlib logger + +Redirect stdlib logger to Go kit logger. + +```go +import ( + "os" + stdlog "log" + kitlog "github.com/go-kit/log" +) + +func main() { + logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout)) + stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) + stdlog.Print("I sure like pie") +} + +// Output: +// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} +``` + +Or, if, for legacy reasons, you need to pipe all of your logging through the +stdlib log package, you can redirect Go kit logger to the stdlib logger. + +```go +logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) +logger.Log("legacy", true, "msg", "at least it's something") + +// Output: +// 2016/01/01 12:34:56 legacy=true msg="at least it's something" +``` + +### Timestamps and callers + +```go +var logger log.Logger +logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) +logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) + +logger.Log("msg", "hello") + +// Output: +// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello +``` + +## Levels + +Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/log/level). + +## Supported output formats + +- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write)) +- JSON + +## Enhancements + +`package log` is centered on the one-method Logger interface. + +```go +type Logger interface { + Log(keyvals ...interface{}) error +} +``` + +This interface, and its supporting code like is the product of much iteration +and evaluation. For more details on the evolution of the Logger interface, +see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), +a talk by [Chris Hines](https://github.com/ChrisHines). +Also, please see +[#63](https://github.com/go-kit/kit/issues/63), +[#76](https://github.com/go-kit/kit/pull/76), +[#131](https://github.com/go-kit/kit/issues/131), +[#157](https://github.com/go-kit/kit/pull/157), +[#164](https://github.com/go-kit/kit/issues/164), and +[#252](https://github.com/go-kit/kit/pull/252) +to review historical conversations about package log and the Logger interface. + +Value-add packages and suggestions, +like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/log/level), +are of course welcome. Good proposals should + +- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/log#With), +- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/log#Caller) in any wrapped contextual loggers, and +- Be friendly to packages that accept only an unadorned log.Logger. + +## Benchmarks & comparisons + +There are a few Go logging benchmarks and comparisons that include Go kit's package log. + +- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log +- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log diff --git a/vendor/github.com/go-kit/log/doc.go b/vendor/github.com/go-kit/log/doc.go new file mode 100644 index 0000000..f744382 --- /dev/null +++ b/vendor/github.com/go-kit/log/doc.go @@ -0,0 +1,116 @@ +// Package log provides a structured logger. +// +// Structured logging produces logs easily consumed later by humans or +// machines. Humans might be interested in debugging errors, or tracing +// specific requests. Machines might be interested in counting interesting +// events, or aggregating information for off-line processing. In both cases, +// it is important that the log messages are structured and actionable. +// Package log is designed to encourage both of these best practices. +// +// Basic Usage +// +// The fundamental interface is Logger. Loggers create log events from +// key/value data. The Logger interface has a single method, Log, which +// accepts a sequence of alternating key/value pairs, which this package names +// keyvals. +// +// type Logger interface { +// Log(keyvals ...interface{}) error +// } +// +// Here is an example of a function using a Logger to create log events. +// +// func RunTask(task Task, logger log.Logger) string { +// logger.Log("taskID", task.ID, "event", "starting task") +// ... +// logger.Log("taskID", task.ID, "event", "task complete") +// } +// +// The keys in the above example are "taskID" and "event". The values are +// task.ID, "starting task", and "task complete". Every key is followed +// immediately by its value. +// +// Keys are usually plain strings. Values may be any type that has a sensible +// encoding in the chosen log format. With structured logging it is a good +// idea to log simple values without formatting them. This practice allows +// the chosen logger to encode values in the most appropriate way. +// +// Contextual Loggers +// +// A contextual logger stores keyvals that it includes in all log events. +// Building appropriate contextual loggers reduces repetition and aids +// consistency in the resulting log output. With, WithPrefix, and WithSuffix +// add context to a logger. We can use With to improve the RunTask example. +// +// func RunTask(task Task, logger log.Logger) string { +// logger = log.With(logger, "taskID", task.ID) +// logger.Log("event", "starting task") +// ... +// taskHelper(task.Cmd, logger) +// ... +// logger.Log("event", "task complete") +// } +// +// The improved version emits the same log events as the original for the +// first and last calls to Log. Passing the contextual logger to taskHelper +// enables each log event created by taskHelper to include the task.ID even +// though taskHelper does not have access to that value. Using contextual +// loggers this way simplifies producing log output that enables tracing the +// life cycle of individual tasks. (See the Contextual example for the full +// code of the above snippet.) +// +// Dynamic Contextual Values +// +// A Valuer function stored in a contextual logger generates a new value each +// time an event is logged. The Valuer example demonstrates how this feature +// works. +// +// Valuers provide the basis for consistently logging timestamps and source +// code location. The log package defines several valuers for that purpose. +// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and +// DefaultCaller. A common logger initialization sequence that ensures all log +// entries contain a timestamp and source location looks like this: +// +// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) +// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) +// +// Concurrent Safety +// +// Applications with multiple goroutines want each log event written to the +// same logger to remain separate from other log events. Package log provides +// two simple solutions for concurrent safe logging. +// +// NewSyncWriter wraps an io.Writer and serializes each call to its Write +// method. Using a SyncWriter has the benefit that the smallest practical +// portion of the logging logic is performed within a mutex, but it requires +// the formatting Logger to make only one call to Write per log event. +// +// NewSyncLogger wraps any Logger and serializes each call to its Log method. +// Using a SyncLogger has the benefit that it guarantees each log event is +// handled atomically within the wrapped logger, but it typically serializes +// both the formatting and output logic. Use a SyncLogger if the formatting +// logger may perform multiple writes per log event. +// +// Error Handling +// +// This package relies on the practice of wrapping or decorating loggers with +// other loggers to provide composable pieces of functionality. It also means +// that Logger.Log must return an error because some +// implementations—especially those that output log data to an io.Writer—may +// encounter errors that cannot be handled locally. This in turn means that +// Loggers that wrap other loggers should return errors from the wrapped +// logger up the stack. +// +// Fortunately, the decorator pattern also provides a way to avoid the +// necessity to check for errors every time an application calls Logger.Log. +// An application required to panic whenever its Logger encounters +// an error could initialize its logger as follows. +// +// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) +// logger := log.LoggerFunc(func(keyvals ...interface{}) error { +// if err := fmtlogger.Log(keyvals...); err != nil { +// panic(err) +// } +// return nil +// }) +package log diff --git a/vendor/github.com/go-kit/log/json_logger.go b/vendor/github.com/go-kit/log/json_logger.go new file mode 100644 index 0000000..0cedbf8 --- /dev/null +++ b/vendor/github.com/go-kit/log/json_logger.go @@ -0,0 +1,91 @@ +package log + +import ( + "encoding" + "encoding/json" + "fmt" + "io" + "reflect" +) + +type jsonLogger struct { + io.Writer +} + +// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a +// single JSON object. Each log event produces no more than one call to +// w.Write. The passed Writer must be safe for concurrent use by multiple +// goroutines if the returned Logger will be used concurrently. +func NewJSONLogger(w io.Writer) Logger { + return &jsonLogger{w} +} + +func (l *jsonLogger) Log(keyvals ...interface{}) error { + n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd + m := make(map[string]interface{}, n) + for i := 0; i < len(keyvals); i += 2 { + k := keyvals[i] + var v interface{} = ErrMissingValue + if i+1 < len(keyvals) { + v = keyvals[i+1] + } + merge(m, k, v) + } + enc := json.NewEncoder(l.Writer) + enc.SetEscapeHTML(false) + return enc.Encode(m) +} + +func merge(dst map[string]interface{}, k, v interface{}) { + var key string + switch x := k.(type) { + case string: + key = x + case fmt.Stringer: + key = safeString(x) + default: + key = fmt.Sprint(x) + } + + // We want json.Marshaler and encoding.TextMarshaller to take priority over + // err.Error() and v.String(). But json.Marshall (called later) does that by + // default so we force a no-op if it's one of those 2 case. + switch x := v.(type) { + case json.Marshaler: + case encoding.TextMarshaler: + case error: + v = safeError(x) + case fmt.Stringer: + v = safeString(x) + } + + dst[key] = v +} + +func safeString(str fmt.Stringer) (s string) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { + s = "NULL" + } else { + panic(panicVal) + } + } + }() + s = str.String() + return +} + +func safeError(err error) (s interface{}) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { + s = nil + } else { + panic(panicVal) + } + } + }() + s = err.Error() + return +} diff --git a/vendor/github.com/go-kit/log/level/doc.go b/vendor/github.com/go-kit/log/level/doc.go new file mode 100644 index 0000000..505d307 --- /dev/null +++ b/vendor/github.com/go-kit/log/level/doc.go @@ -0,0 +1,22 @@ +// Package level implements leveled logging on top of Go kit's log package. To +// use the level package, create a logger as per normal in your func main, and +// wrap it with level.NewFilter. +// +// var logger log.Logger +// logger = log.NewLogfmtLogger(os.Stderr) +// logger = level.NewFilter(logger, level.AllowInfo()) // <-- +// logger = log.With(logger, "ts", log.DefaultTimestampUTC) +// +// Then, at the callsites, use one of the level.Debug, Info, Warn, or Error +// helper methods to emit leveled log events. +// +// logger.Log("foo", "bar") // as normal, no level +// level.Debug(logger).Log("request_id", reqID, "trace_data", trace.Get()) +// if value > 100 { +// level.Error(logger).Log("value", value) +// } +// +// NewFilter allows precise control over what happens when a log event is +// emitted without a level key, or if a squelched level is used. Check the +// Option functions for details. +package level diff --git a/vendor/github.com/go-kit/log/level/level.go b/vendor/github.com/go-kit/log/level/level.go new file mode 100644 index 0000000..c94756c --- /dev/null +++ b/vendor/github.com/go-kit/log/level/level.go @@ -0,0 +1,205 @@ +package level + +import "github.com/go-kit/log" + +// Error returns a logger that includes a Key/ErrorValue pair. +func Error(logger log.Logger) log.Logger { + return log.WithPrefix(logger, Key(), ErrorValue()) +} + +// Warn returns a logger that includes a Key/WarnValue pair. +func Warn(logger log.Logger) log.Logger { + return log.WithPrefix(logger, Key(), WarnValue()) +} + +// Info returns a logger that includes a Key/InfoValue pair. +func Info(logger log.Logger) log.Logger { + return log.WithPrefix(logger, Key(), InfoValue()) +} + +// Debug returns a logger that includes a Key/DebugValue pair. +func Debug(logger log.Logger) log.Logger { + return log.WithPrefix(logger, Key(), DebugValue()) +} + +// NewFilter wraps next and implements level filtering. See the commentary on +// the Option functions for a detailed description of how to configure levels. +// If no options are provided, all leveled log events created with Debug, +// Info, Warn or Error helper methods are squelched and non-leveled log +// events are passed to next unmodified. +func NewFilter(next log.Logger, options ...Option) log.Logger { + l := &logger{ + next: next, + } + for _, option := range options { + option(l) + } + return l +} + +type logger struct { + next log.Logger + allowed level + squelchNoLevel bool + errNotAllowed error + errNoLevel error +} + +func (l *logger) Log(keyvals ...interface{}) error { + var hasLevel, levelAllowed bool + for i := 1; i < len(keyvals); i += 2 { + if v, ok := keyvals[i].(*levelValue); ok { + hasLevel = true + levelAllowed = l.allowed&v.level != 0 + break + } + } + if !hasLevel && l.squelchNoLevel { + return l.errNoLevel + } + if hasLevel && !levelAllowed { + return l.errNotAllowed + } + return l.next.Log(keyvals...) +} + +// Option sets a parameter for the leveled logger. +type Option func(*logger) + +// AllowAll is an alias for AllowDebug. +func AllowAll() Option { + return AllowDebug() +} + +// AllowDebug allows error, warn, info and debug level log events to pass. +func AllowDebug() Option { + return allowed(levelError | levelWarn | levelInfo | levelDebug) +} + +// AllowInfo allows error, warn and info level log events to pass. +func AllowInfo() Option { + return allowed(levelError | levelWarn | levelInfo) +} + +// AllowWarn allows error and warn level log events to pass. +func AllowWarn() Option { + return allowed(levelError | levelWarn) +} + +// AllowError allows only error level log events to pass. +func AllowError() Option { + return allowed(levelError) +} + +// AllowNone allows no leveled log events to pass. +func AllowNone() Option { + return allowed(0) +} + +func allowed(allowed level) Option { + return func(l *logger) { l.allowed = allowed } +} + +// ErrNotAllowed sets the error to return from Log when it squelches a log +// event disallowed by the configured Allow[Level] option. By default, +// ErrNotAllowed is nil; in this case the log event is squelched with no +// error. +func ErrNotAllowed(err error) Option { + return func(l *logger) { l.errNotAllowed = err } +} + +// SquelchNoLevel instructs Log to squelch log events with no level, so that +// they don't proceed through to the wrapped logger. If SquelchNoLevel is set +// to true and a log event is squelched in this way, the error value +// configured with ErrNoLevel is returned to the caller. +func SquelchNoLevel(squelch bool) Option { + return func(l *logger) { l.squelchNoLevel = squelch } +} + +// ErrNoLevel sets the error to return from Log when it squelches a log event +// with no level. By default, ErrNoLevel is nil; in this case the log event is +// squelched with no error. +func ErrNoLevel(err error) Option { + return func(l *logger) { l.errNoLevel = err } +} + +// NewInjector wraps next and returns a logger that adds a Key/level pair to +// the beginning of log events that don't already contain a level. In effect, +// this gives a default level to logs without a level. +func NewInjector(next log.Logger, level Value) log.Logger { + return &injector{ + next: next, + level: level, + } +} + +type injector struct { + next log.Logger + level interface{} +} + +func (l *injector) Log(keyvals ...interface{}) error { + for i := 1; i < len(keyvals); i += 2 { + if _, ok := keyvals[i].(*levelValue); ok { + return l.next.Log(keyvals...) + } + } + kvs := make([]interface{}, len(keyvals)+2) + kvs[0], kvs[1] = key, l.level + copy(kvs[2:], keyvals) + return l.next.Log(kvs...) +} + +// Value is the interface that each of the canonical level values implement. +// It contains unexported methods that prevent types from other packages from +// implementing it and guaranteeing that NewFilter can distinguish the levels +// defined in this package from all other values. +type Value interface { + String() string + levelVal() +} + +// Key returns the unique key added to log events by the loggers in this +// package. +func Key() interface{} { return key } + +// ErrorValue returns the unique value added to log events by Error. +func ErrorValue() Value { return errorValue } + +// WarnValue returns the unique value added to log events by Warn. +func WarnValue() Value { return warnValue } + +// InfoValue returns the unique value added to log events by Info. +func InfoValue() Value { return infoValue } + +// DebugValue returns the unique value added to log events by Debug. +func DebugValue() Value { return debugValue } + +var ( + // key is of type interface{} so that it allocates once during package + // initialization and avoids allocating every time the value is added to a + // []interface{} later. + key interface{} = "level" + + errorValue = &levelValue{level: levelError, name: "error"} + warnValue = &levelValue{level: levelWarn, name: "warn"} + infoValue = &levelValue{level: levelInfo, name: "info"} + debugValue = &levelValue{level: levelDebug, name: "debug"} +) + +type level byte + +const ( + levelDebug level = 1 << iota + levelInfo + levelWarn + levelError +) + +type levelValue struct { + name string + level +} + +func (v *levelValue) String() string { return v.name } +func (v *levelValue) levelVal() {} diff --git a/vendor/github.com/go-kit/log/log.go b/vendor/github.com/go-kit/log/log.go new file mode 100644 index 0000000..62e11ad --- /dev/null +++ b/vendor/github.com/go-kit/log/log.go @@ -0,0 +1,179 @@ +package log + +import "errors" + +// Logger is the fundamental interface for all log operations. Log creates a +// log event from keyvals, a variadic sequence of alternating keys and values. +// Implementations must be safe for concurrent use by multiple goroutines. In +// particular, any implementation of Logger that appends to keyvals or +// modifies or retains any of its elements must make a copy first. +type Logger interface { + Log(keyvals ...interface{}) error +} + +// ErrMissingValue is appended to keyvals slices with odd length to substitute +// the missing value. +var ErrMissingValue = errors.New("(MISSING)") + +// With returns a new contextual logger with keyvals prepended to those passed +// to calls to Log. If logger is also a contextual logger created by With, +// WithPrefix, or WithSuffix, keyvals is appended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func With(logger Logger, keyvals ...interface{}) Logger { + if len(keyvals) == 0 { + return logger + } + l := newContext(logger) + kvs := append(l.keyvals, keyvals...) + if len(kvs)%2 != 0 { + kvs = append(kvs, ErrMissingValue) + } + return &context{ + logger: l.logger, + // Limiting the capacity of the stored keyvals ensures that a new + // backing array is created if the slice must grow in Log or With. + // Using the extra capacity without copying risks a data race that + // would violate the Logger interface contract. + keyvals: kvs[:len(kvs):len(kvs)], + hasValuer: l.hasValuer || containsValuer(keyvals), + sKeyvals: l.sKeyvals, + sHasValuer: l.sHasValuer, + } +} + +// WithPrefix returns a new contextual logger with keyvals prepended to those +// passed to calls to Log. If logger is also a contextual logger created by +// With, WithPrefix, or WithSuffix, keyvals is prepended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func WithPrefix(logger Logger, keyvals ...interface{}) Logger { + if len(keyvals) == 0 { + return logger + } + l := newContext(logger) + // Limiting the capacity of the stored keyvals ensures that a new + // backing array is created if the slice must grow in Log or With. + // Using the extra capacity without copying risks a data race that + // would violate the Logger interface contract. + n := len(l.keyvals) + len(keyvals) + if len(keyvals)%2 != 0 { + n++ + } + kvs := make([]interface{}, 0, n) + kvs = append(kvs, keyvals...) + if len(kvs)%2 != 0 { + kvs = append(kvs, ErrMissingValue) + } + kvs = append(kvs, l.keyvals...) + return &context{ + logger: l.logger, + keyvals: kvs, + hasValuer: l.hasValuer || containsValuer(keyvals), + sKeyvals: l.sKeyvals, + sHasValuer: l.sHasValuer, + } +} + +// WithSuffix returns a new contextual logger with keyvals appended to those +// passed to calls to Log. If logger is also a contextual logger created by +// With, WithPrefix, or WithSuffix, keyvals is appended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func WithSuffix(logger Logger, keyvals ...interface{}) Logger { + if len(keyvals) == 0 { + return logger + } + l := newContext(logger) + // Limiting the capacity of the stored keyvals ensures that a new + // backing array is created if the slice must grow in Log or With. + // Using the extra capacity without copying risks a data race that + // would violate the Logger interface contract. + n := len(l.sKeyvals) + len(keyvals) + if len(keyvals)%2 != 0 { + n++ + } + kvs := make([]interface{}, 0, n) + kvs = append(kvs, keyvals...) + if len(kvs)%2 != 0 { + kvs = append(kvs, ErrMissingValue) + } + kvs = append(l.sKeyvals, kvs...) + return &context{ + logger: l.logger, + keyvals: l.keyvals, + hasValuer: l.hasValuer, + sKeyvals: kvs, + sHasValuer: l.sHasValuer || containsValuer(keyvals), + } +} + +// context is the Logger implementation returned by With, WithPrefix, and +// WithSuffix. It wraps a Logger and holds keyvals that it includes in all +// log events. Its Log method calls bindValues to generate values for each +// Valuer in the context keyvals. +// +// A context must always have the same number of stack frames between calls to +// its Log method and the eventual binding of Valuers to their value. This +// requirement comes from the functional requirement to allow a context to +// resolve application call site information for a Caller stored in the +// context. To do this we must be able to predict the number of logging +// functions on the stack when bindValues is called. +// +// Two implementation details provide the needed stack depth consistency. +// +// 1. newContext avoids introducing an additional layer when asked to +// wrap another context. +// 2. With, WithPrefix, and WithSuffix avoid introducing an additional +// layer by returning a newly constructed context with a merged keyvals +// rather than simply wrapping the existing context. +type context struct { + logger Logger + keyvals []interface{} + sKeyvals []interface{} // suffixes + hasValuer bool + sHasValuer bool +} + +func newContext(logger Logger) *context { + if c, ok := logger.(*context); ok { + return c + } + return &context{logger: logger} +} + +// Log replaces all value elements (odd indexes) containing a Valuer in the +// stored context with their generated value, appends keyvals, and passes the +// result to the wrapped Logger. +func (l *context) Log(keyvals ...interface{}) error { + kvs := append(l.keyvals, keyvals...) + if len(kvs)%2 != 0 { + kvs = append(kvs, ErrMissingValue) + } + if l.hasValuer { + // If no keyvals were appended above then we must copy l.keyvals so + // that future log events will reevaluate the stored Valuers. + if len(keyvals) == 0 { + kvs = append([]interface{}{}, l.keyvals...) + } + bindValues(kvs[:(len(l.keyvals))]) + } + kvs = append(kvs, l.sKeyvals...) + if l.sHasValuer { + bindValues(kvs[len(kvs)-len(l.sKeyvals):]) + } + return l.logger.Log(kvs...) +} + +// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If +// f is a function with the appropriate signature, LoggerFunc(f) is a Logger +// object that calls f. +type LoggerFunc func(...interface{}) error + +// Log implements Logger by calling f(keyvals...). +func (f LoggerFunc) Log(keyvals ...interface{}) error { + return f(keyvals...) +} diff --git a/vendor/github.com/go-kit/log/logfmt_logger.go b/vendor/github.com/go-kit/log/logfmt_logger.go new file mode 100644 index 0000000..a003052 --- /dev/null +++ b/vendor/github.com/go-kit/log/logfmt_logger.go @@ -0,0 +1,62 @@ +package log + +import ( + "bytes" + "io" + "sync" + + "github.com/go-logfmt/logfmt" +) + +type logfmtEncoder struct { + *logfmt.Encoder + buf bytes.Buffer +} + +func (l *logfmtEncoder) Reset() { + l.Encoder.Reset() + l.buf.Reset() +} + +var logfmtEncoderPool = sync.Pool{ + New: func() interface{} { + var enc logfmtEncoder + enc.Encoder = logfmt.NewEncoder(&enc.buf) + return &enc + }, +} + +type logfmtLogger struct { + w io.Writer +} + +// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in +// logfmt format. Each log event produces no more than one call to w.Write. +// The passed Writer must be safe for concurrent use by multiple goroutines if +// the returned Logger will be used concurrently. +func NewLogfmtLogger(w io.Writer) Logger { + return &logfmtLogger{w} +} + +func (l logfmtLogger) Log(keyvals ...interface{}) error { + enc := logfmtEncoderPool.Get().(*logfmtEncoder) + enc.Reset() + defer logfmtEncoderPool.Put(enc) + + if err := enc.EncodeKeyvals(keyvals...); err != nil { + return err + } + + // Add newline to the end of the buffer + if err := enc.EndRecord(); err != nil { + return err + } + + // The Logger interface requires implementations to be safe for concurrent + // use by multiple goroutines. For this implementation that means making + // only one call to l.w.Write() for each call to Log. + if _, err := l.w.Write(enc.buf.Bytes()); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/go-kit/log/nop_logger.go b/vendor/github.com/go-kit/log/nop_logger.go new file mode 100644 index 0000000..1047d62 --- /dev/null +++ b/vendor/github.com/go-kit/log/nop_logger.go @@ -0,0 +1,8 @@ +package log + +type nopLogger struct{} + +// NewNopLogger returns a logger that doesn't do anything. +func NewNopLogger() Logger { return nopLogger{} } + +func (nopLogger) Log(...interface{}) error { return nil } diff --git a/vendor/github.com/go-kit/log/stdlib.go b/vendor/github.com/go-kit/log/stdlib.go new file mode 100644 index 0000000..0338edb --- /dev/null +++ b/vendor/github.com/go-kit/log/stdlib.go @@ -0,0 +1,151 @@ +package log + +import ( + "bytes" + "io" + "log" + "regexp" + "strings" +) + +// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's +// designed to be passed to a Go kit logger as the writer, for cases where +// it's necessary to redirect all Go kit log output to the stdlib logger. +// +// If you have any choice in the matter, you shouldn't use this. Prefer to +// redirect the stdlib log to the Go kit logger via NewStdlibAdapter. +type StdlibWriter struct{} + +// Write implements io.Writer. +func (w StdlibWriter) Write(p []byte) (int, error) { + log.Print(strings.TrimSpace(string(p))) + return len(p), nil +} + +// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib +// logger's SetOutput. It will extract date/timestamps, filenames, and +// messages, and place them under relevant keys. +type StdlibAdapter struct { + Logger + timestampKey string + fileKey string + messageKey string + prefix string + joinPrefixToMsg bool +} + +// StdlibAdapterOption sets a parameter for the StdlibAdapter. +type StdlibAdapterOption func(*StdlibAdapter) + +// TimestampKey sets the key for the timestamp field. By default, it's "ts". +func TimestampKey(key string) StdlibAdapterOption { + return func(a *StdlibAdapter) { a.timestampKey = key } +} + +// FileKey sets the key for the file and line field. By default, it's "caller". +func FileKey(key string) StdlibAdapterOption { + return func(a *StdlibAdapter) { a.fileKey = key } +} + +// MessageKey sets the key for the actual log message. By default, it's "msg". +func MessageKey(key string) StdlibAdapterOption { + return func(a *StdlibAdapter) { a.messageKey = key } +} + +// Prefix configures the adapter to parse a prefix from stdlib log events. If +// you provide a non-empty prefix to the stdlib logger, then your should provide +// that same prefix to the adapter via this option. +// +// By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to +// true if you want to include the parsed prefix in the msg. +func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption { + return func(a *StdlibAdapter) { a.prefix = prefix; a.joinPrefixToMsg = joinPrefixToMsg } +} + +// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed +// logger. It's designed to be passed to log.SetOutput. +func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { + a := StdlibAdapter{ + Logger: logger, + timestampKey: "ts", + fileKey: "caller", + messageKey: "msg", + } + for _, option := range options { + option(&a) + } + return a +} + +func (a StdlibAdapter) Write(p []byte) (int, error) { + p = a.handlePrefix(p) + + result := subexps(p) + keyvals := []interface{}{} + var timestamp string + if date, ok := result["date"]; ok && date != "" { + timestamp = date + } + if time, ok := result["time"]; ok && time != "" { + if timestamp != "" { + timestamp += " " + } + timestamp += time + } + if timestamp != "" { + keyvals = append(keyvals, a.timestampKey, timestamp) + } + if file, ok := result["file"]; ok && file != "" { + keyvals = append(keyvals, a.fileKey, file) + } + if msg, ok := result["msg"]; ok { + msg = a.handleMessagePrefix(msg) + keyvals = append(keyvals, a.messageKey, msg) + } + if err := a.Logger.Log(keyvals...); err != nil { + return 0, err + } + return len(p), nil +} + +func (a StdlibAdapter) handlePrefix(p []byte) []byte { + if a.prefix != "" { + p = bytes.TrimPrefix(p, []byte(a.prefix)) + } + return p +} + +func (a StdlibAdapter) handleMessagePrefix(msg string) string { + if a.prefix == "" { + return msg + } + + msg = strings.TrimPrefix(msg, a.prefix) + if a.joinPrefixToMsg { + msg = a.prefix + msg + } + return msg +} + +const ( + logRegexpDate = `(?P[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` + logRegexpTime = `(?P