x2fromx 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- x2fromx-0.1.0/LICENSE +22 -0
- x2fromx-0.1.0/PKG-INFO +136 -0
- x2fromx-0.1.0/README.md +120 -0
- x2fromx-0.1.0/pyproject.toml +25 -0
- x2fromx-0.1.0/setup.cfg +4 -0
- x2fromx-0.1.0/src/x2fromx/__init__.py +6 -0
- x2fromx-0.1.0/src/x2fromx/builder.py +74 -0
- x2fromx-0.1.0/src/x2fromx/cli.py +47 -0
- x2fromx-0.1.0/src/x2fromx/scanner.py +89 -0
- x2fromx-0.1.0/src/x2fromx.egg-info/PKG-INFO +136 -0
- x2fromx-0.1.0/src/x2fromx.egg-info/SOURCES.txt +13 -0
- x2fromx-0.1.0/src/x2fromx.egg-info/dependency_links.txt +1 -0
- x2fromx-0.1.0/src/x2fromx.egg-info/entry_points.txt +2 -0
- x2fromx-0.1.0/src/x2fromx.egg-info/top_level.txt +1 -0
- x2fromx-0.1.0/tests/test_x2fromx.py +0 -0
x2fromx-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TakoUlzO / Erabytse x2fromx contributors
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
x2fromx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: x2fromx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI & library to convert project directories β text trees. Ideal for AI scaffolding & codebase analysis.
|
|
5
|
+
Author-email: TonNom <ton.email@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/TonUser/x2fromx
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/TonUser/x2fromx/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# π²βοΈπ x2fromx
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/x2fromx/)
|
|
20
|
+
[](https://www.python.org/downloads/)
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
[](https://pepy.tech/project/x2fromx)
|
|
23
|
+
|
|
24
|
+
> **Convert project directories β text trees in one command.**
|
|
25
|
+
> Built for developers & AI enthusiasts who need to scaffold projects from LLM outputs or extract codebase structure for context analysis.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## π Why `x2fromx`?
|
|
30
|
+
- π€ **AI-Ready**: Paste a `tree.txt` into ChatGPT/Claude, get a modified architecture back, and rebuild it instantly.
|
|
31
|
+
- β‘ **Zero Dependencies**: Pure Python standard library. Works everywhere, instantly.
|
|
32
|
+
- π§Ή **Smart Filtering**: Automatically ignores `.git`, `node_modules`, `__pycache__`, binaries, and images.
|
|
33
|
+
- π± **Auto-Seeding**: Creates boilerplate content (`.py`, `.html`, `.js`, etc.) so your IDE doesn't complain.
|
|
34
|
+
- π₯οΈ **CLI & Library**: Use it in your terminal or import it directly into your Python scripts.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## π¦ Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install x2fromx
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
π οΈ CLI Usage
|
|
45
|
+
π Scan a directory β generate a tree file
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
x2fromx scan ./my_existing_project -o structure.txt --print
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
ποΈ Build a project from a tree file
|
|
52
|
+
```
|
|
53
|
+
x2fromx build structure.txt -n my_new_project --overwrite
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Available flags:
|
|
57
|
+
|
|
58
|
+
| Flag | Description |
|
|
59
|
+
| :--- | :--- |
|
|
60
|
+
| scan <dir> | Path to the folder to analyze |
|
|
61
|
+
| build <file> | Path to the .txt tree file |
|
|
62
|
+
| -o, --output | Output filename (default: project_structure.txt) |
|
|
63
|
+
| -n, --name | Root project name for build (default: new_project) |
|
|
64
|
+
| --print | Print the tree in the terminal after saving |
|
|
65
|
+
| --overwrite | Force overwrite if the target folder already exists |
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
π€ AI Workflow (The Killer Feature)
|
|
69
|
+
|
|
70
|
+
1. Extract context: x2fromx scan ./legacy_app -o context.txt
|
|
71
|
+
2. Ask an LLM: "Here is my project structure. Refactor it to add a /tests folder, split routes.py into a router package, and add a Dockerfile. Return the full tree."
|
|
72
|
+
3. Save the response: Paste the LLM's output into refactored.txt
|
|
73
|
+
4. Scaffold instantly: x2fromx build refactored.txt -n app_v2
|
|
74
|
+
5. Start coding: Your IDE opens a ready-to-use structure with placeholders.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
π Python API
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from x2fromx import DirectoryScanner, ProjectBuilder
|
|
81
|
+
|
|
82
|
+
# Scan
|
|
83
|
+
scanner = DirectoryScanner("./src", "tree.txt")
|
|
84
|
+
scanner.save()
|
|
85
|
+
|
|
86
|
+
# Build
|
|
87
|
+
builder = ProjectBuilder("tree.txt", "my_project")
|
|
88
|
+
count, root = builder.build(overwrite=True)
|
|
89
|
+
print(f"Created {count} items in {root}")
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
π Example Output
|
|
94
|
+
```bash
|
|
95
|
+
<!-- TREEVIEW START -->
|
|
96
|
+
my_project/
|
|
97
|
+
β
|
|
98
|
+
βββ π src/
|
|
99
|
+
β βββ π api/
|
|
100
|
+
β β βββ π routes.py # Endpoint API
|
|
101
|
+
β β βββ π schemas.py
|
|
102
|
+
β βββ π main.py # TODO: Implement logic
|
|
103
|
+
βββ π tests/
|
|
104
|
+
β βββ π test_api.py # Tests unitaires
|
|
105
|
+
βββ π README.md # Documentation
|
|
106
|
+
βββ π requirements.txt # DΓ©pendances Python
|
|
107
|
+
```
|
|
108
|
+
<!-- TREEVIEW END -->
|
|
109
|
+
|
|
110
|
+
π€ Contributing
|
|
111
|
+
|
|
112
|
+
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
|
|
113
|
+
1. Fork the repo
|
|
114
|
+
2. Create your feature branch (git checkout -b feature/amazing-feature)
|
|
115
|
+
3. Commit your changes (git commit -m 'Add amazing feature')
|
|
116
|
+
4. Push to the branch (git push origin feature/amazing-feature)
|
|
117
|
+
5. Open a Pull Request
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
π License
|
|
121
|
+
|
|
122
|
+
Distributed under the MIT License. See LICENSE for more information.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## π Support
|
|
127
|
+
|
|
128
|
+
If you use and value this tool, consider supporting its development:
|
|
129
|
+
[](https://github.com/sponsors/takouzlo)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
[](https://python.org)
|
|
133
|
+
[](https://flask.palletsprojects.com)
|
|
134
|
+
[](https://typescriptlang.org)
|
|
135
|
+
[](https://pytorch.org)
|
|
136
|
+
[](https://github.com/erabytse)
|
x2fromx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# π²βοΈπ x2fromx
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/x2fromx/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://pepy.tech/project/x2fromx)
|
|
7
|
+
|
|
8
|
+
> **Convert project directories β text trees in one command.**
|
|
9
|
+
> Built for developers & AI enthusiasts who need to scaffold projects from LLM outputs or extract codebase structure for context analysis.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## π Why `x2fromx`?
|
|
14
|
+
- π€ **AI-Ready**: Paste a `tree.txt` into ChatGPT/Claude, get a modified architecture back, and rebuild it instantly.
|
|
15
|
+
- β‘ **Zero Dependencies**: Pure Python standard library. Works everywhere, instantly.
|
|
16
|
+
- π§Ή **Smart Filtering**: Automatically ignores `.git`, `node_modules`, `__pycache__`, binaries, and images.
|
|
17
|
+
- π± **Auto-Seeding**: Creates boilerplate content (`.py`, `.html`, `.js`, etc.) so your IDE doesn't complain.
|
|
18
|
+
- π₯οΈ **CLI & Library**: Use it in your terminal or import it directly into your Python scripts.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## π¦ Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install x2fromx
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
π οΈ CLI Usage
|
|
29
|
+
π Scan a directory β generate a tree file
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
x2fromx scan ./my_existing_project -o structure.txt --print
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
ποΈ Build a project from a tree file
|
|
36
|
+
```
|
|
37
|
+
x2fromx build structure.txt -n my_new_project --overwrite
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Available flags:
|
|
41
|
+
|
|
42
|
+
| Flag | Description |
|
|
43
|
+
| :--- | :--- |
|
|
44
|
+
| scan <dir> | Path to the folder to analyze |
|
|
45
|
+
| build <file> | Path to the .txt tree file |
|
|
46
|
+
| -o, --output | Output filename (default: project_structure.txt) |
|
|
47
|
+
| -n, --name | Root project name for build (default: new_project) |
|
|
48
|
+
| --print | Print the tree in the terminal after saving |
|
|
49
|
+
| --overwrite | Force overwrite if the target folder already exists |
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
π€ AI Workflow (The Killer Feature)
|
|
53
|
+
|
|
54
|
+
1. Extract context: x2fromx scan ./legacy_app -o context.txt
|
|
55
|
+
2. Ask an LLM: "Here is my project structure. Refactor it to add a /tests folder, split routes.py into a router package, and add a Dockerfile. Return the full tree."
|
|
56
|
+
3. Save the response: Paste the LLM's output into refactored.txt
|
|
57
|
+
4. Scaffold instantly: x2fromx build refactored.txt -n app_v2
|
|
58
|
+
5. Start coding: Your IDE opens a ready-to-use structure with placeholders.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
π Python API
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from x2fromx import DirectoryScanner, ProjectBuilder
|
|
65
|
+
|
|
66
|
+
# Scan
|
|
67
|
+
scanner = DirectoryScanner("./src", "tree.txt")
|
|
68
|
+
scanner.save()
|
|
69
|
+
|
|
70
|
+
# Build
|
|
71
|
+
builder = ProjectBuilder("tree.txt", "my_project")
|
|
72
|
+
count, root = builder.build(overwrite=True)
|
|
73
|
+
print(f"Created {count} items in {root}")
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
π Example Output
|
|
78
|
+
```bash
|
|
79
|
+
<!-- TREEVIEW START -->
|
|
80
|
+
my_project/
|
|
81
|
+
β
|
|
82
|
+
βββ π src/
|
|
83
|
+
β βββ π api/
|
|
84
|
+
β β βββ π routes.py # Endpoint API
|
|
85
|
+
β β βββ π schemas.py
|
|
86
|
+
β βββ π main.py # TODO: Implement logic
|
|
87
|
+
βββ π tests/
|
|
88
|
+
β βββ π test_api.py # Tests unitaires
|
|
89
|
+
βββ π README.md # Documentation
|
|
90
|
+
βββ π requirements.txt # DΓ©pendances Python
|
|
91
|
+
```
|
|
92
|
+
<!-- TREEVIEW END -->
|
|
93
|
+
|
|
94
|
+
π€ Contributing
|
|
95
|
+
|
|
96
|
+
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
|
|
97
|
+
1. Fork the repo
|
|
98
|
+
2. Create your feature branch (git checkout -b feature/amazing-feature)
|
|
99
|
+
3. Commit your changes (git commit -m 'Add amazing feature')
|
|
100
|
+
4. Push to the branch (git push origin feature/amazing-feature)
|
|
101
|
+
5. Open a Pull Request
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
π License
|
|
105
|
+
|
|
106
|
+
Distributed under the MIT License. See LICENSE for more information.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## π Support
|
|
111
|
+
|
|
112
|
+
If you use and value this tool, consider supporting its development:
|
|
113
|
+
[](https://github.com/sponsors/takouzlo)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
[](https://python.org)
|
|
117
|
+
[](https://flask.palletsprojects.com)
|
|
118
|
+
[](https://typescriptlang.org)
|
|
119
|
+
[](https://pytorch.org)
|
|
120
|
+
[](https://github.com/erabytse)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "x2fromx"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI & library to convert project directories β text trees. Ideal for AI scaffolding & codebase analysis."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [{name = "TonNom", email = "ton.email@example.com"}]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
requires-python = ">=3.8"
|
|
18
|
+
dependencies = []
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
x2fromx = "x2fromx.cli:main"
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
"Homepage" = "https://github.com/TonUser/x2fromx"
|
|
25
|
+
"Bug Tracker" = "https://github.com/TonUser/x2fromx/issues"
|
x2fromx-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
class ProjectBuilder:
|
|
7
|
+
def __init__(self, structure_file: str, root_name: str = None):
|
|
8
|
+
self.structure_file = Path(structure_file)
|
|
9
|
+
self.root_name = root_name
|
|
10
|
+
if not self.structure_file.exists():
|
|
11
|
+
raise FileNotFoundError(f"β Le fichier '{structure_file}' est introuvable.")
|
|
12
|
+
|
|
13
|
+
def parse_structure(self) -> list[tuple[str, bool]]:
|
|
14
|
+
paths = []
|
|
15
|
+
with open(self.structure_file, 'r', encoding='utf-8') as f:
|
|
16
|
+
lines = f.readlines()
|
|
17
|
+
path_stack = []
|
|
18
|
+
for line in lines:
|
|
19
|
+
raw_line = line.rstrip('\n')
|
|
20
|
+
if not raw_line.strip(): continue
|
|
21
|
+
clean_for_indent = re.sub(r'[ββββ]', ' ', raw_line)
|
|
22
|
+
leading_spaces = len(clean_for_indent) - len(clean_for_indent.lstrip())
|
|
23
|
+
depth = leading_spaces // 4
|
|
24
|
+
cleaned = re.sub(r'.*?[ββ][β]+\s*', '', raw_line)
|
|
25
|
+
cleaned = re.sub(r'^[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF\U0001F700-\U0001F77F\U0001F780-\U0001F7FF\U0001F800-\U0001F8FF\U0001F900-\U0001F9FF\U0001FA00-\U0001FA6F\U0001FA70-\U0001FAFF\U0000FE00-\U0000FEFF]+', '', cleaned)
|
|
26
|
+
cleaned = cleaned.split('#')[0].strip()
|
|
27
|
+
if not cleaned: continue
|
|
28
|
+
is_dir = cleaned.endswith('/')
|
|
29
|
+
item_name = cleaned[:-1] if is_dir else cleaned
|
|
30
|
+
while path_stack and path_stack[-1][0] >= depth:
|
|
31
|
+
path_stack.pop()
|
|
32
|
+
current_parts = [p[1] for p in path_stack] + [item_name]
|
|
33
|
+
relative_path = os.path.join(*current_parts)
|
|
34
|
+
paths.append((relative_path, is_dir))
|
|
35
|
+
if is_dir:
|
|
36
|
+
path_stack.append((depth, item_name))
|
|
37
|
+
return paths
|
|
38
|
+
|
|
39
|
+
def build(self, overwrite: bool = False) -> tuple[int, Path]:
|
|
40
|
+
project_root = Path(self.root_name) if self.root_name else Path("new_project")
|
|
41
|
+
if project_root.exists():
|
|
42
|
+
if overwrite:
|
|
43
|
+
shutil.rmtree(project_root)
|
|
44
|
+
else:
|
|
45
|
+
raise FileExistsError(f"β οΈ Le dossier '{project_root}' existe dΓ©jΓ . Ajoute `--overwrite` pour forcer.")
|
|
46
|
+
project_root.mkdir(parents=True)
|
|
47
|
+
created_count = 0
|
|
48
|
+
paths = self.parse_structure()
|
|
49
|
+
for rel_path, is_dir in paths:
|
|
50
|
+
full_path = project_root / rel_path
|
|
51
|
+
if is_dir:
|
|
52
|
+
full_path.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
(full_path / ".gitkeep").touch(exist_ok=True)
|
|
54
|
+
created_count += 1
|
|
55
|
+
else:
|
|
56
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
if not full_path.exists():
|
|
58
|
+
full_path.touch()
|
|
59
|
+
self._seed_content(full_path)
|
|
60
|
+
created_count += 1
|
|
61
|
+
return created_count, project_root
|
|
62
|
+
|
|
63
|
+
def _seed_content(self, file_path: Path):
|
|
64
|
+
ext = file_path.suffix.lower()
|
|
65
|
+
content = ""
|
|
66
|
+
if ext == '.py': content = "# TODO: Implement logic\n\ndef main():\n pass\n\nif __name__ == '__main__':\n main()\n"
|
|
67
|
+
elif ext == '.html': content = f"<!DOCTYPE html>\n<html>\n<head><title>{file_path.name}</title></head>\n<body></body>\n</html>"
|
|
68
|
+
elif ext == '.md': content = f"# {file_path.stem}\n\nDocumentation to be written.\n"
|
|
69
|
+
elif ext == '.js': content = f"// TODO: Implement JS logic for {file_path.name}\n"
|
|
70
|
+
elif ext == '.css': content = f"/* Styles for {file_path.name} */\n"
|
|
71
|
+
elif ext in ['.conf', '.sh', '.service']: content = f"# Configuration placeholder for {file_path.name}\n"
|
|
72
|
+
if content:
|
|
73
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
74
|
+
f.write(content)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from .scanner import DirectoryScanner
|
|
4
|
+
from .builder import ProjectBuilder
|
|
5
|
+
|
|
6
|
+
def cmd_scan(args):
|
|
7
|
+
try:
|
|
8
|
+
scanner = DirectoryScanner(args.directory, args.output)
|
|
9
|
+
_, count = scanner.save()
|
|
10
|
+
print(f"β
{count} Γ©lΓ©ments trouvΓ©s. Arborescence sauvegardΓ©e dans '{args.output}'.")
|
|
11
|
+
if args.print_tree:
|
|
12
|
+
with open(args.output, 'r', encoding='utf-8') as f:
|
|
13
|
+
print("\n" + f.read())
|
|
14
|
+
except Exception as e:
|
|
15
|
+
print(f"β Erreur: {e}", file=sys.stderr)
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
def cmd_build(args):
|
|
19
|
+
try:
|
|
20
|
+
builder = ProjectBuilder(args.structure_file, args.project_name)
|
|
21
|
+
count, root = builder.build(overwrite=args.overwrite)
|
|
22
|
+
print(f"β
{count} éléments créés dans '{root}'.")
|
|
23
|
+
except Exception as e:
|
|
24
|
+
print(f"β Erreur: {e}", file=sys.stderr)
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
def main():
|
|
28
|
+
parser = argparse.ArgumentParser(prog="x2fromx", description="Outils pour convertir des arborescences de projets en texte et vice-versa.")
|
|
29
|
+
subparsers = parser.add_subparsers(dest="command", help="Commandes disponibles")
|
|
30
|
+
|
|
31
|
+
p_scan = subparsers.add_parser("scan", help="Scan un dossier et génère un fichier texte arborescent.")
|
|
32
|
+
p_scan.add_argument("directory", help="Chemin vers le dossier Γ scanner.")
|
|
33
|
+
p_scan.add_argument("-o", "--output", default="project_structure.txt", help="Fichier de sortie (dΓ©faut: project_structure.txt)")
|
|
34
|
+
p_scan.add_argument("--print", dest="print_tree", action="store_true", help="Affiche l'arbre dans la console.")
|
|
35
|
+
|
|
36
|
+
p_build = subparsers.add_parser("build", help="CrΓ©e une arborescence Γ partir d'un fichier texte.")
|
|
37
|
+
p_build.add_argument("structure_file", help="Fichier texte contenant l'arborescence.")
|
|
38
|
+
p_build.add_argument("-n", "--name", dest="project_name", default=None, help="Nom du dossier racine (dΓ©faut: new_project)")
|
|
39
|
+
p_build.add_argument("--overwrite", action="store_true", help="Γcrase le dossier s'il existe dΓ©jΓ .")
|
|
40
|
+
|
|
41
|
+
args = parser.parse_args()
|
|
42
|
+
if args.command == "scan": cmd_scan(args)
|
|
43
|
+
elif args.command == "build": cmd_build(args)
|
|
44
|
+
else: parser.print_help(); sys.exit(1)
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
class DirectoryScanner:
|
|
6
|
+
def __init__(self, root_path: str, output_file: str = "project_structure.txt"):
|
|
7
|
+
self.root_path = Path(root_path).resolve()
|
|
8
|
+
self.output_file = output_file
|
|
9
|
+
self.ignore_dirs = {
|
|
10
|
+
'.git', '__pycache__', 'node_modules', '.venv', 'venv',
|
|
11
|
+
'env', '.idea', '.vscode', 'build', 'dist', '.pytest_cache',
|
|
12
|
+
'migrations', 'static_collected'
|
|
13
|
+
}
|
|
14
|
+
self.ignore_extensions = {
|
|
15
|
+
'.pyc', '.pyo', '.so', '.dll', '.exe', '.log', '.db', '.sqlite',
|
|
16
|
+
'.jpg', '.jpeg', '.png', '.gif', '.ico', '.woff', '.woff2', '.ttf',
|
|
17
|
+
'.DS_Store', '.env'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def scan(self) -> list:
|
|
21
|
+
structure = []
|
|
22
|
+
if not self.root_path.exists():
|
|
23
|
+
raise FileNotFoundError(f"β Le dossier '{self.root_path}' n'existe pas.")
|
|
24
|
+
|
|
25
|
+
for dirpath, dirnames, filenames in os.walk(self.root_path):
|
|
26
|
+
dirnames[:] = [d for d in dirnames if d not in self.ignore_dirs and not d.startswith('.')]
|
|
27
|
+
dirnames.sort()
|
|
28
|
+
current_path = Path(dirpath)
|
|
29
|
+
relative_depth = len(current_path.relative_to(self.root_path).parts)
|
|
30
|
+
|
|
31
|
+
if current_path != self.root_path:
|
|
32
|
+
structure.append({'type': 'dir', 'name': current_path.name, 'depth': relative_depth, 'path': current_path})
|
|
33
|
+
|
|
34
|
+
valid_files = [f for f in filenames if not f.startswith('.') and Path(f).suffix.lower() not in self.ignore_extensions]
|
|
35
|
+
valid_files.sort()
|
|
36
|
+
for filename in valid_files:
|
|
37
|
+
file_path = current_path / filename
|
|
38
|
+
structure.append({'type': 'file', 'name': filename, 'depth': relative_depth + 1, 'path': file_path, 'ext': Path(filename).suffix.lower()})
|
|
39
|
+
return structure
|
|
40
|
+
|
|
41
|
+
def generate_tree_text(self, structure: list) -> str:
|
|
42
|
+
if not structure: return ""
|
|
43
|
+
lines = [f"{self.root_path.name}/", "β"]
|
|
44
|
+
is_last_map = {}
|
|
45
|
+
for i, item in enumerate(structure):
|
|
46
|
+
next_item = structure[i+1] if i+1 < len(structure) else None
|
|
47
|
+
is_last_map[i] = next_item['depth'] <= item['depth'] if next_item else True
|
|
48
|
+
|
|
49
|
+
for i, item in enumerate(structure):
|
|
50
|
+
depth, is_last = item['depth'], is_last_map[i]
|
|
51
|
+
is_dir = item['type'] == 'dir'
|
|
52
|
+
prefix = ""
|
|
53
|
+
for d in range(1, depth):
|
|
54
|
+
parent_is_last = self._check_parent_status(structure, i, d)
|
|
55
|
+
prefix += " " if parent_is_last else "β "
|
|
56
|
+
branch = "βββ " if is_last else "βββ "
|
|
57
|
+
icon = "π " if is_dir else "π "
|
|
58
|
+
name = f"{item['name']}/" if is_dir else item['name']
|
|
59
|
+
comment = ""
|
|
60
|
+
if not is_dir:
|
|
61
|
+
ext = item.get('ext', '')
|
|
62
|
+
if name == "requirements.txt": comment = " # DΓ©pendances Python"
|
|
63
|
+
elif name == "wsgi.py": comment = " # Point d'entrΓ©e Apache"
|
|
64
|
+
elif name == "manager.py": comment = " # CΕur logique"
|
|
65
|
+
elif name == "routes.py": comment = " # Routes Web"
|
|
66
|
+
elif ext == ".md": comment = " # Documentation"
|
|
67
|
+
elif ext == ".html": comment = " # Template UI"
|
|
68
|
+
elif ext == ".js": comment = " # Logique Frontend"
|
|
69
|
+
elif ext == ".css": comment = " # Styles"
|
|
70
|
+
elif ext == ".py":
|
|
71
|
+
if "test" in item['path'].parts: comment = " # Tests unitaires"
|
|
72
|
+
elif "api" in item['path'].parts: comment = " # Endpoint API"
|
|
73
|
+
lines.append(f"{prefix}{branch}{icon}{name}{comment}")
|
|
74
|
+
return "\n".join(lines)
|
|
75
|
+
|
|
76
|
+
def _check_parent_status(self, structure, current_index, target_depth):
|
|
77
|
+
for i in range(current_index - 1, -1, -1):
|
|
78
|
+
if structure[i]['depth'] == target_depth:
|
|
79
|
+
next_item = structure[i+1] if i+1 < len(structure) else None
|
|
80
|
+
return next_item['depth'] <= target_depth if next_item else True
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def save(self) -> tuple[str, int]:
|
|
84
|
+
structure = self.scan()
|
|
85
|
+
tree_content = self.generate_tree_text(structure)
|
|
86
|
+
full_content = f"# Structure gΓ©nΓ©rΓ©e automatiquement pour {self.root_path.name}\n# Date: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n{tree_content}\n"
|
|
87
|
+
with open(self.output_file, 'w', encoding='utf-8') as f:
|
|
88
|
+
f.write(full_content)
|
|
89
|
+
return full_content, len(structure)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: x2fromx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI & library to convert project directories β text trees. Ideal for AI scaffolding & codebase analysis.
|
|
5
|
+
Author-email: TonNom <ton.email@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/TonUser/x2fromx
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/TonUser/x2fromx/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# π²βοΈπ x2fromx
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/x2fromx/)
|
|
20
|
+
[](https://www.python.org/downloads/)
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
[](https://pepy.tech/project/x2fromx)
|
|
23
|
+
|
|
24
|
+
> **Convert project directories β text trees in one command.**
|
|
25
|
+
> Built for developers & AI enthusiasts who need to scaffold projects from LLM outputs or extract codebase structure for context analysis.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## π Why `x2fromx`?
|
|
30
|
+
- π€ **AI-Ready**: Paste a `tree.txt` into ChatGPT/Claude, get a modified architecture back, and rebuild it instantly.
|
|
31
|
+
- β‘ **Zero Dependencies**: Pure Python standard library. Works everywhere, instantly.
|
|
32
|
+
- π§Ή **Smart Filtering**: Automatically ignores `.git`, `node_modules`, `__pycache__`, binaries, and images.
|
|
33
|
+
- π± **Auto-Seeding**: Creates boilerplate content (`.py`, `.html`, `.js`, etc.) so your IDE doesn't complain.
|
|
34
|
+
- π₯οΈ **CLI & Library**: Use it in your terminal or import it directly into your Python scripts.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## π¦ Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install x2fromx
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
π οΈ CLI Usage
|
|
45
|
+
π Scan a directory β generate a tree file
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
x2fromx scan ./my_existing_project -o structure.txt --print
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
ποΈ Build a project from a tree file
|
|
52
|
+
```
|
|
53
|
+
x2fromx build structure.txt -n my_new_project --overwrite
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Available flags:
|
|
57
|
+
|
|
58
|
+
| Flag | Description |
|
|
59
|
+
| :--- | :--- |
|
|
60
|
+
| scan <dir> | Path to the folder to analyze |
|
|
61
|
+
| build <file> | Path to the .txt tree file |
|
|
62
|
+
| -o, --output | Output filename (default: project_structure.txt) |
|
|
63
|
+
| -n, --name | Root project name for build (default: new_project) |
|
|
64
|
+
| --print | Print the tree in the terminal after saving |
|
|
65
|
+
| --overwrite | Force overwrite if the target folder already exists |
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
π€ AI Workflow (The Killer Feature)
|
|
69
|
+
|
|
70
|
+
1. Extract context: x2fromx scan ./legacy_app -o context.txt
|
|
71
|
+
2. Ask an LLM: "Here is my project structure. Refactor it to add a /tests folder, split routes.py into a router package, and add a Dockerfile. Return the full tree."
|
|
72
|
+
3. Save the response: Paste the LLM's output into refactored.txt
|
|
73
|
+
4. Scaffold instantly: x2fromx build refactored.txt -n app_v2
|
|
74
|
+
5. Start coding: Your IDE opens a ready-to-use structure with placeholders.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
π Python API
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from x2fromx import DirectoryScanner, ProjectBuilder
|
|
81
|
+
|
|
82
|
+
# Scan
|
|
83
|
+
scanner = DirectoryScanner("./src", "tree.txt")
|
|
84
|
+
scanner.save()
|
|
85
|
+
|
|
86
|
+
# Build
|
|
87
|
+
builder = ProjectBuilder("tree.txt", "my_project")
|
|
88
|
+
count, root = builder.build(overwrite=True)
|
|
89
|
+
print(f"Created {count} items in {root}")
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
π Example Output
|
|
94
|
+
```bash
|
|
95
|
+
<!-- TREEVIEW START -->
|
|
96
|
+
my_project/
|
|
97
|
+
β
|
|
98
|
+
βββ π src/
|
|
99
|
+
β βββ π api/
|
|
100
|
+
β β βββ π routes.py # Endpoint API
|
|
101
|
+
β β βββ π schemas.py
|
|
102
|
+
β βββ π main.py # TODO: Implement logic
|
|
103
|
+
βββ π tests/
|
|
104
|
+
β βββ π test_api.py # Tests unitaires
|
|
105
|
+
βββ π README.md # Documentation
|
|
106
|
+
βββ π requirements.txt # DΓ©pendances Python
|
|
107
|
+
```
|
|
108
|
+
<!-- TREEVIEW END -->
|
|
109
|
+
|
|
110
|
+
π€ Contributing
|
|
111
|
+
|
|
112
|
+
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
|
|
113
|
+
1. Fork the repo
|
|
114
|
+
2. Create your feature branch (git checkout -b feature/amazing-feature)
|
|
115
|
+
3. Commit your changes (git commit -m 'Add amazing feature')
|
|
116
|
+
4. Push to the branch (git push origin feature/amazing-feature)
|
|
117
|
+
5. Open a Pull Request
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
π License
|
|
121
|
+
|
|
122
|
+
Distributed under the MIT License. See LICENSE for more information.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## π Support
|
|
127
|
+
|
|
128
|
+
If you use and value this tool, consider supporting its development:
|
|
129
|
+
[](https://github.com/sponsors/takouzlo)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
[](https://python.org)
|
|
133
|
+
[](https://flask.palletsprojects.com)
|
|
134
|
+
[](https://typescriptlang.org)
|
|
135
|
+
[](https://pytorch.org)
|
|
136
|
+
[](https://github.com/erabytse)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/x2fromx/__init__.py
|
|
5
|
+
src/x2fromx/builder.py
|
|
6
|
+
src/x2fromx/cli.py
|
|
7
|
+
src/x2fromx/scanner.py
|
|
8
|
+
src/x2fromx.egg-info/PKG-INFO
|
|
9
|
+
src/x2fromx.egg-info/SOURCES.txt
|
|
10
|
+
src/x2fromx.egg-info/dependency_links.txt
|
|
11
|
+
src/x2fromx.egg-info/entry_points.txt
|
|
12
|
+
src/x2fromx.egg-info/top_level.txt
|
|
13
|
+
tests/test_x2fromx.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
x2fromx
|
|
File without changes
|