1#!/usr/bin/env python
2#
3# Copyright 2010 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18__author__ = 'rafek@google.com (Rafe Kaplan)'
19
20import contextlib
21
22from . import messages
23from . import util
24
25__all__ = ['IndentationError',
26           'IndentWriter',
27          ]
28
29
30class IndentationError(messages.Error):
31  """Raised when end_indent is called too many times."""
32
33
34class IndentWriter(object):
35  """Utility class to make it easy to write formatted indented text.
36
37  IndentWriter delegates to a file-like object and is able to keep track of the
38  level of indentation.  Each call to write_line will write a line terminated
39  by a new line proceeded by a number of spaces indicated by the current level
40  of indentation.
41
42  IndexWriter overloads the << operator to make line writing operations clearer.
43
44  The indent method returns a context manager that can be used by the Python
45  with statement that makes generating python code easier to use.  For example:
46
47    index_writer << 'def factorial(n):'
48    with index_writer.indent():
49      index_writer << 'if n <= 1:'
50      with index_writer.indent():
51        index_writer << 'return 1'
52      index_writer << 'else:'
53      with index_writer.indent():
54        index_writer << 'return factorial(n - 1)'
55
56  This would generate:
57
58  def factorial(n):
59    if n <= 1:
60      return 1
61    else:
62      return factorial(n - 1)
63  """
64
65  @util.positional(2)
66  def __init__(self, output, indent_space=2):
67    """Constructor.
68
69    Args:
70      output: File-like object to wrap.
71      indent_space: Number of spaces each level of indentation will be.
72    """
73    # Private attributes:
74    #
75    #   __output: The wrapped file-like object.
76    #   __indent_space: String to append for each level of indentation.
77    #   __indentation: The current full indentation string.
78    self.__output = output
79    self.__indent_space = indent_space * ' '
80    self.__indentation = 0
81
82  @property
83  def indent_level(self):
84    """Current level of indentation for IndentWriter."""
85    return self.__indentation
86
87  def write_line(self, line):
88    """Write line to wrapped file-like object using correct indentation.
89
90    The line is written with the current level of indentation printed before it
91    and terminated by a new line.
92
93    Args:
94      line: Line to write to wrapped file-like object.
95    """
96    if line != '':
97      self.__output.write(self.__indentation * self.__indent_space)
98      self.__output.write(line)
99    self.__output.write('\n')
100
101  def begin_indent(self):
102    """Begin a level of indentation."""
103    self.__indentation += 1
104
105  def end_indent(self):
106    """Undo the most recent level of indentation.
107
108    Raises:
109      IndentationError when called with no indentation levels.
110    """
111    if not self.__indentation:
112      raise IndentationError('Unable to un-indent further')
113    self.__indentation -= 1
114
115  @contextlib.contextmanager
116  def indent(self):
117    """Create indentation level compatible with the Python 'with' keyword."""
118    self.begin_indent()
119    yield
120    self.end_indent()
121
122  def __lshift__(self, line):
123    """Syntactic sugar for write_line method.
124
125    Args:
126      line: Line to write to wrapped file-like object.
127    """
128    self.write_line(line)
129