I have a groovy config file that I want to append data too. It would be easier to gather data using python that I want to add but I couldn't find a corresponding ConfigSlurper module in python and there is no straightforward way I saw to be able to do this using ConfigParser or anything. Has anyone done anything like this that has some feedback/advice on best approach?
Asked
Active
Viewed 4,684 times
2 Answers
8
That was a fun exercise.
from shlex import shlex
from ast import literal_eval
TRANSLATION = {
"true": True,
"false": False,
"null": None,
}
class ParseException(Exception):
def __init__(self, token, line):
self.token = token
self.line = line
def __str__(self):
return "ParseException at line %d: invalid token %s" % (self.line, self.token)
class GroovyConfigSlurper:
def __init__(self, source):
self.source = source
def parse(self):
lex = shlex(self.source)
lex.wordchars += "."
state = 1
context = []
result = dict()
while 1:
token = lex.get_token()
if not token:
return result
if state == 1:
if token == "}":
if len(context):
context.pop()
else:
raise ParseException(token, lex.lineno)
else:
name = token
state = 2
elif state == 2:
if token == "=":
state = 3
elif token == "{":
context.append(name)
state = 1
else:
raise ParseException(token, lex.lineno)
elif state == 3:
try:
value = TRANSLATION[token]
except KeyError:
value = literal_eval(token)
key = ".".join(context + [name]).split(".")
current = result
for i in xrange(0, len(key) - 1):
if key[i] not in current:
current[key[i]] = dict()
current = current[key[i]]
current[key[-1]] = value
state = 1
Then, you can do
with open("test.conf", "r") as f:
print GroovyConfigSlurper(f).parse()
# => {'setting': {'smtp': {'mail': {'host': 'smtp.myisp.com', 'auth': {'user': 'server'}}}}, 'grails': {'webflow': {'stateless': True}}, 'resources': {'URL': 'http://localhost:80/resources'}}

Amadan
- 191,408
- 23
- 240
- 301
-
3Looks promising, but it bails when I try to parse a build.gradle file, which is a Groovy DSL: "ParseException at line 1: invalid token plugin" line 1 is: "apply plugin: 'com.android.application'" – vitriolix Jan 29 '16 at 17:54
-
@vitriolix did you ever get a solution to this? – ThunderGrad Oct 23 '20 at 17:42
0
@Amadan's answer above works great. I might also suggest two small modifications, they were needed in our case (parsing Groovy based Nextflow Config files from within a Python code-base):
- support negative numbers (i.e. a monadic minus sign)
- support Groovy-style single-line comments (i.e //)
Also added a simple utility method to write the JSON to file and added the ability to pass in a string file-name instead of a string object.
The updated code looks like this:
from shlex import shlex
from ast import literal_eval
TRANSLATION = {
"true": True,
"false": False,
"null": None,
}
class ParseException(Exception):
def __init__(self, token, line):
self.token = token
self.line = line
def __str__(self):
return "ParseException at line %d: invalid token %s" % (self.line, self.token)
class GroovyConfigParser:
def __init__(self, source):
if isinstance(source, str):
self.source = open(source)
self.should_close_source = True
else:
self.source = source
self.should_close_source = False
def __del__(self):
if self.should_close_source and not self.source.closed:
self.source.close()
def parse(self):
lex = shlex(self.source)
lex.wordchars = lex.wordchars + ".-"
lex.commenters = "//"
state = 1
context = []
result = dict()
while True:
token = lex.get_token()
if not token:
return result
if state == 1:
if token == "}":
if len(context):
context.pop()
else:
raise ParseException(token, lex.lineno)
else:
name = token
state = 2
elif state == 2:
if token == "=":
state = 3
elif token == "{":
context.append(name)
state = 1
else:
raise ParseException(token, lex.lineno)
elif state == 3:
try:
value = TRANSLATION[token]
except KeyError:
value = literal_eval(token)
key = ".".join(context + [name]).split(".")
current = result
for i in range(len(key) - 1):
if key[i] not in current:
current[key[i]] = dict()
current = current[key[i]]
current[key[-1]] = value
state = 1
def write_as_json_file(self, json_file):
import json
with open(json_file, 'w') as file:
json.dump(self.parse(), file, indent=4)

Noam Barkai
- 51
- 2