diff --git a/app/client/components/GristDoc.ts b/app/client/components/GristDoc.ts index b84e2a81..38b2a6f2 100644 --- a/app/client/components/GristDoc.ts +++ b/app/client/components/GristDoc.ts @@ -12,7 +12,6 @@ import {DocComm, DocUserAction} from 'app/client/components/DocComm'; import * as DocConfigTab from 'app/client/components/DocConfigTab'; import * as GridView from 'app/client/components/GridView'; import {Importer} from 'app/client/components/Importer'; -import * as REPLTab from 'app/client/components/REPLTab'; import {ActionGroupWithCursorPos, UndoStack} from 'app/client/components/UndoStack'; import {ViewLayout} from 'app/client/components/ViewLayout'; import {get as getBrowserGlobals} from 'app/client/lib/browserGlobals'; @@ -80,7 +79,7 @@ export interface TabOptions { category?: any; } -const RightPanelTool = StringUnion("none", "docHistory", "validations", "repl"); +const RightPanelTool = StringUnion("none", "docHistory", "validations"); export interface IExtraTool { icon: IconName; @@ -248,13 +247,6 @@ export class GristDoc extends DisposableWithEvents { this.autoDispose(DocConfigTab.create({gristDoc: this})); - const replTab = this.autoDispose(REPLTab.create(this)); - this.autoDispose(this.addOptionsTab( - 'REPL', dom('span.glyphicon.glyphicon-console'), - replTab.buildConfigDomObj(), - { hideSearchContent: true } - )); - this.rightPanelTool = Computed.create(this, (use) => this._getToolContent(use(this._rightPanelTool))); this.comparison = options.comparison || null; @@ -738,10 +730,6 @@ export class GristDoc extends DisposableWithEvents { const content = this._rightPanelTabs.get("Validate Data"); return content ? {icon: 'Validation', label: 'Validation Rules', content} : null; } - case 'repl': { - const content = this._rightPanelTabs.get("REPL"); - return content ? {icon: 'Repl', label: 'REPL', content} : null; - } case 'none': default: { return null; diff --git a/app/client/components/REPLTab.css b/app/client/components/REPLTab.css deleted file mode 100644 index 0edac241..00000000 --- a/app/client/components/REPLTab.css +++ /dev/null @@ -1,106 +0,0 @@ -.repl-container { - padding: 0 10px; -} - -.repl-text { - cursor: pointer; - display: inline-block; - font-family: monospace; - white-space: pre; - tab-size: 4; - -moz-tab-size: 4; - -o-tab-size: 4; - word-wrap: break-word; -} -.repl-field { - display: inline-block; -} -.repl-error { - color: #D00; -} -.repl-text_line:hover { - background-color: #E5E5E5; -} - -.re-eval_line_button { - cursor: pointer; - float: right; - text-align: center; - width: 15px; - color: #808080; -} -.erase_line_button { - cursor: pointer; - float: right; - text-align: center; - width: 15px; - color: #808080; -} -.re-eval_line_button:hover { - color: #000000; -} -.erase_line_button:hover { - color: #000000; -} - -.unselectable { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.pointer_group { - cursor: pointer; - display: inline-block; - width: 25px; -} -.pointer { - font-family: monospace; - font-weight: bold; -} - -.repl-newline { - font-family: monospace; - font-weight: bold; - padding: 2px 10px 15px 10px; -} - -.repl-cursor_editor { - display: inline; -} -.repl-text_editor { - position: absolute; - padding: 2px 0 0 0; - min-width: 10px; - min-height: 100%; - width: 100%; - height: 100%; - border: none; - resize: none; - box-shadow: none; - outline: none; - background: transparent; - z-index: 10; - overflow: hidden; -} - -.repl-content_measure { - position: absolute; - left: 0; - top: 0; - padding-top: 2px; - padding-right: 1em; - border: none; - visibility: hidden; - overflow: visible; -} - -.formula-text { - font-family: monospace; - tab-size: 4; - -moz-tab-size: 4; - -o-tab-size: 4; -} diff --git a/app/client/components/REPLTab.js b/app/client/components/REPLTab.js deleted file mode 100644 index ead3aff5..00000000 --- a/app/client/components/REPLTab.js +++ /dev/null @@ -1,318 +0,0 @@ -/** - * A Tab that contains a REPL. - * The REPL allows the user to write snippets of python code and see the results of evaluating - * them. In particular, the REPL has access to the usercode module, so they can see the results - * of quick operations on their data. - * The REPL supports evaluation of code, removal of lines from history, and re-computation - * and editing of older lines. - */ -var kd = require('../lib/koDom'); -var ko = require('knockout'); -var dom = require('../lib/dom'); -var Base = require('./Base'); -var commands = require('./commands'); - -var NEW_LINE = -1; - -/** - * Hard tab used instead of soft tabs, as soft-tabs would require a lot of additional - * editor logic (partial-width tabs, backspacing a tab, ...) for - * which we may want to eventually use a 3rd-party library for in addition to syntax highlighting, etc - */ -var INDENT_STR = "\t"; - -function REPLTab(gristDoc) { - Base.call(this, gristDoc); - this.replHist = gristDoc.docModel.replHist.createAllRowsModel("id"); - this.docData = gristDoc.docData; - this.editingIndex = ko.observable(null); - this.histIndex = ko.observable(this.replHist.peekLength); - - this.editorActive = ko.observable(false); - this.numLines = ko.observable(0); - this.row = null; - - this._contentSizer = ko.observable(''); - this._originalValue = ''; - this._textInput = null; - - this.commandGroup = this.autoDispose(commands.createGroup( - REPLTab.replCommands, this, this.editorActive)); -} - -Base.setBaseFor(REPLTab); - -/** - * Editor commands for the cellEditor in the REPL Tab - * TODO: Using the command group, distinguish between "on enter" saves and "on blur" saves - * So that we can give up focus on blur - */ -REPLTab.replCommands = { - // TODO: GridView commands are activated more recently after startup. - fieldEditSave: function() { - if (!this._textInput || !this.editorActive() || - !this._textInput.value.trim() && this.editingIndex() === NEW_LINE) { return; } - // TODO: Scroll pane does not automatically scroll down on save. - var self = this; - this.save() - .then(function(success) { - if (success) { - self.editingIndex(NEW_LINE); - self.clear(); - // Refresh the history index. - self.histIndex(self.replHist.peekLength); - } else { - self.write("\n"); - // Since focus is staying in the current input, increment lines. - self.numLines(self.numLines.peek()+1); - } - }); - }, - fieldEditCancel: function() { - this.clear(); - this.editingIndex(NEW_LINE); - }, - nextField: function() { - // In this case, 'nextField' (Tab) inserts a tab. - this.write(INDENT_STR); - }, - historyPrevious: function() { - // Fills the editor with the code previously entered. - if (this.editingIndex() === NEW_LINE) { this.writePrev(); } - }, - historyNext: function() { - // Fills the editor with the code entered after the current code. - if (this.editingIndex() === NEW_LINE) { this.writeNext(); } - } -}; - - -/** - * Sends the entered code as an EvalCode Useraction. - * @param {Function} callback - Is called with a single argument 'success' indicating - * whether the save was successful. - */ -REPLTab.prototype.save = function(callback) { - if (!this._textInput.value.trim()) { - // If its text is cleared, remove history item. - var currentEditIndex = this.editingIndex(); - this.histIndex(this.replHist.peekLength - 1); - this.editorActive(false); - return this.docData.sendAction(["RemoveRecord", "_grist_REPL_Hist", currentEditIndex]); - } - else { - // If something is entered, save value. - var rowId = this.row ? this.row.id() : null; - return this.docData.sendAction(["EvalCode", this._textInput.value, rowId]); - } -}; - -// Builds object with REPLTab dom builder and settings for the sidepane. -REPLTab.prototype.buildConfigDomObj = function() { - return [{ - 'buildDom': this.buildDom.bind(this), - 'keywords': ['repl', 'console', 'python', 'code', 'terminal'] - }]; -}; - -REPLTab.prototype.buildDom = function() { - var self = this; - return dom('div', - kd.foreach(this.replHist, function(replLine) { - return dom('div.repl-container', - dom('div.repl-text_line', - kd.scope(function() { return self.editingIndex() === replLine.id(); }, - function(isEditing) { - if (isEditing) { - return dom('div.field.repl-field', - kd.scope(self.numLines, function(numLines) { - return self.buildPointerGroup(numLines); - }), - self.attachEditorDom(replLine)); - } else { - var numLines = replLine.code().trim().split('\n').length; - return dom('div.repl-field', - dom.on('click', function() { - // TODO: Flickering occurs on click for multiline code segments. - self.editingIndex(replLine.id()); - self.focus(); - }), - self.buildPointerGroup(numLines), - dom('div.repl-text', - kd.text(replLine.code) - ) - ); - } - } - ), - dom('div.erase_line_button.unselectable', dom.on('click', function() { - self.histIndex(self.replHist.peekLength - 1); - return self.docData.sendAction( - ["RemoveRecord", "_grist_REPL_Hist", replLine.id()] - ); - }), '\u2A09'), - dom('div.re-eval_line_button.unselectable', dom.on('click', function() { - return self.docData.sendAction( - ["EvalCode", replLine.code(), replLine.id()] - ); - }), '\u27f3') // 'refresh' symbol - ), - kd.maybe(replLine.outputText, function() { - return dom('div.repl-text.repl-output', kd.text(replLine.outputText)); - }), - kd.maybe(replLine.errorText, function() { - return dom('div.repl-text.repl-error', kd.text(replLine.errorText)); - }) - ); - }), - // Special bottom editor which sends actions to add new records to the REPL hist. - dom('div.repl-newline', - dom.on('click', function() { - self.editingIndex(NEW_LINE); - self.focus(); - }), - dom('div.field.repl-field', - kd.scope(self.numLines, function(numLines) { - return self.buildPointerGroup(self.editingIndex() === NEW_LINE ? numLines : 1); - }), - kd.maybe(ko.pureComputed(function() { return self.editingIndex() === NEW_LINE; }), - function() { return self.attachEditorDom(null); } - ) - ) - ) - ); -}; - -/** - * Builds the set of pointers to the left of the code - * @param {String} code - The code for which the pointer group is to be built. - */ -REPLTab.prototype.buildPointerGroup = function(numLines) { - var pointers = []; - for (var i = 0; i < numLines; i++) { - pointers.push(dom('div.pointer', i ? '...' : '>>>')); - } - return dom('div.pointer_group.unselectable', pointers); -}; - -REPLTab.prototype.buildEditorDom = function() { - var self = this; - return dom('div.repl-cursor_editor', - dom('div.repl-content_measure.formula-text', kd.text(this._contentSizer)), - function() { - self._textInput = dom('textarea.repl-text_editor.formula-text', - kd.value(self.row ? self.row.code() : ""), - dom.on('focus', function() { - self.numLines(this.value.split('\n').length); - }), - dom.on('blur', function() { - if (!this._textInput || !this.editorActive()) { return; } - self.save() - .then(function(success) { - if (success) { - // If editing a new line, clear it to start fresh. - if (self.editingIndex() === NEW_LINE) { self.clear(); } - // Refresh the history index. - self.histIndex(self.replHist.peekLength); - } else { - self.write("\n"); - } - self.editorActive(false); - }); - }), - //Resizes the textbox whenever user writes in it. - dom.on('input', function() { - self.numLines(this.value.split('\n').length); - self.resizeElem(); - }), - dom.defer(function(elem) { - self.resizeElem(); - elem.focus(); - // Set the cursor at the end. - var elemLen = elem.value.length; - elem.selectionStart = elemLen; - elem.selectionEnd = elemLen; - }), - dom.on('mouseup mousedown click', function(event) { event.stopPropagation(); }), - self.commandGroup.attach() - ); - return self._textInput; - } - ); -}; - -/** -* This function measures a hidden div with the same value as the textarea being edited and then resizes the textarea to match. -*/ -REPLTab.prototype.resizeElem = function() { - // \u200B is a zero-width space; it is used so the textbox will expand vertically - // on newlines, but it does not add any width the string - this._contentSizer(this._textInput.value + '\u200B'); - var rect = this._textInput.parentNode.childNodes[0].getBoundingClientRect(); - //Allows form to expand passed its container div. - this._textInput.style.width = Math.ceil(rect.width) + 'px'; - this._textInput.style.height = Math.ceil(rect.height) + 'px'; -}; - -/** - * Appends text to the contents being edited - */ -REPLTab.prototype.write = function(text) { - this._textInput.value += text; - this.resizeElem(); -}; - -/** - * Clears both the current text and any memory of text in the currently edited cell. - */ -REPLTab.prototype.clear = function() { - this._textInput.value = ""; - this._orignalValue = ""; - this.numLines(1); - this.resizeElem(); -}; - -/** - * Restores focus to the most recent input. - */ -REPLTab.prototype.focus = function() { - if (this._textInput) { - this._textInput.focus(); - this.editorActive(true); - } -}; - -/** - * Writes the code entered before the current code to the input. - */ -REPLTab.prototype.writePrev = function() { - this.histIndex(Math.max(this.histIndex.peek() - 1, 0)); - this.clear(); - if (this.replHist.at(this.histIndex.peek())) { - this.write(this.replHist.at(this.histIndex.peek()).code()); - } -}; - -/** - * Writes the code entered after the current code to the input. - */ -REPLTab.prototype.writeNext = function() { - this.histIndex(Math.min(this.histIndex() + 1, this.replHist.peekLength)); - this.clear(); - if (this.histIndex.peek() < this.replHist.peekLength) { - this.write(this.replHist.at(this.histIndex.peek()).code()); - } -}; - -/** -* This function is called in the DOM element where an editor is desired. -* It attaches to as a child of that element with that elements value as default or whatever is set as an override value. -*/ -REPLTab.prototype.attachEditorDom = function(row) { - var self = this; - self.row = row; - self._originalValue = self.row ? self.row.code() : ""; - return self.buildEditorDom(); -}; - -module.exports = REPLTab; diff --git a/app/client/declarations.d.ts b/app/client/declarations.d.ts index a05a202f..1f3ac164 100644 --- a/app/client/declarations.d.ts +++ b/app/client/declarations.d.ts @@ -6,7 +6,6 @@ declare module "app/client/components/DocConfigTab"; declare module "app/client/components/GridView"; declare module "app/client/components/Layout"; declare module "app/client/components/LayoutEditor"; -declare module "app/client/components/REPLTab"; declare module "app/client/components/commandList"; declare module "app/client/lib/Mousetrap"; declare module "app/client/lib/browserGlobals"; diff --git a/app/client/models/DocModel.ts b/app/client/models/DocModel.ts index c74cb747..bf32b233 100644 --- a/app/client/models/DocModel.ts +++ b/app/client/models/DocModel.ts @@ -28,7 +28,6 @@ import {ACLRuleRec, createACLRuleRec} from 'app/client/models/entities/ACLRuleRe import {ColumnRec, createColumnRec} from 'app/client/models/entities/ColumnRec'; import {createDocInfoRec, DocInfoRec} from 'app/client/models/entities/DocInfoRec'; import {createPageRec, PageRec} from 'app/client/models/entities/PageRec'; -import {createREPLRec, REPLRec} from 'app/client/models/entities/REPLRec'; import {createTabBarRec, TabBarRec} from 'app/client/models/entities/TabBarRec'; import {createTableRec, TableRec} from 'app/client/models/entities/TableRec'; import {createTableViewRec, TableViewRec} from 'app/client/models/entities/TableViewRec'; @@ -42,7 +41,6 @@ import {createViewSectionRec, ViewSectionRec} from 'app/client/models/entities/V export {ColumnRec} from 'app/client/models/entities/ColumnRec'; export {DocInfoRec} from 'app/client/models/entities/DocInfoRec'; export {PageRec} from 'app/client/models/entities/PageRec'; -export {REPLRec} from 'app/client/models/entities/REPLRec'; export {TabBarRec} from 'app/client/models/entities/TabBarRec'; export {TableRec} from 'app/client/models/entities/TableRec'; export {TableViewRec} from 'app/client/models/entities/TableViewRec'; @@ -110,7 +108,6 @@ export class DocModel { public tableViews: MTM = this._metaTableModel("_grist_TableViews", createTableViewRec); public tabBar: MTM = this._metaTableModel("_grist_TabBar", createTabBarRec); public validations: MTM = this._metaTableModel("_grist_Validations", createValidationRec); - public replHist: MTM = this._metaTableModel("_grist_REPL_Hist", createREPLRec); public pages: MTM = this._metaTableModel("_grist_Pages", createPageRec); public rules: MTM = this._metaTableModel("_grist_ACLRules", createACLRuleRec); diff --git a/app/client/models/entities/REPLRec.ts b/app/client/models/entities/REPLRec.ts deleted file mode 100644 index 5aa93cd3..00000000 --- a/app/client/models/entities/REPLRec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {DocModel, IRowModel} from 'app/client/models/DocModel'; - -// Record of input code and output text and error info for REPL. -export type REPLRec = IRowModel<"_grist_REPL_Hist"> - -export function createREPLRec(this: REPLRec, docModel: DocModel): void { - // no extra fields -} diff --git a/app/client/ui/Tools.ts b/app/client/ui/Tools.ts index 9a72ff09..929cd758 100644 --- a/app/client/ui/Tools.ts +++ b/app/client/ui/Tools.ts @@ -54,12 +54,6 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse cssPageLink(cssPageIcon('Validation'), cssLinkText('Validate Data'), testId('validate'), dom.on('click', () => gristDoc.showTool('validations')))) ), - // TODO: polish repl and add it back. - dom.maybe((use) => use(gristDoc.app.features).replTool, () => - cssPageEntry( - cssPageLink(cssPageIcon('Repl'), cssLinkText('REPL'), testId('repl'), - dom.on('click', () => gristDoc.showTool('repl')))) - ), cssPageEntry( cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'code'), cssPageLink(cssPageIcon('Code'), diff --git a/app/common/UserConfig.ts b/app/common/UserConfig.ts index dc1cc76b..fbf4c5fe 100644 --- a/app/common/UserConfig.ts +++ b/app/common/UserConfig.ts @@ -25,6 +25,5 @@ export interface ISupportedFeatures { // Plugin views, REPL, and Validations all need work, but are exposed here to allow existing // tests to continue running. These only affect client-side code. customViewPlugin?: boolean; - replTool?: boolean; validationsTool?: boolean; } diff --git a/sandbox/grist/engine.py b/sandbox/grist/engine.py index 5e67302e..24ec1953 100644 --- a/sandbox/grist/engine.py +++ b/sandbox/grist/engine.py @@ -36,7 +36,6 @@ import table as table_module from user import User # pylint:disable=wrong-import-order import useractions import column -import repl import urllib_patch # noqa imported for side effect # pylint:disable=unused-import log = logger.Logger(__name__, logger.INFO) @@ -232,9 +231,6 @@ class Engine(object): # A flag for when a useraction causes a schema change, to verify consistency afterwards. self._schema_updated = False - # Locals dict for recently executed code in the REPL - self._repl = repl.REPLInterpreter() - # Stores an exception representing the first unevaluated cell met while recomputing the # current cell. self._cell_required_error = None @@ -976,11 +972,6 @@ class Engine(object): for (col_obj, values) in cols} ) - def eval_user_code(self, src): - ret = self._repl.runsource(src) - self.gencode.usercode.__dict__.update(self._repl.locals) - return ret - def invalidate_records(self, table_id, row_ids=depend.ALL_ROWS, col_ids=None, data_cols_to_recompute=frozenset()): """ @@ -1017,8 +1008,6 @@ class Engine(object): def rebuild_usercode(self): """ Compiles the usercode from the schema, and updates all tables and columns to match. - Also, keeps the locals in the repl in sync with the user code, so that the repl has access to - usercode and vice-versa. """ self.gencode.make_module(self.schema) @@ -1040,7 +1029,6 @@ class Engine(object): for table_id, table in six.iteritems(old_tables): if table_id not in self.tables: self._update_table_model(table, None) - self._repl.locals.pop(table_id, None) # Update docmodel with references to the updated metadata tables. self.docmodel.update_tables() @@ -1048,11 +1036,6 @@ class Engine(object): # Set flag to rebuild dependencies of trigger columns after any potential renames, etc. self.trigger_columns_changed() - # The order here is important to make sure that when we update the usercode, - # we don't overwrite with outdated usercode entries - self._repl.locals.update(self.gencode.usercode.__dict__) - self.gencode.usercode.__dict__.update(self._repl.locals) - # Update the context used for autocompletions. self._autocomplete_context = AutocompleteContext(self.gencode.usercode.__dict__) diff --git a/sandbox/grist/repl.py b/sandbox/grist/repl.py deleted file mode 100644 index b9ece68f..00000000 --- a/sandbox/grist/repl.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -This module implements an interpreter for a REPL. It subclasses Python's -code.InteractiveInterpreter class, implementing most of its methods, but with -slight changes in order to be convenient for Grist's purposes -""" - -import code -import sys -from collections import namedtuple - -import six - -SUCCESS = 0 -INCOMPLETE = 1 -ERROR = 2 - -EvalTuple = namedtuple("EvalTuple", ("output", "error", "status")) - -#pylint: disable=exec-used, bare-except -class REPLInterpreter(code.InteractiveInterpreter): - - def __init__(self): - code.InteractiveInterpreter.__init__(self) - self.error_text = "" - - def runsource(self, source, filename="", symbol="single"): - """ - Compiles and executes source. Returns an EvalTuple with a status - INCOMPLETE if the code is incomplete, - ERROR if it encountered a compilation or run-time error, - SUCCESS otherwise. - - an output, which gives all of the output of the user's program - (with stderr piped to stdout, essentially, though mock-file objects are used) - - an error, which reports a syntax error at compilation or a runtime error with a - Traceback. - """ - old_stdout = sys.stdout - old_stderr = sys.stderr - - user_output = six.StringIO() - - self.error_text = "" - try: - code = self.compile(source, filename, symbol) - except (OverflowError, SyntaxError, ValueError): - self.showsyntaxerror(filename) - status = ERROR - else: - status = INCOMPLETE if code is None else SUCCESS - - if status == SUCCESS: - - try: - # We use temproray variables to access stdio/stdout - # to make sure the client can't do funky things - # like get/set attr and have that hurt us - sys.stdout = user_output - sys.stderr = user_output - exec(code, self.locals) - except: - # bare except to catch absolutely all things the user can throw - self.showtraceback() - status = ERROR - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - - program_output = user_output.getvalue() - try: - user_output.close() - except: - pass - - return EvalTuple(program_output, self.error_text, status) - - def write(self, txt): - """ - Used by showsyntaxerror and showtraceback - """ - self.error_text += txt - - def runcode(self, code): - """ - This would normally do the part of runsource after compiling the code, but doesn't quite - make sense as its own function for our purposes because it couldn't support an INCOMPLETE - return value, etc. We explicitly hide it here to make sure the base class's version isn't - called by accident. - """ - raise NotImplementedError("REPLInterpreter.runcode not implemented, use runsource instead") diff --git a/sandbox/grist/testscript.json b/sandbox/grist/testscript.json index df932d1b..a70f6fec 100644 --- a/sandbox/grist/testscript.json +++ b/sandbox/grist/testscript.json @@ -2709,167 +2709,6 @@ }] ] }, { - - //------------------------------------------------------------------------ - "TEST_CASE" : "eval_code", - //------------------------------------------------------------------------ - "BODY": [ - ["LOAD_SAMPLE", "basic"], - - ["APPLY", { - "USER_ACTIONS": [ - // Basic tests - ["EvalCode", "print('cats')", null], - ["EvalCode", "2 * 3 - 1 // 7 + 4", null], - // Exception - ["EvalCode", "raise Exception('everything broke')", null], - // Incomplete structure - ["EvalCode", "for x in range(1, 100):", null], - // Re-evaluation - ["EvalCode", "print('cats')", 1], - ["EvalCode", "print('dogs')", 1], - // Function definition - ["EvalCode", "def foo(x):\n\treturn x * 10\n", null], - ["EvalCode", "foo(10)", null] - ], - "ACTIONS" : { - "stored" : [ - ["AddRecord", "_grist_REPL_Hist", 1, - {"code": "print('cats')", "errorText": "", "outputText": "cats\n"}], - ["AddRecord", "_grist_REPL_Hist", 2, - {"code": "2 * 3 - 1 // 7 + 4", "errorText": "", "outputText": "10\n"}], - ["AddRecord", "_grist_REPL_Hist", 3, - {"code": "raise Exception('everything broke')", "errorText": "Traceback (most recent call last):\n File \"\", line 1, in \nException: everything broke\n", "outputText": ""}], - ["UpdateRecord", "_grist_REPL_Hist", 1, - {"code": "print('cats')", "errorText": "", "outputText": "cats\n"}], - ["UpdateRecord", "_grist_REPL_Hist", 1, - {"code": "print('dogs')", "errorText": "", "outputText": "dogs\n"}], - ["AddRecord", "_grist_REPL_Hist", 4, - {"code": "def foo(x):\n\treturn x * 10\n", "errorText": "", "outputText": ""}], - ["AddRecord", "_grist_REPL_Hist", 5, - {"code": "foo(10)", "errorText": "", "outputText": "100\n"}] - ], - "direct": [true, true, true, true, true, true, true], - "undo" : [ - ["RemoveRecord", "_grist_REPL_Hist", 1], - ["RemoveRecord", "_grist_REPL_Hist", 2], - ["RemoveRecord", "_grist_REPL_Hist", 3], - ["UpdateRecord", "_grist_REPL_Hist", 1, - {"code": "print('cats')", "errorText": "", "outputText": "cats\n"}], - ["UpdateRecord", "_grist_REPL_Hist", 1, - {"code": "print('cats')", "errorText": "", "outputText": "cats\n"}], - ["RemoveRecord", "_grist_REPL_Hist", 4], - ["RemoveRecord", "_grist_REPL_Hist", 5] - ], - "retValue" : [true,true,true,false,true,true,true,true] - } - }], - - ["APPLY", { - "USER_ACTIONS": [ - // Access to usercode before and after re-generation - ["EvalCode", "list(map(str, Students.all.firstName))", null], - ["UpdateRecord", "Students", 1, {"firstName": "2e6"}], - ["ModifyColumn", "Students", "firstName", { "type" : "Numeric" }], - ["EvalCode", "list(Students.all.firstName)", 6], - // Make sure that other tables are still usable as well after a schema change. - ["EvalCode", "len(Schools.all)", 6] - ], - "ACTIONS": { - "stored": [ - ["AddRecord", "_grist_REPL_Hist", 6, - {"code": "list(map(str, Students.all.firstName))", "errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}], - - ["UpdateRecord", "Students", 1, {"firstName": "2e6"}], - ["ModifyColumn", "Students", "firstName", {"type": "Numeric"}], - ["UpdateRecord", "Students", 1, {"firstName": 2e6}], - ["UpdateRecord", "_grist_Tables_column", 1, {"type": "Numeric"}], - - ["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)", - "errorText": "", - "outputText": "[2000000.0, AltText('George W'), AltText('Bill'), AltText('George H'), AltText('Ronald'), AltText('Jimmy'), AltText('Gerald')]\n"}], - ["UpdateRecord", "_grist_REPL_Hist", 6, - {"code": "len(Schools.all)", "errorText": "", "outputText": "6\n"}], - ["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], { - "fullName": [["E", "TypeError"], ["E","TypeError"], ["E","TypeError"], - ["E","TypeError"], ["E","TypeError"], ["E","TypeError"], ["E","TypeError"]] - }], - ["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], { - "fullNameLen": [["E","TypeError"], ["E","TypeError"], ["E","TypeError"], - ["E","TypeError"], ["E","TypeError"], ["E","TypeError"], ["E","TypeError"]] - }] - ], - "direct": [true, true, true, false, true, true, true, false, false], - "undo": [ - ["RemoveRecord", "_grist_REPL_Hist", 6], - - ["UpdateRecord", "Students", 1, {"firstName": "Barack"}], - ["UpdateRecord", "Students", 1, {"firstName": "2e6"}], - ["ModifyColumn", "Students", "firstName", {"type": "Text"}], - ["UpdateRecord", "_grist_Tables_column", 1, {"type": "Text"}], - - ["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(map(str, Students.all.firstName))", - "errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}], - ["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)", - "errorText": "", - "outputText": "[2000000.0, AltText('George W'), AltText('Bill'), AltText('George H'), AltText('Ronald'), AltText('Jimmy'), AltText('Gerald')]\n"}], - ["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], - {"fullName": ["Barack Obama", "George W Bush", "Bill Clinton", "George H Bush", "Ronald Reagan", "Jimmy Carter", "Gerald Ford"]}], - ["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], - {"fullNameLen": [12, 13, 12, 13, 13, 12, 11]}] - ], - "retValue": [true,null,null,true,true] - } - }], - - ["APPLY", { - "USER_ACTIONS": [ - // Syntax Error - ["EvalCode", "not correct c", null], - // Other continuations - ["EvalCode", "map(filter, ", null], - ["EvalCode", "[1,2,3,", null], - // sys.exit - ["EvalCode", "import sys", null], - ["EvalCode", "sys.exit(0)", null], - // User reassignment of sys.stdout/sys.stderr - ["EvalCode", "sys.stdout = None", null], - ["EvalCode", "setattr(sys.stdout, 'getvalue', lambda : 2)", null], - ["EvalCode", "def foo():\n global stdout\n exec('stdout = 2')\n", null], - ["EvalCode", "setattr(sys.stderr, 'close', foo)", null] - ], - "ACTIONS": { - "stored": [ - ["AddRecord", "_grist_REPL_Hist", 7, - {"code": "not correct c", "errorText": " File \"\", line 1\n not correct c\n ^\nSyntaxError: invalid syntax\n", "outputText": ""}], - ["AddRecord", "_grist_REPL_Hist", 8, - {"code": "import sys", "errorText": "", "outputText": ""}], - ["AddRecord", "_grist_REPL_Hist", 9, - {"code": "sys.exit(0)", "errorText": "Traceback (most recent call last):\n File \"\", line 1, in \nSystemExit: 0\n", "outputText": ""}], - ["AddRecord", "_grist_REPL_Hist", 10, - {"code": "sys.stdout = None", "errorText": "", "outputText": ""}], - ["AddRecord", "_grist_REPL_Hist", 11, - {"code": "setattr(sys.stdout, 'getvalue', lambda : 2)", "errorText": "", "outputText": 2}], - ["AddRecord", "_grist_REPL_Hist", 12, - {"code": "def foo():\n global stdout\n exec('stdout = 2')\n", "errorText": "", "outputText": ""}], - ["AddRecord", "_grist_REPL_Hist", 13, - {"code": "setattr(sys.stderr, 'close', foo)", "errorText": "", "outputText": ""}] - ], - "direct": [true, true, true, true, true, true, true], - "undo": [ - ["RemoveRecord", "_grist_REPL_Hist", 7], - ["RemoveRecord", "_grist_REPL_Hist", 8], - ["RemoveRecord", "_grist_REPL_Hist", 9], - ["RemoveRecord", "_grist_REPL_Hist", 10], - ["RemoveRecord", "_grist_REPL_Hist", 11], - ["RemoveRecord", "_grist_REPL_Hist", 12], - ["RemoveRecord", "_grist_REPL_Hist", 13] - ], - "retValue" : [true,false,false,true,true,true,true,true,true ] - } - }] - ] -}, { //------------------------------------------------------------------------ "TEST_CASE" : "reserved_keywords", //------------------------------------------------------------------------ diff --git a/sandbox/grist/useractions.py b/sandbox/grist/useractions.py index ceaefed9..7820bf72 100644 --- a/sandbox/grist/useractions.py +++ b/sandbox/grist/useractions.py @@ -17,7 +17,6 @@ import schema from schema import RecalcWhen import summary import import_actions -import repl import textbuilder import usertypes import treeview @@ -239,37 +238,6 @@ class UserActions(object): """ pass - #-------------------------------------- - # User Actions on usercode - #-------------------------------------- - - @useraction - def EvalCode(self, code, row_id): - """ - Evaluates code in the REPL. - a return value of false indicates that the user's code was incomplete - (and in this case, we do not create any docactions) - otherwise, we either add a new record to the REPL_hist (row_id=null) or Update an existing - record (row_id=num) with the results of evaluating the code. - """ - evaluation = self._engine.eval_user_code(code) - if evaluation.status == repl.INCOMPLETE: - return False - - - hist = self._engine.tables["_grist_REPL_Hist"] - record = { "code" : code, "outputText" : evaluation.output, "errorText" : evaluation.error } - if row_id is None: - # This is a new evaluation, append it to the REPL history - action = actions.AddRecord(hist.table_id, hist.next_row_id(), record) - else: - # This is a re-evaluation, update the old retValue - action = actions.UpdateRecord(hist.table_id, row_id, record) - - self._do_doc_action(action) - - return True - #---------------------------------------- # User actions on records. #----------------------------------------