yard 0.6.4 → 0.6.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of yard might be problematic. Click here for more details.

Files changed (107) hide show
  1. data/ChangeLog +341 -0
  2. data/LICENSE +1 -1
  3. data/README.md +31 -6
  4. data/Rakefile +22 -3
  5. data/docs/Tags.md +5 -1
  6. data/docs/WhatsNew.md +18 -1
  7. data/lib/rubygems_plugin.rb +3 -99
  8. data/lib/yard.rb +1 -1
  9. data/lib/yard/autoload.rb +37 -35
  10. data/lib/yard/cli/config.rb +25 -2
  11. data/lib/yard/cli/graph.rb +1 -1
  12. data/lib/yard/cli/yardoc.rb +4 -0
  13. data/lib/yard/code_objects/base.rb +17 -9
  14. data/lib/yard/code_objects/method_object.rb +0 -9
  15. data/lib/yard/code_objects/proxy.rb +6 -0
  16. data/lib/yard/docstring.rb +5 -0
  17. data/lib/yard/handlers/base.rb +3 -1
  18. data/lib/yard/handlers/processor.rb +1 -1
  19. data/lib/yard/handlers/ruby/alias_handler.rb +9 -8
  20. data/lib/yard/handlers/ruby/class_handler.rb +3 -3
  21. data/lib/yard/handlers/ruby/legacy/alias_handler.rb +9 -7
  22. data/lib/yard/handlers/ruby/legacy/method_handler.rb +7 -0
  23. data/lib/yard/handlers/ruby/legacy/private_constant_handler.rb +21 -0
  24. data/lib/yard/handlers/ruby/method_condition_handler.rb +1 -1
  25. data/lib/yard/handlers/ruby/method_handler.rb +7 -0
  26. data/lib/yard/handlers/ruby/private_constant_handler.rb +36 -0
  27. data/lib/yard/parser/ruby/ast_node.rb +41 -24
  28. data/lib/yard/parser/ruby/legacy/ruby_parser.rb +3 -0
  29. data/lib/yard/parser/ruby/legacy/statement.rb +2 -0
  30. data/lib/yard/parser/ruby/legacy/statement_list.rb +27 -4
  31. data/lib/yard/parser/ruby/ruby_parser.rb +71 -36
  32. data/lib/yard/parser/source_parser.rb +11 -10
  33. data/lib/yard/registry.rb +62 -24
  34. data/lib/yard/registry_store.rb +18 -5
  35. data/lib/yard/rubygems/doc_manager.rb +75 -0
  36. data/lib/yard/rubygems/specification.rb +23 -0
  37. data/lib/yard/serializers/process_serializer.rb +1 -1
  38. data/lib/yard/serializers/yardoc_serializer.rb +7 -2
  39. data/lib/yard/server/commands/display_object_command.rb +1 -1
  40. data/lib/yard/server/commands/library_command.rb +2 -2
  41. data/lib/yard/tags/default_factory.rb +1 -1
  42. data/lib/yard/tags/library.rb +2 -2
  43. data/lib/yard/templates/helpers/base_helper.rb +19 -0
  44. data/lib/yard/templates/helpers/html_helper.rb +22 -9
  45. data/lib/yard/templates/helpers/html_syntax_highlight_helper.rb +28 -0
  46. data/lib/yard/templates/helpers/markup_helper.rb +14 -22
  47. data/lib/yard/templates/template.rb +1 -1
  48. data/spec/cli/config_spec.rb +20 -0
  49. data/spec/cli/yardoc_spec.rb +12 -0
  50. data/spec/code_objects/base_spec.rb +13 -0
  51. data/spec/code_objects/proxy_spec.rb +9 -0
  52. data/spec/config_spec.rb +4 -1
  53. data/spec/docstring_spec.rb +5 -0
  54. data/spec/handlers/alias_handler_spec.rb +14 -1
  55. data/spec/handlers/attribute_handler_spec.rb +1 -1
  56. data/spec/handlers/base_spec.rb +21 -21
  57. data/spec/handlers/class_condition_handler_spec.rb +1 -1
  58. data/spec/handlers/class_handler_spec.rb +1 -1
  59. data/spec/handlers/class_variable_handler_spec.rb +1 -1
  60. data/spec/handlers/constant_handler_spec.rb +2 -2
  61. data/spec/handlers/examples/alias_handler_001.rb.txt +14 -0
  62. data/spec/handlers/examples/method_handler_001.rb.txt +6 -0
  63. data/spec/handlers/examples/module_handler_001.rb.txt +4 -0
  64. data/spec/handlers/examples/private_constant_handler_001.rb.txt +8 -0
  65. data/spec/handlers/exception_handler_spec.rb +1 -1
  66. data/spec/handlers/extend_handler_spec.rb +1 -1
  67. data/spec/handlers/method_condition_handler_spec.rb +1 -1
  68. data/spec/handlers/method_handler_spec.rb +7 -1
  69. data/spec/handlers/mixin_handler_spec.rb +1 -1
  70. data/spec/handlers/module_handler_spec.rb +5 -1
  71. data/spec/handlers/private_constant_handler_spec.rb +24 -0
  72. data/spec/handlers/process_handler_spec.rb +1 -1
  73. data/spec/handlers/ruby/base_spec.rb +4 -4
  74. data/spec/handlers/visibility_handler_spec.rb +1 -1
  75. data/spec/handlers/yield_handler_spec.rb +1 -1
  76. data/spec/parser/base_spec.rb +3 -5
  77. data/spec/parser/c_parser_spec.rb +1 -1
  78. data/spec/parser/ruby/ast_node_spec.rb +23 -26
  79. data/spec/parser/ruby/legacy/statement_list_spec.rb +9 -0
  80. data/spec/parser/ruby/ruby_parser_spec.rb +179 -177
  81. data/spec/parser/source_parser_spec.rb +41 -7
  82. data/spec/rake/yardoc_task_spec.rb +3 -3
  83. data/spec/registry_spec.rb +52 -0
  84. data/spec/registry_store_spec.rb +71 -1
  85. data/spec/serializers/yardoc_serializer_spec.rb +18 -7
  86. data/spec/server/rack_adapter_spec.rb +2 -2
  87. data/spec/spec_helper.rb +10 -0
  88. data/spec/tags/default_factory_spec.rb +122 -120
  89. data/spec/templates/helpers/base_helper_spec.rb +38 -14
  90. data/spec/templates/helpers/html_helper_spec.rb +19 -0
  91. data/spec/templates/helpers/html_syntax_highlight_helper_spec.rb +10 -6
  92. data/spec/templates/helpers/markup_helper_spec.rb +21 -5
  93. data/templates/default/class/dot/superklass.erb +1 -1
  94. data/templates/default/docstring/setup.rb +1 -1
  95. data/templates/default/fulldoc/html/css/style.css +12 -4
  96. data/templates/default/fulldoc/html/js/app.js +1 -1
  97. data/templates/default/fulldoc/html/js/jquery.js +5 -143
  98. data/templates/default/layout/html/files.erb +11 -0
  99. data/templates/default/layout/html/headers.erb +1 -1
  100. data/templates/default/layout/html/index.erb +2 -49
  101. data/templates/default/layout/html/listing.erb +4 -0
  102. data/templates/default/layout/html/objects.erb +32 -0
  103. data/templates/default/layout/html/setup.rb +1 -1
  104. data/templates/default/module/dot/info.erb +1 -1
  105. data/templates/default/module/dot/setup.rb +1 -1
  106. metadata +15 -6
  107. data/lib/yard/templates/helpers/html_syntax_highlight_helper18.rb +0 -25
