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.
Files changed (308) hide show
  1. {evolver_tools-32.0.0/src/evolver_tools.egg-info → evolver_tools-33.0.0}/PKG-INFO +2 -2
  2. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/pyproject.toml +2 -2
  3. evolver_tools-33.0.0/src/evolver_tools/vendor/cron_check.py +466 -0
  4. evolver_tools-33.0.0/src/evolver_tools/vendor/csv_pretty.py +323 -0
  5. evolver_tools-33.0.0/src/evolver_tools/vendor/diff_yaml.py +202 -0
  6. evolver_tools-33.0.0/src/evolver_tools/vendor/hexdec.py +161 -0
  7. evolver_tools-33.0.0/src/evolver_tools/vendor/humanize.py +186 -0
  8. {evolver_tools-32.0.0 → evolver_tools-33.0.0/src/evolver_tools.egg-info}/PKG-INFO +2 -2
  9. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools.egg-info/SOURCES.txt +5 -0
  10. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/LICENSE +0 -0
  11. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/README.md +0 -0
  12. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/setup.cfg +0 -0
  13. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/__init__.py +0 -0
  14. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/__main__.py +0 -0
  15. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/autoreg.py +0 -0
  16. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/cli.py +0 -0
  17. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/__init__.py +0 -0
  18. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/agent_b_tool.py +0 -0
  19. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ansi_strip.py +0 -0
  20. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/api_tester.py +0 -0
  21. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ascii_banner.py +0 -0
  22. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ascii_gen.py +0 -0
  23. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/audit_log.py +0 -0
  24. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/b64/__init__.py +0 -0
  25. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/b64/b64.py +0 -0
  26. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/backup.py +0 -0
  27. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/banner/__init__.py +0 -0
  28. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/banner/banner.py +0 -0
  29. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/base32.py +0 -0
  30. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/base58.py +0 -0
  31. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/bookmark.py +0 -0
  32. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cal_tool/__init__.py +0 -0
  33. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cal_tool/cli.py +0 -0
  34. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/calendar_cli.py +0 -0
  35. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/case_convert.py +0 -0
  36. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cert_check.py +0 -0
  37. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cert_info.py +0 -0
  38. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/changelog_gen/__init__.py +0 -0
  39. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/changelog_gen/changelog_gen.py +0 -0
  40. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/changelog_gen.py +0 -0
  41. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/chart_cli/__init__.py +0 -0
  42. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/chart_cli/__main__.py +0 -0
  43. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/checksum_dir.py +0 -0
  44. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/clipboard/__init__.py +0 -0
  45. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/clipboard/clipboard.py +0 -0
  46. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/code_auditor.py +0 -0
  47. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/code_review.py +0 -0
  48. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/code_stats.py +0 -0
  49. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/colorize.py +0 -0
  50. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/colors/__init__.py +0 -0
  51. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/colors/__main__.py +0 -0
  52. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/config_validator.py +0 -0
  53. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/config_vault.py +0 -0
  54. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cowsay.py +0 -0
  55. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crc_check.py +0 -0
  56. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cron/__init__.py +0 -0
  57. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cron/__main__.py +0 -0
  58. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/cron_pretty.py +0 -0
  59. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crontab_helper.py +0 -0
  60. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crypto_box.py +0 -0
  61. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/crypto_price.py +0 -0
  62. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv2json.py +0 -0
  63. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_dedup.py +0 -0
  64. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_filter.py +0 -0
  65. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_head.py +0 -0
  66. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_merge.py +0 -0
  67. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_slice.py +0 -0
  68. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_sort.py +0 -0
  69. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/__init__.py +0 -0
  70. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/__main__.py +0 -0
  71. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/analyzer.py +0 -0
  72. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_stats/cli.py +0 -0
  73. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_to_table.py +0 -0
  74. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_validate.py +0 -0
  75. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/csv_view.py +0 -0
  76. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/date_diff.py +0 -0
  77. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/db_mate.py +0 -0
  78. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/db_schema.py +0 -0
  79. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dedup_files.py +0 -0
  80. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dep_graph.py +0 -0
  81. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dev_dashboard.py +0 -0
  82. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dice_roll.py +0 -0
  83. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_csv.py +0 -0
  84. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_files.py +0 -0
  85. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_tool/__init__.py +0 -0
  86. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/diff_tool/__main__.py +0 -0
  87. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dirsize/__init__.py +0 -0
  88. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/disk_cleanup.py +0 -0
  89. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/disk_usage/__init__.py +0 -0
  90. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/disk_usage/disk_usage.py +0 -0
  91. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dns_lookup.py +0 -0
  92. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/docker_helper.py +0 -0
  93. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/dt_convert.py +0 -0
  94. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_diff.py +0 -0
  95. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_manager.py +0 -0
  96. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_sorter.py +0 -0
  97. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/env_template.py +0 -0
  98. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/envcheck/__init__.py +0 -0
  99. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/epoch.py +0 -0
  100. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/excel2csv.py +0 -0
  101. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/factor.py +0 -0
  102. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ff/__init__.py +0 -0
  103. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ff/__main__.py +0 -0
  104. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/figlet_cli.py +0 -0
  105. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/figlet_tool.py +0 -0
  106. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_encrypt.py +0 -0
  107. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_find.py +0 -0
  108. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_joiner.py +0 -0
  109. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_patch.py +0 -0
  110. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_splitter.py +0 -0
  111. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_type.py +0 -0
  112. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/file_watch.py +0 -0
  113. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/find_dups/__init__.py +0 -0
  114. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/find_dups/cli.py +0 -0
  115. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/find_empty.py +0 -0
  116. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/firewall_rule.py +0 -0
  117. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/fmt/__init__.py +0 -0
  118. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/fmt/fmt.py +0 -0
  119. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/fold.py +0 -0
  120. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/geo_ip.py +0 -0
  121. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/git_branch_cleaner.py +0 -0
  122. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/git_log_pretty.py +0 -0
  123. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/git_stats.py +0 -0
  124. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/gzip_cli.py +0 -0
  125. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hash_check.py +0 -0
  126. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hash_file.py +0 -0
  127. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hashsum/__init__.py +0 -0
  128. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hashsum/__main__.py +0 -0
  129. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hex_tool.py +0 -0
  130. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/hexdump.py +0 -0
  131. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/html2markdown.py +0 -0
  132. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/html2md.py +0 -0
  133. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_headers.py +0 -0
  134. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_live/__init__.py +0 -0
  135. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_live/__main__.py +0 -0
  136. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_server.py +0 -0
  137. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/http_status.py +0 -0
  138. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/image_meta.py +0 -0
  139. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ini2json.py +0 -0
  140. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ini_parser/__init__.py +0 -0
  141. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ini_parser/ini_parser.py +0 -0
  142. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ip_info.py +0 -0
  143. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ip_location.py +0 -0
  144. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipcalc/__init__.py +0 -0
  145. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipcalc/__main__.py +0 -0
  146. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipinfo/__init__.py +0 -0
  147. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ipinfo/__main__.py +0 -0
  148. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/join.py +0 -0
  149. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/joke.py +0 -0
  150. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jq_lite/__init__.py +0 -0
  151. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jq_lite/__main__.py +0 -0
  152. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json2csv/__init__.py +0 -0
  153. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json2csv/__main__.py +0 -0
  154. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json2ini.py +0 -0
  155. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_diff.py +0 -0
  156. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_flatten.py +0 -0
  157. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_keys.py +0 -0
  158. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_merge.py +0 -0
  159. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_path.py +0 -0
  160. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_pretty/__init__.py +0 -0
  161. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_pretty/json_pretty.py +0 -0
  162. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_schema_validate.py +0 -0
  163. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_sort.py +0 -0
  164. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_to_table.py +0 -0
  165. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/json_to_yaml.py +0 -0
  166. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jsonql/__init__.py +0 -0
  167. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jsonql/__main__.py +0 -0
  168. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/jwt_decode.py +0 -0
  169. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/key_value_store.py +0 -0
  170. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license.py +0 -0
  171. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license_cli/__init__.py +0 -0
  172. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license_cli/__main__.py +0 -0
  173. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/license_cli/cli.py +0 -0
  174. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/link_check.py +0 -0
  175. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/log_analyzer.py +0 -0
  176. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/log_hawk.py +0 -0
  177. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/log_tail.py +0 -0
  178. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/mac_address.py +0 -0
  179. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/macrogen.py +0 -0
  180. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_check/__init__.py +0 -0
  181. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_lint.py +0 -0
  182. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_preview.py +0 -0
  183. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_to_html.py +0 -0
  184. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/markdown_toc.py +0 -0
  185. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/math_eval.py +0 -0
  186. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/media_studio.py +0 -0
  187. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/morse.py +0 -0
  188. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/nb/__init__.py +0 -0
  189. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/nb/__main__.py +0 -0
  190. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/net_analyzer.py +0 -0
  191. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/net_speed.py +0 -0
  192. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/network_scan.py +0 -0
  193. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/nl.py +0 -0
  194. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/note_taker.py +0 -0
  195. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/otp_gen.py +0 -0
  196. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/passgen/__init__.py +0 -0
  197. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/password_strength.py +0 -0
  198. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pdf_info.py +0 -0
  199. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pdf_text.py +0 -0
  200. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pipe_viewer.py +0 -0
  201. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pomodoro.py +0 -0
  202. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/port_scan.py +0 -0
  203. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/portcheck/__init__.py +0 -0
  204. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/portcheck/__main__.py +0 -0
  205. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pr_tool/__init__.py +0 -0
  206. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/pr_tool/pr_tool.py +0 -0
  207. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/process_kill.py +0 -0
  208. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/progress_bar.py +0 -0
  209. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/project_doctor/__init__.py +0 -0
  210. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/project_doctor/__main__.py +0 -0
  211. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qc_calc.py +0 -0
  212. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qc_report.py +0 -0
  213. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qc_sample.py +0 -0
  214. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qr_cli.py +0 -0
  215. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/qrcode.py +0 -0
  216. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/quote.py +0 -0
  217. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/quote_tool/__init__.py +0 -0
  218. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/quote_tool/quote.py +0 -0
  219. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/rainbow.py +0 -0
  220. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/random.py +0 -0
  221. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/random_cli.py +0 -0
  222. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/random_string.py +0 -0
  223. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/reminder.py +0 -0
  224. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ren/__init__.py +0 -0
  225. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ren/__main__.py +0 -0
  226. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/replace_text.py +0 -0
  227. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/restore.py +0 -0
  228. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/rot13.py +0 -0
  229. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/route_trace.py +0 -0
  230. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/scan_open_ports.py +0 -0
  231. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/scan_ports.py +0 -0
  232. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/screen_recorder.py +0 -0
  233. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/screenshot_cli.py +0 -0
  234. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/search_files.py +0 -0
  235. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/search_history.py +0 -0
  236. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/secret_scanner.py +0 -0
  237. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/seq.py +0 -0
  238. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/service_check.py +0 -0
  239. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/shuffle.py +0 -0
  240. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/siege_lite/__init__.py +0 -0
  241. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/siege_lite/__main__.py +0 -0
  242. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/slugify.py +0 -0
  243. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/smellfinder/__init__.py +0 -0
  244. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/smellfinder/__main__.py +0 -0
  245. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sort/__init__.py +0 -0
  246. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sort/sort.py +0 -0
  247. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/spinner.py +0 -0
  248. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/split.py +0 -0
  249. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/split_tool/__init__.py +0 -0
  250. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/split_tool/split.py +0 -0
  251. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sql2csv.py +0 -0
  252. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sqlite_cli/__init__.py +0 -0
  253. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sqlite_cli/__main__.py +0 -0
  254. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ssh_key_gen.py +0 -0
  255. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/ssl_check.py +0 -0
  256. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/stopwatch.py +0 -0
  257. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/subnet.py +0 -0
  258. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sysmon/__init__.py +0 -0
  259. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sysmon/__main__.py +0 -0
  260. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/sysmon_pro.py +0 -0
  261. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/system_info.py +0 -0
  262. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/temp_cleaner.py +0 -0
  263. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/template.py +0 -0
  264. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/text_dedent.py +0 -0
  265. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/text_stats.py +0 -0
  266. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/text_wrap.py +0 -0
  267. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/time_duration.py +0 -0
  268. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timeout.py +0 -0
  269. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer/__init__.py +0 -0
  270. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer_pro/__init__.py +0 -0
  271. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer_pro/timer_pro.py +0 -0
  272. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/timer_pro.py +0 -0
  273. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/todo_cli.py +0 -0
  274. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/toml2json.py +0 -0
  275. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/tr.py +0 -0
  276. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/tree.py +0 -0
  277. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/treedir/__init__.py +0 -0
  278. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/treedir/__main__.py +0 -0
  279. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/tsv2csv.py +0 -0
  280. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uniq_tool/__init__.py +0 -0
  281. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uniq_tool/uniq.py +0 -0
  282. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/unit_convert.py +0 -0
  283. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uri_encode.py +0 -0
  284. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/url_parser.py +0 -0
  285. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/urlparse_tool/__init__.py +0 -0
  286. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/urlparse_tool/cli.py +0 -0
  287. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uuid_gen.py +0 -0
  288. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uuid_tool/__init__.py +0 -0
  289. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/uuid_tool/__main__.py +0 -0
  290. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/watch.py +0 -0
  291. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/weather_cli.py +0 -0
  292. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/web_download.py +0 -0
  293. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/web_summary/__init__.py +0 -0
  294. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/web_summary/__main__.py +0 -0
  295. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/whois_lookup.py +0 -0
  296. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/wordcount/__init__.py +0 -0
  297. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/wordcount/__main__.py +0 -0
  298. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/world_clock.py +0 -0
  299. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/xml2json.py +0 -0
  300. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/xml_format.py +0 -0
  301. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml2json/__init__.py +0 -0
  302. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml2json/yaml2json.py +0 -0
  303. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml2toml.py +0 -0
  304. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yaml_validate.py +0 -0
  305. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools/vendor/yes.py +0 -0
  306. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools.egg-info/dependency_links.txt +0 -0
  307. {evolver_tools-32.0.0 → evolver_tools-33.0.0}/src/evolver_tools.egg-info/entry_points.txt +0 -0
  308. {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: 32.0.0
4
- Summary: 234 CLI tools + 9 flagship projects — one pip install
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 = "32.0.0"
8
- description = "234 CLI tools + 9 flagship projects — one pip install"
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()