yard-link_stdlib 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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