yard-link_stdlib 0.1.0 → 0.1.1

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.
@@ -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
  #