# -*- coding: utf-8 -*
"""
The Sage Notebook
AUTHORS:
- William Stein
"""
#############################################################################
#
# Copyright (C) 2006-2009 William Stein <wstein@gmail.com>
# Distributed under the terms of the GNU General Public License (GPL)
# The full text of the GPL is available at:
# http://www.gnu.org/licenses/
#
#############################################################################
from __future__ import print_function, absolute_import
# For debugging sometimes it is handy to use only the reference implementation.
USE_REFERENCE_WORKSHEET_PROCESSES = False
# System libraries
import os
import random
import re
import shutil
import socket
import time
import bz2
from cgi import escape
try:
import cPickle as pickle
except ImportError:
import pickle
try:
basestring
except NameError:
basestring = (str, bytes)
from six import iteritems
# Sage libraries
from sagenb.misc.misc import (pad_zeros, cputime, tmp_dir, load, save,
ignore_nonexistent_files, unicode_str)
# Sage Notebook
from . import css # style
from . import js # javascript
from . import worksheet # individual worksheets (which make up a notebook)
from . import config # internal configuration stuff (currently, just keycodes)
from . import keyboards # keyboard layouts
from . import server_conf # server configuration
from . import user_conf # user configuration
from . import user # users
from .template import template, prettify_time_ago
from flask_babel import gettext, lazy_gettext
try:
# sage is installed
import sage
# [(string: name, bool: optional)]
SYSTEMS = [('sage', False),
('gap', False),
('gp', False),
('html', False),
('latex', False),
('maxima', False),
('python', False),
('r', False),
('sh', False),
('singular', False),
('axiom', True),
('fricas', True),
('kash', True),
('macaulay2', True),
('magma', True),
('maple', True,),
('mathematica', True),
('matlab', True),
('mupad', True),
('octave', True),
('scilab', True)]
except ImportError:
# sage is not installed
SYSTEMS = [('sage', True)] # but gracefully degenerated version of sage mode, e.g., preparsing is trivial
# We also record the system names without (optional) since they are
# used in some of the html menus, etc.
SYSTEM_NAMES = [v[0] for v in SYSTEMS]
MATHJAX = True
JEDITABLE_TINYMCE = True
[docs]class WorksheetDict(dict):
def __init__(self, notebook, *args, **kwds):
self.notebook = notebook
self.storage = notebook._Notebook__storage
dict.__init__(self, *args, **kwds)
def __getitem__(self, item):
if item in self:
return dict.__getitem__(self, item)
try:
if '/' not in item:
raise KeyError(item)
except TypeError:
raise KeyError(item)
username, id = item.split('/')
try:
id=int(id)
except ValueError:
raise KeyError(item)
try:
worksheet = self.storage.load_worksheet(username, id)
except ValueError:
raise KeyError(item)
dict.__setitem__(self, item, worksheet)
return worksheet
[docs]class Notebook(object):
HISTORY_MAX_OUTPUT = 92*5
HISTORY_NCOLS = 90
def __init__(self, dir, user_manager = None):
if isinstance(dir, basestring) and len(dir) > 0 and dir[-1] == "/":
dir = dir[:-1]
if not dir.endswith('.sagenb'):
raise ValueError("dir (=%s) must end with '.sagenb'" % dir)
self._dir = dir
# For now we only support the FilesystemDatastore storage
# backend.
from sagenb.storage import FilesystemDatastore
S = FilesystemDatastore(dir)
self.__storage = S
# Now set the configuration, loaded from the datastore.
try:
self.__conf = S.load_server_conf()
except IOError:
# Worksheet has never been saved before, so the server conf doesn't exist.
self.__worksheets = WorksheetDict(self)
from sagenb.notebook.user_manager import SimpleUserManager, OpenIDUserManager
self._user_manager = OpenIDUserManager(conf=self.conf()) if user_manager is None else user_manager
# Set up email notification logger
import logging
from sagenb.notebook.notification import logger, TwistedEmailHandler
logger.addHandler(TwistedEmailHandler(self.conf(), logging.ERROR))
# also log to stderr
logger.addHandler(logging.StreamHandler())
# Set the list of users
try:
S.load_users(self._user_manager)
except IOError:
pass
# Set the list of worksheets
W = WorksheetDict(self)
self.__worksheets = W
# Store / Refresh public worksheets
for id_number in os.listdir(self.__storage._abspath(self.__storage._user_path("pub"))):
if id_number.isdigit():
a = "pub/"+str(id_number)
if a not in self.__worksheets:
try:
self.__worksheets[a] = self.__storage.load_worksheet("pub",int(id_number))
except Exception:
import traceback
print("Warning: problem loading %s/%s: %s" % ("pub", int(id_number), traceback.format_exc()))
# Set the openid-user dict
try:
self._user_manager.load(S)
except IOError:
pass
[docs] def delete(self):
"""
Delete all files related to this notebook.
This is used for doctesting mainly. This command is obviously
*VERY* dangerous to use on a notebook you actually care about.
You could easily lose all data.
EXAMPLES::
sage: tmp = tmp_dir(ext='.sagenb')
sage: nb = sagenb.notebook.notebook.Notebook(tmp)
sage: sorted(os.listdir(tmp))
['home']
sage: nb.delete()
Now the directory is gone.::
sage: os.listdir(tmp)
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory: '...
"""
self.__storage.delete()
[docs] def systems(self, username=None):
systems = []
for system in SYSTEMS:
if system[1]:
systems.append(system[0] + ' (' + lazy_gettext('optional') + ')')
else:
systems.append(system[0])
return systems
[docs] def system_names(self):
return SYSTEM_NAMES
[docs] def user_manager(self):
"""
Returns self's UserManager object.
EXAMPLES::
sage: n = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: n.user_manager()
<sagenb.notebook.user_manager.OpenIDUserManager object at 0x...>
"""
return self._user_manager
##########################################################
# Users
##########################################################
[docs] def create_default_users(self, passwd):
"""
Create the default users for a notebook.
INPUT:
- ``passwd`` - a string
EXAMPLES::
sage: from six import iteritems
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: sorted(list(iteritems(nb.user_manager().users())))
[('_sage_', _sage_), ('admin', admin), ('guest', guest), ('pub', pub)]
sage: sorted(list(iteritems(nb.user_manager().passwords()))) #random
[('_sage_', ''), ('admin', ''), ('guest', ''), ('pub', '')]
sage: nb.create_default_users('newpassword')
WARNING: User 'pub' already exists -- and is now being replaced.
WARNING: User '_sage_' already exists -- and is now being replaced.
WARNING: User 'guest' already exists -- and is now being replaced.
WARNING: User 'admin' already exists -- and is now being replaced.
sage: sorted(list(iteritems(nb.user_manager().passwords()))) #random
[('_sage_', ''), ('admin', ''), ('guest', ''), ('pub', '')]
sage: len(list(iteritems(nb.user_manager().passwords())))
4
"""
self.user_manager().create_default_users(passwd)
[docs] def user(self, username):
"""
Return an instance of the User class given the ``username`` of a user
in a notebook.
INPUT:
- ``username`` - a string
OUTPUT:
- an instance of User
EXAMPLES::
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.user_manager().create_default_users('password')
sage: nb.user('admin')
admin
sage: nb.user('admin').get_email()
''
sage: nb.user('admin').password() #random
'256$7998210096323979f76e9fedaf1f85bda1561c479ae732f9c1f1abab1291b0b9$373f16b9d5fab80b9a9012af26a6b2d52d92b6d4b64c1836562cbd4264a6e704'
"""
return self.user_manager().user(username)
[docs] def valid_login_names(self):
"""
Return a list of users that can log in.
OUTPUT:
- a list of strings
EXAMPLES::
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: nb.valid_login_names()
['admin']
sage: nb.user_manager().add_user('Mark', 'password', '', force=True)
sage: nb.user_manager().add_user('Sarah', 'password', '', force=True)
sage: nb.user_manager().add_user('David', 'password', '', force=True)
sage: sorted(nb.valid_login_names())
['David', 'Mark', 'Sarah', 'admin']
"""
return self.user_manager().valid_login_names()
[docs] def readonly_user(self, username):
"""
Returns True if the user is supposed to only be a read-only user.
"""
return self.__storage.readonly_user(username)
##########################################################
# Publishing worksheets
##########################################################
def _initialize_worksheet(self, src, W):
r"""
Initialize a new worksheet from a source worksheet.
INPUT:
- ``src`` - a Worksheet instance; the source
- ``W`` - a new Worksheet instance; the target
"""
# Note: Each Worksheet method *_directory actually creates a
# directory, if it doesn't already exist.
# More compact, but significantly less efficient?
# shutil.rmtree(W.cells_directory(), ignore_errors=True)
# shutil.rmtree(W.data_directory(), ignore_errors=True)
# shutil.rmtree(W.snapshots_directory(), ignore_errors=True)
# shutil.copytree(src.cells_directory(), W.cells_directory())
# shutil.copytree(src.data_directory(), W.data_directory())
for sub in ['cells', 'data', 'snapshots']:
target_dir = os.path.join(W.directory(), sub)
if os.path.exists(target_dir):
shutil.rmtree(target_dir, ignore_errors=True)
# Copy images, data files, etc.
for sub in ['cells', 'data']:
source_dir = os.path.join(src.directory(), sub)
if os.path.exists(source_dir):
target_dir = os.path.join(W.directory(), sub)
shutil.copytree(source_dir, target_dir)
W.edit_save(src.edit_text())
W.save()
[docs] def pub_worksheets(self):
path = self.__storage._abspath(self.__storage._user_path("pub"))
v = []
a = ""
for id_number in os.listdir(path):
if id_number.isdigit():
a = "pub"+"/"+id_number
if a in self.__worksheets:
v.append(self.__worksheets[a])
else:
try:
self.__worksheets[a] = self.__storage.load_worksheet("pub", int(id_number))
v.append(self.__worksheets[a])
except Exception:
import traceback
print("Warning: problem loading %s/%s: %s" % ("pub", id_number, traceback.format_exc()))
return v
[docs] def users_worksheets(self, username):
r"""
Returns all worksheets owned by `username`
"""
if username == "pub":
return self.pub_worksheets()
worksheets = self.__storage.worksheets(username)
# if a worksheet has already been loaded in self.__worksheets, return
# that instead since worksheets that are already running should be
# noted as such
return [self.__worksheets[w.filename()] if w.filename() in self.__worksheets else w for w in worksheets]
[docs] def users_worksheets_view(self, username):
r"""
Returns all worksheets viewable by `username`
"""
# Should return worksheets from self.__worksheets if possible
worksheets = self.users_worksheets(username)
user=self.user_manager().user(username)
viewable_worksheets=[self.__storage.load_worksheet(owner, id) for owner,id in user.viewable_worksheets()]
# we double-check that we can actually view these worksheets
# just in case someone forgets to update the map
worksheets.extend([w for w in viewable_worksheets if w.is_viewer(username)])
# if a worksheet has already been loaded in self.__worksheets, return that instead
# since worksheets that are already running should be noted as such
return [self.__worksheets[w.filename()] if w.filename() in self.__worksheets else w for w in worksheets]
[docs] def publish_worksheet(self, worksheet, username):
r"""
Publish a user's worksheet. This creates a new worksheet in
the 'pub' directory with the same contents as ``worksheet``.
INPUT:
- ``worksheet`` - an instance of Worksheet
- ``username`` - a string
OUTPUT:
- a new or existing published instance of Worksheet
EXAMPLES::
sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb'))
sage: nb.user_manager().add_user('Mark','password','',force=True)
sage: W = nb.new_worksheet_with_title_from_text('First steps', owner='Mark')
sage: nb.worksheet_names()
['Mark/0']
sage: nb.create_default_users('password')
sage: nb.publish_worksheet(nb.get_worksheet_with_filename('Mark/0'), 'Mark')
pub/0: [Cell 1: in=, out=]
sage: sorted(nb.worksheet_names())
['Mark/0', 'pub/0']
"""
W = None
# Reuse an existing published version
for X in self.get_worksheets_with_owner('pub'):
if (X.worksheet_that_was_published() == worksheet):
W = X
# Or create a new one.
if W is None:
W = self.create_new_worksheet(worksheet.name(), 'pub')
# Copy cells, output, data, etc.
self._initialize_worksheet(worksheet, W)
# Update metadata.
W.set_worksheet_that_was_published(worksheet)
W.move_to_archive(username)
worksheet.set_published_version(W.filename())
W.record_edit(username)
W.set_name(worksheet.name())
self.__worksheets[W.filename()] = W
W.save()
return W
##########################################################
# Moving, copying, creating, renaming, and listing worksheets
##########################################################
[docs] def scratch_worksheet(self):
try:
return self.__scratch_worksheet
except AttributeError:
W = self.create_new_worksheet('scratch', '_sage_')
self.__scratch_worksheet = W
return W
[docs] def create_new_worksheet(self, worksheet_name, username):
if username!='pub' and self.user_manager().user_is_guest(username):
raise ValueError("guests cannot create new worksheets")
W = self.worksheet(username)
W.set_system(self.system(username))
W.set_name(worksheet_name)
self.save_worksheet(W)
self.__worksheets[W.filename()] = W
return W
[docs] def copy_worksheet(self, ws, owner):
W = self.create_new_worksheet('default', owner)
self._initialize_worksheet(ws, W)
name = "Copy of %s" % ws.name()
W.set_name(name)
return W
[docs] def delete_worksheet(self, filename):
"""
Delete the given worksheet and remove its name from the worksheet
list. Raise a KeyError, if it is missing.
INPUT:
- ``filename`` - a string
"""
try:
W = self.__worksheets[filename]
except KeyError:
raise KeyError("Attempt to delete missing worksheet '%s'" % filename)
W.quit()
shutil.rmtree(W.directory(), ignore_errors=False)
self.deleted_worksheets()[filename] = W
[docs] def deleted_worksheets(self):
try:
return self.__deleted_worksheets
except AttributeError:
self.__deleted_worksheets = {}
return self.__deleted_worksheets
[docs] def empty_trash(self, username):
"""
Empty the trash for the given user.
INPUT:
- ``username`` - a string
This empties the trash for the given user and cleans up all files
associated with the worksheets that are in the trash.
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.new_worksheet_with_title_from_text('Sage', owner='sage')
sage: W._notebook = nb
sage: W.move_to_trash('sage')
sage: nb.worksheet_names()
['sage/0']
sage: nb.empty_trash('sage')
sage: nb.worksheet_names()
[]
"""
X = self.get_worksheets_with_viewer(username)
X = [W for W in X if W.is_trashed(username)]
for W in X:
W.delete_user(username)
if W.owner() is None:
self.delete_worksheet(W.filename())
[docs] def worksheet_names(self):
"""
Return a list of all the names of worksheets in this notebook.
OUTPUT:
- a list of strings.
EXAMPLES:
We make a new notebook with two users and two worksheets,
then list their names::
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.new_worksheet_with_title_from_text('Sage', owner='sage')
sage: nb.user_manager().add_user('wstein','sage','wstein@sagemath.org',force=True)
sage: W2 = nb.new_worksheet_with_title_from_text('Elliptic Curves', owner='wstein')
sage: nb.worksheet_names()
['sage/0', 'wstein/0']
"""
W = self.__worksheets.keys()
W.sort()
return W
##########################################################
# Information about the pool of worksheet compute servers
##########################################################
[docs] def server_pool(self):
return self.conf()['server_pool']
[docs] def set_server_pool(self, servers):
self.conf()['server_pool'] = servers
[docs] def get_ulimit(self):
try:
return self.__ulimit
except AttributeError:
self.__ulimit = ''
return ''
[docs] def set_ulimit(self, ulimit):
self.__ulimit = ulimit
[docs] def get_server(self):
P = self.server_pool()
if P is None or len(P) == 0:
return None
try:
self.__server_number = (self.__server_number + 1) % len(P)
i = self.__server_number
except AttributeError:
self.__server_number = 0
i = 0
return P[i]
[docs] def new_worksheet_process(self):
"""
Return a new worksheet process object with parameters determined by
configuration of this notebook server.
"""
from sagenb.interfaces import (WorksheetProcess_ExpectImplementation,
WorksheetProcess_ReferenceImplementation,
WorksheetProcess_RemoteExpectImplementation)
if USE_REFERENCE_WORKSHEET_PROCESSES:
return WorksheetProcess_ReferenceImplementation()
ulimit = self.get_ulimit()
from sagenb.interfaces import ProcessLimits
# We have to parse the ulimit format to our ProcessLimits.
# The typical format is.
# '-u 400 -v 1000000 -t 3600'
# Despite -t being cputime for ulimit, we map it to walltime,
# since that is the only thing that really makes sense for a
# notebook server.
# -u --> max_processes
# -v --> max_vmem (but we divide by 1000)
# -t -- > max_walltime
max_vmem = max_cputime = max_walltime = None
tbl = {'v': None, 'u': None, 't': None}
for x in ulimit.split('-'):
for k in tbl.keys():
if x.startswith(k):
tbl[k] = int(x.split()[1].strip())
if tbl['v'] is not None:
tbl['v'] = tbl['v'] / 1000.0
process_limits = ProcessLimits(max_vmem=tbl['v'], max_walltime=tbl['t'],
max_processes=tbl['u'])
server_pool = self.server_pool()
if not server_pool or len(server_pool) == 0:
return WorksheetProcess_ExpectImplementation(process_limits=process_limits)
else:
import random
user_at_host = random.choice(server_pool)
python_command = os.path.join(os.environ['SAGE_ROOT'], 'sage -python')
return WorksheetProcess_RemoteExpectImplementation(user_at_host=user_at_host,
process_limits=process_limits,
remote_python=python_command)
def _python_command(self):
"""
"""
try:
return self.__python_command
except AttributeError:
pass
##########################################################
# Configuration settings.
##########################################################
[docs] def system(self, username=None):
"""
The default math software system for new worksheets for a
given user or the whole notebook (if username is None).
"""
return self.user(username).conf()['default_system']
[docs] def pretty_print(self, username=None):
"""
The default typeset setting for new worksheets for
a given user or the whole notebook (if username is None).
TODO -- only implemented for the notebook right now
"""
return self.user(username).conf()['default_pretty_print']
[docs] def set_pretty_print(self, pretty_print):
self.__pretty_print = pretty_print
[docs] def color(self):
"""
The default color scheme for the notebook.
"""
try:
return self.__color
except AttributeError:
self.__color = 'default'
return self.__color
[docs] def set_color(self, color):
self.__color = color
##########################################################
# The notebook history.
##########################################################
[docs] def user_history(self, username):
if not hasattr(self, '_user_history'):
self._user_history = {}
if username in self._user_history:
return self._user_history[username]
history = []
for hunk in self.__storage.load_user_history(username):
hunk = unicode_str(hunk)
history.append(hunk)
self._user_history[username] = history
return history
[docs] def create_new_worksheet_from_history(self, name, username, maxlen=None):
W = self.create_new_worksheet(name, username)
W.edit_save('Log Worksheet\n' + self.user_history_text(username, maxlen=None))
return W
[docs] def user_history_text(self, username, maxlen=None):
history = self.user_history(username)
if maxlen:
history = history[-maxlen:]
return '\n\n'.join([hunk.strip() for hunk in history])
[docs] def add_to_user_history(self, entry, username):
history = self.user_history(username)
history.append(entry)
maxlen = self.user_manager().user_conf(username)['max_history_length']
while len(history) > maxlen:
del history[0]
##########################################################
# Importing and exporting worksheets to files
##########################################################
[docs] def export_worksheet(self, worksheet_filename, output_filename, title=None):
"""
Export a worksheet, creating a sws file on the file system.
INPUT:
- ``worksheet_filename`` - a string e.g., 'username/id_number'
- ``output_filename`` - a string, e.g., 'worksheet.sws'
- ``title`` - title to use for the exported worksheet (if
None, just use current title)
"""
S = self.__storage
W = self.get_worksheet_with_filename(worksheet_filename)
S.save_worksheet(W)
username = W.owner()
id_number = W.id_number()
S.export_worksheet(username, id_number, output_filename, title=title)
[docs] def worksheet(self, username, id_number=None):
"""
Create a new worksheet with given id_number belonging to the
user with given username, or return an already existing
worksheet. If id_number is None, creates a new worksheet
using the next available new id_number for the given user.
INPUT:
- ``username`` -- string
- ``id_number`` - nonnegative integer or None (default)
"""
S = self.__storage
if id_number is None:
id_number = self.new_id_number(username)
try:
W = S.load_worksheet(username, id_number)
except ValueError:
W = S.create_worksheet(username, id_number)
self.__worksheets[W.filename()] = W
return W
[docs] def new_id_number(self, username):
"""
Find the next worksheet id for the given user.
"""
u = self.user(username).conf()
id_number = u['next_worksheet_id_number']
if id_number == -1: # need to initialize
id_number = max([w.id_number() for w in self.worksheet_list_for_user(username)] + [-1]) + 1
u['next_worksheet_id_number'] = id_number + 1
return id_number
[docs] def new_worksheet_with_title_from_text(self, text, owner):
name, _ = worksheet.extract_name(text)
W = self.create_new_worksheet(name, owner)
return W
[docs] def change_worksheet_key(self, old_key, new_key):
ws = self.__worksheets
W = ws[old_key]
ws[new_key] = W
del ws[old_key]
[docs] def import_worksheet(self, filename, owner):
r"""
Import a worksheet with the given ``filename`` and set its
``owner``. If the file extension is not recognized, raise a
ValueError.
INPUT:
- ``filename`` - a string
- ``owner`` - a string
OUTPUT:
- ``worksheet`` - a newly created Worksheet instance
EXAMPLES:
We create a notebook and import a plain text worksheet
into it.
::
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: name = tmp_filename() + '.txt'
sage: open(name,'w').write('foo\n{{{\n2+3\n}}}')
sage: W = nb.import_worksheet(name, 'admin')
W is our newly-created worksheet, with the 2+3 cell in it::
sage: W.name()
u'foo'
sage: W.cell_list()
[TextCell 0: foo, Cell 1: in=2+3, out=]
"""
if not os.path.exists(filename):
raise ValueError("no file %s" % filename)
# Figure out the file extension
ext = os.path.splitext(filename)[1]
if ext.lower() == '.txt':
# A plain text file with {{{'s that defines a worksheet (no graphics).
W = self._import_worksheet_txt(filename, owner)
elif ext.lower() == '.sws':
# An sws file (really a tar.bz2) which defines a worksheet with graphics, etc.
W = self._import_worksheet_sws(filename, owner)
elif ext.lower() == '.html':
# An html file, which should contain the static version of
# a sage help page, as generated by Sphinx
html = open(filename).read()
cell_pattern = r"""{{{id=.*?///.*?}}}"""
docutils_pattern = r"""<meta name="generator" content="Docutils \S+: http://docutils\.sourceforge\.net/" />"""
sphinx_pattern = r"""Created using <a href="http://sphinx\.pocoo\.org/">Sphinx</a>"""
if re.search(cell_pattern, html, re.DOTALL) is not None:
W = self._import_worksheet_txt(filename, owner)
elif re.search(docutils_pattern, html) is not None:
W = self._import_worksheet_docutils_html(filename, owner)
elif re.search(sphinx_pattern, html) is not None:
W = self._import_worksheet_html(filename, owner)
else:
# Unrecognized html file
# We do the default behavior, i.e. we import as if it was generated
# by Sphinx web page
W = self._import_worksheet_html(filename, owner)
elif ext.lower() == '.rst':
# A ReStructuredText file
W = self._import_worksheet_rst(filename, owner)
else:
# We only support txt, sws, html and rst files
raise ValueError("unknown extension '%s'" % ext)
self.__worksheets[W.filename()] = W
return W
def _import_worksheet_txt(self, filename, owner):
r"""
Import a plain text file as a new worksheet.
INPUT:
- ``filename`` - a string; a filename that ends in .txt
- ``owner`` - a string; the imported worksheet's owner
OUTPUT:
- a new instance of Worksheet
EXAMPLES:
We write a plain text worksheet to a file and import it
using this function.::
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: name = tmp_filename() + '.txt'
sage: open(name,'w').write('foo\n{{{\na = 10\n}}}')
sage: W = nb._import_worksheet_txt(name, 'admin'); W
admin/0: [TextCell 0: foo, Cell 1: in=a = 10, out=]
"""
# Open the worksheet txt file and load it in.
worksheet_txt = open(filename).read()
# Create a new worksheet with the right title and owner.
worksheet = self.new_worksheet_with_title_from_text(worksheet_txt, owner)
# Set the new worksheet to have the contents specified by that file.
worksheet.edit_save(worksheet_txt)
return worksheet
def _import_worksheet_sws(self, filename, username):
r"""
Import an sws format worksheet into this notebook as a new
worksheet.
INPUT:
- ``filename`` - a string; a filename that ends in .sws;
internally it must be a tar'd bz2'd file.
- ``username`` - a string
OUTPUT:
- a new Worksheet instance
EXAMPLES:
We create a notebook, then make a worksheet from a plain text
file first.::
sage: nb = sagenb.notebook.notebook.load_notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: name = tmp_filename() + '.txt'
sage: open(name,'w').write('{{{id=0\n2+3\n}}}')
sage: W = nb.import_worksheet(name, 'admin')
sage: W.filename()
'admin/0'
sage: sorted([w.filename() for w in nb.get_all_worksheets()])
['admin/0']
We then export the worksheet to an sws file.::
sage: sws = os.path.join(tmp_dir(), 'tmp.sws')
sage: nb.export_worksheet(W.filename(), sws)
Now we import the sws.::
sage: W = nb._import_worksheet_sws(sws, 'admin')
sage: nb._Notebook__worksheets[W.filename()] = W
Yes, it's there now (as a new worksheet)::
sage: sorted([w.filename() for w in nb.get_all_worksheets()])
['admin/0', 'admin/1']
"""
id_number = self.new_id_number(username)
worksheet = self.__storage.import_worksheet(username, id_number, filename)
# I'm not at all convinced this is a good idea, since we
# support multiple worksheets with the same title very well
# already. So it's commented out.
# self.change_worksheet_name_to_avoid_collision(worksheet)
return worksheet
def _import_worksheet_html(self, filename, owner):
r"""
Import a static html help page generated by Sphinx as a new
worksheet.
INPUT:
- ``filename`` - a string; a filename that ends in .html
- ``owner`` - a string; the imported worksheet's owner
OUTPUT:
- a new instance of Worksheet
EXAMPLES:
We write a plain text worksheet to a file and import it
using this function.::
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: name = tmp_filename() + '.html'
sage: fd = open(name,'w')
sage: fd.write(''.join([
....: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n',
....: ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n',
....: '\n',
....: '<html xmlns="http://www.w3.org/1999/xhtml">\n',
....: ' <head>\n',
....: ' <title>Test notebook — test</title>\n',
....: ' </head>\n',
....: ' <body>\n',
....: ' <div class="document">\n',
....: ' <div class="documentwrapper">\n',
....: ' <div class="bodywrapper">\n',
....: ' <div class="body">\n',
....: '<p>Here are some computations:</p>\n',
....: '\n',
....: '<div class="highlight-python"><div class="highlight"><pre>\n',
....: '<span class="gp">sage',
....: ': </span><span class="mi">1</span><span class="o">+</span><span class="mi">1</span>\n',
....: '<span class="go">2</span>\n',
....: '</pre></div></div>\n',
....: '\n',
....: '</div></div></div></div>\n',
....: '</body></html>']))
sage: fd.close()
sage: W = nb._import_worksheet_html(name, 'admin')
sage: W.name()
u'Test notebook -- test'
sage: W.owner()
'admin'
sage: W.cell_list()
[TextCell 1: <div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body">
<p>Here are some computations:</p>
<BLANKLINE>
<div class="highlight-python">, Cell 0: in=1+1, out=
2, TextCell 2: </div>
<BLANKLINE>
</div></div></div></div>]
sage: cell = W.cell_list()[1]
sage: cell.input_text()
u'1+1'
sage: cell.output_text()
u'<pre class="shrunk">2</pre>'
"""
# Inspired from sagenb.notebook.twist.WorksheetFile.render
doc_page_html = open(filename).read()
from .docHTMLProcessor import SphinxHTMLProcessor
# FIXME: does SphinxHTMLProcessor raise an appropriate message
# if the html file does not contain a Sphinx HTML page?
doc_page = SphinxHTMLProcessor().process_doc_html(doc_page_html)
from .misc import extract_title
title = extract_title(doc_page_html).replace('—','--')
worksheet = self.create_new_worksheet(title, owner)
worksheet.edit_save(doc_page)
# FIXME: An extra compute cell is always added to the end.
# Pop it off.
cells = worksheet.cell_list()
cells.pop()
return worksheet
def _import_worksheet_rst(self, filename, owner):
r"""
Import a ReStructuredText file as a new worksheet.
INPUT:
- ``filename`` - a string; a filename that ends in .rst
- ``owner`` - a string; the imported worksheet's owner
OUTPUT:
- a new instance of Worksheet
EXAMPLES:
sage: sprompt = 'sage' + ':'
sage: rst = '\n'.join(['=============',
....: 'Test Notebook',
....: '=============',
....: '',
....: 'Let\'s do some computations::',
....: '',
....: ' %s 2+2' % sprompt,
....: ' 4',
....: '',
....: '::',
....: '',
....: ' %s x^2' % sprompt,
....: ' x^2'])
sage: name = tmp_filename() + '.rst'
sage: fd = open(name,'w')
sage: fd.write(rst)
sage: fd.close()
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: W = nb._import_worksheet_rst(name, 'admin')
sage: W.name()
u'Test Notebook'
sage: W.owner()
'admin'
sage: W.cell_list()
[TextCell 2: <h1 class="title">Test Notebook</h1>
<BLANKLINE>
<p>Let's do some computations:</p>, Cell 0: in=2+2, out=
4, Cell 1: in=x^2, out=
x^2]
sage: cell = W.cell_list()[1]
sage: cell.input_text()
u'2+2'
sage: cell.output_text()
u'<pre class="shrunk">4</pre>'
"""
rst = open(filename).read()
# docutils removes the backslashes if they are not escaped This is
# not practical because backslashes are almost never escaped in
# Sage docstrings written in ReST. So if the user wants the
# backslashes to be escaped automatically, he adds the comment
# ".. escape-backslashes" in the input file
if re.search(r'^\.\.[ \t]+escape-backslashes', rst, re.MULTILINE) is not None:
rst = rst.replace('\\','\\\\')
# Do the translation rst -> html (using docutils)
from docutils.core import publish_parts
D = publish_parts(rst, writer_name='html')
title = D['title']
html = D['whole']
# Do the translation html -> txt
from .docHTMLProcessor import docutilsHTMLProcessor
translator = docutilsHTMLProcessor()
worksheet_txt = translator.process_doc_html(html)
# Create worksheet
worksheet = self.create_new_worksheet(title, owner)
worksheet.edit_save(worksheet_txt)
return worksheet
def _import_worksheet_docutils_html(self, filename, owner):
r"""
Import a static html help page generated by docutils as a new
worksheet.
INPUT:
- ``filename`` - a string; a filename that ends in .html
- ``owner`` - a string; the imported worksheet's owner
OUTPUT:
- a new instance of Worksheet
EXAMPLES:
sage: sprompt = 'sage' + ':'
sage: rst = '\n'.join(['=============',
....: 'Test Notebook',
....: '=============',
....: '',
....: 'Let\'s do some computations::',
....: '',
....: ' %s 2+2' % sprompt,
....: ' 4',
....: '',
....: '::',
....: '',
....: ' %s x^2' % sprompt,
....: ' x^2'])
sage: from docutils.core import publish_string
sage: html = publish_string(rst, writer_name='html')
sage: name = tmp_filename() + '.html'
sage: fd = open(name,'w')
sage: fd.write(html)
sage: fd.close()
sage: nb = sagenb.notebook.notebook.Notebook(tmp_dir(ext='.sagenb'))
sage: nb.create_default_users('password')
sage: W = nb._import_worksheet_docutils_html(name, 'admin')
sage: W.name()
u'Test Notebook'
sage: W.owner()
'admin'
sage: W.cell_list()
[TextCell 2: <h1 class="title">Test Notebook</h1>
<BLANKLINE>
<p>Let's do some computations:</p>, Cell 0: in=2+2, out=
4, Cell 1: in=x^2, out=
x^2]
sage: cell = W.cell_list()[1]
sage: cell.input_text()
u'2+2'
sage: cell.output_text()
u'<pre class="shrunk">4</pre>'
"""
html = open(filename).read()
# Do the translation html -> txt
from .docHTMLProcessor import docutilsHTMLProcessor
translator = docutilsHTMLProcessor()
worksheet_txt = translator.process_doc_html(html)
# Extract title
from .worksheet import extract_name
title, _ = extract_name(worksheet_txt)
if title.startswith('<h1 class="title">'):
title = title[18:]
if title.endswith('</h1>'):
title = title[:-5]
# Create worksheet
worksheet = self.create_new_worksheet(title, owner)
worksheet.edit_save(worksheet_txt)
return worksheet
[docs] def change_worksheet_name_to_avoid_collision(self, worksheet):
"""
Change the display name of the worksheet if there is already a
worksheet with the same name as this one.
"""
name = worksheet.name()
display_names = [w.name() for w in self.get_worksheets_with_owner(worksheet.owner())]
if name in display_names:
j = name.rfind('(')
if j != -1:
name = name[:j].rstrip()
i = 2
while name + " (%s)" % i in display_names:
i += 1
name = name + " (%s)" % i
worksheet.set_name(name)
##########################################################
# Server configuration
##########################################################
[docs] def conf(self):
try:
return self.__conf
except AttributeError:
C = server_conf.ServerConfiguration()
# if we are newly creating a notebook, then we want to
# have a default model version of 1, currently
# we can't just set the default value in server_conf.py
# to 1 since it would then be 1 for notebooks without the
# model_version property
# TODO: distinguish between a new server config default values
# and default values for missing properties
C['model_version']=1
self.__conf = C
return C
##########################################################
# Computing control
##########################################################
[docs] def set_not_computing(self):
# unpickled, no worksheets will think they are
# being computed, since they clearly aren't (since
# the server just started).
for W in self.__worksheets.values():
W.set_not_computing()
[docs] def quit(self):
for W in list(self.__worksheets.values()):
W.quit()
[docs] def update_worksheet_processes(self):
worksheet.update_worksheets()
[docs] def quit_idle_worksheet_processes(self):
timeout = self.conf()['idle_timeout']
doc_timeout = self.conf()['doc_timeout']
for W in self.__worksheets.values():
if W.compute_process_has_been_started():
if W.docbrowser():
W.quit_if_idle(doc_timeout)
else:
W.quit_if_idle(timeout)
[docs] def quit_worksheet(self, W):
try:
del self.__worksheets[W.filename()]
except KeyError:
pass
##########################################################
# Worksheet HTML generation
##########################################################
[docs] def worksheet_list_for_public(self, username, sort='last_edited', reverse=False, search=None):
W = self.users_worksheets('pub')
if search:
W = [x for x in W if x.satisfies_search(search)]
sort_worksheet_list(W, sort, reverse) # changed W in place
return W
[docs] def worksheet_list_for_user(self, user, typ="active", sort='last_edited', reverse=False, search=None):
X = self.get_worksheets_with_viewer(user)
if typ == "trash":
W = [x for x in X if x.is_trashed(user)]
elif typ == "active":
W = [x for x in X if x.is_active(user)]
else: # typ must be archived
W = [x for x in X if not (x.is_trashed(user) or x.is_active(user))]
if search:
W = [x for x in W if x.satisfies_search(search)]
sort_worksheet_list(W, sort, reverse) # changed W in place
return W
##########################################################
# Revision history for a worksheet
##########################################################
[docs] def html_worksheet_revision_list(self, username, worksheet):
r"""
Return HTML for the revision list of a worksheet.
INPUT:
- ``username`` - a string
- ``worksheet`` - an instance of Worksheet
OUTPUT:
- a string - the HTML for the revision list
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('Test', 'admin')
sage: W.body()
u'\n\n{{{id=1|\n\n///\n}}}'
sage: W.save_snapshot('admin')
sage: nb.html_worksheet_revision_list('admin', W)
u'...Revision...Last Edited...ago...'
"""
data = worksheet.snapshot_data() # pairs ('how long ago', key)
return template(os.path.join("html", "notebook", "worksheet_revision_list.html"),
data = data, worksheet = worksheet,
notebook = self,
username = username)
[docs] def html_specific_revision(self, username, ws, rev):
r"""
Return the HTML for a specific revision of a worksheet.
INPUT:
- ``username`` - a string
- ``ws`` - an instance of Worksheet
- ``rev`` - a string containing the key of the revision
OUTPUT:
- a string - the revision rendered as HTML
"""
t = time.time() - float(rev[:-4])
time_ago = prettify_time_ago(t)
filename = ws.get_snapshot_text_filename(rev)
txt = bz2.decompress(open(filename).read())
W = self.scratch_worksheet()
W.set_name('Revision of ' + ws.name())
W.delete_cells_directory()
W.edit_save(txt)
data = ws.snapshot_data() # pairs ('how long ago', key)
prev_rev = None
next_rev = None
for i in range(len(data)):
if data[i][1] == rev:
if i > 0:
prev_rev = data[i - 1][1]
if i < len(data)-1:
next_rev = data[i + 1][1]
break
return template(os.path.join("html", "notebook",
"specific_revision.html"),
worksheet = W, # the revision, not the original!
username = username, rev = rev, prev_rev = prev_rev,
next_rev = next_rev, time_ago = time_ago)
[docs] def html_share(self, worksheet, username):
r"""
Return the HTML for the "share" page of a worksheet.
INPUT:
- ``username`` - a string
- ``worksheet`` - an instance of Worksheet
OUTPUT:
- string - the share page's HTML representation
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: nb.html_share(W, 'admin')
u'...currently shared...add or remove collaborators...'
"""
return template(os.path.join("html", "notebook", "worksheet_share.html"),
worksheet = worksheet,
notebook = self,
username = username)
[docs] def html_download_or_delete_datafile(self, ws, username, filename):
r"""
Return the HTML for the download or delete datafile page.
INPUT:
- ``username`` - a string
- ``ws`` - an instance of Worksheet
- ``filename`` - a string; the name of the file
OUTPUT:
- a string - the page rendered as HTML
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: nb.html_download_or_delete_datafile(W, 'admin', 'bar')
u'...Data file: bar...DATA is a special variable...uploaded...'
"""
ext = os.path.splitext(filename)[1].lower()
file_is_image, file_is_text = False, False
text_file_content = ""
if ext in ['.png', '.jpg', '.gif']:
file_is_image = True
if ext in ['.txt', '.tex', '.sage', '.spyx', '.py', '.f', '.f90', '.c']:
file_is_text = True
text_file_content = open(os.path.join(ws.data_directory(), filename)).read()
# Detect the character encoding
from BeautifulSoup import UnicodeDammit
text_file_content = UnicodeDammit(text_file_content).unicode
return template(os.path.join("html", "notebook", "download_or_delete_datafile.html"),
worksheet = ws, notebook = self,
username = username,
filename_ = filename,
file_is_image = file_is_image,
file_is_text = file_is_text,
text_file_content = text_file_content)
##########################################################
# Accessing all worksheets with certain properties.
##########################################################
[docs] def active_worksheets_for(self, username):
# TODO: check if the worksheets are active
#return [ws for ws in self.get_worksheets_with_viewer(username) if ws.is_active(username)]
return self.users_worksheets_view(username)
[docs] def get_all_worksheets(self):
"""
We should only call this if the user is admin!
"""
all_worksheets = []
for username in self._user_manager.users():
if username in ['_sage_', 'pub']:
continue
for w in self.users_worksheets(username):
all_worksheets.append(w)
return all_worksheets
[docs] def get_worksheets_with_viewer(self, username):
if self._user_manager.user_is_admin(username): return self.get_all_worksheets()
return self.users_worksheets_view(username)
[docs] def get_worksheets_with_owner(self, owner):
return self.users_worksheets(owner)
[docs] def get_worksheet_with_filename(self, filename):
"""
Get the worksheet with the given filename. If there is no
such worksheet, raise a ``KeyError``.
INPUT:
- ``filename`` - a string
OUTPUT:
- a Worksheet instance
"""
try:
return self.__worksheets[filename]
except KeyError:
raise KeyError("No worksheet with filename '%s'" % filename)
###########################################################
# Saving the whole notebook
###########################################################
[docs] def save(self):
"""
Save this notebook server to disk.
"""
S = self.__storage
S.save_users(self.user_manager().users())
S.save_server_conf(self.conf())
self._user_manager.save(S)
# Save the non-doc-browser worksheets.
for n, W in self.__worksheets.items():
if not n.startswith('doc_browser'):
S.save_worksheet(W)
if hasattr(self, '_user_history'):
for username, H in iteritems(self._user_history):
S.save_user_history(username, H)
[docs] def save_worksheet(self, W, conf_only=False):
self.__storage.save_worksheet(W, conf_only=conf_only)
[docs] def logout(self, username):
r"""
Do not do anything on logout (so far).
In particular, do **NOT** stop all ``username``'s worksheets!
"""
pass
[docs] def delete_doc_browser_worksheets(self):
for w in self.users_worksheets('_sage_'):
if w.name().startswith('doc_browser'):
self.delete_worksheet(w.filename())
###########################################################
# HTML -- generate most html related to the whole notebook page
###########################################################
[docs] def html_plain_text_window(self, worksheet, username):
r"""
Return HTML for the window that displays a plain text version
of the worksheet.
INPUT:
- ``worksheet`` - a Worksheet instance
- ``username`` - a string
OUTPUT:
- a string - the plain text window rendered as HTML
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: nb.html_plain_text_window(W, 'admin')
u'...pre class="plaintext"...cell_intext...textfield...'
"""
plain_text = worksheet.plain_text(prompts=True, banner=False)
plain_text = escape(plain_text).strip()
return template(os.path.join("html", "notebook", "plain_text_window.html"),
worksheet = worksheet,
notebook = self,
username = username, plain_text = plain_text,
MATHJAX = MATHJAX, JEDITABLE_TINYMCE = JEDITABLE_TINYMCE)
[docs] def html_edit_window(self, worksheet, username):
r"""
Return HTML for a window for editing ``worksheet``.
INPUT:
- ``username`` - a string containing the username
- ``worksheet`` - a Worksheet instance
OUTPUT:
- a string - the editing window's HTML representation
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: nb.html_edit_window(W, 'admin')
u'...textarea class="plaintextedit"...{{{id=1|...//...}}}...'
"""
return template(os.path.join("html", "notebook", "edit_window.html"),
worksheet = worksheet,
notebook = self,
username = username)
[docs] def html_beforepublish_window(self, worksheet, username):
r"""
Return HTML for the warning and decision page displayed prior
to publishing the given worksheet.
INPUT:
- ``worksheet`` - an instance of Worksheet
- ``username`` - a string
OUTPUT:
- a string - the pre-publication page rendered as HTML
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: nb.html_beforepublish_window(W, 'admin')
u'...want to publish this worksheet?...re-publish when changes...'
"""
msg = """You can publish your worksheet to the Internet, where anyone will be able to access and view it online.
Your worksheet will be assigned a unique address (URL) that you can send to your friends and colleagues.<br/><br/>
Do you want to publish this worksheet?<br/><br/>
<form method="get" action=".">
<input type="hidden" name="yes" value="" />
<input type="submit" value="Yes" style="margin-left:10px" />
<input type="button" value="No" style="margin-left:5px" onClick="parent.location=\'../'"><br/><br/>
<input type="checkbox" name="auto" style="margin-left:13px" /> Automatically re-publish when changes are made and saved
</form>
"""
return template(os.path.join("html", "notebook", "beforepublish_window.html"),
worksheet = worksheet,
notebook = self,
username = username)
[docs] def html_afterpublish_window(self, worksheet, username, url, dtime):
r"""
Return HTML for a given worksheet's post-publication page.
INPUT:
- ``worksheet`` - an instance of Worksheet
- ``username`` - a string
- ``url`` - a string representing the URL of the published
worksheet
- ``dtime`` - an instance of time.struct_time representing the
publishing time
OUTPUT:
- a string - the post-publication page rendered as HTML
"""
from time import strftime
time = strftime("%B %d, %Y %I:%M %p", dtime)
return template(os.path.join("html", "notebook", "afterpublish_window.html"),
worksheet = worksheet,
notebook = self,
username = username, url = url, time = time)
[docs] def html_upload_data_window(self, ws, username):
r"""
Return HTML for the "Upload Data" window.
INPUT:
- ``worksheet`` - an instance of Worksheet
- ``username`` - a string
OUTPUT:
- a string - the HTML representation of the data upload window
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: nb.html_upload_data_window(W, 'admin')
u'...Upload or Create Data File...Browse...url...name of a new...'
"""
return template(os.path.join("html", "notebook", "upload_data_window.html"),
worksheet = ws, username = username)
[docs] def html(self, worksheet_filename=None, username='guest', admin=False,
do_print=False):
r"""
Return the HTML for a worksheet's index page.
INPUT:
- ``worksheet_filename`` - a string (default: None)
- ``username`` - a string (default: 'guest')
- ``admin`` - a bool (default: False)
OUTPUT:
- a string - the worksheet rendered as HTML
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: nb.html(W.filename(), 'admin')
u'...Test...cell_input...if (e.shiftKey)...state_number...'
"""
if worksheet_filename is None or worksheet_filename == '':
worksheet_filename = None
W = None
else:
try:
W = self.get_worksheet_with_filename(worksheet_filename)
except KeyError:
W = None
from flask import current_app
if W is None:
return current_app.message(gettext("The worksheet does not exist"), username=username)
if W.docbrowser() or W.is_published():
if W.is_published() or self.user_manager().user_is_guest(username):
template_page = os.path.join('html', 'notebook', 'guest_worksheet_page.html')
else:
template_page = os.path.join("html", "notebook", "doc_page.html")
elif do_print:
template_page = os.path.join('html', 'notebook', 'print_worksheet.html')
else:
template_page = os.path.join("html", "notebook", "worksheet_page.html")
return template(template_page, worksheet = W,
notebook = self, do_print=do_print,
username = username)
[docs] def upgrade_model(self):
"""
Upgrade the model, if needed.
- Version 0 (or non-existent model version, which defaults to 0): Original flask notebook
- Version 1: shared worksheet data cached in the User object
"""
model_version=self.conf()['model_version']
if model_version is None or model_version<1:
print("Upgrading model version to version 1")
# this uses code from get_all_worksheets()
user_manager = self.user_manager()
num_users=0
for username in self._user_manager.users():
num_users+=1
if num_users%1000==0:
print('Upgraded %d users' % num_users)
if username in ['_sage_', 'pub']:
continue
try:
for w in self.users_worksheets(username):
owner = w.owner()
id_number = w.id_number()
collaborators = w.collaborators()
for u in collaborators:
try:
user_manager.user(u).viewable_worksheets().add((owner, id_number))
except KeyError:
# user doesn't exist
pass
except (UnicodeEncodeError, OSError):
# Catch UnicodeEncodeError because sometimes a username has a non-ascii character
# Catch OSError since sometimes when moving user directories (which happens
# automatically when getting user's worksheets), OSError: [Errno 39] Directory not empty
# is thrown (we should be using shutil.move instead, probably)
# users with these problems won't have their sharing cached, but they will probably have
# problems logging in anyway, so they probably won't notice not having shared worksheets
import sys
import traceback
print('Error on username %s' % username.encode('utf8'),
file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
pass
print('Done upgrading to model version 1')
self.conf()['model_version'] = 1
####################################################################
[docs]def load_notebook(dir, interface=None, port=None, secure=None, user_manager=None):
"""
Load and return a notebook from a given directory. Create a new
one in that directory, if one isn't already there.
INPUT:
- ``dir`` - a string that defines a directory name
- ``interface`` - the address of the interface the server listens at
- ``port`` - the port the server listens on
- ``secure`` - whether the notebook is secure
OUTPUT:
- a Notebook instance
"""
if not dir.endswith('.sagenb'):
if not os.path.exists(dir + '.sagenb') and os.path.exists(os.path.join(dir, 'nb.sobj')):
try:
nb = migrate_old_notebook_v1(dir)
except KeyboardInterrupt:
raise KeyboardInterrupt("Interrupted notebook migration. Delete the directory '%s' and try again." % (os.path.abspath(dir+'.sagenb')))
return nb
dir += '.sagenb'
dir = make_path_relative(dir)
nb = Notebook(dir)
nb.interface = interface
nb.port = port
nb.secure = secure
# Install this copy of the notebook in misc.py as *the*
# global notebook object used for computations. This is
# mainly to avoid circular references, etc. This also means
# only one notebook can actually be used at any point.
import sagenb.notebook.misc
sagenb.notebook.misc.notebook = nb
return nb
[docs]def migrate_old_notebook_v1(dir):
"""
Back up and migrates an old saved version of notebook to the new one (`sagenb`)
"""
nb_sobj = os.path.join(dir, 'nb.sobj')
old_nb = pickle.loads(open(nb_sobj).read())
######################################################################
# Tell user what is going on and make a backup
######################################################################
print("")
print("*" * 80)
print("*")
print("* The Sage notebook at")
print("*")
print("* '%s'" % os.path.abspath(dir))
print("*")
print("* will be upgraded to a new format and stored in")
print("*")
print("* '%s.sagenb'." % os.path.abspath(dir))
print("*")
print("* Your existing notebook will not be modified in any way.")
print("*")
print("*" * 80)
print("")
ans = raw_input("Would like to continue? [YES or no] ").lower()
if ans not in ['', 'y', 'yes']:
raise RuntimeError("User aborted upgrade.")
# Create new notebook
new_nb = Notebook(dir + '.sagenb')
# Define a function for transfering the attributes of one object to another.
def transfer_attributes(old, new, attributes):
for attr_old, attr_new in attributes:
if hasattr(old, attr_old):
setattr(new, attr_new, getattr(old, attr_old))
# Transfer all the notebook attributes to our new notebook object
new_nb.conf().confs = old_nb.conf().confs
for t in ['pretty_print', 'server_pool', 'ulimit', 'system']:
if hasattr(old_nb, '_Notebook__' + t):
new_nb.conf().confs[t] = getattr(old_nb, '_Notebook__' + t)
# Now update the user data from the old notebook to the new one:
print("Migrating %s user accounts..." % len(old_nb.user_manager().users()))
users = new_nb.user_manager().users()
for username, old_user in iteritems(old_nb.user_manager().users()):
new_user = user.User(old_user.username(), '',
old_user.get_email(), old_user.account_type())
new_user.set_hashed_password(old_user.password())
transfer_attributes(old_user, new_user,
[('_User__email_confirmed', '_email_confirmed'),
('_User__temporary_password', '_temporary_password'),
('_User__is_suspended', '_is_suspended')])
# Fix the __conf field, which is also an instance of a class
new_user.conf().confs = old_user.conf().confs
users[new_user.username()] = new_user
######################################################################
# Set the worksheets of the new notebook equal to the ones from
# the old one.
######################################################################
def migrate_old_worksheet(old_worksheet):
"""
Migrates an old worksheet to the new format.
"""
old_ws_dirname = old_ws._Worksheet__filename.partition(os.path.sep)[-1]
new_ws = new_nb.worksheet(old_ws.owner(), old_ws_dirname)
# some ugly creation of new attributes from what used to be stored
tags = {}
try:
for user, val in iteritems(old_ws._Worksheet__user_view):
if isinstance(user, str):
# There was a bug in the old notebook where sometimes the
# user was the *module* "user", so we don't include that
# invalid data.
tags[user] = [val]
except AttributeError:
pass
import time
last_change = (old_ws.last_to_edit(), old_ws.last_edited())
try:
published_id_number = int(os.path.split(old_ws._Worksheet__published_version)[1])
except AttributeError:
published_id_number = None
ws_pub = old_ws.worksheet_that_was_published().filename().split('/')
ws_pub = (ws_pub[0], int(ws_pub[1]))
obj = {'name': old_ws.name(), 'system': old_ws.system(),
'viewers': old_ws.viewers(),
'collaborators' :old_ws.collaborators(),
'pretty_print': old_ws.pretty_print(),
'ratings': old_ws.ratings(),
'auto_publish': old_ws.is_auto_publish(), 'tags': tags,
'last_change': last_change,
'published_id_number': published_id_number,
'worksheet_that_was_published': ws_pub
}
new_ws.reconstruct_from_basic(obj)
base = os.path.join(dir, 'worksheets', old_ws.filename())
worksheet_file = os.path.join(base, 'worksheet.txt')
if os.path.exists(worksheet_file):
text = open(worksheet_file).read()
# delete first two lines -- we don't embed the title or
# system in the worksheet text anymore.
i = text.find('\n')
text=text[i+1:]
i = text.find('\n')
text=text[i+1:]
new_ws.edit_save(text)
# copy over the DATA directory and cells directories
try:
dest = new_ws.data_directory()
if os.path.exists(dest):
shutil.rmtree(dest)
shutil.copytree(old_ws.data_directory(), dest)
except Exception as msg:
print(msg)
try:
if os.path.exists(old_ws.cells_directory()):
dest = new_ws.cells_directory()
if os.path.exists(dest):
shutil.rmtree(dest)
shutil.copytree(old_ws.cells_directory(), dest)
except Exception as msg:
print(msg)
return new_ws
worksheets = WorksheetDict(new_nb)
num_worksheets = len(old_nb._Notebook__worksheets)
print("Migrating (at most) %s worksheets..." % num_worksheets)
from sage.misc.all import walltime
tm = walltime()
i = 0
for ws_name, old_ws in iteritems(old_nb._Notebook__worksheets):
if old_ws.docbrowser(): continue
i += 1
if i % 25==0:
percent = i / float(num_worksheets)
# total_time * percent = time_so_far, so
# remaining_time = total_time - time_so_far = time_so_far*(1/percent - 1)
print(" Migrated %s (of %s) worksheets (about %.0f seconds remaining)" % (
i, num_worksheets, walltime(tm) * (1 / percent - 1)))
new_ws = migrate_old_worksheet(old_ws)
worksheets[new_ws.filename()] = new_ws
new_nb._Notebook__worksheets = worksheets
# Migrating history
new_nb._user_history = {}
for username in old_nb.user_manager().users().keys():
history_file = os.path.join(dir, 'worksheets', username, 'history.sobj')
if os.path.exists(history_file):
new_nb._user_history[username] = pickle.loads(open(history_file).read())
# Save our newly migrated notebook to disk
new_nb.save()
print("Worksheet migration completed.")
return new_nb
[docs]def make_path_relative(dir):
r"""
Replace an absolute path with a relative path, if possible.
Otherwise, return the given path.
INPUT:
- ``dir`` - a string containing, e.g., a directory name
OUTPUT:
- a string
"""
base, file = os.path.split(dir)
if os.path.exists(file):
return file
return dir
##########################################################
# Misc
##########################################################
[docs]def sort_worksheet_list(v, sort, reverse):
"""
Sort a given list on a given key, in a given order.
INPUT:
- ``sort`` - a string; 'last_edited', 'owner', 'rating', or 'name'
- ``reverse`` - a bool; if True, reverse the order of the sort.
OUTPUT:
- the sorted list
"""
f = None
if sort == 'last_edited':
def c(a):
return a.last_edited()
reverse = not reverse
f = c
elif sort == 'name':
def c(a):
return (a.name().lower(), -a.last_edited())
f = c
elif sort == 'owner':
def c(a):
return (a.owner().lower(), -a.last_edited())
f = c
elif sort == "rating":
def c(a):
return (a.rating(), -a.last_edited())
reverse = not reverse
f = c
else:
raise ValueError("invalid sort key '%s'" % sort)
v.sort(key=f, reverse=reverse)