csvpath 0.0.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 (54) hide show
  1. csvpath/__init__.py +1 -0
  2. csvpath/csvpath.py +368 -0
  3. csvpath/matching/__init__.py +1 -0
  4. csvpath/matching/expression_encoder.py +108 -0
  5. csvpath/matching/expression_math.py +123 -0
  6. csvpath/matching/expression_utility.py +29 -0
  7. csvpath/matching/functions/above.py +36 -0
  8. csvpath/matching/functions/add.py +24 -0
  9. csvpath/matching/functions/below.py +36 -0
  10. csvpath/matching/functions/concat.py +25 -0
  11. csvpath/matching/functions/count.py +44 -0
  12. csvpath/matching/functions/count_lines.py +12 -0
  13. csvpath/matching/functions/count_scans.py +13 -0
  14. csvpath/matching/functions/divide.py +30 -0
  15. csvpath/matching/functions/end.py +18 -0
  16. csvpath/matching/functions/every.py +33 -0
  17. csvpath/matching/functions/first.py +46 -0
  18. csvpath/matching/functions/function.py +31 -0
  19. csvpath/matching/functions/function_factory.py +114 -0
  20. csvpath/matching/functions/inf.py +38 -0
  21. csvpath/matching/functions/is_instance.py +95 -0
  22. csvpath/matching/functions/length.py +33 -0
  23. csvpath/matching/functions/lower.py +21 -0
  24. csvpath/matching/functions/minf.py +167 -0
  25. csvpath/matching/functions/multiply.py +27 -0
  26. csvpath/matching/functions/no.py +10 -0
  27. csvpath/matching/functions/notf.py +26 -0
  28. csvpath/matching/functions/now.py +33 -0
  29. csvpath/matching/functions/orf.py +28 -0
  30. csvpath/matching/functions/percent.py +29 -0
  31. csvpath/matching/functions/random.py +33 -0
  32. csvpath/matching/functions/regex.py +38 -0
  33. csvpath/matching/functions/subtract.py +28 -0
  34. csvpath/matching/functions/tally.py +36 -0
  35. csvpath/matching/functions/upper.py +21 -0
  36. csvpath/matching/matcher.py +215 -0
  37. csvpath/matching/matching_lexer.py +66 -0
  38. csvpath/matching/parser.out +1287 -0
  39. csvpath/matching/parsetab.py +1427 -0
  40. csvpath/matching/productions/equality.py +158 -0
  41. csvpath/matching/productions/expression.py +16 -0
  42. csvpath/matching/productions/header.py +30 -0
  43. csvpath/matching/productions/matchable.py +41 -0
  44. csvpath/matching/productions/term.py +11 -0
  45. csvpath/matching/productions/variable.py +15 -0
  46. csvpath/parser_utility.py +39 -0
  47. csvpath/scanning/__init__.py +1 -0
  48. csvpath/scanning/parser.out +1 -0
  49. csvpath/scanning/parsetab.py +231 -0
  50. csvpath/scanning/scanner.py +165 -0
  51. csvpath/scanning/scanning_lexer.py +47 -0
  52. csvpath-0.0.2.dist-info/METADATA +184 -0
  53. csvpath-0.0.2.dist-info/RECORD +54 -0
  54. csvpath-0.0.2.dist-info/WHEEL +4 -0
