yard-link_stdlib 0.1.0

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.
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ # encoding: UTF-8
3
+
4
+
5
+ # Namespace
6
+ # ========================================================================
7
+
8
+ module YARD
9
+ module LinkStdlib
10
+
11
+
12
+ # Definitions
13
+ # ========================================================================
14
+
15
+ # Dump a hash of values as a `debug`-level log message (`log` is a global
16
+ # function when you're hangin' in the YARD).
17
+ #
18
+ # @example Dump values with a message
19
+ # dump "There was a problem with the ", obj, "object!",
20
+ # value_a: value_a,
21
+ # value_b: value_b
22
+ #
23
+ # @example Dump values without a message
24
+ # dump value_a: value_a, value_b: value_b
25
+ #
26
+ # @param [Array<String | Object>] message
27
+ # Optional log message. Entries will be space-joined to form the message
28
+ # string: strings will be left as-is, and other objects will be
29
+ # stringified by calling their `#inspect` method. See examples.
30
+ #
31
+ # @param [Hash<Symbol, Object>] values
32
+ # Map of names to values to dump.
33
+ #
34
+ # @return
35
+ # Whatever `log.debug` returns.
36
+ #
37
+ def self.dump *message, **values
38
+
39
+ max_name_length = values.
40
+ keys.
41
+ map { |name| name.to_s.length }.
42
+ max
43
+
44
+ values_str = values.
45
+ map { |name, value|
46
+ name_str = "%-#{ max_name_length + 2 }s" % "#{ name }:"
47
+
48
+ " #{ name_str } #{ value.inspect } (#{ value.class })"
49
+ }.
50
+ join( "\n" )
51
+
52
+ message_str = message.
53
+ map { |part|
54
+ case part
55
+ when String
56
+ part
57
+ else
58
+ part.inspect
59
+ end
60
+ }.
61
+ join( " " )
62
+
63
+ log_str = "Values:\n\n#{ values_str }\n"
64
+ log_str = "#{ message_str }\n\n#{ log_str }" unless message_str.empty?
65
+
66
+ log.debug "yard-link_stdlib: #{ log_str }"
67
+ end # .dump
68
+
69
+
70
+ # /Namespace
71
+ # ========================================================================
72
+
73
+ end # module LinkStdlib
74
+ end # module YARD
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+ # encoding: UTF-8
3
+
4
+ # Requirements
5
+ # ========================================================================
6
+
7
+ # Stdlib
8
+ # ------------------------------------------------------------------------
9
+
10
+ require 'zlib'
11
+
12
+ # Deps
13
+ # ------------------------------------------------------------------------
14
+
15
+ require 'yard'
16
+
17
+ # Project / Package
18
+ # ------------------------------------------------------------------------
19
+
20
+ require_relative './dump'
21
+ require_relative './ruby_version'
22
+ require_relative './object_map'
23
+
24
+
25
+ # Namespace
26
+ # ========================================================================
27
+
28
+ module YARD
29
+ module LinkStdlib
30
+
31
+
32
+ # Definitions
33
+ # ========================================================================
34
+
35
+ # A helper module to add to {YARD::Templates::Template.extra_includes} to
36
+ # handle linking stdlib references.
37
+ #
38
+ # @see https://www.rubydoc.info/gems/yard/YARD%2FTemplates%2FTemplate.extra_includes
39
+ #
40
+ module HtmlHelper
41
+
42
+ # The {Proc} we pass to
43
+ #
44
+ # @return [Proc]
45
+ #
46
+ INCLUDE_FILTER = proc do |options|
47
+ HtmlHelper if options.format == :html
48
+ end
49
+
50
+
51
+ # The only real meat of this whole gem - hook into object linking.
52
+ #
53
+ # We link to the stdlib if:
54
+ #
55
+ # 1. We didn't link to anything else (local stuff take precedence).
56
+ # 2. We can find a match for the reference.
57
+ #
58
+ # @see https://www.rubydoc.info/gems/yard/YARD/Templates/Helpers/HtmlHelper#link_object-instance_method
59
+ #
60
+ # @param [YARD::CodeObjects::Base] obj
61
+ # The object to link to.
62
+ #
63
+ # @param [String?] title
64
+ # Optional title to display the link as.
65
+ #
66
+ # @param [nil | ?] anchor
67
+ # Not sure... not doc'd in YARD.
68
+ #
69
+ # @param [Boolean] relative
70
+ # Again, not sure... not doc'd in YARD, but seems like a boolean.
71
+ #
72
+ # @return [String]
73
+ # The HTML source for the link.
74
+ #
75
+ def link_object obj, title = nil, anchor = nil, relative = true
76
+ # See what the super method can do...
77
+ super_link = super
78
+
79
+ # Bail out unless `super` returned a {String}, which I'm guessing would be
80
+ # `nil`, but not sure.
81
+ unless super_link.is_a?( String )
82
+ LinkStdlib.dump "Object not linkable",
83
+ obj: obj,
84
+ super_link: super_link
85
+ return super_link
86
+ end
87
+
88
+ LinkStdlib.dump "Object *may* be linkable!",
89
+ obj: obj,
90
+ super_link: super_link
91
+
92
+ # `key` is what we gonna look up in the stdlib...
93
+ key = super_link
94
+
95
+ # Strip off any leading `::`
96
+ key = key[2..-1] if key.start_with?( '::' )
97
+
98
+ # Stdlib rdoc uses `ClassOrModule::class_method` format for class methods,
99
+ # so we want to convert to that
100
+ stdlib_key = key.sub /\.(\w+[\?\!]?)\z/, '::\1'
101
+
102
+ if ( path = ObjectMap.current.data[ stdlib_key ] )
103
+ LinkStdlib.dump "Matched stdlib link!",
104
+ path: path,
105
+ key: key,
106
+ stdlib_key: stdlib_key
107
+
108
+ version = LinkStdlib::RubyVersion.minor
109
+
110
+ [
111
+ %{<a href="https://docs.ruby-lang.org/en/#{ version }/#{ path }">},
112
+ key,
113
+ %{</a>},
114
+ ].join ''
115
+
116
+ else
117
+ LinkStdlib.dump "Got nada.",
118
+ super_link: super_link
119
+
120
+ super_link
121
+
122
+ end
123
+
124
+ end # #link_object
125
+
126
+ end # module HtmlHelper
127
+
128
+
129
+ # /Namespace
130
+ # ========================================================================
131
+
132
+ end # module LinkStdlib
133
+ end # module YARD
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+ # encoding: UTF-8
3
+
4
+
5
+ # Requirements
6
+ # ========================================================================
7
+
8
+ # Stdlib
9
+ # ----------------------------------------------------------------------------
10
+
11
+ # Encode object maps in JSON... it's just super easy for everything, and
12
+ # gzip should take care of any size concerns.
13
+ require 'json'
14
+
15
+ # Good ol' mkdir_p...
16
+ require 'fileutils'
17
+
18
+ # Store the JSON-encoded maps compressed
19
+ require 'zlib'
20
+
21
+
22
+ # Project / Package
23
+ # ------------------------------------------------------------------------
24
+
25
+ # We need {YARD::LinkStdlib::ROOT} to find `//tmp`
26
+ require_relative './version'
27
+
28
+ # Need to be able to {RubySource.ensure} we have source code we need
29
+ require_relative './ruby_source'
30
+
31
+
32
+ # Namespace
33
+ # ========================================================================
34
+
35
+ module YARD
36
+ module LinkStdlib
37
+
38
+
39
+ # Definitions
40
+ # ========================================================================
41
+
42
+
43
+ class ObjectMap
44
+
45
+ # Mixins
46
+ # ==========================================================================
47
+
48
+ include Comparable
49
+
50
+
51
+ # Class Variables
52
+ # ==========================================================================
53
+
54
+ @@data_dir = LinkStdlib::ROOT.join( 'maps' ).tap { |path|
55
+ FileUtils.mkdir_p( path ) unless path.exist?
56
+ }
57
+
58
+ @@current = nil
59
+
60
+
61
+ # Class Methods
62
+ # ========================================================================
63
+
64
+ def self.data_dir= path
65
+ expanded = Pathname.new( path ).expand_path
66
+
67
+ unless expanded.directory?
68
+ raise ArgumentError,
69
+ "Custom ObjectMap.data_dir must expand to an existing directory," +
70
+ "try creating it first? Received #{ path.inspect }, expanded to " +
71
+ expanded.to_s.inspect
72
+ end
73
+
74
+ @@data_dir = expanded
75
+ end
76
+
77
+
78
+ def self.data_dir
79
+ @@data_dir
80
+ end
81
+
82
+
83
+ def self.current
84
+ version = RubyVersion.get
85
+
86
+ if @@current.nil? || @@current.version != version
87
+ @@current = new( version ).make
88
+ end
89
+
90
+ @@current
91
+ end
92
+
93
+
94
+
95
+ # @todo Document list method.
96
+ #
97
+ # @param [type] arg_name
98
+ # @todo Add name param description.
99
+ #
100
+ # @return [return_type]
101
+ # @todo Document return value.
102
+ #
103
+ def self.list
104
+ data_dir.entries.
105
+ select { |filename| filename.to_s =~ /\Aruby\-(\d+\.)+json\.gz\z/ }.
106
+ map { |filename|
107
+ new File.basename( filename.to_s, '.json.gz' ).sub( /\Aruby\-/, '' )
108
+ }.
109
+ 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
130
+
131
+
132
+ # Ruby version.
133
+ #
134
+ # @return [Gem::Version]
135
+ #
136
+ attr_reader :version
137
+
138
+
139
+ def initialize version
140
+ @version = Gem::Version.new version
141
+ end
142
+
143
+
144
+ def filename
145
+ @filename ||= "ruby-#{ version }.json.gz"
146
+ end
147
+
148
+
149
+ def path
150
+ @path ||= self.class.data_dir.join filename
151
+ end
152
+
153
+
154
+ # Is the object map present for this {#version}?
155
+ #
156
+ # @return [Boolean]
157
+ #
158
+ def present?
159
+ path.exist?
160
+ end
161
+
162
+
163
+ def source
164
+ @source ||= RubySource.new version
165
+ end
166
+
167
+
168
+ def make force: false
169
+ # Bail unless forced or the map is not present
170
+ if force
171
+ log.info "FORCE making object map for Ruby #{ version }..."
172
+ elsif !present?
173
+ log.info "Making object map for Ruby #{ version }..."
174
+ else
175
+ log.info "Object map for Ruby #{ version } is present."
176
+ return self
177
+ end
178
+
179
+ # Make sure we have the source files in place
180
+ source.ensure
181
+
182
+ # Invoke the build script
183
+ LinkStdlib.system! \
184
+ LinkStdlib::ROOT.join( 'bin', 'make_map.rb' ).to_s,
185
+ source.src_path.to_s,
186
+ path.to_s
187
+
188
+ log.info "Made object map for Ruby #{ version }."
189
+
190
+ self
191
+ end
192
+
193
+
194
+ def data reload: false
195
+ if reload || @data.nil?
196
+ @data = Zlib::GzipReader.open path do |gz|
197
+ JSON.load gz.read
198
+ end
199
+ end
200
+
201
+ @data
202
+ end
203
+
204
+
205
+ def <=> other
206
+ version <=> other.version
207
+ end
208
+
209
+
210
+ end # class ObjectMap
211
+
212
+
213
+ # /Namespace
214
+ # ========================================================================
215
+
216
+ end # module LinkStdlib
217
+ end # module YARD
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+ # encoding: UTF-8
3
+
4
+
5
+ # Requirements
6
+ # ========================================================================
7
+
8
+ # Stdlib
9
+ # ----------------------------------------------------------------------------
10
+
11
+ require 'net/http'
12
+ require 'uri'
13
+
14
+
15
+ # Namespace
16
+ # ========================================================================
17
+
18
+ module YARD
19
+ module LinkStdlib
20
+
21
+
22
+ # Definitions
23
+ # ========================================================================
24
+
25
+ # Light utility object around a Ruby {Gem::Version} used to download and
26
+ # extract it's source code to the {LinkStdlib.tmp_dir}.
27
+ #
28
+ class RubySource
29
+
30
+ # Mixins
31
+ # ========================================================================
32
+
33
+ include Comparable
34
+
35
+
36
+ # Class Methods
37
+ # ============================================================================
38
+
39
+ # Ensure the version's source is downloaded and extracted.
40
+ #
41
+ # @example
42
+ # YARD::LinkStdlib::RubySource.ensure '2.5.1'
43
+ #
44
+ # @param [Gem::Version || #to_s] version
45
+ # The Ruby version you need present.
46
+ #
47
+ # @return [RubySource]
48
+ # The utility object instance.
49
+ #
50
+ def self.ensure version
51
+ new( version ).ensure
52
+ end # .ensure
53
+
54
+
55
+ def self.list
56
+ LinkStdlib.tmp_dir.entries.
57
+ select { |filename|
58
+ filename.to_s =~ /\Aruby\-\d+\_\d+\_\d+\z/
59
+ }.
60
+ map { |filename|
61
+ new filename.to_s.sub( /\Aruby\-/, '' ).gsub( '_', '.' )
62
+ }.
63
+ sort
64
+ end
65
+
66
+
67
+ # Ruby version.
68
+ #
69
+ # @return [Gem::Version]
70
+ #
71
+ attr_reader :version
72
+
73
+
74
+ # Construction
75
+ # ========================================================================
76
+
77
+ # Make a new instance for a version.
78
+ #
79
+ # @param [Gem::Version || #to_s] version
80
+ # The Ruby version to work with.
81
+ #
82
+ def initialize version
83
+ @version = Gem::Version.new version
84
+ end
85
+
86
+
87
+ # Instance Methods
88
+ # ========================================================================
89
+
90
+ def ruby_style_version
91
+ @ruby_style_version ||= version.to_s.gsub '.', '_'
92
+ end
93
+
94
+
95
+ def url
96
+ @url ||=
97
+ "https://github.com/ruby/ruby/archive/v#{ ruby_style_version }.tar.gz"
98
+ end
99
+
100
+
101
+ def tar_filename
102
+ @tar_filename ||= "ruby-#{ ruby_style_version }.tar.gz"
103
+ end
104
+
105
+
106
+ def tar_path
107
+ @tar_path ||= LinkStdlib.tmp_dir.join tar_filename
108
+ end
109
+
110
+
111
+ def src_path
112
+ @src_path ||= LinkStdlib.tmp_dir.join "ruby-#{ ruby_style_version }"
113
+ end
114
+
115
+
116
+ def download force: false
117
+ if force
118
+ log.info "FORCING download of Ruby #{ version } tarball..."
119
+ elsif tar_path.exist?
120
+ log.info "Ruby #{ version } tarball present."
121
+ return self
122
+ else
123
+ log.info "Downloading Ruby #{ version } tarball..."
124
+ end
125
+
126
+ response = LinkStdlib.http_get url
127
+ tar_path.open( "wb" ) { |file| file.write response.body }
128
+
129
+ self # For chaining
130
+ end
131
+
132
+
133
+ def extract force: false
134
+ if force
135
+ log.info "FORCING extraction of Ruby #{ version } source tarball..."
136
+ elsif src_path.exist?
137
+ log.info "Ruby #{ version } source present."
138
+ return self
139
+ else
140
+ log.info "Extracting #{ tar_path } -> #{ src_path }..."
141
+ end
142
+
143
+ LinkStdlib.system! \
144
+ 'tar', '-x', # extract
145
+ '-f', tar_path.to_s, # file
146
+ '-C', LinkStdlib.tmp_dir.to_s # directory (chdir)
147
+
148
+ log.info "Source for Ruby #{ version } extracted to #{ src_path }."
149
+
150
+ self # For chaining
151
+ end
152
+
153
+
154
+ def ensure
155
+ if src_path.exist?
156
+ # Nothing to do, source is already in place
157
+ log.info "Source for Ruby #{ version } is present."
158
+ return
159
+ end
160
+
161
+ # Download unless the tar's already there
162
+ download
163
+
164
+ # And we must need to extract it since the src path wasn't there
165
+ extract
166
+
167
+ self # For chaining
168
+ end
169
+
170
+
171
+ def to_s
172
+ %{#<YARD::LinkStdlib::RubySource "#{ version }">}
173
+ end
174
+
175
+ def inspect; to_s; end
176
+
177
+
178
+ def <=> other
179
+ version <=> other.version
180
+ end
181
+
182
+ end # class RubySource
183
+
184
+
185
+ # /Namespace
186
+ # ========================================================================
187
+
188
+ end # module LinkStdlib
189
+ end # module YARD