{"action":"created","comment":{"url":"https://api.github.com/repos/dlanger/500lines/pulls/comments/24302546","id":24302546,"diff_hunk":"@@ -0,0 +1,1344 @@\n+.. raw:: html\n+\n+   <style>\n+     body {font-size: 1.3em; margin: 1em;}\n+     div.document {margin: 0em auto; max-width: 40em;}\n+     pre {background-color: #eee; padding: 0.6em;}\n+   </style>\n+\n+==========================================\n+ Contingent: A Fully Dynamic Build System\n+==========================================\n+\n+Systems to rebuild formatted documents from source texts\n+always seem to do too much work, or too little.\n+\n+They do too much work\n+when they respond to a minor edit\n+by making you wait for unrelated chapters\n+to be re-parsed and re-formatted.\n+But they can also rebuild too little,\n+leaving you with an inconsistent final product.\n+\n+Consider Sphinx 1.2.3, the current version\n+of the document builder\n+that is used for both the official Python language documentation\n+and many other projects in the Python community.\n+Your project’s ``index.rst`` will usually include a table of contents::\n+\n+   Table of Contents\n+   =================\n+\n+   .. toctree::\n+\n+      install.rst\n+      tutorial.rst\n+      api.rst\n+\n+This list of chapter filenames\n+tells Sphinx to include a link to each chapter\n+when it builds the ``index.html`` output file.\n+It will also include links to any sections within each chapter.\n+Stripped of its markup,\n+the text that results from the above ``toctree`` command might be::\n+\n+  Table of Contents\n+\n+  • Installation\n+\n+  • Newcomers Tutorial\n+      • Hello, World\n+      • Adding Logging\n+\n+  • API Reference\n+      • Handy Functions\n+      • Obscure Classes\n+\n+This table of contents is a mash-up\n+of information from four different files.\n+While its basic order and structure come from ``index.rst``,\n+the actual title of each chapter and section\n+is pulled from the three chapter source files themselves.\n+\n+If you later reconsider the tutorial’s chapter title —\n+after all, the word “newcomer” sounds so antique,\n+as if your users are settlers who have just arrived in pioneer Wyoming —\n+then you would edit the first line of ``tutorial.rst``\n+and write something better::\n+\n+  -Newcomers Tutorial\n+  +Beginners Tutorial\n+   ==================\n+\n+   Welcome to the tutorial!\n+   This text will take you through the basics of...\n+\n+When you are ready to rebuild,\n+Sphinx will do exactly the right thing!\n+It will rebuild both the tutorial chapter itself,\n+and also rebuild the index.\n+(Piping the output into ``cat`` makes Sphinx\n+announce each rebuilt file on a separate line,\n+instead of using bare carriage returns\n+to repeatedly overwrite a single line with these progress updates.)\n+::\n+\n+   $ make html | cat\n+   ⋮\n+   writing output... [ 50%] index\n+   writing output... [100%] tutorial\n+\n+Sphinx chose to rebuild both documents.\n+Not only will ``tutorial.html`` now feature its new title up at the top,\n+but the output ``index.html`` will display the updated chapter title\n+in the table of contents.\n+Sphinx has rebuilt everything so that the output is consistent.\n+\n+What if your edit to ``tutorial.rst`` is more minor? ::\n+\n+   Beginners Tutorial\n+   ==================\n+\n+  -Welcome to the tutorial!\n+  +Welcome to our project tutorial!\n+   This text will take you through the basics of...\n+\n+In this case there is no need to rebuild ``index.html``\n+because this minor edit to the interior of a paragraph\n+does not change any of the information in the table of contents.\n+But it turns out that Sphinx is not quite as clever\n+as it might have at first appeared!\n+It goes ahead and does the redundant work of rebuilding\n+``index.html`` even though it will come out exactly the same. ::\n+\n+   writing output... [ 50%] index\n+   writing output... [100%] tutorial\n+\n+You can run ``diff``\n+on the “before” and “after” versions of ``index.html``\n+to confirm that your small edit\n+has had zero effect on the project front page —\n+yet Sphinx made you wait while it was rebuilt anyway.\n+\n+You might not even notice the extra rebuild effort\n+for small documents that are easy to compile.\n+But the delay to your workflow can become significant\n+when you are making frequent tweaks and edits\n+to documents that are long, complex, or that involve the generation\n+of multimedia like plots or animations.\n+While Sphinx is at least making an effort\n+to not rebuild every chapter when you make a single change —\n+it has not, for example, rebuilt ``install.html`` or ``api.html``\n+in response to your ``tutorial.rst`` edit —\n+it is doing more than is necessary.\n+\n+But it turns out that Sphinx does something even worse:\n+it sometimes does too little.\n+\n+To see one of Sphinx’s simplest failure modes,\n+first add a cross reference to the top of your API documentation::\n+\n+   API Reference\n+   =============\n+\n+  +Before reading this, try reading our :doc:`tutorial`!\n+  +\n+   The sections below list every function\n+   and every single class and method offered...\n+\n+With its usual caution as regards the table of contents,\n+Sphinx will dutifully rebuild both this API reference document\n+as well as the ``index.html`` home page of your project::\n+\n+   writing output... [ 50%] api\n+   writing output... [100%] index\n+\n+In the ``api.html`` output file you can confirm\n+that Sphinx has included the attractive human-readable title\n+of the tutorial chapter into the cross reference’s anchor tag::\n+\n+   <p>Before reading this, try reading our\n+   <a class=\"reference internal\" href=\"tutorial.html\">\n+     <em>Beginners Tutorial</em>\n+   </a>!</p>\n+\n+What if you now make another edit\n+to the title at the top of the ``tutorial.rst`` file?\n+You will have invalidated *three* output files:\n+\n+1. The title at the top of ``tutorial.html`` is now out of date,\n+   so the file needs to be rebuilt.\n+\n+2. The table of contents in ``index.html`` still has the old title,\n+   so that document needs to be rebuilt.\n+\n+3. The embedded cross reference in the first paragraph of ``api.html``\n+   still has the old chapter title,\n+   and also needs to be rebuilt.\n+\n+What does Sphinx do? ::\n+\n+   writing output... [ 50%] index\n+   writing output... [100%] tutorial\n+\n+Whoops.\n+\n+Only two files were rebuilt, not three.\n+Sphinx has failed to correctly rebuild your documentation.\n+\n+If you now push your HTML to the web,\n+users will see the old title in the cross reference\n+at the top of ``api.html``\n+but then a different title — the new one —\n+once the link has carried them to ``tutorial.html`` itself.\n+This can happen for many kinds of cross reference that Sphinx supports:\n+chapter titles, section titles, paragraphs,\n+classes, methods, and functions.\n+\n+Build Systems and Consistency\n+=============================\n+\n+The problem outlined above is not specific to Sphinx.\n+Not only does it haunt other document systems, like LaTeX,\n+but it can even plague projects\n+that are simply trying to direct compilation steps\n+with the venerable “make” utility,\n+if their assets happen to cross-reference in interesting ways.\n+\n+As the problem is ancient and universal,\n+its solution is of equally long lineage::\n+\n+   $ rm -r _build/\n+   $ make html\n+\n+You remove all of the output.\n+\n+By eliminating every copy of every intermediate or output asset,\n+the build is forced to start over again completely\n+with nothing cached — with no memory of its earlier states\n+that could possibly lead to a stale product.\n+A hefty ``rm`` ``-r`` solves the problem\n+of guaranteeing consistency before publishing your Sphinx documentation!\n+\n+But could we develop a better approach?\n+\n+What if your build system were a persistent process\n+that noticed every chapter title, every section title,\n+and every cross referenced phrase\n+as it passed from the source code of one document\n+into the text of another?\n+Its decisions about whether to rebuild other documents\n+after a change to a single source file could be precise,\n+instead of mere guesses,\n+and correct,\n+instead of leaving the output in an inconsistent state.\n+\n+The result would be a system like the old static ``make`` tool,\n+but which learned the dependencies between files as they were built —\n+that added and removed dependencies dynamically\n+as cross references were added, updated, and then later deleted.\n+\n+In the sections that follow we will construct such a tool in Python,\n+named Contingent,\n+that guarantees correctness in the presence of dynamic dependencies\n+while performing the fewest possible rebuild steps.\n+While Contingent can be applied to any problem domain,\n+we will run it against a small version of the problem outlined above.\n+\n+Linking Tasks To Make a Graph\n+=============================\n+\n+Any build system needs a way to link inputs and outputs.\n+The three markup texts in our discussion above,\n+for example,\n+each produce a corresponding HTML output file.\n+The most natural way to express these relationships\n+is as a collection of boxes and arrows —\n+or, in mathematician terminology, *nodes* and *edges*\n+to form a graph:\n+\n+.. figure:: figure1.png\n+\n+   **Figure 1.** Three files generated by parsing three input texts.\n+\n+.. * Finally, step back: our API only asks that nodes be represented\n+..   with hashable types. Discuss that tuples are fine too, not just\n+..   strings like we are using in this section.\n+\n+How could we represent such a graph in Python?\n+\n+The language gives priority to four generic data structures\n+by giving them direct support in the language syntax.\n+You can create new instances of the big-four data structures\n+by simply typing them into your source code.\n+\n+* The **tuple** is a read-only sequence\n+  whose syntax is parentheses around comma-separated items.\n+  Tuples are used to hold heterogeneous data —\n+  each slot in a tuple typically means something different.\n+  Here, a tuple holds together a hostname and port number,\n+  and would lose its meaning if the elements were re-ordered::\n+\n+    ('dropbox.com', 443)\n+\n+* The **list** is a mutable sequence\n+  whose syntax is square brackets around comma-separated items.\n+  Lists are used to hold homogenous data —\n+  each item usually has the same structure and meaning as its peers.\n+  Lists can be used either to preserve data’s original input order,\n+  or can be rearranged or sorted\n+  to establish a new and more useful order. ::\n+\n+    ['JavaScript', 'Java', 'PHP', 'Python', 'C#']\n+\n+* The **set** does not preserve order.\n+  Instead, it compares its items by value.\n+  The set remembers only whether a given value has been added,\n+  not how many times,\n+  and is therefore the go-to data structure\n+  for removing duplicates from a data stream.\n+  For example, the following two sets, once the language has built them,\n+  will each have three elements::\n+\n+    {3, 4, 5}\n+    {3, 4, 5, 4, 4, 3, 5, 4, 5, 3, 4, 5}\n+\n+* The **dict** lets the programmer chose the key\n+  by which each value is indexed,\n+  instead of using automatic integer indexing like the tuple and list.\n+  The lookup is backed by a hash table,\n+  which means that dict key lookup runs at the same speed\n+  whether the dict has a dozen or a million keys! ::\n+\n+    {'ssh': 22, 'telnet': 23, 'domain': 53, 'http': 80}\n+\n+A key to Python’s flexibility\n+is that these four data structures are composable.\n+The programmer can arbitrarily nest them inside each other\n+to produce more complex data stores\n+whose rules and syntax remain the simple ones\n+of the underlying tuples, lists, sets, and dicts.\n+\n+Given that each of our graph edges needs\n+to know at least its origin node and its destination node,\n+the simplest possible representation would be a tuple.\n+The top edge in Figure 1 might look like::\n+\n+    ('tutorial.rst', 'tutorial.html')\n+\n+How can we store several edges?\n+While our initial impulse might be\n+to simply throw all of our edge tuples into a list,\n+that would have disadvantages.\n+A list is careful to maintain order,\n+but it is not meaningful to talk about the order of separate edges.\n+And a list would be perfectly happy to hold several copies\n+of exactly the same edge,\n+even though we only want it to be possible\n+to draw a single arrow between ``tutorial.rst`` and ``tutorial.html``.\n+The correct choice is thus the set,\n+which would have us represent Figure 1 as::\n+\n+    {('tutorial.rst', 'tutorial.html'),\n+     ('index.rst', 'index.html'),\n+     ('api.rst', 'api.html')}\n+\n+This would allow quick iteration across all of our edges,\n+fast insert and delete operations for a single edge,\n+and a quick way to check whether a particular edge was present.\n+\n+Unfortunately, those are not the only operations we need.\n+\n+A build system like Contingent\n+is going to focus its processing on nodes, not edges.\n+When building a particular asset like ``api.html``\n+it will need quick access to all of the incoming edges,\n+so that it can adjust them as it learns which resources\n+the build routine uses this time around,\n+and will also need access to the outgoing edges\n+so that any downstream tasks can be re-executed\n+if the content of ``api.html`` is changed.\n+Our set-of-tuples does not make this easy.\n+The entire set would need to be traversed\n+looking for edges that start or end at the ``api.html`` node.\n+\n+Python’s only associative data structure is the dict.\n+We could use a dict to group edges for quick lookup by node::\n+\n+    {'tutorial.rst': {('tutorial.rst', 'tutorial.html')},\n+     'tutorial.html': {('tutorial.rst', 'tutorial.html')},\n+     'index.rst': {('index.rst', 'index.html')},\n+     'index.html': {('index.rst', 'index.html')},\n+     'api.rst': {('api.rst', 'api.html')},\n+     'api.html': {('api.rst', 'api.html')}}\n+\n+Looking up the edges of a particular node would now be blazingly fast,\n+at the cost of having to store every edge twice:\n+once in a set of incoming edges,\n+and once in a set of outgoing edges.\n+But the edges in each set would have to be examined manually\n+to see which are incoming and which are outgoing.\n+It is also slightly redundant to keep naming the node\n+over and over again in its set of edges.\n+\n+The solution to both of these objections\n+is to place incoming and outgoing edges\n+in their own separate data structures,\n+which will also absolve us\n+of having to mention the node over and over again\n+for every one of the edges in which it is involved. ::\n+\n+    incoming = {\n+        'tutorial.html': {'tutorial.rst'},\n+        'index.html': {'index.rst'},\n+        'api.html': {'api.rst'},\n+        }\n+\n+    outgoing = {\n+        'tutorial.rst': {'tutorial.html'},\n+        'index.rst': {'index.html'},\n+        'api.rst': {'api.html'},\n+        }\n+\n+Every edge in this dictionary-of-sets data structure\n+does get represented twice,\n+once as an outgoing edge from one node\n+and again as an incoming edge to another node.\n+But in return for this redundancy,\n+it supports the fast lookup that Contingent needs.\n+\n+The Proper Use of Classes\n+=========================\n+\n+You may have been surprised\n+by the absence of classes in the above discussion\n+of Python data structures.\n+After all, they are a frequent mechanism for organizing data\n+and get defined through a dedicated Python syntax.\n+\n+But it turns out that classes are often\n+to the question of data structure design.\n+Classes simply repeat data structures that we have already seen:\n+\n+* A class instance is *implemented* as a dict.\n+* A class instance is *used* like a mutable tuple.\n+\n+The class offers key lookup into its attribute dictionary\n+through a prettier syntax,\n+where you get to say ``graph.incoming``\n+instead of ``graph[\"incoming\"]``.\n+But, in practice, class instances are almost never used\n+as generic key-value stores.\n+Instead, they are used to organize heterogeneous data\n+by attribute name instead of by an index into a tuple.\n+\n+So instead of putting a hostname and a port number together in a tuple\n+and having to remember later which came first and which came second,\n+you create an ``Address`` class\n+whose instances each have a ``host`` and a ``port`` attribute,\n+pass an ``Address`` around\n+where otherwise you would have had anonymous tuples.\n+Code becomes easier to read and easier to write.\n+But using a class instance does not really change\n+any of the questions we faced above when doing data design:\n+it just provides a prettier and less anonymous container.\n+\n+The true value of classes, then,\n+is not that they change the science of data design.\n+The value of classes\n+is that they let you *hide* your data design from the rest of a program!\n+\n+From the outside, code can simply ask for a new ``Graph`` instance:\n+\n+>>> from contingent import graphlib\n+>>> g = graphlib.Graph()\n+\n+Behind the scenes, a pair of dictionaries has already been built\n+to store edges using the logic we outlined in the previous section:\n+\n+.. include:: contingent/graphlib.py\n+    :code: python\n+    :start-line: 4\n+    :end-line: 6\n+\n+.. include:: contingent/graphlib.py\n+    :code: python\n+    :start-line: 16\n+    :end-line: 19\n+\n+The leading underscore\n+in front of the attribute names ``_inputs_of`` and ``_consequences_of``\n+is a common convention in the Python community\n+to signal that an attribute is private.\n+This convention is one way the community suggests\n+that programmers pass messages and warnings\n+through spacetime to each other.\n+Recognizing the need to signal differences among\n+public versus internal object attributes,\n+the community adopted the single leading underscore\n+as a concise and fairly consistent indicator\n+to other programmers,\n+including our future selves,\n+that the attribute is best treated\n+as part of the internal invisible machinery of the class.\n+\n+Why are we using a “defaultdict” instead of a standard dict?\n+It automatically handles the special case of a missing key.\n+With a normal dict,\n+retrieving a key that does not exist raises a ``KeyError``:\n+\n+>>> consequences_of = {}\n+>>> consequences_of['index.rst'].add('index.html')\n+Traceback (most recent call last):\n+     ...\n+KeyError: 'index.rst'\n+\n+This usually requires special checks throughout the code\n+that uses a dict:\n+\n+.. code:: python\n+\n+    # Special case to handle “we have not seen this task yet”:\n+\n+    if input_task not in self._consequences_of:\n+        self._consequences_of[input_task] = set()\n+\n+    self._consequences_of[input_task].add(consequence_task)\n+\n+The defaultdict lets you provide a function\n+that returns a value for absent keys.\n+When we ask about an edge that the ``Graph`` hasn't yet seen,\n+we will get back an empty ``set`` instead of an exception:\n+\n+>>> from collections import defaultdict\n+>>> consequences_of = defaultdict(set)\n+>>> consequences_of['api.rst']\n+set()\n+\n+Structuring our implementation this way means that\n+each key’s first use can look identical\n+to second-and-subsequent-times that a particular key is used:\n+\n+>>> consequences_of['index.rst'].add('index.html')\n+>>> 'index.html' in consequences_of['index.rst']\n+True\n+\n+Given these techniques, let’s build the graph for Figure 1.\n+\n+First, we need a way to add edges between nodes:\n+\n+.. include:: contingent/graphlib.py\n+    :code: python\n+    :start-line: 40\n+    :end-line: 44\n+\n+This method hides the fact that two, not one,\n+storage steps are required for each new edge\n+so that we know about it in both directions.\n+And notice how ``add_edge()`` does not know or care\n+whether either node has been seen before.\n+Because the inputs and consequences data structures\n+are each a ``defaultdict(set)``,\n+``add_edge()`` remains blissfully ignorant\n+as to the novelty of a node,\n+while ``defaultdict`` takes care of the difference\n+by creating new ``set`` objects on the fly as needed.\n+As we saw above, ``add_edge()`` would be\n+three times longer had we not used ``defaultdict``.\n+More importantly, it would be much more difficult\n+to read its purpose and behavior from the resulting code.\n+This implementation demonstrates a Pythonic\n+approach to problems: simple, direct, and concise.\n+\n+All of these considerations disappear\n+as soon as we turn away from the code inside of the class.\n+Code that is simply using the graph\n+sees only a single verb — the method call —\n+each time an edge is added:\n+\n+>>> g.add_edge('index.rst', 'index.html')\n+>>> g.add_edge('tutorial.rst', 'tutorial.html')\n+>>> g.add_edge('api.rst', 'api.html')\n+\n+Callers should also be given a simple way to visit every edge\n+without having to learn how to traverse our data structure:\n+\n+.. include:: contingent/graphlib.py\n+    :code: python\n+    :start-line: 65\n+    :end-line: 69\n+\n+The ``Graph.sorted()`` method, if you want to examine it later,\n+makes an attempt to sort the nodes\n+in case they have a natural sort order\n+(alphabetical, in this case)\n+that can provide a stable output order for the user.\n+\n+By using this traversal method we can see that,\n+following our three “add” method calls earlier,\n+``g`` now represents the same graph that we saw in Figure 1.\n+\n+>>> from pprint import pprint\n+>>> pprint(g.edges())\n+[('api.rst', 'api.html'),\n+ ('index.rst', 'index.html'),\n+ ('tutorial.rst', 'tutorial.html')]\n+\n+Since we now have a real live Python object,\n+and not just a figure,\n+we can ask it interesting questions!\n+For example, when Contingent is building a blog from source files,\n+it will need to know things like “What depends on ``api.rst``?” when\n+the content of ``api.rst`` changes:\n+\n+>>> g.immediate_consequences_of('api.rst')\n+['api.html']\n+\n+This ``Graph`` is telling Contingent that,\n+when ``api.rst`` changes,\n+``api.html`` is now stale and must be rebuilt.\n+How about ``index.html``?\n+\n+>>> g.immediate_consequences_of('index.html')\n+[]\n+\n+An empty list has been returned,\n+signalling that ``index.html`` is at the right edge of the graph\n+and so nothing further needs to be rebuilt if it changes.\n+This query can be expressed very simply\n+thanks to the work that has already gone in to laying out our data:\n+\n+.. include:: contingent/graphlib.py\n+    :code: python\n+    :start-line: 69\n+    :end-line: 72\n+\n+Careful readers will have noticed that we added edges to our graph\n+without explicitly creating “node” and “edge” objects,\n+and that the nodes themselves in these early examples\n+are simply strings.\n+Coming from other languages and traditions,\n+one might have expected to see\n+user-defined classes and interfaces for everything in the system::\n+\n+    Graph g = new ConcreteGraph();\n+    Node indexRstNode = new StringNode(\"index.rst\");\n+    Node indexHtmlNode = new StringNode(\"index.html\");\n+    Edge indexEdge = new DirectedEdge(indexRstNode, indexHtmlNode);\n+    g.addEdge(indexEdge);\n+\n+The Python language and community explicitly and intentionally emphasize\n+using simple, generic data structures to solve problems,\n+instead of creating custom classes for every minute detail\n+of the problem we want to tackle.\n+This is one facet of the notion of “Pythonic” solutions that you may\n+have read about: Pythonic solutions try to\n+minimize syntactic overhead\n+and leverage Python's powerful built-in tools\n+and extensive standard library.\n+\n+..\n+ >>> from contingent.rendering import as_graphviz\n+ >>> open('figure1.dot', 'w').write(as_graphviz(g)) and None\n+\n+Figure 1 ignored one of the most important relationships\n+that we discovered in the opening section of our chapter:\n+the way that document titles appear in the table of contents.\n+Let’s fill in this detail.\n+We will create a node for each title string\n+that needs to be generated by parsing an input file\n+and then passed to one of our other routines:\n+\n+>>> g.add_edge('api.rst', 'api-title')\n+>>> g.add_edge('api-title', 'index.html')\n+>>> g.add_edge('tutorial.rst', 'tutorial-title')\n+>>> g.add_edge('tutorial-title', 'index.html')\n+\n+The result is a graph that could properly handle\n+rebuilding the table of contents that we discussed\n+in the opening of this chapter.\n+\n+..\n+ >>> open('figure2.dot', 'w').write(as_graphviz(g)) and None\n+\n+.. figure:: figure2.png\n+\n+   **Figure 2.** Being prepared to rebuild ``index.html``\n+   whenever any title that it mentions gets changed.\n+\n+This manual walk-through illustrates what we\n+will eventually have Contingent do for us:\n+the graph ``g`` captures the inputs and consequences\n+for the various artifacts in our project's documentation.\n+\n+..  I wonder if we should defer talking about clear_inputs_of() until\n+..  later, rather than trying to conjure up a motivation for it here.\n+\n+Learning Connections\n+====================\n+\n+We now have a way for Contingent\n+to keep track of tasks and the relationships between them.\n+If we look more closely at Figure 2, however,\n+we see that it is actually a little hand wavy and vague:\n+what does it mean to say that\n+``index.rst``, ``api-title``, and ``tutorial.html`` are “tasks?”\n+\n+Our intuitive notion of these ideas\n+served when we were constructing consequences graphs by hand,\n+but unfortunately computers are not terribly intuitive,\n+so we will need to be more precise about what we want.\n+What are tasks?\n+How are they defined and executed?\n+And how can Contingent know the connections between them?\n+\n+In Contingent, tasks are modeled as functions plus arguments.\n+The functions define actions that the particular project\n+understands how to perform.\n+The arguments provide\n+the specifics: *which* source document should be read,\n+*which* blog title is needed.\n+As they are running,\n+these functions may in turn invoke *other* task functions,\n+passing whatever arguments they need answers for.\n+\n+To see how this works we will actually now implement\n+the documentation builder described at the beginning of the chapter.\n+In order to prevent ourselves from wallowing around in a bog of details,\n+for this illustration we will work with\n+simplified input and output document formats.\n+Our input documents will consist of a title on the first line,\n+with the remainder of the text forming the body.\n+Cross references will simply be source file names\n+enclosed in back ticks,\n+which on output are replaced with the title\n+from the corresponding document in the output.\n+\n+Here is the content of our example\n+``index.txt``, ``api.txt``, and ``tutorial.txt``,\n+illustrating titles, document bodies, and cross-references\n+from our little document format:\n+\n+>>> index = \"\"\"","path":"contingent/chapter.rst","position":722,"original_position":722,"commit_id":"d989989df03e0b60803279d3ac759ad8b055ee22","original_commit_id":"d989989df03e0b60803279d3ac759ad8b055ee22","user":{"login":"dlanger","id":529037,"avatar_url":"https://avatars.githubusercontent.com/u/529037?v=3","gravatar_id":"","url":"https://api.github.com/users/dlanger","html_url":"https://github.com/dlanger","followers_url":"https://api.github.com/users/dlanger/followers","following_url":"https://api.github.com/users/dlanger/following{/other_user}","gists_url":"https://api.github.com/users/dlanger/gists{/gist_id}","starred_url":"https://api.github.com/users/dlanger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dlanger/subscriptions","organizations_url":"https://api.github.com/users/dlanger/orgs","repos_url":"https://api.github.com/users/dlanger/repos","events_url":"https://api.github.com/users/dlanger/events{/privacy}","received_events_url":"https://api.github.com/users/dlanger/received_events","type":"User","site_admin":false},"body":"Showing these as if you're in a REPL feels like it's just complicating things - when we showed files earlier, they weren't in a REPL.","created_at":"2015-02-08T21:18:26Z","updated_at":"2015-02-08T21:18:26Z","html_url":"https://github.com/dlanger/500lines/pull/1#discussion_r24302546","pull_request_url":"https://api.github.com/repos/dlanger/500lines/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/dlanger/500lines/pulls/comments/24302546"},"html":{"href":"https://github.com/dlanger/500lines/pull/1#discussion_r24302546"},"pull_request":{"href":"https://api.github.com/repos/dlanger/500lines/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/dlanger/500lines/pulls/1","id":28886496,"html_url":"https://github.com/dlanger/500lines/pull/1","diff_url":"https://github.com/dlanger/500lines/pull/1.diff","patch_url":"https://github.com/dlanger/500lines/pull/1.patch","issue_url":"https://api.github.com/repos/dlanger/500lines/issues/1","number":1,"state":"open","locked":false,"title":"Chapter review","user":{"login":"dlanger","id":529037,"avatar_url":"https://avatars.githubusercontent.com/u/529037?v=3","gravatar_id":"","url":"https://api.github.com/users/dlanger","html_url":"https://github.com/dlanger","followers_url":"https://api.github.com/users/dlanger/followers","following_url":"https://api.github.com/users/dlanger/following{/other_user}","gists_url":"https://api.github.com/users/dlanger/gists{/gist_id}","starred_url":"https://api.github.com/users/dlanger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dlanger/subscriptions","organizations_url":"https://api.github.com/users/dlanger/orgs","repos_url":"https://api.github.com/users/dlanger/repos","events_url":"https://api.github.com/users/dlanger/events{/privacy}","received_events_url":"https://api.github.com/users/dlanger/received_events","type":"User","site_admin":false},"body":"From the POV of someone who's never done anything serious with Sphinx before.","created_at":"2015-02-08T20:57:33Z","updated_at":"2015-02-08T21:18:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6882e0d6f99897ad3869cb686b4b40ae458fd811","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/dlanger/500lines/pulls/1/commits","review_comments_url":"https://api.github.com/repos/dlanger/500lines/pulls/1/comments","review_comment_url":"https://api.github.com/repos/dlanger/500lines/pulls/comments/{number}","comments_url":"https://api.github.com/repos/dlanger/500lines/issues/1/comments","statuses_url":"https://api.github.com/repos/dlanger/500lines/statuses/d989989df03e0b60803279d3ac759ad8b055ee22","head":{"label":"dlanger:feature/contingent","ref":"feature/contingent","sha":"d989989df03e0b60803279d3ac759ad8b055ee22","user":{"login":"dlanger","id":529037,"avatar_url":"https://avatars.githubusercontent.com/u/529037?v=3","gravatar_id":"","url":"https://api.github.com/users/dlanger","html_url":"https://github.com/dlanger","followers_url":"https://api.github.com/users/dlanger/followers","following_url":"https://api.github.com/users/dlanger/following{/other_user}","gists_url":"https://api.github.com/users/dlanger/gists{/gist_id}","starred_url":"https://api.github.com/users/dlanger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dlanger/subscriptions","organizations_url":"https://api.github.com/users/dlanger/orgs","repos_url":"https://api.github.com/users/dlanger/repos","events_url":"https://api.github.com/users/dlanger/events{/privacy}","received_events_url":"https://api.github.com/users/dlanger/received_events","type":"User","site_admin":false},"repo":{"id":30505370,"name":"500lines","full_name":"dlanger/500lines","owner":{"login":"dlanger","id":529037,"avatar_url":"https://avatars.githubusercontent.com/u/529037?v=3","gravatar_id":"","url":"https://api.github.com/users/dlanger","html_url":"https://github.com/dlanger","followers_url":"https://api.github.com/users/dlanger/followers","following_url":"https://api.github.com/users/dlanger/following{/other_user}","gists_url":"https://api.github.com/users/dlanger/gists{/gist_id}","starred_url":"https://api.github.com/users/dlanger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dlanger/subscriptions","organizations_url":"https://api.github.com/users/dlanger/orgs","repos_url":"https://api.github.com/users/dlanger/repos","events_url":"https://api.github.com/users/dlanger/events{/privacy}","received_events_url":"https://api.github.com/users/dlanger/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dlanger/500lines","description":"500 Lines or Less","fork":true,"url":"https://api.github.com/repos/dlanger/500lines","forks_url":"https://api.github.com/repos/dlanger/500lines/forks","keys_url":"https://api.github.com/repos/dlanger/500lines/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dlanger/500lines/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dlanger/500lines/teams","hooks_url":"https://api.github.com/repos/dlanger/500lines/hooks","issue_events_url":"https://api.github.com/repos/dlanger/500lines/issues/events{/number}","events_url":"https://api.github.com/repos/dlanger/500lines/events","assignees_url":"https://api.github.com/repos/dlanger/500lines/assignees{/user}","branches_url":"https://api.github.com/repos/dlanger/500lines/branches{/branch}","tags_url":"https://api.github.com/repos/dlanger/500lines/tags","blobs_url":"https://api.github.com/repos/dlanger/500lines/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dlanger/500lines/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dlanger/500lines/git/refs{/sha}","trees_url":"https://api.github.com/repos/dlanger/500lines/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dlanger/500lines/statuses/{sha}","languages_url":"https://api.github.com/repos/dlanger/500lines/languages","stargazers_url":"https://api.github.com/repos/dlanger/500lines/stargazers","contributors_url":"https://api.github.com/repos/dlanger/500lines/contributors","subscribers_url":"https://api.github.com/repos/dlanger/500lines/subscribers","subscription_url":"https://api.github.com/repos/dlanger/500lines/subscription","commits_url":"https://api.github.com/repos/dlanger/500lines/commits{/sha}","git_commits_url":"https://api.github.com/repos/dlanger/500lines/git/commits{/sha}","comments_url":"https://api.github.com/repos/dlanger/500lines/comments{/number}","issue_comment_url":"https://api.github.com/repos/dlanger/500lines/issues/comments/{number}","contents_url":"https://api.github.com/repos/dlanger/500lines/contents/{+path}","compare_url":"https://api.github.com/repos/dlanger/500lines/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dlanger/500lines/merges","archive_url":"https://api.github.com/repos/dlanger/500lines/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dlanger/500lines/downloads","issues_url":"https://api.github.com/repos/dlanger/500lines/issues{/number}","pulls_url":"https://api.github.com/repos/dlanger/500lines/pulls{/number}","milestones_url":"https://api.github.com/repos/dlanger/500lines/milestones{/number}","notifications_url":"https://api.github.com/repos/dlanger/500lines/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dlanger/500lines/labels{/name}","releases_url":"https://api.github.com/repos/dlanger/500lines/releases{/id}","created_at":"2015-02-08T20:53:05Z","updated_at":"2015-02-08T20:53:06Z","pushed_at":"2015-02-08T20:56:17Z","git_url":"git://github.com/dlanger/500lines.git","ssh_url":"git@github.com:dlanger/500lines.git","clone_url":"https://github.com/dlanger/500lines.git","svn_url":"https://github.com/dlanger/500lines","homepage":null,"size":1731,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"dlanger:master","ref":"master","sha":"cc4b7e7fe415ffc7f3d8d808b45043a892bddaae","user":{"login":"dlanger","id":529037,"avatar_url":"https://avatars.githubusercontent.com/u/529037?v=3","gravatar_id":"","url":"https://api.github.com/users/dlanger","html_url":"https://github.com/dlanger","followers_url":"https://api.github.com/users/dlanger/followers","following_url":"https://api.github.com/users/dlanger/following{/other_user}","gists_url":"https://api.github.com/users/dlanger/gists{/gist_id}","starred_url":"https://api.github.com/users/dlanger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dlanger/subscriptions","organizations_url":"https://api.github.com/users/dlanger/orgs","repos_url":"https://api.github.com/users/dlanger/repos","events_url":"https://api.github.com/users/dlanger/events{/privacy}","received_events_url":"https://api.github.com/users/dlanger/received_events","type":"User","site_admin":false},"repo":{"id":30505370,"name":"500lines","full_name":"dlanger/500lines","owner":{"login":"dlanger","id":529037,"avatar_url":"https://avatars.githubusercontent.com/u/529037?v=3","gravatar_id":"","url":"https://api.github.com/users/dlanger","html_url":"https://github.com/dlanger","followers_url":"https://api.github.com/users/dlanger/followers","following_url":"https://api.github.com/users/dlanger/following{/other_user}","gists_url":"https://api.github.com/users/dlanger/gists{/gist_id}","starred_url":"https://api.github.com/users/dlanger/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dlanger/subscriptions","organizations_url":"https://api.github.com/users/dlanger/orgs","repos_url":"https://api.github.com/users/dlanger/repos","events_url":"https://api.github.com/users/dlanger/events{/privacy}","received_events_url":"https://api.github.com/users/dlanger/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dlanger/500lines","description":"500 Lines or Less","fork":true,"url":"https://api.github.com/repos/dlanger/500lines","forks_url":"https://api.github.com/repos/dlanger/500lines/forks","keys_url":"https://api.github.com/repos/dlanger/500lines/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dlanger/500lines/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dlanger/500lines/teams","hooks_url":"https://api.github.com/repos/dlanger/500lines/hooks","issue_events_url":"https://api.github.com/repos/dlanger/500lines/issues/events{/number}","events_url":"https://api.github.com/repos/dlanger/500lines/events","assignees_url":"https://api.github.com/repos/dlanger/500lines/assignees{/user}","branches_url":"https://api.github.com/repos/dlanger/500lines/branches{/branch}","tags_url":"https://api.github.com/repos/dlanger/500lines/tags","blobs_url":"https://api.github.com/repos/dlanger/500lines/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dlanger/500lines/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dlanger/500lines/git/refs{/sha}","trees_url":"https://api.github.com/repos/dlanger/500lines/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dlanger/500lines/statuses/{sha}","languages_url":"https://api.github.com/repos/dlanger/500lines/languages","stargazers_url":"https://api.github.com/repos/dlanger/500lines/stargazers","contributors_url":"https://api.github.com/repos/dlanger/500lines/contributors","subscribers_url":"https://api.github.com/repos/dlanger/500lines/subscribers","subscription_url":"https://api.github.com/repos/dlanger/500lines/subscription","commits_url":"https://api.github.com/repos/dlanger/500lines/commits{/sha}","git_commits_url":"https://api.github.com/repos/dlanger/500lines/git/commits{/sha}","comments_url":"https://api.github.com/repos/dlanger/500lines/comments{/number}","issue_comment_url":"https://api.github.com/repos/dlanger/500lines/issues/comments/{number}","contents_url":"https://api.github.com/repos/dlanger/500lines/contents/{+path}","compare_url":"https://api.github.com/repos/dlanger/500lines/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dlanger/500lines/merges","archive_url":"https://api.github.com/repos/dlanger/500lines/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dlanger/500lines/downloads","issues_url":"https://api.github.com/repos/dlanger/500lines/issues{/number}","pulls_url":"https://api.github.com/repos/dlanger/500lines/pulls{/number}","milestones_url":"https://api.github.com/repos/dlanger/500lines/milestones{/number}","notifications_url":"https://api.github.com/repos/dlanger/500lines/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dlanger/500lines/labels{/name}","releases_url":"https://api.github.com/repos/dlanger/500lines/releases{/id}","created_at":"2015-02-08T20:53:05Z","updated_at":"2015-02-08T20:53:06Z","pushed_at":"2015-02-08T20:56:17Z","git_url":"git://github.com/dlanger/500lines.git","ssh_url":"git@github.com:dlanger/500lines.git","clone_url":"https://github.com/dlanger/500lines.git","svn_url":"https://github.com/dlanger/500lines","homepage":null,"size":1731,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dlanger/500lines/pulls/1"},"html":{"href":"https://github.com/dlanger/500lines/pull/1"},"issue":{"href":"https://api.github.com/repos/dlanger/500lines/issues/1"},"comments":{"href":"https://api.github.com/repos/dlanger/500lines/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/dlanger/500lines/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/dlanger/500lines/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/dlanger/500lines/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/dlanger/500lines/statuses/d989989df03e0b60803279d3ac759ad8b055ee22"}}}}