yard-link_stdlib 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # encoding: UTF-8
3
+ # doctest: true
3
4
 
4
5
 
5
6
  # Requirements
@@ -56,11 +57,55 @@ class ObjectMap
56
57
  }
57
58
 
58
59
  @@current = nil
60
+
61
+
62
+ # A map of module names that we know *actually* point to another one.
63
+ #
64
+ # This is basically here to support {YAML}, which internally points to
65
+ # {Psych} in a truly annoying fashion... seems to be a remnant from when
66
+ # `Syck` was around, but that appears to have been yanked out years ago
67
+ # around Ruby 2.0, and I can't think of seeing anyone use anything except
68
+ # {Psych} for about a decade.
69
+ #
70
+ # However, it means that there is no entry in the object map for things like
71
+ # {YAML.load}, which are pretty commonly used. This functionality allows
72
+ # us to address that, by being aware that {YAML} points to {Psych} for
73
+ # practical purposes.
74
+ #
75
+ # @return [Hash<String, String>]
76
+ #
77
+ @@module_aliases = {
78
+ "YAML" => "Psych",
79
+ }
80
+
81
+ @@name_rewrites = {
82
+ # The instance methods of the {JSON} module are encapsulated in a
83
+ # {Module#module_function} context, which adds them as module methods as
84
+ # well, but RDoc doesn't seem to pick that up, so we just transform them
85
+ # to make this bullshit work.
86
+ #
87
+ # Creates mappings like:
88
+ #
89
+ # "JSON::load" => "JSON.html#method-i-load"
90
+ # "JSON::dump" => "JSON.html#method-i-dump"
91
+ #
92
+ /\AJSON#(.*)\z/ => ->( match ) { "JSON::#{ match[ 1 ] }" },
93
+ }
59
94
 
60
95
 
61
- # Class Methods
96
+ # Singleton Methods
62
97
  # ========================================================================
63
-
98
+
99
+ # Set the directory in which to load and store map data. Must exist.
100
+ #
101
+ # @param [String | Pathname] path
102
+ # New data directory. Will be expanded.
103
+ #
104
+ # @return [Pathname]
105
+ #
106
+ # @raise [ArgumentError]
107
+ # If `path` is not a directory.
108
+ #
64
109
  def self.data_dir= path
65
110
  expanded = Pathname.new( path ).expand_path
66
111
 
@@ -74,12 +119,24 @@ class ObjectMap
74
119
  @@data_dir = expanded
75
120
  end
76
121
 
77
-
122
+
123
+ # The directory in which to load and store object map data.
124
+ #
125
+ # @example
126
+ # YARD::LinkStdlib::ObjectMap.data_dir
127
+ # #=> Pathname.new "#{ LinkStdlib::ROOT }/maps"
128
+ #
129
+ # @return [Pathname]
130
+ #
78
131
  def self.data_dir
79
132
  @@data_dir
80
133
  end
81
134
 
82
-
135
+
136
+ # Get the {ObjectMap} for {RubyVersion.get}.
137
+ #
138
+ # @return [ObjectMap]
139
+ #
83
140
  def self.current
84
141
  version = RubyVersion.get
85
142
 
@@ -90,43 +147,40 @@ class ObjectMap
90
147
  @@current
91
148
  end
92
149
 
93
-
94
150
 
95
- # @todo Document list method.
151
+ # Get all the object maps present.
96
152
  #
97
- # @param [type] arg_name
98
- # @todo Add name param description.
153
+ # @return [Array<ObjectMap>]
99
154
  #
100
- # @return [return_type]
101
- # @todo Document return value.
102
- #
103
- def self.list
155
+ def self.all
104
156
  data_dir.entries.
105
157
  select { |filename| filename.to_s =~ /\Aruby\-(\d+\.)+json\.gz\z/ }.
106
158
  map { |filename|
107
159
  new File.basename( filename.to_s, '.json.gz' ).sub( /\Aruby\-/, '' )
108
160
  }.
109
161
  sort
110
- end # .list
111
-
112
-
113
- # def self.cache key, &load
114
- # @cache ||= {}
115
-
116
- # unless @cache.key? key
117
- # @cache[key] = load.call
118
- # end
119
-
120
- # @cache[key]
121
- # end
122
-
123
-
124
- # def self.get version = LinkStdlib::RubyVersion.get, make: true
125
- # cache version do
126
- # make( version ) if make
127
- # load version
128
- # end
129
- # end
162
+ end # .all
163
+
164
+
165
+ # Add a Ruby version (download and build map data, see {#make}).
166
+ #
167
+ # @param [String | Gem::Version] ruby_version
168
+ # Version to add.
169
+ #
170
+ # @param [Boolean] force
171
+ # Pass `true` to re-build map when already present (see {#make}).
172
+ #
173
+ # @return [ObjectMap]
174
+ # Added map.
175
+ #
176
+ def self.add ruby_version, force: false
177
+ new( ruby_version ).make force: force
178
+ end
179
+
180
+
181
+ def self.remove ruby_version, remove_source: true, force: false
182
+ raise "TODO"
183
+ end
130
184
 
131
185
 
132
186
  # Ruby version.
@@ -134,18 +188,45 @@ class ObjectMap
134
188
  # @return [Gem::Version]
135
189
  #
136
190
  attr_reader :version
137
-
138
-
191
+
192
+
193
+ # Construction
194
+ # ==========================================================================
195
+
196
+ # Instantiate an {ObjectMap} for a Ruby version.
197
+ #
198
+ # This just initialized the interface - the source may need to be downloaded
199
+ # and the map generated (see {#make}) to use it for anything.
200
+ #
201
+ # @param [String | Gem::Version] version
202
+ # Ruby version.
203
+ #
139
204
  def initialize version
140
205
  @version = Gem::Version.new version
141
206
  end
207
+
208
+
209
+ # Instance Methods
210
+ # ==========================================================================
142
211
 
143
-
212
+
213
+ # The name for this {ObjectMap}'s data file.
214
+ #
215
+ # @example
216
+ # YARD::LinkStdlib::ObjectMap.new( '2.3.7' ).filename
217
+ # #=> "ruby-2.3.7.json.gz"
218
+ #
219
+ # @return [String]
220
+ #
144
221
  def filename
145
222
  @filename ||= "ruby-#{ version }.json.gz"
146
223
  end
147
224
 
148
-
225
+
226
+ # Absolute path to this {ObjectMap}'s data file.
227
+ #
228
+ # @return [Pathname]
229
+ #
149
230
  def path
150
231
  @path ||= self.class.data_dir.join filename
151
232
  end
@@ -159,12 +240,23 @@ class ObjectMap
159
240
  path.exist?
160
241
  end
161
242
 
162
-
243
+
244
+ # The {RubySource} interface for this {ObjectMap}.
245
+ #
246
+ # @return [RubySource]
247
+ #
163
248
  def source
164
249
  @source ||= RubySource.new version
165
250
  end
166
251
 
167
-
252
+
253
+ # Build the map data file (if needed or forced).
254
+ #
255
+ # @param [Boolean] force
256
+ # Set to true to re-build even if the map data file is present.
257
+ #
258
+ # @return [ObjectMap] self
259
+ #
168
260
  def make force: false
169
261
  # Bail unless forced or the map is not present
170
262
  if force
@@ -193,6 +285,7 @@ class ObjectMap
193
285
 
194
286
  def data reload: false
195
287
  if reload || @data.nil?
288
+ @name_rewrites = nil
196
289
  @data = Zlib::GzipReader.open path do |gz|
197
290
  JSON.load gz.read
198
291
  end
@@ -200,8 +293,129 @@ class ObjectMap
200
293
 
201
294
  @data
202
295
  end
296
+
297
+
298
+ # Names of the objects in {#data} (equivalent to `self.data.keys`).
299
+ #
300
+ # @param [Boolean] reload
301
+ # When `true`, reload the {#data} from disk first.
302
+ #
303
+ # @return [Array<String>]
304
+ #
305
+ def names reload: false
306
+ data( reload: reload ).keys
307
+ end
308
+
309
+
310
+ def name_rewrites reload: false
311
+ data( reload: true ) if reload
312
+
313
+ @name_rewrites ||= \
314
+ data.each_with_object( {} ) do |(name, rel_path), name_rewrites|
315
+ @@name_rewrites.each do |regexp, transformer|
316
+ if (match = regexp.match( name ))
317
+ name_rewrites[ transformer.call match ] = rel_path
318
+ end
319
+ end
320
+ end
321
+ end
322
+
323
+
324
+ # Get the relative path for the URL of an online stdlib document given the
325
+ # code object's name.
326
+ #
327
+ # @example
328
+ # YARD::LinkStdlib::ObjectMap.current.resolve 'String'
329
+ # #=> [ 'String', 'String.html' ]
330
+ #
331
+ # @example De-Aliasing
332
+ # YARD::LinkStdlib::ObjectMap.current.resolve 'YAML.load'
333
+ # #=> [ 'Psych::load', 'Psych.html#method-c-load' ]
334
+ #
335
+ # @param [String] name
336
+ #
337
+ # @return [nil]
338
+ # The (normalized) `name` was not found in the {ObjectMap}.
339
+ #
340
+ # @return [Array[(String, String?)>]
341
+ # The normalized name (which may be totally different than the `name`
342
+ # argument due to de-aliasing) followed by the relative URL path to it's
343
+ # doc.
344
+ #
345
+ def resolve name
346
+ name = LinkStdlib.normalize_name name
347
+ rel_path = data[ name ]
348
+
349
+ if rel_path.nil?
350
+ split = name.split '::'
351
+
352
+ if (de_aliased_module_name = @@module_aliases[ split.first ])
353
+ de_aliased_name = \
354
+ [ de_aliased_module_name, *split[ 1..-1 ] ].join( '::' )
355
+
356
+ if (de_aliased_module_name = data[ de_aliased_name ])
357
+ return [ de_aliased_name, de_aliased_module_name ]
358
+ end
359
+ end
360
+
361
+ if (rewritten_rel_path = name_rewrites[ name ])
362
+ log.debug "Found re-written relative path: " +
363
+ "#{ name } -> #{ rewritten_rel_path.inspect }"
364
+
365
+ return [ name, rewritten_rel_path ]
366
+ end # if rewritten_rel_path
367
+ end # if rel_path.nil?
368
+
369
+ # NOTE `rel_path` may be `nil`, indicating we didn't find shit
370
+ [ name, rel_path ]
371
+ end # .resolve
372
+
373
+
374
+ # Get the doc URL for a name.
375
+ #
376
+ # @example Using defaults
377
+ # YARD::LinkStdlib::ObjectMap.current.url_for 'String'
378
+ # #=> 'https://docs.ruby-lang.org/en/2.3.0/String.html'
379
+ #
380
+ # @example Manually override components
381
+ # YARD::LinkStdlib::ObjectMap.current.url_for 'String',
382
+ # https: false,
383
+ # domain: 'example.com',
384
+ # lang: 'ja'
385
+ # #=> 'http://example.com/ja/2.3.0/String.html'
386
+ #
387
+ # @param [String] name
388
+ # Name of the code object.
389
+ #
390
+ # @param [Hash<Symbol, Object>] url_options
391
+ # Passed to {LinkStdlib.build_url}.
392
+ #
393
+ # @return [nil]
394
+ # The (normalized) `name` was not found in the {ObjectMap}.
395
+ #
396
+ # @return [String]
397
+ # The fully-formed URL to the online doc.
398
+ #
399
+ def url_for name, **url_options
400
+ name, rel_path = resolve name
401
+
402
+ if rel_path
403
+ LinkStdlib.build_url \
404
+ rel_path,
405
+ **url_options,
406
+ version: RubyVersion.minor( version )
407
+ end
408
+ end # .url_for
409
+
410
+
411
+ # Language Integration Instance Methods
412
+ # --------------------------------------------------------------------------
203
413
 
