fcmd 0.2.0__tar.gz → 0.2.2__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 (211) hide show
  1. {fcmd-0.2.0 → fcmd-0.2.2}/.bumpversion.toml +1 -1
  2. {fcmd-0.2.0 → fcmd-0.2.2}/PKG-INFO +4 -5
  3. {fcmd-0.2.0 → fcmd-0.2.2}/README.md +2 -3
  4. {fcmd-0.2.0 → fcmd-0.2.2}/pyproject.toml +1 -2
  5. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/__init__.py +1 -1
  6. fcmd-0.2.2/src/fcmd/__main__.py +6 -0
  7. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/_compat.py +1 -1
  8. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/dag.py +2 -3
  9. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/task.py +0 -1
  10. fcmd-0.2.2/src/fcmd/cli/_env_persist.py +101 -0
  11. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/_profiler_helpers.py +2 -0
  12. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/archivex.py +4 -0
  13. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/asciitool.py +4 -0
  14. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/autofmt.py +5 -1
  15. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/bumpversion.py +4 -0
  16. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/casetool.py +4 -0
  17. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/clr.py +5 -1
  18. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/codetool.py +4 -0
  19. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/colortool.py +4 -0
  20. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/convtool.py +4 -0
  21. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/cryptool.py +4 -0
  22. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/csvtool.py +4 -0
  23. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/dockercmd.py +5 -1
  24. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/envdev.py +123 -40
  25. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/filedate.py +4 -0
  26. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/filelevel.py +4 -0
  27. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/filerename.py +4 -0
  28. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/filesearch.py +4 -0
  29. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/folderback.py +4 -0
  30. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/folderzip.py +5 -1
  31. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/gittool.py +4 -0
  32. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/hashfile.py +4 -0
  33. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/hashtool.py +4 -0
  34. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/idtool.py +4 -0
  35. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/imagetool.py +4 -0
  36. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/iptool.py +4 -0
  37. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/jsontool.py +4 -0
  38. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/lscalc.py +4 -0
  39. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/main.py +3 -0
  40. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/mathtool.py +4 -0
  41. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/nettool.py +4 -0
  42. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/packtool.py +4 -0
  43. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/padtool.py +4 -0
  44. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/pathtool.py +4 -0
  45. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/pdftool.py +4 -0
  46. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/piptool.py +4 -0
  47. fcmd-0.2.2/src/fcmd/cli/portcheck.py +295 -0
  48. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/pymake.py +5 -1
  49. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/randtool.py +4 -0
  50. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/regextool.py +4 -0
  51. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/reseticoncache.py +5 -1
  52. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/screenshot.py +4 -0
  53. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/setenv.py +5 -1
  54. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/sshcopyid.py +5 -1
  55. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/stattool.py +4 -0
  56. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/sysinfo.py +4 -0
  57. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/taskkill.py +4 -0
  58. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/textdiff.py +4 -0
  59. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/timetool.py +4 -0
  60. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/txttool.py +4 -0
  61. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/urltool.py +4 -0
  62. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/which.py +5 -1
  63. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/writefile.py +5 -1
  64. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/xmltool.py +4 -0
  65. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/yamtool.py +4 -0
  66. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/zipencrypt.py +4 -0
  67. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/command.py +1 -0
  68. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/console.py +17 -1
  69. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/executors.py +2 -0
  70. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli.py +0 -1
  71. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_codetool.py +15 -0
  72. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_envdev.py +131 -17
  73. fcmd-0.2.2/tests/test_cli_portcheck.py +443 -0
  74. fcmd-0.2.2/tests/test_env_persist.py +142 -0
  75. {fcmd-0.2.0 → fcmd-0.2.2}/uv.lock +1 -1
  76. fcmd-0.2.0/.trae/.ignore +0 -15
  77. fcmd-0.2.0/.trae/docs/iter-46-p45-structure-refactor.md +0 -52
  78. fcmd-0.2.0/.trae/docs/iter-47-p46-main-split.md +0 -94
  79. fcmd-0.2.0/.trae/docs/iter-48-p47-win7-rich-width.md +0 -84
  80. fcmd-0.2.0/.trae/docs/iter-49-p48-win7-rich-ascii.md +0 -111
  81. fcmd-0.2.0/.trae/docs/iter-50-p49-remove-rich.md +0 -82
  82. fcmd-0.2.0/.trae/req/done/req-01-/345/212/237/350/203/275/351/234/200/346/261/202.md +0 -3
  83. fcmd-0.2.0/.trae/rules/git-commit-message.md +0 -56
  84. fcmd-0.2.0/.trae/rules/rule-01-/345/274/200/345/217/221/346/265/201/347/250/213.md +0 -69
  85. fcmd-0.2.0/.trae/rules/rule-02-/344/272/247/347/211/251/347/272/246/346/235/237.md +0 -30
  86. fcmd-0.2.0/.trae/rules/rule-03-/350/247/246/345/217/221/345/234/272/346/231/257.md +0 -29
  87. fcmd-0.2.0/.trae/skills/python-class-design/SKILL.md +0 -518
  88. fcmd-0.2.0/.trae/skills/python-cli/SKILL.md +0 -718
  89. fcmd-0.2.0/.trae/skills/python-concurrency/SKILL.md +0 -589
  90. fcmd-0.2.0/.trae/skills/python-config/SKILL.md +0 -500
  91. fcmd-0.2.0/.trae/skills/python-file-io/SKILL.md +0 -534
  92. fcmd-0.2.0/.trae/skills/python-gui-pyside/LAYOUT.md +0 -285
  93. fcmd-0.2.0/.trae/skills/python-gui-pyside/PATTERNS.md +0 -664
  94. fcmd-0.2.0/.trae/skills/python-gui-pyside/SKILL.md +0 -891
  95. fcmd-0.2.0/.trae/skills/python-gui-pyside/UI-DESIGN.md +0 -259
  96. fcmd-0.2.0/.trae/skills/python-logging/SKILL.md +0 -497
  97. fcmd-0.2.0/.trae/skills/python-performance/SKILL.md +0 -805
  98. fcmd-0.2.0/.trae/skills/python-project-structure/SKILL.md +0 -907
  99. fcmd-0.2.0/.trae/skills/python-standards/SKILL.md +0 -145
  100. fcmd-0.2.0/.trae/skills/python-subprocess/SKILL.md +0 -500
  101. fcmd-0.2.0/.trae/skills/python-testing/SKILL.md +0 -684
  102. fcmd-0.2.0/src/fcmd/__main__.py +0 -6
  103. fcmd-0.2.0/src/fcmd/cli/basetool.py +0 -260
  104. fcmd-0.2.0/src/fcmd/cli/portcheck.py +0 -128
  105. fcmd-0.2.0/tests/test_cli_basetool.py +0 -255
  106. fcmd-0.2.0/tests/test_cli_portcheck.py +0 -128
  107. {fcmd-0.2.0 → fcmd-0.2.2}/.copier-answers.yml +0 -0
  108. {fcmd-0.2.0 → fcmd-0.2.2}/.coveragerc +0 -0
  109. {fcmd-0.2.0 → fcmd-0.2.2}/.gitattributes +0 -0
  110. {fcmd-0.2.0 → fcmd-0.2.2}/.github/workflows/ci.yml +0 -0
  111. {fcmd-0.2.0 → fcmd-0.2.2}/.github/workflows/release.yml +0 -0
  112. {fcmd-0.2.0 → fcmd-0.2.2}/.gitignore +0 -0
  113. {fcmd-0.2.0 → fcmd-0.2.2}/.pre-commit-config.yaml +0 -0
  114. {fcmd-0.2.0 → fcmd-0.2.2}/.python-version +0 -0
  115. {fcmd-0.2.0 → fcmd-0.2.2}/.readthedocs.yaml +0 -0
  116. {fcmd-0.2.0 → fcmd-0.2.2}/.vscode/settings.json +0 -0
  117. {fcmd-0.2.0 → fcmd-0.2.2}/LICENSE +0 -0
  118. {fcmd-0.2.0 → fcmd-0.2.2}/Makefile +0 -0
  119. {fcmd-0.2.0 → fcmd-0.2.2}/docs/_static/.gitkeep +0 -0
  120. {fcmd-0.2.0 → fcmd-0.2.2}/docs/api.rst +0 -0
  121. {fcmd-0.2.0 → fcmd-0.2.2}/docs/changelog.rst +0 -0
  122. {fcmd-0.2.0 → fcmd-0.2.2}/docs/conf.py +0 -0
  123. {fcmd-0.2.0 → fcmd-0.2.2}/docs/index.rst +0 -0
  124. {fcmd-0.2.0 → fcmd-0.2.2}/pyrefly.toml +0 -0
  125. {fcmd-0.2.0 → fcmd-0.2.2}/pytest.ini +0 -0
  126. {fcmd-0.2.0 → fcmd-0.2.2}/ruff.toml +0 -0
  127. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/__init__.py +0 -0
  128. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/context.py +0 -0
  129. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/errors.py +0 -0
  130. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/profiling.py +0 -0
  131. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/report.py +0 -0
  132. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/apis/toolkit.py +0 -0
  133. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/__init__.py +0 -0
  134. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/_common.py +0 -0
  135. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/cli/_completion_scripts.py +0 -0
  136. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/conditions.py +0 -0
  137. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/models/__init__.py +0 -0
  138. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/models/command.py +0 -0
  139. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/models/filefilter.py +0 -0
  140. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/models/version.py +0 -0
  141. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/py.typed +0 -0
  142. {fcmd-0.2.0 → fcmd-0.2.2}/src/fcmd/yaml_loader.py +0 -0
  143. {fcmd-0.2.0 → fcmd-0.2.2}/tests/__init__.py +0 -0
  144. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_archivex.py +0 -0
  145. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_asciitool.py +0 -0
  146. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_autofmt.py +0 -0
  147. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_bumpversion.py +0 -0
  148. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_casetool.py +0 -0
  149. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_clr.py +0 -0
  150. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_colortool.py +0 -0
  151. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_convtool.py +0 -0
  152. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_cryptool.py +0 -0
  153. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_csvtool.py +0 -0
  154. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_dockercmd.py +0 -0
  155. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_filedate.py +0 -0
  156. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_filelevel.py +0 -0
  157. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_filerename.py +0 -0
  158. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_filesearch.py +0 -0
  159. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_folderback.py +0 -0
  160. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_folderzip.py +0 -0
  161. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_gittool.py +0 -0
  162. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_hashfile.py +0 -0
  163. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_hashtool.py +0 -0
  164. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_idtool.py +0 -0
  165. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_imagetool.py +0 -0
  166. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_iptool.py +0 -0
  167. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_jsontool.py +0 -0
  168. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_lscalc.py +0 -0
  169. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_mathtool.py +0 -0
  170. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_nettool.py +0 -0
  171. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_packtool.py +0 -0
  172. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_padtool.py +0 -0
  173. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_pathtool.py +0 -0
  174. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_pdftool.py +0 -0
  175. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_piptool.py +0 -0
  176. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_profiler.py +0 -0
  177. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_pymake.py +0 -0
  178. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_randtool.py +0 -0
  179. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_regextool.py +0 -0
  180. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_reseticoncache.py +0 -0
  181. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_screenshot.py +0 -0
  182. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_setenv.py +0 -0
  183. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_sshcopyid.py +0 -0
  184. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_stattool.py +0 -0
  185. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_sysinfo.py +0 -0
  186. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_taskkill.py +0 -0
  187. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_textdiff.py +0 -0
  188. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_timetool.py +0 -0
  189. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_txttool.py +0 -0
  190. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_urltool.py +0 -0
  191. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_which.py +0 -0
  192. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_writefile.py +0 -0
  193. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_xmltool.py +0 -0
  194. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_yamtool.py +0 -0
  195. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_cli_zipencrypt.py +0 -0
  196. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_command.py +0 -0
  197. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_conditions.py +0 -0
  198. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_console.py +0 -0
  199. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_context.py +0 -0
  200. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_executors.py +0 -0
  201. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_fcmd.py +0 -0
  202. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_graph.py +0 -0
  203. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_init.py +0 -0
  204. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_models.py +0 -0
  205. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_profiling.py +0 -0
  206. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_report.py +0 -0
  207. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_task.py +0 -0
  208. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_toolkit.py +0 -0
  209. {fcmd-0.2.0 → fcmd-0.2.2}/tests/test_yaml_loader.py +0 -0
  210. {fcmd-0.2.0 → fcmd-0.2.2}/tox.ini +0 -0
  211. {fcmd-0.2.0 → fcmd-0.2.2}/uv.toml +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.2.0"
