Contents
- 1.
- 2.
- 3.
- 4.
- 5.
- 5.1
- 5.2
- 5.3
- 5.4
- 5.5
- 6.
- 7.
- 7.1
- 7.2
- 7.3
- 7.4
- 8.
- 9.
- 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.
editorstylehtml, 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.
editorscriptvar 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.
editorscriptfunction 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.
editorscriptfunction 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.
editorscriptfunction escape(str) {
return str
.replace(/&/g, '')
.replace(/</g, '<')
.replace(/>/g, '>')
}
Tangle the HTML editor element
editorscriptvar 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.
editorscriptfunction 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.
editorscriptvar 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.
editorscriptfunction 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.
editorscriptfunction 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.
editorscriptfunction 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.
editorscriptfunction 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.
editorscriptfunction 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.
headstylesheethtml, 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.