excel-codegen 0.9.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 (82) hide show
  1. excel_codegen/__init__.py +160 -0
  2. excel_codegen/__main__.py +8 -0
  3. excel_codegen/cli.py +1250 -0
  4. excel_codegen/derived.py +465 -0
  5. excel_codegen/example_pack.py +107 -0
  6. excel_codegen/examples/README.md +69 -0
  7. excel_codegen/examples/abs_fpi/ABS_FPI_load_cases.xlsx +0 -0
  8. excel_codegen/examples/abs_fpi/ABS_FPI_load_cases_render.bat +22 -0
  9. excel_codegen/examples/abs_fpi/ABS_FPI_load_cases_render.sh +23 -0
  10. excel_codegen/examples/abs_fpi/README.md +121 -0
  11. excel_codegen/examples/abs_fpi/abs_fpi.yaml +310 -0
  12. excel_codegen/examples/abs_fpi/abs_fpi_external.xlsx +0 -0
  13. excel_codegen/examples/abs_fpi/abs_fpi_external.yaml +162 -0
  14. excel_codegen/examples/abs_fpi/abs_fpi_external_render.bat +22 -0
  15. excel_codegen/examples/abs_fpi/abs_fpi_external_render.sh +23 -0
  16. excel_codegen/examples/abs_fpi/abs_fpi_internal.xlsx +0 -0
  17. excel_codegen/examples/abs_fpi/abs_fpi_internal.yaml +266 -0
  18. excel_codegen/examples/abs_fpi/abs_fpi_internal_render.bat +22 -0
  19. excel_codegen/examples/abs_fpi/abs_fpi_internal_render.sh +23 -0
  20. excel_codegen/examples/abs_fpi/compose.yaml +20 -0
  21. excel_codegen/examples/abs_fpi/generated/COT1-d20.559-mu60.js +124 -0
  22. excel_codegen/examples/abs_fpi/generated/COT1-d20.559-mu60_summary.md +30 -0
  23. excel_codegen/examples/abs_fpi/generated/EXT-T15-mu0-kf-1.js +91 -0
  24. excel_codegen/examples/abs_fpi/generated/EXT-T15-mu0-kf-1_summary.md +23 -0
  25. excel_codegen/examples/abs_fpi/generated/EXT-T15-mu90-kf+1.js +91 -0
  26. excel_codegen/examples/abs_fpi/generated/EXT-T15-mu90-kf+1_summary.md +23 -0
  27. excel_codegen/examples/abs_fpi/generated/EXT-T20.559-mu90-kf+1.js +91 -0
  28. excel_codegen/examples/abs_fpi/generated/EXT-T20.559-mu90-kf+1_summary.md +23 -0
  29. excel_codegen/examples/abs_fpi/generated/WBT6-d15.059-mu0.js +124 -0
  30. excel_codegen/examples/abs_fpi/generated/WBT6-d15.059-mu0_summary.md +30 -0
  31. excel_codegen/examples/abs_fpi/generated/WBT6-d8-mu90.js +124 -0
  32. excel_codegen/examples/abs_fpi/generated/WBT6-d8-mu90_summary.md +30 -0
  33. excel_codegen/examples/abs_fpi/generated/WBT7-d20.559-mu90.js +124 -0
  34. excel_codegen/examples/abs_fpi/generated/WBT7-d20.559-mu90_summary.md +30 -0
  35. excel_codegen/examples/abs_fpi/templates/ext_body.js.j2 +91 -0
  36. excel_codegen/examples/abs_fpi/templates/ext_summary.md.j2 +23 -0
  37. excel_codegen/examples/abs_fpi/templates/int_body.js.j2 +124 -0
  38. excel_codegen/examples/abs_fpi/templates/int_summary.md.j2 +30 -0
  39. excel_codegen/examples/basic/example.yaml +97 -0
  40. excel_codegen/examples/basic/example_formula.yaml +86 -0
  41. excel_codegen/examples/basic/generated/uart_init_Case1.c +6 -0
  42. excel_codegen/examples/basic/generated/uart_init_Case2.c +6 -0
  43. excel_codegen/examples/basic/generated/uart_summary_Case1.md +4 -0
  44. excel_codegen/examples/basic/generated/uart_summary_Case2.md +4 -0
  45. excel_codegen/examples/basic/generated_formula/uart_init_Case1.c +5 -0
  46. excel_codegen/examples/basic/generated_formula/uart_init_Case2.c +5 -0
  47. excel_codegen/examples/basic/generated_formula/uart_pins_Case1.csv +1 -0
  48. excel_codegen/examples/basic/generated_formula/uart_pins_Case2.csv +1 -0
  49. excel_codegen/examples/basic/template.xlsx +0 -0
  50. excel_codegen/examples/basic/template_formula.xlsx +0 -0
  51. excel_codegen/examples/basic/template_formula_render.bat +22 -0
  52. excel_codegen/examples/basic/template_formula_render.sh +23 -0
  53. excel_codegen/examples/basic/template_render.bat +22 -0
  54. excel_codegen/examples/basic/template_render.sh +23 -0
  55. excel_codegen/examples/nastran/generated/case_control.deck +18 -0
  56. excel_codegen/examples/nastran/generated/cc_01_LC1.inc +8 -0
  57. excel_codegen/examples/nastran/generated/cc_02_LC2.inc +8 -0
  58. excel_codegen/examples/nastran/generated/cc_03_LC3.inc +8 -0
  59. excel_codegen/examples/nastran/generated/cc_04_COMB4.inc +8 -0
  60. excel_codegen/examples/nastran/generated/cc_05_LC5.inc +8 -0
  61. excel_codegen/examples/nastran/generated/cc_summary_01_LC1.md +1 -0
  62. excel_codegen/examples/nastran/generated/cc_summary_02_LC2.md +1 -0
  63. excel_codegen/examples/nastran/generated/cc_summary_03_LC3.md +1 -0
  64. excel_codegen/examples/nastran/generated/cc_summary_04_COMB4.md +1 -0
  65. excel_codegen/examples/nastran/generated/cc_summary_05_LC5.md +1 -0
  66. excel_codegen/examples/nastran/nastran_case_control.xlsx +0 -0
  67. excel_codegen/examples/nastran/nastran_case_control.yaml +141 -0
  68. excel_codegen/examples/nastran/nastran_case_control_render.bat +22 -0
  69. excel_codegen/examples/nastran/nastran_case_control_render.sh +23 -0
  70. excel_codegen/excel_io.py +1885 -0
  71. excel_codegen/formula.py +674 -0
  72. excel_codegen/formula_eval.py +685 -0
  73. excel_codegen/jinja_env.py +92 -0
  74. excel_codegen/models.py +1008 -0
  75. excel_codegen/renderer.py +558 -0
  76. excel_codegen/utils.py +225 -0
  77. excel_codegen-0.9.0.dist-info/METADATA +179 -0
  78. excel_codegen-0.9.0.dist-info/RECORD +82 -0
  79. excel_codegen-0.9.0.dist-info/WHEEL +5 -0
  80. excel_codegen-0.9.0.dist-info/entry_points.txt +2 -0
  81. excel_codegen-0.9.0.dist-info/licenses/LICENSE +21 -0
  82. excel_codegen-0.9.0.dist-info/top_level.txt +1 -0
