summaryrefslogtreecommitdiff
path: root/definate/definition_filter.py
blob: 492afc78a99960e99b00bbb85b1a5b6467058907 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/python
#
# Copyright 2012 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module that holds all definition-level filter classes of Definate."""

__author__ = 'msu@google.com (Martin Suess)'


import logging


class Container(object):
  """Container class to hold all information to be passed between filters."""

  def __init__(self, header=None, name='', entries_and_comments=None,
               string_representation=''):
    """Initializer.

    Args:
      header: Optional list of strings to be added as headers.
      name: Optional string representing the name of the definition.
      entries_and_comments: Optional list of tuples (entries, comments) which
        hold all entries for one definition as well as comments.
      string_representation: Optional string holding the string representation
        of the definition (typically used as output e.g. in a file in the end).
    """
    self.header = header if header else []
    self.name = name
    self.entries_and_comments = (
        entries_and_comments if entries_and_comments else [])
    self.string_representation = string_representation


class DefinitionFilter(object):
  """Abstract class defining the interface for the filter chain objects."""

  def Filter(self, container, args):
    """Interface to filter or modify data passed into it.

    Args:
      container: Container object which holds all information for one
        definition. See Container class for details.
      args: Dictionary of arguments depending on the actual filter in use.

    Raises:
      NotImplementedError: In any case since this is not implemented an needs
        to be defined by subclasses.
    """
    raise NotImplementedError(
        'This is an interface only. Implemented by subclasses.')


class SortFilter(DefinitionFilter):
  """DefinitionFilter implementation which sorts all entries for nice output."""

  def Filter(self, container, unused_args):
    """Filter method that sorts all entries in a definition for nice output.

    The filter sorts all entries in ascending order:
    - IPv4 networks
    - IPv6 networks

    Args:
      container: Container object which holds all information for one
        definition. See Container class for details.
      unused_args: No extra arguments required by this filter implementation.

    Returns:
      Container object that has been passed in.
    """
    ipv4_nodes = []
    ipv6_nodes = []

    for node, comment in container.entries_and_comments:
      if node.version == 4:
        ipv4_nodes.append((node, comment))
      elif node.version == 6:
        ipv6_nodes.append((node, comment))
      else:
        logging.warn('Unsupported address version detected: %s', node.version)

    ipv4_nodes = self._RemoveDuplicateNetworks(ipv4_nodes)
    ipv6_nodes = self._RemoveDuplicateNetworks(ipv6_nodes)

    ipv4_nodes.sort()
    ipv6_nodes.sort()

    container.entries_and_comments = ipv4_nodes + ipv6_nodes
    return container

  def _RemoveDuplicateNetworks(self, network_list):
    """Method to remove duplicate networks from the network list.

    Args:
      network_list: List of node/comment tuples where node is an IPNetwork
        object and comment is a string.

    Returns:
      The same list of networks and comments minus duplicate entries.
    """
    result_list = []
    result_dict = {}
    for node, comment in network_list:
      result_dict[str(node)] = (node, comment)
    for node in result_dict:
      result_list.append(result_dict[node])
    return result_list


class AlignFilter(DefinitionFilter):
  """DefinitionFilter implementation which generates nicely aligned output."""

  def Filter(self, container, unused_args):
    """Filter method that aligns the entries in the output nicely.

    This code formats the entries_and_comments by figuring out the
    left-justification from the definition name ('name'), and padding the
    left justification of the comments to 3 spaces after the longest entry
    length.

    In order to do this succinctly, without adding strings together, we use a
    format string that we replace twice.  Once for the (left|right)
    justification bounds, and again with the final values.

    Args:
      container: Container object which holds all information for one
        definition. See Container class for details.
      unused_args: No extra arguments required by this filter implementation.

    Returns:
      Container object that has been passed in.
    """
    first_format_string = '%%s = %%%is# %%s'
    format_string = '%%%is%%%is# %%s'

    max_len = max(len(str(e)) for e, _ in container.entries_and_comments)
    value_justification = -1 * (max_len + 3)
    column_justification = len(container.name) + 3  # 3 for ' = '

    first_format_string %= value_justification
    format_string %= (column_justification, value_justification)

    entry, comment = container.entries_and_comments[0]
    first_string = first_format_string % (container.name, entry, comment)
    output = [first_string]

    for entry, comment in container.entries_and_comments[1:]:
      output.append(format_string % ('', entry, comment))

    container.string_representation = '\n'.join(output)
    return container