source: sage/server/notebook/notebook.py @ 4981:fb0b651e2c6f

Revision 4981:fb0b651e2c6f, 75.0 KB checked in by Bobby Moretti <moretti@…>, 6 years ago (diff)

Made network data (address, port, etc) get passed to the notebook object

Line 
1r"""
2SAGE Notebook Interface
3
4AUTHORS:
5    -- William Stein (2006-05-06): initial version
6    -- Alex Clemesha
7    -- Tom Boothby: support for a wide range of web browsers; refactoring of javascript code; systematic keyboard controls
8
9The SAGE graphical user interface is unusual in that it operates via
10your web browser.  It provides you with SAGE worksheets that you can
11edit and evaluate, which contain scalable typeset mathematics and
12beautiful antialised images.  To try it out immediately, do this:
13
14    sage.: notebook(open_viewer=True)
15    the sage notebook starts...
16
17\subsection{Supported Browsers}
18
19The SAGE notebook should fully work with Firefox (and Mozilla).  It
20may work to some extent with Safari and Opera.  Internet Explorer is
21not supported.
22
23\subsection{Tutorial}
24Here are some things to try in the the notebook to get a feeling
25for it.
26 
27Type "2+2" in the blank box and press "shift-enter".
28The line below"2+2" will turn a different color for a moment while a SAGE kernel
29fires up and computes the answer.
30 
31Your cursor should now be in the next box down.   Type \code{a = 2\^1000}
32and press return, then "a" alone on the second line, then shift-return.
33You'll see a big number.   Also, "a" will appear in the variable
34browser in the left of the screen.    Next, click just to the
35left of the big number in the blue-ish area.  The number will shrink
36a little and change to occupy only one line.  You can see the whole
37number using your browser's horizontal scroll bar.  Click again and
38the number vanishes, to be replaced by a horizontal bar.  Click on the
39bar and the number is back.  If you click "Hide Output" in the upper
40right, all output disappears.
41 
42Next try graphics!  Type "show(plot(sin,0,10))" into an empty
43box and hit shift-enter.   You'll get a graph of sin.   Try another
44function, e.g.,
45\begin{verbatim}
46   show(plot(lambda x: sin(x)^2 - cos(2*x)^3, -5,5))
47\end{verbatim}
48Click on the left side of the figure (twice) to make it disappear.
49 
50One important feature of the SAGE notebook, is that you can
51"queue up" a bunch of calculations in a row, *while* still editing the
52notebook!  As an example, consider computing factorials, which takes a
53while (but not forever).  First, enter the following in a blank box and
54press"shift-return":
55\begin{verbatim}
56def f(n):
57    return len(str(factorial(10^n)))
58\end{verbatim}         
59This defines a function that takes a while to compute.   For example,
60time the execution of "f(5)", by typing (in a new box), "time f(5)".
61It should take a few seconds.   Next try
62"f(6)", which takes quite a while (about 21 seconds on sage.math).
63While f(6) is being computed, note that the output line for f(6) is a
64different color, indicating that it is being computed
65While f(6) is computing (if it finishes first, restart it by
66just hitting shift-enter in the box where "f(6)" is), try typing
67"f(4)" in the next box.  You're allowed to give input, but the
68result doesn't get computed immediately.  You can enter several more
69lines as well, etc.; when the f(6) finally finishes, SAGE goes on
70to compute "f(4)".   You can queue up dozens of calculations.  For
71example, if you hit the "Evaluate" link in the upper right, the
72whole worksheet is queued up for computation.  Try it.   When the
73computation gets stuck on "f(6)", hit the interrupt button (or press escape)
74and the queued up calculations are cancelled.
75 
76Click "Hide Output" in the upper right.   You'll see just your
77input and some little boxes; clicking on the boxes reveals output.
78 
79You can also embed nicely typeset math.  Try this:
80\begin{verbatim}
81f = maxima('sin(x^2)')
82g = f.integrate('x')
83view(g)
84\end{verbatim}
85 
86If this silently fails, type "view(g, debug=True)" instead.
87You need latex and the "convert" and "gs" commands, (use
88an "apt-get install imagemagick gs").  Anyways, you get
89a nicely typeset formula.  Try a matrix next:
90\begin{verbatim}
91A = MatrixSpace(QQ, 5).random_element()
92view(A)
93\end{verbatim}
94Try typing this into a new box:
95\begin{verbatim}
96%latex
97Consider the matrix $$A = \sage{A},$$
98which has square $$A^2 = \sage{A^2}.$$
99\end{verbatim}
100If you would like to typeset a slide (suitable for presentation),
101use \%slide instead.
102Here is another example:
103\begin{verbatim}
104%latex
105The first ten squares are
106$$
107\sage{', '.join([str(sq(i)) for i in range(1,11)])}
108$$
109
110The primes up to 100 are
111$$
112\sage{', '.join(str(p) for p in prime_range(100))}
113$$
114\end{verbatim}
115
116\subsubsection{Using Gap, Magma, GP/PARI}
117Make the first line of the input block \code{\%gap}
118\code{\%magma}, or \code{\%gp}, etc.  The rest of the block
119is fed directly to the corresponding interpreter.
120In this way you can make a single session that has input blocks
121that work with a range of different systems.
122
123(Note -- there is currently no support for
124pulling in objects and evaluating code in SAGE by typing
125"sage(...)" inside the input block.  This is planned.)
126
127\subsubsection{Typesetting Mathematics}
128SAGE \emph{includes} jsMath, which is an implementation of the TeX
129math layout engine in javascript.  If you use the show or view
130commands, they display a given SAGE object typeset using jsmath.
131Moreover, if you put \code{\%jsmath} at the beginning of an input
132cell, the whole cell will be typeset using jsmath.  Also, you can type
133\code{jsmath(obj)} to typeset a given object obj using jsmath.
134
135 
136\subsubsection{Adding and Removing Cells}
137To add a new cell, click on a little black line that appears when you
138hover between any two cells, or above the top one.  To delete a cell
139delete all its contents, then hit backspace one more time.  The cell
140vanishes forever.
141 
142You can also move back and forth between cells using the up and down
143arrow.  In particular, when you are at the top of a cell and press
144the up arrow the cursor jumps to the previous cell.
145Press control-enter in a cell to create a new cell after the
146current cell.
147
148There is no direct support for moving and reorganizing cells, though
149you can copy and paste any individual cell into another one.  However,
150the "Text" buttons provide the full text of the worksheet in a very
151convenient format for copy and paste.
152
153
154\subsubsection{History}
155Click the history button near the top to pop up a history of the last
1561000 (or so) input cells.  After a huge amount of design discussion about
157how to design a history system, a simple popup with the text of
158previous commands seems like the best choice.  It's incredibly simple,
159yet provides an incredible amount of functionality, especially because
160that popup window can be easily searched (at least in Firefox), pasted
161from, etc., and refreshed (use F5 or Ctrl-R).
162
163 
164\subsubsection{Introspection}
165To find all completions for an identifier you are typing press
166the tab key.  This should work exactly like IPython, and even
167respects the \code{trait_names()} method.
168
169To find help for any object in a line, put ? after it
170and press the tab key.  The cursor must be somewhere in the identifier
171with the question mark after it.   For source code, put ?? after
172the identifier and press tab.  You can also put an identifier by
173itself on a line with ? (or ??) after it and press shift-enter.
174
175To get extensive help on an object, type "help(object)" and press
176return.  This works, since I set the PAGER to "cat", and I strip out
177control codes that appear in the output.  And this isn't annoying,
178since web browsers are very good for scrolling through long output.
179 
180 
181\subsubsection{Saving and Loading Individual Objects}
182When you start a notebook you give a name argument
183to it, and it creates a directory.  Inside that directory there
184will be many worksheets (which you can use all at once and easily
185flip through -- not implemented yet), and an object store.
186You can save and load individual objects (using save and load), and they'll
187be listed in the box on the bottom let, e.g., try
188
189a = 5
190save a
191
192and you'll see the "a" appear there.   You can load and save objects
193from any worksheet in any other one.  (Currently the only way to delete
194objects from the list of saved objects is to remove the object from
195the objects subdirectory.)
196 
197\subsubsection{Pasting in Examples}
198Code is evaluated by exec'ing (after preparsing). Only the output
199of the last line of the cell is implicitly printed. If any line
200starts with "sage:" or ">>>" the {\em entire block} is assumed to
201contain text and examples, and only lines that begin with a
202prompt are executed. Thus you can paste in *complete examples*
203from the docs without any editing, and you can write input
204cells that contains non-evaluated plain text mixed with
205examples by starting the block with ">>>" or including an example.
206(NOTE: Lines beginning with ">>>" are still preparsed.)
207 
208\subsubsection{Saving and Loading Notebooks and Worksheets}
209 
210The SAGE notebook is very persistent.  Every time you submit
211a cell for computation, the state of the notebook is saved (a
212few kb's file).  If you quit the notebook and reload, it will
213have everything you typed from the previous session, along
214with all output.
215Firefox has an excellent undo function for text input cells.
216Just hit control-z to have ``infinite undo'' for the input
217you've entered in that particular cell.
218
219You can save all variables in a current session by typing
220\code{save_session [optional_name]}.  You can then load
221those session variables into another worksheet using
222\code{load_session}, or load into the same worksheet next
223time you use it.
224 
225\subsubsection{Architecture}
226 
227The SAGE Notebook is an ``AJAX application'' that can run either
228entirely locally on your desktop machine, or partly on
229a server and via a web browser that could be located somewhere
230else.
231If you run the server and allow remote access (by setting
232address when starting the notebook), you should also set
233the username and password, so not just anybody can access
234the notebook.
235
236Anywhere, here are the components of the SAGE Notebook:
237
238\begin{enumerate}
239\item Web Server: A Python process that uses the
240      Python standard library's
241     BaseHTTPServer.HTTPServer to create a web server.  This
242     process also handles all requests from the web browser,
243     e.g., organizing computation of cells, etc.  It
244     only imports a small
245     subset of the SAGE library.  In particular, if you do
246     "sage -notebook" at the command line, only some of
247     SAGE is imported. 
248 
249 \item SAGE Server:
250     A Python process with all the SAGE libraries loaded; this
251     is started by (1) when a web browser first requests that
252     a cell be evaluated.  There's (up to) one of these
253     for each worksheet.
254 
255 \item WEB Browser: The web browser runs a 1000-line javascript (plus
256     800 lines of css) program that Alex, Tom and I wrote from
257     scratch, which implements much of the browser-side part of the
258     SAGE notebook functionality.
259     
260\end{enumerate}
261 
262When you use the SAGE Notebook, you are mainly interacting with a
263javascript program.  When you do something serious, e.g., request
264computation of some input, create a new cell, etc., a request is made
265from your web browser to the web server telling it what is going on.
266If it's a calculation, the web server tells the SAGE server to get
267started on the calculation, and tells the web browser to check several
268times a second whether there is anything new with the calculation.
269When something new appears it fills that in.  This continues until all
270calculations are done. During this time, you can edit cells, create
271new cells, submit more computations, etc.  Note that output is
272updated as the computation proceeds, so you can verbosely watch
273a computation progress.  For example, try the following from the SAGE
274Notebook:
275
276\begin{verbatim}
277import time
278for i in range(10):
279    print i
280    time.sleep(0.5)
281\end{verbatim}
282 
283You get to watch as the integers from 1 to 10 are "computed".
284Actually, getting this output to be reported as the computation
285proceeds is, I think, \emph{crucial} to making a really usable SAGE
286GUI--users (i.e., me) want to run huge computations and watch the
287output progress.
288 
289The architecture is also good from the point of view of being able to
290interrupt running computations.  What happens when you request an
291interrupt is that the web browser sends a message to the web server,
292which in turn tells the SAGE server to stop computing by sending it
293many interrupt signals (for several seconds) until it either stops, or
294if it's really frozen (due to a bug, or calling into a C function that
295isn't properly wrapped in signal handling, or maybe you run an
296interactive program, e.g., via "os.system('...')"), it'll just kill that SAGE server
297and start a new one.  The result is that the
298user doesn't get a frozen web browser or browser interface at any point,
299and even if the whole SAGE process went down and froze, at least all
300your input and output from your session is still there in your
301browser.  The only thing you've lost is the definition of all your
302variables.  Hit "shift-enter" a few times or "evaluate all" and you're
303back in shape.  This is much better than having to restart the command
304prompt (e.g., with a terminal interface), then paste back in all your
305setup code, etc., Also, you can save variables as you go easily (via
306the "save" command), and get back to where you were quickly.
307
308"""
309
310## This is commented out, since it's not recommended.  I really
311## don't like crap that is both potentially insecure and will
312## break on some setups.
313## \subsubsection{Typesetting with Latex}
314## If you have latex, gv, and the imagemagick programs (e.g., convert)
315## installed on your system, you can do nice latex typesetting from
316## within SAGE.
317## \begin{enumerate}
318## \item As usual the command \code{latex(obj)} outputs latex code
319## to typeset obj.
320## \item The command \code{view(obj)} creates an image representing
321## the object, which you can copy and paste into other documents.
322## \item If you preface a block with \code{\%latex} the rest of the
323## block is typeset and the corresponding image appears.
324## The input is also (mostly) hidden.  Use {\%latex_debug} to debug
325## latex problems.
326## \item If you preface a block with \code{\%slide} the rest of the
327## block is typeset as a slide (bigger san serif font)
328## and the corresponding image appears.  The input is again hidden.
329## Use {\%slide_debug} for debugging.
330## \end{enumerate}
331
332
333
334###########################################################################
335#       Copyright (C) 2006 William Stein <wstein@gmail.com>
336#
337#  Distributed under the terms of the GNU General Public License (GPL)
338#                  http://www.gnu.org/licenses/
339###########################################################################
340
341import os
342import shutil
343import socket
344import re           # regular expressions
345
346# SAGE libraries
347from   sage.structure.sage_object import SageObject, load
348from   sage.misc.viewer     import browser
349from   sage.misc.misc       import alarm, cancel_alarm
350from   sage.server.misc import print_open_msg
351
352# SAGE Notebook
353import css          # style
354import js           # javascript
355import server       # web server
356import worksheet    # individual worksheets (which make up a notebook)
357import config       # internal configuration stuff (currently, just keycodes)
358import keyboards    # keyboard layouts
359
360MAX_WORKSHEETS = 4096  # do not change this willy nilly; that would break existing notebooks (and there is no reason to).
361MAX_HISTORY_LENGTH = 500
362WRAP_NCOLS = 80
363
364JSMATH = True
365
366def open_page(address, port):
367    cmd = '%s http://%s:%s 1>&2 >/dev/null &'%(browser(), address, port)
368    os.system(cmd)
369
370class Notebook(SageObject):
371    def __init__(self, dir='sage_notebook', username=None, 
372                 password=None, color='default', system=None, 
373                 show_debug = False, log_server=False, address='localhost',
374                 port=8000, secure=True):
375        self.__dir = dir
376        self.set_system(system)
377        self.__color = color
378        if not (username is None):
379            self.set_auth(username,password)
380        self.__worksheets = {}
381        self.__load_defaults()
382        self.__filename     = '%s/nb.sobj'%dir
383        self.__worksheet_dir = '%s/worksheets'%dir
384        self.__object_dir   = '%s/objects'%dir
385        self.__makedirs()
386        self.__next_worksheet_id = 0
387        self.__history = []
388        self.__history_count = 0
389        self.__log_server = log_server #log all POST's and GET's
390        self.__server_log = [] #server log list
391        W = self.create_new_worksheet('Worksheet 1')
392        self.__default_worksheet = W
393        self.__show_debug = show_debug
394        self.save()
395
396    def system(self):
397        try:
398            return self.__system
399        except AttributeError:
400            self.__system = None
401            return None
402
403    def set_system(self, system):
404        if system == 'sage':
405            self.__system = None
406        elif system:  # don't change if it is None
407            self.__system = system
408
409    def color(self):
410        try:
411            return self.__color
412        except AttributeError:
413            self.__color = 'default'
414            return self.__color
415
416    def set_color(self,color):
417        self.__color = color
418
419    def set_directory(self, dir):
420        if dir == self.__dir:
421            return
422        self.__dir = dir
423        self.__filename = '%s/nb.sobj'%dir
424        self.__worksheet_dir = '%s/worksheets'%dir
425        self.__object_dir = '%s/objects'%dir
426        for W in self.__worksheets.itervalues():
427            W.set_notebook(self)
428
429    def add_to_history(self, input_text):
430        H = self.history()
431        H.append(input_text)
432        while len(H) > self.max_history_length():
433            del H[0]
434   
435    def history_count_inc(self):
436        self.__history_count += 1
437   
438    def history_count(self):
439        return self.__history_count
440
441    def server_log(self):
442        return self.__server_log
443
444    def log_server(self):
445        return self.__log_server
446   
447    def set_log_server(self, log_server):
448        self.__log_server = log_server
449
450    def history(self):
451        try:
452            s = self.__history
453        except AttributeError:
454            self.__history = []
455            s = self.__history
456        return s
457
458    def history_text(self):
459        return '\n\n'.join([H.strip() for H in self.history()])
460
461    def history_html(self):
462        t = self.history_text()
463        t = t.replace('<','&lt;')
464        s = '<head>\n'
465        s += '<title>SAGE Input History</title>\n'
466        s += '</head>\n'
467        s += '<body>\n'       
468        s += '<pre>' + t + '</pre>\n'
469        s += '<a name="bottom"></a>\n'
470        s += '<script type="text/javascript"> window.location="#bottom"</script>\n'
471        s += '</body>\n'
472        return s
473
474       
475    def history_with_start(self, start):
476        n = len(start)
477        return [x for x in self.history() if x[:n] == start]
478
479    def export_worksheet(self, worksheet_filename, filename):
480        W = self.get_worksheet_with_filename(worksheet_filename)
481        W.save()
482        cmd = 'cd %s && tar -jcf %s.sws "%s" && mv %s.sws ..'%(
483            self.__worksheet_dir,
484            filename, W.filename(), filename)
485        print cmd
486        os.system(cmd)
487
488    def plain_text_worksheet_html(self, name, prompts=True):
489        W = self.get_worksheet_with_filename(name)
490        t = W.plain_text(prompts = prompts)
491        t = t.replace('<','&lt;')
492        s = '<head>\n'
493        s += '<title>SAGE Worksheet: %s</title>\n'%W.name()
494        s += '</head>\n'
495        s += '<body>\n'       
496        s += '<pre>' + t + '</pre>'
497        s += '</body>\n'
498        return s
499
500    def tmpdir(self):
501        d = '%s/tmp'%self.__dir
502        if os.path.exists(d):
503            os.system('rm -rf "%s"'%d)
504        if not os.path.exists(d):
505            os.makedirs(d)
506        return d
507
508    def import_worksheet(self, filename):
509        if not os.path.exists(filename):
510            raise ValueError, "no file %s"%filename
511        if filename[-4:] != '.sws':
512            raise ValueError, "file %s must have extension sws."%filename
513        tmp = self.tmpdir()
514        cmd = 'cd %s; tar -jxf %s'%(tmp, os.path.abspath(filename))
515        print cmd
516        os.system(cmd)
517        try:
518            D = os.listdir(tmp)[0]
519        except IndexError:
520            raise ValueError, "invalid worksheet"
521        worksheet = load('%s/%s/%s.sobj'%(tmp,D,D), compress=False)
522        names = self.worksheet_names()
523        if D in names:
524            m = re.match('.*?([0-9]+)$',D)
525            if m is None:
526                n = 0
527            else:
528                n = int(m.groups()[0])
529            while "%s%d"%(D,n) in names:
530                n += 1
531            cmd = 'mv %s/%s/%s.sobj %s/%s/%s%d.sobj'%(tmp,D,D,tmp,D,D,n)
532            print cmd
533            os.system(cmd)
534            cmd = 'mv %s/%s %s/%s%d'%(tmp,D,tmp,D,n)
535            print cmd
536            os.system(cmd)
537            D = "%s%d"%(D,n)
538            worksheet.set_name(D)
539        print D
540        S = self.__worksheet_dir
541        cmd = 'rm -rf "%s/%s"'%(S,D)
542        print cmd       
543        os.system(cmd)
544        cmd = 'mv %s/%s %s/'%(tmp, D, S)
545        print cmd
546        os.system(cmd)
547        new_id = None
548        id = worksheet.id()
549        for W in self.__worksheets.itervalues():
550            if W.id() == id:
551                new_id = self.__next_worksheet_id
552                self.__next_worksheet_id += 1
553                break
554        worksheet.set_notebook(self, new_id)
555        name = worksheet.name()
556        self.__worksheets[name] = worksheet
557        return worksheet
558
559    # unpickled, no worksheets will think they are
560    # being computed, since they clearly aren't (since
561    # the server just started).
562    def set_not_computing(self):
563        for W in self.__worksheets.values():
564            W.set_not_computing()
565
566    def set_debug(self,show_debug):
567        self.__show_debug = show_debug
568
569    def default_worksheet(self):
570        return self.__default_worksheet
571
572    def directory(self):
573        if not os.path.exists(self.__dir):
574            # prevent "rm -rf" accidents.
575            os.makedirs(self.__dir)
576        return self.__dir
577
578    def DIR(self):
579        """
580        Return the absolute path to the directory that contains
581        the SAGE Notebook directory.
582        """
583        P = os.path.abspath('%s/..'%self.__dir)
584        if not os.path.exists(P):
585            # prevent "rm -rf" accidents.
586            os.makedirs(P)
587        return P
588
589    def max_history_length(self):
590        try:
591            return self.__defaults['max_history_length']
592        except KeyError:
593            return MAX_HISTORY_LENGTH
594       
595    def __load_defaults(self):
596        # in future this will allow override by a file, and
597        # can be set by user via web interface
598        self.__defaults = {'cell_input_color':'#0000000',
599                           'cell_output_color':'#0000EE',
600                           'word_wrap_cols':int(WRAP_NCOLS),
601                           'max_history_length':MAX_HISTORY_LENGTH}
602
603    def worksheet_directory(self):
604        return self.__worksheet_dir
605
606    def object_directory(self):
607        O = self.__object_dir
608        if not os.path.exists(O):
609            os.makedirs(O)
610        return O
611
612    def objects(self):
613        L = [x[:-5] for x in os.listdir(self.object_directory())]
614        L.sort()
615        return L
616
617    def object_list_html(self):
618        m = max([len(x) for x in self.objects()] + [30])
619        s = []
620        a = '<a href="/%s.sobj" class="object_name">\n'
621        for name in self.objects():
622            s.append(a%name + name + '</a>\n')  # '&nbsp;'*(m-len(name)) +
623        return '<br>\n'.join(s)
624
625    def defaults(self):
626        return self.__defaults
627
628    def authorize(self, auth):
629        """
630        Returns True if auth is the correct authorization.
631        """
632        a = self.auth_string()
633        if a == ':':
634            return True
635        return a == auth
636
637    def auth_string(self):
638        try:
639            return self.__auth
640        except AttributeError:
641            self.__auth = ":"
642        return self.__auth
643
644    def set_auth(self, username, password):
645        self.__auth = '%s:%s'%(username, password)
646       
647    def __makedirs(self):
648        if not os.path.exists(self.__dir):
649            os.makedirs(self.__dir)
650        if not os.path.exists(self.__worksheet_dir):           
651            os.makedirs(self.__worksheet_dir)
652        if not os.path.exists(self.__object_dir):                       
653            os.makedirs(self.__object_dir)
654
655    def worksheet_ids(self):
656        return set([W.id() for W in self.__worksheets.itervalues()])
657
658    def create_new_worksheet(self, name='untitled', passcode=''):
659        if name in self.__worksheets.keys():
660            return self.__worksheets[name]
661        name = str(name)
662        passcode = str(passcode)
663        wids = self.worksheet_ids()
664        id = 0
665        while id in wids:
666            id += 1
667
668        if id >= MAX_WORKSHEETS:
669            raise ValueError, 'there can be at most %s worksheets'%MAX_WORKSHEETS
670        self.__next_worksheet_id += 1
671        W = worksheet.Worksheet(name, self, id, system=self.system(), passcode=passcode)
672        self.__worksheets[name] = W
673        return W
674
675    def delete_worksheet(self, name):
676        """
677        Delete the given worksheet and remove its name from the
678        worksheet list.
679        """
680        if not (name in self.__worksheets.keys()):
681            raise KeyError, "Attempt to delete missing worksheet"
682        W = self.__worksheets[name]
683        cmd = 'rm -rf "%s"'%(W.directory())
684        print cmd
685        os.system(cmd)
686           
687        del self.__worksheets[name]
688        if len(self.__worksheets) == 0:
689            return self.create_new_worksheet('_scratch_')
690        else:
691            return self.__worksheets[self.__worksheets.keys()[0]]
692
693    def worksheet_names(self):
694        W = self.__worksheets.keys()
695        W.sort()
696        return W
697
698    def worksheet_html(self, name, do_print=False):
699        W = self.get_worksheet_with_filename(name)
700        s = '<head>\n'
701        s += '<title>SAGE Worksheet: %s</title>\n'%W.name()
702        if do_print:
703            s += '<script type="text/javascript" src="/javascript/jsmath/jsMath.js"></script>\n'
704        s += '<script type="text/javascript" src="/javascript/main.js"></script>\n'
705        s += '<link rel=stylesheet href="/css/main.css">\n'
706        s += '</head>\n'
707        s += '<body>\n'
708        s += W.html(include_title=False, do_print=do_print)
709        if do_print:
710            s += '<script type="text/javascript">jsMath.Process();</script>\n'
711        s += '\n</body>\n'
712        return s
713
714    def get_worksheet_with_name(self, name):
715        return self.__worksheets[name]
716
717    def get_worksheet_with_id(self, id):
718        """
719        Get the worksheet with given id, which is either a name or the id number.
720        If there is no such worksheet, a KeyError is raised.
721       
722        INPUT:
723            id -- something that identifies a worksheet.
724                 string -- use worksheet with that name or filename.
725                 None -- use the default worksheet.
726                 string int -- something that coerces to an integer; worksheet with that number
727
728        OUTPUT:
729            a worksheet.
730        """
731        if id is None:
732            return self.default_worksheet()
733        try:
734            id = int(id)
735            for W in self.__worksheets.itervalues():
736                if W.id() == id:
737                    return W
738        except ValueError:
739            id = str(id).lower()
740            for W in self.__worksheets.itervalues():
741                if W.name().lower() == id or W.filename().lower() == id:
742                    return W
743        raise KeyError, 'no worksheet %s'%id
744
745    def get_worksheet_with_filename(self, filename):
746        if id != None:
747            for W in self.__worksheets.itervalues():
748                if W.filename() == filename:
749                    return W
750        raise KeyError, "no such worksheet %s"%filename
751
752    def get_worksheet_that_has_cell_with_id(self, id):
753        worksheet_id = id // MAX_WORKSHEETS
754        return self.get_worksheet_with_id(worksheet_id)
755
756    def save(self, filename=None):
757        print "-"*70
758       
759        if filename is None:
760            F = os.path.abspath(self.__filename)
761            try:
762                shutil.copy(F, F[:-5] + '-backup.sobj')
763            except IOError:
764                pass
765            F = os.path.abspath(self.__filename)
766        else:
767            F = os.path.abspath(filename)
768           
769        print "Saving notebook to '%s'..."%F
770        D, _ = os.path.split(F)
771        if not os.path.exists(D):
772            os.makedirs(D)
773        SageObject.save(self, F, compress=False)
774        print "Press control-C to stop the notebook server."
775        print "-"*70
776
777    # TODO -- Delete this function
778##     def start(self, port=8000, address='localhost',
779##                     max_tries=128, open_viewer=False,
780##                     jsmath=False):
781##         global JSMATH
782##         JSMATH = jsmath
783##         tries = 0
784##         port = int(port)
785##         max_tries = int(max_tries)
786##         while True:
787##             try:
788##                 notebook_server = server.NotebookServer(self,
789##                          port, address)
790##             except socket.error, msg:
791##                 print msg
792##                 port += 1
793##                 tries += 1
794##                 if tries > max_tries:
795##                     print "Not trying any more ports.  Probably your network is down."
796##                     break
797##                 print "Trying next port (=%s)"%port
798##             else:
799##                 break
800
801##         print_open_msg(address, port)
802##         print "WARNING: The SAGE Notebook works best with ** Firefox/Mozilla **."
803       
804##         f = file('%s/pid'%self.directory(), 'w')
805##         f.writelines(["%d\n"%os.getpid(), str(port)])
806##         f.close()
807
808##         if open_viewer:
809##             open_page(address, port)     
810##         while True:
811##             try:
812##                 notebook_server.serve()
813##             except KeyboardInterrupt, msg:
814##                 break
815##             except Exception, msg:
816##                 print msg
817##                 print "Automatically restarting server."
818##             else:
819##                 break
820##         self.save()
821##         self.quit()
822##         self.save()
823
824    def quit(self):
825        for W in self.__worksheets.itervalues():
826            W.quit()
827
828    def delete_doc_browser_worksheets(self):
829        names = self.worksheet_names()
830        for n in self.__worksheets.keys():
831            if n.startswith('doc_browser'):
832                self.delete_worksheet(n)
833
834    def worksheet_list_html(self, current_worksheet=None):
835        s = []
836        names = self.worksheet_names()
837        m = max([len(x) for x in names] + [30])
838        for n in names:
839            if n.startswith('doc_browser'): continue
840            W = self.__worksheets[n]
841            if W == current_worksheet:
842                cls = 'worksheet_current'
843            else:
844                cls = 'worksheet_other'
845            if W.computing():
846                cls += '_computing' # actively computing
847            name = W.name()
848            name += ' (%s)'%len(W)
849            name = name.replace(' ','&nbsp;')
850            txt = '<a class="%s" onMouseOver="show_worksheet_menu(%s)" href="/ws/%s">%s</a>'%(
851                cls,W.id(), W.filename(),name) 
852            s.append(txt)
853        return '<br>'.join(s)
854
855    def _doc_html_head(self, worksheet_id, css_href):
856        if worksheet_id is not None:
857            worksheet = self.get_worksheet_with_id(worksheet_id)
858            head = '\n<title>%s (%s)</title>'%(worksheet.name(), self.directory())
859        else:
860            head = '\n<title>SAGE Notebook | Welcome</title>'
861        head += '\n<script  type="text/javascript" src="/javascript/main.js"></script>\n'
862        head += '\n<link rel=stylesheet href="/css/main.css" type="text/css" id="main_css">\n'
863       
864        if css_href:
865            head += '\n<link rel=stylesheet type="text/css" href=%s>\n'%(css_href)
866
867        if JSMATH:
868            head += '<script type="text/javascript">jsMath = {Controls: {cookie: {scale: 125}}}</script>\n'
869            #head += '<script type="text/javascript" src="/jsmath/plugins/spriteImageFonts.js"></script>\n'
870            head +=' <script type="text/javascript" src="/jsmath/plugins/noImageFonts.js"></script>\n'
871            head += '<script type="text/javascript" src="/jsmath/jsMath.js"></script>\n'
872            head += "<script type='text/javascript'>jsMath.styles['#jsMath_button'] = jsMath.styles['#jsMath_button'].replace('right','left');</script>\n"
873
874        #head += '<script type="text/javascript">' + js.javascript() + '</script>\n'
875        return head
876
877    def _doc_html_body(self, worksheet_id):
878        worksheet = self.get_worksheet_with_id(worksheet_id)
879        main_body = worksheet.html(authorized = True, confirm_before_leave=False)
880       
881        vbar = '<span class="vbar"></span>'
882
883        body = ''
884        body += '<div class="top_control_bar">\n'
885        body += '  <span class="banner"><a class="banner" href="http://www.sagemath.org">'
886        body += '  <img src="/images/sagelogo.png" alt="SAGE"></a></span>\n'
887        body += '  <span class="control_commands" id="cell_controls">\n'
888        body += '    <a class="history_link" onClick="history_window()">Log</a>' + vbar
889        body += '    <a class="help" onClick="show_help_window()">Help</a>' + vbar
890        # body += '    <a class="slide_mode" onClick="slide_mode()">Slideshow</a>'
891        body += '  </span>\n'
892
893        #these divs appear in backwards order because they're float:right
894        body += '  <div class="hidden" id="slide_controls">\n'
895        body += '    <div class="slideshow_control">\n'
896        body += '      <a class="slide_arrow" onClick="slide_next()">&gt;</a>\n'
897        body += '      <a class="slide_arrow" onClick="slide_last()">&gt;&gt;</a>\n' + vbar
898        body += '      <a class="cell_mode" onClick="cell_mode()">Worksheet</a>\n'
899        body += '    </div>\n'
900        body += '    <div class="slideshow_progress" id="slideshow_progress" onClick="slide_next()">\n'
901        body += '      <div class="slideshow_progress_bar" id="slideshow_progress_bar">&nbsp;</div>\n'
902        body += '      <div class="slideshow_progress_text" id="slideshow_progress_text">&nbsp;</div>\n'
903        body += '    </div>\n'
904        body += '    <div class="slideshow_control">\n'
905        body += '      <a class="slide_arrow" onClick="slide_first()">&lt;&lt;</a>\n'
906        body += '      <a class="slide_arrow" onClick="slide_prev()">&lt;</a>\n'
907        body += '    </div>\n'
908        body += '  </div>\n'
909
910        body += '</div>\n'
911        body += '\n<div class="slideshow" id="worksheet">\n'
912
913        body += main_body + '\n</div>\n'
914
915        # The blank space given by '<br>'*15  is needed so the input doesn't get
916        # stuck at the bottom of the screen. This could be replaced by a region
917        # such that clicking on it creates a new cell at the bottom of the worksheet.
918        body += '<br>'*15
919        body += '\n</div>\n'
920       
921        body += '<span class="pane" id="left_pane"><table bgcolor="white"><tr><td>\n'
922        body += '</td></tr></table></span>\n'
923       
924        body += '  <div class="worksheet_list" id="worksheet_list">%s</div>\n'%self.worksheet_list_html(worksheet)
925        body += '<script type="text/javascript">focus(%s)</script>\n'%(worksheet[0].id())
926        body += '<script type="text/javascript">jsmath_init();</script>\n'
927        body += '<script type="text/javascript">worksheet_locked=false;</script>'
928
929        if worksheet.computing():
930            # Set the update checking back in motion.
931            # body += '<script type="text/javascript"> active_cell_list = %r; \n'%worksheet.queue_id_list()
932            # body += 'for(var i = 0; i < active_cell_list.length; i++)'
933            # body += '    cell_set_running(active_cell_list[i]); \n'
934            # body += 'start_update_check(); </script>\n'
935            body += '<script type="text/javascript">sync_active_cell_list();</script>'
936        return body
937
938    def _html_head(self, worksheet_id):
939        if worksheet_id is not None:
940            worksheet = self.get_worksheet_with_id(worksheet_id)
941            head = '\n<title>%s (%s)</title>'%(worksheet.name(), self.directory())
942        else:
943            head = '\n<title>SAGE Notebook | Welcome</title>'
944        head += '\n<script type="text/javascript" src="/javascript/main.js"></script>\n'
945        head += '\n<link rel=stylesheet href="/css/main.css" type="text/css">\n'
946
947        if JSMATH:
948            head += '<script type="text/javascript">jsMath = {Controls: {cookie: {scale: 115}}}</script>\n'
949            head +=' <script type="text/javascript" src="/javascript/jsmath/plugins/noImageFonts.js"></script>\n'
950            head += '<script type="text/javascript" src="/javascript/jsmath/jsMath.js"></script>\n'
951            head += "<script type='text/javascript'>jsMath.styles['#jsMath_button'] = jsMath.styles['#jsMath_button'].replace('right','left');</script>\n"
952
953        head +=' <script type="text/javascript" src="/javascript/highlight/prettify.js"></script>\n'
954        head += '<link rel=stylesheet href="/css/highlight/prettify.css" type="text/css">\n'
955
956        return head
957
958    def _html_body(self, worksheet_id, show_debug=False, worksheet_authorized=False):
959        if worksheet_id is None or worksheet_id == '':
960            main_body = '<div class="worksheet_title">Welcome to the SAGE Notebook</div>\n'
961            if os.path.isfile(self.directory() + "/index.html"):
962                splash_file = open(self.directory() + "/index.html")
963                main_body+= splash_file.read()
964                splash_file.close()
965            else:
966                dir = os.path.abspath('%s'%self.directory())
967                main_body+= "<br>&nbsp;&nbsp;&nbsp;SAGE Notebook running from <tt>%s</tt>."%dir
968                main_body+= self.help_window()
969                main_body += "&nbsp;&nbsp;&nbsp;Create a file <tt>%s/index.html</tt> to replace this splash page.<br>"%(dir)
970            interrupt_class = "interrupt_grey"
971            worksheet = None
972        else:
973            worksheet = self.get_worksheet_with_id(worksheet_id)
974            if worksheet.computing():
975                interrupt_class = "interrupt"
976            else:
977                interrupt_class = "interrupt_grey"
978            main_body = worksheet.html(authorized = worksheet_authorized)
979
980        add_new_worksheet_menu = """
981             <div class="add_new_worksheet_menu" id="add_worksheet_menu">
982             <input id="new_worksheet_box" class="add_new_worksheet_menu"
983                    onKeyPress="if(is_submit(event)) process_new_worksheet_menu_submit();"></input><br>
984             <button class="add_new_worksheet_menu"  onClick="process_new_worksheet_menu_submit();">New</button>
985             </div>
986        """             
987                    #<button class="add_new_worksheet_menu" onClick="hide_add_new_worksheet_menu()">Cancel</button>
988
989        delete_worksheet_menu = """
990             <div class="delete_worksheet_menu" id="delete_worksheet_menu">
991             <input id="delete_worksheet_box" class="delete_worksheet_menu"
992                    onKeyPress="if(is_submit(event)) process_delete_worksheet_menu_submit();"></input>
993             <button class="delete_worksheet_menu" onClick="process_delete_worksheet_menu_submit();">delete</button>
994             &nbsp;&nbsp;&nbsp;<span class="X" onClick="hide_delete_worksheet_menu()">X</span>
995             </div>
996        """
997
998        vbar = '<span class="vbar"></span>'
999
1000        body = ''
1001
1002        body += '<div class="top_control_bar">\n'
1003        body += '  <span class="banner"><a class="banner" target="_new" href="http://www.sagemath.org">'
1004        body += '  <img src="/images/sagelogo.png" alt="SAGE"></a></span>\n'
1005        body += '  <span class="control_commands" id="cell_controls">\n'
1006        body += """<form method="POST" action="https://localhost:8000/login">  Username: <input type="text" name="email" size="15" />  Password: <input type="password" name="password" size="15" /><br />  <div align="center">  <p><input type="submit" value="Login" /></p>  </div> </form><br /><br />"""
1007        body += '    <a class="history_link" onClick="history_window()">Log</a>' + vbar
1008        body += '    <a class="help" onClick="show_help_window()">Help</a>' + vbar
1009        body += '    <a href="/doc">Documentation</a>' + vbar
1010        body += '     <a href="__upload__.html" class="upload_worksheet">Upload</a>'
1011        body += '  </span>\n'
1012
1013        #these divs appear in backwards order because they're float:right
1014        body += '  <div class="hidden" id="slide_controls">\n'
1015        body += '    <div class="slideshow_control">\n'
1016        body += '      <a class="slide_arrow" onClick="slide_next()">&gt;</a>\n'
1017        body += '      <a class="slide_arrow" onClick="slide_last()">&gt;&gt;</a>\n' + vbar
1018        body += '      <a class="cell_mode" onClick="cell_mode()">Show Full Worksheet</a>\n'
1019        body += '    </div>\n'
1020        body += '    <div class="slideshow_progress" id="slideshow_progress" onClick="slide_next()">\n'
1021        body += '      <div class="slideshow_progress_bar" id="slideshow_progress_bar">&nbsp;</div>\n'
1022        body += '      <div class="slideshow_progress_text" id="slideshow_progress_text">&nbsp;</div>\n'
1023        body += '    </div>\n'
1024        body += '    <div class="slideshow_control">\n'
1025        body += '      <a class="slide_arrow" onClick="slide_first()">&lt;&lt;</a>\n'
1026        body += '      <a class="slide_arrow" onClick="slide_prev()">&lt;</a>\n'
1027        body += '    </div>\n'
1028        body += '  </div>\n'
1029
1030        body += '</div>\n'
1031        body += '\n<div class="worksheet" id="worksheet">\n'
1032        if self.__show_debug or show_debug:
1033            body += "<div class='debug_window'>"
1034            body += "<div class='debug_output'><pre id='debug_output'></pre></div>"
1035            body += "<textarea rows=5 id='debug_input' class='debug_input' "
1036            body += " onKeyPress='return debug_keypress(event);' "
1037            body += " onFocus='debug_focus();' onBlur='debug_blur();'></textarea>"
1038            body += "</div>"
1039
1040        body += main_body + '\n</div>\n'
1041
1042        # The blank space given by '<br>'*15  is needed so the input doesn't get
1043        # stuck at the bottom of the screen. This could be replaced by a region
1044        # such that clicking on it creates a new cell at the bottom of the worksheet.
1045        body += '<br>'*15
1046        body += '\n</div>\n'
1047
1048        body += '<div class="left_pane_bar" id="left_pane_bar" onClick="toggle_left_pane();"></div>\n'
1049        body += '<span class="pane" id="left_pane"><table bgcolor="white"><tr><td>\n'
1050        endpanespan = '</td></tr></table></span>\n'
1051
1052        body += '  <div class="worksheets_topbar">'
1053        body += '     <b>Worksheets</b> '
1054        body += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a class="left_panel_hide" onClick="toggle_left_pane()" class="worksheets_button" id="worksheets_button">Hide</a>'
1055        body += '<br></div>'
1056        body +=    add_new_worksheet_menu
1057        body += '  <div class="worksheet_list" id="worksheet_list">%s</div>\n'%self.worksheet_list_html(worksheet)
1058
1059        if worksheet is None:
1060            return body + endpanespan
1061
1062        body += '<script type="text/javascript">focus(%s)</script>\n'%(worksheet[0].id())
1063        body += '<script type="text/javascript">jsmath_init();</script>\n'
1064
1065        if worksheet_authorized:
1066            body += '<script type="text/javascript">worksheet_locked=false;</script>'
1067        else:
1068            body += '<script type="text/javascript">worksheet_locked=true;</script>'
1069
1070        if worksheet.computing():
1071            # Set the update checking back in motion.
1072            body += '<script type="text/javascript"> active_cell_list = %r; \n'%worksheet.queue_id_list()
1073            body += 'for(var i = 0; i < active_cell_list.length; i++)'
1074            body += '    cell_set_running(active_cell_list[i]); \n'
1075            body += 'start_update_check(); </script>\n'
1076
1077        #body += '<script type="text/javascript">toggle_left_pane()</script>'
1078        return body
1079
1080    def edit_window(self, worksheet):
1081        """
1082        Return a window for editing worksheet.
1083
1084        INPUT:
1085            worksheet -- a worksheet
1086        """
1087        t = worksheet.edit_text()
1088        t = t.replace('<','&lt;')
1089        body_html = ''
1090        body_html += '<h1 class="edit">SAGE Notebook: Editing Worksheet "%s"</h1>\n'%worksheet.name()
1091        body_html += """<b>Warnings:</b> You cannot undo after you save changes (yet).  All graphics will be deleted when you save.<br><br>"""
1092        body_html += '<form method="post" action="save" enctype="multipart/form-data">\n'
1093        body_html += '<input type="submit" value="Save Changes" name="button_save"/>\n'
1094        #body_html += '<input type="submit" value="Preview" name="button_preview"/>\n'
1095        body_html += '<input type="submit" value="Cancel" name="button_cancel"/>\n'
1096        body_html += '<textarea class="edit" id="cell_intext" rows="30" name="textfield">'+t+'</textarea>'
1097        body_html += '</form>'
1098        body_html += """The format is as follows: <pre>
1099Arbitrary HTML       
1100{{{
1101Input
1102///
1103Output
1104}}}
1105Arbitrary HTML
1106{{{
1107Input
1108///
1109Output
1110}}}
1111...
1112</pre>"""
1113
1114         
1115        s = """
1116        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
1117        <html><head><title>SAGE Wiki cell text </title>
1118        <style type="text/css">
1119
1120        textarea.edit {
1121            font-family: courier, monospace;
1122            font-size:12pt;
1123            border: 1px solid #8cacbb; 
1124            color: black;
1125            background-color: white;
1126            padding: 3px;
1127            width: 100%%;
1128            margin-top: 0.5em;
1129        }
1130        </style>
1131       
1132        <script type="text/javascript"> <!--
1133       
1134        %s
1135       
1136        function get_element(id) {
1137            if(document.getElementById)
1138                return document.getElementById(id);
1139            if(document.all)
1140                return document.all[id];
1141            if(document.layers)
1142                return document.layers[id];
1143        }
1144       
1145        function get_cell_list() {
1146            return window.opener.get_cell_list()
1147        }
1148
1149        function send_doc_html() {
1150            var cell_id_list = get_cell_list();
1151            var num = cell_id_list.length;
1152            var lastid = cell_id_list[num-1];
1153            var doc_intext = get_element('cell_intext').value; /*for testing doc_html*/
1154            window.opener.upload_doc_html(lastid,doc_intext);
1155        }
1156       
1157        function send_cell_text() {
1158            var cell_id_list = get_cell_list();
1159            var num = cell_id_list.length;
1160            var lastid = cell_id_list[num-1];
1161            var cell_intext = get_element('cell_intext').value;
1162            window.opener.upload_cell_text(lastid,cell_intext);
1163        }
1164
1165        function send_to_ws(do_eval) {
1166            var f = send_to_ws_callback;
1167            if (do_eval)
1168                f = send_to_ws_eval_callback;
1169            async_request('/delete_cell_all',f, "worksheet_id="+window.opener.get_worksheet_id());
1170        }
1171       
1172        function send_to_ws_callback(status, response_text) {
1173            window.opener.cell_delete_all_callback(status, response_text);
1174            var cell_intext = get_element('cell_intext').value;
1175            window.opener.insert_cells_from_wiki(cell_intext, false);
1176        }
1177       
1178        function send_to_ws_eval_callback(status, response_text) {
1179            window.opener.cell_delete_all_callback(status, response_text);
1180            var cell_intext = get_element('cell_intext').value;
1181            window.opener.insert_cells_from_wiki(cell_intext, true);
1182        }
1183       
1184
1185        function clear_wiki_window() {
1186            get_element('cell_intext').value = ' ';
1187        }
1188        --></script></head>
1189        <body>%s
1190        </body></html>"""%(js.async_lib(), body_html)
1191       
1192        return s
1193
1194    def help_window(self):
1195        try:
1196            return self._help_window
1197        except AttributeError:
1198            pass
1199        help = [
1200            ('Full Text Search of Docs and Source', 'Search the SAGE documentation by typing <pre>search_doc("my query")</pre> in an input cell and press shift-enter.  Search the source code of SAGE by typing <pre>search_src("my query")</pre> and pressing shift-enter.  Arbitrary regular expressions are allowed as queries.'),
1201            ('HTML', 'Begin an input block with %html and it will be output as HTML.  Use the &lt;sage>...&lt;/sage> tag to do computations in an HTML block and have the typeset output inserted.  Use &lt;$>...&lt;/$> and &lt;$$>...&lt;/$$> to insert typeset math in the HTML block.  This does <i>not</i> require latex.'),
1202            ('Shell', 'Begin a block with %sh to have the rest of the block evaluated as a shell script.  The current working directory is maintained.'),
1203            ('Autoevaluate Cells on Load', 'Any cells with "#auto" in the input is automatically evaluated when the worksheet is first opened.'),
1204            ('Create New Worksheet', "Use the menu on the left, or simply put a new worksheet name in the URL, e.g., if your notebook is at http://localhost:8000, then visiting http://localhost:8000/tests will create a new worksheet named tests."),
1205               ('Evaluate Input', 'Press shift-enter.  You can start several calculations at once.  If you press alt-enter instead, then a new cell is created after the current one.'),
1206                ('Timed Evaluation', 'Type "%time" at the beginning of the cell.'),
1207                ('Evaluate all Cells', 'Click <u>Eval All</u> in the upper right.'),
1208                ('Evaluate Cell using <b>GAP, Singular, etc.', 'Put "%gap", "%singular", etc. as the first input line of a cell; the rest of the cell is evaluated in that system.'),
1209                ('Typeset a Cell', 'Make the first line of the cell "%latex". The rest of the cell should be the body of a latex document.  Use \\sage{expr} to access SAGE from within the latex.  Evaluated typeset cells hide their input.  Use "%latex_debug" for a debugging version.  You must have latex for this to work.'),
1210               ('Typeset a slide', 'Same as typesetting a cell but use "%slide" and "%slide_debug"; will use a large san serif font.  You must have latex for this to work.'),
1211                ('Typesetting', 'Type "latex(objname)" for latex that you can paste into your paper.  Type "view(objname)" or "show(objname)", which will display a nicely typeset image (using javascript!).  You do <i>not</i> need latex for this to work.  Type "lprint()" to make it so output is often typeset by default.'),
1212                ('Move between cells', 'Use the up and down arrows on your keyboard.'),
1213                ('Interrupt running calculations',
1214                 'Click <u>Interrupt</u> in the upper right or press escape in any input cell. This will (attempt) to interrupt SAGE by sending many interrupts for several seconds; if this fails, it restarts SAGE (your worksheet is unchanged, but your session is reset).'),
1215                ('Tab completion', 'Press tab while the cursor is on an identifier. On some web browsers (e.g., Opera) you must use control-space instead of tab.'),
1216                ('Print worksheet', 'Click the print button.'),
1217                ('Help About',
1218                 'Type ? immediately after the object or function and press tab.'),
1219                ('Source Code',
1220                 'Put ?? after the object and press tab.'),
1221                ('Hide Cell Input',
1222                 'Put %hide at the beginning of the cell.  This can be followed by %gap, %latex, %maxima, etc.  Note that %hide must be first.  From the edit screen, use %hideall to hide a complete cell.'),
1223                ('Documentation',
1224                 'Click on <a href="/doc_browser?/?index.html">Documentation</a> in the upper right to browse the SAGE tutorial, reference manual, and other documentation.'),
1225                ('Insert New Cell',
1226                 'Put mouse between an output and input until the horizontal line appears and click.  If you press Alt-Enter in a cell, the cell is evaluated and a new cell is inserted after it.'),
1227                ('Delete Cell',
1228                 'Delete cell contents the press backspace.'),
1229                ('Text of Worksheet', 'Click the <u>Text</u> and <u>Doctext</u> links, which are very useful if you need to cut and paste chunks of your session into email or documentation.'),
1230                ('History', 'Click the <u>History</u> link for a history of commands entered in any worksheet of this notebook.  This appears in a popup window, which you can search (control-F) and copy and paste from.'),
1231                ('Hide/Show Output', 'Click on the left side of output to toggle between hidden, shown with word wrap, and shown without word wrap.'),
1232                ('Hide/Show All Output', 'Click <u>Hide</u> in the upper right to hide <i>all</i> output. Click <u>Show</u> to show all output.'),
1233                ('Variables',
1234                 'All variables and functions that you create during this session are listed on the left.  Even predefined variables that you overwrite will appear.'),
1235                ('Objects',
1236                 'All objects that you save in <i>any worksheet</i> are listed on the left.  Use "save(obj, name)" and "obj = load(name)" to save and load objects.'),
1237                ('Loading and Saving Sessions', 'Use "save_session name" to save all variables to an object with given name (if no name is given, defaults to name of worksheet).  Use "load_session name" to <i>merge</i> in all variables from a saved session.'),
1238                ('Loading and Saving Objects', 'Use "save obj1 obj2 ..." and "load obj1 obj2 ...".  This allows very easy moving of objects from one worksheet to another, and saving of objects for later use.'),
1239                ('Loading SAGE/Python Scripts', 'Use "load filename.sage" and "load filename.py".  Load is relative to the path you started the notebook in.  The .sage files are preparsed and .py files are not.   You may omit the .sage or .py extension.  Files may load other files.'),
1240                ('Attaching Scripts', 'Use "attach filename.sage" or "attach filename.py".  Attached files are automatically reloaded when the file changes.  The file $HOME/.sage/init.sage is attached on startup if it exists.'),
1241                ('Downloading and Uploading Worksheets',
1242                 'Click <u>Download</u> in the upper right to download a complete worksheet to a local .sws file, and click <a href="__upload__.html">Upload</a> to upload a saved worksheet to the notebook.  Note that <i>everything</i> that has been submitted is automatically saved to disk when you quit the notebook server (or type "%save_server" into a cell).'),
1243                ('Restart', 'Type "restart" to restart the SAGE interpreter for a given worksheet.  (You have to interrupt first.)'),
1244                ('Input Rules', "Code is evaluated by exec'ing (after preparsing).  Only the output of the last line of the cell is implicitly printed.  If any line starts with \"sage:\" or \">>>\" the entire block is assumed to contain text and examples, so only lines that begin with a prompt are executed.   Thus you can paste in complete examples from the docs without any editing, and you can write input cells that contains non-evaluated plain text mixed with examples by starting the block with \">>>\" or including an example."),
1245                ('Working Directory', 'Each block of code is run from its own directory.  The variable DIR contains the directory from which you started the SAGE notebook.  For example, to open a file in that directory, do "open(DIR+\'filename\')".'),
1246                ('Customizing the look', 'Learn about cascading style sheets (CSS), then create a file notebook.css in your $HOME/.sage directory.  Use "view source" on a notebook web page to see the CSS that you can override.'),
1247                ('Emacs Keybindings', 'If you are using GNU/Linux, you can change (or create) a <tt>.gtkrc-2.0</tt> file.  Add the line <tt>gtk-key-theme-name = "Emacs"</tt> to it.  See <a target="_blank" href="http://kb.mozillazine.org/Emacs_Keybindings_(Firefox)">this page</a> [mozillazine.org] for more details.'),
1248                ('More Help', 'Type "help(sage.server.notebook.notebook)" for a detailed discussion of the architecture of the SAGE notebook and a tutorial (or see the SAGE reference manual).'),
1249                ('Javascript Debugger', 'Type ?debug at the end of a worksheet url to enable the javascript debugger.  A pair of textareas will appear at the top of the worksheet -- the upper of which is for output, the lower is a direct interface to the page\'s javascript environment.  Type any eval()-able javascript into the input box and press shift+enter to execute it.  Type debug_append(str) to print to, and debug_clear() to clear the debug output window.'),
1250                ]
1251
1252        help.sort()
1253        s = """
1254        <br><hr>
1255        <style>
1256        div.help_window {
1257            background-color:white;
1258            border: 3px solid #3d86d0;
1259            top: 10ex;
1260            bottom:10%;
1261            left:25%;
1262            right:15%;
1263            padding:2ex;
1264        }
1265
1266
1267        table.help_window {
1268            background-color:white;
1269            width:100%;
1270        }
1271
1272        td.help_window_cmd {
1273            background-color: #f5e0aa;
1274            width:30%;
1275            padding:1ex;
1276            font-weight:bold;
1277        }
1278
1279        td.help_window_how {
1280            padding:1ex;
1281            width:70%;
1282        }
1283        </style>
1284        <h1 align=center><font color='darkred'>SAGE</font> Notebook Quickstart</h1>
1285        <div class="help_window">
1286
1287        A <i>worksheet</i> is an ordered list of SAGE calculations with output.
1288        A <i>session</i> is a worksheet and a set of variables in some state.
1289        A <i>notebook</i> is a collection of worksheets and saved objects.
1290        <br>
1291        <br>
1292        To get started with SAGE, <a href="doc_browser?/tut/?tut.html">view the tutorial</a>.
1293        <br><br>
1294                 
1295        <table class="help_window">
1296        """
1297        for x, y in help:
1298            s += '<tr><td class="help_window_cmd">%s</td><td class="help_window_how">%s</td></tr>\n'%(x,y)
1299        s += '</table></div>'
1300
1301        s +="""
1302        <br>
1303        AUTHORS: William Stein, Tom Boothby, and Alex Clemesha (with feedback from many people,
1304        especially Fernando Perez and Joe Wetherell).<br><br>
1305        LICENSE: All code included with the standard SAGE is <a href="/license.html">licensed
1306        either under the GPL or a GPL-compatible license</a>.
1307        <br>
1308        """
1309        self._help_window = s
1310        return s
1311
1312    def upload_window(self):
1313        return """
1314          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
1315          <html>
1316            <head>
1317              <title>Upload File</title>
1318              <style>%s</style>
1319              <script type='text/javascript'>%s</script>
1320            </head>
1321            <body onLoad="if(window.focus) window.focus()">
1322              <div class="upload_worksheet_menu" id="upload_worksheet_menu">
1323              <h1><font size=+3 color="darkred">SAGE</font>&nbsp;&nbsp;&nbsp;&nbsp;<font size=+1>Upload your Worksheet</font></h1>
1324              <hr>
1325              <form method="POST" action="upload_worksheet"
1326                    name="upload" enctype="multipart/form-data">
1327              <table><tr>
1328              <td>
1329              Worksheet file:&nbsp&nbsp&nbsp </td>
1330              <td><input class="upload_worksheet_menu" size="40" type="file" name="fileField" id="upload_worksheet_filename"></input></td>
1331              </tr>
1332              <tr><td></td><td></td></tr>
1333              <tr>
1334              <td></td><td><input type="button" class="upload_worksheet_menu" value="Upload Worksheet" onClick="form.submit(); window.close();"></td>
1335              </tr>
1336              </form><br>
1337              </div>
1338            </body>
1339          </html>
1340         """%(css.css(self.color()),js.javascript())
1341
1342    def doc_html(self,worksheet_id, css_href):
1343        try:
1344            W = self.get_worksheet_with_id(worksheet_id)
1345        except KeyError, msg:
1346            W = self.create_new_worksheet(worksheet_id)
1347            worksheet_id = W.id()
1348        head = self._doc_html_head(worksheet_id, css_href)
1349        body = self._doc_html_body(worksheet_id)
1350        if worksheet_id is not None:
1351           body += '<script type="text/javascript">worksheet_id="%s"; worksheet_filename="%s"; worksheet_name="%s"; toggle_left_pane(); </script>'%(worksheet_id, W.filename(), W.name())
1352
1353        return """
1354        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
1355        <html>
1356        <head>%s</head>
1357        <body>%s</body>
1358        </html>
1359        """%(head, body)
1360
1361    def html(self, worksheet_id=None, authorized=True,
1362                   show_debug=False, worksheet_authorized=True):
1363        if worksheet_id is None or worksheet_id == '':
1364            worksheet_id = None
1365            W = None
1366        else:
1367            try:
1368                W = self.get_worksheet_with_id(worksheet_id)
1369            except KeyError, msg:
1370                W = self.create_new_worksheet(worksheet_id)
1371                worksheet_id = W.id()
1372
1373        if authorized:
1374            body = self._html_body(worksheet_id, show_debug=show_debug,
1375                                   worksheet_authorized=worksheet_authorized)
1376        else:
1377            body = self._html_authorize()
1378
1379
1380        head = self._html_head(worksheet_id)
1381
1382        if worksheet_id is not None:
1383            head += '<script  type="text/javascript">worksheet_id="%s"; worksheet_filename="%s"; worksheet_name="%s";</script>'%(worksheet_id, W.filename(), W.name())
1384
1385        return """
1386        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
1387        <html>
1388        <head>%s</head>
1389        <body>%s</body>
1390        </html>
1391        """%(head, body)
1392
1393    def _html_authorize(self):
1394        return """
1395        <h1>SAGE Notebook Server</h1>
1396        <div id="mainbody" class="login">Sign in to the SAGE Notebook<br>
1397        <form>
1398        <table>
1399        <tr><td>
1400          <span class="username">Username:</span></td>
1401          <td><input name="username" class="username"
1402                      onKeyPress="if(is_submit(event)) login(username.value, password.value)"></td>
1403        </tr>
1404        <tr><td>
1405           <span class="password">Password:</span></td>
1406           <td><input name="password" class="username" type="password"
1407                      onKeyPress="if(is_submit(event)) login(username.value, password.value)"></td>
1408        </tr>
1409        <td>&nbsp</td>
1410        <td>
1411           <input type='button' onClick="login(username.value,password.value);" value="Sign in">
1412           </td></table>
1413                   </form></div>
1414     
1415        """
1416
1417    def format_completions_as_html(self, cell_id, completions):
1418        if len(completions) == 0:
1419            return ''
1420        lists = []
1421
1422        # compute the width of each column
1423        column_width = []
1424        for i in range(len(completions[0])):
1425            column_width.append(max([len(x[i]) for x in completions if i < len(x)]))
1426               
1427        for i in range(len(completions)):
1428            row = completions[i]
1429            for j in range(len(row)):
1430                if len(lists) <= j:
1431                    lists.append([])     
1432                cell = """
1433   <li id='completion%s_%s_%s' class='completion_menu_two'>
1434    <a onClick='do_replacement(%s, "%s"); return false;'
1435       onMouseOver='this.focus(); select_replacement(%s,%s);'
1436    >%s</a>
1437   </li>"""%(cell_id, i, j, cell_id, row[j], i,j,
1438             row[j])
1439             #row[j] + '&nbsp;'*(column_width[j]-len(row[j])) )
1440   
1441                lists[j].append(cell)
1442
1443        grid = "<ul class='completion_menu_one'>"
1444        for L in lists:
1445            s = "\n   ".join(L)
1446            grid += "\n <li class='completion_menu_one'>\n  <ul class='completion_menu_two'>\n%s\n  </ul>\n </li>"%s
1447
1448        return grid + "\n</ul>"
1449
1450
1451import sage.interfaces.sage0
1452import time
1453
1454def load_notebook(dir, username=None, password=None, color=None, system=None,
1455                  splashpage=None, address=None, port=None, secure=None):
1456    sobj = '%s/nb.sobj'%dir
1457    if os.path.exists(sobj):
1458        try:
1459            nb = load(sobj, compress=False)
1460        except:
1461            print "****************************************************************"
1462            print "  * * * WARNING   * * * WARNING   * * * WARNING   * * * "
1463            print "WARNING -- failed to load notebook data. Trying the backup file."
1464            print "****************************************************************"
1465            try:
1466                nb = load('%s/nb-backup.sobj'%dir, compress=False)
1467            except:
1468                print "Recovering from last op save failed."
1469                print "Trying save from last startup."
1470                nb = load('%s/nb-older-backup.sobj'%dir, compress=False)
1471
1472        nb.delete_doc_browser_worksheets()
1473        nb.set_directory(dir)
1474        if not (username is None):
1475            nb.set_auth(username=username, password=password)
1476        if not (color is None):
1477            nb.set_color(color)
1478        if not system is None:
1479            nb.set_system(system)
1480        if not splashpage is None:
1481            nb.set_splashpage(splashpage)
1482        nb.set_not_computing()
1483    else:
1484        nb = Notebook(dir,username=username,password=password, color=color,
1485                      system=system)
1486
1487    nb.address = address
1488    nb.port = port
1489    nb.secure = secure
1490    return nb
1491
1492## IMPORTANT!!! If you add any new input variable to notebook,
1493## you *must* similarly modify the restart_on_crash block
1494## at the beginning of the definition of notebook!!
1495def notebook(dir         ='sage_notebook',
1496             port        = 8000,
1497             address     = 'localhost',
1498             open_viewer = True,
1499             max_tries   = 10,
1500             username    = None,
1501             password    = None,
1502             color       = None,
1503             system      = None,
1504             jsmath      = True,
1505             show_debug  = False,
1506             splashpage  = True,
1507             warn        = True,
1508             ignore_lock = False,
1509             log_server = False,
1510             restart_on_crash = False,
1511             auto_restart = 1800,
1512             multisession = True):
1513    r"""
1514    Start a SAGE notebook web server at the given port.
1515
1516    INPUT:
1517        dir -- (default: 'sage_notebook') name of the server directory; your
1518                sessions are saved in a directory with that name.  If
1519                you restart the server with that same name then it will
1520                restart in the state you left it, but with none of the
1521                variables defined (you have to re-eval blocks).
1522        port -- (default: 8000) port on computer where the server is served
1523        address -- (default: 'localhost') address that the server
1524                   will listen on
1525        open_viewer -- bool (default: True); if True, pop up a web browser at the URL
1526        max_tries -- (default: 10) maximum number of ports > port to try in
1527                     case given port can't be opened.
1528        username -- user name used for authenticated logins
1529        password -- password used for authenticated logins
1530        color -- string or pair of html colors, e.g.,
1531                    'gmail'
1532                    'grey'
1533                    ('#ff0000', '#0000ff')
1534        system -- (string) default computer algebra system to use for new
1535                  worksheets, e.g., 'maxima', 'gp', 'axiom', 'mathematica', 'macaulay2',
1536                  'singular', 'gap', 'octave', 'maple', etc.  (even 'latex'!)
1537        jsmath -- whether not to enable javascript typset output for math.
1538        debug -- whether or not to show a javascript debugging window
1539        splashpage -- whether or not to show a splash page when no worksheet is specified.
1540                      you can place a file named index.html into the notebook directory that
1541                      will be shown in place of the default.
1542
1543        restart_on_crash -- if True (the default is False), the server
1544                      will be automatically restarted if it crashes in
1545                      any way.  Use this on a public servers that many
1546                      people might use, and which might be subjected
1547                      to intense usage.  NOTE: Log messages are only displayed
1548                      every 5 seconds in this mode.
1549        auto_restart -- if restart_on_crash is True, always restart
1550                      the server every this many seconds.
1551        multisession -- (default: True) The default is for there to be
1552                       one sage session for each worksheet.  If this
1553                       is False, then there is just one global SAGE
1554                       session, like with Mathematica.
1555
1556    NOTES:
1557
1558    When you type \code{notebook(...)}  you start a web server on the
1559    machine you type that command on.  You don't connect to another
1560    machine.  So do this if you want to start a SAGE notebook
1561    accessible from anywhere:
1562
1563    \begin{enumerate}
1564    \item Figure out the external address of your server, say
1565          'www.sagemath.org', for example.
1566    \item On your server, type
1567        notebook('mysession', address='www.sagemath.org')
1568    \item Assuming you have permission to open a port on that
1569       machine, it will startup and display a URL, e.g.,
1570           \url{http://www.sagemath.org:8000}
1571       Note this URL.
1572    \item Go to any computer in the world (!), or at least
1573       behind your firewall, and use any web browser to
1574       visit the above URL.  You're using \sage.
1575    \end{enumerate}
1576   
1577    \note{There are no security precautions in place \emph{yet}!  If
1578    you open a server as above, and somebody figures this out, they
1579    could use their web browser to connect to the same sage session,
1580    and type something nasty like \code{os.system('cd; rm -rf *')}
1581    and your home directory would be hosed.   I'll be adding an
1582    authentication screen in the near future.  In the meantime
1583    (and even then), you should consider creating a user with
1584    very limited privileges (e.g., empty home directory).}
1585
1586    FIREFOX ISSUE:
1587    If your default web browser if Firefox, then notebook will
1588    open a copy of Firefox at the given URL.  You should
1589    definitely set the "open links in new tabs" option in
1590    Firefox, or you might loose a web page you were looking at.
1591    To do this, just go to
1592   
1593         Edit --> Preferences --> Tabs
1594         
1595    and in "Open links from other apps" select the middle button
1596    instead of the bottom button.
1597    """
1598
1599    import worksheet
1600    worksheet.init_sage_prestart()
1601    worksheet.multisession = multisession
1602
1603    if '/' in dir:
1604        # change current working directory and make the notebook
1605        # directory a subdirectory of the working directory.
1606        base, dir = os.path.split(dir)
1607        os.chdir(base)
1608   
1609    if restart_on_crash:
1610        # Start a new subprocess
1611        def f(x):  # format for passing on
1612            if x is None:
1613                return 'None'
1614            elif isinstance(x, str):
1615                return "'%s'"%x
1616            else:
1617                return str(x)
1618        while True:
1619            S = sage.interfaces.sage0.Sage()
1620            time.sleep(1)
1621            S.eval("from sage.server.notebook.notebook import notebook")
1622            cmd = "notebook(dir=%s,port=%s, address=%s, open_viewer=%s, max_tries=%s, username=%s, password=%s, color=%s, system=%s, jsmath=%s, show_debug=%s, splashpage=%s, warn=%s, ignore_lock=%s, log_server=%s, restart_on_crash=False, multisession=%s)"%(
1623                f(dir), f(port), f(address), f(open_viewer), f(max_tries), f(username),
1624                f(password), f(color), f(system), f(jsmath), f(show_debug), f(splashpage),
1625                f(warn), f(ignore_lock), f(log_server), f(multisession)
1626                )
1627            print cmd
1628            S._send(cmd)
1629            tm = 0
1630            while True:
1631                s = S._get()[1].strip()
1632                if len(s) > 0:
1633                    print s
1634                if not S.is_running():
1635                    break
1636                time.sleep(5)
1637                tm += 5
1638                if tm > auto_restart:
1639                    S.quit()
1640                    break
1641            # end while
1642        # end while
1643        S.quit()
1644        return
1645
1646    if os.path.exists(dir):
1647        if not os.path.isdir(dir):
1648            raise RuntimeError, '"%s" is not a valid SAGE notebook directory (it is not even a directory).'%dir
1649        if not (os.path.exists('%s/nb.sobj'%dir) or os.path.exists('%s/nb-backup.sobj'%dir)):
1650            raise RuntimeError, '"%s" is not a valid SAGE notebook directory (missing nb.sobj).'%dir
1651        pidfile = '%s/pid'%dir
1652
1653        if os.path.exists(pidfile) and not ignore_lock:
1654            f = file(pidfile)
1655            try:
1656                p, oldport = f.readlines()
1657            except ValueError:
1658                p = file(pidfile).read()
1659                oldport = port
1660            f.close()
1661            try:
1662                #This is a hack to check whether or not the process is running.
1663                os.kill(int(p),0)
1664                print "\n".join([" This notebook appears to be running with PID %s.  If it is"%p,
1665                                 " not responding, you will need to kill that process to continue.",
1666                                 " If another (non-sage) process is running with that PID, call",
1667                                 " notebook(..., ignore_lock = True, ...). " ])
1668                if open_viewer:
1669                   open_page(address, int(oldport))
1670                return
1671            except OSError:
1672                pass
1673
1674    nb = load_notebook(dir, username, password, color,system, splashpage)
1675   
1676    nb.save()
1677    shutil.copy('%s/nb.sobj'%dir, '%s/nb-older-backup.sobj'%dir)
1678    nb.set_debug(show_debug)
1679    nb.set_log_server(log_server)
1680    if warn and address!='localhost' and username==None:
1681        print "WARNING -- it is *extremely* dangerous to let the server listen"
1682        print "on an external port without at least setting a username/password!!"
1683    nb.start(port, address, max_tries, open_viewer, jsmath=jsmath)
1684    from sage.interfaces.quit import expect_quitall
1685    expect_quitall(verbose=False)
1686    from sage.misc.misc import delete_tmpfiles
1687    delete_tmpfiles()
1688    if os.path.exists('%s/pid'%dir):
1689        os.remove('%s/pid'%dir)
1690    return nb
1691   
1692
1693########################################################################
1694# NOTES ABOUT THE restart_on_crash option to notebook.
1695## I made some changes to the notebook command so it has the option of running
1696## the server as a completely separate process, which will restart it if it
1697## dies in any way.   I installed this on sage.math and started those servers
1698## running with this.  So if anybody has any systematic way to crash the notebook
1699## servers, please try it!  (The only one I have is to tell a computer lab
1700## full of 40 high school students to all try to crash the server at once...)
1701##
1702## Basically what happens is this:
1703##
1704##   1. You start a Python process then run the notebook command with the
1705##      restart_on_kill option True.
1706##   2. The notebook command starts another Python running, and in that
1707##      it runs the notebook command.  This uses the Sage0 pexpect interface.
1708##   3. It then monitors the process started in 2 -- if the process dies
1709##      or terminates, then 2 occurs again.  The server log output
1710##      is updated every 5 seconds.   If ctrl-c is received,
1711##      then everything cleans up and terminates.
1712##
1713## This means that the SAGE notebook can only currently be totally
1714## crashed if the Python simple http server gets into a hung state.
1715## From looking at the server logs after past crashes, this doesn't
1716## seem to ever happen.  So now instead of the server crashing
1717## under crazy loads, etc., it will automatically reset within seconds --
1718## this would of course kill all running worksheets (which is bad),
1719## but is much better than having the whole sage notebook go down
1720## until it is manually restarted!   In the long run, of course, using
1721## Twisted for the web server should hopefully mean that it doesn't
1722## crash...
1723###############################################################
Note: See TracBrowser for help on using the repository browser.