1
0
Fork 0

Add a copy of the nginx builder module

This commit is contained in:
Luca Beltrame 2021-10-20 23:42:40 +02:00
parent ee9e06b1ee
commit 261e9d9fbb
Signed by: einar
GPG key ID: 4707F46E9EC72DEC
14 changed files with 1237 additions and 0 deletions

View file

@ -0,0 +1,65 @@
"""
The Block API provides objects to programatically generate nginx configurations.
Example::
>>> from nginx.config.api import Config, Section, Location
>>> events = Section('events', worker_connections='1024')
>>> http = Section('http', include='../conf/mime.types')
>>> http.sections.add(
... Section(
... 'server',
... Location(
... '/foo',
... proxy_pass='upstream',
... ),
... server_name='_',
... )
... )
>>> nginx = Config(
... events,
... http,
... worker_processes='auto',
... daemon='on',
... error_log='var/error.log',
... )
>>> print(nginx)
error_log var/error.log;
worker_processes auto;
daemon on;
http {
include ../conf/mime.types;
server {
server_name _;
location /foo {
proxy_pass upstream;
}
}
}
events {
worker_connections 1024;
}
.. The objects in this submodule are largely inspired by code found in https://github.com/FeroxTL/pynginxconfig-new.
"""
from .blocks import EmptyBlock, Block, Location
from .options import Comment, KeyOption, KeyValueOption, KeyMultiValueOption
__all__ = [
'EmptyBlock',
'Block',
'Location',
'KeyOption',
'KeyValueOption',
'KeyValueMultilines',
'KeyMultiValueOption',
'Comment',
'Config',
'Section'
]
# aliases
Config = EmptyBlock
Section = Block

View file

@ -0,0 +1,22 @@
class Base(object):
""" This is the base class for all blocks and options. """
_indent_level = 0
_indent_char = ' '
_indent = 4
_parent = None
def _get_indent(self):
return self._indent_char * self._indent * self._indent_level
def _render(self, name):
return '\n{indent}{name}'.format(
name=name,
indent=self._get_indent()
)
def __str__(self):
return str(self.__repr__())
@property
def parent(self):
return self._parent

View file

@ -0,0 +1,121 @@
import six
from .base import Base
from .options import AttrDict, AttrList, KeyOption, KeyValueOption, KeyMultiValueOption
class Block(Base):
""" A block represent a named section of an Nginx config, such as 'http', 'server' or 'location'
Using this object is as simple as providing a name and any sections or options,
which can be other Block objects or option objects.
Example::
>>> from nginx.config.api import Block
>>> http = Block('http', option='value')
>>> print(http)
http {
option value;
}
"""
def __init__(self, name, *sections, **options):
""" Creates a block.
Sections should be config objects such as Block or EmptyBlock,
Options can be any key/value pair (such as worker_connections=512, etc)
:param name str: The name of the block.
"""
self.name = name
self.sections = AttrList(self)
self.options = AttrDict(self)
self._set_directives(*sections, **options)
@property
def _directives(self):
dirs = self._dump_options() + list(self.sections)
return [directive for directive in dirs if directive is not self]
def _set_directives(self, *sections, **options):
for section in sections:
self.sections.append(section)
for key, value in six.iteritems(options):
setattr(self.options, key, value)
def _build_options(self, key, value):
if isinstance(value, Block):
option = value
elif isinstance(value, list):
option = KeyMultiValueOption(key, value=value)
elif value is None or value == '':
option = KeyOption(key)
else:
if isinstance(value, str) and ' ' in value:
option = KeyMultiValueOption(key, value=value.split())
else:
option = KeyValueOption(key, value=value)
return option
def _dump_options(self):
return [self._build_options(key, value) for key, value in six.iteritems(self.options)]
def __repr__(self):
directives = self._directives
for directive in directives:
if directive is not self:
directive._indent_level = self._indent_level + 1
return '\n{indent}{name}{{{directives}\n{indent}}}'.format(
name='{0} '.format(self.name),
directives=''.join([repr(e) for e in directives]),
indent=self._get_indent(),
)
class EmptyBlock(Block):
""" An unnamed block of options and/or sections.
Empty blocks are useful for representing groups of options.
For example, you can use them to represent options with non-unique keys:
Example::
>>> from nginx.config.helpers import duplicate_options
>>> dupes = duplicate_options('key', ['value', 'other_value', 'third_value'])
>>> type(dupes)
nginx.config.api.blocks.EmptyBlock
>>> print(dupes)
key third_value;
key value;
key other_value;
"""
def __init__(self, *sections, **options):
""" Create an EmptyBlock. """
self.sections = AttrList(self)
self.options = AttrDict(self)
self._set_directives(*sections, **options)
def __repr__(self):
directives = self._directives
for directive in directives:
directive._indent_level = self._indent_level
return ''.join([repr(o) for o in directives])
class Location(Block):
""" A Location is just a named block with "location" prefixed """
def __init__(self, location, *args, **kwargs):
super(Location, self).__init__('location {0}'.format(location), *args, **kwargs)

