avasal
avasal

Reputation: 14854

Python Config parser read comment along with value

I have config file,

[local]
    variable1 : val1 ;#comment1
    variable2 : val2 ;#comment2

code like this reads only value of the key:

class Config(object):
    def __init__(self):
        self.config = ConfigParser.ConfigParser()
        self.config.read('config.py')

    def get_path(self):
        return self.config.get('local', 'variable1')

if __name__ == '__main__':
    c = Config()
    print c.get_path()

but i also want to read the comment present along with the value, any suggestions in this regards will be very helpful.

Upvotes: 8

Views: 7471

Answers (5)

Josh Pachner
Josh Pachner

Reputation: 511

In case anyone comes along afterwards. My situation was I needed to read in a .ini file generated by a Pascal Application. That configparser didn't care about # or ; starting the keys. For example the .ini file would look like this

  [KEYTYPE PATTERNS]
  ##-######=CLAIM

Python's configparser would skip that key value pair. Needed to modify the configparser to not look at # as comments

  config = configparser.ConfigParser(comment_prefixes="")
  config.read("thefile")

I'm sure I could set the comment_prefixes to whatever Pascal uses for comments, but didn't see any, so I set it to an empty string

Upvotes: 0

frm
frm

Reputation: 3486

Your only solutions is to write another ConfigParser overriding the method _read(). In your ConfigParser you should delete all checks about comment removal. This is a dangerous solution, but should work.

class ValuesWithCommentsConfigParser(ConfigParser.ConfigParser):

    def _read(self, fp, fpname):
        from ConfigParser import DEFAULTSECT, MissingSectionHeaderError, ParsingError

        cursect = None                        # None, or a dictionary
        optname = None
        lineno = 0
        e = None                              # None, or an exception
        while True:
            line = fp.readline()
            if not line:
                break
            lineno = lineno + 1
            # comment or blank line?
            if line.strip() == '' or line[0] in '#;':
                continue
            if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
                # no leading whitespace
                continue
                # continuation line?
            if line[0].isspace() and cursect is not None and optname:
                value = line.strip()
                if value:
                    cursect[optname].append(value)
            # a section header or option header?
            else:
                # is it a section header?
                mo = self.SECTCRE.match(line)
                if mo:
                    sectname = mo.group('header')
                    if sectname in self._sections:
                        cursect = self._sections[sectname]
                    elif sectname == DEFAULTSECT:
                        cursect = self._defaults
                    else:
                        cursect = self._dict()
                        cursect['__name__'] = sectname
                        self._sections[sectname] = cursect
                        # So sections can't start with a continuation line
                    optname = None
                # no section header in the file?
                elif cursect is None:
                    raise MissingSectionHeaderError(fpname, lineno, line)
                # an option line?
                else:
                    mo = self._optcre.match(line)
                    if mo:
                        optname, vi, optval = mo.group('option', 'vi', 'value')
                        optname = self.optionxform(optname.rstrip())
                        # This check is fine because the OPTCRE cannot
                        # match if it would set optval to None
                        if optval is not None:
                            optval = optval.strip()
                            # allow empty values
                            if optval == '""':
                                optval = ''
                            cursect[optname] = [optval]
                        else:
                            # valueless option handling
                            cursect[optname] = optval
                    else:
                        # a non-fatal parsing error occurred.  set up the
                        # exception but keep going. the exception will be
                        # raised at the end of the file and will contain a
                        # list of all bogus lines
                        if not e:
                            e = ParsingError(fpname)
                        e.append(lineno, repr(line))
            # if any parsing errors occurred, raise an exception
        if e:
            raise e

        # join the multi-line values collected while reading
        all_sections = [self._defaults]
        all_sections.extend(self._sections.values())
        for options in all_sections:
            for name, val in options.items():
                if isinstance(val, list):
                    options[name] = '\n'.join(val)

In the ValuesWithCommentsConfigParser I fixed some imports and deleted the appropriate sections of code.

Using the same config.ini from my previous answer, I can prove the previous code is correct.

config = ValuesWithCommentsConfigParser()
config.read('config.ini')
assert config.get('local', 'variable1') == 'value1 ; comment1'
assert config.get('local', 'variable2') == 'value2 # comment2'

Upvotes: 3

frm
frm

Reputation: 3486

Accordiing to the ConfigParser module documentation,

Configuration files may include comments, prefixed by specific characters (# and ;). Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names. In the latter case, they need to be preceded by a whitespace character to be recognized as a comment. (For backwards compatibility, only ; starts an inline comment, while # does not.)

If you want to read the "comment" with the value, you can omit the whitespace before the ; character or use the #. But in this case the strings comment1 and comment2 become part of the value and are not considered comments any more.

A better approach would be to use a different property name, such as variable1_comment, or to define another section in the configuration dedicated to comments:

[local]
    variable1 = value1
[comments]
    variable1 = comment1

The first solution requires you to generate a new key using another one (i.e. compute variable1_comment from variable1), the other one allows you to use the same key targeting different sections in the configuration file.

As of Python 2.7.2, is always possibile to read a comment along the line if you use the # character. As the docs say, it's for backward compatibility. The following code should run smoothly:

config = ConfigParser.ConfigParser()
config.read('config.ini')
assert config.get('local', 'variable1') == 'value1'
assert config.get('local', 'variable2') == 'value2 # comment2'

for the following config.ini file:

[local]
variable1 = value1 ; comment1
variable2 = value2 # comment2

If you adopt this solution, remember to manually parse the result of get() for values and comments.

Upvotes: 2

9000
9000

Reputation: 40894

Alas, this is not easily done in general case. Comments are supposed to be ignored by the parser.

In your specific case, it is easy, because # only serves as a comment character if it begins a line. So variable1's value will be "val1 #comment1". I suppose you use something like this, only less brittle:

val1_line = c.get('local', 'var1')
val1, comment = val1_line.split(' #') 

If the value of a 'comment' is needed, probably it is not a proper comment? Consider adding explicit keys for the 'comments', like this:

[local]
  var1: 108.5j
  var1_comment: remember, the flux capacitor capacitance is imaginary! 

Upvotes: 8

deanforwever
deanforwever

Reputation: 46

according to the manuals: Lines beginning with '#' or ';' are ignored and may be used to provide comments.

so the value of variable1 is "val1 #comment1".The comment is part of the value

you can check your config whether you put a Enter before your comment

Upvotes: 1

Related Questions