├── .gitignore ├── docs ├── bg.png ├── index.html ├── rocco.css ├── paige.html └── rocco.html ├── resources ├── .DS_Store ├── felt.png ├── circles.png ├── whitey.png ├── project-paper.png ├── bright-squares.png ├── diagonal-noise.png ├── paige.config ├── rocco.jst ├── paige.jst └── rocco.css ├── bin └── paige ├── paige.config ├── package.json ├── Cakefile ├── README.mdown ├── src ├── paige.coffee └── rocco.coffee ├── lib ├── paige.js └── rocco.js └── vendor └── showdown.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /docs/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/docs/bg.png -------------------------------------------------------------------------------- /resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/resources/.DS_Store -------------------------------------------------------------------------------- /resources/felt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/resources/felt.png -------------------------------------------------------------------------------- /resources/circles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/resources/circles.png -------------------------------------------------------------------------------- /resources/whitey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/resources/whitey.png -------------------------------------------------------------------------------- /resources/project-paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/resources/project-paper.png -------------------------------------------------------------------------------- /resources/bright-squares.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/resources/bright-squares.png -------------------------------------------------------------------------------- /resources/diagonal-noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rthauby/Paige/HEAD/resources/diagonal-noise.png -------------------------------------------------------------------------------- /bin/paige: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib'); 6 | 7 | require(lib + '/paige.js'); -------------------------------------------------------------------------------- /paige.config: -------------------------------------------------------------------------------- 1 | { 2 | "title" : "Paige.js", 3 | "content_file" : "README.mdown", 4 | "include_index" : true, 5 | "docco_files" : "src/*.coffee", 6 | "header" : "Paige.js", 7 | "subheader" : "The quickie-wiki Github project page generator", 8 | "background" : "diagonal-noise", 9 | "github" : "https://github.com/rthauby/Paige" 10 | } -------------------------------------------------------------------------------- /resources/paige.config: -------------------------------------------------------------------------------- 1 | { 2 | "title" : "Paige.js", 3 | "content_file" : "README.mdown", 4 | "include_index" : true, 5 | "docco_files" : ["src/lib/*.coffee","src/lib/client/*.coffee"], // Can be a String or Array 6 | "header" : "Paige.js", 7 | "subheader" : "The quickie-wiki Github project page generator", 8 | "background" : "diagonal-noise", 9 | "output" : "my_new_folder" 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paige", 3 | 4 | "description": "The quickie-wiki Github project page generator", 5 | 6 | "keywords": ["docs", "project", "page", "generator", "coffeescript", "docco", "documentation", "site"], 7 | 8 | "author": "Rodrigo Thauby", 9 | 10 | "version": "0.1.7", 11 | 12 | "dependencies": { 13 | "underscore" :"*" 14 | }, 15 | 16 | "licenses": [{ 17 | "type" : "MIT", 18 | "url" : "http://opensource.org/licenses/mit-license.php" 19 | }], 20 | 21 | "engines": { 22 | "node" : ">=0.2.0" 23 | }, 24 | 25 | "directories": { 26 | "lib" : "./lib" 27 | }, 28 | 29 | "main" : "./lib/paige", 30 | 31 | "bin": { 32 | "paige" : "./bin/paige" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {spawn, exec} = require 'child_process' 2 | 3 | option '-p', '--prefix [DIR]', 'set the installation prefix for `cake install`' 4 | 5 | task 'build', 'continually build the docco library with --watch', -> 6 | coffee = spawn 'coffee', ['-cw', '-o', 'lib', 'src'] 7 | coffee.stdout.on 'data', (data) -> console.log data.toString().trim() 8 | 9 | task 'install', 'install the `docco` command into /usr/local (or --prefix)', (options) -> 10 | base = options.prefix or '/usr/local' 11 | lib = base + '/lib/docco' 12 | exec([ 13 | 'mkdir -p ' + lib 14 | 'cp -rf bin README resources vendor lib ' + lib 15 | 'ln -sf ' + lib + '/bin/docco ' + base + '/bin/docco' 16 | ].join(' && '), (err, stdout, stderr) -> 17 | if err then console.error stderr 18 | ) 19 | 20 | task 'doc', 'rebuild the Docco documentation', -> 21 | exec([ 22 | 'bin/docco src/docco.coffee' 23 | 'sed "s/docco.css/resources\\/docco.css/" < docs/docco.html > index.html' 24 | 'rm -r docs' 25 | ].join(' && '), (err) -> 26 | throw err if err 27 | ) 28 | -------------------------------------------------------------------------------- /resources/rocco.jst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= title %> 6 | 7 | 8 | 9 | 10 | 13 |
14 |
15 | <% if (sources.length > 1) { %> 16 |
17 | Jump To … 18 |
19 |
20 | Index 21 | <% for (var i=0, l=sources.length; i 22 | <% var source = sources[i]; %> 23 | 24 | <%= path.basename(source) %> 25 | 26 | <% } %> 27 |
28 |
29 |
30 | <% } %> 31 | 32 | 33 | 34 | 39 | 41 | 42 | 43 | 44 | <% for (var i=0, l=sections.length; i 45 | <% var section = sections[i]; %> 46 | 47 | 53 | 56 | 57 | <% } %> 58 | 59 |
35 |

36 | <%= title %> 37 |

38 |
40 |
48 |
49 | 50 |
51 | <%= section.docs_html %> 52 |
54 | <%= section.code_html %> 55 |
60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /resources/paige.jst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 90 | 91 | 92 | <% if(github) { %> 93 | 94 | Fork me on GitHub 96 | <% } %> 97 |

<%= header %>

