|
#!/usr/bin/python |
|
''' |
|
# Converts a series of "assignments" to a "JSON" dict. |
|
|
|
It was intended to parse `/var/lib/iscsi/nodes/$TARGET/$PORTAL/default` files |
|
of iSCSI initiator node (where one have ran `iscsiadm` command). The path is |
|
for CentOS 7 based machines -- your mileage may vary. |
|
|
|
## Syntax of input files |
|
|
|
line ::= [ entry ] [ '#' comment ] '\n' |
|
|
|
comment ::= .* # ignored |
|
|
|
entry ::= space* name space* '=' space* value space* |
|
|
|
name ::= simpleName | simpleName '.' name |
|
simpleName ::= identifier | indexed |
|
indexed ::= identifier '[' index ']' |
|
index ::= int |
|
identifier ::= .* # actually, no restrictions other than lack of '[9]' suffix applied |
|
|
|
value ::= null | bool | float | int | string |
|
null ::= 'None' |
|
bool ::= 'Yes' | 'No' |
|
float ::= int '.' [ int ] # actually, anything that has a '.' and can be parsed with float() |
|
int ::= digit | digit int # actually, anything that has no '.' and can be parsed with int() |
|
string ::= .* # actually, anything else, i.e. not null, bool, float, or int. |
|
|
|
## Caveats |
|
|
|
Indexed names are mapped to dicts, not lists! |
|
|
|
Comments and empty lines are just ignored. |
|
''' |
|
|
|
import logging |
|
logging.basicConfig(datefmt='%F %T %z', level=logging.INFO) |
|
logger = logging.getLogger('py2json') |
|
|
|
import sys |
|
import json |
|
|
|
def convert_value(s): |
|
if s == 'None': |
|
return None |
|
if s in ('No',): |
|
return False |
|
if s in ('Yes',): |
|
return True |
|
if '.' in s: |
|
try: |
|
return float(s) |
|
except ValueError: |
|
return s.decode('utf-8') |
|
try: |
|
return int(s) |
|
except ValueError: |
|
return s.decode('utf-8') |
|
|
|
def convert_name(s): |
|
r = [] |
|
for item in s.split('.'): |
|
if item.endswith(']'): |
|
nm, idx = item.split('[', 1) |
|
idx = int(idx.split(']', 1).pop(0)) |
|
idx = str(idx).decode('utf-8') # we make use of dicts instead of lists here |
|
r.append((nm.decode('utf-8'), idx)) |
|
else: |
|
r.append((item.decode('utf-8'), None)) |
|
return r |
|
|
|
def set_value(r, name, value, was): |
|
logger.debug('set_value(r={}, name={}, value={})'.format(`r`, `name`, `value`)) |
|
if name: |
|
(n, i) = name[0] |
|
logger.debug('set_value: n={} i={}'.format(`n`, `i`)) |
|
if i is None: |
|
r[n] = r.get(n, {}) |
|
r[n] = set_value(r[n], name[1:], value, i) |
|
else: |
|
r[n] = r.get(n, {i: {}}) # it should be a list, but... |
|
r[n][i] = set_value(r[n][i], name[1:], value, i) |
|
return r |
|
return value |
|
|
|
def convert(fin=sys.stdin, fout=sys.stdout): |
|
r, n = {}, 0 |
|
for line in fin.xreadlines(): |
|
n += 1 |
|
logger.debug('{}: {}'.format(n, `line`)) |
|
line, comment = map(lambda s: s.strip(), line.split('#', 1) + ['',''])[:2] |
|
if not line: |
|
continue |
|
name, value = map(lambda s: s.strip(), line.split('=', 1)) |
|
name, value = convert_name(name), convert_value(value) |
|
|
|
set_value(r, name, value, None) |
|
|
|
logger.debug('{}: {}={}'.format(n, `name`, `value`)) |
|
return r |
|
|
|
def compare(d1, d2): |
|
if type(d1) is not type(d2): |
|
logger.info('compare({}, {}): type'.format(`d1`, `d2`)) |
|
return False |
|
if type(d1) is not dict: |
|
if d1 != d2: |
|
logger.info('compare({}, {}): non-dict'.format(`d1`, `d2`)) |
|
return d1 == d2 |
|
keys = d1.keys() |
|
if sorted(keys) == sorted(d2.keys()): |
|
for k in keys: |
|
if not compare(d1[k], d2[k]): |
|
return False |
|
return True |
|
else: |
|
logger.info('compare({}, {}): keys {} != {}'.format(`d1`, `d2`, `keys`, `d2.keys()`)) |
|
return False |
|
|
|
def main(): |
|
import pprint |
|
d = convert() |
|
pprint.pprint(d) |
|
print |
|
json.dump(d, fp=sys.stdout, indent=2) |
|
print |
|
s = json.dumps(d) |
|
d2 = json.loads(s) |
|
assert compare(d, d2) |
|
return 0 |
|
|
|
if __name__ == '__main__': |
|
sys.exit(main()) |
|
|
|
# EOF # |