@@ -1,9 +1,12 @@
1
- require 'ripper'
1
+ begin require 'ripper'; rescue LoadError; end
2
2
 
3
3
  module YARD
4
4
  module Parser
5
5
  module Ruby
6
6
  # Ruby 1.9 parser
7
+ # @attr_reader encoding_line
8
+ # @attr_reader shebang_line
9
+ # @attr_reader enumerator
7
10
  class RubyParser < Parser::Base
8
11
  def initialize(source, filename)
9
12
  @parser = RipperParser.new(source, filename)
@@ -12,12 +15,15 @@ module YARD
12
15
  def parse; @parser.parse end
13
16
  def tokenize; @parser.tokens end
14
17
  def enumerator; @parser.enumerator end
18
+ def shebang_line; @parser.shebang_line end
19
+ def encoding_line; @parser.encoding_line end
15
20
  end
16
21
 
17
22
  # Internal parser class
18
23
  # @since 0.5.6
19
24
  class RipperParser < Ripper
20
25
  attr_reader :ast, :charno, :comments, :file, :tokens
26
+ attr_reader :shebang_line, :encoding_line
21
27
  alias root ast
22
28
 
23
29
  def initialize(source, filename, *args)
@@ -27,12 +33,15 @@ module YARD
27
33
  @source = source
