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/nodes.py
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import ast
|
|
2
|
+
import collections
|
|
3
|
+
import itertools
|
|
2
4
|
import operator
|
|
3
5
|
import re
|
|
4
|
-
import itertools
|
|
5
|
-
import collections
|
|
6
|
-
from pyview.vendor import ibis
|
|
7
6
|
from typing import Any, Callable
|
|
8
7
|
|
|
9
|
-
from . import utils
|
|
10
|
-
from . import filters
|
|
11
|
-
from . import errors
|
|
12
|
-
from .tree import PartsTree
|
|
13
8
|
from markupsafe import Markup, escape
|
|
14
9
|
|
|
10
|
+
from pyview.vendor import ibis
|
|
11
|
+
|
|
12
|
+
from . import errors, filters, utils
|
|
13
|
+
from .tree import PartsTree, StreamComprehension
|
|
14
|
+
|
|
15
15
|
# Dictionary of registered keywords for instruction tags.
|
|
16
16
|
instruction_keywords = {}
|
|
17
17
|
|
|
@@ -53,7 +53,6 @@ def register(keyword, endword=None):
|
|
|
53
53
|
# foo.bar.baz('bam')|filter(25, 'text')
|
|
54
54
|
#
|
|
55
55
|
class Expression:
|
|
56
|
-
|
|
57
56
|
re_func_call = re.compile(r"^([\w.]+)\((.*)\)$")
|
|
58
57
|
re_varstring = re.compile(r"^[\w.]+$")
|
|
59
58
|
|
|
@@ -99,9 +98,7 @@ class Expression:
|
|
|
99
98
|
for filter_expr in filter_list:
|
|
100
99
|
_, filter_name, filter_args = self._try_parse_as_func_call(filter_expr)
|
|
101
100
|
if filter_name in filters.filtermap:
|
|
102
|
-
self.filters.append(
|
|
103
|
-
(filter_name, filters.filtermap[filter_name], filter_args)
|
|
104
|
-
)
|
|
101
|
+
self.filters.append((filter_name, filters.filtermap[filter_name], filter_args))
|
|
105
102
|
else:
|
|
106
103
|
msg = f"Unrecognised filter name '{filter_name}'."
|
|
107
104
|
raise errors.TemplateSyntaxError(msg, self.token)
|
|
@@ -154,9 +151,7 @@ class Node:
|
|
|
154
151
|
raise
|
|
155
152
|
except Exception as err:
|
|
156
153
|
if token:
|
|
157
|
-
tagname =
|
|
158
|
-
f"'{token.keyword}'" if token.type == "INSTRUCTION" else token.type
|
|
159
|
-
)
|
|
154
|
+
tagname = f"'{token.keyword}'" if token.type == "INSTRUCTION" else token.type
|
|
160
155
|
msg = f"An unexpected error occurred while parsing the {tagname} tag: "
|
|
161
156
|
msg += f"{err.__class__.__name__}: {err}"
|
|
162
157
|
else:
|
|
@@ -184,9 +179,7 @@ class Node:
|
|
|
184
179
|
if self.token.type == "INSTRUCTION"
|
|
185
180
|
else self.token.type
|
|
186
181
|
)
|
|
187
|
-
msg =
|
|
188
|
-
f"An unexpected error occurred while rendering the {tagname} tag: "
|
|
189
|
-
)
|
|
182
|
+
msg = f"An unexpected error occurred while rendering the {tagname} tag: "
|
|
190
183
|
msg += f"{err.__class__.__name__}: {err}"
|
|
191
184
|
else:
|
|
192
185
|
msg = f"Unexpected rendering error: {err.__class__.__name__}: {err}"
|
|
@@ -285,13 +278,12 @@ class PrintNode(Node):
|
|
|
285
278
|
if content:
|
|
286
279
|
break
|
|
287
280
|
|
|
288
|
-
return (
|
|
289
|
-
str(content) if isinstance(content, Markup) else str(escape(str(content)))
|
|
290
|
-
)
|
|
281
|
+
return str(content) if isinstance(content, Markup) else str(escape(str(content)))
|
|
291
282
|
|
|
292
283
|
|
|
293
284
|
NodeVisitor = Callable[[Node, Any], Any]
|
|
294
285
|
|
|
286
|
+
|
|
295
287
|
# ForNodes implement `for ... in ...` looping over iterables.
|
|
296
288
|
#
|
|
297
289
|
# {% for <var> in <expr> %} ... [ {% empty %} ... ] {% endfor %}
|
|
@@ -302,13 +294,12 @@ NodeVisitor = Callable[[Node, Any], Any]
|
|
|
302
294
|
#
|
|
303
295
|
@register("for", "endfor")
|
|
304
296
|
class ForNode(Node):
|
|
305
|
-
|
|
306
297
|
regex = re.compile(r"for\s+(\w+(?:,\s*\w+)*)\s+in\s+(.+)")
|
|
307
298
|
|
|
308
299
|
def process_token(self, token):
|
|
309
300
|
match = self.regex.match(token.text)
|
|
310
301
|
if match is None:
|
|
311
|
-
msg =
|
|
302
|
+
msg = "Malformed 'for' tag."
|
|
312
303
|
raise errors.TemplateSyntaxError(msg, token)
|
|
313
304
|
self.loopvars = [var.strip() for var in match.group(1).split(",")]
|
|
314
305
|
self.expr = Expression(match.group(2), token)
|
|
@@ -327,9 +318,9 @@ class ForNode(Node):
|
|
|
327
318
|
context.push()
|
|
328
319
|
if unpack:
|
|
329
320
|
try:
|
|
330
|
-
unpacked = dict(zip(self.loopvars, item))
|
|
321
|
+
unpacked = dict(zip(self.loopvars, item, strict=False))
|
|
331
322
|
except Exception as err:
|
|
332
|
-
msg =
|
|
323
|
+
msg = "Unpacking error."
|
|
333
324
|
raise errors.TemplateRenderingError(msg, self.token) from err
|
|
334
325
|
else:
|
|
335
326
|
context.update(unpacked)
|
|
@@ -384,11 +375,23 @@ class ForNode(Node):
|
|
|
384
375
|
}
|
|
385
376
|
|
|
386
377
|
def tree_parts(self, context):
|
|
378
|
+
# Import here to avoid circular imports
|
|
379
|
+
from pyview.stream import Stream
|
|
380
|
+
|
|
387
381
|
output = []
|
|
388
382
|
|
|
389
383
|
def visitor(node, ctx):
|
|
390
384
|
output.append(node.tree_parts(ctx))
|
|
391
385
|
|
|
386
|
+
# Get the collection to check if it's a Stream
|
|
387
|
+
collection = self.expr.eval(context)
|
|
388
|
+
|
|
389
|
+
# Check if this is a Stream
|
|
390
|
+
if isinstance(collection, Stream):
|
|
391
|
+
self.visit_nodes(context, visitor)
|
|
392
|
+
return StreamComprehension(parts=output, stream=collection)
|
|
393
|
+
|
|
394
|
+
# Regular collection - use standard visit
|
|
392
395
|
self.visit_nodes(context, visitor)
|
|
393
396
|
return output
|
|
394
397
|
|
|
@@ -421,7 +424,6 @@ class EmptyNode(Node):
|
|
|
421
424
|
# Note that explicit brackets are not supported.
|
|
422
425
|
@register("if", "endif")
|
|
423
426
|
class IfNode(Node):
|
|
424
|
-
|
|
425
427
|
condition = collections.namedtuple("Condition", "negated lhs op rhs")
|
|
426
428
|
|
|
427
429
|
re_condition = re.compile(
|
|
@@ -484,7 +486,7 @@ class IfNode(Node):
|
|
|
484
486
|
else:
|
|
485
487
|
result = operator.truth(cond.lhs.eval(context))
|
|
486
488
|
except Exception as err:
|
|
487
|
-
msg =
|
|
489
|
+
msg = "An exception was raised while evaluating the condition in the "
|
|
488
490
|
msg += f"'{self.tag}' tag."
|
|
489
491
|
raise errors.TemplateRenderingError(msg, self.token) from err
|
|
490
492
|
if cond.negated:
|
|
@@ -577,14 +579,14 @@ class CycleNode(Node):
|
|
|
577
579
|
try:
|
|
578
580
|
tag, arg = token.text.split(None, 1)
|
|
579
581
|
except:
|
|
580
|
-
msg =
|
|
582
|
+
msg = "Malformed 'cycle' tag."
|
|
581
583
|
raise errors.TemplateSyntaxError(msg, token) from None
|
|
582
584
|
self.expr = Expression(arg, token)
|
|
583
585
|
|
|
584
586
|
def wrender(self, context):
|
|
585
587
|
# We store our state info on the context object to avoid a threading mess if
|
|
586
588
|
# the template is being simultaneously rendered by multiple threads.
|
|
587
|
-
if not
|
|
589
|
+
if self not in context.stash:
|
|
588
590
|
items = self.expr.eval(context)
|
|
589
591
|
if not hasattr(items, "__iter__"):
|
|
590
592
|
items = ""
|
|
@@ -620,9 +622,7 @@ class IncludeNode(Node):
|
|
|
620
622
|
name, expr = chunk.split("=", 1)
|
|
621
623
|
self.variables[name.strip()] = Expression(expr.strip(), token)
|
|
622
624
|
except:
|
|
623
|
-
raise errors.TemplateSyntaxError(
|
|
624
|
-
"Malformed 'include' tag.", token
|
|
625
|
-
) from None
|
|
625
|
+
raise errors.TemplateSyntaxError("Malformed 'include' tag.", token) from None
|
|
626
626
|
else:
|
|
627
627
|
raise errors.TemplateSyntaxError("Malformed 'include' tag.", token)
|
|
628
628
|
|
|
@@ -637,25 +637,27 @@ class IncludeNode(Node):
|
|
|
637
637
|
visitor(context, template.root_node)
|
|
638
638
|
context.pop()
|
|
639
639
|
else:
|
|
640
|
-
msg =
|
|
641
|
-
msg +=
|
|
640
|
+
msg = "No template loader has been specified. "
|
|
641
|
+
msg += "A template loader is required by the 'include' tag in "
|
|
642
642
|
msg += f"template '{self.token.template_id}', line {self.token.line_number}."
|
|
643
643
|
raise errors.TemplateLoadError(msg)
|
|
644
644
|
else:
|
|
645
|
-
msg =
|
|
645
|
+
msg = "Invalid argument for the 'include' tag. "
|
|
646
646
|
msg += f"The variable '{self.template_arg}' should evaluate to a string. "
|
|
647
647
|
msg += f"This variable has the value: {repr(template_name)}."
|
|
648
648
|
raise errors.TemplateRenderingError(msg, self.token)
|
|
649
|
-
|
|
649
|
+
|
|
650
650
|
def wrender(self, context):
|
|
651
651
|
output = []
|
|
652
652
|
self.visit_node(context, lambda ctx, node: output.append(node.render(ctx)))
|
|
653
653
|
return "".join(output)
|
|
654
|
-
|
|
654
|
+
|
|
655
655
|
def tree_parts(self, context) -> PartsTree:
|
|
656
656
|
output = []
|
|
657
|
+
|
|
657
658
|
def visitor(ctx, node):
|
|
658
659
|
output.append(node.tree_parts(ctx))
|
|
660
|
+
|
|
659
661
|
self.visit_node(context, visitor)
|
|
660
662
|
return output[0]
|
|
661
663
|
|
|
@@ -672,9 +674,7 @@ class ExtendsNode(Node):
|
|
|
672
674
|
try:
|
|
673
675
|
tag, arg = token.text.split(None, 1)
|
|
674
676
|
except:
|
|
675
|
-
raise errors.TemplateSyntaxError(
|
|
676
|
-
"Malformed 'extends' tag.", token
|
|
677
|
-
) from None
|
|
677
|
+
raise errors.TemplateSyntaxError("Malformed 'extends' tag.", token) from None
|
|
678
678
|
|
|
679
679
|
expr = Expression(arg, token)
|
|
680
680
|
if expr.is_literal and isinstance(expr.literal, str):
|
|
@@ -744,9 +744,7 @@ class WithNode(Node):
|
|
|
744
744
|
name, expr = chunk.split("=", 1)
|
|
745
745
|
self.variables[name.strip()] = Expression(expr.strip(), token)
|
|
746
746
|
except:
|
|
747
|
-
raise errors.TemplateSyntaxError(
|
|
748
|
-
"Malformed 'with' tag.", token
|
|
749
|
-
) from None
|
|
747
|
+
raise errors.TemplateSyntaxError("Malformed 'with' tag.", token) from None
|
|
750
748
|
|
|
751
749
|
def wrender(self, context):
|
|
752
750
|
context.push()
|
pyview/vendor/ibis/template.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pyview.vendor import ibis
|
|
2
|
+
|
|
2
3
|
from .context import Context
|
|
3
|
-
from .nodes import
|
|
4
|
+
from .nodes import BlockNode, ExtendsNode
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
# A Template object is initialized with a template string containing template markup and a
|
|
@@ -35,14 +36,12 @@ class Template:
|
|
|
35
36
|
|
|
36
37
|
def _render(self, context):
|
|
37
38
|
context.templates.append(self)
|
|
38
|
-
if self.root_node.children and isinstance(
|
|
39
|
-
self.root_node.children[0], ExtendsNode
|
|
40
|
-
):
|
|
39
|
+
if self.root_node.children and isinstance(self.root_node.children[0], ExtendsNode):
|
|
41
40
|
if ibis.loader:
|
|
42
41
|
parent_template = ibis.loader(self.root_node.children[0].parent_name)
|
|
43
42
|
return parent_template._render(context)
|
|
44
43
|
else:
|
|
45
|
-
msg =
|
|
44
|
+
msg = "No template loader has been specified. A template loader is required "
|
|
46
45
|
msg += f"by the 'extends' tag in template '{self.template_id}'."
|
|
47
46
|
raise ibis.errors.TemplateLoadError(msg)
|
|
48
47
|
else:
|
pyview/vendor/ibis/tree.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
|
-
from typing import Any, Union
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Union
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from pyview.stream import Stream
|
|
6
|
+
|
|
7
|
+
Part = Union[str, "PartsTree", "PartsComprehension", "StreamComprehension"]
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
@dataclass
|
|
@@ -33,6 +36,59 @@ class PartsComprehension:
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
|
|
39
|
+
@dataclass
|
|
40
|
+
class StreamComprehension:
|
|
41
|
+
"""
|
|
42
|
+
A comprehension that includes stream metadata for the wire format.
|
|
43
|
+
|
|
44
|
+
This is returned by ForNode.tree_parts() when iterating over a Stream.
|
|
45
|
+
It produces the same {"s": [...], "d": [...]} format as PartsComprehension,
|
|
46
|
+
but also includes a "stream" key with the stream operations.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
parts: list["PartsTree"]
|
|
50
|
+
stream: "Stream"
|
|
51
|
+
|
|
52
|
+
def render_parts(self) -> Union[dict[str, Any], str]:
|
|
53
|
+
# Import here to avoid circular imports
|
|
54
|
+
|
|
55
|
+
# Handle empty stream
|
|
56
|
+
if len(self.parts) == 0:
|
|
57
|
+
# Still need to check for delete/reset operations
|
|
58
|
+
ops = self.stream._get_pending_ops()
|
|
59
|
+
if ops is None:
|
|
60
|
+
return ""
|
|
61
|
+
return {"stream": self.stream._to_wire_format(ops)}
|
|
62
|
+
|
|
63
|
+
if len(self.parts) == 1:
|
|
64
|
+
if isinstance(self.parts[0], PartsTree) and self.parts[0].is_empty():
|
|
65
|
+
# Check for delete/reset operations even with empty parts
|
|
66
|
+
ops = self.stream._get_pending_ops()
|
|
67
|
+
if ops is None:
|
|
68
|
+
return ""
|
|
69
|
+
return {"stream": self.stream._to_wire_format(ops)}
|
|
70
|
+
|
|
71
|
+
def render(p: Part) -> Any:
|
|
72
|
+
if isinstance(p, str):
|
|
73
|
+
return p
|
|
74
|
+
return p.render_parts()
|
|
75
|
+
|
|
76
|
+
statics = self.parts[0].statics
|
|
77
|
+
dynamics = [[render(d) for d in p.dynamics] for p in self.parts]
|
|
78
|
+
|
|
79
|
+
result: dict[str, Any] = {
|
|
80
|
+
"s": statics,
|
|
81
|
+
"d": dynamics,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Add stream metadata
|
|
85
|
+
ops = self.stream._get_pending_ops()
|
|
86
|
+
if ops is not None:
|
|
87
|
+
result["stream"] = self.stream._to_wire_format(ops)
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
|
|
36
92
|
@dataclass
|
|
37
93
|
class PartsTree:
|
|
38
94
|
statics: list[str] = field(default_factory=list)
|
|
@@ -41,12 +97,15 @@ class PartsTree:
|
|
|
41
97
|
def add_static(self, s: str):
|
|
42
98
|
self.statics.append(s)
|
|
43
99
|
|
|
44
|
-
def add_dynamic(self, d: Union[Part, list[Part]]):
|
|
100
|
+
def add_dynamic(self, d: Union[Part, list[Part], StreamComprehension]):
|
|
45
101
|
if len(self.statics) < len(self.dynamics) + 1:
|
|
46
102
|
self.statics.append("")
|
|
47
103
|
|
|
48
104
|
if isinstance(d, str):
|
|
49
105
|
self.dynamics.append(d)
|
|
106
|
+
elif isinstance(d, StreamComprehension):
|
|
107
|
+
# StreamComprehension is added directly (not wrapped)
|
|
108
|
+
self.dynamics.append(d)
|
|
50
109
|
elif isinstance(d, list):
|
|
51
110
|
self.dynamics.append(PartsComprehension(d))
|
|
52
111
|
else:
|
pyview/vendor/ibis/utils.py
CHANGED
|
@@ -3,7 +3,6 @@ import re
|
|
|
3
3
|
|
|
4
4
|
# Splits a string on instances of a delimiter character. Ignores quoted delimiters.
|
|
5
5
|
def splitc(s, delimiter, strip=False, discard_empty=False, maxsplit=-1):
|
|
6
|
-
|
|
7
6
|
tokens, buf, expecting, escaped = [], [], None, False
|
|
8
7
|
|
|
9
8
|
for index, char in enumerate(s):
|
|
@@ -13,18 +12,18 @@ def splitc(s, delimiter, strip=False, discard_empty=False, maxsplit=-1):
|
|
|
13
12
|
expecting = None
|
|
14
13
|
else:
|
|
15
14
|
if char == delimiter:
|
|
16
|
-
tokens.append(
|
|
15
|
+
tokens.append("".join(buf))
|
|
17
16
|
buf = []
|
|
18
17
|
if len(tokens) == maxsplit:
|
|
19
|
-
buf.append(s[index+1:])
|
|
18
|
+
buf.append(s[index + 1 :])
|
|
20
19
|
break
|
|
21
20
|
else:
|
|
22
21
|
buf.append(char)
|
|
23
22
|
if char in ('"', "'"):
|
|
24
23
|
expecting = char
|
|
25
|
-
escaped = not escaped if char ==
|
|
24
|
+
escaped = not escaped if char == "\\" else False
|
|
26
25
|
|
|
27
|
-
tokens.append(
|
|
26
|
+
tokens.append("".join(buf))
|
|
28
27
|
|
|
29
28
|
if strip:
|
|
30
29
|
tokens = [t.strip() for t in tokens]
|
|
@@ -49,20 +48,20 @@ def splitws(s, maxsplit=-1):
|
|
|
49
48
|
if char.isspace():
|
|
50
49
|
if wsrun:
|
|
51
50
|
continue
|
|
52
|
-
tokens.append(
|
|
51
|
+
tokens.append("".join(buf))
|
|
53
52
|
buf = []
|
|
54
53
|
wsrun = True
|
|
55
54
|
if len(tokens) == maxsplit:
|
|
56
|
-
buf.append(s[index+1:].lstrip())
|
|
55
|
+
buf.append(s[index + 1 :].lstrip())
|
|
57
56
|
break
|
|
58
57
|
else:
|
|
59
58
|
buf.append(char)
|
|
60
59
|
wsrun = False
|
|
61
60
|
if char in ('"', "'"):
|
|
62
61
|
expecting = char
|
|
63
|
-
escaped = not escaped if char ==
|
|
62
|
+
escaped = not escaped if char == "\\" else False
|
|
64
63
|
|
|
65
|
-
tokens.append(
|
|
64
|
+
tokens.append("".join(buf))
|
|
66
65
|
return tokens
|
|
67
66
|
|
|
68
67
|
|
|
@@ -71,22 +70,22 @@ def splitre(s, delimiters, keep_delimiters=False):
|
|
|
71
70
|
tokens, buf = [], []
|
|
72
71
|
end_last_match = 0
|
|
73
72
|
|
|
74
|
-
pattern = r
|
|
75
|
-
pattern %=
|
|
73
|
+
pattern = r""""(?:[^\\"]|\\.)*"|'(?:[^\\']|\\.)*'|%s"""
|
|
74
|
+
pattern %= "|".join(delimiters)
|
|
76
75
|
|
|
77
76
|
for match in re.finditer(pattern, s):
|
|
78
77
|
if match.group()[0] in ["'", '"']:
|
|
79
|
-
buf.append(s[end_last_match:match.end()])
|
|
78
|
+
buf.append(s[end_last_match : match.end()])
|
|
80
79
|
end_last_match = match.end()
|
|
81
80
|
continue
|
|
82
|
-
buf.append(s[end_last_match:match.start()])
|
|
83
|
-
tokens.append(
|
|
81
|
+
buf.append(s[end_last_match : match.start()])
|
|
82
|
+
tokens.append("".join(buf))
|
|
84
83
|
buf = []
|
|
85
84
|
end_last_match = match.end()
|
|
86
85
|
if keep_delimiters:
|
|
87
86
|
tokens.append(match.group())
|
|
88
87
|
|
|
89
88
|
buf.append(s[end_last_match:])
|
|
90
|
-
tokens.append(
|
|
89
|
+
tokens.append("".join(buf))
|
|
91
90
|
|
|
92
91
|
return tokens
|