evolver-tools 32.0.0__tar.gz → 33.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-32.0.0/src/evolver_tools.egg-info → evolver_tools-33.0.0}/PKG-INFO +2 -2
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/pyproject.toml +2 -2
- evolver_tools-33.0.0/src/evolver_tools/vendor/cron_check.py +466 -0
- evolver_tools-33.0.0/src/evolver_tools/vendor/csv_pretty.py +323 -0
- evolver_tools-33.0.0/src/evolver_tools/vendor/diff_yaml.py +202 -0
- evolver_tools-33.0.0/src/evolver_tools/vendor/hexdec.py +161 -0
- evolver_tools-33.0.0/src/evolver_tools/vendor/humanize.py +186 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0/src/evolver_tools.egg-info}/PKG-INFO +2 -2
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools.egg-info/SOURCES.txt +5 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/LICENSE +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/README.md +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/setup.cfg +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/autoreg.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/agent_b_tool.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ansi_strip.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/api_tester.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ascii_banner.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ascii_gen.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/audit_log.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/b64/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/b64/b64.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/backup.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/banner/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/banner/banner.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/base32.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/base58.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/bookmark.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cal_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cal_tool/cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/calendar_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/case_convert.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cert_check.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cert_info.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/changelog_gen/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/changelog_gen/changelog_gen.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/changelog_gen.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/chart_cli/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/chart_cli/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/checksum_dir.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/clipboard/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/clipboard/clipboard.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/code_auditor.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/code_review.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/code_stats.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/colorize.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/colors/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/colors/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/config_validator.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/config_vault.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cowsay.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crc_check.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cron/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cron/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cron_pretty.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crontab_helper.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crypto_box.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crypto_price.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv2json.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_dedup.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_filter.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_head.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_merge.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_slice.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_sort.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/analyzer.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_to_table.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_validate.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_view.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/date_diff.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/db_mate.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/db_schema.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dedup_files.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dep_graph.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dev_dashboard.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dice_roll.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_csv.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_files.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_tool/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dirsize/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/disk_cleanup.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/disk_usage/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/disk_usage/disk_usage.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dns_lookup.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/docker_helper.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dt_convert.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_diff.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_manager.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_sorter.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_template.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/envcheck/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/epoch.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/excel2csv.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/factor.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ff/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ff/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/figlet_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/figlet_tool.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_encrypt.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_find.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_joiner.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_patch.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_splitter.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_type.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_watch.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/find_dups/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/find_dups/cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/find_empty.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/firewall_rule.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/fmt/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/fmt/fmt.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/fold.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/geo_ip.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/git_branch_cleaner.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/git_log_pretty.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/git_stats.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/gzip_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hash_check.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hash_file.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hashsum/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hashsum/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hex_tool.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hexdump.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/html2markdown.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/html2md.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_headers.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_live/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_live/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_server.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_status.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/image_meta.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ini2json.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ini_parser/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ini_parser/ini_parser.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ip_info.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ip_location.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipcalc/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipcalc/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipinfo/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipinfo/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/join.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/joke.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jq_lite/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jq_lite/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json2csv/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json2csv/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json2ini.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_diff.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_flatten.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_keys.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_merge.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_path.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_pretty/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_pretty/json_pretty.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_schema_validate.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_sort.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_to_table.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_to_yaml.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jsonql/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jsonql/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jwt_decode.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/key_value_store.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license_cli/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license_cli/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license_cli/cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/link_check.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/log_analyzer.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/log_hawk.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/log_tail.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/mac_address.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/macrogen.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_check/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_lint.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_preview.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_to_html.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_toc.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/math_eval.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/media_studio.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/morse.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/nb/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/nb/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/net_analyzer.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/net_speed.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/network_scan.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/nl.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/note_taker.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/otp_gen.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/passgen/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/password_strength.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pdf_info.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pdf_text.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pipe_viewer.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pomodoro.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/port_scan.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/portcheck/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/portcheck/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pr_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pr_tool/pr_tool.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/process_kill.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/progress_bar.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/project_doctor/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/project_doctor/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qc_calc.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qc_report.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qc_sample.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qr_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qrcode.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/quote.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/quote_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/quote_tool/quote.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/rainbow.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/random.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/random_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/random_string.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/reminder.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ren/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ren/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/replace_text.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/restore.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/rot13.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/route_trace.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/scan_open_ports.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/scan_ports.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/screen_recorder.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/screenshot_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/search_files.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/search_history.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/secret_scanner.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/seq.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/service_check.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/shuffle.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/siege_lite/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/siege_lite/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/slugify.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/smellfinder/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/smellfinder/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sort/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sort/sort.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/spinner.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/split.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/split_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/split_tool/split.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sql2csv.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sqlite_cli/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sqlite_cli/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ssh_key_gen.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ssl_check.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/stopwatch.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/subnet.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sysmon/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sysmon/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sysmon_pro.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/system_info.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/temp_cleaner.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/template.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/text_dedent.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/text_stats.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/text_wrap.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/time_duration.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timeout.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer_pro/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer_pro/timer_pro.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer_pro.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/todo_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/toml2json.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/tr.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/tree.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/treedir/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/treedir/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/tsv2csv.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uniq_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uniq_tool/uniq.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/unit_convert.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uri_encode.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/url_parser.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/urlparse_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/urlparse_tool/cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uuid_gen.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uuid_tool/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uuid_tool/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/watch.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/weather_cli.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/web_download.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/web_summary/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/web_summary/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/whois_lookup.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/wordcount/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/wordcount/__main__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/world_clock.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/xml2json.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/xml_format.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml2json/__init__.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml2json/yaml2json.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml2toml.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml_validate.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yes.py +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools.egg-info/dependency_links.txt +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools.egg-info/entry_points.txt +0 -0
- {evolver_tools-32.0.0 → evolver_tools-33.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: 33.0.0
|
|
4
|
+
Summary: 239 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 = "33.0.0"
|
|
8
|
+
description = "239 CLI tools + 9 flagship projects — one pip install"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.8"
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""cron-check — Validate and describe cron expressions.
|
|
3
|
+
|
|
4
|
+
Usage: cron-check "*/5 * * * *"
|
|
5
|
+
cron-check --describe "0 9 * * 1-5"
|
|
6
|
+
cron-check --next 5 "*/15 * * * *"
|
|
7
|
+
cron-check --validate "0 0 1 * *"
|
|
8
|
+
"""
|
|
9
|
+
import argparse
|
|
10
|
+
import calendar
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
|
|
15
|
+
TOOL_META = {
|
|
16
|
+
"name": "cron-check",
|
|
17
|
+
"func": "main",
|
|
18
|
+
"desc": "Validate and describe cron expressions",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
WEEKDAY_NAMES = [
|
|
22
|
+
"Sunday", "Monday", "Tuesday", "Wednesday",
|
|
23
|
+
"Thursday", "Friday", "Saturday",
|
|
24
|
+
]
|
|
25
|
+
MONTH_NAMES = [
|
|
26
|
+
None, "January", "February", "March", "April", "May", "June",
|
|
27
|
+
"July", "August", "September", "October", "November", "December",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
SPECIAL = {
|
|
31
|
+
"@yearly": "0 0 1 1 *",
|
|
32
|
+
"@annually": "0 0 1 1 *",
|
|
33
|
+
"@monthly": "0 0 1 * *",
|
|
34
|
+
"@weekly": "0 0 * * 0",
|
|
35
|
+
"@daily": "0 0 * * *",
|
|
36
|
+
"@midnight": "0 0 * * *",
|
|
37
|
+
"@hourly": "0 * * * *",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _parse_field(field, lo, hi, name):
|
|
42
|
+
"""Parse a single cron field. Returns set of accepted values or None if wildcard.
|
|
43
|
+
|
|
44
|
+
Raises ValueError on invalid syntax.
|
|
45
|
+
"""
|
|
46
|
+
if field == "*":
|
|
47
|
+
return None # wildcard
|
|
48
|
+
if field.startswith("*/"):
|
|
49
|
+
step = field[2:]
|
|
50
|
+
if not step.isdigit() or int(step) < 1:
|
|
51
|
+
raise ValueError(f"Invalid step in {name}: '{field}'")
|
|
52
|
+
return range(lo, hi + 1, int(step))
|
|
53
|
+
values = set()
|
|
54
|
+
for part in field.split(","):
|
|
55
|
+
part = part.strip()
|
|
56
|
+
if not part:
|
|
57
|
+
raise ValueError(f"Empty element in {name}: '{field}'")
|
|
58
|
+
if "-" in part:
|
|
59
|
+
parts = part.split("-", 1)
|
|
60
|
+
if len(parts) != 2:
|
|
61
|
+
raise ValueError(f"Invalid range in {name}: '{part}'")
|
|
62
|
+
a_str, b_str = parts
|
|
63
|
+
if not a_str.isdigit() or not b_str.isdigit():
|
|
64
|
+
raise ValueError(f"Non-numeric range in {name}: '{part}'")
|
|
65
|
+
a, b = int(a_str), int(b_str)
|
|
66
|
+
if a > b:
|
|
67
|
+
raise ValueError(f"Range inverted in {name}: '{part}'")
|
|
68
|
+
for v in range(a, b + 1):
|
|
69
|
+
if v < lo or v > hi:
|
|
70
|
+
raise ValueError(f"Value {v} out of range [{lo}-{hi}] in {name}")
|
|
71
|
+
values.add(v)
|
|
72
|
+
elif part.isdigit():
|
|
73
|
+
v = int(part)
|
|
74
|
+
if v < lo or v > hi:
|
|
75
|
+
raise ValueError(f"Value {v} out of range [{lo}-{hi}] in {name}")
|
|
76
|
+
values.add(v)
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(f"Invalid token in {name}: '{part}'")
|
|
79
|
+
return values
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def parse_cron(expr):
|
|
83
|
+
"""Parse a 5-field cron expression. Returns (minute, hour, dom, month, dow)
|
|
84
|
+
where each is a set of values or None for wildcard."""
|
|
85
|
+
stripped = expr.strip()
|
|
86
|
+
if stripped in SPECIAL:
|
|
87
|
+
stripped = SPECIAL[stripped]
|
|
88
|
+
|
|
89
|
+
fields = stripped.split()
|
|
90
|
+
if len(fields) != 5:
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"Expected 5 fields, got {len(fields)}. "
|
|
93
|
+
"Format: minute hour day-of-month month day-of-week"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
minute = _parse_field(fields[0], 0, 59, "minute")
|
|
97
|
+
hour = _parse_field(fields[1], 0, 23, "hour")
|
|
98
|
+
dom = _parse_field(fields[2], 1, 31, "day-of-month")
|
|
99
|
+
month = _parse_field(fields[3], 1, 12, "month")
|
|
100
|
+
dow = _parse_field(fields[4], 0, 7, "day-of-week")
|
|
101
|
+
|
|
102
|
+
# Normalize DOW: 7 -> 0 (both mean Sunday)
|
|
103
|
+
if dow is not None:
|
|
104
|
+
dow = {0 if d == 7 else d for d in dow}
|
|
105
|
+
|
|
106
|
+
return minute, hour, dom, month, dow
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _describe_value(val, lo, hi, singular, plural, range_map):
|
|
110
|
+
"""Describe a single field value."""
|
|
111
|
+
if val is None:
|
|
112
|
+
return f"every {plural}" if lo == 0 or lo is None else f"every {singular}"
|
|
113
|
+
if isinstance(val, range) or hasattr(val, "__iter__") and not isinstance(val, (set, frozenset)):
|
|
114
|
+
# step range
|
|
115
|
+
lst = list(val)
|
|
116
|
+
if len(lst) > 1:
|
|
117
|
+
step = lst[1] - lst[0]
|
|
118
|
+
return f"every {step} {plural}"
|
|
119
|
+
if isinstance(val, set):
|
|
120
|
+
# Check if it's a range like 1-5
|
|
121
|
+
if len(val) > 1:
|
|
122
|
+
sorted_vals = sorted(val)
|
|
123
|
+
if sorted_vals == list(range(sorted_vals[0], sorted_vals[-1] + 1)):
|
|
124
|
+
lo_v, hi_v = sorted_vals[0], sorted_vals[-1]
|
|
125
|
+
if range_map and lo_v in range_map and hi_v in range_map:
|
|
126
|
+
return f"{range_map[lo_v]}-{range_map[hi_v]}"
|
|
127
|
+
return f"{lo_v}-{hi_v}"
|
|
128
|
+
items = [range_map.get(v, str(v)) if range_map else str(v) for v in sorted_vals]
|
|
129
|
+
return ", ".join(items)
|
|
130
|
+
return str(val)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _field_desc(field, lo, hi, singular, plural, range_map=None):
|
|
134
|
+
"""Describe a single cron field as English."""
|
|
135
|
+
if field is None:
|
|
136
|
+
return f"every {singular}" if lo == 0 else f"every {singular}"
|
|
137
|
+
|
|
138
|
+
if isinstance(field, range):
|
|
139
|
+
step = field[1] - field[0]
|
|
140
|
+
if step == 1:
|
|
141
|
+
return f"every {singular}"
|
|
142
|
+
return f"every {step} {plural}"
|
|
143
|
+
|
|
144
|
+
sorted_vals = sorted(field)
|
|
145
|
+
if len(sorted_vals) == 1:
|
|
146
|
+
v = sorted_vals[0]
|
|
147
|
+
if range_map and v in range_map:
|
|
148
|
+
name = range_map[v]
|
|
149
|
+
if lo == 1: # day-of-month
|
|
150
|
+
suffix = "st" if v == 1 else "nd" if v == 2 else "rd" if v == 3 else "th"
|
|
151
|
+
return f"on the {v}{suffix}"
|
|
152
|
+
return f"on {name}"
|
|
153
|
+
return str(v)
|
|
154
|
+
|
|
155
|
+
# Multiple values
|
|
156
|
+
if sorted_vals == list(range(sorted_vals[0], sorted_vals[-1] + 1)):
|
|
157
|
+
lo_v, hi_v = sorted_vals[0], sorted_vals[-1]
|
|
158
|
+
if range_map:
|
|
159
|
+
if lo_v == 1:
|
|
160
|
+
# Check if all values covered = "every"
|
|
161
|
+
if sorted_vals == list(range(lo, hi + 1)):
|
|
162
|
+
return f"every {singular}"
|
|
163
|
+
return f"every {singular} ({lo_v}-{hi_v})"
|
|
164
|
+
return f"every {singular} ({lo_v}-{hi_v})"
|
|
165
|
+
|
|
166
|
+
# Mixed values
|
|
167
|
+
items = []
|
|
168
|
+
for v in sorted_vals:
|
|
169
|
+
if range_map and v in range_map:
|
|
170
|
+
items.append(range_map[v])
|
|
171
|
+
else:
|
|
172
|
+
items.append(str(v))
|
|
173
|
+
return ", ".join(items)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def describe(expr):
|
|
177
|
+
"""Produce a human-readable description of a cron expression."""
|
|
178
|
+
minute, hour, dom, month, dow = parse_cron(expr)
|
|
179
|
+
|
|
180
|
+
# Build parts
|
|
181
|
+
parts = []
|
|
182
|
+
|
|
183
|
+
# Time specification
|
|
184
|
+
time_parts = []
|
|
185
|
+
|
|
186
|
+
if minute is None and hour is None:
|
|
187
|
+
time_parts.append("every minute")
|
|
188
|
+
elif hour is None:
|
|
189
|
+
# Every hour, specific minute(s)
|
|
190
|
+
min_desc = _field_desc(minute, 0, 59, "minute", "minutes")
|
|
191
|
+
if minute is not None and len(minute) == 1:
|
|
192
|
+
v = list(minute)[0]
|
|
193
|
+
time_parts.append(f"at minute {v} of every hour")
|
|
194
|
+
else:
|
|
195
|
+
time_parts.append(f"{min_desc} of every hour")
|
|
196
|
+
elif minute is None:
|
|
197
|
+
# Every minute of specific hour
|
|
198
|
+
hour_desc = _field_desc(hour, 0, 23, "hour", "hours")
|
|
199
|
+
time_parts.append(f"every minute past {hour_desc}")
|
|
200
|
+
else:
|
|
201
|
+
# Specific time
|
|
202
|
+
min_desc = _field_desc(minute, 0, 59, "minute", "minutes")
|
|
203
|
+
hour_desc = _field_desc(hour, 0, 23, "hour", "hours")
|
|
204
|
+
|
|
205
|
+
if len(hour) == 1 and len(minute) == 1:
|
|
206
|
+
h = list(hour)[0]
|
|
207
|
+
m = list(minute)[0]
|
|
208
|
+
ampm = "AM" if h < 12 else "PM"
|
|
209
|
+
h12 = h if h <= 12 else h - 12
|
|
210
|
+
if h12 == 0:
|
|
211
|
+
h12 = 12
|
|
212
|
+
time_parts.append(f"at {h12}:{m:02d} {ampm}")
|
|
213
|
+
elif len(hour) == 1:
|
|
214
|
+
h = list(hour)[0]
|
|
215
|
+
ampm = "AM" if h < 12 else "PM"
|
|
216
|
+
h12 = h if h <= 12 else h - 12
|
|
217
|
+
if h12 == 0:
|
|
218
|
+
h12 = 12
|
|
219
|
+
time_parts.append(f"at minute {min_desc} past {h12}:00 {ampm}")
|
|
220
|
+
else:
|
|
221
|
+
time_parts.append(f"at {min_desc} past {hour_desc}")
|
|
222
|
+
|
|
223
|
+
# Day of week
|
|
224
|
+
dow_desc = ""
|
|
225
|
+
if dow is not None and dom is None:
|
|
226
|
+
sorted_dow = sorted(dow)
|
|
227
|
+
if sorted_dow == list(range(sorted_dow[0], sorted_dow[-1] + 1)):
|
|
228
|
+
lo, hi = sorted_dow[0], sorted_dow[-1]
|
|
229
|
+
if lo == 1 and hi == 5:
|
|
230
|
+
dow_desc = ", Monday through Friday"
|
|
231
|
+
elif lo == 0 and hi == 6:
|
|
232
|
+
dow_desc = ", every day of the week"
|
|
233
|
+
elif lo == 0 and hi == 5:
|
|
234
|
+
dow_desc = ", Monday through Saturday"
|
|
235
|
+
else:
|
|
236
|
+
dow_desc = f", {WEEKDAY_NAMES[lo]} through {WEEKDAY_NAMES[hi]}"
|
|
237
|
+
elif len(sorted_dow) == 1:
|
|
238
|
+
dow_desc = f", on {WEEKDAY_NAMES[sorted_dow[0]]}"
|
|
239
|
+
else:
|
|
240
|
+
names = [WEEKDAY_NAMES[d] for d in sorted_dow]
|
|
241
|
+
dow_desc = f", on {' and '.join(n for n in names)}"
|
|
242
|
+
|
|
243
|
+
# Day of month
|
|
244
|
+
dom_desc = ""
|
|
245
|
+
if dom is not None:
|
|
246
|
+
sorted_dom = sorted(dom)
|
|
247
|
+
if len(sorted_dom) == 1:
|
|
248
|
+
v = sorted_dom[0]
|
|
249
|
+
suffix = "st" if v == 1 else "nd" if v == 2 else "rd" if v == 3 else "th"
|
|
250
|
+
dom_desc = f" on the {v}{suffix}"
|
|
251
|
+
elif sorted_dom == list(range(sorted_dom[0], sorted_dom[-1] + 1)):
|
|
252
|
+
lo, hi = sorted_dom[0], sorted_dom[-1]
|
|
253
|
+
if lo == 1 and hi == 31:
|
|
254
|
+
dom_desc = " every day"
|
|
255
|
+
else:
|
|
256
|
+
dom_desc = f" on days {lo}-{hi}"
|
|
257
|
+
else:
|
|
258
|
+
items = []
|
|
259
|
+
for v in sorted_dom:
|
|
260
|
+
suffix = "st" if v == 1 else "nd" if v == 2 else "rd" if v == 3 else "th"
|
|
261
|
+
items.append(f"{v}{suffix}")
|
|
262
|
+
dom_desc = f" on {' and '.join(items)}"
|
|
263
|
+
|
|
264
|
+
# Month
|
|
265
|
+
month_desc = ""
|
|
266
|
+
if month is not None:
|
|
267
|
+
sorted_m = sorted(month)
|
|
268
|
+
if len(sorted_m) == 1:
|
|
269
|
+
month_desc = f" in {MONTH_NAMES[sorted_m[0]]}"
|
|
270
|
+
elif sorted_m == list(range(sorted_m[0], sorted_m[-1] + 1)):
|
|
271
|
+
lo, hi = sorted_m[0], sorted_m[-1]
|
|
272
|
+
if lo == 1 and hi == 12:
|
|
273
|
+
month_desc = " every month"
|
|
274
|
+
else:
|
|
275
|
+
month_desc = f" from {MONTH_NAMES[lo]} to {MONTH_NAMES[hi]}"
|
|
276
|
+
else:
|
|
277
|
+
names = [MONTH_NAMES[m] for m in sorted_m]
|
|
278
|
+
month_desc = f" in {' and '.join(n for n in names)}"
|
|
279
|
+
|
|
280
|
+
# Combine
|
|
281
|
+
result = "".join(time_parts) + dom_desc + month_desc + dow_desc
|
|
282
|
+
|
|
283
|
+
# Clean up
|
|
284
|
+
if result.startswith("every minute every hour"):
|
|
285
|
+
result = "every minute" + result[len("every minute every hour"):]
|
|
286
|
+
|
|
287
|
+
return result.strip().capitalize()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _matches(field, value):
|
|
291
|
+
"""Check if a value matches a parsed field."""
|
|
292
|
+
if field is None:
|
|
293
|
+
return True
|
|
294
|
+
return value in field
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def next_times(expr, count):
|
|
298
|
+
"""Calculate the next N execution times for a cron expression."""
|
|
299
|
+
minute, hour, dom, month, dow = parse_cron(expr)
|
|
300
|
+
|
|
301
|
+
now = datetime.now()
|
|
302
|
+
# Start from the next minute
|
|
303
|
+
current = now.replace(second=0, microsecond=0) + timedelta(minutes=1)
|
|
304
|
+
|
|
305
|
+
results = []
|
|
306
|
+
max_iterations = 100000 # safety valve
|
|
307
|
+
iterations = 0
|
|
308
|
+
|
|
309
|
+
while len(results) < count and iterations < max_iterations:
|
|
310
|
+
iterations += 1
|
|
311
|
+
|
|
312
|
+
# Check month
|
|
313
|
+
if not _matches(month, current.month):
|
|
314
|
+
# Skip to next month
|
|
315
|
+
if current.month == 12:
|
|
316
|
+
current = current.replace(year=current.year + 1, month=1, day=1, hour=0, minute=0)
|
|
317
|
+
else:
|
|
318
|
+
current = current.replace(month=current.month + 1, day=1, hour=0, minute=0)
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
# Check day of month
|
|
322
|
+
if not _matches(dom, current.day):
|
|
323
|
+
current += timedelta(days=1)
|
|
324
|
+
current = current.replace(hour=0, minute=0)
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
# Check day of week
|
|
328
|
+
if not _matches(dow, current.weekday()):
|
|
329
|
+
current += timedelta(days=1)
|
|
330
|
+
current = current.replace(hour=0, minute=0)
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
# Check hour
|
|
334
|
+
if not _matches(hour, current.hour):
|
|
335
|
+
current += timedelta(hours=1)
|
|
336
|
+
current = current.replace(minute=0)
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
# Check minute
|
|
340
|
+
if not _matches(minute, current.minute):
|
|
341
|
+
current += timedelta(minutes=1)
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
# Match!
|
|
345
|
+
results.append(current)
|
|
346
|
+
current += timedelta(minutes=1)
|
|
347
|
+
|
|
348
|
+
return results
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def validate(expr):
|
|
352
|
+
"""Validate a cron expression. Returns True if valid, False otherwise."""
|
|
353
|
+
try:
|
|
354
|
+
parse_cron(expr)
|
|
355
|
+
return True
|
|
356
|
+
except (ValueError, IndexError):
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def main():
|
|
361
|
+
parser = argparse.ArgumentParser(
|
|
362
|
+
description="Validate and describe cron expressions.",
|
|
363
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
364
|
+
epilog=(
|
|
365
|
+
"Examples:\n"
|
|
366
|
+
' cron-check "*/5 * * * *"\n'
|
|
367
|
+
' cron-check -d "0 9 * * 1-5"\n'
|
|
368
|
+
' cron-check --next 5 "*/15 * * * *"\n'
|
|
369
|
+
' cron-check --validate "0 0 1 * *"\n'
|
|
370
|
+
'\n'
|
|
371
|
+
"Exit codes:\n"
|
|
372
|
+
" 0 expression is valid\n"
|
|
373
|
+
" 1 expression is invalid (with --validate)\n"
|
|
374
|
+
),
|
|
375
|
+
)
|
|
376
|
+
parser.add_argument("expression", nargs="?", help="Cron expression (5 fields: minute hour dom month dow)")
|
|
377
|
+
parser.add_argument("-d", "--describe", action="store_true", help="Show human-readable description")
|
|
378
|
+
parser.add_argument("--next", nargs="?", const=5, type=int, default=None,
|
|
379
|
+
help="Show next N execution times (default: 5)")
|
|
380
|
+
parser.add_argument("--validate", action="store_true", help="Just validate; exit 0 (valid) or 1 (invalid)")
|
|
381
|
+
|
|
382
|
+
args = parser.parse_args()
|
|
383
|
+
|
|
384
|
+
if not args.expression:
|
|
385
|
+
parser.print_help()
|
|
386
|
+
sys.exit(1)
|
|
387
|
+
|
|
388
|
+
expr = args.expression
|
|
389
|
+
|
|
390
|
+
# Handle special strings for validation
|
|
391
|
+
if expr.strip() in SPECIAL:
|
|
392
|
+
if args.validate:
|
|
393
|
+
sys.exit(0)
|
|
394
|
+
if args.describe:
|
|
395
|
+
name = expr.strip()
|
|
396
|
+
print(f" {name}")
|
|
397
|
+
desc_map = {
|
|
398
|
+
"@yearly": "Once a year, at midnight on January 1st",
|
|
399
|
+
"@annually": "Once a year, at midnight on January 1st",
|
|
400
|
+
"@monthly": "Once a month, at midnight on the 1st",
|
|
401
|
+
"@weekly": "Once a week, at midnight on Sunday",
|
|
402
|
+
"@daily": "Every day at midnight",
|
|
403
|
+
"@midnight": "Every day at midnight",
|
|
404
|
+
"@hourly": "Every hour at minute 0",
|
|
405
|
+
}
|
|
406
|
+
print(f" \u2192 {desc_map.get(name, '')}")
|
|
407
|
+
return
|
|
408
|
+
if args.next is not None:
|
|
409
|
+
expr = SPECIAL[expr.strip()]
|
|
410
|
+
|
|
411
|
+
# Parse and validate
|
|
412
|
+
try:
|
|
413
|
+
parsed = parse_cron(expr)
|
|
414
|
+
except ValueError as e:
|
|
415
|
+
print(f"Invalid cron expression: {e}", file=sys.stderr)
|
|
416
|
+
if args.validate:
|
|
417
|
+
sys.exit(1)
|
|
418
|
+
sys.exit(1)
|
|
419
|
+
|
|
420
|
+
if args.validate:
|
|
421
|
+
sys.exit(0)
|
|
422
|
+
|
|
423
|
+
# Resolve special strings for display
|
|
424
|
+
display_expr = expr.strip()
|
|
425
|
+
for key, val in SPECIAL.items():
|
|
426
|
+
if val == display_expr and key != display_expr:
|
|
427
|
+
break
|
|
428
|
+
else:
|
|
429
|
+
key = None
|
|
430
|
+
|
|
431
|
+
# Describe mode
|
|
432
|
+
if args.describe:
|
|
433
|
+
print(f" {expr}")
|
|
434
|
+
if key:
|
|
435
|
+
print(f" ({key})")
|
|
436
|
+
print(f" \u2192 {describe(expr)}")
|
|
437
|
+
return
|
|
438
|
+
|
|
439
|
+
# Next times mode
|
|
440
|
+
if args.next is not None:
|
|
441
|
+
count = args.next
|
|
442
|
+
times = next_times(expr, min(count, 100))
|
|
443
|
+
if not times:
|
|
444
|
+
print(f"No future execution times found for: {expr}", file=sys.stderr)
|
|
445
|
+
sys.exit(1)
|
|
446
|
+
print(f" Next {len(times)} execution time{'s' if len(times) != 1 else ''} for:")
|
|
447
|
+
print(f" {expr}")
|
|
448
|
+
if key:
|
|
449
|
+
print(f" ({key})")
|
|
450
|
+
print()
|
|
451
|
+
for when in times:
|
|
452
|
+
print(f" \u2022 {when.strftime('%Y-%m-%d %H:%M %A')}")
|
|
453
|
+
|
|
454
|
+
if len(times) < count:
|
|
455
|
+
print()
|
|
456
|
+
print(f" (Only {len(times)} found within search range)")
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
# Default: validate + describe
|
|
460
|
+
print(f" Expression: {expr}")
|
|
461
|
+
print(f" Valid: \u2713 yes")
|
|
462
|
+
print(f" Schedule: {describe(expr)}")
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
if __name__ == "__main__":
|
|
466
|
+
main()
|