excel_codegen/utils.py ADDED
@@ -0,0 +1,225 @@
1
+ """基础工具:错误类型、Excel 单元格地址运算、Prefix/Value/Suffix 组合值。
2
+
3
+ 本模块刻意不依赖任何第三方库,方便单独单元测试与复用。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ import re
10
+ from collections.abc import Iterable
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ __all__ = [
15
+ "CodeGenError",
16
+ "ConfigError",
17
+ "ExcelError",
18
+ "RenderError",
19
+ "VarValue",
20
+ "cell_ref",
21
+ "column_index_to_letter",
22
+ "column_letter_to_index",
23
+ "fingerprint",
24
+ "is_identifier",
25
+ "parse_cell",
26
+ "safe_filename",
27
+ "slugify",
28
+ "split_cell",
29
+ "split_lines",
30
+ "to_text",
31
+ ]
32
+
33
+
34
+ # --------------------------------------------------------------------------- #
35
+ # 错误类型
36
+ # --------------------------------------------------------------------------- #
37
+ class CodeGenError(Exception):
38
+ """excel_codegen 所有面向用户的错误的基类(CLI 会友好地打印它们)。"""
39
+
40
+
41
+ class ConfigError(CodeGenError):
42
+ """YAML 配置缺失、无法解析或语义非法。"""
43
+
44
+
45
+ class ExcelError(CodeGenError):
46
+ """Excel 文件缺失、无法读取,或工作表结构与配置不一致。"""
47
+
48
+
49
+ class RenderError(CodeGenError):
50
+ """Jinja2 模板语法错误或渲染期错误(例如变量缺失)。"""
51
+
52
+
53
+ # --------------------------------------------------------------------------- #
54
+ # 单元格 / 列标运算
55
+ # --------------------------------------------------------------------------- #
56
+ _CELL_RE = re.compile(r"^\$?([A-Za-z]{1,3})\$?([0-9]+)$")
57
+ _IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
58
+ _UNSAFE_FILENAME_RE = re.compile(r'[<>:"/\\|?*\x00-\x1f]')
59
+
60
+
61
+ def column_letter_to_index(letter: str) -> int:
62
+ """``"A" -> 1``、``"Z" -> 26``、``"AA" -> 27``。非法输入抛 ``ValueError``。"""
63
+ letters = str(letter).strip().upper()
64
+ if not letters or not letters.isalpha():
65
+ raise ValueError(f"非法列标: {letter!r}")
66
+ index = 0
67
+ for char in letters:
68
+ index = index * 26 + (ord(char) - ord("A") + 1)
69
+ if index < 1:
70
+ raise ValueError(f"非法列标: {letter!r}")
71
+ return index
72
+
73
+
74
+ def column_index_to_letter(index: int) -> str:
75
+ """``1 -> "A"``、``27 -> "AA"``。"""
76
+ value = int(index)
77
+ if value < 1:
78
+ raise ValueError(f"列号必须 >= 1,收到 {index!r}")
79
+ letters = ""
80
+ while value:
81
+ value, remainder = divmod(value - 1, 26)
82
+ letters = chr(ord("A") + remainder) + letters
83
+ return letters
84
+
85
+
86
+ def parse_cell(cell: str) -> tuple[int, int]:
87
+ """把 ``"B2"`` 解析为 ``(列号, 行号)``(均从 1 开始)。"""
88
+ match = _CELL_RE.match(str(cell).strip())
89
+ if not match:
90
+ raise ValueError(f"非法单元格引用: {cell!r}(示例: B2)")
91
+ return column_letter_to_index(match.group(1)), int(match.group(2))
92
+
93
+
94
+ def split_cell(cell: str) -> tuple[str, int]:
95
+ """把 ``"B2"`` 解析为 ``("B", 2)``。"""
96
+ column, row = parse_cell(cell)
97
+ return column_index_to_letter(column), row
98
+
99
+
100
+ def cell_ref(column: int, row: int) -> str:
101
+ """``(2, 2) -> "B2"``。"""
102
+ return f"{column_index_to_letter(column)}{row}"
103
+
104
+
105
+ # --------------------------------------------------------------------------- #
106
+ # 值处理
107
+ # --------------------------------------------------------------------------- #
108
+ #: 超过这个量级的浮点不再做"整数化"处理(避免 1e20 变成 21 位数字串)。
109
+ _INT_SAFE_LIMIT = 1e16
110
+
111
+
112
+ def to_text(value: Any) -> str:
113
+ """把 Excel / YAML 值转换成生成代码中应出现的文本形式。
114
+
115
+ * ``None`` -> ``""``
116
+ * ``True`` / ``False`` -> ``"true"`` / ``"false"``
117
+ * ``115200.0`` -> ``"115200"``(整数浮点去掉小数点,避免 Excel 数值失真)
118
+ * ``155.85637499999999`` -> ``"155.856375"``(保留 15 位有效数字,与 Excel/WPS 的显示一致)
119
+ * ``1e20`` -> ``"1e+20"``(量级过大时保留科学计数法)
120
+
121
+ 15 位有效数字这一条很关键:派生参数(``derived:``)是算出来的,二进制浮点的尾巴
122
+ (``0.1 + 0.2`` 那种)如果原样输出,就会出现"Excel 里显示 155.856375、导出文件里写
123
+ 155.85637499999999"的假差异;Excel 的 General 格式本身就是 15 位有效数字,
124
+ 所以这里对齐它。Jinja2 侧还通过 ``finalize`` 对 ``{{ x.value }}`` 施加同一规则。
125
+
126
+ > 残留差异:有效数字超过 15 位的量(例如 17 位整数部分)两边仍可能不同 ——
127
+ > ``excel-codegen check`` 会在公式模式下把公式算一遍来发现它。
128
+ """
129
+ if value is None:
130
+ return ""
131
+ if isinstance(value, bool):
132
+ return "true" if value else "false"
133
+ if isinstance(value, float):
134
+ if value.is_integer() and abs(value) < _INT_SAFE_LIMIT:
135
+ return str(int(value))
136
+ # %.15g:15 位有效数字(<= 0 时保留科学计数法),再取最短往返表示
137
+ return repr(float(f"{value:.15g}"))
138
+ if isinstance(value, str):
139
+ return value
140
+ return str(value)
141
+
142
+
143
+ def fingerprint(*parts: Any) -> str:
144
+ """对若干段文本求一个短指纹(sha1 前 12 位),用于工作簿里的"参数/输出"记录。"""
145
+ rows: list[str] = []
146
+ for part in parts:
147
+ if isinstance(part, str):
148
+ rows.append(part)
149
+ elif isinstance(part, Iterable):
150
+ rows.extend(to_text(item) for item in part)
151
+ else:
152
+ rows.append(to_text(part))
153
+ return hashlib.sha1("\n".join(rows).encode("utf-8")).hexdigest()[:12]
154
+
155
+
156
+ def split_lines(text: str) -> list[str]:
157
+ """按行拆分渲染结果,且不保留结尾空行。"""
158
+ return str(text).splitlines()
159
+
160
+
161
+ def is_identifier(name: str) -> bool:
162
+ """判断是否为合法 Python/Jinja2 标识符(变量名必须满足)。"""
163
+ return bool(_IDENTIFIER_RE.match(str(name)))
164
+
165
+
166
+ def slugify(name: str) -> str:
167
+ """把任意文本转成适合做文件名/标识符的片段。"""
168
+ cleaned = re.sub(r"[^0-9A-Za-z_.-]+", "_", str(name).strip()).strip("_.")
169
+ return cleaned or "unnamed"
170
+
171
+
172
+ def safe_filename(name: str) -> str:
173
+ """去掉 Windows / POSIX 文件名中的非法字符。"""
174
+ cleaned = _UNSAFE_FILENAME_RE.sub("_", str(name).strip()).strip(" .")
175
+ return cleaned or "output"
176
+
177
+
178
+ # --------------------------------------------------------------------------- #
179
+ # Prefix + Value + Suffix
180
+ # --------------------------------------------------------------------------- #
181
+ @dataclass(frozen=True)
182
+ class VarValue:
183
+ """变量值及其前缀/后缀包装对象。
184
+
185
+ 渲染上下文中的每个变量都是 ``VarValue``,因此在 Jinja2 模板里::
186
+
187
+ {{ port }} -> "GPIOA_PORT" (str(VarValue),即 prefix+value+suffix)
188
+ {{ port.value }} -> "A" (纯值)
189
+ {{ port.prefix }} -> "GPIO"
190
+ {{ port.suffix }} -> "_PORT"
191
+
192
+ ``value`` 保留原始类型(int / float / str 均可),只有转成字符串时才做
193
+ "整数浮点去零" 的规范化,因此 ``{{ baud }}`` 输出 ``115200`` 而不是 ``115200.0``。
194
+ """
195
+
196
+ value: Any = ""
197
+ prefix: str = ""
198
+ suffix: str = ""
199
+
200
+ def __post_init__(self) -> None:
201
+ object.__setattr__(self, "prefix", "" if self.prefix is None else str(self.prefix))
202
+ object.__setattr__(self, "suffix", "" if self.suffix is None else str(self.suffix))
203
+
204
+ @property
205
+ def text(self) -> str:
206
+ """纯值的文本形式(``None`` -> ``""``)。"""
207
+ return to_text(self.value)
208
+
209
+ @property
210
+ def is_empty(self) -> bool:
211
+ """值、前缀、后缀全为空时为 ``True``。"""
212
+ return not self.text and not self.prefix and not self.suffix
213
+
214
+ def __str__(self) -> str:
215
+ return f"{self.prefix}{self.text}{self.suffix}"
216
+
217
+ def __bool__(self) -> bool:
218
+ return not self.is_empty
219
+
220
+ def __format__(self, format_spec: str) -> str:
221
+ return format(str(self), format_spec)
222
+
223
+ def as_dict(self) -> dict[str, Any]:
224
+ """便于调试与测试的字典形式。"""
225
+ return {"value": self.value, "prefix": self.prefix, "suffix": self.suffix}
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: excel_codegen
3
+ Version: 0.9.0
4
+ Summary: Excel 模板参数填写 + Jinja2 代码生成器(支持 Prefix/Value/Suffix 组合)
5
+ Author: JG. Luo
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/Dog-Chest/excel_codegen#readme
8
+ Project-URL: Repository, https://github.com/Dog-Chest/excel_codegen
9
+ Project-URL: Changelog, https://github.com/Dog-Chest/excel_codegen/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/Dog-Chest/excel_codegen/issues
11
+ Keywords: excel,openpyxl,jinja2,codegen,template
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Software Development :: Code Generators
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: openpyxl>=3.1.2
22
+ Requires-Dist: jinja2>=3.1.2
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: pydantic>=2.5
25
+ Requires-Dist: typer>=0.12
26
+ Requires-Dist: rich>=13.7
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.4; extra == "dev"
29
+ Requires-Dist: pytest-cov>=4.1; extra == "dev"
30
+ Requires-Dist: ruff>=0.6; extra == "dev"
31
+ Requires-Dist: mypy>=1.11; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # excel_codegen
35
+
36
+ **在 Excel 里填参数,用 YAML + Jinja2 出代码。**
37
+
38
+ [![ci](https://github.com/Dog-Chest/excel_codegen/actions/workflows/ci.yml/badge.svg)](https://github.com/Dog-Chest/excel_codegen/actions/workflows/ci.yml)
39
+ ![python](https://img.shields.io/badge/python-3.11%2B-blue)
40
+ ![license](https://img.shields.io/badge/license-MIT-green)
41
+
42
+ > 🤖 **本项目由 DeepSeek 自动生成**:代码、测试与文档都在 DeepSeek Harness 里由 AI 编写,
43
+ > 人工负责提需求、定方向与验收。
44
+
45
+ 工程师最熟悉的填参数界面是 Excel,最该被评审和版本管理的生成规则却常常散在一次性脚本里。
46
+ 这个工具把两头接起来:**变量与模板写在 YAML**(进 Git、可 diff、可评审、可攒成模板库),
47
+ **生成一本 Excel 之后就脱离命令行干活** —— 参数在 Excel 里填,输出表里的代码由 Excel 自己算,
48
+ 改一格就变,不需要再跑任何命令。
49
+
50
+ ```
51
+ YAML 模板库 一本独立可用的工作簿
52
+ ┌──────────────────┐ ┌───────────────────────────────────────┐
53
+ │ variables │ │ Global / Local Parameter:填参数 │
54
+ │ templates(Jinja2)│──init─►│ Output:**Excel 公式**,改参数自己重算 │
55
+ └──────────────────┘ └───────────────────────────────────────┘
56
+ │ │
57
+ │ 下次复用 / 微调再改 YAML │ 要把代码导成文件时才用命令行
58
+ └──────────────────────────────────┴──► render --outdir
59
+ ```
60
+
61
+ ## 快速开始
62
+
63
+ ```bash
64
+ # 1) 把内置示例拷出来 —— 示例随包发布,装了 pip 包就够,不用克隆仓库
65
+ excel-codegen examples # 看有哪些、各自演示什么
66
+ excel-codegen examples --copy ./examples
67
+
68
+ # 2) 打开示例里那本已经填好样例参数的工作簿就能改:
69
+ # Global Parameter 的 B 列、Local Parameter 的 E/F 列
70
+ # Output 表里是活公式 —— 改完参数**它自己就重算了**,不需要任何命令
71
+
72
+ # 3) 只有「要把代码导成文件」时才回到命令行
73
+ excel-codegen render -c examples/basic/example_formula.yaml \
74
+ -x examples/basic/template_formula.xlsx --outdir out
75
+ ```
76
+
77
+ 想核对「Excel 里算出来的」与「Python 渲染的」是否一致(CI 里很有用):`excel-codegen check`。
78
+
79
+ 没装 uv 就去掉 `uv run`,先按下面「安装」把环境准备好。
80
+
81
+ 内置示例(`excel-codegen examples`,源码在 [`excel_codegen/examples/`](excel_codegen/examples/)):
82
+
83
+ | 示例 | 演示什么 |
84
+ | --- | --- |
85
+ | [`basic`](excel_codegen/examples/basic/) | 入门:`example_formula.yaml` 是**默认的公式模式**(改参数后打开 Excel 就重算);`example.yaml` 显式 `engine: snapshot`,演示过滤器 / 循环 / 导出文件 |
86
+ | [`nastran`](excel_codegen/examples/nastran/) | **一行一个工况**(`local_direction: vertical`)+ 公式模式生成 NASTRAN 工况控制语句:语句留空就不输出,在 Excel 里改一格 Code 列立刻跟着变(指南 §19 / §20) |
87
+ | [`abs_fpi`](excel_codegen/examples/abs_fpi/) | **现场用例**:ABS FPI 内外压 → GeniE。两个规则集可各自单用,也可合成一本项目工作簿共用一张 Global 表;内压用成员表 `Tank Data` 把舱参数只写一遍 |
88
+
89
+ ## 特性
90
+
91
+ | | |
92
+ | --- | --- |
93
+ | **一个 YAML 描述一切** | 全局变量、局部变量、多个输出模板(内联 `code` 或外部 `template_file`) |
94
+ | **Excel 就是表单** | 一行一个全局变量;局部参数默认一个工况一列(右拉即增列),也可切成**一行一个工况**(`local_direction: vertical`,下拉即增行)—— 输入与输出布局各自独立 |
95
+ | **Prefix + Value + Suffix** | `{{ port }}` → `GPIOA_PORT`,`{{ port.value }}` → `A`;空格有意义(`suffix: " m"` 就是 `" m"`) |
96
+ | **默认就是公式模式** | 输出表里写 **Excel 公式**:改参数后打开 Excel 即重算,**不用再跑脚本**,工作簿脱离命令行也独立可用。支持**行内 `{% if %}`**(编译成 `IF()`);模板要用 `{% for %}` / 过滤器 / `{% include %}` 时才显式写 `engine: snapshot` |
97
+ | **派生参数** | `derived: "rho * g"` 让参数引用参数(同工况的 local + global),参数表里是活公式 |
98
+ | **取值约束** | `min` / `max` / `choices` / `pattern` 声明合法取值:Excel 里变下拉列表与数值范围,`render` / `validate` / `check` 每次读表都再查一遍 —— 挡住"手滑把 20.559 打成 205.59 却照样生成代码" |
99
+ | **一本工作簿放多套规则** | `case_filter: "kind == 'EXT'"` 让模板只作用于匹配的工况,共用一张 Global 表 |
100
+ | **跨文件复用** | `extends: [rules/a.yaml, rules/b.yaml]` 把几套规范合并进一份项目配置 —— 变量与模板不用手抄,`template_file` 相对各自文件解析 |
101
+ | **三层作用域** | 船(global)→ 工况(local)→ **舱 / 设备(成员表)**:被多个工况引用的舱参数只写一遍,改一处就够 |
102
+ | **可导出代码文件** | 文件名支持 Jinja2,**参数也能用**(`cc_{{ seq }}_{{ case_name }}.inc`),一次生成多份 |
103
+ | **建表时就把错误拦住** | 模板语法、`{% include %}` 片段、以及公式模式表达不了的写法(过滤器 / 循环 / 跨行 `{% if %}`)在 `init` 就报,并告诉你怎么改 |
104
+ | **自带说明与指纹** | 工作簿里有 `HOWTO` 表与生成指纹;`excel-codegen check` 判定"表里的代码是否已过期",**CI 可用** |
105
+
106
+ ## 现场用例:ABS FPI 内外压 → GeniE
107
+
108
+ [`excel_codegen/examples/abs_fpi/`](excel_codegen/examples/abs_fpi/) 是拿这个工具干真活的现场用例
109
+ (`excel-codegen examples --copy .` 就能拿到)—— 把 **ABS FPI**(5A-3-2/5.5 外压、
110
+ 5.7 内压)的面载荷计算,生成可直接粘进 GeniE 的 JavaScript 函数体。
111
+
112
+ | 工作簿 | 内容 | 工况 |
113
+ | --- | --- | --- |
114
+ | `abs_fpi_external.xlsx` | 外压单用 | 3 |
115
+ | `abs_fpi_internal.xlsx` | 内压单用 | 4 |
116
+ | `ABS_FPI_load_cases.xlsx` | **项目工作簿**:两套规则共用一张 Global 表,主尺度只填一次 | 7 |
117
+
118
+ 三本都是 `engine: excel` —— 在 Excel 里改吃水 / 舱容,代码列立刻重算。
119
+ 模板里那些 C_1 区间、Girth 插值的 `if/else` 是**生成目标语言自己的字面文本**,
120
+ 不是 Jinja 控制流,所以整份模板能进公式模式。
121
+
122
+ 它同时是这个工具的**实测证据**:四层校验链(`validate` → `check`(含公式求值)→
123
+ `verify_excel_engine.py` → `compare_with_rules.js`)每跑一次都重新证明"生成的代码是对的";
124
+ 逐轮试用发现的问题与修复复测记录在 [`abs_fpi/FINDINGS.md`](abs_fpi/FINDINGS.md)
125
+ (`abs_fpi/` 是开发侧的现场脚本与实测报告,**不随包发布**;随包发布的是上面那个示例目录)。
126
+
127
+ > 用例里的舱容与工况组合只是**示意数值**(随手取的几个数),不代表任何真实船舶。
128
+
129
+ ## 文档
130
+
131
+ | 文档 | 内容 |
132
+ | --- | --- |
133
+ | [`docs/template_guide.md`](docs/template_guide.md) | **模板库扩展指南**:YAML 字段、Prefix/Suffix、Jinja2 速查、Excel 表结构、`case_filter`、排错、FAQ、能力边界、公式模式与派生参数 |
134
+ | [`docs/cli.md`](docs/cli.md) | 命令参考(六个命令的全部选项)、作为 Python 库使用、错误类型 |
135
+ | [`docs/setup.md`](docs/setup.md) | 安装与跨平台环境:uv 零配置、venv + pip、Windows / Ubuntu 的坑、Python 版本策略 |
136
+ | [`excel_codegen/examples/README.md`](excel_codegen/examples/README.md) | **内置示例**:三个示例各自演示什么、怎么拷出来、怎么改 |
137
+ | [`CHANGELOG.md`](CHANGELOG.md) | 版本变更历史 |
138
+ | [`abs_fpi/README.md`](abs_fpi/README.md) | 现场项目:怎么用、边界、四层校验链 |
139
+ | [`docs/publishing.md`](docs/publishing.md) | 维护者向:发布到 GitHub / PyPI、密钥与隐私 |
140
+
141
+ ## 安装
142
+
143
+ 需要 **Python 3.11+**(Windows / Linux / macOS 均可)。
144
+
145
+ ```bash
146
+ # 推荐:uv —— 不用建 venv、不用 pip、不用管本机 Python 版本
147
+ curl -LsSf https://astral.sh/uv/install.sh | sh # Windows 见 docs/setup.md
148
+
149
+ # 或经典方式
150
+ python3 -m venv ~/.venvs/excel_codegen && source ~/.venvs/excel_codegen/bin/activate
151
+ pip install -e ".[dev]"
152
+ ```
153
+
154
+ 装好后得到命令 `excel-codegen`(也可用 `python -m excel_codegen`)。
155
+ 跨平台细节、外置盘注意事项与 Python 版本策略见 [`docs/setup.md`](docs/setup.md)。
156
+
157
+ > **PyPI 发布流程已就绪、尚未首次发布**([`docs/publishing.md`](docs/publishing.md) 里有一次性登记步骤)。
158
+ > 发布之后就可以不克隆仓库、直接 `uv tool install excel-codegen`(或 `pipx install excel-codegen`)。
159
+
160
+ ## 开发
161
+
162
+ ```bash
163
+ uv run pytest --cov # 349 项用例 + 覆盖率门槛(85%)
164
+ uv run ruff check . # lint
165
+ uv run ruff format --check . # 格式
166
+ uv run mypy # 类型检查
167
+ ```
168
+
169
+ CI([配置](.github/workflows/ci.yml))在 **Windows / macOS / Linux × Python 3.11 / 3.13**
170
+ 上把上面四条各跑一遍。支持下限是 **3.11**,开发默认 3.12;改下限的规矩见 [`docs/setup.md`](docs/setup.md)。
171
+
172
+ > 质量护栏都在 `pyproject.toml` 里:`[tool.ruff]` 的 `target-version = "py311"` 会拦住
173
+ > "3.12 才支持、但下限写的是 3.11"的语法(真发生过一次:f-string 表达式里写引号在 3.11 是
174
+ > 语法错误,而在本地 3.12 上跑得好好的),`[tool.coverage.report]` 的 `fail_under = 85`
175
+ > 让覆盖率掉了就直接红。
176
+
177
+ ## License
178
+
179
+ MIT —— 见 [`LICENSE`](LICENSE)。
@@ -0,0 +1,82 @@
1
+ excel_codegen/__init__.py,sha256=1Nok4MJ6lbE7O52oFTP1XsIrrGuFApqpme6UalaxY4s,3452
2
+ excel_codegen/__main__.py,sha256=NxENOILb8NAnZv6fRa8SuxaIvovdytVtGimmgYjqlN0,171
3
+ excel_codegen/cli.py,sha256=hD6oH7OHOkwHy5gpj88LKrfKOtZNktZ7hE6kgEQHO44,53871
4
+ excel_codegen/derived.py,sha256=G1g4Wd9O7L5tgOB_MJ2h7oBOzOWkOIaWdXK5YxownZ8,21610
5
+ excel_codegen/example_pack.py,sha256=zvR2OOvp8YEaJKr3MgTgS7RZ3o7TZkS4IHNyzcbA44I,4394
6
+ excel_codegen/excel_io.py,sha256=M0B5YgVqdvXxxhcJ0ZwvALWp1nYz4ISPTsGBqqh7nbs,82018
7
+ excel_codegen/formula.py,sha256=azi82Z399BshgwoFSnmobOZXzsAVgA9S42D2I18Un5Y,30329
8
+ excel_codegen/formula_eval.py,sha256=EzH8hJXkaL-3yrq_WVV5lgxGf2mav9Ysm7qdi25UBAs,28275
9
+ excel_codegen/jinja_env.py,sha256=iN5cE1VOKGGTd62gs_tFvso-mhS1nkiYN9gHTEyS87M,3910
10
+ excel_codegen/models.py,sha256=0GtI1L-VAn4Kf_kOdqUWFpMbEHDrDdEYf0rwGCuLk-w,41961
11
+ excel_codegen/renderer.py,sha256=pkxsMpY3nAXIh_sBRw4rt12dFc4yGC5ZBnYEMS1GKLA,24020
12
+ excel_codegen/utils.py,sha256=ScWxUyeJDS1hpbFbWDsLiEvtS8wQHSGDeoKxndjQ9to,8080
13
+ excel_codegen/examples/README.md,sha256=DP8zQIgBLRK20nFU9rw42D42J5E2AwEsy1bYq5QVpR0,3453
14
+ excel_codegen/examples/abs_fpi/ABS_FPI_load_cases.xlsx,sha256=YQhWdKa2TRrB_qJNtAMHnzYUZC9DRR3r9qcNl4TmHrI,54205
15
+ excel_codegen/examples/abs_fpi/ABS_FPI_load_cases_render.bat,sha256=gAKYV_dq-Ms-xnp84iYeL2vvZO0tNARx882ZWwn0YU4,884
16
+ excel_codegen/examples/abs_fpi/ABS_FPI_load_cases_render.sh,sha256=C1MZf2zMPumHcsp17ULM8zJLnzqdAKEs0pKsAyKda1w,928
17
+ excel_codegen/examples/abs_fpi/README.md,sha256=l7-dp2O5kQIA03zqJnjOL0TWTc8hjVcNdyybv8AqI-c,5463
18
+ excel_codegen/examples/abs_fpi/abs_fpi.yaml,sha256=68eP6JwkJtp7rwENQJQlU_PsfEuFsW_jU85cvNQ3WCQ,8688
19
+ excel_codegen/examples/abs_fpi/abs_fpi_external.xlsx,sha256=VWbWNHqvlPWWCqiL4kHxt5IsnM86zpiHT5lL167Z0Ek,22237
20
+ excel_codegen/examples/abs_fpi/abs_fpi_external.yaml,sha256=vhj29Z1wkUQVVc-HXgIf8jEjVgZKkKzMKox4xJ2oLPY,5912
21
+ excel_codegen/examples/abs_fpi/abs_fpi_external_render.bat,sha256=IuTXkHxuu4Rx7MXlQUROvsHL8onV1XOjdvUT9NfmhPc,898
22
+ excel_codegen/examples/abs_fpi/abs_fpi_external_render.sh,sha256=zW-6k7V1ZzczebSdu7ljWfbw9P2cva31pCsw_gqJ_Uc,942
23
+ excel_codegen/examples/abs_fpi/abs_fpi_internal.xlsx,sha256=xwb-cHOsqnanCnr7kFlkJJX5p-x_kTNZOYkLRbd8Jc4,41315
24
+ excel_codegen/examples/abs_fpi/abs_fpi_internal.yaml,sha256=WbfVa9_vQYtD0CRYQ_8JJg7eo6yOGNqTgDE-3EWdwB4,8096
25
+ excel_codegen/examples/abs_fpi/abs_fpi_internal_render.bat,sha256=JPSt6vwVitnEsWImSPQaI8vBJkGYZ3TlI7x-dkYEP2o,898
26
+ excel_codegen/examples/abs_fpi/abs_fpi_internal_render.sh,sha256=Qnugc4wpajNBbaWFBlOtx1hW9rNKTqduooRPv7tvUSM,942
27
+ excel_codegen/examples/abs_fpi/compose.yaml,sha256=I04Dm-8GpgIuckC9Ki0Cy3cJLuvNadeIWZVxAMohA-c,927
28
+ excel_codegen/examples/abs_fpi/generated/COT1-d20.559-mu60.js,sha256=j8Mdo3egVJ1ZXzSjNitunzgtfyG6-fKUh3xd69t9u78,5636
29
+ excel_codegen/examples/abs_fpi/generated/COT1-d20.559-mu60_summary.md,sha256=r5mrM7cnWhiDKy6XDCOai8Bi4W1yb2ovV4TGZ2dZhzM,1316
30
+ excel_codegen/examples/abs_fpi/generated/EXT-T15-mu0-kf-1.js,sha256=owLxeTfC1vaKhJ0ya41PERADxhYHd_JZC9zzY3QGMX0,4571
31
+ excel_codegen/examples/abs_fpi/generated/EXT-T15-mu0-kf-1_summary.md,sha256=8zdrC_MeBX5JX7oseK9hdTz0QhCB2T4MhfpSU9v-WdM,750
32
+ excel_codegen/examples/abs_fpi/generated/EXT-T15-mu90-kf+1.js,sha256=ajtwP3uDpD2rM2A6r0YKKl4nzE46bnf1o_YRlm58kI0,4570
33
+ excel_codegen/examples/abs_fpi/generated/EXT-T15-mu90-kf+1_summary.md,sha256=YEloqa3ij9eZeU8S2KUCGLb56IGQM9MYcSIRJvdDa9g,749
34
+ excel_codegen/examples/abs_fpi/generated/EXT-T20.559-mu90-kf+1.js,sha256=VhWahdD0xjkX_bwgAaC0rczc_ZDdokAhYlqWnxJ_i4o,4578
35
+ excel_codegen/examples/abs_fpi/generated/EXT-T20.559-mu90-kf+1_summary.md,sha256=rZr1VOh3JqtcQQc4s6ks8HBIs3bftzR3xEg2UvsF9cQ,757
36
+ excel_codegen/examples/abs_fpi/generated/WBT6-d15.059-mu0.js,sha256=2Gw2I38l6yqZXBum3g7RO0OIhcqYlBaaUN3eZKeLZYk,5632
37
+ excel_codegen/examples/abs_fpi/generated/WBT6-d15.059-mu0_summary.md,sha256=740QR56BLmgBJ04BLNTpCysKOozUV7w_TU6cFOoyF7g,1312
38
+ excel_codegen/examples/abs_fpi/generated/WBT6-d8-mu90.js,sha256=mhUWfiAhJHM2yIeCSQEnYizqsomyw8uIHSIigpIfGlA,5622
39
+ excel_codegen/examples/abs_fpi/generated/WBT6-d8-mu90_summary.md,sha256=WO1zqVEAXafcypRsWedu-WcLChtPAuzvSTDe6GJU54Q,1302
40
+ excel_codegen/examples/abs_fpi/generated/WBT7-d20.559-mu90.js,sha256=mizW_vzYeefO8rDy9SlGMB6jisB0DxvJqrJNNwTTViM,5639
41
+ excel_codegen/examples/abs_fpi/generated/WBT7-d20.559-mu90_summary.md,sha256=7Og_tnWEdnBA_sWgSK0VAdFa86ZVCRfZvR-KB1NYaQM,1319
42
+ excel_codegen/examples/abs_fpi/templates/ext_body.js.j2,sha256=TUFhVTadchJucA7mE6HvUy72kl-D8Qn450LFS8Ay-Ew,4721
43
+ excel_codegen/examples/abs_fpi/templates/ext_summary.md.j2,sha256=q-Opg5XOJZjUJ29YRr_riZP3BN0vCO1VKmzBojXd95o,907
44
+ excel_codegen/examples/abs_fpi/templates/int_body.js.j2,sha256=ZflxYX4CBouvCps28sTFzadzp2GcJhvfXz76OiWMxqM,5969
45
+ excel_codegen/examples/abs_fpi/templates/int_summary.md.j2,sha256=7xOEc7Hg67XKULBnG0vIkvdxx_gjFqu6EBl_Mu_UQtM,1656
46
+ excel_codegen/examples/basic/example.yaml,sha256=JiFh_Y24MIxro5nv0o_1Uqv3n_kyouGFEdAXa5Xb-jI,3785
47
+ excel_codegen/examples/basic/example_formula.yaml,sha256=PkiiSRM6s-It0Cs5MyHNNhzCsCskgSz2gGXqmA-FoVM,3229
48
+ excel_codegen/examples/basic/template.xlsx,sha256=YXRTECIH1RZR7SONkP8-kRBYXrrZZtGyNHap6qZudVg,14810
49
+ excel_codegen/examples/basic/template_formula.xlsx,sha256=r-97yumpVjjxuR1thUUmhGcIloL-OQ9IBhC0PoUzz1w,14690
50
+ excel_codegen/examples/basic/template_formula_render.bat,sha256=-WUpyJUVqwUdzGG6qfRij6N75wSgZAmtZoxbGxqdwVM,896
51
+ excel_codegen/examples/basic/template_formula_render.sh,sha256=AurZVoyGj5n-rAmvW5fm9Tm1c55-NrEd5Igfn-V0o3A,940
52
+ excel_codegen/examples/basic/template_render.bat,sha256=tcESzvErD0m0qkRFfCp4K3pvDDQ1HzOwqADVQXSG1t0,864
53
+ excel_codegen/examples/basic/template_render.sh,sha256=xrCXfE6C0BTkgco3f62AVanQdISxiK4YSDVEbRWiS1c,908
54
+ excel_codegen/examples/basic/generated/uart_init_Case1.c,sha256=hUeaXM3dGbCxXl3GP-PaaVttFDFWuLtkQdoI9JfsPSE,159
55
+ excel_codegen/examples/basic/generated/uart_init_Case2.c,sha256=kQVjrYUBf2Yr0YdK96v__go3S2EpgeR7JfdxYnf6aDs,159
56
+ excel_codegen/examples/basic/generated/uart_summary_Case1.md,sha256=ks63sKVv7bK5CR6O4r-skrwceOo7jQmO_0KfCbSX0wM,177
57
+ excel_codegen/examples/basic/generated/uart_summary_Case2.md,sha256=mZCMHshV4LvqEpfH9tQ7i7iGKxdE50OiR6_SYyHPZDM,177
58
+ excel_codegen/examples/basic/generated_formula/uart_init_Case1.c,sha256=apiKWAtcuKQfBf7RQ3SiT-BaiCDIZo4lE7uozRzH-g4,145
59
+ excel_codegen/examples/basic/generated_formula/uart_init_Case2.c,sha256=wDL5bjDITY7JXWj61y8S44fjTF2pJCV1oSjWtbUn93E,145
60
+ excel_codegen/examples/basic/generated_formula/uart_pins_Case1.csv,sha256=Bv3GJCmBIbPXR2UOZkIQOepoPPWLeAGMjPgUriXtmU4,35
61
+ excel_codegen/examples/basic/generated_formula/uart_pins_Case2.csv,sha256=vgETvXepSsnfgh8htd0aAoiAHz-cfwCQ1IzKgtN-tR4,35
62
+ excel_codegen/examples/nastran/nastran_case_control.xlsx,sha256=pGwLST0fyOlh5y2s9Y5h_0jHv5oVgFH3Cs8j40PvUWY,17134
63
+ excel_codegen/examples/nastran/nastran_case_control.yaml,sha256=YtwKh5aR6fixG2wFztgq4B08h7NaisbbSF4NguJJADc,7015
64
+ excel_codegen/examples/nastran/nastran_case_control_render.bat,sha256=xQwLcrcXCpC-L3GHVj2t3z0wZGFqDPjGYx8M9xHWYgc,914
65
+ excel_codegen/examples/nastran/nastran_case_control_render.sh,sha256=Qr6FMyrRwBMEWYvCu9Ex1xjBfyl4PbrW6ZLH4q24ye0,958
66
+ excel_codegen/examples/nastran/generated/case_control.deck,sha256=5i-kWh1Ye6opQup7Fo4HLJFWw16cPGqCO4l0AjQ_Lsg,255
67
+ excel_codegen/examples/nastran/generated/cc_01_LC1.inc,sha256=fExsGNxRjZh__ZegmjjwucyRNWcreQ9iz06GXitmX5Q,54
68
+ excel_codegen/examples/nastran/generated/cc_02_LC2.inc,sha256=MrSPlzw9n4h0ZDQuk_Pg_e4b-cl0cNKFd3H3bIoJZo8,54
69
+ excel_codegen/examples/nastran/generated/cc_03_LC3.inc,sha256=7wawuupAaM6XbaTsDn8ljDH3Icq7OqnkeDP_09hnRM0,81
70
+ excel_codegen/examples/nastran/generated/cc_04_COMB4.inc,sha256=I1CEdN3M4SckduAqYG_2BGQEDgjNPEw1T6Y1352LAFI,71
71
+ excel_codegen/examples/nastran/generated/cc_05_LC5.inc,sha256=x1rawywXR5Ze5wUqN4PNIx8-vu1LgJ0HtSZiC9jaaJg,17
72
+ excel_codegen/examples/nastran/generated/cc_summary_01_LC1.md,sha256=JJ_NQmEZxhmZaHsw7tQ1cY2y7fOdMU7-LJYYRHR8TiU,80
73
+ excel_codegen/examples/nastran/generated/cc_summary_02_LC2.md,sha256=9dz9aM7ObjoF077kKu8WvT0EwOdqVGyBIbOQyV2v3j4,80
74
+ excel_codegen/examples/nastran/generated/cc_summary_03_LC3.md,sha256=766jYdv75yNIXA_fg2X7DAGPnjKyNKEdyGOMLjbc0ac,87
75
+ excel_codegen/examples/nastran/generated/cc_summary_04_COMB4.md,sha256=7Le1sZCDoZKi7szjiip3qssvi2n5obkeDGfic65hC34,101
76
+ excel_codegen/examples/nastran/generated/cc_summary_05_LC5.md,sha256=FtKOK3hu25wYLxJQUmEfzFEW6ndp0ZqSbF-E75ysIZ4,67
77
+ excel_codegen-0.9.0.dist-info/licenses/LICENSE,sha256=JIh2Ogqen2kAAS_aTprsl5HF2A_FAFzdgfVMvO_8UUE,1064
78
+ excel_codegen-0.9.0.dist-info/METADATA,sha256=UI-5tuKhCL4COI-R6Kr0W9xX_NboxE4onfubk2PFYlA,11288
79
+ excel_codegen-0.9.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
80
+ excel_codegen-0.9.0.dist-info/entry_points.txt,sha256=UPVcauQ7UszGs3rp9FJtCCabBKvLYIb0bHX8lIxwKX8,56
81
+ excel_codegen-0.9.0.dist-info/top_level.txt,sha256=4duE-Wu-wKaAg2bD3tfVsraOuaqxk3zFX3UQO2JaX-c,14
82
+ excel_codegen-0.9.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ excel-codegen = excel_codegen.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JG. Luo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ excel_codegen