98 | <% if(subheader != ""){ %> 99 | <%= subheader %> 100 | <% } %> 101 | <% if(include_index){ %> 102 |

Source files:

103 |
    104 | <% for (var i=0, l=subfiles.length; i 105 |
  1. <%= subfiles[i] %>
  2. 106 | <% } %> 107 |
108 | <% } %> 109 | <%= content_html %> 110 | 111 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | ### Paige.js - *Painless page generation for Github code* 2 | 3 | Paige.js is a simple npm package for creating instant [Github Pages](http://pages.github.com/) without having to deal with any HTML *or* CSS coding. Paige let's you just focus on your code while providing a clean and presentable face for your project. The project page for Paige, itself, was generated with paige.js. 4 | 5 | Paige is largely influenced by [Docco](http://jashkenas.github.com/docco/) and [Underscore](http://documentcloud.github.com/underscore/). The idea behind this project was to—in a sense—extend the wonderful functionality of Docco, and take it one step further as a more fully-featured project site. As such, Paige uses Docco for its source-file annotation, and its style is inspired heavily on the beauty and simplicity of the Underscore website. Not only that, but they are both direct dependencies of this project as well. 6 | 7 | Paige.js is [hosted on Github](https://github.com/rthauby/Paige). Please feel free to mess around with it and let me know of any bugs you find. Also you can always reach me on twitter [@rthauby](http://www.twitter.com/rthauby). 8 | 9 | ### Demo 10 | 11 | You're either reading this at the [project page itself](http://rthauby.github.com/Paige/), or on the Github repo site. If it's the latter, you can see the output of running paige against this very Readme file [here](http://rthauby.github.com/Paige/). If it's the former, well, you're looking at it. 12 | 13 | ### Installation Instructions 14 | 15 | You'll be needing [Node.js](http://nodejs.org/) and [Pygments](http://pygments.org/). [Underscore](http://documentcloud.github.com/underscore/) is included as a package dependency. To install Paige.js: 16 | 17 | `` sudo npm install paige `` 18 | 19 | ... then you can just do: 20 | 21 | `` paige path/to/config `` 22 | 23 | ... and go have a beer or two. 24 | 25 | ### Configuration 26 | 27 | Paige takes one argument only, and it's the location to a configuration file. Alternatively you can just skip the location of this file, and Paige will default to *paige.config* at the root of your project. The configuration file must be in JSON format. 28 | 29 | A basic configuration file looks like this: 30 | 31 | { 32 | "title" : "Paige.js", 33 | "content_file" : "README.mdown", 34 | "include_index" : true, 35 | "docco_files" : "src/*.coffee", 36 | "header" : "Paige.js", 37 | "subheader" : "The quickie-wiki Github project page generator", 38 | "background" : "diagonal-noise", 39 | "github" : "https://github.com/rthauby/Paige" 40 | } 41 | 42 | The properties here are: 43 | 44 | - *title*: Title for the page... As in, in the `` tag 45 | - *content_file*: path to the file were your project page main content resides. 46 | - *include_index*: Do you want to list out the project files a-la-docco? 47 | - *docco_files*: If so, where are they? 48 | - *header*: Main header for the page. 49 | - *subheader*: Your tagline goes here. 50 | - *background*: Eye candy. Available flavors are: bright-squares, felt, whitey, diagonal-noise, circles, project-paper. All pattern sourced from the brilliant [Subtle Patterns](http://subtlepatterns.com/) 51 | - *github*: The URL to the GitHub repository, showing the "Fork me on GitHub" ribbon 52 | 53 | ### Known Issues 54 | 55 | - Docco file generation supported only on unix-based system. 56 | - Index of files is not ordered alphabetically. 57 | 58 | ### License 59 | 60 | This entire project is [MIT licensed](http://www.opensource.org/licenses/mit-license.php). 61 | 62 | ### Change Log 63 | 64 | #### 0.1.7 65 | 66 | - Replacing github syntax highlighted code blocks with showdown friendly pre tags. Many thanks to [DamonOehlman](https://github.com/DamonOehlman) 67 | - Add GitHub repository URL config for "Fork me on GitHub" ribbon. Many thanks to [daffl](https://github.com/daffl) 68 | 69 | #### 0.1.5 70 | 71 | Fixed - Correct process.ARGV. Should be process.argv. Many thanks to [mitchellrj](https://github.com/mitchellrj) 72 | 73 | #### 0.1.4 74 | 75 | Incorporated Docco within the Paige project itself for further customization and integration. For now, added custom output directory. 76 | 77 | #### 0.1.3 78 | 79 | Fixed styling issues with headers and `code/pre` tags 80 | 81 | #### 0.1.2 82 | 83 | Minor language tweaks and info. 84 | 85 | #### 0.1.0 86 | 87 | Initial release of Paige.js -------------------------------------------------------------------------------- /src/paige.coffee: -------------------------------------------------------------------------------- 1 | # Require our external dependencies, including **Showdown.js** 2 | # (the JavaScript implementation of Markdown). 3 | _ = require "underscore" 4 | fs = require 'fs' 5 | path = require 'path' 6 | showdown = require('./../vendor/showdown').Showdown 7 | {spawn, exec} = require 'child_process' 8 | events = require('events') 9 | rocco = require './rocco.js' 10 | 11 | subfiles = [] 12 | configuration = { 13 | "title" : "Untitled", 14 | "content_file" : "README.mdown", 15 | "include_index" : false, 16 | "docco_files" : null, 17 | "header" : "Untitled", 18 | "subheader" : "Untitled", 19 | "background" : "bright_squares", 20 | "output" : "docs" 21 | } 22 | 23 | # Read our configuration file. 24 | read_config = (callback) -> 25 | filename = "paige.config" 26 | filename = process.argv[2] if process.argv[2]? 27 | fs.readFile filename, "utf-8", (error, data) -> 28 | if error 29 | console.log "\nCould not find a configuration file. (default: ./paige.config)" 30 | console.log "Create and specify a configuration file. Example:\n\n" 31 | console.log config_template + "\n" 32 | else 33 | config = JSON.parse(data) 34 | process_config(config) 35 | callback(config) if callback 36 | 37 | 38 | # Process the configuration file 39 | process_config = (config={}) -> 40 | _.map config, (value, key, list) -> 41 | configuration[key] = value if config[key]? 42 | 43 | 44 | # Ensure that the destination directory exists. 45 | ensure_directory = (dir, callback) -> 46 | exec "mkdir -p #{dir}", -> callback() 47 | 48 | 49 | # ... 50 | copy_image = -> 51 | desired_image = fs.readFileSync(__dirname + "/../resources/#{configuration.background}.png") 52 | fs.writeFile "#{configuration.output}/bg.png", desired_image 53 | 54 | 55 | # Build the main html file by reading the source Markdown file, and if necessary 56 | # collecting all the filenames of our source. We will then use these names to construct the 57 | # index that's shown at the top of the page. 58 | # We pass the source Markdown file to Showdown, get the result, then pipe it into 59 | # our templating function described above. 60 | process_html_file = -> 61 | source = configuration.content_file 62 | subfiles_names = clean_file_extension(subfiles) if configuration.include_index 63 | clean_subfiles = clean_path_names(subfiles) if configuration.include_index 64 | fs.readFile source, "utf-8", (error, code) -> 65 | if error 66 | console.log "\nThere was a problem reading your the content file: #{source}" 67 | throw error 68 | else 69 | # replace ``` block with
 tags
 70 |       code = code.replace /```\w*\n?([^```]*)```/gm, '
\n$1
' 71 | 72 | content_html = showdown.makeHtml code 73 | html = mdown_template { 74 | content_html: content_html, 75 | title: configuration.title, 76 | header: configuration.header, 77 | subheader: configuration.subheader, 78 | github: configuration.github, 79 | include_index: configuration.include_index, 80 | subfiles: clean_subfiles, 81 | subfiles_names: subfiles_names 82 | } 83 | console.log "paige: #{source} -> #{configuration.output}/index.html" 84 | fs.writeFile "#{configuration.output}/index.html", html 85 | 86 | 87 | # Micro-templating, originally by John Resig, borrowed by way of 88 | # [Underscore.js](http://documentcloud.github.com/underscore/). 89 | template = (str) -> 90 | new Function 'obj', 91 | 'var p=[],print=function(){p.push.apply(p,arguments);};' + 92 | 'with(obj){p.push(\'' + 93 | str.replace(/[\r\t\n]/g, " ") 94 | .replace(/'(?=[^<]*%>)/g,"\t") 95 | .split("'").join("\\'") 96 | .split("\t").join("'") 97 | .replace(/<%=(.+?)%>/g, "',$1,'") 98 | .split('<%').join("');") 99 | .split('%>').join("p.push('") + 100 | "');}return p.join('');" 101 | 102 | 103 | # Kind of hacky, but I can't figure out another way of doing this cleanly. 104 | # Will list all the files that will be used as your source file for passing onto rocco. 105 | get_subfiles = (callback) => 106 | count = 0 107 | find_files = (file, total) => 108 | f_path = file.substr(0,file.lastIndexOf('/')+1) 109 | f_file = file.substr(file.lastIndexOf('/')+1) 110 | exec "find ./#{f_path} -name '#{f_file}' -print", (error, stdout, stderr) -> 111 | count++ 112 | subfiles = _.uniq(_.union(subfiles, stdout.trim().split("\n"))) 113 | if count >= total 114 | callback() if callback 115 | 116 | if _.isArray(configuration.docco_files) 117 | _.each configuration.docco_files, (file) -> 118 | find_files(file,configuration.docco_files.length) 119 | else if _.isString(configuration.docco_files) 120 | find_files(configuration.docco_files,1) 121 | 122 | 123 | # Remove trailing path names from each file from a list 124 | clean_path_names = (names) -> 125 | clean_names = [] 126 | _.each names, (name) -> 127 | clean_names.push name.substr(name.lastIndexOf('/')+1) || name 128 | return clean_names 129 | 130 | 131 | # Remove file extensions from each file from a list 132 | clean_file_extension = (names) -> 133 | clean_names = [] 134 | _.each names, (name) -> 135 | clean_names.push name.substr(0,name.lastIndexOf('.')).substr(name.lastIndexOf('/')+1) || name 136 | return clean_names 137 | 138 | 139 | # Process the rocco files and wrappers if needed. 140 | check_for_rocco = -> 141 | if configuration.docco_files? 142 | rocco(subfiles, configuration) 143 | 144 | 145 | # Some necessary files 146 | mdown_template = template fs.readFileSync(__dirname + '/../resources/paige.jst').toString() 147 | config_template = fs.readFileSync(__dirname + '/../resources/paige.config').toString() 148 | 149 | 150 | # Run the script 151 | read_config (config) -> 152 | ensure_directory configuration.output, -> 153 | get_subfiles -> 154 | copy_image() 155 | process_html_file() 156 | check_for_rocco() -------------------------------------------------------------------------------- /lib/paige.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var check_for_rocco, clean_file_extension, clean_path_names, config_template, configuration, copy_image, ensure_directory, events, exec, fs, get_subfiles, mdown_template, path, process_config, process_html_file, read_config, rocco, showdown, spawn, subfiles, template, _, _ref, 3 | _this = this; 4 | 5 | _ = require("underscore"); 6 | 7 | fs = require('fs'); 8 | 9 | path = require('path'); 10 | 11 | showdown = require('./../vendor/showdown').Showdown; 12 | 13 | _ref = require('child_process'), spawn = _ref.spawn, exec = _ref.exec; 14 | 15 | events = require('events'); 16 | 17 | rocco = require('./rocco.js'); 18 | 19 | subfiles = []; 20 | 21 | configuration = { 22 | "title": "Untitled", 23 | "content_file": "README.mdown", 24 | "include_index": false, 25 | "docco_files": null, 26 | "header": "Untitled", 27 | "subheader": "Untitled", 28 | "background": "bright_squares", 29 | "output": "docs" 30 | }; 31 | 32 | read_config = function(callback) { 33 | var filename; 34 | filename = "paige.config"; 35 | if (process.argv[2] != null) filename = process.argv[2]; 36 | return fs.readFile(filename, "utf-8", function(error, data) { 37 | var config; 38 | if (error) { 39 | console.log("\nCould not find a configuration file. (default: ./paige.config)"); 40 | console.log("Create and specify a configuration file. Example:\n\n"); 41 | return console.log(config_template + "\n"); 42 | } else { 43 | config = JSON.parse(data); 44 | process_config(config); 45 | if (callback) return callback(config); 46 | } 47 | }); 48 | }; 49 | 50 | process_config = function(config) { 51 | if (config == null) config = {}; 52 | return _.map(config, function(value, key, list) { 53 | if (config[key] != null) return configuration[key] = value; 54 | }); 55 | }; 56 | 57 | ensure_directory = function(dir, callback) { 58 | return exec("mkdir -p " + dir, function() { 59 | return callback(); 60 | }); 61 | }; 62 | 63 | copy_image = function() { 64 | var desired_image; 65 | desired_image = fs.readFileSync(__dirname + ("/../resources/" + configuration.background + ".png")); 66 | return fs.writeFile("" + configuration.output + "/bg.png", desired_image); 67 | }; 68 | 69 | process_html_file = function() { 70 | var clean_subfiles, source, subfiles_names; 71 | source = configuration.content_file; 72 | if (configuration.include_index) { 73 | subfiles_names = clean_file_extension(subfiles); 74 | } 75 | if (configuration.include_index) clean_subfiles = clean_path_names(subfiles); 76 | return fs.readFile(source, "utf-8", function(error, code) { 77 | var content_html, html; 78 | if (error) { 79 | console.log("\nThere was a problem reading your the content file: " + source); 80 | throw error; 81 | } else { 82 | code = code.replace(/```\w*\n?([^```]*)```/gm, '
\n$1
'); 83 | content_html = showdown.makeHtml(code); 84 | html = mdown_template({ 85 | content_html: content_html, 86 | title: configuration.title, 87 | header: configuration.header, 88 | subheader: configuration.subheader, 89 | github: configuration.github, 90 | include_index: configuration.include_index, 91 | subfiles: clean_subfiles, 92 | subfiles_names: subfiles_names 93 | }); 94 | console.log("paige: " + source + " -> " + configuration.output + "/index.html"); 95 | return fs.writeFile("" + configuration.output + "/index.html", html); 96 | } 97 | }); 98 | }; 99 | 100 | template = function(str) { 101 | return new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};' + 'with(obj){p.push(\'' + str.replace(/[\r\t\n]/g, " ").replace(/'(?=[^<]*%>)/g, "\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g, "',$1,'").split('<%').join("');").split('%>').join("p.push('") + "');}return p.join('');"); 102 | }; 103 | 104 | get_subfiles = function(callback) { 105 | var count, find_files; 106 | count = 0; 107 | find_files = function(file, total) { 108 | var f_file, f_path; 109 | f_path = file.substr(0, file.lastIndexOf('/') + 1); 110 | f_file = file.substr(file.lastIndexOf('/') + 1); 111 | return exec("find ./" + f_path + " -name '" + f_file + "' -print", function(error, stdout, stderr) { 112 | count++; 113 | subfiles = _.uniq(_.union(subfiles, stdout.trim().split("\n"))); 114 | if (count >= total) if (callback) return callback(); 115 | }); 116 | }; 117 | if (_.isArray(configuration.docco_files)) { 118 | return _.each(configuration.docco_files, function(file) { 119 | return find_files(file, configuration.docco_files.length); 120 | }); 121 | } else if (_.isString(configuration.docco_files)) { 122 | return find_files(configuration.docco_files, 1); 123 | } 124 | }; 125 | 126 | clean_path_names = function(names) { 127 | var clean_names; 128 | clean_names = []; 129 | _.each(names, function(name) { 130 | return clean_names.push(name.substr(name.lastIndexOf('/') + 1) || name); 131 | }); 132 | return clean_names; 133 | }; 134 | 135 | clean_file_extension = function(names) { 136 | var clean_names; 137 | clean_names = []; 138 | _.each(names, function(name) { 139 | return clean_names.push(name.substr(0, name.lastIndexOf('.')).substr(name.lastIndexOf('/') + 1) || name); 140 | }); 141 | return clean_names; 142 | }; 143 | 144 | check_for_rocco = function() { 145 | if (configuration.docco_files != null) return rocco(subfiles, configuration); 146 | }; 147 | 148 | mdown_template = template(fs.readFileSync(__dirname + '/../resources/paige.jst').toString()); 149 | 150 | config_template = fs.readFileSync(__dirname + '/../resources/paige.config').toString(); 151 | 152 | read_config(function(config) { 153 | return ensure_directory(configuration.output, function() { 154 | return get_subfiles(function() { 155 | copy_image(); 156 | process_html_file(); 157 | return check_for_rocco(); 158 | }); 159 | }); 160 | }); 161 | 162 | }).call(this); 163 | -------------------------------------------------------------------------------- /lib/rocco.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var configuration, destination, ensure_directory, exec, ext, fs, generate_documentation, generate_html, get_language, highlight, highlight_end, highlight_start, l, languages, parse, path, rocco, rocco_styles, rocco_template, showdown, sources, spawn, template, _ref; 3 | 4 | generate_documentation = function(source, callback) { 5 | return fs.readFile(source, "utf-8", function(error, code) { 6 | var sections; 7 | if (error) throw error; 8 | sections = parse(source, code); 9 | return highlight(source, sections, function() { 10 | generate_html(source, sections); 11 | return callback(); 12 | }); 13 | }); 14 | }; 15 | 16 | parse = function(source, code) { 17 | var code_text, docs_text, has_code, language, line, lines, save, sections, _i, _len; 18 | lines = code.split('\n'); 19 | sections = []; 20 | language = get_language(source); 21 | has_code = docs_text = code_text = ''; 22 | save = function(docs, code) { 23 | return sections.push({ 24 | docs_text: docs, 25 | code_text: code 26 | }); 27 | }; 28 | for (_i = 0, _len = lines.length; _i < _len; _i++) { 29 | line = lines[_i]; 30 | if (line.match(language.comment_matcher) && !line.match(language.comment_filter)) { 31 | if (has_code) { 32 | save(docs_text, code_text); 33 | has_code = docs_text = code_text = ''; 34 | } 35 | docs_text += line.replace(language.comment_matcher, '') + '\n'; 36 | } else { 37 | has_code = true; 38 | code_text += line + '\n'; 39 | } 40 | } 41 | save(docs_text, code_text); 42 | return sections; 43 | }; 44 | 45 | highlight = function(source, sections, callback) { 46 | var language, output, pygments, section; 47 | language = get_language(source); 48 | pygments = spawn('pygmentize', ['-l', language.name, '-f', 'html', '-O', 'encoding=utf-8,tabsize=2']); 49 | output = ''; 50 | pygments.stderr.addListener('data', function(error) { 51 | if (error) return console.error(error.toString()); 52 | }); 53 | pygments.stdin.addListener('error', function(error) { 54 | console.error("Could not use Pygments to highlight the source."); 55 | return process.exit(1); 56 | }); 57 | pygments.stdout.addListener('data', function(result) { 58 | if (result) return output += result; 59 | }); 60 | pygments.addListener('exit', function() { 61 | var fragments, i, section, _len; 62 | output = output.replace(highlight_start, '').replace(highlight_end, ''); 63 | fragments = output.split(language.divider_html); 64 | for (i = 0, _len = sections.length; i < _len; i++) { 65 | section = sections[i]; 66 | section.code_html = highlight_start + fragments[i] + highlight_end; 67 | section.docs_html = showdown.makeHtml(section.docs_text); 68 | } 69 | return callback(); 70 | }); 71 | if (pygments.stdin.writable) { 72 | pygments.stdin.write(((function() { 73 | var _i, _len, _results; 74 | _results = []; 75 | for (_i = 0, _len = sections.length; _i < _len; _i++) { 76 | section = sections[_i]; 77 | _results.push(section.code_text); 78 | } 79 | return _results; 80 | })()).join(language.divider_text)); 81 | return pygments.stdin.end(); 82 | } 83 | }; 84 | 85 | generate_html = function(source, sections) { 86 | var dest, html, title; 87 | title = path.basename(source); 88 | dest = destination(source); 89 | html = rocco_template({ 90 | header: configuration.header, 91 | subheader: configuration.subheader, 92 | title: title, 93 | sections: sections, 94 | sources: sources, 95 | path: path, 96 | destination: destination 97 | }); 98 | console.log("rocco: " + source + " -> " + dest); 99 | return fs.writeFile(dest, html); 100 | }; 101 | 102 | fs = require('fs'); 103 | 104 | path = require('path'); 105 | 106 | showdown = require('./../vendor/showdown').Showdown; 107 | 108 | _ref = require('child_process'), spawn = _ref.spawn, exec = _ref.exec; 109 | 110 | languages = { 111 | '.coffee': { 112 | name: 'coffee-script', 113 | symbol: '#' 114 | }, 115 | '.js': { 116 | name: 'javascript', 117 | symbol: '//' 118 | }, 119 | '.rb': { 120 | name: 'ruby', 121 | symbol: '#' 122 | }, 123 | '.py': { 124 | name: 'python', 125 | symbol: '#' 126 | } 127 | }; 128 | 129 | for (ext in languages) { 130 | l = languages[ext]; 131 | l.comment_matcher = new RegExp('^\\s*' + l.symbol + '\\s?'); 132 | l.comment_filter = new RegExp('(^#![/]|^\\s*#\\{)'); 133 | l.divider_text = '\n' + l.symbol + 'DIVIDER\n'; 134 | l.divider_html = new RegExp('\\n*' + l.symbol + 'DIVIDER<\\/span>\\n*'); 135 | } 136 | 137 | get_language = function(source) { 138 | return languages[path.extname(source)]; 139 | }; 140 | 141 | destination = function(filepath) { 142 | return configuration.output + '/' + path.basename(filepath, path.extname(filepath)) + '.html'; 143 | }; 144 | 145 | ensure_directory = function(dir, callback) { 146 | return exec("mkdir -p " + dir, function() { 147 | return callback(); 148 | }); 149 | }; 150 | 151 | template = function(str) { 152 | return new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};' + 'with(obj){p.push(\'' + str.replace(/[\r\t\n]/g, " ").replace(/'(?=[^<]*%>)/g, "\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g, "',$1,'").split('<%').join("');").split('%>').join("p.push('") + "');}return p.join('');"); 153 | }; 154 | 155 | rocco_template = template(fs.readFileSync(__dirname + '/../resources/rocco.jst').toString()); 156 | 157 | rocco_styles = fs.readFileSync(__dirname + '/../resources/rocco.css').toString(); 158 | 159 | highlight_start = '
';
160 | 
161 |   highlight_end = '
'; 162 | 163 | sources = []; 164 | 165 | configuration = {}; 166 | 167 | rocco = function(srcs, config) { 168 | var files, next_file; 169 | sources = srcs; 170 | configuration = config; 171 | fs.writeFile("" + configuration.output + "/rocco.css", rocco_styles); 172 | files = sources.slice(0); 173 | next_file = function() { 174 | if (files.length) return generate_documentation(files.shift(), next_file); 175 | }; 176 | return next_file(); 177 | }; 178 | 179 | module.exports = rocco; 180 | 181 | }).call(this); 182 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Paige.js Fork me on GitHub