2
+ current_version = "0.2.2"
3
3
  commit = true
4
4
  exclude = ["template/*"]
5
5
  message = "chore: 更新版本 {current_version} → {new_version}"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: fcmd
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: 极速 Python 工具集应用:DAG 任务调度 + 组合 CLI
5
5
  Author: fcmd
6
6
  License: MIT
@@ -182,7 +182,7 @@ fcmd completion --shell fish | source # fish
182
182
  |------|------|------|
183
183
  | `txttool` | - | 文本处理(大小写/去重/排序/统计/截断) |
184
184
  | `asciitool` | - | ASCII 码表(char/code/table) |
185
- | `basetool` | - | 编码解码(Base64/URL/HTML/Hex) |
185
+ | `codetool` | - | 编解码(Base64/URL/HTML/Hex/ROT13) |
186
186
  | `casetool` | - | 命名风格转换(camel/snake/kebab/pascal) |
187
187
  | `colortool` | - | 颜色换算(RGB/HEX/HSL/HSV) |
188
188
  | `convtool` | - | 单位换算(长度/重量/温度/面积等) |
@@ -218,7 +218,7 @@ fcmd completion --shell fish | source # fish
218
218
  | 工具 | 别名 | 说明 |
219
219
  |------|------|------|
220
220
  | `sysinfo` | - | 系统信息收集(Python/平台/内存/磁盘/CPU) |
