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
  # encoding: UTF-8
2
2
  # frozen_string_literal: true
3
+ # doctest: true
3
4
 
4
5
  # Requirements
5
6
  # =======================================================================
@@ -21,192 +22,463 @@ require_relative "./link_stdlib/html_helper"
21
22
  # =======================================================================
22
23
 
23
24
  module YARD
24
- module LinkStdlib
25
25
 
26
26
 
27
27
  # Definitions
28
28
  # =======================================================================
29
29
 
30
- # Constants
31
- # ----------------------------------------------------------------------------
32
-
33
- # Available helper modules by their format (as found in `options.format`).
34
- #
35
- # We only cover `:html` for the moment, but may add more in the future.
36
- #
37
- # @return [Hash<Symbol, Module>]
38
- #
39
- HELPERS_BY_FORMAT = {
40
- html: HtmlHelper,
41
- }.freeze
42
-
43
-
44
- # The {Proc} that we add to {YARD::Templates::Template.extra_includes} on
45
- # {.install!}. The proc accepts template options and responds with the helper
46
- # module corresponding to the format (if any - right now we only handle
47
- # `:html`).
48
- #
49
- # We want this to be a constant so we can tell if it's there and
50
- # avoid ever double-adding it.
51
- #
52
- # @return [Proc<YARD::Templates::TemplateOptions -> Module?>]
53
- #
54
- HELPER_FOR_OPTIONS = proc { |options|
55
- HELPERS_BY_FORMAT[ options.format ]
56
- }.freeze
57
-
58
-
59
- # Add the {HELPER_FOR_OPTIONS} {Proc} to
60
- # {YARD::Templates::Template.extra_includes} (if it's not there already).
61
- #
62
- # @see https://www.rubydoc.info/gems/yard/YARD/Templates/Template#extra_includes-class_method
63
- #
64
- # @return [nil]
65
- #
66
- def self.install!
67
- # NOTE Due to YARD start-up order, this happens *before* log level is set,
68
- # so the `--debug` CLI switch won't help see it... don't know a way to
69
- # at the moment.
70
- log.debug "Installing `yard-link_stdlib` plugin..."
71
-
72
- unless YARD::Templates::Template.extra_includes.include? HELPER_FOR_OPTIONS
73
- YARD::Templates::Template.extra_includes << HELPER_FOR_OPTIONS
30
+ module LinkStdlib
31
+
32
+ # Constants
33
+ # ============================================================================
34
+
35
+ DEFAULT_DOMAIN = "docs.ruby-lang.org"
36
+
37
+ DEFAULT_LANG = 'en'
38
+
39
+ DEFAULT_HTTP_URLS = true
40
+
41
+ # Available helper modules by their format (as found in `options.format`).
42
+ #
43
+ # We only cover `:html` for the moment, but may add more in the future.
44
+ #
45
+ # @return [Hash<Symbol, Module>]
46
+ #
47
+ HELPERS_BY_FORMAT = {
48
+ html: HtmlHelper,
49
+ }.freeze
50
+
51
+
52
+ # The {Proc} that we add to {YARD::Templates::Template.extra_includes} on
53
+ # {.install!}. The proc accepts template options and responds with the helper
54
+ # module corresponding to the format (if any - right now we only handle
55
+ # `:html`).
56
+ #
57
+ # We want this to be a constant so we can tell if it's there and
58
+ # avoid ever double-adding it.
59
+ #
60
+ # @return [Proc<YARD::Templates::TemplateOptions -> Module?>]
61
+ #
62
+ HELPER_FOR_OPTIONS = proc { |options|
63
+ HELPERS_BY_FORMAT[ options.format ]
64
+ }.freeze
65
+
66
+
67
+ # Singleton Methods
68
+ # ==========================================================================
69
+
70
+ # @!group Configuration Singleton Methods
71
+ # --------------------------------------------------------------------------
72
+
73
+ # Configured to build `https://` URLs by default?
74
+ #
75
+ # @example Default configuration responds with {DEFAULT_HTTP_URLS}
76
+ # YARD::LinkStdlib.https_urls?
77
+ # #=> true
78
+ #
79
+ # @return [Boolean]
80
+ #
81
+ def self.https_urls?
82
+ DEFAULT_HTTP_URLS
83
+ end # .https_urls?
84
+
85
+
86
+ # Configured domain used as the default to {.build_url}.
87
+ #
88
+ # @example Default configuration responds with {DEFAULT_DOMAIN}
89
+ # YARD::LinkStdlib.domain
90
+ # #=> 'docs.ruby-lang.org'
91
+ #
92
+ # @return [String]
93
+ #
94
+ def self.domain
95
+ DEFAULT_DOMAIN
96
+ end # .domain
97
+
98
+
99
+ # Documentation language to {.build_url} for (when not overridden in method
100
+ # call).
101
+ #
102
+ # @example Default configuration responds with {DEFAULT_LANG}
103
+ # YARD::LinkStdlib.lang
104
+ # #=> 'en'
105
+ #
106
+ # @return [String]
107
+ #
108
+ def self.lang
109
+ DEFAULT_LANG
110
+ end # .lang
111
+
112
+ # @!endgroup Configuration Singleton Methods # *****************************
113
+
114
+
115
+ # @!group Resolving Names Singleton Methods
116
+ # --------------------------------------------------------------------------
117
+
118
+ # Build a URL given a relative path to the document (see {.rel_path_for}).
119
+ #
120
+ # Components may all be individually overridden via keyword arguments;
121
+ # otherwise the current configuration values are used.
122
+ #
123
+ # Format targets <docs.ruby-lang.org>, but *may* work for local or
124
+ # alternative versions as well.
125
+ #
126
+ # @note
127
+ # Will **NOT** generate working URLs for <ruby-doc.org> because they
128
+ # divide language docs into "core" and "stdlib" using an unknown methodology
129
+ # (it's *probably* that C code is in "core" and Ruby in "stdlib", but I'm
130
+ # not sure, and not sure who would be).
131
+ #
132
+ # @example Using defaults
133
+ # YARD::LinkStdlib.build_url 'String.html'
134
+ # #=> 'https://docs.ruby-lang.org/en/2.3.0/String.html'
135
+ #
136
+ # @example Manually override components
137
+ # YARD::LinkStdlib.build_url 'String.html',
138
+ # https: false,
139
+ # domain: 'example.com',
140
+ # lang: 'ja',
141
+ # version: '2.6.0'
142
+ # #=> 'http://example.com/ja/2.6.0/String.html'
143
+ #
144
+ # @param [String] rel_path
145
+ # Relative path to the document, as returned from {.rel_path_for}.
146
+ #
147
+ # @param [Boolean] https
148
+ # Build `https://` URLs (versus `http://`)? Defaults to {.https_urls?}.
149
+ #
150
+ # @param [String] domain
151
+ # Domain docs are hosted at. Defaults to {.domain}.
152
+ #
153
+ # @param [String] lang
154
+ # Language to link to, defaults to {.lang}.
155
+ #
156
+ # Note that at the time of writing (2019.03.08) only English ("en") and
157
+ # Japanese ("ja") are available on <docs.ruby-lang.org>.
158
+ #
159
+ # @param [#to_s] version
160
+ # Ruby version for the URL. Anything that supports `#to_s` will work, but
161
+ # meant for use with {String} or {Gem::Version} (the later of which being
162
+ # what {RubyVersion.minor} returns).
163
+ #
164
+ # Note that <docs.ruby-lang.org> uses only *minor* version-level resolution:
165
+ # you can link to `2.3.0` or `2.4.0`, but not `2.3.7`, `2.4.4`, etc.
166
+ #
167
+ # @return [String]
168
+ # Fully-formed URL, ready for clicks!
169
+ #
170
+ def self.build_url rel_path,
171
+ https: self.https_urls?,
172
+ domain: self.domain,
173
+ lang: self.lang,
174
+ version: RubyVersion.minor
175
+ File.join \
176
+ "http#{ https ? 's' : '' }://",
177
+ domain,
178
+ lang,
179
+ version.to_s,
180
+ rel_path
74
181
  end
