Compare commits

..

139 Commits

Author SHA1 Message Date
John Bintz
1ce551581a put the radius back in 2011-06-17 16:34:54 -04:00
John Bintz
b1bf8add92 make rocco nicer with large file lists 2011-06-17 14:58:37 -04:00
Mike West
dde147a171 Merge pull request #51 from Sharpie/patch-1
Add `file` tag for Mustache layouts
2011-06-12 05:28:34 -07:00
Mike West
a175735f0c Merge pull request #50 from zpatten/master
Fix for crash when code is nil in lib/rocco/layout.rb, sections method.
2011-06-12 05:27:36 -07:00
Mike West
57be0b7695 Merge pull request #43 from VerKnowSys/master
Added scala comments
2011-06-12 05:27:03 -07:00
Charlie Sharpsteen
4f81074ad4 Add file tag for Mustache layouts
For when you want to use something more descriptive than `title` in your layout.
2011-06-02 16:30:17 -07:00
Zachary Patten
4c4ef24222 added assignment to handle nil code values 2011-06-01 12:33:08 -05:00
Ryan Tomayko
97a971897f Merge pull request #48 from defunkt/vbump
Version Tweaks
2011-05-30 18:55:42 -07:00
Ryan Tomayko
d149a9bbf4 Merge pull request #47 from defunkt/process-markdown
Split Markdown processing into its own method
2011-05-30 18:55:05 -07:00
Chris Wanstrath
c4da54408a Split Markdown processing into its own method 2011-05-30 17:39:55 -07:00
Chris Wanstrath
d98793dc7e gemspec uses Rocco::VERSION and current date for releases 2011-05-30 16:57:59 -07:00
Chris Wanstrath
9b65c1cd99 bump Rocco::VERSION (0.7 was released) 2011-05-30 16:57:29 -07:00
Ryan Tomayko
7ba75f0503 Merge pull request #46 from dkastner/master
Rake 0.9 Support
2011-05-24 12:29:38 -07:00
Derek Kastner
c1a5017468 Support for Rake < 0.9 2011-05-24 15:14:29 -04:00
Derek Kastner
58e7e4f7d8 Support Rake 0.9 2011-05-24 14:04:31 -04:00
Ryan Tomayko
3e9a14e896 style: no spaces in parens 2011-05-22 20:42:31 -07:00
Ryan Tomayko
9e88a3925c don't include FileTest in top-level, fixes #45 2011-05-22 20:41:06 -07:00
Tymon Tobolski
ba393d1d8c Added scala comments 2011-05-05 22:38:39 +02:00
Mike West
61d84c85c2 Pulling in support for "impure" block comments
Closes issue #37
2011-04-20 21:51:39 +02:00
Mike West
e727446de7 Fixing heredoc test. 2011-04-20 21:51:13 +02:00
Mike West
d4f527f2d6 Merge branch 'block-comment-styles' of https://github.com/hyperbolist/rocco into hyperbolist-block-comment-styles
Conflicts:
	lib/rocco.rb
2011-04-20 21:50:57 +02:00
Mike West
6736281302 Adding XML and HTML support.
Also splitting Pygmentize's type on `+`, and only using the first bit.
`xml+whatever` => `xml` and so on.  Clever.