221
- | `portcheck` | - | 端口检查与扫描(纯 socket,跨平台) |
221
+ | `portcheck` | - | 端口检查与扫描(socket 检测占用,lsof/netstat 查询占用进程,跨平台) |
222
222
  | `taskkill` | - | 按名称终止进程(Windows taskkill / Unix pkill) |
223
223
  | `which` | - | 查找可执行命令路径 |
224
224
  | `clr` | - | 跨平台清屏 |
@@ -237,7 +237,6 @@ fcmd completion --shell fish | source # fish
237
237
  | `envdev` | - | 开发环境镜像源配置(Python/Conda/Rust,Linux 专用操作跳过) |
238
238
  | `setenv` | - | 设置当前进程环境变量(支持 `--default` 仅在未设置时写入) |
239
239
  | `sshcopyid` | - | SSH 公钥部署到远程服务器(依赖 sshpass) |
240
- | `codetool` | - | 代码统计(行数/字符数/空行/注释行) |
241
240
 
242
241
  查看任意工具的子命令与字段详情:
243
242
 
@@ -119,7 +119,7 @@ fcmd completion --shell fish | source # fish
119
119
  |------|------|------|
120
120
  | `txttool` | - | 文本处理(大小写/去重/排序/统计/截断) |
121
121
  | `asciitool` | - | ASCII 码表(char/code/table) |
122
- | `basetool` | - | 编码解码(Base64/URL/HTML/Hex) |
122
+ | `codetool` | - | 编解码(Base64/URL/HTML/Hex/ROT13) |
123
123
  | `casetool` | - | 命名风格转换(camel/snake/kebab/pascal) |
124
124
  | `colortool` | - | 颜色换算(RGB/HEX/HSL/HSV) |
125
125
  | `convtool` | - | 单位换算(长度/重量/温度/面积等) |
@@ -155,7 +155,7 @@ fcmd completion --shell fish | source # fish
155
155
  | 工具 | 别名 | 说明 |
156
156
  |------|------|------|
157
157
  | `sysinfo` | - | 系统信息收集(Python/平台/内存/磁盘/CPU) |
158
- | `portcheck` | - | 端口检查与扫描(纯 socket,跨平台) |
158
+ | `portcheck` | - | 端口检查与扫描(socket 检测占用,lsof/netstat 查询占用进程,跨平台) |
159
159
  | `taskkill` | - | 按名称终止进程(Windows taskkill / Unix pkill) |
160
160
  | `which` | - | 查找可执行命令路径 |
161
161
  | `clr` | - | 跨平台清屏 |
@@ -174,7 +174,6 @@ fcmd completion --shell fish | source # fish
174
174
  | `envdev` | - | 开发环境镜像源配置(Python/Conda/Rust,Linux 专用操作跳过) |
175
175
  | `setenv` | - | 设置当前进程环境变量(支持 `--default` 仅在未设置时写入) |
176
176
  | `sshcopyid` | - | SSH 公钥部署到远程服务器(依赖 sshpass) |
177
- | `codetool` | - | 代码统计(行数/字符数/空行/注释行) |
178
177
 
