class TomParse::Parser

Encapsulate parsed tomdoc documentation.

TODO: Currently uses lazy evaluation, eventually this should be removed and simply parsed all at once.

Constants

TOMDOC_STATUS

Recognized description status.

Attributes

raw[RW]

Public Class Methods

new(text, parse_options={}) click to toggle source

Public: Initialize a TomDoc object.

text - The raw text of a method or class/module comment.

Returns new TomDoc instance.

# File lib/tomparse/parser.rb, line 18
def initialize(text, parse_options={})
  @raw = text.to_s.strip

  @arguments        = []
  @options          = []
  @examples         = []
  @returns          = []
  @raises           = []
  @signatures       = []
  @signature_fields = []
  @tags             = []

  #parse unless @raw.empty?
end
valid?(text) click to toggle source

Validate given comment text.

Returns true if comment is valid, otherwise false.

# File lib/tomparse/parser.rb, line 43
def self.valid?(text)
  new(text).valid?
end

Public Instance Methods

args()
Alias for: arguments
arguments() click to toggle source

Arguments list.

Returns list of arguments.

# File lib/tomparse/parser.rb, line 137
def arguments
  parsed {
    @arguments
  }
end
Also aliased as: args
deprecated?() click to toggle source

Check if method is deprecated.

Returns true if method is deprecated.

# File lib/tomparse/parser.rb, line 247
def deprecated?
  parsed {
    @status == 'Deprecated'
  }
end
description() click to toggle source

Description of method or class/module.

Returns description String.

# File lib/tomparse/parser.rb, line 128
def description
  parsed {
    @description
  }
end
examples() click to toggle source

List of use examples of a method or class/module.

Returns String of examples.

# File lib/tomparse/parser.rb, line 157
def examples
  parsed {
    @examples
  }
end
internal?() click to toggle source

Check if method is internal.

Returns true if method is internal.

# File lib/tomparse/parser.rb, line 238
def internal?
  parsed {
    @status == 'Internal'
  }
end
keyword_arguments()
Alias for: options
options() click to toggle source

Keyword arguments, aka Options.

Returns list of options.

# File lib/tomparse/parser.rb, line 147
def options
  parsed {
    @options
  }
end
Also aliased as: keyword_arguments
parse() click to toggle source

Internal: Parse the Tomdoc formatted comment.

Returns true if there was a comment to parse.

# File lib/tomparse/parser.rb, line 314
def parse
  @parsed = true

  sections = smart_split(tomdoc)

  return false if sections.empty?

  # We are assuming that the first section is always description.
  # And it should be, but people aren't always proper, so perhaps
  # this can be made a little smarter in the future.
  parse_description(sections.shift)

  # The second section may be arguments.
  if sections.first && sections.first =~ /^\w+\s+\-/m
    parse_arguments(sections.shift)
  end

  current = sections.shift
  while current
    case type = section_type(current)
    when :arguments
      parse_arguments(current)
    when :options
      parse_options(current)
    when :example
      parse_example(current)
    when :examples
      parse_examples(current)
    when :yields
      parse_yields(current)
    when :returns
      parse_returns(current)
    when :raises
      parse_raises(current)
    when :signature
      parse_signature(current)
    when Symbol
      parse_tag(current)
    end
    current = sections.shift
  end

  return @parsed
end
public?() click to toggle source

Check if method is public.

Returns true if method is public.

# File lib/tomparse/parser.rb, line 229
def public?
  parsed {
    @status == 'Public'
  }
end
raises() click to toggle source

A list of errors a method might raise.

Returns Array of method raises descriptions.

# File lib/tomparse/parser.rb, line 184
def raises
  parsed {
    @raises
  }
end
returns() click to toggle source

The list of retrun values a method can return.

Returns Array of method return descriptions.

# File lib/tomparse/parser.rb, line 175
def returns
  parsed {
    @returns
  }
end
sections() click to toggle source

List of comment sections. These are divided simply on “nn”.

Returns Array of comment sections.

# File lib/tomparse/parser.rb, line 119
def sections
  parsed {
    @sections
  }
end
signature_fields() click to toggle source

A list of signature fields.

Returns Array of field definitions.

# File lib/tomparse/parser.rb, line 202
def signature_fields
  parsed {
    @signature_fields
  }
end
signatures() click to toggle source

A list of alternate method signatures.

Returns Array of signatures.

# File lib/tomparse/parser.rb, line 193
def signatures
  parsed {
    @signatures 
  }
end
status() click to toggle source

Method status, can be `Public`, `Internal` or `Deprecated`.

Returns [String]

# File lib/tomparse/parser.rb, line 220
def status
  parsed {
    @status
  }
end
tags() click to toggle source

List of tags.

Returns an associatve array of tags. [Array<Array<String>>]

# File lib/tomparse/parser.rb, line 211
def tags
  parsed {
    @tags
  }
end
to_s() click to toggle source

