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
@@ -0,0 +1,165 @@
1
+ import ply.yacc as yacc
2
+ from csvpath.scanning.scanning_lexer import ScanningLexer
3
+ from csvpath.parser_utility import ParserUtility
4
+ from typing import List
5
+
6
+
7
+ class UnexpectedException(Exception):
8
+ pass
9
+
10
+
11
+ class Scanner(object):
12
+ tokens = ScanningLexer.tokens
13
+
14
+ def __init__(self):
15
+ self.lexer = ScanningLexer()
16
+ self.parser = yacc.yacc(module=self, start="path")
17
+ self.these: List = []
18
+ self.all_lines = False
19
+ self.filename = None
20
+ self.from_line = None
21
+ self.to_line = None
22
+ self.path = None
23
+ self.quiet = True
24
+ self.block_print = True
25
+ self.print(f"initialized Scanner: {self}")
26
+
27
+ def __str__(self):
28
+ return f"""
29
+ path: {self.path}
30
+ parser: {self.parser}
31
+ lexer: {self.lexer}
32
+ filename: {self.filename}
33
+ from_line: {self.from_line}
34
+ to_line: {self.to_line}
35
+ all_lines: {self.all_lines}
36
+ these: {self.these}
37
+ """
38
+
39
+ def print(self, msg: str) -> None:
40
+ if not self.block_print:
41
+ print(msg)
42
+
43
+ # ===================
44
+ # parse
45
+ # ===================
46
+
47
+ def parse(self, data):
48
+ self.path = data
49
+ self.parser.parse(data, lexer=self.lexer.lexer)
50
+ return self.parser
51
+
52
+ # ===================
53
+ # productions
54
+ # ===================
55
+
56
+ def p_error(self, p):
57
+ ParserUtility().error(self.parser, p)
58
+
59
+ # we'll want to use $name={ name: path} but for now we're treating filename as a file path
60
+ def p_root(self, p):
61
+ "root : ROOT filename"
62
+
63
+ def p_filename(self, p):
64
+ "filename : FILENAME"
65
+ self.filename = p[1]
66
+
67
+ def p_path(self, p):
68
+ "path : root LEFT_BRACKET expression RIGHT_BRACKET"
69
+ p[0] = p[3]
70
+
71
+ # ===================
72
+
73
+ def p_expression(self, p):
74
+ """expression : expression PLUS term
75
+ | expression MINUS term
76
+ | term"""
77
+ if len(p) == 4:
78
+ if p[2] == "+":
79
+ self._add_two_lines(p)
80
+ elif p[2] == "-":
81
+ self._collect_a_line_range(p)
82
+ else:
83
+ self._collect_a_line_number(p)
84
+ p[0] = self.these if self.these else [self.from_line]
85
+
86
+ def p_term(self, p):
87
+ """term : NUMBER
88
+ | NUMBER ALL_LINES
89
+ | ALL_LINES"""
90
+
91
+ if len(p) == 3:
92
+ self.from_line = p[1]
93
+
94
+ if p[len(p) - 1] == "*":
95
+ self.all_lines = True
96
+ else:
97
+ p[0] = [p[1]]
98
+
99
+ # ===================
100
+ # production support
101
+ # ===================
102
+
103
+ def _add_two_lines(self, p):
104
+ self._move_range_to_these()
105
+ if p[1] and p[1][0] not in self.these:
106
+ self.these.extend(p[1])
107
+ if p[3] and p[3][0] not in self.these:
108
+ self.these.extend(p[3])
109
+
110
+ def _collect_a_line_range(self, p):
111
+ if not isinstance(p[1], list):
112
+ raise UnexpectedException("non array in p[1]")
113
+ #
114
+ # if we continue to not raise unexpected exception we should remove the array tests!
115
+ #
116
+ if self.from_line and self.to_line:
117
+ # we have a from and to range. we have to move the range into
118
+ # these, then add this new range to these too
119
+ self._move_range_to_these()
120
+ fline = p[1][0] if isinstance(p[1], list) else p[1]
121
+ tline = p[3][0] if isinstance(p[3], list) else p[3]
122
+ self._add_range_to_these(fline, tline)
123
+ else:
124
+ if isinstance(p[1], list) and len(p[1]) == 1:
125
+ self.from_line = p[1][0]
126
+ if len(self.these) == 1 and self.these[0] == self.from_line:
127
+ self.these = []
128
+ elif isinstance(p[1], list):
129
+ pass # this is a list of several items -- i.e. it is self.these
130
+ else:
131
+ raise UnexpectedException("non array in p[1]")
132
+ self.from_line = p[1] # does this ever happen?
133
+
134
+ if isinstance(p[3], list):
135
+ self.to_line = p[3][0]
136
+ else:
137
+ raise UnexpectedException("non array in p[3]")
138
+ self.to_line = p[3] # does this ever happen?
139
+ # if we have a multi-element list on the left we set a range
140
+ # using the last item in the list as the from_line and
141
+ # the right side in the to_line. then we clear the range into these
142
+ if isinstance(p[1], list) and len(p[1]) > 1:
143
+ self.from_line = p[1][len(p[1]) - 1]
144
+ self._move_range_to_these()
145
+
146
+ def _collect_a_line_number(self, p):
147
+ if isinstance(p[1], list):
148
+ if p[1] and p[1][0] not in self.these:
149
+ self.these.extend(p[1])
150
+ elif not self.from_line:
151
+ self.from_line = p[1]
152
+
153
+ def _move_range_to_these(self):
154
+ if not self.from_line or not self.to_line:
155
+ return
156
+ for i in range(self.from_line, self.to_line + 1):
157
+ if i not in self.these:
158
+ self.these.append(i)
159
+ self.from_line = None
160
+ self.to_line = None
161
+
162
+ def _add_range_to_these(self, fline, tline):
163
+ for i in range(fline, tline + 1):
164
+ if i not in self.these:
165
+ self.these.append(i)
@@ -0,0 +1,47 @@
1
+ import ply.lex as lex
2
+
3
+
4
+ class ScanningLexer(object):
5
+ tokens = [
6
+ "NUMBER",
7
+ "PLUS",
8
+ "MINUS",
9
+ "LEFT_BRACKET",
10
+ "RIGHT_BRACKET",
11
+ "ROOT",
12
+ "ANY",
13
+ "NAME",
14
+ "FILENAME",
15
+ "ALL_LINES",
16
+ ]
17
+
18
+ t_ignore = " \t\n\r"
19
+ t_ROOT = r"\$"
20
+ t_PLUS = r"\+"
21
+ t_MINUS = r"-"
22
+ t_LEFT_BRACKET = r"\["
23
+ t_RIGHT_BRACKET = r"\]"
24
+ t_ANY = r"\*" # not yet used
25
+ t_NAME = r"[A-Z,a-z,0-9\._]+"
26
+ t_FILENAME = r"[A-Z,a-z,0-9\._/]+"
27
+ t_ALL_LINES = r"\*"
28
+
29
+ def t_NUMBER(self, t):
30
+ r"\d+"
31
+ t.value = int(t.value)
32
+ return t
33
+
34
+ def t_error(self, t):
35
+ print(f"Illegal character '{t.value[0]}'")
36
+ t.lexer.skip(1)
37
+
38
+ def __init__(self):
39
+ self.lexer = lex.lex(module=self)
40
+
41
+ def tokenize(self, data):
42
+ self.lexer.input(data)
43
+ while True:
44
+ tok = self.lexer.token()
45
+ if not tok:
46
+ break
47
+ yield tok
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.1
2
+ Name: csvpath
3
+ Version: 0.0.2
4
+ Summary:
5
+ Author: David Kershaw
6
+ Author-email: dk107dk@hotmail.com
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Dist: ply (>=3.11,<4.0)
11
+ Requires-Dist: pytest (>=8.2.2,<9.0.0)
12
+ Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
13
+ Description-Content-Type: text/markdown
14
+
15
+
16
+ # CsvPath
17
+
18
+ CsvPath defines a declarative syntax for inspecting and updating CSV files. Though much simpler, it is similar to:
19
+ - XPath: CsvPath is to a CSV file like XPath is to an XML file
20
+ - Schematron: Schematron is basically XPath rules applied using XSLT. CsvPath paths can be used as validation rules.
21
+ - CSS selectors: CsvPath picks out structured data in a similar way to how CSS selectors pick out HTML structures.
22
+
23
+ CsvPath is intended to fit with other DataOps and data quality tools. Files are streamed. The interface is simple. Custom functions can be added.
24
+
25
+ # Usage
26
+ CsvPath paths have two parts, scanning and matching. For usage, see the unit tests in [tests/test_scanner.py](tests/test_scanner.py), [tests/test_matcher.py](tests/test_matcher.py) and [tests/test_functions.py](tests/test_functions.py).
27
+
28
+ path = CsvPath(delimiter=",")
29
+ path.parse("$test.csv[5-25][#0=="Frog" @lastname=="Bats" count()==2]")
30
+ for i, line in enumerate( path.next() ):
31
+ print(f"{i}: {line}")
32
+
33
+ print(f"path vars: {path.variables}")
34
+
35
+ This scanning and matching path says:
36
+ - Open test.csv
37
+ - Scan lines 5 through 25
38
+ - Match the second time we see a line where the first column equals "Frog" and set the variable called "lastname" to "Bats"
39
+
40
+ # Scanning
41
+ The scanner enumerates lines. For each line returned, the line number, the scanned line count, and the match count are available. The set of line numbers scanned is also available.
42
+
43
+ The scan part of the path starts with '$' to indicate the root, meaning the file from the top. After the '$' comes the file path. The scanning instructions are in a bracket. The rules are:
44
+ - `[*]` means all
45
+ - `[3*]` means starting from line 3 and going to the end of the file
46
+ - `[3]` by itself means just line 3
47
+ - `[1-3]` means lines 1 through 3
48
+ - `[1+3]` means lines 1 and line 3
49
+ - `[1+3-8]` means line 1 and lines 3 through eight
50
+
51
+ # Matching
52
+ The match part is also bracketed. Matches have space separated
53
+ components that are ANDed together. A match component is one of several types:
54
+ <table>
55
+ <tr>
56
+ <td>Type</td>
57
+ <td>Returns</td>
58
+ <td>Matches</td>
59
+ <td>Description</td>
60
+ <td>Examples</td>
61
+ </tr>
62
+ <tr>
63
+ <td>Term </td><td> Value </td><td> True when used alone, otherwise calculated </td>
64
+ <td>A quoted string or date, optionally quoted number, or
65
+ regex. Regex features are limited. A regex is wrapped in "/" characters.</td>
66
+ <td>
67
+ <li/> `"Massachusetts"`
68
+ <li/> `89.7`
69
+ <li/> `/[0-9a-zA-Z]+!/`
70
+ </td>
71
+ </tr>
72
+ <tr>
73
+ <td>Function </td><td> Calculated </td><td> Calculated </td>
74
+ <td>A function name followed by parentheses. Functions can
75
+ contain terms, variables, headers and other functions. Some functions
76
+ take a specific or unlimited number of types as arguments. </td>
77
+ <td>
78
+ <li/> `not(count()==2)`
79
+ </td>
80
+ </tr>
81
+ <tr>
82
+ <td>Variable </td>
83
+ <td>Value</td>
84
+ <td>True/False when value tested. True when set, True/False existence when used alone</td>
85
+ <td>An @ followed by a name. A variable is
86
+ set or tested depending on the usage. By itself, it is an existence test. When used as
87
+ the left hand side of an "=" its value is set.
88
+ When it is used on either side of an "==" it is an equality test.
89
+ <td>
90
+ <li/> `@weather="cloudy"`
91
+ <li/> `count(@weather=="sunny")`
92
+ <li/> `@weather`
93
+ <li/> `#summer==@weather`
94
+
95
+ #1 is an assignment that sets the variable and returns True. #2 is an argument used as a test in a way that is specific to the function. #3 is an existence test. #4 is a test.
96
+ </td>
97
+ </tr>
98
+ <tr>
99
+ <td>Header </td>
100
+ <td>Value </td>
101
+ <td>True/False existence when used alone, otherwise calculated</td>
102
+ <td>A # followed by a name or integer. The name references a value in line 0, the header
103
+ row. A number references a column by the 0-based column order. </td>
104
+ <td>
105
+ <li/> `#firstname`
106
+ <li/> `#3`
107
+ </td>
108
+ </tr>
109
+ <tr>
110
+ <td>Equality</td>
111
+ <td>Calculated </td>
112
+ <td>True at assignment, otherwise calculated </td>
113
+ <td>Two of the other types joined with an "=" or "==".</td>
114
+ <td>
115
+ <li/> `@type_of_tree="Oak"`
116
+ <li/> `#name == @type_of_tree`
117
+ </td>
118
+ </tr>
119
+ <table>
120
+
121
+ [ #common_name #0=="field" @tail=end() not(in(@tail, 'short|medium')) ]
122
+
123
+ In the path above, the rules applied are:
124
+ - `#common_name` indicates a header named "common_name". Headers are the values in the 0th line. This component of the match is an existence test.
125
+ - `#2` means the 3rd column, counting from 0
126
+ - Functions and column references are ANDed together
127
+ - `@tail` creates a variable named "tail" and sets it to the value of the last column
128
+ - Functions can contain functions, equality tests, and/or literals
129
+
130
+ Most of the work of matching is done in functions. The match functions are:
131
+
132
+ | Function | What it does |Done|
133
+ |-------------------------------|-----------------------------------------------------------|----|
134
+ | add(value, value, ...) | adds numbers | X |
135
+ | after(value) | finds things after a date, number, string | X |
136
+ | average(number, type) | returns the average up to current "line", "scan", "match" | X |
137
+ | before(value) | finds things before a date, number, string | X |
138
+ | concat(value, value) | counts the number of matches | X |
139
+ | count() | counts the number of matches | X |
140
+ | count(value) | count matches of value | X |
141
+ | count_lines() | count lines to this point in the file | X |
142
+ | count_scans() | count lines we checked for match | X |
143
+ | divide(value, value, ...) | divides numbers | X |
144
+ | end() | returns the value of the last column | X |
145
+ | every(value, number) | match every Nth time a value is seen | X |
146
+ | first(value) | match the first occurrence and capture line | X |
147
+ | in(value, list) | match in a pipe-delimited list | X |
148
+ | increment(value, n) | increments a variable by n each time seen | |
149
+ | isinstance(value, typestr) | tests for "int","float","complex","bool","usd" | X |
150
+ | length(value) | returns the length of the value | X |
151
+ | lower(value) | makes value lowercase | X |
152
+ | max(value, type) | largest value seen up to current "line", "scan", "match" | X |
153
+ | median(value, type) | median value up to current "line", "scan", "match" | X |
154
+ | min(value, type) | smallest value seen up to current "line", "scan", "match" | X |
155
+ | multiply(value, value, ...) | multiplies numbers | X |
156
+ | no() | always false | X |
157
+ | not(value) | negates a value | X |
158
+ | now(format) | a datetime, optionally formatted | X |
159
+ | or(value, value,...) | match any one | X |
160
+ | percent(type) | % of total lines for "scan", "match", "line" | X |
161
+ | random(list) | pick from a list | |
162
+ | random(starting, ending) | generates a random int from starting to ending | X |
163
+ | regex(regex-string) | match on a regular expression | X |
164
+ | subtract(value, value, ...) | subtracts numbers | X |
165
+ | tally(value, value, ...) | counts times values are seen, including as a set | X |
166
+ | then(y,m,d,hh,mm,ss,format) | a datetime, optionally formatted | |
167
+ | upper(value) | makes value uppercase | X |
168
+
169
+ # Not Ready For Production
170
+ Anything could change. This project is a hobby.
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
@@ -0,0 +1,54 @@
1
+ csvpath/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
+ csvpath/csvpath.py,sha256=uLDNlR7dALsp9_sKsLKPhTxbgbzNjUUllauLBznvKvM,11858
3
+ csvpath/matching/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
+ csvpath/matching/expression_encoder.py,sha256=fLkRyRwXL6bfEHhrZM_44Hueu6AiCzs4f9QcPxVIqxo,3614
5
+ csvpath/matching/expression_math.py,sha256=eugzV7culmqQ91Dvrmba5vI1K_w6QN8VAE0JRHz19eE,4970
6
+ csvpath/matching/expression_utility.py,sha256=tDCFZ96ugIDdt3pAkZQ2r9KJNn1S85-Tt_Dsp6fYoFA,734
7
+ csvpath/matching/functions/above.py,sha256=X0v69Tm7YMGUraNoeZkJ08ulHyTtLGdE7ZIq5H0YfpU,1267
8
+ csvpath/matching/functions/add.py,sha256=JyrT4Xc34-E4DWzy_k9MrD8gCsSuA223ULCCqcTdYwA,838
9
+ csvpath/matching/functions/below.py,sha256=hQt63ATF3TFLcBn5M63wbBUrSt4zhvIN4zqUYjg_7qI,1264
10
+ csvpath/matching/functions/concat.py,sha256=wmTOsY8awVcTSBoz4FsiKBCyRdN13yPP76BkqHLvgoQ,930
11
+ csvpath/matching/functions/count.py,sha256=PESUdB69GAcAkKKzcDaZzhRm3c97ZmrQWiEg1IN5yoM,1642
12
+ csvpath/matching/functions/count_lines.py,sha256=N2XXVzPuI54szfR-hujH3O0vKWLQpahBakb-MW_l1aw,328
13
+ csvpath/matching/functions/count_scans.py,sha256=OfA1PEwz-c17pXjfvbTHhSreAAaxAQpr1HO80l4DZyY,347
14
+ csvpath/matching/functions/divide.py,sha256=f-aIubXnPYdPXJtULOO3v8AIQK0jrwzNCiU4Ve8V2Yg,1049
15
+ csvpath/matching/functions/end.py,sha256=6qOktqew2tLisdRf1xRvK2mI9vrx0kYfjlzMjRH8t6M,592
16
+ csvpath/matching/functions/every.py,sha256=NcaqEFQ_6Gcs3JQFwA8OndSyuvI5umabbf4XaVgoYVo,1285
17
+ csvpath/matching/functions/first.py,sha256=VR7BW-svsP6ZJBlHlOfjo-TOzHk4x18YYffchHbLMu4,1699
18
+ csvpath/matching/functions/function.py,sha256=PFeRonjMzM4aSsU06-HcL1_4-sSGVsvRYZ6-u71yOoU,949
19
+ csvpath/matching/functions/function_factory.py,sha256=2hCni4XKPOEJtsH5XNqLrB06XGIJmJTFF_wrxiAz7fw,4408
20
+ csvpath/matching/functions/inf.py,sha256=NfFaWrO8DybuQBEvATWu54xnvlE_EgwIBQ3bKS3luTY,1385
21
+ csvpath/matching/functions/is_instance.py,sha256=yMwciRcdR2Hd81JVXWWDEOw0lkSVAkRr3Y1Ud-JgOhw,3026
22
+ csvpath/matching/functions/length.py,sha256=0xactwv9npFhwUOVOEoJQzaVxD2TqJoQCWW_kUzKw40,1032
23
+ csvpath/matching/functions/lower.py,sha256=D6O3IQa80mo6ljqnZClPy687WWjz7ogLUdRImnLT7qo,669
24
+ csvpath/matching/functions/minf.py,sha256=CI3IXnm6u5eapU35hmjALmBairfuAgJuyC0o0WSKs1I,5572
25
+ csvpath/matching/functions/multiply.py,sha256=DiZBf-WYlklAhgEINh7u4oNu48AMIGyLHvVS9T6h8Sc,924
26
+ csvpath/matching/functions/no.py,sha256=qTMiRFRVEvTjt-pHXyymVDS-Wbpya86az16G3MPMmpU,231
27
+ csvpath/matching/functions/notf.py,sha256=EswnL10BK207_UuaWmSyYqOYsSbnAbnn6uR3j9OjQxM,845
28
+ csvpath/matching/functions/now.py,sha256=02MxK71U3ysoXtumWC0-2t-4s6pcuBFUXMyQZZYAyGA,1181
29
+ csvpath/matching/functions/orf.py,sha256=yhmMLEDwnYs2z1zpuwwUY8bMKUoFsDpb69wFvy4sQXw,953
30
+ csvpath/matching/functions/percent.py,sha256=1iHg0rza--Hy3T4t8-uPl_zSVaHVygRlrOUqr3N7HXg,1063
31
+ csvpath/matching/functions/random.py,sha256=MToY8craHnTk1sW6fRWT67PAsGE8lve1x7XnbP3C6ig,1119
32
+ csvpath/matching/functions/regex.py,sha256=pGQ8ylaOQd22igemhLsqTY3arZTiB8ijcEG1AMa020s,1130
33
+ csvpath/matching/functions/subtract.py,sha256=2LaC5qaewj7oNpnxaTaaJ6eX5MbSFYpYhuSDMgMLYVU,1002
34
+ csvpath/matching/functions/tally.py,sha256=8UAzzuTAn9pKNywgetAs-jrVDrJgUGkA0ZtjFl2diZM,1265
35
+ csvpath/matching/functions/upper.py,sha256=k5VvBe9svWNZZVWG54IH1FGVizECx6ux6oXeNVGoFx4,669
36
+ csvpath/matching/matcher.py,sha256=zZGzgwcHOR3C8xhTVQELc0wCaTV2KiZ6xKAuyg0HLRk,6342
37
+ csvpath/matching/matching_lexer.py,sha256=WnxAseLhTtdWq3KCTWEZLJSIntHOV0YN5FNrm8moX_g,1492
38
+ csvpath/matching/parser.out,sha256=SeCgDdmNv37pTrJgmadBg9aBmyox2F5aZniSXYZgfII,54699
39
+ csvpath/matching/parsetab.py,sha256=hPPwG-reUC9GpEOrKpH5NmaGCVZqypvBH71QNlT_YVo,25834
40
+ csvpath/matching/productions/equality.py,sha256=PEaRepqrt4R73g8jwnp2Pye2cecx-c_gne1QocraV-8,4710
41
+ csvpath/matching/productions/expression.py,sha256=goqTN9ikrrM5GwdtOcvN6s6XSOGbzsy9WvCVKtKUw4U,467
42
+ csvpath/matching/productions/header.py,sha256=vlzZbJMgITAJfhU53fOFNg517VlI0VUYlbEZRA3KgbE,1075
43
+ csvpath/matching/productions/matchable.py,sha256=VTkq9KYlhQVe3bE-jYaPSH5GsNHmDbkzdFOSbEnDtN4,1233
44
+ csvpath/matching/productions/term.py,sha256=RxXNhhVTD7yI2gGEgzMJD6BL80Gxt0cUPQxcX4ioO9M,277
45
+ csvpath/matching/productions/variable.py,sha256=euXats1XX1lm50iRQMc0VSDbCylDv5309728-NfoKQg,436
46
+ csvpath/parser_utility.py,sha256=kDh6YYYd3Fhs8iWGRvXCnO4iaNkbfIYGPQKH2x9EOWc,1153
47
+ csvpath/scanning/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
48
+ csvpath/scanning/parser.out,sha256=AmVGteuwH6JwD5wL8_J3-el64Mc6pgNblKxi2byiQIQ,56
49
+ csvpath/scanning/parsetab.py,sha256=i7UQ0jqyT3lITtfe8dmQYAxKIf9rBjK4Qle8sGZxMBs,4106
50
+ csvpath/scanning/scanner.py,sha256=LDUbF-wmUuyHdRK76vEdnUR7qihLE9aJ9mKn_MIccXo,5246
51
+ csvpath/scanning/scanning_lexer.py,sha256=N3o9VTVwQegvJmIxOEFrVpQb2aly3L2hY9uv6cLBxxI,978
52
+ csvpath-0.0.2.dist-info/METADATA,sha256=yUW-_mEq1FWApH4FiBOKv6A73jbeRgVhQS2YqrnjUDY,9078
53
+ csvpath-0.0.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
54
+ csvpath-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any