##// END OF EJS Templates
Merge pull request #4198 from jdfreder/versionconversion_nbconvert...
Min RK -
r13346:b31eb2f2 merge
parent child Browse files
Show More
@@ -0,0 +1,72 b''
1 """API for converting notebooks between versions.
2
3 Authors:
4
5 * Jonathan Frederic
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 import re
20
21 from .reader import get_version, versions
22
23 #-----------------------------------------------------------------------------
24 # Functions
25 #-----------------------------------------------------------------------------
26
27 def convert(nb, to_version):
28 """Convert a notebook node object to a specific version. Assumes that
29 all the versions starting from 1 to the latest major X are implemented.
30 In other words, there should never be a case where v1 v2 v3 v5 exist without
31 a v4. Also assumes that all conversions can be made in one step increments
32 between major versions and ignores minor revisions.
33
34 PARAMETERS:
35 -----------
36 nb : NotebookNode
37 to_version : int
38 Major revision to convert the notebook to. Can either be an upgrade or
39 a downgrade.
40 """
41
42 # Get input notebook version.
43 (version, version_minor) = get_version(nb)
44
45 # Check if destination is current version, if so return contents
46 if version == to_version:
47 return nb
48
49 # If the version exist, try to convert to it one step at a time.
50 elif to_version in versions:
51
52 # Get the the version that this recursion will convert to as a step
53 # closer to the final revision. Make sure the newer of the conversion
54 # functions is used to perform the conversion.
55 if to_version > version:
56 step_version = version + 1
57 convert_function = versions[step_version].upgrade
58 else:
59 step_version = version - 1
60 convert_function = versions[version].downgrade
61
62 # Convert and make sure version changed during conversion.
63 converted = convert_function(nb)
64 if converted.get('nbformat', 1) == version:
65 raise Exception("Cannot convert notebook from v%d to v%d. Operation" \
66 "failed silently." % (major, step_version))
67
68 # Recursively convert until target version is reached.
69 return convert(converted, to_version)
70 else:
71 raise Exception("Cannot convert notebook to v%d because that " \
72 "version doesn't exist" % (to_version))
@@ -0,0 +1,107 b''
1 """API for reading notebooks.
2
3 Authors:
4
5 * Jonathan Frederic
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 import json
20
21 import v1
22 import v2
23 import v3
24
25 versions = {
26 1: v1,
27 2: v2,
28 3: v3,
29 }
30
31 #-----------------------------------------------------------------------------
32 # Code
33 #-----------------------------------------------------------------------------
34
35 class NotJSONError(ValueError):
36 pass
37
38 def parse_json(s, **kwargs):
39 """Parse a JSON string into a dict."""
40 try:
41 nb_dict = json.loads(s, **kwargs)
42 except ValueError:
43 # Limit the error message to 80 characters. Display whatever JSON will fit.
44 raise NotJSONError(("Notebook does not appear to be JSON: %r" % s)[:77] + "...")
45 return nb_dict
46
47 # High level API
48
49 def get_version(nb):
50 """Get the version of a notebook.
51
52 Parameters
53 ----------
54 nb : dict
55 NotebookNode or dict containing notebook data.
56
57 Returns
58 -------
59 Tuple containing major (int) and minor (int) version numbers
60 """
61 major = nb.get('nbformat', 1)
62 minor = nb.get('nbformat_minor', 0)
63 return (major, minor)
64
65
66 def reads(s, **kwargs):
67 """Read a notebook from a json string and return the
68 NotebookNode object.
69
70 This function properly reads notebooks of any version. No version
71 conversion is performed.
72
73 Parameters
74 ----------
75 s : unicode
76 The raw unicode string to read the notebook from.
77
78 Returns
79 -------
80 nb : NotebookNode
81 The notebook that was read.
82 """
83 nb_dict = parse_json(s, **kwargs)
84 (major, minor) = get_version(nb_dict)
85 if major in versions:
86 return versions[major].to_notebook_json(nb_dict, minor=minor)
87 else:
88 raise NBFormatError('Unsupported nbformat version %s' % major)
89
90
91 def read(fp, **kwargs):
92 """Read a notebook from a file and return the NotebookNode object.
93
94 This function properly reads notebooks of any version. No version
95 conversion is performed.
96
97 Parameters
98 ----------
99 fp : file
100 Any file-like object with a read method.
101
102 Returns
103 -------
104 nb : NotebookNode
105 The notebook that was read.
106 """
107 return reads(fp.read(), **kwargs)
@@ -0,0 +1,42 b''
1 """
2 Contains base test class for nbformat
3 """
4 #-----------------------------------------------------------------------------
5 #Copyright (c) 2013, the IPython Development Team.
6 #
7 #Distributed under the terms of the Modified BSD License.
8 #
9 #The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
11
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
15
16 import os
17 import unittest
18
19 import IPython
20
21 #-----------------------------------------------------------------------------
22 # Classes and functions
23 #-----------------------------------------------------------------------------
24
25 class TestsBase(unittest.TestCase):
26 """Base tests class."""
27
28 def fopen(self, f, mode=u'r'):
29 return open(os.path.join(self._get_files_path(), f), mode)
30
31
32 def _get_files_path(self):
33
34 #Get the relative path to this module in the IPython directory.
35 names = self.__module__.split(u'.')[1:-1]
36
37 #Build a path using the IPython directory and the relative path we just
38 #found.
39 path = IPython.__path__[0]
40 for name in names:
41 path = os.path.join(path, name)
42 return path
@@ -0,0 +1,419 b''
1 {
2 "metadata": {
3 "name": "01_notebook_introduction"
4 },
5 "nbformat": 2,
6 "worksheets": [
7 {
8 "cells": [
9 {
10 "cell_type": "markdown",
11 "source": [
12 "# An introduction to the IPython notebook",
13 "",
14 "The IPython web notebook is a frontend that allows for new modes",
15 "of interaction with IPython: this web-based interface allows you to execute Python and IPython",
16 "commands in each input cell just like you would at the IPython terminal or Qt console, but you can",
17 "also save an entire session as a document in a file with the `.ipynb` extension.",
18 "",
19 "The document you are reading now is precisely an example of one such notebook, and we will show you",
20 "here how to best use this new interface.",
21 "",
22 "The first thing to understand is that a notebook consists of a sequence of 'cells' that can contain ",
23 "either text (such as this one) or code meant for execution (such as the next one):",
24 "",
25 "* Text cells can be written using [Markdown syntax](http://daringfireball.net/projects/markdown/syntax) ",
26 "(in a future release we will also provide support for reStructuredText and Sphinx integration, and we ",
27 "welcome help from interested contributors to make that happen).",
28 "",
29 "* Code cells take IPython input (i.e. Python code, `%magics`, `!system calls`, etc) like IPython at",
30 "the terminal or at the Qt Console. The only difference is that in order to execute a cell, you *must*",
31 "use `Shift-Enter`, as pressing `Enter` will add a new line of text to the cell. When you type ",
32 "`Shift-Enter`, the cell content is executed, output displayed and a new cell is created below. Try",
33 "it now by putting your cursor on the next cell and typing `Shift-Enter`:"
34 ]
35 },
36 {
37 "cell_type": "code",
38 "collapsed": false,
39 "input": [
40 "\"This is the new IPython notebook\""
41 ],
42 "language": "python",
43 "outputs": [
44 {
45 "output_type": "pyout",
46 "prompt_number": 1,
47 "text": [
48 "'This is the new IPython notebook'"
49 ]
50 }
51 ],
52 "prompt_number": 1
53 },
54 {
55 "cell_type": "markdown",
56 "source": [
57 "You can re-execute the same cell over and over as many times as you want. Simply put your",
58 "cursor in the cell again, edit at will, and type `Shift-Enter` to execute. ",
59 "",
60 "**Tip:** A cell can also be executed",
61 "*in-place*, where IPython executes its content but leaves the cursor in the same cell. This is done by",
62 "typing `Ctrl-Enter` instead, and is useful if you want to quickly run a command to check something ",
63 "before tping the real content you want to leave in the cell. For example, in the next cell, try issuing",
64 "several system commands in-place with `Ctrl-Enter`, such as `pwd` and then `ls`:"
65 ]
66 },
67 {
68 "cell_type": "code",
69 "collapsed": false,
70 "input": [
71 "ls"
72 ],
73 "language": "python",
74 "outputs": [
75 {
76 "output_type": "stream",
77 "stream": "stdout",
78 "text": [
79 "00_notebook_tour.ipynb formatting.ipynb sympy_quantum_computing.ipynb",
80 "01_notebook_introduction.ipynb python-logo.svg trapezoid_rule.ipynb",
81 "display_protocol.ipynb sympy.ipynb"
82 ]
83 }
84 ],
85 "prompt_number": 2
86 },
87 {
88 "cell_type": "markdown",
89 "source": [
90 "In a cell, you can type anything from a single python expression to an arbitrarily long amount of code ",
91 "(although for reasons of readability, you should probably limit this to a few dozen lines):"
92 ]
93 },
94 {
95 "cell_type": "code",
96 "collapsed": false,
97 "input": [
98 "def f(x):",
99 " \"\"\"My function",
100 " x : parameter\"\"\"",
101 " ",
102 " return x+1",
103 "",
104 "print \"f(3) = \", f(3)"
105 ],
106 "language": "python",
107 "outputs": [
108 {
109 "output_type": "stream",
110 "stream": "stdout",
111 "text": [
112 "f(3) = 4"
113 ]
114 }
115 ],
116 "prompt_number": 3
117 },
118 {
119 "cell_type": "markdown",
120 "source": [
121 "## User interface",
122 "",
123 "When you start a new notebook server with `ipython notebook`, your",
124 "browser should open into the *Dashboard*, a page listing all notebooks",
125 "available in the current directory as well as letting you create new",
126 "notebooks. In this page, you can also drag and drop existing `.py` files",
127 "over the file list to import them as notebooks (see the manual for ",
128 "[further details on how these files are ",
129 "interpreted](http://ipython.org/ipython-doc/stable/interactive/htmlnotebook.html)).",
130 "",
131 "Once you open an existing notebook (like this one) or create a new one,",
132 "you are in the main notebook interface, which consists of a main editing",
133 "area (where these cells are contained) as well as a collapsible left panel, ",
134 "a permanent header area at the top, and a pager that rises from the",
135 "bottom when needed and can be collapsed again."
136 ]
137 },
138 {
139 "cell_type": "markdown",
140 "source": [
141 "### Main editing area",
142 "",
143 "Here, you can move with the arrow keys or using the ",
144 "scroll bars. The cursor enters code cells immediately, but only selects",
145 "text (markdown) cells without entering in them; to enter a text cell,",
146 "use `Enter`, and `Shift-Enter` to exit it again (just like to execute a ",
147 "code cell)."
148 ]
149 },
150 {
151 "cell_type": "markdown",
152 "source": [
153 "### Left panel",
154 "",
155 "This panel contains a number of panes that can be",
156 "collapsed vertically by clicking on their title bar, and the whole panel",
157 "can also be collapsed by clicking on the vertical divider (note that you",
158 "can not *drag* the divider, for now you can only click on it).",
159 "",
160 "The *Notebook* section contains actions that pertain to the whole notebook,",
161 "such as downloading the current notebook either in its original format",
162 "or as a `.py` script, and printing it. When you click the `Print` button,",
163 "a new HTML page opens with a static copy of the notebook; you can then",
164 "use your web browser's mechanisms to save or print this file.",
165 "",
166 "The *Cell* section lets you manipulate individual cells, and the names should ",
167 "be fairly self-explanatory.",
168 "",
169 "The *Kernel* section lets you signal the kernel executing your code. ",
170 "`Interrupt` does the equivalent of hitting `Ctrl-C` at a terminal, and",
171 "`Restart` fully kills the kernel process and starts a fresh one. Obviously",
172 "this means that all your previous variables are destroyed, but it also",
173 "makes it easy to get a fresh kernel in which to re-execute a notebook, perhaps",
174 "after changing an extension module for which Python's `reload` mechanism",
175 "does not work. If you check the 'Kill kernel upon exit' box, when you ",
176 "close the page IPython will automatically shut down the running kernel;",
177 "otherwise the kernels won't close until you stop the whole ",
178 "",
179 "The *Help* section contains links to the documentation of some projects",
180 "closely related to IPython as well as the minimal keybindings you need to",
181 "know. But you should use `Ctrl-m h` (or click the `QuickHelp` button at",
182 "the top) and learn some of the other keybindings, as it will make your ",
183 "workflow much more fluid and efficient.",
184 "",
185 "The *Configuration* section at the bottom lets you change some values",
186 "related to the display of tooltips and the behavior of the tab completer."
187 ]
188 },
189 {
190 "cell_type": "markdown",
191 "source": [
192 "### Header bar",
193 "",
194 "The header area at the top allows you to rename an existing ",
195 "notebook and open up a short help tooltip. This area also indicates",
196 "with a red **Busy** mark on the right whenever the kernel is busy executing",
197 "code."
198 ]
199 },
200 {
201 "cell_type": "markdown",
202 "source": [
203 "### The pager at the bottom",
204 "",
205 "Whenever IPython needs to display additional ",
206 "information, such as when you type `somefunction?` in a cell, the notebook",
207 "opens a pane at the bottom where this information is shown. You can keep",
208 "this pager pane open for reference (it doesn't block input in the main area)",
209 "or dismiss it by clicking on its divider bar."
210 ]
211 },
212 {
213 "cell_type": "markdown",
214 "source": [
215 "### Tab completion and tooltips",
216 "",
217 "The notebook uses the same underlying machinery for tab completion that ",
218 "IPython uses at the terminal, but displays the information differently.",
219 "Whey you complete with the `Tab` key, IPython shows a drop list with all",
220 "available completions. If you type more characters while this list is open,",
221 "IPython automatically eliminates from the list options that don't match the",
222 "new characters; once there is only one option left you can hit `Tab` once",
223 "more (or `Enter`) to complete. You can also select the completion you",
224 "want with the arrow keys or the mouse, and then hit `Enter`.",
225 "",
226 "In addition, if you hit `Tab` inside of open parentheses, IPython will ",
227 "search for the docstring of the last object left of the parens and will",
228 "display it on a tooltip. For example, type `list(<TAB>` and you will",
229 "see the docstring for the builtin `list` constructor:"
230 ]
231 },
232 {
233 "cell_type": "code",
234 "collapsed": true,
235 "input": [
236 "# Position your cursor after the ( and hit the Tab key:",
237 "list("
238 ],
239 "language": "python",
240 "outputs": []
241 },
242 {
243 "cell_type": "markdown",
244 "source": [
245 "## The frontend/kernel model",
246 "",
247 "The IPython notebook works on a client/server model where an *IPython kernel*",
248 "starts in a separate process and acts as a server to executes the code you type,",
249 "while the web browser provides acts as a client, providing a front end environment",
250 "for you to type. But one kernel is capable of simultaneously talking to more than",
251 "one client, and they do not all need to be of the same kind. All IPython frontends",
252 "are capable of communicating with a kernel, and any number of them can be active",
253 "at the same time. In addition to allowing you to have, for example, more than one",
254 "browser session active, this lets you connect clients with different user interface features.",
255 "",
256 "For example, you may want to connect a Qt console to your kernel and use it as a help",
257 "browser, calling `??` on objects in the Qt console (whose pager is more flexible than the",
258 "one in the notebook). You can start a new Qt console connected to your current kernel by ",
259 "using the `%qtconsole` magic, this will automatically detect the necessary connection",
260 "information.",
261 "",
262 "If you want to open one manually, or want to open a text console from a terminal, you can ",
263 "get your kernel's connection information with the `%connect_info` magic:"
264 ]
265 },
266 {
267 "cell_type": "code",
268 "collapsed": false,
269 "input": [
270 "%connect_info"
271 ],
272 "language": "python",
273 "outputs": [
274 {
275 "output_type": "stream",
276 "stream": "stdout",
277 "text": [
278 "{",
279 " \"stdin_port\": 53970, ",
280 " \"ip\": \"127.0.0.1\", ",
281 " \"hb_port\": 53971, ",
282 " \"key\": \"30daac61-6b73-4bae-a7d9-9dca538794d5\", ",
283 " \"shell_port\": 53968, ",
284 " \"iopub_port\": 53969",
285 "}",
286 "",
287 "Paste the above JSON into a file, and connect with:",
288 " $> ipython <app> --existing <file>",
289 "or, if you are local, you can connect with just:",
290 " $> ipython <app> --existing kernel-dd85d1cc-c335-44f4-bed8-f1a2173a819a.json ",
291 "or even just:",
292 " $> ipython <app> --existing ",
293 "if this is the most recent IPython session you have started."
294 ]
295 }
296 ],
297 "prompt_number": 4
298 },
299 {
300 "cell_type": "markdown",
301 "source": [
302 "## The kernel's `raw_input` and `%debug`",
303 "",
304 "The one feature the notebook currently doesn't support as a client is the ability to send data to the kernel's",
305 "standard input socket. That is, if the kernel requires information to be typed interactively by calling the",
306 "builtin `raw_input` function, the notebook will be blocked. This happens for example if you run a script",
307 "that queries interactively for parameters, and very importantly, is how the interactive IPython debugger that ",
308 "activates when you type `%debug` works.",
309 "",
310 "So, in order to be able to use `%debug` or anything else that requires `raw_input`, you can either use a Qt ",
311 "console or a terminal console:",
312 "",
313 "- From the notebook, typing `%qtconsole` finds all the necessary connection data for you.",
314 "- From the terminal, first type `%connect_info` while still in the notebook, and then copy and paste the ",
315 "resulting information, using `qtconsole` or `console` depending on which type of client you want."
316 ]
317 },
318 {
319 "cell_type": "markdown",
320 "source": [
321 "## Display of complex objects",
322 "",
323 "As the 'tour' notebook shows, the IPython notebook has fairly sophisticated display capabilities. In addition",
324 "to the examples there, you can study the `display_protocol` notebook in this same examples folder, to ",
325 "learn how to customize arbitrary objects (in your own code or external libraries) to display in the notebook",
326 "in any way you want, including graphical forms or mathematical expressions."
327 ]
328 },
329 {
330 "cell_type": "markdown",
331 "source": [
332 "## Plotting support",
333 "",
334 "As we've explained already, the notebook is just another frontend talking to the same IPython kernel that",
335 "you're already familiar with, so the same options for plotting support apply.",
336 "",
337 "If you start the notebook with `--pylab`, you will get matplotlib's floating, interactive windows and you",
338 "can call the `display` function to paste figures into the notebook document. If you start it with ",
339 "`--pylab inline`, all plots will appear inline automatically. In this regard, the notebook works identically",
340 "to the Qt console.",
341 "",
342 "Note that if you start the notebook server with pylab support, *all* kernels are automatically started in",
343 "pylab mode and with the same choice of backend (i.e. floating windows or inline figures). But you can also",
344 "start the notebook server simply by typing `ipython notebook`, and then selectively turn on pylab support ",
345 "only for the notebooks you want by using the `%pylab` magic (see its docstring for details)."
346 ]
347 },
348 {
349 "cell_type": "code",
350 "collapsed": false,
351 "input": [
352 "%pylab inline",
353 "plot(rand(100))"
354 ],
355 "language": "python",
356 "outputs": [
357 {
358 "output_type": "stream",
359 "stream": "stdout",
360 "text": [
361 "",
362 "Welcome to pylab, a matplotlib-based Python environment [backend: module://IPython.zmq.pylab.backend_inline].",
363 "For more information, type 'help(pylab)'."
364 ]
365 },
366 {
367 "output_type": "pyout",
368 "prompt_number": 5,
369 "text": [
370 "[<matplotlib.lines.Line2D at 0x11165bcd0>]"
371 ]
372 },
373 {
374 "output_type": "display_data",
375 "png": "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\n"
376 }
377 ],
378 "prompt_number": 5
379 },
380 {
381 "cell_type": "markdown",
382 "source": [
383 "## Security",
384 "",
385 "By default the notebook only listens on localhost, so it does not expose your computer to attacks coming from",
386 "the internet. By default the notebook does not require any authentication, but you can configure it to",
387 "ask for a password before allowing access to the files. ",
388 "",
389 "Furthermore, you can require the notebook to encrypt all communications by using SSL and making all connections",
390 "using the https protocol instead of plain http. This is a good idea if you decide to run your notebook on",
391 "addresses that are visible from the internet. For further details on how to configure this, see the",
392 "[security section](http://ipython.org/ipython-doc/stable/interactive/htmlnotebook.html#security) of the ",
393 "manual.",
394 "",
395 "Finally, note that you can also run a notebook with the `--read-only` flag, which lets you provide access",
396 "to your notebook documents to others without letting them execute code (which can be useful to broadcast",
397 "a computation to colleagues or students, for example). The read-only flag behaves differently depending",
398 "on whether the server has a password or not:",
399 "",
400 "- Passwordless server: users directly see all notebooks in read-only mode.",
401 "- Password-protected server: users can see all notebooks in read-only mode, but a login button is available",
402 "and once a user authenticates, he or she obtains write/execute privileges.",
403 "",
404 "The first case above makes it easy to broadcast on the fly an existing notebook by simply starting a *second* ",
405 "notebook server in the same directory as the first, but in read-only mode. This can be done without having",
406 "to configure a password first (which requires calling a hashing function and editing a configuration file)."
407 ]
408 },
409 {
410 "cell_type": "code",
411 "collapsed": true,
412 "input": [],
413 "language": "python",
414 "outputs": []
415 }
416 ]
417 }
418 ]
419 } No newline at end of file
@@ -0,0 +1,143 b''
1 {
2 "metadata": {
3 "cell_tags": [
4 [
5 "<None>",
6 null
7 ]
8 ],
9 "name": ""
10 },
11 "nbformat": 3,
12 "nbformat_minor": 0,
13 "worksheets": [
14 {
15 "cells": [
16 {
17 "cell_type": "heading",
18 "level": 1,
19 "metadata": {},
20 "source": [
21 "nbconvert latex test"
22 ]
23 },
24 {
25 "cell_type": "markdown",
26 "metadata": {},
27 "source": [
28 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
29 ]
30 },
31 {
32 "cell_type": "heading",
33 "level": 2,
34 "metadata": {},
35 "source": [
36 "Printed Using Python"
37 ]
38 },
39 {
40 "cell_type": "code",
41 "collapsed": false,
42 "input": [
43 "next_paragraph = \"\"\"\n",
44 "Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem \n",
45 "quis orci condimentum, sed feugiat dui porta.\n",
46 "\"\"\"\n",
47 "\n",
48 "def nifty_print(text):\n",
49 " \"\"\"Used to test syntax highlighting\"\"\"\n",
50 " \n",
51 " print(text * 2)\n",
52 "\n",
53 "nifty_print(next_paragraph)"
54 ],
55 "language": "python",
56 "metadata": {},
57 "outputs": [
58 {
59 "output_type": "stream",
60 "stream": "stdout",
61 "text": [
62 "\n",
63 "Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem \n",
64 "quis orci condimentum, sed feugiat dui porta.\n",
65 "\n",
66 "Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem \n",
67 "quis orci condimentum, sed feugiat dui porta.\n",
68 "\n"
69 ]
70 }
71 ],
72 "prompt_number": 3
73 },
74 {
75 "cell_type": "heading",
76 "level": 2,
77 "metadata": {},
78 "source": [
79 "Pyout"
80 ]
81 },
82 {
83 "cell_type": "code",
84 "collapsed": false,
85 "input": [
86 "Text = \"\"\"\n",
87 "Aliquam blandit aliquet enim, eget scelerisque eros adipiscing quis. Nunc sed metus \n",
88 "ut lorem condimentum condimentum nec id enim. Sed malesuada cursus hendrerit. Praesent \n",
89 "et commodo justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. \n",
90 "Curabitur et magna ante. Proin luctus tellus sit amet egestas laoreet. Sed dapibus \n",
91 "neque ac nulla mollis cursus. Fusce mollis egestas libero mattis facilisis.\n",
92 "\"\"\"\n",
93 "Text"
94 ],
95 "language": "python",
96 "metadata": {},
97 "outputs": [
98 {
99 "metadata": {},
100 "output_type": "pyout",
101 "prompt_number": 2,
102 "text": [
103 "'\\nAliquam blandit aliquet enim, eget scelerisque eros adipiscing quis. Nunc sed metus \\nut lorem condimentum condimentum nec id enim. Sed malesuada cursus hendrerit. Praesent \\net commodo justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. \\nCurabitur et magna ante. Proin luctus tellus sit amet egestas laoreet. Sed dapibus \\nneque ac nulla mollis cursus. Fusce mollis egestas libero mattis facilisis.\\n'"
104 ]
105 }
106 ],
107 "prompt_number": 2
108 },
109 {
110 "cell_type": "heading",
111 "level": 3,
112 "metadata": {},
113 "source": [
114 "Image"
115 ]
116 },
117 {
118 "cell_type": "code",
119 "collapsed": false,
120 "input": [
121 "from IPython.core.display import Image\n",
122 "Image(data=\"http://ipython.org/_static/IPy_header.png\")"
123 ],
124 "language": "python",
125 "metadata": {},
126 "outputs": [
127 {
128 "metadata": {},
129 "output_type": "pyout",
130 "png": "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\n",
131 "prompt_number": 6,
132 "text": [
133 "<IPython.core.display.Image at 0x2100f90>"
134 ]
135 }
136 ],
137 "prompt_number": 6
138 }
139 ],
140 "metadata": {}
141 }
142 ]
143 } No newline at end of file
@@ -0,0 +1,69 b''
1 """
2 Contains tests class for convert.py
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 from .base import TestsBase
16
17 from ..convert import convert
18 from ..reader import read, get_version
19 from ..current import current_nbformat
20
21 #-----------------------------------------------------------------------------
22 # Classes and functions
23 #-----------------------------------------------------------------------------
24
25 class TestConvert(TestsBase):
26
27 def test_downgrade(self):
28 """Do notebook downgrades work?"""
29
30 # Open a version 3 notebook and attempt to downgrade it to version 2.
31 with self.fopen(u'test3.ipynb', u'r') as f:
32 nb = read(f)
33 nb = convert(nb, 2)
34
35 # Check if downgrade was successful.
36 (major, minor) = get_version(nb)
37 self.assertEqual(major, 2)
38
39
40 def test_upgrade(self):
41 """Do notebook upgrades work?"""
42
43 # Open a version 2 notebook and attempt to upgrade it to version 3.
44 with self.fopen(u'test2.ipynb', u'r') as f:
45 nb = read(f)
46 nb = convert(nb, 3)
47
48 # Check if upgrade was successful.
49 (major, minor) = get_version(nb)
50 self.assertEqual(major, 3)
51
52
53 def test_open_current(self):
54 """Can an old notebook be opened and converted to the current version
55 while remembering the original version of the notebook?"""
56
57 # Open a version 2 notebook and attempt to upgrade it to the current version
58 # while remembering it's version information.
59 with self.fopen(u'test2.ipynb', u'r') as f:
60 nb = read(f)
61 (original_major, original_minor) = get_version(nb)
62 nb = convert(nb, current_nbformat)
63
64 # Check if upgrade was successful.
65 (major, minor) = get_version(nb)
66 self.assertEqual(major, current_nbformat)
67
68 # Check if the original major revision was remembered.
69 self.assertEqual(original_major, 2)
@@ -0,0 +1,36 b''
1 """
2 Contains tests class for current.py
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 from .base import TestsBase
16
17 from ..reader import get_version
18 from ..current import read, current_nbformat
19
20 #-----------------------------------------------------------------------------
21 # Classes and functions
22 #-----------------------------------------------------------------------------
23
24 class TestCurrent(TestsBase):
25
26 def test_read(self):
27 """Can older notebooks be opened and automatically converted to the current
28 nbformat?"""
29
30 # Open a version 2 notebook.
31 with self.fopen(u'test2.ipynb', u'r') as f:
32 nb = read(f, u'json')
33
34 # Check that the notebook was upgraded to the latest version automatically.
35 (major, minor) = get_version(nb)
36 self.assertEqual(major, current_nbformat)
@@ -0,0 +1,38 b''
1 """
2 Contains tests class for reader.py
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 from .base import TestsBase
16
17 from ..reader import read, get_version
18
19 #-----------------------------------------------------------------------------
20 # Classes and functions
21 #-----------------------------------------------------------------------------
22
23 class TestReader(TestsBase):
24
25 def test_read(self):
26 """Can older notebooks be opened without modification?"""
27
28 # Open a version 3 notebook. Make sure it is still version 3.
29 with self.fopen(u'test3.ipynb', u'r') as f:
30 nb = read(f)
31 (major, minor) = get_version(nb)
32 self.assertEqual(major, 3)
33
34 # Open a version 2 notebook. Make sure it is still version 2.
35 with self.fopen(u'test2.ipynb', u'r') as f:
36 nb = read(f)
37 (major, minor) = get_version(nb)
38 self.assertEqual(major, 2)
@@ -1,226 +1,200 b''
1 1 """The official API for working with notebooks in the current format version.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 * Jonathan Frederic
6 7 """
7 8
8 9 #-----------------------------------------------------------------------------
9 10 # Copyright (C) 2008-2011 The IPython Development Team
10 11 #
11 12 # Distributed under the terms of the BSD License. The full license is in
12 13 # the file COPYING, distributed as part of this software.
13 14 #-----------------------------------------------------------------------------
14 15
15 16 #-----------------------------------------------------------------------------
16 17 # Imports
17 18 #-----------------------------------------------------------------------------
18 19
19 20 from __future__ import print_function
20 import json
21
21 22 from xml.etree import ElementTree as ET
22 23 import re
23 24
24 from IPython.nbformat import v3
25 from IPython.nbformat import v2
26 from IPython.nbformat import v1
27
28 25 from IPython.nbformat.v3 import (
29 26 NotebookNode,
30 27 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
31 28 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
32 29 nbformat_minor, to_notebook_json
33 30 )
34 31
32 from .reader import reads as reader_reads
33 from .reader import versions
34 from .convert import convert
35
35 36 #-----------------------------------------------------------------------------
36 37 # Code
37 38 #-----------------------------------------------------------------------------
38 39
39 40 current_nbformat = nbformat
40 41 current_nbformat_minor = nbformat_minor
41 42
42 43
43 44 class NBFormatError(ValueError):
44 45 pass
45 46
46 class NotJSONError(ValueError):
47 pass
48
49
50 def parse_json(s, **kwargs):
51 """Parse a string into a (nbformat, dict) tuple."""
52 try:
53 d = json.loads(s, **kwargs)
54 except ValueError:
55 raise NotJSONError("Notebook does not appear to be JSON: %r" % s[:16])
56 nbf = d.get('nbformat', 1)
57 nbm = d.get('nbformat_minor', 0)
58 return nbf, nbm, d
59
60 47
61 48 def parse_py(s, **kwargs):
62 49 """Parse a string into a (nbformat, string) tuple."""
63 50 nbf = current_nbformat
64 51 nbm = current_nbformat_minor
65 52
66 53 pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
67 54 m = re.search(pattern,s)
68 55 if m is not None:
69 56 digits = m.group('nbformat').split('.')
70 57 nbf = int(digits[0])
71 58 if len(digits) > 1:
72 59 nbm = int(digits[1])
73 60
74 61 return nbf, nbm, s
75 62
76 63
77 64 def reads_json(s, **kwargs):
78 65 """Read a JSON notebook from a string and return the NotebookNode object."""
79 nbf, minor, d = parse_json(s, **kwargs)
80 if nbf == 1:
81 nb = v1.to_notebook_json(d, **kwargs)
82 nb = v3.convert_to_this_nbformat(nb, orig_version=1)
83 elif nbf == 2:
84 nb = v2.to_notebook_json(d, **kwargs)
85 nb = v3.convert_to_this_nbformat(nb, orig_version=2)
86 elif nbf == 3:
87 nb = v3.to_notebook_json(d, **kwargs)
88 nb = v3.convert_to_this_nbformat(nb, orig_version=3, orig_minor=minor)
89 else:
90 raise NBFormatError('Unsupported JSON nbformat version %s (supported version: %i)' % (nbf, 3))
91 return nb
66 return convert(reader_reads(s), current_nbformat)
92 67
93 68
94 69 def writes_json(nb, **kwargs):
95 return v3.writes_json(nb, **kwargs)
70 return versions[current_nbformat].writes_json(nb, **kwargs)
96 71
97 72
98 73 def reads_py(s, **kwargs):
99 74 """Read a .py notebook from a string and return the NotebookNode object."""
100 75 nbf, nbm, s = parse_py(s, **kwargs)
101 if nbf == 2:
102 nb = v2.to_notebook_py(s, **kwargs)
103 elif nbf == 3:
104 nb = v3.to_notebook_py(s, **kwargs)
76 if nbf in (2, 3):
77 nb = versions[nbf].to_notebook_py(s, **kwargs)
105 78 else:
106 79 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
107 80 return nb
108 81
109 82
110 83 def writes_py(nb, **kwargs):
111 return v3.writes_py(nb, **kwargs)
84 # nbformat 3 is the latest format that supports py
85 return versions[3].writes_py(nb, **kwargs)
112 86
113 87
114 88 # High level API
115 89
116 90
117 91 def reads(s, format, **kwargs):
118 92 """Read a notebook from a string and return the NotebookNode object.
119 93
120 94 This function properly handles notebooks of any version. The notebook
121 95 returned will always be in the current version's format.
122 96
123 97 Parameters
124 98 ----------
125 99 s : unicode
126 100 The raw unicode string to read the notebook from.
127 101 format : (u'json', u'ipynb', u'py')
128 102 The format that the string is in.
129 103
130 104 Returns
131 105 -------
132 106 nb : NotebookNode
133 107 The notebook that was read.
134 108 """
135 109 format = unicode(format)
136 110 if format == u'json' or format == u'ipynb':
137 111 return reads_json(s, **kwargs)
138 112 elif format == u'py':
139 113 return reads_py(s, **kwargs)
140 114 else:
141 115 raise NBFormatError('Unsupported format: %s' % format)
142 116
143 117
144 118 def writes(nb, format, **kwargs):
145 119 """Write a notebook to a string in a given format in the current nbformat version.
146 120
147 121 This function always writes the notebook in the current nbformat version.
148 122
149 123 Parameters
150 124 ----------
151 125 nb : NotebookNode
152 126 The notebook to write.
153 127 format : (u'json', u'ipynb', u'py')
154 128 The format to write the notebook in.
155 129
156 130 Returns
157 131 -------
158 132 s : unicode
159 133 The notebook string.
160 134 """
161 135 format = unicode(format)
162 136 if format == u'json' or format == u'ipynb':
163 137 return writes_json(nb, **kwargs)
164 138 elif format == u'py':
165 139 return writes_py(nb, **kwargs)
166 140 else:
167 141 raise NBFormatError('Unsupported format: %s' % format)
168 142
169 143
170 144 def read(fp, format, **kwargs):
171 145 """Read a notebook from a file and return the NotebookNode object.
172 146
173 147 This function properly handles notebooks of any version. The notebook
174 148 returned will always be in the current version's format.
175 149
176 150 Parameters
177 151 ----------
178 152 fp : file
179 153 Any file-like object with a read method.
180 154 format : (u'json', u'ipynb', u'py')
181 155 The format that the string is in.
182 156
183 157 Returns
184 158 -------
185 159 nb : NotebookNode
186 160 The notebook that was read.
187 161 """
188 162 return reads(fp.read(), format, **kwargs)
189 163
190 164
191 165 def write(nb, fp, format, **kwargs):
192 166 """Write a notebook to a file in a given format in the current nbformat version.
193 167
194 168 This function always writes the notebook in the current nbformat version.
195 169
196 170 Parameters
197 171 ----------
198 172 nb : NotebookNode
199 173 The notebook to write.
200 174 fp : file
201 175 Any file-like object with a write method.
202 176 format : (u'json', u'ipynb', u'py')
203 177 The format to write the notebook in.
204 178
205 179 Returns
206 180 -------
207 181 s : unicode
208 182 The notebook string.
209 183 """
210 184 return fp.write(writes(nb, format, **kwargs))
211 185
212 186 def _convert_to_metadata():
213 187 """Convert to a notebook having notebook metadata."""
214 188 import glob
215 189 for fname in glob.glob('*.ipynb'):
216 190 print('Converting file:',fname)
217 191 with open(fname,'r') as f:
218 192 nb = read(f,u'json')
219 193 md = new_metadata()
220 194 if u'name' in nb:
221 195 md.name = nb.name
222 196 del nb[u'name']
223 197 nb.metadata = md
224 198 with open(fname,'w') as f:
225 199 write(nb, f, u'json')
226 200
@@ -1,24 +1,23 b''
1 1 """The main module for the v1 notebook format."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2008-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 from .nbbase import (
15 15 NotebookNode,
16 16 new_code_cell, new_text_cell, new_notebook
17 17 )
18 18
19 19 from .nbjson import reads as reads_json, writes as writes_json
20 20 from .nbjson import reads as read_json, writes as write_json
21 21 from .nbjson import to_notebook as to_notebook_json
22 22
23 from .convert import convert_to_this_nbformat
24
23 from .convert import upgrade
@@ -1,16 +1,16 b''
1 1 """Convert notebook to the v1 format."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2008-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Code
12 12 #-----------------------------------------------------------------------------
13 13
14 def convert_to_this_nbformat(nb, orig_version=None):
14 def upgrade(nb, orig_version=None):
15 15 raise ValueError('Cannot convert to v1 notebook format')
16 16
@@ -1,78 +1,78 b''
1 1 """The main API for the v2 notebook format.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from .nbbase import (
20 20 NotebookNode,
21 21 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
22 22 new_metadata, new_author
23 23 )
24 24
25 25 from .nbjson import reads as reads_json, writes as writes_json
26 26 from .nbjson import reads as read_json, writes as write_json
27 27 from .nbjson import to_notebook as to_notebook_json
28 28
29 29 from .nbxml import reads as reads_xml
30 30 from .nbxml import reads as read_xml
31 31 from .nbxml import to_notebook as to_notebook_xml
32 32
33 33 from .nbpy import reads as reads_py, writes as writes_py
34 34 from .nbpy import reads as read_py, writes as write_py
35 35 from .nbpy import to_notebook as to_notebook_py
36 36
37 from .convert import convert_to_this_nbformat
37 from .convert import downgrade, upgrade
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Code
41 41 #-----------------------------------------------------------------------------
42 42
43 43 def parse_filename(fname):
44 44 """Parse a notebook filename.
45 45
46 46 This function takes a notebook filename and returns the notebook
47 47 format (json/py) and the notebook name. This logic can be
48 48 summarized as follows:
49 49
50 50 * notebook.ipynb -> (notebook.ipynb, notebook, json)
51 51 * notebook.json -> (notebook.json, notebook, json)
52 52 * notebook.py -> (notebook.py, notebook, py)
53 53 * notebook -> (notebook.ipynb, notebook, json)
54 54
55 55 Parameters
56 56 ----------
57 57 fname : unicode
58 58 The notebook filename. The filename can use a specific filename
59 59 extention (.ipynb, .json, .py) or none, in which case .ipynb will
60 60 be assumed.
61 61
62 62 Returns
63 63 -------
64 64 (fname, name, format) : (unicode, unicode, unicode)
65 65 The filename, notebook name and format.
66 66 """
67 67 if fname.endswith(u'.ipynb'):
68 68 format = u'json'
69 69 elif fname.endswith(u'.json'):
70 70 format = u'json'
71 71 elif fname.endswith(u'.py'):
72 72 format = u'py'
73 73 else:
74 74 fname = fname + u'.ipynb'
75 75 format = u'json'
76 76 name = fname.split('.')[0]
77 77 return fname, name, format
78 78
@@ -1,50 +1,61 b''
1 1 """Code for converting notebooks to and from the v2 format.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 * Jonathan Frederic
6 7 """
7 8
8 9 #-----------------------------------------------------------------------------
9 10 # Copyright (C) 2008-2011 The IPython Development Team
10 11 #
11 12 # Distributed under the terms of the BSD License. The full license is in
12 13 # the file COPYING, distributed as part of this software.
13 14 #-----------------------------------------------------------------------------
14 15
15 16 #-----------------------------------------------------------------------------
16 17 # Imports
17 18 #-----------------------------------------------------------------------------
18 19
19 20 from .nbbase import (
20 21 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
21 22 )
22 23
23 24 #-----------------------------------------------------------------------------
24 25 # Code
25 26 #-----------------------------------------------------------------------------
26 27
27 def convert_to_this_nbformat(nb, orig_version=1):
28 def upgrade(nb, from_version=1):
28 29 """Convert a notebook to the v2 format.
29 30
30 31 Parameters
31 32 ----------
32 33 nb : NotebookNode
33 34 The Python representation of the notebook to convert.
34 35 orig_version : int
35 36 The original version of the notebook to convert.
36 37 """
37 38 if orig_version == 1:
38 39 newnb = new_notebook()
39 40 ws = new_worksheet()
40 41 for cell in nb.cells:
41 42 if cell.cell_type == u'code':
42 43 newcell = new_code_cell(input=cell.get('code'),prompt_number=cell.get('prompt_number'))
43 44 elif cell.cell_type == u'text':
44 45 newcell = new_text_cell(u'markdown',source=cell.get('text'))
45 46 ws.cells.append(newcell)
46 47 newnb.worksheets.append(ws)
47 48 return newnb
48 49 else:
49 50 raise ValueError('Cannot convert a notebook from v%s to v2' % orig_version)
50 51
52
53 def downgrade(nb):
54 """Convert a v2 notebook to v1.
55
56 Parameters
57 ----------
58 nb : NotebookNode
59 The Python representation of the notebook to convert.
60 """
61 raise Exception("Downgrade from notebook v2 to v1 is not supported.")
@@ -1,74 +1,74 b''
1 1 """The main API for the v3 notebook format.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from .nbbase import (
20 20 NotebookNode,
21 21 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
22 22 new_metadata, new_author, new_heading_cell, nbformat, nbformat_minor
23 23 )
24 24
25 25 from .nbjson import reads as reads_json, writes as writes_json
26 26 from .nbjson import reads as read_json, writes as write_json
27 27 from .nbjson import to_notebook as to_notebook_json
28 28
29 29 from .nbpy import reads as reads_py, writes as writes_py
30 30 from .nbpy import reads as read_py, writes as write_py
31 31 from .nbpy import to_notebook as to_notebook_py
32 32
33 from .convert import convert_to_this_nbformat
33 from .convert import downgrade, upgrade
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Code
37 37 #-----------------------------------------------------------------------------
38 38
39 39 def parse_filename(fname):
40 40 """Parse a notebook filename.
41 41
42 42 This function takes a notebook filename and returns the notebook
43 43 format (json/py) and the notebook name. This logic can be
44 44 summarized as follows:
45 45
46 46 * notebook.ipynb -> (notebook.ipynb, notebook, json)
47 47 * notebook.json -> (notebook.json, notebook, json)
48 48 * notebook.py -> (notebook.py, notebook, py)
49 49 * notebook -> (notebook.ipynb, notebook, json)
50 50
51 51 Parameters
52 52 ----------
53 53 fname : unicode
54 54 The notebook filename. The filename can use a specific filename
55 55 extention (.ipynb, .json, .py) or none, in which case .ipynb will
56 56 be assumed.
57 57
58 58 Returns
59 59 -------
60 60 (fname, name, format) : (unicode, unicode, unicode)
61 61 The filename, notebook name and format.
62 62 """
63 63 if fname.endswith(u'.ipynb'):
64 64 format = u'json'
65 65 elif fname.endswith(u'.json'):
66 66 format = u'json'
67 67 elif fname.endswith(u'.py'):
68 68 format = u'py'
69 69 else:
70 70 fname = fname + u'.ipynb'
71 71 format = u'json'
72 72 name = fname.split('.')[0]
73 73 return fname, name, format
74 74
@@ -1,59 +1,90 b''
1 1 """Code for converting notebooks to and from the v2 format.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 * Min RK
7 * Jonathan Frederic
6 8 """
7 9
8 10 #-----------------------------------------------------------------------------
9 11 # Copyright (C) 2008-2011 The IPython Development Team
10 12 #
11 13 # Distributed under the terms of the BSD License. The full license is in
12 14 # the file COPYING, distributed as part of this software.
13 15 #-----------------------------------------------------------------------------
14 16
15 17 #-----------------------------------------------------------------------------
16 18 # Imports
17 19 #-----------------------------------------------------------------------------
18 20
19 21 from .nbbase import (
20 22 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
21 23 nbformat, nbformat_minor
22 24 )
23 25
24 26 from IPython.nbformat import v2
25 27
26 28 #-----------------------------------------------------------------------------
27 29 # Code
28 30 #-----------------------------------------------------------------------------
29 31
30 def convert_to_this_nbformat(nb, orig_version=2, orig_minor=0):
31 """Convert a notebook to the v3 format.
32 def upgrade(nb, from_version=2, from_minor=0):
33 """Convert a notebook to v3.
32 34
33 35 Parameters
34 36 ----------
35 37 nb : NotebookNode
36 38 The Python representation of the notebook to convert.
37 orig_version : int
39 from_version : int
38 40 The original version of the notebook to convert.
39 orig_minor : int
41 from_minor : int
40 42 The original minor version of the notebook to convert (only relevant for v >= 3).
41 43 """
42 if orig_version == 1:
43 nb = v2.convert_to_this_nbformat(nb)
44 orig_version = 2
45 if orig_version == 2:
44 if from_version == 2:
46 45 # Mark the original nbformat so consumers know it has been converted.
47 46 nb.nbformat = nbformat
48 47 nb.nbformat_minor = nbformat_minor
49 48
50 49 nb.orig_nbformat = 2
51 50 return nb
52 elif orig_version == 3:
53 if orig_minor != nbformat_minor:
54 nb.orig_nbformat_minor = orig_minor
51 elif from_version == 3:
52 if from_minor != nbformat_minor:
53 nb.orig_nbformat_minor = from_minor
55 54 nb.nbformat_minor = nbformat_minor
56 55 return nb
57 56 else:
58 raise ValueError('Cannot convert a notebook from v%s to v3' % orig_version)
57 raise ValueError('Cannot convert a notebook directly from v%s to v3. ' \
58 'Try using the IPython.nbformat.convert module.' % from_version)
59
59 60
61 def heading_to_md(cell):
62 """turn heading cell into corresponding markdown"""
63 cell.cell_type = "markdown"
64 level = cell.pop('level', 1)
65 cell.source = '#'*level + ' ' + cell.source
66
67
68 def raw_to_md(cell):
69 """let raw passthrough as markdown"""
70 cell.cell_type = "markdown"
71
72
73 def downgrade(nb):
74 """Convert a v3 notebook to v2.
75
76 Parameters
77 ----------
78 nb : NotebookNode
79 The Python representation of the notebook to convert.
80 """
81 if nb.nbformat != 3:
82 return nb
83 nb.nbformat = 2
84 for ws in nb.worksheets:
85 for cell in ws.cells:
86 if cell.cell_type == 'heading':
87 heading_to_md(cell)
88 elif cell.cell_type == 'raw':
89 raw_to_md(cell)
90 return nb No newline at end of file
@@ -1,502 +1,503 b''
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11 from __future__ import print_function
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import os
24 24 import sys
25 25
26 26 try:
27 27 from configparser import ConfigParser
28 28 except:
29 29 from ConfigParser import ConfigParser
30 30 from distutils.command.build_py import build_py
31 31 from distutils.cmd import Command
32 32 from glob import glob
33 33 from subprocess import call
34 34
35 35 from setupext import install_data_ext
36 36
37 37 #-------------------------------------------------------------------------------
38 38 # Useful globals and utility functions
39 39 #-------------------------------------------------------------------------------
40 40
41 41 # A few handy globals
42 42 isfile = os.path.isfile
43 43 pjoin = os.path.join
44 44 repo_root = os.path.dirname(os.path.abspath(__file__))
45 45
46 46 def oscmd(s):
47 47 print(">", s)
48 48 os.system(s)
49 49
50 50 # Py3 compatibility hacks, without assuming IPython itself is installed with
51 51 # the full py3compat machinery.
52 52
53 53 try:
54 54 execfile
55 55 except NameError:
56 56 def execfile(fname, globs, locs=None):
57 57 locs = locs or globs
58 58 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
59 59
60 60 # A little utility we'll need below, since glob() does NOT allow you to do
61 61 # exclusion on multiple endings!
62 62 def file_doesnt_endwith(test,endings):
63 63 """Return true if test is a file and its name does NOT end with any
64 64 of the strings listed in endings."""
65 65 if not isfile(test):
66 66 return False
67 67 for e in endings:
68 68 if test.endswith(e):
69 69 return False
70 70 return True
71 71
72 72 #---------------------------------------------------------------------------
73 73 # Basic project information
74 74 #---------------------------------------------------------------------------
75 75
76 76 # release.py contains version, authors, license, url, keywords, etc.
77 77 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
78 78
79 79 # Create a dict with the basic information
80 80 # This dict is eventually passed to setup after additional keys are added.
81 81 setup_args = dict(
82 82 name = name,
83 83 version = version,
84 84 description = description,
85 85 long_description = long_description,
86 86 author = author,
87 87 author_email = author_email,
88 88 url = url,
89 89 download_url = download_url,
90 90 license = license,
91 91 platforms = platforms,
92 92 keywords = keywords,
93 93 classifiers = classifiers,
94 94 cmdclass = {'install_data': install_data_ext},
95 95 )
96 96
97 97
98 98 #---------------------------------------------------------------------------
99 99 # Find packages
100 100 #---------------------------------------------------------------------------
101 101
102 102 def find_packages():
103 103 """
104 104 Find all of IPython's packages.
105 105 """
106 106 excludes = ['deathrow', 'quarantine']
107 107 packages = []
108 108 for dir,subdirs,files in os.walk('IPython'):
109 109 package = dir.replace(os.path.sep, '.')
110 110 if any(package.startswith('IPython.'+exc) for exc in excludes):
111 111 # package is to be excluded (e.g. deathrow)
112 112 continue
113 113 if '__init__.py' not in files:
114 114 # not a package
115 115 continue
116 116 packages.append(package)
117 117 return packages
118 118
119 119 #---------------------------------------------------------------------------
120 120 # Find package data
121 121 #---------------------------------------------------------------------------
122 122
123 123 def find_package_data():
124 124 """
125 125 Find IPython's package_data.
126 126 """
127 127 # This is not enough for these things to appear in an sdist.
128 128 # We need to muck with the MANIFEST to get this to work
129 129
130 130 # exclude static things that we don't ship (e.g. mathjax)
131 131 excludes = ['mathjax']
132 132
133 133 # add 'static/' prefix to exclusions, and tuplify for use in startswith
134 134 excludes = tuple([os.path.join('static', ex) for ex in excludes])
135 135
136 136 # walk notebook resources:
137 137 cwd = os.getcwd()
138 138 os.chdir(os.path.join('IPython', 'html'))
139 139 static_walk = list(os.walk('static'))
140 140 static_data = []
141 141 for parent, dirs, files in static_walk:
142 142 if parent.startswith(excludes):
143 143 continue
144 144 for f in files:
145 145 static_data.append(os.path.join(parent, f))
146 146
147 147 os.chdir(os.path.join('tests',))
148 148 js_tests = glob('casperjs/*.*') + glob('casperjs/*/*')
149 149 os.chdir(cwd)
150 150
151 151 package_data = {
152 152 'IPython.config.profile' : ['README*', '*/*.py'],
153 153 'IPython.core.tests' : ['*.png', '*.jpg'],
154 154 'IPython.testing' : ['*.txt'],
155 155 'IPython.testing.plugin' : ['*.txt'],
156 156 'IPython.html' : ['templates/*'] + static_data,
157 157 'IPython.html.tests' : js_tests,
158 158 'IPython.qt.console' : ['resources/icon/*.svg'],
159 159 'IPython.nbconvert' : ['templates/*.tpl', 'templates/latex/*.tplx',
160 160 'templates/latex/skeleton/*.tplx', 'templates/skeleton/*',
161 161 'templates/reveal_internals/*.tpl', 'tests/files/*.*',
162 'exporters/tests/files/*.*']
162 'exporters/tests/files/*.*'],
163 'IPython.nbformat' : ['tests/*.ipynb']
163 164 }
164 165 return package_data
165 166
166 167
167 168 #---------------------------------------------------------------------------
168 169 # Find data files
169 170 #---------------------------------------------------------------------------
170 171
171 172 def make_dir_struct(tag,base,out_base):
172 173 """Make the directory structure of all files below a starting dir.
173 174
174 175 This is just a convenience routine to help build a nested directory
175 176 hierarchy because distutils is too stupid to do this by itself.
176 177
177 178 XXX - this needs a proper docstring!
178 179 """
179 180
180 181 # we'll use these a lot below
181 182 lbase = len(base)
182 183 pathsep = os.path.sep
183 184 lpathsep = len(pathsep)
184 185
185 186 out = []
186 187 for (dirpath,dirnames,filenames) in os.walk(base):
187 188 # we need to strip out the dirpath from the base to map it to the
188 189 # output (installation) path. This requires possibly stripping the
189 190 # path separator, because otherwise pjoin will not work correctly
190 191 # (pjoin('foo/','/bar') returns '/bar').
191 192
192 193 dp_eff = dirpath[lbase:]
193 194 if dp_eff.startswith(pathsep):
194 195 dp_eff = dp_eff[lpathsep:]
195 196 # The output path must be anchored at the out_base marker
196 197 out_path = pjoin(out_base,dp_eff)
197 198 # Now we can generate the final filenames. Since os.walk only produces
198 199 # filenames, we must join back with the dirpath to get full valid file
199 200 # paths:
200 201 pfiles = [pjoin(dirpath,f) for f in filenames]
201 202 # Finally, generate the entry we need, which is a pari of (output
202 203 # path, files) for use as a data_files parameter in install_data.
203 204 out.append((out_path, pfiles))
204 205
205 206 return out
206 207
207 208
208 209 def find_data_files():
209 210 """
210 211 Find IPython's data_files.
211 212
212 213 Most of these are docs.
213 214 """
214 215
215 216 docdirbase = pjoin('share', 'doc', 'ipython')
216 217 manpagebase = pjoin('share', 'man', 'man1')
217 218
218 219 # Simple file lists can be made by hand
219 220 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
220 221 if not manpages:
221 222 # When running from a source tree, the manpages aren't gzipped
222 223 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
223 224
224 225 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
225 226
226 227 # For nested structures, use the utility above
227 228 example_files = make_dir_struct(
228 229 'data',
229 230 pjoin('docs','examples'),
230 231 pjoin(docdirbase,'examples')
231 232 )
232 233 manual_files = make_dir_struct(
233 234 'data',
234 235 pjoin('docs','html'),
235 236 pjoin(docdirbase,'manual')
236 237 )
237 238
238 239 # And assemble the entire output list
239 240 data_files = [ (manpagebase, manpages),
240 241 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
241 242 ] + manual_files + example_files
242 243
243 244 return data_files
244 245
245 246
246 247 def make_man_update_target(manpage):
247 248 """Return a target_update-compliant tuple for the given manpage.
248 249
249 250 Parameters
250 251 ----------
251 252 manpage : string
252 253 Name of the manpage, must include the section number (trailing number).
253 254
254 255 Example
255 256 -------
256 257
257 258 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
258 259 ('docs/man/ipython.1.gz',
259 260 ['docs/man/ipython.1'],
260 261 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
261 262 """
262 263 man_dir = pjoin('docs', 'man')
263 264 manpage_gz = manpage + '.gz'
264 265 manpath = pjoin(man_dir, manpage)
265 266 manpath_gz = pjoin(man_dir, manpage_gz)
266 267 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
267 268 locals() )
268 269 return (manpath_gz, [manpath], gz_cmd)
269 270
270 271 # The two functions below are copied from IPython.utils.path, so we don't need
271 272 # to import IPython during setup, which fails on Python 3.
272 273
273 274 def target_outdated(target,deps):
274 275 """Determine whether a target is out of date.
275 276
276 277 target_outdated(target,deps) -> 1/0
277 278
278 279 deps: list of filenames which MUST exist.
279 280 target: single filename which may or may not exist.
280 281
281 282 If target doesn't exist or is older than any file listed in deps, return
282 283 true, otherwise return false.
283 284 """
284 285 try:
285 286 target_time = os.path.getmtime(target)
286 287 except os.error:
287 288 return 1
288 289 for dep in deps:
289 290 dep_time = os.path.getmtime(dep)
290 291 if dep_time > target_time:
291 292 #print "For target",target,"Dep failed:",dep # dbg
292 293 #print "times (dep,tar):",dep_time,target_time # dbg
293 294 return 1
294 295 return 0
295 296
296 297
297 298 def target_update(target,deps,cmd):
298 299 """Update a target with a given command given a list of dependencies.
299 300
300 301 target_update(target,deps,cmd) -> runs cmd if target is outdated.
301 302
302 303 This is just a wrapper around target_outdated() which calls the given
303 304 command if target is outdated."""
304 305
305 306 if target_outdated(target,deps):
306 307 os.system(cmd)
307 308
308 309 #---------------------------------------------------------------------------
309 310 # Find scripts
310 311 #---------------------------------------------------------------------------
311 312
312 313 def find_scripts(entry_points=False, suffix=''):
313 314 """Find IPython's scripts.
314 315
315 316 if entry_points is True:
316 317 return setuptools entry_point-style definitions
317 318 else:
318 319 return file paths of plain scripts [default]
319 320
320 321 suffix is appended to script names if entry_points is True, so that the
321 322 Python 3 scripts get named "ipython3" etc.
322 323 """
323 324 if entry_points:
324 325 console_scripts = [s % suffix for s in [
325 326 'ipython%s = IPython:start_ipython',
326 327 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
327 328 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
328 329 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
329 330 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
330 331 'iptest%s = IPython.testing.iptestcontroller:main',
331 332 'irunner%s = IPython.lib.irunner:main',
332 333 ]]
333 334 gui_scripts = []
334 335 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
335 336 else:
336 337 parallel_scripts = pjoin('IPython','parallel','scripts')
337 338 main_scripts = pjoin('IPython','scripts')
338 339 scripts = [
339 340 pjoin(parallel_scripts, 'ipengine'),
340 341 pjoin(parallel_scripts, 'ipcontroller'),
341 342 pjoin(parallel_scripts, 'ipcluster'),
342 343 pjoin(parallel_scripts, 'iplogger'),
343 344 pjoin(main_scripts, 'ipython'),
344 345 pjoin(main_scripts, 'irunner'),
345 346 pjoin(main_scripts, 'iptest')
346 347 ]
347 348 return scripts
348 349
349 350 #---------------------------------------------------------------------------
350 351 # Verify all dependencies
351 352 #---------------------------------------------------------------------------
352 353
353 354 def check_for_dependencies():
354 355 """Check for IPython's dependencies.
355 356
356 357 This function should NOT be called if running under setuptools!
357 358 """
358 359 from setupext.setupext import (
359 360 print_line, print_raw, print_status,
360 361 check_for_sphinx, check_for_pygments,
361 362 check_for_nose, check_for_pexpect,
362 363 check_for_pyzmq, check_for_readline,
363 364 check_for_jinja2, check_for_tornado
364 365 )
365 366 print_line()
366 367 print_raw("BUILDING IPYTHON")
367 368 print_status('python', sys.version)
368 369 print_status('platform', sys.platform)
369 370 if sys.platform == 'win32':
370 371 print_status('Windows version', sys.getwindowsversion())
371 372
372 373 print_raw("")
373 374 print_raw("OPTIONAL DEPENDENCIES")
374 375
375 376 check_for_sphinx()
376 377 check_for_pygments()
377 378 check_for_nose()
378 379 check_for_pexpect()
379 380 check_for_pyzmq()
380 381 check_for_tornado()
381 382 check_for_readline()
382 383 check_for_jinja2()
383 384
384 385 #---------------------------------------------------------------------------
385 386 # VCS related
386 387 #---------------------------------------------------------------------------
387 388
388 389 # utils.submodule has checks for submodule status
389 390 execfile(pjoin('IPython','utils','submodule.py'), globals())
390 391
391 392 class UpdateSubmodules(Command):
392 393 """Update git submodules
393 394
394 395 IPython's external javascript dependencies live in a separate repo.
395 396 """
396 397 description = "Update git submodules"
397 398 user_options = []
398 399
399 400 def initialize_options(self):
400 401 pass
401 402
402 403 def finalize_options(self):
403 404 pass
404 405
405 406 def run(self):
406 407 failure = False
407 408 try:
408 409 self.spawn('git submodule init'.split())
409 410 self.spawn('git submodule update --recursive'.split())
410 411 except Exception as e:
411 412 failure = e
412 413 print(e)
413 414
414 415 if not check_submodule_status(repo_root) == 'clean':
415 416 print("submodules could not be checked out")
416 417 sys.exit(1)
417 418
418 419
419 420 def git_prebuild(pkg_dir, build_cmd=build_py):
420 421 """Return extended build or sdist command class for recording commit
421 422
422 423 records git commit in IPython.utils._sysinfo.commit
423 424
424 425 for use in IPython.utils.sysinfo.sys_info() calls after installation.
425 426
426 427 Also ensures that submodules exist prior to running
427 428 """
428 429
429 430 class MyBuildPy(build_cmd):
430 431 ''' Subclass to write commit data into installation tree '''
431 432 def run(self):
432 433 build_cmd.run(self)
433 434 # this one will only fire for build commands
434 435 if hasattr(self, 'build_lib'):
435 436 self._record_commit(self.build_lib)
436 437
437 438 def make_release_tree(self, base_dir, files):
438 439 # this one will fire for sdist
439 440 build_cmd.make_release_tree(self, base_dir, files)
440 441 self._record_commit(base_dir)
441 442
442 443 def _record_commit(self, base_dir):
443 444 import subprocess
444 445 proc = subprocess.Popen('git rev-parse --short HEAD',
445 446 stdout=subprocess.PIPE,
446 447 stderr=subprocess.PIPE,
447 448 shell=True)
448 449 repo_commit, _ = proc.communicate()
449 450 repo_commit = repo_commit.strip().decode("ascii")
450 451
451 452 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
452 453 if os.path.isfile(out_pth) and not repo_commit:
453 454 # nothing to write, don't clobber
454 455 return
455 456
456 457 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
457 458
458 459 # remove to avoid overwriting original via hard link
459 460 try:
460 461 os.remove(out_pth)
461 462 except (IOError, OSError):
462 463 pass
463 464 with open(out_pth, 'w') as out_file:
464 465 out_file.writelines([
465 466 '# GENERATED BY setup.py\n',
466 467 'commit = "%s"\n' % repo_commit,
467 468 ])
468 469 return require_submodules(MyBuildPy)
469 470
470 471
471 472 def require_submodules(command):
472 473 """decorator for instructing a command to check for submodules before running"""
473 474 class DecoratedCommand(command):
474 475 def run(self):
475 476 if not check_submodule_status(repo_root) == 'clean':
476 477 print("submodules missing! Run `setup.py submodule` and try again")
477 478 sys.exit(1)
478 479 command.run(self)
479 480 return DecoratedCommand
480 481
481 482 #---------------------------------------------------------------------------
482 483 # Notebook related
483 484 #---------------------------------------------------------------------------
484 485
485 486 class CompileCSS(Command):
486 487 """Recompile Notebook CSS
487 488
488 489 Regenerate the compiled CSS from LESS sources.
489 490
490 491 Requires various dev dependencies, such as fabric and lessc.
491 492 """
492 493 description = "Recompile Notebook CSS"
493 494 user_options = []
494 495
495 496 def initialize_options(self):
496 497 pass
497 498
498 499 def finalize_options(self):
499 500 pass
500 501
501 502 def run(self):
502 503 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
General Comments 0
You need to be logged in to leave comments. Login now