Source code for sagenb.notebook.worksheet

# -*- coding: utf-8 -*-
r"""
A Worksheet

A worksheet is embedded in a web page that is served by the Sage
server. It is a linearly-ordered collections of numbered cells,
where a cell is a single input/output block.

The worksheet module is responsible for running calculations in a
worksheet, spawning Sage processes that do all of the actual work
and are controlled via pexpect, and reporting on results of
calculations. The state of the cells in a worksheet is stored on
the file system (not in the notebook pickle sobj).

AUTHORS:

- William Stein
"""
from __future__ import absolute_import

###########################################################################
#       Copyright (C) 2006-2009 William Stein <wstein@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#                  http://www.gnu.org/licenses/
###########################################################################
from six import iteritems

# Import standard Python libraries that we will use below
import base64
import bz2
import calendar
import copy
import os
import re
import shutil
import string
import time
import traceback
import locale

# General sage library code
from sagenb.misc.misc import (cython, load, save, verbose, DOT_SAGENB,
                              walltime, ignore_nonexistent_files,
                              set_restrictive_permissions,
                              set_permissive_permissions,
                              encoded_str, unicode_str)

from sagenb.misc.remote_file import get_remote_file

from sagenb.interfaces import (WorksheetProcess_ExpectImplementation,
                               WorksheetProcess_ReferenceImplementation,
                               WorksheetProcess_RemoteExpectImplementation)

from sagenb.misc.format import relocate_future_imports

# Imports specifically relevant to the sage notebook
from .cell import Cell, TextCell
from .template import template, clean_name, prettify_time_ago
from flask_babel import gettext, lazy_gettext
_ = gettext

# Set some constants that will be used for regular expressions below.
whitespace = re.compile(r'\s')  # Match any whitespace character
non_whitespace = re.compile(r'\S')

# The file to which the Sage code that will be evaluated is written.
CODE_PY = "___code___.py"

# Constants that control the behavior of the worksheet.
INTERRUPT_TRIES = 3    # number of times to send control-c to
                       # subprocess before giving up
INITIAL_NUM_CELLS = 1  # number of empty cells in new worksheets
WARN_THRESHOLD = 100   # The number of seconds, so if there was no
                       # activity on this worksheet for this many
                       # seconds, then editing is considered safe.
                       # Used when multiple people are editing the
                       # same worksheet.

# The strings used to synchronized the compute subprocesses.
# WARNING:  If you make any changes to this, be sure to change the
# error line below that looks like this:
#         cmd += 'print("\\x01r\\x01e%s")' % self.synchro()
SC         = '\x01'
SAGE_BEGIN = SC + 'b'
SAGE_END   = SC + 'e'
SAGE_ERROR = SC + 'r'

# Integers that define which folder this worksheet is in relative to a
# given user.
ARCHIVED = 0
ACTIVE   = 1
TRASH    = 2


