Module: Lazydoc::Utils

Included in:
Comment
Defined in:
lib/lazydoc/utils.rb

Overview

A number of utility methods used by Comment, factored out for testing and re-use.

Class Method Summary collapse

Class Method Details

.categorize(fragment, indent) ⇒ Object

utility method used by scan to categorize and yield the appropriate objects to add the fragment to a comment



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/lazydoc/utils.rb', line 216

def categorize(fragment, indent) # :nodoc:
  case
  when fragment == indent
    # empty comment line
    yield [""]
    yield []
  when indent.empty?
    # continuation line
    yield fragment.rstrip
  else 
    # indented line
    yield [fragment.rstrip]
    yield []
  end
end

.convert_to_scanner(str) ⇒ Object

Converts str to a StringScanner (or returns str if it already is a StringScanner). Raises a TypeError if str is not a String or a StringScanner.



17
18
19
20
21
22
23
# File 'lib/lazydoc/utils.rb', line 17

def convert_to_scanner(str)
  case str
  when String then StringScanner.new(str)
  when StringScanner then str
  else raise TypeError, "can't convert #{str.class} into StringScanner"
  end
end

.determine_line_number(scanner) ⇒ Object

Returns the line at which scanner currently resides. The position of scanner is not modified.



174
175
176
# File 'lib/lazydoc/utils.rb', line 174

def determine_line_number(scanner)
  scanner.string[0, scanner.pos].count("\n")
end

.match_index(lines, regexp) ⇒ Object

Returns the index of the line in lines matching regexp, or nil if no line matches regexp.



197
198
199
200
201
202
203
204
# File 'lib/lazydoc/utils.rb', line 197

def match_index(lines, regexp)
  index = 0
  lines.each do |line|
    return index if line =~ regexp
    index += 1
  end
  nil
end

.scan(line) ⇒ Object

Scan determines if and how to add a line fragment to a comment and yields the appropriate fragments to the block. Returns true if fragments are yielded and false otherwise.

Content may be built from an array of lines using scan like so:

lines = [
  "# comments spanning multiple",
  "# lines are collected",
  "#",
  "#   while indented lines",
  "#   are preserved individually",
  "#    ",
  "not a comment line",
  "# skipped since the loop breaks",
  "# at the first non-comment line"]

c = Comment.new
lines.each do |line|
  break unless Utils.scan(line) do |fragment|
    c.push(fragment)  
  end
end

c.content   
# => [
# ['comments spanning multiple', 'lines are collected'],
# [''],
# ['  while indented lines'],
# ['  are preserved individually'],
# [''],
# []]


58
59
60
61
62
63
64
# File 'lib/lazydoc/utils.rb', line 58

def scan(line) # :yields: fragment
  return false unless line =~ /^[ \t]*#[ \t]?(([ \t]*).*?)\r?$/
  categorize($1, $2) do |fragment|
    yield(fragment)
  end
  true
end

.scan_args(str) ⇒ Object

Parses an argument string (anything following the method name in a standard method definition, including parenthesis, comments, default values, etc) into an array of strings.

Utils.parse_args("(a, b='default', *c, &block)")  
# => ["a", "b='default'", "*c", "&block"]

Note the %-syntax for strings and arrays is not fully supported, ie %w, %Q, %q, etc. may not parse correctly. The same is true for multiline argument strings.

Accepts a String or a StringScanner.



78
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
119
120
121
122
123
# File 'lib/lazydoc/utils.rb', line 78

