pry
CouchApp Literate Programming

Contents

  1. 1. 
  2. 2. 
  3. 3. 
  4. 4. 
  5. 5. 
    1. 5.1 
    2. 5.2 
    3. 5.3 
    4. 5.4 
    5. 5.5 
  6. 6. 
  7. 7. 
    1. 7.1 
    2. 7.2 
    3. 7.3 
    4. 7.4 
  8. 8. 
  9. 9. 
  10. 10. 

1. Introduction

CouchDB a database management system server that use HTTP and HTTPS protocols primarily to deliver JSON documents. CouchApp a client-server application framework based on a Web browser front-end and a CouchDB back-end.

pry is a CouchApp that manages literate programs on CouchDB. It uses markover to 'tangle' a markdown document into a CouchApp and 'weave' it into a HTML document. From the same README.md, it produces a README.json and a README.html. CouchDB delivers a design document that is very similar to the README.json file.

pry itself has a small API: for the source README and the derived JSON and HTML versions. In addition it implements an editor. The editor optionally takes a id argument with a path to the source document.

pry is a rudimentry deployment tool along the lines of Kanso, couchapp, or capp.

pry doesn't support attachments to documents. It only supports the creation of a JSON object (or document) which is deployed to the CouchDB. By convention, the source document is called README.md and it is included in the JSON document. This might be a little confusing at first. The important point is that you shouldn't create a JSON document with a field named README.md since this will be used by pry to store the source document (but it is possible to use a different field name).

By default, pry operates on itself. In the editor, it will download the source document (in the README.md field) into a browser-based editor. You can modify pry, changing its documentation or source code in the editor. By changing the _id of the document, you can upload it to a different location. You can change the database used as well in the editor.

Using the id parameter, pry will edit another source document by downloading its source document (in the README.md field) into the editor.

For example, after opening the pry editor you can replace its contents with:

```#+_id
foo
```

```#+history
It is derived from fubar.
```

and upload this document. The resulting JSON object will have 3 fields: _id, history, and README.md. In the editor, these fields are visible in the Source Code tab. Then you can edit this document by passing in ?id=example/foo. Recall that markover uses a + (plus sign) to indicate that a field should be quoted. You can create a new document by renaming the _id field to bar and uploading.

You can change the source document name using the option md to the editor. For example, ?md=READMEFIRST.md would look for the READMEFIRST.md field for the source document.

2. CouchDB fields

There is only one field that is require in CouchDB for every document: _id. This is the identifier that the JSON document is addressed by. In this case, pry is a design document:

_id
_design/pry

3. CouchApp fields

There are many optional fields used by a CouchApp, but pry is only using two fields: rewrites and shows.

Rewrites are used to translate nice URLs into the URLs used internally. They are particularly useful when combined with a virtual host. For example, by creating a DNS for 'example', CouchDB can be configured to use the path '/example/_design/pry/_rewrite'. Then URLs like 'http://example/README.html' are translated to 'http://example/example/_design/pry/_rewrite/README.html'. Note that it is difficult to use pry with virtually hosts. You are better off using the real hostname.

This represents the entire API for pry.

rewrites
[ { "from": "", "to": "." }, { "from": "/README.html", "to": "_show/README.html" }, { "from": "/README.md", "to": "_show/README.md" }, { "from": "/README.json", "to": "_show/README.json" }, { "from": "editor", "to": "_show/editor" }, { "from": "*", "to": "_show/404" } ]

The first URL returns a JSON object of the pry design document. The other URLs are converted into show function calls.

CouchApps have limited support for javascript. The shows object is one of the places where you can embed anonymous javascript functions in a string. Each function has 2 arguments: the document and the request. Since they are embedded in strings it is easier to implement the details in another functions. But the other functions must be contained within a CommonJS module. In this case the module 'main' is used. These anonymous rewrite functions tend to be one-liners.

