nova-forge 0.1.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.
nova_forge/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
File without changes
nova_forge/cli.py ADDED
@@ -0,0 +1,30 @@
1
+ import argparse
2
+ from pathlib import Path
3
+
4
+ from .core.scaffolder import run_scaffolder
5
+ from .core.tree_builder import run_tree_builder
6
+
7
+
8
+ def main():
9
+ parser = argparse.ArgumentParser(description="Nova-Forge: 企业级项目锻造炉")
10
+ subparsers = parser.add_subparsers(dest="command", help="可用命令")
11
+
12
+ # 命令 1: 初始化配置 (原来的 nova_forge)
13
+ subparsers.add_parser("init", help="初始化项目配置文件 (pyproject, README, LICENSE 等)")
14
+
15
+ # 命令 2: 根据 txt 生成目录树 (原来的 nova_tree)
16
+ tree_parser = subparsers.add_parser("tree", help="根据 project_structure.txt 生成项目目录")
17
+ tree_parser.add_argument("-f", "--file", default="project_structure.txt", help="指定结构树文件")
18
+
19
+ args = parser.parse_args()
20
+
21
+ if args.command == "init":
22
+ run_scaffolder()
23
+ elif args.command == "tree":
24
+ run_tree_builder(Path(args.file))
25
+ else:
26
+ parser.print_help()
27
+
28
+
29
+ if __name__ == "__main__":
30
+ main()
File without changes
@@ -0,0 +1,91 @@
1
+ # src/nova_forge/core/scaffolder.py
2
+ import datetime
3
+ import os
4
+ from string import Template
5
+
6
+ # ✅ 从 templates.py 导入所有模版
7
+ from .templates import (
8
+ DEFAULT_GITHUB_USER, DEFAULT_REAL_NAME, DEFAULT_DESC_EN, DEFAULT_DESC_ZH, DEFAULT_DEPS,
9
+ PYPROJECT_TMPL, LICENSE_TMPL, README_EN_TMPL, README_ZH_TMPL,
10
+ CHANGELOG_TMPL, INIT_PY_TMPL, WORKFLOW_TMPL, GITIGNORE_TMPL,
11
+ INIT_GIT_TMPL, BUILD_PROD_TMPL, CB
12
+ )
13
+
14
+
15
+ def ask(prompt, default):
16
+ res = input(f"{prompt} [{default}]: ").strip()
17
+ return res if res else default
18
+
19
+
20
+ def run_scaffolder():
21
+ print("🔥 欢迎使用 Nova-Forge 终极项目锻造炉\n")
22
+
23
+ # 1. 收集信息
24
+ project_name = ask("📦 项目名 (PyPI包名, 如 nova-chat)", "my-awesome-project")
25
+ repo_name = ask("📁 GitHub仓库名 (如 Nova-Agent)", project_name)
26
+ github_user = ask("👤 GitHub用户名", DEFAULT_GITHUB_USER)
27
+ real_name = ask("✍️ 真实姓名 (用于 LICENSE)", DEFAULT_REAL_NAME)
28
+ desc_en = ask("🇬🇧 英文描述", DEFAULT_DESC_EN)
29
+ desc_zh = ask("🇨🇳 中文描述", DEFAULT_DESC_ZH)
30
+
31
+ year = datetime.datetime.now().year
32
+ month = datetime.datetime.now().strftime("%m")
33
+ day = datetime.datetime.now().strftime("%d")
34
+ module_name = project_name.replace('-', '_')
35
+
36
+ # 用于注入的全局字典
37
+ context = {
38
+ "PROJECT_NAME": project_name,
39
+ "REPO_NAME": repo_name,
40
+ "GITHUB_USER": github_user,
41
+ "REAL_NAME": real_name,
42
+ "DESC_EN": desc_en,
43
+ "DESC_ZH": desc_zh,
44
+ "YEAR": year,
45
+ "MONTH": month,
46
+ "DAY": day,
47
+ "MODULE_NAME": module_name,
48
+ "DEPS": DEFAULT_DEPS,
49
+ "CB": CB # ✅ 核心:将防截断的反引号传入
50
+ }
51
+
52
+ def write_file(path, tmpl_string, custom_context=None):
53
+ """通用写文件函数"""
54
+ ctx = {**context, **(custom_context or {})}
55
+ content = Template(tmpl_string).safe_substitute(ctx)
56
+ path = Template(path).safe_substitute(ctx)
57
+
58
+ if not path.endswith("/"):
59
+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
60
+ else:
61
+ os.makedirs(path, exist_ok=True)
62
+
63
+ if not path.endswith("/"):
64
+ with open(path, "w", encoding="utf-8") as f:
65
+ f.write(content)
66
+ print(f"✅ 已生成: {path}")
67
+
68
+ # 2. 执行生成
69
+ write_file("pyproject.toml", PYPROJECT_TMPL)
70
+ write_file("LICENSE", LICENSE_TMPL)
71
+ write_file("README.md", README_EN_TMPL)
72
+ write_file("README_zh.md", README_ZH_TMPL)
73
+ write_file("CHANGELOG.md", CHANGELOG_TMPL)
74
+ write_file(".gitignore", GITIGNORE_TMPL)
75
+
76
+ # 骨架文件
77
+ os.makedirs("src", exist_ok=True)
78
+ write_file("src/$MODULE_NAME/__init__.py", INIT_PY_TMPL)
79
+
80
+ # 工具脚本
81
+ write_file("init_git.py", INIT_GIT_TMPL)
82
+ write_file("build_prod.bat", BUILD_PROD_TMPL)
83
+ write_file(".github/workflows/publish.yml", WORKFLOW_TMPL)
84
+
85
+ # 空的 project_structure.txt
86
+ with open("project_structure.txt", "w", encoding="utf-8") as f:
87
+ pass
88
+ print("✅ 已生成: project_structure.txt (空文件)")
89
+
90
+ print("\n🎉 企业级全家桶生成完毕!")
91
+ print("👉 已包含:动态版本号、CHANGELOG、GitHub Actions 自动发版!")
@@ -0,0 +1,332 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Nova-Forge 模版库
4
+ 集中管理所有的文件生成模版,保持 scaffolder.py 的整洁。
5
+ """
6
+
7
+ # ==================== 默认配置 ====================
8
+ DEFAULT_GITHUB_USER = "eagleye-china"
9
+ DEFAULT_REAL_NAME = "单保录"
10
+ DEFAULT_DESC_EN = "A stunning, GPU-accelerated desktop application for PySide6."
11
+ DEFAULT_DESC_ZH = "基于 PySide6 和 QML 打造的炫酷桌面级应用框架。"
12
+ DEFAULT_DEPS = '["PySide6>=6.6", "openai>=1.0", "markdown>=3.5"]'
13
+
14
+ # ✅ 核心防截断变量
15
+ CB = "`" * 3
16
+
17
+ # ==================== pyproject.toml ====================
18
+ PYPROJECT_TMPL = """# ==========================================================
19
+ # 🚀 $PROJECT_NAME 企业级打包配置
20
+ # ==========================================================
21
+ [build-system]
22
+ requires = ["setuptools>=68.2", "wheel"]
23
+ build-backend = "setuptools.build_meta"
24
+
25
+ [project]
26
+ name = "$PROJECT_NAME"
27
+ version = "0.1.0"
28
+ description = "$DESC_EN"
29
+ readme = "README.md"
30
+ requires-python = ">=3.10"
31
+ license = "MIT"
32
+ authors = [{ name = "$GITHUB_USER", email = "zdt168@sina.cn" }]
33
+ dependencies = $DEPS
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/$GITHUB_USER/$REPO_NAME"
37
+ Repository = "https://github.com/$GITHUB_USER/$REPO_NAME"
38
+ Issues = "https://github.com/$GITHUB_USER/$REPO_NAME/issues"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.setuptools.package-data]
44
+ "$MODULE_NAME" = [
45
+ "ui/*.qml",
46
+ "qml/*.qml",
47
+ "resources/*"
48
+ ]
49
+
50
+ [tool.ruff]
51
+ line-length = 100
52
+ target-version = "py310"
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
56
+ ignore = ["N802", "E501"]
57
+
58
+ [tool.mypy]
59
+ python_version = "3.10"
60
+ strict = true
61
+ ignore_missing_imports = true
62
+ """
63
+
64
+ # ==================== LICENSE ====================
65
+ LICENSE_TMPL = """MIT License
66
+
67
+ Copyright (c) $YEAR $REAL_NAME ($GITHUB_USER)
68
+
69
+ Permission is hereby granted, free of charge, to any person obtaining a copy
70
+ of this software and associated documentation files (the "Software"), to deal
71
+ in the Software without restriction, including without limitation the rights
72
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
73
+ copies of the Software, and to permit persons to whom the Software is
74
+ furnished to do so, subject to the following conditions:
75
+
76
+ The above copyright notice and this permission notice shall be included in all
77
+ copies or substantial portions of the Software.
78
+
79
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
80
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
81
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
82
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
83
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
84
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
85
+ SOFTWARE.
86
+ """
87
+
88
+ # ==================== README.md ====================
89
+ README_EN_TMPL = """# $PROJECT_NAME 🌟
90
+
91
+ > **$DESC_EN**
92
+
93
+ **English** | [简体中文](https://github.com/$GITHUB_USER/$REPO_NAME/blob/main/README_zh.md)
94
+
95
+ [![PyPI version](https://img.shields.io/pypi/v/$PROJECT_NAME?color=blue&label=PyPI)](https://pypi.org/project/$PROJECT_NAME/)
96
+ [![Python Version](https://img.shields.io/pypi/pyversions/$PROJECT_NAME)](https://pypi.org/project/$PROJECT_NAME/)
97
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/$GITHUB_USER/$REPO_NAME/blob/main/LICENSE)
98
+ [![GitHub Stars](https://img.shields.io/github/stars/$GITHUB_USER/$REPO_NAME?style=social)](https://github.com/$GITHUB_USER/$REPO_NAME)
99
+
100
+ ## 🤔 Why $PROJECT_NAME?
101
+
102
+ - 🚀 **Blazing Fast**: Built with pure PySide6 and QML.
103
+ - 🎨 **Stunning UI**: Native dark mode, smooth animations.
104
+
105
+ ## 📦 Installation
106
+
107
+ ${CB}bash
108
+ pip install $PROJECT_NAME
109
+ ${CB}
110
+
111
+ ## 🚀 Quick Start
112
+
113
+ ${CB}python
114
+ from $MODULE_NAME import __version__
115
+ print(f"Version: {__version__}")
116
+ ${CB}
117
+
118
+ ## 📜 License
119
+
120
+ MIT License. See [LICENSE](https://github.com/$GITHUB_USER/$REPO_NAME/blob/main/LICENSE).
121
+ """
122
+
123
+ # ==================== README_zh.md ====================
124
+ README_ZH_TMPL = """# $PROJECT_NAME 🌟
125
+
126
+ > **$DESC_ZH**
127
+
128
+ [English](https://github.com/$GITHUB_USER/$REPO_NAME/blob/main/README.md) | **简体中文**
129
+
130
+ [![PyPI version](https://img.shields.io/pypi/v/$PROJECT_NAME?color=blue&label=PyPI)](https://pypi.org/project/$PROJECT_NAME/)
131
+ [![Python Version](https://img.shields.io/pypi/pyversions/$PROJECT_NAME)](https://pypi.org/project/$PROJECT_NAME/)
132
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/$GITHUB_USER/$REPO_NAME/blob/main/LICENSE)
133
+ [![GitHub Stars](https://img.shields.io/github/stars/$GITHUB_USER/$REPO_NAME?style=social)](https://github.com/$GITHUB_USER/$REPO_NAME)
134
+
135
+ ## 🤔 为什么选择 $PROJECT_NAME?
136
+
137
+ - 🚀 **极致性能**:基于原生 PySide6 和 QML,拒绝笨重的 WebView。
138
+ - 🎨 **顶级颜值**:原生暗黑模式,丝滑动画,像素级布局。
139
+
140
+ ## 📦 安装
141
+
142
+ ${CB}bash
143
+ pip install $PROJECT_NAME
144
+ ${CB}
145
+
146
+ ## 📜 开源协议
147
+
148
+ MIT License。
149
+ """
150
+
151
+ # ==================== CHANGELOG.md ====================
152
+ CHANGELOG_TMPL = """# Changelog
153
+ 所有版本变更记录,遵循 [Semantic Versioning](https://semver.org/) 规范。
154
+
155
+ ## [0.1.0] - $YEAR-$MONTH-$DAY
156
+ ### ✨ Features
157
+ - 初始版本发布,搭建完整的企业级工程骨架。
158
+ - 完成 PyPI 和 GitHub 的发版流水线配置。
159
+ """
160
+
161
+ # ==================== __init__.py ====================
162
+ INIT_PY_TMPL = """# -*- coding: utf-8 -*-
163
+ from importlib.metadata import version, PackageNotFoundError
164
+
165
+ try:
166
+ __version__: str = version("$PROJECT_NAME")
167
+ except PackageNotFoundError:
168
+ __version__ = "0.1.0"
169
+
170
+ __all__ = ["__version__"]
171
+ """
172
+
173
+ # ==================== GitHub Actions ====================
174
+ WORKFLOW_TMPL = """name: Publish to PyPI
175
+
176
+ on:
177
+ push:
178
+ tags:
179
+ - 'v*'
180
+
181
+ jobs:
182
+ build-and-publish:
183
+ runs-on: ubuntu-latest
184
+ environment: pypi
185
+ permissions:
186
+ id-token: write
187
+ steps:
188
+ - uses: actions/checkout@v4
189
+ - name: Set up Python
190
+ uses: actions/setup-python@v5
191
+ with:
192
+ python-version: '3.10'
193
+ - name: Build package
194
+ run: |
195
+ python -m pip install --upgrade pip
196
+ pip install build
197
+ python -m build
198
+ - name: Publish to PyPI
199
+ uses: pypa/gh-action-pypi-publish@release/v1
200
+ """
201
+
202
+ # ==================== .gitignore ====================
203
+ GITIGNORE_TMPL = """# Python
204
+ __pycache__/
205
+ *.py[cod]
206
+ build/
207
+ dist/
208
+ *.egg-info/
209
+
210
+ # IDE
211
+ .idea/
212
+ .vscode/
213
+ .DS_Store
214
+
215
+ # Environments
216
+ .env
217
+ .venv
218
+ venv/
219
+ """
220
+
221
+ # ==================== init_git.py ====================
222
+ INIT_GIT_TMPL = """import os
223
+ import re
224
+ import subprocess
225
+ import sys
226
+ from pathlib import Path
227
+
228
+ GITHUB_USER = "$GITHUB_USER"
229
+
230
+ def run(cmd, ignore_error=False):
231
+ print(f"👉 正在执行: {' '.join(cmd)}")
232
+ result = subprocess.run(cmd, capture_output=True, text=True)
233
+ if result.returncode != 0 and not ignore_error:
234
+ print(f"❌ 错误: {result.stderr}")
235
+ raise RuntimeError(result.stderr)
236
+ if result.stdout.strip():
237
+ print(result.stdout.strip())
238
+ return result
239
+
240
+ def get_project_info():
241
+ current_dir = Path.cwd()
242
+ # ✅ 修复:直接使用生成时写死的 REPO_NAME,防止路径错乱
243
+ repo_name = "$REPO_NAME"
244
+ project_name = "$PROJECT_NAME"
245
+ version = "0.1.0"
246
+ toml_path = current_dir / "pyproject.toml"
247
+ if toml_path.exists():
248
+ with open(toml_path, "r", encoding="utf-8") as f:
249
+ content = f.read()
250
+ # ✅ 修复正则转义问题
251
+ name_match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', content)
252
+ if name_match: project_name = name_match.group(1)
253
+
254
+ version_match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
255
+ if version_match: version = version_match.group(1)
256
+ return repo_name, project_name, version
257
+
258
+ def main():
259
+ repo_name, project_name, version = get_project_info()
260
+ remote_url = f"git@github.com:{GITHUB_USER}/{repo_name}.git"
261
+ tag_name = f"v{version}"
262
+
263
+ print(f"🚀 开始自动初始化并发布【{project_name}】 (v{version})...")
264
+
265
+ if not os.path.exists(".git"):
266
+ run(["git", "init"])
267
+ run(["git", "branch", "-M", "main"])
268
+ run(["git", "config", "core.autocrlf", "false"])
269
+ run(["git", "add", "."])
270
+ run(["git", "commit", "-m", f"feat: initial release of {project_name} v{version}"])
271
+ print("✅ 本地 Git 初始化完成!")
272
+ else:
273
+ print("✅ 已检测到 Git 仓库,跳过初始化。")
274
+
275
+ try:
276
+ remotes = subprocess.run(["git", "remote"], capture_output=True, text=True).stdout
277
+ if "origin" not in remotes:
278
+ run(["git", "remote", "add", "origin", remote_url])
279
+ print(f"✅ 已关联远程仓库: {remote_url}")
280
+ else:
281
+ current_url = subprocess.run(["git", "remote", "get-url", "origin"], capture_output=True, text=True).stdout.strip()
282
+ if current_url != remote_url:
283
+ run(["git", "remote", "set-url", "origin", remote_url])
284
+ print(f"✅ 已更新远程仓库地址: {remote_url}")
285
+ else:
286
+ print(f"✅ 远程仓库已配置正确,无需修改。")
287
+ except Exception as e:
288
+ print(f"❌ 配置远程仓库失败: {e}")
289
+
290
+ print("\\n📤 正在推送代码到 GitHub...")
291
+ try:
292
+ run(["git", "push", "-f", "-u", "origin", "main"])
293
+ print("✅ 代码推送成功!")
294
+ except RuntimeError:
295
+ print("❌ 推送失败,请检查 SSH Key 或网络连接!")
296
+ sys.exit(1)
297
+
298
+ print(f"\\n🏷️ 准备发布版本 {tag_name}...")
299
+ try:
300
+ tags = subprocess.run(["git", "tag"], capture_output=True, text=True).stdout
301
+ if tag_name not in tags.split():
302
+ run(["git", "tag", tag_name])
303
+ print(f"✅ 标签 {tag_name} 创建成功。")
304
+ else:
305
+ print(f"✅ 标签 {tag_name} 已存在。")
306
+ run(["git", "push", "origin", tag_name])
307
+ print(f"🚀 标签 {tag_name} 推送成功!GitHub Actions 发版流水线已触发!")
308
+ except Exception as e:
309
+ print(f"❌ 标签推送失败: {e}")
310
+
311
+ print("\\n🎉 全自动发布流程结束!请前往 GitHub 的 Actions 页面查看发版进度。")
312
+
313
+ if __name__ == "__main__":
314
+ main()
315
+ """
316
+
317
+ # ==================== build_prod.bat ====================
318
+ BUILD_PROD_TMPL = """@echo off
319
+ chcp 65001 >nul
320
+ echo =========================================
321
+ echo $PROJECT_NAME 打包与发布
322
+ echo =========================================
323
+
324
+ if exist dist rmdir /s /q dist
325
+ if exist build rmdir /s /q build
326
+ if exist src\\*.egg-info rmdir /s /q src\\*.egg-info
327
+
328
+ python -m build
329
+ python -m twine check dist/*
330
+ python -m twine upload dist/*
331
+ pause
332
+ """
@@ -0,0 +1,248 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Nova Tree 🌲
4
+ ============
5
+ 项目目录结构生成器 (完美防套娃、智能识别版)
6
+ 根据同目录下的 `project_structure.txt`,一键生成完整的工程目录。
7
+ """
8
+ import re
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+
12
+
13
+ # ==========================================
14
+ # 📦 数据模型
15
+ # ==========================================
16
+ @dataclass(slots=True)
17
+ class Node:
18
+ name: str
19
+ is_dir: bool
20
+ level: int = 0
21
+ children: list["Node"] = field(default_factory=list)
22
+
23
+ def add_child(self, child: "Node") -> None:
24
+ self.children.append(child)
25
+
26
+
27
+ @dataclass(slots=True)
28
+ class BuildResult:
29
+ created_dirs: list[Path] = field(default_factory=list)
30
+ created_files: list[Path] = field(default_factory=list)
31
+ skipped: list[Path] = field(default_factory=list)
32
+ failed: list[tuple[Path, str]] = field(default_factory=list)
33
+
34
+ def summary(self) -> str:
35
+ return (
36
+ f"📁 新建目录: {len(self.created_dirs)} | "
37
+ f"📄 新建文件: {len(self.created_files)} | "
38
+ f"⏭️ 跳过已存在: {len(self.skipped)} | "
39
+ f"❌ 失败: {len(self.failed)}"
40
+ )
41
+
42
+
43
+ # ==========================================
44
+ # 🔍 结构解析器
45
+ # ==========================================
46
+ class StructureParser:
47
+ _TREE_MARKERS = ("├── ", "└── ", "├─ ", "└─ ", "|--- ", "|-- ", "└─ ")
48
+ _INDENT_CHARS = set("│ ├└─|")
49
+
50
+ def parse(self, text: str) -> list[Node]:
51
+ roots: list[Node] = []
52
+ stack: list[tuple[int, Node]] = []
53
+
54
+ for raw in text.splitlines():
55
+ parsed = self._parse_line(raw)
56
+ if parsed is None:
57
+ continue
58
+
59
+ level, name, is_dir = parsed
60
+ node = Node(name=name, is_dir=is_dir, level=level)
61
+
62
+ while stack and stack[-1][0] >= level:
63
+ stack.pop()
64
+
65
+ if stack:
66
+ stack[-1][1].add_child(node)
67
+ else:
68
+ roots.append(node)
69
+
70
+ stack.append((level, node))
71
+
72
+ return roots
73
+
74
+ def _parse_line(self, line: str) -> tuple[int, str, bool] | None:
75
+ line = line.rstrip()
76
+ if not line.strip() or all(c in "│ ├└─|" for c in line.strip()):
77
+ return None
78
+ if "|" in line and not any(m in line for m in self._TREE_MARKERS):
79
+ return None
80
+ if line.lstrip().startswith(("```", "---", "===")):
81
+ return None
82
+
83
+ for marker in self._TREE_MARKERS:
84
+ idx = line.find(marker)
85
+ if idx >= 0:
86
+ prefix = line[:idx]
87
+ if all(ch in self._INDENT_CHARS for ch in prefix):
88
+ level = idx // 4 + 1
89
+ name = line[idx + len(marker):]
90
+ clean_name = self._clean_name(name)
91
+ if clean_name:
92
+ return level, clean_name, self._is_dir(name)
93
+
94
+ if line[0].isspace() and not line.lstrip().startswith(("def ", "class ", "import ", "from ", "@")):
95
+ stripped = line.lstrip()
96
+ level = (len(line) - len(stripped)) // 4
97
+ clean_name = self._clean_name(stripped)
98
+ if clean_name:
99
+ return level, clean_name, self._is_dir(stripped)
100
+
101
+ clean_name = self._clean_name(line)
102
+ if clean_name:
103
+ return 0, clean_name, self._is_dir(line)
104
+
105
+ return None
106
+
107
+ @staticmethod
108
+ def _clean_name(name: str) -> str:
109
+ name = name.split("#")[0].strip()
110
+ name = name.rstrip("/\\:").strip()
111
+ name = re.sub(r'[<>:"|?*]', '_', name)
112
+ return name.strip()
113
+
114
+ @staticmethod
115
+ def _is_dir(name: str) -> bool:
116
+ name = name.rstrip()
117
+ if name.endswith(("/", "\\")):
118
+ return True
119
+ last = name.split("/")[-1].split("\\")[-1]
120
+ return "." not in last
121
+
122
+
123
+ # ==========================================
124
+ # 🛠️ 项目构建器
125
+ # ==========================================
126
+ class ProjectBuilder:
127
+ def build(self, roots: list[Node], target: Path):
128
+ result = BuildResult()
129
+ target = Path(target).resolve()
130
+
131
+ current_dir_name = target.name
132
+ nodes_to_walk = []
133
+ real_root = target
134
+
135
+ # ✅ 核心逻辑 1:智能防套娃
136
+ if len(roots) == 1 and roots[0].is_dir:
137
+ if roots[0].name == current_dir_name:
138
+ print(f"🔍 检测到当前目录名与根节点名一致 ('{current_dir_name}'),跳过根目录创建,直接生成子结构。")
139
+ nodes_to_walk = roots[0].children
140
+ else:
141
+ print(f"🔍 根节点名 ('{roots[0].name}') 与当前目录 ('{current_dir_name}') 不一致,将创建新根节点。")
142
+ real_root = target / roots[0].name
143
+ real_root.mkdir(parents=True, exist_ok=True)
144
+ nodes_to_walk = roots[0].children
145
+ else:
146
+ print(f"🔍 检测到多个顶级节点或散落文件,直接在当前目录生成。")
147
+ nodes_to_walk = roots
148
+
149
+ # 1. 递归生成节点
150
+ for node in nodes_to_walk:
151
+ self._create_node(node, real_root, result)
152
+
153
+ return result
154
+
155
+ def _create_node(self, node: Node, parent: Path, result: BuildResult):
156
+ path = parent / node.name
157
+
158
+ if node.is_dir:
159
+ if not path.exists():
160
+ path.mkdir(parents=True, exist_ok=True)
161
+ result.created_dirs.append(path)
162
+ else:
163
+ result.skipped.append(path)
164
+
165
+ # 自动补全 __init__.py (如果是 Python 包且有子节点)
166
+ if node.children and not any(c.name == "__init__.py" for c in node.children):
167
+ init_file = path / "__init__.py"
168
+ self._write_file(init_file, "", result)
169
+
170
+ for child in node.children:
171
+ self._create_node(child, path, result)
172
+ else:
173
+ self._write_file(path, "", result)
174
+
175
+ def _write_file(self, path: Path, content: str, result: BuildResult):
176
+ if path.exists():
177
+ result.skipped.append(path)
178
+ return
179
+
180
+ path.parent.mkdir(parents=True, exist_ok=True)
181
+ if not content:
182
+ content = self._default_content(path)
183
+
184
+ try:
185
+ path.write_text(content, encoding="utf-8")
186
+ result.created_files.append(path)
187
+ except Exception as e:
188
+ result.failed.append((path, str(e)))
189
+
190
+ @staticmethod
191
+ def _default_content(path: Path) -> str:
192
+ name = path.name
193
+ suffix = path.suffix.lower()
194
+
195
+ if name == "__init__.py": return ""
196
+ if suffix == ".py":
197
+ return f'# -*- coding: utf-8 -*-\n# {path.stem}.py\n\n'
198
+ if suffix == ".md":
199
+ return f"# {path.stem}\n\n> 文档生成中...\n"
200
+ if suffix == ".qrc":
201
+ return '<RCC>\n <qresource prefix="/">\n </qresource>\n</RCC>\n'
202
+ if suffix == ".qml":
203
+ return 'import QtQuick\n\nItem {\n width: 100\n height: 100\n}\n'
204
+ return ""
205
+
206
+
207
+ # ==========================================
208
+ # 🚀 主程序入口
209
+ # ==========================================
210
+ # ==========================================
211
+ # 🚀 主程序入口 (改造为可传参函数)
212
+ # ==========================================
213
+ def run_tree_builder(file_path: Path):
214
+ # 确保传入的是 Path 对象
215
+ file_path = Path(file_path)
216
+
217
+ # ✅ 核心逻辑 2:空文件与缺失检测
218
+ if not file_path.exists():
219
+ print(f"❌ 错误: 找不到 [{file_path.name}] 文件!")
220
+ print("💡 请先创建该文件并填入目录树。")
221
+ return
222
+
223
+ content = file_path.read_text(encoding="utf-8").strip()
224
+ if not content:
225
+ print(f"❌ 错误: [{file_path.name}] 文件内容为空!")
226
+ print("💡 请写入类似以下的格式:\n项目名/\n├── src/\n│ └── main.py")
227
+ return
228
+
229
+ print("🔥 启动 Nova Tree 终极结构生成器...\n")
230
+
231
+ parser = StructureParser()
232
+ roots = parser.parse(content)
233
+
234
+ if not roots:
235
+ print("❌ 错误: 未能解析出有效目录,请检查 txt 语法格式。")
236
+ return
237
+
238
+ builder = ProjectBuilder()
239
+ result = builder.build(roots, target=Path.cwd())
240
+
241
+ print("\n" + "=" * 40)
242
+ print("🎉 构建执行完毕!")
243
+ print(result.summary())
244
+ if result.failed:
245
+ print("\n❌ 失败列表:")
246
+ for path, err in result.failed:
247
+ print(f" - {path}: {err}")
248
+ print("=" * 40)
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: nova-forge
3
+ Version: 0.1.0
4
+ Summary: The ultimate project forging tool for PySide6 ecosystems. Scaffold enterprise-grade architectures in seconds.
5
+ Author-email: eagleye-china <zdt168@sina.cn>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/eagleye-china/Nova-Forge
8
+ Project-URL: Repository, https://github.com/eagleye-china/Nova-Forge
9
+ Project-URL: Issues, https://github.com/eagleye-china/Nova-Forge/issues
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Environment :: Console
13
+ Classifier: Topic :: Software Development :: Code Generators
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: build>=1.0.0
18
+ Requires-Dist: twine>=5.0.0
19
+ Dynamic: license-file
20
+
21
+ # Nova-Forge 🛠️
22
+
23
+ > **The ultimate project forging tool for PySide6 ecosystems. Scaffold enterprise-grade architectures in seconds.**
24
+
25
+ **English** | [简体中文](https://github.com/eagleye-china/Nova-Forge/blob/main/README_zh.md)
26
+
27
+ [![PyPI version](https://img.shields.io/pypi/v/nova-forge?color=blue&label=PyPI)](https://pypi.org/project/nova-forge/)
28
+ [![Python Version](https://img.shields.io/pypi/pyversions/nova-forge)](https://pypi.org/project/nova-forge/)
29
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/eagleye-china/Nova-Forge/blob/main/LICENSE)
30
+ [![GitHub Stars](https://img.shields.io/github/stars/eagleye-china/Nova-Forge?style=social)](https://github.com/eagleye-china/Nova-Forge)
31
+
32
+ **Nova-Forge** is a blazing-fast CLI scaffolding tool designed to eliminate the boilerplate of modern Python desktop applications.
33
+
34
+ Stop copying and pasting `pyproject.toml`, fumbling with MIT licenses, or manually creating hundreds of folders. With a single command, Nova-Forge generates a complete, production-ready `src-layout` project equipped with CI/CD, bilingual docs, and PyPI packaging scripts.
35
+
36
+ ## ✨ Features
37
+
38
+ - 🚀 **`nova-forge init`**: Generates `pyproject.toml`, bilingual `README.md`, `LICENSE`, and a GitHub Actions workflow for automatic PyPI publishing.
39
+ - 🌲 **`nova-forge tree`**: Builds an entire directory structure instantly from a simple text-based tree diagram.
40
+ - 🤖 **AI-Ready**: Perfect for pairing with AI coding assistants (like DeepSeek or ChatGPT) to instantly materialize AI-generated architectures.
41
+
42
+ ## 📦 Installation
43
+
44
+ ```bash
45
+ pip install nova-forge
46
+ ```
47
+
48
+ ## 🛠️ Usage
49
+
50
+ ### 1. Initialize Project Configuration
51
+ Run this in your empty project folder. It will interactively ask for your project details and generate all necessary metadata.
52
+
53
+ ```bash
54
+ nova-forge init
55
+ ```
56
+
57
+ ### 2. Build Directory Structure
58
+ Got a project layout from your AI architect? Paste it into `project_structure.txt`:
59
+
60
+ ```text
61
+ my-awesome-app/
62
+ ├── src/
63
+ │ └── my_awesome_app/
64
+ │ ├── __init__.py
65
+ │ └── main.py
66
+ └── examples/
67
+ ```
68
+
69
+ Then run:
70
+
71
+ ```bash
72
+ nova-forge tree
73
+ ```
74
+
75
+ *Boom! Your entire folder structure is created with auto-generated `__init__.py` and boilerplate code.*
76
+
77
+ ## 📜 License
78
+
79
+ MIT License. See [LICENSE](https://github.com/eagleye-china/Nova-Forge/blob/main/LICENSE) for details.
@@ -0,0 +1,13 @@
1
+ nova_forge/__init__.py,sha256=Pru0BlFBASFCFo7McHdohtKkUtgMPDwbGfyUZlE2_Vw,21
2
+ nova_forge/cli.py,sha256=l8zqsEJOyoRGPxbFpB1EEiZGkeMTVuqc_t5-9MnQdso,1022
3
+ nova_forge/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ nova_forge/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ nova_forge/core/scaffolder.py,sha256=MG30jIBuE44Tu4uG_Xn4Eg021lfrFhI6aUckR-kw0a4,3321
6
+ nova_forge/core/templates.py,sha256=H2cLM5qdOAKNkkpHft0UHOnMZeR5QeYG6aLMkrRGDao,11021
7
+ nova_forge/core/tree_builder.py,sha256=Rbk_qICQ6umUTL8LtUOceIOnCx_jYY6v-js_PCOyx8g,8770
8
+ nova_forge-0.1.0.dist-info/licenses/LICENSE,sha256=0YT_10WZMShp4eZlZVRiKfVdpM0g7SFNnZHYuHUnmS0,1103
9
+ nova_forge-0.1.0.dist-info/METADATA,sha256=a5G0nqoCryOSIxsNK-Jyqys60Vio8dbb9R2Zgbnsxn0,3269
10
+ nova_forge-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
11
+ nova_forge-0.1.0.dist-info/entry_points.txt,sha256=q3TXfUxQEqDx_QbMGTvdCdP992QlB1MRQCQw0YLruhA,51
12
+ nova_forge-0.1.0.dist-info/top_level.txt,sha256=4MM7qIlmTnEgQidIQuXb5dHOClBl9b7l9nLLMq5ZgVE,11
13
+ nova_forge-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nova-forge = nova_forge.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 单保录 (eagleye-china)
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
+ nova_forge