Skip to content

Instantly share code, notes, and snippets.

@graemearthur
Forked from mklabs/bash
Created September 13, 2012 22:22
Show Gist options
  • Save graemearthur/3718148 to your computer and use it in GitHub Desktop.
Save graemearthur/3718148 to your computer and use it in GitHub Desktop.
docs simple as cake
#!/bin/bash
# this script install itself (npm package), create a tasks/docs.js, install the docs' task dependencies,
# and run the gh-pages task.
#
# Install:
#
# curl https://raw.github.com/gist/1332036/70f8f3f9b85082569aff7f10773fc40e2fd7388d/bash | sh
#
# It'll generate documentation based on the likely location of
# directories in your package.json (lib/doc), create a gh-pages (empty branch),
# add the docs/ folder generated and perform a new commit.
#
# Directories - http://npmjs.org/doc/json.html#directories
#
# * directories.lib: Walk the dir for any .js/.coffee file and execute docco to generate docs page.
# * directories.doc: Walk the dir for any markdown file, parse them and generate documentation pages using the index.html mustache template.
#
#
# The push is left to you.
npm install https://gist.github.com/gists/1332036/download --loglevel info
echo Ok.. Now install tasks/docs.js deps
npm install mkdirp markdown findit mustache
echo Install Done, Use it:
./node_modules/cakes-docs/node_modules/.bin/cheesecake
./node_modules/cakes-docs/node_modules/.bin/cheesecake init
./node_modules/cakes-docs/node_modules/.bin/cheesecake gh-pages
var path = require('path'),
fs = require('fs'),
exec = require('child_process').exec,
mkdirp = require('mkdirp'),
markdown = require('markdown'),
findit = require('findit'),
mustache = require('mustache'),
template = fs.readFileSync(path.join(__dirname, 'support', 'index.html'), 'utf8');
task('docs', 'Generates documention, based on the likely location of directories in your package.json', function(options, em) {
em.emit('log', 'Doc start');
var package = JSON.parse(fs.readFileSync(gimme(module, 'package.json', em), 'utf8'));
var dirs = package.directories = package.directories || {};
// http://npmjs.org/doc/json.html#directories
dirs.lib = dirs.lib || './lib';
dirs.bin = dirs.bin || './bin';
dirs.man = dirs.man || './man';
dirs.doc = dirs.doc || './';
dirs.example = dirs.example || './examples';
// default repository, fail safe fallback
package.repository = package.repository || {};
package.repository.url = package.repository.url || '';
// default config.doc location, the link to docco entry point
package.config = package.config || {};
package.config.doc = package.config.doc || '';
em
.on('doc:lib', next.bind(em, 'lib'))
.on('doc:doc', next.bind(em, 'doc'))
mkdirp(path.resolve('docs/docco'), 0755, function(err) {
if(err) return em.emit('error', err);
docco(dirs, em);
});
em.on('doc:lib', function(files) {
markdowns(dirs, package, files, em);
});
var remaining = 2;
function next() {
if(--remaining) return;
em.emit('end');
}
});
task('gh-pages', 'Set up a gh-pages branch.', function(options, em) {
em.emit('log', 'Setting up a gh-pages branch');
gem.on('end:docs', function() {
var commands = [
'git symbolic-ref HEAD refs/heads/gh-pages',
'rm .git/index',
'git add docs/',
'git clean -fdx',
'git mv docs/index.html index.html',
'git commit -m "Docs commit"'
].join('&&');
exec(commands, function(err, stdout) {
if(err) return em.emit('error', err);
em.emit('silly', stdout);
em.emit('end');
});
});
invoke('docs');
});
// ### Helpers
function gimme(pmodule, name, em, dir) {
var dir = path.dirname(dir || pmodule.filename);
var files = fs.readdirSync(dir);
if (~files.indexOf(name)) {
return path.join(dir, name);
}
if (dir === '/') {
return em.emit('error', new Error('Could not find ' + name + ' up from: ' + dir));
}
return gimme(pmodule, name, em, dir);
}
function docco(dirs, em) {
em.emit('log', 'Trying to generate documentation at the likely location of lib folder: ' + path.resolve(dirs.lib));
if(!dirs.lib) {
em.emit('warn', 'no dirs lib');
return em.emit('doc:lib');
}
var files = findit.findSync(dirs.lib).filter(function(it) {
var ext = path.extname(it);
return !!~['.js', '.coffee'].indexOf(ext);
});
exec('./node_modules/cakes-docs/node_modules/.bin/docco ' + files.join(' '), function(err, stdout) {
if(err) return em.emit('error', err);
em.emit('log', stdout);
em.emit('doc:lib', files);
});
}
function markdowns(dirs, pkg, doccos, em) {
em.emit('log', 'Trying to generate documentation at the likely location of doc folder: ' + path.resolve(dirs.doc));
em.emit('data', doccos);
if(!dirs.doc) {
em.emit('doc', 'no dirs doc');
return em.emit('doc:doc');
}
var parts = pkg.repository.url.match(/git:\/\/github.com\/([a-z0-9_\-+=.]+)\/([a-z0-9_\-+=.]+).git/),
username = parts && parts[1],
repo = parts && parts[2];
var source = path.resolve(dirs.doc),
files = findit.findSync(source)
.filter(function(it) {
// you must be a file
if(!fs.statSync(it).isFile()) return false;
// you must be a markdown one
if(!~['.mkd', '.md', '.markdown'].indexOf(path.extname(it))) return false;
// also, are you in node_modules folder?
if(/node_modules/.test(it)) return false;
return true;
})
.map(function(it) {
return {
file: path.basename(it),
path: it,
content: fs.readFileSync(it, 'utf8')
}
});
var remaining = files.length;
files.forEach(function(mds) {
em.emit('log', 'Generating from ' + mds.file);
var html = markdown.parse(postparse(preparse(mds.content), username, repo, [username, repo].join('/')));
pkg.content = html;
pkg.docpath = pkg.config.doc || 'docs/' + path.basename(doccos.slice(-1)[0]).replace(/\..+$/, '') + '.html';
html = mustache.to_html(template, pkg, {});
em.emit('log', 'Writing html page for ' + mds.path);
em.emit('log', 'Creating folder structure for ' + path.dirname(output));
// If that's a readme, and is at the root of the repo, write as index.html
var output = new RegExp('readme', 'i').test(mds.file) && path.dirname(mds.path) === path.resolve() ? 'index.html' : mds.file,
output = path.join(__dirname, '..', 'docs', path.dirname(mds.path.replace(source, '')), output);
mkdirp(path.dirname(output), 0755, function(err) {
if(err) return em.emit('error', err);
em.emit('log', path.dirname(output) + ' created. Writing ' + mds.file);
fs.writeFile(output.replace(/\..+/, '.html'), html, function(err) {
if(err) return em.emit('errpr', err);
em.emit('log', 'Ok for ' + mds.path);
if(--remaining) return;
em.emit('doc:doc');
});
});
});
}
// handle ```js kind of marker, remove them and indent code block with 4 spaces.
function preparse(file) {
var lines = file.split('\n'),
code = false,
sections = [];
lines = lines.map(function(line) {
if(/^```\w+$/.test(line)) {
code = true;
return '';
}
if(/^```$/.test(line)) {
code = false;
return '';
}
var prefix = code ? new Array(8).join(' ') : '';
return prefix + line;
});
return lines.join('\n');
}
// ok.. lame function name
function postparse(text, username, repoName, nameWithOwner) {
// ** GFM ** Auto-link URLs
text = text.replace(/https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/g, function(wholeMatch,matchIndex){
var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
if (left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) {return wholeMatch}
href = wholeMatch.replace(/^http:\/\/github.com\//, "https://github.com/")
return "[" + wholeMatch + "](" + wholeMatch + ")";
});
// ** GFM ** Auto-link sha1 if both name and repo are defined
text = text.replace(/[a-f0-9]{40}/ig, function(wholeMatch,matchIndex) {
if(!nameWithOwner) return wholeMatch;
var left = text.slice(0, matchIndex), right = text.slice(matchIndex);
if (left.match(/@$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
return "[" + wholeMatch.substring(0, 7) + "](http://github.com/" + nameWithOwner + "/commit/" + wholeMatch + ")";
});
// ** GFM ** Auto-link user/repo@sha1
text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch,repo,sha) {
return "["+ repo + "@" + sha.substring(0,7) + "](http://github.com/" + repo + "/commit/" + sha + ")";
});
// ** GFM ** Auto-link #issue if nameWithOwner is defined
text = text.replace(/#([0-9]+)/ig, function(wholeMatch,issue,matchIndex){
if (!nameWithOwner) {return wholeMatch;}
var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
if (left == "" || left.match(/[a-z0-9_\-+=.]$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
return "[" + wholeMatch + "](http://github.com/" + nameWithOwner + "/issues/#issue/" + issue + ")";
});
// ** GFM ** Auto-link user/repo#issue
text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch,repo,issue){
return "[" + wholeMatch + "](http://github.com/" + repo + "/issues/#issue/" + issue + ")";
});
return text;
}
<!doctype html>
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!-- Consider adding a manifest.appcache: h5bp.com/d/Offline -->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ name }} :: {{ description }}</title>
<meta name="description" content="{{ description }} ">
<meta name="author" content="{{ author }}">
<!-- Mobile viewport optimized: j.mp/bplateviewport -->
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
/* HTML5 ✰ Boilerplate
* ==|== normalize ==========================================================
*/
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
audio:not([controls]) { display: none; }
[hidden] { display: none; }
html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
body { margin: 0; font-size: 13px; line-height: 1.231; }
body, button, input, select, textarea { font-family: sans-serif; color: #222; }
::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
::selection { background: #fe57a1; color: #fff; text-shadow: none; }
a { color: #00e; }
a:visited { color: #551a8b; }
a:hover { color: #06e; }
a:focus { outline: thin dotted; }
a:hover, a:active { outline: 0; }
abbr[title] { border-bottom: 1px dotted; }
b, strong { font-weight: bold; }
blockquote { margin: 1em 40px; }
dfn { font-style: italic; }
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
ins { background: #ff9; color: #000; text-decoration: none; }
mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
pre, code, kbd, samp { font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 1em; }
pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
q { quotes: none; }
q:before, q:after { content: ""; content: none; }
small { font-size: 85%; }
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
sup { top: -0.5em; }
sub { bottom: -0.25em; }
ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
dd { margin: 0 0 0 40px; }
nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
svg:not(:root) { overflow: hidden; }
figure { margin: 0; }
form { margin: 0; }
fieldset { border: 0; margin: 0; padding: 0; }
label { cursor: pointer; }
legend { border: 0; *margin-left: -7px; padding: 0; }
button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
button, input { line-height: normal; *overflow: visible; }
table button, table input { *overflow: auto; }
button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; }
input[type="checkbox"], input[type="radio"] { box-sizing: border-box; }
input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
textarea { overflow: auto; vertical-align: top; resize: vertical; }
input:valid, textarea:valid { }
input:invalid, textarea:invalid { background-color: #f0dddd; }
table { border-collapse: collapse; border-spacing: 0; }
td { vertical-align: top; }
/* ==|== primary styles =====================================================
Author:
========================================================================== */
body {
padding: 50px;
color: #8b8b8b;
}
h1, h2, h3 {
font-size: 33px;
font-weight: normal;
color: #333;
-webkit-font-smoothing: antialiased;
}
h2 {
padding: 5px 0;
font-size: 24px;
border-bottom: 3px solid #eee;
}
h3 {
padding: 5px 0;
font-size: 18px;
border-bottom: 3px solid #eee;
}
h4 { font-size: 16px; }
h5 { font-size: 14px; }
a { color: #33f; text-decoration: none; }
a:visited { color: #06e; }
a:hover { color: #06e; text-decoration: underline; }
a:focus { outline: thin dotted; }
a:hover, a:active { outline: 0; }
.docs { text-align: right }
/** wikistyle,credits to github guys. **/
p{margin:1em 0 !important;line-height:1.5em !important;}
a.absent{color:#a00;}
ul {margin:1em 0 1em 0.5em !important;}
ol {margin:1em 0 1em 0.5em !important;}
ul li{margin-top:.5em;margin-bottom:.5em;}
ul ul, ul ol, ol ol, ol ul{ margin: 0 !important;}
blockquote{margin:1em 0 !important;border-left:5px solid #ddd !important;padding-left:.6em !important;color:#555 !important;}
dt{font-weight:bold !important;margin-left:1em !important;}
dd{margin-left:2em !important;margin-bottom:1em !important;}
table{margin:1em 0 !important;}
table{margin:1em 0 !important;}
table th{border-bottom:1px solid #bbb !important;padding:.2em 1em !important;}
table td{border-bottom:1px solid #ddd !important;padding:.2em 1em !important;}
pre{margin:1em 0;font-size:12px;background-color:#eee;border:1px solid #ddd;padding:5px;line-height:1.5em;color:#444;overflow:auto;-webkit-box-shadow:rgba(0,0,0,0.07) 0 1px 2px inset;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
pre::-webkit-scrollbar{height:8px;width:8px;}
pre::-webkit-scrollbar-track-piece{margin-bottom:10px;background-color:#eee;border-bottom-left-radius:4px 4px;border-bottom-right-radius:4px 4px;border-top-left-radius:4px 4px;border-top-right-radius:4px 4px;}
pre::-webkit-scrollbar-thumb:vertical{height:25px;background-color:#ccc;-webkit-border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(255,255,255,1);}
pre::-webkit-scrollbar-thumb:horizontal{width:25px;background-color:#ccc;-webkit-border-radius:4px;}
a code, a:link code, a:visited code{color:#4183c4 !important;}
img{max-width:100%;}
pre.console{margin:1em 0 !important;font-size:12px !important;background-color:black !important;padding:.5em !important;line-height:1.5em !important;color:white !important;}
pre.console code{padding:0 !important;font-size:12px !important;background-color:black !important;border:none !important;color:white !important;}
pre.console span{color:#888 !important;}
pre.console span.command{color:yellow !important;}
.frame{margin:0;display:inline-block;}
.frame img{display:block;}
.frame>span{display:block;border:1px solid #aaa;padding:4px;}
.frame span span{display:block;font-size:10pt;margin:0;padding:4px 0 2px 0;text-align:center;line-height:10pt;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;}
.float-left{float:left;padding:.5em 1em .25em 0;}
.float-right{float:right;padding:.5em 0 .25em 1em;}
.align-left{display:block;text-align:left;}
.align-center{display:block;text-align:center;}
.align-right{display:block;text-align:right;}
/* ==|== non-semantic helper classes ======================================== */
.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; }
.ir br { display: none; }
.hidden { display: none !important; visibility: hidden; }
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
.invisible { visibility: hidden; }
.clearfix:before, .clearfix:after { content: ""; display: table; }
.clearfix:after { clear: both; }
.clearfix { zoom: 1; }
/* ==|== media queries ====================================================== */
@media only screen and (min-width: 480px) {
}
@media only screen and (min-width: 768px) {
}
/* ==|== print styles ======================================================= */
@media print {
* { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; }
a, a:visited { text-decoration: underline; }
a[href]:after { content: " (" attr(href) ")"; }
abbr[title]:after { content: " (" attr(title) ")"; }
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
thead { display: table-header-group; }
tr, img { page-break-inside: avoid; }
img { max-width: 100% !important; }
@page { margin: 0.5cm; }
p, h2, h3 { orphans: 3; widows: 3; }
h2, h3 { page-break-after: avoid; }
}
</style>
</head>
<body>
<header>
<h1>{{ name }}<h1>
<h2>{{ description }}</h2>
</header>
<div role="main">
{{#docpath}}
<p class="docs"><a href="{{docpath}}">Moar docs</a></p>
{{/docpath}}
{{{ content }}}
</div>
</body>
</html>
{
"name": "cakes-docs",
"version": "0.0.1",
"dependencies": {
"mustache": "0.3.1-dev",
"mkdirp": "0.0.7",
"markdown": "~0.3.1",
"findit": "~0.1.1",
"docco": "~0.3.0",
"cheesecake": "https://gist.github.com/gists/1332010/download"
},
"scripts": { "postinstall": "sh postinstall.sh" }
}
#!/bin/bash
echo Copying tasks/docs.js and tasks/support/index.html
base=../..
mkdir -p $base/tasks/support
cp docs.js $base/tasks/ && cp index.html $base/tasks/support
echo Install done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment