예제 #1
0
  def make_module(self, schema):
    """Regenerates the code text and usercode module from upated document schema."""
    # Collect summary tables to group them by source table.
    summary_tables = {}
    for table_info in schema.itervalues():
      source_table_id = summary.decode_summary_table_name(table_info.tableId)
      if source_table_id:
        summary_tables.setdefault(source_table_id, []).append(table_info)

    fullparts = ["import grist\n" +
                 "from functions import *       # global uppercase functions\n" +
                 "import datetime, math, re     # modules commonly needed in formulas\n"]
    userparts = fullparts[:]
    for table_info in schema.itervalues():
      fullparts.append("\n\n")
      fullparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId)))
      if not _is_special_table(table_info.tableId):
        userparts.append("\n\n")
        userparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId),
          filter_for_user=True))

    # Once all formulas are generated, replace the formula cache with the newly-populated version.
    self._formula_cache = self._new_formula_cache
    self._new_formula_cache = {}
    self._full_builder = textbuilder.Combiner(fullparts)
    self._user_builder = textbuilder.Combiner(userparts)
    self._usercode = exec_module_text(self._full_builder.get_text())
예제 #2
0
  def _make_table_model(self, table_info, summary_tables, filter_for_user=False):
    """
    Returns the code for a table model.
    If filter_for_user is True, includes only user-visible columns.
    """
    table_id = table_info.tableId
    source_table_id = summary.decode_summary_table_name(table_id)

    # Sort columns by "isFormula" to output all data columns before all formula columns.
    columns = sorted(table_info.columns.itervalues(), key=lambda c: c.isFormula)
    if filter_for_user:
      columns = [c for c in columns if is_visible_column(c.colId)]
    parts = ["@grist.UserTable\nclass %s:\n" % table_id]
    if source_table_id:
      parts.append(indent(textbuilder.Text("_summarySourceTable = %r\n" % source_table_id)))

    for col_info in columns:
      parts.append(indent(self._make_field(col_info, table_id)))

    if summary_tables:
      # Include summary formulas, for the user's information.
      formulas = OrderedDict((c.colId, c) for s in summary_tables
                             for c in s.columns.itervalues() if c.isFormula)
      parts.append(indent(textbuilder.Text("\nclass _Summary:\n")))
      for col_info in formulas.itervalues():
        parts.append(indent(self._make_field(col_info, table_id), levels=2))

    return textbuilder.Combiner(parts)
예제 #3
0
    def _make_formula_field(self,
                            col_info,
                            table_id,
                            name=None,
                            include_type=True,
                            additional_params=()):
        """Returns the code for a formula field."""
        # If the caller didn't specify a special name, use the colId
        name = name or col_info.colId

        decl = "def %s(%s):\n" % (name, ', '.join(['rec', 'table'] +
                                                  list(additional_params)))

        # This is where we get to use the formula cache, and save the work of rebuilding formulas.
        key = (table_id, col_info.colId, col_info.formula)
        body = self._formula_cache.get(key)
        if body is None:
            default = get_type_default(col_info.type)
            body = codebuilder.make_formula_body(col_info.formula, default,
                                                 (table_id, col_info.colId))
        self._new_formula_cache[key] = body

        decorator = ''
        if include_type and col_info.type != 'Any':
            decorator = '@grist.formulaType(%s)\n' % get_grist_type(
                col_info.type)
        return textbuilder.Combiner(
            ['\n' + decorator + decl,
             indent(body), '\n'])
예제 #4
0
 def _make_data_field(self, col_info, table_id):
   """Returns the code for a data field."""
   parts = []
   if col_info.formula:
     parts.append(self._make_formula_field(col_info, table_id,
                                           name=table.get_default_func_name(col_info.colId),
                                           include_type=False))
   parts.append("%s = %s\n" % (col_info.colId, get_grist_type(col_info.type)))
   return textbuilder.Combiner(parts)
예제 #5
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")))