Skip to content

Commit d5a1336

Browse files
committed
feat(cli): excel formula helper;
- Check for balanced brackets.
1 parent e9dbd82 commit d5a1336

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

changes/0014.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a CLI subcommand to help with balancing Excel formulae.

ckanext/recombinant/cli.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import csv
44
import sys
55
import json
6+
import re
7+
from openpyxl.formula import Tokenizer
68

79
from typing import Dict, List, Any, Optional, TextIO
810

@@ -20,6 +22,7 @@
2022
from ckanext.recombinant.read_csv import csv_data_batch
2123
from ckanext.recombinant.write_excel import excel_template
2224
from ckanext.recombinant.logic import _update_triggers
25+
from ckanext.recombinant.errors import RecombinantFieldError
2326

2427

2528
DATASTORE_PAGINATE = 10000 # max records for single datastore query
@@ -36,6 +39,106 @@ def recombinant():
3639
pass
3740

3841

42+
def _check_matching_brackets(formula: str):
43+
"""
44+
Checks for balanced brackets.
45+
"""
46+
lefts = [(t.start(), 1) for t in
47+
re.finditer(r"\[", formula)]
48+
rights = [(t.start(), -1) for t in
49+
re.finditer(r"\]", formula)]
50+
if len(lefts) < len(rights):
51+
raise RecombinantFieldError(
52+
'Encountered missing opening bracket [')
53+
if len(lefts) > len(rights):
54+
raise RecombinantFieldError(
55+
'Encountered missing closing bracket ]')
56+
57+
lefts = [(t.start(), 1) for t in
58+
re.finditer(r"\{", formula)]
59+
rights = [(t.start(), -1) for t in
60+
re.finditer(r"\}", formula)]
61+
if len(lefts) < len(rights):
62+
raise RecombinantFieldError(
63+
'Encountered missing opening bracket {')
64+
if len(lefts) > len(rights):
65+
raise RecombinantFieldError(
66+
'Encountered missing closing bracket }')
67+
68+
lefts = [(t.start(), 1) for t in
69+
re.finditer(r"\(", formula)]
70+
rights = [(t.start(), -1) for t in
71+
re.finditer(r"\)", formula)]
72+
if len(lefts) < len(rights):
73+
raise RecombinantFieldError(
74+
'Encountered missing opening bracket (')
75+
if len(lefts) > len(rights):
76+
raise RecombinantFieldError(
77+
'Encountered missing closing bracket )')
78+
79+
80+
@recombinant.command(
81+
short_help="Checks Excel formulae for basic syntax errors.")
82+
@click.argument("dataset_type", required=False)
83+
@click.argument("datastore_id", required=False)
84+
@click.option('-v', '--verbose', is_flag=True,
85+
type=click.BOOL, help='Increase verbosity.')
86+
def check_excel_syntax(dataset_type: Optional[str] = None,
87+
datastore_id: Optional[str] = None,
88+
verbose: bool = False):
89+
"""
90+
Check Excel formulae for basic syntax errors.
91+
Checks excel_error_formula and excel_required_formula.
92+
93+
Full Usage:\n
94+
recombinant check-excel-syntax [DATASET_TYPE [DATASTORE_ID]]
95+
"""
96+
types = [dataset_type] if dataset_type else get_dataset_types()
97+
for dtype in types:
98+
geno = get_geno(dtype)
99+
if verbose:
100+
click.echo('Checking Excel formulae for type: %s' % dtype)
101+
for resource in geno.get('resources', []):
102+
if verbose:
103+
click.echo('Checking Excel formulae for resource: %s' %
104+
resource['resource_name'])
105+
for field in resource.get('fields', []):
106+
if datastore_id and field['datastore_id'] != datastore_id:
107+
continue
108+
if 'excel_error_formula' in field:
109+
formula = Tokenizer(field['excel_error_formula'])
110+
errors = False
111+
for f in formula.items:
112+
try:
113+
_check_matching_brackets(f.value)
114+
except RecombinantFieldError as e:
115+
errors = True
116+
click.echo('%s:%s.%s - syntax BAD' %
117+
(dtype, resource['resource_name'],
118+
field['datastore_id']))
119+
click.echo('\t%s' % e)
120+
if not errors and verbose:
121+
click.echo('%s:%s.%s - excel_error_formula syntax OK' %
122+
(dtype, resource['resource_name'],
123+
field['datastore_id']))
124+
if 'excel_required_formula' in field:
125+
formula = Tokenizer(field['excel_required_formula'])
126+
errors = False
127+
for f in formula.items:
128+
try:
129+
_check_matching_brackets(f.value)
130+
except RecombinantFieldError as e:
131+
errors = True
132+
click.echo('%s:%s.%s - syntax BAD' %
133+
(dtype, resource['resource_name'],
134+
field['datastore_id']))
135+
click.echo('\t%s' % e)
136+
if not errors and verbose:
137+
click.echo('%s:%s.%s - excel_required_formula syntax OK' %
138+
(dtype, resource['resource_name'],
139+
field['datastore_id']))
140+
141+
39142
@recombinant.command(
40143
short_help="Display some information about the "
41144
"status of recombinant datasets.")

0 commit comments

Comments
 (0)