Paige.js

The quickie-wiki Github project page generator

Source files:

  1. paige.coffee
  2. rocco.coffee

Paige.js - Painless page generation for Github code

2 | 3 |

Paige.js is a simple npm package for creating instant Github Pages without having to deal with any HTML or CSS coding. Paige let's you just focus on your code while providing a clean and presentable face for your project. The project page for Paige, itself, was generated with paige.js.

4 | 5 |

Paige is largely influenced by Docco and Underscore. The idea behind this project was to—in a sense—extend the wonderful functionality of Docco, and take it one step further as a more fully-featured project site. As such, Paige uses Docco for its source-file annotation, and its style is inspired heavily on the beauty and simplicity of the Underscore website. Not only that, but they are both direct dependencies of this project as well.

6 | 7 |

Paige.js is hosted on Github. Please feel free to mess around with it and let me know of any bugs you find. Also you can always reach me on twitter @rthauby.

8 | 9 |

Demo

10 | 11 |

You're either reading this at the project page itself, or on the Github repo site. If it's the latter, you can see the output of running paige against this very Readme file here. If it's the former, well, you're looking at it.

12 | 13 |

Installation Instructions

14 | 15 |

You'll be needing Node.js and Pygments. Underscore is included as a package dependency. To install Paige.js:

16 | 17 |

sudo npm install paige

18 | 19 |

... then you can just do:

20 | 21 |

paige path/to/config

22 | 23 |

... and go have a beer or two.

24 | 25 |

Configuration

26 | 27 |

Paige takes one argument only, and it's the location to a configuration file. Alternatively you can just skip the location of this file, and Paige will default to paige.config at the root of your project. The configuration file must be in JSON format.

28 | 29 |

A basic configuration file looks like this:

30 | 31 |
{
32 |   "title" :             "Paige.js",
33 |   "content_file" :      "README.mdown",
34 |   "include_index" :     true,
35 |   "docco_files" :       "src/*.coffee",
36 |   "header" :            "Paige.js",
37 |   "subheader" :         "The quickie-wiki Github project page generator",
38 |   "background" :        "diagonal-noise",
39 |   "github" :                  "https://github.com/rthauby/Paige"
40 | }
41 | 42 |

The properties here are:

43 | 44 | 54 | 55 |

Known Issues

56 | 57 | 61 | 62 |

License

63 | 64 |

This entire project is MIT licensed.

65 | 66 |

Change Log

67 | 68 |

0.1.7

69 | 70 | 74 | 75 |

0.1.5

76 | 77 |

Fixed - Correct process.ARGV. Should be process.argv. Many thanks to mitchellrj

78 | 79 |

0.1.4

80 | 81 |

Incorporated Docco within the Paige project itself for further customization and integration. For now, added custom output directory.

82 | 83 |

0.1.3

84 | 85 |

Fixed styling issues with headers and code/pre tags

86 | 87 |

0.1.2

88 | 89 |

Minor language tweaks and info.

90 | 91 |

0.1.0

92 | 93 |

Initial release of Paige.js

