onetick-py 1.162.2__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.
Files changed (152) hide show
  1. locator_parser/__init__.py +0 -0
  2. locator_parser/acl.py +73 -0
  3. locator_parser/actions.py +266 -0
  4. locator_parser/common.py +365 -0
  5. locator_parser/io.py +41 -0
  6. locator_parser/locator.py +150 -0
  7. onetick/__init__.py +101 -0
  8. onetick/doc_utilities/__init__.py +3 -0
  9. onetick/doc_utilities/napoleon.py +40 -0
  10. onetick/doc_utilities/ot_doctest.py +140 -0
  11. onetick/doc_utilities/snippets.py +280 -0
  12. onetick/lib/__init__.py +4 -0
  13. onetick/lib/instance.py +138 -0
  14. onetick/py/__init__.py +290 -0
  15. onetick/py/_stack_info.py +89 -0
  16. onetick/py/_version.py +2 -0
  17. onetick/py/aggregations/__init__.py +11 -0
  18. onetick/py/aggregations/_base.py +645 -0
  19. onetick/py/aggregations/_docs.py +912 -0
  20. onetick/py/aggregations/compute.py +286 -0
  21. onetick/py/aggregations/functions.py +2216 -0
  22. onetick/py/aggregations/generic.py +104 -0
  23. onetick/py/aggregations/high_low.py +80 -0
  24. onetick/py/aggregations/num_distinct.py +83 -0
  25. onetick/py/aggregations/order_book.py +427 -0
  26. onetick/py/aggregations/other.py +1014 -0
  27. onetick/py/backports.py +26 -0
  28. onetick/py/cache.py +373 -0
  29. onetick/py/callback/__init__.py +5 -0
  30. onetick/py/callback/callback.py +275 -0
  31. onetick/py/callback/callbacks.py +131 -0
  32. onetick/py/compatibility.py +752 -0
  33. onetick/py/configuration.py +736 -0
  34. onetick/py/core/__init__.py +0 -0
  35. onetick/py/core/_csv_inspector.py +93 -0
  36. onetick/py/core/_internal/__init__.py +0 -0
  37. onetick/py/core/_internal/_manually_bound_value.py +6 -0
  38. onetick/py/core/_internal/_nodes_history.py +250 -0
  39. onetick/py/core/_internal/_op_utils/__init__.py +0 -0
  40. onetick/py/core/_internal/_op_utils/every_operand.py +9 -0
  41. onetick/py/core/_internal/_op_utils/is_const.py +10 -0
  42. onetick/py/core/_internal/_per_tick_scripts/tick_list_sort_template.script +121 -0
  43. onetick/py/core/_internal/_proxy_node.py +140 -0
  44. onetick/py/core/_internal/_state_objects.py +2307 -0
  45. onetick/py/core/_internal/_state_vars.py +87 -0
  46. onetick/py/core/_source/__init__.py +0 -0
  47. onetick/py/core/_source/_symbol_param.py +95 -0
  48. onetick/py/core/_source/schema.py +97 -0
  49. onetick/py/core/_source/source_methods/__init__.py +0 -0
  50. onetick/py/core/_source/source_methods/aggregations.py +810 -0
  51. onetick/py/core/_source/source_methods/applyers.py +296 -0
  52. onetick/py/core/_source/source_methods/columns.py +141 -0
  53. onetick/py/core/_source/source_methods/data_quality.py +301 -0
  54. onetick/py/core/_source/source_methods/debugs.py +270 -0
  55. onetick/py/core/_source/source_methods/drops.py +120 -0
  56. onetick/py/core/_source/source_methods/fields.py +619 -0
  57. onetick/py/core/_source/source_methods/filters.py +1001 -0
  58. onetick/py/core/_source/source_methods/joins.py +1393 -0
  59. onetick/py/core/_source/source_methods/merges.py +566 -0
  60. onetick/py/core/_source/source_methods/misc.py +1325 -0
  61. onetick/py/core/_source/source_methods/pandases.py +155 -0
  62. onetick/py/core/_source/source_methods/renames.py +356 -0
  63. onetick/py/core/_source/source_methods/sorts.py +183 -0
  64. onetick/py/core/_source/source_methods/switches.py +142 -0
  65. onetick/py/core/_source/source_methods/symbols.py +117 -0
  66. onetick/py/core/_source/source_methods/times.py +627 -0
  67. onetick/py/core/_source/source_methods/writes.py +702 -0
  68. onetick/py/core/_source/symbol.py +202 -0
  69. onetick/py/core/_source/tmp_otq.py +222 -0
  70. onetick/py/core/column.py +209 -0
  71. onetick/py/core/column_operations/__init__.py +0 -0
  72. onetick/py/core/column_operations/_methods/__init__.py +4 -0
  73. onetick/py/core/column_operations/_methods/_internal.py +28 -0
  74. onetick/py/core/column_operations/_methods/conversions.py +215 -0
  75. onetick/py/core/column_operations/_methods/methods.py +294 -0
  76. onetick/py/core/column_operations/_methods/op_types.py +150 -0
  77. onetick/py/core/column_operations/accessors/__init__.py +0 -0
  78. onetick/py/core/column_operations/accessors/_accessor.py +30 -0
  79. onetick/py/core/column_operations/accessors/decimal_accessor.py +92 -0
  80. onetick/py/core/column_operations/accessors/dt_accessor.py +464 -0
  81. onetick/py/core/column_operations/accessors/float_accessor.py +160 -0
  82. onetick/py/core/column_operations/accessors/str_accessor.py +1374 -0
  83. onetick/py/core/column_operations/base.py +1061 -0
  84. onetick/py/core/cut_builder.py +149 -0
  85. onetick/py/core/db_constants.py +20 -0
  86. onetick/py/core/eval_query.py +244 -0
  87. onetick/py/core/lambda_object.py +442 -0
  88. onetick/py/core/multi_output_source.py +193 -0
  89. onetick/py/core/per_tick_script.py +2253 -0
  90. onetick/py/core/query_inspector.py +465 -0
  91. onetick/py/core/source.py +1663 -0
  92. onetick/py/db/__init__.py +2 -0
  93. onetick/py/db/_inspection.py +1042 -0
  94. onetick/py/db/db.py +1423 -0
  95. onetick/py/db/utils.py +64 -0
  96. onetick/py/docs/__init__.py +0 -0
  97. onetick/py/docs/docstring_parser.py +112 -0
  98. onetick/py/docs/utils.py +81 -0
  99. onetick/py/functions.py +2354 -0
  100. onetick/py/license.py +188 -0
  101. onetick/py/log.py +88 -0
  102. onetick/py/math.py +947 -0
  103. onetick/py/misc.py +437 -0
  104. onetick/py/oqd/__init__.py +22 -0
  105. onetick/py/oqd/eps.py +1195 -0
  106. onetick/py/oqd/sources.py +325 -0
  107. onetick/py/otq.py +211 -0
  108. onetick/py/pyomd_mock.py +47 -0
  109. onetick/py/run.py +841 -0
  110. onetick/py/servers.py +173 -0
  111. onetick/py/session.py +1342 -0
  112. onetick/py/sources/__init__.py +19 -0
  113. onetick/py/sources/cache.py +167 -0
  114. onetick/py/sources/common.py +126 -0
  115. onetick/py/sources/csv.py +642 -0
  116. onetick/py/sources/custom.py +85 -0
  117. onetick/py/sources/data_file.py +305 -0
  118. onetick/py/sources/data_source.py +1049 -0
  119. onetick/py/sources/empty.py +94 -0
  120. onetick/py/sources/odbc.py +337 -0
  121. onetick/py/sources/order_book.py +238 -0
  122. onetick/py/sources/parquet.py +168 -0
  123. onetick/py/sources/pit.py +191 -0
  124. onetick/py/sources/query.py +495 -0
  125. onetick/py/sources/snapshots.py +419 -0
  126. onetick/py/sources/split_query_output_by_symbol.py +198 -0
  127. onetick/py/sources/symbology_mapping.py +123 -0
  128. onetick/py/sources/symbols.py +357 -0
  129. onetick/py/sources/ticks.py +825 -0
  130. onetick/py/sql.py +70 -0
  131. onetick/py/state.py +256 -0
  132. onetick/py/types.py +2056 -0
  133. onetick/py/utils/__init__.py +70 -0
  134. onetick/py/utils/acl.py +93 -0
  135. onetick/py/utils/config.py +186 -0
  136. onetick/py/utils/default.py +49 -0
  137. onetick/py/utils/file.py +38 -0
  138. onetick/py/utils/helpers.py +76 -0
  139. onetick/py/utils/locator.py +94 -0
  140. onetick/py/utils/perf.py +499 -0
  141. onetick/py/utils/query.py +49 -0
  142. onetick/py/utils/render.py +1139 -0
  143. onetick/py/utils/script.py +244 -0
  144. onetick/py/utils/temp.py +471 -0
  145. onetick/py/utils/types.py +118 -0
  146. onetick/py/utils/tz.py +82 -0
  147. onetick_py-1.162.2.dist-info/METADATA +148 -0
  148. onetick_py-1.162.2.dist-info/RECORD +152 -0
  149. onetick_py-1.162.2.dist-info/WHEEL +5 -0
  150. onetick_py-1.162.2.dist-info/entry_points.txt +2 -0
  151. onetick_py-1.162.2.dist-info/licenses/LICENSE +21 -0
  152. onetick_py-1.162.2.dist-info/top_level.txt +2 -0