shows
{ "404": "function(doc, req){ return {body: '<h1>404 - Document not found</h1>\\n'}, headers: {'Content-Type': 'text/html'}}}", "editor": "function(doc, req){ return {body: require('main').edit.call(this, req), headers: {'Content-Type': 'text/html'}}}", "README.md": "function(doc, req){ return {body: this['README.md'], headers: {'Content-Type': 'text/markdown'}}}", "README.html": "function(doc, req){ return {body: require('main').weave.call(this), headers: {'Content-Type': 'text/html'}}}", "README.json": "function(doc, req){ return {body: require('main').tangle.call(this), headers: {'Content-Type': 'text/plain'}}}" }

4. Program Structure

pry relies on markover for document processing, which relies on marked and highlight.js. The editor in pry is implemented in the Web browser (of course). So there are some challenges to constructing an application (pry) in the CouchDB environment that will serve up another application (the editor) in the Web browser environment. The main issues revolve around special characters and strings that are not allowed in certain situations.

5. Editor

The pry editor is a meta-level HTML document. That is, in this document we are writing the code that will delivered to the Web browser that will provide the editor for the document.

5.1 HTML layout

The editor is an HTML document with 4 tabs.

editorcontent
<ul class='tabs'> <li><a href='#tab1'>Document</a></li> <li><a href='#tab2'>Source Code</a></li> <li><a href='#tab3'>HTML</a></li> <li><a href='#tab4'>Update</a></li> </ul> <div class='display'> <div id='tab1'><input type="text" id='_src' value=''></input><textarea id='_editor'></textarea></div> <div id='tab2'><div id='_code'></div></div> <div id='tab3'></div> <div id='tab4'></div> </div>

5.2 Editor stylesheet

The editor needs its own stylesheet.

First, reset the stylesheet.

editorstyle
html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, a, textarea, input, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { display: block; margin: 0; padding: 0; border: 0; outline: 0; line-height: 140%; font-size: inherit; color: black; vertical-align: baseline; text-decoration: none; background: transparent; } a, code, span, b, i, em { display: inline; } body { font-size: 100%; }

Highlight active elements in the interface.

editorstyle
#tab1 #_src, #tab1 #_editor, .tabs li { border: solid black 1px; } .tabs li:hover, #tab1 #_editor:focus, #tab1 #_src:focus { border: solid orange 1px; }

For the coding environment, use a monospace type. Set up the tabs with the same margins.

editorstyle
.display, .tabs { margin: 0.4em; } #tab1 #_editor, #tab1 #_src { width: 99%; font-family: Courier, monospace; padding: 0.5%; } #tab1 #_editor { resize: none; height: 40em; }

Configure the layout of the menu.

editorstyle
.tabs li { list-style: none; display: inline; padding: 0.4em; } .tabs a, .tabs input { display: inline-block; color: gray; } .tabs a.active { color: black; }

For the source code tab show the formatted output.

editorstyle
.display #_code { white-space: pre; overflow: visible; word-wrap: break-word; }

Hide the menu when printing.

editorstyle
@media print { .tabs { display: none; } }

5.3 The Editor javascript

After the document is loaded, insert the source into the editor and configure the tabs. The source location is initialized as an absolute path on the couchdb host, not a virtual host.

Tangle and weave can take a significant amount of time. The longwait function is called after the document is modified and the user has stopped typing. In the first pass it handles the 2nd tab and in the second pass it handles the 3rd tab. A short timeout is used between passes to allow for user interactions to update. Perhaps this could be implemented in a worker process instead of the main thread. The delay time is estimated from the previous runtime.

editorscript
var tab2todo = true function longwait() { var st = new Date() if (tab2todo) { setuptab2() this.timeout = setTimeout(longwait, 50) } else { setuptab3() } tab2todo = !tab2todo var ms = new Date() - st if (ms < 500) ms = 500 this.delay = 2 * ms }

Getting and putting documents happens asynchronously. Usually the time gaps are not too significant between the request and the delivery of a result.

