lazydoc 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README CHANGED
@@ -63,7 +63,7 @@ Lazydoc::Comment.
63
63
  # constant attribute, or an end
64
64
  # key
65
65
  # ..............................
66
- #}
66
+ # }
67
67
 
68
68
  In addition, individual lines of code may be registered and resolved by Lazydoc:
69
69
 
@@ -73,11 +73,46 @@ In addition, individual lines of code may be registered and resolved by Lazydoc:
73
73
  doc.resolve
74
74
  comment.subject # => " def method_one"
75
75
  comment.content # => [["comment content for a code comment", "may similarly span multiple lines"]]
76
+
77
+ A variety of helper methods exist to register methods, in particular:
78
+
79
+ class Helpers
80
+ extend Lazydoc::Attributes
81
+
82
+ register_method___
83
+ # method_one was registered by the
84
+ # helper
85
+ def method_one(a, b='str', &c)
86
+ end
87
+
88
+ # method_two illustrates registration
89
+ # of the line that *calls* method_two
90
+ def method_two
91
+ Lazydoc.register_caller
92
+ end
93
+ end
94
+
95
+ # *THIS* is the line that gets registered
96
+ # by method_two
97
+ Helpers.new.method_two
98
+
99
+ doc = Helpers.lazydoc
100
+ doc.resolve
101
+
102
+ m1 = doc.comments[0]
103
+ m1.method_name # => "method_one"
104
+ m1.arguments # => ["a", "b='str'", "&c"]
105
+ m1.to_s # => "method_one was registered by the helper"
106
+
107
+ comment = doc.comments[1]
108
+ comment.subject # => "Helpers.new.method_two"
109
+ comment.to_s # => "*THIS* is the line that gets registered by method_two"
76
110
 
77
111
  Check out these links for development, and bug tracking.
78
112
 
