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