| 1 | r""" |
|---|
| 2 | SAGE Notebook Interface |
|---|
| 3 | |
|---|
| 4 | AUTHORS: |
|---|
| 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 | |
|---|
| 9 | The SAGE graphical user interface is unusual in that it operates via |
|---|
| 10 | your web browser. It provides you with SAGE worksheets that you can |
|---|
| 11 | edit and evaluate, which contain scalable typeset mathematics and |
|---|
| 12 | beautiful 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 | |
|---|
| 19 | The SAGE notebook should fully work with Firefox (and Mozilla). It |
|---|
| 20 | may work to some extent with Safari and Opera. Internet Explorer is |
|---|
| 21 | not supported. |
|---|
| 22 | |
|---|
| 23 | \subsection{Tutorial} |
|---|
| 24 | Here are some things to try in the the notebook to get a feeling |
|---|
| 25 | for it. |
|---|
| 26 | |
|---|
| 27 | Type "2+2" in the blank box and press "shift-enter". |
|---|
| 28 | The line below"2+2" will turn a different color for a moment while a SAGE kernel |
|---|
| 29 | fires up and computes the answer. |
|---|
| 30 | |
|---|
| 31 | Your cursor should now be in the next box down. Type \code{a = 2\^1000} |
|---|
| 32 | and press return, then "a" alone on the second line, then shift-return. |
|---|
| 33 | You'll see a big number. Also, "a" will appear in the variable |
|---|
| 34 | browser in the left of the screen. Next, click just to the |
|---|
| 35 | left of the big number in the blue-ish area. The number will shrink |
|---|
| 36 | a little and change to occupy only one line. You can see the whole |
|---|
| 37 | number using your browser's horizontal scroll bar. Click again and |
|---|
| 38 | the number vanishes, to be replaced by a horizontal bar. Click on the |
|---|
| 39 | bar and the number is back. If you click "Hide Output" in the upper |
|---|
| 40 | right, all output disappears. |
|---|
| 41 | |
|---|
| 42 | Next try graphics! Type "show(plot(sin,0,10))" into an empty |
|---|
| 43 | box and hit shift-enter. You'll get a graph of sin. Try another |
|---|
| 44 | function, e.g., |
|---|
| 45 | \begin{verbatim} |
|---|
| 46 | show(plot(lambda x: sin(x)^2 - cos(2*x)^3, -5,5)) |
|---|
| 47 | \end{verbatim} |
|---|
| 48 | Click on the left side of the figure (twice) to make it disappear. |
|---|
| 49 | |
|---|
| 50 | One important feature of the SAGE notebook, is that you can |
|---|
| 51 | "queue up" a bunch of calculations in a row, *while* still editing the |
|---|
| 52 | notebook! As an example, consider computing factorials, which takes a |
|---|
| 53 | while (but not forever). First, enter the following in a blank box and |
|---|
| 54 | press"shift-return": |
|---|
| 55 | \begin{verbatim} |
|---|
| 56 | def f(n): |
|---|
| 57 | return len(str(factorial(10^n))) |
|---|
| 58 | \end{verbatim} |
|---|
| 59 | This defines a function that takes a while to compute. For example, |
|---|
| 60 | time the execution of "f(5)", by typing (in a new box), "time f(5)". |
|---|
| 61 | It should take a few seconds. Next try |
|---|
| 62 | "f(6)", which takes quite a while (about 21 seconds on sage.math). |
|---|
| 63 | While f(6) is being computed, note that the output line for f(6) is a |
|---|
| 64 | different color, indicating that it is being computed |
|---|
| 65 | While f(6) is computing (if it finishes first, restart it by |
|---|
| 66 | just 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 |
|---|
| 68 | result doesn't get computed immediately. You can enter several more |
|---|
| 69 | lines as well, etc.; when the f(6) finally finishes, SAGE goes on |
|---|
| 70 | to compute "f(4)". You can queue up dozens of calculations. For |
|---|
| 71 | example, if you hit the "Evaluate" link in the upper right, the |
|---|
| 72 | whole worksheet is queued up for computation. Try it. When the |
|---|
| 73 | computation gets stuck on "f(6)", hit the interrupt button (or press escape) |
|---|
| 74 | and the queued up calculations are cancelled. |
|---|
| 75 | |
|---|
| 76 | Click "Hide Output" in the upper right. You'll see just your |
|---|
| 77 | input and some little boxes; clicking on the boxes reveals output. |
|---|
| 78 | |
|---|
| 79 | You can also embed nicely typeset math. Try this: |
|---|
| 80 | \begin{verbatim} |
|---|
| 81 | f = maxima('sin(x^2)') |
|---|
| 82 | g = f.integrate('x') |
|---|
| 83 | view(g) |
|---|
| 84 | \end{verbatim} |
|---|
| 85 | |
|---|
| 86 | If this silently fails, type "view(g, debug=True)" instead. |
|---|
| 87 | You need latex and the "convert" and "gs" commands, (use |
|---|
| 88 | an "apt-get install imagemagick gs"). Anyways, you get |
|---|
| 89 | a nicely typeset formula. Try a matrix next: |
|---|
| 90 | \begin{verbatim} |
|---|
| 91 | A = MatrixSpace(QQ, 5).random_element() |
|---|
| 92 | view(A) |
|---|
| 93 | \end{verbatim} |
|---|
| 94 | Try typing this into a new box: |
|---|
| 95 | \begin{verbatim} |
|---|
| 96 | %latex |
|---|
| 97 | Consider the matrix $$A = \sage{A},$$ |
|---|
| 98 | which has square $$A^2 = \sage{A^2}.$$ |
|---|
| 99 | \end{verbatim} |
|---|
| 100 | If you would like to typeset a slide (suitable for presentation), |
|---|
| 101 | use \%slide instead. |
|---|
| 102 | Here is another example: |
|---|
| 103 | \begin{verbatim} |
|---|
| 104 | %latex |
|---|
| 105 | The first ten squares are |
|---|
| 106 | $$ |
|---|
| 107 | \sage{', '.join([str(sq(i)) for i in range(1,11)])} |
|---|
| 108 | $$ |
|---|
| 109 | |
|---|
| 110 | The 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} |
|---|
| 117 | Make the first line of the input block \code{\%gap} |
|---|
| 118 | \code{\%magma}, or \code{\%gp}, etc. The rest of the block |
|---|
| 119 | is fed directly to the corresponding interpreter. |
|---|
| 120 | In this way you can make a single session that has input blocks |
|---|
| 121 | that work with a range of different systems. |
|---|
| 122 | |
|---|
| 123 | (Note -- there is currently no support for |
|---|
| 124 | pulling in objects and evaluating code in SAGE by typing |
|---|
| 125 | "sage(...)" inside the input block. This is planned.) |
|---|
| 126 | |
|---|
| 127 | \subsubsection{Typesetting Mathematics} |
|---|
| 128 | SAGE \emph{includes} jsMath, which is an implementation of the TeX |
|---|
| 129 | math layout engine in javascript. If you use the show or view |
|---|
| 130 | commands, they display a given SAGE object typeset using jsmath. |
|---|
| 131 | Moreover, if you put \code{\%jsmath} at the beginning of an input |
|---|
| 132 | cell, 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} |
|---|
| 137 | To add a new cell, click on a little black line that appears when you |
|---|
| 138 | hover between any two cells, or above the top one. To delete a cell |
|---|
| 139 | delete all its contents, then hit backspace one more time. The cell |
|---|
| 140 | vanishes forever. |
|---|
| 141 | |
|---|
| 142 | You can also move back and forth between cells using the up and down |
|---|
| 143 | arrow. In particular, when you are at the top of a cell and press |
|---|
| 144 | the up arrow the cursor jumps to the previous cell. |
|---|
| 145 | Press control-enter in a cell to create a new cell after the |
|---|
| 146 | current cell. |
|---|
| 147 | |
|---|
| 148 | There is no direct support for moving and reorganizing cells, though |
|---|
| 149 | you can copy and paste any individual cell into another one. However, |
|---|
| 150 | the "Text" buttons provide the full text of the worksheet in a very |
|---|
| 151 | convenient format for copy and paste. |
|---|
| 152 | |
|---|
| 153 | |
|---|
| 154 | \subsubsection{History} |
|---|
| 155 | Click the history button near the top to pop up a history of the last |
|---|
| 156 | 1000 (or so) input cells. After a huge amount of design discussion about |
|---|
| 157 | how to design a history system, a simple popup with the text of |
|---|
| 158 | previous commands seems like the best choice. It's incredibly simple, |
|---|
| 159 | yet provides an incredible amount of functionality, especially because |
|---|
| 160 | that popup window can be easily searched (at least in Firefox), pasted |
|---|
| 161 | from, etc., and refreshed (use F5 or Ctrl-R). |
|---|
| 162 | |
|---|
| 163 | |
|---|
| 164 | \subsubsection{Introspection} |
|---|
| 165 | To find all completions for an identifier you are typing press |
|---|
| 166 | the tab key. This should work exactly like IPython, and even |
|---|
| 167 | respects the \code{trait_names()} method. |
|---|
| 168 | |
|---|
| 169 | To find help for any object in a line, put ? after it |
|---|
| 170 | and press the tab key. The cursor must be somewhere in the identifier |
|---|
| 171 | with the question mark after it. For source code, put ?? after |
|---|
| 172 | the identifier and press tab. You can also put an identifier by |
|---|
| 173 | itself on a line with ? (or ??) after it and press shift-enter. |
|---|
| 174 | |
|---|
| 175 | To get extensive help on an object, type "help(object)" and press |
|---|
| 176 | return. This works, since I set the PAGER to "cat", and I strip out |
|---|
| 177 | control codes that appear in the output. And this isn't annoying, |
|---|
| 178 | since web browsers are very good for scrolling through long output. |
|---|
| 179 | |
|---|
| 180 | |
|---|
| 181 | \subsubsection{Saving and Loading Individual Objects} |
|---|
| 182 | When you start a notebook you give a name argument |
|---|
| 183 | to it, and it creates a directory. Inside that directory there |
|---|
| 184 | will be many worksheets (which you can use all at once and easily |
|---|
| 185 | flip through -- not implemented yet), and an object store. |
|---|
| 186 | You can save and load individual objects (using save and load), and they'll |
|---|
| 187 | be listed in the box on the bottom let, e.g., try |
|---|
| 188 | |
|---|
| 189 | a = 5 |
|---|
| 190 | save a |
|---|
| 191 | |
|---|
| 192 | and you'll see the "a" appear there. You can load and save objects |
|---|
| 193 | from any worksheet in any other one. (Currently the only way to delete |
|---|
| 194 | objects from the list of saved objects is to remove the object from |
|---|
| 195 | the objects subdirectory.) |
|---|
| 196 | |
|---|
| 197 | \subsubsection{Pasting in Examples} |
|---|
| 198 | Code is evaluated by exec'ing (after preparsing). Only the output |
|---|
| 199 | of the last line of the cell is implicitly printed. If any line |
|---|
| 200 | starts with "sage:" or ">>>" the {\em entire block} is assumed to |
|---|
| 201 | contain text and examples, and only lines that begin with a |
|---|
| 202 | prompt are executed. Thus you can paste in *complete examples* |
|---|
| 203 | from the docs without any editing, and you can write input |
|---|
| 204 | cells that contains non-evaluated plain text mixed with |
|---|
| 205 | examples 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 | |
|---|
| 210 | The SAGE notebook is very persistent. Every time you submit |
|---|
| 211 | a cell for computation, the state of the notebook is saved (a |
|---|
| 212 | few kb's file). If you quit the notebook and reload, it will |
|---|
| 213 | have everything you typed from the previous session, along |
|---|
| 214 | with all output. |
|---|
| 215 | Firefox has an excellent undo function for text input cells. |
|---|
| 216 | Just hit control-z to have ``infinite undo'' for the input |
|---|
| 217 | you've entered in that particular cell. |
|---|
| 218 | |
|---|
| 219 | You can save all variables in a current session by typing |
|---|
| 220 | \code{save_session [optional_name]}. You can then load |
|---|
| 221 | those session variables into another worksheet using |
|---|
| 222 | \code{load_session}, or load into the same worksheet next |
|---|
| 223 | time you use it. |
|---|
| 224 | |
|---|
| 225 | \subsubsection{Architecture} |
|---|
| 226 | |
|---|
| 227 | The SAGE Notebook is an ``AJAX application'' that can run either |
|---|
| 228 | entirely locally on your desktop machine, or partly on |
|---|
| 229 | a server and via a web browser that could be located somewhere |
|---|
| 230 | else. |
|---|
| 231 | If you run the server and allow remote access (by setting |
|---|
| 232 | address when starting the notebook), you should also set |
|---|
| 233 | the username and password, so not just anybody can access |
|---|
| 234 | the notebook. |
|---|
| 235 | |
|---|
| 236 | Anywhere, 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 | |
|---|
| 262 | When you use the SAGE Notebook, you are mainly interacting with a |
|---|
| 263 | javascript program. When you do something serious, e.g., request |
|---|
| 264 | computation of some input, create a new cell, etc., a request is made |
|---|
| 265 | from your web browser to the web server telling it what is going on. |
|---|
| 266 | If it's a calculation, the web server tells the SAGE server to get |
|---|
| 267 | started on the calculation, and tells the web browser to check several |
|---|
| 268 | times a second whether there is anything new with the calculation. |
|---|
| 269 | When something new appears it fills that in. This continues until all |
|---|
| 270 | calculations are done. During this time, you can edit cells, create |
|---|
| 271 | new cells, submit more computations, etc. Note that output is |
|---|
| 272 | updated as the computation proceeds, so you can verbosely watch |
|---|
| 273 | a computation progress. For example, try the following from the SAGE |
|---|
| 274 | Notebook: |
|---|
| 275 | |
|---|
| 276 | \begin{verbatim} |
|---|
| 277 | import time |
|---|
| 278 | for i in range(10): |
|---|
| 279 | print i |
|---|
| 280 | time.sleep(0.5) |
|---|
| 281 | \end{verbatim} |
|---|
| 282 | |
|---|
| 283 | You get to watch as the integers from 1 to 10 are "computed". |
|---|
| 284 | Actually, getting this output to be reported as the computation |
|---|
| 285 | proceeds is, I think, \emph{crucial} to making a really usable SAGE |
|---|
| 286 | GUI--users (i.e., me) want to run huge computations and watch the |
|---|
| 287 | output progress. |
|---|
| 288 | |
|---|
| 289 | The architecture is also good from the point of view of being able to |
|---|
| 290 | interrupt running computations. What happens when you request an |
|---|
| 291 | interrupt is that the web browser sends a message to the web server, |
|---|
| 292 | which in turn tells the SAGE server to stop computing by sending it |
|---|
| 293 | many interrupt signals (for several seconds) until it either stops, or |
|---|
| 294 | if it's really frozen (due to a bug, or calling into a C function that |
|---|
| 295 | isn't properly wrapped in signal handling, or maybe you run an |
|---|
| 296 | interactive program, e.g., via "os.system('...')"), it'll just kill that SAGE server |
|---|
| 297 | and start a new one. The result is that the |
|---|
| 298 | user doesn't get a frozen web browser or browser interface at any point, |
|---|
| 299 | and even if the whole SAGE process went down and froze, at least all |
|---|
| 300 | your input and output from your session is still there in your |
|---|
| 301 | browser. The only thing you've lost is the definition of all your |
|---|
| 302 | variables. Hit "shift-enter" a few times or "evaluate all" and you're |
|---|
| 303 | back in shape. This is much better than having to restart the command |
|---|
| 304 | prompt (e.g., with a terminal interface), then paste back in all your |
|---|
| 305 | setup code, etc., Also, you can save variables as you go easily (via |
|---|
| 306 | the "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 | |
|---|
| 341 | import os |
|---|
| 342 | import shutil |
|---|
| 343 | import socket |
|---|
| 344 | import re # regular expressions |
|---|
| 345 | |
|---|
| 346 | # SAGE libraries |
|---|
| 347 | from sage.structure.sage_object import SageObject, load |
|---|
| 348 | from sage.misc.viewer import browser |
|---|
| 349 | from sage.misc.misc import alarm, cancel_alarm |
|---|
| 350 | from sage.server.misc import print_open_msg |
|---|
| 351 | |
|---|
| 352 | # SAGE Notebook |
|---|
| 353 | import css # style |
|---|
| 354 | import js # javascript |
|---|
| 355 | import server # web server |
|---|
| 356 | import worksheet # individual worksheets (which make up a notebook) |
|---|
| 357 | import config # internal configuration stuff (currently, just keycodes) |
|---|
| 358 | import keyboards # keyboard layouts |
|---|
| 359 | |
|---|
| 360 | MAX_WORKSHEETS = 4096 # do not change this willy nilly; that would break existing notebooks (and there is no reason to). |
|---|
| 361 | MAX_HISTORY_LENGTH = 500 |
|---|
| 362 | WRAP_NCOLS = 80 |
|---|
| 363 | |
|---|
| 364 | JSMATH = True |
|---|
| 365 | |
|---|
| 366 | def open_page(address, port): |
|---|
| 367 | cmd = '%s http://%s:%s 1>&2 >/dev/null &'%(browser(), address, port) |
|---|
| 368 | os.system(cmd) |
|---|
| 369 | |
|---|
| 370 | class 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('<','<') |
|---|
| 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('<','<') |
|---|
| 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') # ' '*(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(' ',' ') |
|---|
| 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()">></a>\n' |
|---|
| 897 | body += ' <a class="slide_arrow" onClick="slide_last()">>></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"> </div>\n' |
|---|
| 902 | body += ' <div class="slideshow_progress_text" id="slideshow_progress_text"> </div>\n' |
|---|
| 903 | body += ' </div>\n' |
|---|
| 904 | body += ' <div class="slideshow_control">\n' |
|---|
| 905 | body += ' <a class="slide_arrow" onClick="slide_first()"><<</a>\n' |
|---|
| 906 | body += ' <a class="slide_arrow" onClick="slide_prev()"><</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> SAGE Notebook running from <tt>%s</tt>."%dir |
|---|
| 968 | main_body+= self.help_window() |
|---|
| 969 | main_body += " 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 | <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()">></a>\n' |
|---|
| 1017 | body += ' <a class="slide_arrow" onClick="slide_last()">>></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"> </div>\n' |
|---|
| 1022 | body += ' <div class="slideshow_progress_text" id="slideshow_progress_text"> </div>\n' |
|---|
| 1023 | body += ' </div>\n' |
|---|
| 1024 | body += ' <div class="slideshow_control">\n' |
|---|
| 1025 | body += ' <a class="slide_arrow" onClick="slide_first()"><<</a>\n' |
|---|
| 1026 | body += ' <a class="slide_arrow" onClick="slide_prev()"><</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 += ' <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('<','<') |
|---|
| 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> |
|---|
| 1099 | Arbitrary HTML |
|---|
| 1100 | {{{ |
|---|
| 1101 | Input |
|---|
| 1102 | /// |
|---|
| 1103 | Output |
|---|
| 1104 | }}} |
|---|
| 1105 | Arbitrary HTML |
|---|
| 1106 | {{{ |
|---|
| 1107 | Input |
|---|
| 1108 | /// |
|---|
| 1109 | Output |
|---|
| 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 <sage>...</sage> tag to do computations in an HTML block and have the typeset output inserted. Use <$>...</$> and <$$>...</$$> 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> <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:    </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> </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] + ' '*(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 | |
|---|
| 1451 | import sage.interfaces.sage0 |
|---|
| 1452 | import time |
|---|
| 1453 | |
|---|
| 1454 | def 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!! |
|---|
| 1495 | def 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 | ############################################################### |
|---|