evolver-tools 23.0.0__tar.gz → 25.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 (278) hide show
  1. {evolver_tools-23.0.0/src/evolver_tools.egg-info → evolver_tools-25.0.0}/PKG-INFO +2 -2
  2. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/pyproject.toml +2 -2
  3. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/cli.py +1 -1
  4. evolver_tools-25.0.0/src/evolver_tools/vendor/code_review.py +605 -0
  5. evolver_tools-25.0.0/src/evolver_tools/vendor/csv_view.py +403 -0
  6. evolver_tools-25.0.0/src/evolver_tools/vendor/gzip_cli.py +81 -0
  7. evolver_tools-25.0.0/src/evolver_tools/vendor/json_diff.py +96 -0
  8. evolver_tools-25.0.0/src/evolver_tools/vendor/jwt_decode.py +81 -0
  9. evolver_tools-25.0.0/src/evolver_tools/vendor/macrogen.py +345 -0
  10. evolver_tools-25.0.0/src/evolver_tools/vendor/markdown_lint.py +114 -0
  11. evolver_tools-25.0.0/src/evolver_tools/vendor/pdf_text.py +474 -0
  12. evolver_tools-25.0.0/src/evolver_tools/vendor/random_cli.py +150 -0
  13. evolver_tools-25.0.0/src/evolver_tools/vendor/world_clock.py +80 -0
  14. {evolver_tools-23.0.0 → evolver_tools-25.0.0/src/evolver_tools.egg-info}/PKG-INFO +2 -2
  15. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools.egg-info/SOURCES.txt +7 -0
  16. evolver_tools-23.0.0/src/evolver_tools/vendor/csv_view.py +0 -157
  17. evolver_tools-23.0.0/src/evolver_tools/vendor/macrogen.py +0 -113
  18. evolver_tools-23.0.0/src/evolver_tools/vendor/pdf_text.py +0 -97
  19. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/LICENSE +0 -0
  20. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/README.md +0 -0
  21. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/setup.cfg +0 -0
  22. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/__init__.py +0 -0
  23. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/__main__.py +0 -0
  24. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/autoreg.py +0 -0
  25. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/__init__.py +0 -0
  26. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/agent_b_tool.py +0 -0
  27. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ansi_strip.py +0 -0
  28. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/api_tester.py +0 -0
  29. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ascii_gen.py +0 -0
  30. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/audit_log.py +0 -0
  31. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/b64/__init__.py +0 -0
  32. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/b64/b64.py +0 -0
  33. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/backup.py +0 -0
  34. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/banner/__init__.py +0 -0
  35. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/banner/banner.py +0 -0
  36. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/banner.py +0 -0
  37. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/base32.py +0 -0
  38. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/base58.py +0 -0
  39. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/bookmark.py +0 -0
  40. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cal_tool/__init__.py +0 -0
  41. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cal_tool/cli.py +0 -0
  42. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/calendar_cli.py +0 -0
  43. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cert_check.py +0 -0
  44. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cert_info.py +0 -0
  45. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/changelog_gen/__init__.py +0 -0
  46. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/changelog_gen/changelog_gen.py +0 -0
  47. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/changelog_gen.py +0 -0
  48. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/chart_cli/__init__.py +0 -0
  49. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/chart_cli/__main__.py +0 -0
  50. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/checksum_dir.py +0 -0
  51. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/clipboard/__init__.py +0 -0
  52. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/clipboard/clipboard.py +0 -0
  53. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/code_auditor.py +0 -0
  54. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/code_stats.py +0 -0
  55. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/colorize.py +0 -0
  56. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/colors/__init__.py +0 -0
  57. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/colors/__main__.py +0 -0
  58. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/config_validator.py +0 -0
  59. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/config_vault.py +0 -0
  60. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cowsay.py +0 -0
  61. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/crc_check.py +0 -0
  62. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cron/__init__.py +0 -0
  63. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cron/__main__.py +0 -0
  64. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/cron_pretty.py +0 -0
  65. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/crontab_helper.py +0 -0
  66. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/crypto_box.py +0 -0
  67. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/crypto_price.py +0 -0
  68. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/csv2json.py +0 -0
  69. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/csv_merge.py +0 -0
  70. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/csv_stats/__init__.py +0 -0
  71. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/csv_stats/__main__.py +0 -0
  72. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/csv_stats/analyzer.py +0 -0
  73. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/csv_stats/cli.py +0 -0
  74. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/csv_validate.py +0 -0
  75. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/date_diff.py +0 -0
  76. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/db_mate.py +0 -0
  77. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/db_schema.py +0 -0
  78. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/dep_graph.py +0 -0
  79. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/dev_dashboard.py +0 -0
  80. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/dice_roll.py +0 -0
  81. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/diff_csv.py +0 -0
  82. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/diff_files.py +0 -0
  83. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/diff_tool/__init__.py +0 -0
  84. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/diff_tool/__main__.py +0 -0
  85. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/dirsize/__init__.py +0 -0
  86. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/disk_cleanup.py +0 -0
  87. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/disk_usage/__init__.py +0 -0
  88. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/disk_usage/disk_usage.py +0 -0
  89. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/dns_lookup.py +0 -0
  90. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/docker_helper.py +0 -0
  91. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/dt_convert.py +0 -0
  92. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/env_diff.py +0 -0
  93. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/env_manager.py +0 -0
  94. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/env_template.py +0 -0
  95. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/envcheck/__init__.py +0 -0
  96. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/epoch.py +0 -0
  97. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/excel2csv.py +0 -0
  98. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/factor.py +0 -0
  99. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ff/__init__.py +0 -0
  100. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ff/__main__.py +0 -0
  101. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/figlet_cli.py +0 -0
  102. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/figlet_tool.py +0 -0
  103. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/file_encrypt.py +0 -0
  104. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/file_find.py +0 -0
  105. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/file_joiner.py +0 -0
  106. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/file_splitter.py +0 -0
  107. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/file_watch.py +0 -0
  108. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/find_dups/__init__.py +0 -0
  109. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/find_dups/cli.py +0 -0
  110. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/firewall_rule.py +0 -0
  111. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/fmt/__init__.py +0 -0
  112. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/fmt/fmt.py +0 -0
  113. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/fold.py +0 -0
  114. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/geo_ip.py +0 -0
  115. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/git_branch_cleaner.py +0 -0
  116. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/git_log_pretty.py +0 -0
  117. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/git_stats.py +0 -0
  118. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/hash_check.py +0 -0
  119. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/hash_file.py +0 -0
  120. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/hashsum/__init__.py +0 -0
  121. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/hashsum/__main__.py +0 -0
  122. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/hex_tool.py +0 -0
  123. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/hexdump.py +0 -0
  124. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/html2markdown.py +0 -0
  125. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/html2md.py +0 -0
  126. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/http_headers.py +0 -0
  127. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/http_live/__init__.py +0 -0
  128. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/http_live/__main__.py +0 -0
  129. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/http_server.py +0 -0
  130. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/image_meta.py +0 -0
  131. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ini2json.py +0 -0
  132. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ini_parser/__init__.py +0 -0
  133. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ini_parser/ini_parser.py +0 -0
  134. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ip_info.py +0 -0
  135. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ip_location.py +0 -0
  136. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ipcalc/__init__.py +0 -0
  137. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ipcalc/__main__.py +0 -0
  138. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ipinfo/__init__.py +0 -0
  139. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ipinfo/__main__.py +0 -0
  140. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/join.py +0 -0
  141. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/joke.py +0 -0
  142. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/jq_lite/__init__.py +0 -0
  143. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/jq_lite/__main__.py +0 -0
  144. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json2csv/__init__.py +0 -0
  145. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json2csv/__main__.py +0 -0
  146. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json2ini.py +0 -0
  147. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json_flatten.py +0 -0
  148. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json_merge.py +0 -0
  149. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json_pretty/__init__.py +0 -0
  150. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json_pretty/json_pretty.py +0 -0
  151. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/json_schema_validate.py +0 -0
  152. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/jsonql/__init__.py +0 -0
  153. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/jsonql/__main__.py +0 -0
  154. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/key_value_store.py +0 -0
  155. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/license.py +0 -0
  156. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/license_cli/__init__.py +0 -0
  157. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/license_cli/__main__.py +0 -0
  158. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/license_cli/cli.py +0 -0
  159. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/log_analyzer.py +0 -0
  160. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/log_hawk.py +0 -0
  161. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/log_tail.py +0 -0
  162. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/markdown_check/__init__.py +0 -0
  163. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/markdown_preview.py +0 -0
  164. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/markdown_toc.py +0 -0
  165. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/math_eval.py +0 -0
  166. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/media_studio.py +0 -0
  167. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/morse.py +0 -0
  168. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/nb/__init__.py +0 -0
  169. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/nb/__main__.py +0 -0
  170. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/net_analyzer.py +0 -0
  171. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/net_speed.py +0 -0
  172. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/network_scan.py +0 -0
  173. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/nl.py +0 -0
  174. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/note_taker.py +0 -0
  175. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/otp_gen.py +0 -0
  176. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/passgen/__init__.py +0 -0
  177. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/password_strength.py +0 -0
  178. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/pdf_info.py +0 -0
  179. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/pipe_viewer.py +0 -0
  180. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/pomodoro.py +0 -0
  181. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/port_scan.py +0 -0
  182. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/portcheck/__init__.py +0 -0
  183. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/portcheck/__main__.py +0 -0
  184. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/pr_tool/__init__.py +0 -0
  185. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/pr_tool/pr_tool.py +0 -0
  186. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/process_kill.py +0 -0
  187. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/progress_bar.py +0 -0
  188. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/project_doctor/__init__.py +0 -0
  189. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/project_doctor/__main__.py +0 -0
  190. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/qc_calc.py +0 -0
  191. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/qc_report.py +0 -0
  192. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/qc_sample.py +0 -0
  193. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/qr_cli.py +0 -0
  194. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/qrcode.py +0 -0
  195. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/quote.py +0 -0
  196. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/quote_tool/__init__.py +0 -0
  197. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/quote_tool/quote.py +0 -0
  198. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/rainbow.py +0 -0
  199. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/random.py +0 -0
  200. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/reminder.py +0 -0
  201. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ren/__init__.py +0 -0
  202. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ren/__main__.py +0 -0
  203. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/replace_text.py +0 -0
  204. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/restore.py +0 -0
  205. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/rot13.py +0 -0
  206. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/route_trace.py +0 -0
  207. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/scan_ports.py +0 -0
  208. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/screen_recorder.py +0 -0
  209. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/screenshot_cli.py +0 -0
  210. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/search_files.py +0 -0
  211. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/search_history.py +0 -0
  212. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/secret_scanner.py +0 -0
  213. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/seq.py +0 -0
  214. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/service_check.py +0 -0
  215. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/shuffle.py +0 -0
  216. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/siege_lite/__init__.py +0 -0
  217. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/siege_lite/__main__.py +0 -0
  218. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/smellfinder/__init__.py +0 -0
  219. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/smellfinder/__main__.py +0 -0
  220. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sort/__init__.py +0 -0
  221. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sort/sort.py +0 -0
  222. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/spinner.py +0 -0
  223. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/split.py +0 -0
  224. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/split_tool/__init__.py +0 -0
  225. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/split_tool/split.py +0 -0
  226. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sql2csv.py +0 -0
  227. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sqlite_cli/__init__.py +0 -0
  228. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sqlite_cli/__main__.py +0 -0
  229. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ssh_key_gen.py +0 -0
  230. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/ssl_check.py +0 -0
  231. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/stopwatch.py +0 -0
  232. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/subnet.py +0 -0
  233. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sysmon/__init__.py +0 -0
  234. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sysmon/__main__.py +0 -0
  235. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/sysmon_pro.py +0 -0
  236. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/system_info.py +0 -0
  237. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/temp_cleaner.py +0 -0
  238. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/template.py +0 -0
  239. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/text_stats.py +0 -0
  240. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/timeout.py +0 -0
  241. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/timer/__init__.py +0 -0
  242. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/timer_pro/__init__.py +0 -0
  243. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/timer_pro/timer_pro.py +0 -0
  244. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/timer_pro.py +0 -0
  245. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/todo_cli.py +0 -0
  246. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/toml2json.py +0 -0
  247. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/tr.py +0 -0
  248. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/tree.py +0 -0
  249. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/treedir/__init__.py +0 -0
  250. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/treedir/__main__.py +0 -0
  251. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/tsv2csv.py +0 -0
  252. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/uniq_tool/__init__.py +0 -0
  253. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/uniq_tool/uniq.py +0 -0
  254. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/unit_convert.py +0 -0
  255. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/uri_encode.py +0 -0
  256. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/url_parser.py +0 -0
  257. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/urlparse_tool/__init__.py +0 -0
  258. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/urlparse_tool/cli.py +0 -0
  259. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/uuid_gen.py +0 -0
  260. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/uuid_tool/__init__.py +0 -0
  261. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/uuid_tool/__main__.py +0 -0
  262. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/watch.py +0 -0
  263. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/weather_cli.py +0 -0
  264. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/web_download.py +0 -0
  265. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/web_summary/__init__.py +0 -0
  266. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/web_summary/__main__.py +0 -0
  267. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/whois_lookup.py +0 -0
  268. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/wordcount/__init__.py +0 -0
  269. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/wordcount/__main__.py +0 -0
  270. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/xml2json.py +0 -0
  271. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/yaml2json/__init__.py +0 -0
  272. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/yaml2json/yaml2json.py +0 -0
  273. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/yaml2toml.py +0 -0
  274. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/yaml_validate.py +0 -0
  275. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools/vendor/yes.py +0 -0
  276. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools.egg-info/dependency_links.txt +0 -0
  277. {evolver_tools-23.0.0 → evolver_tools-25.0.0}/src/evolver_tools.egg-info/entry_points.txt +0 -0
  278. {evolver_tools-23.0.0 → evolver_tools-25.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: 23.0.0
4
- Summary: 201 CLI tools + 9 flagship projects — one pip install
3
+ Version: 25.0.0
4
+ Summary: 208 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 = "23.0.0"
8
- description = "201 CLI tools + 9 flagship projects — one pip install"
7
+ version = "25.0.0"
8
+ description = "208 CLI tools + 9 flagship projects — one pip install"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.8"
@@ -14,7 +14,7 @@ from evolver_tools.autoreg import auto_discover
14
14
  def list_tools():
15
15
  """Display all available tools."""
16
16
  tools = auto_discover()
17
- print(f'\x1b[1;36m===== EVOLVER Tools v23.0.0 =====\x1b[0m')
17
+ print(f'\x1b[1;36m===== EVOLVER Tools v25.0.0 =====\x1b[0m')
18
18
  print()
19
19
  for name, info in sorted(tools.items()):
20
20
  print(f' \033[1;33m{name:<18}\033[0m {info["desc"]}')
@@ -0,0 +1,605 @@
1
+ #!/usr/bin/env python3
2
+ """code-review: Static code review tool for Python files using AST analysis.
3
+
4
+ Analyzes Python files for common issues: long functions, too many parameters,
5
+ missing docstrings, TODO/FIXME comments, too many imports, duplicate code blocks,
6
+ and cyclomatic complexity.
7
+
8
+ Usage:
9
+ code-review file1.py file2.py
10
+ code-review --path src/
11
+ code-review --path src/ --min-complexity 10 --json --verbose
12
+ """
13
+
14
+ import ast
15
+ import os
16
+ import sys
17
+ import json
18
+ import glob
19
+ from collections import defaultdict
20
+
21
+ # ── Terminal Colors ──────────────────────────────────────────────────────────
22
+ COLORS = {
23
+ "reset": "\033[0m",
24
+ "bold": "\033[1m",
25
+ "red": "\033[91m",
26
+ "green": "\033[92m",
27
+ "yellow": "\033[93m",
28
+ "blue": "\033[94m",
29
+ "magenta": "\033[95m",
30
+ "cyan": "\033[96m",
31
+ "white": "\033[97m",
32
+ "dim": "\033[2m",
33
+ }
34
+ NO_COLORS = {k: "" for k in COLORS}
35
+
36
+
37
+ def _c(color_name, text, bold=False, use_color=True):
38
+ """Return colorized text string."""
39
+ c = COLORS if use_color else NO_COLORS
40
+ b = c["bold"] if bold else ""
41
+ return f"{b}{c.get(color_name, '')}{text}{c['reset']}"
42
+
43
+
44
+ # ── Issue Data ───────────────────────────────────────────────────────────────
45
+
46
+ class Issue:
47
+ """Represents a single code review issue."""
48
+
49
+ def __init__(self, issue_type, message, file_path, line=1, severity="info",
50
+ function_name=None):
51
+ self.type = issue_type
52
+ self.message = message
53
+ self.file = file_path
54
+ self.line = line
55
+ self.severity = severity # 'error', 'warning', 'info'
56
+ self.function_name = function_name
57
+
58
+ def to_dict(self):
59
+ return {
60
+ "type": self.type,
61
+ "message": self.message,
62
+ "file": self.file,
63
+ "line": self.line,
64
+ "severity": self.severity,
65
+ "function": self.function_name,
66
+ }
67
+
68
+ def __repr__(self):
69
+ return (
70
+ f"[{self.severity.upper()}] {self.file}:{self.line} "
71
+ f"({self.type}) {self.message}"
72
+ )
73
+
74
+
75
+ # ── Analysis Functions ──────────────────────────────────────────────────────
76
+
77
+ def _get_source_lines(source):
78
+ """Return list of lines from source string."""
79
+ return source.splitlines()
80
+
81
+
82
+ def analyze_long_functions(tree, source_lines, file_path, max_lines=50):
83
+ """Find functions with more than max_lines lines of body."""
84
+ issues = []
85
+ for node in ast.walk(tree):
86
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
87
+ if not node.body:
88
+ continue
89
+ first_line = node.body[0].lineno
90
+ last_line = node.body[-1].end_lineno or node.body[-1].lineno
91
+ body_length = last_line - first_line + 1
92
+ if body_length > max_lines:
93
+ issues.append(Issue(
94
+ issue_type="long-function",
95
+ message=(
96
+ f"Function '{node.name}' has {body_length} lines "
97
+ f"(max: {max_lines})"
98
+ ),
99
+ file_path=file_path,
100
+ line=node.lineno,
101
+ severity="warning",
102
+ function_name=node.name,
103
+ ))
104
+ return issues
105
+
106
+
107
+ def analyze_too_many_params(tree, file_path, max_params=5):
108
+ """Find functions with more than max_params parameters."""
109
+ issues = []
110
+ for node in ast.walk(tree):
111
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
112
+ # Count positional + keyword-only + positional-keyword args
113
+ args = node.args
114
+ total = (
115
+ len(args.args)
116
+ + len(args.kwonlyargs)
117
+ + len(args.posonlyargs)
118
+ )
119
+ # Don't count self/cls as a param
120
+ if total > 0 and node.body:
121
+ first_stmt = node.body[0]
122
+ if isinstance(first_stmt, ast.Expr) and isinstance(first_stmt.value, ast.Constant):
123
+ # Has docstring — this is a real function
124
+ pass
125
+ # Check if it's a method (first param named self or cls)
126
+ method_extra = 0
127
+ if args.args and args.args[0].arg in ("self", "cls"):
128
+ method_extra = 1
129
+
130
+ effective = total
131
+ if effective > max_params:
132
+ issues.append(Issue(
133
+ issue_type="too-many-params",
134
+ message=(
135
+ f"Function '{node.name}' has {effective} parameters "
136
+ f"(max: {max_params})"
137
+ ),
138
+ file_path=file_path,
139
+ line=node.lineno,
140
+ severity="warning",
141
+ function_name=node.name,
142
+ ))
143
+ return issues
144
+
145
+
146
+ def analyze_missing_docstrings(tree, file_path):
147
+ """Find functions, classes, and modules without docstrings."""
148
+ issues = []
149
+
150
+ # Module-level docstring
151
+ if (not isinstance(tree.body[0], ast.Expr)
152
+ or not isinstance(tree.body[0].value, ast.Constant)
153
+ or not isinstance(tree.body[0].value.value, str)):
154
+ issues.append(Issue(
155
+ issue_type="missing-docstring",
156
+ message="Module is missing a docstring",
157
+ file_path=file_path,
158
+ line=1,
159
+ severity="info",
160
+ ))
161
+
162
+ for node in ast.walk(tree):
163
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
164
+ if (not node.body
165
+ or not isinstance(node.body[0], ast.Expr)
166
+ or not isinstance(node.body[0].value, ast.Constant)
167
+ or not isinstance(node.body[0].value.value, str)):
168
+ # Skip dunder methods
169
+ if node.name.startswith("__") and node.name.endswith("__"):
170
+ continue
171
+ issues.append(Issue(
172
+ issue_type="missing-docstring",
173
+ message=f"Function '{node.name}' is missing a docstring",
174
+ file_path=file_path,
175
+ line=node.lineno,
176
+ severity="info",
177
+ function_name=node.name,
178
+ ))
179
+ elif isinstance(node, ast.ClassDef):
180
+ if (not node.body
181
+ or not isinstance(node.body[0], ast.Expr)
182
+ or not isinstance(node.body[0].value, ast.Constant)
183
+ or not isinstance(node.body[0].value.value, str)):
184
+ issues.append(Issue(
185
+ issue_type="missing-docstring",
186
+ message=f"Class '{node.name}' is missing a docstring",
187
+ file_path=file_path,
188
+ line=node.lineno,
189
+ severity="info",
190
+ ))
191
+ return issues
192
+
193
+
194
+ def analyze_todo_comments(source, file_path):
195
+ """Find TODO and FIXME comments in source."""
196
+ issues = []
197
+ for i, line in enumerate(source.splitlines(), 1):
198
+ stripped = line.strip()
199
+ if stripped.startswith("#"):
200
+ lower = stripped.lower()
201
+ if "todo" in lower:
202
+ issues.append(Issue(
203
+ issue_type="todo-comment",
204
+ message=f"TODO comment found: {stripped.strip('# ')}",
205
+ file_path=file_path,
206
+ line=i,
207
+ severity="info",
208
+ ))
209
+ elif "fixme" in lower:
210
+ issues.append(Issue(
211
+ issue_type="fixme-comment",
212
+ message=f"FIXME comment found: {stripped.strip('# ')}",
213
+ file_path=file_path,
214
+ line=i,
215
+ severity="warning",
216
+ ))
217
+ return issues
218
+
219
+
220
+ def analyze_imports(tree, file_path, max_imports=20):
221
+ """Flag files with too many import statements."""
222
+ count = 0
223
+ for node in ast.walk(tree):
224
+ if isinstance(node, (ast.Import, ast.ImportFrom)):
225
+ count += 1
226
+ issues = []
227
+ if count > max_imports:
228
+ issues.append(Issue(
229
+ issue_type="too-many-imports",
230
+ message=f"File has {count} import statements (max: {max_imports})",
231
+ file_path=file_path,
232
+ line=1,
233
+ severity="info",
234
+ ))
235
+ return issues
236
+
237
+
238
+ def analyze_complexity(tree, file_path, min_complexity=10):
239
+ """Calculate cyclomatic complexity and flag functions above threshold."""
240
+ issues = []
241
+
242
+ for node in ast.walk(tree):
243
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
244
+ complexity = 1 # Base complexity
245
+ for child in ast.walk(node):
246
+ if isinstance(child, (ast.If, ast.While, ast.For, ast.AsyncFor)):
247
+ complexity += 1
248
+ elif isinstance(child, ast.ExceptHandler):
249
+ complexity += 1
250
+ elif isinstance(child, (ast.With, ast.AsyncWith)):
251
+ complexity += 1
252
+ elif isinstance(child, ast.BoolOp):
253
+ # Each 'and'/'or' adds to complexity
254
+ complexity += len(child.values) - 1
255
+ elif isinstance(child, (ast.Assert,)):
256
+ complexity += 1
257
+ elif isinstance(child, ast.Try):
258
+ complexity += len(child.handlers)
259
+
260
+ # Ternary expressions: x if cond else y
261
+ elif isinstance(child, ast.IfExp):
262
+ complexity += 1
263
+
264
+ if complexity >= min_complexity:
265
+ issues.append(Issue(
266
+ issue_type="high-complexity",
267
+ message=(
268
+ f"Function '{node.name}' has cyclomatic complexity "
269
+ f"of {complexity} (min: {min_complexity})"
270
+ ),
271
+ file_path=file_path,
272
+ line=node.lineno,
273
+ severity="warning",
274
+ function_name=node.name,
275
+ ))
276
+ return issues
277
+
278
+
279
+ def analyze_duplicate_code(tree, source_lines, file_path, min_block_lines=5):
280
+ """Detect duplicate code blocks by comparing function body line hashes."""
281
+ issues = []
282
+ functions = []
283
+
284
+ for node in ast.walk(tree):
285
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
286
+ if not node.body:
287
+ continue
288
+ first = node.body[0].lineno
289
+ last = node.body[-1].end_lineno
290
+ body_lines = source_lines[first - 1:last]
291
+ # Normalize: strip indentation for comparison
292
+ normalized = "\n".join(
293
+ line.strip() for line in body_lines
294
+ if line.strip() and not line.strip().startswith("#")
295
+ )
296
+ functions.append((node.name, first, normalized))
297
+
298
+ # Compare each pair of functions
299
+ seen = set()
300
+ for i, (name_a, line_a, body_a) in enumerate(functions):
301
+ for j, (name_b, line_b, body_b) in enumerate(functions):
302
+ if i >= j:
303
+ continue
304
+ if body_a and body_a == body_b and len(body_a.splitlines()) >= min_block_lines:
305
+ pair_key = tuple(sorted([name_a, name_b]))
306
+ if pair_key not in seen:
307
+ seen.add(pair_key)
308
+ nlines = body_a.count("\n") + 1
309
+ issues.append(Issue(
310
+ issue_type="duplicate-code",
311
+ message=(
312
+ f"Functions '{name_a}' (line {line_a}) and "
313
+ f"'{name_b}' (line {line_b}) have identical "
314
+ f"body ({nlines} lines)"
315
+ ),
316
+ file_path=file_path,
317
+ line=line_a,
318
+ severity="info",
319
+ function_name=name_a,
320
+ ))
321
+ return issues
322
+
323
+
324
+ # ── File Processing ──────────────────────────────────────────────────────────
325
+
326
+ def analyze_file(file_path, max_lines=50, max_params=5, max_imports=20,
327
+ min_complexity=10, verbose=False):
328
+ """Analyze a single Python file and return list of Issues."""
329
+ issues = []
330
+ try:
331
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
332
+ source = f.read()
333
+ except (OSError, IOError) as e:
334
+ issues.append(Issue(
335
+ issue_type="io-error",
336
+ message=f"Cannot read file: {e}",
337
+ file_path=file_path,
338
+ line=1,
339
+ severity="error",
340
+ ))
341
+ return issues
342
+
343
+ try:
344
+ tree = ast.parse(source, filename=file_path)
345
+ except SyntaxError as e:
346
+ issues.append(Issue(
347
+ issue_type="syntax-error",
348
+ message=f"Syntax error: {e.msg} (line {e.lineno})",
349
+ file_path=file_path,
350
+ line=e.lineno or 1,
351
+ severity="error",
352
+ ))
353
+ return issues
354
+
355
+ source_lines = source.splitlines()
356
+
357
+ checks = [
358
+ ("long-functions", lambda: analyze_long_functions(
359
+ tree, source_lines, file_path, max_lines)),
360
+ ("too-many-params", lambda: analyze_too_many_params(
361
+ tree, file_path, max_params)),
362
+ ("missing-docstrings", lambda: analyze_missing_docstrings(
363
+ tree, file_path)),
364
+ ("todo-fixme", lambda: analyze_todo_comments(source, file_path)),
365
+ ("imports", lambda: analyze_imports(tree, file_path, max_imports)),
366
+ ("complexity", lambda: analyze_complexity(
367
+ tree, file_path, min_complexity)),
368
+ ("duplicate-code", lambda: analyze_duplicate_code(
369
+ tree, source_lines, file_path)),
370
+ ]
371
+
372
+ if verbose:
373
+ for name, check in checks:
374
+ result = check()
375
+ issues.extend(result)
376
+ if result:
377
+ print(f" Check '{name}': {len(result)} issue(s)", file=sys.stderr)
378
+ else:
379
+ for _, check in checks:
380
+ issues.extend(check())
381
+
382
+ return issues
383
+
384
+
385
+ def collect_py_files(paths):
386
+ """Collect all .py files from given paths (files or directories)."""
387
+ files = []
388
+ for p in paths:
389
+ if os.path.isfile(p):
390
+ if p.endswith(".py"):
391
+ files.append(p)
392
+ elif os.path.isdir(p):
393
+ for root, dirs, fnames in os.walk(p):
394
+ # Skip common non-source dirs
395
+ dirs[:] = [d for d in dirs
396
+ if not d.startswith(".")
397
+ and d not in ("__pycache__", "node_modules",
398
+ "venv", ".venv", "env", ".env",
399
+ "dist", "build", "eggs", ".eggs")]
400
+ for fname in fnames:
401
+ if fname.endswith(".py"):
402
+ files.append(os.path.join(root, fname))
403
+ else:
404
+ print(
405
+ _c("red", f"Error: path not found: {p}"),
406
+ file=sys.stderr,
407
+ )
408
+ return sorted(set(files))
409
+
410
+
411
+ # ── Output Formatting ────────────────────────────────────────────────────────
412
+
413
+ def print_issue(issue, verbose=False, use_color=True):
414
+ """Print a single issue with colorized output."""
415
+ severity_colors = {
416
+ "error": "red",
417
+ "warning": "yellow",
418
+ "info": "cyan",
419
+ }
420
+ severity_labels = {
421
+ "error": "ERROR",
422
+ "warning": "WARN",
423
+ "info": "INFO",
424
+ }
425
+
426
+ color = severity_colors.get(issue.severity, "white")
427
+ label = severity_labels.get(issue.severity, "INFO")
428
+
429
+ parts = [
430
+ _c(color, f"[{label}]", use_color=use_color),
431
+ _c("white", f" {issue.file}:{issue.line}", bold=True, use_color=use_color),
432
+ _c("dim", f" {issue.type}", use_color=use_color),
433
+ _c("reset", f" {issue.message}", use_color=use_color),
434
+ ]
435
+
436
+ if verbose and issue.function_name:
437
+ parts.append(_c("magenta", f" (in {issue.function_name})",
438
+ use_color=use_color))
439
+
440
+ print("".join(parts))
441
+
442
+
443
+ def print_summary(files_analyzed, total_issues, by_severity, use_color=True):
444
+ """Print a summary of the review."""
445
+ sep = _c("dim", "─" * 60, use_color=use_color)
446
+ print()
447
+ print(sep)
448
+ print(_c("bold", " Code Review Summary", use_color=use_color))
449
+ print(sep)
450
+ print(f" Files analyzed: {_c('green', str(files_analyzed), use_color=use_color)}")
451
+ print(f" Total issues: {_c('yellow', str(total_issues), use_color=use_color)}")
452
+ if by_severity.get("error"):
453
+ print(f" Errors: {_c('red', str(by_severity['error']), use_color=use_color)}")
454
+ if by_severity.get("warning"):
455
+ print(f" Warnings: {_c('yellow', str(by_severity['warning']), use_color=use_color)}")
456
+ if by_severity.get("info"):
457
+ print(f" Info: {_c('cyan', str(by_severity['info']), use_color=use_color)}")
458
+ print(sep)
459
+
460
+
461
+ def print_file_header(file_path, use_color=True):
462
+ """Print a header for a file section."""
463
+ print()
464
+ print(_c("blue", f" ── {file_path}", bold=True, use_color=use_color))
465
+
466
+
467
+ # ── Main CLI ────────────────────────────────────────────────────────────────
468
+
469
+ TOOL_META = {
470
+ "name": "code-review",
471
+ "func": "main",
472
+ "desc": "Static code review for Python files (AST analysis)",
473
+ "usage": (
474
+ "code-review [files...] [--path DIR] [--min-complexity N] "
475
+ "[--json] [--verbose]"
476
+ ),
477
+ }
478
+
479
+
480
+ def main(argv=None):
481
+ """Main entry point. Parses args and runs the review."""
482
+ if argv is None:
483
+ argv = sys.argv[1:]
484
+
485
+ # Parse args manually (pure stdlib)
486
+ paths = []
487
+ target_dir = None
488
+ min_complexity = 10
489
+ json_output = False
490
+ verbose = False
491
+
492
+ i = 0
493
+ while i < len(argv):
494
+ arg = argv[i]
495
+ if arg == "--path":
496
+ i += 1
497
+ if i >= len(argv):
498
+ print("Error: --path requires a directory argument",
499
+ file=sys.stderr)
500
+ return 1
501
+ target_dir = argv[i]
502
+ elif arg == "--min-complexity":
503
+ i += 1
504
+ if i >= len(argv):
505
+ print("Error: --min-complexity requires a number",
506
+ file=sys.stderr)
507
+ return 1
508
+ try:
509
+ min_complexity = int(argv[i])
510
+ except ValueError:
511
+ print(f"Error: --min-complexity must be an integer, got '{argv[i]}'",
512
+ file=sys.stderr)
513
+ return 1
514
+ elif arg == "--json":
515
+ json_output = True
516
+ elif arg == "--verbose":
517
+ verbose = True
518
+ elif arg.startswith("-"):
519
+ print(
520
+ f"Error: Unknown argument '{arg}'. "
521
+ f"Usage: {TOOL_META['usage']}",
522
+ file=sys.stderr,
523
+ )
524
+ return 1
525
+ else:
526
+ paths.append(arg)
527
+ i += 1
528
+
529
+ # Determine files to analyze
530
+ if target_dir:
531
+ if paths:
532
+ print(
533
+ "Warning: --path and file arguments both provided. "
534
+ "Using --path only.",
535
+ file=sys.stderr,
536
+ )
537
+ paths = [target_dir]
538
+
539
+ if not paths:
540
+ # Default to current directory
541
+ paths = ["."]
542
+
543
+ py_files = collect_py_files(paths)
544
+
545
+ if not py_files:
546
+ print(
547
+ _c("yellow", "No Python files found to analyze."),
548
+ file=sys.stderr,
549
+ )
550
+ return 0
551
+
552
+ # Analyze all files
553
+ all_issues = {}
554
+ for f in py_files:
555
+ if verbose:
556
+ print(
557
+ _c("dim", f"Analyzing {f}...", use_color=not json_output),
558
+ file=sys.stderr,
559
+ )
560
+ issues = analyze_file(
561
+ f,
562
+ min_complexity=min_complexity,
563
+ verbose=verbose,
564
+ )
565
+ if issues:
566
+ all_issues[f] = issues
567
+
568
+ # Flatten for summary
569
+ flat_issues = [iss for iss_list in all_issues.values()
570
+ for iss in iss_list]
571
+ total_issues = len(flat_issues)
572
+ files_analyzed = len(py_files)
573
+
574
+ # Count by severity
575
+ by_severity = defaultdict(int)
576
+ for iss in flat_issues:
577
+ by_severity[iss.severity] += 1
578
+
579
+ # Output
580
+ if json_output:
581
+ output = {
582
+ "tool": "code-review",
583
+ "files_analyzed": files_analyzed,
584
+ "total_issues": total_issues,
585
+ "issues_by_severity": dict(by_severity),
586
+ "issues": [iss.to_dict() for iss in flat_issues],
587
+ }
588
+ print(json.dumps(output, indent=2))
589
+ else:
590
+ use_color = sys.stdout.isatty() and os.name != "nt"
591
+ for fpath, issues in all_issues.items():
592
+ print_file_header(fpath, use_color=use_color)
593
+ for iss in issues:
594
+ print_issue(iss, verbose=verbose, use_color=use_color)
595
+
596
+ print_summary(
597
+ files_analyzed, total_issues, by_severity,
598
+ use_color=use_color,
599
+ )
600
+
601
+ return 1 if total_issues > 0 else 0
602
+
603
+
604
+ if __name__ == "__main__":
605
+ sys.exit(main())