tbtools-cli 1.2.0__py3-none-any.whl

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 (139) hide show
  1. tbtools_cli/__init__.py +18 -0
  2. tbtools_cli/auto_commands.py +1149 -0
  3. tbtools_cli/cli.py +528 -0
  4. tbtools_cli/cli_load.py +352 -0
  5. tbtools_cli/cli_rpc.py +303 -0
  6. tbtools_cli/cli_tools_registry.py +93 -0
  7. tbtools_cli/cli_top.py +1160 -0
  8. tbtools_cli/command_metadata.json +4606 -0
  9. tbtools_cli/command_spec.py +382 -0
  10. tbtools_cli/config.example.toml +19 -0
  11. tbtools_cli/config.py +43 -0
  12. tbtools_cli/core.py +508 -0
  13. tbtools_cli/errors.py +38 -0
  14. tbtools_cli/presets.py +100 -0
  15. tbtools_cli/scenarios.py +92 -0
  16. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/AdmixtureCli.java +91 -0
  17. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/AmazingMetaCli.java +78 -0
  18. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/AssemblyRecommandCli.java +47 -0
  19. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BamIndexCli.java +31 -0
  20. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BamSortCli.java +43 -0
  21. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BamStateCli.java +49 -0
  22. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BarPlotterCli.java +18 -0
  23. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BarplotCli.java +168 -0
  24. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BatchVizMotifsCli.java +47 -0
  25. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BlastXmlConvertCli.java +45 -0
  26. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/BlatExecutorCli.java +66 -0
  27. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/CalcRepeatCli.java +62 -0
  28. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/CalculateSimilarityCli.java +34 -0
  29. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/CddMotifCli.java +46 -0
  30. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/CircleGeneViewerCli.java +81 -0
  31. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/CircosCli.java +76 -0
  32. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/ColorSchemeCli.java +29 -0
  33. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/CtgGroupCli.java +29 -0
  34. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/CubeHeatmapCli.java +70 -0
  35. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/DeHistCli.java +46 -0
  36. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/DegramdomCli.java +33 -0
  37. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/DiffExpCli.java +44 -0
  38. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/DistanceCli.java +53 -0
  39. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/DualSynCli.java +147 -0
  40. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/EggnogCli.java +39 -0
  41. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/ExprCorrCli.java +27 -0
  42. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/FastaMergerCli.java +36 -0
  43. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/FastaTableConvertCli.java +38 -0
  44. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/FileCleanerCli.java +31 -0
  45. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/FileSplitCli.java +15 -0
  46. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/FindBlockDualCli.java +71 -0
  47. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/FindBlockMultipleCli.java +67 -0
  48. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/FindPathCli.java +15 -0
  49. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GSEAWrapperCli.java +33 -0
  50. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GenBank2FastaCli.java +32 -0
  51. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GeneDensityCli.java +38 -0
  52. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GeneLocGffCli.java +140 -0
  53. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GeneStructureCli.java +90 -0
  54. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GenericCli.java +177 -0
  55. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GetENALinksCli.java +36 -0
  56. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GetSubNewickTreeCli.java +50 -0
  57. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GoEnrichCli.java +42 -0
  58. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GoLevelCli.java +59 -0
  59. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GroupedBarCli.java +95 -0
  60. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GsaDiagCli.java +45 -0
  61. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GxfFilterCli.java +39 -0
  62. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/GxfSortCli.java +23 -0
  63. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/HclustCli.java +43 -0
  64. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/HeatmapCli.java +89 -0
  65. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/HmmerSuiteCli.java +47 -0
  66. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/JgblocksCli.java +65 -0
  67. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/KeggEnrichCli.java +31 -0
  68. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/LayoutHeatmapCli.java +90 -0
  69. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MCScanXCli.java +80 -0
  70. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MCScanXFastCli.java +41 -0
  71. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MSACli.java +46 -0
  72. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MakeMotifCli.java +39 -0
  73. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MarkerDesignCli.java +62 -0
  74. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MarkerToolsCli.java +57 -0
  75. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/Mast2TabCli.java +27 -0
  76. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MastCli.java +54 -0
  77. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MastRunCli.java +39 -0
  78. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/Meme2TabCli.java +26 -0
  79. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MemeCli.java +74 -0
  80. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MemeRunCli.java +40 -0
  81. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MgGxfCli.java +35 -0
  82. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MicroSynCli.java +118 -0
  83. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MirIdentifyCli.java +55 -0
  84. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MotifCli.java +75 -0
  85. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MotifPatternCli.java +67 -0
  86. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MotifShiftCli.java +30 -0
  87. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MountainPlotCli.java +44 -0
  88. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/MultiSuperHeatCli.java +64 -0
  89. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/NeedlemanWunschCli.java +76 -0
  90. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PafGC.java +15 -0
  91. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PafVizCli.java +67 -0
  92. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PeakDistCli.java +58 -0
  93. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/Pep2CodonCli.java +26 -0
  94. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PfamMotifCli.java +43 -0
  95. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PhyloTreeCli.java +92 -0
  96. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PileUpCli.java +55 -0
  97. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PlantCAREResultClassifyCli.java +36 -0
  98. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/PubmedSearchCli.java +25 -0
  99. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/QpcrCli.java +69 -0
  100. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/QpcrDdctCli.java +28 -0
  101. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/QpcrProcCli.java +28 -0
  102. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/QuickGenomeDotCli.java +66 -0
  103. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/QuickProteinAnnoCli.java +34 -0
  104. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/QuickRunIQtreeCli.java +69 -0
  105. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/QuickTrimALCli.java +67 -0
  106. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/RNAplotCli.java +129 -0
  107. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/RegionBedToGFF3Cli.java +43 -0
  108. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/RegionDepthCli.java +29 -0
  109. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SamBamCovCli.java +33 -0
  110. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SeqConverterCli.java +17 -0
  111. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SeqLenTrackCli.java +41 -0
  112. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SeveralSpeciesCli.java +132 -0
  113. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SimpleEnricherCli.java +44 -0
  114. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SimpleHmmscanCli.java +32 -0
  115. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SixFrameTranlaterCli.java +35 -0
  116. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/StructAnnoCompareCli.java +66 -0
  117. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SubmitSMARTCli.java +30 -0
  118. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/SuperCircosCli.java +330 -0
  119. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TableColManipCli.java +56 -0
  120. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TableCollapseCli.java +33 -0
  121. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TargetScoreCli.java +47 -0
  122. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TauCalcCli.java +28 -0
  123. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TaxonomyBatchCli.java +72 -0
  124. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TreeCli.java +75 -0
  125. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TreeRootingCli.java +30 -0
  126. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/TrimMSACli.java +33 -0
  127. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/UnrootedTreeCli.java +44 -0
  128. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/UpSetCli.java +90 -0
  129. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/Venn5Cli.java +60 -0
  130. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/Venn6Cli.java +65 -0
  131. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/ViolinCli.java +66 -0
  132. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/VisualizeCli.java +57 -0
  133. tbtools_cli-1.2.0.data/data/tbtools_cli/bridges/VizGFACli.java +39 -0
  134. tbtools_cli-1.2.0.dist-info/METADATA +504 -0
  135. tbtools_cli-1.2.0.dist-info/RECORD +139 -0
  136. tbtools_cli-1.2.0.dist-info/WHEEL +5 -0
  137. tbtools_cli-1.2.0.dist-info/entry_points.txt +2 -0
  138. tbtools_cli-1.2.0.dist-info/licenses/LICENSE +21 -0
  139. tbtools_cli-1.2.0.dist-info/top_level.txt +1 -0