-------------------------------------------------------------------------------- /src/rocco.coffee: -------------------------------------------------------------------------------- 1 | #### Rocco 2 | 3 | # Rocco is a _minimal_ alteration on the original Docco, by [Jeremy Ashkenas](https://github.com/jashkenas). 4 | # Rocco was incorporated into the Paige project for ease of customization and extendability that are 5 | # beyond the scope of the original Docco project. 6 | 7 | # What follows is the original, unaltered documentation for Docco from Jeremy Ashkenas. 8 | 9 | #### Main Documentation Generation Functions 10 | 11 | # Generate the documentation for a source file by reading it in, splitting it 12 | # up into comment/code sections, highlighting them for the appropriate language, 13 | # and merging them into an HTML template. 14 | generate_documentation = (source, callback) -> 15 | fs.readFile source, "utf-8", (error, code) -> 16 | throw error if error 17 | sections = parse source, code 18 | highlight source, sections, -> 19 | generate_html source, sections 20 | callback() 21 | 22 | # Given a string of source code, parse out each comment and the code that 23 | # follows it, and create an individual **section** for it. 24 | # Sections take the form: 25 | # 26 | # { 27 | # docs_text: ... 28 | # docs_html: ... 29 | # code_text: ... 30 | # code_html: ... 31 | # } 32 | # 33 | parse = (source, code) -> 34 | lines = code.split '\n' 35 | sections = [] 36 | language = get_language source 37 | has_code = docs_text = code_text = '' 38 | 39 | save = (docs, code) -> 40 | sections.push docs_text: docs, code_text: code 41 | 42 | for line in lines 43 | if line.match(language.comment_matcher) and not line.match(language.comment_filter) 44 | if has_code 45 | save docs_text, code_text 46 | has_code = docs_text = code_text = '' 47 | docs_text += line.replace(language.comment_matcher, '') + '\n' 48 | else 49 | has_code = yes 50 | code_text += line + '\n' 51 | save docs_text, code_text 52 | sections 53 | 54 | # Highlights a single chunk of CoffeeScript code, using **Pygments** over stdio, 55 | # and runs the text of its corresponding comment through **Markdown**, using 56 | # [Showdown.js](http://attacklab.net/showdown/). 57 | # 58 | # We process the entire file in a single call to Pygments by inserting little 59 | # marker comments between each section and then splitting the result string 60 | # wherever our markers occur. 61 | highlight = (source, sections, callback) -> 62 | language = get_language source 63 | pygments = spawn 'pygmentize', ['-l', language.name, '-f', 'html', '-O', 'encoding=utf-8,tabsize=2'] 64 | output = '' 65 | 66 | pygments.stderr.addListener 'data', (error) -> 67 | console.error error.toString() if error 68 | 69 | pygments.stdin.addListener 'error', (error) -> 70 | console.error "Could not use Pygments to highlight the source." 71 | process.exit 1 72 | 73 | pygments.stdout.addListener 'data', (result) -> 74 | output += result if result 75 | 76 | pygments.addListener 'exit', -> 77 | output = output.replace(highlight_start, '').replace(highlight_end, '') 78 | fragments = output.split language.divider_html 79 | for section, i in sections 80 | section.code_html = highlight_start + fragments[i] + highlight_end 81 | section.docs_html = showdown.makeHtml section.docs_text 82 | callback() 83 | 84 | if pygments.stdin.writable 85 | pygments.stdin.write((section.code_text for section in sections).join(language.divider_text)) 86 | pygments.stdin.end() 87 | 88 | # Once all of the code is finished highlighting, we can generate the HTML file 89 | # and write out the documentation. Pass the completed sections into the template 90 | # found in `resources/rocco.jst` 91 | generate_html = (source, sections) -> 92 | title = path.basename source 93 | dest = destination source 94 | html = rocco_template { 95 | header: configuration.header, 96 | subheader: configuration.subheader, 97 | title: title, 98 | sections: sections, 99 | sources: sources, 100 | path: path, 101 | destination: destination 102 | } 103 | console.log "rocco: #{source} -> #{dest}" 104 | fs.writeFile dest, html 105 | 106 | #### Helpers & Setup 107 | 108 | # Require our external dependencies, including **Showdown.js** 109 | # (the JavaScript implementation of Markdown). 110 | fs = require 'fs' 111 | path = require 'path' 112 | showdown = require('./../vendor/showdown').Showdown 113 | {spawn, exec} = require 'child_process' 114 | 115 | # A list of the languages that rocco supports, mapping the file extension to 116 | # the name of the Pygments lexer and the symbol that indicates a comment. To 117 | # add another language to rocco's repertoire, add it here. 118 | languages = 119 | '.coffee': 120 | name: 'coffee-script', symbol: '#' 121 | '.js': 122 | name: 'javascript', symbol: '//' 123 | '.rb': 124 | name: 'ruby', symbol: '#' 125 | '.py': 126 | name: 'python', symbol: '#' 127 | 128 | # Build out the appropriate matchers and delimiters for each language. 129 | for ext, l of languages 130 | 131 | # Does the line begin with a comment? 132 | l.comment_matcher = new RegExp('^\\s*' + l.symbol + '\\s?') 133 | 134 | # Ignore [hashbangs](http://en.wikipedia.org/wiki/Shebang_(Unix)) 135 | # and interpolations... 136 | l.comment_filter = new RegExp('(^#![/]|^\\s*#\\{)') 137 | 138 | # The dividing token we feed into Pygments, to delimit the boundaries between 139 | # sections. 140 | l.divider_text = '\n' + l.symbol + 'DIVIDER\n' 141 | 142 | # The mirror of `divider_text` that we expect Pygments to return. We can split 143 | # on this to recover the original sections. 144 | # Note: the class is "c" for Python and "c1" for the other languages 145 | l.divider_html = new RegExp('\\n*' + l.symbol + 'DIVIDER<\\/span>\\n*') 146 | 147 | # Get the current language we're documenting, based on the extension. 148 | get_language = (source) -> languages[path.extname(source)] 149 | 150 | # Compute the destination HTML path for an input source file path. If the source 151 | # is `lib/example.coffee`, the HTML will be at `docs/example.html` 152 | destination = (filepath) -> 153 | configuration.output + '/' + path.basename(filepath, path.extname(filepath)) + '.html' 154 | 155 | # Ensure that the destination directory exists. 156 | ensure_directory = (dir, callback) -> 157 | exec "mkdir -p #{dir}", -> callback() 158 | 159 | # Micro-templating, originally by John Resig, borrowed by way of 160 | # [Underscore.js](http://documentcloud.github.com/underscore/). 161 | template = (str) -> 162 | new Function 'obj', 163 | 'var p=[],print=function(){p.push.apply(p,arguments);};' + 164 | 'with(obj){p.push(\'' + 165 | str.replace(/[\r\t\n]/g, " ") 166 | .replace(/'(?=[^<]*%>)/g,"\t") 167 | .split("'").join("\\'") 168 | .split("\t").join("'") 169 | .replace(/<%=(.+?)%>/g, "',$1,'") 170 | .split('<%').join("');") 171 | .split('%>').join("p.push('") + 172 | "');}return p.join('');" 173 | 174 | # Create the template that we will use to generate the rocco HTML page. 175 | rocco_template = template fs.readFileSync(__dirname + '/../resources/rocco.jst').toString() 176 | 177 | # The CSS styles we'd like to apply to the documentation. 178 | rocco_styles = fs.readFileSync(__dirname + '/../resources/rocco.css').toString() 179 | 180 | # The start of each Pygments highlight block. 181 | highlight_start = '
'
182 | 
183 | # The end of each Pygments highlight block.
184 | highlight_end   = '
' 185 | 186 | #### Modifications 187 | 188 | # Slight modification to the way rocco ran initially. 189 | # This gives us a little more freedom to experiment with 190 | # rocco's inputs and configurability. 191 | 192 | sources = [] 193 | configuration = {} 194 | 195 | rocco = (srcs, config) -> 196 | sources = srcs 197 | configuration = config 198 | fs.writeFile "#{configuration.output}/rocco.css", rocco_styles 199 | files = sources.slice(0) 200 | next_file = -> generate_documentation files.shift(), next_file if files.length 201 | next_file() 202 | 203 | module.exports = rocco -------------------------------------------------------------------------------- /docs/rocco.css: -------------------------------------------------------------------------------- 1 | #navbar { 2 | position: fixed; 3 | width: 960px; 4 | height: 34px; 5 | background: #fff; 6 | border-bottom: 1px solid #cdcdcd; 7 | border-right: 1px solid #cdcdcd; 8 | border-bottom-right-radius: 5px; 9 | -webkit-border-bottom-right-radius: 5px; 10 | -moz-border-bottom-right-radius: 5px; 11 | z-index: 9; 12 | } 13 | #navbar a, #navbar a:visited { 14 | font-weight: normal; 15 | padding: 0 2px; 16 | text-decoration: none; 17 | color: #3f6fa5; 18 | } 19 | #navbar a:active, #navbar a:hover { 20 | text-decoration: underline; 21 | } 22 | #navbar h3 { 23 | top: 0; 24 | margin: 0; 25 | padding: 4px 20px; 26 | } 27 | #navbar h3 a { 28 | float: left; 29 | display: block; 30 | padding-right: 20px; 31 | } 32 | #container { 33 | padding-top: 20px; 34 | } 35 | body { 36 | background: #fff url("bg.png"); 37 | } 38 | /*--------------------- Layout and Typography ----------------------------*/ 39 | body { 40 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 41 | font-size: 15px; 42 | line-height: 22px; 43 | color: #252519; 44 | margin: 0; padding: 0; 45 | } 46 | a { 47 | color: #261a3b; 48 | } 49 | a:visited { 50 | color: #261a3b; 51 | } 52 | p { 53 | margin: 0 0 15px 0; 54 | } 55 | h1, h2, h3, h4, h5, h6 { 56 | margin: 0px 0 15px 0; 57 | } 58 | h1 { 59 | margin-top: 40px; 60 | } 61 | #container { 62 | position: relative; 63 | } 64 | #background { 65 | position: fixed; 66 | top: 0; left: 525px; right: 0; bottom: 0; 67 | background: #f5f5ff; 68 | border-left: 1px solid #e5e5ee; 69 | z-index: -1; 70 | } 71 | #jump_to, #jump_page { 72 | background: white; 73 | border-bottom: 1px solid #cdcdcd; 74 | border-left: 1px solid #cdcdcd; 75 | border-bottom-left-radius: 5px; 76 | -webkit-border-bottom-left-radius: 5px; 77 | -moz-border-radius-bottomleft: 5px; 78 | cursor: pointer; 79 | text-align: right; 80 | } 81 | #jump_to, #jump_wrapper { 82 | position: fixed; 83 | right: 0; top: 0; 84 | padding: 5px 30px; 85 | } 86 | #jump_wrapper { 87 | padding: 0; 88 | display: none; 89 | } 90 | #jump_to:hover #jump_wrapper { 91 | display: block; 92 | } 93 | #jump_page { 94 | padding: 5px 0 3px; 95 | margin: 0 0 25px 25px; 96 | } 97 | #jump_page .source { 98 | display: block; 99 | padding: 5px 30px; 100 | text-decoration: none; 101 | border-top: 1px solid #eee; 102 | } 103 | #jump_page .source:hover { 104 | background: #f5f5ff; 105 | } 106 | #jump_page .source:first-child { 107 | } 108 | table td { 109 | border: 0; 110 | outline: 0; 111 | } 112 | td.docs, th.docs { 113 | max-width: 450px; 114 | min-width: 450px; 115 | min-height: 5px; 116 | padding: 10px 25px 1px 50px; 117 | overflow-x: hidden; 118 | vertical-align: top; 119 | text-align: left; 120 | } 121 | .docs pre { 122 | margin: 15px 0 15px; 123 | padding-left: 15px; 124 | } 125 | .docs p tt, .docs p code { 126 | background: #f8f8ff; 127 | border: 1px solid #dedede; 128 | font-size: 12px; 129 | padding: 0 0.2em; 130 | } 131 | .pilwrap { 132 | position: relative; 133 | } 134 | .pilcrow { 135 | font: 12px Arial; 136 | text-decoration: none; 137 | color: #454545; 138 | position: absolute; 139 | top: 3px; left: -20px; 140 | padding: 1px 2px; 141 | opacity: 0; 142 | -webkit-transition: opacity 0.2s linear; 143 | } 144 | td.docs:hover .pilcrow { 145 | opacity: 1; 146 | } 147 | td.code, th.code { 148 | padding: 14px 15px 16px 25px; 149 | width: 100%; 150 | vertical-align: top; 151 | background: #f5f5ff; 152 | border-left: 1px solid #e5e5ee; 153 | } 154 | pre, tt, code { 155 | font-size: 12px; line-height: 18px; 156 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 157 | margin: 0; padding: 0; 158 | } 159 | 160 | 161 | /*---------------------- Syntax Highlighting -----------------------------*/ 162 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 163 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 164 | body .hll { background-color: #ffffcc } 165 | body .c { color: #408080; font-style: italic } /* Comment */ 166 | body .err { border: 1px solid #FF0000 } /* Error */ 167 | body .k { color: #954121 } /* Keyword */ 168 | body .o { color: #666666 } /* Operator */ 169 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 170 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 171 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 172 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 173 | body .gd { color: #A00000 } /* Generic.Deleted */ 174 | body .ge { font-style: italic } /* Generic.Emph */ 175 | body .gr { color: #FF0000 } /* Generic.Error */ 176 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 177 | body .gi { color: #00A000 } /* Generic.Inserted */ 178 | body .go { color: #808080 } /* Generic.Output */ 179 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 180 | body .gs { font-weight: bold } /* Generic.Strong */ 181 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 182 | body .gt { color: #0040D0 } /* Generic.Traceback */ 183 | body .kc { color: #954121 } /* Keyword.Constant */ 184 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 185 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 186 | body .kp { color: #954121 } /* Keyword.Pseudo */ 187 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 188 | body .kt { color: #B00040 } /* Keyword.Type */ 189 | body .m { color: #666666 } /* Literal.Number */ 190 | body .s { color: #219161 } /* Literal.String */ 191 | body .na { color: #7D9029 } /* Name.Attribute */ 192 | body .nb { color: #954121 } /* Name.Builtin */ 193 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 194 | body .no { color: #880000 } /* Name.Constant */ 195 | body .nd { color: #AA22FF } /* Name.Decorator */ 196 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 197 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 198 | body .nf { color: #0000FF } /* Name.Function */ 199 | body .nl { color: #A0A000 } /* Name.Label */ 200 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 201 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 202 | body .nv { color: #19469D } /* Name.Variable */ 203 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 204 | body .w { color: #bbbbbb } /* Text.Whitespace */ 205 | body .mf { color: #666666 } /* Literal.Number.Float */ 206 | body .mh { color: #666666 } /* Literal.Number.Hex */ 207 | body .mi { color: #666666 } /* Literal.Number.Integer */ 208 | body .mo { color: #666666 } /* Literal.Number.Oct */ 209 | body .sb { color: #219161 } /* Literal.String.Backtick */ 210 | body .sc { color: #219161 } /* Literal.String.Char */ 211 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 212 | body .s2 { color: #219161 } /* Literal.String.Double */ 213 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 214 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 215 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 216 | body .sx { color: #954121 } /* Literal.String.Other */ 217 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 218 | body .s1 { color: #219161 } /* Literal.String.Single */ 219 | body .ss { color: #19469D } /* Literal.String.Symbol */ 220 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 221 | body .vc { color: #19469D } /* Name.Variable.Class */ 222 | body .vg { color: #19469D } /* Name.Variable.Global */ 223 | body .vi { color: #19469D } /* Name.Variable.Instance */ 224 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /resources/rocco.css: -------------------------------------------------------------------------------- 1 | #navbar { 2 | position: fixed; 3 | width: 960px; 4 | height: 34px; 5 | background: #fff; 6 | border-bottom: 1px solid #cdcdcd; 7 | border-right: 1px solid #cdcdcd; 8 | border-bottom-right-radius: 5px; 9 | -webkit-border-bottom-right-radius: 5px; 10 | -moz-border-bottom-right-radius: 5px; 11 | z-index: 9; 12 | } 13 | #navbar a, #navbar a:visited { 14 | font-weight: normal; 15 | padding: 0 2px; 16 | text-decoration: none; 17 | color: #3f6fa5; 18 | } 19 | #navbar a:active, #navbar a:hover { 20 | text-decoration: underline; 21 | } 22 | #navbar h3 { 23 | top: 0; 24 | margin: 0; 25 | padding: 4px 20px; 26 | } 27 | #navbar h3 a { 28 | float: left; 29 | display: block; 30 | padding-right: 20px; 31 | } 32 | #container { 33 | padding-top: 20px; 34 | } 35 | body { 36 | background: #fff url("bg.png"); 37 | } 38 | /*--------------------- Layout and Typography ----------------------------*/ 39 | body { 40 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 41 | font-size: 15px; 42 | line-height: 22px; 43 | color: #252519; 44 | margin: 0; padding: 0; 45 | } 46 | a { 47 | color: #261a3b; 48 | } 49 | a:visited { 50 | color: #261a3b; 51 | } 52 | p { 53 | margin: 0 0 15px 0; 54 | } 55 | h1, h2, h3, h4, h5, h6 { 56 | margin: 0px 0 15px 0; 57 | } 58 | h1 { 59 | margin-top: 40px; 60 | } 61 | #container { 62 | position: relative; 63 | } 64 | #background { 65 | position: fixed; 66 | top: 0; left: 525px; right: 0; bottom: 0; 67 | background: #f5f5ff; 68 | border-left: 1px solid #e5e5ee; 69 | z-index: -1; 70 | } 71 | #jump_to, #jump_page { 72 | background: white; 73 | border-bottom: 1px solid #cdcdcd; 74 | border-left: 1px solid #cdcdcd; 75 | border-bottom-left-radius: 5px; 76 | -webkit-border-bottom-left-radius: 5px; 77 | -moz-border-radius-bottomleft: 5px; 78 | cursor: pointer; 79 | text-align: right; 80 | } 81 | #jump_to, #jump_wrapper { 82 | position: fixed; 83 | right: 0; top: 0; 84 | padding: 5px 30px; 85 | } 86 | #jump_wrapper { 87 | padding: 0; 88 | display: none; 89 | } 90 | #jump_to:hover #jump_wrapper { 91 | display: block; 92 | } 93 | #jump_page { 94 | padding: 5px 0 3px; 95 | margin: 0 0 25px 25px; 96 | } 97 | #jump_page .source { 98 | display: block; 99 | padding: 5px 30px; 100 | text-decoration: none; 101 | border-top: 1px solid #eee; 102 | } 103 | #jump_page .source:hover { 104 | background: #f5f5ff; 105 | } 106 | #jump_page .source:first-child { 107 | } 108 | table td { 109 | border: 0; 110 | outline: 0; 111 | } 112 | td.docs, th.docs { 113 | max-width: 450px; 114 | min-width: 450px; 115 | min-height: 5px; 116 | padding: 10px 25px 1px 50px; 117 | overflow-x: hidden; 118 | vertical-align: top; 119 | text-align: left; 120 | } 121 | .docs pre { 122 | margin: 15px 0 15px; 123 | padding-left: 15px; 124 | } 125 | .docs p tt, .docs p code { 126 | background: #f8f8ff; 127 | border: 1px solid #dedede; 128 | font-size: 12px; 129 | padding: 0 0.2em; 130 | } 131 | .pilwrap { 132 | position: relative; 133 | } 134 | .pilcrow { 135 | font: 12px Arial; 136 | text-decoration: none; 137 | color: #454545; 138 | position: absolute; 139 | top: 3px; left: -20px; 140 | padding: 1px 2px; 141 | opacity: 0; 142 | -webkit-transition: opacity 0.2s linear; 143 | } 144 | td.docs:hover .pilcrow { 145 | opacity: 1; 146 | } 147 | td.code, th.code { 148 | padding: 14px 15px 16px 25px; 149 | width: 100%; 150 | vertical-align: top; 151 | background: #f5f5ff; 152 | border-left: 1px solid #e5e5ee; 153 | } 154 | pre, tt, code { 155 | font-size: 12px; line-height: 18px; 156 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 157 | margin: 0; padding: 0; 158 | } 159 | 160 | 161 | /*---------------------- Syntax Highlighting -----------------------------*/ 162 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 163 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 164 | body .hll { background-color: #ffffcc } 165 | body .c { color: #408080; font-style: italic } /* Comment */ 166 | body .err { border: 1px solid #FF0000 } /* Error */ 167 | body .k { color: #954121 } /* Keyword */ 168 | body .o { color: #666666 } /* Operator */ 169 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 170 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 171 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 172 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 173 | body .gd { color: #A00000 } /* Generic.Deleted */ 174 | body .ge { font-style: italic } /* Generic.Emph */ 175 | body .gr { color: #FF0000 } /* Generic.Error */ 176 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 177 | body .gi { color: #00A000 } /* Generic.Inserted */ 178 | body .go { color: #808080 } /* Generic.Output */ 179 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 180 | body .gs { font-weight: bold } /* Generic.Strong */ 181 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 182 | body .gt { color: #0040D0 } /* Generic.Traceback */ 183 | body .kc { color: #954121 } /* Keyword.Constant */ 184 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 185 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 186 | body .kp { color: #954121 } /* Keyword.Pseudo */ 187 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 188 | body .kt { color: #B00040 } /* Keyword.Type */ 189 | body .m { color: #666666 } /* Literal.Number */ 190 | body .s { color: #219161 } /* Literal.String */ 191 | body .na { color: #7D9029 } /* Name.Attribute */ 192 | body .nb { color: #954121 } /* Name.Builtin */ 193 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 194 | body .no { color: #880000 } /* Name.Constant */ 195 | body .nd { color: #AA22FF } /* Name.Decorator */ 196 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 197 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 198 | body .nf { color: #0000FF } /* Name.Function */ 199 | body .nl { color: #A0A000 } /* Name.Label */ 200 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 201 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 202 | body .nv { color: #19469D } /* Name.Variable */ 203 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 204 | body .w { color: #bbbbbb } /* Text.Whitespace */ 205 | body .mf { color: #666666 } /* Literal.Number.Float */ 206 | body .mh { color: #666666 } /* Literal.Number.Hex */ 207 | body .mi { color: #666666 } /* Literal.Number.Integer */ 208 | body .mo { color: #666666 } /* Literal.Number.Oct */ 209 | body .sb { color: #219161 } /* Literal.String.Backtick */ 210 | body .sc { color: #219161 } /* Literal.String.Char */ 211 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 212 | body .s2 { color: #219161 } /* Literal.String.Double */ 213 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 214 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 215 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 216 | body .sx { color: #954121 } /* Literal.String.Other */ 217 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 218 | body .s1 { color: #219161 } /* Literal.String.Single */ 219 | body .ss { color: #19469D } /* Literal.String.Symbol */ 220 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 221 | body .vc { color: #19469D } /* Name.Variable.Class */ 222 | body .vg { color: #19469D } /* Name.Variable.Global */ 223 | body .vi { color: #19469D } /* Name.Variable.Instance */ 224 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/paige.html: -------------------------------------------------------------------------------- 1 | paige.coffee

