PageRenderTime 27ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/dumbhippo/branches/production/super/lib/super/expander.py

https://gitlab.com/manoj-makkuboy/magnetism
Python | 204 lines | 146 code | 22 blank | 36 comment | 18 complexity | abf0c0e9e3b7a396594145a041b8262d MD5 | raw file
  1. # Copyright 2005, Red Hat, Inc.
  2. import re
  3. import sys
  4. class Expander:
  5. """A class that performs file expansion doing substition
  6. of parameters (as @@paramname@@) and handling of special
  7. control statemts (@@if/@@elif/@@else/@@endif blocks
  8. and @@error). The way the class works is that it is
  9. an iterator that filters another iterator. Typically
  10. the source iterator would be a file object."""
  11. def __init__(self, scope, source, display_path):
  12. """Create a new Expander object
  13. scope -- the object for doing param lookups (Config or Service)
  14. source -- the source iterator, typically a file
  15. display_path -- how to display the source in error messages
  16. """
  17. self.display_path = display_path
  18. self.source = source
  19. self.scope = scope
  20. self.done = False
  21. self._expand_line = self._compile_expand_line(scope)
  22. self.line = 0
  23. # We keep track of active conditionals with a stack;
  24. # elements of the stack are tuples of the form
  25. #
  26. # (doing_output, have_seen_true, have_seen_else)
  27. #
  28. # doing_output - if we are in a branch where we should output
  29. # have_seen_true - if we've already seen a true @@if or @@elif
  30. # have_seen_else - if we've already seen @@else
  31. #
  32. self.cond_stack = []
  33. def next(self):
  34. """Get the next line as per the standard iteration
  35. protocol. Raises StopIteration if there are no more
  36. lines"""
  37. if self.done:
  38. raise StopIteration
  39. while True: # Iterate until we return a line to the caller
  40. self.line = self.line + 1
  41. try:
  42. line = self.source.next()
  43. except StopIteration:
  44. self.done = True
  45. if (self.cond_stack):
  46. # Change self.line back to the line number of the
  47. # start of the @@if
  48. self.line = self.cond_stack[-1][3]
  49. self._die("@@if is not closed by @@endif")
  50. raise StopIteration
  51. if (line[0:2] == "@@"):
  52. self._handle_condition(line)
  53. continue
  54. if self._doing_output():
  55. return self._expand_line(line)
  56. def __iter__(self):
  57. """Return the object itself, as per the standard iteration
  58. protocol"""
  59. return self
  60. def _compile_expand_line(self, scope):
  61. """Return a function that does parameter expansion on
  62. a line. We do things this way so that we can compile
  63. the regular expression only once per file and still
  64. encapsulate the substitution"""
  65. subst = re.compile("@@((?:[a-zA-Z_][a-zA-Z_0-9]*\\.)?[a-zA-Z_][a-zA-Z0-9_]*)(?::(properties|xml))?@@")
  66. xmlMatch = re.compile("[<&'\"]")
  67. def xmlEscape(m):
  68. c = m.group(0)
  69. if (c == '<'):
  70. return '&lt;'
  71. elif (c == '&'):
  72. return '&amp;'
  73. elif (c == "'"):
  74. return '&apos;'
  75. elif (c == '"'):
  76. return '&quot;'
  77. propertiesMatch = re.compile(r"[^ -~]")
  78. def propertiesEscape(m):
  79. c = m.group(0)
  80. if (c == '\r'):
  81. return r"\r"
  82. elif (c == '\n'):
  83. return r"\n"
  84. elif (c == '\t'):
  85. return r"\t"
  86. else:
  87. return r"\u%04x" % ord(c)
  88. def repl(m):
  89. result = scope.expand_parameter(m.group(1))
  90. if m.group(2) == "properties":
  91. result = propertiesMatch.sub(propertiesEscape, result)
  92. elif m.group(2) == "xml":
  93. result = xmlMatch.sub(xmlEscape, result)
  94. return result
  95. def expand_line(line):
  96. return subst.sub(repl, line)
  97. return expand_line
  98. def _die(self, message):
  99. """ Print file/line number information with a message and exit """
  100. print >>sys.stderr, "%s:%d: %s" % (self.display_path, self.line, message)
  101. sys.exit(1)
  102. def _doing_output(self):
  103. """Return True if we are currently writing output"""
  104. return not self.cond_stack or self.cond_stack[0][0]
  105. def _true_condition(self, str):
  106. """Check to see if the argument of @@if or @@elif is true.
  107. Exits with an error message if the argument isn't valid"""
  108. str = str.strip();
  109. # Support literal 'yes' and 'no' to let people
  110. # conditionalize stuff in and out in #if 0 style
  111. if str == "yes":
  112. return True
  113. elif str == "no":
  114. return False
  115. try:
  116. return self.scope.is_true_parameter(str)
  117. except KeyError:
  118. self._die("Parameter name '%s' not recognized" % str)
  119. except ValueError, e:
  120. self._die(e)
  121. def _handle_condition(self, line):
  122. """Handle a line that is a super directive like @@if.
  123. Exits with an error message if the line is unrecognized"""
  124. m = re.match("@@\s*if\s+(.*)", line)
  125. if m:
  126. is_true = self._true_condition(m.group(1))
  127. self.cond_stack.append((is_true, is_true, False, self.line))
  128. return
  129. m = re.match("@@\s*elif\s+(.*)", line)
  130. if m:
  131. if not self.cond_stack:
  132. self._die("@@elif without @@if")
  133. old = self.cond_stack[-1]
  134. if (old[2]): # Already seen @else
  135. self._die("@@elif after else")
  136. if (old[1]): # Already seen something true
  137. self.cond_stack[-1] = (False, True, False, old[3])
  138. return
  139. is_true = self._true_condition(m.group(1))
  140. self.cond_stack[-1] = (is_true, is_true, False, old[3])
  141. return
  142. m = re.match("@@\s*else\s+(.*)", line)
  143. if m:
  144. if not self.cond_stack:
  145. self._die("@@else without @@if")
  146. old = self.cond_stack[-1]
  147. if (old[2]): # Already seen @else
  148. self._die("@@else after @@else")
  149. if (old[1]): # Already seen something true
  150. self.cond_stack[-1] = (False, True, True, old[3])
  151. return
  152. self.cond_stack[-1] = (True, True, True, old[3])
  153. return
  154. m = re.match("@@\s*endif\s+(.*)", line)
  155. if m:
  156. if not self.cond_stack:
  157. self._die("@@endif without @@if")
  158. self.cond_stack.pop()
  159. return
  160. m = re.match("@@\s*error\s+(.*)", line)
  161. if m:
  162. if self._doing_output():
  163. # Support optional quoting
  164. str = m.group(1).strip()
  165. str = re.sub('^"(.*)"$','\\1', str)
  166. self._die(str)
  167. return
  168. m = re.match("(@@\s*\S+).*", line)
  169. self._die("Unrecognized super command: %s" % m.group(1))