Raw documentation text.

Returns String of raw documentation text.

# File lib/tomparse/parser.rb, line 36
def to_s
  @raw
end
tomdoc() click to toggle source

The raw comment text cleaned-up and ready for section parsing.

Returns cleaned-up comment String.

# File lib/tomparse/parser.rb, line 80
def tomdoc
  lines = raw.split("\n")

  # remove remark symbol
  if lines.all?{ |line| /^\s*#/ =~ line }   
    lines = lines.map do |line|
      line =~ /^(\s*#)/ ? line.sub($1, '') : nil
    end
  end

  # for some reason the first line is coming in without indention
  # regardless, so we temporary remove it
  first = lines.shift

  # remove indention
  spaces = lines.map do |line|
    next if line.strip.empty?
    md = /^(\s*)/.match(line)
    md ? md[1].size : nil
  end.compact

  space = spaces.min || 0
  lines = lines.map do |line|
    if line.strip.empty?
      line.strip
    else
      line[space..-1]
    end
  end

  # put first line back
  lines.unshift(first.sub(/^\s*/,'')) if first

  lines.compact.join("\n")
end
valid?() click to toggle source

Validate raw comment.

TODO: This needs improvement.

Returns true if comment is valid, otherwise false.

# File lib/tomparse/parser.rb, line 52
def valid?
  begin
    new(text).validate
    true
  rescue ParseError
    false
  end
end
validate() click to toggle source

Validate raw comment.

Returns true if comment is valid. Raises ParseError if comment is not valid.

# File lib/tomparse/parser.rb, line 65
def validate
  if !raw.include?('Returns')
    raise ParseError.new("No `Returns' statement.")
  end

  if sections.size < 2
    raise ParseError.new("No description section found.")
  end

  true
end
yields() click to toggle source

Description of a methods yield procedure.

Returns String decription of yield procedure.

# File lib/tomparse/parser.rb, line 166
def yields
  parsed {
    @yields
  }
end

Private Instance Methods

argument_line?(line) click to toggle source

Check if a line of text could be an argument definition. I.e. it has a word followed by a dash.

Return [Boolean]

# File lib/tomparse/parser.rb, line 430
def argument_line?(line)
  /^\w+\s+\-/m =~ line.strip
end
clean_example(text) click to toggle source
# File lib/tomparse/parser.rb, line 675
def clean_example(text)
  lines = text.rstrip.lines.to_a
  # remove blank lines from top
  lines.shift while lines.first.strip.empty?
  # determine the indention
  indent = least_indent(lines)
  # remove the indention
  tab = " " * indent
  lines = lines.map{ |line| line.sub(tab, '') }
  # put the lines back together
  lines.join
end
least_indent(lines) click to toggle source

Given a multi-line string, determine the minimum indention.

# File lib/tomparse/parser.rb, line 689
def least_indent(lines)
  indents = []
  lines.map do |line|
    next if line.strip.empty?
    if md = /^\ */.match(line)
      indents << md[0].size
    end
  end
  indents.min || 0
end
parse_arguments(section) click to toggle source

Parse arguments section. Arguments occur subsequent to the description.

section - String containing argument definitions.

Returns nothing.

# File lib/tomparse/parser.rb, line 487
def parse_arguments(section)
  args = []
  last_indent = nil

  section.lines.each do |line|
    next if /^Arguments\s*$/i =~ line  # optional header
    next if line.strip.empty?
    indent = line.scan(/^\s*/)[0].to_s.size

    if last_indent && indent >= last_indent
      args.last.description << "\r\n" + line
    else
      param, desc = line.split(" - ")
      args << Argument.new(param.strip, desc.to_s.strip) if param #&& desc
      last_indent = indent + 1
    end
  end

  args.each do |arg|
    arg.parse(arg.description)
  end

  @arguments = args
end
parse_description(section) click to toggle source

Parse description.

section - String containig description.

Returns nothing.

# File lib/tomparse/parser.rb, line 468
def parse_description(section)
  if md = /^([A-Z]\w+\:)/.match(section)
    @status = md[1].chomp(':')
    if TOMDOC_STATUS.include?(@status)
      @description = md.post_match.strip
    else
      @description = section.strip
    end
  else
    @description = section.strip
  end   
end
parse_example(section) click to toggle source

Parse example.

section - String starting with `Example`.

Returns nothing.

# File lib/tomparse/parser.rb, line 548
def parse_example(section)
  # remove the initial `Example` line and right strip
  section = section.sub(/.*?\n/, '')
  example = clean_example(section)
  @examples << example unless example.strip.empty?
end
parse_examples(section) click to toggle source

Parse examples.

section - String starting with `Examples`.

Returns nothing.

# File lib/tomparse/parser.rb, line 560
def parse_examples(section)
  # remove the initial `Examples` line and right strip
  section = section.sub(/.*?\n/, '')
  section.split("\n\n").each do |ex|
    next if ex.strip.empty?
    example = clean_example(ex)
    @examples << example
  end
end
parse_options(section) click to toggle source

the description.

section - String containing argument definitions.

Returns nothing.

# File lib/tomparse/parser.rb, line 517
def parse_options(section)
  opts = []
  last_indent = nil

  section.lines.each do |line|
    next if /^\s*Options\s*$/i =~ line  # optional header
    next if line.strip.empty?
    indent = line.scan(/^\s*/)[0].to_s.size

    if last_indent && indent > 0 && indent >= last_indent
      opts.last.description << "\r\n" + line
    else
      param, desc = line.split(" - ")
      opts << Option.new(param.strip, desc.strip) if param && desc
    end

    last_indent = indent
  end

  #opts.each do |opt|
  #  opt.parse(arg.description)
  #end

  @options = opts
end
parse_raises(section) click to toggle source

Parse raises section.

section - String contaning Raises text.

Returns nothing.

# File lib/tomparse/parser.rb, line 594
def parse_raises(section)
  text = section.gsub(/\s+/, ' ').strip
  @raises << text.strip
end
parse_returns(section) click to toggle source

Parse returns section.

section - String contaning Returns and/or Raises lines.

Returns nothing.

# File lib/tomparse/parser.rb, line 584
def parse_returns(section)
  text = section.gsub(/\s+/, ' ').strip
  @returns << text
end
parse_signature(section) click to toggle source

Parse signature section.

IMPORTANT! This is not mojombo TomDoc! Rather signatures are simply a list of alternate ways to call a method, e.g. when *args is used but only specific argument patterns are possible.

section - String starting with `Signature`.

Returns nothing.

# File lib/tomparse/parser.rb, line 608
def parse_signature(section)
  signatures = []

  section = section.sub(/^\s*Signature(s)?/, '').strip

  lines = section.lines.to_a

  lines.each do |line|
    next if line.strip.empty?
    signatures << line.strip
  end

  @signatures = signatures

  #if line =~ /^\w+\s*\-/m
  #  parse_signature_fields(sections.shift)
  #end
end
parse_tag(section) click to toggle source

Tags are arbitrary sections designated by a capitalized label and a colon.

label - String name of the tag. section - String of the tag section.

Returns nothing.

# File lib/tomparse/parser.rb, line 662
def parse_tag(section)
  md = /^([A-Z]\w+)\:\ /m.match(section)
 
  label = md[1]
  desc  = md.post_match

  warn "No label?" unless label

  @tags << [label, desc.strip] if label
end
parse_yields(section) click to toggle source

Parse yields section.

section - String contaning Yields line.

Returns nothing.

# File lib/tomparse/parser.rb, line 575
def parse_yields(section)
  @yields = section.strip
end
parsed(&block) click to toggle source

Has the comment been parsed yet?

# File lib/tomparse/parser.rb, line 362
def parsed(&block)
  parse unless @parsed
  block.call
end
section_type(section) click to toggle source

Determine section type.

# File lib/tomparse/parser.rb, line 435
def section_type(section)
  case section
  when /\AArguments\s*$/
    :arguments
  when /\AOptions\s*$/
    :options
  when /\AExamples\s*$/
    :examples
  when /\AExample\s*$/
    :example
  when /\ASignature(s)?\s*$/
    :signature
  when /^Yield(s)?/
    :yields
  when /^Return(s)?/
    :returns
  when /^Raise(s)?/
    :raises
  when /\A([A-Z]\w+)\:\ /
    $1.to_sym
  else
    nil
  end
end
smart_split(doc) click to toggle source

Split the documentation up into proper sections. The method works by building up a list of linenos of where each section begins.

Returns an array section strings. [Array<String>]

# File lib/tomparse/parser.rb, line 372
def smart_split(doc)
  splits = []
  index  = -1

  lines = doc.lines.to_a

  # Remove any blank lines off the top.
  lines.shift while lines.first && lines.first.strip.empty?

  # Keep a copy of the lines for later use.
  doc_lines = lines.dup
 

  # The first line may have a `Public`/`Private`/`Deprecated` marker.
  # So we just skip the first line.
  lines.shift
  index += 1

  # The description is always the first section, but it may have
  # multiple paragraphs. And the second section may be an arguments
  # list without a header. This loop handles that.
  while line = lines.shift
    index += 1
    if argument_line?(line)
      splits << index
      break
    elsif section_type(line)
      splits << index
      break
    end
  end
  
  # The rest of the the document should have identifiable section markers.
  while line = lines.shift
    index += 1
    if section_type(line)
      splits << index
    end
  end

  # Now we split the documentation up into sections using
  # the line indexes we collected above.
  sections = []
  b = 0
  splits.shift if splits.first == 0
  splits.each do |i|
    sections << doc_lines[b...i].join
    b = i
  end
  sections << doc_lines[b..-1].join

  return sections
end