lazydoc 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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