Skip to content

Instantly share code, notes, and snippets.

@jn0
Last active July 7, 2017 08:55
Show Gist options
  • Save jn0/cefb9135555d910c584c54ca67a186be to your computer and use it in GitHub Desktop.
Save jn0/cefb9135555d910c584c54ca67a186be to your computer and use it in GitHub Desktop.

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

  1. Indexed names are mapped to dicts, not lists!
  2. Comments and empty lines are just ignored.
#!/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 #
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment