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.
- csvpath/__init__.py +1 -0
- csvpath/csvpath.py +368 -0
- csvpath/matching/__init__.py +1 -0
- csvpath/matching/expression_encoder.py +108 -0
- csvpath/matching/expression_math.py +123 -0
- csvpath/matching/expression_utility.py +29 -0
- csvpath/matching/functions/above.py +36 -0
- csvpath/matching/functions/add.py +24 -0
- csvpath/matching/functions/below.py +36 -0
- csvpath/matching/functions/concat.py +25 -0
- csvpath/matching/functions/count.py +44 -0
- csvpath/matching/functions/count_lines.py +12 -0
- csvpath/matching/functions/count_scans.py +13 -0
- csvpath/matching/functions/divide.py +30 -0
- csvpath/matching/functions/end.py +18 -0
- csvpath/matching/functions/every.py +33 -0
- csvpath/matching/functions/first.py +46 -0
- csvpath/matching/functions/function.py +31 -0
- csvpath/matching/functions/function_factory.py +114 -0
- csvpath/matching/functions/inf.py +38 -0
- csvpath/matching/functions/is_instance.py +95 -0
- csvpath/matching/functions/length.py +33 -0
- csvpath/matching/functions/lower.py +21 -0
- csvpath/matching/functions/minf.py +167 -0
- csvpath/matching/functions/multiply.py +27 -0
- csvpath/matching/functions/no.py +10 -0
- csvpath/matching/functions/notf.py +26 -0
- csvpath/matching/functions/now.py +33 -0
- csvpath/matching/functions/orf.py +28 -0
- csvpath/matching/functions/percent.py +29 -0
- csvpath/matching/functions/random.py +33 -0
- csvpath/matching/functions/regex.py +38 -0
- csvpath/matching/functions/subtract.py +28 -0
- csvpath/matching/functions/tally.py +36 -0
- csvpath/matching/functions/upper.py +21 -0
- csvpath/matching/matcher.py +215 -0
- csvpath/matching/matching_lexer.py +66 -0
- csvpath/matching/parser.out +1287 -0
- csvpath/matching/parsetab.py +1427 -0
- csvpath/matching/productions/equality.py +158 -0
- csvpath/matching/productions/expression.py +16 -0
- csvpath/matching/productions/header.py +30 -0
- csvpath/matching/productions/matchable.py +41 -0
- csvpath/matching/productions/term.py +11 -0
- csvpath/matching/productions/variable.py +15 -0
- csvpath/parser_utility.py +39 -0
- csvpath/scanning/__init__.py +1 -0
- csvpath/scanning/parser.out +1 -0
- csvpath/scanning/parsetab.py +231 -0
- csvpath/scanning/scanner.py +165 -0
- csvpath/scanning/scanning_lexer.py +47 -0
- csvpath-0.0.2.dist-info/METADATA +184 -0
- csvpath-0.0.2.dist-info/RECORD +54 -0
- 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,,
|