evolver-tools 30.0.0__tar.gz → 31.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 (299) hide show
  1. {evolver_tools-30.0.0/src/evolver_tools.egg-info → evolver_tools-31.0.0}/PKG-INFO +2 -2
  2. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/pyproject.toml +2 -2
  3. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/cli.py +1 -1
  4. evolver_tools-31.0.0/src/evolver_tools/vendor/_test_find_empty.py +1 -0
  5. evolver_tools-31.0.0/src/evolver_tools/vendor/csv_filter.py +120 -0
  6. evolver_tools-31.0.0/src/evolver_tools/vendor/env_sorter.py +321 -0
  7. evolver_tools-31.0.0/src/evolver_tools/vendor/file_patch.py +189 -0
  8. evolver_tools-31.0.0/src/evolver_tools/vendor/find_empty.py +161 -0
  9. evolver_tools-31.0.0/src/evolver_tools/vendor/mac_address.py +85 -0
  10. {evolver_tools-30.0.0 → evolver_tools-31.0.0/src/evolver_tools.egg-info}/PKG-INFO +2 -2
  11. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools.egg-info/SOURCES.txt +6 -0
  12. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/LICENSE +0 -0
  13. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/README.md +0 -0
  14. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/setup.cfg +0 -0
  15. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/__init__.py +0 -0
  16. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/__main__.py +0 -0
  17. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/autoreg.py +0 -0
  18. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/__init__.py +0 -0
  19. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/agent_b_tool.py +0 -0
  20. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ansi_strip.py +0 -0
  21. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/api_tester.py +0 -0
  22. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ascii_banner.py +0 -0
  23. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ascii_gen.py +0 -0
  24. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/audit_log.py +0 -0
  25. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/b64/__init__.py +0 -0
  26. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/b64/b64.py +0 -0
  27. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/backup.py +0 -0
  28. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/banner/__init__.py +0 -0
  29. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/banner/banner.py +0 -0
  30. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/base32.py +0 -0
  31. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/base58.py +0 -0
  32. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/bookmark.py +0 -0
  33. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cal_tool/__init__.py +0 -0
  34. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cal_tool/cli.py +0 -0
  35. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/calendar_cli.py +0 -0
  36. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/case_convert.py +0 -0
  37. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cert_check.py +0 -0
  38. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cert_info.py +0 -0
  39. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/changelog_gen/__init__.py +0 -0
  40. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/changelog_gen/changelog_gen.py +0 -0
  41. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/changelog_gen.py +0 -0
  42. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/chart_cli/__init__.py +0 -0
  43. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/chart_cli/__main__.py +0 -0
  44. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/checksum_dir.py +0 -0
  45. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/clipboard/__init__.py +0 -0
  46. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/clipboard/clipboard.py +0 -0
  47. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/code_auditor.py +0 -0
  48. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/code_review.py +0 -0
  49. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/code_stats.py +0 -0
  50. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/colorize.py +0 -0
  51. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/colors/__init__.py +0 -0
  52. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/colors/__main__.py +0 -0
  53. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/config_validator.py +0 -0
  54. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/config_vault.py +0 -0
  55. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cowsay.py +0 -0
  56. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/crc_check.py +0 -0
  57. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cron/__init__.py +0 -0
  58. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cron/__main__.py +0 -0
  59. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/cron_pretty.py +0 -0
  60. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/crontab_helper.py +0 -0
  61. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/crypto_box.py +0 -0
  62. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/crypto_price.py +0 -0
  63. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv2json.py +0 -0
  64. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_dedup.py +0 -0
  65. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_merge.py +0 -0
  66. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_slice.py +0 -0
  67. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_stats/__init__.py +0 -0
  68. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_stats/__main__.py +0 -0
  69. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_stats/analyzer.py +0 -0
  70. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_stats/cli.py +0 -0
  71. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_to_table.py +0 -0
  72. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_validate.py +0 -0
  73. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/csv_view.py +0 -0
  74. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/date_diff.py +0 -0
  75. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/db_mate.py +0 -0
  76. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/db_schema.py +0 -0
  77. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/dedup_files.py +0 -0
  78. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/dep_graph.py +0 -0
  79. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/dev_dashboard.py +0 -0
  80. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/dice_roll.py +0 -0
  81. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/diff_csv.py +0 -0
  82. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/diff_files.py +0 -0
  83. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/diff_tool/__init__.py +0 -0
  84. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/diff_tool/__main__.py +0 -0
  85. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/dirsize/__init__.py +0 -0
  86. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/disk_cleanup.py +0 -0
  87. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/disk_usage/__init__.py +0 -0
  88. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/disk_usage/disk_usage.py +0 -0
  89. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/dns_lookup.py +0 -0
  90. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/docker_helper.py +0 -0
  91. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/dt_convert.py +0 -0
  92. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/env_diff.py +0 -0
  93. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/env_manager.py +0 -0
  94. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/env_template.py +0 -0
  95. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/envcheck/__init__.py +0 -0
  96. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/epoch.py +0 -0
  97. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/excel2csv.py +0 -0
  98. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/factor.py +0 -0
  99. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ff/__init__.py +0 -0
  100. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ff/__main__.py +0 -0
  101. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/figlet_cli.py +0 -0
  102. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/figlet_tool.py +0 -0
  103. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/file_encrypt.py +0 -0
  104. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/file_find.py +0 -0
  105. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/file_joiner.py +0 -0
  106. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/file_splitter.py +0 -0
  107. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/file_watch.py +0 -0
  108. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/find_dups/__init__.py +0 -0
  109. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/find_dups/cli.py +0 -0
  110. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/firewall_rule.py +0 -0
  111. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/fmt/__init__.py +0 -0
  112. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/fmt/fmt.py +0 -0
  113. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/fold.py +0 -0
  114. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/geo_ip.py +0 -0
  115. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/git_branch_cleaner.py +0 -0
  116. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/git_log_pretty.py +0 -0
  117. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/git_stats.py +0 -0
  118. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/gzip_cli.py +0 -0
  119. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/hash_check.py +0 -0
  120. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/hash_file.py +0 -0
  121. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/hashsum/__init__.py +0 -0
  122. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/hashsum/__main__.py +0 -0
  123. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/hex_tool.py +0 -0
  124. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/hexdump.py +0 -0
  125. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/html2markdown.py +0 -0
  126. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/html2md.py +0 -0
  127. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/http_headers.py +0 -0
  128. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/http_live/__init__.py +0 -0
  129. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/http_live/__main__.py +0 -0
  130. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/http_server.py +0 -0
  131. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/http_status.py +0 -0
  132. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/image_meta.py +0 -0
  133. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ini2json.py +0 -0
  134. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ini_parser/__init__.py +0 -0
  135. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ini_parser/ini_parser.py +0 -0
  136. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ip_info.py +0 -0
  137. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ip_location.py +0 -0
  138. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ipcalc/__init__.py +0 -0
  139. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ipcalc/__main__.py +0 -0
  140. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ipinfo/__init__.py +0 -0
  141. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ipinfo/__main__.py +0 -0
  142. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/join.py +0 -0
  143. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/joke.py +0 -0
  144. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/jq_lite/__init__.py +0 -0
  145. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/jq_lite/__main__.py +0 -0
  146. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json2csv/__init__.py +0 -0
  147. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json2csv/__main__.py +0 -0
  148. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json2ini.py +0 -0
  149. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_diff.py +0 -0
  150. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_flatten.py +0 -0
  151. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_merge.py +0 -0
  152. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_path.py +0 -0
  153. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_pretty/__init__.py +0 -0
  154. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_pretty/json_pretty.py +0 -0
  155. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_schema_validate.py +0 -0
  156. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_sort.py +0 -0
  157. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_to_table.py +0 -0
  158. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/json_to_yaml.py +0 -0
  159. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/jsonql/__init__.py +0 -0
  160. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/jsonql/__main__.py +0 -0
  161. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/jwt_decode.py +0 -0
  162. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/key_value_store.py +0 -0
  163. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/license.py +0 -0
  164. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/license_cli/__init__.py +0 -0
  165. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/license_cli/__main__.py +0 -0
  166. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/license_cli/cli.py +0 -0
  167. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/log_analyzer.py +0 -0
  168. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/log_hawk.py +0 -0
  169. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/log_tail.py +0 -0
  170. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/macrogen.py +0 -0
  171. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/markdown_check/__init__.py +0 -0
  172. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/markdown_lint.py +0 -0
  173. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/markdown_preview.py +0 -0
  174. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/markdown_to_html.py +0 -0
  175. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/markdown_toc.py +0 -0
  176. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/math_eval.py +0 -0
  177. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/media_studio.py +0 -0
  178. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/morse.py +0 -0
  179. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/nb/__init__.py +0 -0
  180. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/nb/__main__.py +0 -0
  181. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/net_analyzer.py +0 -0
  182. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/net_speed.py +0 -0
  183. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/network_scan.py +0 -0
  184. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/nl.py +0 -0
  185. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/note_taker.py +0 -0
  186. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/otp_gen.py +0 -0
  187. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/passgen/__init__.py +0 -0
  188. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/password_strength.py +0 -0
  189. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/pdf_info.py +0 -0
  190. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/pdf_text.py +0 -0
  191. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/pipe_viewer.py +0 -0
  192. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/pomodoro.py +0 -0
  193. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/port_scan.py +0 -0
  194. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/portcheck/__init__.py +0 -0
  195. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/portcheck/__main__.py +0 -0
  196. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/pr_tool/__init__.py +0 -0
  197. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/pr_tool/pr_tool.py +0 -0
  198. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/process_kill.py +0 -0
  199. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/progress_bar.py +0 -0
  200. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/project_doctor/__init__.py +0 -0
  201. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/project_doctor/__main__.py +0 -0
  202. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/qc_calc.py +0 -0
  203. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/qc_report.py +0 -0
  204. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/qc_sample.py +0 -0
  205. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/qr_cli.py +0 -0
  206. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/qrcode.py +0 -0
  207. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/quote.py +0 -0
  208. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/quote_tool/__init__.py +0 -0
  209. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/quote_tool/quote.py +0 -0
  210. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/rainbow.py +0 -0
  211. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/random.py +0 -0
  212. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/random_cli.py +0 -0
  213. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/random_string.py +0 -0
  214. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/reminder.py +0 -0
  215. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ren/__init__.py +0 -0
  216. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ren/__main__.py +0 -0
  217. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/replace_text.py +0 -0
  218. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/restore.py +0 -0
  219. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/rot13.py +0 -0
  220. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/route_trace.py +0 -0
  221. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/scan_open_ports.py +0 -0
  222. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/scan_ports.py +0 -0
  223. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/screen_recorder.py +0 -0
  224. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/screenshot_cli.py +0 -0
  225. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/search_files.py +0 -0
  226. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/search_history.py +0 -0
  227. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/secret_scanner.py +0 -0
  228. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/seq.py +0 -0
  229. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/service_check.py +0 -0
  230. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/shuffle.py +0 -0
  231. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/siege_lite/__init__.py +0 -0
  232. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/siege_lite/__main__.py +0 -0
  233. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/slugify.py +0 -0
  234. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/smellfinder/__init__.py +0 -0
  235. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/smellfinder/__main__.py +0 -0
  236. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sort/__init__.py +0 -0
  237. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sort/sort.py +0 -0
  238. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/spinner.py +0 -0
  239. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/split.py +0 -0
  240. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/split_tool/__init__.py +0 -0
  241. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/split_tool/split.py +0 -0
  242. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sql2csv.py +0 -0
  243. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sqlite_cli/__init__.py +0 -0
  244. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sqlite_cli/__main__.py +0 -0
  245. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ssh_key_gen.py +0 -0
  246. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/ssl_check.py +0 -0
  247. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/stopwatch.py +0 -0
  248. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/subnet.py +0 -0
  249. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sysmon/__init__.py +0 -0
  250. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sysmon/__main__.py +0 -0
  251. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/sysmon_pro.py +0 -0
  252. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/system_info.py +0 -0
  253. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/temp_cleaner.py +0 -0
  254. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/template.py +0 -0
  255. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/text_dedent.py +0 -0
  256. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/text_stats.py +0 -0
  257. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/text_wrap.py +0 -0
  258. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/time_duration.py +0 -0
  259. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/timeout.py +0 -0
  260. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/timer/__init__.py +0 -0
  261. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/timer_pro/__init__.py +0 -0
  262. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/timer_pro/timer_pro.py +0 -0
  263. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/timer_pro.py +0 -0
  264. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/todo_cli.py +0 -0
  265. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/toml2json.py +0 -0
  266. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/tr.py +0 -0
  267. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/tree.py +0 -0
  268. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/treedir/__init__.py +0 -0
  269. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/treedir/__main__.py +0 -0
  270. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/tsv2csv.py +0 -0
  271. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/uniq_tool/__init__.py +0 -0
  272. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/uniq_tool/uniq.py +0 -0
  273. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/unit_convert.py +0 -0
  274. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/uri_encode.py +0 -0
  275. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/url_parser.py +0 -0
  276. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/urlparse_tool/__init__.py +0 -0
  277. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/urlparse_tool/cli.py +0 -0
  278. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/uuid_gen.py +0 -0
  279. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/uuid_tool/__init__.py +0 -0
  280. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/uuid_tool/__main__.py +0 -0
  281. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/watch.py +0 -0
  282. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/weather_cli.py +0 -0
  283. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/web_download.py +0 -0
  284. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/web_summary/__init__.py +0 -0
  285. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/web_summary/__main__.py +0 -0
  286. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/whois_lookup.py +0 -0
  287. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/wordcount/__init__.py +0 -0
  288. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/wordcount/__main__.py +0 -0
  289. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/world_clock.py +0 -0
  290. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/xml2json.py +0 -0
  291. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/xml_format.py +0 -0
  292. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/yaml2json/__init__.py +0 -0
  293. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/yaml2json/yaml2json.py +0 -0
  294. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/yaml2toml.py +0 -0
  295. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/yaml_validate.py +0 -0
  296. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools/vendor/yes.py +0 -0
  297. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools.egg-info/dependency_links.txt +0 -0
  298. {evolver_tools-30.0.0 → evolver_tools-31.0.0}/src/evolver_tools.egg-info/entry_points.txt +0 -0
  299. {evolver_tools-30.0.0 → evolver_tools-31.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: 30.0.0
4
- Summary: 233 CLI tools + 9 flagship projects — one pip install
3
+ Version: 31.0.0
4
+ Summary: 238 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 = "30.0.0"
8
- description = "233 CLI tools + 9 flagship projects — one pip install"
7
+ version = "31.0.0"
8
+ description = "238 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 v30.0.0 =====\x1b[0m')
17
+ print(f'\x1b[1;36m===== EVOLVER Tools v31.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,120 @@
1
+ #!/usr/bin/env python3
2
+ """csv_filter.py — Filter CSV rows by column value matching.
3
+
4
+ Usage:
5
+ cat data.csv | python csv_filter.py --column name --value Alice
6
+ python csv_filter.py data.csv --column age --value 30
7
+ python csv_filter.py data.csv --column city --value "New York" --delimiter ';'
8
+ python csv_filter.py data.csv --column status --value active --invert
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import csv
14
+ import sys
15
+ import argparse
16
+ from typing import Dict, List, Sequence, TextIO
17
+
18
+
19
+ TOOL_META = {
20
+ "name": "csv-filter",
21
+ "func": "main",
22
+ "desc": "Filter CSV rows by column value",
23
+ }
24
+
25
+
26
+ def build_parser() -> argparse.ArgumentParser:
27
+ parser = argparse.ArgumentParser(
28
+ description="Filter CSV rows by column value matching.",
29
+ )
30
+ parser.add_argument(
31
+ "file",
32
+ nargs="?",
33
+ default=None,
34
+ help="CSV file path (omit or use '-' to read from stdin)",
35
+ )
36
+ parser.add_argument(
37
+ "--column",
38
+ required=True,
39
+ help="Column name to filter on",
40
+ )
41
+ parser.add_argument(
42
+ "--value",
43
+ required=True,
44
+ help="Value to match (exact string match)",
45
+ )
46
+ parser.add_argument(
47
+ "--delimiter",
48
+ default=",",
49
+ help="CSV delimiter character (default: ',')",
50
+ )
51
+ parser.add_argument(
52
+ "--invert",
53
+ action="store_true",
54
+ default=False,
55
+ help="Invert match — print rows where column does NOT equal value",
56
+ )
57
+ return parser
58
+
59
+
60
+ def open_input(file_arg: str | None) -> TextIO:
61
+ """Return a file handle for the given argument or stdin."""
62
+ if file_arg is None or file_arg == "-":
63
+ return sys.stdin
64
+ return open(file_arg, newline="")
65
+
66
+
67
+ def filter_rows(
68
+ reader: csv.DictReader,
69
+ column: str,
70
+ value: str,
71
+ invert: bool,
72
+ ) -> List[Dict[str, str]]:
73
+ """Filter rows from a DictReader where column matches (or doesn't match) value."""
74
+ results: List[Dict[str, str]] = []
75
+ for row in reader:
76
+ cell = row.get(column, "")
77
+ if invert:
78
+ if cell != value:
79
+ results.append(row)
80
+ else:
81
+ if cell == value:
82
+ results.append(row)
83
+ return results
84
+
85
+
86
+ def write_rows(rows: List[Dict[str, str]], fieldnames: Sequence[str], delimiter: str) -> None:
87
+ """Write filtered rows to stdout as CSV."""
88
+ writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames, delimiter=delimiter)
89
+ writer.writeheader()
90
+ writer.writerows(rows)
91
+
92
+
93
+ def main(argv: list[str] | None = None) -> None:
94
+ parser = build_parser()
95
+ args = parser.parse_args(argv)
96
+
97
+ fh = open_input(args.file)
98
+ try:
99
+ reader = csv.DictReader(fh, delimiter=args.delimiter)
100
+ if not reader.fieldnames:
101
+ sys.exit("error: CSV has no header row (column names required)")
102
+
103
+ fieldnames = reader.fieldnames
104
+
105
+ if args.column not in fieldnames:
106
+ sys.exit(
107
+ f"error: column '{args.column}' not found in CSV header. "
108
+ f"Available columns: {', '.join(fieldnames)}"
109
+ )
110
+
111
+ rows = filter_rows(reader, args.column, args.value, args.invert)
112
+ finally:
113
+ if fh is not sys.stdin:
114
+ fh.close()
115
+
116
+ write_rows(rows, fieldnames, args.delimiter)
117
+
118
+
119
+ if __name__ == "__main__":
120
+ main()
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env python3
2
+ """env-sorter — Sort environment variables in .env files alphabetically.
3
+
4
+ Usage:
5
+ env-sorter --file .env
6
+ env-sorter --file .env --in-place
7
+ env-sorter --file .env --sort-by value
8
+ env-sorter --file .env --group
9
+ env-sorter --file .env --remove-dups
10
+ env-sorter --file .env --in-place --group --sort-by value --remove-dups
11
+
12
+ Sorts the content of dotenv-style files while preserving comments and
13
+ blank lines as best possible. Supports grouping by common prefix,
14
+ sorting by name or value, and deduplication.
15
+ """
16
+
17
+ import sys
18
+ import os
19
+
20
+
21
+ TOOL_META = {
22
+ "name": "env-sorter",
23
+ "func": "main",
24
+ "desc": "Sort environment variables in .env files",
25
+ }
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # .env parsing & writing
30
+ # ---------------------------------------------------------------------------
31
+
32
+ def parse_env(text):
33
+ """Parse .env text into a list of (kind, key, value) tuples.
34
+
35
+ Each tuple is one of:
36
+ ('blank', None, None) — empty line
37
+ ('comment', None, text) — comment line (includes leading ``#``)
38
+ ('var', key, value) — KEY=VALUE assignment
39
+ Order is preserved as-is.
40
+ """
41
+ entries = []
42
+ for line in text.splitlines(keepends=True):
43
+ stripped = line.strip()
44
+ if stripped == "":
45
+ entries.append(("blank", None, None))
46
+ elif stripped.startswith("#"):
47
+ entries.append(("comment", None, line))
48
+ elif "=" in stripped:
49
+ # Split on first '=' only
50
+ key, _, val = line.partition("=")
51
+ entries.append(("var", key.rstrip(), val))
52
+ else:
53
+ # Lines that don't match any pattern — treat as literal
54
+ entries.append(("comment", None, line))
55
+ return entries
56
+
57
+
58
+ def _get_prefix(key, sep="_"):
59
+ """Return the prefix of *key* (everything before the first separator)."""
60
+ idx = key.find(sep)
61
+ if idx == -1:
62
+ return ""
63
+ return key[:idx]
64
+
65
+
66
+ def group_sort(entries, sort_key="name", group=True, remove_dups=False):
67
+ """Sort *entries* by variable key/value with optional grouping & dedup.
68
+
69
+ Parameters
70
+ ----------
71
+ entries : list[tuple]
72
+ Parsed entries from :func:`parse_env`.
73
+ sort_key : str
74
+ ``"name"`` (default) sorts by variable name; ``"value"`` sorts by
75
+ value (ties broken by name).
76
+ group : bool
77
+ When *True*, entries are grouped by prefix (everything before the
78
+ first ``_``). Blank lines and comments stay in their relative
79
+ position between groups.
80
+ remove_dups : bool
81
+ When *True*, only the *last* occurrence of a variable name is kept.
82
+ """
83
+ # Separate non-variable entries and variable entries
84
+ non_vars = [(i, e) for i, e in enumerate(entries) if e[0] != "var"]
85
+ var_entries = [(i, e) for i, e in enumerate(entries) if e[0] == "var"]
86
+
87
+ if remove_dups:
88
+ seen = {}
89
+ # Iterate in original order so the *last* occurrence wins
90
+ for idx, e in var_entries:
91
+ seen[e[1]] = (idx, e)
92
+ var_entries = sorted(seen.values(), key=lambda x: x[0])
93
+
94
+ # Build sort key for each variable entry
95
+ def _sort_key(item):
96
+ idx, e = item
97
+ key = e[1]
98
+ val = e[2].lstrip()
99
+ if sort_key == "value":
100
+ primary = val.lower() if val else ""
101
+ secondary = key.lower()
102
+ else:
103
+ primary = key.lower()
104
+ secondary = ""
105
+ if group:
106
+ prefix = _get_prefix(key)
107
+ return (prefix, primary, secondary, idx)
108
+ return (primary, secondary, idx)
109
+
110
+ var_entries.sort(key=_sort_key)
111
+
112
+ if group:
113
+ return _interleave_groups(var_entries, non_vars, entries)
114
+ else:
115
+ return _rebuild(var_entries, non_vars)
116
+
117
+
118
+ def _interleave_groups(var_entries, non_vars, original_entries):
119
+ """Interleave non-variable lines between groups of variable entries.
120
+
121
+ Non-variable lines (blanks, comments) that appeared *between* groups
122
+ in the original file are placed before each group. Trailing
123
+ non-variable lines are appended at the end.
124
+ """
125
+ # Determine groups
126
+ groups = []
127
+ current_group_key = None
128
+ current_group = []
129
+ for idx, e in var_entries:
130
+ prefix = _get_prefix(e[1])
131
+ if prefix != current_group_key:
132
+ if current_group:
133
+ groups.append((current_group_key, current_group))
134
+ current_group_key = prefix
135
+ current_group = []
136
+ current_group.append((idx, e))
137
+ if current_group:
138
+ groups.append((current_group_key, current_group))
139
+
140
+ # Map original index to its entry
141
+ original_by_idx = {i: e for i, e in enumerate(original_entries)}
142
+
143
+ # Assign non-var lines to groups based on proximity in original file
144
+ # Simple approach: split non_vars at the boundaries between groups
145
+ if not groups:
146
+ return [e for _, e in sorted(non_vars, key=lambda x: x[0])]
147
+
148
+ # Collect boundaries: the indices where each group started in the original
149
+ group_boundaries = [(g[0][0], g) for g in groups] # (start_idx, group_data)
150
+
151
+ result = []
152
+ non_var_iter = iter(sorted(non_vars, key=lambda x: x[0]))
153
+ consumed = set()
154
+
155
+ for g_idx, (g_start, (g_prefix, g_entries)) in enumerate(group_boundaries):
156
+ # Add non-var lines that appeared before this group in the original
157
+ while True:
158
+ try:
159
+ nv_idx, nv_entry = next(non_var_iter)
160
+ except StopIteration:
161
+ break
162
+ if nv_idx > g_start and g_idx > 0:
163
+ # This non-var belongs to the previous group, put it back
164
+ # Actually let's just insert any non-var that's before the group start
165
+ if nv_idx < g_start:
166
+ result.append(nv_entry)
167
+ consumed.add(nv_idx)
168
+ else:
169
+ # Put it back by recreating the iterator
170
+ # Simple: just add all remaining non-vars at the end
171
+ non_var_iter = iter([(nv_idx, nv_entry)] + list(non_var_iter))
172
+ break
173
+ else:
174
+ if nv_idx < g_start:
175
+ result.append(nv_entry)
176
+ consumed.add(nv_idx)
177
+ else:
178
+ non_var_iter = iter([(nv_idx, nv_entry)] + list(non_var_iter))
179
+ break
180
+
181
+ # Add the group entries
182
+ for _, e in g_entries:
183
+ result.append(e)
184
+
185
+ # Add any remaining non-var lines
186
+ for nv_idx, nv_entry in sorted(non_vars, key=lambda x: x[0]):
187
+ if nv_idx not in consumed and nv_idx not in {id(e) for e in result}:
188
+ result.append(nv_entry)
189
+
190
+ return result
191
+
192
+
193
+ def _rebuild(var_entries, non_vars):
194
+ """Merge sorted variable entries with non-variable lines.
195
+
196
+ Non-variable lines are inserted in their original relative positions
197
+ where possible; otherwise appended at the end.
198
+ """
199
+ result = []
200
+ non_var_sorted = sorted(non_vars, key=lambda x: x[0])
201
+ nv_idx = 0
202
+
203
+ # Place non-var lines that appeared before the first variable at top
204
+ while nv_idx < len(non_var_sorted) and non_var_sorted[nv_idx][0] < var_entries[0][0]:
205
+ result.append(non_var_sorted[nv_idx][1])
206
+ nv_idx += 1
207
+
208
+ for idx, e in var_entries:
209
+ result.append(e)
210
+ # Add any non-var lines that sit between this var and the next
211
+ while nv_idx < len(non_var_sorted) and (
212
+ nv_idx + 1 >= len(non_var_sorted)
213
+ or (
214
+ idx < non_var_sorted[nv_idx][0]
215
+ and (
216
+ nv_idx + 1 >= len(var_entries)
217
+ or non_var_sorted[nv_idx][0] < var_entries[nv_idx + 1][0]
218
+ if nv_idx + 1 < len(var_entries)
219
+ else True
220
+ )
221
+ )
222
+ ):
223
+ result.append(non_var_sorted[nv_idx][1])
224
+ nv_idx += 1
225
+
226
+ # Any leftover non-var lines
227
+ while nv_idx < len(non_var_sorted):
228
+ result.append(non_var_sorted[nv_idx][1])
229
+ nv_idx += 1
230
+
231
+ return result
232
+
233
+
234
+ def serialize_entries(entries):
235
+ """Convert a list of entry tuples back into a single string."""
236
+ lines = []
237
+ for e in entries:
238
+ kind = e[0]
239
+ if kind == "blank":
240
+ lines.append("\n")
241
+ elif kind == "comment":
242
+ lines.append(e[2]) # e[2] is the full line
243
+ elif kind == "var":
244
+ lines.append(f"{e[1]}={e[2]}")
245
+ return "".join(lines)
246
+
247
+
248
+ # ---------------------------------------------------------------------------
249
+ # CLI
250
+ # ---------------------------------------------------------------------------
251
+
252
+ def main():
253
+ args = sys.argv[1:]
254
+
255
+ if "-h" in args or "--help" in args:
256
+ print(__doc__)
257
+ return
258
+
259
+ filepath = None
260
+ in_place = False
261
+ sort_by = "name"
262
+ group = False
263
+ remove_dups = False
264
+
265
+ i = 0
266
+ while i < len(args):
267
+ a = args[i]
268
+ if a == "--file" and i + 1 < len(args):
269
+ filepath = args[i + 1]
270
+ i += 2
271
+ elif a == "--in-place":
272
+ in_place = True
273
+ i += 1
274
+ elif a == "--sort-by" and i + 1 < len(args):
275
+ val = args[i + 1].lower()
276
+ if val not in ("name", "value"):
277
+ print(f"Error: --sort-by must be 'name' or 'value', got '{val}'", file=sys.stderr)
278
+ sys.exit(1)
279
+ sort_by = val
280
+ i += 2
281
+ elif a == "--group":
282
+ group = True
283
+ i += 1
284
+ elif a == "--remove-dups":
285
+ remove_dups = True
286
+ i += 1
287
+ else:
288
+ print(f"Error: unknown argument '{a}'", file=sys.stderr)
289
+ sys.exit(1)
290
+
291
+ # Read input
292
+ if filepath:
293
+ try:
294
+ with open(filepath, "r") as f:
295
+ text = f.read()
296
+ except FileNotFoundError:
297
+ print(f"Error: file not found '{filepath}'", file=sys.stderr)
298
+ sys.exit(1)
299
+ except PermissionError:
300
+ print(f"Error: permission denied '{filepath}'", file=sys.stderr)
301
+ sys.exit(1)
302
+ else:
303
+ # Read from stdin
304
+ text = sys.stdin.read()
305
+
306
+ entries = parse_env(text)
307
+ sorted_entries = group_sort(entries, sort_by, group, remove_dups)
308
+ output = serialize_entries(sorted_entries)
309
+
310
+ if in_place:
311
+ if not filepath:
312
+ print("Error: --in-place requires --file", file=sys.stderr)
313
+ sys.exit(1)
314
+ with open(filepath, "w") as f:
315
+ f.write(output)
316
+ else:
317
+ sys.stdout.write(output)
318
+
319
+
320
+ if __name__ == "__main__":
321
+ main()
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env python3
2
+ """file-patch — Apply simple text patches (find/replace) to files.
3
+
4
+ Performs a single find-and-replace operation on a text file.
5
+ Supports dry-run mode and automatic backup creation.
6
+
7
+ Usage:
8
+ file-patch --file README.md --old "foo" --new "bar"
9
+ file-patch --file config.py --old "DEBUG = True" --new "DEBUG = False" --dry-run
10
+ file-patch --file settings.ini --old "port=8080" --new "port=9090" --backup
11
+ file-patch --file script.sh --old "some old text" --new ""
12
+
13
+ Options:
14
+ --file PATH Path to the file to patch (required)
15
+ --old TEXT Text to find (required)
16
+ --new TEXT Replacement text (required, may be empty string)
17
+ --dry-run Show what would be changed without modifying the file
18
+ --backup Create a .bak backup of the original file
19
+ -h, --help Show this help message
20
+ """
21
+
22
+ import sys
23
+ import os
24
+ import argparse
25
+
26
+
27
+ TOOL_META = {
28
+ "name": "file-patch",
29
+ "func": "main",
30
+ "desc": "Apply text patches to files",
31
+ }
32
+
33
+
34
+ def apply_patch(
35
+ filepath: str,
36
+ old_text: str,
37
+ new_text: str,
38
+ dry_run: bool = False,
39
+ backup: bool = False,
40
+ ) -> int:
41
+ """Apply a find/replace patch to a file.
42
+
43
+ Reads the file, performs exactly one replacement of *old_text* with
44
+ *new_text*, and writes the result back. If *old_text* is not found,
45
+ or is found more than once, the function exits with an error.
46
+
47
+ Args:
48
+ filepath: Path to the file to patch.
49
+ old_text: The exact text to search for.
50
+ new_text: The text to replace it with.
51
+ dry_run: If True, only display what would change.
52
+ backup: If True, create a .bak copy before modifying.
53
+
54
+ Returns:
55
+ 0 on success, 1 on failure.
56
+ """
57
+ if not os.path.isfile(filepath):
58
+ print(f"Error: file not found: {filepath}", file=sys.stderr)
59
+ return 1
60
+
61
+ # Read the file
62
+ try:
63
+ with open(filepath, "r", encoding="utf-8") as f:
64
+ content = f.read()
65
+ except (OSError, UnicodeDecodeError) as e:
66
+ print(f"Error: cannot read '{filepath}': {e}", file=sys.stderr)
67
+ return 1
68
+
69
+ # Count occurrences
70
+ count = content.count(old_text)
71
+
72
+ if count == 0:
73
+ print(f"Error: string not found in '{filepath}'", file=sys.stderr)
74
+ return 1
75
+
76
+ if count > 1:
77
+ print(
78
+ f"Error: found {count} occurrences of the search string in "
79
+ f"'{filepath}'. Expected exactly 1. Use a more specific "
80
+ f"--old string.",
81
+ file=sys.stderr,
82
+ )
83
+ return 1
84
+
85
+ # Perform the replacement
86
+ new_content = content.replace(old_text, new_text, 1)
87
+
88
+ # Show diff-style output
89
+ _show_patch(filepath, old_text, new_text, count)
90
+
91
+ if dry_run:
92
+ print(f"[DRY-RUN] No changes written to '{filepath}'.")
93
+ return 0
94
+
95
+ # Create backup if requested
96
+ if backup:
97
+ backup_path = filepath + ".bak"
98
+ try:
99
+ # Avoid overwriting an existing backup
100
+ if os.path.exists(backup_path):
101
+ base, ext = backup_path, ""
102
+ idx = 1
103
+ while os.path.exists(f"{base}.{idx}"):
104
+ idx += 1
105
+ backup_path = f"{filepath}.bak.{idx}"
106
+ with open(backup_path, "w", encoding="utf-8") as bf:
107
+ bf.write(content)
108
+ print(f"Backup created: {backup_path}")
109
+ except OSError as e:
110
+ print(f"Error: cannot create backup '{backup_path}': {e}", file=sys.stderr)
111
+ return 1
112
+
113
+ # Write the patched file
114
+ try:
115
+ with open(filepath, "w", encoding="utf-8") as f:
116
+ f.write(new_content)
117
+ print(f"Patched: {filepath}")
118
+ except OSError as e:
119
+ print(f"Error: cannot write '{filepath}': {e}", file=sys.stderr)
120
+ return 1
121
+
122
+ return 0
123
+
124
+
125
+ def _show_patch(filepath: str, old_text: str, new_text: str, count: int) -> None:
126
+ """Print a human-readable summary of the patch."""
127
+ print(f"File: {filepath} ({count} occurrence{'s' if count != 1 else ''})")
128
+ # Show context: first line of old text
129
+ old_first = old_text.split("\n")[0]
130
+ new_first = new_text.split("\n")[0]
131
+ if old_text == new_text:
132
+ print(" (no change — old and new text are identical)")
133
+ else:
134
+ print(f" -{old_first}")
135
+ print(f" +{new_first}")
136
+ print()
137
+
138
+
139
+ def main():
140
+ parser = argparse.ArgumentParser(
141
+ description="Apply a simple text find/replace patch to a file.",
142
+ formatter_class=argparse.RawDescriptionHelpFormatter,
143
+ epilog=(
144
+ "Examples:\n"
145
+ " file-patch --file README.md --old \"foo\" --new \"bar\"\n"
146
+ " file-patch --file config.py --old \"DEBUG = True\" "
147
+ "--new \"DEBUG = False\" --dry-run\n"
148
+ " file-patch --file settings.ini --old \"port=8080\" "
149
+ "--new \"port=9090\" --backup\n"
150
+ " file-patch --file script.sh --old \"some old text\" --new \"\"\n"
151
+ ),
152
+ )
153
+ parser.add_argument(
154
+ "--file", required=True,
155
+ help="Path to the file to patch",
156
+ )
157
+ parser.add_argument(
158
+ "--old", required=True,
159
+ help="Text to find (exact match)",
160
+ )
161
+ parser.add_argument(
162
+ "--new", required=True,
163
+ default="",
164
+ help="Replacement text (may be empty string)",
165
+ )
166
+ parser.add_argument(
167
+ "--dry-run", action="store_true",
168
+ help="Show what would be changed without modifying the file",
169
+ )
170
+ parser.add_argument(
171
+ "--backup", action="store_true",
172
+ help="Create a .bak backup of the original file before patching",
173
+ )
174
+ args = parser.parse_args()
175
+
176
+ filepath = os.path.abspath(args.file)
177
+
178
+ exit_code = apply_patch(
179
+ filepath=filepath,
180
+ old_text=args.old,
181
+ new_text=args.new,
182
+ dry_run=args.dry_run,
183
+ backup=args.backup,
184
+ )
185
+ sys.exit(exit_code)
186
+
187
+
188
+ if __name__ == "__main__":
189
+ main()