quill-delta-python312 0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quill_delta_python312-0.1/LICENSE.txt +21 -0
- quill_delta_python312-0.1/PKG-INFO +122 -0
- quill_delta_python312-0.1/README.md +97 -0
- quill_delta_python312-0.1/delta/__init__.py +3 -0
- quill_delta_python312-0.1/delta/base.py +330 -0
- quill_delta_python312-0.1/delta/deep_eq.py +143 -0
- quill_delta_python312-0.1/delta/html.py +332 -0
- quill_delta_python312-0.1/delta/op.py +170 -0
- quill_delta_python312-0.1/pyproject.toml +42 -0
- quill_delta_python312-0.1/quill_delta_python312.egg-info/PKG-INFO +122 -0
- quill_delta_python312-0.1/quill_delta_python312.egg-info/SOURCES.txt +21 -0
- quill_delta_python312-0.1/quill_delta_python312.egg-info/dependency_links.txt +1 -0
- quill_delta_python312-0.1/quill_delta_python312.egg-info/requires.txt +5 -0
- quill_delta_python312-0.1/quill_delta_python312.egg-info/top_level.txt +1 -0
- quill_delta_python312-0.1/setup.cfg +4 -0
- quill_delta_python312-0.1/setup.py +35 -0
- quill_delta_python312-0.1/tests/test_compose.py +157 -0
- quill_delta_python312-0.1/tests/test_delta.py +12 -0
- quill_delta_python312-0.1/tests/test_diff.py +141 -0
- quill_delta_python312-0.1/tests/test_helpers.py +170 -0
- quill_delta_python312-0.1/tests/test_html.py +243 -0
- quill_delta_python312-0.1/tests/test_op.py +140 -0
- quill_delta_python312-0.1/tests/test_transform.py +192 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Brantley Harris
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quill-delta-python312
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Fork of delta-python with dependencies updated for python 3.12 and up
|
|
5
|
+
Home-page: https://github.com/yesyves/quill-delta-python312
|
|
6
|
+
Author: Brantley Harris
|
|
7
|
+
Author-email: brantley@forge.works
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Requires-Dist: diff-match-patch>=20181111.0
|
|
12
|
+
Provides-Extra: html
|
|
13
|
+
Requires-Dist: lxml>=6.0; extra == "html"
|
|
14
|
+
Requires-Dist: cssutils>=2.0; extra == "html"
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: provides-extra
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Delta (Python Port)
|
|
28
|
+
|
|
29
|
+
Python port of the javascript Delta library for QuillJS: https://github.com/quilljs/delta
|
|
30
|
+
|
|
31
|
+
Some basic pythonizing has been done, but mostly it works exactly like the above library.
|
|
32
|
+
|
|
33
|
+
There is no other python specific documentation at this time, sorry. Please see the tests
|
|
34
|
+
for reference examples.
|
|
35
|
+
|
|
36
|
+
## Install with [Poetry](https://poetry.eustace.io/docs/#installation)
|
|
37
|
+
|
|
38
|
+
With HTML rendering:
|
|
39
|
+
|
|
40
|
+
> poetry add -E html quill-delta
|
|
41
|
+
|
|
42
|
+
Without HTML rendering:
|
|
43
|
+
|
|
44
|
+
> poetry add quill-delta
|
|
45
|
+
|
|
46
|
+
## Install with pip
|
|
47
|
+
|
|
48
|
+
Note: If you're using `zsh`, see below.
|
|
49
|
+
|
|
50
|
+
With HTML rendering:
|
|
51
|
+
|
|
52
|
+
> pip install quill-delta[html]
|
|
53
|
+
|
|
54
|
+
With HTML rendering (zsh):
|
|
55
|
+
|
|
56
|
+
> pip install quill-delta"[html]"
|
|
57
|
+
|
|
58
|
+
Without HTML rendering:
|
|
59
|
+
|
|
60
|
+
> pip install quill-delta
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Rendering HTML in Python
|
|
64
|
+
|
|
65
|
+
This library includes a module `delta.html` that renders html from an operation list,
|
|
66
|
+
allowing you to render Quill Delta operations in full from a Python server.
|
|
67
|
+
|
|
68
|
+
For example:
|
|
69
|
+
|
|
70
|
+
from delta import html
|
|
71
|
+
|
|
72
|
+
ops = [
|
|
73
|
+
{ "insert":"Quill\nEditor\n\n" },
|
|
74
|
+
{ "insert": "bold",
|
|
75
|
+
"attributes": {"bold": True}},
|
|
76
|
+
{ "insert":" and the " },
|
|
77
|
+
{ "insert":"italic",
|
|
78
|
+
"attributes": { "italic": True }},
|
|
79
|
+
{ "insert":"\n\nNormal\n" },
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
html.render(ops)
|
|
83
|
+
|
|
84
|
+
Result (line formatting added for readability):
|
|
85
|
+
|
|
86
|
+
<p>Quill</p>
|
|
87
|
+
<p>Editor</p>
|
|
88
|
+
<p><br></p>
|
|
89
|
+
<p><strong>bold</strong> and the <em>italic</em></p>
|
|
90
|
+
<p><br></p>
|
|
91
|
+
<p>Normal</p>
|
|
92
|
+
|
|
93
|
+
[See test_html.py](tests/test_html.py) for more examples.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Developing
|
|
97
|
+
|
|
98
|
+
## Setup
|
|
99
|
+
If you'd to contribute to quill-delta-python, get started setting your development environment by running:
|
|
100
|
+
|
|
101
|
+
Checkout the repository
|
|
102
|
+
|
|
103
|
+
> git clone https://github.com/forgeworks/quill-delta-python.git
|
|
104
|
+
|
|
105
|
+
Make sure you have python 3 installed, e.g.,
|
|
106
|
+
|
|
107
|
+
> python --version
|
|
108
|
+
|
|
109
|
+
From inside your new quill-delta-python directory:
|
|
110
|
+
|
|
111
|
+
> python3 -m venv env
|
|
112
|
+
> source env/bin/activate
|
|
113
|
+
> pip install poetry
|
|
114
|
+
> poetry install -E html
|
|
115
|
+
|
|
116
|
+
## Tests
|
|
117
|
+
To run tests do:
|
|
118
|
+
|
|
119
|
+
> py.test
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
|
|
2
|
+
# Delta (Python Port)
|
|
3
|
+
|
|
4
|
+
Python port of the javascript Delta library for QuillJS: https://github.com/quilljs/delta
|
|
5
|
+
|
|
6
|
+
Some basic pythonizing has been done, but mostly it works exactly like the above library.
|
|
7
|
+
|
|
8
|
+
There is no other python specific documentation at this time, sorry. Please see the tests
|
|
9
|
+
for reference examples.
|
|
10
|
+
|
|
11
|
+
## Install with [Poetry](https://poetry.eustace.io/docs/#installation)
|
|
12
|
+
|
|
13
|
+
With HTML rendering:
|
|
14
|
+
|
|
15
|
+
> poetry add -E html quill-delta
|
|
16
|
+
|
|
17
|
+
Without HTML rendering:
|
|
18
|
+
|
|
19
|
+
> poetry add quill-delta
|
|
20
|
+
|
|
21
|
+
## Install with pip
|
|
22
|
+
|
|
23
|
+
Note: If you're using `zsh`, see below.
|
|
24
|
+
|
|
25
|
+
With HTML rendering:
|
|
26
|
+
|
|
27
|
+
> pip install quill-delta[html]
|
|
28
|
+
|
|
29
|
+
With HTML rendering (zsh):
|
|
30
|
+
|
|
31
|
+
> pip install quill-delta"[html]"
|
|
32
|
+
|
|
33
|
+
Without HTML rendering:
|
|
34
|
+
|
|
35
|
+
> pip install quill-delta
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Rendering HTML in Python
|
|
39
|
+
|
|
40
|
+
This library includes a module `delta.html` that renders html from an operation list,
|
|
41
|
+
allowing you to render Quill Delta operations in full from a Python server.
|
|
42
|
+
|
|
43
|
+
For example:
|
|
44
|
+
|
|
45
|
+
from delta import html
|
|
46
|
+
|
|
47
|
+
ops = [
|
|
48
|
+
{ "insert":"Quill\nEditor\n\n" },
|
|
49
|
+
{ "insert": "bold",
|
|
50
|
+
"attributes": {"bold": True}},
|
|
51
|
+
{ "insert":" and the " },
|
|
52
|
+
{ "insert":"italic",
|
|
53
|
+
"attributes": { "italic": True }},
|
|
54
|
+
{ "insert":"\n\nNormal\n" },
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
html.render(ops)
|
|
58
|
+
|
|
59
|
+
Result (line formatting added for readability):
|
|
60
|
+
|
|
61
|
+
<p>Quill</p>
|
|
62
|
+
<p>Editor</p>
|
|
63
|
+
<p><br></p>
|
|
64
|
+
<p><strong>bold</strong> and the <em>italic</em></p>
|
|
65
|
+
<p><br></p>
|
|
66
|
+
<p>Normal</p>
|
|
67
|
+
|
|
68
|
+
[See test_html.py](tests/test_html.py) for more examples.
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Developing
|
|
72
|
+
|
|
73
|
+
## Setup
|
|
74
|
+
If you'd to contribute to quill-delta-python, get started setting your development environment by running:
|
|
75
|
+
|
|
76
|
+
Checkout the repository
|
|
77
|
+
|
|
78
|
+
> git clone https://github.com/forgeworks/quill-delta-python.git
|
|
79
|
+
|
|
80
|
+
Make sure you have python 3 installed, e.g.,
|
|
81
|
+
|
|
82
|
+
> python --version
|
|
83
|
+
|
|
84
|
+
From inside your new quill-delta-python directory:
|
|
85
|
+
|
|
86
|
+
> python3 -m venv env
|
|
87
|
+
> source env/bin/activate
|
|
88
|
+
> pip install poetry
|
|
89
|
+
> poetry install -E html
|
|
90
|
+
|
|
91
|
+
## Tests
|
|
92
|
+
To run tests do:
|
|
93
|
+
|
|
94
|
+
> py.test
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import diff_match_patch
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from functools import reduce
|
|
6
|
+
except:
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
from . import op
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
NULL_CHARACTER = chr(0)
|
|
13
|
+
DIFF_EQUAL = 0
|
|
14
|
+
DIFF_INSERT = 1
|
|
15
|
+
DIFF_DELETE = -1
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def merge(a, b):
|
|
19
|
+
return copy.deepcopy(a or {}).update(b or {})
|
|
20
|
+
|
|
21
|
+
def differ(a, b, timeout=1):
|
|
22
|
+
differ = diff_match_patch.diff_match_patch()
|
|
23
|
+
differ.Diff_Timeout = timeout
|
|
24
|
+
return differ.diff_main(a, b)
|
|
25
|
+
|
|
26
|
+
def smallest(*parts):
|
|
27
|
+
return min(filter(lambda x: x is not None, parts))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Delta(object):
|
|
31
|
+
def __init__(self, ops=None, **attrs):
|
|
32
|
+
if hasattr(ops, 'ops'):
|
|
33
|
+
ops = ops.ops
|
|
34
|
+
self.ops = ops or []
|
|
35
|
+
self.__dict__.update(attrs)
|
|
36
|
+
|
|
37
|
+
def __eq__(self, other):
|
|
38
|
+
return self.ops == other.ops
|
|
39
|
+
|
|
40
|
+
def __repr__(self):
|
|
41
|
+
return "{}({})".format(self.__class__.__name__, self.ops)
|
|
42
|
+
|
|
43
|
+
def insert(self, text, **attrs):
|
|
44
|
+
if text == "":
|
|
45
|
+
return self
|
|
46
|
+
new_op = {'insert': text}
|
|
47
|
+
if attrs:
|
|
48
|
+
new_op['attributes'] = attrs
|
|
49
|
+
return self.push(new_op)
|
|
50
|
+
|
|
51
|
+
def delete(self, length):
|
|
52
|
+
if length <= 0:
|
|
53
|
+
return self
|
|
54
|
+
return self.push({'delete': length});
|
|
55
|
+
|
|
56
|
+
def retain(self, length, **attrs):
|
|
57
|
+
if length <= 0:
|
|
58
|
+
return self
|
|
59
|
+
new_op = {'retain': length}
|
|
60
|
+
if attrs:
|
|
61
|
+
new_op['attributes'] = attrs
|
|
62
|
+
return self.push(new_op)
|
|
63
|
+
|
|
64
|
+
def push(self, operation):
|
|
65
|
+
index = len(self.ops)
|
|
66
|
+
new_op = copy.deepcopy(operation)
|
|
67
|
+
try:
|
|
68
|
+
last_op = self.ops[index - 1]
|
|
69
|
+
except IndexError:
|
|
70
|
+
self.ops.append(new_op)
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
if op.type(new_op) == op.type(last_op) == 'delete':
|
|
74
|
+
last_op['delete'] += new_op['delete']
|
|
75
|
+
return self
|
|
76
|
+
|
|
77
|
+
if op.type(last_op) == 'delete' and op.type(new_op) == 'insert':
|
|
78
|
+
index -= 1
|
|
79
|
+
try:
|
|
80
|
+
last_op = self.ops[index - 1]
|
|
81
|
+
except IndexError:
|
|
82
|
+
self.ops.insert(0, new_op)
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
if new_op.get('attributes') == last_op.get('attributes'):
|
|
86
|
+
if isinstance(new_op.get('insert'), str) and isinstance(last_op.get('insert'), str):
|
|
87
|
+
last_op['insert'] += new_op['insert']
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
if isinstance(new_op.get('retain'), int) and isinstance(last_op.get('retain'), int):
|
|
91
|
+
last_op['retain'] += new_op['retain']
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
self.ops.insert(index, new_op)
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def extend(self, ops):
|
|
98
|
+
if hasattr(ops, 'ops'):
|
|
99
|
+
ops = ops.ops
|
|
100
|
+
if not ops:
|
|
101
|
+
return self
|
|
102
|
+
self.push(ops[0])
|
|
103
|
+
self.ops.extend(ops[1:])
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def concat(self, other):
|
|
107
|
+
delta = self.__class__(copy.deepcopy(self.ops))
|
|
108
|
+
delta.extend(other)
|
|
109
|
+
return delta
|
|
110
|
+
|
|
111
|
+
def chop(self):
|
|
112
|
+
try:
|
|
113
|
+
last_op = self.ops[-1]
|
|
114
|
+
if op.type(last_op) == 'retain' and not last_op.get('attributes'):
|
|
115
|
+
self.ops.pop()
|
|
116
|
+
except IndexError:
|
|
117
|
+
pass
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def document(self):
|
|
121
|
+
parts = []
|
|
122
|
+
for op in self:
|
|
123
|
+
insert = op.get('insert')
|
|
124
|
+
if insert:
|
|
125
|
+
if isinstance(insert, str):
|
|
126
|
+
parts.append(insert)
|
|
127
|
+
else:
|
|
128
|
+
parts.append(NULL_CHARACTER)
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError("document() can only be called on Deltas that have only insert ops")
|
|
131
|
+
return "".join(parts)
|
|
132
|
+
|
|
133
|
+
def __iter__(self):
|
|
134
|
+
return iter(self.ops)
|
|
135
|
+
|
|
136
|
+
def __getitem__(self, index):
|
|
137
|
+
if isinstance(index, int):
|
|
138
|
+
start = index
|
|
139
|
+
stop = index + 1
|
|
140
|
+
|
|
141
|
+
elif isinstance(index, slice):
|
|
142
|
+
start = index.start or 0
|
|
143
|
+
stop = index.stop or None
|
|
144
|
+
|
|
145
|
+
if index.step is not None:
|
|
146
|
+
print(index)
|
|
147
|
+
raise ValueError("no support for step slices")
|
|
148
|
+
|
|
149
|
+
if (start is not None and start < 0) or (stop is not None and stop < 0):
|
|
150
|
+
raise ValueError("no support for negative indexing.")
|
|
151
|
+
|
|
152
|
+
ops = []
|
|
153
|
+
iter = self.iterator()
|
|
154
|
+
index = 0
|
|
155
|
+
while iter.has_next():
|
|
156
|
+
if stop is not None and index >= stop:
|
|
157
|
+
break
|
|
158
|
+
if index < start:
|
|
159
|
+
next_op = iter.next(start - index)
|
|
160
|
+
else:
|
|
161
|
+
if stop is not None:
|
|
162
|
+
next_op = iter.next(stop-index)
|
|
163
|
+
else:
|
|
164
|
+
next_op = iter.next()
|
|
165
|
+
ops.append(next_op)
|
|
166
|
+
index += op.length(next_op)
|
|
167
|
+
|
|
168
|
+
return Delta(ops)
|
|
169
|
+
|
|
170
|
+
def __len__(self):
|
|
171
|
+
return sum(op.length(o) for o in self.ops)
|
|
172
|
+
|
|
173
|
+
def iterator(self):
|
|
174
|
+
return op.iterator(self.ops)
|
|
175
|
+
|
|
176
|
+
def change_length(self):
|
|
177
|
+
length = 0
|
|
178
|
+
for operator in self:
|
|
179
|
+
if op.type(operator) == 'delete':
|
|
180
|
+
length -= operator['delete']
|
|
181
|
+
else:
|
|
182
|
+
length += op.length(operator)
|
|
183
|
+
return length
|
|
184
|
+
|
|
185
|
+
def length(self):
|
|
186
|
+
return sum(op.length(o) for o in self)
|
|
187
|
+
|
|
188
|
+
def compose(self, other):
|
|
189
|
+
self_it = self.iterator()
|
|
190
|
+
other_it = other.iterator()
|
|
191
|
+
delta = self.__class__()
|
|
192
|
+
while self_it.has_next() or other_it.has_next():
|
|
193
|
+
if other_it.peek_type() == 'insert':
|
|
194
|
+
delta.push(other_it.next())
|
|
195
|
+
elif self_it.peek_type() == 'delete':
|
|
196
|
+
delta.push(self_it.next())
|
|
197
|
+
else:
|
|
198
|
+
length = smallest(self_it.peek_length(), other_it.peek_length())
|
|
199
|
+
self_op = self_it.next(length)
|
|
200
|
+
other_op = other_it.next(length)
|
|
201
|
+
if 'retain' in other_op:
|
|
202
|
+
new_op = {}
|
|
203
|
+
if 'retain' in self_op:
|
|
204
|
+
new_op['retain'] = length
|
|
205
|
+
elif 'insert' in self_op:
|
|
206
|
+
new_op['insert'] = self_op['insert']
|
|
207
|
+
# Preserve null when composing with a retain, otherwise remove it for inserts
|
|
208
|
+
attributes = op.compose(self_op.get('attributes'), other_op.get('attributes'), isinstance(self_op.get('retain'), int))
|
|
209
|
+
if (attributes):
|
|
210
|
+
new_op['attributes'] = attributes
|
|
211
|
+
delta.push(new_op)
|
|
212
|
+
# Other op should be delete, we could be an insert or retain
|
|
213
|
+
# Insert + delete cancels out
|
|
214
|
+
elif op.type(other_op) == 'delete' and 'retain' in self_op:
|
|
215
|
+
delta.push(other_op)
|
|
216
|
+
return delta.chop()
|
|
217
|
+
|
|
218
|
+
def diff(self, other):
|
|
219
|
+
"""
|
|
220
|
+
Returns a diff of two *documents*, which is defined as a delta
|
|
221
|
+
with only inserts.
|
|
222
|
+
"""
|
|
223
|
+
if self.ops == other.ops:
|
|
224
|
+
return self.__class__()
|
|
225
|
+
|
|
226
|
+
self_doc = self.document()
|
|
227
|
+
other_doc = other.document()
|
|
228
|
+
self_it = self.iterator()
|
|
229
|
+
other_it = other.iterator()
|
|
230
|
+
|
|
231
|
+
delta = self.__class__()
|
|
232
|
+
for code, text in differ(self_doc, other_doc):
|
|
233
|
+
length = len(text)
|
|
234
|
+
while length > 0:
|
|
235
|
+
op_length = 0
|
|
236
|
+
if code == DIFF_INSERT:
|
|
237
|
+
op_length = min(other_it.peek_length(), length)
|
|
238
|
+
delta.push(other_it.next(op_length))
|
|
239
|
+
elif code == DIFF_DELETE:
|
|
240
|
+
op_length = min(length, self_it.peek_length())
|
|
241
|
+
self_it.next(op_length)
|
|
242
|
+
delta.delete(op_length)
|
|
243
|
+
elif code == DIFF_EQUAL:
|
|
244
|
+
op_length = min(self_it.peek_length(), other_it.peek_length(), length)
|
|
245
|
+
self_op = self_it.next(op_length)
|
|
246
|
+
other_op = other_it.next(op_length)
|
|
247
|
+
if self_op.get('insert') == other_op.get('insert'):
|
|
248
|
+
attributes = op.diff(self_op.get('attributes'), other_op.get('attributes'))
|
|
249
|
+
delta.retain(op_length, **(attributes or {}))
|
|
250
|
+
else:
|
|
251
|
+
delta.push(other_op).delete(op_length)
|
|
252
|
+
else:
|
|
253
|
+
raise RuntimeError("Diff library returned unknown op code: %r", code)
|
|
254
|
+
if op_length == 0:
|
|
255
|
+
return
|
|
256
|
+
length -= op_length
|
|
257
|
+
return delta.chop()
|
|
258
|
+
|
|
259
|
+
def each_line(self, fn, newline='\n'):
|
|
260
|
+
for line, attributes, index in self.iter_lines():
|
|
261
|
+
if fn(line, attributes, index) is False:
|
|
262
|
+
break
|
|
263
|
+
|
|
264
|
+
def iter_lines(self, newline='\n'):
|
|
265
|
+
iter = self.iterator()
|
|
266
|
+
line = self.__class__()
|
|
267
|
+
i = 0
|
|
268
|
+
while iter.has_next():
|
|
269
|
+
if iter.peek_type() != 'insert':
|
|
270
|
+
return
|
|
271
|
+
self_op = iter.peek()
|
|
272
|
+
start = op.length(self_op) - iter.peek_length()
|
|
273
|
+
if isinstance(self_op.get('insert'), str):
|
|
274
|
+
index = self_op['insert'][start:].find(newline)
|
|
275
|
+
else:
|
|
276
|
+
index = -1
|
|
277
|
+
|
|
278
|
+
if index < 0:
|
|
279
|
+
line.push(iter.next())
|
|
280
|
+
elif index > 0:
|
|
281
|
+
line.push(iter.next(index))
|
|
282
|
+
else:
|
|
283
|
+
yield line, iter.next(1).get('attributes', {}), i
|
|
284
|
+
i += 1
|
|
285
|
+
line = Delta()
|
|
286
|
+
if len(line) > 0:
|
|
287
|
+
yield line, {}, i
|
|
288
|
+
|
|
289
|
+
def transform(self, other, priority=False):
|
|
290
|
+
if isinstance(other, int):
|
|
291
|
+
return self.transform_position(other, priority)
|
|
292
|
+
|
|
293
|
+
self_it = self.iterator()
|
|
294
|
+
other_it = other.iterator()
|
|
295
|
+
delta = Delta()
|
|
296
|
+
|
|
297
|
+
while self_it.has_next() or other_it.has_next():
|
|
298
|
+
if self_it.peek_type() == 'insert' and (priority or other_it.peek_type() != 'insert'):
|
|
299
|
+
delta.retain(op.length(self_it.next()))
|
|
300
|
+
elif other_it.peek_type() == 'insert':
|
|
301
|
+
delta.push(other_it.next())
|
|
302
|
+
else:
|
|
303
|
+
length = smallest(self_it.peek_length(), other_it.peek_length())
|
|
304
|
+
self_op = self_it.next(length)
|
|
305
|
+
other_op = other_it.next(length)
|
|
306
|
+
if self_op.get('delete'):
|
|
307
|
+
# Our delete either makes their delete redundant or removes their retain
|
|
308
|
+
continue
|
|
309
|
+
elif other_op.get('delete'):
|
|
310
|
+
delta.push(other_op)
|
|
311
|
+
else:
|
|
312
|
+
# We retain either their retain or insert
|
|
313
|
+
delta.retain(length, **(op.transform(self_op.get('attributes'), other_op.get('attributes'), priority) or {}))
|
|
314
|
+
|
|
315
|
+
return delta.chop()
|
|
316
|
+
|
|
317
|
+
def transform_position(self, index, priority=False):
|
|
318
|
+
iter = self.iterator()
|
|
319
|
+
offset = 0
|
|
320
|
+
while iter.has_next() and offset <= index:
|
|
321
|
+
length = iter.peek_length()
|
|
322
|
+
next_type = iter.peek_type()
|
|
323
|
+
iter.next()
|
|
324
|
+
if next_type == 'delete':
|
|
325
|
+
index -= min(length, index - offset)
|
|
326
|
+
continue
|
|
327
|
+
elif next_type == 'insert' and (offset < index or not priority):
|
|
328
|
+
index += length
|
|
329
|
+
offset += length
|
|
330
|
+
return index
|