182
+
183
+ # @!endgroup Resolving Names Singleton Methods # ***************************
184
+
185
+
186
+ # @!group Querying Singleton Methods
187
+ # --------------------------------------------------------------------------
188
+
189
+ # Find names in the {ObjectMap.current} that match terms.
190
+ #
191
+ # Terms are tested with `#===`, allowing use of {String}, {Regexp}, and
192
+ # potentially others.
193
+ #
194
+ # `mode` controls if names must match any or all terms.
195
+ #
196
+ # @param [Array<Object>] terms
197
+ # Objects that will be tested with `#===` against names in the map to select
198
+ # results.
199
+ #
200
+ # @return [Array<String>]
201
+ # Matching names.
202
+ #
203
+ def self.grep *terms, mode: :any
204
+ ObjectMap.
205
+ current.
206
+ names.
207
+ select { |key|
208
+ case mode
209
+ when :any
210
+ terms.any? { |term| term === key }
211
+ when :all
212
+ terms.all? { |term| term === key }
213
+ else
214
+ raise ArgumentError,
215
+ "Bad mode, expected `:any` or `:all`, found #{ mode.inspect }"
216
+ end
217
+ }.
218
+ sort_by( &:downcase )
219
+ end # .grep
220
+
221
+ # @!endgroup Querying Singleton Methods # **********************************
222
+
223
+
224
+ # @!group Installation Singleton Methods
225
+ # --------------------------------------------------------------------------
226
+
227
+ # Add the {HELPER_FOR_OPTIONS} {Proc} to
228
+ # {YARD::Templates::Template.extra_includes} (if it's not there already).
229
+ #
230
+ # @see https://www.rubydoc.info/gems/yard/YARD/Templates/Template#extra_includes-class_method
231
+ #
232
+ # @return [nil]
233
+ #
234
+ def self.install!
235
+ # NOTE Due to YARD start-up order, this happens *before* log level is set,
236
+ # so the `--debug` CLI switch won't help see it... don't know a way to
237
+ # at the moment.
238
+ log.debug "Installing `yard-link_stdlib` plugin..."
239
+
240
+ unless YARD::Templates::Template.extra_includes.include? HELPER_FOR_OPTIONS
241
+ YARD::Templates::Template.extra_includes << HELPER_FOR_OPTIONS
242
+ end
243
+
244
+ YARD::CLI::CommandParser.commands[:stdlib] ||= YARD::CLI::LinkStdlib
245
+
246
+ nil
247
+ end # .install!
248
+
249
+ # @!endgroup Installation Singleton Methods # ******************************
75
250
 