When the document is available putDoc1 initializes the editor. putDoc is called when the document has been delivered.

editorscript
function putDoc1(md) { $("#_editor").text(md) setuptab1() this.timeout = setTimeout(longwait, 50) } function putDoc(errflag, md) { if (errflag) md = '' else md = md[mdref] putDoc1(md) }

makeurl adds server location information to a path.

editorscript
function makeurl(path) { return location.protocol + '//' + location.host + '/' + path }

This is called when the document is loaded. It installs a timeout on the keyup event in the editor, records the path to the source document, and gets the source document. The source document is either pre-downloaded or we have to get it.

editorscript
$(document).ready(function () { $("#_editor").keyup(function() { if (this.timeout) clearTimeout(this.timeout) tab2todo = true this.timeout = setTimeout(longwait, this.delay || 1000) }) $("#_src").val(mddb) markover = require('markover') if (typeof md == "undefined") { var url = makeurl(mddb) getDocument(url, putDoc) } else putDoc1(md) })

In HTML documents the processing of '&' and '<' characters is tricky. This function handles HTML escapes codes.

editorscript
function escape(str) { return str .replace(/&/g, '') .replace(/</g, '<') .replace(/>/g, '>') }

Tangle the HTML editor element

editorscript
var markover function tangleDoc() { var src = $('#_editor')[0].value var el = $('#_code') var res if (typeof mdref == "undefined" || mdref == "") res = markover.tangleJSON(src) else res = markover.tangleJSON(src, {sourcetarget: mdref}) return res }

Functions to set up the tabs.

editorscript
function setuptab1() { $("#_editor")[0].focus() } function setuptab2() { var el = $('#_code') var txt = tangleDoc() el.text(txt) } function setuptab3() { var src = $('#_editor')[0].value var el = $('#tab3') var txt = markover.weaveJSON(src, {contentonly: true}) el.html(txt) } function setuptab4() { var src = $('#_editor')[0].value var url = $('#_src')[0].value.split('/') url = url[0] var txt = tangleDoc() var json = JSON.parse(txt) var id = json._id json[mdref] = src url = [url, id].join('/') $('#tab4').html('</h1>Updating ' + id + '</h1>') url = makeurl(url) updatedoc(url, json) } var editfunction = { tab1: function(){setuptab1()}, tab4: function(){setuptab4()} }

Set up the menus to show the active tab. Install the onclick event handler to switch tabs.

editorscript
$('ul.tabs').each(function() { var active, content, links = $(this).find('a'), inputs = $(this).find('input') active = $(links[0]) active.addClass('active') content = $(active[0].hash) links.not(active).each(function () { $(this.hash).hide() }) $(this).on('click', 'a', function(e){ active.removeClass('active') content.hide() active = $(this) content = $(this.hash) active.addClass('active') var f = editfunction[this.hash.substring(1)] if (typeof f !== "undefined") { f() } content.show() e.preventDefault() }) })

Quick and dirty implementation of CommonJS require function. If a name has been loaded, it is returned. The loading of the modules is described below.

editorscript
var require = function require(name) { if (require.loaded[name]) return require.loaded[name] throw new Error("module missing:" + name) } require.loaded = {}

5.4 Update document

Before a document can be updated, the revision of the current document is needed.

Perhaps the revision could be reported by the server, but for now we simply get the current version from the server.

This function gets a document from a CouchDB server and calls the callback function with the errflag, the JSON response, the URL and some other data.

editorscript
function getDocument(url, cb, data) { $.ajax({ url: url, dataType: 'json' }) .done(function(d, s, x) { cb(false, x.responseJSON, url, data) }) .fail(function(x, s, e) { cb(true, x.responseJSON, url, data) }) }

Similarly, this function puts a document to a CouchDB server, calling the callback function with the errflag, and the JSON response.

editorscript
function putDocument(url, doc, cb) { $.ajax({ url: url, type: 'PUT', data: JSON.stringify(doc), contentType: 'application/json', dataType: 'json' }) .done(function(d, s, x) { cb(false, x.responseJSON) }) .fail(function(x, s, e) { cb(true, x.responseJSON) }) }