204
-
414
+ # Compare {ObjectMap} instances by their {#version} (used to sort them).
415
+ #
416
+ # @return [Fixnum]
417
+ # `0` is equal, negatives and positives denote order.
418
+ #
205
419
  def <=> other
206
420
  version <=> other.version
207
421
  end
@@ -35,7 +35,22 @@ class RubySource
35
35
 
36
36
  # Class Methods
37
37
  # ============================================================================
38
-
38
+
39
+
40
+ def self.make_missing?
41
+ unless instance_variable_defined? :@make_missing
42
+ @make_missing = true
43
+ end
44
+
45
+ @make_missing
46
+ end
47
+
48
+
49
+ def self.make_missing= value
50
+ @make_missing = !!value
51
+ end
52
+
53
+
39
54
  # Ensure the version's source is downloaded and extracted.
40
55
  #
41
56
  # @example
@@ -157,6 +172,12 @@ class RubySource
157
172
  log.info "Source for Ruby #{ version } is present."
158
173
  return
159
174
  end
175
+
176
+ unless self.class.make_missing?
177
+ raise RuntimeError,
178
+ "Object map for Ruby version #{ version } missing; " +
179
+ "not configured to auto-make"
180
+ end
160
181
 
161
182
  # Download unless the tar's already there
162
183
  download
@@ -91,8 +91,9 @@ module RubyVersion
91
91
  #
92
92
  # @return [Gem::Version]
93
93
  #
94
- def self.minor
95
- Gem::Version.new( ( get.segments[0..1] + [0] ).map( &:to_s ).join( '.' ) )
94
+ def self.minor version = self.get
95
+ Gem::Version.new \
96
+ ( version.segments[0..1] + [0] ).map( &:to_s ).join( '.' )
96
97
  end
97
98
 
98
99
 
@@ -101,7 +102,7 @@ module RubyVersion
101
102
  # we were happy with from the gemspec.
102
103
  #
103
104
  # @param [:minimum_supported, :latest_stable, :current_runtime] value
104
- # Just picks which of these versions {#fallback} will use:
105
+ # Just picks which of these versions {.fallback} will use:
105
106
  #
106
107
  # 1. {MINIMUM_SUPPORTED}
107
108
  # 2. {LATEST_STABLE}
@@ -55,13 +55,13 @@ def self.repo?
55
55
  ROOT.join( 'dev' ).directory?
56
56
  end
57
57
 
58
- # {Singleton} extension of {Gem::Version} that loads {Locd::VERSION} and
58
+ # {Singleton} extension of {Gem::Version} that loads {VERSION} and
59
59
  # provides some convenient methods.
60
60
  #
61
61
  class Version < Gem::Version
62
62
  include ::Singleton
63
63
 
64
- # Private method to instantiate the {#instance} using the {Locd::VERSION}
64
+ # Private method to instantiate the {.instance} using the {VERSION}
65
65
  # {String}.
66
66
  #
67
67
  # @return [Version]
@@ -74,7 +74,7 @@ class Version < Gem::Version
74
74
  # instances.
75
75
  private_class_method :new
76
76
 
77
- # Proxies to the {#instance}'s {#dev?}.
77
+ # Proxies to the {.instance}'s {#dev?}.
78
78
  #
79
79
  # @return (see #dev?)
80
80
  #