##// 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 """The official API for working with notebooks in the current format version.
1 """The official API for working with notebooks in the current format version.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Jonathan Frederic
6 """
7 """
7
8
8 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
14
15
15 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
16 # Imports
17 # Imports
17 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
18
19
19 from __future__ import print_function
20 from __future__ import print_function
20 import json
21
21 from xml.etree import ElementTree as ET
22 from xml.etree import ElementTree as ET
22 import re
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 from IPython.nbformat.v3 import (
25 from IPython.nbformat.v3 import (
29 NotebookNode,
26 NotebookNode,
30 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
27 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
31 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
28 parse_filename, new_metadata, new_author, new_heading_cell, nbformat,
32 nbformat_minor, to_notebook_json
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 # Code
37 # Code
37 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
38
39
39 current_nbformat = nbformat
40 current_nbformat = nbformat
40 current_nbformat_minor = nbformat_minor
41 current_nbformat_minor = nbformat_minor
41
42
42
43
43 class NBFormatError(ValueError):
44 class NBFormatError(ValueError):
44 pass
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 def parse_py(s, **kwargs):
48 def parse_py(s, **kwargs):
62 """Parse a string into a (nbformat, string) tuple."""
49 """Parse a string into a (nbformat, string) tuple."""
63 nbf = current_nbformat
50 nbf = current_nbformat
64 nbm = current_nbformat_minor
51 nbm = current_nbformat_minor
65
52
66 pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
53 pattern = r'# <nbformat>(?P<nbformat>\d+[\.\d+]*)</nbformat>'
67 m = re.search(pattern,s)
54 m = re.search(pattern,s)
68 if m is not None:
55 if m is not None:
69 digits = m.group('nbformat').split('.')
56 digits = m.group('nbformat').split('.')
70 nbf = int(digits[0])
57 nbf = int(digits[0])
71 if len(digits) > 1:
58 if len(digits) > 1:
72 nbm = int(digits[1])
59 nbm = int(digits[1])
73
60
74 return nbf, nbm, s
61 return nbf, nbm, s
75
62
76
63
77 def reads_json(s, **kwargs):
64 def reads_json(s, **kwargs):
78 """Read a JSON notebook from a string and return the NotebookNode object."""
65 """Read a JSON notebook from a string and return the NotebookNode object."""
79 nbf, minor, d = parse_json(s, **kwargs)
66 return convert(reader_reads(s), current_nbformat)
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
92
67
93
68
94 def writes_json(nb, **kwargs):
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 def reads_py(s, **kwargs):
73 def reads_py(s, **kwargs):
99 """Read a .py notebook from a string and return the NotebookNode object."""
74 """Read a .py notebook from a string and return the NotebookNode object."""
100 nbf, nbm, s = parse_py(s, **kwargs)
75 nbf, nbm, s = parse_py(s, **kwargs)
101 if nbf == 2:
76 if nbf in (2, 3):
102 nb = v2.to_notebook_py(s, **kwargs)
77 nb = versions[nbf].to_notebook_py(s, **kwargs)
103 elif nbf == 3:
104 nb = v3.to_notebook_py(s, **kwargs)
105 else:
78 else:
106 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
79 raise NBFormatError('Unsupported PY nbformat version: %i' % nbf)
107 return nb
80 return nb
108
81
109
82
110 def writes_py(nb, **kwargs):
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 # High level API
88 # High level API
115
89
116
90
117 def reads(s, format, **kwargs):
91 def reads(s, format, **kwargs):
118 """Read a notebook from a string and return the NotebookNode object.
92 """Read a notebook from a string and return the NotebookNode object.
119
93
120 This function properly handles notebooks of any version. The notebook
94 This function properly handles notebooks of any version. The notebook
121 returned will always be in the current version's format.
95 returned will always be in the current version's format.
122
96
123 Parameters
97 Parameters
124 ----------
98 ----------
125 s : unicode
99 s : unicode
126 The raw unicode string to read the notebook from.
100 The raw unicode string to read the notebook from.
127 format : (u'json', u'ipynb', u'py')
101 format : (u'json', u'ipynb', u'py')
128 The format that the string is in.
102 The format that the string is in.
129
103
130 Returns
104 Returns
131 -------
105 -------
132 nb : NotebookNode
106 nb : NotebookNode
133 The notebook that was read.
107 The notebook that was read.
134 """
108 """
135 format = unicode(format)
109 format = unicode(format)
136 if format == u'json' or format == u'ipynb':
110 if format == u'json' or format == u'ipynb':
137 return reads_json(s, **kwargs)
111 return reads_json(s, **kwargs)
138 elif format == u'py':
112 elif format == u'py':
139 return reads_py(s, **kwargs)
113 return reads_py(s, **kwargs)
140 else:
114 else:
141 raise NBFormatError('Unsupported format: %s' % format)
115 raise NBFormatError('Unsupported format: %s' % format)
142
116
143
117
144 def writes(nb, format, **kwargs):
118 def writes(nb, format, **kwargs):
145 """Write a notebook to a string in a given format in the current nbformat version.
119 """Write a notebook to a string in a given format in the current nbformat version.
146
120
147 This function always writes the notebook in the current nbformat version.
121 This function always writes the notebook in the current nbformat version.
148
122
149 Parameters
123 Parameters
150 ----------
124 ----------
151 nb : NotebookNode
125 nb : NotebookNode
152 The notebook to write.
126 The notebook to write.
153 format : (u'json', u'ipynb', u'py')
127 format : (u'json', u'ipynb', u'py')
154 The format to write the notebook in.
128 The format to write the notebook in.
155
129
156 Returns
130 Returns
157 -------
131 -------
158 s : unicode
132 s : unicode
159 The notebook string.
133 The notebook string.
160 """
134 """
161 format = unicode(format)
135 format = unicode(format)
162 if format == u'json' or format == u'ipynb':
136 if format == u'json' or format == u'ipynb':
163 return writes_json(nb, **kwargs)
137 return writes_json(nb, **kwargs)
164 elif format == u'py':
138 elif format == u'py':
165 return writes_py(nb, **kwargs)
139 return writes_py(nb, **kwargs)
166 else:
140 else:
167 raise NBFormatError('Unsupported format: %s' % format)
141 raise NBFormatError('Unsupported format: %s' % format)
168
142
169
143
170 def read(fp, format, **kwargs):
144 def read(fp, format, **kwargs):
171 """Read a notebook from a file and return the NotebookNode object.
145 """Read a notebook from a file and return the NotebookNode object.
172
146
173 This function properly handles notebooks of any version. The notebook
147 This function properly handles notebooks of any version. The notebook
174 returned will always be in the current version's format.
148 returned will always be in the current version's format.
175
149
176 Parameters
150 Parameters
177 ----------
151 ----------
178 fp : file
152 fp : file
179 Any file-like object with a read method.
153 Any file-like object with a read method.
180 format : (u'json', u'ipynb', u'py')
154 format : (u'json', u'ipynb', u'py')
181 The format that the string is in.
155 The format that the string is in.
182
156
183 Returns
157 Returns
184 -------
158 -------
185 nb : NotebookNode
159 nb : NotebookNode
186 The notebook that was read.
160 The notebook that was read.
187 """
161 """
188 return reads(fp.read(), format, **kwargs)
162 return reads(fp.read(), format, **kwargs)
189
163
190
164
191 def write(nb, fp, format, **kwargs):
165 def write(nb, fp, format, **kwargs):
192 """Write a notebook to a file in a given format in the current nbformat version.
166 """Write a notebook to a file in a given format in the current nbformat version.
193
167
194 This function always writes the notebook in the current nbformat version.
168 This function always writes the notebook in the current nbformat version.
195
169
196 Parameters
170 Parameters
197 ----------
171 ----------
198 nb : NotebookNode
172 nb : NotebookNode
199 The notebook to write.
173 The notebook to write.
200 fp : file
174 fp : file
201 Any file-like object with a write method.
175 Any file-like object with a write method.
202 format : (u'json', u'ipynb', u'py')
176 format : (u'json', u'ipynb', u'py')
203 The format to write the notebook in.
177 The format to write the notebook in.
204
178
205 Returns
179 Returns
206 -------
180 -------
207 s : unicode
181 s : unicode
208 The notebook string.
182 The notebook string.
209 """
183 """
210 return fp.write(writes(nb, format, **kwargs))
184 return fp.write(writes(nb, format, **kwargs))
211
185
212 def _convert_to_metadata():
186 def _convert_to_metadata():
213 """Convert to a notebook having notebook metadata."""
187 """Convert to a notebook having notebook metadata."""
214 import glob
188 import glob
215 for fname in glob.glob('*.ipynb'):
189 for fname in glob.glob('*.ipynb'):
216 print('Converting file:',fname)
190 print('Converting file:',fname)
217 with open(fname,'r') as f:
191 with open(fname,'r') as f:
218 nb = read(f,u'json')
192 nb = read(f,u'json')
219 md = new_metadata()
193 md = new_metadata()
220 if u'name' in nb:
194 if u'name' in nb:
221 md.name = nb.name
195 md.name = nb.name
222 del nb[u'name']
196 del nb[u'name']
223 nb.metadata = md
197 nb.metadata = md
224 with open(fname,'w') as f:
198 with open(fname,'w') as f:
225 write(nb, f, u'json')
199 write(nb, f, u'json')
226
200
@@ -1,24 +1,23 b''
1 """The main module for the v1 notebook format."""
1 """The main module for the v1 notebook format."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2008-2011 The IPython Development Team
4 # Copyright (C) 2008-2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 from .nbbase import (
14 from .nbbase import (
15 NotebookNode,
15 NotebookNode,
16 new_code_cell, new_text_cell, new_notebook
16 new_code_cell, new_text_cell, new_notebook
17 )
17 )
18
18
19 from .nbjson import reads as reads_json, writes as writes_json
19 from .nbjson import reads as reads_json, writes as writes_json
20 from .nbjson import reads as read_json, writes as write_json
20 from .nbjson import reads as read_json, writes as write_json
21 from .nbjson import to_notebook as to_notebook_json
21 from .nbjson import to_notebook as to_notebook_json
22
22
23 from .convert import convert_to_this_nbformat
23 from .convert import upgrade
24
@@ -1,16 +1,16 b''
1 """Convert notebook to the v1 format."""
1 """Convert notebook to the v1 format."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2008-2011 The IPython Development Team
4 # Copyright (C) 2008-2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Code
11 # Code
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 def convert_to_this_nbformat(nb, orig_version=None):
14 def upgrade(nb, orig_version=None):
15 raise ValueError('Cannot convert to v1 notebook format')
15 raise ValueError('Cannot convert to v1 notebook format')
16
16
@@ -1,78 +1,78 b''
1 """The main API for the v2 notebook format.
1 """The main API for the v2 notebook format.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from .nbbase import (
19 from .nbbase import (
20 NotebookNode,
20 NotebookNode,
21 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
21 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
22 new_metadata, new_author
22 new_metadata, new_author
23 )
23 )
24
24
25 from .nbjson import reads as reads_json, writes as writes_json
25 from .nbjson import reads as reads_json, writes as writes_json
26 from .nbjson import reads as read_json, writes as write_json
26 from .nbjson import reads as read_json, writes as write_json
27 from .nbjson import to_notebook as to_notebook_json
27 from .nbjson import to_notebook as to_notebook_json
28
28
29 from .nbxml import reads as reads_xml
29 from .nbxml import reads as reads_xml
30 from .nbxml import reads as read_xml
30 from .nbxml import reads as read_xml
31 from .nbxml import to_notebook as to_notebook_xml
31 from .nbxml import to_notebook as to_notebook_xml
32
32
33 from .nbpy import reads as reads_py, writes as writes_py
33 from .nbpy import reads as reads_py, writes as writes_py
34 from .nbpy import reads as read_py, writes as write_py
34 from .nbpy import reads as read_py, writes as write_py
35 from .nbpy import to_notebook as to_notebook_py
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 # Code
40 # Code
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 def parse_filename(fname):
43 def parse_filename(fname):
44 """Parse a notebook filename.
44 """Parse a notebook filename.
45
45
46 This function takes a notebook filename and returns the notebook
46 This function takes a notebook filename and returns the notebook
47 format (json/py) and the notebook name. This logic can be
47 format (json/py) and the notebook name. This logic can be
48 summarized as follows:
48 summarized as follows:
49
49
50 * notebook.ipynb -> (notebook.ipynb, notebook, json)
50 * notebook.ipynb -> (notebook.ipynb, notebook, json)
51 * notebook.json -> (notebook.json, notebook, json)
51 * notebook.json -> (notebook.json, notebook, json)
52 * notebook.py -> (notebook.py, notebook, py)
52 * notebook.py -> (notebook.py, notebook, py)
53 * notebook -> (notebook.ipynb, notebook, json)
53 * notebook -> (notebook.ipynb, notebook, json)
54
54
55 Parameters
55 Parameters
56 ----------
56 ----------
57 fname : unicode
57 fname : unicode
58 The notebook filename. The filename can use a specific filename
58 The notebook filename. The filename can use a specific filename
59 extention (.ipynb, .json, .py) or none, in which case .ipynb will
59 extention (.ipynb, .json, .py) or none, in which case .ipynb will
60 be assumed.
60 be assumed.
61
61
62 Returns
62 Returns
63 -------
63 -------
64 (fname, name, format) : (unicode, unicode, unicode)
64 (fname, name, format) : (unicode, unicode, unicode)
65 The filename, notebook name and format.
65 The filename, notebook name and format.
66 """
66 """
67 if fname.endswith(u'.ipynb'):
67 if fname.endswith(u'.ipynb'):
68 format = u'json'
68 format = u'json'
69 elif fname.endswith(u'.json'):
69 elif fname.endswith(u'.json'):
70 format = u'json'
70 format = u'json'
71 elif fname.endswith(u'.py'):
71 elif fname.endswith(u'.py'):
72 format = u'py'
72 format = u'py'
73 else:
73 else:
74 fname = fname + u'.ipynb'
74 fname = fname + u'.ipynb'
75 format = u'json'
75 format = u'json'
76 name = fname.split('.')[0]
76 name = fname.split('.')[0]
77 return fname, name, format
77 return fname, name, format
78
78
@@ -1,50 +1,61 b''
1 """Code for converting notebooks to and from the v2 format.
1 """Code for converting notebooks to and from the v2 format.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Jonathan Frederic
6 """
7 """
7
8
8 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
10 # Copyright (C) 2008-2011 The IPython Development Team
10 #
11 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
14
15
15 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
16 # Imports
17 # Imports
17 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
18
19
19 from .nbbase import (
20 from .nbbase import (
20 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
21 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
21 )
22 )
22
23
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
24 # Code
25 # Code
25 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
26
27
27 def convert_to_this_nbformat(nb, orig_version=1):
28 def upgrade(nb, from_version=1):
28 """Convert a notebook to the v2 format.
29 """Convert a notebook to the v2 format.
29
30
30 Parameters
31 Parameters
31 ----------
32 ----------
32 nb : NotebookNode
33 nb : NotebookNode
33 The Python representation of the notebook to convert.
34 The Python representation of the notebook to convert.
34 orig_version : int
35 orig_version : int
35 The original version of the notebook to convert.
36 The original version of the notebook to convert.
36 """
37 """
37 if orig_version == 1:
38 if orig_version == 1:
38 newnb = new_notebook()
39 newnb = new_notebook()
39 ws = new_worksheet()
40 ws = new_worksheet()
40 for cell in nb.cells:
41 for cell in nb.cells:
41 if cell.cell_type == u'code':
42 if cell.cell_type == u'code':
42 newcell = new_code_cell(input=cell.get('code'),prompt_number=cell.get('prompt_number'))
43 newcell = new_code_cell(input=cell.get('code'),prompt_number=cell.get('prompt_number'))
43 elif cell.cell_type == u'text':
44 elif cell.cell_type == u'text':
44 newcell = new_text_cell(u'markdown',source=cell.get('text'))
45 newcell = new_text_cell(u'markdown',source=cell.get('text'))
45 ws.cells.append(newcell)
46 ws.cells.append(newcell)
46 newnb.worksheets.append(ws)
47 newnb.worksheets.append(ws)
47 return newnb
48 return newnb
48 else:
49 else:
49 raise ValueError('Cannot convert a notebook from v%s to v2' % orig_version)
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 """The main API for the v3 notebook format.
1 """The main API for the v3 notebook format.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from .nbbase import (
19 from .nbbase import (
20 NotebookNode,
20 NotebookNode,
21 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
21 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet,
22 new_metadata, new_author, new_heading_cell, nbformat, nbformat_minor
22 new_metadata, new_author, new_heading_cell, nbformat, nbformat_minor
23 )
23 )
24
24
25 from .nbjson import reads as reads_json, writes as writes_json
25 from .nbjson import reads as reads_json, writes as writes_json
26 from .nbjson import reads as read_json, writes as write_json
26 from .nbjson import reads as read_json, writes as write_json
27 from .nbjson import to_notebook as to_notebook_json
27 from .nbjson import to_notebook as to_notebook_json
28
28
29 from .nbpy import reads as reads_py, writes as writes_py
29 from .nbpy import reads as reads_py, writes as writes_py
30 from .nbpy import reads as read_py, writes as write_py
30 from .nbpy import reads as read_py, writes as write_py
31 from .nbpy import to_notebook as to_notebook_py
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 # Code
36 # Code
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 def parse_filename(fname):
39 def parse_filename(fname):
40 """Parse a notebook filename.
40 """Parse a notebook filename.
41
41
42 This function takes a notebook filename and returns the notebook
42 This function takes a notebook filename and returns the notebook
43 format (json/py) and the notebook name. This logic can be
43 format (json/py) and the notebook name. This logic can be
44 summarized as follows:
44 summarized as follows:
45
45
46 * notebook.ipynb -> (notebook.ipynb, notebook, json)
46 * notebook.ipynb -> (notebook.ipynb, notebook, json)
47 * notebook.json -> (notebook.json, notebook, json)
47 * notebook.json -> (notebook.json, notebook, json)
48 * notebook.py -> (notebook.py, notebook, py)
48 * notebook.py -> (notebook.py, notebook, py)
49 * notebook -> (notebook.ipynb, notebook, json)
49 * notebook -> (notebook.ipynb, notebook, json)
50
50
51 Parameters
51 Parameters
52 ----------
52 ----------
53 fname : unicode
53 fname : unicode
54 The notebook filename. The filename can use a specific filename
54 The notebook filename. The filename can use a specific filename
55 extention (.ipynb, .json, .py) or none, in which case .ipynb will
55 extention (.ipynb, .json, .py) or none, in which case .ipynb will
56 be assumed.
56 be assumed.
57
57
58 Returns
58 Returns
59 -------
59 -------
60 (fname, name, format) : (unicode, unicode, unicode)
60 (fname, name, format) : (unicode, unicode, unicode)
61 The filename, notebook name and format.
61 The filename, notebook name and format.
62 """
62 """
63 if fname.endswith(u'.ipynb'):
63 if fname.endswith(u'.ipynb'):
64 format = u'json'
64 format = u'json'
65 elif fname.endswith(u'.json'):
65 elif fname.endswith(u'.json'):
66 format = u'json'
66 format = u'json'
67 elif fname.endswith(u'.py'):
67 elif fname.endswith(u'.py'):
68 format = u'py'
68 format = u'py'
69 else:
69 else:
70 fname = fname + u'.ipynb'
70 fname = fname + u'.ipynb'
71 format = u'json'
71 format = u'json'
72 name = fname.split('.')[0]
72 name = fname.split('.')[0]
73 return fname, name, format
73 return fname, name, format
74
74
@@ -1,59 +1,90 b''
1 """Code for converting notebooks to and from the v2 format.
1 """Code for converting notebooks to and from the v2 format.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Min RK
7 * Jonathan Frederic
6 """
8 """
7
9
8 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
10 #
12 #
11 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
14
16
15 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
16 # Imports
18 # Imports
17 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
18
20
19 from .nbbase import (
21 from .nbbase import (
20 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
22 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
21 nbformat, nbformat_minor
23 nbformat, nbformat_minor
22 )
24 )
23
25
24 from IPython.nbformat import v2
26 from IPython.nbformat import v2
25
27
26 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
27 # Code
29 # Code
28 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
29
31
30 def convert_to_this_nbformat(nb, orig_version=2, orig_minor=0):
32 def upgrade(nb, from_version=2, from_minor=0):
31 """Convert a notebook to the v3 format.
33 """Convert a notebook to v3.
32
34
33 Parameters
35 Parameters
34 ----------
36 ----------
35 nb : NotebookNode
37 nb : NotebookNode
36 The Python representation of the notebook to convert.
38 The Python representation of the notebook to convert.
37 orig_version : int
39 from_version : int
38 The original version of the notebook to convert.
40 The original version of the notebook to convert.
39 orig_minor : int
41 from_minor : int
40 The original minor version of the notebook to convert (only relevant for v >= 3).
42 The original minor version of the notebook to convert (only relevant for v >= 3).
41 """
43 """
42 if orig_version == 1:
44 if from_version == 2:
43 nb = v2.convert_to_this_nbformat(nb)
44 orig_version = 2
45 if orig_version == 2:
46 # Mark the original nbformat so consumers know it has been converted.
45 # Mark the original nbformat so consumers know it has been converted.
47 nb.nbformat = nbformat
46 nb.nbformat = nbformat
48 nb.nbformat_minor = nbformat_minor
47 nb.nbformat_minor = nbformat_minor
49
48
50 nb.orig_nbformat = 2
49 nb.orig_nbformat = 2
51 return nb
50 return nb
52 elif orig_version == 3:
51 elif from_version == 3:
53 if orig_minor != nbformat_minor:
52 if from_minor != nbformat_minor:
54 nb.orig_nbformat_minor = orig_minor
53 nb.orig_nbformat_minor = from_minor
55 nb.nbformat_minor = nbformat_minor
54 nb.nbformat_minor = nbformat_minor
56 return nb
55 return nb
57 else:
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 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11 from __future__ import print_function
11 from __future__ import print_function
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import os
23 import os
24 import sys
24 import sys
25
25
26 try:
26 try:
27 from configparser import ConfigParser
27 from configparser import ConfigParser
28 except:
28 except:
29 from ConfigParser import ConfigParser
29 from ConfigParser import ConfigParser
30 from distutils.command.build_py import build_py
30 from distutils.command.build_py import build_py
31 from distutils.cmd import Command
31 from distutils.cmd import Command
32 from glob import glob
32 from glob import glob
33 from subprocess import call
33 from subprocess import call
34
34
35 from setupext import install_data_ext
35 from setupext import install_data_ext
36
36
37 #-------------------------------------------------------------------------------
37 #-------------------------------------------------------------------------------
38 # Useful globals and utility functions
38 # Useful globals and utility functions
39 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
40
40
41 # A few handy globals
41 # A few handy globals
42 isfile = os.path.isfile
42 isfile = os.path.isfile
43 pjoin = os.path.join
43 pjoin = os.path.join
44 repo_root = os.path.dirname(os.path.abspath(__file__))
44 repo_root = os.path.dirname(os.path.abspath(__file__))
45
45
46 def oscmd(s):
46 def oscmd(s):
47 print(">", s)
47 print(">", s)
48 os.system(s)
48 os.system(s)
49
49
50 # Py3 compatibility hacks, without assuming IPython itself is installed with
50 # Py3 compatibility hacks, without assuming IPython itself is installed with
51 # the full py3compat machinery.
51 # the full py3compat machinery.
52
52
53 try:
53 try:
54 execfile
54 execfile
55 except NameError:
55 except NameError:
56 def execfile(fname, globs, locs=None):
56 def execfile(fname, globs, locs=None):
57 locs = locs or globs
57 locs = locs or globs
58 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
58 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
59
59
60 # A little utility we'll need below, since glob() does NOT allow you to do
60 # A little utility we'll need below, since glob() does NOT allow you to do
61 # exclusion on multiple endings!
61 # exclusion on multiple endings!
62 def file_doesnt_endwith(test,endings):
62 def file_doesnt_endwith(test,endings):
63 """Return true if test is a file and its name does NOT end with any
63 """Return true if test is a file and its name does NOT end with any
64 of the strings listed in endings."""
64 of the strings listed in endings."""
65 if not isfile(test):
65 if not isfile(test):
66 return False
66 return False
67 for e in endings:
67 for e in endings:
68 if test.endswith(e):
68 if test.endswith(e):
69 return False
69 return False
70 return True
70 return True
71
71
72 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
73 # Basic project information
73 # Basic project information
74 #---------------------------------------------------------------------------
74 #---------------------------------------------------------------------------
75
75
76 # release.py contains version, authors, license, url, keywords, etc.
76 # release.py contains version, authors, license, url, keywords, etc.
77 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
77 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
78
78
79 # Create a dict with the basic information
79 # Create a dict with the basic information
80 # This dict is eventually passed to setup after additional keys are added.
80 # This dict is eventually passed to setup after additional keys are added.
81 setup_args = dict(
81 setup_args = dict(
82 name = name,
82 name = name,
83 version = version,
83 version = version,
84 description = description,
84 description = description,
85 long_description = long_description,
85 long_description = long_description,
86 author = author,
86 author = author,
87 author_email = author_email,
87 author_email = author_email,
88 url = url,
88 url = url,
89 download_url = download_url,
89 download_url = download_url,
90 license = license,
90 license = license,
91 platforms = platforms,
91 platforms = platforms,
92 keywords = keywords,
92 keywords = keywords,
93 classifiers = classifiers,
93 classifiers = classifiers,
94 cmdclass = {'install_data': install_data_ext},
94 cmdclass = {'install_data': install_data_ext},
95 )
95 )
96
96
97
97
98 #---------------------------------------------------------------------------
98 #---------------------------------------------------------------------------
99 # Find packages
99 # Find packages
100 #---------------------------------------------------------------------------
100 #---------------------------------------------------------------------------
101
101
102 def find_packages():
102 def find_packages():
103 """
103 """
104 Find all of IPython's packages.
104 Find all of IPython's packages.
105 """
105 """
106 excludes = ['deathrow', 'quarantine']
106 excludes = ['deathrow', 'quarantine']
107 packages = []
107 packages = []
108 for dir,subdirs,files in os.walk('IPython'):
108 for dir,subdirs,files in os.walk('IPython'):
109 package = dir.replace(os.path.sep, '.')
109 package = dir.replace(os.path.sep, '.')
110 if any(package.startswith('IPython.'+exc) for exc in excludes):
110 if any(package.startswith('IPython.'+exc) for exc in excludes):
111 # package is to be excluded (e.g. deathrow)
111 # package is to be excluded (e.g. deathrow)
112 continue
112 continue
113 if '__init__.py' not in files:
113 if '__init__.py' not in files:
114 # not a package
114 # not a package
115 continue
115 continue
116 packages.append(package)
116 packages.append(package)
117 return packages
117 return packages
118
118
119 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
120 # Find package data
120 # Find package data
121 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
122
122
123 def find_package_data():
123 def find_package_data():
124 """
124 """
125 Find IPython's package_data.
125 Find IPython's package_data.
126 """
126 """
127 # This is not enough for these things to appear in an sdist.
127 # This is not enough for these things to appear in an sdist.
128 # We need to muck with the MANIFEST to get this to work
128 # We need to muck with the MANIFEST to get this to work
129
129
130 # exclude static things that we don't ship (e.g. mathjax)
130 # exclude static things that we don't ship (e.g. mathjax)
131 excludes = ['mathjax']
131 excludes = ['mathjax']
132
132
133 # add 'static/' prefix to exclusions, and tuplify for use in startswith
133 # add 'static/' prefix to exclusions, and tuplify for use in startswith
134 excludes = tuple([os.path.join('static', ex) for ex in excludes])
134 excludes = tuple([os.path.join('static', ex) for ex in excludes])
135
135
136 # walk notebook resources:
136 # walk notebook resources:
137 cwd = os.getcwd()
137 cwd = os.getcwd()
138 os.chdir(os.path.join('IPython', 'html'))
138 os.chdir(os.path.join('IPython', 'html'))
139 static_walk = list(os.walk('static'))
139 static_walk = list(os.walk('static'))
140 static_data = []
140 static_data = []
141 for parent, dirs, files in static_walk:
141 for parent, dirs, files in static_walk:
142 if parent.startswith(excludes):
142 if parent.startswith(excludes):
143 continue
143 continue
144 for f in files:
144 for f in files:
145 static_data.append(os.path.join(parent, f))
145 static_data.append(os.path.join(parent, f))
146
146
147 os.chdir(os.path.join('tests',))
147 os.chdir(os.path.join('tests',))
148 js_tests = glob('casperjs/*.*') + glob('casperjs/*/*')
148 js_tests = glob('casperjs/*.*') + glob('casperjs/*/*')
149 os.chdir(cwd)
149 os.chdir(cwd)
150
150
151 package_data = {
151 package_data = {
152 'IPython.config.profile' : ['README*', '*/*.py'],
152 'IPython.config.profile' : ['README*', '*/*.py'],
153 'IPython.core.tests' : ['*.png', '*.jpg'],
153 'IPython.core.tests' : ['*.png', '*.jpg'],
154 'IPython.testing' : ['*.txt'],
154 'IPython.testing' : ['*.txt'],
155 'IPython.testing.plugin' : ['*.txt'],
155 'IPython.testing.plugin' : ['*.txt'],
156 'IPython.html' : ['templates/*'] + static_data,
156 'IPython.html' : ['templates/*'] + static_data,
157 'IPython.html.tests' : js_tests,
157 'IPython.html.tests' : js_tests,
158 'IPython.qt.console' : ['resources/icon/*.svg'],
158 'IPython.qt.console' : ['resources/icon/*.svg'],
159 'IPython.nbconvert' : ['templates/*.tpl', 'templates/latex/*.tplx',
159 'IPython.nbconvert' : ['templates/*.tpl', 'templates/latex/*.tplx',
160 'templates/latex/skeleton/*.tplx', 'templates/skeleton/*',
160 'templates/latex/skeleton/*.tplx', 'templates/skeleton/*',
161 'templates/reveal_internals/*.tpl', 'tests/files/*.*',
161 'templates/reveal_internals/*.tpl', 'tests/files/*.*',
162 'exporters/tests/files/*.*']
162 'exporters/tests/files/*.*'],
163 'IPython.nbformat' : ['tests/*.ipynb']
163 }
164 }
164 return package_data
165 return package_data
165
166
166
167
167 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
168 # Find data files
169 # Find data files
169 #---------------------------------------------------------------------------
170 #---------------------------------------------------------------------------
170
171
171 def make_dir_struct(tag,base,out_base):
172 def make_dir_struct(tag,base,out_base):
172 """Make the directory structure of all files below a starting dir.
173 """Make the directory structure of all files below a starting dir.
173
174
174 This is just a convenience routine to help build a nested directory
175 This is just a convenience routine to help build a nested directory
175 hierarchy because distutils is too stupid to do this by itself.
176 hierarchy because distutils is too stupid to do this by itself.
176
177
177 XXX - this needs a proper docstring!
178 XXX - this needs a proper docstring!
178 """
179 """
179
180
180 # we'll use these a lot below
181 # we'll use these a lot below
181 lbase = len(base)
182 lbase = len(base)
182 pathsep = os.path.sep
183 pathsep = os.path.sep
183 lpathsep = len(pathsep)
184 lpathsep = len(pathsep)
184
185
185 out = []
186 out = []
186 for (dirpath,dirnames,filenames) in os.walk(base):
187 for (dirpath,dirnames,filenames) in os.walk(base):
187 # we need to strip out the dirpath from the base to map it to the
188 # we need to strip out the dirpath from the base to map it to the
188 # output (installation) path. This requires possibly stripping the
189 # output (installation) path. This requires possibly stripping the
189 # path separator, because otherwise pjoin will not work correctly
190 # path separator, because otherwise pjoin will not work correctly
190 # (pjoin('foo/','/bar') returns '/bar').
191 # (pjoin('foo/','/bar') returns '/bar').
191
192
192 dp_eff = dirpath[lbase:]
193 dp_eff = dirpath[lbase:]
193 if dp_eff.startswith(pathsep):
194 if dp_eff.startswith(pathsep):
194 dp_eff = dp_eff[lpathsep:]
195 dp_eff = dp_eff[lpathsep:]
195 # The output path must be anchored at the out_base marker
196 # The output path must be anchored at the out_base marker
196 out_path = pjoin(out_base,dp_eff)
197 out_path = pjoin(out_base,dp_eff)
197 # Now we can generate the final filenames. Since os.walk only produces
198 # Now we can generate the final filenames. Since os.walk only produces
198 # filenames, we must join back with the dirpath to get full valid file
199 # filenames, we must join back with the dirpath to get full valid file
199 # paths:
200 # paths:
200 pfiles = [pjoin(dirpath,f) for f in filenames]
201 pfiles = [pjoin(dirpath,f) for f in filenames]
201 # Finally, generate the entry we need, which is a pari of (output
202 # Finally, generate the entry we need, which is a pari of (output
202 # path, files) for use as a data_files parameter in install_data.
203 # path, files) for use as a data_files parameter in install_data.
203 out.append((out_path, pfiles))
204 out.append((out_path, pfiles))
204
205
205 return out
206 return out
206
207
207
208
208 def find_data_files():
209 def find_data_files():
209 """
210 """
210 Find IPython's data_files.
211 Find IPython's data_files.
211
212
212 Most of these are docs.
213 Most of these are docs.
213 """
214 """
214
215
215 docdirbase = pjoin('share', 'doc', 'ipython')
216 docdirbase = pjoin('share', 'doc', 'ipython')
216 manpagebase = pjoin('share', 'man', 'man1')
217 manpagebase = pjoin('share', 'man', 'man1')
217
218
218 # Simple file lists can be made by hand
219 # Simple file lists can be made by hand
219 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
220 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
220 if not manpages:
221 if not manpages:
221 # When running from a source tree, the manpages aren't gzipped
222 # When running from a source tree, the manpages aren't gzipped
222 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
223 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
223
224
224 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
225 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
225
226
226 # For nested structures, use the utility above
227 # For nested structures, use the utility above
227 example_files = make_dir_struct(
228 example_files = make_dir_struct(
228 'data',
229 'data',
229 pjoin('docs','examples'),
230 pjoin('docs','examples'),
230 pjoin(docdirbase,'examples')
231 pjoin(docdirbase,'examples')
231 )
232 )
232 manual_files = make_dir_struct(
233 manual_files = make_dir_struct(
233 'data',
234 'data',
234 pjoin('docs','html'),
235 pjoin('docs','html'),
235 pjoin(docdirbase,'manual')
236 pjoin(docdirbase,'manual')
236 )
237 )
237
238
238 # And assemble the entire output list
239 # And assemble the entire output list
239 data_files = [ (manpagebase, manpages),
240 data_files = [ (manpagebase, manpages),
240 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
241 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
241 ] + manual_files + example_files
242 ] + manual_files + example_files
242
243
243 return data_files
244 return data_files
244
245
245
246
246 def make_man_update_target(manpage):
247 def make_man_update_target(manpage):
247 """Return a target_update-compliant tuple for the given manpage.
248 """Return a target_update-compliant tuple for the given manpage.
248
249
249 Parameters
250 Parameters
250 ----------
251 ----------
251 manpage : string
252 manpage : string
252 Name of the manpage, must include the section number (trailing number).
253 Name of the manpage, must include the section number (trailing number).
253
254
254 Example
255 Example
255 -------
256 -------
256
257
257 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
258 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
258 ('docs/man/ipython.1.gz',
259 ('docs/man/ipython.1.gz',
259 ['docs/man/ipython.1'],
260 ['docs/man/ipython.1'],
260 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
261 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
261 """
262 """
262 man_dir = pjoin('docs', 'man')
263 man_dir = pjoin('docs', 'man')
263 manpage_gz = manpage + '.gz'
264 manpage_gz = manpage + '.gz'
264 manpath = pjoin(man_dir, manpage)
265 manpath = pjoin(man_dir, manpage)
265 manpath_gz = pjoin(man_dir, manpage_gz)
266 manpath_gz = pjoin(man_dir, manpage_gz)
266 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
267 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
267 locals() )
268 locals() )
268 return (manpath_gz, [manpath], gz_cmd)
269 return (manpath_gz, [manpath], gz_cmd)
269
270
270 # The two functions below are copied from IPython.utils.path, so we don't need
271 # The two functions below are copied from IPython.utils.path, so we don't need
271 # to import IPython during setup, which fails on Python 3.
272 # to import IPython during setup, which fails on Python 3.
272
273
273 def target_outdated(target,deps):
274 def target_outdated(target,deps):
274 """Determine whether a target is out of date.
275 """Determine whether a target is out of date.
275
276
276 target_outdated(target,deps) -> 1/0
277 target_outdated(target,deps) -> 1/0
277
278
278 deps: list of filenames which MUST exist.
279 deps: list of filenames which MUST exist.
279 target: single filename which may or may not exist.
280 target: single filename which may or may not exist.
280
281
281 If target doesn't exist or is older than any file listed in deps, return
282 If target doesn't exist or is older than any file listed in deps, return
282 true, otherwise return false.
283 true, otherwise return false.
283 """
284 """
284 try:
285 try:
285 target_time = os.path.getmtime(target)
286 target_time = os.path.getmtime(target)
286 except os.error:
287 except os.error:
287 return 1
288 return 1
288 for dep in deps:
289 for dep in deps:
289 dep_time = os.path.getmtime(dep)
290 dep_time = os.path.getmtime(dep)
290 if dep_time > target_time:
291 if dep_time > target_time:
291 #print "For target",target,"Dep failed:",dep # dbg
292 #print "For target",target,"Dep failed:",dep # dbg
292 #print "times (dep,tar):",dep_time,target_time # dbg
293 #print "times (dep,tar):",dep_time,target_time # dbg
293 return 1
294 return 1
294 return 0
295 return 0
295
296
296
297
297 def target_update(target,deps,cmd):
298 def target_update(target,deps,cmd):
298 """Update a target with a given command given a list of dependencies.
299 """Update a target with a given command given a list of dependencies.
299
300
300 target_update(target,deps,cmd) -> runs cmd if target is outdated.
301 target_update(target,deps,cmd) -> runs cmd if target is outdated.
301
302
302 This is just a wrapper around target_outdated() which calls the given
303 This is just a wrapper around target_outdated() which calls the given
303 command if target is outdated."""
304 command if target is outdated."""
304
305
305 if target_outdated(target,deps):
306 if target_outdated(target,deps):
306 os.system(cmd)
307 os.system(cmd)
307
308
308 #---------------------------------------------------------------------------
309 #---------------------------------------------------------------------------
309 # Find scripts
310 # Find scripts
310 #---------------------------------------------------------------------------
311 #---------------------------------------------------------------------------
311
312
312 def find_scripts(entry_points=False, suffix=''):
313 def find_scripts(entry_points=False, suffix=''):
313 """Find IPython's scripts.
314 """Find IPython's scripts.
314
315
315 if entry_points is True:
316 if entry_points is True:
316 return setuptools entry_point-style definitions
317 return setuptools entry_point-style definitions
317 else:
318 else:
318 return file paths of plain scripts [default]
319 return file paths of plain scripts [default]
319
320
320 suffix is appended to script names if entry_points is True, so that the
321 suffix is appended to script names if entry_points is True, so that the
321 Python 3 scripts get named "ipython3" etc.
322 Python 3 scripts get named "ipython3" etc.
322 """
323 """
323 if entry_points:
324 if entry_points:
324 console_scripts = [s % suffix for s in [
325 console_scripts = [s % suffix for s in [
325 'ipython%s = IPython:start_ipython',
326 'ipython%s = IPython:start_ipython',
326 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
327 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
327 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
328 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
328 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
329 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
329 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
330 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
330 'iptest%s = IPython.testing.iptestcontroller:main',
331 'iptest%s = IPython.testing.iptestcontroller:main',
331 'irunner%s = IPython.lib.irunner:main',
332 'irunner%s = IPython.lib.irunner:main',
332 ]]
333 ]]
333 gui_scripts = []
334 gui_scripts = []
334 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
335 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
335 else:
336 else:
336 parallel_scripts = pjoin('IPython','parallel','scripts')
337 parallel_scripts = pjoin('IPython','parallel','scripts')
337 main_scripts = pjoin('IPython','scripts')
338 main_scripts = pjoin('IPython','scripts')
338 scripts = [
339 scripts = [
339 pjoin(parallel_scripts, 'ipengine'),
340 pjoin(parallel_scripts, 'ipengine'),
340 pjoin(parallel_scripts, 'ipcontroller'),
341 pjoin(parallel_scripts, 'ipcontroller'),
341 pjoin(parallel_scripts, 'ipcluster'),
342 pjoin(parallel_scripts, 'ipcluster'),
342 pjoin(parallel_scripts, 'iplogger'),
343 pjoin(parallel_scripts, 'iplogger'),
343 pjoin(main_scripts, 'ipython'),
344 pjoin(main_scripts, 'ipython'),
344 pjoin(main_scripts, 'irunner'),
345 pjoin(main_scripts, 'irunner'),
345 pjoin(main_scripts, 'iptest')
346 pjoin(main_scripts, 'iptest')
346 ]
347 ]
347 return scripts
348 return scripts
348
349
349 #---------------------------------------------------------------------------
350 #---------------------------------------------------------------------------
350 # Verify all dependencies
351 # Verify all dependencies
351 #---------------------------------------------------------------------------
352 #---------------------------------------------------------------------------
352
353
353 def check_for_dependencies():
354 def check_for_dependencies():
354 """Check for IPython's dependencies.
355 """Check for IPython's dependencies.
355
356
356 This function should NOT be called if running under setuptools!
357 This function should NOT be called if running under setuptools!
357 """
358 """
358 from setupext.setupext import (
359 from setupext.setupext import (
359 print_line, print_raw, print_status,
360 print_line, print_raw, print_status,
360 check_for_sphinx, check_for_pygments,
361 check_for_sphinx, check_for_pygments,
361 check_for_nose, check_for_pexpect,
362 check_for_nose, check_for_pexpect,
362 check_for_pyzmq, check_for_readline,
363 check_for_pyzmq, check_for_readline,
363 check_for_jinja2, check_for_tornado
364 check_for_jinja2, check_for_tornado
364 )
365 )
365 print_line()
366 print_line()
366 print_raw("BUILDING IPYTHON")
367 print_raw("BUILDING IPYTHON")
367 print_status('python', sys.version)
368 print_status('python', sys.version)
368 print_status('platform', sys.platform)
369 print_status('platform', sys.platform)
369 if sys.platform == 'win32':
370 if sys.platform == 'win32':
370 print_status('Windows version', sys.getwindowsversion())
371 print_status('Windows version', sys.getwindowsversion())
371
372
372 print_raw("")
373 print_raw("")
373 print_raw("OPTIONAL DEPENDENCIES")
374 print_raw("OPTIONAL DEPENDENCIES")
374
375
375 check_for_sphinx()
376 check_for_sphinx()
376 check_for_pygments()
377 check_for_pygments()
377 check_for_nose()
378 check_for_nose()
378 check_for_pexpect()
379 check_for_pexpect()
379 check_for_pyzmq()
380 check_for_pyzmq()
380 check_for_tornado()
381 check_for_tornado()
381 check_for_readline()
382 check_for_readline()
382 check_for_jinja2()
383 check_for_jinja2()
383
384
384 #---------------------------------------------------------------------------
385 #---------------------------------------------------------------------------
385 # VCS related
386 # VCS related
386 #---------------------------------------------------------------------------
387 #---------------------------------------------------------------------------
387
388
388 # utils.submodule has checks for submodule status
389 # utils.submodule has checks for submodule status
389 execfile(pjoin('IPython','utils','submodule.py'), globals())
390 execfile(pjoin('IPython','utils','submodule.py'), globals())
390
391
391 class UpdateSubmodules(Command):
392 class UpdateSubmodules(Command):
392 """Update git submodules
393 """Update git submodules
393
394
394 IPython's external javascript dependencies live in a separate repo.
395 IPython's external javascript dependencies live in a separate repo.
395 """
396 """
396 description = "Update git submodules"
397 description = "Update git submodules"
397 user_options = []
398 user_options = []
398
399
399 def initialize_options(self):
400 def initialize_options(self):
400 pass
401 pass
401
402
402 def finalize_options(self):
403 def finalize_options(self):
403 pass
404 pass
404
405
405 def run(self):
406 def run(self):
406 failure = False
407 failure = False
407 try:
408 try:
408 self.spawn('git submodule init'.split())
409 self.spawn('git submodule init'.split())
409 self.spawn('git submodule update --recursive'.split())
410 self.spawn('git submodule update --recursive'.split())
410 except Exception as e:
411 except Exception as e:
411 failure = e
412 failure = e
412 print(e)
413 print(e)
413
414
414 if not check_submodule_status(repo_root) == 'clean':
415 if not check_submodule_status(repo_root) == 'clean':
415 print("submodules could not be checked out")
416 print("submodules could not be checked out")
416 sys.exit(1)
417 sys.exit(1)
417
418
418
419
419 def git_prebuild(pkg_dir, build_cmd=build_py):
420 def git_prebuild(pkg_dir, build_cmd=build_py):
420 """Return extended build or sdist command class for recording commit
421 """Return extended build or sdist command class for recording commit
421
422
422 records git commit in IPython.utils._sysinfo.commit
423 records git commit in IPython.utils._sysinfo.commit
423
424
424 for use in IPython.utils.sysinfo.sys_info() calls after installation.
425 for use in IPython.utils.sysinfo.sys_info() calls after installation.
425
426
426 Also ensures that submodules exist prior to running
427 Also ensures that submodules exist prior to running
427 """
428 """
428
429
429 class MyBuildPy(build_cmd):
430 class MyBuildPy(build_cmd):
430 ''' Subclass to write commit data into installation tree '''
431 ''' Subclass to write commit data into installation tree '''
431 def run(self):
432 def run(self):
432 build_cmd.run(self)
433 build_cmd.run(self)
433 # this one will only fire for build commands
434 # this one will only fire for build commands
434 if hasattr(self, 'build_lib'):
435 if hasattr(self, 'build_lib'):
435 self._record_commit(self.build_lib)
436 self._record_commit(self.build_lib)
436
437
437 def make_release_tree(self, base_dir, files):
438 def make_release_tree(self, base_dir, files):
438 # this one will fire for sdist
439 # this one will fire for sdist
439 build_cmd.make_release_tree(self, base_dir, files)
440 build_cmd.make_release_tree(self, base_dir, files)
440 self._record_commit(base_dir)
441 self._record_commit(base_dir)
441
442
442 def _record_commit(self, base_dir):
443 def _record_commit(self, base_dir):
443 import subprocess
444 import subprocess
444 proc = subprocess.Popen('git rev-parse --short HEAD',
445 proc = subprocess.Popen('git rev-parse --short HEAD',
445 stdout=subprocess.PIPE,
446 stdout=subprocess.PIPE,
446 stderr=subprocess.PIPE,
447 stderr=subprocess.PIPE,
447 shell=True)
448 shell=True)
448 repo_commit, _ = proc.communicate()
449 repo_commit, _ = proc.communicate()
449 repo_commit = repo_commit.strip().decode("ascii")
450 repo_commit = repo_commit.strip().decode("ascii")
450
451
451 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
452 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
452 if os.path.isfile(out_pth) and not repo_commit:
453 if os.path.isfile(out_pth) and not repo_commit:
453 # nothing to write, don't clobber
454 # nothing to write, don't clobber
454 return
455 return
455
456
456 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
457 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
457
458
458 # remove to avoid overwriting original via hard link
459 # remove to avoid overwriting original via hard link
459 try:
460 try:
460 os.remove(out_pth)
461 os.remove(out_pth)
461 except (IOError, OSError):
462 except (IOError, OSError):
462 pass
463 pass
463 with open(out_pth, 'w') as out_file:
464 with open(out_pth, 'w') as out_file:
464 out_file.writelines([
465 out_file.writelines([
465 '# GENERATED BY setup.py\n',
466 '# GENERATED BY setup.py\n',
466 'commit = "%s"\n' % repo_commit,
467 'commit = "%s"\n' % repo_commit,
467 ])
468 ])
468 return require_submodules(MyBuildPy)
469 return require_submodules(MyBuildPy)
469
470
470
471
471 def require_submodules(command):
472 def require_submodules(command):
472 """decorator for instructing a command to check for submodules before running"""
473 """decorator for instructing a command to check for submodules before running"""
473 class DecoratedCommand(command):
474 class DecoratedCommand(command):
474 def run(self):
475 def run(self):
475 if not check_submodule_status(repo_root) == 'clean':
476 if not check_submodule_status(repo_root) == 'clean':
476 print("submodules missing! Run `setup.py submodule` and try again")
477 print("submodules missing! Run `setup.py submodule` and try again")
477 sys.exit(1)
478 sys.exit(1)
478 command.run(self)
479 command.run(self)
479 return DecoratedCommand
480 return DecoratedCommand
480
481
481 #---------------------------------------------------------------------------
482 #---------------------------------------------------------------------------
482 # Notebook related
483 # Notebook related
483 #---------------------------------------------------------------------------
484 #---------------------------------------------------------------------------
484
485
485 class CompileCSS(Command):
486 class CompileCSS(Command):
486 """Recompile Notebook CSS
487 """Recompile Notebook CSS
487
488
488 Regenerate the compiled CSS from LESS sources.
489 Regenerate the compiled CSS from LESS sources.
489
490
490 Requires various dev dependencies, such as fabric and lessc.
491 Requires various dev dependencies, such as fabric and lessc.
491 """
492 """
492 description = "Recompile Notebook CSS"
493 description = "Recompile Notebook CSS"
493 user_options = []
494 user_options = []
494
495
495 def initialize_options(self):
496 def initialize_options(self):
496 pass
497 pass
497
498
498 def finalize_options(self):
499 def finalize_options(self):
499 pass
500 pass
500
501
501 def run(self):
502 def run(self):
502 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
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