76
- YARD::CLI::CommandParser.commands[:stdlib] ||= YARD::CLI::LinkStdlib
77
-
78
- nil
79
- end # .install!
80
-
81
-
82
- # General Utilities
83
- # ----------------------------------------------------------------------------
84
-
85
- # @param [Symbol | #to_s] value
86
- # Either an object whose string representation expands to a path to an
87
- # existing directory, or one of the following symbols:
88
- #
89
- # 1. `:system`, `:global` - `/tmp/yard-link_stdlib`
90
- # 2.
91
- #
92
- # @return [Pathname]
93
- # The assigned path.
94
- #
95
- def self.tmp_dir= value
96
- @tmp_dir = case value
97
- when :system, :global
98
- Pathname.new '/tmp/yard-link_stdlib'
99
- when :user
100
- Pathname.new( '~/tmp/yard-link_stdlib' ).expand_path
101
- when :gem, :install
102
- ROOT.join 'tmp'
103
- when :project
104
- Pathname.getwd.join 'tmp', 'yard-link_stdlib'
105
- else
106
- dir = Pathname.new( value.to_s ).expand_path
107
-
108
- unless dir.directory?
109
- raise ArgumentError,
110
- "When assigning a custom tmp_dir path it must be an existing " +
111
- "directory, received #{ value.to_s.inspect }"
251
+
252
+ # General Utilities
253
+ # ----------------------------------------------------------------------------
254
+
255
+ # Normalize a stdlib name: remove "::" prefix if present, and convert "." to
256
+ # "::".
257
+ #
258
+ # @example Just passing through
259
+ # YARD::LinkStdlib.normalize_name 'String#length'
260
+ # #=> 'String#length'
261
+ #
262
+ # @example Strip "::" prefix
263
+ # YARD::LinkStdlib.normalize_name '::String#length'
264
+ # #=> 'String#length'
265
+ #
266
+ # @example Puke if it's not a {String}
267
+ # YARD::LinkStdlib.normalize_name 123
268
+ # #=> raise TypeError, %(`name` must be a String, given Fixnum: 123)
269
+ #
270
+ # @param [::String] name
271
+ # Code object name, as it may appear in YARD.
272
+ #
273
+ # @return [::String]
274
+ #
275
+ def self.normalize_name name
276
+ unless name.is_a? ::String
277
+ raise TypeError,
278
+ "`name` must be a String, given #{ name.class }: #{ name.inspect }"
279
+ end
280
+
281
+ # Strip off any leading `::`
282
+ if name.start_with? '::'
283
+ name = name[ 2..-1 ]
112
284
  end
