csvpath 0.0.473__tar.gz → 0.0.475__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {csvpath-0.0.473 → csvpath-0.0.475}/PKG-INFO +1 -2
- {csvpath-0.0.473 → csvpath-0.0.475}/config/config.ini +4 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/csvpath.py +1 -1
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/csvpaths.py +1 -1
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/function_factory.py +11 -14
- csvpath-0.0.475/csvpath/matching/functions/function_finder.py +55 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/config.py +107 -35
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/implementing_functions.md +11 -2
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions.md +14 -1
- {csvpath-0.0.473 → csvpath-0.0.475}/pyproject.toml +1 -2
- csvpath-0.0.473/csvpath/matching/functions/stats/correlate.py +0 -41
- {csvpath-0.0.473 → csvpath-0.0.475}/LICENSE +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/README.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/__init__.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/managers/__init__.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/managers/csvpaths_manager.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/managers/file_manager.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/managers/result.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/managers/results_manager.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/__init__.py +0 -0
- {csvpath-0.0.473/csvpath/scanning → csvpath-0.0.475/csvpath/matching/functions}/__init__.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/all.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/andf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/any.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/between.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/empty.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/exists.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/inf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/no.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/notf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/orf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/boolean/yes.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/count.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/count_headers.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/count_lines.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/count_scans.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/counter.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/every.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/has_matches.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/increment.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/tally.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/counting/total_lines.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/dates/datef.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/dates/now.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/function.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/function_focus.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/collect.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/empty_stack.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/end.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/header_name.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/header_names_mismatch.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/headers.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/mismatch.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/replace.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/reset_headers.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/lines/advance.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/lines/after_blank.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/lines/dups.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/lines/first.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/lines/first_line.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/lines/last.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/lines/stop.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/above.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/add.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/divide.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/equals.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/mod.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/multiply.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/round.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/subtract.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/math/sum.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/misc/importf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/misc/intf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/misc/nonef.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/misc/random.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/print/jinjaf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/print/print_line.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/print/print_queue.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/print/printf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/print/table.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/stats/minf.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/stats/percent.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/stats/percent_unique.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/stats/stdev.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/concat.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/length.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/lower.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/metaphone.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/num.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/regex.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/starts_with.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/strip.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/substring.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/strings/upper.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/testing/debug.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/validation.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/validity/fail.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/validity/failed.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/variables/get.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/variables/pushpop.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/variables/put.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/variables/track.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/variables/variables.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/lark_parser.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/lark_transformer.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/matcher.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/__init__.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/equality.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/expression.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/header.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/matchable.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/qualified.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/reference.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/term.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/productions/variable.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/util/exceptions.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/util/expression_encoder.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/util/expression_utility.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/util/lark_print_parser.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/util/print_parser.py +0 -0
- {csvpath-0.0.473/csvpath/matching/functions → csvpath-0.0.475/csvpath/scanning}/__init__.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/scanning/exceptions.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/scanning/parser.out +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/scanning/parsetab.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/scanning/scanner.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/scanning/scanning_lexer.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/cache.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/config_exception.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/error.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/exceptions.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/last_line_stats.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/line_counter.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/line_monitor.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/log_utility.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/metadata_parser.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/csvpath/util/printer.py +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/asbool.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/assignment.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/config.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/examples.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/files.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/above.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/advance.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/after_blank.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/all.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/andor.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/any.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/average.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/between.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/collect.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/correlate.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/count.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/count_headers.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/counter.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/date.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/empty.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/empty_stack.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/end.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/every.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/fail.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/first.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/get.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/has_dups.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/has_matches.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/header.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/header_name.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/header_names_mismatch.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/import.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/in.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/increment.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/jinja.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/last.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/line_number.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/max.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/metaphone.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/mismatch.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/no.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/not.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/now.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/percent_unique.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/pop.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/print.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/print_line.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/print_queue.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/regex.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/replace.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/reset_headers.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/stdev.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/stop.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/string_functions.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/subtract.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/sum.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/tally.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/total_lines.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/track.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/variables.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/functions/variables_and_headers.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/grammar.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/headers.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/images/logo-wordmark-white-on-black-trimmed-padded.png +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/images/logo-wordmark-white-trimmed.png +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/paths.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/qualifiers.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/references.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/terms.md +0 -0
- {csvpath-0.0.473 → csvpath-0.0.475}/docs/variables.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: csvpath
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.475
|
|
4
4
|
Summary: A declarative language for data extraction and validation of CSV files
|
|
5
5
|
Author: David Kershaw
|
|
6
6
|
Author-email: dk107dk@hotmail.com
|
|
@@ -22,7 +22,6 @@ Requires-Dist: inflect (>=7.3.1,<8.0.0)
|
|
|
22
22
|
Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
|
|
23
23
|
Requires-Dist: lark (>=1.2.2,<2.0.0)
|
|
24
24
|
Requires-Dist: metaphone (>=0.6,<0.7)
|
|
25
|
-
Requires-Dist: pandas (>=2.2.2,<3.0.0)
|
|
26
25
|
Requires-Dist: ply (>=3.11,<4.0)
|
|
27
26
|
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
28
27
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
@@ -318,7 +318,7 @@ class CsvPath(CsvPathPublic, ErrorCollector): # pylint: disable=R0902, R0904
|
|
|
318
318
|
@property
|
|
319
319
|
def config(self) -> Config: # pylint: disable=C0116
|
|
320
320
|
if not self._config:
|
|
321
|
-
self._config = Config(
|
|
321
|
+
self._config = Config()
|
|
322
322
|
return self._config
|
|
323
323
|
|
|
324
324
|
def has_errors(self) -> bool: # pylint: disable=C0116
|
|
@@ -132,7 +132,7 @@ class CsvPaths(CsvPathsPublic, CsvPathsCoordinator):
|
|
|
132
132
|
self.quotechar = quotechar
|
|
133
133
|
self.skip_blank_lines = skip_blank_lines
|
|
134
134
|
self.current_matcher: CsvPath = None
|
|
135
|
-
self._config = Config(
|
|
135
|
+
self._config = Config()
|
|
136
136
|
self.logger = LogUtility.logger(self)
|
|
137
137
|
self.logger.info("initialized CsvPaths")
|
|
138
138
|
self._errors = []
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pylint: disable=C0114
|
|
2
2
|
from csvpath.matching.productions.expression import Matchable
|
|
3
3
|
from .function import Function
|
|
4
|
+
from .function_finder import FunctionFinder
|
|
4
5
|
from .dates.now import Now
|
|
5
6
|
from .dates.datef import Date
|
|
6
7
|
from .strings.lower import Lower
|
|
@@ -56,7 +57,8 @@ from .stats.percent import Percent
|
|
|
56
57
|
from .stats.minf import Min, Max, Average
|
|
57
58
|
from .stats.percent_unique import PercentUnique
|
|
58
59
|
from .stats.stdev import Stdev
|
|
59
|
-
|
|
60
|
+
|
|
61
|
+
# from .stats.correlate import Correlate
|
|
60
62
|
from .print.printf import Print
|
|
61
63
|
from .print.table import HeaderTable, RowTable, VarTable, RunTable
|
|
62
64
|
from .print.print_line import PrintLine
|
|
@@ -260,8 +262,8 @@ class FunctionFactory:
|
|
|
260
262
|
f = Strip(matcher, name, child)
|
|
261
263
|
elif name == "jinja":
|
|
262
264
|
f = Jinjaf(matcher, name, child)
|
|
263
|
-
elif name == "correlate":
|
|
264
|
-
f = Correlate(matcher, name, child)
|
|
265
|
+
# elif name == "correlate":
|
|
266
|
+
# f = Correlate(matcher, name, child)
|
|
265
267
|
elif name in ["count_headers", "count_headers_in_line"]:
|
|
266
268
|
f = CountHeaders(matcher, name, child)
|
|
267
269
|
elif name == "percent_unique":
|
|
@@ -292,9 +294,6 @@ class FunctionFactory:
|
|
|
292
294
|
f = Stack(matcher, name, child)
|
|
293
295
|
elif name in ["stdev", "pstdev"]:
|
|
294
296
|
f = Stdev(matcher, name, child)
|
|
295
|
-
#
|
|
296
|
-
# dup_lines can also decide matches
|
|
297
|
-
#
|
|
298
297
|
elif name == "has_dups":
|
|
299
298
|
f = HasDups(matcher, name, child)
|
|
300
299
|
elif name == "count_dups":
|
|
@@ -373,15 +372,13 @@ class FunctionFactory:
|
|
|
373
372
|
f = Num(matcher, name, child)
|
|
374
373
|
elif name == "counter":
|
|
375
374
|
f = Counter(matcher, name, child)
|
|
376
|
-
|
|
377
375
|
else:
|
|
378
|
-
if
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
f = f(matcher, name, child)
|
|
376
|
+
if f is None and find_external_functions:
|
|
377
|
+
if FunctionFinder.EXTERNALS not in FunctionFactory.NOT_MY_FUNCTION:
|
|
378
|
+
FunctionFinder().load(matcher, cls)
|
|
379
|
+
if name in FunctionFactory.NOT_MY_FUNCTION:
|
|
380
|
+
f = cls.NOT_MY_FUNCTION[name]
|
|
381
|
+
f = f(matcher, name, child)
|
|
385
382
|
if not find_external_functions:
|
|
386
383
|
return None
|
|
387
384
|
if f is None:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# pylint: disable=C0114
|
|
2
|
+
import os
|
|
3
|
+
import importlib
|
|
4
|
+
from csvpath.util.config_exception import ConfigurationException
|
|
5
|
+
from .boolean.yes import Yes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FunctionFinder:
|
|
9
|
+
EXTERNALS = "externalfunctionsloaded"
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def load(cls, matcher, func_fact) -> None:
|
|
13
|
+
# any problems loading will bubble up to the nearest
|
|
14
|
+
# expression and be handled there.
|
|
15
|
+
config = matcher.csvpath.config
|
|
16
|
+
# find the list
|
|
17
|
+
path = config.function_imports
|
|
18
|
+
# read the list
|
|
19
|
+
if path is None:
|
|
20
|
+
matcher.csvpath.logger.error("No [functions][imports] in config.ini")
|
|
21
|
+
return
|
|
22
|
+
if not os.path.exists(path):
|
|
23
|
+
matcher.csvpath.logger.error(
|
|
24
|
+
f"[functions][imports] path in {config.configpath} does not exist"
|
|
25
|
+
)
|
|
26
|
+
return
|
|
27
|
+
with open(path, "r") as file:
|
|
28
|
+
i = 0
|
|
29
|
+
for line in file:
|
|
30
|
+
i += 1
|
|
31
|
+
cls._add_function(matcher, func_fact, line)
|
|
32
|
+
matcher.csvpath.logger.info("Added %s external functions", i)
|
|
33
|
+
# add a sentinel to keep us from attempting reload.
|
|
34
|
+
# this instance will never be found, but the dict will
|
|
35
|
+
# never be empty
|
|
36
|
+
func_fact.add_function(cls.EXTERNALS, Yes(None, cls.EXTERNALS))
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _add_function(cls, matcher, func_fact, s: str) -> None:
|
|
40
|
+
s = s.strip()
|
|
41
|
+
if s != "":
|
|
42
|
+
instance = None
|
|
43
|
+
# instantiate the classes
|
|
44
|
+
# function_name module classname
|
|
45
|
+
cs = s.split(" ")
|
|
46
|
+
if len(cs) == 6 and cs[0] == "from" and cs[2] == "import" and cs[4] == "as":
|
|
47
|
+
module = importlib.import_module(cs[1])
|
|
48
|
+
class_ = getattr(module, cs[3])
|
|
49
|
+
instance = class_(matcher, cs[5])
|
|
50
|
+
# load to FunctionFactory.add
|
|
51
|
+
func_fact.add_function(cs[5], instance)
|
|
52
|
+
else:
|
|
53
|
+
raise ConfigurationException(
|
|
54
|
+
"Unclear external function import setup: {s}"
|
|
55
|
+
)
|
|
@@ -42,6 +42,8 @@ class Sections(Enum):
|
|
|
42
42
|
CSV_FILES = "csv_files"
|
|
43
43
|
ERRORS = "errors"
|
|
44
44
|
LOGGING = "logging"
|
|
45
|
+
FUNCTIONS = "functions"
|
|
46
|
+
CACHE = "cache"
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
class Config:
|
|
@@ -50,22 +52,45 @@ class Config:
|
|
|
50
52
|
- set a CSVPATH_CONFIG_FILE env var
|
|
51
53
|
- create a Config instance set its CONFIG member and call reload
|
|
52
54
|
- or set Config.CONFIG and reload to reset all instances w/o own specific settings
|
|
55
|
+
Also, you can pass Config(load=False) to give you the opportunity to set some/all
|
|
56
|
+
properties programmatically.
|
|
53
57
|
"""
|
|
54
58
|
|
|
55
59
|
CONFIG: str = "config/config.ini"
|
|
56
60
|
CSVPATH_CONFIG_FILE_ENV: str = "CSVPATH_CONFIG_PATH"
|
|
57
61
|
|
|
58
|
-
def __init__(self,
|
|
59
|
-
self.
|
|
62
|
+
def __init__(self, *, load=True):
|
|
63
|
+
self.load = load
|
|
64
|
+
self._cache_dir_path = None
|
|
65
|
+
self._function_imports = None
|
|
66
|
+
self._csvpath_file_extensions = None
|
|
67
|
+
self._csv_file_extensions = None
|
|
68
|
+
self._csvpath_errors_policy = None
|
|
69
|
+
self._csvpaths_errors_policy = None
|
|
70
|
+
self._csvpath_log_level = None
|
|
71
|
+
self._csvpaths_log_level = None
|
|
72
|
+
self._log_file = None
|
|
73
|
+
self._log_files_to_keep = None
|
|
74
|
+
self._log_file_size = None
|
|
60
75
|
self._config = RawConfigParser()
|
|
61
76
|
self.log_file_handler = None
|
|
62
77
|
self._configpath = environ.get(Config.CSVPATH_CONFIG_FILE_ENV)
|
|
63
78
|
if self._configpath is None:
|
|
64
79
|
self._configpath = Config.CONFIG
|
|
65
|
-
self.
|
|
80
|
+
if self.load:
|
|
81
|
+
self._load_config()
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def load(self) -> bool:
|
|
85
|
+
return self._load
|
|
86
|
+
|
|
87
|
+
@load.setter
|
|
88
|
+
def load(self, l:bool) -> None:
|
|
89
|
+
self._load = l
|
|
66
90
|
|
|
67
91
|
def reload(self):
|
|
68
92
|
self._config = RawConfigParser()
|
|
93
|
+
self._load = True
|
|
69
94
|
self._load_config()
|
|
70
95
|
|
|
71
96
|
def set_config_path_and_reload(self, path: str) -> None:
|
|
@@ -94,10 +119,19 @@ class Config:
|
|
|
94
119
|
)
|
|
95
120
|
|
|
96
121
|
def _create_default_config(self) -> None:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
directory = ""
|
|
123
|
+
file = ""
|
|
124
|
+
if self._configpath is None or self._configpath.strip() == "":
|
|
125
|
+
raise ConfiguationException("Config path cannot be None")
|
|
126
|
+
if self._configpath.find(os.sep) > 0:
|
|
127
|
+
s = self._configpath.rfind(os.sep)
|
|
128
|
+
directory = self._configpath[0:s]
|
|
129
|
+
name = self._configpath[s+1:]
|
|
130
|
+
if directory != "":
|
|
131
|
+
if not path.exists(directory):
|
|
132
|
+
os.makedirs(directory)
|
|
133
|
+
with open(self._configpath, "w") as file:
|
|
134
|
+
c = """
|
|
101
135
|
[csvpath_files]
|
|
102
136
|
extensions = txt, csvpath, csvpaths
|
|
103
137
|
[csv_files]
|
|
@@ -113,20 +147,25 @@ log_files_to_keep = 100
|
|
|
113
147
|
log_file_size = 52428800
|
|
114
148
|
[config]
|
|
115
149
|
path =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
150
|
+
[functions]
|
|
151
|
+
imports =
|
|
152
|
+
[cache]
|
|
153
|
+
path =
|
|
154
|
+
"""
|
|
155
|
+
file.write(c)
|
|
156
|
+
print(f"Created a default config file at {directory} with name {name}.")
|
|
119
157
|
print("If you want your config to be somewhere else remember to")
|
|
120
158
|
print("update the path in the default config.ini")
|
|
121
159
|
|
|
122
160
|
def _assure_logs_path(self) -> None:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
filepath
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
161
|
+
if self.load:
|
|
162
|
+
filepath = self.log_file
|
|
163
|
+
if not filepath or filepath.strip() == "":
|
|
164
|
+
filepath = "logs/csvpath.log"
|
|
165
|
+
self.log_file = filepath
|
|
166
|
+
dirpath = self._get_dir_path(filepath)
|
|
167
|
+
if dirpath and not path.exists(dirpath):
|
|
168
|
+
os.makedirs(dirpath)
|
|
130
169
|
|
|
131
170
|
def _get_dir_path(self, filepath):
|
|
132
171
|
if filepath.find(os.sep) > -1:
|
|
@@ -135,20 +174,23 @@ path =
|
|
|
135
174
|
return None
|
|
136
175
|
|
|
137
176
|
def _assure_cache_path(self) -> None:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"No cache path available. Check config.ini [cache][path]."
|
|
144
|
-
)
|
|
177
|
+
if self.load:
|
|
178
|
+
if self.cache_dir_path is None or self.cache_dir_path.strip() == "":
|
|
179
|
+
self.cache_dir_path = "cache"
|
|
180
|
+
if not path.exists(self.cache_dir_path):
|
|
181
|
+
os.makedirs(self.cache_dir_path)
|
|
145
182
|
|
|
146
183
|
def _assure_config_file_path(self) -> None:
|
|
147
|
-
if
|
|
148
|
-
self._configpath
|
|
149
|
-
|
|
184
|
+
if self.load:
|
|
185
|
+
if not self._configpath or self._configpath.strip() == "":
|
|
186
|
+
self._configpath = Config.CONFIG
|
|
187
|
+
if not os.path.isfile(self._configpath):
|
|
188
|
+
self._create_default_config()
|
|
150
189
|
|
|
151
190
|
def _load_config(self, norecurse=False):
|
|
191
|
+
if self._load is False:
|
|
192
|
+
print("WARNING: _load_config called on a config instance that is set to not load")
|
|
193
|
+
return
|
|
152
194
|
self._assure_config_file_path()
|
|
153
195
|
#
|
|
154
196
|
#
|
|
@@ -172,6 +214,23 @@ path =
|
|
|
172
214
|
self.log_file_size = self._get(
|
|
173
215
|
Sections.LOGGING.value, LogFile.LOG_FILE_SIZE.value
|
|
174
216
|
)
|
|
217
|
+
# path to external functions list. external functions are very optional.
|
|
218
|
+
# not blowing up when absent seems reasonable.
|
|
219
|
+
try:
|
|
220
|
+
self.function_imports = self._get(Sections.FUNCTIONS.value, "imports")
|
|
221
|
+
print(f"config fuc im: {self.function_imports}")
|
|
222
|
+
except Exception:
|
|
223
|
+
print("WARNING: config cannot load [functions][imports] from {self.configpath}")
|
|
224
|
+
pass
|
|
225
|
+
# likewise caching.
|
|
226
|
+
try:
|
|
227
|
+
self.cache_dir_path = self._get(Sections.CACHE.value, "path")
|
|
228
|
+
except Exception:
|
|
229
|
+
print("WARNING: config cannot load [cache][path] from {self.configpath}")
|
|
230
|
+
pass
|
|
231
|
+
#
|
|
232
|
+
# reload if another config path is set
|
|
233
|
+
#
|
|
175
234
|
path = self._get("config", "path")
|
|
176
235
|
if path:
|
|
177
236
|
path = path.strip().lower()
|
|
@@ -262,16 +321,29 @@ path =
|
|
|
262
321
|
|
|
263
322
|
# ======================================
|
|
264
323
|
|
|
324
|
+
@property
|
|
325
|
+
def configpath(self) -> str:
|
|
326
|
+
return self._configpath
|
|
327
|
+
|
|
328
|
+
@configpath.setter
|
|
329
|
+
def configpath(self, path: str) -> None:
|
|
330
|
+
self._configpath = path
|
|
331
|
+
|
|
265
332
|
@property
|
|
266
333
|
def cache_dir_path(self) -> str:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
334
|
+
return self._cache_dir_path
|
|
335
|
+
|
|
336
|
+
@cache_dir_path.setter
|
|
337
|
+
def cache_dir_path(self, p) -> None:
|
|
338
|
+
self._cache_dir_path = p
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def function_imports(self) -> str:
|
|
342
|
+
return self._function_imports
|
|
343
|
+
|
|
344
|
+
@function_imports.setter
|
|
345
|
+
def function_imports(self, path: str) -> None:
|
|
346
|
+
self._function_imports = path
|
|
275
347
|
|
|
276
348
|
@property
|
|
277
349
|
def csvpath_file_extensions(self) -> list[str]:
|
|
@@ -80,17 +80,26 @@ When your function needs its arguments you can call:
|
|
|
80
80
|
- self._value_two(skip=skip)
|
|
81
81
|
- self._siblings()
|
|
82
82
|
|
|
83
|
-
When you need to get a value or a match from a child object you use `to_value()` or `matches()`. Remember to pass the skip list as
|
|
83
|
+
When you need to get a value or a match from a child object you use `to_value()` or `matches()`. Remember to pass the skip list as the named argument `skip`.
|
|
84
84
|
|
|
85
85
|
## Registering
|
|
86
86
|
|
|
87
|
-
To register your function for use, add it to the FunctionFactory like this:
|
|
87
|
+
To register your function for use, add it to the `FunctionFactory` like this:
|
|
88
88
|
|
|
89
89
|
```python
|
|
90
90
|
from csvpath.matching.functions.function_factory import FunctionFactory
|
|
91
91
|
FunctionFactory.add_function(name='iamafunction', function=my_function_instance)
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
Alternatively, add a file path to your `config.ini` file under the key `[functions][imports]` that points to a list of functions to register. The key is like:
|
|
95
|
+
|
|
96
|
+
[functions]
|
|
97
|
+
imports = my_imports/functions.txt
|
|
98
|
+
|
|
99
|
+
In the file list every function on its own line using a format like that of Python imports:
|
|
100
|
+
|
|
101
|
+
from a.b.c.my_function import MyFunction as iamafunction
|
|
102
|
+
|
|
94
103
|
To use your function do something like:
|
|
95
104
|
|
|
96
105
|
```bash
|
|
@@ -41,7 +41,20 @@ Some functions will optionally make use of an arbitrary name qualifier to better
|
|
|
41
41
|
|
|
42
42
|
## Custom Functions
|
|
43
43
|
|
|
44
|
-
Creating your own function is easy. Once you create a function, you register it with the `FunctionFactory` class. You
|
|
44
|
+
Creating your own function is easy. Once you create a function, you register it with the `FunctionFactory` class. You can register your functions either programmatically or by creating an import file listing your functions. Your import file must be referenced in your `config.ini` file at `[functions][imports]` like this:
|
|
45
|
+
|
|
46
|
+
[functions]
|
|
47
|
+
imports = my_project_assets/my_functions.imports
|
|
48
|
+
|
|
49
|
+
Each custom function has its own line in your imports file. The format is the same as you use for importing classes into Python files -- under the hood the mechanism is similar. E.g.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from my_function.for_stuff.me import Me as function_me
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
In this example your class is `Me` and the name of the function you use in your csvpath is `function_me()`
|
|
56
|
+
|
|
57
|
+
Use your functions in csvpaths by simply referring to them by name like you would any built-in function.
|
|
45
58
|
|
|
46
59
|
<a href='https://github.com/dk107dk/csvpath/blob/main/docs/functions/implementing_functions.md'>Read more about implementing your own functions here.</a>
|
|
47
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "csvpath"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.475"
|
|
4
4
|
description = "A declarative language for data extraction and validation of CSV files"
|
|
5
5
|
authors = ["David Kershaw <dk107dk@hotmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -27,7 +27,6 @@ include = ["config", "logs", "docs"]
|
|
|
27
27
|
python = "^3.12"
|
|
28
28
|
ply = "^3.11"
|
|
29
29
|
python-dateutil = "^2.9.0.post0"
|
|
30
|
-
pandas = "^2.2.2"
|
|
31
30
|
jinja2 = "^3.1.4"
|
|
32
31
|
inflect = "^7.3.1"
|
|
33
32
|
lark = "^1.2.2"
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# pylint: disable=C0114
|
|
2
|
-
import pandas as pd
|
|
3
|
-
from ..function_focus import ValueProducer
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Correlate(ValueProducer):
|
|
7
|
-
"""does a statistical correlation test on the values of two headers"""
|
|
8
|
-
|
|
9
|
-
def check_valid(self) -> None:
|
|
10
|
-
self.validate_two_args()
|
|
11
|
-
super().check_valid()
|
|
12
|
-
|
|
13
|
-
def _produce_value(self, skip=None) -> None:
|
|
14
|
-
child = self.children[0]
|
|
15
|
-
left = child.left
|
|
16
|
-
right = child.right
|
|
17
|
-
leftlist = left.to_value(skip=skip)
|
|
18
|
-
rightlist = right.to_value(skip=skip)
|
|
19
|
-
leftlist, rightlist = self._trim(leftlist, rightlist)
|
|
20
|
-
ll = pd.Series(leftlist)
|
|
21
|
-
rl = pd.Series(rightlist)
|
|
22
|
-
corr = ll.corr(rl)
|
|
23
|
-
f = float(corr)
|
|
24
|
-
f = round(f, 2)
|
|
25
|
-
self.value = f
|
|
26
|
-
|
|
27
|
-
def _decide_match(self, skip=None) -> None:
|
|
28
|
-
self.to_value(skip=skip)
|
|
29
|
-
self.match = self._noop_match() # pragma: no cover
|
|
30
|
-
|
|
31
|
-
def _trim(self, leftlist, rightlist):
|
|
32
|
-
n = len(leftlist) if len(leftlist) < len(rightlist) else len(rightlist)
|
|
33
|
-
ll = []
|
|
34
|
-
rl = []
|
|
35
|
-
for i in range(0, n):
|
|
36
|
-
try:
|
|
37
|
-
ll.append(float(leftlist[i]))
|
|
38
|
-
rl.append(float(rightlist[i]))
|
|
39
|
-
except (TypeError, ValueError):
|
|
40
|
-
pass
|
|
41
|
-
return ll, rl
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csvpath-0.0.473 → csvpath-0.0.475}/csvpath/matching/functions/headers/header_names_mismatch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|