Updating a document happens through a series of asynchronous calls. The first step is to get the current version of the document.

editorscript
function updatedoc(url, json) { getDocument(url, updatedoc1, json) }

If there isn't a current document, assume that a new document is being created, otherwise add the revision number to the new version of the document.

editorscript
function updatedoc1(errflag, resp, url, doc) { var rev var doit = false var err if (errflag) { if (resp) { err = resp.error doit = resp.error == "not_found" } else { doit = true } } else { doit = true doc._rev = resp._rev } if (!doit) $('#tab4').html('</h1>Update Error: ' + (err || "unknown") + '</h1>') else { $('#tab4').html('</h1>Updating ' + url + '</h1>') putDocument(url, doc, updatedocwithrev) } }

Now the put worked or it didn't. Report the result.

editorscript
function updatedocwithrev(errflag, doc) { if (errflag) $('#tab4').html('</h1>Update Error: ' + "unknown" + '</h1>') else $('#tab4').html('</h1>Updated ' + doc.id + ' to revision ' + doc.rev + '</h1>') }

5.5 Main module

The main module implements the functionality of pry.

main
!(function() { var markover = require('markover') var main = {

First tangle and weave are handled. These operations happen on the server side and are unrelated to the editor.

main
weave: function () { return markover.weaveJSON(this['README.md']) }, tangle: function() { return markover.tangleJSON(this['README.md'], {sourcetarget: 'README.md'}) },

The '</script>' tag needs special handling here. It isn't allowed inside a string within a javascript block. The 'fixup' function replaces '</script>' with '<&#' and '47;script>' combined, but the script block delimiters in the 'edit' function should not be replaced, and they use '</'+'script>'.

main
fixup: function(str) { return str.replace(/<\/script>/gm, '<&#'+'47;script>') },

Regular multiline text is encoded in JSON strings. Special characters are mapped to escape codes.

main
quotestring: function(code) { return ('"' + code .replace(/\\/gm, '\\\\') .replace(/\"/gm /*"*/, '\\"') .replace(/\n/gm , '\\n') .replace(/\t/gm , '\\t') + '"') },

Produce the HTML edit document. First get the encoded source document. Note that 'fixup' runs on this document, so any reference to '</script>' must be handled with care.

main
edit: function(req) { var mdref = 'README.md' var p = req.path var mddb = p.slice(0,3).join('/') var src if (req.query.md) { mdref = req.query.md } if (req.query.id) { mddb = req.query.id } else src = main.quotestring(main.fixup(this[mdref])) var buf = []

The editor header material

edithead
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta http-equiv="Content-Language" content="en"> <meta name="viewport" content="initial-scale=1"> <title>Editor</title> <style>

Push the header material, the style and additional script references. Note the handling of the </script> tag.

main
buf.push(this.edithead) buf.push(this.editorstyle) buf.push('</style>'); // syntax help ['https://code.jquery.com/jquery-2.1.3.min.js'].forEach(function(e){ buf.push('<script type="text/javascript" src="'+e+'"></'+'script>') }) buf.push("</head><body>") buf.push(this.editorcontent) buf.push("</body></html>")

After the document the on-the-fly script is included. First record the location of the source document, its contents and the basic edit scripts.

main
buf.push('<script type="text/javascript">') buf.push('var mdref = "' + mdref + '"') buf.push('var mddb = "' + mddb + '"') buf.push('var md ' + (src ? (' = ' + src) : '')) buf.push(this.editorscript) var self = this

This is my quick and dirty implementation of pre-loaded CommonJS modules. The modules are assumed to exist in the design document, self. An initial list of modules is set in incs. For each module, check if it hasn't been loaded. If not, then statically scan the module code and push any dependent modules onto incs. Module names and contents are pushed into mods

main
var incs = ["markover"] var e var r var i var exports = {} var mods = [] for(i = 0; i < incs.length; i++) { e = incs[i] if (exports[e] == undefined && self[e] != undefined) { main.scanrequire(incs, self[e]) if (self[e] != null) { mods.push([e, self[e]]) exports[e] = true } } }

Process mods in reverse order. All dependent modules should be loaded before the requiring module. Initialize a empty module and exports. Wrap the module code in a function with arguments of module, exports, and require. Call the wrap function with the prepared arguments. Check for non-empty results and store it in loaded. Clean up temporary objects.

This should work for modules that are not mutually dependent.

An alternative implementation would be to put a default value for all modules that are in mods into require.loaded and then update the values as they get loaded. May be the default value would be a function that would return the real object after it has actually been loaded.

main
i = mods.length while ( (--i) >= 0) { e = mods[i][0] r = mods[i][1] buf.push('require.module = {}') buf.push('require.exports = {}') buf.push("require.loading = function (module, exports, require) {") buf.push(main.fixup(r)) buf.push("}") buf.push("require.loading(require.module, require.exports, require)") buf.push('if (typeof require.module.exports == "undefined") require.module.exports = require.exports') buf.push('require.loaded["' + e + '"] = require.module.exports') buf.push('delete require.module.exports') }

The final step is some clean and closing the script. Note the '</script>' handling.

main
buf.push('delete require.loading') buf.push('delete require.module') buf.push('delete require.exports') buf.push('</'+'script>') buf.push("") return buf.join('\n') },

scanrequire statically looks for module names

main
scanrequire: function(incs, str) { var m = str.match(/require\(['"]([^'"]*)['"]\)/gm) if (m != null) { m.forEach(function(e) { var name = e.substring(9,e.length-2) if (incs.indexOf(name) == -1) incs.push(name) }) } }

Now, export the editor module

main
} module.exports = main }).call(function () { return this || (typeof window !== 'undefined' ? window : global) })

