コード例 #1
0
 def test_replacer(self):
     value = object()
     t1 = textbuilder.Text("To be or not\n  to be?\n", value)
     patches = make_regexp_patches(
         t1.get_text(), re.compile(r'be|to', re.I), lambda m:
         (m.group() + m.group()).upper())
     t2 = textbuilder.Replacer(t1, patches)
     self.assertEqual(t2.get_text(), "TOTO BEBE or not\n  TOTO BEBE?\n")
     self.assertEqual(
         t2.map_back_patch(make_patch(t2.get_text(), 0, 4, "xxx")),
         (t1.get_text(), value, Patch(0, 2, "To", "xxx")))
     self.assertEqual(
         t2.map_back_patch(make_patch(t2.get_text(), 5, 9, "xxx")),
         (t1.get_text(), value, Patch(3, 5, "be", "xxx")))
     self.assertEqual(
         t2.map_back_patch(make_patch(t2.get_text(), 18, 23, "xxx")),
         (t1.get_text(), value, Patch(14, 17, " to", "xxx")))
     # Match the entire second line
     self.assertEqual(
         t2.map_back_patch(make_patch(t2.get_text(), 17, 29, "xxx")),
         (t1.get_text(), value, Patch(13, 21, "  to be?", "xxx")))
コード例 #2
0
 def test_combiner(self):
     valueA, valueB = object(), object()
     t1 = textbuilder.Text("To be or not\n  to be?\n", valueA)
     patches = make_regexp_patches(
         t1.get_text(), re.compile(r'be|to', re.I), lambda m:
         (m.group() + m.group()).upper())
     t2 = textbuilder.Replacer(t1, patches)
     t3 = textbuilder.Text("That is the question", valueB)
     t4 = textbuilder.Combiner(["[", t2, t3, "]"])
     self.assertEqual(
         t4.get_text(),
         "[TOTO BEBE or not\n  TOTO BEBE?\nThat is the question]")
     self.assertEqual(
         t4.map_back_patch(make_patch(t4.get_text(), 1, 5, "xxx")),
         (t1.get_text(), valueA, Patch(0, 2, "To", "xxx")))
     self.assertEqual(
         t4.map_back_patch(make_patch(t4.get_text(), 18, 30, "xxx")),
         (t1.get_text(), valueA, Patch(13, 21, "  to be?", "xxx")))
     self.assertEqual(
         t4.map_back_patch(make_patch(t4.get_text(), 0, 1, "xxx")), None)
     self.assertEqual(
         t4.map_back_patch(make_patch(t4.get_text(), 31, 38, "xxx")),
         (t3.get_text(), valueB, Patch(0, 7, "That is", "xxx")))
コード例 #3
0
def make_formula_body(formula, default_value, assoc_value=None):
  """
  Given a formula, returns a textbuilder.Builder object suitable to be the body of a function,
  with the formula transformed to replace `$foo` with `rec.foo`, and to insert `return` if
  appropriate. Assoc_value is associated with textbuilder.Text() to be returned by map_back_patch.
  """
  if isinstance(formula, six.binary_type):
    formula = formula.decode('utf8')

  if not formula.strip():
    return textbuilder.Text('return ' + repr(default_value), assoc_value)

  formula_builder_text = textbuilder.Text(formula, assoc_value)

  # Start with a temporary builder, since we need to translate "$" before we can parse the code at
  # all (namely, we turn '$foo' into 'DOLLARfoo' first). Once we can parse the code, we'll create
  # a proper set of patches. Note that we initially translate into 'DOLLARfoo' rather than
  # 'rec.foo', so that the translated entity is a single token: this makes for more precisely
  # reported errors if there are any.
  tmp_patches = textbuilder.make_regexp_patches(formula, DOLLAR_REGEX, 'DOLLAR')
  tmp_formula = textbuilder.Replacer(formula_builder_text, tmp_patches)

  # Parse the formula into an abstract syntax tree (AST), catching syntax errors.
  try:
    atok = asttokens.ASTTokens(tmp_formula.get_text(), parse=True)
  except SyntaxError as e:
    return textbuilder.Text(_create_syntax_error_code(tmp_formula, formula, e))

  # Parse formula and generate error code on assignment to rec
  with use_inferences(InferRecAssignment):
    try:
      astroid.parse(tmp_formula.get_text())
    except SyntaxError as e:
      return textbuilder.Text(_create_syntax_error_code(tmp_formula, formula, e))

  # Once we have a tree, go through it and create a subset of the dollar patches that are actually
  # relevant. E.g. this is where we'll skip the "$foo" patches that appear in strings or comments.
  patches = []
  for node in ast.walk(atok.tree):
    if isinstance(node, ast.Name) and node.id.startswith('DOLLAR'):
      input_pos = tmp_formula.map_back_offset(node.first_token.startpos)
      m = DOLLAR_REGEX.match(formula, input_pos)
      # If there is no match, then we must have had a "DOLLARblah" identifier that didn't come
      # from translating a "$" prefix.
      if m:
        patches.append(textbuilder.make_patch(formula, m.start(0), m.end(0), 'rec.'))

    # Wrap arguments to the top-level "IF()" function into lambdas, for lazy evaluation. This is
    # to ensure it's not affected by an exception in the unused value, to match Excel behavior.
    if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
      lazy_args_slice = LAZY_ARG_FUNCTIONS.get(node.func.id)
      if lazy_args_slice:
        for arg in node.args[lazy_args_slice]:
          start, end = map(tmp_formula.map_back_offset, atok.get_text_range(arg))
          patches.append(textbuilder.make_patch(formula, start, start, 'lambda: ('))
          patches.append(textbuilder.make_patch(formula, end, end, ')'))

  # If the last statement is an expression that has its result unused (an ast.Expr node),
  # then insert a "return" keyword.
  last_statement = atok.tree.body[-1] if atok.tree.body else None
  if isinstance(last_statement, ast.Expr):
    input_pos = tmp_formula.map_back_offset(last_statement.first_token.startpos)
    patches.append(textbuilder.make_patch(formula, input_pos, input_pos, "return "))
  elif last_statement is None:
    # If we have an empty body (e.g. just a comment), add a 'pass' at the end.
    patches.append(textbuilder.make_patch(formula, len(formula), len(formula), '\npass'))

  # Apply the new set of patches to the original formula to get the real output.
  final_formula = textbuilder.Replacer(formula_builder_text, patches)

  # Try parsing again before returning it just in case we have new syntax errors. These are
  # possible in cases when a single token ('DOLLARfoo') is valid but an expression ('rec.foo') is
  # not, e.g. `foo($bar=1)` or `def $foo()`.
  try:
    atok = asttokens.ASTTokens(final_formula.get_text(), parse=True)
  except SyntaxError as e:
    return textbuilder.Text(_create_syntax_error_code(final_formula, formula, e))

  # We return the text-builder object whose .get_text() is the final formula.
  return final_formula
コード例 #4
0
def indent(body, levels=1):
  """Indents all lines in body (which should be a textbuilder.Builder), except empty ones."""
  patches = textbuilder.make_regexp_patches(body.get_text(), indent_line_re, indent_str * levels)
  return textbuilder.Replacer(body, patches)