View file

@ -0,0 +1,139 @@
from .base import Base
class KeyOption(Base):
""" A KeyOption represents a directive with no value.
For example: http://nginx.org/en/docs/http/ngx_http_core_module.html#internal
"""
def __init__(self, name):
self.name = self.value = name
def __repr__(self):
return self._render(
'{name};'.format(
name=self.name,
)
)
class KeyValueOption(Base):
""" A key/value directive. This covers most directives available for Nginx """
def __init__(self, name, value=''):
self.name = name
if isinstance(value, bool):
self.value = 'off' if value is False else 'on'
elif isinstance(value, int):
self.value = str(value)
elif isinstance(value, list):
self.value = [str(e) for e in value]
else:
self.value = value
def __repr__(self):
return self._render(
'{name} {value};'.format(
name=self.name,
value=self.value
)
)
class KeyMultiValueOption(KeyValueOption):
""" A key with multiple space delimited options.
For example: http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
Example::
>>> from nginx.config.api.options import KeyMultiValueOption
>>> a_log = KeyMultiValueOption('access_log', ['/path/to/log.gz', 'combined', 'gzip', 'flush=5m'])
>>> print(a_log)
access_log /path/to/log.gz combined gzip flush=5m;
"""
def __repr__(self):
return self._render(
'{name} {value};'.format(
name=self.name,
value=' '.join(self.value)
)
)
class KeyValuesMultiLines(Base):
def __init__(self, name, values=[]):
self.name = name
self.lines = []
for value in values:
if isinstance(value, list):
self.lines.append(' '.join([str(v) for v in value]))
else:
self.lines.append(str(value))
def __repr__(self):
return ''.join(
[self._render('{name} {value};'.format(name=self.name, value=line)) for line in self.lines]
)
class Comment(Base):
""" A simple comment object. """
_offset = ''
_comment = ''
def __init__(self, offset='', comment='', **kwargs):
self._offset = offset
self._comment = comment
super(Comment, self).__init__(**kwargs)
def __repr__(self):
return self._render(
'{offset}# {comment}'.format(
offset=self._offset,
comment=self._comment,
)
)
class AttrDict(dict):
""" A dictionary that exposes it's values as attributes. """
def __init__(self, owner):
self.__dict__ = self
self._owner = owner
def __setitem__(self, key, val):
if hasattr(val, '_parent'):
val._parent = self._owner
return super(AttrDict, self).__setitem__(key, val)
def __repr__(self):
owner = self.pop('_owner')
ret = super(AttrDict, self).__repr__()
self._owner = owner
return ret
class AttrList(AttrDict):
""" A dictionary/list hybrid that exposes values as attributes. """
def __iter__(self):
return iter(self.values())
def append(self, item):
if hasattr(item, '_parent'):
item._parent = self._owner
if hasattr(item, 'name'):
self[item.name] = item
else:
self[hash(item)] = item
def add(self, *items):
for item in items:
self.append(item)
# alias for backwards compatibility
KeyValuesMultilines = KeyValuesMultiLines