6. HTML layout

This HTML layout is used for the weave document. Here is the typical start of an HTML document.

head
<!DOCTYPE html> <html lang="en" class=""> <head>

These meta lines might help.

head
<meta charset="utf-8"> <meta http-equiv="Content-Language" content="en"> <meta name="viewport" content="initial-scale=1">

And the transition from head to body

transition
</head> <body>

Tail end

tail
</body> </html>

7. HTML stylesheet

Make the formatted README look a little smarter:

7.1 Vanilla style

This first section resets all HTML elements to reasonable default values.

headstylesheet
html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, a, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { display: block; margin:0; padding:0; border:0; outline:0; line-height: 140%; font-size: inherit; color: black; vertical-align:baseline; text-decoration: none; background:transparent; } a, code, span, b, i, em { display: inline; } body { font-size: 100%; } @media print { body { font-size: 80%; } }

7.2 Preliminary Material

The following stylesheet operates within the content generated by applying weave to the source document. markover marks up 3 areas before the main document body: title, tag, and a table of contents.

Title and tag are each in a single id and only appear once in the display document.

stylesheet
#content { font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; } #content #title { clear: both; text-align: center; font-size: 140%; font-weight: bold; margin: 2em 0 0 0; } #content #tag { clear: both; text-align: center; font-size: 110%; margin: 0 0 1em 0; }

The table of contents, in a single toc id, is slightly more complex.

stylesheet
#content #toc { padding: 0 0 0 1em; max-width: 96%; page-break-after: always; } @media screen and (min-width: 641px) { #content #toc { float: left; max-width: 26%; } } #content .tocsn { float: left; } #content .tocsr { overflow: hidden; } #content #toc ol { list-style-type: none; } #content #toc ol ol { margin: 0 0 0 1em; }

7.3 Main body

The main document is in a single doc id Make the toc and doc with the same margins.

