evolver-tools 3.0.0__tar.gz → 5.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evolver_tools-3.0.0/src/evolver_tools.egg-info → evolver_tools-5.0.0}/PKG-INFO +2 -2
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/pyproject.toml +2 -2
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/cli.py +1 -1
- evolver_tools-5.0.0/src/evolver_tools/vendor/code_auditor.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/config_vault.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/crypto_box.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/crypto_price.py +108 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/db_mate.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/dev_dashboard.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/dice_roll.py +99 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/ip_location.py +108 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/log_hawk.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/media_studio.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/net_analyzer.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/sysmon_pro.py +9 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/text_stats.py +119 -0
- evolver_tools-5.0.0/src/evolver_tools/vendor/unit_convert.py +137 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0/src/evolver_tools.egg-info}/PKG-INFO +2 -2
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools.egg-info/SOURCES.txt +14 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/LICENSE +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/README.md +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/setup.cfg +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/autoreg.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/agent_b_tool.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/api_tester.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ascii_gen.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/audit_log.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/b64/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/b64/b64.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/backup.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/banner/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/banner/banner.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/banner.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/bookmark.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/cal_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/cal_tool/cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/cert_check.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/changelog_gen/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/changelog_gen/changelog_gen.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/changelog_gen.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/chart_cli/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/chart_cli/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/checksum_dir.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/clipboard/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/clipboard/clipboard.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/colorize.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/colors/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/colors/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/config_validator.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/cron/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/cron/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/crontab_helper.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/csv_stats/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/csv_stats/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/csv_stats/analyzer.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/csv_stats/cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/db_schema.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/dep_graph.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/diff_csv.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/diff_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/diff_tool/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/dirsize/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/disk_usage/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/disk_usage/disk_usage.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/dt_convert.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/env_manager.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/envcheck/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/excel2csv.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ff/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ff/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/figlet_cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/figlet_tool.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/file_encrypt.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/find_dups/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/find_dups/cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/firewall_rule.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/fmt/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/fmt/fmt.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/git_branch_cleaner.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/hashsum/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/hashsum/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/html2markdown.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/html2md.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/http_live/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/http_live/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/image_meta.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ini_parser/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ini_parser/ini_parser.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ipcalc/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ipcalc/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ipinfo/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ipinfo/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/join.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/joke.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/jq_lite/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/jq_lite/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/json2csv/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/json2csv/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/json_pretty/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/json_pretty/json_pretty.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/jsonql/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/jsonql/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/license_cli/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/license_cli/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/license_cli/cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/log_analyzer.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/log_tail.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/markdown_check/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/morse.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/nb/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/nb/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/net_speed.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/note_taker.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/otp_gen.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/passgen/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/password_strength.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/portcheck/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/portcheck/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/pr_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/pr_tool/pr_tool.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/process_kill.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/progress_bar.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/project_doctor/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/project_doctor/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/qrcode.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/quote_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/quote_tool/quote.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/rainbow.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/reminder.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ren/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ren/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/restore.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/scan_ports.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/service_check.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/shuffle.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/siege_lite/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/siege_lite/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/smellfinder/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/smellfinder/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/sort/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/sort/sort.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/spinner.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/split.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/split_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/split_tool/split.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/sql2csv.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/sqlite_cli/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/sqlite_cli/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ssh_key_gen.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/ssl_check.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/stopwatch.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/sysmon/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/sysmon/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/timer/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/timer_pro/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/timer_pro/timer_pro.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/timer_pro.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/todo_cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/treedir/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/treedir/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/uniq_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/uniq_tool/uniq.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/urlparse_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/urlparse_tool/cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/uuid_tool/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/uuid_tool/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/weather_cli.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/web_summary/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/web_summary/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/wordcount/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/wordcount/__main__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/xml2json.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/yaml2json/__init__.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools/vendor/yaml2json/yaml2json.py +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools.egg-info/dependency_links.txt +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools.egg-info/entry_points.txt +0 -0
- {evolver_tools-3.0.0 → evolver_tools-5.0.0}/src/evolver_tools.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evolver-tools
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 5.0.0
|
|
4
|
+
Summary: 105 essential CLI tools + 9 flagship projects — one pip install
|
|
5
5
|
Author: EVOLVER
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://evolver-dev.github.io/evolver-tools
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "evolver-tools"
|
|
7
|
-
version = "
|
|
8
|
-
description = "
|
|
7
|
+
version = "5.0.0"
|
|
8
|
+
description = "105 essential CLI tools + 9 flagship projects — one pip install"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.8"
|
|
@@ -14,7 +14,7 @@ from evolver_tools.autoreg import auto_discover
|
|
|
14
14
|
def list_tools():
|
|
15
15
|
"""Display all available tools."""
|
|
16
16
|
tools = auto_discover()
|
|
17
|
-
print(f'\x1b[1;36m===== EVOLVER Tools
|
|
17
|
+
print(f'\x1b[1;36m===== EVOLVER Tools v5.0.0 =====\x1b[0m')
|
|
18
18
|
print()
|
|
19
19
|
for name, info in sorted(tools.items()):
|
|
20
20
|
print(f' \033[1;33m{name:<18}\033[0m {info["desc"]}')
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""code-auditor — Code analysis suite (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "code-auditor", "desc": "Code analysis: complexity, security, style, deps — CLI+TUI", "func": "main"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from code_auditor.cli import main
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/code-auditor/src")
|
|
9
|
+
from code_auditor.cli import main
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""config-vault — Configuration security toolkit (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "config-vault", "desc": "Config security: validate, encrypt, scan secrets — CLI+TUI", "func": "main"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from config_vault.cli import main
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/config-vault/src")
|
|
9
|
+
from config_vault.cli import main
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""crypto-box — Security toolkit (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "crypto-box", "desc": "Security: encrypt, hash, password, OTP, SSL — CLI+TUI", "func": "cli_main"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from crypto_box.cli import cli_main
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/crypto-box/src")
|
|
9
|
+
from crypto_box.cli import cli_main
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Cryptocurrency price ticker using CoinGecko free API.
|
|
4
|
+
|
|
5
|
+
Fetches real-time cryptocurrency prices in a specified fiat currency.
|
|
6
|
+
Uses only Python stdlib (urllib, json, sys) — zero external dependencies.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python -m evolver_tools.vendor.crypto_price --coin bitcoin
|
|
10
|
+
python -m evolver_tools.vendor.crypto_price --coin ethereum --currency eur
|
|
11
|
+
python -m evolver_tools.vendor.crypto_price --list
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
import urllib.request
|
|
18
|
+
import urllib.error
|
|
19
|
+
|
|
20
|
+
TOOL_META = {
|
|
21
|
+
'name': 'crypto-price',
|
|
22
|
+
'func': 'main',
|
|
23
|
+
'desc': 'Cryptocurrency price ticker (CoinGecko)',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
COINGECKO_SIMPLE_URL = 'https://api.coingecko.com/api/v3/simple/price'
|
|
27
|
+
COINGECKO_LIST_URL = 'https://api.coingecko.com/api/v3/coins/list'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _api_get(url: str) -> dict:
|
|
31
|
+
"""Make a GET request to the CoinGecko API and return parsed JSON."""
|
|
32
|
+
req = urllib.request.Request(url, headers={'User-Agent': 'crypto-price/1.0'})
|
|
33
|
+
try:
|
|
34
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
35
|
+
return json.loads(resp.read().decode('utf-8'))
|
|
36
|
+
except urllib.error.HTTPError as e:
|
|
37
|
+
msg = e.read().decode('utf-8', errors='replace') if e.fp else ''
|
|
38
|
+
print(f'HTTP error {e.code}: {msg}', file=sys.stderr)
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
except urllib.error.URLError as e:
|
|
41
|
+
print(f'Network error: {e.reason}', file=sys.stderr)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
except (json.JSONDecodeError, OSError, ValueError) as e:
|
|
44
|
+
print(f'Error parsing response: {e}', file=sys.stderr)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def list_coins() -> None:
|
|
49
|
+
"""Fetch and display all available coins from CoinGecko."""
|
|
50
|
+
data = _api_get(COINGECKO_LIST_URL)
|
|
51
|
+
print(f'Total coins listed: {len(data)}\n')
|
|
52
|
+
# Show first 50 as a sample; user can filter with grep
|
|
53
|
+
for coin in data[:50]:
|
|
54
|
+
print(f'{coin["id"]:40s} {coin["symbol"]:10s} {coin["name"]}')
|
|
55
|
+
if len(data) > 50:
|
|
56
|
+
print(f'\n... and {len(data) - 50} more. Pipe through grep or use --coin <id>.')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_price(coin: str, currency: str) -> None:
|
|
60
|
+
"""Fetch and display the price for a single coin."""
|
|
61
|
+
url = f'{COINGECKO_SIMPLE_URL}?ids={coin}&vs_currencies={currency}'
|
|
62
|
+
data = _api_get(url)
|
|
63
|
+
if coin not in data:
|
|
64
|
+
print(
|
|
65
|
+
f'Coin "{coin}" not found. Use --list to see available coins.',
|
|
66
|
+
file=sys.stderr,
|
|
67
|
+
)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
if currency not in data[coin]:
|
|
70
|
+
print(
|
|
71
|
+
f'Currency "{currency}" not available for {coin}.',
|
|
72
|
+
file=sys.stderr,
|
|
73
|
+
)
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
price = data[coin][currency]
|
|
76
|
+
print(f'{coin}: {price:.8f} {currency.upper()}')
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main() -> None:
|
|
80
|
+
"""Parse arguments and run the appropriate action."""
|
|
81
|
+
parser = argparse.ArgumentParser(
|
|
82
|
+
description='Cryptocurrency price ticker (CoinGecko)',
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
'--coin',
|
|
86
|
+
default='bitcoin',
|
|
87
|
+
help='Coin ID to look up (default: bitcoin)',
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
'--currency',
|
|
91
|
+
default='usd',
|
|
92
|
+
help='Fiat currency symbol (default: usd)',
|
|
93
|
+
)
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
'--list',
|
|
96
|
+
action='store_true',
|
|
97
|
+
help='List all available coins and exit',
|
|
98
|
+
)
|
|
99
|
+
args = parser.parse_args()
|
|
100
|
+
|
|
101
|
+
if args.list:
|
|
102
|
+
list_coins()
|
|
103
|
+
else:
|
|
104
|
+
get_price(args.coin, args.currency)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == '__main__':
|
|
108
|
+
main()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""db-mate — Database manager (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "db-mate", "desc": "Database management: SQLite browser, schema, queries — CLI+TUI", "func": "entry"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from db_mate.cli import entry
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/db-mate/src")
|
|
9
|
+
from db_mate.cli import entry
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""dev-dashboard — Developer dashboard (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "dev-dashboard", "desc": "Developer dashboard: git, system, ports, processes — CLI+TUI", "func": "main"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from dev_dashboard.cli import main
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/dev-dashboard/src")
|
|
9
|
+
from dev_dashboard.cli import main
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Zero-dependency CLI dice roller (d4, d6, d8, d10, d12, d20, d100)."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import random
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
TOOL_META = {
|
|
9
|
+
'name': 'dice-roll',
|
|
10
|
+
'func': 'main',
|
|
11
|
+
'desc': 'Dice roller (d4, d6, d8, d10, d12, d20, d100)',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_dice_spec(spec: str) -> tuple[int, int]:
|
|
16
|
+
"""Parse a dice spec like '2d6' into (count, sides).
|
|
17
|
+
|
|
18
|
+
Raises ValueError on invalid format or unsupported die type.
|
|
19
|
+
"""
|
|
20
|
+
if 'd' not in spec:
|
|
21
|
+
raise ValueError(f"Invalid dice spec '{spec}': missing 'd' (e.g. 2d6)")
|
|
22
|
+
|
|
23
|
+
parts = spec.split('d', maxsplit=1)
|
|
24
|
+
count_str, sides_str = parts
|
|
25
|
+
|
|
26
|
+
if not sides_str:
|
|
27
|
+
raise ValueError(f"Invalid dice spec '{spec}': no sides after 'd'")
|
|
28
|
+
|
|
29
|
+
count = int(count_str) if count_str else 1
|
|
30
|
+
sides = int(sides_str)
|
|
31
|
+
|
|
32
|
+
valid_sides = {4, 6, 8, 10, 12, 20, 100}
|
|
33
|
+
if sides not in valid_sides:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"Unsupported die type d{sides}. Supported: d4, d6, d8, d10, d12, d20, d100"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if count < 1:
|
|
39
|
+
raise ValueError(f"Number of dice must be at least 1, got {count}")
|
|
40
|
+
|
|
41
|
+
return count, sides
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def roll_dice(count: int, sides: int) -> list[int]:
|
|
45
|
+
"""Roll `count` dice with `sides` sides, returning a list of results."""
|
|
46
|
+
return [random.randint(1, sides) for _ in range(count)]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def format_results(results: list[int], sides: int) -> str:
|
|
50
|
+
"""Format a roll result as a human-readable string."""
|
|
51
|
+
total = sum(results)
|
|
52
|
+
parts = ', '.join(str(r) for r in results)
|
|
53
|
+
label = f"d{sides}"
|
|
54
|
+
return f"{label}: [{parts}] = {total}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main(argv: list[str] | None = None) -> int:
|
|
58
|
+
"""CLI entry point. Parse args, roll dice, print results."""
|
|
59
|
+
parser = argparse.ArgumentParser(
|
|
60
|
+
description='Dice roller — roll any supported die type.',
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
'--dice',
|
|
64
|
+
type=str,
|
|
65
|
+
default='1d6',
|
|
66
|
+
help='Dice to roll, e.g. 2d6, 1d20, 3d10 (default: 1d6)',
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
'--rolls',
|
|
70
|
+
type=int,
|
|
71
|
+
default=1,
|
|
72
|
+
help='Number of times to roll (default: 1)',
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
args = parser.parse_args(argv)
|
|
76
|
+
|
|
77
|
+
if args.rolls < 1:
|
|
78
|
+
print(f"error: --rolls must be at least 1, got {args.rolls}", file=sys.stderr)
|
|
79
|
+
return 1
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
count, sides = parse_dice_spec(args.dice)
|
|
83
|
+
except ValueError as e:
|
|
84
|
+
print(f"error: {e}", file=sys.stderr)
|
|
85
|
+
return 1
|
|
86
|
+
|
|
87
|
+
for i in range(args.rolls):
|
|
88
|
+
results = roll_dice(count, sides)
|
|
89
|
+
line = format_results(results, sides)
|
|
90
|
+
if args.rolls > 1:
|
|
91
|
+
print(f"Roll #{i + 1}: {line}")
|
|
92
|
+
else:
|
|
93
|
+
print(line)
|
|
94
|
+
|
|
95
|
+
return 0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
sys.exit(main())
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
GeoIP location lookup using ip-api.com (free, no API key required).
|
|
4
|
+
|
|
5
|
+
Queries the public ip-api.com API to determine the geographic location
|
|
6
|
+
of an IP address. Uses only Python stdlib (urllib, json, sys) —
|
|
7
|
+
zero external dependencies.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python -m evolver_tools.vendor.ip_location
|
|
11
|
+
python -m evolver_tools.vendor.ip_location 8.8.8.8
|
|
12
|
+
python -m evolver_tools.vendor.ip_location --json 1.1.1.1
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
import urllib.error
|
|
19
|
+
import urllib.request
|
|
20
|
+
|
|
21
|
+
TOOL_META = {
|
|
22
|
+
'name': 'ip-location',
|
|
23
|
+
'func': 'main',
|
|
24
|
+
'desc': 'GeoIP location lookup (ip-api.com)',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
API_BASE = 'http://ip-api.com/json/'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _api_get(ip: str) -> dict:
|
|
31
|
+
"""Query ip-api.com for the given IP and return parsed JSON."""
|
|
32
|
+
url = API_BASE + ip
|
|
33
|
+
req = urllib.request.Request(url, headers={'User-Agent': 'ip-location/1.0'})
|
|
34
|
+
try:
|
|
35
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
36
|
+
return json.loads(resp.read().decode('utf-8'))
|
|
37
|
+
except urllib.error.HTTPError as e:
|
|
38
|
+
msg = e.read().decode('utf-8', errors='replace') if e.fp else ''
|
|
39
|
+
print(f'HTTP error {e.code}: {msg}', file=sys.stderr)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
except urllib.error.URLError as e:
|
|
42
|
+
print(f'Network error: {e.reason}', file=sys.stderr)
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
except (json.JSONDecodeError, OSError, ValueError) as e:
|
|
45
|
+
print(f'Error parsing response: {e}', file=sys.stderr)
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def lookup(ip: str, as_json: bool = False) -> None:
|
|
50
|
+
"""Look up an IP address and display the result."""
|
|
51
|
+
data = _api_get(ip)
|
|
52
|
+
|
|
53
|
+
if data.get('status') != 'success':
|
|
54
|
+
msg = data.get('message', 'unknown error')
|
|
55
|
+
print(f'Lookup failed for "{ip}": {msg}', file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
if as_json:
|
|
59
|
+
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
fields = {
|
|
63
|
+
'IP': data.get('query', ip),
|
|
64
|
+
'Country': data.get('country', ''),
|
|
65
|
+
'Country Code': data.get('countryCode', ''),
|
|
66
|
+
'Region': data.get('regionName', ''),
|
|
67
|
+
'Region Code': data.get('region', ''),
|
|
68
|
+
'City': data.get('city', ''),
|
|
69
|
+
'ZIP': data.get('zip', ''),
|
|
70
|
+
'Latitude': str(data.get('lat', '')),
|
|
71
|
+
'Longitude': str(data.get('lon', '')),
|
|
72
|
+
'Timezone': data.get('timezone', ''),
|
|
73
|
+
'ISP': data.get('isp', ''),
|
|
74
|
+
'Organization': data.get('org', ''),
|
|
75
|
+
'AS': data.get('as', ''),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
max_key_len = max(len(k) for k in fields)
|
|
79
|
+
for key, val in fields.items():
|
|
80
|
+
if val:
|
|
81
|
+
print(f'{key:>{max_key_len + 1}} {val}')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main() -> None:
|
|
85
|
+
"""Parse arguments and run the lookup."""
|
|
86
|
+
parser = argparse.ArgumentParser(
|
|
87
|
+
description='GeoIP location lookup via ip-api.com',
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
'ip',
|
|
91
|
+
nargs='?',
|
|
92
|
+
default='',
|
|
93
|
+
help='IP address to look up (default: auto, queries your own IP)',
|
|
94
|
+
)
|
|
95
|
+
parser.add_argument(
|
|
96
|
+
'--json',
|
|
97
|
+
action='store_true',
|
|
98
|
+
dest='as_json',
|
|
99
|
+
help='Output raw JSON response',
|
|
100
|
+
)
|
|
101
|
+
args = parser.parse_args()
|
|
102
|
+
|
|
103
|
+
ip = args.ip if args.ip else ''
|
|
104
|
+
lookup(ip, as_json=args.as_json)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == '__main__':
|
|
108
|
+
main()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""log-hawk — Log analysis toolkit (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "log-hawk", "desc": "Log analysis: parse, filter, tail, stats — CLI+TUI", "func": "main"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from log_hawk.cli import main
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/log-hawk/src")
|
|
9
|
+
from log_hawk.cli import main
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""media-studio — Media utility toolkit (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "media-studio", "desc": "Media utilities: QR, ASCII art, banner, Morse, figlet — CLI+TUI", "func": "main"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from media_studio.cli import main
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/media-studio/src")
|
|
9
|
+
from media_studio.cli import main
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""net-analyzer — Network analysis (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "net-analyzer", "desc": "Network analysis: ping, trace, port scan, DNS — CLI+TUI", "func": "main"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from net_analyzer.__main__ import main
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/net-analyzer/src")
|
|
9
|
+
from net_analyzer.__main__ import main
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""sysmon-pro — System monitor (CLI+TUI). Integrated from evolver-packages."""
|
|
2
|
+
TOOL_META = {"name": "sysmon-pro", "desc": "System monitor: CPU, memory, disk, processes, network — CLI+TUI", "func": "run_cli"}
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from sysmon_pro.cli import run as run_cli
|
|
6
|
+
except ImportError:
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, "/root/evolver-packages/sysmon-pro/src")
|
|
9
|
+
from sysmon_pro.cli import run as run_cli
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""text-stats — File/stdin text statistics analyzer.
|
|
3
|
+
|
|
4
|
+
Usage: text-stats [--file PATH]
|
|
5
|
+
cat file.txt | text-stats
|
|
6
|
+
|
|
7
|
+
Analyzes text and reports: word count, character count, line count,
|
|
8
|
+
sentence count, average word length, and estimated reading time.
|
|
9
|
+
Zero-dependency (stdlib only).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import argparse
|
|
14
|
+
|
|
15
|
+
# Average reading speed: ~238 words per minute (common benchmark)
|
|
16
|
+
WPM = 238
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def count_sentences(text):
|
|
20
|
+
"""Count sentences by splitting on terminal punctuation."""
|
|
21
|
+
import re
|
|
22
|
+
sentences = re.split(r'[.!?]+', text)
|
|
23
|
+
return len([s for s in sentences if s.strip()])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def count_words(text):
|
|
27
|
+
"""Count words (whitespace-delimited tokens containing letters)."""
|
|
28
|
+
words = text.split()
|
|
29
|
+
return len(words)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def count_chars(text):
|
|
33
|
+
"""Count characters including spaces, excluding newlines."""
|
|
34
|
+
return len(text)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def average_word_length(text):
|
|
38
|
+
"""Compute average word length (characters per word)."""
|
|
39
|
+
words = [w for w in text.split() if w.strip()]
|
|
40
|
+
if not words:
|
|
41
|
+
return 0.0
|
|
42
|
+
return sum(len(w) for w in words) / len(words)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def reading_time(word_count):
|
|
46
|
+
"""Estimate reading time in minutes (decimal)."""
|
|
47
|
+
return word_count / WPM
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def format_reading_time(minutes):
|
|
51
|
+
"""Format reading time as human-readable string."""
|
|
52
|
+
if minutes < 1:
|
|
53
|
+
secs = round(minutes * 60)
|
|
54
|
+
return f"{secs} sec"
|
|
55
|
+
mins = int(minutes)
|
|
56
|
+
secs = round((minutes - mins) * 60)
|
|
57
|
+
if secs == 0:
|
|
58
|
+
return f"{mins} min"
|
|
59
|
+
return f"{mins} min {secs} sec"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main():
|
|
63
|
+
parser = argparse.ArgumentParser(
|
|
64
|
+
description="Text statistics analyzer — word/char/line/sentence counts, "
|
|
65
|
+
"avg word length, reading time."
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--file", "-f",
|
|
69
|
+
type=str,
|
|
70
|
+
help="Path to input file (reads from stdin if omitted)"
|
|
71
|
+
)
|
|
72
|
+
args = parser.parse_args()
|
|
73
|
+
|
|
74
|
+
# Read input
|
|
75
|
+
if args.file:
|
|
76
|
+
try:
|
|
77
|
+
with open(args.file, "r", encoding="utf-8") as fh:
|
|
78
|
+
text = fh.read()
|
|
79
|
+
except FileNotFoundError:
|
|
80
|
+
print(f"Error: file not found: {args.file}", file=sys.stderr)
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
except IOError as e:
|
|
83
|
+
print(f"Error reading file: {e}", file=sys.stderr)
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
elif not sys.stdin.isatty():
|
|
86
|
+
text = sys.stdin.read()
|
|
87
|
+
else:
|
|
88
|
+
parser.print_help()
|
|
89
|
+
sys.exit(0)
|
|
90
|
+
|
|
91
|
+
# Compute statistics
|
|
92
|
+
chars = count_chars(text)
|
|
93
|
+
words = count_words(text)
|
|
94
|
+
lines = text.count("\n")
|
|
95
|
+
if lines == 0 and text:
|
|
96
|
+
lines = 1
|
|
97
|
+
sentences = count_sentences(text)
|
|
98
|
+
avg_wlen = average_word_length(text)
|
|
99
|
+
read_mins = reading_time(words)
|
|
100
|
+
read_str = format_reading_time(read_mins)
|
|
101
|
+
|
|
102
|
+
# Output
|
|
103
|
+
print(f"Characters: {chars}")
|
|
104
|
+
print(f"Words: {words}")
|
|
105
|
+
print(f"Lines: {lines}")
|
|
106
|
+
print(f"Sentences: {sentences}")
|
|
107
|
+
print(f"Avg word length: {avg_wlen:.2f}")
|
|
108
|
+
print(f"Reading time: {read_str} ({WPM} wpm)")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# === Auto-registration metadata ===
|
|
112
|
+
TOOL_META = {
|
|
113
|
+
"name": "text-stats",
|
|
114
|
+
"func": "main",
|
|
115
|
+
"desc": "Text statistics (words, chars, sentences, reading time)",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
main()
|