all_worksheet_processes = []
[docs]def update_worksheets(): """ Iterate through and "update" all the worksheets. This is needed for things like wall timeouts. """ for S in all_worksheet_processes: S.update()
[docs]def worksheet_filename(name, owner): """ Return the relative directory name of this worksheet with given name and owner. INPUT: - ``name`` - string, which may have spaces and funny characters, which are replaced by underscores. - ``owner`` - string, with no spaces or funny characters OUTPUT: string EXAMPLES:: sage: sagenb.notebook.worksheet.worksheet_filename('Example worksheet 3', 'sage10') 'sage10/Example_worksheet_3' sage: sagenb.notebook.worksheet.worksheet_filename('Example#%&! work\\sheet 3', 'sage10') 'sage10/Example_____work_sheet_3' """ return os.path.join(owner, clean_name(name))
[docs]def Worksheet_from_basic(obj, notebook_worksheet_directory): """ INPUT: - ``obj`` -- a dictionary (a basic Python objet) from which a worksheet can be reconstructed. - ``notebook_worksheet_directory`` - string; the directory in which the notebook object that contains this worksheet stores worksheets, i.e., nb.worksheet_directory(). OUTPUT: - a worksheet EXAMPLES:: sage: import sagenb.notebook.worksheet sage: W = sagenb.notebook.worksheet.Worksheet('test', 0, tmp_dir(), system='gap', owner='sageuser', pretty_print=True, auto_publish=True) sage: _=W.new_cell_after(0); B = W.basic() sage: W0 = sagenb.notebook.worksheet.Worksheet_from_basic(B, tmp_dir()) sage: W0.basic() == B True """ W = Worksheet() W.reconstruct_from_basic(obj, notebook_worksheet_directory) return W
[docs]class Worksheet(object): def __init__(self, name=None, id_number=None, notebook_worksheet_directory=None, system=None, owner=None, pretty_print=False, auto_publish=False, create_directories=True, live_3D=False): r""" Create and initialize a new worksheet. INPUT: - ``name`` - string; the name of this worksheet - ``id_number`` - Integer; name of the directory in which the worksheet's data is stored - ``notebook_worksheet_directory`` - string; the directory in which the notebook object that contains this worksheet stores worksheets, i.e., nb.worksheet_directory(). - ``system`` - string; 'sage', 'gp', 'singular', etc. - the math software system in which all code is evaluated by default - ``owner`` - string; username of the owner of this worksheet - ``pretty_print`` - bool (default: False); whether all output is pretty printed by default. - ``create_directories`` -- bool (default: True): if True, creates various files and directories where data will be stored. This option is here only for the migrate_old_notebook method in notebook.py - ``live_3D`` - bool (default: False); whether 3-D plots should be loaded live (interactive). Too many live plots may make a worksheet unresponsive because of the javascript load. EXAMPLES: We test the constructor via an indirect doctest:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: import sagenb.notebook.misc sage: sagenb.notebook.misc.notebook = nb sage: W = nb.create_new_worksheet('Test with unicode ěščřžýáíéďĎ', 'admin') sage: W admin/0: [Cell 1: in=, out=] """ if name is None: # A fresh worksheet self.clear() return # Record the basic properties of the worksheet self.__system = system self.__pretty_print = pretty_print self.__owner = owner self.__viewers = [] self.__collaborators = [] self.__autopublish = auto_publish self.__saved_by_info = {} self.__live_3D = live_3D # state sequence number, used for sync self.__state_number = 0 # Initialize the cell id counter. self.__next_id = 0 self.set_name(name) # set the directory in which the worksheet files will be stored. # We also add the hash of the name, since the cleaned name loses info, e.g., # it could be all _'s if all characters are funny. self.__id_number = int(id_number) filename = os.path.join(owner, str(id_number)) self.__filename = filename self.__dir = os.path.join(notebook_worksheet_directory, str(id_number)) if create_directories: self.create_directories() self.clear()
[docs] def increase_state_number(self): if self.is_published() or self.docbrowser(): return try: self.__state_number += 1 except AttributeError: self.__state_number = 0
[docs] def state_number(self): if self.is_published() or self.docbrowser(): return 0 try: return self.__state_number except AttributeError: self.__state_number = 0 return 0
[docs] def create_directories(self): # creating directories should be a function of the storage backend, not here if not os.path.exists(self.__dir): os.makedirs(self.__dir) set_restrictive_permissions(self.__dir, allow_execute=True) set_restrictive_permissions(self.snapshot_directory()) set_restrictive_permissions(self.cells_directory())
[docs] def id_number(self): """ Return the id number of this worksheet, which is an integer. EXAMPLES:: sage: from sagenb.notebook.worksheet import Worksheet sage: W = Worksheet('test', 2, tmp_dir(), owner='sageuser') sage: W.id_number() 2 sage: type(W.id_number()) <type 'int'> """ try: return self.__id_number except AttributeError: self.__id_number = int(os.path.split(self.__filename)[1]) return self.__id_number
[docs] def basic(self): """ Output a dictionary of basic Python objects that defines the configuration of this worksheet, except the actual cells and the data files in the DATA directory and images and other data in the individual cell directories. EXAMPLES:: sage: import sagenb.notebook.worksheet sage: W = sagenb.notebook.worksheet.Worksheet('test', 0, tmp_dir(), owner='sage') sage: sorted((W.basic().items())) [('auto_publish', False), ('collaborators', []), ('id_number', 0), ('last_change', ('sage', ...)), ('live_3D', False), ('name', u'test'), ('owner', 'sage'), ('pretty_print', False), ('published_id_number', None), ('ratings', []), ('saved_by_info', {}), ('system', None), ('tags', {'sage': [1]}), ('viewers', []), ('worksheet_that_was_published', ('sage', 0))] """ try: published_id_number = int(os.path.split(self.__published_version)[1]) except AttributeError: published_id_number = None try: ws_pub = self.__worksheet_came_from except AttributeError: ws_pub = (self.owner(), self.id_number()) try: saved_by_info = self.__saved_by_info except AttributeError: saved_by_info = {} d = {############# # basic identification 'name': unicode(self.name()), 'id_number': int(self.id_number()), ############# # default type of computation system that evaluates cells 'system': self.system(), ############# # permission: who can look at the worksheet 'owner': self.owner(), 'viewers': self.viewers(), 'collaborators': self.collaborators(), ############# # publishing worksheets (am I published?); auto-publish me? # If this worksheet is published, then the published_id_number # is the id of the published version of this worksheet. Otherwise, # it is None. 'published_id_number': published_id_number, # If this is a published worksheet, then ws_pub # is a 2-tuple ('username', id_number) of a non-published # worksheet. Otherwise ws_pub is None. 'worksheet_that_was_published': ws_pub, # Whether or not this worksheet should automatically be # republished when changed. 'auto_publish': self.is_auto_publish(), # Appearance: e.g., whether to pretty print this # worksheet by default 'pretty_print': self.pretty_print(), # Whether to load 3-D live. Should only be set to # true by the user as worksheets with more than 1 or 2 # live (interactive) 3-D plots may bog down because of # javascript overload. 'live_3D': self.live_3D(), # what other users think of this worksheet: list of # triples # (username, rating, comment) 'ratings': self.ratings(), #??? 'saved_by_info':saved_by_info, # dictionary mapping usernames to list of tags that # reflect what the tages are for that user. A tag can be # an integer: # 0,1,2 (=ARCHIVED,ACTIVE,TRASH), # or a string (not yet supported). # This is used for now to fill in the __user_views. 'tags': self.tags(), # information about when this worksheet was last changed, # and by whom: # last_change = ('username', time.time()) 'last_change': self.last_change(), } return d
[docs] def reconstruct_from_basic(self, obj, notebook_worksheet_directory=None): """ Reconstruct as much of the worksheet's configuration as possible from the properties that happen to be set in the basic dictionary obj. INPUT: - ``obj`` -- a dictionary of basic Python objects - ``notebook_worksheet_directory`` -- must be given if ``id_number`` is a key of obj; otherwise not. EXAMPLES:: sage: import sagenb.notebook.worksheet sage: W = sagenb.notebook.worksheet.Worksheet('test', 0, tmp_dir(), system='gap', owner='sageuser', pretty_print=True, auto_publish=True) sage: W.new_cell_after(0) Cell 1: in=, out= sage: b = W.basic() sage: W0 = sagenb.notebook.worksheet.Worksheet() sage: W0.reconstruct_from_basic(b, tmp_dir()) sage: W0.basic() == W.basic() True """ try: del self.__cells except AttributeError: pass for key, value in iteritems(obj): if key == 'name': if repr(value) == '<_LazyString broken>': value = '' self.set_name(value) elif key == 'id_number': self.__id_number = value if 'owner' in obj: owner = obj['owner'] self.__owner = owner filename = os.path.join(owner, str(value)) self.__filename = filename self.__dir = os.path.join(notebook_worksheet_directory, str(value)) elif key in ['system', 'owner', 'viewers', 'collaborators', 'pretty_print', 'ratings', 'live_3D']: # ugly setattr(self, '_Worksheet__' + key, value) elif key == 'auto_publish': self.__autopublish = value elif key == 'tags': self.set_tags(value) elif key == 'last_change': self.set_last_change(value[0], value[1]) elif key == 'published_id_number' and value is not None: self.set_published_version('pub/%s' % value) elif key == 'worksheet_that_was_published': self.set_worksheet_that_was_published(value) self.create_directories()
def __eq__(self, other): """ We compare two worksheets. INPUT: - ``self, other`` -- worksheets OUTPUT: - boolean - comparison is on the underlying file names. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W2 = nb.create_new_worksheet('test2', 'admin') sage: W1 = nb.create_new_worksheet('test1', 'admin') sage: W1 == W1 True sage: W2 == W1 False """ return self.filename() == other.filename() def __ne__(self, other): """ We compare two worksheets. INPUT: - ``self, other`` -- worksheets OUTPUT: - boolean - comparison is on the underlying file names. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W2 = nb.create_new_worksheet('test2', 'admin') sage: W1 = nb.create_new_worksheet('test1', 'admin') sage: W1 != W1 False sage: W2 != W1 True """ return self.filename() != other.filename() def __lt__(self, other): """ We compare two worksheets. INPUT: - ``self, other`` -- worksheets OUTPUT: - boolean - comparison is on the underlying file names. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W2 = nb.create_new_worksheet('test2', 'admin') sage: W1 = nb.create_new_worksheet('test1', 'admin') sage: W1 < W2 False sage: W2 < W1 True """ return self.filename() < other.filename() def __gt__(self, other): """ We compare two worksheets. INPUT: - ``self, other`` -- worksheets OUTPUT: - boolean - comparison is on the underlying file names. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W2 = nb.create_new_worksheet('test2', 'admin') sage: W1 = nb.create_new_worksheet('test1', 'admin') sage: W1 > W2 True sage: W2 > W1 False """ return self.filename() > other.filename() def __le__(self, other): """ We compare two worksheets. INPUT: - ``self, other`` -- worksheets OUTPUT: - boolean - comparison is on the underlying file names. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W2 = nb.create_new_worksheet('test2', 'admin') sage: W1 = nb.create_new_worksheet('test1', 'admin') sage: W1 <= W2 False sage: W2 <= W1 True """ return self.filename() <= other.filename() def __ge__(self, other): """ We compare two worksheets. INPUT: - ``self, other`` -- worksheets OUTPUT: - boolean - comparison is on the underlying file names. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W2 = nb.create_new_worksheet('test2', 'admin') sage: W1 = nb.create_new_worksheet('test1', 'admin') sage: W1 >= W2 True sage: W2 >= W1 False """ return self.filename() >= other.filename() def __repr__(self): r""" Return string representation of this worksheet, which is simply the string representation of the underlying list of cells. OUTPUT: string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('test1', 'admin') sage: W.__repr__() 'admin/0: [Cell 1: in=, out=]' sage: W.edit_save('{{{\n2+3\n///\n5\n}}}\n{{{id=10|\n2+8\n///\n10\n}}}') sage: W.__repr__() 'admin/0: [Cell 0: in=2+3, out=\n5, Cell 10: in=2+8, out=\n10]' """ return '%s/%s: %s' % (self.owner(), self.id_number(), self.cell_list()) def __len__(self): r""" Return the number of cells in this worksheet. OUTPUT: int EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('test1', 'admin') sage: len(W) 1 sage: W.edit_save('{{{\n2+3\n///\n5\n}}}\n{{{id=10|\n2+8\n///\n10\n}}}') sage: len(W) 2 """ return len(self.cell_list())
[docs] def worksheet_html_filename(self): """ Return path to the underlying plane text file that defines the worksheet. """ return os.path.join(self.__dir, 'worksheet.html')
[docs] def download_name(self): """ Return the download name of this worksheet. """ return os.path.split(self.name())[-1]
[docs] def docbrowser(self): """ Return True if this is a docbrowser worksheet. OUTPUT: bool EXAMPLES: We first create a standard worksheet for which docbrowser is of course False:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('test1', 'admin') sage: W.docbrowser() False We create a worksheet for which docbrowser is True:: sage: W = nb.create_new_worksheet('doc_browser_0', '_sage_') sage: W.docbrowser() True """ return self.owner() == '_sage_'
########################################################## # Basic properties ##########################################################
[docs] def collaborators(self): """ Return a (reference to the) list of the collaborators who can also view and modify this worksheet. OUTPUT: list EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('test1', 'admin') sage: C = W.collaborators(); C [] sage: C.append('sage') sage: W.collaborators() ['sage'] """ try: return self.__collaborators except AttributeError: self.__collaborators = [] return self.__collaborators
[docs] def collaborator_names(self, max=None): """ Returns a string of the non-owner collaborators on this worksheet. INPUT: - ``max`` - an integer. If this is specified, then only max number of collaborators are shown. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('test1', 'admin') sage: C = W.collaborators(); C [] sage: C.append('sage') sage: C.append('wstein') sage: W.collaborator_names() 'sage, wstein' sage: W.collaborator_names(max=1) 'sage, ...' """ collaborators = [x for x in self.collaborators() if x != self.owner()] if max is not None and len(collaborators) > max: collaborators = collaborators[:max] + ['...'] return ", ".join(collaborators)
[docs] def set_collaborators(self, v): """ Set the list of collaborators to those listed in the list v of strings. INPUT: - ``v`` - a list of strings EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: nb.user_manager().add_user('hilbert','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('test1', 'admin') sage: W.set_collaborators(['sage', 'admin', 'hilbert', 'sage']) Note that repeats are not added multiple times and admin - the owner - isn't added:: sage: W.collaborators() ['hilbert', 'sage'] """ users = self.notebook().user_manager().users() owner = self.owner() collaborators = set([u for u in v if u in users and u != owner]) self.__collaborators = sorted(collaborators)
[docs] def viewers(self): """ Return list of viewers of this worksheet. OUTPUT: - ``list`` - of string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: nb.user_manager().add_user('hilbert','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('test1', 'admin') sage: W.add_viewer('hilbert') sage: W.viewers() ['hilbert'] sage: W.add_viewer('sage') sage: W.viewers() ['hilbert', 'sage'] """ try: return self.__viewers except AttributeError: self.__viewers = [] return self.__viewers
[docs] def viewer_names(self, max=None): """ Returns a string of the non-owner viewers on this worksheet. INPUT: - ``max`` - an integer. If this is specified, then only max number of viewers are shown. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('test1', 'admin') sage: C = W.viewers(); C [] sage: C.append('sage') sage: C.append('wstein') sage: W.viewer_names() 'sage, wstein' sage: W.viewer_names(max=1) 'sage, ...' """ viewers = [x for x in self.viewers() if x != self.owner()] if max is not None and len(viewers) > max: viewers = viewers[:max] + ['...'] return ", ".join(viewers)
[docs] def delete_notebook_specific_data(self): """ Delete data from this worksheet this is specific to a certain notebook. This means deleting the attached files, collaborators, and viewers. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: nb.user_manager().add_user('hilbert','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('test1', 'admin') sage: W.add_viewer('hilbert') sage: W.delete_notebook_specific_data() sage: W.viewers() [] sage: W.add_collaborator('hilbert') sage: W.collaborators() ['admin', 'hilbert'] sage: W.delete_notebook_specific_data() sage: W.collaborators() ['admin'] """ self.__attached = {} self.__collaborators = [self.owner()] self.__viewers = []
[docs] def name(self, username=None): r""" Return the name of this worksheet. OUTPUT: string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.name() u'A Test Worksheet' sage: W = nb.create_new_worksheet('ěščřžýáíéďĎ', 'admin') sage: W.name() u'\u011b\u0161\u010d\u0159\u017e\xfd\xe1\xed\xe9\u010f\u010e' """ try: return self.__name except AttributeError: self.__name = gettext("Untitled") return self.__name
[docs] def set_name(self, name): """ Set the name of this worksheet. INPUT: - ``name`` - string EXAMPLES: We create a worksheet and change the name:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.set_name('A renamed worksheet') sage: W.name() u'A renamed worksheet' """ if len(name.strip()) == 0: name = gettext('Untitled') name = unicode_str(name) self.__name = name
[docs] def set_filename_without_owner(self, nm): r""" Set this worksheet filename (actually directory) by getting the owner from the pre-stored owner via ``self.owner()``. INPUT: - ``nm`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.filename() 'admin/0' sage: W.set_filename_without_owner('5') sage: W.filename() 'admin/5' """ filename = os.path.join(self.owner(), nm) self.set_filename(filename)
[docs] def set_filename(self, filename): """ Set the worksheet filename (actually directory). INPUT: - ``filename`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.filename() 'admin/0' sage: W.set_filename('admin/10') sage: W.filename() 'admin/10' """ old_filename = self.__filename self.__filename = filename self.__dir = os.path.join(self.notebook()._dir, filename) self.notebook().change_worksheet_key(old_filename, filename)
[docs] def filename(self): """ Return the filename (really directory) where the files associated to this worksheet are stored. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.filename() 'admin/0' sage: os.path.isdir(os.path.join(nb._dir, 'home', W.filename())) True """ return self.__filename
[docs] def filename_without_owner(self): """ Return the part of the worksheet filename after the last /, i.e., without any information about the owner of this worksheet. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.filename_without_owner() '0' sage: W.filename() 'admin/0' """ return os.path.split(self.__filename)[-1]
[docs] def directory(self): """ Return the full path to the directory where this worksheet is stored. OUTPUT: string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.directory() '.../home/admin/0' """ return self.__dir
[docs] def data_directory(self): """ Return path to directory where worksheet data is stored. OUTPUT: string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.data_directory() '.../home/admin/0/data' """ d = os.path.join(self.directory(), 'data') if not os.path.exists(d): os.makedirs(d) return d
[docs] def attached_data_files(self): """ Return a list of the file names of files in the worksheet data directory. OUTPUT: list of strings EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.attached_data_files() [] sage: open('%s/foo.data'%W.data_directory(),'w').close() sage: W.attached_data_files() ['foo.data'] """ D = self.data_directory() if not os.path.exists(D): return [] return os.listdir(D)
[docs] def cells_directory(self): """ Return the directory in which the cells of this worksheet are evaluated. OUTPUT: string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.cells_directory() '.../home/admin/0/cells' """ path = os.path.join(self.directory(), 'cells') if not os.path.exists(path): os.makedirs(path) return path
[docs] def notebook(self): """ Return the notebook that contains this worksheet. OUTPUT: a Notebook object. .. note:: This really returns the Notebook object that is set as a global variable of the misc module. This is done *even* in the Flask version of the notebook as it is set in func:`sagenb.notebook.notebook.load_notebook`. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.notebook() <...sagenb.notebook.notebook.Notebook...> sage: W.notebook() is sagenb.notebook.misc.notebook True """ if not hasattr(self, '_notebook'): from . import misc self._notebook = misc.notebook return self._notebook
[docs] def save(self, conf_only=False): self.notebook().save_worksheet(self, conf_only=conf_only)
[docs] def system(self): """ Return the math software system in which by default all input to the notebook is evaluated. OUTPUT: string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.system() 'sage' sage: W.set_system('mathematica') sage: W.system() 'mathematica' """ try: return self.__system except AttributeError: self.__system = 'sage' return 'sage'
[docs] def system_index(self): """ Return the index of the current system into the Notebook's list of systems. If the current system isn't in the list, then change to the default system. This can happen if, e.g., the list changes, e.g., when changing from a notebook with Sage installed to running a server from the same directory without Sage installed. We might as well support this. OUTPUT: integer """ S = self.system() names = self.notebook().system_names() if S not in names: S = names[0] self.set_system(S) return 0 else: return names.index(S)
[docs] def set_system(self, system='sage'): """ Set the math software system in which input is evaluated by default. INPUT: - ``system`` - string (default: 'sage') EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.set_system('magma') sage: W.system() 'magma' """ self.__system = system.strip()
[docs] def pretty_print(self): """ Return True if output should be pretty printed by default. OUTPUT: - ``bool`` - True of False EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.pretty_print() False sage: W.set_pretty_print('true') sage: W.pretty_print() True sage: W.quit() sage: nb.delete() """ try: return self.__pretty_print except AttributeError: self.__pretty_print = False return self.__pretty_print
[docs] def set_pretty_print(self, check='false'): """ Set whether or not output should be pretty printed by default. INPUT: - ``check`` - string (default: 'false'); either 'true' or 'false'. .. note:: The reason the input is a string and lower case instead of a Python bool is because this gets called indirectly from JavaScript. (And, Jason Grout wrote this and didn't realize how unpythonic this design is - it should be redone to use True/False.) EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.set_pretty_print('false') sage: W.pretty_print() False sage: W.set_pretty_print('true') sage: W.pretty_print() True sage: W.quit() sage: nb.delete() """ if check == 'false': check = False else: check = True self.__pretty_print = check self.eval_asap_no_output("pretty_print_default(%r)" % check)
########################################################## # 3-D plots ##########################################################
[docs] def live_3D(self): """ Return True if the 3-D plots should be loaded live (interactive) by default. OUTPUT: - ``bool`` - True of False EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.live_3D() False sage: W.quit() sage: nb.delete() """ try: return self.__live_3D except AttributeError: self.__live_3D = False return self.__live_3D
[docs] def set_live_3D(self, check=False): """ Set whether or not 3-D plots should be live by default. Not a good idea for worksheets with more than 1 or 2 3-D plots. INPUT: - ``check`` - boolean EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('A Test Worksheet', 'admin') sage: W.set_live_3D(False) sage: W.live_3D() False sage: W.set_live_3D(True) sage: W.live_3D() True sage: W.quit() sage: nb.delete() """ self.__live_3D = check
########################################################## # Publication ##########################################################
[docs] def is_auto_publish(self): """ Returns True if this worksheet should be automatically published. """ try: return self.__autopublish except AttributeError: self.__autopublish = False return False
[docs] def set_auto_publish(self, x): self.__autopublish = x
[docs] def is_published(self): """ Return True if this worksheet is a published worksheet. OUTPUT: - ``bool`` - whether or not owner is 'pub' EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.is_published() False sage: W.set_owner('pub') sage: W.is_published() True """ return self.owner() == 'pub'
[docs] def worksheet_that_was_published(self): """ Return a fresh copy of the worksheet that was published to get this worksheet, if this worksheet was published. Otherwise just return this worksheet. OUTPUT: Worksheet EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.worksheet_that_was_published() is W True sage: S = nb.publish_worksheet(W, 'admin') sage: S.worksheet_that_was_published() is S False sage: S.worksheet_that_was_published() is W True """ try: return self.notebook().get_worksheet_with_filename('%s/%s' % self.__worksheet_came_from) except Exception: # things can go wrong (especially with old migrated # Sage notebook servers!), but we don't want such # problems to crash the notebook server. return self
[docs] def publisher(self): """ Return username of user that published this worksheet. OUTPUT: string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: S = nb.publish_worksheet(W, 'admin') sage: S.publisher() 'admin' """ return self.worksheet_that_was_published().owner()
[docs] def is_publisher(self, username): """ Return True if username is the username of the publisher of this worksheet, assuming this worksheet was published. INPUT: - ``username`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: P = nb.publish_worksheet(W, 'admin') sage: P.is_publisher('hearst') False sage: P.is_publisher('admin') True """ return self.publisher() == username
[docs] def has_published_version(self): """ Return True if there is a published version of this worksheet. OUTPUT: bool EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: P = nb.publish_worksheet(W, 'admin') sage: P.has_published_version() False sage: W.has_published_version() True """ try: self.published_version() return True except ValueError: return False
[docs] def set_published_version(self, filename): """ Set the published version of this worksheet to be the worksheet with given filename. INPUT: - ``filename`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: P = nb.publish_worksheet(W, 'admin') # indirect test sage: W._Worksheet__published_version 'pub/0' sage: W.set_published_version('pub/1') sage: W._Worksheet__published_version 'pub/1' """ self.__published_version = filename
[docs] def published_version(self): """ If this worksheet was published, return the published version of this worksheet. Otherwise, raise a ValueError. OUTPUT: a worksheet (or raise a ValueError) EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: P = nb.publish_worksheet(W, 'admin') sage: W.published_version() is P True """ try: filename = self.__published_version try: W = self.notebook().get_worksheet_with_filename(filename) return W except KeyError: del self.__published_version raise ValueError except AttributeError: raise ValueError("no published version")
[docs] def set_worksheet_that_was_published(self, W): """ Set the owner and id_number of the worksheet that was published to get self. INPUT: - ``W`` -- worksheet or 2-tuple ('owner', id_number) EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: P = nb.publish_worksheet(W, 'admin') sage: P.worksheet_that_was_published() is W True We fake things and make it look like P published itself:: sage: P.set_worksheet_that_was_published(P) sage: P.worksheet_that_was_published() is P True """ if isinstance(W, tuple): self.__worksheet_came_from = W else: self.__worksheet_came_from = (W.owner(), W.id_number())
[docs] def rate(self, x, comment, username): """ Set the rating on this worksheet by the given user to x and also set the given comment. INPUT: - ``x`` - integer - ``comment`` - string - ``username`` - string EXAMPLES: We create a worksheet and rate it, then look at the ratings. :: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.rate(3, 'this is great', 'hilbert') sage: W.ratings() [('hilbert', 3, 'this is great')] Note that only the last rating by a user counts:: sage: W.rate(1, 'this lacks content', 'riemann') sage: W.rate(0, 'this lacks content', 'riemann') sage: W.ratings() [('hilbert', 3, 'this is great'), ('riemann', 0, 'this lacks content')] """ r = self.ratings() x = int(x) for i in range(len(r)): if r[i][0] == username: r[i] = (username, x, comment) return else: r.append((username, x, comment))
[docs] def is_rater(self, username): """ Return True is the user with given username has rated this worksheet. INPUT: - ``username`` - string OUTPUT: bool EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.rate(0, 'this lacks content', 'riemann') sage: W.is_rater('admin') False sage: W.is_rater('riemann') True """ try: return username in [x[0] for x in self.ratings()] except TypeError: return False
[docs] def ratings(self): """ Return all the ratings of this worksheet. OUTPUT: - ``list`` - a reference to the list of ratings. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.ratings() [] sage: W.rate(0, 'this lacks content', 'riemann') sage: W.rate(3, 'this is great', 'hilbert') sage: W.ratings() [('riemann', 0, 'this lacks content'), ('hilbert', 3, 'this is great')] """ try: return self.__ratings except AttributeError: v = [] self.__ratings = v return v
[docs] def html_ratings_info(self, username=None): r""" Return html that renders to give a summary of how this worksheet has been rated. OUTPUT: - ``string`` -- a string of HTML as a bunch of table rows. EXAMPLES:: sage: from sagenb.flask_version import base # random output -- depends on warnings issued by other sage packages sage: app = base.create_app(tmp_dir(ext='.sagenb')) sage: ctx = app.app_context() sage: ctx.push() sage: nb = base.notebook sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.rate(0, 'this lacks content', 'riemann') sage: W.rate(3, 'this is great', 'hilbert') sage: W.html_ratings_info() u'...hilbert...3...this is great...this lacks content...' """ return template(os.path.join('html', 'worksheet', 'ratings_info.html'), worksheet = self, username = username)
[docs] def rating(self): """ Return overall average rating of self. OUTPUT: float or the int -1 to mean "not rated" EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.rating() -1 sage: W.rate(0, 'this lacks content', 'riemann') sage: W.rate(3, 'this is great', 'hilbert') sage: W.rating() 1.5 """ r = [x[1] for x in self.ratings()] if len(r) == 0: rating = -1 # means "not rated" else: rating = float(sum(r)) / float(len(r)) return rating
########################################################## # Active, trash can and archive ##########################################################
[docs] def everyone_has_deleted_this_worksheet(self): """ Return True if all users have deleted this worksheet, so we know we can safely purge it from disk. OUTPUT: bool EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.everyone_has_deleted_this_worksheet() False sage: W.move_to_trash('admin') sage: W.everyone_has_deleted_this_worksheet() True """ for user in self.collaborators() + [self.owner()]: # When the worksheet has been deleted by the owner, # self.owner() returns None, so we have to be careful # about that case. if user is not None and not self.is_trashed(user): return False return True
[docs] def user_view(self, user): """ Return the view that the given user has of this worksheet. If the user currently doesn't have a view set it to ACTIVE and return ACTIVE. INPUT: - ``user`` - a string OUTPUT: - ``Python int`` - one of ACTIVE, ARCHIVED, TRASH, which are defined in worksheet.py EXAMPLES: We create a new worksheet and get the view, which is ACTIVE:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.user_view('admin') 1 sage: sagenb.notebook.worksheet.ACTIVE 1 Now for the admin user we move W to the archive:: sage: W.move_to_archive('admin') The view is now archive. :: sage: W.user_view('admin') 0 sage: sagenb.notebook.worksheet.ARCHIVED 0 For any other random viewer the view is set by default to ACTIVE. :: sage: W.user_view('foo') 1 """ try: return self.__user_view[user] except AttributeError: self.__user_view = {} except KeyError: pass self.__user_view[user] = ACTIVE return ACTIVE
[docs] def tags(self): """ A temporary trivial tags implementation. """ try: d = dict(self.__user_view) except AttributeError: self.user_view(self.owner()) d = copy.copy(self.__user_view) for user, val in iteritems(d): if not isinstance(val, list): d[user] = [val] return d
[docs] def set_tags(self, tags): """ Set the tags -- for now we ignore everything except ACTIVE, ARCHIVED, TRASH. INPUT: - ``tags`` -- dictionary with keys usernames and values a list of tags, where a tag is a string or ARCHIVED, ACTIVE, TRASH. """ d = {} for user, v in iteritems(tags): if len(v) >= 1: d[user] = v[0] # must be a single int for now, until # the tag system is implemented self.__user_view = d
[docs] def set_user_view(self, user, x): """ Set the view on this worksheet for the given user. INPUT: - ``user`` - a string - ``x`` - int, one of the variables ACTIVE, ARCHIVED, TRASH in worksheet.py EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.set_user_view('admin', sagenb.notebook.worksheet.ARCHIVED) sage: W.user_view('admin') == sagenb.notebook.worksheet.ARCHIVED True """ if not isinstance(user, (str, unicode)): raise TypeError("user (=%s) must be a string" % user) try: self.__user_view[user] = x except (KeyError, AttributeError): self.user_view(user) self.__user_view[user] = x # it is important to save the configuration and changing the # views, e.g., moving to trash, etc., since the user can't # easily click save without changing the view back. self.save(conf_only=True)
[docs] def user_view_is(self, user, x): """ Return True if the user view of user is x. INPUT: - ``user`` - a string - ``x`` - int, one of the variables ACTIVE, ARCHIVED, TRASH in worksheet.py EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Publish Test', 'admin') sage: W.user_view_is('admin', sagenb.notebook.worksheet.ARCHIVED) False sage: W.user_view_is('admin', sagenb.notebook.worksheet.ACTIVE) True """ return self.user_view(user) == x
[docs] def is_archived(self, user): """ Return True if this worksheet is archived for the given user. INPUT: - ``user`` - string OUTPUT: bool EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Archived Test', 'admin') sage: W.is_archived('admin') False sage: W.move_to_archive('admin') sage: W.is_archived('admin') True """ return self.user_view_is(user, ARCHIVED)
[docs] def is_active(self, user): """ Return True if this worksheet is active for the given user. INPUT: - ``user`` - string OUTPUT: bool EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Active Test', 'admin') sage: W.is_active('admin') True sage: W.move_to_archive('admin') sage: W.is_active('admin') False """ return self.user_view_is(user, ACTIVE)
[docs] def is_trashed(self, user): """ Return True if this worksheet is in the trash for the given user. INPUT: - ``user`` - string OUTPUT: bool EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Trash Test', 'admin') sage: W.is_trashed('admin') False sage: W.move_to_trash('admin') sage: W.is_trashed('admin') True """ return self.user_view_is(user, TRASH)
[docs] def move_to_archive(self, user): """ Move this worksheet to be archived for the given user. INPUT: - ``user`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Archive Test', 'admin') sage: W.move_to_archive('admin') sage: W.is_archived('admin') True """ self.set_user_view(user, ARCHIVED) if self.viewers() == [user]: self.quit()
[docs] def set_active(self, user): """ Set his worksheet to be active for the given user. INPUT: - ``user`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Active Test', 'admin') sage: W.move_to_archive('admin') sage: W.is_active('admin') False sage: W.set_active('admin') sage: W.is_active('admin') True """ self.set_user_view(user, ACTIVE)
[docs] def move_to_trash(self, user): """ Move this worksheet to the trash for the given user. INPUT: - ``user`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Trash Test', 'admin') sage: W.move_to_trash('admin') sage: W.is_trashed('admin') True """ self.set_user_view(user, TRASH) if self.viewers() == [user]: self.quit()
[docs] def move_out_of_trash(self, user): """ Exactly the same as set_active(user). INPUT: - ``user`` - string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Active Test', 'admin') sage: W.move_to_trash('admin') sage: W.is_active('admin') False sage: W.move_out_of_trash('admin') sage: W.is_active('admin') True """ self.set_active(user)
[docs] def delete_cells_directory(self): r""" Delete the directory in which all the cell computations occur. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Test', 'sage') sage: W.edit_save('{{{\n3^20\n}}}') sage: W.cell_list()[0].evaluate() sage: W.check_comp() # random output -- depends on computer speed sage: sorted(os.listdir(W.directory())) ['cells', 'data', 'worksheet.html', 'worksheet_conf.pickle'] sage: W.save_snapshot('admin') sage: sorted(os.listdir(W.directory())) ['cells', 'data', 'snapshots', 'worksheet.html', 'worksheet_conf.pickle'] sage: W.delete_cells_directory() sage: sorted(os.listdir(W.directory())) ['data', 'snapshots', 'worksheet.html', 'worksheet_conf.pickle'] sage: W.quit() sage: nb.delete() """ dir = self.cells_directory() if os.path.exists(dir): shutil.rmtree(dir)
########################################################## # Owner/viewer/user management ##########################################################
[docs] def owner(self): try: return self.__owner except AttributeError: self.__owner = 'pub' return 'pub'
[docs] def is_owner(self, username): return self.owner() == username
[docs] def set_owner(self, owner): self.__owner = owner if not owner in self.collaborators(): self.__collaborators.append(owner)
[docs] def is_only_viewer(self, user): try: return user in self.__viewers except AttributeError: return False
[docs] def is_viewer(self, user): try: return user in self.__viewers or user in self.__collaborators or user == self.publisher() except AttributeError: return True
[docs] def is_collaborator(self, user): return user in self.collaborators()
[docs] def user_can_edit(self, user): """ Return True if the user with given name is allowed to edit this worksheet. INPUT: - ``user`` - string OUTPUT: bool EXAMPLES: We create a notebook with one worksheet and two users. :: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: nb.user_manager().add_user('william', 'william', 'wstein@sagemath.org', force=True) sage: W = nb.create_new_worksheet('Test', 'sage') sage: W.user_can_edit('sage') True At first the user 'william' can't edit this worksheet:: sage: W.user_can_edit('william') False After adding 'william' as a collaborator he can edit the worksheet. :: sage: W.add_collaborator('william') sage: W.user_can_edit('william') True Clean up:: sage: nb.delete() """ return self.is_collaborator(user) or self.is_owner(user)
[docs] def delete_user(self, user): """ Delete a user from having any view or ownership of this worksheet. INPUT: - ``user`` - string; the name of a user EXAMPLES: We create a notebook with 2 users and 1 worksheet that both view. :: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('wstein','sage','wstein@sagemath.org',force=True) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.new_worksheet_with_title_from_text('Sage', owner='sage') sage: W.add_viewer('wstein') sage: W.owner() 'sage' sage: W.viewers() ['wstein'] We delete the sage user from the worksheet W. This makes wstein the new owner. :: sage: W.delete_user('sage') sage: W.viewers() ['wstein'] sage: W.owner() 'wstein' Then we delete wstein from W, which makes the owner None. :: sage: W.delete_user('wstein') sage: W.owner() is None True sage: W.viewers() [] Finally, we clean up. :: sage: nb.delete() """ if user in self.collaborators(): self.__collaborators.remove(user) if user in self.__viewers: self.__viewers.remove(user) if self.__owner == user: if len(self.__collaborators) > 0: self.__owner = self.__collaborators[0] elif len(self.__viewers) > 0: self.__owner = self.__viewers[0] else: # Now there is nobody to take over ownership. We # assign the owner None, which means nobody owns it. # It will get purged elsewhere. self.__owner = None
[docs] def add_viewer(self, user): """ Add the given user as an allowed viewer of this worksheet. INPUT: - ``user`` - string (username) EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: nb.user_manager().add_user('diophantus','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Viewer test', 'admin') sage: W.add_viewer('diophantus') sage: W.viewers() ['diophantus'] """ try: if not user in self.__viewers: self.__viewers.append(user) except AttributeError: self.__viewers = [user]
[docs] def add_collaborator(self, user): """ Add the given user as a collaborator on this worksheet. INPUT: - ``user`` - a string EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: nb.user_manager().add_user('diophantus','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Collaborator test', 'admin') sage: W.collaborators() [] sage: W.add_collaborator('diophantus') sage: W.collaborators() ['diophantus'] """ try: if not user in self.__collaborators: self.__collaborators.append(user) except AttributeError: self.__collaborators = [user]
########################################################## # Searching ########################################################## ########################################################## # Saving ##########################################################
[docs] def save_snapshot(self, user, E=None): if not self.body_is_loaded(): return self.uncache_snapshot_data() path = self.snapshot_directory() basename = str(int(time.time())) filename = os.path.join(path, '%s.bz2' % basename) if E is None: E = self.edit_text() worksheet_html = self.worksheet_html_filename() open(filename, 'w').write(bz2.compress(E.encode('utf-8', 'ignore'))) open(worksheet_html, 'w').write(self.body().encode('utf-8', 'ignore')) self.limit_snapshots() try: X = self.__saved_by_info except AttributeError: X = {} self.__saved_by_info = X X[basename] = user if self.is_auto_publish(): self.notebook().publish_worksheet(self, user)
[docs] def get_snapshot_text_filename(self, name): path = self.snapshot_directory() return os.path.join(path, name)
[docs] def user_autosave_interval(self, username): return self.notebook().user(username)['autosave_interval']
[docs] def autosave(self, username): return try: last = self.__last_autosave except AttributeError: self.__last_autosave = time.time() return t = time.time() if t - last >= self.user_autosave_interval(username): self.__last_autosave = t self.save_snapshot(username)
[docs] def revert_to_snapshot(self, name): path = self.snapshot_directory() filename = os.path.join(path, '%s.txt' % name) E = bz2.decompress(open(filename).read()) self.edit_save(E)
def _saved_by_info(self, x, username=None): try: u = self.__saved_by_info[x] return u except (KeyError, AttributeError): return ''
[docs] def snapshot_data(self): try: self.__filenames except AttributeError: filenames = os.listdir(self.snapshot_directory()) filenames.sort() self.__filenames = filenames t = time.time() v = [] for x in self.__filenames: base = os.path.splitext(x)[0] if self._saved_by_info(x): v.append((_('%(t)s ago by %(le)s',) % {'t': prettify_time_ago(t - float(base)), 'le': self._saved_by_info(base)}, x)) else: v.append((_('%(seconds)s ago', seconds=prettify_time_ago(t - float(base))), x)) return v
[docs] def uncache_snapshot_data(self): try: del self.__snapshot_data except AttributeError: pass
[docs] def revert_to_last_saved_state(self): filename = self.worksheet_html_filename() if os.path.exists(filename): E = open(filename).read() else: # nothing was ever saved! E = '' self.edit_save(E)
[docs] def snapshot_directory(self): path = os.path.join(os.path.abspath(self.__dir), 'snapshots') if not os.path.exists(path): os.makedirs(path) return path
[docs] def limit_snapshots(self): r""" This routine will limit the number of snapshots of a worksheet, as specified by a hard-coded value below. Prior behavior was to allow unlimited numbers of snapshots and so this routine will not delete files created prior to this change. This assumes snapshot names correspond to the ``time.time()`` method used to create base filenames in seconds in UTC time, and that there are no other extraneous files in the directory. """ # This should be user-configurable with an option like 'max_snapshots' max_snaps = 30 amnesty = int(calendar.timegm(time.strptime("01 May 2009", "%d %b %Y"))) path = self.snapshot_directory() snapshots = os.listdir(path) snapshots.sort() for i in range(len(snapshots) - max_snaps): creation = int(os.path.splitext(snapshots[i])[0]) if creation > amnesty: os.remove(os.path.join(path, snapshots[i]))
########################################################## # Exporting the worksheet in plain text command-line format ##########################################################
[docs] def plain_text(self, prompts=False, banner=True): """ Return a plain-text version of the worksheet. INPUT: - ``prompts`` - if True format for inclusion in docstrings. """ s = '' if banner: s += "#" * 80 + '\n' s += "# Worksheet: %s" % self.name() + '\n' s += "#" * 80 + '\n\n' for C in self.cell_list(): t = C.plain_text(prompts=prompts).strip('\n') if t != '': s += '\n' + t return s
[docs] def input_text(self): """ Return text version of the input to the worksheet. """ return '\n\n---\n\n'.join([C.input_text() for C in self.cell_list()])
########################################################## # Editing the worksheet in plain text format (export and import) ##########################################################
[docs] def body(self): """ OUTPUT: -- ``string`` -- Plain text representation of the body of the worksheet. """ s = '' for C in self.cell_list(): t = C.edit_text().strip() if t: s += '\n\n' + t return s
[docs] def set_body(self, body): self.edit_save(body)
[docs] def body_is_loaded(self): """ Return True if the body if this worksheet has been loaded from disk. """ try: self.__cells return True except AttributeError: return False
[docs] def edit_text(self): """ Returns a plain-text version of the worksheet with {{{}}} wiki-formatting, suitable for hand editing. """ return self.body()
[docs] def reset_interact_state(self): """ Reset the interact state of this worksheet. """ try: S = self.__sage except AttributeError: return try: S.execute('sagenb.notebook.interact.reset_state()') except OSError: # Doesn't matter, since if S is not running, no need # to zero out the state dictionary. return
[docs] def edit_save_old_format(self, text, username=None): text.replace('\r\n', '\n') name, i = extract_name(text) self.set_name(name) text = text[i:] system, i = extract_system(text) if system == "None": system = "sage" self.set_system(system) text = text[i:] self.edit_save(text)
[docs] def edit_save(self, text, ignore_ids=False): r""" Set the contents of this worksheet to the worksheet defined by the plain text string text, which should be a sequence of HTML and code blocks. INPUT: - ``text`` - a string - ``ignore_ids`` - bool (default: False); if True ignore all the IDs in the {{{}}} code block. EXAMPLES: We create a new test notebook and a worksheet. :: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Test Edit Save', 'sage') We set the contents of the worksheet using the edit_save command. :: sage: W.edit_save('{{{\n2+3\n///\n5\n}}}\n{{{\n2+8\n///\n10\n}}}') sage: W sage/0: [Cell 0: in=2+3, out= 5, Cell 1: in=2+8, out= 10] sage: W.name() u'Test Edit Save' We check that loading a worksheet whose last cell is a :class:`~sagenb.notebook.cell.TextCell` properly increments the worksheet's cell count (see Sage trac ticket `#8443`_). .. _#8443: http://trac.sagemath.org/sage_trac/ticket/8443 :: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage', 'sage', 'sage@sagemath.org', force=True) sage: W = nb.create_new_worksheet('Test trac #8443', 'sage') sage: W.edit_save('{{{\n1+1\n///\n}}}') sage: W.cell_id_list() [0] sage: W.next_id() 1 sage: W.edit_save("{{{\n1+1\n///\n}}}\n\n<p>a text cell</p>") sage: len(set(W.cell_id_list())) == 3 True sage: W.cell_id_list() [0, 1, 2] sage: W.next_id() 3 """ # Clear any caching. try: del self.__html except AttributeError: pass self.reset_interact_state() text.replace('\r\n', '\n') data = [] while True: plain_text = extract_text_before_first_compute_cell(text).strip() if len(plain_text) > 0: T = plain_text data.append(('plain', T)) try: meta, input, output, i = extract_first_compute_cell(text) data.append(('compute', (meta, input, output))) except EOFError: # print(msg) # -- don't print msg, just outputs a blank # line every time, which makes for an # ugly and unprofessional log. break text = text[i:] ids = set([x[0]['id'] for typ, x in data if typ == 'compute' and 'id' in x[0]]) used_ids = set([]) cells = [] for typ, T in data: if typ == 'plain': if len(T) > 0: id = next_available_id(ids) ids.add(id) cells.append(self._new_text_cell(T, id=id)) used_ids.add(id) elif typ == 'compute': meta, input, output = T if not ignore_ids and 'id' in meta: id = meta['id'] if id in used_ids: # In this case don't reuse, since ids must be unique. id = next_available_id(ids) ids.add(id) html = True else: id = next_available_id(ids) ids.add(id) html = False used_ids.add(id) try: self.__cells C = self.get_cell_with_id(id = id) if C.is_text_cell(): C = self._new_cell(id) except AttributeError: C = self._new_cell(id) C.set_input_text(input) C.set_output_text(output, '') if html: C.update_html_output(output) cells.append(C) self.__cells = cells # Set the next id. This *depends* on self.cell_list() being # set!! self.set_cell_counter() # There must be at least one cell. if len(cells) == 0 or cells[-1].is_text_cell(): self.append_new_cell() if not self.is_published(): for c in self.cell_list(): if c.is_interactive_cell(): c.delete_output()
########################################################## # HTML rendering of the whole worksheet ##########################################################
[docs] def html_cell_list(self, do_print=False, username=None): """ INPUT: - do_print - a boolean OUTPUT: - string -- the HTML for the list of cells """ ncols = self.notebook().conf()['word_wrap_cols'] cells_html = "" if self.is_published(): try: return self.__html except AttributeError: for cell in self.cell_list(): cells_html += cell.html(ncols, do_print=True, username=self.username) + '\n' s = template(os.path.join('html', 'worksheet', 'published_worksheet.html'), ncols = ncols, cells_html = cells_html, username=username) self.__html = s return s for cell in self.cell_list(): try: cells_html += cell.html(ncols, do_print=do_print, username=self.username) + '\n' except Exception as msg: # catch any exception, since this exception is raised # sometimes, at least for some worksheets: # exceptions.UnicodeDecodeError: 'ascii' codec can't decode byte # 0xc2 in position 825: ordinal not in range(128) # and this causes the entire worksheet to fail to # save/render, which is obviously *not* good (much # worse than a weird issue with one cell). print(msg) return cells_html
[docs] def html(self, do_print=False, publish=False, username=None): r""" INPUT: - publish - a boolean stating whether the worksheet is published - do_print - a boolean OUTPUT: - string -- the HTML for the worksheet EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Test', 'admin') sage: W.html() u'...cell_input_active...worksheet_cell_list...cell_1...cell_output_html_1...' """ if self.is_published(): try: return self.__html except AttributeError: pass s = template(os.path.join("html", "notebook", "worksheet.html"), do_print=do_print, publish=publish, worksheet=self, username=username) if self.is_published(): self.__html = s return s
[docs] def truncated_name(self, max=30): name = self.name() if len(name) > max: name = name[:max] + ' ...' return name
########################################################## # Last edited ##########################################################
[docs] def last_change(self): """ Return information about when this worksheet was last changed and by whom. OUTPUT: - ``username`` -- string of username who last edited this worksheet - ``float`` -- seconds since epoch of the time when this worksheet was last edited """ return (self.last_to_edit(), self.last_edited())
[docs] def set_last_change(self, username, tm): """ Set the time and who last changed this worksheet. INPUT: - ``username`` -- (string) name of a user - ``tm`` -- (float) seconds since epoch EXAMPLES:: sage: W = sagenb.notebook.worksheet.Worksheet('test', 0, tmp_dir(), owner='sage') sage: W.last_change() ('sage', ...) sage: W.set_last_change('john', 1255029800) sage: W.last_change() ('john', 1255029800.0) We make sure these other functions have been correctly updated:: sage: W.last_edited() 1255029800.0 sage: W.last_to_edit() 'john' sage: W.date_edited() # Output depends on timezone time.struct_time(tm_year=2009, tm_mon=10, ...) sage: t = W.time_since_last_edited() # just test that call works """ username = str(username) tm = float(tm) self.__date_edited = (time.localtime(tm), username) self.__last_edited = (tm, username)
# TODO: all code below needs to be re-organized, but without # breaking old worksheet migration. Do this after I wrote a # "basic" method for the *old* sage notebook codebase. At that # point I'll be able to greatly simplify worksheet migration and # totally refactor anything I want in the new sagenb code.
[docs] def last_edited(self): try: return self.__last_edited[0] except AttributeError: t = time.time() self.__last_edited = (t, self.owner()) return t
[docs] def date_edited(self): """ Returns the date the worksheet was last edited. """ try: return self.__date_edited[0] except AttributeError: t = time.localtime() self.__date_edited = (t, self.owner()) return t
[docs] def last_to_edit(self): try: return self.__last_edited[1] except AttributeError: return self.owner()
[docs] def record_edit(self, user): self.__last_edited = (time.time(), user) self.__date_edited = (time.localtime(), user) self.autosave(user)
[docs] def time_since_last_edited(self): return time.time() - self.last_edited()
[docs] def warn_about_other_person_editing(self, username, threshold = WARN_THRESHOLD): r""" Check to see if another user besides username was the last to edit this worksheet during the last ``threshold`` seconds. If so, return True and that user name. If not, return False. INPUT: - ``username`` - user who would like to edit this file. - ``threshold`` - number of seconds, so if there was no activity on this worksheet for this many seconds, then editing is considered safe. """ if self.time_since_last_edited() < threshold: user = self.last_to_edit() if user != username: return True, user return False
[docs] def html_time_since_last_edited(self, username=None): t = self.time_since_last_edited() tm = prettify_time_ago(t) return template(os.path.join("html", "worksheet", "time_since_last_edited.html"), last_editor = self.last_to_edit(), time = tm, username=username)
[docs] def html_time_last_edited(self, username=None): return template(os.path.join("html", "worksheet", "time_last_edited.html"), time = convert_time_to_string(self.last_edited()), last_editor = self.last_to_edit(), username=username)
[docs] def html_time_nice_edited(self, username=None): """ Returns a "nice" html time since last edit. If the last edit was in the last 24 hours, return a "x hours ago". Otherwise, return a specific date. """ t = self.time_since_last_edited() if t < 3600*24: return self.html_time_since_last_edited(username=username) else: return self.html_time_last_edited(username=username)
########################################################## # Managing cells and groups of cells in this worksheet ##########################################################
[docs] def cell_id_list(self): r""" Returns a list of ID's of all cells in this worksheet. OUTPUT: - a new list of integers and/or strings EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Test Edit Save', 'admin') Now we set the worksheet to have two cells with the default id of 0 and another with id 10. :: sage: W.edit_save('{{{\n2+3\n///\n5\n}}}\n{{{id=10|\n2+8\n///\n10\n}}}') sage: W.cell_id_list() [0, 10] """ return [C.id() for C in self.cell_list()]
[docs] def compute_cell_id_list(self): """ Returns a list of ID's of all compute cells in this worksheet. OUTPUT: - a new list of integers and/or strings """ return [C.id() for C in self.cell_list() if C.is_compute_cell()]
[docs] def onload_id_list(self): """ Returns a list of ID's of cells the remote client should evaluate after the worksheet loads. Unlike '%auto' cells, which the server chooses to evaluate, onload cells fire only after the client sends a request. Currently, we use onload cells to set up published interacts. OUTPUT: - a new list of integer and/or string IDs """ return [C.id() for C in self.cell_list() if C.is_interactive_cell()]
[docs] def cell_list(self): r""" Returns a reference to the list of this worksheet's cells. OUTPUT: - a list of :class:`sagenb.notebook.cell.Cell_generic` instances .. note:: This function loads the cell list from disk (the file worksheet.html) if it isn't available in memory. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Test Edit Save', 'admin') sage: W.edit_save('{{{\n2+3\n///\n5\n}}}\n{{{\n2+8\n///\n10\n}}}') sage: v = W.cell_list(); v [Cell 0: in=2+3, out= 5, Cell 1: in=2+8, out= 10] sage: v[0] Cell 0: in=2+3, out= 5 """ try: return self.__cells except AttributeError: # load from disk worksheet_html = self.worksheet_html_filename() if not os.path.exists(worksheet_html): self.__cells = [] else: self.set_body(open(worksheet_html).read()) return self.__cells
[docs] def compute_cell_list(self): r""" Returns a list of this worksheet's compute cells. OUTPUT: - a list of :class:`sagenb.notebook.cell.Cell` instances EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Test', 'admin') sage: W.edit_save('foo\n{{{\n2+3\n///\n5\n}}}bar\n{{{\n2+8\n///\n10\n}}}') sage: v = W.compute_cell_list(); v [Cell 1: in=2+3, out= 5, Cell 3: in=2+8, out= 10] sage: v[0] Cell 1: in=2+3, out= 5 """ return [C for C in self.cell_list() if C.is_compute_cell()]
[docs] def append_new_cell(self): """ Creates and appends a new compute cell to this worksheet's list of cells. OUTPUT: - a new :class:`sagenb.notebook.cell.Cell` instance EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Test Edit Save', 'admin') sage: W admin/0: [Cell 1: in=, out=] sage: W.append_new_cell() Cell 2: in=, out= sage: W admin/0: [Cell 1: in=, out=, Cell 2: in=, out=] """ C = self._new_cell() self.cell_list().append(C) return C
[docs] def new_cell_before(self, id, input=''): """ Inserts a new compute cell before a cell with the given ID. If the ID does not match any cell in this worksheet's list, it inserts a new cell at the end. INPUT: - ``id`` - an integer or a string; the ID of the cell to find - ``input`` - a string (default: ''); the new cell's input text OUTPUT: - a new :class:`sagenb.notebook.cell.Cell` instance """ cells = self.cell_list() for i in range(len(cells)): if cells[i].id() == id: C = self._new_cell(input=input) cells.insert(i, C) return C C = self._new_cell(input=input) cells.append(C) return C
[docs] def new_text_cell_before(self, id, input=''): """ Inserts a new text cell before the cell with the given ID. If the ID does not match any cell in this worksheet's list, it inserts a new cell at the end. INPUT: - ``id`` - an integer or a string; the ID of the cell to find - ``input`` - a string (default: ''); the new cell's input text OUTPUT: - a new :class:`sagenb.notebook.cell.TextCell` instance """ cells = self.cell_list() for i in range(len(cells)): if cells[i].id() == id: C = self._new_text_cell(plain_text=input) cells.insert(i, C) return C C = self._new_text_cell(plain_text=input) cells.append(C) return C
[docs] def new_cell_after(self, id, input=''): """ Inserts a new compute cell into this worksheet's cell list after the cell with the given ID. If the ID does not match any cell, it inserts the new cell at the end of the list. INPUT: - ``id`` - an integer or a string; the ID of the cell to find - ``input`` - a string (default: ''); the new cell's input text OUTPUT: - a new :class:`sagenb.notebook.cell.Cell` instance """ cells = self.cell_list() for i in range(len(cells)): if cells[i].id() == id: C = self._new_cell(input=input) cells.insert(i + 1, C) return C C = self._new_cell(input=input) cells.append(C) return C
[docs] def new_text_cell_after(self, id, input=''): """ Inserts a new text cell into this worksheet's cell list after the cell with the given ID. If the ID does not match any cell, it inserts the new cell at the end of the list. INPUT: - ``id`` - an integer or a string; the ID of the cell to find - ``input`` - a string (default: ''); the new cell's input text OUTPUT: - a new :class:`sagenb.notebook.cell.TextCell` instance """ cells = self.cell_list() for i in range(len(cells)): if cells[i].id() == id: C = self._new_text_cell(plain_text=input) cells.insert(i + 1, C) return C C = self._new_text_cell(plain_text=input) cells.append(C) return C
[docs] def delete_cell_with_id(self, id): r""" Deletes a cell from this worksheet's cell list. This also deletes the cell's output and files. INPUT: - ``id`` - an integer or string; the cell's ID OUTPUT: - an integer or string; ID of the preceding cell EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.create_default_users('password') sage: W = nb.create_new_worksheet('Test Delete Cell', 'admin') sage: W.edit_save('{{{id=foo|\n2+3\n///\n5\n}}}\n{{{id=9|\n2+8\n///\n10\n}}}{{{id=dont_delete_me|\n2*3\n///\n6\n}}}\n') sage: W.cell_id_list() ['foo', 9, 'dont_delete_me'] sage: C = W.cell_list()[1] # save a reference to the cell sage: C.output_text(raw=True) u'\n10' sage: open(os.path.join(C.directory(), 'bar'), 'w').write('hello') sage: C.files() ['bar'] sage: C.files_html('') u'<a target="_new" href=".../cells/9/bar" class="file_link">bar</a>' sage: W.delete_cell_with_id(C.id()) 'foo' sage: C.output_text(raw=True) u'' sage: C.files() [] sage: W.cell_id_list() ['foo', 'dont_delete_me'] """ cells = self.cell_list() for i in range(len(cells)): if cells[i].id() == id: # Delete this cell from the queued up calculation list: C = cells[i] if C in self.__queue and self.__queue[0] != C: self.__queue.remove(C) # Delete the cell's output. C.delete_output() # Delete this cell from the list of cells in this worksheet: del cells[i] if i > 0: return cells[i - 1].id() else: break return cells[0].id()
########################################################## # Managing whether computing is happening: stop, start, clear, etc. ##########################################################
[docs] def clear(self): self.__comp_is_running = False self.__queue = [] self.__cells = [] for i in range(INITIAL_NUM_CELLS): self.append_new_cell()
[docs] def computing(self): """ Return whether or not a cell is currently being run in the worksheet Sage process. """ try: return self.__comp_is_running except AttributeError: return False
[docs] def set_not_computing(self): self.__comp_is_running = False self.__queue = []
[docs] def quit(self): try: S = self.__sage except AttributeError: # no sage running anyways! self.notebook().quit_worksheet(self) return try: S.quit() except AttributeError as msg: print("WARNING: %s" % msg) except Exception as msg: print(msg) print("WARNING: Error deleting Sage object!") try: os.kill(pid, 9) except: pass del self.__sage # We do this to avoid getting a stale Sage that uses old code. self.save() self.clear_queue() del self.__cells import shutil for cell in self.cell_list(): try: dir = cell._directory_name() except AttributeError: continue if os.path.exists(dir) and not os.listdir(dir): shutil.rmtree(dir, ignore_errors=True) self.notebook().quit_worksheet(self)
[docs] def next_block_id(self): try: i = self.__next_block_id except AttributeError: i = 0 i += 1 self.__next_block_id = i return i
[docs] def compute_process_has_been_started(self): """ Return True precisely if the compute process has been started, irregardless of whether or not it is currently churning away on a computation. """ try: return self.__sage.is_started() except AttributeError: return False
[docs] def initialize_sage(self): S = self.__sage try: from . import misc cmd = """ import base64 import sagenb.misc.support as _support_ import sagenb.notebook.interact as _interact_ # for setting current cell id DATA = %r DIR = %r import sys; sys.path.append(DATA) _support_.init(None, globals()) # The following is Sage-specific -- this immediately bombs out if sage isn't installed. from sage.all_notebook import * sage.plot.plot.EMBEDDED_MODE=True sage.misc.latex.EMBEDDED_MODE=True # TODO: For now we take back sagenb interact; do this until the sage notebook # gets removed from the sage library. from sagenb.notebook.all import * try: load(os.path.join(os.environ['DOT_SAGE'], 'init.sage'), globals(),attach=True) except (KeyError, IOError): pass """ % (os.path.join(os.path.abspath(self.data_directory()),''), misc.DIR) S.execute(cmd) S.output_status() except Exception as msg: print("ERROR initializing compute process:\n") print(msg) del self.__sage raise RuntimeError(msg) # make sure we have a __sage attribute # We do this to diagnose google issue 81; once we # have fixed that issue, we can remove this next statement T = self.__sage A = self.attached_files() for F in A.iterkeys(): A[F] = 0 # expire all # Check to see if the typeset/pretty print button is checked. # If so, send code to initialize the worksheet to have the # right pretty printing mode. if self.pretty_print(): S.execute('pretty_print_default(True);') if not self.is_published(): self._enqueue_auto_cells() # make sure we have a __sage attribute # We do this to diagnose google issue 81; once we # have fixed that issue, we can remove this next statement T = self.__sage return S
[docs] def sage(self): """ Return a started up copy of Sage initialized for computations. If this is a published worksheet, just return None, since published worksheets must not have any compute functionality. OUTPUT: a Sage interface """ if self.is_published(): return None try: S = self.__sage if S.is_started(): return S except AttributeError: pass self.__sage = self.notebook().new_worksheet_process() all_worksheet_processes.append(self.__sage) self.__next_block_id = 0 # make sure we have a __sage attribute # We do this to diagnose google issue 81; once we # have fixed that issue, we can remove this next statement S = self.__sage self.initialize_sage() # make sure we have a __sage attribute # We do this to diagnose google issue 81; once we # have fixed that issue, we can remove this next statement S = self.__sage return self.__sage
[docs] def eval_asap_no_output(self, cmd, username=None): C = self._new_cell(hidden=True) C.set_asap(True) C.set_no_output(True) C.set_input_text(cmd) self.enqueue(C, username=username)
[docs] def cell_directory(self, C): return C.directory()
[docs] def start_next_comp(self): if len(self.__queue) == 0: return if self.__comp_is_running: #self._record_that_we_are_computing() return C = self.__queue[0] cell_system = self.get_cell_system(C) percent_directives = C.percent_directives() if C.interrupted(): # don't actually compute return if cell_system == 'sage' and C.introspect(): before_prompt, after_prompt = C.introspect() I = before_prompt else: I = C.cleaned_input_text() if I in ['restart', 'quit', 'exit']: self.restart_sage() S = self.system() if S is None: S = 'sage' C.set_output_text('Exited %s process' % S,'') return #Handle any percent directives if 'save_server' in percent_directives: self.notebook().save() id = self.next_block_id() C.code_id = id # prevent directory disappear problems input = '' # This is useful mainly for interact -- it allows a cell to # know its ID. input += '_interact_.SAGE_CELL_ID=%r\n__SAGE_TMP_DIR__=os.getcwd()\n' % C.id() if C.time(): input += '__SAGE_t__=cputime()\n__SAGE_w__=walltime()\n' # If the input ends in a question mark and is *not* a comment # line, then we introspect on it. if cell_system == 'sage' and len(I) != 0: #Get the last line of a possible multiline input Istrip = I.strip().split('\n').pop() if Istrip.endswith('?') and not Istrip.startswith('#'): C.set_introspect(I, '') #Handle line continuations: join lines that end in a backslash #_except_ in LaTeX mode. if cell_system not in ['latex', 'sage', 'python']: I = I.replace('\\\n','') C._before_preparse = input + I input += self.preparse_input(I, C) try: input = relocate_future_imports(input) except SyntaxError: t = traceback.format_exc() s = 'File "<unknown>",' i = t.find(s) if i != -1: t = t[i+len(s):] i = t.find('\n') try: n = int(t[t[:i].rfind(' '):i]) # line number of the exception try: t = 'Syntax Error:\n %s'%C._before_preparse.split('\n')[n-1] except IndexError: pass if False: if i != -1: t = t[i:] v = [w for w in t.split('\n') if w] t = '\n'.join(['Syntax Error:'] + v[0:-1]) C.set_output_text(t, '') del self.__queue[0] return except ValueError: pass if C.time() and not C.introspect(): input += '; print("CPU time: %.2f s, Wall time: %.2f s"%(cputime(__SAGE_t__), walltime(__SAGE_w__)))\n' self.__comp_is_running = True self.sage().execute(input, os.path.abspath(self.data_directory()))
[docs] def check_comp(self, wait=0.2): r""" Check on currently computing cells in the queue. INPUT: - ``wait`` - float (default: 0.2); how long to wait for output. EXAMPLES:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Test', 'sage') sage: W.edit_save('{{{\n3^20\n}}}') sage: W.cell_list()[0].evaluate() sage: W.check_comp() # random output -- depends on computer speed ('d', Cell 0: in=3^20, out= 3486784401 ) sage: W.quit() sage: nb.delete() """ if len(self.__queue) == 0: return 'e', None S = self.sage() C = self.__queue[0] if C.interrupted(): self.__comp_is_running = False del self.__queue[0] return 'd', C try: output_status = S.output_status() except RuntimeError as msg: verbose("Computation was interrupted or failed. Restarting.\n%s" % msg) self.__comp_is_running = False self.start_next_comp() return 'w', C out = self.postprocess_output(output_status.output, C) if not output_status.done: # Still computing if not C.introspect(): C.set_output_text(out, '') ######################################################## # Create temporary symlinks to output files seen so far if len(output_status.filenames) > 0: cell_dir = os.path.abspath(self.cell_directory(C)) if not os.path.exists(cell_dir): os.makedirs(cell_dir) for X in output_status.filenames: if os.path.split(X)[1] == CODE_PY: continue target = os.path.join(cell_dir, os.path.split(X)[1]) if os.path.exists(target): os.unlink(target) os.symlink(X, target) ######################################################## return 'w', C if C.introspect() and not C.is_no_output(): before_prompt, after_prompt = C.introspect() if len(before_prompt) == 0: return if before_prompt[-1] != '?': # completions if hasattr(C, '_word_being_completed'): c = self.best_completion(out, C._word_being_completed) else: c = '' C.set_changed_input_text(before_prompt + c + after_prompt) out = self.completions_html(C.id(), out) C.set_introspect_html(out, completing=True) else: if C.eval_method == 'introspect': C.set_introspect_html(out, completing=False) else: C.set_introspect_html('') C.set_output_text('<html><!--notruncate-->' + out + '</html>', '') # Finished a computation. self.__comp_is_running = False del self.__queue[0] if C.is_no_output(): # Clean up the temp directories associated to C, and do # not set any output text that C might have got. d = self.cell_directory(C) for X in os.listdir(d): if os.path.split(X)[-1] != CODE_PY: Y = os.path.join(d, X) if os.path.isfile(Y): try: os.unlink(Y) except: pass else: shutil.rmtree(Y, ignore_errors=True) return 'd', C if not C.introspect(): filenames = output_status.filenames if len(filenames) > 0: # Move files to the cell directory cell_dir = os.path.abspath(self.cell_directory(C)) # We wipe the cell directory and make a new one to # clean up any cruft (like dead symbolic links to # temporary files that were deleted, old files from # old evaluations, ...). try: shutil.rmtree(cell_dir) except OSError: # Probably the directory didn't exist. If there # is a different problem, the makedirs() below will # see it. pass os.makedirs(cell_dir) for X in filenames: if os.path.split(X)[-1] == CODE_PY: continue target = os.path.join(cell_dir, os.path.split(X)[1]) # We move X to target. Note that we don't actually # do a rename: in a client/server setup, X might be # owned by a different Unix user than ourselves. if os.path.isdir(X): shutil.copytree(X, target, ignore=ignore_nonexistent_files) shutil.rmtree(X, ignore_errors=True) else: shutil.copy(X, target) os.unlink(X) set_restrictive_permissions(target) # Generate html, etc. html = C.files_html(out) C.set_output_text(out, html, sage=self.sage()) C.set_introspect_html('') return 'd', C
[docs] def interrupt(self, callback = None, timeout = 1): r""" Interrupt all currently queued up calculations. INPUT: - ``timeout`` -- time to wait for interruption to succeed - ``callback`` -- callback to be called. Called with True if interrupt succeeds, else called with False. OUTPUT: - ``deferred`` - a Deferred object with the given callbacks and errbacks EXAMPLES: We create a worksheet and start a large factorization going:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Test', 'sage') sage: W.edit_save('{{{\nfactor(2^997-1)\n}}}') sage: W.cell_list()[0].evaluate() It's running still:: sage: W.check_comp() ('w', Cell 0: in=factor(2^997-1), out=...) We interrupt it successfully. :: sage: W.interrupt() # not tested -- needs running reactor True Now we check and nothing is computing. :: sage: W.check_comp() # random -- could fail on heavily loaded machine ('e', None) Clean up. :: sage: W.quit() sage: nb.delete() """ if len(self.__queue) == 0: # nothing to do return True # stop the current computation in the running Sage S = self.__sage S.interrupt() import time time.sleep(timeout) if S.is_computing(): return False else: return True
[docs] def clear_queue(self): # empty the queue for C in self.__queue: C.interrupt() self.__queue = [] self.__comp_is_running = False
[docs] def restart_sage(self): """ Restart Sage kernel. """ self.quit() self.sage() self.start_next_comp()
[docs] def worksheet_command(self, cmd): # return URL in the web browser of the given cmd return '/home/%s/%s' % (self.filename(), cmd)
########################################################## # Idle timeout ##########################################################
[docs] def quit_if_idle(self, timeout): r""" Quit the worksheet process if it has been "idle" for more than ``timeout`` seconds, where idle is by definition that the worksheet has not reported back that it is actually computing. I.e., an ignored worksheet process (since the user closed their browser) is also considered idle, even if code is running. """ # Quit only if timeout is greater than zero if timeout > 0 and self.time_idle() > timeout: # worksheet name may contain unicode, so we use %r, which prints # the \xXX form for unicode characters print("Quitting ignored worksheet process for %r." % self.name()) self.quit()
[docs] def time_idle(self): return walltime() - self.last_compute_walltime()
[docs] def last_compute_walltime(self): try: return self.__last_compute_walltime except AttributeError: t = walltime() self.__last_compute_walltime = t return t
def _record_that_we_are_computing(self, username=None): self.__last_compute_walltime = walltime() if username: self.record_edit(username)
[docs] def ping(self, username): if self.is_published(): return self._record_that_we_are_computing(username)
########################################################## # Enqueuing cells ##########################################################
[docs] def queue(self): return list(self.__queue)
[docs] def queue_id_list(self): return [c.id() for c in self.__queue]
[docs] def enqueue(self, C, username=None, next=False): r""" Queue a cell for evaluation in this worksheet. INPUT: - ``C`` - a :class:`sagenb.notebook.cell.Cell` instance - ``username`` - a string (default: None); the name of the user evaluating this cell (mainly used for login) - ``next`` - a boolean (default: False); ignored .. note:: If ``C.is_asap()`` is True, then we put ``C`` as close to the beginning of the queue as possible, but after all "asap" cells. Otherwise, ``C`` goes at the end of the queue. """ if self.is_published(): return self._record_that_we_are_computing(username) if not C.is_compute_cell(): raise TypeError if C.worksheet() != self: raise ValueError("C must be have self as worksheet.") # Now enqueue the requested cell. if not (C in self.__queue): if C.is_asap(): if self.computing(): i = 1 else: i = 0 while i < len(self.__queue) and self.__queue[i].is_asap(): i += 1 self.__queue.insert(i, C) else: self.__queue.append(C) self.start_next_comp()
def _enqueue_auto_cells(self): for c in self.cell_list(): if c.is_auto_cell(): self.enqueue(c)
[docs] def next_id(self): try: return self.__next_id except AttributeError: self.set_cell_counter() return self.__next_id
[docs] def set_cell_counter(self): self.__next_id = 1 + max([C.id() for C in self.cell_list() if isinstance(C.id(), int)] + [-1])
def _new_text_cell(self, plain_text, id=None): if id is None: id = self.next_id() self.__next_id += 1 return TextCell(id, plain_text, self)
[docs] def next_hidden_id(self): try: i = self.__next_hidden_id self.__next_hidden_id -= 1 except AttributeError: i = -1 self.__next_hidden_id = -2 return i
def _new_cell(self, id=None, hidden=False, input=''): if id is None: if hidden: id = self.next_hidden_id() else: id = self.next_id() self.__next_id += 1 return Cell(id, input, '', self)
[docs] def append(self, L): self.cell_list().append(L)
########################################################## # Accessing existing cells ##########################################################
[docs] def get_cell_with_id_or_none(self, id): """ Gets a pre-existing cell with this id, or returns None. """ for c in self.cell_list(): if c.id() == id: return c return None
[docs] def get_cell_with_id(self, id): """ Get a pre-existing cell with this id, or creates a new one with it. """ return self.get_cell_with_id_or_none(id) or self._new_cell(id)
[docs] def synchronize(self, s): try: i = (self.__synchro + 1)%65536 except AttributeError: i = 0 self.__synchro = i return 'print("%s%s")\n'%(SAGE_BEGIN,i) + s + '\nprint("%s%s")\n' % (SAGE_END, i)
[docs] def synchro(self): try: return self.__synchro except AttributeError: return 0
[docs] def check_cell(self, id): """ Checks the status of a given compute cell. INPUT: - ``id`` - an integer or a string; the cell's ID. OUTPUT: - a (string, :class:`sagenb.notebook.cell.Cell`)-tuple; the cell's status ('d' for "done" or 'w' for "working") and the cell itself. """ cell = self.get_cell_with_id(id) if cell in self.__queue: status = 'w' else: status = 'd' return status, cell
[docs] def is_last_id_and_previous_is_nonempty(self, id): if self.cell_list()[-1].id() != id: return False if len(self.cell_list()) == 1: return False if len(self.cell_list()[-2].output_text(ncols=0)) == 0: return False return True
########################################################## # (Tab) Completions ##########################################################
[docs] def best_completion(self, s, word): completions = s.split() if len(completions) == 0: return '' n = len(word) i = n m = min([len(x) for x in completions]) while i <= m: word = completions[0][:i] for w in completions[1:]: if w[:i] != word: return w[n:i-1] i += 1 return completions[0][n:m]
[docs] def completions_html(self, id, s, cols=3): if 'no completions of' in s: return '' completions = s.split() n = len(completions) l = n / cols + n % cols if n == 1: return '' # don't show a window, just replace it rows = [] for r in range(0, l): row = [] for c in range(cols): try: cell = completions[r + l * c] row.append(cell) except: row.append('') rows.append(row) return format_completions_as_html(id, rows)
########################################################## # Processing of input and output to worksheet process. ##########################################################
[docs] def preparse_input(self, input, C): introspect = C.introspect() if introspect: input = self.preparse_introspection_input(input, C, introspect) else: switched, input = self.check_for_system_switching(input, C) if not switched: input = self.preparse_nonswitched_input(input) input += '\n' return input
[docs] def preparse_introspection_input(self, input, C, introspect): before_prompt, after_prompt = introspect i = 0 while i < len(after_prompt): if after_prompt[i] == '?': if i < len(after_prompt)-1 and after_prompt[i + 1] == '?': i += 1 before_prompt += after_prompt[:i + 1] after_prompt = after_prompt[i + 1:] C.set_introspect(before_prompt, after_prompt) break elif after_prompt[i] in ['"', "'", ' ', '\t', '\n']: break i += 1 if before_prompt.endswith('??'): input = self._get_last_identifier(before_prompt[:-2]) input = 'print(_support_.source_code("%s", globals(), system="%s"))' % (input, self.system()) elif before_prompt.endswith('?'): input = self._get_last_identifier(before_prompt[:-1]) input = 'print(_support_.docstring("%s", globals(), system="%s"))' % (input, self.system()) else: input = self._get_last_identifier(before_prompt) C._word_being_completed = input input = 'print("\\n".join(_support_.completions("%s", globals(), system="%s")))' % (input, self.system()) return input
[docs] def preparse_nonswitched_input(self, input): """ Preparse the input to a Sage Notebook cell. INPUT: - ``input`` -- a string OUTPUT: - a string """ input = ignore_prompts_and_output(input).rstrip() input = self.preparse(input) return input
def _strip_synchro_from_start_of_output(self, s): z = SAGE_BEGIN + str(self.synchro()) i = s.find(z) if i == -1: # Did not find any synchronization info in the output # stream. j = s.find('Traceback') if j != -1: # Probably there was an error; better not hide it. return s[j:] else: # Maybe we just read too early -- suppress displaying # anything yet. return '' else: return s[i + len(z):]
[docs] def postprocess_output(self, out, C): if C.introspect(): return out out = out.replace("NameError: name 'os' is not defined", "NameError: name 'os' is not defined\nTHERE WAS AN ERROR LOADING THE SAGE LIBRARIES. Try starting Sage from the command line to see what the error is.") # Todo: what does this do? document this try: tb = 'Traceback (most recent call last):' i = out.find(tb) if i != -1: t = '.py", line' j = out.find(t) z = out[j+5:].find(',') n = int(out[j+len(t):z + j+5]) k = out[j:].find('\n') if k != -1: k += j l = out[k+1:].find('\n') if l != -1: l += k+1 I = C._before_preparse.split('\n') out = out[:i + len(tb)+1] + ' ' + I[n-2] + out[l:] except (ValueError, IndexError): pass return out
def _get_last_identifier(self, s): import sagenb.misc.support as support return support.get_rightmost_identifier(s)
[docs] def preparse(self, s): """ Return preparsed version of input code ``s``, ready to be sent to the Sage process for evaluation. The output is a "safe string" (no funny characters). INPUT: - ``s`` -- a string OUTPUT: - a string """ # The extra newline below is necessary, since otherwise source # code introspection doesn't include the last line. return 'open("%s","w").write("# -*- coding: utf-8 -*-\\n" + _support_.preparse_worksheet_cell(base64.b64decode("%s"),globals())+"\\n"); execfile(os.path.abspath("%s"))'%(CODE_PY, base64.b64encode(s.encode('utf-8', 'ignore')), CODE_PY)
########################################################## # Loading and attaching files ##########################################################
[docs] def load_any_changed_attached_files(self, s): r""" Modify ``s`` by prepending any necessary load commands corresponding to attached files that have changed. """ A = self.attached_files() init_sage = DOT_SAGENB + 'init.sage' if not init_sage in A.keys() and os.path.exists(init_sage): A[init_sage] = 0 # since we change A during the iteration # we need to make a copy first for F, tm in list(A.items()): try: new_tm = os.path.getmtime(F) except OSError: del A[F] else: if new_tm > tm: A[F] = new_tm s = 'load %s\n' % F + s return s
[docs] def attached_files(self): try: A = self.__attached except AttributeError: A = {} self.__attached = A return A
[docs] def attach(self, filename): A = self.attached_files() try: A[filename] = os.path.getmtime(filename) except OSError: print("WARNING: File %s vanished" % filename)
[docs] def detach(self, filename): A = self.attached_files() try: A.pop(filename) except KeyError: pass
def _normalized_filenames(self, L): i = L.find('#') if i != -1: L = L[:i] a = [] for filename in L.split(): filename = filename.strip('"\'') if not filename.endswith('.py') and not filename.endswith('.sage') and \ not filename.endswith('.sobj') and not os.path.exists(filename): if os.path.exists(filename + '.sage'): filename = filename + '.sage' elif os.path.exists(filename + '.py'): filename = filename + '.py' elif os.path.exists(filename + '.sobj'): filename = filename + '.sobj' a.append(filename) return a
[docs] def load_path(self): D = self.cells_directory() return [os.path.join(self.directory(), 'data')] + [D + x for x in os.listdir(D)]
[docs] def hunt_file(self, filename): if filename.lower().startswith('http://'): filename = get_remote_file(filename) if not os.path.exists(filename): fn = os.path.split(filename)[-1] for D in self.load_path(): t = os.path.join(D, fn) if os.path.exists(t): filename = t break if os.path.exists(t + '.sobj'): filename = t + '.sobj' break return os.path.abspath(filename)
def _load_file(self, filename, files_seen_so_far, this_file): if filename.endswith('.sobj'): name = os.path.splitext(filename)[0] name = os.path.split(name)[-1] return '%s = load("%s");'%(name, filename) if filename in files_seen_so_far: t = "print('WARNING: Not loading %s -- would create recursive load')" % filename try: F = open(filename).read() except IOError: return "print('Error loading %s -- file not found')" % filename else: filename_orig = filename filename = filename.rstrip('.txt') if filename.endswith('.py'): t = F elif filename.endswith('.spyx') or filename.endswith('.pyx'): cur = os.path.abspath(os.curdir) try: mod, dir = cython.cython(filename_orig, compile_message=True, use_cache=True) except (IOError, OSError, RuntimeError) as msg: return "print(r'''Error compiling cython file:\n%s''')" % msg t = "import sys\n" t += "sys.path.append('%s')\n"%dir t += "from %s import *\n"%mod return t elif filename.endswith('.sage'): t = self.preparse(F) else: t = "print('Loading of file \"%s\" has type not implemented.')" % filename t = self.do_sage_extensions_preparsing(t, files_seen_so_far + [this_file], filename) return t def _save_objects(self, s): s = s.replace(',',' ').replace('(',' ').replace(')',' ') v = s.split() return ';'.join(['save(%s,"%s")'%(x,x) for x in v]) def _eval_cmd(self, system, cmd): return u"print(_support_.syseval(%s, %r, __SAGE_TMP_DIR__))" % (system, cmd) ########################################################## # Parsing the %cython, %mathjax, %python, etc., extension. ##########################################################
[docs] def get_cell_system(self, cell): r""" Returns the system that will run the input in cell. This defaults to worksheet's system if there is not one specifically given in the cell. EXAMPLES:: sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Test', 'sage') sage: W.edit_save('{{{\n2+3\n}}}\n\n{{{\n%gap\nSymmetricGroup(5)\n}}}') sage: c0, c1 = W.cell_list() sage: W.get_cell_system(c0) 'sage' sage: W.get_cell_system(c1) u'gap' sage: W.edit_save('{{{\n%sage\n2+3\n}}}\n\n{{{\nSymmetricGroup(5)\n}}}') sage: W.set_system('gap') sage: c0, c1 = W.cell_list() sage: W.get_cell_system(c0) u'sage' sage: W.get_cell_system(c1) 'gap' """ if cell.system() is not None: system = cell.system() else: system = self.system() return system
[docs] def cython_import(self, cmd, cell): # Choice: Can use either C.relative_id() or # self.next_block_id(). C.relative_id() has the advantage # that block evals are cached, i.e., no need to recompile. On # the other hand tracebacks don't work if you change a cell # and create a new function in it. Caching is also annoying # since the linked .c file disappears. # TODO: This design will *only* work on local machines -- need # to redesign so works even if compute worksheet process is # remote! id = self.next_block_id() code = os.path.join(self.directory(), 'code') if not os.path.exists(code): os.makedirs(code) spyx = os.path.abspath(os.path.join(code, 'sage%s.spyx'%id)) if not (os.path.exists(spyx) and open(spyx).read() == cmd): open(spyx,'w').write(cmd.encode('utf-8', 'ignore')) return '_support_.cython_import_all("%s", globals())'%spyx
[docs] def check_for_system_switching(self, input, cell): r""" Check for input cells that start with ``%foo``, where ``foo`` is an object with an eval method. INPUT: - ``s`` - a string of the code from the cell to be executed - ``C`` - the cell object EXAMPLES: First, we set up a new notebook and worksheet. :: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Test', 'sage') We first test running a native command in 'sage' mode and then a GAP cell within Sage mode. :: sage: W.edit_save('{{{\n2+3\n}}}\n\n{{{\n%gap\nSymmetricGroup(5)\n}}}') sage: c0, c1 = W.cell_list() sage: W.check_for_system_switching(c0.cleaned_input_text(), c0) (False, u'2+3') sage: W.check_for_system_switching(c1.cleaned_input_text(), c1) (True, u"print(_support_.syseval(gap, u'SymmetricGroup(5)', __SAGE_TMP_DIR__))") :: sage: c0.evaluate() sage: W.check_comp() #random output -- depends on the computer's speed ('d', Cell 0: in=2+3, out= 5 ) sage: c1.evaluate() sage: W.check_comp() #random output -- depends on the computer's speed ('d', Cell 1: in=%gap SymmetricGroup(5), out= Sym( [ 1 .. 5 ] ) ) Next, we run the same commands but from 'gap' mode. :: sage: W.edit_save('{{{\n%sage\n2+3\n}}}\n\n{{{\nSymmetricGroup(5)\n}}}') sage: W.set_system('gap') sage: c0, c1 = W.cell_list() sage: W.check_for_system_switching(c0.cleaned_input_text(), c0) (False, u'2+3') sage: W.check_for_system_switching(c1.cleaned_input_text(), c1) (True, u"print(_support_.syseval(gap, u'SymmetricGroup(5)', __SAGE_TMP_DIR__))") sage: c0.evaluate() sage: W.check_comp() #random output -- depends on the computer's speed ('d', Cell 0: in=%sage 2+3, out= 5 ) sage: c1.evaluate() sage: W.check_comp() #random output -- depends on the computer's speed ('d', Cell 1: in=SymmetricGroup(5), out= Sym( [ 1 .. 5 ] ) ) sage: W.quit() sage: nb.delete() """ system = self.get_cell_system(cell) if system == 'sage': return False, input elif system in ['cython', 'pyrex', 'sagex']: return True, self.cython_import(input, cell) else: cmd = self._eval_cmd(system, input) return True, cmd
########################################################## # List of attached files. ##########################################################
[docs] def attached_html(self, username=None): return template(os.path.join("html", "worksheet", "attached.html"), attached_files = self.attached_files(), username=username)
########################################################## # Showing and hiding all cells ##########################################################
[docs] def show_all(self): for C in self.cell_list(): try: C.set_cell_output_type('wrap') except AttributeError: # for backwards compatibility pass
[docs] def hide_all(self): for C in self.cell_list(): try: C.set_cell_output_type('hidden') except AttributeError: pass
[docs] def delete_all_output(self, username): r""" Delete all the output, files included, in all the worksheet cells. INPUT: - ``username`` - name of the user requesting the deletion. EXAMPLES: We create a new notebook, user, and a worksheet:: sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb')) sage: nb.user_manager().add_user('sage','sage','sage@sagemath.org',force=True) sage: W = nb.create_new_worksheet('Test', 'sage') sage: W.edit_save("{{{\n2+3\n///\n5\n}}}\n{{{\nopen('afile', 'w').write('some text')\nprint('hello')\n///\n\n}}}") We have two cells:: sage: W.cell_list() [Cell 0: in=2+3, out= 5, Cell 1: in=open('afile', 'w').write('some text') print('hello'), out= ] sage: C0 = W.cell_list()[1] sage: open(os.path.join(C0.directory(), 'xyz'), 'w').write('bye') sage: C0.files() ['xyz'] sage: C1 = W.cell_list()[1] sage: C1.evaluate() sage: W.check_comp() # random output -- depends on computer speed ('w', Cell 1: in=open('afile', 'w').write('some text') print('hello'), out=) sage: W.check_comp() # random output -- depends on computer speed ('d', Cell 1: in=open('afile', 'w').write('some text') print('hello'), out= hello ) sage: W.check_comp() # random output -- depends on computer speed ('e', None) sage: C1.files() # random output -- depends on computer speed ['afile'] We now delete the output, observe that it is gone:: sage: W.delete_all_output('sage') sage: W.cell_list() [Cell 0: in=2+3, out=, Cell 1: in=open('afile', 'w').write('some text') print('hello'), out=] sage: C0.files(), C1.files() ([], []) If an invalid user tries to delete all output, a ValueError is raised:: sage: W.delete_all_output('hacker') Traceback (most recent call last): ... ValueError: user 'hacker' not allowed to edit this worksheet Clean up:: sage: W.quit() sage: nb.delete() """ if not self.user_can_edit(username): raise ValueError("user '%s' not allowed to edit this worksheet" % username) for C in self.cell_list(): C.delete_output()
__internal_test1 = 'def foo(x):\n """\n EXAMPLES:\n sage: 2+2\n 4\n """\n return x\n'.lstrip() __internal_test2 = ''' sage: 2 + 2 4 '''.lstrip()
[docs]def ignore_prompts_and_output(aString): r""" Given a string s that defines an input block of code, if the first line begins in ``sage:`` (or ``>>>``), strip out all lines that don't begin in either ``sage:`` (or ``>>>``) or ``...``, and remove all ``sage:`` (or ``>>>``) and ``...`` from the beginning of the remaining lines. TESTS:: sage: test1 = sagenb.notebook.worksheet.__internal_test1 sage: test1 == sagenb.notebook.worksheet.ignore_prompts_and_output(test1) True sage: test2 = sagenb.notebook.worksheet.__internal_test2 sage: sagenb.notebook.worksheet.ignore_prompts_and_output(test2) '2 + 2\n' """ s = aString.lstrip() is_example = s.startswith('sage:') or s.startswith('>>>') if not is_example: return aString # return original, not stripped copy new = '' lines = s.split('\n') for line in lines: line = line.lstrip() if line.startswith('sage:'): new += after_first_word(line).lstrip() + '\n' elif line.startswith('>>>'): new += after_first_word(line).lstrip() + '\n' elif line.startswith('...'): new += after_first_word(line) + '\n' return new
[docs]def extract_text_before_first_compute_cell(text): """ OUTPUT: Everything in text up to the first {{{. """ i = text.find('{{{') if i == -1: return text return text[:i]
[docs]def extract_first_compute_cell(text): """ INPUT: a block of wiki-like marked up text OUTPUT: - ``meta`` - meta information about the cell (as a dictionary) - ``input`` - string, the input text - ``output`` - string, the output text - ``end`` - integer, first position after }}} in text. """ # Find the input block i = text.find('{{{') if i == -1: raise EOFError j = text[i:].find('\n') if j == -1: raise EOFError k = text[i:].find('|') if k != -1 and k < j: try: meta = dictify(text[i + 3:i + k]) except TypeError: meta = {} i += k + 1 else: meta = {} i += 3 j = text[i:].find('\n}}}') if j == -1: j = len(text) else: j += i k = text[i:].find('\n///') if k == -1 or k + i > j: input = text[i:j] output = '' else: input = text[i:i + k].strip() output = text[i + k + 4:j] return meta, input.strip(), output, j + 4
[docs]def after_first_word(s): r""" Return everything after the first whitespace in the string s. Returns the empty string if there is nothing after the first whitespace. INPUT: - ``s`` - string OUTPUT: a string EXAMPLES:: sage: from sagenb.notebook.worksheet import after_first_word sage: after_first_word("\%gap\n2+2\n") '2+2\n' sage: after_first_word("2+2") '' """ i = whitespace.search(s) if i is None: return '' return s[i.start() + 1:]
[docs]def first_word(s): r""" Returns everything before the first whitespace in the string s. If there is no whitespace, then the entire string s is returned. EXAMPLES:: sage: from sagenb.notebook.worksheet import first_word sage: first_word("\%gap\n2+2\n") '\\%gap' sage: first_word("2+2") '2+2' """ i = whitespace.search(s) if i is None: return s return s[:i.start()]
[docs]def format_completions_as_html(cell_id, completions, username=None): """ Returns tabular HTML code for a list of introspection completions. INPUT: - ``cell_id`` - an integer or a string; the ID of the ambient cell - ``completions`` - a nested list of completions in row-major order OUTPUT: - a string """ if len(completions) == 0: return '' return template(os.path.join("html", "worksheet", "completions.html"), cell_id = cell_id, # Transpose and enumerate completions to column-major completions_enumerated = enumerate(map(list, zip(*completions))))
[docs]def extract_name(text): # The first line is the title i = non_whitespace.search(text) if i is None: name = _('Untitled') n = 0 else: i = i.start() j = text[i:].find('\n') if j != -1: name = text[i:i + j] n = j + 1 else: name = text[i:] n = len(text) - 1 return name.strip(), n
[docs]def extract_system(text): # If the first line is "system: ..." , then it is the system. Otherwise the system is Sage. i = non_whitespace.search(text) if i is None: return 'sage', 0 else: i = i.start() if not text[i:].startswith('system:'): return 'sage', 0 j = text[i:].find('\n') if j != -1: system = text[i:i + j][7:].strip() n = j + 1 else: system = text[i:][7:].strip() n = len(text) - 1 return system, n
[docs]def dictify(s): """ INPUT: - ``s`` - a string like 'in=5, out=7' OUTPUT: - ``dict`` - such as 'in':5, 'out':7 """ w = [] try: for v in s.split(','): a, b = v.strip().split('=') try: b = eval(b) except: pass w.append([a, b]) except ValueError: return {} return dict(w)
[docs]def next_available_id(v): """ Return smallest nonnegative integer not in v. """ i = 0 while i in v: i += 1 return i
[docs]def convert_time_to_string(t): """ Converts ``t`` (in Unix time) to a locale-specific string describing the time and date. """ from flask_babel import format_datetime import datetime, time try: return format_datetime(datetime.datetime.fromtimestamp(float(t))) except AttributeError: #testing as opposed to within the Flask app return time.strftime('%B %d, %Y %I:%M %p', time.localtime(float(t)))
# For pybabel lazy_gettext('January') lazy_gettext('February') lazy_gettext('March') lazy_gettext('April') lazy_gettext('May') lazy_gettext('June') lazy_gettext('July') lazy_gettext('August') lazy_gettext('September') lazy_gettext('October') lazy_gettext('November') lazy_gettext('December')
[docs]def split_search_string_into_keywords(s): r""" The point of this function is to allow for searches like this:: "ws 7" foo bar Modular '"the" end' i.e., where search terms can be in quotes and the different quote types can be mixed. INPUT: - ``s`` - a string OUTPUT: - ``list`` - a list of strings """ ans = [] while len(s) > 0: word, i = _get_next(s, '"') if i != -1: ans.append(word) s = s[i:] word, j = _get_next(s, "'") if j != -1: ans.append(word) s = s[j:] if i == -1 and j == -1: break ans.extend(s.split()) return ans
def _get_next(s, quote='"'): i = s.find(quote) if i != -1: j = s[i + 1:].find(quote) if j != -1: return s[i + 1:i + 1 + j].strip(), i + 1 + j return None, -1