paige.coffee

Require our external dependencies, including Showdown.js 2 | (the JavaScript implementation of Markdown).

_ =               require "underscore"
  3 | fs =              require 'fs'
  4 | path =            require 'path'
  5 | showdown =        require('./../vendor/showdown').Showdown
  6 | {spawn, exec} =   require 'child_process'
  7 | events      =     require('events')
  8 | rocco =           require './rocco.js'
  9 | 
 10 | subfiles = []
 11 | configuration = {
 12 |   "title" :             "Untitled",
 13 |   "content_file" :      "README.mdown",
 14 |   "include_index" :     false,
 15 |   "docco_files" :       null,
 16 |   "header" :            "Untitled",
 17 |   "subheader" :         "Untitled",
 18 |   "background" :        "bright_squares",
 19 |   "output" :            "docs"
 20 | }

Read our configuration file.

read_config = (callback) ->
 21 |   filename = "paige.config"
 22 |   filename = process.argv[2] if process.argv[2]?
 23 |   fs.readFile filename, "utf-8", (error, data) ->
 24 |     if error
 25 |       console.log "\nCould not find a configuration file. (default: ./paige.config)"
 26 |       console.log "Create and specify a configuration file. Example:\n\n"
 27 |       console.log config_template + "\n"
 28 |     else
 29 |       config = JSON.parse(data)
 30 |       process_config(config)
 31 |       callback(config) if callback

Process the configuration file

process_config = (config={}) ->
 32 |   _.map config, (value, key, list) ->
 33 |     configuration[key] = value if config[key]?

Ensure that the destination directory exists.

ensure_directory = (dir, callback) ->
 34 |   exec "mkdir -p #{dir}", -> callback()

...

copy_image = ->
 35 |   desired_image = fs.readFileSync(__dirname + "/../resources/#{configuration.background}.png")
 36 |   fs.writeFile "#{configuration.output}/bg.png", desired_image

Build the main html file by reading the source Markdown file, and if necessary 37 | collecting all the filenames of our source. We will then use these names to construct the 38 | index that's shown at the top of the page. 39 | We pass the source Markdown file to Showdown, get the result, then pipe it into 40 | our templating function described above.

process_html_file = ->
 41 |   source = configuration.content_file
 42 |   subfiles_names = clean_file_extension(subfiles) if configuration.include_index
 43 |   clean_subfiles = clean_path_names(subfiles) if configuration.include_index
 44 |   fs.readFile source, "utf-8", (error, code) ->
 45 |     if error
 46 |       console.log "\nThere was a problem reading your the content file: #{source}"
 47 |       throw error
 48 |     else