79
113
  * Website[http://tap.rubyforge.org/lazydoc]
80
114
  * Github[http://github.com/bahuvrihi/lazydoc/tree/master]
115
+ * Lighthouse[http://bahuvrihi.lighthouseapp.com/projects/19948-lazydoc/tickets?q=all]
81
116
  * {Google Group}[http://groups.google.com/group/ruby-on-tap]
82
117
 
83
118
  == Usage
@@ -1,24 +1,30 @@
1
+ require 'lazydoc'
2
+
1
3
  module Lazydoc
2
- # Attributes adds methods to declare class-level accessors
3
- # for Lazydoc attributes. The source_file for the class must
4
- # be set manually.
4
+ # Attributes adds methods to declare class-level accessors for Lazydoc
5
+ # attributes.
5
6
  #
6
7
  # # ConstName::key value
7
8
  # class ConstName
8
- # class << self
9
- # include Lazydoc::Attributes
10
- # end
9
+ # extend Lazydoc::Attributes
11
10
  #
12
- # self.source_file = __FILE__
13
11
  # lazy_attr :key
14
12
  # end
15
13
  #
14
+ # ConstName.source_file # => __FILE__
16
15
  # ConstName::key.subject # => 'value'
17
16
  #
18
17
  module Attributes
19
18
 
20
- # The source_file for self. Must be set independently.
19
+ # The source_file for self. By default set to the file where
20
+ # Attributes extends a class (if you include Attributes, you
21
+ # must set source_file manually).
21
22
  attr_accessor :source_file
23
+
24
+ def self.extended(base) # :nodoc:
25
+ caller[1] =~ CALLER_REGEXP
26
+ base.source_file ||= $1
27
+ end
22
28
 
23
29
  # Returns the lazydoc for source_file
24
30
  def lazydoc(resolve=true)
@@ -37,6 +43,11 @@ end
37
43
  def #{key}=(comment)
38
44
  Lazydoc[source_file][to_s]['#{attribute}'] = comment
39
45
  end}
40
- end
46
+ end
47
+
48
+ # Registers the next method.
49
+ def register_method___(comment_class=Method)
50
+ lazydoc(false).register___(comment_class, 1)
51
+ end
41
52
  end
42
53
  end
@@ -105,7 +105,7 @@ module Lazydoc
105
105
  else raise TypeError, "can't convert #{str.class} into StringScanner or String"
106
106
  end
107
107
 
108
- comment = Comment.new
108
+ comment = self.new
109
109
  while scanner.scan(/\r?\n?[ \t]*#[ \t]?(([ \t]*).*?)\r?$/)
110
110
  fragment = scanner[1]
111
111
  indent = scanner[2]
@@ -124,8 +124,10 @@ module Lazydoc
124
124
  if parse_subject
125
125
  scanner.skip(/\s+/)
126
126
  unless scanner.peek(1) == '#'
127
- comment.subject = scanner.scan(/.+?$/)
128
- comment.subject.strip! unless comment.subject == nil
127
+ if subject = scanner.scan(/.+?$/)
128
+ subject.strip!
129
+ end
130
+ comment.subject = subject
129
131
  end
130
132
  end
131
133
 
@@ -172,7 +174,40 @@ module Lazydoc
172
174
  end
173
175
  true
174
176
  end
175
-
177
+
178
+ # Scans a stripped trailing comment off of str, tolerant to a leader
179
+ # that uses '#' within a string. Returns nil for strings without a
180
+ # trailing comment.
181
+ #
182
+ # Comment.scan_trailer "str with # trailer" # => "trailer"
183
+ # Comment.scan_trailer "'# in str' # trailer" # => "trailer"
184
+ # Comment.scan_trailer "str with without trailer" # => nil
185
+ #
186
+ # Note the %-syntax for strings is not fully supported, ie %Q, %q,
187
+ # etc. may not parse correctly. Accepts Strings or a StringScanner.
188
+ def scan_trailer(str)
189
+ scanner = case str
190
+ when StringScanner then str
191
+ when String then StringScanner.new(str)
192
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
193
+ end
194
+
195
+ args = []
196
+ brakets = braces = parens = 0
197
+ start = scanner.pos
198
+ while scanner.skip(/.*?['"#]/)
199
+ pos = scanner.pos - 1
200
+
201
+ case str[pos]
202
+ when ?# then return scanner.rest.strip # return the trailer
203
+ when ?' then skip_quote(scanner, /'/) # parse over quoted strings
204
+ when ?" then skip_quote(scanner, /"/) # parse over double-quoted string
205
+ end
206
+ end
207
+
208
+ return nil
209
+ end
210
+
176
211
  # Splits a line of text along whitespace breaks into fragments of cols
177
212
  # width. Tabs in the line will be expanded into tabsize spaces;
178
213
  # fragments are rstripped of whitespace.
@@ -208,6 +243,13 @@ module Lazydoc
208
243
  yield []
209
244
  end
210
245
  end
246
+
247
+ # helper method to skip to the next non-escaped instance
248
+ # matching the quote regexp (/'/ or /"/).
249
+ def skip_quote(scanner, regexp) # :nodoc:
250
+ scanner.skip_until(regexp)
251
+ scanner.skip_until(regexp) while scanner.string[scanner.pos-2] == ?\\
252
+ end
211
253
  end
212
254
 
213
255
  # An array of comment fragments organized into lines
@@ -421,10 +463,12 @@ module Lazydoc
421
463
  # quietly exit if a line number was not found
422
464
  return self unless n.kind_of?(Integer)
423
465
 
466
+ # update negative line numbers
467
+ n += lines.length if n < 0
424
468
  unless n < lines.length
425
- raise RangeError, "line_number outside of lines: #{line_number} (#{lines.length})"
469
+ raise RangeError, "line_number outside of lines: #{n} (#{lines.length})"
426
470
  end
427
-
471
+
428
472
  self.line_number = n
429
473
  self.subject = lines[n]
430
474
  self.content.clear
@@ -455,6 +499,11 @@ module Lazydoc
455
499
  !content.find {|line| !line.empty?}
456
500
  end
457
501
 
502
+ # Returns a comment trailing the subject.
503
+ def trailer
504
+ subject ? Comment.scan_trailer(subject) : nil
505
+ end
506
+
458
507
  # Returns content as a string where line fragments are joined by
459
508
  # fragment_sep and lines are joined by line_sep.
460
509
  def to_s(fragment_sep=" ", line_sep="\n", strip=true)
@@ -1,4 +1,5 @@
1
1
  require 'lazydoc/comment'
2
+ require 'lazydoc/method'
2
3
 
3
4
  module Lazydoc
4
5
 
@@ -100,12 +101,36 @@ module Lazydoc
100
101
  comment
101
102
  end
102
103
 
103
- # Registers a regexp matching methods by the specified
104
- # name.
105
- def register_method(method, comment_class=Comment)
106
- register(/^\s*def\s+#{method}(\W|$)/, comment_class)
104
+ # Registers a regexp matching the first method by the specified name.
105
+ def register_method(method_name, comment_class=Method)
106
+ register(Method.method_regexp(method_name), comment_class)
107
107
  end
108
-
108
+
109
+ # Registers the next comment.
110
+ #
111
+ # lazydoc = Document.new(__FILE__)
112
+ #
113
+ # lazydoc.register___
114
+ # # this is the comment
115
+ # # that is registered
116
+ # def method(a,b,c)
117
+ # end
118
+ #
119
+ # lazydoc.resolve
120
+ # m = lazydoc.comments[0]
121
+ # m.subject # => "def method(a,b,c)"
122
+ # m.to_s # => "this is the comment that is registered"
123
+ #
124
+ def register___(comment_class=Comment, caller_index=0)
125
+ caller[caller_index] =~ CALLER_REGEXP
126
+ block = lambda do |lines|
127
+ n = $3.to_i
128
+ n += 1 while lines[n] =~ /^\s*(#.*)?$/
129
+ n
130
+ end
131
+ register(block, comment_class)
132
+ end
133
+
109
134
  # Scans str for constant attributes and adds them to to self. Code
110
135
  # comments are also resolved against str. If no str is specified,
111
136
  # the contents of source_file are used instead.
@@ -0,0 +1,128 @@
1
+ module Lazydoc
2
+
3
+ # Method represents a code comment for a standard method definition.
4
+ # Methods give access to the method name, the arguments, and the
5
+ # trailing comment, if present.
6
+ #
7
+ # sample_method = %Q{
8
+ # # This is the comment body
9
+ # def method_name(a, b='default', &c) # trailing comment
10
+ # end
11
+ # }
12
+ #
13
+ # m = Method.parse(sample_method)
14
+ # m.method_name # => "method_name"
15
+ # m.arguments # => ["a", "b='default'", "&c"]
16
+ # m.trailer # => "trailing comment"
17
+ # m.to_s # => "This is the comment body"
18
+ #
19
+ class Method < Comment
20
+ class << self
21
+
22
+ # Generates a regexp matching a standard definition of the
23
+ # specified method.
24
+ #
25
+ # m = Method.method_regexp("method")
26
+ # m =~ "def method" # => true
27
+ # m =~ "def method(with, args, &block)" # => true
28
+ # m !~ "def some_other_method" # => true
29
+ #
30
+ def method_regexp(method_name)
31
+ /^\s*def\s+#{method_name}(\W|$)/
32
+ end
33
+
34
+ # Parses an argument string (anything following the method name in a
35
+ # standard method definition, including parenthesis/comments/default
36
+ # values etc) into an array of strings.
37
+ #
38
+ # Method.parse_args("(a, b='default', &block)")
39
+ # # => ["a", "b='default'", "&block"]
40
+ #
41
+ # Note the %-syntax for strings and arrays is not fully supported,
42
+ # ie %w, %Q, %q, etc. may not parse correctly. The same is true
43
+ # for multiline argument strings.
44
+ def parse_args(str)
45
+ scanner = case str
46
+ when StringScanner then str
47
+ when String then StringScanner.new(str)
48
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
49
+ end
50
+ str = scanner.string
51
+
52
+ # skip whitespace and leading LPAREN
53
+ scanner.skip(/\s*\(?\s*/)
54
+
55
+ args = []
56
+ brakets = braces = parens = 0
57
+ start = scanner.pos
58
+ broke = while scanner.skip(/.*?['"#,\(\)\{\}\[\]]/)
59
+ pos = scanner.pos - 1
60
+
61
+ case str[pos]
62
+ when ?,,nil
63
+ # skip if in brakets, braces, or parenthesis
64
+ next if parens > 0 || brakets > 0 || braces > 0
65
+
66
+ # ok, found an arg
67
+ args << str[start, pos-start].strip
68
+ start = pos + 1
69
+
70
+ when ?# then break(true) # break on a comment
71
+ when ?' then skip_quote(scanner, /'/) # parse over quoted strings
72
+ when ?" then skip_quote(scanner, /"/) # parse over double-quoted string
73
+
74
+ when ?( then parens += 1 # for brakets, braces, and parenthesis
75
+ when ?) # simply track the nesting EXCEPT for
76
+ break(true) if parens == 0 # RPAREN. If the closing parenthesis
77
+ parens -= 1 # is found, break.
78
+ when ?[ then braces += 1
79
+ when ?] then braces -= 1
80
+ when ?{ then brakets += 1
81
+ when ?} then brakets -= 1
82
+ end
83
+ end
84
+
85
+ # parse out the final arg. if the loop broke (ie
86
+ # a comment or the closing parenthesis was found)
87
+ # then the end position is determined by the
88
+ # scanner, otherwise take all that remains
89
+ pos = broke ? scanner.pos-1 : str.length
90
+ args << str[start, pos-start].strip
91
+
92
+ args
93
+ end
94
+ end
95
+
96
+ # Matches a standard method definition. After the match:
97
+ #
98
+ # $1:: the method name
99
+ # $2:: the argument string, which may be parsed by parse_args
100
+ #
101
+ METHOD_DEF = /^\s*def (\w+)(.*)$/
102
+
103
+ # The resolved method name
104
+ attr_reader :method_name
105
+
106
+ # An array of the resolved arguments for the method
107
+ attr_reader :arguments
108
+
109
+ def initialize(*args)
110
+ super
111
+ @method_name = nil
112
+ @arguments = []
113
+ end
114
+
115
+ # Overridden to parse and set the method_name, arguments, and
116
+ # trailer in addition to setting the subject.
117
+ def subject=(value)
118
+ unless value =~ METHOD_DEF
119
+ raise ArgumentError, "not a method definition: #{value}"
120
+ end
121
+
122
+ @method_name = $1
123
+ @arguments = Method.parse_args($2)
124
+
125
+ super
126
+ end
127
+ end
128
+ end
data/lib/lazydoc.rb CHANGED
@@ -49,6 +49,26 @@ module Lazydoc
49
49
  Lazydoc[source_file].register(line_number, comment_class)
50
50
  end
51
51
 
52
+ # Registers the method at the specified index in the call stack, to
53
+ # the file where the method was called. Using the default index of
54
+ # 1, register_caller registers the caller of the method where
55
+ # register_caller is called. For instance:
56
+ #
57
+ # module Sample
58
+ # module_function
59
+ # def method
60
+ # Lazydoc.register_caller
61
+ # end
62
+ # end
63
+ #
64
+ # # this is the line that gets registered
65
+ # Sample.method
66
+ #
67
+ def register_caller(comment_class=Comment, caller_index=1)
68
+ caller[caller_index] =~ CALLER_REGEXP
69
+ Lazydoc[$1].register($3.to_i - 1, comment_class)
70
+ end
71
+
52
72
  # Resolves all lazydocs which include the specified code comments.
53
73
  def resolve_comments(comments)
54
74
  registry.each do |doc|
@@ -172,6 +192,8 @@ module Lazydoc
172
192
  end
173
193
  end
174
194
 
195
+ # Parses the usage for a file, ie the first comment in the file
196
+ # following an optional bang line.
175
197
  def usage(path, cols=80)
176
198
  scanner = StringScanner.new(File.read(path))
177
199
  scanner.scan(/^#!.*?$/)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazydoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-11-12 00:00:00 -07:00
12
+ date: 2008-12-07 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -36,6 +36,7 @@ files:
36
36
  - lib/lazydoc/attributes.rb
37
37
  - lib/lazydoc/comment.rb
38
38
  - lib/lazydoc/document.rb
39
+ - lib/lazydoc/method.rb
39
40
  - README
40
41
  - MIT-LICENSE
41
42
  has_rdoc: true
@@ -60,9 +61,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
61
  requirements: []
61
62
 
62
63
  rubyforge_project: tap
63
- rubygems_version: 1.2.0
64
+ rubygems_version: 1.3.1
64
65
  signing_key:
65
66
  specification_version: 2
66
- summary: lazydoc
67
+ summary: Lazily pull documentation out of source files.
67
68
  test_files: []
68
69