pyview-web 0.3.0__py3-none-any.whl → 0.8.0a2__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.
- pyview/__init__.py +16 -6
- pyview/assets/js/app.js +1 -0
- pyview/assets/js/uploaders.js +221 -0
- pyview/assets/package-lock.json +16 -14
- pyview/assets/package.json +2 -2
- pyview/async_stream_runner.py +2 -1
- pyview/auth/__init__.py +3 -1
- pyview/auth/provider.py +6 -6
- pyview/auth/required.py +7 -10
- pyview/binding/__init__.py +47 -0
- pyview/binding/binder.py +134 -0
- pyview/binding/context.py +33 -0
- pyview/binding/converters.py +191 -0
- pyview/binding/helpers.py +78 -0
- pyview/binding/injectables.py +119 -0
- pyview/binding/params.py +105 -0
- pyview/binding/result.py +32 -0
- pyview/changesets/__init__.py +2 -0
- pyview/changesets/changesets.py +8 -3
- pyview/cli/commands/create_view.py +4 -3
- pyview/cli/main.py +1 -1
- pyview/components/__init__.py +72 -0
- pyview/components/base.py +212 -0
- pyview/components/lifecycle.py +85 -0
- pyview/components/manager.py +366 -0
- pyview/components/renderer.py +14 -0
- pyview/components/slots.py +73 -0
- pyview/csrf.py +4 -2
- pyview/events/AutoEventDispatch.py +98 -0
- pyview/events/BaseEventHandler.py +51 -8
- pyview/events/__init__.py +2 -1
- pyview/instrumentation/__init__.py +3 -3
- pyview/instrumentation/interfaces.py +57 -33
- pyview/instrumentation/noop.py +21 -18
- pyview/js.py +20 -23
- pyview/live_routes.py +5 -3
- pyview/live_socket.py +167 -44
- pyview/live_view.py +24 -12
- pyview/meta.py +14 -2
- pyview/phx_message.py +7 -8
- pyview/playground/__init__.py +10 -0
- pyview/playground/builder.py +118 -0
- pyview/playground/favicon.py +39 -0
- pyview/pyview.py +54 -20
- pyview/session.py +2 -0
- pyview/static/assets/app.js +2088 -806
- pyview/static/assets/uploaders.js +221 -0
- pyview/stream.py +308 -0
- pyview/template/__init__.py +11 -1
- pyview/template/live_template.py +12 -8
- pyview/template/live_view_template.py +338 -0
- pyview/template/render_diff.py +33 -7
- pyview/template/root_template.py +21 -9
- pyview/template/serializer.py +2 -5
- pyview/template/template_view.py +170 -0
- pyview/template/utils.py +3 -2
- pyview/uploads.py +344 -55
- pyview/vendor/flet/pubsub/__init__.py +3 -1
- pyview/vendor/flet/pubsub/pub_sub.py +10 -18
- pyview/vendor/ibis/__init__.py +3 -7
- pyview/vendor/ibis/compiler.py +25 -32
- pyview/vendor/ibis/context.py +13 -15
- pyview/vendor/ibis/errors.py +0 -6
- pyview/vendor/ibis/filters.py +70 -76
- pyview/vendor/ibis/loaders.py +6 -7
- pyview/vendor/ibis/nodes.py +40 -42
- pyview/vendor/ibis/template.py +4 -5
- pyview/vendor/ibis/tree.py +62 -3
- pyview/vendor/ibis/utils.py +14 -15
- pyview/ws_handler.py +116 -86
- {pyview_web-0.3.0.dist-info → pyview_web-0.8.0a2.dist-info}/METADATA +39 -33
- pyview_web-0.8.0a2.dist-info/RECORD +80 -0
- pyview_web-0.8.0a2.dist-info/WHEEL +4 -0
- pyview_web-0.8.0a2.dist-info/entry_points.txt +3 -0
- pyview_web-0.3.0.dist-info/LICENSE +0 -21
- pyview_web-0.3.0.dist-info/RECORD +0 -58
- pyview_web-0.3.0.dist-info/WHEEL +0 -4
- pyview_web-0.3.0.dist-info/entry_points.txt +0 -3
pyview/vendor/ibis/compiler.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
from . import nodes
|
|
2
|
-
from . import errors
|
|
3
|
-
|
|
1
|
+
from . import errors, nodes
|
|
4
2
|
|
|
5
3
|
# Token delimiters.
|
|
6
|
-
comment_start =
|
|
7
|
-
comment_end =
|
|
8
|
-
print_start =
|
|
9
|
-
print_end =
|
|
10
|
-
eprint_start =
|
|
11
|
-
eprint_end =
|
|
12
|
-
instruction_start =
|
|
13
|
-
instruction_end =
|
|
4
|
+
comment_start = "{#"
|
|
5
|
+
comment_end = "#}"
|
|
6
|
+
print_start = "{{"
|
|
7
|
+
print_end = "}}"
|
|
8
|
+
eprint_start = "{$"
|
|
9
|
+
eprint_end = "$}"
|
|
10
|
+
instruction_start = "{%"
|
|
11
|
+
instruction_end = "%}"
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
# Returns the root node of the compiled node tree.
|
|
@@ -20,10 +18,9 @@ def compile(template_string, template_id):
|
|
|
20
18
|
|
|
21
19
|
# Tokens come in four different types: TEXT, PRINT, EPRINT, and INSTRUCTION.
|
|
22
20
|
class Token:
|
|
23
|
-
|
|
24
21
|
def __init__(self, token_type, token_text, template_id, line_number):
|
|
25
22
|
words = token_text.split()
|
|
26
|
-
self.keyword = words[0] if words else
|
|
23
|
+
self.keyword = words[0] if words else ""
|
|
27
24
|
self.type = token_type
|
|
28
25
|
self.text = token_text
|
|
29
26
|
self.template_id = template_id
|
|
@@ -35,7 +32,6 @@ class Token:
|
|
|
35
32
|
|
|
36
33
|
# The Lexer takes a template string as input and chops it into a list of Tokens.
|
|
37
34
|
class Lexer:
|
|
38
|
-
|
|
39
35
|
def __init__(self, template_string, template_id):
|
|
40
36
|
self.template_string = template_string
|
|
41
37
|
self.template_id = template_id
|
|
@@ -63,7 +59,7 @@ class Lexer:
|
|
|
63
59
|
return False
|
|
64
60
|
|
|
65
61
|
def advance(self):
|
|
66
|
-
if self.template_string[self.index] ==
|
|
62
|
+
if self.template_string[self.index] == "\n":
|
|
67
63
|
self.line_number += 1
|
|
68
64
|
self.index += 1
|
|
69
65
|
|
|
@@ -84,7 +80,7 @@ class Lexer:
|
|
|
84
80
|
start_line_number = self.line_number
|
|
85
81
|
while self.index < len(self.template_string):
|
|
86
82
|
if self.match(eprint_end):
|
|
87
|
-
text = self.template_string[start_index:self.index].strip()
|
|
83
|
+
text = self.template_string[start_index : self.index].strip()
|
|
88
84
|
self.tokens.append(Token("EPRINT", text, self.template_id, start_line_number))
|
|
89
85
|
self.index += len(eprint_end)
|
|
90
86
|
return
|
|
@@ -98,7 +94,7 @@ class Lexer:
|
|
|
98
94
|
start_line_number = self.line_number
|
|
99
95
|
while self.index < len(self.template_string):
|
|
100
96
|
if self.match(print_end):
|
|
101
|
-
text = self.template_string[start_index:self.index].strip()
|
|
97
|
+
text = self.template_string[start_index : self.index].strip()
|
|
102
98
|
self.tokens.append(Token("PRINT", text, self.template_id, start_line_number))
|
|
103
99
|
self.index += len(print_end)
|
|
104
100
|
return
|
|
@@ -112,35 +108,33 @@ class Lexer:
|
|
|
112
108
|
start_line_number = self.line_number
|
|
113
109
|
while self.index < len(self.template_string):
|
|
114
110
|
if self.match(instruction_end):
|
|
115
|
-
text = self.template_string[start_index:self.index].strip()
|
|
111
|
+
text = self.template_string[start_index : self.index].strip()
|
|
116
112
|
self.tokens.append(Token("INSTRUCTION", text, self.template_id, start_line_number))
|
|
117
113
|
self.index += len(instruction_end)
|
|
118
114
|
return
|
|
119
115
|
self.advance()
|
|
120
|
-
msg =
|
|
116
|
+
msg = "Unclosed instruction tag."
|
|
121
117
|
raise errors.TemplateLexingError(msg, self.template_id, start_line_number)
|
|
122
118
|
|
|
123
119
|
def read_text(self):
|
|
124
120
|
start_index = self.index
|
|
125
121
|
start_line_number = self.line_number
|
|
126
122
|
while self.index < len(self.template_string):
|
|
127
|
-
if
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
elif self.match(instruction_start):
|
|
123
|
+
if (
|
|
124
|
+
self.match(comment_start)
|
|
125
|
+
or self.match(eprint_start)
|
|
126
|
+
or self.match(print_start)
|
|
127
|
+
or self.match(instruction_start)
|
|
128
|
+
):
|
|
134
129
|
break
|
|
135
130
|
self.advance()
|
|
136
|
-
text = self.template_string[start_index:self.index]
|
|
131
|
+
text = self.template_string[start_index : self.index]
|
|
137
132
|
self.tokens.append(Token("TEXT", text, self.template_id, start_line_number))
|
|
138
133
|
|
|
139
134
|
|
|
140
135
|
# The Parser takes a template string as input, lexes it into a token stream, then compiles the
|
|
141
136
|
# token stream into a tree of nodes.
|
|
142
137
|
class Parser:
|
|
143
|
-
|
|
144
138
|
def __init__(self, template_string, template_id):
|
|
145
139
|
self.template_string = template_string
|
|
146
140
|
self.template_id = template_id
|
|
@@ -173,8 +167,8 @@ class Parser:
|
|
|
173
167
|
stack[-1].exit_scope()
|
|
174
168
|
stack.pop()
|
|
175
169
|
expecting.pop()
|
|
176
|
-
elif token.keyword ==
|
|
177
|
-
msg =
|
|
170
|
+
elif token.keyword == "":
|
|
171
|
+
msg = "Empty instruction tag."
|
|
178
172
|
raise errors.TemplateSyntaxError(msg, token)
|
|
179
173
|
else:
|
|
180
174
|
msg = f"Unrecognised instruction tag '{token.keyword}'."
|
|
@@ -182,10 +176,9 @@ class Parser:
|
|
|
182
176
|
|
|
183
177
|
if expecting:
|
|
184
178
|
token = stack[-1].token
|
|
185
|
-
msg =
|
|
179
|
+
msg = "Unexpected end of template. "
|
|
186
180
|
msg += f"Ibis was expecting a closing '{expecting[-1]}' tag to close the "
|
|
187
181
|
msg += f"'{token.keyword}' tag opened in line {token.line_number}."
|
|
188
182
|
raise errors.TemplateSyntaxError(msg, token)
|
|
189
183
|
|
|
190
184
|
return stack.pop()
|
|
191
|
-
|
pyview/vendor/ibis/context.py
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from . import errors
|
|
3
2
|
|
|
3
|
+
from . import errors
|
|
4
4
|
|
|
5
5
|
# User-configurable functions and variables available in all contexts.
|
|
6
6
|
builtins = {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
"now": datetime.datetime.now,
|
|
8
|
+
"range": range,
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
# A wrapper around a stack of dictionaries.
|
|
13
13
|
class DataStack:
|
|
14
|
-
|
|
15
14
|
def __init__(self):
|
|
16
|
-
|
|
15
|
+
self.stack = []
|
|
17
16
|
|
|
18
17
|
def __getitem__(self, key):
|
|
19
18
|
for d in reversed(self.stack):
|
|
@@ -25,16 +24,17 @@ class DataStack:
|
|
|
25
24
|
# A Context object is a wrapper around the user's input data. Its `.resolve()` method contains
|
|
26
25
|
# the lookup-logic for resolving dotted variable names.
|
|
27
26
|
class Context:
|
|
28
|
-
|
|
29
27
|
def __init__(self, data_dict, strict_mode):
|
|
30
28
|
# Stack of data dictionaries for the .resolve() method.
|
|
31
29
|
self.data = DataStack()
|
|
32
30
|
|
|
33
31
|
# Standard builtins.
|
|
34
|
-
self.data.stack.append(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
self.data.stack.append(
|
|
33
|
+
{
|
|
34
|
+
"context": self,
|
|
35
|
+
"is_defined": self.is_defined,
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
38
|
|
|
39
39
|
# User-configurable builtins.
|
|
40
40
|
self.data.stack.append(builtins)
|
|
@@ -75,7 +75,7 @@ class Context:
|
|
|
75
75
|
def resolve(self, varstring, token):
|
|
76
76
|
words = []
|
|
77
77
|
result = self.data
|
|
78
|
-
for word in varstring.split(
|
|
78
|
+
for word in varstring.split("."):
|
|
79
79
|
words.append(word)
|
|
80
80
|
if hasattr(result, word):
|
|
81
81
|
result = getattr(result, word)
|
|
@@ -95,7 +95,7 @@ class Context:
|
|
|
95
95
|
|
|
96
96
|
def is_defined(self, varstring):
|
|
97
97
|
current = self.data
|
|
98
|
-
for word in varstring.split(
|
|
98
|
+
for word in varstring.split("."):
|
|
99
99
|
if hasattr(current, word):
|
|
100
100
|
current = getattr(current, word)
|
|
101
101
|
else:
|
|
@@ -111,9 +111,8 @@ class Context:
|
|
|
111
111
|
|
|
112
112
|
# Null type returned when a context lookup fails.
|
|
113
113
|
class Undefined:
|
|
114
|
-
|
|
115
114
|
def __str__(self):
|
|
116
|
-
return
|
|
115
|
+
return ""
|
|
117
116
|
|
|
118
117
|
def __bool__(self):
|
|
119
118
|
return False
|
|
@@ -135,4 +134,3 @@ class Undefined:
|
|
|
135
134
|
|
|
136
135
|
def __ne__(self, other):
|
|
137
136
|
return True
|
|
138
|
-
|
pyview/vendor/ibis/errors.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# Base class for all exception types raised by the template engine.
|
|
2
2
|
class TemplateError(Exception):
|
|
3
|
-
|
|
4
3
|
def __init__(self, msg):
|
|
5
4
|
self.msg = msg
|
|
6
5
|
|
|
@@ -17,7 +16,6 @@ class TemplateLoadError(TemplateError):
|
|
|
17
16
|
|
|
18
17
|
# This exception type is raised if the lexer cannot tokenize a template string.
|
|
19
18
|
class TemplateLexingError(TemplateError):
|
|
20
|
-
|
|
21
19
|
def __init__(self, msg, template_id, line_number):
|
|
22
20
|
super().__init__(msg)
|
|
23
21
|
self.template_id = template_id
|
|
@@ -29,7 +27,6 @@ class TemplateLexingError(TemplateError):
|
|
|
29
27
|
|
|
30
28
|
# This exception type may be raised while a template is being compiled.
|
|
31
29
|
class TemplateSyntaxError(TemplateError):
|
|
32
|
-
|
|
33
30
|
def __init__(self, msg, token):
|
|
34
31
|
super().__init__(msg)
|
|
35
32
|
self.token = token
|
|
@@ -37,7 +34,6 @@ class TemplateSyntaxError(TemplateError):
|
|
|
37
34
|
|
|
38
35
|
# This exception type may be raised while a template is being rendered.
|
|
39
36
|
class TemplateRenderingError(TemplateError):
|
|
40
|
-
|
|
41
37
|
def __init__(self, msg, token):
|
|
42
38
|
super().__init__(msg)
|
|
43
39
|
self.token = token
|
|
@@ -45,8 +41,6 @@ class TemplateRenderingError(TemplateError):
|
|
|
45
41
|
|
|
46
42
|
# This exception type is raised in strict mode if a variable cannot be resolved.
|
|
47
43
|
class UndefinedVariable(TemplateError):
|
|
48
|
-
|
|
49
44
|
def __init__(self, msg, token):
|
|
50
45
|
super().__init__(msg)
|
|
51
46
|
self.token = token
|
|
52
|
-
|
pyview/vendor/ibis/filters.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import pprint
|
|
3
1
|
import html
|
|
2
|
+
import pprint
|
|
4
3
|
import random
|
|
4
|
+
import re
|
|
5
5
|
|
|
6
6
|
from . import context
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
9
|
import pygments
|
|
10
|
-
import pygments.lexers
|
|
11
10
|
import pygments.formatters
|
|
11
|
+
import pygments.lexers
|
|
12
12
|
except ImportError:
|
|
13
13
|
pygments = None
|
|
14
14
|
|
|
@@ -29,7 +29,6 @@ filtermap = {}
|
|
|
29
29
|
#
|
|
30
30
|
# If no name is supplied the function name will be used.
|
|
31
31
|
def register(nameorfunc=None):
|
|
32
|
-
|
|
33
32
|
if callable(nameorfunc):
|
|
34
33
|
filtermap[nameorfunc.__name__] = nameorfunc
|
|
35
34
|
return nameorfunc
|
|
@@ -43,126 +42,126 @@ def register(nameorfunc=None):
|
|
|
43
42
|
|
|
44
43
|
@register
|
|
45
44
|
def argtest(*args):
|
|
46
|
-
"""
|
|
47
|
-
return
|
|
45
|
+
"""Test filter: returns arguments as a concatenated string."""
|
|
46
|
+
return "|".join(str(arg) for arg in args)
|
|
48
47
|
|
|
49
48
|
|
|
50
49
|
@register
|
|
51
50
|
def default(obj, fallback):
|
|
52
|
-
"""
|
|
51
|
+
"""Returns `obj` if `obj` is truthy, otherwise `fallback`."""
|
|
53
52
|
return obj or fallback
|
|
54
53
|
|
|
55
54
|
|
|
56
55
|
@register
|
|
57
|
-
def dtformat(dt, format=
|
|
58
|
-
"""
|
|
56
|
+
def dtformat(dt, format="%Y-%m-%d %H:%M"):
|
|
57
|
+
"""Formats a datetime object using the specified format string."""
|
|
59
58
|
return dt.strftime(format)
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
@register
|
|
63
62
|
def endswith(s, suffix):
|
|
64
|
-
"""
|
|
63
|
+
"""True if the string ends with the specified suffix."""
|
|
65
64
|
return s.endswith(suffix)
|
|
66
65
|
|
|
67
66
|
|
|
68
67
|
@register
|
|
69
|
-
@register(
|
|
70
|
-
@register(
|
|
68
|
+
@register("e")
|
|
69
|
+
@register("esc")
|
|
71
70
|
def escape(s, quotes=True):
|
|
72
|
-
"""
|
|
71
|
+
"""Converts html syntax characters to character entities."""
|
|
73
72
|
return html.escape(s, quotes)
|
|
74
73
|
|
|
75
74
|
|
|
76
75
|
@register
|
|
77
76
|
def first(seq):
|
|
78
|
-
"""
|
|
77
|
+
"""Returns the first element in the sequence `seq`."""
|
|
79
78
|
return seq[0]
|
|
80
79
|
|
|
81
80
|
|
|
82
81
|
@register
|
|
83
82
|
def firsth(html):
|
|
84
|
-
"""
|
|
85
|
-
match = re.search(r
|
|
86
|
-
return match.group(2) if match else
|
|
83
|
+
"""Returns the content of the first heading element."""
|
|
84
|
+
match = re.search(r"<h(\d)+[^>]*>(.*?)</h\1>", html, flags=re.DOTALL)
|
|
85
|
+
return match.group(2) if match else ""
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
@register
|
|
90
89
|
def firsth1(html):
|
|
91
|
-
"""
|
|
92
|
-
match = re.search(r
|
|
93
|
-
return match.group(1) if match else
|
|
90
|
+
"""Returns the content of the first h1 element."""
|
|
91
|
+
match = re.search(r"<h1[^>]*>(.*?)</h1>", html, flags=re.DOTALL)
|
|
92
|
+
return match.group(1) if match else ""
|
|
94
93
|
|
|
95
94
|
|
|
96
95
|
@register
|
|
97
96
|
def firstp(html):
|
|
98
|
-
"""
|
|
99
|
-
match = re.search(r
|
|
100
|
-
return match.group(1) if match else
|
|
97
|
+
"""Returns the content of the first p element."""
|
|
98
|
+
match = re.search(r"<p[^>]*>(.*?)</p>", html, flags=re.DOTALL)
|
|
99
|
+
return match.group(1) if match else ""
|
|
101
100
|
|
|
102
101
|
|
|
103
|
-
@register(
|
|
102
|
+
@register("reversed")
|
|
104
103
|
def get_reversed(seq):
|
|
105
|
-
"""
|
|
104
|
+
"""Returns a reverse iterator over the sequence `seq`."""
|
|
106
105
|
return reversed(seq)
|
|
107
106
|
|
|
108
107
|
|
|
109
108
|
@register
|
|
110
109
|
def index(seq, i):
|
|
111
|
-
"""
|
|
110
|
+
"""Returns the ith element in the sequence `seq`."""
|
|
112
111
|
return seq[i]
|
|
113
112
|
|
|
114
113
|
|
|
115
|
-
@register(
|
|
114
|
+
@register("divisible_by")
|
|
116
115
|
def is_divisible_by(n, d):
|
|
117
|
-
"""
|
|
116
|
+
"""True if the integer `n` is a multiple of the integer `d`."""
|
|
118
117
|
return n % d == 0
|
|
119
118
|
|
|
120
119
|
|
|
121
|
-
@register(
|
|
120
|
+
@register("even")
|
|
122
121
|
def is_even(n):
|
|
123
|
-
"""
|
|
122
|
+
"""True if the integer `n` is even."""
|
|
124
123
|
return n % 2 == 0
|
|
125
124
|
|
|
126
125
|
|
|
127
|
-
@register(
|
|
126
|
+
@register("odd")
|
|
128
127
|
def is_odd(n):
|
|
129
|
-
"""
|
|
128
|
+
"""True if the integer `n` is odd."""
|
|
130
129
|
return n % 2 != 0
|
|
131
130
|
|
|
132
131
|
|
|
133
132
|
@register
|
|
134
|
-
def join(seq, sep=
|
|
135
|
-
"""
|
|
133
|
+
def join(seq, sep=""):
|
|
134
|
+
"""Joins elements of the sequence `seq` with the string `sep`."""
|
|
136
135
|
return sep.join(str(item) for item in seq)
|
|
137
136
|
|
|
138
137
|
|
|
139
138
|
@register
|
|
140
139
|
def last(seq):
|
|
141
|
-
"""
|
|
140
|
+
"""Returns the last element in the sequence `seq`."""
|
|
142
141
|
return seq[-1]
|
|
143
142
|
|
|
144
143
|
|
|
145
|
-
@register(
|
|
144
|
+
@register("len")
|
|
146
145
|
def length(seq):
|
|
147
|
-
"""
|
|
146
|
+
"""Returns the length of the sequence `seq`."""
|
|
148
147
|
return len(seq)
|
|
149
148
|
|
|
150
149
|
|
|
151
150
|
@register
|
|
152
151
|
def lower(s):
|
|
153
|
-
"""
|
|
152
|
+
"""Returns the string `s` converted to lowercase."""
|
|
154
153
|
return s.lower()
|
|
155
154
|
|
|
156
155
|
|
|
157
|
-
@register(
|
|
156
|
+
@register("pprint")
|
|
158
157
|
def prettyprint(obj):
|
|
159
|
-
"""
|
|
158
|
+
"""Returns a pretty-printed representation of `obj`."""
|
|
160
159
|
return pprint.pformat(obj)
|
|
161
160
|
|
|
162
161
|
|
|
163
162
|
@register
|
|
164
163
|
def pygmentize(text, lang=None):
|
|
165
|
-
"""
|
|
164
|
+
"""Applies syntax highlighting using Pygments.
|
|
166
165
|
|
|
167
166
|
If no language is specified, Pygments will attempt to guess the correct
|
|
168
167
|
lexer to use. If Pygments is not available or if an appropriate lexer
|
|
@@ -192,107 +191,102 @@ def pygmentize(text, lang=None):
|
|
|
192
191
|
|
|
193
192
|
@register
|
|
194
193
|
def random(seq):
|
|
195
|
-
"""
|
|
194
|
+
"""Returns a random element from the sequence `seq`."""
|
|
196
195
|
return random.choice(seq)
|
|
197
196
|
|
|
198
197
|
|
|
199
|
-
@register(
|
|
198
|
+
@register("repr")
|
|
200
199
|
def to_repr(obj):
|
|
201
|
-
"""
|
|
200
|
+
"""Returns the result of calling repr() on `obj`."""
|
|
202
201
|
return repr(obj)
|
|
203
202
|
|
|
204
203
|
|
|
205
204
|
@register
|
|
206
205
|
def slice(seq, start, stop=None, step=None):
|
|
207
|
-
"""
|
|
206
|
+
"""Returns the start:stop:step slice of the sequence `seq`."""
|
|
208
207
|
return seq[start:stop:step]
|
|
209
208
|
|
|
210
209
|
|
|
211
210
|
@register
|
|
212
211
|
def spaceless(html):
|
|
213
|
-
"""
|
|
214
|
-
return re.sub(r
|
|
212
|
+
"""Strips all whitespace between html/xml tags."""
|
|
213
|
+
return re.sub(r">\s+<", "><", html)
|
|
215
214
|
|
|
216
215
|
|
|
217
216
|
@register
|
|
218
217
|
def startswith(s, prefix):
|
|
219
|
-
"""
|
|
218
|
+
"""True if the string starts with the specified prefix."""
|
|
220
219
|
return s.startswith(prefix)
|
|
221
220
|
|
|
222
221
|
|
|
223
|
-
@register(
|
|
222
|
+
@register("str")
|
|
224
223
|
def to_str(obj):
|
|
225
|
-
"""
|
|
224
|
+
"""Returns the result of calling str() on `obj`."""
|
|
226
225
|
return str(obj)
|
|
227
226
|
|
|
228
227
|
|
|
229
228
|
@register
|
|
230
229
|
def striptags(html):
|
|
231
|
-
"""
|
|
232
|
-
return re.sub(r
|
|
230
|
+
"""Returns the string `html` with all html tags stripped."""
|
|
231
|
+
return re.sub(r"<[^>]*>", "", html)
|
|
233
232
|
|
|
234
233
|
|
|
235
234
|
@register
|
|
236
|
-
def teaser(s, delimiter=
|
|
237
|
-
"""
|
|
238
|
-
or an empty string if `delimiter` is not found.
|
|
235
|
+
def teaser(s, delimiter="<!-- more -->"):
|
|
236
|
+
"""Returns the portion of the string `s` before `delimiter`,
|
|
237
|
+
or an empty string if `delimiter` is not found."""
|
|
239
238
|
index = s.find(delimiter)
|
|
240
239
|
if index == -1:
|
|
241
|
-
return
|
|
240
|
+
return ""
|
|
242
241
|
else:
|
|
243
242
|
return s[:index]
|
|
244
243
|
|
|
245
244
|
|
|
246
245
|
@register
|
|
247
|
-
@register(
|
|
246
|
+
@register("title")
|
|
248
247
|
def titlecase(s):
|
|
249
|
-
"""
|
|
250
|
-
return re.sub(
|
|
251
|
-
r"[A-Za-z]+('[A-Za-z]+)?",
|
|
252
|
-
lambda m: m.group(0)[0].upper() + m.group(0)[1:],
|
|
253
|
-
s
|
|
254
|
-
)
|
|
248
|
+
"""Returns the string `s` converted to titlecase."""
|
|
249
|
+
return re.sub(r"[A-Za-z]+('[A-Za-z]+)?", lambda m: m.group(0)[0].upper() + m.group(0)[1:], s)
|
|
255
250
|
|
|
256
251
|
|
|
257
252
|
@register
|
|
258
|
-
def truncatechars(s, n, ellipsis=
|
|
259
|
-
"""
|
|
253
|
+
def truncatechars(s, n, ellipsis="..."):
|
|
254
|
+
"""Truncates the string `s` to at most `n` characters."""
|
|
260
255
|
if len(s) > n:
|
|
261
|
-
return s[:n - 3].rstrip(
|
|
256
|
+
return s[: n - 3].rstrip(" .,;:?!") + ellipsis
|
|
262
257
|
else:
|
|
263
258
|
return s
|
|
264
259
|
|
|
265
260
|
|
|
266
261
|
@register
|
|
267
|
-
def truncatewords(s, n, ellipsis=
|
|
268
|
-
"""
|
|
262
|
+
def truncatewords(s, n, ellipsis=" [...]"):
|
|
263
|
+
"""Truncates the string `s` to at most `n` words."""
|
|
269
264
|
words = s.split()
|
|
270
265
|
if len(words) > n:
|
|
271
|
-
return
|
|
266
|
+
return " ".join(words[:n]) + ellipsis
|
|
272
267
|
else:
|
|
273
|
-
return
|
|
268
|
+
return " ".join(words)
|
|
274
269
|
|
|
275
270
|
|
|
276
271
|
@register
|
|
277
272
|
def upper(s):
|
|
278
|
-
"""
|
|
273
|
+
"""Returns the string `s` converted to uppercase."""
|
|
279
274
|
return s.upper()
|
|
280
275
|
|
|
281
276
|
|
|
282
277
|
@register
|
|
283
278
|
def wrap(s, tag):
|
|
284
|
-
"""
|
|
285
|
-
return
|
|
279
|
+
"""Wraps a string in opening and closing tags."""
|
|
280
|
+
return "<%s>%s</%s>" % (tag, str(s), tag)
|
|
286
281
|
|
|
287
282
|
|
|
288
283
|
@register
|
|
289
284
|
def if_undefined(obj, fallback):
|
|
290
|
-
"""
|
|
285
|
+
"""Returns `obj` if `obj` is defined, otherwise `fallback`."""
|
|
291
286
|
return fallback if isinstance(obj, context.Undefined) else obj
|
|
292
287
|
|
|
293
288
|
|
|
294
289
|
@register
|
|
295
290
|
def is_defined(obj):
|
|
296
|
-
"""
|
|
291
|
+
"""Returns true if `obj` is defined, otherwise false."""
|
|
297
292
|
return not isinstance(obj, context.Undefined)
|
|
298
|
-
|
pyview/vendor/ibis/loaders.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from .template import Template
|
|
4
3
|
from .errors import TemplateLoadError
|
|
4
|
+
from .template import Template
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Loads templates from the file system. Assumes files are utf-8 encoded. Compiled templates are
|
|
@@ -20,7 +20,6 @@ from .errors import TemplateLoadError
|
|
|
20
20
|
# template = loader('subdir/foo.txt')
|
|
21
21
|
#
|
|
22
22
|
class FileLoader:
|
|
23
|
-
|
|
24
23
|
def __init__(self, base_dir):
|
|
25
24
|
self.base_dir = base_dir
|
|
26
25
|
self.cache = {}
|
|
@@ -32,7 +31,7 @@ class FileLoader:
|
|
|
32
31
|
path = os.path.join(self.base_dir, filename)
|
|
33
32
|
if os.path.isfile(path):
|
|
34
33
|
try:
|
|
35
|
-
with open(path, encoding=
|
|
34
|
+
with open(path, encoding="utf-8") as file:
|
|
36
35
|
template_string = file.read()
|
|
37
36
|
except OSError as err:
|
|
38
37
|
msg = f"FileLoader cannot load the template file '{filename}'."
|
|
@@ -42,14 +41,15 @@ class FileLoader:
|
|
|
42
41
|
self.cache[filename] = template
|
|
43
42
|
return template
|
|
44
43
|
|
|
45
|
-
msg =
|
|
44
|
+
msg = (
|
|
45
|
+
f"FileLoader with base '{self.base_dir}' cannot locate the template file '{filename}'."
|
|
46
|
+
)
|
|
46
47
|
raise TemplateLoadError(msg)
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
# Like FileLoader but templates are automatically recompiled if the underlying template file
|
|
50
51
|
# is modified.
|
|
51
52
|
class FileReloader:
|
|
52
|
-
|
|
53
53
|
def __init__(self, base_dir):
|
|
54
54
|
self.base_dir = base_dir
|
|
55
55
|
self.cache = {}
|
|
@@ -63,7 +63,7 @@ class FileReloader:
|
|
|
63
63
|
return self.cache[filename][1]
|
|
64
64
|
|
|
65
65
|
try:
|
|
66
|
-
with open(path, encoding=
|
|
66
|
+
with open(path, encoding="utf-8") as file:
|
|
67
67
|
template_string = file.read()
|
|
68
68
|
except OSError as err:
|
|
69
69
|
msg = f"FileReloader cannot load the template file '{filename}'."
|
|
@@ -80,7 +80,6 @@ class FileReloader:
|
|
|
80
80
|
# Loads templates from a dictionary of template strings. Templates are compiled once and cached for
|
|
81
81
|
# future use.
|
|
82
82
|
class DictLoader:
|
|
83
|
-
|
|
84
83
|
def __init__(self, template_strings):
|
|
85
84
|
self.templates = {}
|
|
86
85
|
self.template_strings = template_strings
|