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())
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)
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'])
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)
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")))