csvpath/__init__.py ADDED
@@ -0,0 +1 @@
1
+
csvpath/csvpath.py ADDED
@@ -0,0 +1,368 @@
1
+ import csv
2
+ from typing import List, Dict, Any
3
+ from collections.abc import Iterator
4
+ from csvpath.matching.matcher import Matcher
5
+ from csvpath.matching.expression_encoder import ExpressionEncoder
6
+ from csvpath.matching.expression_math import ExpressionMath
7
+ from csvpath.scanning.scanner import Scanner
8
+
9
+
10
+ class NoFileException(Exception):
11
+ pass
12
+
13
+
14
+ class CsvPath:
15
+ def __init__(
16
+ self, *, filename=None, delimiter=",", quotechar='"', block_print=True
17
+ ):
18
+ self.filename = filename
19
+ self.scanner = None
20
+ self.value = None
21
+ self.scan = None
22
+ self.match = None
23
+ self.modify = None
24
+ self.headers = None
25
+ self.line_number = 0
26
+ self.scan_count = 0
27
+ self.match_count = 0
28
+ self.variables: Dict[str, Any] = {}
29
+ self.delimiter = delimiter
30
+ self.quotechar = quotechar
31
+ self.block_print = block_print
32
+ self.total_lines = -1
33
+ self._verbose = False
34
+ self._dump_json = False
35
+ self._do_math = False # off by default, still experimental
36
+ self._collect_matchers = False
37
+ self.matchers = []
38
+ self.jsons = []
39
+
40
+ def dump_json(self):
41
+ self._dump_json = not self._dump_json
42
+
43
+ def parse(self, data):
44
+ self.scanner = Scanner()
45
+ s, mat, mod = self._find_scan_match_modify(data)
46
+ self.scan = s
47
+ self.match = mat
48
+ self.modify = mod
49
+ self.scanner.parse(s)
50
+ self._load_headers()
51
+ self.get_total_lines()
52
+ return self.scanner
53
+
54
+ def verbose(self, set_verbose: bool = True) -> None:
55
+ self._verbose = set_verbose
56
+
57
+ # prints what the user needs to see
58
+ def verbosity(self, msg: Any) -> None:
59
+ if self._verbose:
60
+ print(f"{msg}")
61
+
62
+ # prints what the developer needs to see
63
+ def print(self, msg: str) -> None:
64
+ if not self.block_print:
65
+ print(msg)
66
+
67
+ def _load_headers(self) -> None:
68
+ with open(self.scanner.filename, "r") as file:
69
+ reader = csv.reader(
70
+ file, delimiter=self.delimiter, quotechar=self.quotechar
71
+ )
72
+ for row in reader:
73
+ self.headers = row
74
+ break
75
+ hs = self.headers[:]
76
+ self.headers = []
77
+ for header in hs:
78
+ header = header.strip()
79
+ header = header.replace(";", "")
80
+ header = header.replace(",", "")
81
+ header = header.replace("|", "")
82
+ header = header.replace("\t", "")
83
+ header = header.replace("`", "")
84
+ self.headers.append(header)
85
+ self.verbosity(f"header: {header}")
86
+
87
+ def _find_scan_match_modify(self, data):
88
+ scan = ""
89
+ matches = ""
90
+ modify = ""
91
+ p = 0
92
+ for i, c in enumerate(data):
93
+ if p == 0:
94
+ scan = scan + c
95
+ elif p == 1:
96
+ matches = matches + c
97
+ else:
98
+ modify = modify + c
99
+ if c == "]":
100
+ p = p + 1
101
+ scan = scan.strip()
102
+ scan = scan if len(scan) > 0 else None
103
+ matches = matches.strip()
104
+ matches = matches if len(matches) > 0 else None
105
+ modify = modify.strip()
106
+ modify = modify if len(modify) > 0 else None
107
+ self.verbosity(f"scan: {scan}")
108
+ self.verbosity(f"matches: {matches}")
109
+ self.verbosity(f"modify: {modify}")
110
+ return scan, matches, modify
111
+
112
+ def __str__(self):
113
+ return f"""
114
+ path: {self.scanner.path}
115
+ filename: {self.filename}
116
+ parser: {self.scanner}
117
+ from_line: {self.scanner.from_line}
118
+ to_line: {self.scanner.to_line}
119
+ all_lines: {self.scanner.all_lines}
120
+ these: {self.scanner.these}
121
+ """
122
+
123
+ @property
124
+ def from_line(self):
125
+ return self.scanner.from_line
126
+
127
+ @property
128
+ def to_line(self):
129
+ return self.scanner.to_line
130
+
131
+ @property
132
+ def all_lines(self):
133
+ return self.scanner.all_lines
134
+
135
+ @property
136
+ def path(self):
137
+ return self.scanner.path
138
+
139
+ @property
140
+ def these(self):
141
+ return self.scanner.these
142
+
143
+ @property
144
+ def filename(self):
145
+ return self.file_name
146
+
147
+ @filename.setter
148
+ def filename(self, f):
149
+ self.file_name = f
150
+
151
+ def collect(self) -> List[List[Any]]:
152
+ lines = []
153
+ for _ in self.next():
154
+ _ = _[:]
155
+ lines.append(_)
156
+ return lines
157
+
158
+ def next(self):
159
+ if self.scanner.filename is None:
160
+ raise NoFileException("there is no filename")
161
+ self.verbosity(f"filename: {self.scanner.filename}")
162
+ total_lines = -1
163
+ if self._verbose:
164
+ total_lines = self.get_total_lines()
165
+ self.verbosity(f"total lines: {total_lines}")
166
+
167
+ with open(self.scanner.filename, "r") as file:
168
+ reader = csv.reader(
169
+ file, delimiter=self.delimiter, quotechar=self.quotechar
170
+ )
171
+ for line in reader:
172
+ self.verbosity(f"line number: {self.line_number} of {total_lines}")
173
+ if self.includes(self.line_number):
174
+ self.scan_count = self.scan_count + 1
175
+ self.print(f"CsvPath.next: line:{line}")
176
+ self.verbosity(f"scan count: {self.scan_count}")
177
+ if self.matches(line):
178
+ self.match_count = self.match_count + 1
179
+ self.verbosity(f"match count: {self.match_count}")
180
+ yield line
181
+ self.line_number = self.line_number + 1
182
+
183
+ def get_total_lines(self) -> int:
184
+ if self.total_lines == -1:
185
+ with open(self.scanner.filename, "r") as file:
186
+ reader = csv.reader(
187
+ file, delimiter=self.delimiter, quotechar=self.quotechar
188
+ )
189
+ for line in reader:
190
+ self.total_lines += 1
191
+ return self.total_lines
192
+
193
+ def current_line_number(self) -> int:
194
+ return self.line_number
195
+
196
+ def current_scan_count(self) -> int:
197
+ return self.scan_count
198
+
199
+ def current_match_count(self) -> int:
200
+ return self.match_count
201
+
202
+ def do_math(self):
203
+ self._do_math = not self._do_math
204
+
205
+ def collect_matchers(self):
206
+ self._collect_matchers = not self._collect_matchers
207
+
208
+ def matches(self, line) -> bool:
209
+ if not self.match:
210
+ return True
211
+ self.print(f"CsvPath.matches: the match path: {self.match}")
212
+
213
+ matcher = Matcher(
214
+ csvpath=self, data=self.match, line=line, headers=self.headers
215
+ )
216
+
217
+ if self._do_math:
218
+ em = ExpressionMath()
219
+ for e in matcher.expressions:
220
+ em.do_math(e[0])
221
+
222
+ if self._dump_json:
223
+ jsonstr = ExpressionEncoder().valued_list_to_json(matcher.expressions)
224
+ self.jsons.append(jsonstr)
225
+
226
+ matched = matcher.matches()
227
+ if self._collect_matchers: # and matched
228
+ self.matchers.append(matcher)
229
+
230
+ return matched
231
+
232
+ def set_variable(self, name: str, *, value: Any, tracking: Any = None) -> None:
233
+ if not name:
234
+ raise Exception("name cannot be None")
235
+ if name in self.variables:
236
+ self.print(f"CsvPath.set_variable: existing value: {self.variables[name]}")
237
+ else:
238
+ self.print("CsvPath.set_variable: no existing value")
239
+ if tracking is not None:
240
+ if name not in self.variables:
241
+ self.variables[name] = {}
242
+ instances = self.variables[name]
243
+ instances[tracking] = value
244
+ else:
245
+ self.variables[name] = value
246
+
247
+ def get_variable(
248
+ self, name: str, *, tracking: Any = None, set_if_none: Any = None
249
+ ) -> Any:
250
+ if not name:
251
+ raise Exception("name cannot be None")
252
+ thevalue = None
253
+ if tracking is not None:
254
+ thedict = None
255
+ if name in self.variables:
256
+ thedict = self.variables[name]
257
+ if not thedict:
258
+ thedict = {}
259
+ self.variables[name] = thedict
260
+ thedict[tracking] = set_if_none
261
+ else:
262
+ thedict = {}
263
+ thedict[tracking] = set_if_none
264
+ self.variables[name] = thedict
265
+ thevalue = thedict.get(tracking)
266
+ if not thevalue and set_if_none is not None:
267
+ thedict[tracking] = set_if_none
268
+ thevalue = set_if_none
269
+ else:
270
+ if name not in self.variables:
271
+ self.variables[name] = set_if_none
272
+ thevalue = self.variables[name]
273
+ return thevalue
274
+
275
+ def includes(self, line: int) -> bool:
276
+ from_line = self.scanner.from_line
277
+ to_line = self.scanner.to_line
278
+ all_lines = self.scanner.all_lines
279
+ these = self.scanner.these
280
+ return self._includes(
281
+ line, from_line=from_line, to_line=to_line, all_lines=all_lines, these=these
282
+ )
283
+
284
+ def _includes(
285
+ self,
286
+ line: int,
287
+ *,
288
+ from_line: int = None,
289
+ to_line: int = None,
290
+ all_lines: bool = None,
291
+ these: List[int] = [],
292
+ ) -> bool:
293
+ if line is None:
294
+ return False
295
+ if from_line is None and all_lines:
296
+ return True
297
+ if from_line is not None and all_lines:
298
+ return line >= from_line
299
+ if from_line == line:
300
+ return True
301
+ if from_line is not None and to_line is not None and from_line > to_line:
302
+ return line >= to_line and line <= from_line
303
+ if from_line is not None and to_line is not None:
304
+ return line >= from_line and line <= to_line
305
+ if line in these:
306
+ return True
307
+ if to_line is not None:
308
+ return line < to_line
309
+ return False
310
+
311
+ def line_numbers(self) -> Iterator[int | str]:
312
+ these = self.scanner.these
313
+ from_line = self.scanner.from_line
314
+ to_line = self.scanner.to_line
315
+ all_lines = self.scanner.all_lines
316
+ return self._line_numbers(
317
+ these=these, from_line=from_line, to_line=to_line, all_lines=all_lines
318
+ )
319
+
320
+ def _line_numbers(
321
+ self,
322
+ *,
323
+ these: List[int] = [],
324
+ from_line: int = None,
325
+ to_line: int = None,
326
+ all_lines: bool = None,
327
+ ) -> Iterator[int | str]:
328
+ if len(these) > 0:
329
+ for i in these:
330
+ yield i
331
+ else:
332
+ if from_line is not None and to_line is not None and from_line > to_line:
333
+ for i in range(to_line, from_line + 1):
334
+ yield i
335
+ elif from_line is not None and to_line is not None:
336
+ for i in range(from_line, to_line + 1):
337
+ yield i
338
+ elif from_line is not None:
339
+ if all_lines:
340
+ yield f"{from_line}..."
341
+ else:
342
+ yield from_line
343
+ elif to_line is not None:
344
+ yield f"0..{to_line}"
345
+
346
+ def collect_line_numbers(self) -> List[int | str]:
347
+ these = self.scanner.these
348
+ from_line = self.scanner.from_line
349
+ to_line = self.scanner.to_line
350
+ all_lines = self.scanner.all_lines
351
+ return self._collect_line_numbers(
352
+ these=these, from_line=from_line, to_line=to_line, all_lines=all_lines
353
+ )
354
+
355
+ def _collect_line_numbers(
356
+ self,
357
+ *,
358
+ these: List[int] = [],
359
+ from_line: int = None,
360
+ to_line: int = None,
361
+ all_lines: bool = None,
362
+ ) -> List[int | str]:
363
+ collect = []
364
+ for i in self._line_numbers(
365
+ these=these, from_line=from_line, to_line=to_line, all_lines=all_lines
366
+ ):
367
+ collect.append(i)
368
+ return collect
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,108 @@
1
+ from csvpath.matching.productions.expression import Expression
2
+ from csvpath.matching.productions.equality import Equality
3
+ from csvpath.matching.productions.variable import Variable
4
+ from csvpath.matching.productions.header import Header
5
+ from csvpath.matching.productions.term import Term
6
+ from csvpath.matching.functions.function import Function
7
+ from typing import Any, List
8
+
9
+
10
+ class ExpressionEncoder:
11
+ def list_to_json(self, alist: List[Any]) -> str:
12
+ json = "[ "
13
+ for _ in alist:
14
+ json = f"{json} {self.to_json(_)} "
15
+ json = f"{json} ] "
16
+ return json
17
+
18
+ def simple_list_to_json(self, alist: List[Any]) -> str:
19
+ json = "[ "
20
+ for i, _ in enumerate(alist):
21
+ json = f"{json} {self.to_json(_)} "
22
+ if i < len(alist) - 1:
23
+ json = f"{json}, "
24
+ json = f"{json} ] "
25
+ return json
26
+
27
+ def valued_list_to_json(self, alist: List[List[Any]]) -> str:
28
+ json = "[ "
29
+ for i, _ in enumerate(alist):
30
+ json = f"{json} {self.to_json(_[0])} "
31
+ if i < len(alist) - 1:
32
+ json = f"{json}, "
33
+ json = f"{json} ] "
34
+ return json
35
+
36
+ def to_json(self, o):
37
+ if o is None:
38
+ return "None"
39
+ json = ""
40
+ return self._encode(json, o)
41
+
42
+ def _encode(self, json: str, o) -> str:
43
+ if isinstance(o, Expression):
44
+ return self.expression(json, o)
45
+ elif isinstance(o, Equality):
46
+ return self.equality(json, o)
47
+ elif isinstance(o, Function):
48
+ return self.function(json, o)
49
+ elif isinstance(o, Header):
50
+ return self.header(json, o)
51
+ elif isinstance(o, Variable):
52
+ return self.variable(json, o)
53
+ elif isinstance(o, Term):
54
+ return self.term(json, o)
55
+ elif o is None:
56
+ return f'{json} "None" '
57
+ else:
58
+ raise Exception(f"what am I {o}")
59
+
60
+ def matchable(self, json: str, m) -> str:
61
+ json = f'{json} "base_class":"matchable", '
62
+ json = f'{json} "parent_class":"{m.parent.__class__}", '
63
+ json = f'{json} "value":"{m.value}", '
64
+ json = f'{json} "name":"{m.name}", '
65
+ json = f'{json} "children": [ '
66
+ for i, _ in enumerate(m.children):
67
+ json = self._encode(json, _)
68
+ if i < len(m.children) - 1:
69
+ json = f"{json}, "
70
+ json = f"{json} ] "
71
+ return json
72
+
73
+ def expression(self, json: str, e) -> str:
74
+ json = f"{json} " + '{ "type":"expression", '
75
+ json = self.matchable(json, e)
76
+ json = f"{json} " + "} "
77
+ return json
78
+
79
+ def equality(self, json: str, e) -> str:
80
+ json = f"{json} " + '{ "type":"equality", '
81
+ json = self.matchable(json, e)
82
+ json = f'{json}, "op":"{e.op}" '
83
+ json = f"{json} " + "} "
84
+ return json
85
+
86
+ def function(self, json: str, f) -> str:
87
+ json = f"{json} " + '{ "type":"function", '
88
+ json = self.matchable(json, f)
89
+ json = f"{json} " + "} "
90
+ return json
91
+
92
+ def header(self, json: str, h) -> str:
93
+ json = f"{json} " + '{ "type":"header", '
94
+ json = self.matchable(json, h)
95
+ json = f"{json} " + "} "
96
+ return json
97
+
98
+ def variable(self, json: str, v) -> str:
99
+ json = f"{json} " + '{ "type":"variable", '
100
+ json = self.matchable(json, v)
101
+ json = f"{json} " + "} "
102
+ return json
103
+
104
+ def term(self, json: str, t) -> str:
105
+ json = f"{json} " + '{ "type":"term", '
106
+ json = self.matchable(json, t)
107
+ json = f"{json} " + "} "
108
+ return json
@@ -0,0 +1,123 @@
1
+ from csvpath.matching.productions.equality import Equality
2
+ from csvpath.matching.productions.variable import Variable
3
+ from csvpath.matching.productions.term import Term
4
+ from csvpath.matching.productions.header import Header
5
+ from csvpath.matching.functions.function import Function
6
+
7
+ """
8
+ from csvpath.matching.expression_encoder import ExpressionEncoder
9
+ from csvpath.matching.expression_utility import ExpressionUtility
10
+ """
11
+
12
+
13
+ class ExpressionMath:
14
+ """this code works up to a point. there are limitations in
15
+ the number of operations and there is no precedence or
16
+ grouping. you can enable math with the CsvPath.do_math()
17
+ toggle but unless you know it will solve a specific problem
18
+ you shouldn't. the grammar needs to be reworked to make
19
+ arithmetic possible without functions, but it isn't a
20
+ priority."""
21
+
22
+ def is_terminal(self, o):
23
+ return (
24
+ isinstance(o, Variable)
25
+ or isinstance(o, Term)
26
+ or isinstance(o, Header)
27
+ or isinstance(o, Function)
28
+ )
29
+
30
+ def do_math(self, expression):
31
+ for i, _ in enumerate(expression.children):
32
+ self.drop_down_pull_up(expression, i, _)
33
+
34
+ def math(self, op, left, right):
35
+ if left is None or right is None:
36
+ raise Exception(
37
+ f"ExpresionMath.math: operands cannot be None: {left}, {right}"
38
+ )
39
+ if op == "+":
40
+ return left + right
41
+ elif op == "-":
42
+ return left - right
43
+ elif op == "*":
44
+ return left * right
45
+ elif op == "/":
46
+ return left / right
47
+ else:
48
+ raise Exception(f"op cannot be {op}")
49
+
50
+ #
51
+ # why is this not combining the last two terms?
52
+ #
53
+ def combine_terms(self, parent, i, child):
54
+ if isinstance(child, Equality) and child.op in ["-", "+", "*", "/"]:
55
+ lv = child.left.to_value()
56
+ if child.right is not None:
57
+ rv = child.right.to_value()
58
+ term = Term(parent.matcher)
59
+ term.value = self.math(child.op, lv, rv)
60
+ parent.children[i] = term
61
+ term.parent = parent
62
+ return term, i
63
+ else:
64
+ print("not combining terms")
65
+ return child, i
66
+ else:
67
+ print("not an equality with math")
68
+ return child, i
69
+
70
+ def push_down_right_terminal(self, parent, i, child):
71
+ eq = isinstance(parent, Equality)
72
+ op = parent.op in ["-", "+", "*", "/"] if eq else None
73
+ """
74
+ print(f"@ push_down_right_terminal: child {ExpressionUtility._dotted('', child)}")
75
+ json2 = ExpressionEncoder().simple_list_to_json([child])
76
+ print(f"@ push_down_right_terminal: child: {json2}")
77
+ """
78
+ if eq and op:
79
+ second = self.is_terminal(child)
80
+ if second:
81
+ third = isinstance(child.parent, Equality) and isinstance(
82
+ child.parent.left, Equality
83
+ )
84
+ if third:
85
+ # move child down to left
86
+ term = Term(child.matcher)
87
+ try:
88
+ term.value = self.math(
89
+ parent.op, child.value, child.parent.left.right.value
90
+ )
91
+ child.parent.left.right = term
92
+ term.parent = child.parent.left
93
+ # remove child from it's original place now that term includes its value
94
+ child.parent.right = None
95
+ replace_me = child.parent.parent.index_of_child(child.parent)
96
+ child.parent.parent.children[replace_me] = child.parent.left
97
+ except Exception as ex:
98
+ print(f"problems? or maybe just mathed out. ex: {ex}")
99
+
100
+ def drop_down_pull_up(self, parent, i, child):
101
+ # work right to left
102
+ cs = child.children[:]
103
+ cs.reverse()
104
+ for j, _ in enumerate(cs):
105
+ self.drop_down_pull_up(child, j, _)
106
+
107
+ # if we're a terminal within an equality with math and another terminal
108
+ # we want to reduce ourselves before anything else. to do that we need to
109
+ # shortcut to the parent equality
110
+ choose = isinstance(parent, Equality) and parent.op in ["-", "+", "*", "/"]
111
+ if choose and child.parent.both_terminal():
112
+ try:
113
+ x = child.parent.parent.children.index(parent)
114
+ child, i = self.combine_terms(parent.parent, x, parent)
115
+ except Exception:
116
+ print("no such child anymore. presumably mathed out")
117
+ pass
118
+ else:
119
+ # this method won't combine terms in this equality, but we might
120
+ # do a pull-up-push-down below. do we need to make this call?
121
+ child, i = self.combine_terms(parent, i, child)
122
+
123
+ self.push_down_right_terminal(parent, i, child)
@@ -0,0 +1,29 @@
1
+ import hashlib
2
+
3
+
4
+ class ExpressionUtility:
5
+ @classmethod
6
+ def get_id(self, thing):
7
+ # gets a durable ID so funcs like count() can persist throughout the scan
8
+ id = str(thing)
9
+ p = thing.parent
10
+ while p:
11
+ id = id + str(p)
12
+ if p.parent:
13
+ p = p.parent
14
+ else:
15
+ break
16
+ return hashlib.sha256(id.encode("utf-8")).hexdigest()
17
+
18
+ @classmethod
19
+ def _dotted(self, s, o):
20
+ if o is None:
21
+ return s
22
+ cs = str(o.__class__)
23
+ cs = cs[cs.rfind(".") :]
24
+ c = cs[0 : cs.find("'")]
25
+ s = f"{c}{s}"
26
+ try:
27
+ return self._dotted(s, o.parent)
28
+ except Exception:
29
+ return s
@@ -0,0 +1,36 @@
1
+ from typing import Any
2
+ from csvpath.matching.functions.function import Function, ChildrenException
3
+
4
+
5
+ class Above(Function):
6
+ def to_value(self, *, skip=[]) -> Any:
7
+ if self in skip:
8
+ return True
9
+ if len(self.children) != 1:
10
+ self.matcher.print(
11
+ f"Above.to_value: must have 1 equality child: {self.children}"
12
+ )
13
+ raise ChildrenException("Above function must have 1 child")
14
+ if self.children[0].op != ",":
15
+ raise ChildrenException(
16
+ f"Above function must have an equality with the ',' operation, not {self.children[0].op}"
17
+ )
18
+ thischild = self.children[0].children[0]
19
+ abovethatchild = self.children[0].children[1]
20
+
21
+ this_is = thischild.to_value(skip=skip)
22
+ above_that = abovethatchild.to_value(skip=skip)
23
+ this = -1
24
+ that = -1
25
+ try:
26
+ this = float(this_is)
27
+ that = float(above_that)
28
+ except Exception:
29
+ raise Exception(
30
+ f"Above.to_value: this: {this}, a {this.__class__}, and {that}, a {that.__class__}"
31
+ )
32
+ b = this > that
33
+ return b
34
+
35
+ def matches(self, *, skip=[]) -> bool:
36
+ return self.to_value(skip=skip)
@@ -0,0 +1,24 @@
1
+ from typing import Any
2
+ from csvpath.matching.functions.function import Function, ChildrenException
3
+ from csvpath.matching.productions.equality import Equality
4
+
5
+
6
+ class Add(Function):
7
+ def to_value(self, *, skip=[]) -> Any:
8
+ if not self.value:
9
+ if len(self.children) != 1:
10
+ raise ChildrenException("no children. there must be 1 equality child")
11
+ child = self.children[0]
12
+ if not isinstance(child, Equality):
13
+ raise ChildrenException("must be 1 equality child")
14
+
15
+ siblings = child.commas_to_list()
16
+ ret = 0
17
+ for i, sib in enumerate(siblings):
18
+ v = sib.to_value(skip=skip)
19
+ ret = v + ret
20
+ self.value = ret
21
+ return self.value
22
+
23
+ def matches(self, *, skip=[]) -> bool:
24
+ return True