28
34
  @tokens = []
29
35
  @comments = {}
36
+ @comments_flags = {}
30
37
  @heredoc_tokens = []
31
38
  @map = {}
32
39
  @ns_charno = 0
33
40
  @list = []
34
41
  @charno = 0
35
42
  @groups = []
43
+ @shebang_line = nil
44
+ @encoding_line = nil
36
45
  end
37
46
 
38
47
  def parse
@@ -116,15 +125,15 @@ module YARD
116
125
  PARSER_EVENT_TABLE.each do |event, arity|
117
126
  node_class = AstNode.node_class_for(event)
118
127
 
119
- if /_new\z/ =~ event and arity == 0
128
+ if /_new\z/ =~ event.to_s and arity == 0
120
129
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
121
130
  def on_#{event}(*args)
122
- #{node_class}.new(:list, args, listchar: charno...charno, listline: lineno..lineno)
131
+ #{node_class}.new(:list, args, :listchar => charno...charno, :listline => lineno..lineno)
123
132
  end
124
133
  eof
125
- elsif /_add(_.+)?\z/ =~ event
134
+ elsif /_add(_.+)?\z/ =~ event.to_s
126
135
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
127
- undef on_#{event} if instance_method(:on_#{event})
136
+ begin; undef on_#{event}; rescue NameError; end
128
137
  def on_#{event}(list, item)
129
138
  list.push(item)
130
139
  list
@@ -132,16 +141,16 @@ module YARD
132
141
  eof
133
142
  elsif MAPPINGS.has_key?(event)
134
143
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
135
- undef on_#{event} if instance_method(:on_#{event})
144
+ begin; undef on_#{event}; rescue NameError; end
136
145
  def on_#{event}(*args)
137
146
  visit_event #{node_class}.new(:#{event}, args)
138
147
  end
139
148
  eof
140
149
  else
141
150
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
142
- undef on_#{event} if instance_method(:on_#{event})
151
+ begin; undef on_#{event}; rescue NameError; end
143
152
  def on_#{event}(*args)
144
- #{node_class}.new(:#{event}, args, listline: lineno..lineno, listchar: charno...charno)
153
+ #{node_class}.new(:#{event}, args, :listline => lineno..lineno, :listchar => charno...charno)
145
154
  end
146
155
  eof
147
156
  end
@@ -150,17 +159,18 @@ module YARD
150
159
  SCANNER_EVENTS.each do |event|
151
160
  ast_token = AST_TOKENS.include?(event)
152
161
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
153
- undef on_#{event} if instance_method(:on_#{event})
162
+ begin; undef on_#{event}; rescue NameError; end
154
163
  def on_#{event}(tok)
