Code Monkey home page Code Monkey logo

apacheconfig's Introduction

Apache-style config parser

PyPI Python Versions Build status Coverage Status GitHub license

This is a pure-Python implementation of Apache-like configuration file parser into Python built-in types. Similar file format is utilized by Perl's Config::General module.

How to use apacheconfig

With apacheconfig you can build a tree of Python objects from Apache configuration file.

For example, the following Apache configuration:

<cops>
  name stein
  age  25
  <colors>
    color \#000000
  </colors>
</cops>

Would be transformed by apacheconfig into:

{
'cops': {
  'name': 'stein',
  'age': '25',
  'colors': {
    'color': '#000000'
  }
}

By running the following code:

from apacheconfig import *

with make_loader() as loader:
    config = loader.load('httpd.conf')

print(config)

You can also dump the config dict back into Apache configuration file form by calling dump or dumps method:

from apacheconfig import *

with make_loader() as loader:
    config = loader.load('httpd.conf')

# ...potentially modify the `config` dict anyhow...

print(loader.dumps(config))

Please, note that dumped config file might differ in its representation from the original because dict form does not contain all pieces of the original file (like comments), some items may get modified on load (like variable expansion) and included files rendered into the single dict.

Parsing options

Parser behavior can be modified by passing it one or more options. The options are passed as a dictionary:

from apacheconfig import *

options = {
    'lowercasenames': True
}

with make_loader(**options) as loader:
    config = loader.load('httpd.conf')

print(config)

The options are largely patterned after Perl's Config::General module, documentation for the options is also borrowed there.

The following options are currently supported:

allowmultioptions

If the value is False, then multiple identical options are disallowed. The default is True in which case values of the identical options are collected into a list.

forcearray

You may force a single config line to get parsed into a list by turning on the option forcearray and by surrounding the value of the config entry by [].

Example:

hostlist = [foo.bar]

Will result in a single value array entry if the forcearray option is turned on.

lowercasenames

If set to True, then all options found in the config will be converted to lowercase. This allows you to provide case-in-sensitive configs. The values of the options will not lowercased.

nostripvalues

If set to False, then each value found in the config will not be right-stripped. This allows you to gather the options as long as their trailing whitespaces.

useapacheinclude

If set to True, the parser will consider "include ..." as valid include statement (just like the well known Apache include statement).

It also supports apache's "IncludeOptional" statement with the same behavior, that is, if the include file doesn't exist no error will be thrown.

includeagain

If set to True, you will be able to include a sub-configfile multiple times. With the default, False, duplicate includes are silently ignored and only the first include will succeed.

Reincluding a configfile can be useful if it contains data that you want to be present in multiple places in the data tree.

Note, however, that there is currently no check for include recursion.

includerelative

If set to True, included files with a relative path (i.e. "cfg/blah.conf") will be opened from within the location of the configfile instead from within the location of the script ($0). This works only if the configfile has a absolute pathname (i.e. "/etc/main.conf").

If the configpath option has been set and if the file to be included could not be found in the location relative to the current config file, the module will search within configpath for the file.

includedirectories

If set to True, you may specify include a directory, in which case all files inside the directory will be loaded in ASCII order. Directory includes will not recurse into subdirectories. This is comparable to including a directory in Apache-style config files.

includeglob

If set to True, you may specify a glob pattern for an include to include all matching files (e.g. <<include conf.d/*.conf>>).

An include option will not cause a parser error if the glob didn't return anything.

configpath

You can use this variable to specify a search path for relative config files which have to be included. The apacheconfig tool will search within this path for the file if it cannot find the file at the location relative to the current config file.

To provide multiple search paths you can specify an array reference for the path. For example:

options = {
    'configpath': ['dira', 'dirb']
}

mergeduplicateblocks

If set to True, then duplicate blocks, that means blocks and named blocks, will be merged into a single one The default behavior is to create an array if some junk in a config appears more than once.

mergeduplicateoptions

If set to True, then duplicate options will be merged. That means, if the same option occurs more than once, the last one will be used in the resulting config dictionary.

autotrue

If set to True, then options in your config file, whose values are set to true or false values, will be normalised to 1 or 0 respectively.

The following values will be considered as true:

  • yes
  • on
  • 1
  • true

The following values will be considered as false:

  • no
  • off
  • 0
  • false

This effect is case-insensitive, i.e. both Yes or No will result in 1.

namedblocks

This option enables tag splitting by the first whitespace and turning the trailing piece into a nested block. True by default.

flagbits

This option takes one required parameter, which must be a dictionary defining variables for which you want to preset values. Each variable you have defined in this dictionary and which occurs in your config file, will cause this variable being set to the preset values to which the value in the config file refers to.

Multiple flags can be used, separated by the pipe character |.

For example, this option:

options = {
    'flagbits': {
        'mode': {
            'CLEAR': '1',
            'STRONG': '1',
            'UNSECURE': '32bit'
        }
    }
}

In this example we are defining a variable named mode which may contain one or more of CLEAR, STRONG and UNSECURE as value.

The appropriate config entry may look like this:

mode = CLEAR | UNSECURE

The parser will create a dictionary which will be the value of the key mode. This dictionary will contain all flags which you have pre-defined, but only those which were set in the config will contain the pre-defined value, the other ones will be undefined.

The resulting config structure would look like this after parsing:

config = {
    'mode': {
        'CLEAR': '1',
        'UNSECURE': '32bit',
        'STRONG': None
    }
}

This method allows the user to set multiple pre-defined values for one option.

Please beware, that all occurrences of those variables will be handled this way, there is no way to distinguish between variables in different scopes. That means, if mode would also occur inside a named block, it would also parsed this way.

Values which are not defined in the dictionary supplied to the parameter flagbits and used in the corresponding variable in the config will be ignored.

defaultconfig

The value of this option should be a dictionary holding default options-values. This causes the module to populate the resulting config dictionary with the given values, which allows you to set default values for particular config options directly.

Note that you probably want to use this with mergeduplicateoptions, otherwise a default value already in the configuration file will produce an array of two values.

interpolatevars

If set to True, variable interpolation will be done on your config input.

Variables can be defined everywhere in the config and can be used afterwards as the value of an option. Variables cannot be used as keys or as part of keys.

If you define a variable inside a block or a named block then it is only visible within this block or within blocks which are defined inside this block.

For example:

# sample config which uses variables
basedir    = /opt/ora
user       = t_space
sys        = unix
<table intern>
 instance  = INTERN
 owner     = $user                 # "t_space"
 logdir    = $basedir/log          # "/opt/ora/log"
 sys       = macos
 <procs>
     misc1 = ${sys}_${instance}    # macos_INTERN
     misc2 = $user                 # "t_space"
 </procs>
</table>

This will result in the following structure:

{
  'basedir': '/opt/ora',
  'user': 't_space'
  'sys': 'unix',
  'table': {
    'intern': {
      'sys': 'macos',
      'logdir': '/opt/ora/log',
      'instance': 'INTERN',
      'owner': 't_space',
      'procs': {
        'misc1': 'macos_INTERN',
        'misc2': 't_space'
      }
    }
  }
}

As you can see, the variable sys has been defined twice. Inside the block a variable ${sys} has been used, which then were interpolated into the value of sys defined inside the

block, not the sys variable one level above. If sys were not defined inside the
block then the "global" variable sys would have been used instead with the value of unix.

Variables inside double quotes will be interpolated, but variables inside single quotes will not interpolated unless allowsinglequoteinterpolation option is set.

In addition you can surround variable names with curly braces to avoid misinterpretation by the parser.

interpolateenv

If set to True, environment variables can be referenced in configs, their values will be substituted in place of their reference in the value.

This option enables interpolatevars.

allowsinglequoteinterpolation

By default variables inside single quotes will not be interpolated. If you turn on this option, they will be interpolated as well.

strictvars

By default this is set to True, which causes parser to fail if an undefined variable with interpolatevars turned on occurs in a config. Set to False to avoid such error messages.

ccomments

The parser will process C-style comments as well as hash-style comments. By default C-style comments are processed, you can disable that by setting ccomments option to False.

noescape

If you want to preserve escaping special characters ($"#) in the configuration data, turn this parameter on. It is set to False by default.

preservewhitespace

When this option is enabled, the parser would collect insignificant whitespaces into AST. This information could then be used for code generation. The default is False.

multilinehashcomments

Escaped newlines at the end of hash comments add the following line to the comment. Apache's native config parser processes hash comments this way. Set to False by default.

disableemptyelementtags

When this option is enabled, the parser does not recognize empty element tags such as <block name/>. The former, for instance, would be re-interpreted as opening tag with tag name block, and argument name/. The default is False.

Parser plugins

You can alter the behavior of the parser by supplying callables which will be invoked on certain hooks during config file processing and parsing.

The general approach works like this:

def pre_open_hook(file, base):
    print('trying to open %s... ' % file)
    if 'blah' in file:
      print('ignored')
      return False, file, base
    else:
      print('allowed')
      return True, file, base

options = {
    'plug': {
        'pre_open': pre_open_hook
    }
}

Output:

trying to open cfg ... allowed
trying to open x/*.conf ... allowed
trying to open x/1.conf ... allowed
trying to open x/2.conf ... allowed
trying to open x/blah.conf ... ignored

As you can see, we wrote a little function which takes a filename and a base directory as parameters. We tell the parser via the plug option to call this sub every time before it attempts to open a file.

General processing continues as usual if the first value of the returned array is True. The second value of that tuple depends on the kind of hook being called.

The following hooks are available so far:

pre_open

Takes two parameters: filename and basedirectory.

Has to return a tuple consisting of 3 values:

  • True or False (continue processing or not)
  • filename
  • base directory

The returned basedirectory and filename will be used for opening the file.

pre_read

Takes two parameters: the source of the contents read from and a string containing the raw contents. This hook gets the unaltered, original contents as it's read from a file (or some other source).

Has to return an array of 3 values:

  • True or False (continue processing or not)
  • the source of the contents or None if loads() is invoked rather than load()
  • a string that replaces the read contents

You can use this hook to apply your own normalizations or whatever.

Command-line tool

The library comes with a simple command-line tool apacheconfigtool which can convert Apache-style config files into JSON. The tool is also useful for playing with config file formats and parser options.

$ apacheconfigtool  --help
usage: apacheconfigtool [-h] [-v] [--json-input] [--allowmultioptions]
                        [--forcearray] [--lowercasenames] [--nostripvalues]
                        [--useapacheinclude] [--includeagain]
                        [--includerelative] [--includedirectories]
                        [--includeglob] [--mergeduplicateblocks]
                        [--mergeduplicateoptions] [--namedblocks] [--autotrue]
                        [--interpolatevars] [--interpolateenv]
                        [--allowsinglequoteinterpolation] [--strictvars]
                        [--noescape] [--ccomments]
                        [--configpath CONFIGPATH] [--flagbits <JSON>]
                        [--defaultconfig <JSON>]
                        file [file ...]

Dump Apache config files into JSON

positional arguments:
  file                  Path to the configuration file to dump

optional arguments:
  -h, --help            show this help message and exit
  -v, --version         show program's version number and exit
  --json-input          Expect JSON file(s) on input, produce Apache
                        configuration

parsing options:
  --allowmultioptions   Collect multiple identical options into a list
  --forcearray          Force a single config line to get parsed into a list
                        by turning on this option and by surrounding the value
                        of the config entry by []
  --lowercasenames      All options found in the config will be converted to
                        lowercase
  --nostripvalues       All values found in the config will not be right-
                        stripped
  --useapacheinclude    Consider "include ..." as valid include statement
  --includeagain        Allow including sub-configfiles multiple times
  --includerelative     Open included config files from within the location of
                        the configfile instead from within the location of the
                        script
  --includedirectories  Include statement may point to a directory, in which
                        case all files inside the directory will be loaded in
                        ASCII order
  --includeglob         Include statement may point to a glob pattern, in
                        which case all files matching the pattern will be
                        loaded in ASCII order
  --mergeduplicateblocks
                        Duplicate blocks (blocks and named blocks), will be
                        merged into a single one
  --mergeduplicateoptions
                        If the same option occurs more than once, the last one
                        will be used in the resulting config dictionary
  --namedblocks         Do not split tags by the first whitespace and turn the
                        trailing part into a nested block
  --autotrue            Turn various forms of binary values in config into "1"
                        and "0"
  --interpolatevars     Enable variable interpolation
  --interpolateenv      Enable process environment variable interpolation
  --allowsinglequoteinterpolation
                        Perform variable interpolation even when being in
                        single quotes
  --strictvars          Do not fail on an undefined variable when performing
                        interpolation
  --noescape            Preserve special escape characters left outs in the
                        configuration values
  --ccomments           Do not parse C-style comments
  --configpath CONFIGPATH
                        Search path for the configuration files being
                        included. Can repeat.
  --flagbits <JSON>     Named bits for an option in form of a JSON object of
                        the following structure {"OPTION": {"NAME": "VALUE"}}
  --defaultconfig <JSON>
                        Default values for parsed configuration in form of a
                        JSON object

$ apacheconfigtool --includedirectories include-directory-test.conf
{
  "final_include": "true",
  "seen_first_config": "true",
  "seen_second_config": "true",
  "inner": {
    "final_include": "true",
    "seen_third_config": "true"
  },
  "seen_third_config": "true"
}

How to get apacheconfig

The apacheconfig package is distributed under terms and conditions of 2-clause BSD license. Source code is freely available as a GitHub repo.

You could pip install apacheconfig or download it from PyPI.

If something does not work as expected, open an issue at GitHub.

Copyright (c) 2018-2020, Ilya Etingof. All rights reserved.

apacheconfig's People

Contributors

anviar avatar avictor0826 avatar barnabasj avatar eax64 avatar etingof avatar horlogeskynet avatar m0namon avatar sydneyli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

apacheconfig's Issues

Constant-ize tag names

Turn literals in the code into a "constant" variables to make the code easier to manage and allow code analyzers to catch possible issues.

Suggested here!

Blank space after heredoc terminator is causing incorrect parsing

Config:

# comment
  
TAG << MYTAG

some line

MYTAG # comment2

<tag>
    key value
</tag>

Please note extra space after second MYTAG keyword.

with python parser:

{}

Also for config:

TAG << MYTAG
some line
MYTAG # comment

I am getting

raise ApacheConfigError("Parser error at '%s'" % p.value if p else 'Unexpected EOF')

apacheconfig.error.ApacheConfigError: Unexpected EOF

Can you please check?

Handle backslash at end so that it doesn't consider line after blank line

For config file:

<main>
   key1 abc \
        pqr\

   key2 value2
</main>

This is parsing it into:

{"main": {"key1": "abc pqr key2 value2"}}

while expected parsing is
{"main": {"key1": "abc pqr ", "key2": "value2"}}

Also for config:

<main>
   key1 abc \
        pqr\

   <tag>
        key2 value2
    </tag>
</main>

It is raising below error:

raise ApacheConfigError("Parser error at '%s'" % p.value if p else 'Unexpected EOF')

apacheconfig.error.ApacheConfigError: Parser error at 'main'

Can you please check?

New line and tab characters get removed when '\' is used to split a line in heredoc

If we use '\' to split a line inside a heredoc then all the new line and tab characters are getting removed.
For example:

PYTHON <<END
def fn():
	print "hi"
	return 1 + fn2()

def fn2():
	return 3
END

produces:

{'PYTHON': 'def fn():\n\tprint "hi"\n\treturn 1 + fn2()\n\ndef fn2():\n\treturn 3'}

where as

PYTHON2 <<END2
def fn():
        print "hi"
        return 1 + \
	fn2()

def fn2():
        return 3

END2

produces:

{'PYTHON2': 'def fn(): print "hi" return 1 + fn2() def fn2(): return 3'}

Drop support for deprecated Pythons

<2.7 and <3.4 are deprecated, I think we can drop support by changing the python_requires in setup.py.

Once we do this, we can re-enable Sphinx builds on Travis for all builds: see discussion in #77.

Incorrect parsing of line with only key

For config file:

<main>
   key1
   key2 value2
</main>

This is raising following error

raise ApacheConfigError("Illegal character '%s'" % t.value[0])
apacheconfig.error.ApacheConfigError: Illegal character 'k'

while perl parse will parse into

{  
   "main" => {  
      "key1"=> undef,
      "key2"=> "value2"
   }
}

Can you please check?

Getting syntax error on trying to use the Parser.

Hi,

On trying to parse a config file using the parser, I get the following syntax error:

ERROR:/my_project/apacheconfig/lexer.py:358: Invalid regular expression for rule 't_OPTION_AND_VALUE'. unbalanced parenthesis
ERROR:/my_project/apacheconfig/lexer.py:358. Make sure '#' in rule 't_OPTION_AND_VALUE' is escaped with '#'
Traceback (most recent call last):
File "/my_project/test.py", line 114, in
create_object_from_config()
File "/my_project/test.py", line 75, in create_object_from_config
dict = load_config(config_path)
File "/my_project/test.py", line 39, in load_config
with make_loader(**options) as config_loader:
File "/opt/python/python-2.7/lib64/python2.7/contextlib.py", line 17, in enter
return self.gen.next()
File "/my_project/apacheconfig/init.py", line 24, in make_loader
yield ApacheConfigLoader(ApacheConfigParser(ApacheConfigLexer()),
File "/my_project/apacheconfig/lexer.py", line 144, in init
self.reset()
File "/my_project/apacheconfig/lexer.py", line 152, in reset
errorlog=log if self._debug else None
File "/usr/local/python/python-2.7/std/lib/python2.7/site-packages/ply/lex.py", line 910, in lex
raise SyntaxError("Can't build lexer")
SyntaxError: Can't build lexer

I am using the following options:

options = {
'noescape': True,
'autotrue': True,
'ccomments': False
}

Extra spaces at end are not trimmed

Config:

<main>
    key value 
</main>

(There is one space extra after value. i.e, key value)

With perl parser, extra space at end is trimmed off.

$VAR1 = {
          'main' => {
                      'key' => 'value'
                    }
        };

with python parser, space at end is not trimmed

{
    "main": {
        "key": "value "
    }
}

with make_loader() as loader: (SyntaxError: invalid syntax)

Hi,
I'm trying to parse a config file with python2.4...
when trying to load the conf file as a dict i get the following error:

CODE:
with make_loader() as loader:
httpd_dict = loader.load(httpd_conf_file)

ERROR:
with make_loader() as loader:
^
SyntaxError: invalid syntax

Can someone give me a clue?

Incorrect parsing of line with only key

For config file:

<main>
   key1
   key2 value2
</main>

python parser is parsing into

{  
   "main":{  
      "key1":"key2 value2"
   }
}

while perl parse will parse into

{  
   "main" => {  
      "key1"=> undef,
      "key2"=> "value2"
   }
}

Can this be handled?

Unable to parse heredoc inside a block properly

Config:

<main>
    PYTHON <<MYPYTHON
        def a():
            x = y
            return
    MYPYTHON
</main>

With python version of config parser I am getting parse error

But with perl version:

$VAR1 = {
          'main' => {
                      'PYTHON' => 'def a():
        x = y
        return'
                    }
        };

Can you please check?

What should happen when a block and option collide?

Should blocks and options have separate namespaces during parsing, or should this behavior be disallowed?
For instance:

a = 1
<a/>

The parsing results seem to vary quite a bit using Config::General as well, depending on the order of the lines.

Option for multi-line hash comments

Native apache has support for multi-line hash comments, such as:

# here is a \
   multi-line hash comment

where the entire body is interpreted to be a part of the hash comment.

Perl's General::Config doesn't seem to support this, but maybe we can provide an option for it.

Use of same `ApacheConfigError` makes debugging harder

Hey,

Let's take a look at a likely case :

The point is : The module should use different Error classes to allow distinction between them.

Tell me what you think about that !

Have a good day ๐ŸŒž

EDIT : FYI, if there is a problem with interpolated variables in one file for instance, the whole iteration (for includeglob and surely includedirectories) is stopped.
Each g_include call should watch for throwed exceptions coming from load or _merge_contents instead of letting them being catched by g_includeoptional parent call. WDYT ?

Build docs

Let's build & upload Sphinx docs to PyPi!

white space not ignored when key value pair splits in multiple lines

for the file

<mytag>
		key1 		this is a \
				very long line
</mytag>

the perl parser produces

{
  "mytag": {
    "key1": "this is a very long line"
  }
}

where as the python parser produces

{
  "mytag": {
    "key1": "this is a \t\t\t\tvery long line"
  }
}

The python parser doesn't ignores spaces if key value pair is splitting in multiple lines

Missing PyPi dependency

Hi Ilya,

'starting working with your module around here, and it looks like the dependency to ply is not explicitly set for PyPi.
Installing apacheconfig with pip does not automatically install ply.

Are you aware of this ?

Thanks, bye ๐Ÿ‘‹

Reading from a remote host.

Hello,
I was thinking about using your parser to create a new module for testinfra. But for this to work I would have to read from the target host via a different command then open. If you could give me some pointers on how you would implement this, I would like to create a PR with the changes.
Thanks in advance
Barnabas

Convert inmemory loaded file into dict

Currently to use the library we need to provide the file path as below

with make_loader() as loader:
    config = loader.load('httpd.conf')

However if the file is already loaded in the memory (say as a string), is there any way to convert directly from that in-memory loaded file into dict.

Support for multi-line block tags

Perl's General::Config has support for multi-line block tags. Something like:

<block \
  with a \
  multiline name\
>
</block>

gets turned into:
'block' => { 'with a multiline name' => {} }

As it is now, multi-line block tags are parsed as option-value statements, which leads to parsing errors later down the line.

NoEscape option of Config::General

Are there plans to add some option for "NoEscape" equivalent in this python version?

From perldoc:
-NoEscape
If you want to use the data ( scalar or final leaf ) without escaping special character, turn this parameter on. It is not enabled by default.

When starting config with backslash parsing fails

For below config:

# comment
\
<main>
    key value
</main>

This is raising an error:

raise ApacheConfigError("Illegal character '%s'" % t.value[0])

apacheconfig.error.ApacheConfigError: Illegal character ''

while perl parser parse it properly.

I am not sure whether perl's behavior is correct but just wanted to bring it to your notice.

Also it might be related to #34.

Can you please check?

Parsing then writing a config file adds syntax errors

Hi,
Thanks for apacheconfig. It has issues however, and some Apache drective are not handled as they should.

For instance, the following code turns the example config file into a defective version
code

import apacheconfig
with apacheconfig.make_loader(useapacheinclude=False) as loader:
        config = loader.load("/path/to/config.conf")
print(loader.dumps(config))

original config file

<IfModule mod_ssl.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains
    <Directory />
    Require all denied
    Options None
    AllowOverride None
    </Directory>
</IfModule>

modified config file

<IfModule>
  <mod_ssl.c>
        Header "always set Strict-Transport-Security "max-age=31536000; includeSubDomains""
        <Directory>
          </>
            Require "all denied"
            Options None
            AllowOverride None
          <//>
        </Directory>
  </mod_ssl.c>
</IfModule>

(as stated in the Readme), this module changes the way the config file looks, but it also changes what they mean, which is not expected.
In this example, it has broken directives that have arguments (and Apache complains that, e.g. <IfModule> directive requires additional arguments), and has added quotes where it shouldn't.

Also, it would be great that comments were kept, even though I understand that it is hard to keep them due to the dictionary-like object the file is converted into.

I am using Python 3.7.3 and apacheconfig version 0.3.2 (installed from pip).

dumps result is different than expected

Hello,

I would have a question.
I have a config like the following:

<thing-pool 123>
  thing thing1
  thing thing2
</thing-pool 123>

After loading it the given dict looks ok.

"thing-pool": [
          {
            "123": {
                "thing": "thing1"
            }
        }
]

But after dumping this dict I get the following:

<thing-pool>
  <123>
    thing "thing1"
    thing "thing2"
  </123>
</thing-pool>

My question is if I can somehow force the dump to use the structure like on the top?

Complete docs migration to Sphinx

Sphinx docs infrastructure has been introduced since 0.3.0. We already have some docs (mostly, CLI usage) in README. This issue is a reminder to:

  • Move CLI docs from README to Sphinx
  • Make Sphinx gathering docstrings and publishing them as an API doc

Comment and escaping # in same line

Config

<main>
    key value req\#123 # comment1
    # comment2
</main>

With python parser

{
    "main": {
        "key": "value req#123 # comment1"
    }
}

with perl parser

$VAR1 = {
          'main' => {
                      'key' => 'value req#123'
                    }
        };

Can you please check?

Option for disabling `OPEN_CLOSE_TAG`

Hiya! So the Apache parser doesn't support the "open/close tag" (AKA empty-element tag) -- that is, using <block/> as a shortcut for <block> </block>. This enables people to have a section like:

<Directory />
</Directory>

(a pretty common apache block!) without breaking the parser. Perl's Config::General throws an error when parsing the above since the first line is interpreted as an open/close tag, but Apache's parser is fine with it.

To resolve this conflict, I might propose adding an option to disable OPEN_CLOSE_TAG parsing.

ConfigFileReadError: Config file "ports.conf" not found in search path

Hi Ilya,

First, thank you very much for your helpful project.

if i run :

from apacheconfig import *

options = {
    'lowercasenames': True,
    'useapacheinclude': True,
    'includerelative': True,
    'includedirectories': True,
    'includeglob': True
}

with make_loader() as loader:
    config = loader.load('/etc/apache2/apache2.conf')

print(config)

and i get

$ python apache_config.py
Traceback (most recent call last):
  File "apache_config.py", line 12, in <module>
    config = loader.load('/etc/apache2/apache2.conf')
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 385, in load
    return self.loads(f.read(), source=filepath)
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 349, in loads
    self._ast_cache[source] = self._walkast(ast)
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 325, in _walkast
    return handler(ast[1:])
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 39, in g_config
    items = self._walkast(subtree)
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 325, in _walkast
    return handler(ast[1:])
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 92, in g_contents
    items = self._walkast(subtree)
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 325, in _walkast
    return handler(ast[1:])
  File "/usr/local/lib/python2.7/dist-packages/apacheconfig/loader.py", line 261, in g_include
    raise error.ConfigFileReadError('Config file "%s" not found in search path %s' % (filename, ':'.join(configpath)))
apacheconfig.error.ConfigFileReadError: Config file "ports.conf" not found in search path .

i have an Ubuntu 16.04.1 LTS with Apache/2.4.18 (Ubuntu)

kind regards!
Oliver

dumps does not handle certain options correctly

The issue

I'm currently running on the latest master branch of apacheconfig for testing purposes. I am unable to correctly grab options such as LocationMatch, Directory, and VirtualHost from an Apache config file and dump them to string. The option gets correctly picked up into the config dict, but when I run dumps, it converts the directive's option into it's own directive.

Minimal Test Case

import apacheconfig as ac

configFile = """<VirtualHost *:80>
  ServerName orange.com
</VirtualHost>
"""

_options = {
    'configpath': '/etc/apache2',
    'includeglob': True,
    'multilinehashcomments': True
}
with ac.make_loader(**_options) as loader:
    configStruct = loader.loads(configFile)
    print(loader.dumps(configStruct))

I would expect it to output essentially the same data as what was contained in the string, however, it returns the following:

<VirtualHost>
  <*:80>
    ServerName "orange.com"
  </*:80>
</VirtualHost>

Writable loader.py interface

Thought I'd create an issue for more thorough discussion about this!

loader.py transforms the apache configuration into a Python dict. Although we can write this configuration to a new file, a lot of information isn't saved by a Python dict-- for instance, whitespace and field ordering.

We're looking to be able to alter fields, then write them back to a file without changing the parts of the file that we did not change.

Problem with mergeduplicateblocks

Maybe I'm missunderstanding something but right know I have multiple Header options set in my httpd.conf.

Header ...
FileETag ...
Header ...
HttpProtocolOptions ...
Header ...

Arent't these options and should not be affected by mergeduplicateblocs but by mergeduplicateoptions? Because right know I get following Error:
ApacheConfigError: Cannot merge duplicate items "Header"

The only options I'm using are configpath and mergeduplicateblocks=True

Option for closing brackets on the same line.

Apache 2.2, which is the version of Apache on CentOS 6, sometimes allows syntax like:

<Block1>
<Block2>
</Block2> </Block1>

Where the final closing brackets are on the same line. @etingof, what do you think about enabling an option to support this? It seems to actually be a bug in Apache 2.2's parser, but we may have to support it (to prevent regressions) until CentOS 6 is EOL'ed.

Parsing incorrectly to nested array when there is comment between two lines with same key

I am using following configuration to parse

<main>
    <body>
        key value

        # comment
        key value
        key value
    </body>
</main>

and expecting result to be

{  
   "main":{  
      "body":{  
         "key":[  
            "value",
            "value",
            "value"
         ]
      }
   }
}

but parsing result of the library is

{  
   "main":{  
      "body":{  
         "key":[  
            "value",
            [  
               "value",
               "value"
            ]
         ]
      }
   }
}

Here is code used for parsing:

from apacheconfig import *
options = {
    'lowercasenames': True
}
with make_loader(**options) as loader:
    config = loader.load('test.conf')

Getting line number of config where error has occurred ?

It will be more intuitive if the Exceptions thrown also includes the line number of the config where error has occurred. for eg:
<open>
a 1
</close>

Then we may get something like "In line no:3 closing tag without any opening tag" or something similar.

`ApacheConfigError` with "<Directory />"

Hey, it's me again (already !),

Looks like the lexer parser can't handle config files with something like :

<Directory />
# ...
</Directory>

We got this error thrown :

apacheconfig.error.ApacheConfigError: Parser error at 'Directory'

This seems to come from the > not separated from the /.

Nevertheless, this configuration is likely possible with Apache default files ๐Ÿ˜จ

Thanks for your time,
Bye

Investigate DeprecationWarnings from ply/lex.py

On some Python 3 tests, we're getting warnings that look like the following:

ply/lex.py:760: DeprecationWarning: Flags not at the start of the expression '(?P(<t_TAG>'
ply/lex.py:498: DeprecationWarning: Flags not at the start of the expression '(?P(<t_TAG>(?'

Support for hash escape.

Can we please have support for hash escaping.

<main>
    key value\#123 
</main>

Perl parser:

{
    "main": {
        "key": "'value\\#123"
    }
}

Python parser:

{'main': {'key': 'value#123'}}

Provide option to avoid quoting values while dumping data

Is there a way we can provide option to avoid this quoting string values while dumping strings when they have special characters?

def _dump_dict():
..........
if val.isalnum():
text += '%s%s %s\n' % (spacing, key, val)
else:
text += '%s%s "%s"\n' % (spacing, key, val)

Initial newline and whitespaces ignored by perl parser but not by python parser

Perl Config::General module ignores the initial newline characters and white-spaces in the beginning of heredoc but this python parser doesn't. eg:

Config :

PERL <<END_OF_CODE


    line1;
line2;
END_OF_CODE

With Perl parser:
{"PERL":"line1;\nline2;"}

With Python parser:
{'PERL': '\n\n line1;\nline2;'}

Do you think if this behavior should be improved ?

Add linter to CI

Common linters include pep8 (which is now called pycodestyle), flake8, or pylint. From talking to folks, it seems flake8 might be a good choice.

Comment at end of value are not considered as comments

Config

<main>
    key value # comment1
    # comment2
</main>

With python parser

{
    "main": {
        "key": "value # comment1"
    }
}

with perl parser

$VAR1 = {
          'main' => {
                      'key' => 'value'
                    }
        };

Can you please check?

P.S.: You are awesome.

Some troubles with `ApacheIncludesLexer`

Hey Ilya, one more issue from me !

It looks like there is a problem with the ApacheIncludesLexer 'cause Include operations do not work when they looks like :

Include conf.d/*.conf

... due to the 'I', and because the token is 'include[\t ]+[^\n\r]+' (and case-sensitive).

It has been a headache to debug, and I don't know how to solve this problem as you instanciate only one lex element for the whole parsing, and because of course the case-sensitivity could be important in some cases.

It works well if we change the line to :

include conf.d/*.conf

Thanks again, have a good WE ๐Ÿ‘Œ

EDIT : Well... We could add a new token doing the same job but with a 'I'... Maybe there is a cleaner solution ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.