def scan_args(str)
  scanner = convert_to_scanner(str)
  str = scanner.string
  
  # skip whitespace and leading LPAREN
  scanner.skip(/\s*\(?\s*/) 
  
  args = []
  brakets = braces = parens = 0
  start = scanner.pos
  broke = while scanner.skip(/.*?['"#,\(\)\{\}\[\]]/)
    pos = scanner.pos - 1
    
    case str[pos]
    when ?,,nil
      # skip if in brakets, braces, or parenthesis
      next if parens > 0 || brakets > 0 || braces > 0
      
      # ok, found an arg
      args << str[start, pos-start].strip
      start = pos + 1
    
    when ?# then break(true)                # break on a comment
    when ?' then skip_quote(scanner, /'/)   # parse over quoted strings
    when ?" then skip_quote(scanner, /"/)   # parse over double-quoted string
      
    when ?( then parens += 1                # for brakets, braces, and parenthesis
    when ?)                                 # simply track the nesting EXCEPT for
      break(true) if parens == 0            # RPAREN.  If the closing parenthesis
      parens -= 1                           # is found, break.
    when ?[ then braces += 1
    when ?] then braces -= 1
    when ?{ then brakets += 1
    when ?} then brakets -= 1
    end
  end
  
  # parse out the final arg.  if the loop broke (ie 
  # a comment or the closing parenthesis was found) 
  # then the end position is determined by the 
  # scanner, otherwise take all that remains
  pos = broke ? scanner.pos-1 : str.length
  args << str[start, pos-start].strip unless pos == start

  args
end

.scan_index(scanner, regexp) ⇒ Object

Returns the index of the line where scanner ends up after the first match to regexp (starting at position 0). The existing position of scanner is not modified by this method. Returns nil if the scanner cannot match regexp.

scanner = StringScanner.new %Q{zero\none\ntwo\nthree}
Utils.scan_index(scanner, /two/)         # => 2
Utils.scan_index(scanner, /no match/)    # => nil


187
188
189
190
191
192
193
# File 'lib/lazydoc/utils.rb', line 187

def scan_index(scanner, regexp)
  pos = scanner.pos
  scanner.pos = 0
  n = scanner.skip_until(regexp) ? determine_line_number(scanner) : nil
  scanner.pos = pos
  n
end

.scan_trailer(str) ⇒ Object

Scans a stripped trailing comment off the input. Returns nil for strings without a trailing comment.

Utils.scan_trailer "str with # trailer"           # => "trailer"
Utils.scan_trailer "'# in str' # trailer"         # => "trailer"
Utils.scan_trailer "str with without trailer"     # => nil

Note the %Q and %q syntax for defining strings is not supported within the leader and may not parse correctly:

Utils.scan_trailer "%Q{# in str} # trailer"       # => "in str} # trailer"

Accepts a String or a StringScanner.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/lazydoc/utils.rb', line 138

def scan_trailer(str)
  scanner = convert_to_scanner(str)

  args = []
  brakets = braces = parens = 0
  start = scanner.pos
  while scanner.skip(/.*?['"#]/)
    pos = scanner.pos - 1
    
    case str[pos]
    when ?# then return scanner.rest.strip     # return the trailer
    when ?' then skip_quote(scanner, /'/)      # parse over quoted strings
    when ?" then skip_quote(scanner, /"/)      # parse over double-quoted string
    end
  end
  
  return nil
end

.skip_quote(scanner, regexp) ⇒ Object

helper method to skip to the next non-escaped instance matching the quote regexp (/‘/ or /“/).



208
209
210
211
# File 'lib/lazydoc/utils.rb', line 208

def skip_quote(scanner, regexp) # :nodoc:
  scanner.skip_until(regexp)
  scanner.skip_until(regexp) while scanner.string[scanner.pos-2] == ?\\
end

.split_lines(str) ⇒ Object



10
11
12
# File 'lib/lazydoc/utils.rb', line 10

def split_lines(str)
  (str.empty? ? [""] : str.split(/\r?\n/))
end

.wrap(line, cols = 80, tabsize = 2) ⇒ Object

Splits a line of text along whitespace breaks into fragments of cols width. Tabs in the line will be expanded into tabsize spaces; fragments are rstripped of whitespace.

Utils.wrap("some line that will wrap", 10)       # => ["some line", "that will", "wrap"]
Utils.wrap("     line that will wrap    ", 10)   # => ["     line", "that will", "wrap"]
Utils.wrap("                            ", 10)   # => []

The wrapping algorithm is slightly modified from: blog.macromates.com/2006/wrapping-text-with-regular-expressions/



167
168
169
170
# File 'lib/lazydoc/utils.rb', line 167

def wrap(line, cols=80, tabsize=2)
  line = line.gsub(/\t/, " " * tabsize) unless tabsize == nil
  line.gsub(/(.{1,#{cols}})( +|$\r?\n?)|(.{1,#{cols}})/, "\\1\\3\n").split(/\s*?\n/)
end