myprecious 0.0.8 → 0.1.2
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.
- checksums.yaml +4 -4
- data/bin/myprecious +5 -1
- data/lib/myprecious.rb +536 -107
- data/lib/myprecious/data_caches.rb +71 -0
- data/lib/myprecious/python_packages.rb +1175 -0
- data/lib/myprecious/ruby_gems.rb +273 -0
- metadata +134 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be0d5a3297a77bea22a403968fa7c3a23f2fd7c2d4ce96b957c832788f29de91
|
4
|
+
data.tar.gz: d82b540dbb7be2ffb7601fe571cc07482d2fbe87563b8d78d937bc791bd22d3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79e7317fa010abdc6ab9d7207d7be1229d85e2ba2a53cb2225e1a8bed7c95063d51294fab4917f1449e106210e1aa3ef5c52fa743bde9e6fb85f582a6942694a
|
7
|
+
data.tar.gz: 0fb7d8ec2c159fe58867b2f5cbbfa95a9a27c7f0e6f020ea2f69fb2ecf55b49b2ed385857c3a6162670c07a39871b54724f2005c71dd6897e691438701d33316
|
data/bin/myprecious
CHANGED
data/lib/myprecious.rb
CHANGED
@@ -1,121 +1,550 @@
|
|
1
|
-
require 'gems'
|
2
1
|
require 'date'
|
3
2
|
require 'git'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'pathname'
|
5
|
+
require 'rake/toolkit_program'
|
6
|
+
require 'uri'
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
8
|
+
# Declare the module here so it doesn't cause problems for files in the
|
9
|
+
# "myprecious" directory (or _does_ cause problems if they try to declare
|
10
|
+
# it a class)
|
11
|
+
module MyPrecious
|
12
|
+
DATA_DIR = Pathname('~/.local/lib/myprecious').expand_path
|
13
|
+
ONE_DAY = 60 * 60 * 24
|
14
|
+
end
|
15
|
+
require 'myprecious/data_caches'
|
16
|
+
|
17
|
+
module MyPrecious
|
18
|
+
extend Rake::DSL
|
19
|
+
|
20
|
+
Program = Rake::ToolkitProgram
|
21
|
+
Program.title = "myprecious Dependecy Reporting Tool"
|
22
|
+
|
23
|
+
def self.common_program_args(parser, args)
|
24
|
+
parser.on(
|
25
|
+
'-o', '--out FILE',
|
26
|
+
"Output file to generate",
|
27
|
+
) {|fpath| args.output_file = Pathname(fpath)}
|
28
|
+
|
29
|
+
args.target = Pathname.pwd
|
30
|
+
parser.on(
|
31
|
+
'-C', '--dir PATH',
|
32
|
+
"Path to inspect",
|
33
|
+
) do |fpath|
|
34
|
+
fpath = Pathname(fpath)
|
35
|
+
parser.invalid_args!("#{fpath} does not exist.") unless fpath.exist?
|
36
|
+
args.target = fpath
|
37
|
+
end
|
38
|
+
|
39
|
+
parser.on(
|
40
|
+
'--[no-]cache',
|
41
|
+
"Control caching of gem information"
|
42
|
+
) {|v| MyPrecious.caching_disabled = v}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Declare the tasks exposed as subcommands in this block
|
46
|
+
Program.command_tasks do
|
47
|
+
desc "Generate report on Ruby gems"
|
48
|
+
task('ruby-gems').parse_args(into: OpenStruct.new) do |parser, args|
|
49
|
+
parser.expect_positional_cardinality(0)
|
50
|
+
common_program_args(parser, args)
|
51
|
+
end
|
52
|
+
task 'ruby-gems' do
|
53
|
+
require 'myprecious/ruby_gems'
|
54
|
+
args = Program.args
|
55
|
+
out_fpath = args.output_file || Reporting.default_output_fpath(args.target)
|
56
|
+
|
57
|
+
col_order = Reporting.read_column_order_from(out_fpath)
|
58
|
+
|
59
|
+
# Get all gems used via RubyGemInfo.each_gem_used, accumulating version requirements
|
60
|
+
gems = RubyGemInfo.accum_gem_lock_info(args.target)
|
61
|
+
|
62
|
+
out_fpath.open('w') do |outf|
|
63
|
+
# Header
|
64
|
+
outf.puts "Last updated: #{Time.now.rfc2822}; Use for directional purposes only, this data is not real time and might be slightly inaccurate"
|
65
|
+
outf.puts
|
66
|
+
|
67
|
+
Reporting.header_lines(
|
68
|
+
col_order,
|
69
|
+
RubyGemInfo.method(:col_title)
|
70
|
+
).each {|l| outf.puts l}
|
71
|
+
|
72
|
+
# Iterate all gems in name order, pulling column values from the RubyGemInfo objects
|
73
|
+
gems.keys.sort_by {|n| n.downcase}.map {|name| gems[name]}.each do |gem|
|
74
|
+
Reporting.on_dependency(gem.name) do
|
75
|
+
outf.puts col_order.markdown_columns(MarkdownAdapter.new(gem))
|
42
76
|
end
|
43
|
-
elsif line_number > 2
|
44
|
-
gem_name_index = word_array[gem_name_pos].strip
|
45
|
-
#extract just the name of the gem from the first column
|
46
|
-
#since that column contains a markdown-formatted hyperlink
|
47
|
-
gem_name_index = gem_name_index[/\[(.*?)\]/,1]
|
48
|
-
gem_lines[gem_name_index] = line_number
|
49
77
|
end
|
50
|
-
|
51
78
|
end
|
52
|
-
|
53
|
-
else
|
54
|
-
File.open(repo_name+'.md', 'w') { |write_file|
|
55
|
-
write_file.puts "Last updated:" + Date.today.to_s + "; Use for directional purposes only, this data is not real time and might be slightly inaccurate" + "\n\n"
|
56
|
-
write_file.puts "Gem | Our Version | Latest Version | Date available | Age (in days) | License Type | Change Log"
|
57
|
-
write_file.puts "--- | --- | --- | --- | --- | --- | ---"
|
58
|
-
}
|
79
|
+
|
59
80
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
81
|
+
|
82
|
+
desc "Generate report on Python packages"
|
83
|
+
task('python-packages').parse_args(into: OpenStruct.new) do |parser, args|
|
84
|
+
parser.expect_positional_cardinality(0)
|
85
|
+
common_program_args(parser, args)
|
86
|
+
|
87
|
+
parser.on(
|
88
|
+
'-r', '--requirements-file FILE',
|
89
|
+
"requirements.txt-style file to read"
|
90
|
+
) do |file|
|
91
|
+
if args.requirements_file
|
92
|
+
invalid_args!("Only one requirements file may be specified.")
|
93
|
+
end
|
94
|
+
args.requirements_file = file
|
95
|
+
end
|
96
|
+
end
|
97
|
+
task 'python-packages' do
|
98
|
+
require 'myprecious/python_packages'
|
99
|
+
args = Program.args
|
100
|
+
out_fpath = args.output_file || Reporting.default_output_fpath(args.target)
|
101
|
+
|
102
|
+
col_order = Reporting.read_column_order_from(out_fpath)
|
103
|
+
|
104
|
+
req_file = args.requirements_file
|
105
|
+
req_file ||= PyPackageInfo.guess_req_file(args.target)
|
106
|
+
req_file = args.target.join(req_file) unless req_file.nil?
|
107
|
+
if req_file.nil? || !req_file.exist?
|
108
|
+
invalid_args!("Unable to guess requirement file name; specify with '-r FILE' option.")
|
109
|
+
end
|
110
|
+
pkgs = PyPackageInfo::Reader.new(req_file).each_installed_package
|
111
|
+
|
112
|
+
out_fpath.open('w') do |outf|
|
113
|
+
# Header
|
114
|
+
outf.puts "Last updated: #{Time.now.rfc2822}; Use for directional purposes only, this data is not real time and might be slightly inaccurate"
|
115
|
+
outf.puts
|
116
|
+
|
117
|
+
Reporting.header_lines(
|
118
|
+
col_order,
|
119
|
+
PyPackageInfo.method(:col_title)
|
120
|
+
).each {|l| outf.puts l}
|
121
|
+
|
122
|
+
pkgs = pkgs.to_a
|
123
|
+
pkgs.sort_by! {|pkg| pkg.name.downcase}
|
124
|
+
pkgs.each do |pkg|
|
125
|
+
Reporting.on_dependency(pkg.name) do
|
126
|
+
outf.puts col_order.markdown_columns(MarkdownAdapter.new(pkg))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
desc "Clear all myprecious data caches"
|
133
|
+
task('clear-caches').parse_args(into: OpenStruct.new) do |parser, args|
|
134
|
+
args.prompt = true
|
135
|
+
parser.on('--[no-]confirm', "Control confirmation prompt") {|v| args.prompt = v}
|
136
|
+
|
137
|
+
# This option exists to force users to specify the full "--no-confirm"
|
138
|
+
parser.on('--no-confir', "Warn about incomplete --no-confirm") do
|
139
|
+
parser.invalid_args!("Incomplete --no-confirm flag")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
task 'clear-caches' do
|
143
|
+
# Load all "myprecious/*.rb" files so we know where all the caches are
|
144
|
+
Dir[File.expand_path('myprecious/*.rb', __dir__)].each {|f| require f}
|
145
|
+
|
146
|
+
next if Program.args.prompt && !yes_no('Delete all cached data (y/n)?')
|
147
|
+
MyPrecious.data_caches.each do |cache|
|
69
148
|
begin
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
149
|
+
rm_r cache
|
150
|
+
rescue Errno::ENOENT
|
151
|
+
# No problem, we wanted to delete this anyway
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Prompt user for a yes/no answer
|
159
|
+
#
|
160
|
+
# It doesn't matter if they've redirected STDIN/STDOUT -- this grabs the TTY
|
161
|
+
# directly.
|
162
|
+
#
|
163
|
+
def self.yes_no(prompt)
|
164
|
+
Pathname('/dev/tty').open('r+') do |term|
|
165
|
+
loop do
|
166
|
+
term.write("#{prompt} ")
|
167
|
+
case term.gets[0..-2]
|
168
|
+
when 'y', 'Y', 'yes', 'Yes', 'YES'
|
169
|
+
return true
|
170
|
+
when 'n', 'N', 'no', 'No', 'NO'
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Tool for getting information about the Git repository associated with a
|
179
|
+
# directory
|
180
|
+
#
|
181
|
+
class GitInfoExtractor
|
182
|
+
URL_PATTERN = /\/([^\/]+)\.git$/
|
183
|
+
|
184
|
+
def initialize(dir)
|
185
|
+
super()
|
186
|
+
@dir = dir
|
187
|
+
end
|
188
|
+
attr_reader :dir
|
189
|
+
|
190
|
+
def git_info
|
191
|
+
@git_info ||= Git.open(self.dir)
|
192
|
+
end
|
193
|
+
|
194
|
+
def origin_remote
|
195
|
+
git_info.remotes.find {|r| r.name == 'origin'}
|
196
|
+
end
|
197
|
+
|
198
|
+
def repo_name
|
199
|
+
@repo_name ||= (URL_PATTERN =~ origin_remote.url) && $1
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Common behavior in dependency report generation
|
205
|
+
#
|
206
|
+
# The methods here are not specific to any language or dependency management
|
207
|
+
# framework. They do work with ColumnOrder and an expected set of attributes
|
208
|
+
# on the dependency information objects.
|
209
|
+
#
|
210
|
+
module Reporting
|
211
|
+
##
|
212
|
+
# Compute the default output filepath for a directory
|
213
|
+
#
|
214
|
+
def default_output_fpath(dir)
|
215
|
+
dir / (GitInfoExtractor.new(dir).repo_name + "-dependency-tracking.md")
|
216
|
+
end
|
217
|
+
module_function :default_output_fpath
|
218
|
+
|
219
|
+
##
|
220
|
+
# Read the column order from the file at the given path
|
221
|
+
#
|
222
|
+
# If +fpath+ indicates a file that does not exist, return the default
|
223
|
+
# ColumnOrder.
|
224
|
+
#
|
225
|
+
def read_column_order_from(fpath)
|
226
|
+
result = ColumnOrder.new
|
227
|
+
begin
|
228
|
+
prev_line = nil
|
229
|
+
fpath.open {|inf| inf.each_line do |line|
|
230
|
+
if prev_line && /^-+(?:\|-+)*$/ =~ line.gsub(' ', '')
|
231
|
+
result.read_order_from_headers(prev_line)
|
232
|
+
break
|
109
233
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
234
|
+
prev_line = line
|
235
|
+
end}
|
236
|
+
rescue Errno::ENOENT
|
237
|
+
# No problem
|
238
|
+
end
|
239
|
+
return result
|
240
|
+
end
|
241
|
+
module_function :read_column_order_from
|
242
|
+
|
243
|
+
##
|
244
|
+
# Generate header lines for the output Markdown table
|
245
|
+
#
|
246
|
+
# Returns an Array of strings, currently two lines giving header text and
|
247
|
+
# divider row.
|
248
|
+
#
|
249
|
+
def header_lines(order, titlizer)
|
250
|
+
col_titles = order.map {|c| titlizer.call(c)}
|
251
|
+
|
252
|
+
# Check that all attributes in #order round-trip through the title name
|
253
|
+
order.zip(col_titles) do |attr, title|
|
254
|
+
unless title.kind_of?(Symbol) || ColumnOrder.col_from_text_name(title) == attr
|
255
|
+
raise "'#{attr}' does not round-trip (rendered as #{result.inspect})"
|
113
256
|
end
|
114
257
|
end
|
258
|
+
|
259
|
+
return [
|
260
|
+
col_titles.join(" | "),
|
261
|
+
(["---"] * col_titles.length).join(" | "),
|
262
|
+
]
|
263
|
+
end
|
264
|
+
module_function :header_lines
|
265
|
+
# TODO: Mark dependencies with colors a la https://stackoverflow.com/a/41247934
|
266
|
+
|
267
|
+
##
|
268
|
+
# Converts an attribute name to the column title for generic attributes
|
269
|
+
#
|
270
|
+
# Dependency info classes (like RubyGemInfo) can delegate common column
|
271
|
+
# title generation to this function.
|
272
|
+
#
|
273
|
+
def common_col_title(attr)
|
274
|
+
case attr
|
275
|
+
when :current_version then 'Our Version'
|
276
|
+
when :age then 'Age (in days)'
|
277
|
+
when :latest_version then 'Latest Version'
|
278
|
+
when :latest_released then 'Date Available'
|
279
|
+
when :recommended_version then 'Recommended Version'
|
280
|
+
when :license then 'License Type'
|
281
|
+
when :changelog then 'Change Log'
|
282
|
+
when :obsolescence then 'How Bad'
|
283
|
+
else
|
284
|
+
warn("'#{attr}' column does not have a mapped name")
|
285
|
+
attr
|
286
|
+
end
|
287
|
+
end
|
288
|
+
module_function :common_col_title
|
289
|
+
|
290
|
+
##
|
291
|
+
# Determine obsolescence level from days
|
292
|
+
#
|
293
|
+
# Returns one of +nil+, +:mild+, +:moderate+, or +:severe+.
|
294
|
+
#
|
295
|
+
# +at_least_moderate:+ allows putting a floor of +:moderate+ obsolescence
|
296
|
+
# on the result.
|
297
|
+
#
|
298
|
+
def obsolescence_by_age(days, at_least_moderate: false)
|
299
|
+
return case
|
300
|
+
when days < 270
|
301
|
+
at_least_moderate ? :moderate : nil
|
302
|
+
when days < 500
|
303
|
+
at_least_moderate ? :moderate : :mild
|
304
|
+
when days < 730
|
305
|
+
:moderate
|
306
|
+
else
|
307
|
+
:severe
|
308
|
+
end
|
309
|
+
end
|
310
|
+
module_function :obsolescence_by_age
|
311
|
+
|
312
|
+
##
|
313
|
+
# Wrap output from processing of an individual dependency with intro and
|
314
|
+
# outro messages
|
315
|
+
#
|
316
|
+
def on_dependency(name)
|
317
|
+
progress_out = $stdout
|
318
|
+
progress_out.puts("--- Reporting on #{name}...")
|
319
|
+
yield
|
320
|
+
progress_out.puts(" (done)")
|
321
|
+
end
|
322
|
+
module_function :on_dependency
|
323
|
+
end
|
324
|
+
|
325
|
+
##
|
326
|
+
# Order of columns in a Markdown table
|
327
|
+
#
|
328
|
+
# Contains the default column ordering when constructed. Columns are
|
329
|
+
# identified by the Symbol commonly used as an attribute on a dependency
|
330
|
+
# info object (e.g. RubyGemInfo instance). Objects of this class behave
|
331
|
+
# to some extent like frozen Array instances.
|
332
|
+
#
|
333
|
+
class ColumnOrder
|
334
|
+
DEFAULT = %i[name current_version age latest_version latest_released recommended_version license changelog].freeze
|
335
|
+
COLUMN_FROM_TEXT_NAME = {
|
336
|
+
'gem' => :name,
|
337
|
+
'package' => :name,
|
338
|
+
'module' => :name,
|
339
|
+
'our version' => :current_version,
|
340
|
+
'how bad' => :obsolescence,
|
341
|
+
'latest version' => :latest_version,
|
342
|
+
'date available' => :latest_released,
|
343
|
+
'age (in days)' => :age,
|
344
|
+
'license type' => :license,
|
345
|
+
/change ?log/ => :changelog,
|
346
|
+
'recommended version' => :recommended_version,
|
347
|
+
}
|
348
|
+
|
349
|
+
def initialize
|
350
|
+
super
|
351
|
+
@order = DEFAULT
|
352
|
+
end
|
353
|
+
|
354
|
+
##
|
355
|
+
# Get the +n+-th column attribute Symbol
|
356
|
+
#
|
357
|
+
def [](n)
|
358
|
+
@order[n]
|
359
|
+
end
|
360
|
+
|
361
|
+
def length
|
362
|
+
@order.length
|
363
|
+
end
|
364
|
+
|
365
|
+
def each(&blk)
|
366
|
+
@order.each(&blk)
|
367
|
+
end
|
368
|
+
include Enumerable
|
369
|
+
|
370
|
+
##
|
371
|
+
# Update the column order to match those in the given line
|
372
|
+
#
|
373
|
+
# Columns not included in the line are appended in the order they
|
374
|
+
# appear in the default order.
|
375
|
+
#
|
376
|
+
def read_order_from_headers(headers_line)
|
377
|
+
headers = headers_line.split('|').map {|h| h.strip.squeeze(' ')}
|
378
|
+
@order = headers.map {|h| self.class.col_from_text_name(h)}.compact
|
379
|
+
|
380
|
+
# Add in any missing columns at the end
|
381
|
+
@order.concat(DEFAULT - @order)
|
382
|
+
|
383
|
+
return @order.dup
|
384
|
+
end
|
385
|
+
|
386
|
+
##
|
387
|
+
# Render a line to include in a Markdown table for the given dependency
|
388
|
+
#
|
389
|
+
# The dependency must know how to respond to (Ruby) messages (i.e.
|
390
|
+
# have attributes) for all columns currently included in the order as
|
391
|
+
# represented by this instance.
|
392
|
+
#
|
393
|
+
def markdown_columns(dependency)
|
394
|
+
map {|attr| dependency.send(attr)}.join(" | ")
|
395
|
+
end
|
396
|
+
|
397
|
+
##
|
398
|
+
# Given a text name, derive the equivalent column attribute
|
399
|
+
#
|
400
|
+
def self.col_from_text_name(n)
|
401
|
+
n = n.downcase
|
402
|
+
entry = COLUMN_FROM_TEXT_NAME.find {|k, v| k === n}
|
403
|
+
return entry && entry[1]
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
##
|
408
|
+
# Extension of String that can accomodate some additional commentary
|
409
|
+
#
|
410
|
+
# The +update_info+ attribute is used to pass information about changes
|
411
|
+
# to licensing between the current and recommended version of a dependency,
|
412
|
+
# and may be +nil+.
|
413
|
+
#
|
414
|
+
class LicenseDescription < String
|
415
|
+
attr_accessor :update_info
|
416
|
+
end
|
417
|
+
|
418
|
+
##
|
419
|
+
# Dependency info wrapper to generate nice Markdown columns
|
420
|
+
#
|
421
|
+
# This wrapper takes basic data from the underlying dependency info object
|
422
|
+
# and returns enhanced Markdown for selected columns (e.g. +name+).
|
423
|
+
#
|
424
|
+
class MarkdownAdapter
|
425
|
+
def initialize(dep)
|
426
|
+
super()
|
427
|
+
@dependency = dep
|
428
|
+
end
|
429
|
+
attr_reader :dependency
|
430
|
+
|
431
|
+
##
|
432
|
+
# Generate Markdown linking the +name+ to the homepage for the dependency
|
433
|
+
#
|
434
|
+
def name
|
435
|
+
cswatch = begin
|
436
|
+
color_swatch + ' '
|
437
|
+
rescue StandardError
|
438
|
+
''
|
439
|
+
end
|
440
|
+
"#{cswatch}[#{dependency.name}](#{dependency.homepage_uri})"
|
441
|
+
rescue StandardError
|
442
|
+
dependency.name
|
443
|
+
end
|
444
|
+
|
445
|
+
##
|
446
|
+
# Include information about temporal difference between current and
|
447
|
+
# recommended versions
|
448
|
+
#
|
449
|
+
def recommended_version
|
450
|
+
recommended_version = dependency.recommended_version
|
451
|
+
if dependency.current_version < recommended_version
|
452
|
+
span_comment = begin
|
453
|
+
if days_newer = dependency.days_between_current_and_recommended
|
454
|
+
" -- #{days_newer} days newer"
|
455
|
+
else
|
456
|
+
""
|
457
|
+
end
|
458
|
+
end
|
459
|
+
"**#{recommended_version}**#{span_comment}"
|
460
|
+
else
|
461
|
+
recommended_version
|
462
|
+
end
|
463
|
+
rescue StandardError
|
464
|
+
recommended_version || "(error)"
|
465
|
+
end
|
466
|
+
|
467
|
+
##
|
468
|
+
# Include update info in the license column
|
469
|
+
#
|
470
|
+
def license
|
471
|
+
value = dependency.license
|
472
|
+
if value.update_info
|
473
|
+
"#{value}<br/>(#{value.update_info})"
|
474
|
+
else
|
475
|
+
value
|
476
|
+
end
|
477
|
+
rescue StandardError
|
478
|
+
"(error)"
|
479
|
+
end
|
480
|
+
|
481
|
+
##
|
482
|
+
# Render short links for http: or https: changelog URLs
|
483
|
+
#
|
484
|
+
def changelog
|
485
|
+
base_val = begin
|
486
|
+
dependency.changelog
|
487
|
+
rescue StandardError
|
488
|
+
return "(error)"
|
489
|
+
end
|
490
|
+
|
491
|
+
begin
|
492
|
+
uri = URI.parse(base_val)
|
493
|
+
if ['http', 'https'].include?(uri.scheme)
|
494
|
+
return "[on #{uri.hostname}](#{base_val})"
|
495
|
+
end
|
496
|
+
rescue StandardError
|
497
|
+
end
|
498
|
+
return base_val
|
499
|
+
end
|
500
|
+
|
501
|
+
def obsolescence
|
502
|
+
color_swatch
|
503
|
+
rescue StandardError
|
504
|
+
''
|
505
|
+
end
|
506
|
+
|
507
|
+
##
|
508
|
+
# Get a CSS-style hex color code corresponding to the obsolescence of the dependency
|
509
|
+
#
|
510
|
+
def color
|
511
|
+
case dependency.obsolescence
|
512
|
+
when :mild then "dde418"
|
513
|
+
when :moderate then "f9b733"
|
514
|
+
when :severe then "fb0e0e"
|
515
|
+
else "4dda1b"
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
##
|
520
|
+
# Markdown for an obsolescence color swatch
|
521
|
+
#
|
522
|
+
# Sourced from: https://stackoverflow.com/a/41247934
|
523
|
+
#
|
524
|
+
def color_swatch
|
525
|
+
""
|
526
|
+
end
|
527
|
+
|
528
|
+
##
|
529
|
+
# Delegate other attribute queries to the base dependency object
|
530
|
+
#
|
531
|
+
# Errors are caught and rendered as "(error)"
|
532
|
+
#
|
533
|
+
def method_missing(meth, *args, &blk)
|
534
|
+
dependency.send(meth, *args, &blk)
|
535
|
+
rescue NoMethodError
|
536
|
+
raise
|
537
|
+
rescue StandardError
|
538
|
+
"(error)"
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
module URIModuleMethods
|
543
|
+
def try_parse(s)
|
544
|
+
parse(s)
|
545
|
+
rescue URI::InvalidURIError
|
546
|
+
nil
|
115
547
|
end
|
116
|
-
File.open(repo_name+'.md', 'w') { |f| f.write(final_write.join) }
|
117
|
-
file.close
|
118
548
|
end
|
549
|
+
URI.extend(URIModuleMethods)
|
119
550
|
end
|
120
|
-
|
121
|
-
MyPrecious.update
|