Class: Rocco
- Inherits:
-
Object
- Object
- Rocco
- Defined in:
- lib/rocco.rb,
lib/rocco/tasks.rb
Overview
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.
Defined Under Namespace
Constant Summary collapse
- VERSION =
'0.5'
Instance Attribute Summary collapse
-
#file ⇒ Object
readonly
The filename as given to ‘Rocco.new`.
-
#options ⇒ Object
readonly
The merged options array.
-
#sections ⇒ Object
readonly
A list of two-tuples representing each section of the source file.
-
#sources ⇒ Object
readonly
A list of all source filenames included in the documentation set.
-
#template_file ⇒ Object
readonly
An absolute path to a file that ought be used as a template for the HTML-rendered documentation.
Class Method Summary collapse
Instance Method Summary collapse
-
#detect_language ⇒ Object
If ‘pygmentize` is available, we can use it to autodetect a file’s language based on its filename.
-
#generate_comment_chars ⇒ Object
Given a file’s language, we should be able to autopopulate the ‘comment_chars` variables for single-line comments.
-
#highlight(blocks) ⇒ Object
Take the result of ‘split` and apply Markdown formatting to comments and syntax highlighting to source code.
-
#highlight_pygmentize(code) ⇒ Object
We ‘popen` a read/write pygmentize process in the parent and then fork off a child process to write the input.
-
#highlight_webservice(code) ⇒ Object
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.
-
#initialize(filename, sources = [], options = {}, &block) ⇒ Rocco
constructor
A new instance of Rocco.
-
#parse(data) ⇒ Object
Parse the raw file data into a list of two-tuples.
-
#pygmentize? ⇒ Boolean
Returns ‘true` if `pygmentize` is available locally, `false` otherwise.
-
#split(sections) ⇒ Object
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.
- #to_html ⇒ Object
Constructor Details
#initialize(filename, sources = [], options = {}, &block) ⇒ Rocco
Returns a new instance of Rocco.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/rocco.rb', line 79 def initialize(filename, sources=[], ={}, &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() @template_file = @options[:template_file] # If we detect a language if detect_language() != "text" # then assign the detected language to `:language` @options[:language] = detect_language() # and look for some comment characters @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() end if(@options[:comment_chars][:regex]) @comment_pattern = Regexp.new(@options[:comment_chars][:regex]) else @comment_pattern = Regexp.new("^\\s*#{@options[:comment_chars]}\s?") end puts @comment_pattern.source @sections = highlight(split(parse(@data))) end |
Instance Attribute Details
#file ⇒ Object (readonly)
The filename as given to ‘Rocco.new`.
196 197 198 |
# File 'lib/rocco.rb', line 196 def file @file end |
#options ⇒ Object (readonly)
The merged options array
199 200 201 |
# File 'lib/rocco.rb', line 199 def @options end |
#sections ⇒ Object (readonly)
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.
205 206 207 |
# File 'lib/rocco.rb', line 205 def sections @sections end |
#sources ⇒ Object (readonly)
A list of all source filenames included in the documentation set. Useful for building an index of other files.
209 210 211 |
# File 'lib/rocco.rb', line 209 def sources @sources end |
#template_file ⇒ Object (readonly)
An absolute path to a file that ought be used as a template for the HTML-rendered documentation.
213 214 215 |
# File 'lib/rocco.rb', line 213 def template_file @template_file end |
Class Method Details
Instance Method Details
#detect_language ⇒ Object
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.
132 133 134 135 136 137 138 139 140 |
# File 'lib/rocco.rb', line 132 def detect_language @_language ||= begin if pygmentize? lang = %x[pygmentize -N #{@file}].strip! else "text" end end end |
#generate_comment_chars ⇒ Object
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][: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.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/rocco.rb', line 170 def generate_comment_chars @_commentchar ||= begin language = @options[:language] comment_styles = { "bash" => { :single => "#", :multi => nil }, "c" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } }, "coffee-script" => { :single => "#", :multi => { :start => "###", :middle => nil, :end => "###" } }, "cpp" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } }, "java" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } }, "js" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/" } }, "php" => { :single => "//", :multi => { :start => "/**", :middle => "*", :end => "*/"}, :regex => "//(.*)|(?:\\*+\\n)(?m:(.*?))\\*+/" }, "lua" => { :single => "--", :multi => nil }, "python" => { :single => "#", :multi => { :start => '"""', :middle => nil, :end => '"""' } }, "ruby" => { :single => "#", :multi => nil }, "scheme" => { :single => ";;", :multi => nil }, } if comment_styles[language] comment_styles[language] else @options[:comment_chars] end end end |
#highlight(blocks) ⇒ Object
Take the result of ‘split` and apply Markdown formatting to comments and syntax highlighting to source code.
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/rocco.rb', line 265 def highlight(blocks) docs_blocks, code_blocks = blocks # 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 = Markdown.new(markdown, :smart). to_html. split(/\n*<h5>DIVIDER<\/h5>\n*/m) # Combine all code blocks into a single big stream and run through either # `pygmentize(1)` or <http://pygments.appspot.com> code_stream = code_blocks.join("\n\n#{@options[:comment_chars][:single]} DIVIDER\n\n") if pygmentize? code_html = highlight_pygmentize(code_stream) else code_html = 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(/\n*<span class="c.?">#{@options[:comment_chars][:single]} DIVIDER<\/span>\n*/m). 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 |
#highlight_pygmentize(code) ⇒ Object
We ‘popen` a read/write pygmentize process in the parent and then fork off a child process to write the input.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/rocco.rb', line 299 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 |
#highlight_webservice(code) ⇒ Object
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.
320 321 322 323 324 325 |
# File 'lib/rocco.rb', line 320 def highlight_webservice(code) Net::HTTP.post_form( URI.parse('http://pygments.appspot.com/'), {'lang' => @options[:language], 'code' => code} ).body end |
#parse(data) ⇒ Object
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. The first line is ignored if it is a shebang line.
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/rocco.rb', line 227 def parse(data) sections = [] docs, code = [], [] lines = data.split("\n") lines.shift if lines[0] =~ /^\#\!/ lines.each do |line| case line when @comment_pattern if code.any? sections << [docs, code] docs, code = [], [] end docs << line else code << line end end sections << [docs, code] if docs.any? || code.any? sections end |
#pygmentize? ⇒ Boolean
Returns ‘true` if `pygmentize` is available locally, `false` otherwise.
121 122 123 124 |
# File 'lib/rocco.rb', line 121 def pygmentize? # Memoize the result, we'll call this a few times @_pygmentize ||= ENV['PATH'].split(':').any? { |dir| executable?("#{dir}/pygmentize") } end |
#split(sections) ⇒ Object
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.
251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/rocco.rb', line 251 def split(sections) docs_blocks, code_blocks = [], [] sections.each do |docs,code| docs_blocks << docs.map { |line| line.sub(@comment_pattern, '') }.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 |