11import gzip
22import json
33import tarfile
4+ from collections import OrderedDict
45from copy import deepcopy
56from io import BytesIO
67
78import six
8- from jinja2 import Environment , PackageLoader
99from jsonschema import FormatChecker , validate
1010from jsonschema .exceptions import ValidationError as JsonSchemaError
1111
12- from ..exceptions import ValidationError
13- from ..schema import DEFAULT_FILE_MODE
14- from ..utils import evaluate_vars , merge_config
12+ from ... exceptions import ValidationError
13+ from ... schema import DEFAULT_FILE_MODE
14+ from ... utils import evaluate_vars , merge_config
1515
1616
1717class BaseBackend (object ):
1818 """
1919 Base Backend class
2020 """
2121 schema = None
22- env_path = 'netjsonconfig.backends.base'
2322 FILE_SECTION_DELIMITER = '# ---------- files ---------- #'
23+ intermediate_data = None
2424
2525 def __init__ (self , config , templates = [], context = {}):
2626 """
@@ -35,8 +35,6 @@ def __init__(self, config, templates=[], context={}):
3535 config = deepcopy (self ._load (config ))
3636 self .config = self ._merge_config (config , templates )
3737 self .config = self ._evaluate_vars (self .config , context )
38- self .env = Environment (loader = PackageLoader (self .env_path , 'templates' ),
39- trim_blocks = True )
4038
4139 def _load (self , config ):
4240 """
@@ -113,29 +111,23 @@ def render(self, files=True):
113111 :returns: string with output
114112 """
115113 self .validate ()
116- output = ''
117- # iterate over the available renderers
118- # to build the target configuration
119- # from the configuration dictionary
120- for renderer_class in self .renderers :
121- renderer = renderer_class (self )
122- additional_output = renderer .render ()
123- # add an additional new line
124- # to separate blocks of configuration
125- # generated by different renderers
126- if output and additional_output :
127- output += '\n '
128- # concatenate the render configuration
129- output += additional_output
130-
114+ # convert NetJSON config to intermediate data structure
115+ if self .intermediate_data is None :
116+ self .to_intermediate ()
117+ # render intermediate data structure into native configuration
118+ renderer = self .renderer (self )
119+ output = renderer .render ()
120+ # remove reference to renderer instance (not needed anymore)
121+ del renderer
131122 # are we required to include
132123 # additional files?
133124 if files :
134125 # render additional files
135126 files_output = self ._render_files ()
136127 if files_output :
137- output += files_output .replace ('\n \n \n ' , '\n \n ' ) # max 3 \n
138- # finally return the whole configuration
128+ # max 2 new lines
129+ output += files_output .replace ('\n \n \n ' , '\n \n ' )
130+ # return the configuration
139131 return output
140132
141133 def json (self , validate = True , * args , ** kwargs ):
@@ -235,59 +227,22 @@ def _add_file(self, tar, name, contents, mode=DEFAULT_FILE_MODE):
235227 info .mode = int (mode , 8 ) # permissions converted to decimal notation
236228 tar .addfile (tarinfo = info , fileobj = byte_contents )
237229
238-
239- class BaseRenderer (object ):
240- """
241- Renderers are used to generate specific configuration blocks.
242- """
243- block_name = None
244-
245- def __init__ (self , backend ):
246- self .config = backend .config
247- self .env = backend .env
248- self .backend = backend
249-
250- @classmethod
251- def get_name (cls ):
230+ def to_intermediate (self ):
252231 """
253- Get the name of the rendered without prefix
232+ Converts the NetJSON configuration dictionary (self.config)
233+ to the intermediate data structure (self.intermediate_data) that will
234+ be then used by the renderer class to generate the router configuration
254235 """
255- return str (cls .__name__ ).replace ('Renderer' , '' ).lower ()
256-
257- def cleanup (self , output ):
258- """
259- Performs cleanup of output (indentation, new lines)
260-
261- :param output: string representation of the client configuration
262- """
263- return output
264-
265- def render (self ):
266- """
267- Renders config block with jinja2 templating engine
268- """
269- # get jinja2 template
270- template_name = '{0}.jinja2' .format (self .get_name ())
271- template = self .env .get_template (template_name )
272- # render template and cleanup
273- context = self .get_context ()
274- output = template .render (** context )
275- return self .cleanup (output )
276-
277- def get_context (self ):
278- """
279- Builds context dictionary to be used in jinja2 templates
280- For every method prefixed with `__get`, such as ``_get_name``, creates an entry in
281- the context dictionary whose key is ``name`` and it's value
282- it the output of ``_get_name``
283- """
284- # get list of private methods that start with "_get_"
285- methods = [method for method in dir (self ) if method .startswith ('_get_' )]
286- context = {}
287- # build context
288- for method in methods :
289- key = method .replace ('_get_' , '' )
290- context [key ] = getattr (self , method )()
291- # determine if all context values are empty
292- context ['is_empty' ] = not any (context .values ())
293- return context
236+ self .validate ()
237+ data = OrderedDict ()
238+ for converter_class in self .converters :
239+ # skip unnecessary loop cycles
240+ if not converter_class .should_run (self .config ):
241+ continue
242+ converter = converter_class (self )
243+ for item in converter .to_intermediate ():
244+ key , value = item
245+ if value :
246+ data .setdefault (key , [])
247+ data [key ] += value
248+ self .intermediate_data = data
0 commit comments