jx 0.2.0__py3-none-any.whl → 0.3.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.
- jx/__init__.py +2 -1
- jx/attrs.py +57 -25
- jx/catalog.py +94 -42
- jx/component.py +3 -6
- jx/exceptions.py +3 -1
- jx/meta.py +4 -3
- jx/parser.py +1 -1
- jx/utils.py +2 -1
- {jx-0.2.0.dist-info → jx-0.3.0.dist-info}/METADATA +7 -10
- jx-0.3.0.dist-info/RECORD +13 -0
- jx-0.2.0.dist-info/RECORD +0 -13
- {jx-0.2.0.dist-info → jx-0.3.0.dist-info}/WHEEL +0 -0
- {jx-0.2.0.dist-info → jx-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {jx-0.2.0.dist-info → jx-0.3.0.dist-info}/top_level.txt +0 -0
jx/__init__.py
CHANGED
jx/attrs.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
2
|
+
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import typing as t
|
|
6
6
|
from collections import UserString
|
|
7
7
|
from functools import cached_property
|
|
@@ -14,10 +14,6 @@ CLASS_ALT_KEY = "classes"
|
|
|
14
14
|
CLASS_KEYS = (CLASS_KEY, CLASS_ALT_KEY)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def split(ssl: str) -> list[str]:
|
|
18
|
-
return re.split(r"\s+", ssl.strip())
|
|
19
|
-
|
|
20
|
-
|
|
21
17
|
def quote(text: str) -> str:
|
|
22
18
|
if '"' in text:
|
|
23
19
|
if "'" in text:
|
|
@@ -45,6 +41,10 @@ class LazyString(UserString):
|
|
|
45
41
|
|
|
46
42
|
|
|
47
43
|
class Attrs:
|
|
44
|
+
__classes: tuple[str, ...]
|
|
45
|
+
__attributes: dict[str, str | LazyString]
|
|
46
|
+
__properties: set[str]
|
|
47
|
+
|
|
48
48
|
def __init__(self, attrs: "dict[str, t.Any| LazyString]") -> None:
|
|
49
49
|
"""
|
|
50
50
|
Contains all the HTML attributes/properties (a property is an
|
|
@@ -62,11 +62,15 @@ class Attrs:
|
|
|
62
62
|
attributes: "dict[str, str | LazyString]" = {}
|
|
63
63
|
properties: set[str] = set()
|
|
64
64
|
|
|
65
|
-
class_names =
|
|
65
|
+
class_names = (" ".join([
|
|
66
66
|
str(attrs.pop(CLASS_KEY, "")),
|
|
67
67
|
str(attrs.get(CLASS_ALT_KEY, "")),
|
|
68
|
-
]))
|
|
69
|
-
|
|
68
|
+
])).strip().split()
|
|
69
|
+
classes = []
|
|
70
|
+
for name in class_names:
|
|
71
|
+
if name and name not in classes:
|
|
72
|
+
classes.append(name)
|
|
73
|
+
self.__classes = tuple(classes)
|
|
70
74
|
|
|
71
75
|
for name, value in attrs.items():
|
|
72
76
|
if name.startswith("_"):
|
|
@@ -83,7 +87,7 @@ class Attrs:
|
|
|
83
87
|
@property
|
|
84
88
|
def classes(self) -> str:
|
|
85
89
|
"""
|
|
86
|
-
All the HTML classes
|
|
90
|
+
All the HTML classes separated by a space.
|
|
87
91
|
|
|
88
92
|
Example:
|
|
89
93
|
|
|
@@ -91,11 +95,11 @@ class Attrs:
|
|
|
91
95
|
attrs = Attrs({"class": "italic bold bg-blue wide abcde"})
|
|
92
96
|
attrs.set(class="bold text-white")
|
|
93
97
|
print(attrs.classes)
|
|
94
|
-
|
|
98
|
+
italic bold bg-blue wide abcde text-white
|
|
95
99
|
```
|
|
96
100
|
|
|
97
101
|
"""
|
|
98
|
-
return " ".join(
|
|
102
|
+
return " ".join(self.__classes)
|
|
99
103
|
|
|
100
104
|
@property
|
|
101
105
|
def as_dict(self) -> dict[str, t.Any]:
|
|
@@ -116,7 +120,7 @@ class Attrs:
|
|
|
116
120
|
attrs.as_dict
|
|
117
121
|
{
|
|
118
122
|
"aria_label": "hello",
|
|
119
|
-
"class": "ipsum
|
|
123
|
+
"class": "lorem ipsum",
|
|
120
124
|
"id": "world",
|
|
121
125
|
"data_test": True,
|
|
122
126
|
"hidden": True
|
|
@@ -155,8 +159,6 @@ class Attrs:
|
|
|
155
159
|
- The underscores in the names will be translated automatically to dashes,
|
|
156
160
|
so `aria_selected` becomes the attribute `aria-selected`.
|
|
157
161
|
|
|
158
|
-
TODO: vue-style
|
|
159
|
-
|
|
160
162
|
Example:
|
|
161
163
|
|
|
162
164
|
```python
|
|
@@ -172,7 +174,7 @@ class Attrs:
|
|
|
172
174
|
attrs = Attrs({"class": "b c a"})
|
|
173
175
|
attrs.set(class="c b f d e")
|
|
174
176
|
attrs.as_dict
|
|
175
|
-
{"class": "
|
|
177
|
+
{"class": "b c a f d e"}
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
"""
|
|
@@ -212,7 +214,9 @@ class Attrs:
|
|
|
212
214
|
continue
|
|
213
215
|
|
|
214
216
|
if name in CLASS_KEYS:
|
|
215
|
-
self.
|
|
217
|
+
if not self.__classes:
|
|
218
|
+
self.add_class(value)
|
|
219
|
+
continue
|
|
216
220
|
|
|
217
221
|
name = name.replace("_", "-")
|
|
218
222
|
if name not in self.__attributes:
|
|
@@ -220,10 +224,10 @@ class Attrs:
|
|
|
220
224
|
|
|
221
225
|
def add_class(self, *values: str) -> None:
|
|
222
226
|
"""
|
|
223
|
-
Adds one or more classes to the list of classes,
|
|
227
|
+
Adds one or more classes to the end of the list of classes,
|
|
228
|
+
if not already present.
|
|
224
229
|
|
|
225
230
|
Arguments:
|
|
226
|
-
|
|
227
231
|
values:
|
|
228
232
|
One or more class names to add, separated by spaces.
|
|
229
233
|
|
|
@@ -231,15 +235,44 @@ class Attrs:
|
|
|
231
235
|
|
|
232
236
|
```python
|
|
233
237
|
attrs = Attrs({"class": "a b c"})
|
|
234
|
-
attrs.add_class("c
|
|
238
|
+
attrs.add_class("c d")
|
|
235
239
|
attrs.as_dict
|
|
236
240
|
{"class": "a b c d"}
|
|
237
241
|
```
|
|
238
242
|
|
|
239
243
|
"""
|
|
240
244
|
for names in values:
|
|
241
|
-
for name in split(
|
|
242
|
-
self.__classes
|
|
245
|
+
for name in names.strip().split():
|
|
246
|
+
if name not in self.__classes:
|
|
247
|
+
self.__classes += (name,)
|
|
248
|
+
|
|
249
|
+
def prepend_class(self, *values: str) -> None:
|
|
250
|
+
"""
|
|
251
|
+
Adds one or more classes to the beginning of the list of classes,
|
|
252
|
+
if not already present.
|
|
253
|
+
|
|
254
|
+
Arguments:
|
|
255
|
+
values:
|
|
256
|
+
One or more class names to add, separated by spaces.
|
|
257
|
+
|
|
258
|
+
Example:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
attrs = Attrs({"class": "a b c"})
|
|
262
|
+
attrs.add_class("c d |")
|
|
263
|
+
attrs.as_dict
|
|
264
|
+
{"class": "d | a b c"}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
new_classes = [
|
|
269
|
+
name
|
|
270
|
+
for names in values
|
|
271
|
+
for name in names.strip().split()
|
|
272
|
+
if name not in self.__classes
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
self.__classes = tuple(new_classes) + self.__classes
|
|
243
276
|
|
|
244
277
|
def remove_class(self, *names: str) -> None:
|
|
245
278
|
"""
|
|
@@ -255,8 +288,7 @@ class Attrs:
|
|
|
255
288
|
```
|
|
256
289
|
|
|
257
290
|
"""
|
|
258
|
-
for
|
|
259
|
-
self.__classes.remove(name)
|
|
291
|
+
self.__classes = tuple(c for c in self.__classes if c not in names)
|
|
260
292
|
|
|
261
293
|
def get(self, name: str, default: t.Any = None) -> t.Any:
|
|
262
294
|
"""
|
|
@@ -358,7 +390,7 @@ class Attrs:
|
|
|
358
390
|
Removes an attribute or property.
|
|
359
391
|
"""
|
|
360
392
|
if name in CLASS_KEYS:
|
|
361
|
-
self.__classes =
|
|
393
|
+
self.__classes = ()
|
|
362
394
|
if name in self.__attributes:
|
|
363
395
|
del self.__attributes[name]
|
|
364
396
|
if name in self.__properties:
|
jx/catalog.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
2
|
+
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import typing as t
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from pathlib import Path
|
|
@@ -23,7 +24,7 @@ class CData:
|
|
|
23
24
|
mtime: float
|
|
24
25
|
code: CodeType | None = None
|
|
25
26
|
required: tuple[str, ...] = ()
|
|
26
|
-
optional: dict[str, t.Any] = field(default_factory=dict)
|
|
27
|
+
optional: dict[str, t.Any] = field(default_factory=dict) # { attr: default_value }
|
|
27
28
|
imports: dict[str, str] = field(default_factory=dict) # { name: relpath }
|
|
28
29
|
css: tuple[str, ...] = ()
|
|
29
30
|
js: tuple[str, ...] = ()
|
|
@@ -31,8 +32,9 @@ class CData:
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class Catalog:
|
|
35
|
+
|
|
34
36
|
# IDEA: This dict could be replaced by a dict-like object
|
|
35
|
-
# that
|
|
37
|
+
# that uses a LRU cache (to limit the memory used)
|
|
36
38
|
# or even a shared Redis/Memcache cache.
|
|
37
39
|
components: dict[str, CData]
|
|
38
40
|
|
|
@@ -40,11 +42,11 @@ class Catalog:
|
|
|
40
42
|
self,
|
|
41
43
|
folder: str | Path | None = None,
|
|
42
44
|
*,
|
|
43
|
-
auto_reload: bool = True,
|
|
44
45
|
jinja_env: jinja2.Environment | None = None,
|
|
46
|
+
extensions: list | None = None,
|
|
45
47
|
filters: dict[str, t.Any] | None = None,
|
|
46
48
|
tests: dict[str, t.Any] | None = None,
|
|
47
|
-
|
|
49
|
+
auto_reload: bool = True,
|
|
48
50
|
**globals: t.Any,
|
|
49
51
|
) -> None:
|
|
50
52
|
"""
|
|
@@ -54,19 +56,19 @@ class Catalog:
|
|
|
54
56
|
folder:
|
|
55
57
|
Optional folder path to scan for components. It's a shortcut to
|
|
56
58
|
calling `add_folder` when only one is used.
|
|
57
|
-
auto_reload:
|
|
58
|
-
Whether to check the last-modified time of the components files and
|
|
59
|
-
automatically re-process them if they change. The performance impact of
|
|
60
|
-
leaving it on is minimal, but *might* be noticeable when rendering a page
|
|
61
|
-
that uses a large number of different components.
|
|
62
59
|
jinja_env:
|
|
63
60
|
Optional Jinja2 environment to use for rendering.
|
|
64
|
-
filters:
|
|
65
|
-
Optional extra Jinja2 filters to add to the environment.
|
|
66
61
|
extensions:
|
|
67
62
|
Optional extra Jinja2 extensions to add to the environment.
|
|
63
|
+
filters:
|
|
64
|
+
Optional extra Jinja2 filters to add to the environment.
|
|
68
65
|
tests:
|
|
69
66
|
Optional extra Jinja2 tests to add to the environment.
|
|
67
|
+
auto_reload:
|
|
68
|
+
Whether to check the last-modified time of the components files and
|
|
69
|
+
automatically re-process them if they change. The performance impact of
|
|
70
|
+
leaving it on is minimal, but *might* be noticeable when rendering a
|
|
71
|
+
component that uses a large number of child components.
|
|
70
72
|
**globals:
|
|
71
73
|
Variables to make available to all components by default.
|
|
72
74
|
|
|
@@ -84,11 +86,7 @@ class Catalog:
|
|
|
84
86
|
self.add_folder(folder)
|
|
85
87
|
|
|
86
88
|
def add_folder(
|
|
87
|
-
self,
|
|
88
|
-
path: str | Path,
|
|
89
|
-
*,
|
|
90
|
-
prefix: str = "",
|
|
91
|
-
preload: bool = True
|
|
89
|
+
self, path: str | Path, *, prefix: str = "", preload: bool = True
|
|
92
90
|
) -> None:
|
|
93
91
|
"""
|
|
94
92
|
Add a folder path from which to search for components, optionally under a prefix.
|
|
@@ -141,9 +139,7 @@ class Catalog:
|
|
|
141
139
|
logger.debug(f"Component already exists: {relpath}")
|
|
142
140
|
continue
|
|
143
141
|
cdata = CData(
|
|
144
|
-
base_path=base_path,
|
|
145
|
-
path=filepath,
|
|
146
|
-
mtime=filepath.stat().st_mtime
|
|
142
|
+
base_path=base_path, path=filepath, mtime=filepath.stat().st_mtime
|
|
147
143
|
)
|
|
148
144
|
self.components[relpath] = cdata
|
|
149
145
|
|
|
@@ -151,7 +147,9 @@ class Catalog:
|
|
|
151
147
|
for relpath in self.components:
|
|
152
148
|
self.components[relpath] = self.get_component_data(relpath)
|
|
153
149
|
|
|
154
|
-
def render(
|
|
150
|
+
def render(
|
|
151
|
+
self, relpath: str, globals: dict[str, t.Any] | None = None, **kwargs
|
|
152
|
+
) -> str:
|
|
155
153
|
"""
|
|
156
154
|
Render a component with the given relative path and context.
|
|
157
155
|
|
|
@@ -173,6 +171,64 @@ class Catalog:
|
|
|
173
171
|
relpath = relpath.replace("\\", "/").strip("/")
|
|
174
172
|
co = self.get_component(relpath)
|
|
175
173
|
|
|
174
|
+
globals = globals or {}
|
|
175
|
+
globals.update(
|
|
176
|
+
{
|
|
177
|
+
"assets": {
|
|
178
|
+
"css": co.collect_css,
|
|
179
|
+
"js": co.collect_js,
|
|
180
|
+
"render_css": co.render_css,
|
|
181
|
+
"render_js": co.render_js,
|
|
182
|
+
"render": co.render_assets,
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
co.globals = globals
|
|
187
|
+
|
|
188
|
+
return co.render(**kwargs)
|
|
189
|
+
|
|
190
|
+
def render_string(
|
|
191
|
+
self, source: str, globals: dict[str, t.Any] | None = None, **kwargs
|
|
192
|
+
) -> str:
|
|
193
|
+
"""
|
|
194
|
+
Render a component from a string source.
|
|
195
|
+
Works like `render`, but the component is not cached and cannot do relative imports.
|
|
196
|
+
|
|
197
|
+
Arguments:
|
|
198
|
+
source:
|
|
199
|
+
The Jinja2 source code of the component to render.
|
|
200
|
+
globals:
|
|
201
|
+
Optional global variables to make available to the component and all its
|
|
202
|
+
imported components.
|
|
203
|
+
**kwargs:
|
|
204
|
+
Keyword arguments to pass to the component.
|
|
205
|
+
They will be available in the component's context but not to its imported components.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
The rendered component as a string.
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
meta = extract_metadata(source, base_path=Path(), fullpath=Path())
|
|
212
|
+
name = "<string>"
|
|
213
|
+
|
|
214
|
+
parser = JxParser(name=name, source=source, components=list(meta.imports.keys()))
|
|
215
|
+
parsed_source, slots = parser.parse()
|
|
216
|
+
|
|
217
|
+
code = self.jinja_env.compile(source=parsed_source, name=name, filename=name)
|
|
218
|
+
tmpl = jinja2.Template.from_code(self.jinja_env, code, self.jinja_env.globals)
|
|
219
|
+
|
|
220
|
+
co = Component(
|
|
221
|
+
relpath=name,
|
|
222
|
+
tmpl=tmpl,
|
|
223
|
+
get_component=self.get_component,
|
|
224
|
+
required=meta.required,
|
|
225
|
+
optional=meta.optional,
|
|
226
|
+
imports=meta.imports,
|
|
227
|
+
css=meta.css,
|
|
228
|
+
js=meta.js,
|
|
229
|
+
slots=slots,
|
|
230
|
+
)
|
|
231
|
+
|
|
176
232
|
globals = globals or {}
|
|
177
233
|
globals.update({
|
|
178
234
|
"assets": {
|
|
@@ -181,7 +237,7 @@ class Catalog:
|
|
|
181
237
|
"render_css": co.render_css,
|
|
182
238
|
"render_js": co.render_js,
|
|
183
239
|
"render": co.render_assets,
|
|
184
|
-
}
|
|
240
|
+
}
|
|
185
241
|
})
|
|
186
242
|
co.globals = globals
|
|
187
243
|
|
|
@@ -189,8 +245,8 @@ class Catalog:
|
|
|
189
245
|
|
|
190
246
|
def get_component_data(self, relpath: str) -> CData:
|
|
191
247
|
"""
|
|
192
|
-
Get the component data from the cache
|
|
193
|
-
|
|
248
|
+
Get the component data from the cache.
|
|
249
|
+
If the file has been updated, the component is re-processed.
|
|
194
250
|
|
|
195
251
|
Arguments:
|
|
196
252
|
relpath:
|
|
@@ -214,15 +270,11 @@ class Catalog:
|
|
|
214
270
|
meta = extract_metadata(source, base_path=cdata.base_path, fullpath=cdata.path)
|
|
215
271
|
|
|
216
272
|
parser = JxParser(
|
|
217
|
-
name=relpath,
|
|
218
|
-
source=source,
|
|
219
|
-
components=list(meta.imports.keys())
|
|
273
|
+
name=relpath, source=source, components=list(meta.imports.keys())
|
|
220
274
|
)
|
|
221
275
|
parsed_source, slots = parser.parse()
|
|
222
276
|
code = self.jinja_env.compile(
|
|
223
|
-
source=parsed_source,
|
|
224
|
-
name=relpath,
|
|
225
|
-
filename=cdata.path.as_posix()
|
|
277
|
+
source=parsed_source, name=relpath, filename=cdata.path.as_posix()
|
|
226
278
|
)
|
|
227
279
|
|
|
228
280
|
cdata.code = code
|
|
@@ -247,9 +299,7 @@ class Catalog:
|
|
|
247
299
|
cdata = self.get_component_data(relpath)
|
|
248
300
|
assert cdata.code is not None
|
|
249
301
|
tmpl = jinja2.Template.from_code(
|
|
250
|
-
self.jinja_env,
|
|
251
|
-
cdata.code,
|
|
252
|
-
self.jinja_env.globals
|
|
302
|
+
self.jinja_env, cdata.code, self.jinja_env.globals
|
|
253
303
|
)
|
|
254
304
|
|
|
255
305
|
co = Component(
|
|
@@ -279,9 +329,6 @@ class Catalog:
|
|
|
279
329
|
"""
|
|
280
330
|
Create a new Jinja2 environment with the specified settings.
|
|
281
331
|
|
|
282
|
-
If an existing environment is provided, an "overlay" of it will
|
|
283
|
-
be created and used.
|
|
284
|
-
|
|
285
332
|
Arguments:
|
|
286
333
|
jinja_env:
|
|
287
334
|
Optional Jinja2 environment to use as a base.
|
|
@@ -297,16 +344,21 @@ class Catalog:
|
|
|
297
344
|
"""
|
|
298
345
|
jinja_env = jinja_env or getattr(self, "jinja_env", None)
|
|
299
346
|
if jinja_env:
|
|
300
|
-
|
|
347
|
+
# It could be `jinja_env.overlay()` instead, but that might
|
|
348
|
+
# might lead to confusion if the user expects changes
|
|
349
|
+
# to the original environment to be reflected here.
|
|
350
|
+
env = jinja_env
|
|
301
351
|
else:
|
|
302
352
|
env = jinja2.Environment()
|
|
303
353
|
|
|
304
354
|
globals = globals or {}
|
|
305
|
-
globals.update(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
355
|
+
globals.update(
|
|
356
|
+
{
|
|
357
|
+
# A unique ID generator for HTML elements, see `utils.get_random_id`
|
|
358
|
+
# docstring for more information.
|
|
359
|
+
"_get_random_id": utils.get_random_id,
|
|
360
|
+
}
|
|
361
|
+
)
|
|
310
362
|
env.globals.update(globals)
|
|
311
363
|
|
|
312
364
|
filters = filters or {}
|
jx/component.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
2
|
+
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import typing as t
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
|
|
@@ -12,9 +12,6 @@ from .attrs import Attrs
|
|
|
12
12
|
from .exceptions import MissingRequiredArgument
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
rx_external_url = re.compile(r"^[a-z]+://", re.IGNORECASE)
|
|
16
|
-
|
|
17
|
-
|
|
18
15
|
class Component:
|
|
19
16
|
__slots__ = (
|
|
20
17
|
"relpath",
|
|
@@ -85,7 +82,7 @@ class Component:
|
|
|
85
82
|
content: str | None = None,
|
|
86
83
|
attrs: Attrs | dict[str, t.Any] | None = None,
|
|
87
84
|
caller: Callable[[str], str] | None = None,
|
|
88
|
-
**params: t.Any
|
|
85
|
+
**params: t.Any,
|
|
89
86
|
) -> Markup:
|
|
90
87
|
content = content if content is not None else caller("") if caller else ""
|
|
91
88
|
attrs = attrs.as_dict if isinstance(attrs, Attrs) else attrs or {}
|
jx/exceptions.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
2
|
+
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
class JxException(Exception):
|
|
6
7
|
"""Base class for all Jx exceptions."""
|
|
7
8
|
|
|
@@ -18,6 +19,7 @@ class ImportError(JxException):
|
|
|
18
19
|
Raised when an import fails.
|
|
19
20
|
This is usually caused by a missing or inaccessible component.
|
|
20
21
|
"""
|
|
22
|
+
|
|
21
23
|
def __init__(self, relpath: str, **kw) -> None:
|
|
22
24
|
msg = f"Component not found: {relpath}"
|
|
23
25
|
super().__init__(msg, **kw)
|
jx/meta.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
2
|
+
Jx | Copyright (c) Juan-Pablo Scaletti
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import ast
|
|
5
6
|
import re
|
|
6
7
|
import typing as t
|
|
@@ -56,9 +57,9 @@ def extract_metadata(source: str, base_path: Path, fullpath: Path) -> Meta:
|
|
|
56
57
|
source:
|
|
57
58
|
The template source code.
|
|
58
59
|
base_path:
|
|
59
|
-
Absolute base path for all the template files
|
|
60
|
+
Absolute base path for all the template files, for relative imports.
|
|
60
61
|
fullpath:
|
|
61
|
-
The absolute full path of the current template.
|
|
62
|
+
The absolute full path of the current template, for relative imports.
|
|
62
63
|
|
|
63
64
|
Returns:
|
|
64
65
|
A `Meta` object containing the extracted metadata.
|
jx/parser.py
CHANGED
jx/utils.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Replace your HTML templates with Python server-Side components
|
|
5
5
|
Author-email: Juan Pablo Scaletti <juanpablo@jpscaletti.com>
|
|
6
|
-
Project-URL:
|
|
7
|
-
Project-URL:
|
|
8
|
-
Project-URL: documentation, https://jx.scaletti.dev/
|
|
6
|
+
Project-URL: Homepage, https://jx.scaletti.dev/
|
|
7
|
+
Project-URL: GitHub, https://github.com/jpsca/jx
|
|
9
8
|
Classifier: Development Status :: 4 - Beta
|
|
10
9
|
Classifier: Environment :: Web Environment
|
|
11
10
|
Classifier: Intended Audience :: Developers
|
|
@@ -19,23 +18,21 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
19
18
|
Classifier: Topic :: Software Development :: User Interfaces
|
|
20
19
|
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
21
20
|
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: <4,>=3.
|
|
21
|
+
Requires-Python: <4,>=3.12
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
License-File: LICENSE
|
|
25
24
|
Requires-Dist: jinja2>=3.0
|
|
26
|
-
Requires-Dist: markupsafe>=2.0
|
|
27
|
-
Requires-Dist: ty>=0.0.1a15
|
|
28
25
|
Dynamic: license-file
|
|
29
26
|
|
|
30
27
|
<h1>
|
|
31
|
-
<img src="
|
|
28
|
+
<img src="./docs/logo-jx.png" height="100" align="top">
|
|
32
29
|
</h1>
|
|
33
30
|
|
|
34
31
|
Super components powers for your Jinja templates.
|
|
35
32
|
|
|
36
33
|
<p>
|
|
37
|
-
<img alt="python: 3.11, 3.12, 3.13, 3.14" src="
|
|
38
|
-
<img alt="license: MIT" src="
|
|
34
|
+
<img alt="python: 3.11, 3.12, 3.13, 3.14" src="./docs/python.svg">
|
|
35
|
+
<img alt="license: MIT" src="./docs/license.svg">
|
|
39
36
|
</p>
|
|
40
37
|
|
|
41
38
|
From chaos to clarity: The power of components in your server-side-rendered Python web app.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
jx/__init__.py,sha256=f05q3I3xvO72xqVOhtWI_KMSVa0vACbGsK2eMrhzO1g,126
|
|
2
|
+
jx/attrs.py,sha256=3cSSJb-idpD2CdhA_BZLX-OId1t8c2nzJp0jSJpTjvs,11344
|
|
3
|
+
jx/catalog.py,sha256=6NxK88krwH0Aw86SeRSW4Ws7pVENNu9dCtrAFYVKg5o,13512
|
|
4
|
+
jx/component.py,sha256=04Ic7wGm65uUPAiVGhz8YbD2orBbasKd1dFo4FtbIv4,7004
|
|
5
|
+
jx/exceptions.py,sha256=eQVCjt49FOgOjdGPPAhU4eIgBvumQ6BXmTh_siYpyIg,1493
|
|
6
|
+
jx/meta.py,sha256=nKSeZzgdtJEjPoc7PaYGhBkBbwFRC97XZc_GUMBndPI,5167
|
|
7
|
+
jx/parser.py,sha256=fLDIBqt1hAzCOkRMZoOsAJOswtrw7PE3EV9ztw1K358,12841
|
|
8
|
+
jx/utils.py,sha256=BTxDPhiWuKHRcVCND07OGkYLhzPp9G6iDgDC7H2o4Lo,643
|
|
9
|
+
jx-0.3.0.dist-info/licenses/LICENSE,sha256=RHwNifuIFfQM9QUhA2FQfnqlBcnhBHlJEVp8QcRllew,1076
|
|
10
|
+
jx-0.3.0.dist-info/METADATA,sha256=Dt5KJ7rJoGa7UXVRDq1ZEf_1LZQQO_BIbuDHoeIYAGo,2540
|
|
11
|
+
jx-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
jx-0.3.0.dist-info/top_level.txt,sha256=P61YQxqfmzVpxTMe3C48gt0vc6fnHLF8Ml0JXC-QuEI,3
|
|
13
|
+
jx-0.3.0.dist-info/RECORD,,
|
jx-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
jx/__init__.py,sha256=MZ7KbNZkQx2D5CJpccSKuyJi179iAff3a9jG7DwEBAE,152
|
|
2
|
-
jx/attrs.py,sha256=_Cj1s4PEHiq_TiG2IvKiUkTqyThH4YgS4HwUn09zzGU,10414
|
|
3
|
-
jx/catalog.py,sha256=EaG0jNqf4JJkif9Nrnp72vEr8kX1BslJGRig9pOGgC8,11567
|
|
4
|
-
jx/component.py,sha256=9TkyX10brWRCDw6AC0tEJ70DWaDmkl_UE8i_Uqfta6M,7100
|
|
5
|
-
jx/exceptions.py,sha256=rDWxkgOqeMPptmQ21joB2ujfpIU5EwMKtAL-ImB9nuA,1518
|
|
6
|
-
jx/meta.py,sha256=lh3EFyp2_Y0R80aXaT3kVPNPcB3CREdHHePdQP4ZnR4,5148
|
|
7
|
-
jx/parser.py,sha256=LW1iHh5_Ac8B368RhfiBPe3w7_1tdLf5LjCl3rfNKu8,12868
|
|
8
|
-
jx/utils.py,sha256=dZMIAdVLdmkyPsiX14a9aIkZRfgAnf0SNEnvfyg8K98,669
|
|
9
|
-
jx-0.2.0.dist-info/licenses/LICENSE,sha256=RHwNifuIFfQM9QUhA2FQfnqlBcnhBHlJEVp8QcRllew,1076
|
|
10
|
-
jx-0.2.0.dist-info/METADATA,sha256=F0kiqI8PoNntEX_EuiOkdu9OzHbHiaYZ8eWsvAWZptA,2761
|
|
11
|
-
jx-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
jx-0.2.0.dist-info/top_level.txt,sha256=P61YQxqfmzVpxTMe3C48gt0vc6fnHLF8Ml0JXC-QuEI,3
|
|
13
|
-
jx-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|