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.