# -*- 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 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
##########################################################
[docs] def satisfies_search(self, search):
"""
Return True if all words in search are in the saved text of the
worksheet.
INPUT:
- ``search`` - a string that describes a search query, i.e., a
space-separated collections of words.
OUTPUT:
- a boolean
"""
# Load the worksheet data file from disk.
filename = self.worksheet_html_filename()
if os.path.exists(filename):
contents = open(filename).read().decode('utf-8', 'ignore')
else:
contents = u' '
try:
r = [unicode(x.lower()) for x in [self.owner(), self.publisher(), self.name(), contents]]
r = u" ".join(r)
except UnicodeDecodeError:
return False
# Check that every single word is in the file from disk.
for W in split_search_string_into_keywords(search):
W = unicode_str(W)
if W.lower() not in r:
# Some word from the text is not in the search list, so
# we return False.
return False
# Every single word is there.
return True
##########################################################
# 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(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.
##########################################################
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 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 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