storm-cli 1.0.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.
- main.py +753 -0
- src/__init__.py +2 -0
- src/ast.py +100 -0
- src/column.py +56 -0
- src/enum.py +21 -0
- src/error_handler.py +35 -0
- src/generic_controller_csharp.py +106 -0
- src/generic_mapper_csharp.py +35 -0
- src/generic_pagination_csharp.py +44 -0
- src/generic_query_chsarp.py +18 -0
- src/generic_service_csharp.py +112 -0
- src/interpreter.py +2300 -0
- src/keyword.py +18 -0
- src/parser.py +397 -0
- src/pos.py +11 -0
- src/table.py +37 -0
- src/template.py +14 -0
- src/tok.py +12 -0
- src/tok_type.py +13 -0
- src/tokenizer.py +238 -0
- storm_cli-1.0.0.dist-info/METADATA +350 -0
- storm_cli-1.0.0.dist-info/RECORD +26 -0
- storm_cli-1.0.0.dist-info/WHEEL +5 -0
- storm_cli-1.0.0.dist-info/entry_points.txt +2 -0
- storm_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
- storm_cli-1.0.0.dist-info/top_level.txt +2 -0
src/tokenizer.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import unicodedata
|
|
2
|
+
|
|
3
|
+
from src.tok import Tok
|
|
4
|
+
from src.pos import Pos
|
|
5
|
+
from src.tok_type import TokenType
|
|
6
|
+
from src.keyword import Keyword
|
|
7
|
+
from src.error_handler import raise_error
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Tokenizer:
|
|
11
|
+
def __init__(self, fpath: str, fdata: str):
|
|
12
|
+
self.file_path = fpath
|
|
13
|
+
self.file_data = fdata
|
|
14
|
+
self.pos = 0
|
|
15
|
+
self.line = 1
|
|
16
|
+
self.column = 1
|
|
17
|
+
|
|
18
|
+
self.keywords = {k.value for k in Keyword}
|
|
19
|
+
|
|
20
|
+
self._two_char = {
|
|
21
|
+
'++', '--', '==', '!=', '<=', '>=', '&&', '||',
|
|
22
|
+
'<<', '>>', '->', '+=', '-=', '*=', '/=', '%=',
|
|
23
|
+
'&=', '|=', '^=', '##',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
self._three_char = {'<<=', '>>='}
|
|
27
|
+
|
|
28
|
+
def _pos(self) -> Pos:
|
|
29
|
+
return Pos(self.line, self.column)
|
|
30
|
+
|
|
31
|
+
def _advance(self) -> str:
|
|
32
|
+
ch = self.file_data[self.pos]
|
|
33
|
+
if ch == '\n':
|
|
34
|
+
self.line += 1
|
|
35
|
+
self.column = 1
|
|
36
|
+
else:
|
|
37
|
+
self.column += 1
|
|
38
|
+
self.pos += 1
|
|
39
|
+
return ch
|
|
40
|
+
|
|
41
|
+
def _peek(self, n: int = 0) -> str:
|
|
42
|
+
idx = self.pos + n
|
|
43
|
+
if idx < len(self.file_data):
|
|
44
|
+
return self.file_data[idx]
|
|
45
|
+
return ''
|
|
46
|
+
|
|
47
|
+
def _skip_whitespace(self):
|
|
48
|
+
while self.pos < len(self.file_data):
|
|
49
|
+
ch = self.file_data[self.pos]
|
|
50
|
+
if ch in ' \t\r\n':
|
|
51
|
+
self._advance()
|
|
52
|
+
else:
|
|
53
|
+
break
|
|
54
|
+
|
|
55
|
+
def _skip_line_comment(self):
|
|
56
|
+
while self.pos < len(self.file_data) and self.file_data[self.pos] != '\n':
|
|
57
|
+
self._advance()
|
|
58
|
+
|
|
59
|
+
def _skip_block_comment(self):
|
|
60
|
+
self._advance()
|
|
61
|
+
self._advance()
|
|
62
|
+
while self.pos < len(self.file_data):
|
|
63
|
+
if self.file_data[self.pos] == '*' and self._peek(1) == '/':
|
|
64
|
+
self._advance()
|
|
65
|
+
self._advance()
|
|
66
|
+
return
|
|
67
|
+
self._advance()
|
|
68
|
+
raise_error(self.file_path, self.file_data, 'unterminated block comment', self._pos())
|
|
69
|
+
|
|
70
|
+
def _read_string(self, quote: str) -> Tok:
|
|
71
|
+
start = self._pos()
|
|
72
|
+
self._advance()
|
|
73
|
+
chars = []
|
|
74
|
+
while self.pos < len(self.file_data):
|
|
75
|
+
ch = self.file_data[self.pos]
|
|
76
|
+
if ch == '\\':
|
|
77
|
+
self._advance()
|
|
78
|
+
if self.pos >= len(self.file_data):
|
|
79
|
+
raise_error(self.file_path, self.file_data, 'unterminated string', start)
|
|
80
|
+
esc = self._advance()
|
|
81
|
+
if esc == 'n':
|
|
82
|
+
chars.append('\n')
|
|
83
|
+
elif esc == 't':
|
|
84
|
+
chars.append('\t')
|
|
85
|
+
elif esc == 'r':
|
|
86
|
+
chars.append('\r')
|
|
87
|
+
elif esc == '0':
|
|
88
|
+
chars.append('\0')
|
|
89
|
+
elif esc == '\\':
|
|
90
|
+
chars.append('\\')
|
|
91
|
+
elif esc == '\'':
|
|
92
|
+
chars.append('\'')
|
|
93
|
+
elif esc == '"':
|
|
94
|
+
chars.append('"')
|
|
95
|
+
elif esc == 'x':
|
|
96
|
+
hex_digits = []
|
|
97
|
+
while self.pos < len(self.file_data):
|
|
98
|
+
c = self.file_data[self.pos]
|
|
99
|
+
if c.isdigit() or c.lower() in 'abcdef':
|
|
100
|
+
hex_digits.append(self._advance())
|
|
101
|
+
else:
|
|
102
|
+
break
|
|
103
|
+
if hex_digits:
|
|
104
|
+
chars.append(chr(int(''.join(hex_digits), 16)))
|
|
105
|
+
else:
|
|
106
|
+
chars.append(esc)
|
|
107
|
+
elif ch == quote:
|
|
108
|
+
self._advance()
|
|
109
|
+
return Tok(TokenType.STRING, ''.join(chars), start)
|
|
110
|
+
elif ch == '\n':
|
|
111
|
+
raise_error(self.file_path, self.file_data, 'unterminated string', start)
|
|
112
|
+
else:
|
|
113
|
+
chars.append(self._advance())
|
|
114
|
+
raise_error(self.file_path, self.file_data, 'unterminated string', start)
|
|
115
|
+
|
|
116
|
+
def _read_number(self) -> Tok:
|
|
117
|
+
start = self._pos()
|
|
118
|
+
result = [self._advance()]
|
|
119
|
+
|
|
120
|
+
if result[0] == '0' and self.pos < len(self.file_data):
|
|
121
|
+
nxt = self.file_data[self.pos].lower()
|
|
122
|
+
if nxt == 'x':
|
|
123
|
+
result.append(self._advance())
|
|
124
|
+
if self.pos >= len(self.file_data) or not (self.file_data[self.pos].isdigit() or self.file_data[self.pos].lower() in 'abcdef'):
|
|
125
|
+
raise_error(self.file_path, self.file_data, 'invalid hex literal', start)
|
|
126
|
+
while self.pos < len(self.file_data):
|
|
127
|
+
c = self.file_data[self.pos]
|
|
128
|
+
if c.isdigit() or c.lower() in 'abcdef':
|
|
129
|
+
result.append(self._advance())
|
|
130
|
+
else:
|
|
131
|
+
break
|
|
132
|
+
return Tok(TokenType.HEX, ''.join(result), start)
|
|
133
|
+
elif nxt == 'o':
|
|
134
|
+
result.append(self._advance())
|
|
135
|
+
if self.pos >= len(self.file_data) or self.file_data[self.pos] not in '01234567':
|
|
136
|
+
raise_error(self.file_path, self.file_data, 'invalid octal literal', start)
|
|
137
|
+
while self.pos < len(self.file_data):
|
|
138
|
+
c = self.file_data[self.pos]
|
|
139
|
+
if c in '01234567':
|
|
140
|
+
result.append(self._advance())
|
|
141
|
+
else:
|
|
142
|
+
break
|
|
143
|
+
return Tok(TokenType.OCT, ''.join(result), start)
|
|
144
|
+
elif nxt == 'b':
|
|
145
|
+
result.append(self._advance())
|
|
146
|
+
if self.pos >= len(self.file_data) or self.file_data[self.pos] not in '01':
|
|
147
|
+
raise_error(self.file_path, self.file_data, 'invalid binary literal', start)
|
|
148
|
+
while self.pos < len(self.file_data):
|
|
149
|
+
c = self.file_data[self.pos]
|
|
150
|
+
if c in '01':
|
|
151
|
+
result.append(self._advance())
|
|
152
|
+
else:
|
|
153
|
+
break
|
|
154
|
+
return Tok(TokenType.BIN, ''.join(result), start)
|
|
155
|
+
|
|
156
|
+
while self.pos < len(self.file_data):
|
|
157
|
+
c = self.file_data[self.pos]
|
|
158
|
+
if c.isdigit():
|
|
159
|
+
result.append(self._advance())
|
|
160
|
+
else:
|
|
161
|
+
break
|
|
162
|
+
return Tok(TokenType.INT, ''.join(result), start)
|
|
163
|
+
|
|
164
|
+
def _read_identifier(self) -> Tok:
|
|
165
|
+
start = self._pos()
|
|
166
|
+
result = []
|
|
167
|
+
while self.pos < len(self.file_data):
|
|
168
|
+
ch = self.file_data[self.pos]
|
|
169
|
+
cat = unicodedata.category(ch)
|
|
170
|
+
if not result:
|
|
171
|
+
if ch == '_' or ch.isalpha() or cat.startswith('L'):
|
|
172
|
+
result.append(self._advance())
|
|
173
|
+
else:
|
|
174
|
+
break
|
|
175
|
+
else:
|
|
176
|
+
if ch == '_' or ch.isalnum():
|
|
177
|
+
result.append(self._advance())
|
|
178
|
+
elif cat in ('Nd', 'Pc', 'Mn', 'Mc'):
|
|
179
|
+
result.append(self._advance())
|
|
180
|
+
else:
|
|
181
|
+
break
|
|
182
|
+
word = ''.join(result)
|
|
183
|
+
if word in self.keywords:
|
|
184
|
+
return Tok(TokenType.KEYWORD, word, start)
|
|
185
|
+
return Tok(TokenType.IDENTIFIER, word, start)
|
|
186
|
+
|
|
187
|
+
def _read_symbol(self) -> Tok:
|
|
188
|
+
start = self._pos()
|
|
189
|
+
|
|
190
|
+
if self._peek(2) and self.file_data[self.pos:self.pos + 3] in self._three_char:
|
|
191
|
+
val = self.file_data[self.pos:self.pos + 3]
|
|
192
|
+
self._advance()
|
|
193
|
+
self._advance()
|
|
194
|
+
self._advance()
|
|
195
|
+
return Tok(TokenType.SYMBOL, val, start)
|
|
196
|
+
|
|
197
|
+
if self._peek(1) and self.file_data[self.pos:self.pos + 2] in self._two_char:
|
|
198
|
+
val = self.file_data[self.pos:self.pos + 2]
|
|
199
|
+
self._advance()
|
|
200
|
+
self._advance()
|
|
201
|
+
return Tok(TokenType.SYMBOL, val, start)
|
|
202
|
+
|
|
203
|
+
ch = self._advance()
|
|
204
|
+
if ch == '.' and self._peek() == '.':
|
|
205
|
+
raise_error(self.file_path, self.file_data, 'unexpected token', start)
|
|
206
|
+
return Tok(TokenType.SYMBOL, ch, start)
|
|
207
|
+
|
|
208
|
+
def nextToken(self) -> Tok:
|
|
209
|
+
while self.pos < len(self.file_data):
|
|
210
|
+
self._skip_whitespace()
|
|
211
|
+
if self.pos >= len(self.file_data):
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
ch = self.file_data[self.pos]
|
|
215
|
+
|
|
216
|
+
if ch == '/' and self._peek(1) == '/':
|
|
217
|
+
self._skip_line_comment()
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
if ch == '/' and self._peek(1) == '*':
|
|
221
|
+
self._skip_block_comment()
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
if ch in '"\'':
|
|
225
|
+
return self._read_string(ch)
|
|
226
|
+
|
|
227
|
+
if ch.isdigit():
|
|
228
|
+
return self._read_number()
|
|
229
|
+
|
|
230
|
+
if ch == '_' or ch.isalpha() or unicodedata.category(ch).startswith('L'):
|
|
231
|
+
return self._read_identifier()
|
|
232
|
+
|
|
233
|
+
if ch in '+-*/%&|^~!<>=.,;:{}[]()?#\\':
|
|
234
|
+
return self._read_symbol()
|
|
235
|
+
|
|
236
|
+
raise_error(self.file_path, self.file_data, f"unexpected character '{ch}'", self._pos())
|
|
237
|
+
|
|
238
|
+
return Tok(TokenType.EOF, '', self._pos())
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: storm-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Declarative API scaffold: define your data model in .storm format and generate complete C# (.NET) or PHP (Laravel) backends with zero boilerplate
|
|
5
|
+
Author-email: Philipp Andrew Redondo <hollishake@pm.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/HolliShake/storm-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/HolliShake/storm-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/HolliShake/storm-cli/issues
|
|
10
|
+
Keywords: api,scaffold,code-generation,dotnet,csharp,laravel,php,webapi,rest,cli,storm
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
22
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: colorama>=0.4.0
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<img src="https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/1f4a5.svg" width="64" height="64" alt="" />
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
<h1 align="center">⚡ API Starter Kit</h1>
|
|
34
|
+
<p align="center"><strong>Declare your data model once. Generate everything.</strong></p>
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+" />
|
|
38
|
+
<img src="https://img.shields.io/badge/tests-148%20passed-brightgreen.svg" alt="148 tests passed" />
|
|
39
|
+
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License" />
|
|
40
|
+
<img src="https://img.shields.io/badge/templates-.NET%20%7C%20Laravel-purple.svg" alt="Templates" />
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
A CLI tool that scaffolds full‑stack API projects from a **declarative schema file**.
|
|
46
|
+
Write your data model in `.storm` format and get complete, production‑ready backend code — controllers, services, models, DTOs, migrations, OpenAPI annotations — in **C# (.NET)** or **PHP (Laravel 12+)** with zero boilerplate.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 📦 Quick Start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1. Install Python dependencies
|
|
54
|
+
pip install -r requirements.txt
|
|
55
|
+
|
|
56
|
+
# 2. Scaffold a project (pick your template)
|
|
57
|
+
python main.py --init --template dotnet-csharp --name MyApi # .NET
|
|
58
|
+
python main.py --init --template laravel-php # Laravel
|
|
59
|
+
|
|
60
|
+
# 3. Edit schema.storm to define your data model
|
|
61
|
+
|
|
62
|
+
# 4. Generate all source files
|
|
63
|
+
python main.py --generate
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🧭 Commands
|
|
69
|
+
|
|
70
|
+
| Flag | Alias | Description |
|
|
71
|
+
|------|-------|-------------|
|
|
72
|
+
| `--init` | `-i` | Scaffold a new project from a template |
|
|
73
|
+
| `--generate` | `-g` | Regenerate all source files from `schema.storm` |
|
|
74
|
+
| `--template` | `-t` | Template: `dotnet-csharp`, `dotnet-csharp-clean-architecture`, or `laravel-php` |
|
|
75
|
+
| `--name` | `-n` | Project name (defaults to current folder name) |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 🏗️ Templates
|
|
80
|
+
|
|
81
|
+
| Template | Stack | ORM | Auth |
|
|
82
|
+
|----------|-------|-----|------|
|
|
83
|
+
| `dotnet-csharp` | ASP.NET Core Web API | EF Core (SQL Server) | ASP.NET Identity |
|
|
84
|
+
| `dotnet-csharp-clean-architecture` | Clean Architecture (Domain → Application → Infrastructure → API) | EF Core (SQL Server) | ASP.NET Identity |
|
|
85
|
+
| `laravel-php` | Laravel 13+ · PHP 8.5+ | Eloquent | — (pkg ready) |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 📐 Schema Language (`.storm`)
|
|
90
|
+
|
|
91
|
+
Define your entire data model in a clean, readable syntax — one file, no ceremony.
|
|
92
|
+
|
|
93
|
+
```storm
|
|
94
|
+
// ═══ Enums ═══════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
enum Status {
|
|
97
|
+
Active = "active",
|
|
98
|
+
Inactive = "inactive",
|
|
99
|
+
Pending = "pending"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
enum Role {
|
|
103
|
+
Admin = "admin",
|
|
104
|
+
User = "user"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ═══ Tables ═════════════════════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
table User {
|
|
110
|
+
id: uuid pk;
|
|
111
|
+
name: string(min=2, max=100);
|
|
112
|
+
email: string(max=255) unique;
|
|
113
|
+
password: string(max=255);
|
|
114
|
+
role: Role;
|
|
115
|
+
status: Status;
|
|
116
|
+
createdAt: datetime;
|
|
117
|
+
updatedAt: datetime;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
table Product {
|
|
121
|
+
id: int? pk;
|
|
122
|
+
name: string(min=1, max=200);
|
|
123
|
+
price: double(min=0) = 0;
|
|
124
|
+
stock: int(min=0) = 0;
|
|
125
|
+
status: Status;
|
|
126
|
+
ownerId: uuid;
|
|
127
|
+
owner: User; // ← FK: BelongsTo User
|
|
128
|
+
createdAt: datetime;
|
|
129
|
+
updatedAt: datetime;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 🔤 Type System
|
|
134
|
+
|
|
135
|
+
| Storm Type | C# | PHP / OA | Migration | Notes |
|
|
136
|
+
|------------|-----|----------|-----------|-------|
|
|
137
|
+
| `int` | `int` | `integer` | `integer` | Supports `pk`, `unique`, `min`, `max` |
|
|
138
|
+
| `long` | `long` | `integer` | `bigInteger` | |
|
|
139
|
+
| `float` | `float` | `number` | `float` | |
|
|
140
|
+
| `double` | `double` | `number` | `double` | |
|
|
141
|
+
| `string` | `string` | `string` | `string` | Supports `min`, `max` |
|
|
142
|
+
| `bool` | `bool` | `boolean` | `boolean` | |
|
|
143
|
+
| `uuid` | `Guid` | `string` | `uuid` | |
|
|
144
|
+
| `datetime` | `DateTime` | `string` (date‑time) | `dateTime` | |
|
|
145
|
+
|
|
146
|
+
- Append `?` for nullable: `int?`, `uuid?`
|
|
147
|
+
- Reference another table → **foreign key** (BelongsTo / HasMany generated automatically)
|
|
148
|
+
- Reference an enum → typed enum column with casts
|
|
149
|
+
- Default values support **constant expressions**: `price:double = 0`, `qty:int = 5 * 4`
|
|
150
|
+
|
|
151
|
+
### 🧮 Expressions
|
|
152
|
+
|
|
153
|
+
Constant expressions in default values are evaluated at parse time:
|
|
154
|
+
|
|
155
|
+
| Expression | Result |
|
|
156
|
+
|------------|--------|
|
|
157
|
+
| `5 * 4` | `20` |
|
|
158
|
+
| `(1 + 2) * 3` | `9` |
|
|
159
|
+
| `-5` | `-5` |
|
|
160
|
+
| `!true` | `false` |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 🎯 Generated Artifacts
|
|
165
|
+
|
|
166
|
+
### .NET (`dotnet-csharp` / clean‑architecture)
|
|
167
|
+
|
|
168
|
+
| File | Path | Description |
|
|
169
|
+
|------|------|-------------|
|
|
170
|
+
| `{Entity}.cs` | `Models/` | Entity class (User extends `IdentityUser`) |
|
|
171
|
+
| `{Entity}RequestDto.cs` | `Dtos/` | Create / update payload |
|
|
172
|
+
| `{Entity}ResponseDto.cs` | `Dtos/` | Full API response |
|
|
173
|
+
| `{Entity}ResponseSimplifiedDto.cs` | `Dtos/` | Lightweight response (no FK expansion) |
|
|
174
|
+
| `I{Entity}Service.cs` | `IServices/` | Interface: CRUD + FK + Enum pagination |
|
|
175
|
+
| `{Entity}Service.cs` | `Services/` | EF Core + AutoMapper implementation |
|
|
176
|
+
| `{Entity}Controller.cs` | `Controllers/` | REST API with Swagger + Scalar annotations |
|
|
177
|
+
| `{Entity}MappingProfile.cs` | `Mappers/` | AutoMapper profile |
|
|
178
|
+
| `Program.cs` | `/` | App bootstrap: Identity, DbContext, AutoMapper, Scrutor, Scalar |
|
|
179
|
+
| `AppDbContext.cs` | `Data/` | EF Core context with `DbSet<T>` |
|
|
180
|
+
|
|
181
|
+
Shared base files (once):
|
|
182
|
+
|
|
183
|
+
| File | Description |
|
|
184
|
+
|------|-------------|
|
|
185
|
+
| `IGenericService.cs` | Generic CRUD interface |
|
|
186
|
+
| `GenericService.cs` | Generic EF Core implementation |
|
|
187
|
+
| `GenericController.cs` | Generic REST controller |
|
|
188
|
+
| `PaginatedResult.cs` | Pagination DTO + extension |
|
|
189
|
+
| `PaginateQuery.cs` | Query parameters: `page`, `rows` |
|
|
190
|
+
|
|
191
|
+
**Clean Architecture** maps the same files into namespace‑aware paths:
|
|
192
|
+
`Domain/`, `Application/`, `Infrastructure/`, `API/`.
|
|
193
|
+
|
|
194
|
+
### Laravel PHP
|
|
195
|
+
|
|
196
|
+
| File | Path | Description |
|
|
197
|
+
|------|------|-------------|
|
|
198
|
+
| `{Entity}.php` | `app/Models/` | Eloquent model with `#[OA\Schema]` annotations |
|
|
199
|
+
| `{Entity}Controller.php` | `app/Controllers/` | Full CRUD with `#[OA\Get/Post/Put/Delete]` |
|
|
200
|
+
| `{Entity}Service.php` | `app/Services/` | Pagination, CRUD, FK & enum filters, validation rules |
|
|
201
|
+
| `{Enum}.php` | `app/Static/` | PHP 8.1+ backed enum with `#[OA\Schema]` |
|
|
202
|
+
| `Controller.php` | `app/Controllers/` | Base controller with `ok()`, `notFound()` + shared OA schemas |
|
|
203
|
+
| `*_create_*_table.php` | `database/migrations/` | Schema migration with FK cascades |
|
|
204
|
+
| `api.php` | `routes/` | All RESTful routes including FK & enum filter endpoints |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 🔗 Auto‑Generated Endpoints
|
|
209
|
+
|
|
210
|
+
For a `Product` table with FK `owner:User` and enum `status:Status`:
|
|
211
|
+
|
|
212
|
+
### C#
|
|
213
|
+
| Method | Route | Description |
|
|
214
|
+
|--------|-------|-------------|
|
|
215
|
+
| `GET` | `/api/Product` | Paginated list (search, filter, page, rows) |
|
|
216
|
+
| `GET` | `/api/Product/{id}` | Single item by ID |
|
|
217
|
+
| `GET` | `/api/Product/user/{ownerId}` | Paginated list filtered by User |
|
|
218
|
+
| `GET` | `/api/Product/status/{status}` | Paginated list filtered by Status |
|
|
219
|
+
| `POST` | `/api/Product` | Create |
|
|
220
|
+
| `PUT` | `/api/Product/{id}` | Update |
|
|
221
|
+
| `DELETE` | `/api/Product/{id}` | Delete |
|
|
222
|
+
|
|
223
|
+
### PHP
|
|
224
|
+
| Method | Route | Description |
|
|
225
|
+
|--------|-------|-------------|
|
|
226
|
+
| `GET` | `/api/product` | Paginated list |
|
|
227
|
+
| `GET` | `/api/product/{id}` | Single item |
|
|
228
|
+
| `GET` | `/api/product/user/{ownerId}` | Filtered by User |
|
|
229
|
+
| `GET` | `/api/product/status/{status}` | Filtered by Status |
|
|
230
|
+
| `POST` | `/api/product/create` | Create |
|
|
231
|
+
| `PUT` | `/api/product/update/{id}` | Update |
|
|
232
|
+
| `DELETE` | `/api/product/delete/{id}` | Delete |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 📁 Project Structure
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
apistarterkit/
|
|
240
|
+
├── main.py # CLI entry point
|
|
241
|
+
├── storm.config.json # Project config (auto‑generated)
|
|
242
|
+
├── schema.storm # Your data model (edit this!)
|
|
243
|
+
├── requirements.txt # Python deps
|
|
244
|
+
│
|
|
245
|
+
├── src/ # Core engine
|
|
246
|
+
│ ├── interpreter.py # Code generator (C# + PHP)
|
|
247
|
+
│ ├── parser.py # Recursive‑descent parser
|
|
248
|
+
│ ├── tokenizer.py # Lexer
|
|
249
|
+
│ ├── ast.py # AST node types
|
|
250
|
+
│ ├── enum.py # Enum AST handler
|
|
251
|
+
│ ├── table.py # Table AST handler
|
|
252
|
+
│ ├── column.py # Column type system
|
|
253
|
+
│ ├── template.py # Template enum
|
|
254
|
+
│ ├── error_handler.py # Formatted error reporting
|
|
255
|
+
│ ├── keyword.py # Language keywords
|
|
256
|
+
│ ├── tok_type.py # Token type enum
|
|
257
|
+
│ ├── tok.py # Token class
|
|
258
|
+
│ ├── pos.py # Position tracking
|
|
259
|
+
│ ├── generic_controller_csharp.py
|
|
260
|
+
│ ├── generic_service_csharp.py
|
|
261
|
+
│ ├── generic_mapper_csharp.py
|
|
262
|
+
│ ├── generic_pagination_csharp.py
|
|
263
|
+
│ └── generic_query_chsarp.py
|
|
264
|
+
│
|
|
265
|
+
├── tests/ # Test suite (148 tests)
|
|
266
|
+
│ ├── conftest.py
|
|
267
|
+
│ ├── test_codegen.py # Template output tests (70)
|
|
268
|
+
│ ├── test_parser.py # Parser correctness (33)
|
|
269
|
+
│ ├── test_tokenizer.py # Lexer correctness (26)
|
|
270
|
+
│ ├── test_error_handler.py # Error formatting (5)
|
|
271
|
+
│ └── test_integration.py # End‑to‑end parse (7)
|
|
272
|
+
│
|
|
273
|
+
└── pyvenv/ # Python virtual environment
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 📦 Dependencies
|
|
279
|
+
|
|
280
|
+
### C# (.NET)
|
|
281
|
+
|
|
282
|
+
Auto‑installed during `--init`:
|
|
283
|
+
|
|
284
|
+
| Package | Purpose |
|
|
285
|
+
|---------|---------|
|
|
286
|
+
| `Microsoft.EntityFrameworkCore` | ORM |
|
|
287
|
+
| `Microsoft.EntityFrameworkCore.SqlServer` | SQL Server provider |
|
|
288
|
+
| `Microsoft.EntityFrameworkCore.Design` | Migrations |
|
|
289
|
+
| `Microsoft.AspNetCore.Identity.EntityFrameworkCore` | ASP.NET Identity |
|
|
290
|
+
| `AutoMapper` | Object mapping |
|
|
291
|
+
| `Scrutor` | Assembly scanning / DI |
|
|
292
|
+
| `Scalar.AspNetCore` | OpenAPI UI |
|
|
293
|
+
|
|
294
|
+
### PHP (Laravel)
|
|
295
|
+
|
|
296
|
+
Auto‑installed during `--init`:
|
|
297
|
+
|
|
298
|
+
| Package | Purpose |
|
|
299
|
+
|---------|---------|
|
|
300
|
+
| `laravel/framework` | Core framework (`^13.0`) |
|
|
301
|
+
| `zircote/swagger-php` | OpenAPI attribute annotations |
|
|
302
|
+
| `spatie/laravel-query-builder` | Eloquent query builder |
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## ✅ Requirements
|
|
307
|
+
|
|
308
|
+
| Dependency | Version | Required For |
|
|
309
|
+
|------------|---------|--------------|
|
|
310
|
+
| Python | 3.10+ | CLI runtime (all templates) |
|
|
311
|
+
| .NET SDK | 8.0+ | `dotnet-csharp`, `dotnet-csharp-clean-architecture` |
|
|
312
|
+
| EF Core CLI | `dotnet-ef` | `dotnet-csharp`, `dotnet-csharp-clean-architecture` |
|
|
313
|
+
| PHP | 8.5+ | `laravel-php` |
|
|
314
|
+
| Composer | — | `laravel-php` |
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 🧪 Testing
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# Full suite (148 tests)
|
|
322
|
+
python -m pytest tests/ -q
|
|
323
|
+
|
|
324
|
+
# Code‑generation only
|
|
325
|
+
python -m pytest tests/test_codegen.py -v
|
|
326
|
+
|
|
327
|
+
# With coverage
|
|
328
|
+
python -m pytest tests/ --cov=src
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## ⚙️ Workflow
|
|
334
|
+
|
|
335
|
+
```mermaid
|
|
336
|
+
flowchart LR
|
|
337
|
+
A["<b>schema.storm</b><br/>Declarative schema"] --> B("<b>main.py</b><br/>Storm CLI")
|
|
338
|
+
B --> C["<b>Parser</b><br/>AST + validation"]
|
|
339
|
+
C --> D["<b>Interpreter</b><br/>Dependency graph"]
|
|
340
|
+
D --> E{Template}
|
|
341
|
+
E -->|dotnet-csharp| F["<b>C#/.NET</b><br/>Models, DTOs, Services<br/>Controllers, Mappers<br/>DbContext, Program.cs"]
|
|
342
|
+
E -->|clean-arch| G["<b>C# Clean Arch</b><br/>Domain / Application<br/>Infrastructure / API"]
|
|
343
|
+
E -->|laravel-php| H["<b>PHP / Laravel</b><br/>Models + OA Schemas<br/>Controllers + OA Routes<br/>Services, Migrations<br/>Routes"]
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## 📄 License
|
|
349
|
+
|
|
350
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
main.py,sha256=pniCMQLb0o8Ysg8eajvGFAqkaMqbnw08Y6gFNCRQShw,27687
|
|
2
|
+
src/__init__.py,sha256=mpDvbK-NQ8BFC5jxvsOwgO7Yiv981iEBawgakO8V1nA,106
|
|
3
|
+
src/ast.py,sha256=gh2LZ-ftNidmQ9xTfWXo9TY5fhD-CvXpZ4DmOfQ4ZIk,2759
|
|
4
|
+
src/column.py,sha256=rotMxxjFnJJwfH-2z65v4MV0Z_RI4PMG6MST28nfG_U,1661
|
|
5
|
+
src/enum.py,sha256=s9vuYEO3ymC4jK7XSDm125nrSTfrGzOi6EinZyRRBo4,428
|
|
6
|
+
src/error_handler.py,sha256=7xDWk4OTM-wls7GE_rA4FrAwx7Cbe5XC3gaOTPLiKbA,1073
|
|
7
|
+
src/generic_controller_csharp.py,sha256=PAaOIxAifdOtpuk4jWH-q54Zi_zCv9QuD9wx9hFqyfs,3778
|
|
8
|
+
src/generic_mapper_csharp.py,sha256=9ffZDqBSBj1l_NvJiW3CrLVh5vHb4oig-agceI84R6Y,769
|
|
9
|
+
src/generic_pagination_csharp.py,sha256=BspY1HSDqYdWJAHFec0GYXBVif2of-iUqhijcqY9eag,1151
|
|
10
|
+
src/generic_query_chsarp.py,sha256=0oBJpj0n5jJrPwuGvaDAmUZpqSfD7y1i9ROvbq6gKlU,245
|
|
11
|
+
src/generic_service_csharp.py,sha256=mP-W0_qxXd2D3yh21nuXujN_AOpCvoDmPjcsLhfV1MU,3439
|
|
12
|
+
src/interpreter.py,sha256=GKsXDV69J-a2MEoTO-Dens2xSwBk2hbIVjGVykX6VwM,90689
|
|
13
|
+
src/keyword.py,sha256=U8Rtbhp0rPleUFG-M6ZNQpMEs6P63mqQqdo4sHKC_Tg,317
|
|
14
|
+
src/parser.py,sha256=jlJrNl-WomFB7RgYbiR5nzLQbrAL4dazlUWEtAcJ_3g,17528
|
|
15
|
+
src/pos.py,sha256=nT8xHkcLotHZOIkcA3DXtPMzL3j1SUd9iC0HNhUUZnY,193
|
|
16
|
+
src/table.py,sha256=Q1rIc6NqRuniPQCPRSdq3N3l2PmJvvPFnjp2iJEzppI,1044
|
|
17
|
+
src/template.py,sha256=fUXoRlstCakmE-qBmXeF8rxjUTuOdDfhBqCuyBxUuEw,248
|
|
18
|
+
src/tok.py,sha256=rQEDwC0PpjZk3jfVsx-czVInoeachpXwOBW3symVbvE,333
|
|
19
|
+
src/tok_type.py,sha256=DDd7PEvLK7smxHBtjcD09B86rtBAZCle703mXd7mME0,225
|
|
20
|
+
src/tokenizer.py,sha256=6SwzB8Vpm9TAufrr6fuVbvM8YCAvMWUBskByXGE1Mb8,8755
|
|
21
|
+
storm_cli-1.0.0.dist-info/licenses/LICENSE,sha256=SKXpqq-gsi9MLS_Esr3EurgrXMSRpJ-souhlBwSvGH0,1100
|
|
22
|
+
storm_cli-1.0.0.dist-info/METADATA,sha256=RMBdFpVVN8Hwh9FqT9Pw8X_GARVt5lobLw-qjKc5h78,13166
|
|
23
|
+
storm_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
24
|
+
storm_cli-1.0.0.dist-info/entry_points.txt,sha256=FklyLtoCjIpodgWpT2LW99CzgKnXqQKlncyVeLgHjkQ,36
|
|
25
|
+
storm_cli-1.0.0.dist-info/top_level.txt,sha256=7dQR-Fa4VIhfnQo3p1CubPBIGo7YRYTo2kAuwuj_HuI,9
|
|
26
|
+
storm_cli-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Philipp Andrew Redondo
|
|
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.
|