replace ``` block with

 tags

      code = code.replace /```\w*\n?([^```]*)```/gm, '<pre>\n$1</pre>'
 49 |     
 50 |       content_html = showdown.makeHtml code
 51 |       html = mdown_template {
 52 |         content_html:     content_html,
 53 |         title:            configuration.title,
 54 |         header:           configuration.header,
 55 |         subheader:        configuration.subheader,
 56 |         github:           configuration.github,
 57 |         include_index:    configuration.include_index,
 58 |         subfiles:         clean_subfiles,
 59 |         subfiles_names:   subfiles_names
 60 |       }
 61 |       console.log "paige: #{source} -> #{configuration.output}/index.html"
 62 |       fs.writeFile "#{configuration.output}/index.html", html

Micro-templating, originally by John Resig, borrowed by way of 63 | Underscore.js.

template = (str) ->
 64 |   new Function 'obj',
 65 |     'var p=[],print=function(){p.push.apply(p,arguments);};' +
 66 |     'with(obj){p.push(\'' +
 67 |     str.replace(/[\r\t\n]/g, " ")
 68 |        .replace(/'(?=[^<]*%>)/g,"\t")
 69 |        .split("'").join("\\'")
 70 |        .split("\t").join("'")
 71 |        .replace(/<%=(.+?)%>/g, "',$1,'")
 72 |        .split('<%').join("');")
 73 |        .split('%>').join("p.push('") +
 74 |        "');}return p.join('');"

Kind of hacky, but I can't figure out another way of doing this cleanly. 75 | Will list all the files that will be used as your source file for passing onto rocco.

get_subfiles = (callback) =>
 76 |   count = 0
 77 |   find_files = (file, total) =>
 78 |     f_path = file.substr(0,file.lastIndexOf('/')+1)
 79 |     f_file = file.substr(file.lastIndexOf('/')+1)
 80 |     exec "find ./#{f_path} -name '#{f_file}' -print", (error, stdout, stderr) ->
 81 |       count++
 82 |       subfiles = _.uniq(_.union(subfiles, stdout.trim().split("\n")))
 83 |       if count >= total
 84 |         callback() if callback
 85 | 
 86 |   if _.isArray(configuration.docco_files)
 87 |     _.each configuration.docco_files, (file) ->
 88 |       find_files(file,configuration.docco_files.length)
 89 |   else if _.isString(configuration.docco_files)
 90 |     find_files(configuration.docco_files,1)

Remove trailing path names from each file from a list

clean_path_names = (names) ->
 91 |   clean_names = []
 92 |   _.each names, (name) ->
 93 |     clean_names.push name.substr(name.lastIndexOf('/')+1) || name
 94 |   return clean_names

Remove file extensions from each file from a list

clean_file_extension = (names) ->
 95 |   clean_names = []
 96 |   _.each names, (name) ->
 97 |     clean_names.push name.substr(0,name.lastIndexOf('.')).substr(name.lastIndexOf('/')+1) || name
 98 |   return clean_names

Process the rocco files and wrappers if needed.

check_for_rocco = ->
 99 |   if configuration.docco_files?
100 |     rocco(subfiles, configuration)

Some necessary files

mdown_template =    template fs.readFileSync(__dirname + '/../resources/paige.jst').toString()
101 | config_template =   fs.readFileSync(__dirname + '/../resources/paige.config').toString()

Run the script

read_config (config) ->
102 |   ensure_directory configuration.output, ->
103 |     get_subfiles ->
104 |       copy_image()
105 |       process_html_file()
106 |       check_for_rocco()
107 | 
108 | 
-------------------------------------------------------------------------------- /docs/rocco.html: -------------------------------------------------------------------------------- 1 | rocco.coffee

rocco.coffee

Rocco

Rocco is a minimal alteration on the original Docco, by Jeremy Ashkenas. 2 | Rocco was incorporated into the Paige project for ease of customization and extendability that are 3 | beyond the scope of the original Docco project.

What follows is the original, unaltered documentation for Docco from Jeremy Ashkenas.

Main Documentation Generation Functions

Generate the documentation for a source file by reading it in, splitting it 4 | up into comment/code sections, highlighting them for the appropriate language, 5 | and merging them into an HTML template.

generate_documentation = (source, callback) ->
  6 |   fs.readFile source, "utf-8", (error, code) ->
  7 |     throw error if error
  8 |     sections = parse source, code
  9 |     highlight source, sections, ->
 10 |       generate_html source, sections
 11 |       callback()

Given a string of source code, parse out each comment and the code that 12 | follows it, and create an individual section for it. 13 | Sections take the form:

14 | 15 |
{
 16 |   docs_text: ...
 17 |   docs_html: ...
 18 |   code_text: ...
 19 |   code_html: ...
 20 | }
parse = (source, code) ->
 21 |   lines    = code.split '\n'
 22 |   sections = []
 23 |   language = get_language source
 24 |   has_code = docs_text = code_text = ''
 25 | 
 26 |   save = (docs, code) ->
 27 |     sections.push docs_text: docs, code_text: code
 28 | 
 29 |   for line in lines
 30 |     if line.match(language.comment_matcher) and not line.match(language.comment_filter)
 31 |       if has_code
 32 |         save docs_text, code_text
 33 |         has_code = docs_text = code_text = ''
 34 |       docs_text += line.replace(language.comment_matcher, '') + '\n'
 35 |     else
 36 |       has_code = yes
 37 |       code_text += line + '\n'
 38 |   save docs_text, code_text
 39 |   sections

Highlights a single chunk of CoffeeScript code, using Pygments over stdio, 40 | and runs the text of its corresponding comment through Markdown, using 41 | Showdown.js.

42 | 43 |

We process the entire file in a single call to Pygments by inserting little 44 | marker comments between each section and then splitting the result string 45 | wherever our markers occur.

highlight = (source, sections, callback) ->
 46 |   language = get_language source
 47 |   pygments = spawn 'pygmentize', ['-l', language.name, '-f', 'html', '-O', 'encoding=utf-8,tabsize=2']
 48 |   output   = ''
 49 | 
 50 |   pygments.stderr.addListener 'data',  (error)  ->
 51 |     console.error error.toString() if error
 52 | 
 53 |   pygments.stdin.addListener 'error',  (error)  ->
 54 |     console.error "Could not use Pygments to highlight the source."
 55 |     process.exit 1
 56 | 
 57 |   pygments.stdout.addListener 'data', (result) ->
 58 |     output += result if result
 59 | 
 60 |   pygments.addListener 'exit', ->
 61 |     output = output.replace(highlight_start, '').replace(highlight_end, '')
 62 |     fragments = output.split language.divider_html
 63 |     for section, i in sections
 64 |       section.code_html = highlight_start + fragments[i] + highlight_end
 65 |       section.docs_html = showdown.makeHtml section.docs_text
 66 |     callback()
 67 | 
 68 |   if pygments.stdin.writable
 69 |     pygments.stdin.write((section.code_text for section in sections).join(language.divider_text))
 70 |     pygments.stdin.end()

Once all of the code is finished highlighting, we can generate the HTML file 71 | and write out the documentation. Pass the completed sections into the template 72 | found in resources/rocco.jst

generate_html = (source, sections) ->
 73 |   title = path.basename source
 74 |   dest  = destination source
 75 |   html  = rocco_template {
 76 |     header: configuration.header, 
 77 |     subheader: configuration.subheader, 
 78 |     title: title, 
 79 |     sections: sections, 
 80 |     sources: sources, 
 81 |     path: path, 
 82 |     destination: destination
 83 |   }
 84 |   console.log "rocco: #{source} -> #{dest}"
 85 |   fs.writeFile dest, html

Helpers & Setup

Require our external dependencies, including Showdown.js 86 | (the JavaScript implementation of Markdown).

fs       = require 'fs'
 87 | path     = require 'path'
 88 | showdown = require('./../vendor/showdown').Showdown
 89 | {spawn, exec} = require 'child_process'

A list of the languages that rocco supports, mapping the file extension to 90 | the name of the Pygments lexer and the symbol that indicates a comment. To 91 | add another language to rocco's repertoire, add it here.

languages =
 92 |   '.coffee':
 93 |     name: 'coffee-script', symbol: '#'
 94 |   '.js':
 95 |     name: 'javascript', symbol: '//'
 96 |   '.rb':
 97 |     name: 'ruby', symbol: '#'
 98 |   '.py':
 99 |     name: 'python', symbol: '#'

Build out the appropriate matchers and delimiters for each language.

for ext, l of languages

Does the line begin with a comment?

  l.comment_matcher = new RegExp('^\\s*' + l.symbol + '\\s?')

Ignore hashbangs) 100 | and interpolations...

  l.comment_filter = new RegExp('(^#![/]|^\\s*#\\{)')

The dividing token we feed into Pygments, to delimit the boundaries between 101 | sections.

  l.divider_text = '\n' + l.symbol + 'DIVIDER\n'

The mirror of divider_text that we expect Pygments to return. We can split 102 | on this to recover the original sections. 103 | Note: the class is "c" for Python and "c1" for the other languages

  l.divider_html = new RegExp('\\n*<span class="c1?">' + l.symbol + 'DIVIDER<\\/span>\\n*')

Get the current language we're documenting, based on the extension.

get_language = (source) -> languages[path.extname(source)]

Compute the destination HTML path for an input source file path. If the source 104 | is lib/example.coffee, the HTML will be at docs/example.html

destination = (filepath) ->
105 |   configuration.output + '/' + path.basename(filepath, path.extname(filepath)) + '.html'

Ensure that the destination directory exists.

ensure_directory = (dir, callback) ->
106 |   exec "mkdir -p #{dir}", -> callback()

Micro-templating, originally by John Resig, borrowed by way of 107 | Underscore.js.

template = (str) ->
108 |   new Function 'obj',
109 |     'var p=[],print=function(){p.push.apply(p,arguments);};' +
110 |     'with(obj){p.push(\'' +
111 |     str.replace(/[\r\t\n]/g, " ")
112 |        .replace(/'(?=[^<]*%>)/g,"\t")
113 |        .split("'").join("\\'")
114 |        .split("\t").join("'")
115 |        .replace(/<%=(.+?)%>/g, "',$1,'")
116 |        .split('<%').join("');")
117 |        .split('%>').join("p.push('") +
118 |        "');}return p.join('');"

Create the template that we will use to generate the rocco HTML page.

rocco_template  = template fs.readFileSync(__dirname + '/../resources/rocco.jst').toString()

The CSS styles we'd like to apply to the documentation.

rocco_styles    = fs.readFileSync(__dirname + '/../resources/rocco.css').toString()

The start of each Pygments highlight block.

highlight_start = '<div class="highlight"><pre>'

The end of each Pygments highlight block.

highlight_end   = '</pre></div>'

Modifications

Slight modification to the way rocco ran initially. 119 | This gives us a little more freedom to experiment with 120 | rocco's inputs and configurability.

sources = []
121 | configuration = {}
122 | 
123 | rocco = (srcs, config) ->
124 |   sources = srcs
125 |   configuration = config
126 |   fs.writeFile "#{configuration.output}/rocco.css", rocco_styles
127 |   files = sources.slice(0)
128 |   next_file = -> generate_documentation files.shift(), next_file if files.length
129 |   next_file()
130 |   
131 | module.exports = rocco
132 | 
133 | 
-------------------------------------------------------------------------------- /vendor/showdown.js: -------------------------------------------------------------------------------- 1 | // 2 | // showdown.js -- A javascript port of Markdown. 3 | // 4 | // Copyright (c) 2007 John Fraser. 5 | // 6 | // Original Markdown Copyright (c) 2004-2005 John Gruber 7 | // 8 | // 9 | // Redistributable under a BSD-style open source license. 10 | // See license.txt for more information. 11 | // 12 | // The full source distribution is at: 13 | // 14 | // A A L 15 | // T C A 16 | // T K B 17 | // 18 | // 19 | // 20 | 21 | // 22 | // Wherever possible, Showdown is a straight, line-by-line port 23 | // of the Perl version of Markdown. 24 | // 25 | // This is not a normal parser design; it's basically just a 26 | // series of string substitutions. It's hard to read and 27 | // maintain this way, but keeping Showdown close to the original 28 | // design makes it easier to port new features. 29 | // 30 | // More importantly, Showdown behaves like markdown.pl in most 31 | // edge cases. So web applications can do client-side preview 32 | // in Javascript, and then build identical HTML on the server. 33 | // 34 | // This port needs the new RegExp functionality of ECMA 262, 35 | // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers 36 | // should do fine. Even with the new regular expression features, 37 | // We do a lot of work to emulate Perl's regex functionality. 38 | // The tricky changes in this file mostly have the "attacklab:" 39 | // label. Major or self-explanatory changes don't. 40 | // 41 | // Smart diff tools like Araxis Merge will be able to match up 42 | // this file with markdown.pl in a useful way. A little tweaking 43 | // helps: in a copy of markdown.pl, replace "#" with "//" and 44 | // replace "$text" with "text". Be sure to ignore whitespace 45 | // and line endings. 46 | // 47 | 48 | 49 | // 50 | // Showdown usage: 51 | // 52 | // var text = "Markdown *rocks*."; 53 | // 54 | // var converter = new Showdown.converter(); 55 | // var html = converter.makeHtml(text); 56 | // 57 | // alert(html); 58 | // 59 | // Note: move the sample code to the bottom of this 60 | // file before uncommenting it. 61 | // 62 | 63 | 64 | // 65 | // Showdown namespace 66 | // 67 | var Showdown = {}; 68 | 69 | // 70 | // converter 71 | // 72 | // Wraps all "globals" so that the only thing 73 | // exposed is makeHtml(). 74 | // 75 | Showdown.converter = function() { 76 | 77 | // 78 | // Globals: 79 | // 80 | 81 | // Global hashes, used by various utility routines 82 | var g_urls; 83 | var g_titles; 84 | var g_html_blocks; 85 | 86 | // Used to track when we're inside an ordered or unordered list 87 | // (see _ProcessListItems() for details): 88 | var g_list_level = 0; 89 | 90 | 91 | this.makeHtml = function(text) { 92 | // 93 | // Main function. The order in which other subs are called here is 94 | // essential. Link and image substitutions need to happen before 95 | // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the 96 | // and tags get encoded. 97 | // 98 | 99 | // Clear the global hashes. If we don't clear these, you get conflicts 100 | // from other articles when generating a page which contains more than 101 | // one article (e.g. an index page that shows the N most recent 102 | // articles): 103 | g_urls = new Array(); 104 | g_titles = new Array(); 105 | g_html_blocks = new Array(); 106 | 107 | // attacklab: Replace ~ with ~T 108 | // This lets us use tilde as an escape char to avoid md5 hashes 109 | // The choice of character is arbitray; anything that isn't 110 | // magic in Markdown will work. 111 | text = text.replace(/~/g,"~T"); 112 | 113 | // attacklab: Replace $ with ~D 114 | // RegExp interprets $ as a special character 115 | // when it's in a replacement string 116 | text = text.replace(/\$/g,"~D"); 117 | 118 | // Standardize line endings 119 | text = text.replace(/\r\n/g,"\n"); // DOS to Unix 120 | text = text.replace(/\r/g,"\n"); // Mac to Unix 121 | 122 | // Make sure text begins and ends with a couple of newlines: 123 | text = "\n\n" + text + "\n\n"; 124 | 125 | // Convert all tabs to spaces. 126 | text = _Detab(text); 127 | 128 | // Strip any lines consisting only of spaces and tabs. 129 | // This makes subsequent regexen easier to write, because we can 130 | // match consecutive blank lines with /\n+/ instead of something 131 | // contorted like /[ \t]*\n+/ . 132 | text = text.replace(/^[ \t]+$/mg,""); 133 | 134 | // Turn block-level HTML blocks into hash entries 135 | text = _HashHTMLBlocks(text); 136 | 137 | // Strip link definitions, store in hashes. 138 | text = _StripLinkDefinitions(text); 139 | 140 | text = _RunBlockGamut(text); 141 | 142 | text = _UnescapeSpecialChars(text); 143 | 144 | // attacklab: Restore dollar signs 145 | text = text.replace(/~D/g,"$$"); 146 | 147 | // attacklab: Restore tildes 148 | text = text.replace(/~T/g,"~"); 149 | 150 | return text; 151 | } 152 | 153 | 154 | var _StripLinkDefinitions = function(text) { 155 | // 156 | // Strips link definitions from text, stores the URLs and titles in 157 | // hash references. 158 | // 159 | 160 | // Link defs are in the form: ^[id]: url "optional title" 161 | 162 | /* 163 | var text = text.replace(/ 164 | ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 165 | [ \t]* 166 | \n? // maybe *one* newline 167 | [ \t]* 168 | ? // url = $2 169 | [ \t]* 170 | \n? // maybe one newline 171 | [ \t]* 172 | (?: 173 | (\n*) // any lines skipped = $3 attacklab: lookbehind removed 174 | ["(] 175 | (.+?) // title = $4 176 | [")] 177 | [ \t]* 178 | )? // title is optional 179 | (?:\n+|$) 180 | /gm, 181 | function(){...}); 182 | */ 183 | var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm, 184 | function (wholeMatch,m1,m2,m3,m4) { 185 | m1 = m1.toLowerCase(); 186 | g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive 187 | if (m3) { 188 | // Oops, found blank lines, so it's not a title. 189 | // Put back the parenthetical statement we stole. 190 | return m3+m4; 191 | } else if (m4) { 192 | g_titles[m1] = m4.replace(/"/g,"""); 193 | } 194 | 195 | // Completely remove the definition from the text 196 | return ""; 197 | } 198 | ); 199 | 200 | return text; 201 | } 202 | 203 | 204 | var _HashHTMLBlocks = function(text) { 205 | // attacklab: Double up blank lines to reduce lookaround 206 | text = text.replace(/\n/g,"\n\n"); 207 | 208 | // Hashify HTML blocks: 209 | // We only want to do this for block-level HTML tags, such as headers, 210 | // lists, and tables. That's because we still want to wrap

s around 211 | // "paragraphs" that are wrapped in non-block-level tags, such as anchors, 212 | // phrase emphasis, and spans. The list of tags we're looking for is 213 | // hard-coded: 214 | var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" 215 | var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" 216 | 217 | // First, look for nested blocks, e.g.: 218 | //

219 | //
220 | // tags for inner block must be indented. 221 | //
222 | //
223 | // 224 | // The outermost tags must start at the left margin for this to match, and 225 | // the inner nested divs must be indented. 226 | // We need to do this before the next, more liberal match, because the next 227 | // match will start at the first `
` and stop at the first `
`. 228 | 229 | // attacklab: This regex can be expensive when it fails. 230 | /* 231 | var text = text.replace(/ 232 | ( // save in $1 233 | ^ // start of line (with /m) 234 | <($block_tags_a) // start tag = $2 235 | \b // word break 236 | // attacklab: hack around khtml/pcre bug... 237 | [^\r]*?\n // any number of lines, minimally matching 238 | // the matching end tag 239 | [ \t]* // trailing spaces/tabs 240 | (?=\n+) // followed by a newline 241 | ) // attacklab: there are sentinel newlines at end of document 242 | /gm,function(){...}}; 243 | */ 244 | text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); 245 | 246 | // 247 | // Now match more liberally, simply from `\n` to `\n` 248 | // 249 | 250 | /* 251 | var text = text.replace(/ 252 | ( // save in $1 253 | ^ // start of line (with /m) 254 | <($block_tags_b) // start tag = $2 255 | \b // word break 256 | // attacklab: hack around khtml/pcre bug... 257 | [^\r]*? // any number of lines, minimally matching 258 | .* // the matching end tag 259 | [ \t]* // trailing spaces/tabs 260 | (?=\n+) // followed by a newline 261 | ) // attacklab: there are sentinel newlines at end of document 262 | /gm,function(){...}}; 263 | */ 264 | text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); 265 | 266 | // Special case just for
. It was easier to make a special case than 267 | // to make the other regex more complicated. 268 | 269 | /* 270 | text = text.replace(/ 271 | ( // save in $1 272 | \n\n // Starting after a blank line 273 | [ ]{0,3} 274 | (<(hr) // start tag = $2 275 | \b // word break 276 | ([^<>])*? // 277 | \/?>) // the matching end tag 278 | [ \t]* 279 | (?=\n{2,}) // followed by a blank line 280 | ) 281 | /g,hashElement); 282 | */ 283 | text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); 284 | 285 | // Special case for standalone HTML comments: 286 | 287 | /* 288 | text = text.replace(/ 289 | ( // save in $1 290 | \n\n // Starting after a blank line 291 | [ ]{0,3} // attacklab: g_tab_width - 1 292 | 295 | [ \t]* 296 | (?=\n{2,}) // followed by a blank line 297 | ) 298 | /g,hashElement); 299 | */ 300 | text = text.replace(/(\n\n[ ]{0,3}[ \t]*(?=\n{2,}))/g,hashElement); 301 | 302 | // PHP and ASP-style processor instructions ( and <%...%>) 303 | 304 | /* 305 | text = text.replace(/ 306 | (?: 307 | \n\n // Starting after a blank line 308 | ) 309 | ( // save in $1 310 | [ ]{0,3} // attacklab: g_tab_width - 1 311 | (?: 312 | <([?%]) // $2 313 | [^\r]*? 314 | \2> 315 | ) 316 | [ \t]* 317 | (?=\n{2,}) // followed by a blank line 318 | ) 319 | /g,hashElement); 320 | */ 321 | text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); 322 | 323 | // attacklab: Undo double lines (see comment at top of this function) 324 | text = text.replace(/\n\n/g,"\n"); 325 | return text; 326 | } 327 | 328 | var hashElement = function(wholeMatch,m1) { 329 | var blockText = m1; 330 | 331 | // Undo double lines 332 | blockText = blockText.replace(/\n\n/g,"\n"); 333 | blockText = blockText.replace(/^\n/,""); 334 | 335 | // strip trailing blank lines 336 | blockText = blockText.replace(/\n+$/g,""); 337 | 338 | // Replace the element text with a marker ("~KxK" where x is its key) 339 | blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; 340 | 341 | return blockText; 342 | }; 343 | 344 | var _RunBlockGamut = function(text) { 345 | // 346 | // These are all the transformations that form block-level 347 | // tags like paragraphs, headers, and list items. 348 | // 349 | text = _DoHeaders(text); 350 | 351 | // Do Horizontal Rules: 352 | var key = hashBlock("
"); 353 | text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); 354 | text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); 355 | text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); 356 | 357 | text = _DoLists(text); 358 | text = _DoCodeBlocks(text); 359 | text = _DoBlockQuotes(text); 360 | 361 | // We already ran _HashHTMLBlocks() before, in Markdown(), but that 362 | // was to escape raw HTML in the original Markdown source. This time, 363 | // we're escaping the markup we've just created, so that we don't wrap 364 | //

tags around block-level tags. 365 | text = _HashHTMLBlocks(text); 366 | text = _FormParagraphs(text); 367 | 368 | return text; 369 | } 370 | 371 | 372 | var _RunSpanGamut = function(text) { 373 | // 374 | // These are all the transformations that occur *within* block-level 375 | // tags like paragraphs, headers, and list items. 376 | // 377 | 378 | text = _DoCodeSpans(text); 379 | text = _EscapeSpecialCharsWithinTagAttributes(text); 380 | text = _EncodeBackslashEscapes(text); 381 | 382 | // Process anchor and image tags. Images must come first, 383 | // because ![foo][f] looks like an anchor. 384 | text = _DoImages(text); 385 | text = _DoAnchors(text); 386 | 387 | // Make links out of things like `` 388 | // Must come after _DoAnchors(), because you can use < and > 389 | // delimiters in inline links like [this](). 390 | text = _DoAutoLinks(text); 391 | text = _EncodeAmpsAndAngles(text); 392 | text = _DoItalicsAndBold(text); 393 | 394 | // Do hard breaks: 395 | text = text.replace(/ +\n/g,"
\n"); 396 | 397 | return text; 398 | } 399 | 400 | var _EscapeSpecialCharsWithinTagAttributes = function(text) { 401 | // 402 | // Within tags -- meaning between < and > -- encode [\ ` * _] so they 403 | // don't conflict with their use in Markdown for code, italics and strong. 404 | // 405 | 406 | // Build a regex to find HTML tags and comments. See Friedl's 407 | // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. 408 | var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi; 409 | 410 | text = text.replace(regex, function(wholeMatch) { 411 | var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`"); 412 | tag = escapeCharacters(tag,"\\`*_"); 413 | return tag; 414 | }); 415 | 416 | return text; 417 | } 418 | 419 | var _DoAnchors = function(text) { 420 | // 421 | // Turn Markdown link shortcuts into XHTML
tags. 422 | // 423 | // 424 | // First, handle reference-style links: [link text] [id] 425 | // 426 | 427 | /* 428 | text = text.replace(/ 429 | ( // wrap whole match in $1 430 | \[ 431 | ( 432 | (?: 433 | \[[^\]]*\] // allow brackets nested one level 434 | | 435 | [^\[] // or anything else 436 | )* 437 | ) 438 | \] 439 | 440 | [ ]? // one optional space 441 | (?:\n[ ]*)? // one optional newline followed by spaces 442 | 443 | \[ 444 | (.*?) // id = $3 445 | \] 446 | )()()()() // pad remaining backreferences 447 | /g,_DoAnchors_callback); 448 | */ 449 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag); 450 | 451 | // 452 | // Next, inline-style links: [link text](url "optional title") 453 | // 454 | 455 | /* 456 | text = text.replace(/ 457 | ( // wrap whole match in $1 458 | \[ 459 | ( 460 | (?: 461 | \[[^\]]*\] // allow brackets nested one level 462 | | 463 | [^\[\]] // or anything else 464 | ) 465 | ) 466 | \] 467 | \( // literal paren 468 | [ \t]* 469 | () // no id, so leave $3 empty 470 | ? // href = $4 471 | [ \t]* 472 | ( // $5 473 | (['"]) // quote char = $6 474 | (.*?) // Title = $7 475 | \6 // matching quote 476 | [ \t]* // ignore any spaces/tabs between closing quote and ) 477 | )? // title is optional 478 | \) 479 | ) 480 | /g,writeAnchorTag); 481 | */ 482 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag); 483 | 484 | // 485 | // Last, handle reference-style shortcuts: [link text] 486 | // These must come last in case you've also got [link test][1] 487 | // or [link test](/foo) 488 | // 489 | 490 | /* 491 | text = text.replace(/ 492 | ( // wrap whole match in $1 493 | \[ 494 | ([^\[\]]+) // link text = $2; can't contain '[' or ']' 495 | \] 496 | )()()()()() // pad rest of backreferences 497 | /g, writeAnchorTag); 498 | */ 499 | text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); 500 | 501 | return text; 502 | } 503 | 504 | var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { 505 | if (m7 == undefined) m7 = ""; 506 | var whole_match = m1; 507 | var link_text = m2; 508 | var link_id = m3.toLowerCase(); 509 | var url = m4; 510 | var title = m7; 511 | 512 | if (url == "") { 513 | if (link_id == "") { 514 | // lower-case and turn embedded newlines into spaces 515 | link_id = link_text.toLowerCase().replace(/ ?\n/g," "); 516 | } 517 | url = "#"+link_id; 518 | 519 | if (g_urls[link_id] != undefined) { 520 | url = g_urls[link_id]; 521 | if (g_titles[link_id] != undefined) { 522 | title = g_titles[link_id]; 523 | } 524 | } 525 | else { 526 | if (whole_match.search(/\(\s*\)$/m)>-1) { 527 | // Special case for explicit empty url 528 | url = ""; 529 | } else { 530 | return whole_match; 531 | } 532 | } 533 | } 534 | 535 | url = escapeCharacters(url,"*_"); 536 | var result = ""; 545 | 546 | return result; 547 | } 548 | 549 | 550 | var _DoImages = function(text) { 551 | // 552 | // Turn Markdown image shortcuts into tags. 553 | // 554 | 555 | // 556 | // First, handle reference-style labeled images: ![alt text][id] 557 | // 558 | 559 | /* 560 | text = text.replace(/ 561 | ( // wrap whole match in $1 562 | !\[ 563 | (.*?) // alt text = $2 564 | \] 565 | 566 | [ ]? // one optional space 567 | (?:\n[ ]*)? // one optional newline followed by spaces 568 | 569 | \[ 570 | (.*?) // id = $3 571 | \] 572 | )()()()() // pad rest of backreferences 573 | /g,writeImageTag); 574 | */ 575 | text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag); 576 | 577 | // 578 | // Next, handle inline images: ![alt text](url "optional title") 579 | // Don't forget: encode * and _ 580 | 581 | /* 582 | text = text.replace(/ 583 | ( // wrap whole match in $1 584 | !\[ 585 | (.*?) // alt text = $2 586 | \] 587 | \s? // One optional whitespace character 588 | \( // literal paren 589 | [ \t]* 590 | () // no id, so leave $3 empty 591 | ? // src url = $4 592 | [ \t]* 593 | ( // $5 594 | (['"]) // quote char = $6 595 | (.*?) // title = $7 596 | \6 // matching quote 597 | [ \t]* 598 | )? // title is optional 599 | \) 600 | ) 601 | /g,writeImageTag); 602 | */ 603 | text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag); 604 | 605 | return text; 606 | } 607 | 608 | var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) { 609 | var whole_match = m1; 610 | var alt_text = m2; 611 | var link_id = m3.toLowerCase(); 612 | var url = m4; 613 | var title = m7; 614 | 615 | if (!title) title = ""; 616 | 617 | if (url == "") { 618 | if (link_id == "") { 619 | // lower-case and turn embedded newlines into spaces 620 | link_id = alt_text.toLowerCase().replace(/ ?\n/g," "); 621 | } 622 | url = "#"+link_id; 623 | 624 | if (g_urls[link_id] != undefined) { 625 | url = g_urls[link_id]; 626 | if (g_titles[link_id] != undefined) { 627 | title = g_titles[link_id]; 628 | } 629 | } 630 | else { 631 | return whole_match; 632 | } 633 | } 634 | 635 | alt_text = alt_text.replace(/"/g,"""); 636 | url = escapeCharacters(url,"*_"); 637 | var result = "\""" + _RunSpanGamut(m1) + "");}); 665 | 666 | text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, 667 | function(matchFound,m1){return hashBlock("

" + _RunSpanGamut(m1) + "

");}); 668 | 669 | // atx-style headers: 670 | // # Header 1 671 | // ## Header 2 672 | // ## Header 2 with closing hashes ## 673 | // ... 674 | // ###### Header 6 675 | // 676 | 677 | /* 678 | text = text.replace(/ 679 | ^(\#{1,6}) // $1 = string of #'s 680 | [ \t]* 681 | (.+?) // $2 = Header text 682 | [ \t]* 683 | \#* // optional closing #'s (not counted) 684 | \n+ 685 | /gm, function() {...}); 686 | */ 687 | 688 | text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, 689 | function(wholeMatch,m1,m2) { 690 | var h_level = m1.length; 691 | return hashBlock("" + _RunSpanGamut(m2) + ""); 692 | }); 693 | 694 | return text; 695 | } 696 | 697 | // This declaration keeps Dojo compressor from outputting garbage: 698 | var _ProcessListItems; 699 | 700 | var _DoLists = function(text) { 701 | // 702 | // Form HTML ordered (numbered) and unordered (bulleted) lists. 703 | // 704 | 705 | // attacklab: add sentinel to hack around khtml/safari bug: 706 | // http://bugs.webkit.org/show_bug.cgi?id=11231 707 | text += "~0"; 708 | 709 | // Re-usable pattern to match any entirel ul or ol list: 710 | 711 | /* 712 | var whole_list = / 713 | ( // $1 = whole list 714 | ( // $2 715 | [ ]{0,3} // attacklab: g_tab_width - 1 716 | ([*+-]|\d+[.]) // $3 = first list item marker 717 | [ \t]+ 718 | ) 719 | [^\r]+? 720 | ( // $4 721 | ~0 // sentinel for workaround; should be $ 722 | | 723 | \n{2,} 724 | (?=\S) 725 | (?! // Negative lookahead for another list item marker 726 | [ \t]* 727 | (?:[*+-]|\d+[.])[ \t]+ 728 | ) 729 | ) 730 | )/g 731 | */ 732 | var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; 733 | 734 | if (g_list_level) { 735 | text = text.replace(whole_list,function(wholeMatch,m1,m2) { 736 | var list = m1; 737 | var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; 738 | 739 | // Turn double returns into triple returns, so that we can make a 740 | // paragraph for the last item in a list, if necessary: 741 | list = list.replace(/\n{2,}/g,"\n\n\n");; 742 | var result = _ProcessListItems(list); 743 | 744 | // Trim any trailing whitespace, to put the closing `` 745 | // up on the preceding line, to get it past the current stupid 746 | // HTML block parser. This is a hack to work around the terrible 747 | // hack that is the HTML block parser. 748 | result = result.replace(/\s+$/,""); 749 | result = "<"+list_type+">" + result + "\n"; 750 | return result; 751 | }); 752 | } else { 753 | whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; 754 | text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { 755 | var runup = m1; 756 | var list = m2; 757 | 758 | var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; 759 | // Turn double returns into triple returns, so that we can make a 760 | // paragraph for the last item in a list, if necessary: 761 | var list = list.replace(/\n{2,}/g,"\n\n\n");; 762 | var result = _ProcessListItems(list); 763 | result = runup + "<"+list_type+">\n" + result + "\n"; 764 | return result; 765 | }); 766 | } 767 | 768 | // attacklab: strip sentinel 769 | text = text.replace(/~0/,""); 770 | 771 | return text; 772 | } 773 | 774 | _ProcessListItems = function(list_str) { 775 | // 776 | // Process the contents of a single ordered or unordered list, splitting it 777 | // into individual list items. 778 | // 779 | // The $g_list_level global keeps track of when we're inside a list. 780 | // Each time we enter a list, we increment it; when we leave a list, 781 | // we decrement. If it's zero, we're not in a list anymore. 782 | // 783 | // We do this because when we're not inside a list, we want to treat 784 | // something like this: 785 | // 786 | // I recommend upgrading to version 787 | // 8. Oops, now this line is treated 788 | // as a sub-list. 789 | // 790 | // As a single paragraph, despite the fact that the second line starts 791 | // with a digit-period-space sequence. 792 | // 793 | // Whereas when we're inside a list (or sub-list), that line will be 794 | // treated as the start of a sub-list. What a kludge, huh? This is 795 | // an aspect of Markdown's syntax that's hard to parse perfectly 796 | // without resorting to mind-reading. Perhaps the solution is to 797 | // change the syntax rules such that sub-lists must start with a 798 | // starting cardinal number; e.g. "1." or "a.". 799 | 800 | g_list_level++; 801 | 802 | // trim trailing blank lines: 803 | list_str = list_str.replace(/\n{2,}$/,"\n"); 804 | 805 | // attacklab: add sentinel to emulate \z 806 | list_str += "~0"; 807 | 808 | /* 809 | list_str = list_str.replace(/ 810 | (\n)? // leading line = $1 811 | (^[ \t]*) // leading whitespace = $2 812 | ([*+-]|\d+[.]) [ \t]+ // list marker = $3 813 | ([^\r]+? // list item text = $4 814 | (\n{1,2})) 815 | (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) 816 | /gm, function(){...}); 817 | */ 818 | list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, 819 | function(wholeMatch,m1,m2,m3,m4){ 820 | var item = m4; 821 | var leading_line = m1; 822 | var leading_space = m2; 823 | 824 | if (leading_line || (item.search(/\n{2,}/)>-1)) { 825 | item = _RunBlockGamut(_Outdent(item)); 826 | } 827 | else { 828 | // Recursion for sub-lists: 829 | item = _DoLists(_Outdent(item)); 830 | item = item.replace(/\n$/,""); // chomp(item) 831 | item = _RunSpanGamut(item); 832 | } 833 | 834 | return "
  • " + item + "
  • \n"; 835 | } 836 | ); 837 | 838 | // attacklab: strip sentinel 839 | list_str = list_str.replace(/~0/g,""); 840 | 841 | g_list_level--; 842 | return list_str; 843 | } 844 | 845 | 846 | var _DoCodeBlocks = function(text) { 847 | // 848 | // Process Markdown `
    ` blocks.
     849 | //
     850 | 
     851 | 	/*
     852 | 		text = text.replace(text,
     853 | 			/(?:\n\n|^)
     854 | 			(								// $1 = the code block -- one or more lines, starting with a space/tab
     855 | 				(?:
     856 | 					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
     857 | 					.*\n+
     858 | 				)+
     859 | 			)
     860 | 			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
     861 | 		/g,function(){...});
     862 | 	*/
     863 | 
     864 | 	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
     865 | 	text += "~0";
     866 | 
     867 | 	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
     868 | 		function(wholeMatch,m1,m2) {
     869 | 			var codeblock = m1;
     870 | 			var nextChar = m2;
     871 | 
     872 | 			codeblock = _EncodeCode( _Outdent(codeblock));
     873 | 			codeblock = _Detab(codeblock);
     874 | 			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
     875 | 			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
     876 | 
     877 | 			codeblock = "
    " + codeblock + "
    \n"; 878 | 879 | return hashBlock(codeblock) + nextChar; 880 | } 881 | ); 882 | 883 | // attacklab: strip sentinel 884 | text = text.replace(/~0/,""); 885 | 886 | return text; 887 | } 888 | 889 | var hashBlock = function(text) { 890 | text = text.replace(/(^\n+|\n+$)/g,""); 891 | return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n"; 892 | } 893 | 894 | 895 | var _DoCodeSpans = function(text) { 896 | // 897 | // * Backtick quotes are used for spans. 898 | // 899 | // * You can use multiple backticks as the delimiters if you want to 900 | // include literal backticks in the code span. So, this input: 901 | // 902 | // Just type ``foo `bar` baz`` at the prompt. 903 | // 904 | // Will translate to: 905 | // 906 | //

    Just type foo `bar` baz at the prompt.

    907 | // 908 | // There's no arbitrary limit to the number of backticks you 909 | // can use as delimters. If you need three consecutive backticks 910 | // in your code, use four for delimiters, etc. 911 | // 912 | // * You can use spaces to get literal backticks at the edges: 913 | // 914 | // ... type `` `bar` `` ... 915 | // 916 | // Turns to: 917 | // 918 | // ... type `bar` ... 919 | // 920 | 921 | /* 922 | text = text.replace(/ 923 | (^|[^\\]) // Character before opening ` can't be a backslash 924 | (`+) // $2 = Opening run of ` 925 | ( // $3 = The code block 926 | [^\r]*? 927 | [^`] // attacklab: work around lack of lookbehind 928 | ) 929 | \2 // Matching closer 930 | (?!`) 931 | /gm, function(){...}); 932 | */ 933 | 934 | text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, 935 | function(wholeMatch,m1,m2,m3,m4) { 936 | var c = m3; 937 | c = c.replace(/^([ \t]*)/g,""); // leading whitespace 938 | c = c.replace(/[ \t]*$/g,""); // trailing whitespace 939 | c = _EncodeCode(c); 940 | return m1+""+c+""; 941 | }); 942 | 943 | return text; 944 | } 945 | 946 | 947 | var _EncodeCode = function(text) { 948 | // 949 | // Encode/escape certain characters inside Markdown code runs. 950 | // The point is that in code, these characters are literals, 951 | // and lose their special Markdown meanings. 952 | // 953 | // Encode all ampersands; HTML entities are not 954 | // entities within a Markdown code span. 955 | text = text.replace(/&/g,"&"); 956 | 957 | // Do the angle bracket song and dance: 958 | text = text.replace(//g,">"); 960 | 961 | // Now, escape characters that are magic in Markdown: 962 | text = escapeCharacters(text,"\*_{}[]\\",false); 963 | 964 | // jj the line above breaks this: 965 | //--- 966 | 967 | //* Item 968 | 969 | // 1. Subitem 970 | 971 | // special char: * 972 | //--- 973 | 974 | return text; 975 | } 976 | 977 | 978 | var _DoItalicsAndBold = function(text) { 979 | 980 | // must go first: 981 | text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, 982 | "$2"); 983 | 984 | text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, 985 | "$2"); 986 | 987 | return text; 988 | } 989 | 990 | 991 | var _DoBlockQuotes = function(text) { 992 | 993 | /* 994 | text = text.replace(/ 995 | ( // Wrap whole match in $1 996 | ( 997 | ^[ \t]*>[ \t]? // '>' at the start of a line 998 | .+\n // rest of the first line 999 | (.+\n)* // subsequent consecutive lines 1000 | \n* // blanks 1001 | )+ 1002 | ) 1003 | /gm, function(){...}); 1004 | */ 1005 | 1006 | text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, 1007 | function(wholeMatch,m1) { 1008 | var bq = m1; 1009 | 1010 | // attacklab: hack around Konqueror 3.5.4 bug: 1011 | // "----------bug".replace(/^-/g,"") == "bug" 1012 | 1013 | bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting 1014 | 1015 | // attacklab: clean up hack 1016 | bq = bq.replace(/~0/g,""); 1017 | 1018 | bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines 1019 | bq = _RunBlockGamut(bq); // recurse 1020 | 1021 | bq = bq.replace(/(^|\n)/g,"$1 "); 1022 | // These leading spaces screw with
     content, so we need to fix that:
    1023 | 			bq = bq.replace(
    1024 | 					/(\s*
    [^\r]+?<\/pre>)/gm,
    1025 | 				function(wholeMatch,m1) {
    1026 | 					var pre = m1;
    1027 | 					// attacklab: hack around Konqueror 3.5.4 bug:
    1028 | 					pre = pre.replace(/^  /mg,"~0");
    1029 | 					pre = pre.replace(/~0/g,"");
    1030 | 					return pre;
    1031 | 				});
    1032 | 
    1033 | 			return hashBlock("
    \n" + bq + "\n
    "); 1034 | }); 1035 | return text; 1036 | } 1037 | 1038 | 1039 | var _FormParagraphs = function(text) { 1040 | // 1041 | // Params: 1042 | // $text - string to process with html

    tags 1043 | // 1044 | 1045 | // Strip leading and trailing lines: 1046 | text = text.replace(/^\n+/g,""); 1047 | text = text.replace(/\n+$/g,""); 1048 | 1049 | var grafs = text.split(/\n{2,}/g); 1050 | var grafsOut = new Array(); 1051 | 1052 | // 1053 | // Wrap

    tags. 1054 | // 1055 | var end = grafs.length; 1056 | for (var i=0; i= 0) { 1061 | grafsOut.push(str); 1062 | } 1063 | else if (str.search(/\S/) >= 0) { 1064 | str = _RunSpanGamut(str); 1065 | str = str.replace(/^([ \t]*)/g,"

    "); 1066 | str += "

    " 1067 | grafsOut.push(str); 1068 | } 1069 | 1070 | } 1071 | 1072 | // 1073 | // Unhashify HTML blocks 1074 | // 1075 | end = grafsOut.length; 1076 | for (var i=0; i= 0) { 1079 | var blockText = g_html_blocks[RegExp.$1]; 1080 | blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs 1081 | grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); 1082 | } 1083 | } 1084 | 1085 | return grafsOut.join("\n\n"); 1086 | } 1087 | 1088 | 1089 | var _EncodeAmpsAndAngles = function(text) { 1090 | // Smart processing for ampersands and angle brackets that need to be encoded. 1091 | 1092 | // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: 1093 | // http://bumppo.net/projects/amputator/ 1094 | text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); 1095 | 1096 | // Encode naked <'s 1097 | text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); 1098 | 1099 | return text; 1100 | } 1101 | 1102 | 1103 | var _EncodeBackslashEscapes = function(text) { 1104 | // 1105 | // Parameter: String. 1106 | // Returns: The string, with after processing the following backslash 1107 | // escape sequences. 1108 | // 1109 | 1110 | // attacklab: The polite way to do this is with the new 1111 | // escapeCharacters() function: 1112 | // 1113 | // text = escapeCharacters(text,"\\",true); 1114 | // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); 1115 | // 1116 | // ...but we're sidestepping its use of the (slow) RegExp constructor 1117 | // as an optimization for Firefox. This function gets called a LOT. 1118 | 1119 | text = text.replace(/\\(\\)/g,escapeCharacters_callback); 1120 | text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); 1121 | return text; 1122 | } 1123 | 1124 | 1125 | var _DoAutoLinks = function(text) { 1126 | 1127 | text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"
    $1"); 1128 | 1129 | // Email addresses: 1130 | 1131 | /* 1132 | text = text.replace(/ 1133 | < 1134 | (?:mailto:)? 1135 | ( 1136 | [-.\w]+ 1137 | \@ 1138 | [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ 1139 | ) 1140 | > 1141 | /gi, _DoAutoLinks_callback()); 1142 | */ 1143 | text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, 1144 | function(wholeMatch,m1) { 1145 | return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); 1146 | } 1147 | ); 1148 | 1149 | return text; 1150 | } 1151 | 1152 | 1153 | var _EncodeEmailAddress = function(addr) { 1154 | // 1155 | // Input: an email address, e.g. "foo@example.com" 1156 | // 1157 | // Output: the email address as a mailto link, with each character 1158 | // of the address encoded as either a decimal or hex entity, in 1159 | // the hopes of foiling most address harvesting spam bots. E.g.: 1160 | // 1161 | // foo 1163 | // @example.com 1164 | // 1165 | // Based on a filter by Matthew Wickline, posted to the BBEdit-Talk 1166 | // mailing list: 1167 | // 1168 | 1169 | // attacklab: why can't javascript speak hex? 1170 | function char2hex(ch) { 1171 | var hexDigits = '0123456789ABCDEF'; 1172 | var dec = ch.charCodeAt(0); 1173 | return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); 1174 | } 1175 | 1176 | var encode = [ 1177 | function(ch){return "&#"+ch.charCodeAt(0)+";";}, 1178 | function(ch){return "&#x"+char2hex(ch)+";";}, 1179 | function(ch){return ch;} 1180 | ]; 1181 | 1182 | addr = "mailto:" + addr; 1183 | 1184 | addr = addr.replace(/./g, function(ch) { 1185 | if (ch == "@") { 1186 | // this *must* be encoded. I insist. 1187 | ch = encode[Math.floor(Math.random()*2)](ch); 1188 | } else if (ch !=":") { 1189 | // leave ':' alone (to spot mailto: later) 1190 | var r = Math.random(); 1191 | // roughly 10% raw, 45% hex, 45% dec 1192 | ch = ( 1193 | r > .9 ? encode[2](ch) : 1194 | r > .45 ? encode[1](ch) : 1195 | encode[0](ch) 1196 | ); 1197 | } 1198 | return ch; 1199 | }); 1200 | 1201 | addr = "" + addr + ""; 1202 | addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part 1203 | 1204 | return addr; 1205 | } 1206 | 1207 | 1208 | var _UnescapeSpecialChars = function(text) { 1209 | // 1210 | // Swap back in all the special characters we've hidden. 1211 | // 1212 | text = text.replace(/~E(\d+)E/g, 1213 | function(wholeMatch,m1) { 1214 | var charCodeToReplace = parseInt(m1); 1215 | return String.fromCharCode(charCodeToReplace); 1216 | } 1217 | ); 1218 | return text; 1219 | } 1220 | 1221 | 1222 | var _Outdent = function(text) { 1223 | // 1224 | // Remove one level of line-leading tabs or spaces 1225 | // 1226 | 1227 | // attacklab: hack around Konqueror 3.5.4 bug: 1228 | // "----------bug".replace(/^-/g,"") == "bug" 1229 | 1230 | text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width 1231 | 1232 | // attacklab: clean up hack 1233 | text = text.replace(/~0/g,"") 1234 | 1235 | return text; 1236 | } 1237 | 1238 | var _Detab = function(text) { 1239 | // attacklab: Detab's completely rewritten for speed. 1240 | // In perl we could fix it by anchoring the regexp with \G. 1241 | // In javascript we're less fortunate. 1242 | 1243 | // expand first n-1 tabs 1244 | text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width 1245 | 1246 | // replace the nth with two sentinels 1247 | text = text.replace(/\t/g,"~A~B"); 1248 | 1249 | // use the sentinel to anchor our regex so it doesn't explode 1250 | text = text.replace(/~B(.+?)~A/g, 1251 | function(wholeMatch,m1,m2) { 1252 | var leadingText = m1; 1253 | var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width 1254 | 1255 | // there *must* be a better way to do this: 1256 | for (var i=0; i