boilergen 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boilergen-1.2.2/.github/workflows/publish.yml +28 -0
- boilergen-1.2.2/.gitignore +2 -0
- boilergen-1.2.2/.idea/.gitignore +3 -0
- boilergen-1.2.2/.idea/BoilerGen.iml +13 -0
- boilergen-1.2.2/.idea/inspectionProfiles/Project_Default.xml +59 -0
- boilergen-1.2.2/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- boilergen-1.2.2/.idea/misc.xml +14 -0
- boilergen-1.2.2/.idea/modules.xml +8 -0
- boilergen-1.2.2/.idea/vcs.xml +7 -0
- boilergen-1.2.2/PKG-INFO +6 -0
- boilergen-1.2.2/README.md +187 -0
- boilergen-1.2.2/boilergen/__init__.py +0 -0
- boilergen-1.2.2/boilergen/__main__.py +4 -0
- boilergen-1.2.2/boilergen/builder/__init__.py +0 -0
- boilergen-1.2.2/boilergen/builder/generation_logic.py +32 -0
- boilergen-1.2.2/boilergen/builder/hooks.py +21 -0
- boilergen-1.2.2/boilergen/builder/output_selection.py +60 -0
- boilergen-1.2.2/boilergen/builder/parser/__init__.py +0 -0
- boilergen-1.2.2/boilergen/builder/parser/configs.py +161 -0
- boilergen-1.2.2/boilergen/builder/parser/injections.py +250 -0
- boilergen-1.2.2/boilergen/builder/parser/tags.py +54 -0
- boilergen-1.2.2/boilergen/builder/project_setup.py +251 -0
- boilergen-1.2.2/boilergen/cli/__init__.py +7 -0
- boilergen-1.2.2/boilergen/cli/commands.py +225 -0
- boilergen-1.2.2/boilergen/cli/run_config.py +10 -0
- boilergen-1.2.2/boilergen/core/__init__.py +0 -0
- boilergen-1.2.2/boilergen/core/display.py +273 -0
- boilergen-1.2.2/boilergen/core/navigator.py +333 -0
- boilergen-1.2.2/boilergen/core/template.py +42 -0
- boilergen-1.2.2/boilergen/core/template_finder.py +111 -0
- boilergen-1.2.2/boilergen/templates/gh-Workflows/Realease Version to Github/template/.github/workflows/flutter-release.yml +157 -0
- boilergen-1.2.2/boilergen/templates/gh-Workflows/Realease Version to Github/template.yaml +3 -0
- boilergen-1.2.2/boilergen.config +5 -0
- boilergen-1.2.2/pyproject.toml +13 -0
- boilergen-1.2.2/requirements.txt +9 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Upload Python Package to PyPI when a Release is Created
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi-publish:
|
|
9
|
+
name: Publish release to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment:
|
|
12
|
+
name: pypi
|
|
13
|
+
url: https://pypi.org/p/boilergen
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v4
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.x"
|
|
22
|
+
- name: Install build backend
|
|
23
|
+
run: pip install build
|
|
24
|
+
|
|
25
|
+
- name: Build package
|
|
26
|
+
run: python -m build
|
|
27
|
+
- name: Publish package distributions to PyPI
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="PYTHON_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
6
|
+
</content>
|
|
7
|
+
<orderEntry type="inheritedJdk" />
|
|
8
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
9
|
+
</component>
|
|
10
|
+
<component name="PackageRequirementsSettings">
|
|
11
|
+
<option name="versionSpecifier" value="Strong equality (==x.y.z)" />
|
|
12
|
+
</component>
|
|
13
|
+
</module>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<component name="InspectionProjectProfileManager">
|
|
2
|
+
<profile version="1.0">
|
|
3
|
+
<option name="myName" value="Project Default" />
|
|
4
|
+
<inspection_tool class="PyAsyncCallInspection" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
|
|
5
|
+
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
6
|
+
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
7
|
+
<option name="ourVersions">
|
|
8
|
+
<value>
|
|
9
|
+
<list size="1">
|
|
10
|
+
<item index="0" class="java.lang.String" itemvalue="3.13" />
|
|
11
|
+
</list>
|
|
12
|
+
</value>
|
|
13
|
+
</option>
|
|
14
|
+
</inspection_tool>
|
|
15
|
+
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
16
|
+
<option name="ignoredPackages">
|
|
17
|
+
<value>
|
|
18
|
+
<list size="21">
|
|
19
|
+
<item index="0" class="java.lang.String" itemvalue="craiyon.py" />
|
|
20
|
+
<item index="1" class="java.lang.String" itemvalue="nextcord" />
|
|
21
|
+
<item index="2" class="java.lang.String" itemvalue="revChatGPT" />
|
|
22
|
+
<item index="3" class="java.lang.String" itemvalue="requests" />
|
|
23
|
+
<item index="4" class="java.lang.String" itemvalue="yfinance" />
|
|
24
|
+
<item index="5" class="java.lang.String" itemvalue="duolingo" />
|
|
25
|
+
<item index="6" class="java.lang.String" itemvalue="aiohttp" />
|
|
26
|
+
<item index="7" class="java.lang.String" itemvalue="pyenchant" />
|
|
27
|
+
<item index="8" class="java.lang.String" itemvalue="httpx" />
|
|
28
|
+
<item index="9" class="java.lang.String" itemvalue="googletrans" />
|
|
29
|
+
<item index="10" class="java.lang.String" itemvalue="google_trans_new" />
|
|
30
|
+
<item index="11" class="java.lang.String" itemvalue="openai" />
|
|
31
|
+
<item index="12" class="java.lang.String" itemvalue="fortnite-api" />
|
|
32
|
+
<item index="13" class="java.lang.String" itemvalue="geopy" />
|
|
33
|
+
<item index="14" class="java.lang.String" itemvalue="typing_extensions" />
|
|
34
|
+
<item index="15" class="java.lang.String" itemvalue="numpy" />
|
|
35
|
+
<item index="16" class="java.lang.String" itemvalue="mysql-connector-python" />
|
|
36
|
+
<item index="17" class="java.lang.String" itemvalue="pandas" />
|
|
37
|
+
<item index="18" class="java.lang.String" itemvalue="cryptography" />
|
|
38
|
+
<item index="19" class="java.lang.String" itemvalue="bcrypt" />
|
|
39
|
+
<item index="20" class="java.lang.String" itemvalue="matplotlib" />
|
|
40
|
+
</list>
|
|
41
|
+
</value>
|
|
42
|
+
</option>
|
|
43
|
+
</inspection_tool>
|
|
44
|
+
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
|
45
|
+
<option name="ignoredErrors">
|
|
46
|
+
<list>
|
|
47
|
+
<option value="E501" />
|
|
48
|
+
</list>
|
|
49
|
+
</option>
|
|
50
|
+
</inspection_tool>
|
|
51
|
+
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
|
52
|
+
<option name="ignoredErrors">
|
|
53
|
+
<list>
|
|
54
|
+
<option value="N806" />
|
|
55
|
+
</list>
|
|
56
|
+
</option>
|
|
57
|
+
</inspection_tool>
|
|
58
|
+
</profile>
|
|
59
|
+
</component>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="Black">
|
|
4
|
+
<option name="sdkName" value="Python 3.12 (BoilerGen)" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="DiscordProjectSettings">
|
|
7
|
+
<option name="show" value="ASK" />
|
|
8
|
+
<option name="description" value="" />
|
|
9
|
+
</component>
|
|
10
|
+
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (BoilerGen)" project-jdk-type="Python SDK" />
|
|
11
|
+
<component name="PyCharmProfessionalAdvertiser">
|
|
12
|
+
<option name="shown" value="true" />
|
|
13
|
+
</component>
|
|
14
|
+
</project>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/BoilerGen.iml" filepath="$PROJECT_DIR$/.idea/BoilerGen.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
boilergen-1.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# BoilerGen
|
|
2
|
+
|
|
3
|
+
BoilerGen creates files from reusable templates and injects necessary code (e.g. imports, registrations) directly into your project — without breaking existing code.
|
|
4
|
+
|
|
5
|
+
No more manual integration steps. No more forgotten imports. Just working boilerplate.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
1. [Install Python (3.11+) and pip](https://realpython.com/installing-python/)
|
|
10
|
+
2. Clone this repository using `git clone https://github.com/HumanBot000/BoilerGen.git`
|
|
11
|
+
3. Open your preferred command line and `cd` into the project directory.
|
|
12
|
+
4. Run `pip install -r requirements.txt`
|
|
13
|
+
5. Run `pip install -e .`
|
|
14
|
+
6. [Set up your first templates](https://github.com/HumanBot000/BoilerGen?tab=readme-ov-file#templates)
|
|
15
|
+
7. Run `boilergen create` and follow the instructions.
|
|
16
|
+
→ All available commands can be accessed by `boilergen --help`.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Templates
|
|
20
|
+
|
|
21
|
+
Templates are pre-defined code snippets that can be reused across multiple projects with the same tech stack.
|
|
22
|
+
If you already have a boilerplate repository, you may need to edit some snippets to follow [BoilerGen's tagging rules](https://github.com/HumanBot000/BoilerGen?tab=readme-ov-file#tagging).
|
|
23
|
+
|
|
24
|
+
Templates are configured in the `boilergen/templates` directory and can be grouped into multiple subgroups [(see examples)](https://github.com/HumanBot000/BoilerGen/tree/master/boilergen/templates).
|
|
25
|
+
|
|
26
|
+
## Template.yaml
|
|
27
|
+
Each template needs a `template.yaml` file for its Template Definition.
|
|
28
|
+
We highly encourage you to take a look at the [(Example Template repository)](https://github.com/HumanBot000/boilergen-templates).
|
|
29
|
+
Otherwise, here is a quick breakdown:
|
|
30
|
+
```yaml
|
|
31
|
+
id: flask
|
|
32
|
+
label: "Flask Base App"
|
|
33
|
+
requires: [example]
|
|
34
|
+
config:
|
|
35
|
+
debug: True
|
|
36
|
+
port: 5000
|
|
37
|
+
```
|
|
38
|
+
### Fields
|
|
39
|
+
| id | The technical identifier for this template (Must be unique across all Templates) |
|
|
40
|
+
|---------- |-------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| label | The human-readable name of this Template (This will be shown in the Template browser) |
|
|
42
|
+
| requires | List of templates this template relies on (dependence management). This will be needed for injections. Use the `id` field of the template. |
|
|
43
|
+
| config | A Map of default values for [boilergen configurations](https://github.com/HumanBot000/BoilerGen?tab=readme-ov-file#configurations) |
|
|
44
|
+
## Tagging
|
|
45
|
+
|
|
46
|
+
Often, multiple code snippets depend on each other and can't simply be copy-pasted and expected to work (e.g., special API routes need to be registered in the main API definition before startup). To simplify this process, BoilerGen uses a tagging system to automatically adjust your code.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# <<boilergen:imports
|
|
50
|
+
from flask import Flask
|
|
51
|
+
# boilergen:imports>>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> Depending on your language of choice, you may need to edit the [comment syntax](https://gist.github.com/dk949/88b2652284234f723decaeb84db2576c). BoilerGen will comply with this, but the core syntax remains the same.
|
|
55
|
+
|
|
56
|
+
### Tagging Syntax Explained
|
|
57
|
+
|
|
58
|
+
- `<<` indicates an opening tag.
|
|
59
|
+
- `>>` indicates a closing tag.
|
|
60
|
+
- The comment contains the keyword `boilergen`, identifying it as a special tag.
|
|
61
|
+
- After `boilergen`, a colon `:` and a unique identifier (e.g., `imports`) must follow.
|
|
62
|
+
- Everything between the tag lines is the tag's content and will be used for code injection.
|
|
63
|
+
> ⚠️ Tag opening and closing definitions **may not happen inline**. They need their own line with with no additional syntax.
|
|
64
|
+
>
|
|
65
|
+
> ⚠️ You **must not** use this exact syntax (`<<boilergen:...>>`) in any context not intended for BoilerGen. Doing so will corrupt your template.
|
|
66
|
+
>
|
|
67
|
+
> ⚠️ Identifiers must be unique **within** a template. We strongly recommend aslo keeping them unique **across** all templates to avoid confusion.
|
|
68
|
+
|
|
69
|
+
Example of unique identifiers:
|
|
70
|
+
```text
|
|
71
|
+
boilergen:main-template_imports
|
|
72
|
+
boilergen:main-template_routes
|
|
73
|
+
```
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Configurations
|
|
77
|
+
|
|
78
|
+
To simplify simple variations between projects (e.g., changing the app name or enabling debug mode), templates support configurable variables. These can be set in a `template.yaml` file or supplied interactively during `boilergen create`.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
debug = bool("boilergen:config | debug | True")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Configuration Syntax Explained
|
|
85
|
+
|
|
86
|
+
- Follows the same general structure as tagging.
|
|
87
|
+
- Does **not** require a unique identifier after the colon.
|
|
88
|
+
- The format is:
|
|
89
|
+
`boilergen:config | config_name | default_value`
|
|
90
|
+
- The `default_value` is optional, but must be provided at some point.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
```python
|
|
94
|
+
app.run(
|
|
95
|
+
host='boilergen:config | IP | "0.0.0.0"',
|
|
96
|
+
port="boilergen:config | port",
|
|
97
|
+
debug=debug
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> In this example:
|
|
102
|
+
> - `host` will be parsed as a `str ("0.0.0.0")` .
|
|
103
|
+
> - `debug` is already parsed using `bool(...)` above
|
|
104
|
+
|
|
105
|
+
We **strongly recommend** not placing configuration tags inside **inline comments**, as this may break the syntax highlighting and parsing in your language-specific editor or runtime. BoilerGen tries to **verify data types**, but we **strongly recommend** accepting them as **Strings** and parsing them individually, depending on your language,
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### Configuration Precedence
|
|
110
|
+
|
|
111
|
+
The order of precedence for resolving configuration values is:
|
|
112
|
+
|
|
113
|
+
```mermaid
|
|
114
|
+
stateDiagram-v2
|
|
115
|
+
step1: CLI input during project creation
|
|
116
|
+
step2: Value from template.yaml
|
|
117
|
+
step3: Default value in template
|
|
118
|
+
step1 --> step2
|
|
119
|
+
step2 --> step3
|
|
120
|
+
```
|
|
121
|
+
## Injections
|
|
122
|
+
Injections are a way to specify insertion/editing operations to files of foreign Templates.
|
|
123
|
+
|
|
124
|
+
### Defining Injections
|
|
125
|
+
Injection definitions are located inside a special `injections;` folder at the parent level.
|
|
126
|
+
```
|
|
127
|
+
boilergen/
|
|
128
|
+
├── templates/
|
|
129
|
+
│ ├── base-template/
|
|
130
|
+
│ │ ├── template.yaml
|
|
131
|
+
│ │ └── template/
|
|
132
|
+
│ │ └── api/
|
|
133
|
+
│ │ └── test-file.txt
|
|
134
|
+
│ └── test-template/
|
|
135
|
+
│ ├── injections/
|
|
136
|
+
│ │ ├── data-file1.txt
|
|
137
|
+
│ │ ├── data-file2.txt
|
|
138
|
+
│ │ └── injections.yaml
|
|
139
|
+
│ ├── template/
|
|
140
|
+
│ └── template.yaml
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Generally you define injections in the extending template, not the base template.
|
|
144
|
+
|
|
145
|
+
#### injections.yaml
|
|
146
|
+
This File lays out a structure on how the injection behaves.
|
|
147
|
+
```yaml
|
|
148
|
+
injections:
|
|
149
|
+
- target: base
|
|
150
|
+
at:
|
|
151
|
+
file: api/test-file.txt
|
|
152
|
+
tag: start
|
|
153
|
+
method:
|
|
154
|
+
insert:
|
|
155
|
+
- bottom
|
|
156
|
+
from: data-file1.txt
|
|
157
|
+
|
|
158
|
+
- target: base
|
|
159
|
+
at:
|
|
160
|
+
file: api/test-file.txt
|
|
161
|
+
tag: main
|
|
162
|
+
method:
|
|
163
|
+
replace:
|
|
164
|
+
from: data-file2.txt
|
|
165
|
+
```
|
|
166
|
+
This setup takes the whole content of `data-file1.txt` and inserts right before the closing definition of the "start" tag at `api/test-file.txt` inside the base template. It does the same for data-file2.txt but replaces the whole "main" section of the file.
|
|
167
|
+
|
|
168
|
+
##### Fields
|
|
169
|
+
| name | description | note | possible values |
|
|
170
|
+
|----------- |-------------------------------------------------------------------------------------- |---------------------------------------------------------------------------- |--------------------------- |
|
|
171
|
+
| at | The relative path to the file to inject into | This must be defined inside the `requires` part of the template.yaml file. | |
|
|
172
|
+
| tag | The identifier of the tag to inject into | Can't be used alongside `line` in the same injection. | |
|
|
173
|
+
| line | A single integer describing the line the injection affects | Can't be used alongside `tag` in the same injection. | |
|
|
174
|
+
| method | what to do with the current tag content | | insert, replace |
|
|
175
|
+
| -> insert | Where to insert the new content | Can only be used when method is `insert` | above, below, top, bottom |
|
|
176
|
+
| from | The relative path to the file inside the injecting template to pull the content from | | |
|
|
177
|
+
|
|
178
|
+
## Hooks
|
|
179
|
+
Hooks are a way to specify operations to be executed on specific times during the project generation process.
|
|
180
|
+
|
|
181
|
+
### Defining Hooks
|
|
182
|
+
Hooks are configured in the `boilergen/hooks` directory. The file name specifies the event the hook is triggered on.
|
|
183
|
+
Currently supported events are:
|
|
184
|
+
- `pre-generation.txt`
|
|
185
|
+
- `post-generation.txt`
|
|
186
|
+
|
|
187
|
+
Inside those files, you can define shell commands to be executed. Each command is separated by a newline. The execution order is top to bottom.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import boilergen.cli.run_config
|
|
2
|
+
from boilergen.builder.parser.tags import TemplateFile
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def generate_file_content_data(file: TemplateFile, run_config: boilergen.cli.run_config.RunConfig):
|
|
6
|
+
text = file.content
|
|
7
|
+
# Configs
|
|
8
|
+
for config in sorted(file.configs, key=lambda c: c.replacement_start, reverse=True):
|
|
9
|
+
start = config.replacement_start
|
|
10
|
+
end = config.replacement_end
|
|
11
|
+
if start > 0 and end < len(text):
|
|
12
|
+
if not run_config.disable_quote_parsing_for_configs:
|
|
13
|
+
if text[start - 1] in ['"', "'"] and text[end] in ['"', "'"]:
|
|
14
|
+
start -= 1
|
|
15
|
+
end += 1
|
|
16
|
+
text = text[:start] + config.insertion_value + text[end:]
|
|
17
|
+
|
|
18
|
+
lines = text.splitlines()
|
|
19
|
+
# Tag removal
|
|
20
|
+
for index,tag in enumerate(sorted(file.tags, key=lambda t: t.line_start, reverse=True)):
|
|
21
|
+
lines[tag.line_start - 1] = ""
|
|
22
|
+
lines[tag.line_end - 1] = ""
|
|
23
|
+
"""del lines[tag.line_start]
|
|
24
|
+
del lines[tag.line_end-1]
|
|
25
|
+
for other_tag in sorted(file.tags, key=lambda t: t.line_start, reverse=True)[index:]:
|
|
26
|
+
other_tag.line_start -= 2
|
|
27
|
+
other_tag.line_end -= 2
|
|
28
|
+
"""
|
|
29
|
+
text = "\n".join(lines)
|
|
30
|
+
file.content = text
|
|
31
|
+
|
|
32
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def process_post_generation_hook(output_path: str, hook_position: str):
|
|
5
|
+
hook_position = os.path.join(hook_position, "hooks")
|
|
6
|
+
os.chdir(output_path)
|
|
7
|
+
if not os.path.exists(os.path.join(hook_position, "post-generation.txt")):
|
|
8
|
+
return
|
|
9
|
+
with open(os.path.join(hook_position, "post-generation.txt"), "r") as f:
|
|
10
|
+
for line in f.readlines():
|
|
11
|
+
os.system(line)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def process_pre_generation_hook(output_path: str, hook_position: str):
|
|
15
|
+
hook_position = os.path.join(hook_position, "hooks")
|
|
16
|
+
os.chdir(output_path)
|
|
17
|
+
if not os.path.exists(os.path.join(hook_position, "pre-generation.txt")):
|
|
18
|
+
return
|
|
19
|
+
with open(os.path.join(hook_position, "pre-generation.txt"), "r") as f:
|
|
20
|
+
for line in f.readlines():
|
|
21
|
+
os.system(line)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import stat
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import questionary
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
import boilergen.core.template
|
|
10
|
+
from boilergen.builder.hooks import process_post_generation_hook, process_pre_generation_hook
|
|
11
|
+
from boilergen.builder.project_setup import create_project
|
|
12
|
+
import boilergen.core.display
|
|
13
|
+
|
|
14
|
+
def force_remove_readonly(func, path, _):
|
|
15
|
+
os.chmod(path, stat.S_IWRITE)
|
|
16
|
+
func(path)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def clear_cloned_repo(template_dir, minimal_ui, console):
|
|
20
|
+
if template_dir.endswith(f"cloned_templates"):
|
|
21
|
+
if minimal_ui:
|
|
22
|
+
print("Removing remote templates...")
|
|
23
|
+
else:
|
|
24
|
+
console.print("[red]Removing remote templates...[/red]")
|
|
25
|
+
shutil.rmtree(template_dir, onerror=force_remove_readonly)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ask_for_output_location(selected_templates: List[boilergen.core.template.Template], run_config, template_dir):
|
|
29
|
+
output_selection = questionary.prompt(
|
|
30
|
+
[
|
|
31
|
+
{
|
|
32
|
+
"type": "input",
|
|
33
|
+
"name": "output",
|
|
34
|
+
"message": "Where do you want to generate the output?",
|
|
35
|
+
"default": os.path.join(os.getcwd(), "output"),
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
)["output"]
|
|
39
|
+
if not os.path.exists(output_selection):
|
|
40
|
+
os.makedirs(output_selection, exist_ok=True)
|
|
41
|
+
else:
|
|
42
|
+
if run_config.clear_output:
|
|
43
|
+
if typer.confirm(
|
|
44
|
+
f"Output directory {output_selection} does already exist. Do you want to overwrite it? {typer.style("(This will delete existing data!)", fg=typer.colors.RED)}",
|
|
45
|
+
default=False
|
|
46
|
+
):
|
|
47
|
+
try:
|
|
48
|
+
shutil.rmtree(output_selection) # recursively delete
|
|
49
|
+
os.makedirs(output_selection, exist_ok=True) # recreate clean output dir
|
|
50
|
+
except PermissionError:
|
|
51
|
+
raise PermissionError(
|
|
52
|
+
"Permission denied while trying to delete the output directory. Try running with admin privileges.")
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Output directory {output_selection} does already exist. Run with --clear-output to overwrite it.")
|
|
56
|
+
template_dir = os.sep.join(template_dir.split(os.sep)[:-1])
|
|
57
|
+
process_pre_generation_hook(output_selection,template_dir)
|
|
58
|
+
create_project(output_selection, selected_templates, run_config)
|
|
59
|
+
process_post_generation_hook(output_selection,template_dir)
|
|
60
|
+
clear_cloned_repo(template_dir, run_config.minimal_ui, boilergen.core.display.console)
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# todo The regex and quote detection was written entirely by AI, seems to work but unit tests are top priority
|
|
2
|
+
import re
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NotDefinedType:
|
|
7
|
+
def __repr__(self):
|
|
8
|
+
return "NOT_DEFINED"
|
|
9
|
+
|
|
10
|
+
def __str__(self):
|
|
11
|
+
return "NOT_DEFINED"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
NOT_DEFINED = NotDefinedType()
|
|
15
|
+
|
|
16
|
+
ValueType = Union[str, bool, None, NotDefinedType]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ValueConfig:
|
|
20
|
+
def __init__(self, identifier: str, replacement_start: int, replacement_end: int,
|
|
21
|
+
in_template_value: ValueType, yaml_value: ValueType, cli_value: ValueType):
|
|
22
|
+
self.identifier = identifier
|
|
23
|
+
self.replacement_start = replacement_start
|
|
24
|
+
self.replacement_end = replacement_end
|
|
25
|
+
self.in_template_value = in_template_value
|
|
26
|
+
self.yaml_value = yaml_value
|
|
27
|
+
self.cli_value = cli_value
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def insertion_value(self):
|
|
31
|
+
value_order = [self.cli_value, self.yaml_value, self.in_template_value]
|
|
32
|
+
for value in value_order:
|
|
33
|
+
if value is not NOT_DEFINED:
|
|
34
|
+
return value
|
|
35
|
+
return NOT_DEFINED
|
|
36
|
+
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
return self.__str__()
|
|
39
|
+
|
|
40
|
+
def __str__(self):
|
|
41
|
+
return f"ValueConfig(identifier={self.identifier}, in_template_value={self.in_template_value}, yaml_value={self.yaml_value}, cli_value={self.cli_value})"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_configs(file_content: str):
|
|
45
|
+
full_pattern = re.compile(
|
|
46
|
+
r'boilergen:config\s*\|\s*'
|
|
47
|
+
r'([^\s|]+)' # identifier
|
|
48
|
+
r'(?:\s*\|\s*'
|
|
49
|
+
r'([^"\']*(?:"[^"]*"|\'[^\']*\'[^"\']*)*)' # optional value (may contain quoted parts)
|
|
50
|
+
r')?'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
offset = 0
|
|
54
|
+
configs = []
|
|
55
|
+
|
|
56
|
+
for line_number, line in enumerate(file_content.splitlines(keepends=True), start=1):
|
|
57
|
+
in_quotes = []
|
|
58
|
+
i = 0
|
|
59
|
+
while i < len(line):
|
|
60
|
+
if line[i] in ('"', "'"):
|
|
61
|
+
quote_char = line[i]
|
|
62
|
+
start_quote = i
|
|
63
|
+
i += 1
|
|
64
|
+
while i < len(line) and line[i] != quote_char:
|
|
65
|
+
if line[i] == '\\':
|
|
66
|
+
i += 2
|
|
67
|
+
else:
|
|
68
|
+
i += 1
|
|
69
|
+
if i < len(line):
|
|
70
|
+
in_quotes.append((start_quote, i, quote_char, line[start_quote:i + 1]))
|
|
71
|
+
i += 1
|
|
72
|
+
else:
|
|
73
|
+
i += 1
|
|
74
|
+
|
|
75
|
+
# 1. Matches **within quotes** (current logic)
|
|
76
|
+
for start_quote, end_quote, quote_char, quoted_content in in_quotes:
|
|
77
|
+
inner_content = quoted_content[1:-1]
|
|
78
|
+
for m in full_pattern.finditer(inner_content):
|
|
79
|
+
match_start = offset + start_quote + 1 + m.start()
|
|
80
|
+
match_end = offset + start_quote + 1 + m.end()
|
|
81
|
+
identifier = m.group(1).strip()
|
|
82
|
+
raw_value = m.group(2).strip() if m.group(2) is not None else None
|
|
83
|
+
interpreted_value = interpret_value(raw_value, quote_char)
|
|
84
|
+
|
|
85
|
+
config = ValueConfig(
|
|
86
|
+
identifier=identifier,
|
|
87
|
+
replacement_start=match_start,
|
|
88
|
+
replacement_end=match_end,
|
|
89
|
+
in_template_value=interpreted_value,
|
|
90
|
+
yaml_value=NOT_DEFINED,
|
|
91
|
+
cli_value=NOT_DEFINED
|
|
92
|
+
)
|
|
93
|
+
configs.append(config)
|
|
94
|
+
|
|
95
|
+
# 2. Matches **outside of quotes**
|
|
96
|
+
quote_ranges = [(s, e) for s, e, *_ in in_quotes]
|
|
97
|
+
|
|
98
|
+
def is_outside_quotes(pos):
|
|
99
|
+
return not any(start <= pos < end + 1 for start, end in quote_ranges)
|
|
100
|
+
|
|
101
|
+
for m in full_pattern.finditer(line):
|
|
102
|
+
if is_outside_quotes(m.start()):
|
|
103
|
+
match_start = offset + m.start()
|
|
104
|
+
match_end = offset + m.end()
|
|
105
|
+
identifier = m.group(1).strip()
|
|
106
|
+
raw_value = m.group(2).strip() if m.group(2) is not None else None
|
|
107
|
+
interpreted_value = interpret_value(raw_value, None)
|
|
108
|
+
|
|
109
|
+
config = ValueConfig(
|
|
110
|
+
identifier=identifier,
|
|
111
|
+
replacement_start=match_start,
|
|
112
|
+
replacement_end=match_end,
|
|
113
|
+
in_template_value=interpreted_value,
|
|
114
|
+
yaml_value=NOT_DEFINED,
|
|
115
|
+
cli_value=NOT_DEFINED
|
|
116
|
+
)
|
|
117
|
+
configs.append(config)
|
|
118
|
+
|
|
119
|
+
offset += len(line)
|
|
120
|
+
|
|
121
|
+
return configs
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def fetch_yaml_configs(configs: list[ValueConfig], yaml_data: dict):
|
|
126
|
+
for config in configs:
|
|
127
|
+
if isinstance(yaml_data, dict) and "config" in yaml_data:
|
|
128
|
+
if config.identifier in yaml_data["config"]:
|
|
129
|
+
config.yaml_value = yaml_data["config"][config.identifier]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def interpret_value(raw: Union[str, None, NotDefinedType], outer_quote: Union[str, None]) -> ValueType:
|
|
133
|
+
"""
|
|
134
|
+
Interpret the raw value depending on the outer quote context.
|
|
135
|
+
|
|
136
|
+
- If raw is None or NOT_DEFINED, return NOT_DEFINED.
|
|
137
|
+
- Strip raw.
|
|
138
|
+
- If outer_quote is set:
|
|
139
|
+
- If raw starts and ends with matching quotes, preserve raw (including quotes).
|
|
140
|
+
- Else return raw stripped (no added quotes).
|
|
141
|
+
- If outer_quote is None:
|
|
142
|
+
- Return raw stripped (no quotes stripped or added).
|
|
143
|
+
"""
|
|
144
|
+
if raw is None or raw is NOT_DEFINED:
|
|
145
|
+
return NOT_DEFINED
|
|
146
|
+
|
|
147
|
+
txt = raw.strip()
|
|
148
|
+
if not txt: # Empty string case
|
|
149
|
+
return txt
|
|
150
|
+
if outer_quote:
|
|
151
|
+
# We're inside outer quotes like "boilergen:config | debug | True"
|
|
152
|
+
if len(txt) >= 2 and txt[0] == txt[-1] and txt[0] in ('"', "'"):
|
|
153
|
+
# Value has explicit inner quotes: "boilergen:config | debug | 'True'" -> 'True'
|
|
154
|
+
# or 'boilergen:config | host | "0.0.0.0"' -> "0.0.0.0"
|
|
155
|
+
return txt
|
|
156
|
+
else:
|
|
157
|
+
# Value has no inner quotes: "boilergen:config | debug | True" -> True
|
|
158
|
+
return txt
|
|
159
|
+
else:
|
|
160
|
+
# No outer quote context: just return stripped value
|
|
161
|
+
return txt
|