179
178
  查看任意工具的子命令与字段详情:
180
179
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fcmd"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "极速 Python 工具集应用:DAG 任务调度 + 组合 CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
@@ -27,7 +27,6 @@ fcmd = "fcmd.cli.main:main"
27
27
  archivex = "fcmd.cli.archivex:main"
28
28
  asciitool = "fcmd.cli.asciitool:main"
29
29
  autofmt = "fcmd.cli.autofmt:main"
30
- basetool = "fcmd.cli.basetool:main"
31
30
  bumpversion = "fcmd.cli.bumpversion:main"
32
31
  casetool = "fcmd.cli.casetool:main"
33
32
  clr = "fcmd.cli.clr:main"
@@ -22,7 +22,7 @@ from __future__ import annotations
22
22
 
23
23
  from typing import Any
24
24
 
25
- __version__ = "0.2.0"
25
+ __version__ = "0.2.2"
26
26
 
27
27
  __all__ = [
28
28
  "Context",
@@ -0,0 +1,6 @@
1
+ """``python -m fcmd`` 入口。"""
2
+
3
+ from fcmd.cli.main import main
4
+
5
+ if __name__ == "__main__": # pragma: no cover - 脚本入口,pytest 经 import 触发,不可达
6
+ main()
@@ -1,6 +1,6 @@
1
1
  import sys
2
2
 
3
- if sys.version_info >= (3, 13):
3
+ if sys.version_info >= (3, 13): # pragma: no cover - 测试环境单版本
4
4
  from typing import TypeVar
5
5
  else:
6
6
  from typing_extensions import TypeVar
@@ -362,9 +362,8 @@ class Graph:
362
362
  def subgraph_with_deps(self, names: Iterable[str]) -> Graph:
363
363
  """返回包含 ``names`` 及其所有传递依赖的新图。
364
364
 
365
- 与 :meth:`subgraph_by_names` 不同,本方法会沿 ``depends_on`` 和
366
- ``soft_depends_on`` 向上遍历,确保被选中的任务所需的上游全部包含在内,
367
- 使子图可独立执行。
365
+ 沿 ``depends_on`` 和 ``soft_depends_on`` 向上遍历,确保被选中
366
+ 任务所需的上游全部包含在内,使子图可独立执行。
368
367
 
369
368
  参数
370
369
  ----
@@ -64,7 +64,6 @@ __all__ = [
64
64
  "RunConfig",
65
65
  "TaskCmd",
66
66
  "TaskEvent",
67
- "TaskFn",
68
67
  "TaskResult",
69
68
  "TaskSpec",
70
69
  "TaskStatus",
@@ -0,0 +1,101 @@
1
+ """_env_persist - 跨平台环境变量持久化辅助(私有,不参与工具发现)。
2
+
3
+ ``os.environ[name] = value`` 仅修改当前进程的环境变量副本,进程退出即失效。
4
+ 本模块提供 :func:`persist_env`,将环境变量实质写入用户层面:
5
+
6
+ - Windows:写入注册表 ``HKEY_CURRENT_USER\\Environment``(用户级环境变量),
7
+ 并广播 ``WM_SETTINGCHANGE`` 通知已运行的进程刷新环境(如资源管理器、
8
+ 新开的终端)。相比 ``setx`` 无 1024 字符长度截断限制,且不依赖外部命令。
9
+ - Linux/macOS:在 shell 启动文件(默认 ``~/.profile``)中追加或更新
10
+ ``export NAME="value"`` 行,登录后新终端自动生效。
11
+
12
+ 无论平台,同时更新当前进程的 ``os.environ``,使本次会话内立即可用。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import re
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ __all__ = ["persist_env", "shell_profile_path"]
23
+
24
+ # Windows 注册表用户环境变量键路径(HKEY_CURRENT_USER 下)。
25
+ _WIN_ENV_KEY: str = "Environment"
26
+
27
+
28
+ def shell_profile_path() -> Path:
29
+ """返回 Linux/macOS 上用于持久化环境变量的 shell 启动文件路径。
30
+
31
+ 优先使用 ``~/.profile``(登录 shell 通用,bash/sh/zsh 均会读取)。
32
+ """
33
+ return Path.home() / ".profile"
34
+
35
+
36
+ def _persist_env_windows(name: str, value: str) -> None: # pragma: no cover - 仅 Windows 运行
37
+ """在 Windows 上将环境变量写入注册表 ``HKCU\\Environment`` 并广播刷新通知。"""
38
+ import ctypes
39
+ import winreg
40
+
41
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, _WIN_ENV_KEY, 0, winreg.KEY_SET_VALUE) as key:
42
+ winreg.SetValueEx(key, name, 0, winreg.REG_EXPAND_SZ, value)
43
+
44
+ # 广播 WM_SETTINGCHANGE,通知已运行进程刷新环境块(否则新终端才生效)。
45
+ hwnd_broadcast = 0xFFFF # HWND_BROADCAST
46
+ wm_settingchange = 0x001A
47
+ smto_abortifhung = 0x0002
48
+ ctypes.windll.user32.SendMessageTimeoutW(
49
+ hwnd_broadcast,
50
+ wm_settingchange,
51
+ 0,
52
+ "Environment",
53
+ smto_abortifhung,
54
+ 5000,
55
+ None,
56
+ )
57
+
58
+
59
+ def _persist_env_unix(name: str, value: str) -> None:
60
+ """在 Linux/macOS 上将 ``export NAME="value"`` 追加或更新到 shell 启动文件。"""
61
+ profile = shell_profile_path()
62
+ profile.parent.mkdir(parents=True, exist_ok=True)
63
+
64
+ export_line = f'export {name}="{value}"'
65
+ try:
66
+ existing = profile.read_text(encoding="utf-8")
67
+ except (OSError, UnicodeDecodeError):
68
+ existing = ""
69
+
70
+ # 匹配同名变量的已有 export 行(行首可有空白),存在则替换,否则追加。
71
+ pattern = re.compile(rf"^\s*export\s+{re.escape(name)}=.*$", re.MULTILINE)
72
+ if pattern.search(existing):
73
+ updated = pattern.sub(export_line, existing)
74
+ else:
75
+ prefix = existing if existing.endswith("\n") or not existing else existing + "\n"
76
+ updated = f"{prefix}{export_line}\n"
77
+
78
+ profile.write_text(updated, encoding="utf-8")
79
+
80
+
81
+ def persist_env(name: str, value: str) -> None:
82
+ """持久化设置环境变量(跨平台),并同步更新当前进程。
83
+
84
+ Parameters
85
+ ----------
86
+ name:
87
+ 环境变量名。
88
+ value:
89
+ 环境变量值。
90
+
91
+ Notes
92
+ -----
93
+ Windows 写入注册表用户环境变量并广播刷新;Linux/macOS 写入
94
+ ``~/.profile``。两种平台均更新 ``os.environ`` 使本进程立即可用。
95
+ 持久化写入通常需重开终端或重新登录后对新进程生效。
96
+ """
97
+ os.environ[name] = value
98
+ if sys.platform == "win32":
99
+ _persist_env_windows(name, value)
100
+ else:
101
+ _persist_env_unix(name, value)
@@ -112,6 +112,7 @@ def output_profile(
112
112
  if export == "text":
113
113
  sys.stdout.write(profile.describe())
114
114
  sys.stdout.write("\n")
115
+ sys.stdout.flush()
115
116
  return
116
117
 
117
118
  # HTML 格式
@@ -129,4 +130,5 @@ def output_profile(
129
130
  try:
130
131
  webbrowser.open(f"file:///{out_path.resolve().as_posix()}")
131
132
  except Exception as e:
133
+ # webbrowser.open 可抛 OS 级或 webbrowser.Error,属非关键功能
132
134
  get_console().print(f"[yellow]警告:[/yellow] 无法打开浏览器: {e}")
@@ -529,3 +529,7 @@ def archivex_create(source: Path, output: Path) -> None:
529
529
  @fcmd.main("archivex")
530
530
  def main() -> None:
531
531
  pass
532
+
533
+
534
+ if __name__ == "__main__":
535
+ main()
@@ -182,3 +182,7 @@ def table_cmd(start: int = _PRINTABLE_START, end: int = _PRINTABLE_END) -> None:
182
182
  @fcmd.main("asciitool")
183
183
  def main() -> None:
184
184
  pass
185
+
186
+
187
+ if __name__ == "__main__":
188
+ main()
@@ -59,4 +59,8 @@ def lint(target: str = ".", fix: bool = False) -> None:
59
59
 
60
60
  @fcmd.main("autofmt")
61
61
  def main() -> None:
62
- pass
62
+ pass # pragma: no cover - @fcmd.main 装饰器替换函数体,pass 永不执行
63
+
64
+
65
+ if __name__ == "__main__": # pragma: no cover
66
+ main()
@@ -305,3 +305,7 @@ def bump_project_version(part: BumpVersionType = "patch", no_tag: bool = False)
305
305
  @fcmd.main("bumpversion")
306
306
  def main() -> None:
307
307
  pass
308
+
309
+
310
+ if __name__ == "__main__":
311
+ main()
@@ -193,3 +193,7 @@ def kebab_cmd(text: str) -> None:
193
193
  @fcmd.main("casetool")
194
194
  def main() -> None:
195
195
  pass
196
+
197
+
198
+ if __name__ == "__main__":
199
+ main()
@@ -57,4 +57,8 @@ def clear_screen_run() -> None:
57
57
 
58
58
  @fcmd.main("clr")
59
59
  def main() -> None:
60
- pass
60
+ pass # pragma: no cover - @fcmd.main 装饰器替换函数体,pass 永不执行
61
+
62
+
63
+ if __name__ == "__main__": # pragma: no cover
64
+ main()
@@ -306,3 +306,7 @@ def html_cmd(text: str, decode: bool = False) -> None:
306
306
  @fcmd.main("codetool")
307
307
  def main() -> None:
308
308
  pass
309
+
310
+
311
+ if __name__ == "__main__":
312
+ main()
@@ -273,3 +273,7 @@ def hsl2rgb_cmd(h: float, s: float, light: float) -> None:
273
273
  @fcmd.main("colortool")
274
274
  def main() -> None:
275
275
  pass
276
+
277
+
278
+ if __name__ == "__main__":
279
+ main()
@@ -308,3 +308,7 @@ def datasize_cmd(
308
308
  @fcmd.main("convtool")
309
309
  def main() -> None:
310
310
  pass
311
+
312
+
313
+ if __name__ == "__main__":
314
+ main()
@@ -352,3 +352,7 @@ def decrypt_cmd(text: str, password: str = "", key: str = "", env: bool = False)
352
352
  @fcmd.main("cryptool")
353
353
  def main() -> None:
354
354
  pass
355
+
356
+
357
+ if __name__ == "__main__":
358
+ main()
@@ -454,3 +454,7 @@ def csvtool_merge(files: list[Path], mode: str = "union", output: str = "") -> N
454
454
  @fcmd.main("csvtool")
455
455
  def main() -> None:
456
456
  pass
457
+
458
+
459
+ if __name__ == "__main__":
460
+ main()
@@ -46,4 +46,8 @@ def docker_login(username: str = "", registry: str = _DEFAULT_REGISTRY) -> None:
46
46
 
47
47
  @fcmd.main("dockercmd")
48
48
  def main() -> None:
49
- pass
49
+ pass # pragma: no cover - @fcmd.main 装饰器替换函数体,pass 永不执行
50
+
51
+
52
+ if __name__ == "__main__": # pragma: no cover
53
+ main()
@@ -1,6 +1,6 @@
1
1
  """envdev - 开发环境镜像源配置工具。
2
2
 
3
- 配置 Python / Conda / Rust 镜像源(Linux 还会安装 Qt 库、中文字体、Docker)。
3
+ 配置 Python / Conda / Rust / Bun 镜像源(Linux 还会安装 Qt 库、中文字体、Docker)。
4
4
  所有镜像源参数互不影响,可单独使用。Linux 专用操作(系统镜像/Qt/字体/Docker)
5
5
  在非 Linux 平台上由函数内部跳过。
6
6
 
@@ -9,32 +9,30 @@
9
9
  fcmd envdev setup-python # 配置 Python 镜像源(默认清华)
10
10
  fcmd envdev setup-python --mirror aliyun # 配置阿里云镜像源
11
11
  fcmd envdev setup-conda --mirror ustc # 配置 Conda 镜像源
12
- fcmd envdev setup-rust --mirror tsinghua # 配置 Rust 镜像源
13
- fcmd envdev download-rustup # 下载 Rustup 安装脚本
14
- fcmd envdev install-rust --version stable # 安装 Rust 工具链
12
+ fcmd envdev rust --mirror tsinghua nightly # 配置 Rust 环境(镜像源 + 下载 + 安装)
13
+ fcmd envdev js-bun # 配置 Bun 环境(镜像源 + 安装)
15
14
  """
16
15
 
17
16
  from __future__ import annotations
18
17
 
19
18
  import getpass
20
- import os
21
19
  import shutil
22
20
  import sys
23
21
  from pathlib import Path
24
22
 
25
23
  import fcmd
24
+ from fcmd.cli._env_persist import persist_env
26
25
  from fcmd.models import run_command
27
26
 
28
27
  __all__ = [
29
- "download_rustup_script",
30
28
  "install_linux_docker",
31
29
  "install_linux_fonts",
32
30
  "install_linux_qt_libs",
33
- "install_rust_toolchain",
31
+ "setup_bun_env",
34
32
  "setup_conda_mirror",
35
33
  "setup_linux_system_mirror",
36
34
  "setup_python_mirror",
37
- "setup_rust_mirror",
35
+ "setup_rust_env",
38
36
  ]
39
37
 
40
38
  # ============================================================================
@@ -163,6 +161,9 @@ _INSTALL_MIRROR_SCRIPT: str = "sudo bash /tmp/linuxmirrors.sh"
163
161
  _RUSTUP_DOWNLOAD_URL_LINUX: str = "https://mirrors.aliyun.com/repo/rust/rustup-init.sh"
164
162
  _RUSTUP_DOWNLOAD_URL_WINDOWS: str = "https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe"
165
163
 
164
+ _BUN_NPM_REGISTRY: str = "https://registry.npmmirror.com"
165
+ _BUN_INSTALL_SCRIPT_URL: str = "https://bun.sh/install"
166
+
166
167
 
167
168
  # ============================================================================
168
169
  # 私有辅助
@@ -182,16 +183,17 @@ def _pip_config_path() -> Path:
182
183
 
183
184
 
184
185
  @fcmd.tool("envdev", subcommand="setup-python", help="配置 Python 镜像源")
185
- def setup_python_mirror(mirror: str = "tsinghua") -> None:
186
- """配置 Python 镜像源(设置环境变量 + 写入 pip 配置文件)。
186
+ def setup_python_mirror(mirror: str = "aliyun") -> None:
187
+ """配置 Python 镜像源(持久化环境变量 + 写入 pip 配置文件)。
187
188
 
188
- 设置 ``PIP_INDEX_URL`` / ``PIP_TRUSTED_HOSTS`` / ``UV_INDEX_URL`` /
189
- ``UV_PYTHON_INSTALL_MIRROR`` 等环境变量,并写入 pip 配置文件。
189
+ 通过 :func:`persist_env` 持久化 ``PIP_INDEX_URL`` / ``PIP_TRUSTED_HOSTS`` /
190
+ ``UV_INDEX_URL`` / ``UV_PYTHON_INSTALL_MIRROR`` 等环境变量(Windows 写注册表,
191
+ Linux/macOS 写 ``~/.profile``,同时更新当前进程),并写入 pip 配置文件。
190
192
 
191
193
  Parameters
192
194
  ----------
193
195
  mirror:
194
- 镜像源名称:tsinghua/aliyun/huaweicloud/ustc/zju(默认 tsinghua)
196
+ 镜像源名称:tsinghua/aliyun/huaweicloud/ustc/zju(默认 aliyun)
195
197
  """
196
198
  if mirror not in _PIP_INDEX_URLS:
197
199
  print(f"未知 Python 镜像源: {mirror}")
@@ -200,12 +202,13 @@ def setup_python_mirror(mirror: str = "tsinghua") -> None:
200
202
  index_url = _PIP_INDEX_URLS[mirror]
201
203
  trusted_host = _PIP_TRUSTED_HOSTS[mirror]
202
204
 
203
- os.environ["PIP_INDEX_URL"] = index_url
204
- os.environ["PIP_TRUSTED_HOSTS"] = trusted_host
205
- os.environ["UV_INDEX_URL"] = index_url
206
- os.environ["UV_PYTHON_INSTALL_MIRROR"] = _UV_PYTHON_INSTALL_MIRROR
207
- os.environ["UV_HTTP_TIMEOUT"] = "600"
208
- os.environ["UV_LINK_MODE"] = "copy"
205
+ print(f"配置 Python 镜像源: {mirror}")
206
+ persist_env("PIP_INDEX_URL", index_url)
207
+ persist_env("PIP_TRUSTED_HOSTS", trusted_host)
208
+ persist_env("UV_INDEX_URL", index_url)
209
+ persist_env("UV_PYTHON_INSTALL_MIRROR", _UV_PYTHON_INSTALL_MIRROR)
210
+ persist_env("UV_HTTP_TIMEOUT", "600")
211
+ persist_env("UV_LINK_MODE", "copy")
209
212
 
210
213
  config_path = _pip_config_path()
211
214
  config_path.parent.mkdir(parents=True, exist_ok=True)
@@ -215,13 +218,13 @@ def setup_python_mirror(mirror: str = "tsinghua") -> None:
215
218
 
216
219
 
217
220
  @fcmd.tool("envdev", subcommand="setup-conda", help="配置 Conda 镜像源")
218
- def setup_conda_mirror(mirror: str = "tsinghua") -> None:
221
+ def setup_conda_mirror(mirror: str = "aliyun") -> None:
219
222
  """配置 Conda 镜像源(写入 ~/.condarc)。
220
223
 
221
224
  Parameters
222
225
  ----------
223
226
  mirror:
224
- 镜像源名称:tsinghua/ustc/bsfu/aliyun(默认 tsinghua)
227
+ 镜像源名称:tsinghua/ustc/bsfu/aliyun(默认 aliyun)
225
228
  """
226
229
  if mirror not in _CONDA_MIRROR_URLS:
227
230
  print(f"未知 Conda 镜像源: {mirror}")
@@ -235,27 +238,31 @@ def setup_conda_mirror(mirror: str = "tsinghua") -> None:
235
238
  print(f"Conda 镜像源已配置: {mirror} -> {config_path}")
236
239
 
237
240
 
238
- @fcmd.tool("envdev", subcommand="setup-rust", help="配置 Rust 镜像源")
239
- def setup_rust_mirror(mirror: str = "tsinghua") -> None:
240
- """配置 Rust 镜像源(设置环境变量 + 写入 cargo config + 创建 sccache 目录)。
241
+ # ============================================================================
242
+ # Rust 工具链安装
243
+ # ============================================================================
244
+ @fcmd.tool("envdev", subcommand="setup-rust", help="配置 Rust 镜像源", hidden=True)
245
+ def _setup_rust_mirror(mirror: str = "aliyun") -> None:
246
+ """配置 Rust 镜像源(持久化环境变量 + 写入 cargo config + 创建 sccache 目录)。
241
247
 
242
- 设置 ``RUSTUP_DIST_SERVER`` / ``RUSTUP_UPDATE_ROOT`` / ``RUST_SCCACHE_DIR``
243
- 等环境变量,写入 ``~/.cargo/config.toml``,并创建 sccache 缓存目录。
248
+ 通过 :func:`persist_env` 持久化 ``RUSTUP_DIST_SERVER`` / ``RUSTUP_UPDATE_ROOT`` /
249
+ ``RUST_SCCACHE_DIR`` 等环境变量,写入 ``~/.cargo/config.toml``,并创建
250
+ sccache 缓存目录。
244
251
 
245
252
  Parameters
246
253
  ----------
247
254
  mirror:
248
- 镜像源名称:tsinghua/ustc/aliyun(默认 tsinghua)
255
+ 镜像源名称:tsinghua/ustc/aliyun(默认 aliyun)
249
256
  """
250
257
  if mirror not in _RUSTUP_MIRRORS:
251
258
  print(f"未知 Rust 镜像源: {mirror}")
252
259
  return
253
260
 
254
261
  mirrors = _RUSTUP_MIRRORS[mirror]
255
- os.environ["RUSTUP_DIST_SERVER"] = mirrors["RUSTUP_DIST_SERVER"]
256
- os.environ["RUSTUP_UPDATE_ROOT"] = mirrors["RUSTUP_UPDATE_ROOT"]
257
- os.environ["RUST_SCCACHE_DIR"] = str(_RUST_SCCACHE_DIR)
258
- os.environ["RUST_SCCACHE_CACHE_SIZE"] = _RUST_SCCACHE_CACHE_SIZE
262
+ persist_env("RUSTUP_DIST_SERVER", mirrors["RUSTUP_DIST_SERVER"])
263
+ persist_env("RUSTUP_UPDATE_ROOT", mirrors["RUSTUP_UPDATE_ROOT"])
264
+ persist_env("RUST_SCCACHE_DIR", str(_RUST_SCCACHE_DIR))
265
+ persist_env("RUST_SCCACHE_CACHE_SIZE", _RUST_SCCACHE_CACHE_SIZE)
259
266
 
260
267
  _RUST_SCCACHE_DIR.mkdir(parents=True, exist_ok=True)
261
268
 
@@ -271,13 +278,8 @@ def setup_rust_mirror(mirror: str = "tsinghua") -> None:
271
278
  print(f"Rust 镜像源已配置: {mirror} -> {config_path}")
272
279
 
273
280
 
274
- # ============================================================================
275
- # Rust 工具链安装
276
- # ============================================================================
277
-
278
-
279
- @fcmd.tool("envdev", subcommand="download-rustup", help="下载 Rustup 安装脚本")
280
- def download_rustup_script() -> None:
281
+ @fcmd.tool("envdev", subcommand="download-rustup", help="下载 Rustup 安装脚本", hidden=True)
282
+ def _download_rustup() -> None:
281
283
  """下载 Rustup 安装脚本(跨平台,已安装 rustup 时跳过)。
282
284
 
283
285
  Linux 下载 ``rustup-init.sh``,Windows 下载 ``rustup-init.exe``。
@@ -304,8 +306,8 @@ def download_rustup_script() -> None:
304
306
  run_command(["curl", "-fsSL", _RUSTUP_DOWNLOAD_URL_LINUX, "-o", "rustup-init.sh"])
305
307
 
306
308
 
307
- @fcmd.tool("envdev", subcommand="install-rust", help="安装 Rust 工具链")
308
- def install_rust_toolchain(version: str = "stable") -> None:
309
+ @fcmd.tool("envdev", subcommand="install-rust", help="安装 Rust 工具链", hidden=True)
310
+ def _install_rust_toolchain(version: str = "stable") -> None:
309
311
  """安装 Rust 工具链(rustup 未安装时跳过)。
310
312
 
311
313
  Parameters
@@ -321,6 +323,83 @@ def install_rust_toolchain(version: str = "stable") -> None:
321
323
  print(f"Rust 工具链 {version} 安装完成")
322
324
 
323
325
 
326
+ @fcmd.tool("envdev", subcommand="rust", help="配置 Rust 环境")
327
+ def setup_rust_env(mirror: str = "aliyun", rust_version: str = "stable") -> None:
328
+ """配置 Rust 环境(镜像源 + 下载 rustup + 安装工具链)。
329
+
330
+ 依次执行:配置 Rust 镜像源(环境变量 + ``~/.cargo/config.toml`` + sccache 目录)、
331
+ 下载 Rustup 安装脚本(已安装 rustup 时跳过)、安装指定版本工具链
332
+ (rustup 未安装时跳过)。
333
+
334
+ Parameters
335
+ ----------
336
+ mirror:
337
+ 镜像源名称:tsinghua/ustc/aliyun(默认 aliyun)
338
+ rust_version:
339
+ Rust 版本:``stable`` / ``nightly`` / ``beta``(默认 ``stable``)
340
+ """
341
+ _setup_rust_mirror(mirror)
342
+ _download_rustup()
343
+ _install_rust_toolchain(rust_version)
344
+
345
+
346
+ # ============================================================================
347
+ # JavaScript 工具链安装
348
+ # ============================================================================
349
+
350
+
351
+ @fcmd.tool("envdev", subcommand="setup-bun", help="配置 Bun 镜像源", hidden=True)
352
+ def _setup_bun_mirror() -> None:
353
+ """配置 Bun npm 镜像源(持久化环境变量 + 写入 ``~/.bunfig.toml``)。
354
+
355
+ 通过 :func:`persist_env` 持久化 ``BUN_CONFIG_REGISTRY`` 环境变量,
356
+ 并写入 bunfig.toml 指向 npmmirror。
357
+ """
358
+ persist_env("BUN_CONFIG_REGISTRY", _BUN_NPM_REGISTRY)
359
+
360
+ config_path = Path.home() / ".bunfig.toml"
361
+ config_path.parent.mkdir(parents=True, exist_ok=True)
362
+ content = f'[install]\nregistry = "{_BUN_NPM_REGISTRY}"\n'
363
+ config_path.write_text(content, encoding="utf-8")
364
+ print(f"Bun 镜像源已配置: {_BUN_NPM_REGISTRY} -> {config_path}")
365
+
366
+
367
+ @fcmd.tool("envdev", subcommand="install-bun", help="安装 Bun", hidden=True)
368
+ def _install_bun() -> None:
369
+ """安装 Bun.js(已安装时跳过)。
370
+
371
+ Linux/macOS 通过官方脚本安装(Linux 先安装 curl/zip/unzip 依赖);
372
+ Windows 提示使用 PowerShell 安装命令。
373
+ """
374
+ if shutil.which("bun") is not None:
375
+ print("bun 已安装,跳过安装")
376
+ return
377
+
378
+ if sys.platform == "win32":
379
+ print("Windows 请使用 PowerShell 执行: irm bun.sh/install.ps1 | iex")
380
+ return
381
+
382
+ if sys.platform.startswith("linux"):
383
+ print("更新系统包...")
384
+ run_command(["sudo", "apt", "update"])
385
+ run_command(["sudo", "apt", "install", "-y", "curl", "zip", "unzip"])
386
+
387
+ print("下载并安装 Bun.js...")
388
+ run_command(["bash", "-c", f"curl -fsSL {_BUN_INSTALL_SCRIPT_URL} | bash"])
389
+ print("Bun.js 安装完成")
390
+
391
+
392
+ @fcmd.tool("envdev", subcommand="js-bun", help="配置 Bun 环境")
393
+ def setup_bun_env() -> None:
394
+ """配置 Bun 环境(配置 npm 镜像源 + 安装 Bun.js)。
395
+
396
+ 依次执行:配置 Bun npm 镜像源(环境变量 + ``~/.bunfig.toml``)、
397
+ 安装 Bun.js(已安装时跳过)。
398
+ """
399
+ _setup_bun_mirror()
400
+ _install_bun()
401
+
402
+
324
403
  # ============================================================================
325
404
  # Linux 专用子命令
326
405
  # ============================================================================
@@ -396,3 +475,7 @@ def install_linux_docker() -> None:
396
475
  @fcmd.main("envdev")
397
476
  def main() -> None:
398
477
  pass
478
+
479
+
480
+ if __name__ == "__main__":
481
+ main()
@@ -167,3 +167,7 @@ def process_files_date_clear(files: list[Path]) -> None:
167
167
  @fcmd.main("filedate")
168
168
  def main() -> None:
169
169
  pass
170
+
171
+
172
+ if __name__ == "__main__":
173
+ main()
@@ -142,3 +142,7 @@ def process_files_level(files: list[Path], level: int = 0) -> None:
142
142
  @fcmd.main("filelevel")
143
143
  def main() -> None:
144
144
  pass
145
+
146
+
147
+ if __name__ == "__main__":
148
+ main()