285
+
286
+ # Stdlib rdoc uses `ClassOrModule::class_method` format for class methods,
287
+ # so we want to convert to that
288
+ name.sub /\.(\w+[\?\!]?)\z/, '::\1'
289
+ end # .normalize_name
290
+
291
+
292
+ # Set the {.tmp_dir} where we put temporary files (like Ruby source
293
+ # downloads).
294
+ #
295
+ # @param [Symbol | #to_s] value
296
+ # Either an object whose string representation expands to a path to an
297
+ # existing directory, or one of the following symbols:
298
+ #
299
+ # 1. `:system`, `:global` → `/tmp/yard-link_stdlib`.
300
+ #
301
+ # 2. `:user` → `~/tmp/yard-link_stdlib`.
302
+ #
303
+ # 3. `:gem`, `:install` → `tmp` relative to `yard-link_stdlib`'s root
304
+ # directory ({YARD::LinkStdlib::ROOT}).
305
+ #
306
+ # @return [Pathname]
307
+ # The assigned path.
308
+ #
309
+ def self.tmp_dir= value
310
+ @tmp_dir = case value
311
+ when :system, :global
312
+ Pathname.new '/tmp/yard-link_stdlib'
313
+ when :user
314
+ Pathname.new( '~/tmp/yard-link_stdlib' ).expand_path
315
+ when :gem, :install
316
+ ROOT.join 'tmp'
317
+ when :project
318
+ Pathname.getwd.join 'tmp', 'yard-link_stdlib'
319
+ else
320
+ dir = Pathname.new( value.to_s ).expand_path
321
+
322
+ unless dir.directory?
323
+ raise ArgumentError,
324
+ "When assigning a custom tmp_dir path it must be an existing " +
325
+ "directory, received #{ value.to_s.inspect }"
326
+ end
327
+ end
328
+
329
+ FileUtils.mkdir_p @tmp_dir unless @tmp_dir.exist?
330
+
331
+ @tmp_dir
113
332
  end
114
333
 
115
- FileUtils.mkdir_p @tmp_dir unless @tmp_dir.exist?
116
334
 