Closes issue #36.
2011-04-19 20:03:54 +02:00
Mike West
b0582c9deb Merging in XML, extending to HTML. 2011-04-19 20:02:00 +02:00
Mike West
6193734ee1 Merging in docblock annotations.
Closes issue #34.
2011-04-19 19:55:18 +02:00
Mike West
3ebbb82f14 One more test. 2011-04-19 19:54:42 +02:00
Mike West
c1afb8a0d4 Merge branch 'docblock-annotations' of https://github.com/bobthecow/rocco into bobthecow-docblock-annotations 2011-04-19 19:44:39 +02:00
Justin Hileman
fe09cbe0e3 Adding PHP syntax.
Closes issue #35.
2011-04-19 19:42:28 +02:00
Dotan J. Nahum
c5d0f3ba43 Adding C# support.
Closes issue #39.
2011-04-19 19:19:08 +02:00
Mike West
d0690e6269 Stub out heredoc support. Needs more tests. (Issue #40) 2011-04-13 21:35:41 +02:00
Eric Sherman
6c96fcb681 added xml comment support 2011-03-30 23:38:38 -04:00
Eric Sherman
c39dbd7816 added support for impure block comment styles 2011-03-30 23:22:37 -04:00
Justin Hileman
8dd30853aa Add naïve support for Docblock @annotations. 2011-03-20 16:24:13 -04:00
Mike West
e5e0d53f18 Revert "Fixing a bug in layout.rb's template directory location"
This reverts commit d495074320.
2011-03-17 20:16:12 +01:00
Mike West
d495074320 Fixing a bug in layout.rb's template directory location
Should take care of GH-31.  Thanks to [Karel Minarik][1]

[1]: http://www.karmi.cz
2011-03-17 19:30:53 +01:00
rick
b993200fc1 link to github pages hosted docco css file. 2011-03-16 19:34:29 -07:00
Ryan Tomayko
66e8f9a319 0.6 release 2011-03-05 04:49:43 -08:00
Ryan Tomayko
f9dd29eafa 2 char indent in tests 2011-03-05 04:46:53 -08:00
Ryan Tomayko
3f1a8dfef3 use consistent relative require for test/helper 2011-03-05 04:46:40 -08:00
Ryan Tomayko
19506b13db don't reopen stderr in tests -- we're losing backtraces 2011-03-05 04:43:41 -08:00
Ryan Tomayko
3224735671 be serious about 80c line lengths in lib/rocco.rb 2011-03-05 04:32:34 -08:00
Ryan Tomayko
a841026e52 CHANGELOG formatting 2011-03-05 04:00:52 -08:00
Ryan Tomayko
f40e57baae two char indent and whitespace error fixes 2011-03-05 03:53:00 -08:00
Ryan Tomayko
a2d316b20d fix issue with locating submodule template with newer versions of mustache 2011-03-05 03:37:32 -08:00
Ryan Tomayko
66723da96f do not add ./ to paths 2011-03-05 03:37:05 -08:00
Mike West
b2a76fac29 Updating changelog. 2011-02-12 10:05:59 +01:00
Luke Andrew
6874288c8a [GH-28]: Descriptive section names.
Header sections will get a descriptive hash anchor: rather than
`section-1`, the section will be given an id containing the header text
with spaces stripped and replaced with `_`.  `section-Header_Goes_Here`
for instance.

Thanks to [Luke Andrew][1] for the initial work on this patch.

[1]: https://github.com/zyx
2011-02-12 09:59:50 +01:00
Mike West
5271a2ba31 Dropping some debug 'puts' statements. 2010-11-25 17:09:48 +01:00
Mike West
12b26fe704 Relative paths in the source jumplist
Adding a dependency on `Pathname`, which provides the excellent
`relative_path_from` method.  That happens to be exactly what's needed
to fix the pathnames for the source jumplist issue that [Topfunky][]
reported.

Closes GH-26.

[Topfunky]: https://github.com/topfunky
2010-11-25 16:41:30 +01:00
Mike West
9d49139090 Fixing block comment highlighting for block-only languages
Languages without single-line comments (CSS) explode when running
through `highlight`, as the `DIVIDER` mechanism doesn't deal well with
`nil` comment characters.  I've reworked the mechanism such that it
uses multi-line comments when single-line comments aren't available.
2010-11-25 16:12:28 +01:00
Mike West
1fa99adbda Every project needs a changelog. 2010-11-23 19:55:36 +01:00
Mike West
cffe49a813 Fixing bugs that popped up as soon as I ran rake 2010-11-22 15:00:36 +01:00
Mike West
1262d50857 Merge branch 'blockcomments' 2010-11-22 14:42:56 +01:00
Mike West
b11543d382 Normalizing leading space in comments
That is:

    def function:
        """
            This is a comment
            with _lots_ of leading
            space!  OMG!
        """
        pass

Will parse into:

    [
        [
            [   "This is a comment",
                "with _lots_ of leading",
                "space!  OMG!"
            ],
            ...
        ]
    ]
2010-11-22 14:42:21 +01:00
Mike West
d0211ecc99 Python block comments (no middle character), and CSS syntax 2010-11-22 13:38:03 +01:00
Mike West
77dff765b6 Fixing tests for block comments. 2010-11-22 08:41:54 +01:00
Mike West
f177a9d7e2 Block comment parsing: basics.
Block comments are parsed out, but the commentchar removal isn't working
yet.  I'll refactor that code out of it's current home, and move it into
`parse`, as I need to know what _kind_ of comment it is that I'm
stripping.  Carrying that metadata around doesn't make any sense, so
I'll just convert the comment on the fly into a set of non-comment
strings.
2010-11-22 08:25:40 +01:00
Mike West
d067210faa Refactoring comment_char internals: prepping for block comments 2010-11-21 16:53:22 +01:00
Mike West
7609e1a624 Adding Ruby block comment syntax.
Working towards GH-22.
2010-11-21 16:36:20 +01:00
Vasily Poloynyov
ba93d23634 Really fixing extensionless file support
Pieced together a fix using Regex and `File.extname` from http://github.com/vast/rocco

Closes GH-24.
2010-11-21 16:18:28 +01:00
Mike West
515966dcc9 Locking the CSS file down to Docco's v0.3.0
I'm a bit loath to change the rendering entirely without talking to
Ryan, but I'd really prefer to replicate Docco's CSS file generation
rather than hotlinking out to the GitHub-hosted version.  Offline
support would require it, if for no other reason...

Closes GH-23.  Thanks to [Myles Byrne](http://www.myles.id.au/)
2010-11-21 15:41:22 +01:00
Mike West
6aa2217706 Refactoring tests out into separate files. 2010-10-24 11:08:04 +02:00
Mike West
e506c5172a Skipping Python/Ruby 1.9 source encoding
In the same way that it makes sense to skip the shebang (#!) line in
scripts, it makes sense to skip the encoding definition in Python files
(described by [PEP 263][p]) and Ruby 1.9 files (similar enough syntax
that it's not worth worrying about.

[p]: http://www.python.org/dev/peps/pep-0263/
2010-10-21 20:10:30 +02:00
Mike West
a4d0e41413 Cleanup inline documentation, small code reorg. 2010-10-21 20:04:17 +02:00
Mike West
b87f4a63e3 Merge branch 'language' 2010-10-20 17:08:46 +02:00
Mike West
185da24fc3 Cleaning up indent spacing in test file. 2010-10-20 17:07:32 +02:00
Mike West
020e8050bc Autopopulate comment_chars for known languages
Adding comment characters for bash, c, c++, coffee script, java, javascript, lua, python, ruby, and scheme.  Paving the way for block-comment parsing later on...

Closes issue #20.
2010-10-20 17:07:14 +02:00
Mike West
0b392c1094 Attempt to autodetect file language
`pygmentize` 1.0+ has an `-N` option that attempts to match a file (via
the extension) to a language lexer.  If `pygmentize` is installed, we'll
run it with this option to get a language.

If no language is detected, `pygmentize -N` returns `text`.  In that case,
we'll first look for a user-provided language to use as a fallback.  If no
language was provided, highlight using `ruby` as a reasonable default.

Closes issue #19.
2010-10-20 15:11:07 +02:00
Mike West
a43f0fc584 Typo: Closes issue #11
Thanks to Paul Chavard ( http://github.com/tchak ) for
the report and fix.
2010-10-20 14:27:03 +02:00
Mike West
1b211bcc08 Specify encoding for Pygments
This closes issue #10, in theory, but I'm not completely happy with the
behavior.  The output for both UTF-8 and ISO-8859-1 sources is arguably
correct, but I think it'd be better to do some autodetecting of the file
encoding, and explicitly convert everything to UTF-8 on input.  One
option is the [`chardet` gem][gem], but I'm loath to add another
dependency to Rocco...

[gem]: http://rubygems.org/gems/chardet/versions/0.9.0
2010-10-19 13:32:03 +02:00
Mike West
38683a8cc2 Cleaning up tests after bugfix merges:
As a result of fixing issue #15, a few tests broken.  This commit brings
the tests up to date with the latest behavior.
2010-10-19 13:08:13 +02:00
Ryan Tomayko
94b3fd4e51 task :default => :test 2010-10-19 03:39:13 -07:00
Ryan Tomayko
f144e7e3b2 Merge remote branch 'mikewest/test' 2010-10-19 03:35:46 -07:00
Ryan Tomayko
236fb2731c Merge remote branch 'mikewest/template'
Conflicts:
	lib/rocco.rb
2010-10-19 03:35:33 -07:00
Ryan Tomayko
6266408828 Merge remote branch 'mikewest/master' 2010-10-19 03:35:13 -07:00
Mike West
6cf8de0a02 Adding a basic test suite. 2010-10-17 20:46:28 +02:00
Mike West
bb9b167b13 Variables for use in Mustache templates.
Added:

*   `docs?`:    True if `docs` contains text of any sort, False if
                it's empty.

*   `code?`:    True if `code` contains text of any sort, False if
                it's empty.

*   `empty?`:   True if both `code` and `docs` are empty.  False
                otherwise.

*   `header?`:  True if `docs` contains _only_ a HTML header.  False
                otherwise.
2010-10-17 13:16:45 +02:00
Mike West
bb8fcb9ef0 Adding CLI argument template
In v0.5, the Mustache template is hardcoded as
`./lib/rocco/layout.mustache`.  This makes it quite difficult to
style generated content as one must edit the layout file inside the
gem itself to make changes.

I propose leaving that file as a sensible default, but allowing the user
to specify an absolute or relative (to the current working directory)
path to a mustach template of her choosing.  That's implemented in this
commit.
2010-10-17 12:25:35 +02:00
Mike West
b9b69d98fb Fixing (among other things) alternate header syntax
The following works in Docco, but not in Rocco:

    Level 1 Heading
    ===============

    Level 2 Heading
    ---------------

Happily, the fix is trivial.  In Docco, the regex for comments is:

    # Does the line begin with a comment?
    l.comment_matcher = new RegExp('^\\s*' + l.symbol + '\\s?')

Changing Rocco's comment pattern to:

    @comment_pattern = Regexp.new("^\\s*#{@options[:comment_chars]}\s?")

Solves the problem for me.
2010-10-14 18:57:35 +02:00
Mike West
198be61e7c Fixing code highlighting in bash mode
Rocco splits against `<span class="c.">`, which works fine for Ruby
where the `span` has a class of `c1`, but fails for Bash (and probably
other languages), where the `span` has a class of `c`.  The fix is
trivial.
2010-10-14 18:31:20 +02:00
Mike West
939e7f0e8a Integrate pilcrow change from Docco
Rocco uses the Docco CSS directly, so when they make an update to the
HTML/CSS, Rocco needs to play along.  In this case, Docco changed from
`#` to `¶`, and changed classnames as well (in
[f8a88d66b381a1c04358][]).

This commit migrates that change to Rocco.

[f8a88d66b381a1c04358]:
f8a88d66b3
2010-10-14 18:21:31 +02:00
Ryan Tomayko
e57b208570 0.5 release 2010-09-10 10:32:31 -07:00
Colin Shea
6aa7bd6a33 Really support extension-less files (and still support -o) 2010-04-03 08:14:44 +08:00
Ryan Tomayko
889fcb286b Merge Ruby 1.9 fixes from evaryont/master 2010-04-02 16:02:27 -07:00
Ryan Tomayko
eed5d48981 avoid which(1) 2010-04-02 15:59:52 -07:00
Ryan Tomayko
957e5cf197 Merge a bunch of stuff from burke/master 2010-04-02 15:53:39 -07:00
Ryan Tomayko
feb22ad147 create output dirs before writing files 2010-04-02 15:46:12 -07:00
Marko Mikulicic
367437fec5 honour -o 2010-04-02 15:39:16 -07:00
Burke Libbey
c1a4dd756a prefer .blank? to == '' 2010-03-30 15:26:31 -05:00
Burke Libbey
3dc4f87c12 code DIVIDER is now language-agnostic. 2010-03-30 15:18:17 -05:00
Burke Libbey
6595d5f885 bin/rocco works for extensionless files now (again?) too. README -> README.html. 2010-03-30 15:01:01 -05:00
Burke Libbey
8c948bbb95 fixed -o option 2010-03-30 14:55:26 -05:00
Burke Libbey
55700ff584 change a class in a regex to deal with the way pygments outputs C code. 2010-03-30 14:38:01 -05:00
Burke Libbey
2f54f4c424 send @options['language'] to pygments.appspot.com, not just 'ruby'. 2010-03-30 14:08:12 -05:00
Burke Libbey
17eeb9e75f fixed syntax error, re-added warning for webservice 2010-03-30 14:07:30 -05:00
Burke Libbey
68b1529714 Removed warning about pygmentize not being installed. 2010-03-30 13:57:45 -05:00
Burke Libbey
fb4b5404ae just use the pygments.appspot.com if there's no pygmentize on the path, rather than as a command line flag 2010-03-30 13:46:24 -05:00
Colin Shea
c1837853d6 Use spaces, not tabs. 2010-03-29 08:22:42 -04:00
Colin Shea
4fa61ea14a Use readlines, not read 2010-03-29 08:21:58 -04:00
Simon Rozet
bc8bdccb7d allow to use pygments.appspot.com instead of pygmentize(1) 2010-03-19 15:53:51 -07:00
Ryan Tomayko
545fd53b88 0.4 release 2010-03-19 12:08:37 -07:00
Ryan Tomayko
2f8337f49c Merge custom langs and comment chars from jdp/master 2010-03-19 12:05:25 -07:00
jdp
1978a5fe98 made filename munging more idiomatic, rake tasks work properly again 2010-03-17 02:35:06 -04:00
jdp
3af16f3afe table of contents generation works properly again 2010-03-17 00:41:06 -04:00
jdp
3b48e38cba leading tabs in code are now replaced with two spaces! 2010-03-16 22:58:29 -04:00
Ryan Tomayko
bda3a62e42 0.3 release 2010-03-16 08:25:52 -07:00
Ryan Tomayko
b6ece339b9 comments are considered doc only when # is followed by space
This lets you force comments over to the code side by using any
character other than ' ' after the '#' character.
2010-03-16 08:24:30 -07:00
jdp
37aeba2247 updated usage for bin/rocco 2010-03-16 05:27:49 -04:00
jdp
bf401ef38b quick cleanup of docs i never erased 2010-03-16 05:19:53 -04:00
jdp
cf12978037 generalized it so that pygment lexer and comment characters can be specified, made shebang line ignore more clear 2010-03-16 05:17:17 -04:00
Samuel Reis
02dc9c6c4c fix shebang line on documentation side 2010-03-15 21:10:27 -07:00
Ryan Tomayko
832048d946 implement the file switcher thingy 2010-03-11 08:01:46 -08:00
Ryan Tomayko
27b2ee03db rocco command gets --output <dir> option + usage message 2010-03-11 07:58:25 -08:00
Ryan Tomayko
73c433f484 0.2 release 2010-03-11 06:30:02 -08:00
Ryan Tomayko
459b492390 holla 2010-03-11 06:30:02 -08:00
Ryan Tomayko
895bf7a759 Rocco::make and Rocco::Task for intelligent rocco builds in Rake 2010-03-11 06:30:02 -08:00
Ryan Tomayko
a7282e4606 generated rocco.rb doc links to sources for effect 2010-03-11 06:30:02 -08:00
Ryan Tomayko
ef7dbdcda0 rake task to update gh-pages branch 2010-03-11 06:29:58 -08:00
Ryan Tomayko
8559a79837 clean up gemspec generation 2010-03-11 03:11:28 -08:00
Ryan Tomayko
e467978b69 ascii art is a requirement 2010-03-10 12:33:25 -08:00
Ryan Tomayko
cc09e0ff1e and that's it 2010-03-10 04:07:21 -08:00
Ryan Tomayko
399a7ebff0 octothorpe thpptt pow. let there be permalinks and backgrounds. 2010-03-10 04:07:08 -08:00
Ryan Tomayko
2250694c32 update generated doc 2010-03-09 09:17:19 -08:00
Ryan Tomayko
94eaea6339 we don't want zombies running around 2010-03-09 09:16:53 -08:00
Ryan Tomayko
580d0ef7fc improve documentation 2010-03-09 09:15:50 -08:00
Ryan Tomayko
606087986e fall back to BlueCloth if rdiscount isn't available 2010-03-09 08:56:01 -08:00
Ryan Tomayko
5a544ca5de fix DIVIDER in output when no comments at top of file 2010-03-09 08:55:17 -08:00
Ryan Tomayko
84cff4945e update site 2010-03-08 17:53:03 -08:00
Ryan Tomayko
39a4c00fc2 don't need that 2010-03-08 17:52:29 -08:00
Ryan Tomayko
cf10e21b30 gem building machinery 2010-03-08 17:45:52 -08:00
Ryan Tomayko
3234f7013f oops. we want that at index.html 2010-03-08 17:35:20 -08:00
Ryan Tomayko
c3cfde7ebd add link to gh-pages doc 2010-03-08 17:33:48 -08:00
Ryan Tomayko
8dba9306bc throw this in here until we can get a real gh-pages branch going 2010-03-08 17:31:32 -08:00
Ryan Tomayko
4a0dfe7421 rocco command acts like documented 2010-03-08 17:30:40 -08:00
Ryan Tomayko
50fb900e5e flesh out the introductory documentation 2010-03-08 17:09:18 -08:00
Ryan Tomayko
5eca6cdc9b use mustache for layout 2010-03-08 15:21:28 -08:00
Ryan Tomayko
7b0f7d31fd rocco command tries to setup load path 2010-03-08 15:05:01 -08:00
Ryan Tomayko
1b4389ef72 first working version. just barely. not useful. 2010-03-08 10:19:34 -08:00
Ryan Tomayko
b69e02c802 first spike 2010-03-08 09:56:18 -08:00
Ryan Tomayko
9f3826d8e0 README 2010-03-08 08:27:24 -08:00
31 changed files with 1907 additions and 1602 deletions

63
CHANGES.md Normal file
View File

@ -0,0 +1,63 @@
CHANGES
=======
0.6 (2011-03-05)
----------------
This release brought to you almost entirely
by [mikewest](http://github.com/mikewest).
### Features
* Added `-t`/`--template` CLI option that allows you to specify a Mustache
template that ought be used when rendering the final documentation.
(Issue #16)
* More variables in templates:
* `docs?`: True if `docs` contains text of any sort, False if it's empty.
* `code?`: True if `code` contains text of any sort, False if it's empty.
* `empty?`: True if both `code` and `docs` are empty. False otherwise.
* `header?`: True if `docs` contains _only_ a HTML header. False otherwise.
* Test suite! (Run `rake test`)
* Autodetect file's language if Pygments is installed locally (Issue #19)
* Autopopulate comment characters for known languages (Issue #20)
* Correctly parse block comments (Issue #22)
* Stripping encoding definitions from Ruby and Python files in the same
way we strip shebang lines (Issue #21)
* Adjusting section IDs to contain descriptive test from headers. A header
section's ID might be `section-Header_text_goes_here` for friendlier URLs.
Other section IDs will remain the same (`section-2` will stay
`section-2`). (Issue #28)
### Bugs Fixed
* Docco's CSS changed: we updated Rocco's HTML accordingly, and pinned
the CSS file to Docco's 0.3.0 tag. (Issues #12 and #23)
* Fixed code highlighting for shell scripts (among others) (Issue #13)
* Fixed buggy regex for comment char stripping (Issue #15)
* Specifying UTF-8 encoding for Pygments (Issue #10)
* Extensionless file support (thanks to [Vasily Polovnyov][vast] for the
fix!) (Issue #24)
* Fixing language support for Pygments webservice (Issue #11)
* The source jumplist now generates correctly relative URLs (Issue #26)
* Fixed an issue with using mustache's `template_path=` incorrectly.
[vast]: https://github.com/vast
0.5
---
Rocco 0.5 emerged from the hazy mists, complete and unfettered by history.

18
COPYING Normal file
View File

@ -0,0 +1,18 @@
Copyright (c) 2010 Ryan Tomayko <http://tomayko.com/about>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

23
README Normal file
View File

@ -0,0 +1,23 @@
___ ___ ___ ___ ___
/\ \ /\ \ /\ \ /\ \ /\ \
/::\ \ /::\ \ /::\ \ /::\ \ /::\ \
/::\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\ /:/\:\__\
\;:::/ / \:\/:/ / \:\ \/__/ \:\ \/__/ \:\/:/ /
|:\/__/ \::/ / \:\__\ \:\__\ \::/ /
\|__| \/__/ \/__/ \/__/ \/__/
Rocco is a quick-and-dirty, literate-programming-style documentation
generator for Ruby. See the Rocco generated docs for more information:
<http://rtomayko.github.com/rocco/>
Rocco is a port of, and borrows heavily from, Docco -- the original
quick-and-dirty, hundred-line-long, literate-programming-style
documentation generator in CoffeeScript:
<http://jashkenas.github.com/docco/>

115
Rakefile Normal file
View File

@ -0,0 +1,115 @@
$LOAD_PATH.unshift 'lib'
require 'rake/testtask'
require 'rake/clean'
task :default => [:sup, :docs, :test]
desc 'Holla'
task :sup do
verbose do
lines = File.read('README').split("\n")[0,12]
lines.map! { |line| line[15..-1] }
puts lines.join("\n")
end
end
desc 'Run tests (default)'
Rake::TestTask.new(:test) do |t|
t.test_files = FileList['test/suite.rb']
t.ruby_opts = ['-rubygems'] if defined? Gem
end
# Bring in Rocco tasks
require 'rocco/tasks'
Rocco::make 'docs/'
desc 'Build rocco docs'
task :docs => :rocco
directory 'docs/'
desc 'Build docs and open in browser for the reading'
task :read => :docs do
sh 'open docs/lib/rocco.html'
end
# Make index.html a copy of rocco.html
file 'docs/index.html' => 'docs/lib/rocco.html' do |f|
cp 'docs/lib/rocco.html', 'docs/index.html', :preserve => true
end
task :docs => 'docs/index.html'
CLEAN.include 'docs/index.html'
# Alias for docs task
task :doc => :docs
# GITHUB PAGES ===============================================================
desc 'Update gh-pages branch'
task :pages => ['docs/.git', :docs] do
rev = `git rev-parse --short HEAD`.strip
Dir.chdir 'docs' do
sh "git add *.html"
sh "git commit -m 'rebuild pages from #{rev}'" do |ok,res|
if ok
verbose { puts "gh-pages updated" }
sh "git push -q o HEAD:gh-pages"
end
end
end
end
# Update the pages/ directory clone
file 'docs/.git' => ['docs/', '.git/refs/heads/gh-pages'] do |f|
sh "cd docs && git init -q && git remote add o ../.git" if !File.exist?(f.name)
sh "cd docs && git fetch -q o && git reset -q --hard o/gh-pages && touch ."
end
CLOBBER.include 'docs/.git'
# PACKAGING =================================================================
if defined?(Gem)
SPEC = eval(File.read('rocco.gemspec'))
def package(ext='')
"pkg/rocco-#{SPEC.version}" + ext
end
desc 'Build packages'
task :package => %w[.gem .tar.gz].map {|e| package(e)}
desc 'Build and install as local gem'
task :install => package('.gem') do
sh "gem install #{package('.gem')}"
end
directory 'pkg/'
file package('.gem') => %w[pkg/ rocco.gemspec] + SPEC.files do |f|
sh "gem build rocco.gemspec"
mv File.basename(f.name), f.name
end
file package('.tar.gz') => %w[pkg/] + SPEC.files do |f|
sh "git archive --format=tar HEAD | gzip > #{f.name}"
end
end
# GEMSPEC ===================================================================
file 'rocco.gemspec' => FileList['{lib,test,bin}/**','Rakefile'] do |f|
version = File.read('lib/rocco.rb')[/VERSION = '(.*)'/] && $1
date = Time.now.strftime("%Y-%m-%d")
spec = File.
read(f.name).
sub(/s\.version\s*=\s*'.*'/, "s.version = '#{version}'")
parts = spec.split(" # = MANIFEST =\n")
files = `git ls-files`.
split("\n").sort.reject{ |file| file =~ /^\./ }.
map{ |file| " #{file}" }.join("\n")
parts[1] = " s.files = %w[\n#{files}\n ]\n"
spec = parts.join(" # = MANIFEST =\n")
spec.sub!(/s.date = '.*'/, "s.date = '#{date}'")
File.open(f.name, 'w') { |io| io.write(spec) }
puts "#{f.name} #{version} (#{date})"
end

100
bin/rocco Executable file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env ruby
#/ Usage: rocco [-l <lang>] [-c <chars>] [-o <dir>] <file>...
#/ Generate literate-programming-style documentation for Ruby source <file>s.
#/
#/ Options:
#/ -l, --language=<lang> The Pygments lexer to use to highlight code
#/ -c, --comment-chars=<chars>
#/ The string to recognize as a comment marker
#/ -o, --output=<dir> Directory where generated HTML files are written
#/ -t, --template=<path> The file to use as template when rendering HTML
#/ -d, --docblocks Parse Docblock @annotations in comments
#/ --help Show this help message
require 'optparse'
require 'fileutils'
# Write usage message to stdout and exit.
def usage(stream=$stderr, status=1)
stream.puts File.readlines(__FILE__).
grep(/^#\//).
map { |line| line.sub(/^#. ?/, '') }.
join
exit status
end
# Like `Kernel#abort` but writes a note encouraging the user to consult
# `rocco --help` for more information.
def abort_with_note(message=nil)
$stderr.puts message if message
abort "See `rocco --help' for usage information."
end
# Parse command line options, aborting if anything goes wrong.
output_dir = '.'
sources = []
options = {}
ARGV.options { |o|
o.program_name = File.basename($0)
o.on("-o", "--output=DIR") { |dir| output_dir = dir }
o.on("-l", "--language=LANG") { |lang| options[:language] = lang }
o.on("-c", "--comment-chars=CHARS") { |chars| options[:comment_chars] = Regexp.escape(chars) }
o.on("-t", "--template=TEMPLATE") { |template| options[:template_file] = template }
o.on("-d", "--docblocks") { options[:docblocks] = true }
o.on_tail("-h", "--help") { usage($stdout, 0) }
o.parse!
} or abort_with_note
# Use http://pygments.appspot.com in case `pygmentize(1)` isn't available.
if ! ENV['PATH'].split(':').any? { |dir| File.exist?("#{dir}/pygmentize") }
unless options[:webservice]
$stderr.puts "pygmentize not in PATH; using pygments.appspot.com instead"
options[:webservice] = true
end
end
# Eat sources from ARGV.
sources << ARGV.shift while ARGV.any?
# Make sure we have some files to work with.
if sources.empty?
abort_with_note "#{File.basename($0)}: no input <file>s given"
end
# What a fucking mess. Most of this is duplicated in rocco.rb too.
libdir = File.expand_path('../../lib', __FILE__).sub(/^#{Dir.pwd}\//, '')
begin
require 'rdiscount'
require 'rocco'
rescue LoadError
case $!.to_s
when /rdiscount/
if !defined?(Gem)
warn "warn: #$!. trying again with rubygems"
require 'rubygems'
retry
else
require 'bluecloth'
Markdown = BlueCloth
$LOADED_FEATURES << 'rdiscount.rb'
retry
end
when /rocco/
if !$:.include?(libdir)
warn "warn: #$!. trying again with #{libdir} on load path"
$:.unshift(libdir)
retry
end
end
raise
end
# Run each file through Rocco and write output.
sources.each do |filename|
rocco = Rocco.new(filename, sources, options)
dest = filename.sub(Regexp.new("#{File.extname(filename)}$"),".html")
dest = File.join(output_dir, dest) if output_dir != '.'
puts "rocco: #{filename} -> #{dest}"
FileUtils.mkdir_p File.dirname(dest)
File.open(dest, 'wb') { |fd| fd.write(rocco.to_html) }
end

View File

@ -1,822 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>rocco.rb</title>
<link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
</head>
<body>
<div id='container'>
<div id="background"></div>
<div id="jump_to">
Jump To &hellip;
<div id="jump_wrapper">
<div id="jump_page">
<a class="source" href="rocco.html">rocco.rb</a>
<a class="source" href="rocco/layout.html">layout.rb</a>
<a class="source" href="rocco/tasks.html">tasks.rb</a>
</div>
</div>
</div>
<table cellspacing=0 cellpadding=0>
<thead>
<tr>
<th class=docs><h1>rocco.rb</h1></th>
<th class=code></th>
</tr>
</thead>
<tbody>
<tr id='section-1'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-1">&#182;</a>
</div>
<p><strong>Rocco</strong> is a Ruby port of <a href="http://jashkenas.github.com/docco/">Docco</a>, the quick-and-dirty,
hundred-line-long, literate-programming-style documentation generator.</p>
<p>Rocco reads Ruby source files and produces annotated source documentation
in HTML format. Comments are formatted with <a href="http://daringfireball.net/projects/markdown/">Markdown</a> and presented
alongside syntax highlighted code so as to give an annotation effect.
This page is the result of running Rocco against <a href="http://github.com/rtomayko/rocco/blob/master/lib/rocco.rb#commit">its own source file</a>.</p>
<p>Most of this was written while waiting for <a href="http://nodejs.org/">node.js</a> to build (so I
could use Docco!). Docco&rsquo;s gorgeous HTML and CSS are taken verbatim.
The main difference is that Rocco is written in Ruby instead of
<a href="http://coffeescript.org/">CoffeeScript</a> and may be a bit easier to obtain and install in
existing Ruby environments or where node doesn&rsquo;t run yet.</p>
<p>Install Rocco with Rubygems:</p>
<pre><code>gem install rocco
</code></pre>
<p>Once installed, the <code>rocco</code> command can be used to generate documentation
for a set of Ruby source files:</p>
<pre><code>rocco lib/*.rb
</code></pre>
<p>The HTML files are written to the current working directory.</p>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-Prerequisites'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-Prerequisites">&#182;</a>
</div>
<h3>Prerequisites</h3>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-3'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-3">&#182;</a>
</div>
<p>We&rsquo;ll need a Markdown library. <a href="http://github.com/rtomayko/rdiscount">RDiscount</a>, if we&rsquo;re lucky. Otherwise,
issue a warning and fall back on using BlueCloth.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="k">begin</span>
<span class="nb">require</span> <span class="s1">&#39;rdiscount&#39;</span>
<span class="k">rescue</span> <span class="no">LoadError</span> <span class="o">=&gt;</span> <span class="n">boom</span>
<span class="nb">warn</span> <span class="s2">&quot;WARNING: </span><span class="si">#{</span><span class="n">boom</span><span class="si">}</span><span class="s2">. Trying bluecloth.&quot;</span>
<span class="nb">require</span> <span class="s1">&#39;bluecloth&#39;</span>
<span class="no">Markdown</span> <span class="o">=</span> <span class="no">BlueCloth</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-4'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-4">&#182;</a>
</div>
<p>We use <a href="http://defunkt.github.com/mustache/">{{ mustache }}</a> for
HTML templating.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">&#39;mustache&#39;</span></pre></div>
</td>
</tr>
<tr id='section-5'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-5">&#182;</a>
</div>
<p>We use <code>Net::HTTP</code> to highlight code via <a href="http://pygments.appspot.com">http://pygments.appspot.com</a></p>
</td>
<td class=code>
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">&#39;net/http&#39;</span></pre></div>
</td>
</tr>
<tr id='section-6'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-6">&#182;</a>
</div>
<p>Code is run through <a href="http://pygments.org/">Pygments</a> for syntax
highlighting. If it&rsquo;s not installed, locally, use a webservice.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="kp">include</span> <span class="no">FileTest</span>
<span class="k">if</span> <span class="o">!</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PATH&#39;</span><span class="o">].</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;:&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">any?</span> <span class="p">{</span> <span class="o">|</span><span class="n">dir</span><span class="o">|</span> <span class="n">executable?</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">#{</span><span class="n">dir</span><span class="si">}</span><span class="s2">/pygmentize&quot;</span><span class="p">)</span> <span class="p">}</span>
<span class="nb">warn</span> <span class="s2">&quot;WARNING: Pygments not found. Using webservice.&quot;</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-Public_Interface'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-Public_Interface">&#182;</a>
</div>
<h3>Public Interface</h3>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-8'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-8">&#182;</a>
</div>
<p><code>Rocco.new</code> takes a source <code>filename</code>, an optional list of source filenames
for other documentation sources, an <code>options</code> hash, and an optional <code>block</code>.
The <code>options</code> hash respects three members:</p>
<ul>
<li><p><code>:language</code>: specifies which Pygments lexer to use if one can&rsquo;t be
auto-detected from the filename. <em>Defaults to <code>ruby</code></em>.</p></li>
<li><p><code>:comment_chars</code>, which specifies the comment characters of the
target language. <em>Defaults to <code>#</code></em>.</p></li>
<li><p><code>:template_file</code>, which specifies a external template file to use
when rendering the final, highlighted file via Mustache. <em>Defaults
to <code>nil</code> (that is, Mustache will use <code>./lib/rocco/layout.mustache</code>)</em>.</p></li>
</ul>
</td>
<td class=code>
<div class='highlight'><pre><span class="k">class</span> <span class="nc">Rocco</span>
<span class="no">VERSION</span> <span class="o">=</span> <span class="s1">&#39;0.6&#39;</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">sources</span><span class="o">=[]</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{},</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
<span class="vi">@file</span> <span class="o">=</span> <span class="n">filename</span>
<span class="vi">@sources</span> <span class="o">=</span> <span class="n">sources</span></pre></div>
</td>
</tr>
<tr id='section-9'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-9">&#182;</a>
</div>
<p>When <code>block</code> is given, it must read the contents of the file using
whatever means necessary and return it as a string. With no <code>block</code>,
the file is read to retrieve data.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="vi">@data</span> <span class="o">=</span>
<span class="k">if</span> <span class="nb">block_given?</span>
<span class="k">yield</span>
<span class="k">else</span>
<span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">defaults</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">:language</span> <span class="o">=&gt;</span> <span class="s1">&#39;ruby&#39;</span><span class="p">,</span>
<span class="ss">:comment_chars</span> <span class="o">=&gt;</span> <span class="s1">&#39;#&#39;</span><span class="p">,</span>
<span class="ss">:template_file</span> <span class="o">=&gt;</span> <span class="kp">nil</span>
<span class="p">}</span>
<span class="vi">@options</span> <span class="o">=</span> <span class="n">defaults</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="n">options</span><span class="p">)</span></pre></div>
</td>
</tr>
<tr id='section-10'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-10">&#182;</a>
</div>
<p>If we detect a language</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">if</span> <span class="n">detect_language</span><span class="p">()</span> <span class="o">!=</span> <span class="s2">&quot;text&quot;</span></pre></div>
</td>
</tr>
<tr id='section-11'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-11">&#182;</a>
</div>
<p>then assign the detected language to <code>:language</code>, and look for
comment characters based on that language</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]</span> <span class="o">=</span> <span class="n">detect_language</span><span class="p">()</span>
<span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span> <span class="o">=</span> <span class="n">generate_comment_chars</span><span class="p">()</span></pre></div>
</td>
</tr>
<tr id='section-12'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-12">&#182;</a>
</div>
<p>If we didn&rsquo;t detect a language, but the user provided one, use it
to look around for comment characters to override the default.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">elsif</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]</span> <span class="o">!=</span> <span class="n">defaults</span><span class="o">[</span><span class="ss">:language</span><span class="o">]</span>
<span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span> <span class="o">=</span> <span class="n">generate_comment_chars</span><span class="p">()</span></pre></div>
</td>
</tr>
<tr id='section-13'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-13">&#182;</a>
</div>
<p>If neither is true, then convert the default comment character string
into the comment_char syntax (we&rsquo;ll discuss that syntax in detail when
we get to <code>generate_comment_chars()</code> in a moment.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">else</span>
<span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="kp">nil</span>
<span class="p">}</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-14'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-14">&#182;</a>
</div>
<p>Turn <code>:comment_chars</code> into a regex matching a series of spaces, the
<code>:comment_chars</code> string, and the an optional space. We&rsquo;ll use that
to detect single-line comments.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="vi">@comment_pattern</span> <span class="o">=</span>
<span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&quot;^</span><span class="se">\\</span><span class="s2">s*</span><span class="si">#{</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:single</span><span class="o">]</span><span class="si">}</span><span class="se">\s</span><span class="s2">?&quot;</span><span class="p">)</span></pre></div>
</td>
</tr>
<tr id='section-15'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-15">&#182;</a>
</div>
<p><code>parse()</code> the file contents stored in <code>@data</code>. Run the result through
<code>split()</code> and that result through <code>highlight()</code> to generate the final
section list.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="vi">@sections</span> <span class="o">=</span> <span class="n">highlight</span><span class="p">(</span><span class="nb">split</span><span class="p">(</span><span class="n">parse</span><span class="p">(</span><span class="vi">@data</span><span class="p">)))</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-16'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-16">&#182;</a>
</div>
<p>The filename as given to <code>Rocco.new</code>.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:file</span></pre></div>
</td>
</tr>
<tr id='section-17'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-17">&#182;</a>
</div>
<p>The merged options array</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:options</span></pre></div>
</td>
</tr>
<tr id='section-18'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-18">&#182;</a>
</div>
<p>A list of two-tuples representing each <em>section</em> of the source file. Each
item in the list has the form: <code>[docs_html, code_html]</code>, where both
elements are strings containing the documentation and source code HTML,
respectively.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:sections</span></pre></div>
</td>
</tr>
<tr id='section-19'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-19">&#182;</a>
</div>
<p>A list of all source filenames included in the documentation set. Useful
for building an index of other files.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:sources</span></pre></div>
</td>
</tr>
<tr id='section-20'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-20">&#182;</a>
</div>
<p>Generate HTML output for the entire document.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="nb">require</span> <span class="s1">&#39;rocco/layout&#39;</span>
<span class="k">def</span> <span class="nf">to_html</span>
<span class="no">Rocco</span><span class="o">::</span><span class="no">Layout</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:template_file</span><span class="o">]</span><span class="p">)</span><span class="o">.</span><span class="n">render</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-Helper_Functions'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-Helper_Functions">&#182;</a>
</div>
<h2>Helper Functions</h2>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-22'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-22">&#182;</a>
</div>
<p>Returns <code>true</code> if <code>pygmentize</code> is available locally, <code>false</code> otherwise.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">pygmentize?</span>
<span class="vi">@_pygmentize</span> <span class="o">||=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PATH&#39;</span><span class="o">].</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;:&#39;</span><span class="p">)</span><span class="o">.</span>
<span class="n">any?</span> <span class="p">{</span> <span class="o">|</span><span class="n">dir</span><span class="o">|</span> <span class="n">executable?</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">#{</span><span class="n">dir</span><span class="si">}</span><span class="s2">/pygmentize&quot;</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-23'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-23">&#182;</a>
</div>
<p>If <code>pygmentize</code> is available, we can use it to autodetect a file&rsquo;s
language based on its filename. Filenames without extensions, or with
extensions that <code>pygmentize</code> doesn&rsquo;t understand will return <code>text</code>.
We&rsquo;ll also return <code>text</code> if <code>pygmentize</code> isn&rsquo;t available.</p>
<p>We&rsquo;ll memoize the result, as we&rsquo;ll call this a few times.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">detect_language</span>
<span class="vi">@_language</span> <span class="o">||=</span>
<span class="k">if</span> <span class="n">pygmentize?</span>
<span class="sx">%x[pygmentize -N </span><span class="si">#{</span><span class="vi">@file</span><span class="si">}</span><span class="sx">]</span><span class="o">.</span><span class="n">strip!</span>
<span class="k">else</span>
<span class="s2">&quot;text&quot;</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-24'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-24">&#182;</a>
</div>
<p>Given a file&rsquo;s language, we should be able to autopopulate the
<code>comment_chars</code> variables for single-line comments. If we don&rsquo;t
have comment characters on record for a given language, we&rsquo;ll
use the user-provided <code>:comment_char</code> option (which defaults to
<code>#</code>).</p>
<p>Comment characters are listed as:</p>
<pre><code>{ :single =&gt; "//",
:multi_start =&gt; "/**",
:multi_middle =&gt; "*",
:multi_end =&gt; "*/" }
</code></pre>
<p><code>:single</code> denotes the leading character of a single-line comment.
<code>:multi_start</code> denotes the string that should appear alone on a
line of code to begin a block of documentation. <code>:multi_middle</code>
denotes the leading character of block comment content, and
<code>:multi_end</code> is the string that ought appear alone on a line to
close a block of documentation. That is:</p>
<pre><code>/** [:multi][:start]
* [:multi][:middle]
...
* [:multi][:middle]
*/ [:multi][:end]
</code></pre>
<p>If a language only has one type of comment, the missing type
should be assigned <code>nil</code>.</p>
<p>At the moment, we&rsquo;re only returning <code>:single</code>. Consider this
groundwork for block comment parsing.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="no">COMMENT_STYLES</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;bash&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;#&quot;</span><span class="p">,</span> <span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="kp">nil</span> <span class="p">},</span>
<span class="s2">&quot;c&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;//&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s2">&quot;/**&quot;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="s2">&quot;*&quot;</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s2">&quot;*/&quot;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;coffee-script&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;#&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s2">&quot;###&quot;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="kp">nil</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s2">&quot;###&quot;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;cpp&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;//&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s2">&quot;/**&quot;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="s2">&quot;*&quot;</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s2">&quot;*/&quot;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;css&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="kp">nil</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s2">&quot;/**&quot;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="s2">&quot;*&quot;</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s2">&quot;*/&quot;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;java&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;//&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s2">&quot;/**&quot;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="s2">&quot;*&quot;</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s2">&quot;*/&quot;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;js&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;//&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s2">&quot;/**&quot;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="s2">&quot;*&quot;</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s2">&quot;*/&quot;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;lua&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;--&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="kp">nil</span>
<span class="p">},</span>
<span class="s2">&quot;python&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;#&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s1">&#39;&quot;&quot;&quot;&#39;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="kp">nil</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s1">&#39;&quot;&quot;&quot;&#39;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;rb&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;#&quot;</span><span class="p">,</span>
<span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:start</span> <span class="o">=&gt;</span> <span class="s1">&#39;=begin&#39;</span><span class="p">,</span> <span class="ss">:middle</span> <span class="o">=&gt;</span> <span class="kp">nil</span><span class="p">,</span> <span class="ss">:end</span> <span class="o">=&gt;</span> <span class="s1">&#39;=end&#39;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="s2">&quot;scheme&quot;</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="ss">:single</span> <span class="o">=&gt;</span> <span class="s2">&quot;;;&quot;</span><span class="p">,</span> <span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="kp">nil</span> <span class="p">},</span>
<span class="p">}</span>
<span class="k">def</span> <span class="nf">generate_comment_chars</span>
<span class="vi">@_commentchar</span> <span class="o">||=</span>
<span class="k">if</span> <span class="no">COMMENT_STYLES</span><span class="o">[</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]]</span>
<span class="no">COMMENT_STYLES</span><span class="o">[</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]]</span>
<span class="k">else</span>
<span class="p">{</span> <span class="ss">:single</span> <span class="o">=&gt;</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span><span class="p">,</span> <span class="ss">:multi</span> <span class="o">=&gt;</span> <span class="kp">nil</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-Internal_Parsing_and_Highlighting'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-Internal_Parsing_and_Highlighting">&#182;</a>
</div>
<h2>Internal Parsing and Highlighting</h2>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-26'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-26">&#182;</a>
</div>
<p>Parse the raw file data into a list of two-tuples. Each tuple has the
form <code>[docs, code]</code> where both elements are arrays containing the
raw lines parsed from the input file, comment characters stripped.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="n">sections</span> <span class="o">=</span> <span class="o">[]</span>
<span class="n">docs</span><span class="p">,</span> <span class="n">code</span> <span class="o">=</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span>
<span class="n">lines</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span></pre></div>
</td>
</tr>
<tr id='section-27'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-27">&#182;</a>
</div>
<p>The first line is ignored if it is a shebang line. We also ignore the
PEP 263 encoding information in python sourcefiles, and the similar ruby
1.9 syntax.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">lines</span><span class="o">.</span><span class="n">shift</span> <span class="k">if</span> <span class="n">lines</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=~</span> <span class="sr">/^\#\!/</span>
<span class="n">lines</span><span class="o">.</span><span class="n">shift</span> <span class="k">if</span> <span class="n">lines</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=~</span> <span class="sr">/coding[:=]\s*[-\w.]+/</span> <span class="o">&amp;&amp;</span>
<span class="o">[</span> <span class="s2">&quot;python&quot;</span><span class="p">,</span> <span class="s2">&quot;rb&quot;</span> <span class="o">].</span><span class="n">include?</span><span class="p">(</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]</span><span class="p">)</span></pre></div>
</td>
</tr>
<tr id='section-28'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-28">&#182;</a>
</div>
<p>To detect both block comments and single-line comments, we&rsquo;ll set
up a tiny state machine, and loop through each line of the file.
This requires an <code>in_comment_block</code> boolean, and a few regular
expressions for line tests.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">in_comment_block</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">single_line_comment</span><span class="p">,</span> <span class="n">block_comment_start</span><span class="p">,</span> <span class="n">block_comment_mid</span><span class="p">,</span> <span class="n">block_comment_end</span> <span class="o">=</span>
<span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="kp">nil</span>
<span class="k">if</span> <span class="ow">not</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:single</span><span class="o">].</span><span class="n">nil?</span>
<span class="n">single_line_comment</span> <span class="o">=</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&quot;^</span><span class="se">\\</span><span class="s2">s*</span><span class="si">#{</span><span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:single</span><span class="o">]</span><span class="p">)</span><span class="si">}</span><span class="se">\\</span><span class="s2">s?&quot;</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">if</span> <span class="ow">not</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:multi</span><span class="o">].</span><span class="n">nil?</span>
<span class="n">block_comment_start</span> <span class="o">=</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&quot;^</span><span class="se">\\</span><span class="s2">s*</span><span class="si">#{</span><span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:multi</span><span class="o">][</span><span class="ss">:start</span><span class="o">]</span><span class="p">)</span><span class="si">}</span><span class="se">\\</span><span class="s2">s*$&quot;</span><span class="p">)</span>
<span class="n">block_comment_end</span> <span class="o">=</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&quot;^</span><span class="se">\\</span><span class="s2">s*</span><span class="si">#{</span><span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:multi</span><span class="o">][</span><span class="ss">:end</span><span class="o">]</span><span class="p">)</span><span class="si">}</span><span class="se">\\</span><span class="s2">s*$&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:multi</span><span class="o">][</span><span class="ss">:middle</span><span class="o">]</span>
<span class="n">block_comment_mid</span> <span class="o">=</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&quot;^</span><span class="se">\\</span><span class="s2">s*</span><span class="si">#{</span><span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:multi</span><span class="o">][</span><span class="ss">:middle</span><span class="o">]</span><span class="p">)</span><span class="si">}</span><span class="se">\\</span><span class="s2">s?&quot;</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">lines</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span></pre></div>
</td>
</tr>
<tr id='section-29'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-29">&#182;</a>
</div>
<p>If we&rsquo;re currently in a comment block, check whether the line matches
the <em>end</em> of a comment block.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">if</span> <span class="n">in_comment_block</span>
<span class="k">if</span> <span class="n">block_comment_end</span> <span class="o">&amp;&amp;</span> <span class="n">line</span><span class="o">.</span><span class="n">match</span><span class="p">(</span> <span class="n">block_comment_end</span> <span class="p">)</span>
<span class="n">in_comment_block</span> <span class="o">=</span> <span class="kp">false</span>
<span class="k">else</span>
<span class="n">docs</span> <span class="o">&lt;&lt;</span> <span class="n">line</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span> <span class="n">block_comment_mid</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span> <span class="p">)</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-30'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-30">&#182;</a>
</div>
<p>Otherwise, check whether the line matches the beginning of a block, or
a single-line comment all on it&rsquo;s lonesome. In either case, if there&rsquo;s
code, start a new section</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">else</span>
<span class="k">if</span> <span class="n">block_comment_start</span> <span class="o">&amp;&amp;</span> <span class="n">line</span><span class="o">.</span><span class="n">match</span><span class="p">(</span> <span class="n">block_comment_start</span> <span class="p">)</span>
<span class="n">in_comment_block</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">if</span> <span class="n">code</span><span class="o">.</span><span class="n">any?</span>
<span class="n">sections</span> <span class="o">&lt;&lt;</span> <span class="o">[</span><span class="n">docs</span><span class="p">,</span> <span class="n">code</span><span class="o">]</span>
<span class="n">docs</span><span class="p">,</span> <span class="n">code</span> <span class="o">=</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span>
<span class="k">end</span>
<span class="k">elsif</span> <span class="n">single_line_comment</span> <span class="o">&amp;&amp;</span> <span class="n">line</span><span class="o">.</span><span class="n">match</span><span class="p">(</span> <span class="n">single_line_comment</span> <span class="p">)</span>
<span class="k">if</span> <span class="n">code</span><span class="o">.</span><span class="n">any?</span>
<span class="n">sections</span> <span class="o">&lt;&lt;</span> <span class="o">[</span><span class="n">docs</span><span class="p">,</span> <span class="n">code</span><span class="o">]</span>
<span class="n">docs</span><span class="p">,</span> <span class="n">code</span> <span class="o">=</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span>
<span class="k">end</span>
<span class="n">docs</span> <span class="o">&lt;&lt;</span> <span class="n">line</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span> <span class="n">single_line_comment</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span> <span class="p">)</span>
<span class="k">else</span>
<span class="n">code</span> <span class="o">&lt;&lt;</span> <span class="n">line</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">sections</span> <span class="o">&lt;&lt;</span> <span class="o">[</span><span class="n">docs</span><span class="p">,</span> <span class="n">code</span><span class="o">]</span> <span class="k">if</span> <span class="n">docs</span><span class="o">.</span><span class="n">any?</span> <span class="o">||</span> <span class="n">code</span><span class="o">.</span><span class="n">any?</span>
<span class="n">normalize_leading_spaces</span><span class="p">(</span> <span class="n">sections</span> <span class="p">)</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-31'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-31">&#182;</a>
</div>
<p>Normalizes documentation whitespace by checking for leading whitespace,
removing it, and then removing the same amount of whitespace from each
succeeding line. That is:</p>
<pre><code>def func():
"""
Comment 1
Comment 2
"""
print "omg!"
</code></pre>
<p>should yield a comment block of <code>Comment 1\nComment 2</code> and code of
<code>def func():\n print "omg!"</code></p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">normalize_leading_spaces</span><span class="p">(</span> <span class="n">sections</span> <span class="p">)</span>
<span class="n">sections</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">section</span><span class="o">|</span>
<span class="k">if</span> <span class="n">section</span><span class="o">.</span><span class="n">any?</span> <span class="o">&amp;&amp;</span> <span class="n">section</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">any?</span>
<span class="n">leading_space</span> <span class="o">=</span> <span class="n">section</span><span class="o">[</span><span class="mi">0</span><span class="o">][</span><span class="mi">0</span><span class="o">].</span><span class="n">match</span><span class="p">(</span> <span class="s2">&quot;^</span><span class="se">\s</span><span class="s2">+&quot;</span> <span class="p">)</span>
<span class="k">if</span> <span class="n">leading_space</span>
<span class="n">section</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span>
<span class="n">section</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">map</span><span class="p">{</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span> <span class="n">line</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr"> /^</span><span class="si">#{</span><span class="n">leading_space</span><span class="o">.</span><span class="n">to_s</span><span class="si">}</span><span class="sr">/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span> <span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">section</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-32'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-32">&#182;</a>
</div>
<p>Take the list of paired <em>sections</em> two-tuples and split into two
separate lists: one holding the comments with leaders removed and
one with the code blocks.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">split</span><span class="p">(</span><span class="n">sections</span><span class="p">)</span>
<span class="n">docs_blocks</span><span class="p">,</span> <span class="n">code_blocks</span> <span class="o">=</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span>
<span class="n">sections</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">docs</span><span class="p">,</span><span class="n">code</span><span class="o">|</span>
<span class="n">docs_blocks</span> <span class="o">&lt;&lt;</span> <span class="n">docs</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">code_blocks</span> <span class="o">&lt;&lt;</span> <span class="n">code</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span>
<span class="n">tabs</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sr">/^(\t+)/</span><span class="p">)</span>
<span class="n">tabs</span> <span class="p">?</span> <span class="n">line</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/^\t+/</span><span class="p">,</span> <span class="s1">&#39; &#39;</span> <span class="o">*</span> <span class="n">tabs</span><span class="o">.</span><span class="n">captures</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">length</span><span class="p">)</span> <span class="p">:</span> <span class="n">line</span>
<span class="k">end</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">end</span>
<span class="o">[</span><span class="n">docs_blocks</span><span class="p">,</span> <span class="n">code_blocks</span><span class="o">]</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-33'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-33">&#182;</a>
</div>
<p>Take the result of <code>split</code> and apply Markdown formatting to comments and
syntax highlighting to source code.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">highlight</span><span class="p">(</span><span class="n">blocks</span><span class="p">)</span>
<span class="n">docs_blocks</span><span class="p">,</span> <span class="n">code_blocks</span> <span class="o">=</span> <span class="n">blocks</span></pre></div>
</td>
</tr>
<tr id='section-34'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-34">&#182;</a>
</div>
<p>Combine all docs blocks into a single big markdown document with section
dividers and run through the Markdown processor. Then split it back out
into separate sections.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">markdown</span> <span class="o">=</span> <span class="n">docs_blocks</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n\n</span><span class="s2">##### DIVIDER</span><span class="se">\n\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">docs_html</span> <span class="o">=</span> <span class="no">Markdown</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">markdown</span><span class="p">,</span> <span class="ss">:smart</span><span class="p">)</span><span class="o">.</span>
<span class="n">to_html</span><span class="o">.</span>
<span class="nb">split</span><span class="p">(</span><span class="sr">/\n*&lt;h5&gt;DIVIDER&lt;\/h5&gt;\n*/m</span><span class="p">)</span></pre></div>
</td>
</tr>
<tr id='section-35'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-35">&#182;</a>
</div>
<p>Combine all code blocks into a single big stream with section dividers and
run through either <code>pygmentize(1)</code> or <a href="http://pygments.appspot.com">http://pygments.appspot.com</a></p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">span</span><span class="p">,</span> <span class="n">espan</span> <span class="o">=</span> <span class="s1">&#39;&lt;span class=&quot;c.?&quot;&gt;&#39;</span><span class="p">,</span> <span class="s1">&#39;&lt;/span&gt;&#39;</span>
<span class="k">if</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:single</span><span class="o">]</span>
<span class="n">front</span> <span class="o">=</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:single</span><span class="o">]</span>
<span class="n">divider_input</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="se">\n\n</span><span class="si">#{</span><span class="n">front</span><span class="si">}</span><span class="s2"> DIVIDER</span><span class="se">\n\n</span><span class="s2">&quot;</span>
<span class="n">divider_output</span> <span class="o">=</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
<span class="o">[</span> <span class="s2">&quot;</span><span class="se">\\</span><span class="s2">n*&quot;</span><span class="p">,</span>
<span class="n">span</span><span class="p">,</span>
<span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">front</span><span class="p">),</span>
<span class="s1">&#39; DIVIDER&#39;</span><span class="p">,</span>
<span class="n">espan</span><span class="p">,</span>
<span class="s2">&quot;</span><span class="se">\\</span><span class="s2">n*&quot;</span>
<span class="o">].</span><span class="n">join</span><span class="p">,</span> <span class="no">Regexp</span><span class="o">::</span><span class="no">MULTILINE</span>
<span class="p">)</span>
<span class="k">else</span>
<span class="n">front</span> <span class="o">=</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:multi</span><span class="o">][</span><span class="ss">:start</span><span class="o">]</span>
<span class="n">back</span> <span class="o">=</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">][</span><span class="ss">:multi</span><span class="o">][</span><span class="ss">:end</span><span class="o">]</span>
<span class="n">divider_input</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="se">\n\n</span><span class="si">#{</span><span class="n">front</span><span class="si">}</span><span class="se">\n</span><span class="s2">DIVIDER</span><span class="se">\n</span><span class="si">#{</span><span class="n">back</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">&quot;</span>
<span class="n">divider_output</span> <span class="o">=</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
<span class="o">[</span> <span class="s2">&quot;</span><span class="se">\\</span><span class="s2">n*&quot;</span><span class="p">,</span>
<span class="n">span</span><span class="p">,</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">front</span><span class="p">),</span> <span class="n">espan</span><span class="p">,</span>
<span class="s2">&quot;</span><span class="se">\\</span><span class="s2">n&quot;</span><span class="p">,</span>
<span class="n">span</span><span class="p">,</span> <span class="s2">&quot;DIVIDER&quot;</span><span class="p">,</span> <span class="n">espan</span><span class="p">,</span>
<span class="s2">&quot;</span><span class="se">\\</span><span class="s2">n&quot;</span><span class="p">,</span>
<span class="n">span</span><span class="p">,</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">back</span><span class="p">),</span> <span class="n">espan</span><span class="p">,</span>
<span class="s2">&quot;</span><span class="se">\\</span><span class="s2">n*&quot;</span>
<span class="o">].</span><span class="n">join</span><span class="p">,</span> <span class="no">Regexp</span><span class="o">::</span><span class="no">MULTILINE</span>
<span class="p">)</span>
<span class="k">end</span>
<span class="n">code_stream</span> <span class="o">=</span> <span class="n">code_blocks</span><span class="o">.</span><span class="n">join</span><span class="p">(</span> <span class="n">divider_input</span> <span class="p">)</span>
<span class="n">code_html</span> <span class="o">=</span>
<span class="k">if</span> <span class="n">pygmentize?</span>
<span class="n">highlight_pygmentize</span><span class="p">(</span><span class="n">code_stream</span><span class="p">)</span>
<span class="k">else</span>
<span class="n">highlight_webservice</span><span class="p">(</span><span class="n">code_stream</span><span class="p">)</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-36'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-36">&#182;</a>
</div>
<p>Do some post-processing on the pygments output to split things back
into sections and remove partial <code>&lt;pre&gt;</code> blocks.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">code_html</span> <span class="o">=</span> <span class="n">code_html</span><span class="o">.</span>
<span class="nb">split</span><span class="p">(</span><span class="n">divider_output</span><span class="p">)</span><span class="o">.</span>
<span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">code</span><span class="o">|</span> <span class="n">code</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\n?&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;/m</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span>
<span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">code</span><span class="o">|</span> <span class="n">code</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\n?&lt;\/pre&gt;&lt;\/div&gt;\n/m</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">}</span></pre></div>
</td>
</tr>
<tr id='section-37'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-37">&#182;</a>
</div>
<p>Lastly, combine the docs and code lists back into a list of two-tuples.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">docs_html</span><span class="o">.</span><span class="n">zip</span><span class="p">(</span><span class="n">code_html</span><span class="p">)</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-38'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-38">&#182;</a>
</div>
<p>We <code>popen</code> a read/write pygmentize process in the parent and
then fork off a child process to write the input.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">highlight_pygmentize</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
<span class="n">code_html</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="nb">open</span><span class="p">(</span><span class="s2">&quot;|pygmentize -l </span><span class="si">#{</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]</span><span class="si">}</span><span class="s2"> -O encoding=utf-8 -f html&quot;</span><span class="p">,</span> <span class="s1">&#39;r+&#39;</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">fd</span><span class="o">|</span>
<span class="n">pid</span> <span class="o">=</span>
<span class="nb">fork</span> <span class="p">{</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_read</span>
<span class="n">fd</span><span class="o">.</span><span class="n">write</span> <span class="n">code</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_write</span>
<span class="nb">exit!</span>
<span class="p">}</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_write</span>
<span class="n">code_html</span> <span class="o">=</span> <span class="n">fd</span><span class="o">.</span><span class="n">read</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_read</span>
<span class="no">Process</span><span class="o">.</span><span class="n">wait</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">code_html</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-39'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-39">&#182;</a>
</div>
<p>Pygments is not one of those things that&rsquo;s trivial for a ruby user to install,
so we&rsquo;ll fall back on a webservice to highlight the code if it isn&rsquo;t available.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">highlight_webservice</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
<span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">post_form</span><span class="p">(</span>
<span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">&#39;http://pygments.appspot.com/&#39;</span><span class="p">),</span>
<span class="p">{</span><span class="s1">&#39;lang&#39;</span> <span class="o">=&gt;</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]</span><span class="p">,</span> <span class="s1">&#39;code&#39;</span> <span class="o">=&gt;</span> <span class="n">code</span><span class="p">}</span>
<span class="p">)</span><span class="o">.</span><span class="n">body</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-40'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-40">&#182;</a>
</div>
<p>And that&rsquo;s it.</p>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
</table>
</div>
</body>

View File

@ -1,89 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>layout.rb</title>
<link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
</head>
<body>
<div id='container'>
<div id="background"></div>
<div id="jump_to">
Jump To &hellip;
<div id="jump_wrapper">
<div id="jump_page">
<a class="source" href="rocco.html">rocco.rb</a>
<a class="source" href="layout.html">layout.rb</a>
<a class="source" href="tasks.html">tasks.rb</a>
</div>
</div>
</div>
<table cellspacing=0 cellpadding=0>
<thead>
<tr>
<th class=docs><h1>layout.rb</h1></th>
<th class=code></th>
</tr>
</thead>
<tbody>
<tr id='section-1'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-1">&#182;</a>
</div>
</td>
<td class=code>
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">&#39;mustache&#39;</span>
<span class="k">class</span> <span class="nc">Rocco</span><span class="o">::</span><span class="no">Layout</span> <span class="o">&lt;</span> <span class="no">Mustache</span>
<span class="nb">self</span><span class="o">.</span><span class="n">template_path</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">__FILE__</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">doc</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span>
<span class="vi">@doc</span> <span class="o">=</span> <span class="n">doc</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">file</span><span class="o">.</span><span class="n">nil?</span>
<span class="no">Rocco</span><span class="o">::</span><span class="no">Layout</span><span class="o">.</span><span class="n">template_file</span> <span class="o">=</span> <span class="n">file</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">title</span>
<span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vi">@doc</span><span class="o">.</span><span class="n">file</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">sections</span>
<span class="n">num</span> <span class="o">=</span> <span class="mi">0</span>
<span class="vi">@doc</span><span class="o">.</span><span class="n">sections</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">docs</span><span class="p">,</span><span class="n">code</span><span class="o">|</span>
<span class="p">{</span>
<span class="ss">:docs</span> <span class="o">=&gt;</span> <span class="n">docs</span><span class="p">,</span>
<span class="ss">:docs?</span> <span class="o">=&gt;</span> <span class="o">!</span><span class="n">docs</span><span class="o">.</span><span class="n">empty?</span><span class="p">,</span>
<span class="ss">:header?</span> <span class="o">=&gt;</span> <span class="sr">/^&lt;h.&gt;.+&lt;\/h.&gt;$/</span><span class="o">.</span><span class="n">match</span><span class="p">(</span> <span class="n">docs</span> <span class="p">),</span>
<span class="ss">:code</span> <span class="o">=&gt;</span> <span class="n">code</span><span class="p">,</span>
<span class="ss">:code?</span> <span class="o">=&gt;</span> <span class="o">!</span><span class="n">code</span><span class="o">.</span><span class="n">empty?</span><span class="p">,</span>
<span class="ss">:empty?</span> <span class="o">=&gt;</span> <span class="p">(</span> <span class="n">code</span><span class="o">.</span><span class="n">empty?</span> <span class="o">&amp;&amp;</span> <span class="n">docs</span><span class="o">.</span><span class="n">empty?</span> <span class="p">),</span>
<span class="ss">:num</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="n">num</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">sources?</span>
<span class="vi">@doc</span><span class="o">.</span><span class="n">sources</span><span class="o">.</span><span class="n">length</span> <span class="o">&gt;</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">sources</span>
<span class="vi">@doc</span><span class="o">.</span><span class="n">sources</span><span class="o">.</span><span class="n">sort</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">source</span><span class="o">|</span>
<span class="p">{</span>
<span class="ss">:path</span> <span class="o">=&gt;</span> <span class="n">source</span><span class="p">,</span>
<span class="ss">:basename</span> <span class="o">=&gt;</span> <span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">source</span><span class="p">),</span>
<span class="ss">:url</span> <span class="o">=&gt;</span> <span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">source</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">)</span><span class="o">[</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="o">-</span><span class="mi">2</span><span class="o">].</span><span class="n">join</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;.html&#39;</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
</table>
</div>
</body>

531
lib/rocco.rb Normal file
View File

@ -0,0 +1,531 @@
# **Rocco** is a Ruby port of [Docco][do], the quick-and-dirty,
# hundred-line-long, literate-programming-style documentation generator.
#
# Rocco reads Ruby source files and produces annotated source documentation
# in HTML format. Comments are formatted with [Markdown][md] and presented
# alongside syntax highlighted code so as to give an annotation effect.
# This page is the result of running Rocco against [its own source file][so].
#
# Most of this was written while waiting for [node.js][no] to build (so I
# could use Docco!). Docco's gorgeous HTML and CSS are taken verbatim.
# The main difference is that Rocco is written in Ruby instead of
# [CoffeeScript][co] and may be a bit easier to obtain and install in
# existing Ruby environments or where node doesn't run yet.
#
# Install Rocco with Rubygems:
#
# gem install rocco
#
# Once installed, the `rocco` command can be used to generate documentation
# for a set of Ruby source files:
#
# rocco lib/*.rb
#
# The HTML files are written to the current working directory.
#
# [no]: http://nodejs.org/
# [do]: http://jashkenas.github.com/docco/
# [co]: http://coffeescript.org/
# [md]: http://daringfireball.net/projects/markdown/
# [so]: http://github.com/rtomayko/rocco/blob/master/lib/rocco.rb#commit
#### Prerequisites
# We'll need a Markdown library. [RDiscount][rd], if we're lucky. Otherwise,
# issue a warning and fall back on using BlueCloth.
#
# [rd]: http://github.com/rtomayko/rdiscount
begin
require 'rdiscount'
rescue LoadError => boom
warn "WARNING: #{boom}. Trying bluecloth."
require 'bluecloth'
Markdown = BlueCloth
end
# We use [{{ mustache }}](http://defunkt.github.com/mustache/) for
# HTML templating.
require 'mustache'
# We use `Net::HTTP` to highlight code via <http://pygments.appspot.com>
require 'net/http'
# Code is run through [Pygments](http://pygments.org/) for syntax
# highlighting. If it's not installed, locally, use a webservice.
if !ENV['PATH'].split(':').any? { |dir| File.executable?("#{dir}/pygmentize") }
warn "WARNING: Pygments not found. Using webservice."
end
#### Public Interface
# `Rocco.new` takes a source `filename`, an optional list of source filenames
# for other documentation sources, an `options` hash, and an optional `block`.
# The `options` hash respects three members:
#
# * `:language`: specifies which Pygments lexer to use if one can't be
# auto-detected from the filename. _Defaults to `ruby`_.
#
# * `:comment_chars`, which specifies the comment characters of the
# target language. _Defaults to `#`_.
#
# * `:template_file`, which specifies a external template file to use
# when rendering the final, highlighted file via Mustache. _Defaults
# to `nil` (that is, Mustache will use `./lib/rocco/layout.mustache`)_.
#
class Rocco
VERSION = '0.7'
def initialize(filename, sources=[], options={}, &block)
@file = filename
@sources = sources
# When `block` is given, it must read the contents of the file using
# whatever means necessary and return it as a string. With no `block`,
# the file is read to retrieve data.
@data =
if block_given?
yield
else
File.read(filename)
end
defaults = {
:language => 'ruby',
:comment_chars => '#',
:template_file => nil
}
@options = defaults.merge(options)
# If we detect a language
if detect_language() != "text"
# then assign the detected language to `:language`, and look for
# comment characters based on that language
@options[:language] = detect_language()
@options[:comment_chars] = generate_comment_chars()
# If we didn't detect a language, but the user provided one, use it
# to look around for comment characters to override the default.
elsif @options[:language] != defaults[:language]
@options[:comment_chars] = generate_comment_chars()
# If neither is true, then convert the default comment character string
# into the comment_char syntax (we'll discuss that syntax in detail when
# we get to `generate_comment_chars()` in a moment.
else
@options[:comment_chars] = {
:single => @options[:comment_chars],
:multi => nil
}
end
# Turn `:comment_chars` into a regex matching a series of spaces, the
# `:comment_chars` string, and the an optional space. We'll use that
# to detect single-line comments.
@comment_pattern =
Regexp.new("^\\s*#{@options[:comment_chars][:single]}\s?")
# `parse()` the file contents stored in `@data`. Run the result through
# `split()` and that result through `highlight()` to generate the final
# section list.
@sections = highlight(split(parse(@data)))
end
# The filename as given to `Rocco.new`.
attr_reader :file
# The merged options array
attr_reader :options
# A list of two-tuples representing each *section* of the source file. Each
# item in the list has the form: `[docs_html, code_html]`, where both
# elements are strings containing the documentation and source code HTML,
# respectively.
attr_reader :sections
# A list of all source filenames included in the documentation set. Useful
# for building an index of other files.
attr_reader :sources
# Generate HTML output for the entire document.
require 'rocco/layout'
def to_html
Rocco::Layout.new(self, @options[:template_file]).render
end
# Helper Functions
# ----------------
# Returns `true` if `pygmentize` is available locally, `false` otherwise.
def pygmentize?
@_pygmentize ||= ENV['PATH'].split(':').
any? { |dir| File.executable?("#{dir}/pygmentize") }
end
# If `pygmentize` is available, we can use it to autodetect a file's
# language based on its filename. Filenames without extensions, or with
# extensions that `pygmentize` doesn't understand will return `text`.
# We'll also return `text` if `pygmentize` isn't available.
#
# We'll memoize the result, as we'll call this a few times.
def detect_language
@_language ||=
if pygmentize?
%x[pygmentize -N #{@file}].strip.split('+').first
else
"text"
end
end
# Given a file's language, we should be able to autopopulate the
# `comment_chars` variables for single-line comments. If we don't
# have comment characters on record for a given language, we'll
# use the user-provided `:comment_char` option (which defaults to
# `#`).
#
# Comment characters are listed as:
#
# { :single => "//",
# :multi_start => "/**",
# :multi_middle => "*",
# :multi_end => "*/" }
#
# `:single` denotes the leading character of a single-line comment.
# `:multi_start` denotes the string that should appear alone on a
# line of code to begin a block of documentation. `:multi_middle`
# denotes the leading character of block comment content, and
# `:multi_end` is the string that ought appear alone on a line to
# close a block of documentation. That is:
#
# /** [:multi][:start]
# * [:multi][:middle]
# ...
# * [:multi][:middle]
# */ [:multi][:end]
#
# If a language only has one type of comment, the missing type
# should be assigned `nil`.
#
# At the moment, we're only returning `:single`. Consider this
# groundwork for block comment parsing.
C_STYLE_COMMENTS = {
:single => "//",
:multi => { :start => "/**", :middle => "*", :end => "*/" },
:heredoc => nil
}
COMMENT_STYLES = {
"bash" => { :single => "#", :multi => nil },
"c" => C_STYLE_COMMENTS,
"coffee-script" => {
:single => "#",
:multi => { :start => "###", :middle => nil, :end => "###" },
:heredoc => nil
},
"cpp" => C_STYLE_COMMENTS,
"csharp" => C_STYLE_COMMENTS,
"css" => {
:single => nil,
:multi => { :start => "/**", :middle => "*", :end => "*/" },
:heredoc => nil
},
"html" => {
:single => nil,
:multi => { :start => '<!--', :middle => nil, :end => '-->' },
:heredoc => nil
},
"java" => C_STYLE_COMMENTS,
"js" => C_STYLE_COMMENTS,
"lua" => {
:single => "--",
:multi => nil,
:heredoc => nil
},
"php" => C_STYLE_COMMENTS,
"python" => {
:single => "#",
:multi => { :start => '"""', :middle => nil, :end => '"""' },
:heredoc => nil
},
"rb" => {
:single => "#",
:multi => { :start => '=begin', :middle => nil, :end => '=end' },
:heredoc => "<<-"
},
"scala" => C_STYLE_COMMENTS,
"scheme" => { :single => ";;", :multi => nil, :heredoc => nil },
"xml" => {
:single => nil,
:multi => { :start => '<!--', :middle => nil, :end => '-->' },
:heredoc => nil
},
}
def generate_comment_chars
@_commentchar ||=
if COMMENT_STYLES[@options[:language]]
COMMENT_STYLES[@options[:language]]
else
{ :single => @options[:comment_chars], :multi => nil, :heredoc => nil }
end
end
# Internal Parsing and Highlighting
# ---------------------------------
# Parse the raw file data into a list of two-tuples. Each tuple has the
# form `[docs, code]` where both elements are arrays containing the
# raw lines parsed from the input file, comment characters stripped.
def parse(data)
sections = []
docs, code = [], []
lines = data.split("\n")
# The first line is ignored if it is a shebang line. We also ignore the
# PEP 263 encoding information in python sourcefiles, and the similar ruby
# 1.9 syntax.
lines.shift if lines[0] =~ /^\#\!/
lines.shift if lines[0] =~ /coding[:=]\s*[-\w.]+/ &&
[ "python", "rb" ].include?(@options[:language])
# To detect both block comments and single-line comments, we'll set
# up a tiny state machine, and loop through each line of the file.
# This requires an `in_comment_block` boolean, and a few regular
# expressions for line tests. We'll do the same for fake heredoc parsing.
in_comment_block = false
in_heredoc = false
single_line_comment, block_comment_start, block_comment_mid, block_comment_end =
nil, nil, nil, nil
if not @options[:comment_chars][:single].nil?
single_line_comment = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:single])}\\s?")
end
if not @options[:comment_chars][:multi].nil?
block_comment_start = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*$")
block_comment_end = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
block_comment_one_liner = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
block_comment_start_with = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:start])}\\s*(.*?)$")
block_comment_end_with = Regexp.new("\\s*(.*?)\\s*#{Regexp.escape(@options[:comment_chars][:multi][:end])}\\s*$")
if @options[:comment_chars][:multi][:middle]
block_comment_mid = Regexp.new("^\\s*#{Regexp.escape(@options[:comment_chars][:multi][:middle])}\\s?")
end
end
if not @options[:comment_chars][:heredoc].nil?
heredoc_start = Regexp.new("#{Regexp.escape(@options[:comment_chars][:heredoc])}(\\S+)$")
end
lines.each do |line|
# If we're currently in a comment block, check whether the line matches
# the _end_ of a comment block or the _end_ of a comment block with a
# comment.
if in_comment_block
if block_comment_end && line.match(block_comment_end)
in_comment_block = false
elsif block_comment_end_with && line.match(block_comment_end_with)
in_comment_block = false
docs << line.match(block_comment_end_with).captures.first.
sub(block_comment_mid || '', '')
else
docs << line.sub(block_comment_mid || '', '')
end
# If we're currently in a heredoc, we're looking for the end of the
# heredoc, and everything it contains is code.
elsif in_heredoc
if line.match(Regexp.new("^#{Regexp.escape(in_heredoc)}$"))
in_heredoc = false
end
code << line
# Otherwise, check whether the line starts a heredoc. If so, note the end
# pattern, and the line is code. Otherwise check whether the line matches
# the beginning of a block, or a single-line comment all on it's lonesome.
# In either case, if there's code, start a new section.
else
if heredoc_start && line.match(heredoc_start)
in_heredoc = $1
code << line
elsif block_comment_one_liner && line.match(block_comment_one_liner)
if code.any?
sections << [docs, code]
docs, code = [], []
end
docs << line.match(block_comment_one_liner).captures.first
elsif block_comment_start && line.match(block_comment_start)
in_comment_block = true
if code.any?
sections << [docs, code]
docs, code = [], []
end
elsif block_comment_start_with && line.match(block_comment_start_with)
in_comment_block = true
if code.any?
sections << [docs, code]
docs, code = [], []
end
docs << line.match(block_comment_start_with).captures.first
elsif single_line_comment && line.match(single_line_comment)
if code.any?
sections << [docs, code]
docs, code = [], []
end
docs << line.sub(single_line_comment || '', '')
else
code << line
end
end
end
sections << [docs, code] if docs.any? || code.any?
normalize_leading_spaces(sections)
end
# Normalizes documentation whitespace by checking for leading whitespace,
# removing it, and then removing the same amount of whitespace from each
# succeeding line. That is:
#
# def func():
# """
# Comment 1
# Comment 2
# """
# print "omg!"
#
# should yield a comment block of `Comment 1\nComment 2` and code of
# `def func():\n print "omg!"`
def normalize_leading_spaces(sections)
sections.map do |section|
if section.any? && section[0].any?
leading_space = section[0][0].match("^\s+")
if leading_space
section[0] =
section[0].map{ |line| line.sub(/^#{leading_space.to_s}/, '') }
end
end
section
end
end
# Take the list of paired *sections* two-tuples and split into two
# separate lists: one holding the comments with leaders removed and
# one with the code blocks.
def split(sections)
docs_blocks, code_blocks = [], []
sections.each do |docs,code|
docs_blocks << docs.join("\n")
code_blocks << code.map do |line|
tabs = line.match(/^(\t+)/)
tabs ? line.sub(/^\t+/, ' ' * tabs.captures[0].length) : line
end.join("\n")
end
[docs_blocks, code_blocks]
end
# Take a list of block comments and convert Docblock @annotations to
# Markdown syntax.
def docblock(docs)
docs.map do |doc|
doc.split("\n").map do |line|
line.match(/^@\w+/) ? line.sub(/^@(\w+)\s+/, '> **\1** ')+" " : line
end.join("\n")
end
end
# Take the result of `split` and apply Markdown formatting to comments and
# syntax highlighting to source code.
def highlight(blocks)
docs_blocks, code_blocks = blocks
# Pre-process Docblock @annotations.
if @options[:docblocks]
docs_blocks = docblock(docs_blocks)
end
# Combine all docs blocks into a single big markdown document with section
# dividers and run through the Markdown processor. Then split it back out
# into separate sections.
markdown = docs_blocks.join("\n\n##### DIVIDER\n\n")
docs_html = process_markdown(markdown).
split(/\n*<h5>DIVIDER<\/h5>\n*/m)
# Combine all code blocks into a single big stream with section dividers and
# run through either `pygmentize(1)` or <http://pygments.appspot.com>
span, espan = '<span class="c.?">', '</span>'
if @options[:comment_chars][:single]
front = @options[:comment_chars][:single]
divider_input = "\n\n#{front} DIVIDER\n\n"
divider_output = Regexp.new(
[ "\\n*",
span,
Regexp.escape(CGI.escapeHTML(front)),
' DIVIDER',
espan,
"\\n*"
].join, Regexp::MULTILINE
)
else
front = @options[:comment_chars][:multi][:start]
back = @options[:comment_chars][:multi][:end]
divider_input = "\n\n#{front}\nDIVIDER\n#{back}\n\n"
divider_output = Regexp.new(
[ "\\n*",
span, Regexp.escape(CGI.escapeHTML(front)), espan,
"\\n",
span, "DIVIDER", espan,
"\\n",
span, Regexp.escape(CGI.escapeHTML(back)), espan,
"\\n*"
].join, Regexp::MULTILINE
)
end
code_stream = code_blocks.join(divider_input)
code_html =
if pygmentize?
highlight_pygmentize(code_stream)
else
highlight_webservice(code_stream)
end
# Do some post-processing on the pygments output to split things back
# into sections and remove partial `<pre>` blocks.
code_html = code_html.
split(divider_output).
map { |code| code.sub(/\n?<div class="highlight"><pre>/m, '') }.
map { |code| code.sub(/\n?<\/pre><\/div>\n/m, '') }
# Lastly, combine the docs and code lists back into a list of two-tuples.
docs_html.zip(code_html)
end
# Convert Markdown to classy HTML.
def process_markdown(text)
Markdown.new(text, :smart).to_html
end
# We `popen` a read/write pygmentize process in the parent and
# then fork off a child process to write the input.
def highlight_pygmentize(code)
code_html = nil
open("|pygmentize -l #{@options[:language]} -O encoding=utf-8 -f html", 'r+') do |fd|
pid =
fork {
fd.close_read
fd.write code
fd.close_write
exit!
}
fd.close_write
code_html = fd.read
fd.close_read
Process.wait(pid)
end
code_html
end
# Pygments is not one of those things that's trivial for a ruby user to install,
# so we'll fall back on a webservice to highlight the code if it isn't available.
def highlight_webservice(code)
Net::HTTP.post_form(
URI.parse('http://pygments.appspot.com/'),
{'lang' => @options[:language], 'code' => code}
).body
end
end
# And that's it.

190
lib/rocco/docco.css Normal file
View File

@ -0,0 +1,190 @@
/*--------------------- Layout and Typography ----------------------------*/
body {
font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
font-size: 15px;
line-height: 22px;
color: #252519;
margin: 0; padding: 0;
}
a {
color: #261a3b;
}
a:visited {
color: #261a3b;
}
p {
margin: 0 0 15px 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 0px 0 15px 0;
}
h1 {
margin-top: 40px;
}
#container {
position: relative;
}
#background {
position: fixed;
top: 0; left: 525px; right: 0; bottom: 0;
background: #f5f5ff;
border-left: 1px solid #e5e5ee;
z-index: -1;
}
#jump_to, #jump_page {
background: white;
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
font: 10px Arial;
text-transform: uppercase;
cursor: pointer;
text-align: right;
}
#jump_to, #jump_wrapper {
position: fixed;
top: 0; right: 0;
padding: 5px 10px;
}
#jump_wrapper {
padding: 0;
display: none;
}
#jump_to:hover #jump_wrapper {
display: block;
}
#jump_page {
padding: 5px 0 3px;
margin: 0 0 25px 25px;
overflow: hidden;
zoom: 1;
}
#jump_page .source {
float: left;
display: inline;
padding: 5px 10px;
text-decoration: none;
border-top: 1px solid #eee;
}
#jump_page .source:hover {
background: #f5f5ff;
}
#jump_page .source:first-child {
}
table td {
border: 0;
outline: 0;
}
td.docs, th.docs {
max-width: 450px;
min-width: 450px;
min-height: 5px;
padding: 10px 25px 1px 50px;
overflow-x: hidden;
vertical-align: top;
text-align: left;
}
.docs pre {
margin: 15px 0 15px;
padding-left: 15px;
}
.docs p tt, .docs p code {
background: #f8f8ff;
border: 1px solid #dedede;
font-size: 12px;
padding: 0 0.2em;
}
.pilwrap {
position: relative;
}
.pilcrow {
font: 12px Arial;
text-decoration: none;
color: #454545;
position: absolute;
top: 3px; left: -20px;
padding: 1px 2px;
opacity: 0;
-webkit-transition: opacity 0.2s linear;
}
td.docs:hover .pilcrow {
opacity: 1;
}
td.code, th.code {
padding: 14px 15px 16px 25px;
width: 100%;
vertical-align: top;
background: #f5f5ff;
border-left: 1px solid #e5e5ee;
}
pre, tt, code {
font-size: 12px; line-height: 18px;
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
margin: 0; padding: 0;
}
/*---------------------- Syntax Highlighting -----------------------------*/
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
body .hll { background-color: #ffffcc }
body .c { color: #408080; font-style: italic } /* Comment */
body .err { border: 1px solid #FF0000 } /* Error */
body .k { color: #954121 } /* Keyword */
body .o { color: #666666 } /* Operator */
body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
body .cp { color: #BC7A00 } /* Comment.Preproc */
body .c1 { color: #408080; font-style: italic } /* Comment.Single */
body .cs { color: #408080; font-style: italic } /* Comment.Special */
body .gd { color: #A00000 } /* Generic.Deleted */
body .ge { font-style: italic } /* Generic.Emph */
body .gr { color: #FF0000 } /* Generic.Error */
body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
body .gi { color: #00A000 } /* Generic.Inserted */
body .go { color: #808080 } /* Generic.Output */
body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
body .gs { font-weight: bold } /* Generic.Strong */
body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
body .gt { color: #0040D0 } /* Generic.Traceback */
body .kc { color: #954121 } /* Keyword.Constant */
body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
body .kp { color: #954121 } /* Keyword.Pseudo */
body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
body .kt { color: #B00040 } /* Keyword.Type */
body .m { color: #666666 } /* Literal.Number */
body .s { color: #219161 } /* Literal.String */
body .na { color: #7D9029 } /* Name.Attribute */
body .nb { color: #954121 } /* Name.Builtin */
body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
body .no { color: #880000 } /* Name.Constant */
body .nd { color: #AA22FF } /* Name.Decorator */
body .ni { color: #999999; font-weight: bold } /* Name.Entity */
body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
body .nf { color: #0000FF } /* Name.Function */
body .nl { color: #A0A000 } /* Name.Label */
body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
body .nt { color: #954121; font-weight: bold } /* Name.Tag */
body .nv { color: #19469D } /* Name.Variable */
body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
body .w { color: #bbbbbb } /* Text.Whitespace */
body .mf { color: #666666 } /* Literal.Number.Float */
body .mh { color: #666666 } /* Literal.Number.Hex */
body .mi { color: #666666 } /* Literal.Number.Integer */
body .mo { color: #666666 } /* Literal.Number.Oct */
body .sb { color: #219161 } /* Literal.String.Backtick */
body .sc { color: #219161 } /* Literal.String.Char */
body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
body .s2 { color: #219161 } /* Literal.String.Double */
body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
body .sh { color: #219161 } /* Literal.String.Heredoc */
body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
body .sx { color: #954121 } /* Literal.String.Other */
body .sr { color: #BB6688 } /* Literal.String.Regex */
body .s1 { color: #219161 } /* Literal.String.Single */
body .ss { color: #19469D } /* Literal.String.Symbol */
body .bp { color: #954121 } /* Name.Builtin.Pseudo */
body .vc { color: #19469D } /* Name.Variable.Class */
body .vg { color: #19469D } /* Name.Variable.Global */
body .vi { color: #19469D } /* Name.Variable.Instance */
body .il { color: #666666 } /* Literal.Number.Integer.Long */

46
lib/rocco/layout.mustache Normal file
View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>{{ title }}</title>
<style type="text/css">{{{ styles }}}</style>
</head>
<body>
<div id='container'>
<div id="background"></div>
{{#sources?}}
<div id="jump_to">
Jump To &hellip;
<div id="jump_wrapper">
<div id="jump_page">
{{#sources}}
<a class="source" href="{{ url }}">{{ basename }}</a>
{{/sources}}
</div>
</div>
</div>
{{/sources?}}
<table cellspacing=0 cellpadding=0>
<thead>
<tr>
<th class=docs><h1>{{ title }}</h1></th>
<th class=code></th>
</tr>
</thead>
<tbody>
{{#sections}}
<tr id='section-{{ section_id }}'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-{{ section_id }}">&#182;</a>
</div>
{{{ docs }}}
</td>
<td class=code>
<div class='highlight'><pre>{{{ code }}}</pre></div>
</td>
</tr>
{{/sections}}
</table>
</div>
</body>

63
lib/rocco/layout.rb Normal file
View File

@ -0,0 +1,63 @@
require 'mustache'
require 'pathname'
class Rocco::Layout < Mustache
self.template_path = "#{File.dirname(__FILE__)}/.."
def initialize(doc, file=nil)
@doc = doc
if not file.nil?
Rocco::Layout.template_file = file
end
end
def title
File.basename(@doc.file)
end
def file
@doc.file
end
def styles
File.read(File.expand_path('../docco.css', __FILE__))
end
def sections
num = 0
@doc.sections.map do |docs,code|
code ||= ''
is_header = /^<h.>(.+)<\/h.>$/.match( docs )
header_text = is_header && is_header[1].split.join("_")
num += 1
{
:docs => docs,
:docs? => !docs.empty?,
:header? => is_header,
:code => code,
:code? => !code.empty?,
:empty? => ( code.empty? && docs.empty? ),
:section_id => is_header ? header_text : num
}
end
end
def sources?
@doc.sources.length > 1
end
def sources
currentpath = Pathname.new( File.dirname( @doc.file ) )
@doc.sources.sort { |left, right| File.split(left).last <=> File.split(right).last }.map do |source|
htmlpath = Pathname.new( source.sub( Regexp.new( "#{File.extname(source)}$"), ".html" ) )
{
:path => source,
:basename => File.basename(source),
:url => htmlpath.relative_path_from( currentpath )
}
end
end
end

123
lib/rocco/tasks.rb Normal file
View File

@ -0,0 +1,123 @@
#### Rocco Rake Tasks
#
# To use the Rocco Rake tasks, require `rocco/tasks` in your `Rakefile`
# and define a Rake task with `rocco_task`. In its simplest form, `rocco_task`
# takes the path to a destination directory where HTML docs should be built:
#
# require 'rocco/tasks'
#
# desc "Build Rocco Docs"
# Rocco::make 'docs/'
#
# This creates a `:rocco` rake task, which can then be run with:
#
# rake rocco
#
# It's a good idea to guard against Rocco not being available, since your
# Rakefile will fail to load otherwise. Consider doing something like this,
# so that your Rakefile will still work
#
# begin
# require 'rocco/tasks'
# Rocco::make 'docs/'
# rescue LoadError
# warn "#$! -- rocco tasks not loaded."
# task :rocco
# end
#
# It's also possible to pass a glob pattern:
#
# Rocco::make 'html/', 'lib/thing/**/*.rb'
#
# Or a list of glob patterns:
#
# Rocco::make 'html/', ['lib/thing.rb', 'lib/thing/*.rb']
#
# Finally, it is also possible to specify which Pygments language you would
# like to use to highlight the code, as well as the comment characters for the
# language in the `options` hash:
#
# Rocco::make 'html/', 'lib/thing/**/*.rb', {
# :language => 'io',
# :comment_chars => '#'
# }
#
# Might be nice to defer this until we actually need to build docs but this
# will have to do for now.
require 'rocco'
# Reopen the Rocco class and add a `make` class method. This is a simple bit
# of sugar over `Rocco::Task.new`. If you want your Rake task to be named
# something other than `:rocco`, you can use `Rocco::Task` directly.
class Rocco
def self.make(dest='docs/', source_files='lib/**/*.rb', options={})
Task.new(:rocco, dest, source_files, options)
end
# `Rocco::Task.new` takes a task name, the destination directory docs
# should be built under, and a source file pattern or file list.
class Task
include Rake::DSL if defined?(Rake::DSL)
def initialize(task_name, dest='docs/', sources='lib/**/*.rb', options={})
@name = task_name
@dest = dest[-1] == ?/ ? dest : "#{dest}/"
@sources = FileList[sources]
@options = options
# Make sure there's a `directory` task defined for our destination.
define_directory_task @dest
# Run over the source file list, constructing destination filenames
# and defining file tasks.
@sources.each do |source_file|
dest_file = source_file.sub(Regexp.new("#{File.extname(source_file)}$"), ".html")
define_file_task source_file, "#{@dest}#{dest_file}"
# If `rake/clean` was required, add the generated files to the list.
# That way all Rocco generated are removed when running `rake clean`.
CLEAN.include "#{@dest}#{dest_file}" if defined? CLEAN
end
end
# Define the destination directory task and make the `:rocco` task depend
# on it. This causes the destination directory to be created if it doesn't
# already exist.
def define_directory_task(path)
directory path
task @name => path
end
# Setup a `file` task for a single Rocco output file (`dest_file`). It
# depends on the source file, the destination directory, and all of Rocco's
# internal source code, so that the destination file is rebuilt when any of
# those changes.
#
# You can run these tasks directly with Rake:
#
# rake docs/foo.html docs/bar.html
#
# ... would generate the `foo.html` and `bar.html` files but only if they
# don't already exist or one of their dependencies was changed.
def define_file_task(source_file, dest_file)
prerequisites = [@dest, source_file] + rocco_source_files
file dest_file => prerequisites do |f|
verbose { puts "rocco: #{source_file} -> #{dest_file}" }
rocco = Rocco.new(source_file, @sources.to_a, @options)
FileUtils.mkdir_p(File.dirname(dest_file))
File.open(dest_file, 'wb') { |fd| fd.write(rocco.to_html) }
end
task @name => dest_file
end
# Return a `FileList` that includes all of Roccos source files. This causes
# output files to be regenerated properly when someone upgrades the Rocco
# library.
def rocco_source_files
libdir = File.expand_path('../..', __FILE__)
FileList["#{libdir}/rocco.rb", "#{libdir}/rocco/**"]
end
end
end

56
rocco.gemspec Normal file
View File

@ -0,0 +1,56 @@
$LOAD_PATH.unshift 'lib'
require "rocco"
Gem::Specification.new do |s|
s.specification_version = 2 if s.respond_to? :specification_version=
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.name = 'rocco'
s.version = Rocco::VERSION
s.date = Time.now.strftime('%Y-%m-%d')
s.description = "Docco in Ruby"
s.summary = s.description
s.authors = ["Ryan Tomayko", "Mike West"]
s.email = ["r@tomayko.com", "<mike@mikewest.org>"]
# = MANIFEST =
s.files = %w[
CHANGES.md
COPYING
README
Rakefile
bin/rocco
lib/rocco.rb
lib/rocco/layout.mustache
lib/rocco/layout.rb
lib/rocco/tasks.rb
rocco.gemspec
test/fixtures/issue10.iso-8859-1.rb
test/fixtures/issue10.utf-8.rb
test/helper.rb
test/suite.rb
test/test_basics.rb
test/test_block_comments.rb
test/test_comment_normalization.rb
test/test_commentchar_detection.rb
test/test_descriptive_section_names.rb
test/test_language_detection.rb
test/test_reported_issues.rb
test/test_skippable_lines.rb
test/test_source_list.rb
]
# = MANIFEST =
s.executables = ["rocco"]
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/}
s.add_dependency 'rdiscount'
s.add_dependency 'mustache'
s.has_rdoc = false
s.homepage = "http://rtomayko.github.com/rocco/"
s.require_paths = %w[lib]
s.rubygems_version = '1.1.1'
end

View File

@ -1,449 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>rocco.rb</title>
<link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
</head>
<body>
<div id='container'>
<div id="background"></div>
<div id="jump_to">
Jump To &hellip;
<div id="jump_wrapper">
<div id="jump_page">
<a class="source" href="rocco.html">rocco.rb</a>
<a class="source" href="layout.html">layout.rb</a>
<a class="source" href="tasks.html">tasks.rb</a>
</div>
</div>
</div>
<table cellspacing=0 cellpadding=0>
<thead>
<tr>
<th class=docs><h1>rocco.rb</h1></th>
<th class=code></th>
</tr>
</thead>
<tbody>
<tr id='section-1'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-1">&#182;</a>
</div>
<p><strong>Rocco</strong> is a Ruby port of <a href="http://jashkenas.github.com/docco/">Docco</a>, the quick-and-dirty,
hundred-line-long, literate-programming-style documentation generator.</p>
<p>Rocco reads Ruby source files and produces annotated source documentation
in HTML format. Comments are formatted with <a href="http://daringfireball.net/projects/markdown/">Markdown</a> and presented
alongside syntax highlighted code so as to give an annotation effect.
This page is the result of running Rocco against <a href="http://github.com/rtomayko/rocco/blob/master/lib/rocco.rb#commit">its own source file</a>.</p>
<p>Most of this was written while waiting for <a href="http://nodejs.org/">node.js</a> to build (so I
could use Docco!). Docco&rsquo;s gorgeous HTML and CSS are taken verbatim.
The main difference is that Rocco is written in Ruby instead of
<a href="http://coffeescript.org/">CoffeeScript</a> and may be a bit easier to obtain and install in
existing Ruby environments or where node doesn&rsquo;t run yet.</p>
<p>Install Rocco with Rubygems:</p>
<pre><code>gem install rocco
</code></pre>
<p>Once installed, the <code>rocco</code> command can be used to generate documentation
for a set of Ruby source files:</p>
<pre><code>rocco lib/*.rb
</code></pre>
<p>The HTML files are written to the current working directory.</p>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-2'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-2">&#182;</a>
</div>
<h3>Prerequisites</h3>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-3'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-3">&#182;</a>
</div>
<p>We&rsquo;ll need a Markdown library. <a href="http://github.com/rtomayko/rdiscount">RDiscount</a>, if we&rsquo;re lucky. Otherwise,
issue a warning and fall back on using BlueCloth.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="k">begin</span>
<span class="nb">require</span> <span class="s1">&#39;rdiscount&#39;</span>
<span class="k">rescue</span> <span class="no">LoadError</span> <span class="o">=&gt;</span> <span class="n">boom</span>
<span class="nb">warn</span> <span class="s2">&quot;WARNING: </span><span class="si">#{</span><span class="n">boom</span><span class="si">}</span><span class="s2">. Trying bluecloth.&quot;</span>
<span class="nb">require</span> <span class="s1">&#39;bluecloth&#39;</span>
<span class="no">Markdown</span> <span class="o">=</span> <span class="no">BlueCloth</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-4'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-4">&#182;</a>
</div>
<p>We use <a href="http://defunkt.github.com/mustache/">{{ mustache }}</a> for
HTML templating.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">&#39;mustache&#39;</span></pre></div>
</td>
</tr>
<tr id='section-5'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-5">&#182;</a>
</div>
<p>We use <code>Net::HTTP</code> to highlight code via <a href="http://pygments.appspot.com">http://pygments.appspot.com</a></p>
</td>
<td class=code>
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">&#39;net/http&#39;</span></pre></div>
</td>
</tr>
<tr id='section-6'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-6">&#182;</a>
</div>
<p>Code is run through <a href="http://pygments.org/">Pygments</a> for syntax
highlighting. If it&rsquo;s not installed, locally, use a webservice.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="kp">include</span> <span class="no">FileTest</span>
<span class="k">if</span> <span class="o">!</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PATH&#39;</span><span class="o">].</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;:&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">any?</span> <span class="p">{</span> <span class="o">|</span><span class="n">dir</span><span class="o">|</span> <span class="n">executable?</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">#{</span><span class="n">dir</span><span class="si">}</span><span class="s2">/pygmentize&quot;</span><span class="p">)</span> <span class="p">}</span>
<span class="nb">warn</span> <span class="s2">&quot;WARNING: Pygments not found. Using webservice.&quot;</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-7'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-7">&#182;</a>
</div>
<h3>Public Interface</h3>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-8'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-8">&#182;</a>
</div>
<p><code>Rocco.new</code> takes a source <code>filename</code>, an optional list of source filenames
for other documentation sources, an <code>options</code> hash, and an optional <code>block</code>.
The <code>options</code> hash respects two members: <code>:language</code>, which specifies which
Pygments lexer to use; and <code>:comment_chars</code>, which specifies the comment
characters of the target language. The options default to <code>'ruby'</code> and <code>'#'</code>,
respectively.
When <code>block</code> is given, it must read the contents of the file using whatever
means necessary and return it as a string. With no <code>block</code>, the file is read
to retrieve data.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="k">class</span> <span class="nc">Rocco</span>
<span class="no">VERSION</span> <span class="o">=</span> <span class="s1">&#39;0.5&#39;</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">sources</span><span class="o">=[]</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{},</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
<span class="vi">@file</span> <span class="o">=</span> <span class="n">filename</span>
<span class="vi">@data</span> <span class="o">=</span>
<span class="k">if</span> <span class="nb">block_given?</span>
<span class="k">yield</span>
<span class="k">else</span>
<span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">defaults</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">:language</span> <span class="o">=&gt;</span> <span class="s1">&#39;ruby&#39;</span><span class="p">,</span>
<span class="ss">:comment_chars</span> <span class="o">=&gt;</span> <span class="s1">&#39;#&#39;</span><span class="p">,</span>
<span class="ss">:template_file</span> <span class="o">=&gt;</span> <span class="kp">nil</span>
<span class="p">}</span>
<span class="vi">@options</span> <span class="o">=</span> <span class="n">defaults</span><span class="o">.</span><span class="n">merge</span><span class="p">(</span><span class="n">options</span><span class="p">)</span>
<span class="vi">@sources</span> <span class="o">=</span> <span class="n">sources</span>
<span class="vi">@comment_pattern</span> <span class="o">=</span> <span class="no">Regexp</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s2">&quot;^</span><span class="se">\\</span><span class="s2">s*</span><span class="si">#{</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span><span class="si">}</span><span class="se">\s</span><span class="s2">?&quot;</span><span class="p">)</span>
<span class="vi">@template_file</span> <span class="o">=</span> <span class="vi">@options</span><span class="o">[</span><span class="ss">:template_file</span><span class="o">]</span>
<span class="vi">@sections</span> <span class="o">=</span> <span class="n">highlight</span><span class="p">(</span><span class="nb">split</span><span class="p">(</span><span class="n">parse</span><span class="p">(</span><span class="vi">@data</span><span class="p">)))</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-9'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-9">&#182;</a>
</div>
<p>The filename as given to <code>Rocco.new</code>.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:file</span></pre></div>
</td>
</tr>
<tr id='section-10'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-10">&#182;</a>
</div>
<p>A list of two-tuples representing each <em>section</em> of the source file. Each
item in the list has the form: <code>[docs_html, code_html]</code>, where both
elements are strings containing the documentation and source code HTML,
respectively.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:sections</span></pre></div>
</td>
</tr>
<tr id='section-11'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-11">&#182;</a>
</div>
<p>A list of all source filenames included in the documentation set. Useful
for building an index of other files.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:sources</span></pre></div>
</td>
</tr>
<tr id='section-12'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-12">&#182;</a>
</div>
<p>An absolute path to a file that ought be used as a template for the
HTML-rendered documentation.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="kp">attr_reader</span> <span class="ss">:template_file</span></pre></div>
</td>
</tr>
<tr id='section-13'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-13">&#182;</a>
</div>
<p>Generate HTML output for the entire document.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="nb">require</span> <span class="s1">&#39;rocco/layout&#39;</span>
<span class="k">def</span> <span class="nf">to_html</span>
<span class="no">Rocco</span><span class="o">::</span><span class="no">Layout</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="vi">@template_file</span><span class="p">)</span><span class="o">.</span><span class="n">render</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-14'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-14">&#182;</a>
</div>
<h3>Internal Parsing and Highlighting</h3>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-15'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-15">&#182;</a>
</div>
<p>Parse the raw file data into a list of two-tuples. Each tuple has the
form <code>[docs, code]</code> where both elements are arrays containing the
raw lines parsed from the input file. The first line is ignored if it
is a shebang line.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="n">sections</span> <span class="o">=</span> <span class="o">[]</span>
<span class="n">docs</span><span class="p">,</span> <span class="n">code</span> <span class="o">=</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span>
<span class="n">lines</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">lines</span><span class="o">.</span><span class="n">shift</span> <span class="k">if</span> <span class="n">lines</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=~</span> <span class="sr">/^\#\!/</span>
<span class="n">lines</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span>
<span class="k">case</span> <span class="n">line</span>
<span class="k">when</span> <span class="vi">@comment_pattern</span>
<span class="k">if</span> <span class="n">code</span><span class="o">.</span><span class="n">any?</span>
<span class="n">sections</span> <span class="o">&lt;&lt;</span> <span class="o">[</span><span class="n">docs</span><span class="p">,</span> <span class="n">code</span><span class="o">]</span>
<span class="n">docs</span><span class="p">,</span> <span class="n">code</span> <span class="o">=</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span>
<span class="k">end</span>
<span class="n">docs</span> <span class="o">&lt;&lt;</span> <span class="n">line</span>
<span class="k">else</span>
<span class="n">code</span> <span class="o">&lt;&lt;</span> <span class="n">line</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">sections</span> <span class="o">&lt;&lt;</span> <span class="o">[</span><span class="n">docs</span><span class="p">,</span> <span class="n">code</span><span class="o">]</span> <span class="k">if</span> <span class="n">docs</span><span class="o">.</span><span class="n">any?</span> <span class="o">||</span> <span class="n">code</span><span class="o">.</span><span class="n">any?</span>
<span class="n">sections</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-16'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-16">&#182;</a>
</div>
<p>Take the list of paired <em>sections</em> two-tuples and split into two
separate lists: one holding the comments with leaders removed and
one with the code blocks.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">split</span><span class="p">(</span><span class="n">sections</span><span class="p">)</span>
<span class="n">docs_blocks</span><span class="p">,</span> <span class="n">code_blocks</span> <span class="o">=</span> <span class="o">[]</span><span class="p">,</span> <span class="o">[]</span>
<span class="n">sections</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">docs</span><span class="p">,</span><span class="n">code</span><span class="o">|</span>
<span class="n">docs_blocks</span> <span class="o">&lt;&lt;</span> <span class="n">docs</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span> <span class="n">line</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="vi">@comment_pattern</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">code_blocks</span> <span class="o">&lt;&lt;</span> <span class="n">code</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span>
<span class="n">tabs</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sr">/^(\t+)/</span><span class="p">)</span>
<span class="n">tabs</span> <span class="p">?</span> <span class="n">line</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/^\t+/</span><span class="p">,</span> <span class="s1">&#39; &#39;</span> <span class="o">*</span> <span class="n">tabs</span><span class="o">.</span><span class="n">captures</span><span class="o">[</span><span class="mi">0</span><span class="o">].</span><span class="n">length</span><span class="p">)</span> <span class="p">:</span> <span class="n">line</span>
<span class="k">end</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">end</span>
<span class="o">[</span><span class="n">docs_blocks</span><span class="p">,</span> <span class="n">code_blocks</span><span class="o">]</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-17'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-17">&#182;</a>
</div>
<p>Take the result of <code>split</code> and apply Markdown formatting to comments and
syntax highlighting to source code.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">highlight</span><span class="p">(</span><span class="n">blocks</span><span class="p">)</span>
<span class="n">docs_blocks</span><span class="p">,</span> <span class="n">code_blocks</span> <span class="o">=</span> <span class="n">blocks</span></pre></div>
</td>
</tr>
<tr id='section-18'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-18">&#182;</a>
</div>
<p>Combine all docs blocks into a single big markdown document with section
dividers and run through the Markdown processor. Then split it back out
into separate sections.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">markdown</span> <span class="o">=</span> <span class="n">docs_blocks</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n\n</span><span class="s2">##### DIVIDER</span><span class="se">\n\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">docs_html</span> <span class="o">=</span> <span class="no">Markdown</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">markdown</span><span class="p">,</span> <span class="ss">:smart</span><span class="p">)</span><span class="o">.</span>
<span class="n">to_html</span><span class="o">.</span>
<span class="nb">split</span><span class="p">(</span><span class="sr">/\n*&lt;h5&gt;DIVIDER&lt;\/h5&gt;\n*/m</span><span class="p">)</span></pre></div>
</td>
</tr>
<tr id='section-19'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-19">&#182;</a>
</div>
<p>Combine all code blocks into a single big stream and run through either
<code>pygmentize(1)</code> or <a href="http://pygments.appspot.com">http://pygments.appspot.com</a></p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">code_stream</span> <span class="o">=</span> <span class="n">code_blocks</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">\n\n</span><span class="si">#{</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span><span class="si">}</span><span class="s2"> DIVIDER</span><span class="se">\n\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PATH&#39;</span><span class="o">].</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;:&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">any?</span> <span class="p">{</span> <span class="o">|</span><span class="n">dir</span><span class="o">|</span> <span class="n">executable?</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">#{</span><span class="n">dir</span><span class="si">}</span><span class="s2">/pygmentize&quot;</span><span class="p">)</span> <span class="p">}</span>
<span class="n">code_html</span> <span class="o">=</span> <span class="n">highlight_pygmentize</span><span class="p">(</span><span class="n">code_stream</span><span class="p">)</span>
<span class="k">else</span>
<span class="n">code_html</span> <span class="o">=</span> <span class="n">highlight_webservice</span><span class="p">(</span><span class="n">code_stream</span><span class="p">)</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-20'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-20">&#182;</a>
</div>
<p>Do some post-processing on the pygments output to split things back
into sections and remove partial <code>&lt;pre&gt;</code> blocks.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">code_html</span> <span class="o">=</span> <span class="n">code_html</span><span class="o">.</span>
<span class="nb">split</span><span class="p">(</span><span class="sr">/\n*&lt;span class=&quot;c.?&quot;&gt;</span><span class="si">#{</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:comment_chars</span><span class="o">]</span><span class="si">}</span><span class="sr"> DIVIDER&lt;\/span&gt;\n*/m</span><span class="p">)</span><span class="o">.</span>
<span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">code</span><span class="o">|</span> <span class="n">code</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\n?&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;/m</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span>
<span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">code</span><span class="o">|</span> <span class="n">code</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\n?&lt;\/pre&gt;&lt;\/div&gt;\n/m</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span> <span class="p">}</span></pre></div>
</td>
</tr>
<tr id='section-21'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-21">&#182;</a>
</div>
<p>Lastly, combine the docs and code lists back into a list of two-tuples.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">docs_html</span><span class="o">.</span><span class="n">zip</span><span class="p">(</span><span class="n">code_html</span><span class="p">)</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-22'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-22">&#182;</a>
</div>
<p>We <code>popen</code> a read/write pygmentize process in the parent and
then fork off a child process to write the input.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">highlight_pygmentize</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
<span class="n">code_html</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="nb">open</span><span class="p">(</span><span class="s2">&quot;|pygmentize -l </span><span class="si">#{</span><span class="vi">@options</span><span class="o">[</span><span class="ss">:language</span><span class="o">]</span><span class="si">}</span><span class="s2"> -O encoding=utf-8 -f html&quot;</span><span class="p">,</span> <span class="s1">&#39;r+&#39;</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">fd</span><span class="o">|</span>
<span class="n">pid</span> <span class="o">=</span>
<span class="nb">fork</span> <span class="p">{</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_read</span>
<span class="n">fd</span><span class="o">.</span><span class="n">write</span> <span class="n">code</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_write</span>
<span class="nb">exit!</span>
<span class="p">}</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_write</span>
<span class="n">code_html</span> <span class="o">=</span> <span class="n">fd</span><span class="o">.</span><span class="n">read</span>
<span class="n">fd</span><span class="o">.</span><span class="n">close_read</span>
<span class="no">Process</span><span class="o">.</span><span class="n">wait</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">code_html</span>
<span class="k">end</span>
</pre></div>
</td>
</tr>
<tr id='section-23'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-23">&#182;</a>
</div>
<p>Pygments is not one of those things that&rsquo;s trivial for a ruby user to install,
so we&rsquo;ll fall back on a webservice to highlight the code if it isn&rsquo;t available.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">highlight_webservice</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
<span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">post_form</span><span class="p">(</span>
<span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">&#39;http://pygments.appspot.com/&#39;</span><span class="p">),</span>
<span class="p">{</span><span class="s1">&#39;lang&#39;</span> <span class="o">=&gt;</span> <span class="vi">@options</span><span class="o">[</span><span class="s1">&#39;language&#39;</span><span class="o">]</span><span class="p">,</span> <span class="s1">&#39;code&#39;</span> <span class="o">=&gt;</span> <span class="n">code</span><span class="p">}</span>
<span class="p">)</span><span class="o">.</span><span class="n">body</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-24'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-24">&#182;</a>
</div>
<p>And that&rsquo;s it.</p>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
</table>
</div>
</body>

View File

@ -1,242 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>tasks.rb</title>
<link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
</head>
<body>
<div id='container'>
<div id="background"></div>
<div id="jump_to">
Jump To &hellip;
<div id="jump_wrapper">
<div id="jump_page">
<a class="source" href="rocco.html">rocco.rb</a>
<a class="source" href="layout.html">layout.rb</a>
<a class="source" href="tasks.html">tasks.rb</a>
</div>
</div>
</div>
<table cellspacing=0 cellpadding=0>
<thead>
<tr>
<th class=docs><h1>tasks.rb</h1></th>
<th class=code></th>
</tr>
</thead>
<tbody>
<tr id='section-1'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-1">&#182;</a>
</div>
<h3>Rocco Rake Tasks</h3>
<p>To use the Rocco Rake tasks, require <code>rocco/tasks</code> in your <code>Rakefile</code>
and define a Rake task with <code>rocco_task</code>. In its simplest form, <code>rocco_task</code>
takes the path to a destination directory where HTML docs should be built:</p>
<pre><code>require 'rocco/tasks'
desc "Build Rocco Docs"
Rocco::make 'docs/'
</code></pre>
<p>This creates a <code>:rocco</code> rake task, which can then be run with:</p>
<pre><code>rake rocco
</code></pre>
<p>It&rsquo;s a good idea to guard against Rocco not being available, since your
Rakefile will fail to load otherwise. Consider doing something like this,
so that your Rakefile will still work</p>
<pre><code>begin
require 'rocco/tasks'
Rocco::make 'docs/'
rescue LoadError
warn "#$! -- rocco tasks not loaded."
task :rocco
end
</code></pre>
<p>It&rsquo;s also possible to pass a glob pattern:</p>
<pre><code>Rocco::make 'html/', 'lib/thing/**/*.rb'
</code></pre>
<p>Or a list of glob patterns:</p>
<pre><code>Rocco::make 'html/', ['lib/thing.rb', 'lib/thing/*.rb']
</code></pre>
<p>Finally, it is also possible to specify which Pygments language you would
like to use to highlight the code, as well as the comment characters for the
language in the <code>options</code> hash:</p>
<p> Rocco::make &lsquo;html/&rsquo;, &lsquo;lib/thing/<em>*/</em>.rb&rsquo;, {</p>
<pre><code> :language =&gt; 'io',
:comment_chars =&gt; '#'
</code></pre>
<p> }</p>
</td>
<td class=code>
<div class='highlight'><pre></pre></div>
</td>
</tr>
<tr id='section-2'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-2">&#182;</a>
</div>
<p>Might be nice to defer this until we actually need to build docs but this
will have to do for now.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">&#39;rocco&#39;</span></pre></div>
</td>
</tr>
<tr id='section-3'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-3">&#182;</a>
</div>
<p>Reopen the Rocco class and add a <code>make</code> class method. This is a simple bit
of sugar over <code>Rocco::Task.new</code>. If you want your Rake task to be named
something other than <code>:rocco</code>, you can use <code>Rocco::Task</code> directly.</p>
</td>
<td class=code>
<div class='highlight'><pre><span class="k">class</span> <span class="nc">Rocco</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">make</span><span class="p">(</span><span class="n">dest</span><span class="o">=</span><span class="s1">&#39;docs/&#39;</span><span class="p">,</span> <span class="n">source_files</span><span class="o">=</span><span class="s1">&#39;lib/**/*.rb&#39;</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{})</span>
<span class="no">Task</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:rocco</span><span class="p">,</span> <span class="n">dest</span><span class="p">,</span> <span class="n">source_files</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-4'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-4">&#182;</a>
</div>
<p><code>Rocco::Task.new</code> takes a task name, the destination directory docs
should be built under, and a source file pattern or file list.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">class</span> <span class="nc">Task</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">task_name</span><span class="p">,</span> <span class="n">dest</span><span class="o">=</span><span class="s1">&#39;docs/&#39;</span><span class="p">,</span> <span class="n">sources</span><span class="o">=</span><span class="s1">&#39;lib/**/*.rb&#39;</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{})</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="n">task_name</span>
<span class="vi">@dest</span> <span class="o">=</span> <span class="n">dest</span><span class="o">[-</span><span class="mi">1</span><span class="o">]</span> <span class="o">==</span> <span class="sc">?/</span> <span class="p">?</span> <span class="n">dest</span> <span class="p">:</span> <span class="s2">&quot;</span><span class="si">#{</span><span class="n">dest</span><span class="si">}</span><span class="s2">/&quot;</span>
<span class="vi">@sources</span> <span class="o">=</span> <span class="no">FileList</span><span class="o">[</span><span class="n">sources</span><span class="o">]</span>
<span class="vi">@options</span> <span class="o">=</span> <span class="n">options</span></pre></div>
</td>
</tr>
<tr id='section-5'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-5">&#182;</a>
</div>
<p>Make sure there&rsquo;s a <code>directory</code> task defined for our destination.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="n">define_directory_task</span> <span class="vi">@dest</span></pre></div>
</td>
</tr>
<tr id='section-6'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-6">&#182;</a>
</div>
<p>Run over the source file list, constructing destination filenames
and defining file tasks.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="vi">@sources</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">source_file</span><span class="o">|</span>
<span class="n">dest_file</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">source_file</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">)</span><span class="o">[</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="o">-</span><span class="mi">2</span><span class="o">].</span><span class="n">join</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;.html&#39;</span>
<span class="n">define_file_task</span> <span class="n">source_file</span><span class="p">,</span> <span class="s2">&quot;</span><span class="si">#{</span><span class="vi">@dest</span><span class="si">}#{</span><span class="n">dest_file</span><span class="si">}</span><span class="s2">&quot;</span></pre></div>
</td>
</tr>
<tr id='section-7'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-7">&#182;</a>
</div>
<p>If <code>rake/clean</code> was required, add the generated files to the list.
That way all Rocco generated are removed when running <code>rake clean</code>.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="no">CLEAN</span><span class="o">.</span><span class="n">include</span> <span class="s2">&quot;</span><span class="si">#{</span><span class="vi">@dest</span><span class="si">}#{</span><span class="n">dest_file</span><span class="si">}</span><span class="s2">&quot;</span> <span class="k">if</span> <span class="n">defined?</span> <span class="no">CLEAN</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-8'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-8">&#182;</a>
</div>
<p>Define the destination directory task and make the <code>:rocco</code> task depend
on it. This causes the destination directory to be created if it doesn&rsquo;t
already exist.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">define_directory_task</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="n">directory</span> <span class="n">path</span>
<span class="n">task</span> <span class="vi">@name</span> <span class="o">=&gt;</span> <span class="n">path</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-9'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-9">&#182;</a>
</div>
<p>Setup a <code>file</code> task for a single Rocco output file (<code>dest_file</code>). It
depends on the source file, the destination directory, and all of Rocco&rsquo;s
internal source code, so that the destination file is rebuilt when any of
those changes.</p>
<p>You can run these tasks directly with Rake:</p>
<pre><code>rake docs/foo.html docs/bar.html
</code></pre>
<p>&hellip; would generate the <code>foo.html</code> and <code>bar.html</code> files but only if they
don&rsquo;t already exist or one of their dependencies was changed.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">define_file_task</span><span class="p">(</span><span class="n">source_file</span><span class="p">,</span> <span class="n">dest_file</span><span class="p">)</span>
<span class="n">prerequisites</span> <span class="o">=</span> <span class="o">[</span><span class="vi">@dest</span><span class="p">,</span> <span class="n">source_file</span><span class="o">]</span> <span class="o">+</span> <span class="n">rocco_source_files</span>
<span class="n">file</span> <span class="n">dest_file</span> <span class="o">=&gt;</span> <span class="n">prerequisites</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span>
<span class="n">verbose</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">&quot;rocco: </span><span class="si">#{</span><span class="n">source_file</span><span class="si">}</span><span class="s2"> -&gt; </span><span class="si">#{</span><span class="n">dest_file</span><span class="si">}</span><span class="s2">&quot;</span> <span class="p">}</span>
<span class="n">rocco</span> <span class="o">=</span> <span class="no">Rocco</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">source_file</span><span class="p">,</span> <span class="vi">@sources</span><span class="o">.</span><span class="n">to_a</span><span class="p">,</span> <span class="vi">@options</span><span class="p">)</span>
<span class="no">File</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">dest_file</span><span class="p">,</span> <span class="s1">&#39;wb&#39;</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">fd</span><span class="o">|</span> <span class="n">fd</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">rocco</span><span class="o">.</span><span class="n">to_html</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">task</span> <span class="vi">@name</span> <span class="o">=&gt;</span> <span class="n">dest_file</span>
<span class="k">end</span></pre></div>
</td>
</tr>
<tr id='section-10'>
<td class=docs>
<div class="pilwrap">
<a class="pilcrow" href="#section-10">&#182;</a>
</div>
<p>Return a <code>FileList</code> that includes all of Roccos source files. This causes
output files to be regenerated properly when someone upgrades the Rocco
library.</p>
</td>
<td class=code>
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">rocco_source_files</span>
<span class="n">libdir</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="s1">&#39;../..&#39;</span><span class="p">,</span> <span class="bp">__FILE__</span><span class="p">)</span>
<span class="no">FileList</span><span class="o">[</span><span class="s2">&quot;</span><span class="si">#{</span><span class="n">libdir</span><span class="si">}</span><span class="s2">/rocco.rb&quot;</span><span class="p">,</span> <span class="s2">&quot;</span><span class="si">#{</span><span class="n">libdir</span><span class="si">}</span><span class="s2">/rocco/**&quot;</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></pre></div>
</td>
</tr>
</table>
</div>
</body>

1
test/fixtures/issue10.iso-8859-1.rb vendored Normal file
View File

@ -0,0 +1 @@
# hello wörld

1
test/fixtures/issue10.utf-8.rb vendored Normal file
View File

@ -0,0 +1 @@
# hello ąćęłńóśźż

20
test/helper.rb Normal file
View File

@ -0,0 +1,20 @@
rootdir = File.expand_path('../../lib', __FILE__)
$LOAD_PATH.unshift "#{rootdir}/lib"
require 'test/unit'
begin; require 'turn'; rescue LoadError; end
begin
require 'rdiscount'
rescue LoadError
if !defined?(Gem)
require 'rubygems'
retry
end
end
require 'rocco'
def roccoize( filename, contents, options = {} )
Rocco.new( filename, [ filename ], options ) {
contents
}
end

5
test/suite.rb Normal file
View File

@ -0,0 +1,5 @@
require File.expand_path('../helper', __FILE__)
require 'test/unit'
Dir[File.expand_path('../test_*.rb', __FILE__)].
each { |file| require file }

63
test/test_basics.rb Normal file
View File

@ -0,0 +1,63 @@
require File.expand_path('../helper', __FILE__)
class RoccoBasicTests < Test::Unit::TestCase
def test_rocco_exists_and_is_instancable
roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
end
def test_filename
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
assert_equal "filename.rb", r.file
end
def test_sources
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
assert_equal [ "filename.rb" ], r.sources
end
def test_sections
r = roccoize( "filename.rb", "# Comment 1\ndef codeblock\nend\n" )
assert_equal 1, r.sections.length
assert_equal 2, r.sections[ 0 ].length
assert_equal "<p>Comment 1</p>\n", r.sections[ 0 ][ 0 ]
assert_equal "<span class=\"k\">def</span> <span class=\"nf\">codeblock</span>\n<span class=\"k\">end</span>", r.sections[ 0 ][ 1 ]
end
def test_parsing
r = Rocco.new( 'test' ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
],
r.parse( "# Comment 1\ndef codeblock\nend\n" )
)
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock" ] ],
[ [ "Comment 2" ], [ "end" ] ]
],
r.parse( "# Comment 1\ndef codeblock\n# Comment 2\nend\n" )
)
end
def test_splitting
r = Rocco.new( 'test' ) { "" } # Generate throwaway instance so I can test `split`
assert_equal(
[
[ "Comment 1" ],
[ "def codeblock\nend" ]
],
r.split([ [ [ "Comment 1" ], [ "def codeblock", "end" ] ] ])
)
assert_equal(
[
[ "Comment 1", "Comment 2" ],
[ "def codeblock", "end" ]
],
r.split( [
[ [ "Comment 1" ], [ "def codeblock" ] ],
[ [ "Comment 2" ], [ "end" ] ]
] )
)
end
end

View File

@ -0,0 +1,64 @@
require File.expand_path('../helper', __FILE__)
class RoccoBlockCommentTest < Test::Unit::TestCase
def test_one_liner
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
],
r.parse( "/** Comment 1 */\ndef codeblock\nend\n" )
)
end
def test_block_start_with_comment
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
],
r.parse( "/** Comment 1a\n * Comment 1b\n */\ndef codeblock\nend\n" )
)
end
def test_block_end_with_comment
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
],
r.parse( "/**\n * Comment 1a\n Comment 1b */\ndef codeblock\nend\n" )
)
end
def test_block_end_with_comment_and_middle
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
],
r.parse( "/**\n * Comment 1a\n * Comment 1b */\ndef codeblock\nend\n" )
)
end
def test_block_start_with_comment_and_end_with_comment
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
],
r.parse( "/** Comment 1a\n Comment 1b */\ndef codeblock\nend\n" )
)
end
def test_block_start_with_comment_and_end_with_comment_and_middle
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
],
r.parse( "/** Comment 1a\n * Comment 1b */\ndef codeblock\nend\n" )
)
end
end

101
test/test_block_comments.rb Normal file
View File

@ -0,0 +1,101 @@
require File.expand_path('../helper', __FILE__)
class RoccoBlockCommentTest < Test::Unit::TestCase
def test_basics
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ]
],
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n" )
)
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ]
],
r.parse( "/**\n * Comment 1a\n * Comment 1b\n */\ndef codeblock\nend\n" )
)
end
def test_multiple_blocks
r = Rocco.new( 'test', '', { :language => "c" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2" ], [] ]
],
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\n" )
)
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2" ], [ "if false", "end" ] ]
],
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\nif false\nend" )
)
end
def test_block_without_middle_character
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2" ], [] ]
],
r.parse( "\"\"\"\n Comment 1\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2\n\"\"\"\n" )
)
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2" ], [ "if false", "end" ] ]
],
r.parse( "\"\"\"\n Comment 1\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2\n\"\"\"\nif false\nend" )
)
end
def test_language_without_single_line_comments_parse
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2" ], [ "if false", "end" ] ]
],
r.parse( "/**\n * Comment 1\n */\ndef codeblock\nend\n/**\n * Comment 2\n */\nif false\nend" )
)
end
def test_language_without_single_line_comments_split
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ "Comment 1", "Comment 2" ],
[ "def codeblock\nend", "if false\nend" ]
],
r.split( [
[ [ "Comment 1" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2" ], [ "if false", "end" ] ]
] )
)
end
def test_language_without_single_line_comments_highlight
r = Rocco.new( 'test', '', { :language => "css" } ) { "" } # Generate throwaway instance so I can test `parse`
highlighted = r.highlight( r.split( r.parse( "/**\n * This is a comment!\n */\n.rule { goes: here; }\n/**\n * Comment 2\n */\n.rule2 { goes: here; }" ) ) )
assert_equal(
"<p>This is a comment!</p>",
highlighted[0][0]
)
assert_equal(
"<p>Comment 2</p>\n",
highlighted[1][0]
)
assert(
!highlighted[0][1].include?("DIVIDER") &&
!highlighted[1][1].include?("DIVIDER"),
"`DIVIDER` stripped successfully."
)
assert( true )
end
end

View File

@ -0,0 +1,25 @@
require File.expand_path('../helper', __FILE__)
class RoccoCommentNormalization < Test::Unit::TestCase
def test_normal_comments
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2a", " Comment 2b" ], [] ]
],
r.parse( "\"\"\"\n Comment 1a\n Comment 1b\n\"\"\"\ndef codeblock\nend\n\"\"\"\n Comment 2a\n Comment 2b\n\"\"\"\n" )
)
end
def test_single_line_comments
r = Rocco.new( 'test', '', { :language => "python" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1a", "Comment 1b" ], [ "def codeblock", "end" ] ],
[ [ "Comment 2a", " Comment 2b" ], [] ]
],
r.parse( "# Comment 1a\n# Comment 1b\ndef codeblock\nend\n# Comment 2a\n# Comment 2b\n" )
)
end
end

View File

@ -0,0 +1,28 @@
require File.expand_path('../helper', __FILE__)
class RoccoAutomaticCommentChars < Test::Unit::TestCase
def test_basic_detection
r = Rocco.new( 'filename.js' ) { "" }
assert_equal "//", r.options[:comment_chars][:single]
end
def test_fallback_language
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "js" } ) { "" }
assert_equal "//", r.options[:comment_chars][:single]
end
def test_fallback_default
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever' ) { "" }
assert_equal "#", r.options[:comment_chars][:single], "`:comment_chars` should be `#` when falling back to defaults."
end
def test_fallback_user
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :comment_chars => "user" } ) { "" }
assert_equal "user", r.options[:comment_chars][:single], "`:comment_chars` should be the user's default when falling back to user-provided settings."
end
def test_fallback_user_with_unknown_language
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "not-a-language", :comment_chars => "user" } ) { "" }
assert_equal "user", r.options[:comment_chars][:single], "`:comment_chars` should be the user's default when falling back to user-provided settings."
end
end

View File

@ -0,0 +1,30 @@
require File.expand_path('../helper', __FILE__)
class RoccoDescriptiveSectionNamesTests < Test::Unit::TestCase
def test_section_name
r = roccoize( "filename.rb", "# # Comment 1\ndef codeblock\nend\n" )
html = r.to_html
assert(
html.include?( "<tr id='section-Comment_1'>" ),
"The first section should be named"
)
assert(
html.include?( '<a class="pilcrow" href="#section-Comment_1">' ),
"The rendered HTML should link to a named section"
)
end
def test_section_numbering
r = roccoize( "filename.rb", "# # Header 1\ndef codeblock\nend\n# Comment 1\ndef codeblock1\nend\n# # Header 2\ndef codeblock2\nend" )
html = r.to_html
assert(
html.include?( '<a class="pilcrow" href="#section-Header_1">' ) &&
html.include?( '<a class="pilcrow" href="#section-Header_2">' ),
"First and second headers should be named sections"
)
assert(
html.include?( '<a class="pilcrow" href="#section-2">' ),
"Sections should continue numbering as though headers were counted."
)
end
end

View File

@ -0,0 +1,22 @@
require File.expand_path('../helper', __FILE__)
class RoccoDocblockAnnotationsTest < Test::Unit::TestCase
def test_basics
r = Rocco.new( 'test', '', { :language => "c", :docblocks => true } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
"Comment\n\n> **param** mixed foo \n> **return** void "
],
r.docblock( ["Comment\n\n@param mixed foo\n@return void"] )
)
end
def test_highlighted_in_blocks
r = Rocco.new( 'test', '', { :language => "c", :docblocks => true } ) { "" } # Generate throwaway instance so I can test `parse`
highlighted = r.highlight( r.split( r.parse( "/**\n * Comment\n * @param type name\n */\ndef codeblock\nend\n" ) ) )
assert_equal(
"<p>Comment</p>\n\n<blockquote><p><strong>param</strong> type name</p></blockquote>\n",
highlighted[0][0]
)
end
end

13
test/test_heredoc.rb Normal file
View File

@ -0,0 +1,13 @@
require File.expand_path('../helper', __FILE__)
class RoccoHeredocTest < Test::Unit::TestCase
def test_basics
r = Rocco.new( 'test', '', { :language => "rb" } ) { "" } # Generate throwaway instance so I can test `parse`
assert_equal(
[
[ [ "Comment 1" ], [ "heredoc <<-EOH", "#comment", "code", "EOH" ] ]
],
r.parse( "# Comment 1\nheredoc <<-EOH\n#comment\ncode\nEOH" )
)
end
end

View File

@ -0,0 +1,27 @@
require File.expand_path('../helper', __FILE__)
class RoccoLanguageDetection < Test::Unit::TestCase
def test_basic_detection
r = Rocco.new( 'filename.py' ) { "" }
if r.pygmentize?
assert_equal "python", r.detect_language(), "`detect_language()` should return the correct language"
assert_equal "python", r.options[:language], "`@options[:language]` should be set to the correct language"
end
end
def test_fallback_default
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever' ) { "" }
if r.pygmentize?
assert_equal "text", r.detect_language(), "`detect_language()` should return `text` when nothing else is detected"
assert_equal "ruby", r.options[:language], "`@options[:language]` should be set to `ruby` when nothing else is detected"
end
end
def test_fallback_user
r = Rocco.new( 'filename.an_extension_with_no_meaning_whatsoever', '', { :language => "c" } ) { "" }
if r.pygmentize?
assert_equal "text", r.detect_language(), "`detect_language()` should return `text` nothing else is detected"
assert_equal "c", r.options[:language], "`@options[:language]` should be set to the user's setting when nothing else is detected"
end
end
end

View File

@ -0,0 +1,86 @@
# encoding: utf-8
require File.expand_path('../helper', __FILE__)
class RoccoIssueTests < Test::Unit::TestCase
def test_issue07_incorrect_parsing_in_c_mode
# Precursor to issue #13 below, Rocco incorrectly parsed C/C++
# http://github.com/rtomayko/rocco/issues#issue/7
r = Rocco.new( 'issue7.c', [], { :language => 'c' } ) {
"// *stdio.h* declares *puts*\n#include <stdio.h>\n\n//### code hello world\n\n// every C program contains function *main*.\nint main (int argc, char *argv[]) {\n puts('hello world');\n return 0;\n}\n\n// that's it!"
}
r.sections.each do | section |
if not section[1].nil?
assert(
!section[1].include?("<span class=\"c\"># DIVIDER</span>"),
"`# DIVIDER` present in code text, which means the highligher screwed up somewhere."
)
end
end
end
def test_issue10_utf8_processing
# Rocco has issues with strange UTF-8 characters: need to explicitly set the encoding for Pygments
# http://github.com/rtomayko/rocco/issues#issue/10
r = Rocco.new( File.dirname(__FILE__) + "/fixtures/issue10.utf-8.rb" )
assert_equal(
"<p>hello ąćęłńóśźż</p>\n",
r.sections[0][0],
"UTF-8 input files ought behave correctly."
)
# and, just for grins, ensure that iso-8859-1 works too.
# @TODO: Is this really the correct behavior? Converting text
# to UTF-8 on the way out is probably preferable.
r = Rocco.new( File.dirname(__FILE__) + "/fixtures/issue10.iso-8859-1.rb" )
assert_equal(
"<p>hello w\366rld</p>\n",
r.sections[0][0],
"ISO-8859-1 input should probably also behave correctly."
)
end
def test_issue12_css_octothorpe_classname_change
# Docco changed some CSS classes. Rocco needs to update its default template.
# http://github.com/rtomayko/rocco/issues#issue/12
r = Rocco.new( 'issue12.sh' ) {
"# Comment 1\n# Comment 1\nprint 'omg!'"
}
html = r.to_html
assert(
!html.include?( "<div class=\"octowrap\">" ),
"`octowrap` wrapper is present in rendered HTML. This ought be replaced with `pilwrap`."
)
assert(
!html.include?( "<a class=\"octothorpe\" href=\"#section-1\">" ),
"`octothorpe` link is present in rendered HTML. This ought be replaced with `pilcrow`."
)
end
def test_issue13_incorrect_code_divider_parsing
# In `bash` mode (among others), the comment class is `c`, not `c1`.
# http://github.com/rtomayko/rocco/issues#issue/13
r = Rocco.new( 'issue13.sh', [], { :language => 'bash' } ) {
"# Comment 1\necho 'code';\n# Comment 2\necho 'code';\n# Comment 3\necho 'code';\n"
}
r.sections.each do | section |
if not section[1].nil?
assert(
!section[1].include?("<span class=\"c\"># DIVIDER</span>"),
"`# DIVIDER` present in code text, which means the highligher screwed up somewhere."
)
end
end
end
def test_issue15_extra_space_after_comment_character_remains
# After the comment character, a single space should be removed.
# http://github.com/rtomayko/rocco/issues#issue/15
r = Rocco.new( 'issue15.sh') {
"# Comment 1\n# ---------\necho 'code';"
}
assert(
!r.sections[0][0].include?( "<hr />" ),
"`<hr />` present in rendered documentation text. It should be a header, not text followed by a horizontal rule."
)
assert_equal("<h2>Comment 1</h2>\n", r.sections[0][0])
end
end

View File

@ -0,0 +1,64 @@
require File.expand_path('../helper', __FILE__)
class RoccoSkippableLines < Test::Unit::TestCase
def test_shebang_first_line
r = Rocco.new( 'filename.sh' ) { "" }
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock" ] ],
[ [ "Comment 2" ], [ "end" ] ]
],
r.parse( "#!/usr/bin/env bash\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
"Shebang should be stripped when it appears as the first line."
)
end
def test_shebang_in_content
r = Rocco.new( 'filename.sh' ) { "" }
assert_equal(
[
# @TODO: `#!/` shouldn't be recognized as a comment.
[ [ "Comment 1", "!/usr/bin/env bash" ], [ "def codeblock" ] ],
[ [ "Comment 2" ], [ "end" ] ]
],
r.parse( "# Comment 1\n#!/usr/bin/env bash\ndef codeblock\n# Comment 2\nend\n" ),
"Shebang shouldn't be stripped anywhere other than as the first line."
)
end
def test_encoding_in_ruby
r = Rocco.new( 'filename.rb' ) { "" }
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock" ] ],
[ [ "Comment 2" ], [ "end" ] ]
],
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
)
end
def test_encoding_in_python
r = Rocco.new( 'filename.py' ) { "" }
assert_equal(
[
[ [ "Comment 1" ], [ "def codeblock" ] ],
[ [ "Comment 2" ], [ "end" ] ]
],
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
)
end
def test_encoding_in_notpython
r = Rocco.new( 'filename.sh' ) { "" }
assert_equal(
[
[ [ "encoding: utf-8", "Comment 1" ], [ "def codeblock" ] ],
[ [ "Comment 2" ], [ "end" ] ]
],
r.parse( "#!/usr/bin/env bash\n# encoding: utf-8\n# Comment 1\ndef codeblock\n# Comment 2\nend\n" ),
"Strings matching the PEP 263 encoding definition regex should be stripped when they appear at the top of a python document."
)
end
end

29
test/test_source_list.rb Normal file
View File

@ -0,0 +1,29 @@
require File.expand_path('../helper', __FILE__)
class RoccoSourceListTests < Test::Unit::TestCase
def test_flat_sourcelist
r = Rocco.new( 'issue26.sh', [ 'issue26a.sh', 'issue26b.sh', 'issue26c.sh' ] ) {
"# Comment 1\n# Comment 1\nprint 'omg!'"
}
html = r.to_html
assert(
html.include?( '<a class="source" href="issue26a.html">issue26a.sh</a>' ) &&
html.include?( '<a class="source" href="issue26b.html">issue26b.sh</a>' ) &&
html.include?( '<a class="source" href="issue26c.html">issue26c.sh</a>' ),
"URLs correctly generated for files in a flat directory structure"
)
end
def test_heiarachical_sourcelist
r = Rocco.new( 'a/issue26.sh', [ 'a/issue26a.sh', 'b/issue26b.sh', 'c/issue26c.sh' ] ) {
"# Comment 1\n# Comment 1\nprint 'omg!'"
}
html = r.to_html
assert(
html.include?( '<a class="source" href="issue26a.html">issue26a.sh</a>' ) &&
html.include?( '<a class="source" href="../b/issue26b.html">issue26b.sh</a>' ) &&
html.include?( '<a class="source" href="../c/issue26c.html">issue26c.sh</a>' ),
"URLs correctly generated for files in a flat directory structure"
)
end
end