stylesheet
#content #doc, #content #toc { margin: 0 1em 0 1em; } #content p { text-indent: 1em; } #content code { font-size: 120%; } #content pre code { font-family: "Courier New", Courier, monospace; font-size: 100%; } @media screen and (min-width: 641px) { #content #doc { overflow: hidden; max-width: 70%; } }

Field names are in a field class.

stylesheet
#content pre .field { display:block; margin: 1em 1em 0em 0em; } #content pre .field::after { content: " :="; }

The code blocks are in the code tag and have classes that identify their language.

stylesheet
#content pre code { display: block; margin: 1em; padding: 1em; border: solid 1px #aaa; } @media screen { #content pre code { background-color: AliceBlue; overflow: auto; max-height: 30em; } } @media print { #content pre { page-break-inside: avoid; } #content pre code { overflow: visible; word-wrap: break-word; } }

Each language uses a slightly different light background color.

stylesheet
@media screen { #content .lang-js { background-color: ivory; } #content .lang-md { background-color: lightyellow; } #content .lang-html { background-color: floralwhite; } #content .lang-json { background-color: honeydew; } #content .lang-css { background-color: cornsilk; } #content .lang-sh { background-color: lemonchiffon; } }

The header font sizes are set.

stylesheet
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6 { margin: 0.5em 0em; page-break-after: avoid } #content h1 { font-size: 140%; } #content h2 { font-size: 130%; } #content h3 { font-size: 120%; } #content h4, #content h5, #content h6 { font-size: 110%; }

More styling

stylesheet
#content a:hover { color: #aaa; } #content a:visited, #content a { color: #000;} @media screen { #content a { text-decoration: underline; } } @media print { #content a { text-decoration: none; } #content #doc a[href]:after { content: " (" attr(href) ")"; font-size: 90%; } }

7.4 Highlight markup

highlight.js specific styles for code blocks. Basic type markup:

stylesheet
.hljs { overflow: auto; padding: 0.5em; color: #333; } .hljs-comment, .diff .hljs-header, .hljs-javadoc { color: #999; font-style: italic; } .hljs-keyword, .css .rule .hljs-keyword, .hljs-winutils, .nginx .hljs-title, .hljs-subst, .hljs-request, .hljs-status { color: #333; font-weight: bold; } .hljs-number, .hljs-hexcolor, .ruby .hljs-constant { color: #088; } .hljs-string, .hljs-tag .hljs-value, .hljs-phpdoc, .hljs-dartdoc, .tex .hljs-formula { color: #d14; } .hljs-title, .hljs-id, .scss .hljs-preprocessor { color: #900; font-weight: bold; } .hljs-list .hljs-keyword, .hljs-subst { font-weight: normal; } .hljs-class .hljs-title, .hljs-type, .vhdl .hljs-literal, .tex .hljs-command { color: #458; font-weight: bold; } .hljs-tag, .hljs-tag .hljs-title, .hljs-rules .hljs-property, .django .hljs-tag .hljs-keyword { color: #000080; font-weight: normal; } .hljs-attribute, .hljs-variable, .lisp .hljs-body { color: #008080; } .hljs-regexp { color: #009926; } .hljs-built_in { color: #0086b3; }

Language specific markup

stylesheet
.hljs-symbol, .ruby .hljs-symbol .hljs-string, .lisp .hljs-keyword, .clojure .hljs-keyword, .scheme .hljs-keyword, .tex .hljs-special, .hljs-prompt { color: #990073; }

Other uncommon specialize markup:

stylesheet
.hljs-preprocessor, .hljs-pragma, .hljs-pi, .hljs-doctype, .hljs-shebang, .hljs-cdata { color: #999; font-weight: bold; } .hljs-deletion { background: #fdd; } .hljs-addition { background: #dfd; } .diff .hljs-change { background: #0086b3; } .hljs-chunk { color: #aaa; }

Using CSS it is possibly to have a separate layout for print media. markover overrides certain values for print media.

stylesheet
@media print { .hljs { overflow: visible; word-wrap: break-word; } .hljs-number, .hljs-hexcolor, .ruby .hljs-constant { color: #888; } .hljs-string, .hljs-tag .hljs-value, .hljs-phpdoc, .hljs-dartdoc, .tex .hljs-formula { color: #ddd; } .hljs-title, .hljs-id, .scss .hljs-preprocessor { color: #999; } .hljs-class .hljs-title, .hljs-type, .vhdl .hljs-literal, .tex .hljs-command { color: #888; } .hljs-tag, .hljs-tag .hljs-title, .hljs-rules .hljs-property, .django .hljs-tag .hljs-keyword { color: #888; } .hljs-attribute, .hljs-variable, .lisp .hljs-body { color: #888; } .hljs-regexp { color: #999; } .hljs-symbol, .ruby .hljs-symbol .hljs-string, .lisp .hljs-keyword, .clojure .hljs-keyword, .scheme .hljs-keyword, .tex .hljs-special, .hljs-prompt { color: #999; } .hljs-built_in { color: #888; } .hljs-deletion { background: #ddd; } .hljs-addition { background: #ddd; } .diff .hljs-change { background: #888; } }

8. Modules

pry makes use of several CommonJS modules. The code is omitted from the weave output.

markover provides weave and tangle.

markover depends on marked for parsing markdown documents.

markover depends on highlight.js for source code syntax highlighting.

My version of highlight.js reimplements the 'index.js' file in the distribution. It requires 'highlight_highlight.js' and then requires all the languages that are used in the document and registers them

This seems to leave require.loaded with 2 versions of highlight: the raw version without any languages and this one which has the languages included.

9. Sharing the Code

Using the CouchDB replicator, the JSON version of this document can be moved from one CouchDB to another.

Or, select the README.json then copy and paste the document to your CouchDB.

If you have markover installed, then use the following to build a JSON object.

node -e "require('markover').tangleStream({sourcetarget: 'README.md'})" < README.md > pry.json

And the following for the HTML documentation.

node -e "require('markover').weaveStream()" < README.md > index.html

The display of this 'README.md' file on GitHub is readable, but not quite right since GitHub doesn't process the field names for the code blocks. The generated index.html file is better. Also on GitHub you can copy and paste the pry.json file into the CouchDB database.

10. Final Thoughts

Although the existing editor works well enough, a more sophisticated editor would be useful for large projects. It should be possible to change to the ACE editor. I made minor changes in the markdown mode to work with hash tagged field names, adding json submode, etc. Another interesting approach would be the Hallo editor.

Clearly tools like kanso provide many additional facilities for deploying a CouchApp. Many of these capabilities could be included in pry. But before implementing more capabilities, I will try using the existing system for a while.

In order to bootstap pry on a CouchDB I wrote a separate nodejs utility that used markover to tangle the source document and then upload the JSON to the CouchDB. Run the utility as follows:

node update.js < README.md

To verify that the editor was working properly, I would make minor changes to the documentation and code. But the bulk of the editing occured on my local machine. This brings into question whether pry works effectively.

I've excluded the source from the documentation.

Somewhat surprisingly it is possible to edit documents on mobile devices. The pry document does take some time to tangle and weave on a mobile device.

I was able to copy and paste the pry JSON to deploy it on Cloudant. And it is able to update itself on it.

Although attachments are not consistent with the 'one' in, 'one' out structure of pry, there is a clear utility in supporting attachments.

Being able to insert code could help with the layout of the shows field. Currently, the embedded anonymous functions are simple, yet hard to understand. Being able to insert a quoted function into the middle of the shows structure is certainly a solution. Another possibility is to break the shows structure apart, like:

```#shows
{
  "404" :
```

```#+shows
function(doc, req) {
  return {
    body: '<h1>404 - Document not found</h1>\n',
    headers: {'Content-Type': 'text/html'}
  }
},
```

etc.

```#shows
}
```

This would make the shows functions much more readable. However, this requires a change to markover to handle appending quoted fields to non-quoted fields.