155
164
  visit_ns_token(:#{event}, tok, #{ast_token.inspect})
156
165
  end
157
166
  eof
158
167
  end
159
168
 
160
- REV_MAPPINGS.select {|k| k.is_a?(Symbol) }.each do |event, value|
169
+ REV_MAPPINGS.select {|k,v| k.is_a?(Symbol) }.each do |pair|
170
+ event, value = *pair
161
171
  ast_token = AST_TOKENS.include?(event)
162
172
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
163
- undef on_#{event} if instance_method(:on_#{event})
173
+ begin; undef on_#{event}; rescue NameError; end
164
174
  def on_#{event}(tok)
165
175
  (@map[:#{event}] ||= []) << [lineno, charno]
166
176
  visit_ns_token(:#{event}, tok, #{ast_token.inspect})
@@ -170,7 +180,7 @@ module YARD
170
180
 
171
181
  [:kw, :op].each do |event|
172
182
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
173
- undef on_#{event} if instance_method(:on_#{event})
183
+ begin; undef on_#{event}; rescue NameError; end
174
184
  def on_#{event}(tok)
175
185
  unless @last_ns_token == [:kw, "def"] ||
176
186
  (@tokens.last && @tokens.last[0] == :symbeg)
@@ -183,7 +193,7 @@ module YARD
183
193
 
184
194
  [:sp, :nl, :ignored_nl].each do |event|
185
195
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
186
- undef on_#{event} if instance_method(:on_#{event})
196
+ begin; undef on_#{event}; rescue NameError; end
187
197
  def on_#{event}(tok)
188
198
  add_token(:#{event}, tok)
189
199
  @charno += tok.length
@@ -192,7 +202,8 @@ module YARD
192
202
  end
193
203
 
194
204
  def visit_event(node)
195
- lstart, sstart = *@map[MAPPINGS[node.type]].pop
205
+ map = @map[MAPPINGS[node.type]]
206
+ lstart, sstart = *(map ? map.pop : [lineno, lineno])
196
207
  node.source_range = Range.new(sstart, @ns_charno - 1)
197
208
  node.line_range = Range.new(lstart, lineno)
198
209
  node
@@ -213,7 +224,7 @@ module YARD
213
224
  @charno += data.length
214
225
  @ns_charno = charno
215
226
  if ast_token
216
- AstNode.new(token, [data], line: lineno..lineno, char: ch..charno-1, token: true)
227
+ AstNode.new(token, [data], :line => lineno..lineno, :char => ch..charno-1, :token => true)
217
228
  end
218
229
  end
219
230
 
@@ -280,7 +291,7 @@ module YARD
280
291
  ll, lc = *@map[:aref].pop
281
292
  sr = args.first.source_range.first..lc
282
293
  lr = args.first.line_range.first..ll
283
- AstNode.new(:aref, args, char: sr, line: lr)
294
+ AstNode.new(:aref, args, :char => sr, :line => lr)
284
295
  end
285
296
 
286
297
  def on_rbracket(tok)
@@ -301,31 +312,34 @@ module YARD
301
312
  end
302
313
 
303
314
  def on_const_path_ref(*args)
304
- klass = AstNode.node_class_for(:const_path_ref)
305
- klass.new(:const_path_ref, args, listline: lineno..lineno, listchar: charno..charno)
315
+ ReferenceNode.new(:const_path_ref, args, :listline => lineno..lineno, :listchar => charno..charno)
306
316
  end
307
317
 
308
318
  [:if_mod, :unless_mod, :while_mod].each do |kw|
309
319
  node_class = AstNode.node_class_for(kw)
310
320
  module_eval(<<-eof, __FILE__, __LINE__ + 1)
311
- undef on_#{kw} if instance_method(:on_#{kw})
321
+ begin; undef on_#{kw}; rescue NameError; end
312
322
  def on_#{kw}(*args)
313
323
  sr = args.last.source_range.first..args.first.source_range.last
314
324
  lr = args.last.line_range.first..args.first.line_range.last
315
- #{node_class}.new(:#{kw}, args, line: lr, char: sr)
325
+ #{node_class}.new(:#{kw}, args, :line => lr, :char => sr)
316
326
  end
317
327
  eof
318
328
  end
319
329
 
320
330
  def on_qwords_new
321
- visit_event AstNode.new(:qwords_literal, [])
331
+ visit_event LiteralNode.new(:qwords_literal, [])
322
332
  end
323
333
 
324
334
  def on_string_literal(*args)
325
- node = visit_event_arr(AstNode.new(:string_literal, args))
326
- if @source[charno, 1] !~ /["']/
327
- nsr = node.source_range
328
- node.source_range = Range.new(nsr.first, nsr.last + 1)
335
+ node = visit_event_arr(LiteralNode.new(:string_literal, args))
336
+ if args.size == 1
337
+ r = args[0].source_range
338
+ if node.source_range != Range.new(r.first - 1, r.last + 1)
339
+ klass = AstNode.node_class_for(node[0].type)
340
+ r = Range.new(node.source_range.first + 1, node.source_range.last - 1)
341
+ node[0] = klass.new(node[0].type, [@source[r]], :line => node.line_range, :char => r)
342
+ end
329
343
  end
330
344
  node
331
345
  end
@@ -335,7 +349,7 @@ module YARD
335
349
  end
336
350
 
337
351
  def on_string_content(*args)
338
- AstNode.new(:string_content, args, listline: lineno..lineno, listchar: charno..charno)
352
+ AstNode.new(:string_content, args, :listline => lineno..lineno, :listchar => charno..charno)
339
353
  end
340
354
 
341
355
  def on_rescue(exc, *args)
@@ -344,7 +358,7 @@ module YARD
344
358
  end
345
359
 
346
360
  def on_void_stmt
347
- AstNode.new(:void_stmt, [], line: lineno..lineno, char: charno...charno)
361
+ AstNode.new(:void_stmt, [], :line => lineno..lineno, :char => charno...charno)
348
362
  end
349
363
 
350
364
  def on_params(*args)
@@ -353,18 +367,18 @@ module YARD
353
367
  if arg.first.class == Array
354
368
  arg.map! do |sub_arg|
355
369
  if sub_arg.class == Array
356
- AstNode.new(:default_arg, sub_arg, listline: lineno..lineno, listchar: charno..charno)
370
+ AstNode.new(:default_arg, sub_arg, :listline => lineno..lineno, :listchar => charno..charno)
357
371
  else
358
372
  sub_arg
359
373
  end
360
374
  end
361
375
  end
362
- AstNode.new(:list, arg, listline: lineno..lineno, listchar: charno..charno)
376
+ AstNode.new(:list, arg, :listline => lineno..lineno, :listchar => charno..charno)
363
377
  else
364
378
  arg
365
379
  end
366
380
  end
367
- ParameterNode.new(:params, args, listline: lineno..lineno, listchar: charno..charno)
381
+ ParameterNode.new(:params, args, :listline => lineno..lineno, :listchar => charno..charno)
368
382
  end
369
383
 
370
384
  def on_label(data)
@@ -372,11 +386,26 @@ module YARD
372
386
  ch = charno
373
387
  @charno += data.length
374
388
  @ns_charno = charno
375
- AstNode.new(:label, [data[0...-1]], line: lineno..lineno, char: ch..charno-1, token: true)
389
+ AstNode.new(:label, [data[0...-1]], :line => lineno..lineno, :char => ch..charno-1, :token => true)
376
390
  end
377
391
 
378
392
  def on_comment(comment)
393
+ not_comment = false
394
+ if @last_ns_token.nil? || @last_ns_token.size == 0
395
+ if comment =~ SourceParser::SHEBANG_LINE && !@encoding_line
396
+ @shebang_line = comment
397
+ not_comment = true
398
+ elsif comment =~ SourceParser::ENCODING_LINE
399
+ @encoding_line = comment
400
+ not_comment = true
401
+ end
402
+ end
403
+
379
404
  visit_ns_token(:comment, comment)
405
+ if not_comment
406
+ @last_ns_token = nil
407
+ return
408
+ end
380
409
  case comment
381
410
  when /\A# @group\s+(.+)\s*\Z/
382
411
  @groups.unshift [lineno, $1]
@@ -385,16 +414,20 @@ module YARD
385
414
  @groups.unshift [lineno, nil]
386
415
  return
387
416
  end
388
-
389
- comment = comment.gsub(/^\#{1,2}\s{0,1}/, '').chomp
390
- append_comment = @comments[lineno - 1]
391
417
 
418
+ comment = comment.gsub(/^(\#{1,2})\s{0,1}/, '').chomp
419
+ append_comment = @comments[lineno - 1]
420
+ hash_flag = $1 == '##' ? true : false
421
+
392
422
  if append_comment && @comments_last_column == column
393
423
  @comments.delete(lineno - 1)
424
+ @comments_flags[lineno] = @comments_flags[lineno - 1]
425
+ @comments_flags.delete(lineno - 1)
394
426
  comment = append_comment + "\n" + comment
395
427
  end
396
428
 
397
429
  @comments[lineno] = comment
430
+ @comments_flags[lineno] = hash_flag if !append_comment
398
431
  @comments_last_column = column
399
432
  end
400
433
 
@@ -424,9 +457,11 @@ module YARD
424
457
  (node.line - 2).upto(node.line) do |line|
425
458
  comment = @comments[line]
426
459
  if comment && !comment.empty?
460
+ node.docstring_hash_flag = @comments_flags[line]
427
461
  node.docstring = comment
428
462
  node.docstring_range = ((line - comment.count("\n"))..line)
429
- comments.delete(line)
463
+ @comments.delete(line)
464
+ @comments_flags.delete(line)
430
465
  break
431
466
  end
432
467
  end
@@ -447,7 +482,7 @@ module YARD
447
482
  freeze_tree(child)
448
483
  end
449
484
  end
450
- end
485
+ end if defined?(::Ripper)
451
486
  end
452
487
  end
453
488
  end
@@ -1,8 +1,6 @@
1
1
  require 'stringio'
2
2
 
3
- begin
4
- require 'continuation'
5
- rescue LoadError; end
3
+ begin require 'continuation'; rescue LoadError; end
6
4
 
7
5
  module YARD
8
6
  module Parser
@@ -34,6 +32,9 @@ module YARD
34
32
  # @see Handlers::Base
35
33
  # @see CodeObjects::Base
36
34
  class SourceParser
35
+ SHEBANG_LINE = /\A\s*#!\S+/
36
+ ENCODING_LINE = /\A(?:\s*#*!.*\r?\n)?\s*#+.*coding\s*[:=]{1,2}\s*(\S+)/i
37
+
37
38
  class << self
38
39
  # @return [Symbol] the default parser type (defaults to :ruby)
39
40
  attr_reader :parser_type
@@ -133,13 +134,13 @@ module YARD
133
134
  end
134
135
 
135
136
  # Returns the validated parser type. Basically, enforces that :ruby
136
- # type is never set from Ruby 1.8
137
+ # type is never set if the Ripper library is not available
137
138
  #
138
139
  # @param [Symbol] type the parser type to set
139
140
  # @return [Symbol] the validated parser type
140
141
  # @private
141
142
  def validated_parser_type(type)
142
- RUBY18 && type == :ruby ? :ruby18 : type
143
+ !defined?(::Ripper) && type == :ruby ? :ruby18 : type
143
144
  end
144
145
 
145
146
  private
@@ -169,11 +170,11 @@ module YARD
169
170
  end
170
171
  end
171
172
 
172
- self.parser_type = :ruby
173
-
174
- register_parser_type :ruby, Ruby::RubyParser if RUBY19
173
+ register_parser_type :ruby, Ruby::RubyParser
175
174
  register_parser_type :ruby18, Ruby::Legacy::RubyParser
176
175
  register_parser_type :c, CParser, ['c', 'cc', 'cxx', 'cpp']
176
+
177
+ self.parser_type = :ruby
177
178
 
178
179
  # The filename being parsed by the parser.
179
180
  attr_reader :file
@@ -238,8 +239,8 @@ module YARD
238
239
  # Searches for encoding line and forces encoding
239
240
  # @since 0.5.3
240
241
  def convert_encoding(content)
241
- return content if RUBY18
242
- if content =~ /\A(?:\s*#*!.*\r?\n)?\s*#+.*coding\s*[:=]{1,2}\s*(\S+)/i
242
+ return content unless content.respond_to?(:force_encoding)
243
+ if content =~ ENCODING_LINE
243
244
  content.force_encoding($1)
244
245
  else
245
246
  content
data/lib/yard/registry.rb CHANGED
@@ -9,15 +9,27 @@ module YARD
9
9
  # objects are done on the singleton Registry instance using the {Registry.at}
10
10
  # or {Registry.resolve} methods.
11
11
  #
12
- # The registry is saved to a "yardoc" file, which can be loaded back to
13
- # perform any lookups.
12
+ # == Saving / Loading a Registry
13
+ # The registry is saved to a "yardoc file" (actually a directory), which can
14
+ # be loaded back to perform any lookups. See {Registry.load!} and
15
+ # {Registry.save} for information on saving and loading of a yardoc file.
14
16
  #
15
- # This class is a singleton class. Any method called on the class will be
16
- # delegated to the instance.
17
+ # == Threading Notes
18
+ # The registry class is a singleton class that is accessed directly in many
19
+ # places across YARD. To mitigate threading issues, YARD (0.6.5+) makes
20
+ # the Registry thread local. This means all access to a registry for a specific
21
+ # object set must occur in the originating thread.
22
+ #
23
+ # @example Loading a Registry
24
+ # Registry.load!('/path/to/yardocfile') # loads all objects into memory
25
+ # Registry.at('YARD::CodeObjects::Base').docstring
26
+ # # => "+Base+ is the superclass of all code objects ..."
27
+ # @example Performing a Search on a Registry
28
+ # Registry.resolve(P('YARD::CodeObjects::Base'), '#docstring')
29
+ # # => <
17
30
  module Registry
18
31
  DEFAULT_YARDOC_FILE = ".yardoc"
19
32
  LOCAL_YARDOC_INDEX = File.expand_path('~/.yard/gem_index')
20
- @yardoc_file = DEFAULT_YARDOC_FILE
21
33
 
22
34
  extend Enumerable
23
35
 
@@ -58,6 +70,10 @@ module YARD
58
70
  # @return [String] the yardoc filename
59
71
  # @see DEFAULT_YARDOC_FILE
60
72
  attr_accessor :yardoc_file
73
+ def yardoc_file=(v) Thread.current[:__yard_yardoc_file__] = v end
74
+ def yardoc_file
75
+ Thread.current[:__yard_yardoc_file__] ||= DEFAULT_YARDOC_FILE
76
+ end
61
77
 
62
78
  # @group Loading Data from Disk
63
79
 
@@ -81,9 +97,9 @@ module YARD
81
97
  if File.exists?(yardoc_file) && !reparse
82
98
  load_yardoc
83
99
  else
84
- size = @store.keys.size
100
+ size = thread_local_store.keys.size
85
101
  YARD.parse(files)
86
- save if @store.keys.size > size
102
+ save if thread_local_store.keys.size > size
87
103
  end
88
104
  elsif files.is_a?(String)
89
105
  load_yardoc(files)
@@ -99,7 +115,7 @@ module YARD
99
115
  # @return [Registry] the registry object (for chaining)
100
116
  def load_yardoc(file = yardoc_file)
101
117
  clear
102
- @store.load(file)
118
+ thread_local_store.load(file)
103
119
  self
104
120
  end
105
121
 
@@ -113,7 +129,7 @@ module YARD
113
129
  # @since 0.5.1
114
130
  def load!(file = yardoc_file)
115
131
  clear
116
- @store.load!(file)
132
+ thread_local_store.load!(file)
117
133
  self
118
134
  end
119
135
 
@@ -127,7 +143,7 @@ module YARD
127
143
  # @return [Registry] the registry object (for chaining)
128
144
  # @since 0.5.1
129
145
  def load_all
130
- @store.load_all
146
+ thread_local_store.load_all
131
147
  self
132
148
  end
133
149
 
@@ -138,13 +154,13 @@ module YARD
138
154
  # @param [String] file the yardoc file to save to
139
155
  # @return [Boolean] true if the file was saved
140
156
  def save(merge = false, file = yardoc_file)
141
- @store.save(merge, file)
157
+ thread_local_store.save(merge, file)
142
158
  end
143
159
 
144
160
  # Deletes the yardoc file from disk
145
161
  # @return [void]
146
162
  def delete_from_disk
147
- @store.destroy
163
+ thread_local_store.destroy
148
164
  end
149
165
 
150
166
  # @group Adding and Deleting Objects from the Registry
@@ -155,20 +171,20 @@ module YARD
155
171
  # @return [CodeObjects::Base] the registered object
156
172
  def register(object)
157
173
  return if object.is_a?(CodeObjects::Proxy)
158
- @store[object.path] = object
174
+ thread_local_store[object.path] = object
159
175
  end
160
176
 
161
177
  # Deletes an object from the registry
162
178
  # @param [CodeObjects::Base] object the object to remove
163
179
  # @return [void]
164
180
  def delete(object)
165
- @store.delete(object.path)
181
+ thread_local_store.delete(object.path)
166
182
  end
167
183
 
168
184
  # Clears the registry
169
185
  # @return [void]
170
186
  def clear
171
- @store = RegistryStore.new
187
+ self.thread_local_store = RegistryStore.new
172
188
  end
173
189
 
174
190
  # @group Accessing Objects in the Registry
@@ -191,7 +207,7 @@ module YARD
191
207
  # @return [Array<CodeObjects::Base>] the list of objects found
192
208
  # @see CodeObjects::Base#type
193
209
  def all(*types)
194
- @store.values.select do |obj|
210
+ thread_local_store.values.select do |obj|
195
211
  if types.empty?
196
212
  obj != root
197
213
  else
@@ -207,7 +223,7 @@ module YARD
207
223
  # @param [Boolean] reload whether to load entire database
208
224
  # @return [Array<String>] all of the paths in the registry.
209
225
  def paths(reload = false)
210
- @store.keys(reload).map {|k| k.to_s }
226
+ thread_local_store.keys(reload).map {|k| k.to_s }
211
227
  end
212
228
 
213
229
  # Returns the object at a specific path.
@@ -215,12 +231,12 @@ module YARD
215
231
  # returns the {root} object.
216
232
  # @return [CodeObjects::Base] the object at path
217
233
  # @return [nil] if no object is found
218
- def at(path) path ? @store[path] : nil end
234
+ def at(path) path ? thread_local_store[path] : nil end
219
235
  alias_method :[], :at
220
236
 
221
237
  # The root namespace object.
222
238
  # @return [CodeObjects::RootObject] the root object in the namespace
223
- def root; @store[:root] end
239
+ def root; thread_local_store[:root] end
224
240
 
225
241
  # Attempts to find an object by name starting at +namespace+, performing
226
242
  # a lookup similar to Ruby's method of resolving a constant in a namespace.
@@ -285,7 +301,7 @@ module YARD
285
301
 
286
302
  # @return [Hash{String => String}] a set of checksums for files
287
303
  def checksums
288
- @store.checksums
304
+ thread_local_store.checksums
289
305
  end
290
306
 
291
307
  # @param [String] data data to checksum
@@ -294,13 +310,25 @@ module YARD
294
310
  Digest::SHA1.hexdigest(data)
295
311
  end
296
312
 
297
- # @group Managing Internal State (Testing Only)
313
+ # @group Managing Internal State (Advanced / Testing Only)
314
+
315
+ # Whether or not the Registry storage should load everything into a
316
+ # single object database (for disk efficiency), or spread them out
317
+ # (for load time efficiency).
318
+ #
319
+ # @note Setting this attribute to nil will offload the decision to
320
+ # the {RegistryStore storage adapter}.
321
+ # @return [Boolean, nil] if this value is set to nil, the storage
322
+ # adapter will decide how to store the data.
323
+ attr_accessor :single_object_db
324
+ def single_object_db=(v) Thread.current[:__yard_single_db__] = v end
325
+ def single_object_db; Thread.current[:__yard_single_db__] end
298
326
 
299
327
  # The assumed types of a list of paths. This method is used by CodeObjects::Base
300
328
  # @return [{String => Symbol}] a set of unresolved paths and their assumed type
301
329
  # @private
302
330
  def proxy_types
303
- @store.proxy_types
331
+ thread_local_store.proxy_types
304
332
  end
305
333
 
306
334
  # @group Legacy Methods
@@ -355,8 +383,18 @@ module YARD
355
383
  File.exist?(path) ? path : nil
356
384
  end
357
385
  end
386
+
387
+ # @group Threading support
388
+
389
+ # @since 0.6.5
390
+ def thread_local_store
391
+ Thread.current[:__yard_registry__] ||= clear
392
+ end
393
+
394
+ # @since 0.6.5
395
+ def thread_local_store=(value)
396
+ Thread.current[:__yard_registry__] = value
397
+ end
358
398
  end
359
-
360
- clear
361
399
  end
362
400
  end