117
- @tmp_dir
118
- end
335
+ # Get where to put temporary shit, most Ruby source code that's been downloaded
336
+ # to generate the link maps from.
337
+ #
338
+ # @return [Pathname]
339
+ #
340
+ def self.tmp_dir &block
341
+ if @tmp_dir.nil?
342
+ self.tmp_dir = repo? ? :gem : :user
343
+ end
344
+
345
+ if block
346
+ Dir.chdir @tmp_dir, &block
347
+ else
348
+ @tmp_dir
349
+ end
350
+ end
119
351
 
120
352
 
121
- # Get where to put temporary shit, most Ruby source code that's been downloaded
122
- # to generate the link maps from.
123
- #
124
- # @return [Pathname]
125
- #
126
- def self.tmp_dir &block
127
- if @tmp_dir.nil?
128
- self.tmp_dir = repo? ? :gem : :user
353
+ # Run a {Kernel#system}, raising if it fails.
354
+ #
355
+ # @param [Array] args
356
+ # See {Kernel#system}.
357
+ #
358
+ # @return [true]
359
+ #
360
+ # @raise [SystemCallError]
361
+ # If the command fails.
362
+ #
363
+ def self.system! *args
364
+ opts = args[-1].is_a?( Hash ) ? args.pop : {}
365
+ env = args[0].is_a?( Hash ) ? args.shift : {}
366
+
367
+ log.info [
368
+ "Making system call:",
369
+ "\t#{ Shellwords.join args }",
370
+ ( opts.empty? ? nil : "\toptions: #{ opts.inspect }" ),
371
+ ( env.empty? ? nil : "\tenv: #{ env.inspect }" ),
372
+ ].compact.join( "\n" )
373
+
374
+ Kernel.system( *args ).tap { |success|
375
+ unless success
376
+ raise SystemCallError.new \
377
+ %{ Code #{ $?.exitstatus } error executing #{ args.inspect } },
378
+ $?.exitstatus
379
+ end
380
+ }
129
381
  end
130
382
 
131
- if block
132
- Dir.chdir @tmp_dir, &block
133
- else
134
- @tmp_dir
383
+
384
+ # Make a `GET` request. Follows redirects. Handles SSL.
385
+ #
386
+ # @param [String] url
387
+ # What ya want.
388
+ #
389
+ # @param [Integer] redirect_limit
390
+ # Max number of redirects to follow before it gives up.
391
+ #
392
+ # @return [Net::HTTPResponse]
393
+ # The first successful response that's not a redirect.
394
+ #
395
+ # @raise [Net::HTTPError]
396
+ # If there was an HTTP error.
397
+ #
398
+ # @raise
399
+ #
400
+ def self.http_get url, redirect_limit = 5
401
+ raise "Too many HTTP redirects" if redirect_limit < 0
402
+
403
+ uri = URI url
404
+ request = Net::HTTP::Get.new uri.path
405
+ response = Net::HTTP.start(
406
+ uri.host,
407
+ uri.port,
408
+ use_ssl: uri.scheme == 'https',
409
+ ) { |http| http.request request }
410
+
411
+ case response
412
+ when Net::HTTPSuccess
413
+ response
414
+ when Net::HTTPRedirection
415
+ http_get response['location'], redirect_limit - 1
416
+ else
417
+ response.error!
418
+ end
135
419
  end
136
- end
137
-
138
-
139
- # Run a {Kernel.system}, raising if it fails.
140
- #
141
- # @param [Array] *args
142
- # See {Kernel.system}.
143
- #
144
- # @return [true]
145
- #
146
- # @raise [SystemCallError]
147
- # If the command fails.
148
- #
149
- def self.system! *args
150
- opts = args[-1].is_a?( Hash ) ? args.pop : {}
151
- env = args[0].is_a?( Hash ) ? args.shift : {}
152
-
153
- log.info [
154
- "Making system call:",
155
- "\t#{ Shellwords.join args }",
156
- ( opts.empty? ? nil : "\toptions: #{ opts.inspect }" ),
157
- ( env.empty? ? nil : "\tenv: #{ env.inspect }" ),
158
- ].compact.join( "\n" )
159
-
160
- Kernel.system( *args ).tap { |success|
161
- unless success
162
- raise SystemCallError.new \
163
- %{ Code #{ $?.exitstatus } error executing #{ args.inspect } },
164
- $?.exitstatus
165
- end
166
- }
167
- end
168
-
169
-
170
- # Make a `GET` request. Follows redirects. Handles SSL.
171
- #
172
- # @param [String] url
173
- # What ya want.
174
- #
175
- # @param [Integer] redirect_limit
176
- # Max number of redirects to follow before it gives up.
177
- #
178
- # @return [Net::HTTPResponse]
179
- # The first successful response that's not a redirect.
180
- #
181
- # @raise [Net::HTTPError]
182
- # If there was an HTTP error.
183
- #
184
- # @raise
185
- #
186
- def self.http_get url, redirect_limit = 5
187
- raise "Too many HTTP redirects" if redirect_limit < 0
188
-
189
- uri = URI url
190
- request = Net::HTTP::Get.new uri.path
191
- response = Net::HTTP.start(
192
- uri.host,
193
- uri.port,
194
- use_ssl: uri.scheme == 'https',
195
- ) { |http| http.request request }
196
-
197
- case response
198
- when Net::HTTPSuccess
199
- response
200
- when Net::HTTPRedirection
201
- http_get response['location'], redirect_limit - 1
202
- else
203
- response.error!
204
- end
205
- end
420
+
421
+
422
+ # Dump a hash of values as a `debug`-level log message (`log` is a global
423
+ # function when you're hangin' in the YARD).
424
+ #
425
+ # @example Dump values with a message
426
+ # obj = [ 1, 2, 3 ]
427
+ #
428
+ # dump "There was a problem with the ", obj, "object!",
429
+ # value_a: 'aye!',
430
+ # value_b: 'bzzz'
431
+ #
432
+ # @example Dump values without a message
433
+ # dump value_a: 'aye!', value_b: 'bzzz'
434
+ #
435
+ # @param [Array<String | Object>] message
436
+ # Optional log message. Entries will be space-joined to form the message
437
+ # string: strings will be left as-is, and other objects will be
438
+ # stringified by calling their `#inspect` method. See examples.
439
+ #
440
+ # @param [Hash<Symbol, Object>] values
441
+ # Map of names to values to dump.
442
+ #
443
+ # @return
444
+ # Whatever `log.debug` returns.
445
+ #
446
+ def self.dump *message, **values
447
+
448
+ max_name_length = values.
449
+ keys.
450
+ map { |name| name.to_s.length }.
451
+ max
452
+
453
+ values_str = values.
454
+ map { |name, value|
455
+ name_str = "%-#{ max_name_length + 2 }s" % "#{ name }:"
456
+
457
+ " #{ name_str } #{ value.inspect } (#{ value.class })"
458
+ }.
459
+ join( "\n" )
460
+
461
+ message_str = message.
462
+ map { |part|
463
+ case part
464
+ when String
465
+ part
466
+ else
467
+ part.inspect
468
+ end
469
+ }.
470
+ join( " " )
471
+
472
+ log_str = "Values:\n\n#{ values_str }\n"
473
+ log_str = "#{ message_str }\n\n#{ log_str }" unless message_str.empty?
474
+
475
+ log.debug "yard-link_stdlib: #{ log_str }"
476
+ end # .dump
477
+
478
+ end # module LinkStdlib
206
479
 
207
480
 
208
481
  # /Namespace
209
482
  # =======================================================================
210
483
 
211
- end # module LinkStdlib
212
484
  end # module YARD
Binary file
Binary file
Binary file