tbtools_cli/cli.py ADDED
@@ -0,0 +1,528 @@
1
+ """tbtools-cli 主入口 — Python click 重构版"""
2
+ import os
3
+ import sys
4
+
5
+ import click
6
+
7
+ # ---- 配置 ----
8
+ # 仅源码直跑时(父目录有 pyproject.toml)才插入搜索路径;pip 安装后不需要(评审: 避免污染搜索路径)
9
+ if os.path.isfile(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "pyproject.toml")):
10
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11
+ import tbtools_cli.auto_commands as _ac
12
+ from tbtools_cli.cli_tools_registry import CLI_TOOLS
13
+ from tbtools_cli.core import (
14
+ _,
15
+ JAR,
16
+ ROOT,
17
+ cp,
18
+ ensure_bridge,
19
+ get_pitfall_hint,
20
+ pre_flight,
21
+ safe_temp,
22
+ resolve_output,
23
+ run_java,
24
+ run_plot,
25
+ stdout_path,
26
+ )
27
+ from tbtools_cli.presets import apply_preset
28
+ from tbtools_cli.cli_rpc import build_rpc_group
29
+ import tbtools_cli.cli_load as cli_load
30
+ from tbtools_cli.cli_top import register_top # 批次 B: 顶层命令拆分
31
+ from tbtools_cli import __version__ as _CLI_VERSION # 单一源: pyproject
32
+
33
+ from tbtools_cli.cli_load import ( # 批次 B: 动态注册拆分
34
+ CATEGORY_MAP,
35
+ _groups,
36
+ _load_auto_commands,
37
+ _load_dynamic_commands,
38
+ build_and_load,
39
+ )
40
+
41
+
42
+
43
+ # ---- 通用选项 ----
44
+ def common_options(f):
45
+ """通用选项装饰器:--verbose, --quiet, --format, --preset, --width, --height, --threads"""
46
+ f = click.option("--verbose", "-V", is_flag=True, default=False, help="显示完整堆栈(debug 模式)")(f)
47
+ f = click.option("--quiet", "-q", is_flag=True, default=False, help="静默模式(只显示错误)")(f)
48
+ f = click.option("--format", "-f", "fmt", default=None, help="输出格式: svg|png|pdf")(f)
49
+ f = click.option("--preset", default=None, help="出版预设: nature|cell|plant_journal|wide|poster")(f)
50
+ f = click.option("--height", "-H", type=int, default=None, help="画布高度")(f)
51
+ f = click.option("--width", "-W", type=int, default=None, help="画布宽度")(f)
52
+ f = click.option("--threads", "-t", type=int, default=None, help="线程数")(f)
53
+ return f
54
+
55
+
56
+
57
+ def _plot_prelude(cmd, in_file, out_file, preset, width, height, fmt, bridge):
58
+ """绘图命令公共前奏: pre_flight → preset → resolve_output → ensure_bridge。
59
+ (第三轮审查:消除 volcano/heatmap 等命令前缀的逐字复制)返回 (out_file, width, height)。"""
60
+ pre_flight(cmd, in_file)
61
+ if preset:
62
+ p = apply_preset(preset, width=width, height=height)
63
+ if not p:
64
+ print(f"❌ 未知预设: {preset}", file=sys.stderr)
65
+ sys.exit(1)
66
+ if 'width' in p and not width:
67
+ width = p['width']
68
+ if 'height' in p and not height:
69
+ height = p['height']
70
+ out_file = resolve_output(out_file, fmt)
71
+ if bridge:
72
+ ensure_bridge(bridge)
73
+ return out_file, width, height
74
+
75
+
76
+ def _append_size(args, width, height):
77
+ """公共尾部: width/height 拼装(--width/--height 风格)"""
78
+ if width:
79
+ args += ["--width", str(width)]
80
+ if height:
81
+ args += ["--height", str(height)]
82
+ return args
83
+ # ---- 主命令组 ----
84
+ class RootGroup(click.Group):
85
+ """顶层:未知命令时给分组建议 + 拼写纠错"""
86
+ def resolve_command(self, ctx, args):
87
+ try:
88
+ return super().resolve_command(ctx, args)
89
+ except click.UsageError:
90
+ if not args:
91
+ raise
92
+ name = args[0]
93
+ # 兼容旧写法/README:顶层裸命令自动转发到分组(如 tbtools seqlogo → tbtools seq logo)
94
+ for gname, g in _groups.items():
95
+ if name in g.commands:
96
+ return gname, g.commands[name], args[1:] # F841 修复: 删未用 sub 变量
97
+ # 拼写纠错(对分组名+顶层命令;前缀匹配优先——venn2 案例:n=3 截断挤掉正确建议)
98
+ import difflib
99
+ candidates = sorted(set(list(_groups.keys()) + [c for c in cli.commands.keys()]))
100
+ prefix_hits = [c for c in candidates if c.startswith(name)]
101
+ close = prefix_hits[:5] or difflib.get_close_matches(name, candidates, n=3, cutoff=0.6)
102
+ if close:
103
+ click.echo(_("❌ 未知命令: {n}", "❌ Unknown command: {n}").format(n=name), err=True)
104
+ click.echo(f" 你是不是想用: {' / '.join(close)}?", err=True)
105
+ else:
106
+ click.echo(_("❌ 未知命令: {n}", "❌ Unknown command: {n}").format(n=name), err=True)
107
+ click.echo(" 查看: tbtools list", err=True)
108
+ ctx.exit(2)
109
+
110
+
111
+ @click.group(cls=RootGroup, invoke_without_command=True)
112
+ @click.version_option(_CLI_VERSION, prog_name="tbtools-cli")
113
+ @click.pass_context
114
+ def cli(ctx):
115
+ """TBtools-II 全功能 CLI(命令/工具/RPC 数字见 tbtools list)"""
116
+ if ctx.invoked_subcommand is None:
117
+ click.echo(ctx.get_help())
118
+
119
+ # ---- 命令组:序列/结构 ----
120
+ @cli.group("seq")
121
+ def seq_group():
122
+ """序列/结构域命令"""
123
+
124
+ @seq_group.command("logo")
125
+ @click.argument("input_file")
126
+ @click.argument("output_file")
127
+ @click.option("--scale-ic/--no-scale-ic", default=True, help="按信息含量缩放")
128
+ @click.option("--show-pos/--no-show-pos", default=False, help="显示位置编号")
129
+ @common_options
130
+ def seqlogo(input_file, output_file, scale_ic, show_pos, verbose, quiet, fmt, preset, height, width, threads):
131
+ """序列 LOGO 图"""
132
+ output_file, width, height = _plot_prelude("logo", input_file, output_file, preset, width, height, fmt, None)
133
+ args = ["java", "-Xmx2g", "-cp", JAR,
134
+ "biocjava.bioDoer.seqLogo.makeSeqLogo",
135
+ "--inFile", input_file, "--OutGraph", output_file]
136
+ if not scale_ic:
137
+ args += ["--scaleIC=false"]
138
+ if show_pos:
139
+ args += ["--showPos=true"]
140
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="seqlogo")
141
+ sys.exit(ec)
142
+
143
+ @seq_group.command("msa")
144
+ @click.argument("aligned_fasta")
145
+ @click.argument("output_file")
146
+ @click.option("--padding", type=int, default=0, help="序列间填充")
147
+ @common_options
148
+ def seq_msa(aligned_fasta, output_file, padding, verbose, quiet, fmt, preset, height, width, threads):
149
+ """多序列比对可视化"""
150
+ output_file, width, height = _plot_prelude("msa", aligned_fasta, output_file, preset, width, height, fmt, "MSACli")
151
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
152
+ "MSACli", aligned_fasta, output_file]
153
+ if padding:
154
+ args += ["--padding", str(padding)]
155
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="msa")
156
+ sys.exit(ec)
157
+
158
+ @seq_group.command("structure")
159
+ @click.argument("gff_file")
160
+ @click.argument("id_list")
161
+ @click.argument("output_file")
162
+ @click.option("--genome", "-g", default=None, help="基因组 FASTA(可选)")
163
+ @common_options
164
+ def seq_structure(gff_file, id_list, output_file, genome, verbose, quiet, fmt, preset, height, width, threads):
165
+ """基因结构图(外显子/UTR 从 GFF)"""
166
+ output_file, width, height = _plot_prelude("structure", gff_file, output_file, preset, width, height, fmt, "GeneStructureCli")
167
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
168
+ "GeneStructureCli", gff_file, id_list, output_file]
169
+ if genome:
170
+ args += [genome]
171
+ if width: args += [str(width)]
172
+ if height: args += [str(height)]
173
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="structure")
174
+ sys.exit(ec)
175
+
176
+
177
+ # 旧名别名(README/老用户兼容):genestructure == structure
178
+ seq_group.add_command(seq_structure, name="genestructure")
179
+
180
+ @seq_group.command("motif")
181
+ @click.argument("meme_xml")
182
+ @click.argument("id_list")
183
+ @click.argument("output_file")
184
+ @common_options
185
+ def seq_motif(meme_xml, id_list, output_file, verbose, quiet, fmt, preset, height, width, threads):
186
+ """Motif 分布图(MEME XML)"""
187
+ output_file, width, height = _plot_prelude("motif", meme_xml, output_file, preset, width, height, fmt, "MotifCli")
188
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
189
+ "MotifCli", meme_xml, id_list, output_file]
190
+ if width: args += [str(width)]
191
+ if height: args += [str(height)]
192
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="motif")
193
+ sys.exit(ec)
194
+
195
+ # ---- 命令组:表达/统计 ----
196
+ @cli.group("expr")
197
+ def expr_group():
198
+ """表达/统计命令"""
199
+
200
+ @expr_group.command("volcano")
201
+ @click.argument("deg_file")
202
+ @click.argument("output_file")
203
+ @click.option("--pval-cutoff", "-p", type=float, default=0.05, help="P值阈值")
204
+ @click.option("--fc-cutoff", "-c", type=float, default=1.0, help="Log2FC 阈值")
205
+ @common_options
206
+ def volcano(deg_file, output_file, pval_cutoff, fc_cutoff, verbose, quiet, fmt, preset, height, width, threads):
207
+ """火山图(DEG: GeneID Log2FC pvalue)"""
208
+ output_file, width, height = _plot_prelude("volcano", deg_file, output_file, preset, width, height, fmt, "GenericCli")
209
+ args = ["java", "-Xmx2g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
210
+ "GenericCli", "biocjava.bioDoer.JIGplotToolkit.VocanoPlot.vocanoPlot", "show",
211
+ output_file, "--set", "inData", deg_file]
212
+ args += ["--set", "log2FoldChange", "true", "--set", "negLogPvalue", "true"]
213
+ args += ["--set", "pvalueCutOff", str(pval_cutoff), "--set", "foldChangeCutOff", str(fc_cutoff)]
214
+ args += ["--set", "normPointSize", "5.0", "--set", "showTopChangeNum", "5"]
215
+ if width:
216
+ args += ["--width", str(width)]
217
+ if height:
218
+ args += ["--height", str(height)]
219
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="volcano")
220
+ sys.exit(ec)
221
+
222
+ @expr_group.command("heatmap")
223
+ @click.argument("matrix_file")
224
+ @click.argument("output_file")
225
+ @click.option("--log2/--no-log2", default=False, help="log2 转换")
226
+ @click.option("--row-scale/--no-row-scale", default=False, help="行标准化")
227
+ @click.option("--cluster-row/--no-cluster-row", default=False, help="行聚类")
228
+ @click.option("--cluster-col/--no-cluster-col", default=False, help="列聚类")
229
+ @common_options
230
+ def heatmap(matrix_file, output_file, log2, row_scale, cluster_row, cluster_col, verbose, quiet, fmt, preset, height, width, threads):
231
+ """热图(表达矩阵)"""
232
+ output_file, width, height = _plot_prelude("heatmap", matrix_file, output_file, preset, width, height, fmt, "HeatmapCli")
233
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
234
+ "HeatmapCli", matrix_file, output_file]
235
+ if log2:
236
+ args += ["--log2"]
237
+ if row_scale:
238
+ args += ["--rowScale"]
239
+ if cluster_row:
240
+ args += ["--clusterRow"]
241
+ if cluster_col:
242
+ args += ["--clusterCol"]
243
+ if width:
244
+ args += ["--width", str(width)]
245
+ if height:
246
+ args += ["--height", str(height)]
247
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="heatmap")
248
+ sys.exit(ec)
249
+
250
+ @expr_group.command("pca")
251
+ @click.argument("matrix_file")
252
+ @click.argument("output_file")
253
+ @click.argument("direction", type=click.Choice(["row", "col"]), default="row")
254
+ @click.option("--scale/--no-scale", default=False, help="标准化")
255
+ @common_options
256
+ def expr_pca(matrix_file, output_file, direction, scale, verbose, quiet, fmt, preset, height, width, threads):
257
+ """PCA 图"""
258
+ output_file, width, height = _plot_prelude("pca", matrix_file, output_file, preset, width, height, fmt, "GenericCli")
259
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
260
+ "GenericCli", "biocjava.bioDoer.JIGplotToolkit.PCAanalysis.PCAanalysis",
261
+ "doPCA+postGraph", output_file,
262
+ "--set", "inTabFile", matrix_file,
263
+ "--set", "rowName", "true", "--set", "colName", "true",
264
+ "--set", "processDirect", "Rows" if direction == "row" else "Columns"]
265
+ if scale: args += ["--set", "scale", "true"]
266
+ args += ["--set", "pointSize", "8.0", "--set", "showLabel", "true"]
267
+ if width: args += ["--width", str(width)]
268
+ if height: args += ["--height", str(height)]
269
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="pca")
270
+ sys.exit(ec)
271
+
272
+ @expr_group.command("hclust")
273
+ @click.argument("distance_file")
274
+ @click.argument("output_file")
275
+ @common_options
276
+ def expr_hclust(distance_file, output_file, verbose, quiet, fmt, preset, height, width, threads):
277
+ """层次聚类树(三列距离文件 GeneA\tGeneB\tdist)"""
278
+ output_file, width, height = _plot_prelude("hclust", distance_file, output_file, preset, width, height, fmt, "HclustCli")
279
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
280
+ "HclustCli", distance_file, output_file]
281
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="hclust")
282
+ sys.exit(ec)
283
+
284
+ @expr_group.command("dehist")
285
+ @click.argument("deg_file")
286
+ @click.argument("output_file")
287
+ @common_options
288
+ def expr_dehist(deg_file, output_file, verbose, quiet, fmt, preset, height, width, threads):
289
+ """差异表达双直方图"""
290
+ pre_flight("dehist", deg_file)
291
+ output_file = resolve_output(output_file, fmt)
292
+ # FIX(G5): 原注册类 DiffExp.DualHistPlot.DiffExpDualHistPlot 在 2.535 jar 不存在,
293
+ # 真实类 RNAseqViz.DiffExpDualHistPlot main 硬编码 → DeHistCli 桥(doctor 死命令探测发现)
294
+ ensure_bridge("DeHistCli")
295
+ args = ["java", "-Xmx2g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
296
+ "DeHistCli", deg_file, output_file]
297
+ if width: args += [str(width)]
298
+ if height: args += [str(height)]
299
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="dehist")
300
+ sys.exit(ec)
301
+
302
+ # ---- 命令组:树/进化 ----
303
+ @cli.group("tree")
304
+ def tree_group():
305
+ """树/进化命令"""
306
+
307
+ @tree_group.command("draw")
308
+ @click.argument("config_file")
309
+ @click.argument("output_file")
310
+ @common_options
311
+ def tree_draw(config_file, output_file, verbose, quiet, fmt, preset, height, width, threads):
312
+ """树+注释图(TreeTreeTree 多轨道)"""
313
+ output_file, width, height = _plot_prelude("draw", config_file, output_file, preset, width, height, fmt, "TreeCli")
314
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
315
+ "TreeCli", config_file, output_file]
316
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="draw")
317
+ sys.exit(ec)
318
+
319
+ @tree_group.command("unrooted")
320
+ @click.argument("newick_file")
321
+ @click.argument("output_file")
322
+ @common_options
323
+ def tree_unrooted(newick_file, output_file, verbose, quiet, fmt, preset, height, width, threads):
324
+ """无根树可视化"""
325
+ output_file, width, height = _plot_prelude("unrooted", newick_file, output_file, preset, width, height, fmt, "UnrootedTreeCli")
326
+ args = ["java", "-Xmx3g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
327
+ "UnrootedTreeCli", newick_file, output_file]
328
+ if width: args += ["--width", str(width)]
329
+ if height: args += ["--height", str(height)]
330
+ ec = run_plot(args, verbose=verbose, quiet=quiet, command_name="unrooted")
331
+ sys.exit(ec)
332
+
333
+ @tree_group.command("rooting")
334
+ @click.argument("input_nwk")
335
+ @click.argument("output_nwk")
336
+ @common_options
337
+ def tree_rooting(input_nwk, output_nwk, verbose, quiet, fmt, preset, height, width, threads):
338
+ """MAD 系统发育定根"""
339
+ # FIX(G5): 原注册类 newickParser.TreeTreeTree.TreeRootingByMAD 在 2.535 jar 不存在,
340
+ # 改走既有 TreeRootingCli 桥(quickMadRoot,08/29 已验证)
341
+ ensure_bridge("TreeRootingCli")
342
+ args = ["java", "-Xmx2g", "-cp", cp(os.path.join(ROOT, "build"), JAR),
343
+ "TreeRootingCli", input_nwk, output_nwk]
344
+ ec = run_java(args, verbose=verbose, quiet=quiet, command_name="rooting")
345
+ sys.exit(ec)
346
+
347
+
348
+ # 旧名别名(README/老用户兼容):treeRooting == rooting
349
+ tree_group.add_command(tree_rooting, name="treeRooting")
350
+
351
+ @tree_group.command("one-step")
352
+ @click.argument("pep_fasta")
353
+ @click.argument("output_prefix")
354
+ @click.option("--bb-time", "-b", type=int, default=1000, help="IQ-TREE bootstrap iterations")
355
+ @common_options
356
+ def tree_onesteptree(pep_fasta, output_prefix, bb_time, verbose, quiet, fmt, preset, height, width, threads):
357
+ """一步法 ML 树(muscle → trimal → IQ-TREE)"""
358
+ # FIX(G5): 原注册类 Phylogenetics.OneStepTree 在 2.475/2.535 jar 均不存在(外部测试 P1-1
359
+ # + 本地 doctor 探测复核);真实引擎为 BioSoftPipeServer.OneStepMLTree。
360
+ # ⚠️ 该引擎 ArgsParser 只认 --inPepFie/--outFilePrefix/--clean/--bbTime,没有 --threads!
361
+ args = ["java", "-Xmx4g", "-cp", JAR,
362
+ "biocjava.bioIO.BioSoftPipeServer.OneStepMLTree",
363
+ "--inPepFie", pep_fasta, "--outFilePrefix", output_prefix,
364
+ "--bbTime", str(bb_time)]
365
+ ec = run_java(args, verbose=verbose, quiet=quiet, command_name="onesteptree")
366
+ sys.exit(ec)
367
+
368
+ # ---- 命令组:工具 ----
369
+ # N2: GUI 面板类工具黑名单(headless 无参直通会弹 Swing 窗口悬挂)
370
+ _GUI_TOOLS = {"RNAplotAdvance", "PlotRNAfold", "AmazingGeneView", "BlastZone", "SequenceZone"}
371
+
372
+ class ToolGroup(click.Group):
373
+ """tool 分组:未知子命令自动转发 auto_commands + --help 列出全部"""
374
+ def resolve_command(self, ctx, args):
375
+ try:
376
+ return super().resolve_command(ctx, args)
377
+ except click.UsageError:
378
+ if args:
379
+ name = args[0]
380
+ impl = getattr(_ac, f'_{name}_impl', None)
381
+ if impl:
382
+ doc = (impl.__doc__ or '').split(':',1)[1].strip() if ':' in (impl.__doc__ or '') else f'{name} [参数...]'
383
+ pitfall = get_pitfall_hint(name)
384
+ help_text = doc + (f'\n\n⚠️ {pitfall}' if pitfall else '')
385
+ # FIX(P0-1): 旧写法闭包捕获 tool 分组自身 Context(ctx.args 恒空),
386
+ # 所有参数被丢弃。改用 pass_context 拿子命令自己的 Context。
387
+ # (外部测试 2026-09-19 实测,Windows 已验证)
388
+ @click.pass_context
389
+ def _fwd(sctx, _impl=impl):
390
+ sys.exit(_impl(list(sctx.args)))
391
+ cmd = click.Command(name=name,
392
+ callback=_fwd,
393
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
394
+ help=help_text)
395
+ return name, cmd, args[1:]
396
+ # FIX(P0-3): 回退到共享注册表(82 个 CLI 工具,原仅旧入口 tbcli.py 可达,
397
+ # 外部测试 §3.3:rpkmCal/statFasta/tpmCalc 等在新入口全部未找到)
398
+ cls = CLI_TOOLS.get(name)
399
+ if cls:
400
+ @click.pass_context
401
+ def _fwd_reg(sctx, _cls=cls, _name=name):
402
+ # N2: GUI 类工具无参直通会弹 Swing 窗口悬挂——黑名单无参时打印用法即退出
403
+ if _name in _GUI_TOOLS and not sctx.args:
404
+ click.echo(f"❌ {_name} 是 GUI 面板类工具,headless 下不可用;请提供参数直接调用引擎[Usage]查看签名", file=sys.stderr)
405
+ click.echo(f" 💡 查看引擎真实参数: java -cp $TBTOOLS_JAR {_cls} --bogus x(逼出 Usage)", file=sys.stderr)
406
+ sys.exit(1)
407
+ java_args = ["java", "-Xmx4g", "-cp", JAR, _cls] + list(sctx.args)
408
+ sys.exit(run_java(java_args, command_name=_name))
409
+ cmd = click.Command(name=name,
410
+ callback=_fwd_reg,
411
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
412
+ help=f"{name} [引擎命名参数...]\n\n⚠️ ArgsParser 系引擎一律 --key value 空格分隔,--key=value 会被拒绝")
413
+ return name, cmd, args[1:]
414
+ click.echo(f"❌ 未知工具: {name}", file=sys.stderr)
415
+ count = 0
416
+ for n in sorted(dir(_ac)):
417
+ if n.startswith('_') and n.endswith('_impl') and not n.startswith('__'):
418
+ cmd = n[1:-5]
419
+ doc = getattr(_ac, n).__doc__ or ''
420
+ short = doc.split(':',1)[1].strip()[:50] if ':' in doc else ''
421
+ click.echo(f" {cmd:20s} {short}", file=sys.stderr)
422
+ count += 1
423
+ for tname in sorted(CLI_TOOLS):
424
+ if getattr(_ac, f'_{tname}_impl', None):
425
+ continue
426
+ click.echo(f" {tname:20s} {CLI_TOOLS[tname].split('.')[-1]}", file=sys.stderr)
427
+ count += 1
428
+ click.echo(f"\n共 {count} 个工具,查看: tbtools list tools", file=sys.stderr)
429
+ ctx.exit(2)
430
+ raise
431
+
432
+ def format_options(self, ctx, formatter):
433
+ """重写 --help:手动命令 + auto_command 工具全列出"""
434
+ super().format_options(ctx, formatter)
435
+ plot_groups = {'seq', 'expr', 'tree', 'syn', 'sets', 'chipseq'}
436
+ entries = []
437
+ for n in sorted(dir(_ac)):
438
+ if n.startswith('_') and n.endswith('_impl') and not n.startswith('__'):
439
+ cmd = n[1:-5]
440
+ cat = CATEGORY_MAP.get(cmd, 'engine')
441
+ if cat in plot_groups:
442
+ continue
443
+ doc = getattr(_ac, n).__doc__ or ''
444
+ short = doc.split(':',1)[1].strip()[:50] if ':' in doc else ''
445
+ entries.append(f"{cmd:20s} {short}")
446
+ if entries:
447
+ with formatter.section(f'可用工具(共 {len(entries)} 个,完整列表: tbtools list tools)'):
448
+ for e in entries[:20]:
449
+ formatter.write_text(e)
450
+ if len(entries) > 20:
451
+ formatter.write_text(f"... 及其他 {len(entries)-20} 个")
452
+
453
+ @cli.group("tool", cls=ToolGroup)
454
+ def tool_group():
455
+ """命令行工具(82 个)"""
456
+
457
+ @tool_group.command("stat-fasta")
458
+ @click.argument("input_file")
459
+ @click.argument("output_file")
460
+ @common_options
461
+ def tool_stat_fasta(input_file, output_file, verbose, quiet, fmt, preset, height, width, threads):
462
+ """FASTA 序列统计"""
463
+ # stdin 管道支持
464
+ if input_file == "-":
465
+ tmp = safe_temp(suffix=".fa")
466
+ with open(tmp, "wb") as f:
467
+ f.write(sys.stdin.buffer.read())
468
+ input_file = tmp
469
+ if output_file == "-":
470
+ output_file = stdout_path()
471
+ args = ["java", "-Xmx2g", "-cp", JAR,
472
+ "biocjava.bioIO.FastX.FastaIndex.QuickStatFasta",
473
+ "--inFasta", input_file, "--outPutFile", output_file]
474
+ ec = run_java(args, verbose=verbose, quiet=quiet, command_name="stat_fasta")
475
+ sys.exit(ec)
476
+
477
+ @tool_group.command("cds2protein")
478
+ @click.argument("cds_fasta")
479
+ @click.argument("output_file")
480
+ @common_options
481
+ def tool_cds2protein(cds_fasta, output_file, verbose, quiet, fmt, preset, height, width, threads):
482
+ """CDS → 蛋白质翻译"""
483
+ if output_file == "-": output_file = stdout_path()
484
+ # FIX(G5): 原注册类 JIGplotToolkit.Protein.CdsToProtein 在 2.535 jar 不存在,
485
+ # 真实引擎 bioIO.ORF.Translater(ArgsParser: --inFa/--outFa)
486
+ args = ["java", "-Xmx2g", "-cp", JAR,
487
+ "biocjava.bioIO.ORF.Translater",
488
+ "--inFa", cds_fasta, "--outFa", output_file]
489
+ ec = run_java(args, verbose=verbose, quiet=quiet, command_name="cds2protein")
490
+ sys.exit(ec)
491
+
492
+ @tool_group.command("fasta-extract")
493
+ @click.argument("input_fasta")
494
+ @click.argument("id_list")
495
+ @click.argument("output_file")
496
+ @common_options
497
+ def tool_fasta_extract(input_fasta, id_list, output_file, verbose, quiet, fmt, preset, height, width, threads):
498
+ """按 ID 列表提取 FASTA 序列(idList: 一行一个 ID,不带 > 号;与原始 header 精确匹配)"""
499
+ if input_fasta == "-":
500
+ tmp = safe_temp(suffix=".fa")
501
+ with open(tmp, "wb") as f: f.write(sys.stdin.buffer.read())
502
+ input_fasta = tmp
503
+ if output_file == "-": output_file = stdout_path()
504
+ # FIX(G5): 原注册类 bioIO.FastX.FastaIndex.ExtractFasta 路径错误,
505
+ # 真实类 bioDoer.Fasta.ExtractFasta(ArgsParser: --inFa/--inIDList/--outFa)
506
+ args = ["java", "-Xmx2g", "-cp", JAR,
507
+ "biocjava.bioDoer.Fasta.ExtractFasta",
508
+ "--inFa", input_fasta, "--inIDList", id_list, "--outFa", output_file]
509
+ ec = run_java(args, verbose=verbose, quiet=quiet, command_name="fasta_extract")
510
+ sys.exit(ec)
511
+
512
+ # ---- 动态装配(批次 B: cli_load)----
513
+ build_and_load(cli)
514
+ register_top(cli, cli_load)
515
+
516
+ _load_dynamic_commands()
517
+ _load_auto_commands()
518
+
519
+
520
+ # 旧名别名(README/老用户兼容,顶层/分组均可用)——须在 cli() 前注册
521
+ seq_group.add_command(seqlogo, name="seqlogo")
522
+ expr_group.add_command(heatmap, name="heatmap2")
523
+ # ---- RPC 分组注册(批次 B: 从 cli_rpc 模块组装)----
524
+ cli.add_command(build_rpc_group())
525
+
526
+
527
+ if __name__ == "__main__":
528
+ cli()