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