File without changes
locator_parser/acl.py ADDED
@@ -0,0 +1,73 @@
1
+ from locator_parser.common import Entity
2
+ from locator_parser.actions import DoNothing
3
+
4
+
5
+ class Allow(Entity):
6
+ TAG = "ALLOW"
7
+ SINGLE = True
8
+
9
+
10
+ class DB(Entity):
11
+ TAG = "DB"
12
+ HAS_PROPERTIES = True
13
+ CHILDREN = [Allow]
14
+
15
+
16
+ class DBs(Entity):
17
+ TAG = "DATABASES"
18
+ CHILDREN = [DB]
19
+
20
+
21
+ class User(Entity):
22
+ TAG = "USER"
23
+ SINGLE = True
24
+
25
+
26
+ class Role(Entity):
27
+ TAG = "ROLE"
28
+ HAS_PROPERTIES = True
29
+ CHILDREN = [User]
30
+
31
+
32
+ class Roles(Entity):
33
+ TAG = "ROLES"
34
+ CHILDREN = [Role]
35
+
36
+
37
+ class EP(Entity):
38
+ TAG = "EP"
39
+ HAS_PROPERTIES = True
40
+ CHILDREN = [Allow]
41
+
42
+
43
+ class EPs(Entity):
44
+ TAG = "EVENT_PROCESSORS"
45
+ CHILDREN = [EP]
46
+
47
+
48
+ def parse_acl(reader, writer, action=DoNothing()):
49
+ writer.refresh()
50
+ reader.set_writer(writer)
51
+
52
+ roles_p = Roles()
53
+ databases_p = DBs()
54
+ eps_p = EPs()
55
+
56
+ for _ in reader:
57
+ roles_p(reader, writer, action)
58
+ databases_p(reader, writer, action)
59
+ eps_p(reader, writer, action)
60
+
61
+
62
+ # ----------------------------------------- #
63
+ # set parent
64
+ for _, cls in list(globals().items()):
65
+ try:
66
+ if issubclass(cls, Entity) and cls != Entity:
67
+ for child in cls.CHILDREN:
68
+ child.PARENT = Entity
69
+
70
+ if not cls.HAS_PROPERTIES and not cls.SINGLE:
71
+ child.PARENT = cls
72
+ except Exception:
73
+ continue
@@ -0,0 +1,266 @@
1
+ from locator_parser.common import Action, Entity
2
+ from collections import OrderedDict
3
+
4
+
5
+ class Add(Action):
6
+ """
7
+ Add entity.
8
+
9
+ Example:
10
+ raw_db = Add(RawDB(id='PRIMARY'))
11
+ raw_db.add_where(DB, id='DB_2')
12
+ """
13
+
14
+ def __init__(self, obj):
15
+ self.what_to_add = obj.to_lines()
16
+
17
+ super(Add, self).__init__()
18
+
19
+ if obj.__class__.PARENT is not Entity:
20
+ self._add_conditions[(obj.__class__.PARENT.TAG.lower(), None, None)] = False
21
+
22
+ def do(self, obj, all_lines, properties, writer):
23
+ writer.add(self.what_to_add)
24
+
25
+
26
+ class Modify(Action):
27
+ """
28
+ Modify entity properties.
29
+ If property doesn't exist, then it will be added.
30
+ If property is set to None, then it will be removed.
31
+
32
+ Example:
33
+ action = Modify(symbology='TDEQ', db_archive_tmp_dir=None)
34
+ action.add_where(DB, id="DB_1")
35
+ """
36
+
37
+ def __init__(self, **kwargs):
38
+ if len(kwargs) > 1:
39
+ raise Exception("It is not supported to set multiple keys yet!")
40
+
41
+ field, value = "", ""
42
+ delete = False
43
+
44
+ for k, v in kwargs.items():
45
+ if v is None:
46
+ delete = True
47
+
48
+ field, value = k, str(v)
49
+
50
+ self.init(field, value, delete)
51
+
52
+ super(Modify, self).__init__()
53
+
54
+ def init(self, field, value, delete):
55
+ self.field = field.lower()
56
+ self.value = '"' + value + '"'
57
+ self.delete = delete
58
+
59
+ def do(self, obj, all_lines, properties_lines, writer):
60
+ if not obj.__class__.HAS_PROPERTIES:
61
+ raise Exception("Entity with tag '" + obj.__class__.TAG + "' does not expect to have properties!")
62
+
63
+ new_properties = list(properties_lines)
64
+
65
+ if not hasattr(obj, self.field):
66
+ for line in new_properties:
67
+ end_pos = line.find(">")
68
+
69
+ if end_pos != -1:
70
+ if line.find("/>") != -1 and line.find("/>") < end_pos:
71
+ end_pos = line.find("/>")
72
+ # if
73
+
74
+ line = line[:end_pos] + " %s=%s" % (self.field, self.value) + line[end_pos:]
75
+ break
76
+
77
+ new_properties[-1] = line
78
+ else:
79
+ for inx, line in enumerate(new_properties):
80
+ pos = line.lower().find(self.field + "=")
81
+
82
+ if pos != -1:
83
+ # modify property
84
+ t_line = line[pos + len(self.field):].split()[0].split("/>")[0].split(">")[0].split("=")[1]
85
+
86
+ if self.delete:
87
+ line = line.replace(line[pos: pos + len(self.field) + 1] + t_line, "")
88
+ else:
89
+ line = line.replace(
90
+ line[pos: pos + len(self.field) + 1] + t_line,
91
+ line[pos: pos + len(self.field) + 1] + self.value,
92
+ )
93
+
94
+ break
95
+
96
+ new_properties[inx] = line
97
+
98
+ writer.modify(len(all_lines), properties_lines, new_properties)
99
+
100
+
101
+ class Delete(Action):
102
+ """
103
+ Delete entity
104
+
105
+ Example:
106
+ action = Delete()
107
+ action.add_where(DB, id="DB_1")
108
+ """
109
+
110
+ def do(self, obj, all_lines, properites_lines, writer):
111
+ writer.delete(all_lines)
112
+
113
+
114
+ class Get(Action):
115
+ """
116
+ Populate self.result object with properites of found object
117
+
118
+ Example:
119
+ action = Get()
120
+ action.add_where(DB, id='LB')
121
+ """
122
+
123
+ def do(self, obj, all_lines, properties_lines, writer):
124
+ self.lines = all_lines
125
+
126
+ # self.result = obj.__class__()
127
+ properties_dict = {}
128
+
129
+ properties_lines = " ".join(properties_lines)
130
+
131
+ # variables to concat space separated
132
+ # values
133
+ res_token = ""
134
+ double_quotes_count = 0
135
+ single_quotes_count = 0
136
+
137
+ for token in filter(lambda x: x != "" and x != "<" and x != "/>" and x != ">", properties_lines.split()[1:]):
138
+ # ---------------------
139
+ # Logic to combine values in quotes are separated
140
+ # by space
141
+ if '"' in token:
142
+ double_quotes_count += token.count('"')
143
+ if "'" in token:
144
+ single_quotes_count += token.count("'")
145
+
146
+ if double_quotes_count > 0 or single_quotes_count > 0:
147
+ if (double_quotes_count + single_quotes_count) % 2 == 0:
148
+ res_token += token
149
+ double_quotes_count = 0
150
+ single_quotes_count = 0
151
+ else:
152
+ res_token += token
153
+ continue
154
+ else:
155
+ res_token = token
156
+ # --------------------
157
+
158
+ t_list = res_token.split("=")
159
+
160
+ # refresh to do not concat
161
+ # already ready token
162
+ res_token = ""
163
+ double_quotes_count = 0
164
+ single_quotes_count = 0
165
+
166
+ key, value = t_list[0], t_list[1]
167
+
168
+ if value[-2:] == "/>":
169
+ value = value[:-2]
170
+
171
+ if value[-1:] == ">" or value[-1:] == "/":
172
+ value = value[:-1]
173
+
174
+ properties_dict[key.lower()] = value.replace('"', "")
175
+ # setattr(self.result, key.lower(), value.replace('"', ''))
176
+
177
+ self.result = obj.__class__(**properties_dict)
178
+
179
+ for key, value in properties_dict.items():
180
+ setattr(self.result, key, value)
181
+
182
+
183
+ class GetAll(Action):
184
+
185
+ def __init__(self):
186
+ super(GetAll, self).__init__()
187
+ self.result = []
188
+
189
+ def do(self, obj, all_lines, properties_lines, writer):
190
+ get = Get()
191
+ get.do(obj, all_lines, properties_lines, writer)
192
+
193
+ self.result.append(get.result)
194
+
195
+ def apply(self, tag, entity):
196
+ if self.executed:
197
+ self.just_executed = False
198
+ self.executed = False
199
+
200
+ # Base is one that we are looking for; this entity has only
201
+ # tag specified.
202
+ # Here is logic to refresh the base, because we have to go
203
+ # through the subtree.
204
+ is_base = False
205
+ for key, value in reversed(self.conditions.items()):
206
+ t, f, v = key
207
+
208
+ if f is None and v is None:
209
+ is_base = True
210
+
211
+ if is_base:
212
+ self.conditions[key] = False
213
+ # leave, because we support only the last open-ended base
214
+ break
215
+
216
+ # first time it updates and then does nothing
217
+ self.conditions.update(self._add_conditions)
218
+ self._add_conditions = OrderedDict()
219
+
220
+ tag = tag.lower()
221
+
222
+ for key, value in self.conditions.items():
223
+ t, f, v = key
224
+
225
+ if tag == t:
226
+ if f:
227
+ if hasattr(entity, f) and getattr(entity, f) == '"' + v + '"':
228
+ self.conditions[(t, f, v)] = True
229
+ else:
230
+ # if we move to the same similar section, but with another key,
231
+ # then set it to False, because in the previous section it might be
232
+ # set to True
233
+ self.conditions[(t, f, v)] = False
234
+ else:
235
+ self.conditions[(t, f, v)] = True
236
+ else:
237
+ if value:
238
+ continue
239
+
240
+ if not self.conditions[(t, f, v)]:
241
+ break
242
+
243
+ self.executed = self.is_result()
244
+ self.just_executed = self.executed
245
+
246
+
247
+ class Show(Action):
248
+ """
249
+ Print object and all children objects
250
+
251
+ Example:
252
+ action = Show()
253
+ action.add_where(DB, id='S_ORDERS_LB')
254
+ """
255
+
256
+ def do(self, obj, all_lines, properties_lines, writer):
257
+ print("\n".join(all_lines))
258
+
259
+
260
+ class DoNothing(Action):
261
+ """
262
+ Default action that does nothing
263
+ """
264
+
265
+ def do(self, *args, **kwargs):
266
+ pass
@@ -0,0 +1,365 @@
1
+ import re
2
+ from collections import OrderedDict
3
+ from abc import ABC, abstractmethod
4
+ from typing import List, Type
5
+
6
+
7
+ def get_properties(declaration):
8
+ """
9
+ Construct dictionary from properties in format
10
+ <prop_1=value_1 prop_2=value_2 ... >
11
+ or
12
+ <prop_1=value_1 prop_2=value_2 ... />
13
+ and return it back
14
+ """
15
+ tokens = declaration.split()
16
+
17
+ properties = {
18
+ token.split("=")[0].lower(): token.split("=")[1].replace("/>", "").replace(">", "")
19
+ for token in tokens[1:]
20
+ if "=" in token
21
+ }
22
+
23
+ return properties
24
+
25
+
26
+ class Generator(object):
27
+
28
+ def __iter__(self):
29
+ return self
30
+
31
+ def __next__(self):
32
+ self.current = next(self.generator)
33
+ return self.current
34
+
35
+
36
+ class Action(ABC):
37
+
38
+ def __init__(self):
39
+ self.conditions = OrderedDict()
40
+ # additional conditions that we can deduce
41
+ # it's applicable for the sections without properties
42
+ #
43
+ # For example, if you add DB, then you can forget that
44
+ # it's situated in the single <databases> section; that
45
+ # information we retrieve from the entities inheritance
46
+ # put here and use along with conditions dict
47
+ self._add_conditions = OrderedDict()
48
+
49
+ self.executed = False
50
+ self.just_executed = False
51
+
52
+ @abstractmethod
53
+ def do(self):
54
+ pass
55
+
56
+ def clone(self, *args, **kwargs):
57
+ obj = self.__class__(*args, **kwargs)
58
+ obj.conditions = dict(self.conditions)
59
+
60
+ for key, _ in obj.conditions.items():
61
+ obj.conditions[key] = False
62
+
63
+ return obj
64
+
65
+ def apply(self, tag, entity):
66
+ if self.executed:
67
+ self.just_executed = False
68
+ return
69
+
70
+ # first time it updates and then does nothing
71
+ self.conditions.update(self._add_conditions)
72
+ self._add_conditions = OrderedDict()
73
+
74
+ tag = tag.lower()
75
+
76
+ for key, value in self.conditions.items():
77
+ t, f, v = key
78
+
79
+ if tag == t:
80
+ if f:
81
+ if hasattr(entity, f) and getattr(entity, f) == '"' + v + '"':
82
+ self.conditions[(t, f, v)] = True
83
+ else:
84
+ # if we move to the same similar section, but with another key,
85
+ # then set it to False, because in the previous section it might be
86
+ # set to True
87
+ self.conditions[(t, f, v)] = False
88
+ else:
89
+ self.conditions[(t, f, v)] = True
90
+ else:
91
+ if value:
92
+ continue
93
+
94
+ if not self.conditions[(t, f, v)]:
95
+ break
96
+
97
+ self.executed = self.is_result()
98
+ self.just_executed = self.executed
99
+
100
+ def is_result(self):
101
+ for v in self.conditions.values():
102
+ if not v:
103
+ return False
104
+
105
+ return True
106
+
107
+ def add_where(self, tag, **kwargs):
108
+ if type(tag) is str:
109
+ tag = tag
110
+ else:
111
+ # for case when it's class name
112
+ tag = tag.TAG
113
+
114
+ if len(kwargs) > 1:
115
+ raise Exception("It is not support to set multiple keys yet!")
116
+
117
+ if len(kwargs) == 0:
118
+ self.conditions[(tag.lower(), None, None)] = False
119
+ else:
120
+ self.conditions[(tag.lower(), list(kwargs.keys())[0].lower(), list(kwargs.values())[0])] = False
121
+
122
+
123
+ def apply_actions(func, reader, writer, actions=[], flush=False):
124
+ from locator_parser.io import LinesReader
125
+ from locator_parser.actions import DoNothing
126
+
127
+ if len(actions) == 0:
128
+ actions.append(DoNothing())
129
+
130
+ func(reader, writer, actions[0])
131
+
132
+ for action in actions[1:]:
133
+ lines = "\n".join(map(lambda x: x.replace("\n", ""), writer.lines))
134
+ new_reader = LinesReader(lines)
135
+
136
+ func(new_reader, writer, action)
137
+
138
+ if flush:
139
+ writer.flush()
140
+
141
+ for action in actions:
142
+ if not action.executed:
143
+ return False
144
+
145
+ return True
146
+
147
+
148
+ class Writer(ABC):
149
+
150
+ NOTHING_MSG = 0
151
+ NEW_MSG = 1
152
+ MODIFY_MSG = 2
153
+ DELETE_MSG = 3
154
+
155
+ def __init__(self):
156
+ self.refresh()
157
+
158
+ def refresh(self):
159
+ self.__gen = self.__generator__()
160
+ # init generator to send further messages
161
+ next(self.__gen)
162
+
163
+ self.lines = []
164
+
165
+ def __generator__(self):
166
+ while True:
167
+ msg_type = yield
168
+
169
+ if msg_type == Writer.NOTHING_MSG:
170
+ # since second value is a just next line
171
+ line = yield
172
+ self.lines.append(line)
173
+ elif msg_type == Writer.NEW_MSG:
174
+ lines = yield
175
+
176
+ assert type(lines) is list
177
+
178
+ self.lines = self.lines[:-1] + lines + self.lines[-1:]
179
+ elif msg_type == Writer.MODIFY_MSG:
180
+ pos, orig_lines, new_lines = yield
181
+
182
+ for inx, pair in enumerate(zip(orig_lines, new_lines)):
183
+ orig_line, new_line = pair
184
+
185
+ self.lines[-pos + inx] = self.lines[-pos + inx].replace(orig_line, new_line)
186
+ elif msg_type == Writer.DELETE_MSG:
187
+ lines_to_delete = yield
188
+
189
+ for _ in range(len(lines_to_delete)):
190
+ self.lines.pop()
191
+
192
+ def __next__(self):
193
+ return self.__send(None)
194
+
195
+ def __send(self, arg):
196
+ """private method to not allow user to send something directly"""
197
+ self.__gen.send(arg)
198
+
199
+ def put_as_is(self, line):
200
+ self.__send(Writer.NOTHING_MSG)
201
+ self.__send(line)
202
+
203
+ def add(self, lines):
204
+ self.__send(Writer.NEW_MSG)
205
+ self.__send(lines)
206
+
207
+ def modify(self, pos, orig_lines, new_lines):
208
+ self.__send(Writer.MODIFY_MSG)
209
+ self.__send((pos, orig_lines, new_lines))
210
+
211
+ def delete(self, lines):
212
+ self.__send(Writer.DELETE_MSG)
213
+ self.__send(lines)
214
+
215
+ @abstractmethod
216
+ def flush(self):
217
+ pass
218
+
219
+
220
+ class Reader(Generator):
221
+
222
+ def __init__(self):
223
+ self.generator = self.__generator__()
224
+ self.writer = None
225
+ self.current = None
226
+
227
+ def set_writer(self, writer):
228
+ self.__writer = writer
229
+
230
+ def __generator__(self):
231
+ for line in self.iterable_object:
232
+ # put just coming line as is into the writer
233
+ self.__writer.put_as_is(line)
234
+
235
+ line = line.strip()
236
+
237
+ if line == "" or line[0:1] == "#":
238
+ continue
239
+
240
+ yield line
241
+
242
+
243
+ class Entity(Generator, ABC):
244
+
245
+ # Tag to identify entity
246
+ TAG = ""
247
+ # Whether entity has properties
248
+ HAS_PROPERTIES = False
249
+ # Whether it is single line properties,
250
+ # it means that entity ends with '/>' instead of '</TAG>'
251
+ SINGLE = False
252
+ # children entities
253
+ CHILDREN: List[Type["Entity"]] = []
254
+
255
+ def __init__(self, **kwargs):
256
+ if self.__class__.SINGLE:
257
+ self.__class__.HAS_PROPERTIES = True
258
+
259
+ self.__tag = self.__class__.TAG.lower()
260
+
261
+ # define condition to identify section
262
+ pattern = "<\\s*" + self.__tag
263
+ if self.__class__.HAS_PROPERTIES:
264
+ pattern += "\\s+"
265
+ else:
266
+ pattern += "\\s*>"
267
+
268
+ self.__cond = lambda x: re.match(pattern, x.lower())
269
+ self.__lines = []
270
+ self.__properties_lines = []
271
+ self.__do = False
272
+ self.__custom_properties = kwargs
273
+
274
+ def to_lines(self):
275
+ result = []
276
+ line = "<" + self.__class__.TAG + " "
277
+
278
+ for key, value in self.__custom_properties.items():
279
+ line += key + '="' + str(value) + '" '
280
+
281
+ if self.__class__.SINGLE:
282
+ line += "/>"
283
+ result.append(line)
284
+ else:
285
+ line += ">"
286
+ result.append(line)
287
+
288
+ offset = " "
289
+ for child in self.__class__.CHILDREN:
290
+ if not child.SINGLE and not child.HAS_PROPERTIES:
291
+ result.append(offset + "<" + child.TAG + ">")
292
+ result.append(offset + "</" + child.TAG + ">")
293
+
294
+ result.append("</" + self.__class__.TAG + ">")
295
+
296
+ return result
297
+
298
+ def __process(self, reader, writer, action):
299
+ line = reader.current
300
+ pattern = "</\\s*" + self.__tag + "\\s*>"
301
+
302
+ while True:
303
+ self.__lines.append(line)
304
+
305
+ if self.__class__.SINGLE:
306
+ if line.find("/>") != -1:
307
+ # the final line for single and we have to return it
308
+ yield line
309
+
310
+ if action.just_executed:
311
+ self.finish(writer, action)
312
+
313
+ return
314
+ else:
315
+ if re.match(pattern, line.lower()):
316
+ self.finish(writer, action)
317
+ return
318
+
319
+ line = next(reader)
320
+
321
+ yield line
322
+
323
+ def finish(self, writer, action):
324
+ if self.__do:
325
+ action.do(self, self.__lines, self.__properties_lines, writer)
326
+
327
+ def __read_properties__(self):
328
+ line = self.current
329
+ self.__properties_lines.append(line)
330
+ declare = line
331
+
332
+ while line.find(">") == -1:
333
+ line = next(self)
334
+ self.__properties_lines.append(line)
335
+ declare += " " + line
336
+
337
+ properties = get_properties(declare)
338
+ self.__dict__.update(properties)
339
+
340
+ def __process_children__(self, reader, writer, action):
341
+ for _ in self:
342
+ for child_p in self.__class__.CHILDREN:
343
+ child_p()(self, writer, action)
344
+
345
+ def __call__(self, reader, writer, action):
346
+ if self.__cond(reader.current):
347
+ self.generator = self.__process(reader, writer, action)
348
+ self.current = reader.current
349
+
350
+ self.__read_properties__()
351
+ action.apply(self.__class__.TAG, self)
352
+
353
+ if action.just_executed:
354
+ self.__do = True
355
+
356
+ self.parse(self, writer, action)
357
+
358
+ self.__process_children__(self, writer, action)
359
+
360
+ return True
361
+
362
+ return False
363
+
364
+ def parse(self, reader, writer, action):
365
+ pass