gemstar 1.1 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/gemstar/change_log.rb +198 -5
- data/lib/gemstar/git_hub.rb +3 -2
- data/lib/gemstar/ruby_gems_metadata.rb +36 -0
- data/lib/gemstar/version.rb +1 -1
- data/lib/gemstar/web/app.rb +50 -6
- data/lib/gemstar/web/templates/app.css +13 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aba5f74f93dc292f713a6f5d1a824cc04d7601fffac736f075c75f08ea769ff7
|
|
4
|
+
data.tar.gz: f13a06cba387d9ae4ec744ed6e9703bbe8c1aea95cd8f27ab2f4efd816af33d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e45c7adfbfae8779801141084c0097d57be63415398c1a1b695a090042aa1c8fc6c94764275963aca7ef361de4e63f38f39a172b5158814282cd43ce865f5556
|
|
7
|
+
data.tar.gz: ad082b18db97c9f7a1a1c74de309fad18ca282cdd41c42918b27818b7bcc6da895fc9320660f35a0546028b75ed56726a5a545b7e709f4eb82d9a49237f938f5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 1.1.1
|
|
4
|
+
|
|
5
|
+
- Server: Show release dates for changelog entries.
|
|
6
|
+
- Fix GitHub release discovery with direct tag-page fallback and paginated GitHub tags support.
|
|
7
|
+
- Add changelog_uri fallback for GitHub repos with no changelog_uri metadata.
|
|
8
|
+
- Fix change log display for aws, herb, and pagy gems.
|
|
9
|
+
|
|
3
10
|
## 1.1
|
|
4
11
|
|
|
5
12
|
- Server: Add **JavaScript package support** to browse change logs for your project's packages. This currently
|
data/lib/gemstar/change_log.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "cgi"
|
|
3
|
+
require "date"
|
|
3
4
|
require "json"
|
|
5
|
+
require "time"
|
|
4
6
|
|
|
5
7
|
module Gemstar
|
|
6
8
|
class ChangeLog
|
|
@@ -31,7 +33,7 @@ module Gemstar
|
|
|
31
33
|
return @sections if !cache_only && defined?(@sections) && !force_refresh
|
|
32
34
|
|
|
33
35
|
metadata_key = @metadata.respond_to?(:cache_key) ? @metadata.cache_key : @metadata.gem_name
|
|
34
|
-
cache_key = "sections-
|
|
36
|
+
cache_key = "sections-v5-#{metadata_key}"
|
|
35
37
|
serialized = if cache_only
|
|
36
38
|
Cache.peek(cache_key)
|
|
37
39
|
else
|
|
@@ -82,6 +84,31 @@ module Gemstar
|
|
|
82
84
|
result
|
|
83
85
|
end
|
|
84
86
|
|
|
87
|
+
def release_dates(versions: nil, cache_only: false, force_refresh: false)
|
|
88
|
+
requested_versions = normalize_requested_versions(versions)
|
|
89
|
+
metadata_key = @metadata.respond_to?(:cache_key) ? @metadata.cache_key : @metadata.gem_name
|
|
90
|
+
cache_key = "release-dates-v2-#{metadata_key}"
|
|
91
|
+
serialized = if cache_only
|
|
92
|
+
Cache.peek(cache_key)
|
|
93
|
+
else
|
|
94
|
+
Cache.fetch(cache_key, force: force_refresh) do
|
|
95
|
+
JSON.generate(compute_release_dates(force_refresh: force_refresh))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
dates = if serialized
|
|
100
|
+
decode_sections(serialized) || {}
|
|
101
|
+
elsif cache_only
|
|
102
|
+
{}
|
|
103
|
+
else
|
|
104
|
+
compute_release_dates(force_refresh: force_refresh)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
return dates if requested_versions.empty?
|
|
108
|
+
|
|
109
|
+
dates.select { |version, _date| requested_versions.include?(normalize_version_key(version)) }
|
|
110
|
+
end
|
|
111
|
+
|
|
85
112
|
def compute_sections(force_refresh: false)
|
|
86
113
|
changelog_sections = parse_changelog_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
87
114
|
github_sections = parse_github_release_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
@@ -93,6 +120,23 @@ module Gemstar
|
|
|
93
120
|
sections
|
|
94
121
|
end
|
|
95
122
|
|
|
123
|
+
def compute_release_dates(force_refresh: false)
|
|
124
|
+
registry_dates = if @metadata.respond_to?(:registry_release_dates)
|
|
125
|
+
@metadata.registry_release_dates(cache_only: false, force_refresh: force_refresh)
|
|
126
|
+
else
|
|
127
|
+
{}
|
|
128
|
+
end
|
|
129
|
+
return registry_dates unless registry_dates.nil? || registry_dates.empty?
|
|
130
|
+
|
|
131
|
+
changelog_dates = parse_changelog_release_dates(cache_only: false, force_refresh: force_refresh)
|
|
132
|
+
return changelog_dates unless changelog_dates.nil? || changelog_dates.empty?
|
|
133
|
+
|
|
134
|
+
repo_uri = @metadata&.repo_uri(cache_only: false, force_refresh: force_refresh)
|
|
135
|
+
return {} unless repo_uri&.include?("github.com")
|
|
136
|
+
|
|
137
|
+
parse_github_tag_dates(repo_uri, cache_only: false, force_refresh: force_refresh)
|
|
138
|
+
end
|
|
139
|
+
|
|
96
140
|
def decode_sections(serialized)
|
|
97
141
|
JSON.parse(serialized)
|
|
98
142
|
rescue JSON::ParserError
|
|
@@ -101,9 +145,7 @@ module Gemstar
|
|
|
101
145
|
|
|
102
146
|
def merge_section_sources(changelog_sections, github_sections)
|
|
103
147
|
return github_sections if changelog_sections.nil? || changelog_sections.empty?
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
github_sections.merge(changelog_sections)
|
|
148
|
+
changelog_sections
|
|
107
149
|
end
|
|
108
150
|
|
|
109
151
|
def extract_relevant_sections(old_version, new_version)
|
|
@@ -155,12 +197,22 @@ module Gemstar
|
|
|
155
197
|
nil
|
|
156
198
|
end
|
|
157
199
|
|
|
200
|
+
def extract_release_date_from_heading(line)
|
|
201
|
+
return nil unless line
|
|
202
|
+
|
|
203
|
+
raw_date = line.to_s[/\b(\d{4}-\d{2}-\d{2})\b/, 1]
|
|
204
|
+
format_release_date(raw_date)
|
|
205
|
+
end
|
|
206
|
+
|
|
158
207
|
def changelog_uri_candidates(cache_only: false, force_refresh: false)
|
|
159
208
|
candidates = []
|
|
160
209
|
|
|
161
210
|
repo_uri = @metadata.repo_uri(cache_only: cache_only, force_refresh: force_refresh)
|
|
162
211
|
return [] if repo_uri.nil? || repo_uri.empty?
|
|
163
212
|
|
|
213
|
+
meta = @metadata.meta(cache_only: cache_only, force_refresh: force_refresh)
|
|
214
|
+
candidates += changelog_uri_markdown_candidates(meta["changelog_uri"]) if meta
|
|
215
|
+
|
|
164
216
|
changelog_source = metadata_changelog_source(repo_uri, cache_only: cache_only, force_refresh: force_refresh)
|
|
165
217
|
return [] unless changelog_source
|
|
166
218
|
|
|
@@ -169,7 +221,6 @@ module Gemstar
|
|
|
169
221
|
end
|
|
170
222
|
|
|
171
223
|
# Add the gem's changelog_uri last as it's usually not the most parsable:
|
|
172
|
-
meta = @metadata.meta(cache_only: cache_only, force_refresh: force_refresh)
|
|
173
224
|
candidates += [Gemstar::GitHub::github_blob_to_raw(meta["changelog_uri"])] if meta
|
|
174
225
|
|
|
175
226
|
candidates.flatten!
|
|
@@ -179,6 +230,30 @@ module Gemstar
|
|
|
179
230
|
candidates
|
|
180
231
|
end
|
|
181
232
|
|
|
233
|
+
def changelog_uri_markdown_candidates(changelog_uri)
|
|
234
|
+
raw_uri = Gemstar::GitHub::github_blob_to_raw(changelog_uri)
|
|
235
|
+
return [] if raw_uri.to_s.empty?
|
|
236
|
+
|
|
237
|
+
candidates = []
|
|
238
|
+
candidates << raw_uri if raw_uri.match?(/\.(?:md|markdown|rdoc|txt)\z/i)
|
|
239
|
+
|
|
240
|
+
begin
|
|
241
|
+
uri = URI(raw_uri)
|
|
242
|
+
path = uri.path.to_s
|
|
243
|
+
if path.end_with?("/")
|
|
244
|
+
uri.path = "#{path.chomp("/")}.md"
|
|
245
|
+
candidates << uri.to_s
|
|
246
|
+
elsif File.extname(path).empty?
|
|
247
|
+
uri.path = "#{path}.md"
|
|
248
|
+
candidates << uri.to_s
|
|
249
|
+
end
|
|
250
|
+
rescue URI::InvalidURIError
|
|
251
|
+
nil
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
candidates
|
|
255
|
+
end
|
|
256
|
+
|
|
182
257
|
def metadata_changelog_source(repo_uri, cache_only:, force_refresh:)
|
|
183
258
|
if @metadata.respond_to?(:changelog_source)
|
|
184
259
|
return @metadata.changelog_source(repo_uri: repo_uri, cache_only: cache_only, force_refresh: force_refresh)
|
|
@@ -287,6 +362,21 @@ module Gemstar
|
|
|
287
362
|
sections
|
|
288
363
|
end
|
|
289
364
|
|
|
365
|
+
def parse_changelog_release_dates(cache_only: false, force_refresh: false)
|
|
366
|
+
c = content(cache_only: cache_only, force_refresh: force_refresh)
|
|
367
|
+
return {} if c.nil? || c.strip.empty?
|
|
368
|
+
return {} if c.include?("<html") || c.include?("<!DOCTYPE html")
|
|
369
|
+
|
|
370
|
+
c.lines.each_with_object({}) do |line, dates|
|
|
371
|
+
line = line.gsub(/^=+/) { |m| "#" * m.length }
|
|
372
|
+
next unless VERSION_PATTERNS.any? { |re| line.match?(re) }
|
|
373
|
+
|
|
374
|
+
version = extract_version_from_heading(line)
|
|
375
|
+
date = extract_release_date_from_heading(line)
|
|
376
|
+
dates[version] ||= date if version && date
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
290
380
|
def parse_github_release_sections(cache_only: false, force_refresh: false)
|
|
291
381
|
begin
|
|
292
382
|
require "nokogiri"
|
|
@@ -489,6 +579,40 @@ module Gemstar
|
|
|
489
579
|
sections
|
|
490
580
|
end
|
|
491
581
|
|
|
582
|
+
def parse_github_tag_dates(repo_uri, cache_only:, force_refresh:)
|
|
583
|
+
return {} unless repo_uri&.include?("github.com")
|
|
584
|
+
|
|
585
|
+
url = github_tags_url(repo_uri)
|
|
586
|
+
return {} unless url
|
|
587
|
+
|
|
588
|
+
dates = {}
|
|
589
|
+
seen_urls = {}
|
|
590
|
+
|
|
591
|
+
while url && !seen_urls[url]
|
|
592
|
+
seen_urls[url] = true
|
|
593
|
+
html = if cache_only
|
|
594
|
+
Cache.peek("tags-#{url}")
|
|
595
|
+
else
|
|
596
|
+
Cache.fetch("tags-#{url}", force: force_refresh) do
|
|
597
|
+
begin
|
|
598
|
+
URI.open(url, read_timeout: 8)&.read
|
|
599
|
+
rescue => e
|
|
600
|
+
puts "#{url}: #{e}" if Gemstar.debug?
|
|
601
|
+
nil
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
break if html.nil? || html.strip.empty?
|
|
607
|
+
|
|
608
|
+
page_dates, next_url = parse_single_github_tag_dates_page(html, repo_uri)
|
|
609
|
+
dates.merge!(page_dates) { |_version, existing, _new| existing }
|
|
610
|
+
url = next_url
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
dates
|
|
614
|
+
end
|
|
615
|
+
|
|
492
616
|
def parse_single_github_tags_page(html, repo_uri)
|
|
493
617
|
require "nokogiri"
|
|
494
618
|
|
|
@@ -539,6 +663,75 @@ module Gemstar
|
|
|
539
663
|
[{}, nil]
|
|
540
664
|
end
|
|
541
665
|
|
|
666
|
+
def parse_single_github_tag_dates_page(html, repo_uri)
|
|
667
|
+
require "nokogiri"
|
|
668
|
+
|
|
669
|
+
doc = begin
|
|
670
|
+
Nokogiri::HTML5(html)
|
|
671
|
+
rescue => _
|
|
672
|
+
Nokogiri::HTML(html)
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
dates = {}
|
|
676
|
+
repo_path = URI(repo_uri).path
|
|
677
|
+
release_prefix = "#{repo_path}/releases/tag/"
|
|
678
|
+
tree_prefix = "#{repo_path}/tree/"
|
|
679
|
+
|
|
680
|
+
doc.css("a[href]").each do |link|
|
|
681
|
+
href = link["href"].to_s
|
|
682
|
+
tag_name =
|
|
683
|
+
if href.start_with?(release_prefix)
|
|
684
|
+
href.delete_prefix(release_prefix)
|
|
685
|
+
elsif href.start_with?(tree_prefix)
|
|
686
|
+
href.delete_prefix(tree_prefix)
|
|
687
|
+
end
|
|
688
|
+
next if tag_name.to_s.empty?
|
|
689
|
+
next unless github_tag_matches_metadata?(tag_name)
|
|
690
|
+
|
|
691
|
+
version = normalize_github_tag_version(tag_name)
|
|
692
|
+
next if version.to_s.empty?
|
|
693
|
+
|
|
694
|
+
datetime = github_tag_datetime_for(link)
|
|
695
|
+
next if datetime.to_s.empty?
|
|
696
|
+
|
|
697
|
+
dates[version] ||= format_release_date(datetime)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
next_href =
|
|
701
|
+
doc.at_css('a[rel="next"], a.next_page')&.[]("href") ||
|
|
702
|
+
doc.css("a[href]").find do |link|
|
|
703
|
+
href = link["href"].to_s
|
|
704
|
+
text = link.text.to_s.gsub(/\s+/, " ").strip
|
|
705
|
+
href.include?("/tags?after=") && text == "Next"
|
|
706
|
+
end&.[]("href")
|
|
707
|
+
next_url = if next_href && !next_href.empty?
|
|
708
|
+
URI.join(repo_uri, next_href).to_s
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
[dates.compact, next_url]
|
|
712
|
+
rescue LoadError
|
|
713
|
+
[{}, nil]
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def github_tag_datetime_for(link)
|
|
717
|
+
container = link.at_xpath('ancestor::*[self::li or self::div][.//relative-time or .//time-ago][1]')
|
|
718
|
+
time_node = container&.at_css("relative-time[datetime], time-ago[datetime]") ||
|
|
719
|
+
link.xpath('following::relative-time[@datetime] | following::time-ago[@datetime]').first
|
|
720
|
+
time_node&.[]("datetime")
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def format_release_date(datetime)
|
|
724
|
+
if datetime.to_s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
|
|
725
|
+
date = Date.strptime(datetime.to_s, "%Y-%m-%d")
|
|
726
|
+
return date.strftime("%b #{date.day}, %Y")
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
time = Time.parse(datetime.to_s).utc
|
|
730
|
+
time.strftime("%b #{time.day}, %Y")
|
|
731
|
+
rescue ArgumentError
|
|
732
|
+
nil
|
|
733
|
+
end
|
|
734
|
+
|
|
542
735
|
def parse_single_github_release_page(html, version)
|
|
543
736
|
require "nokogiri"
|
|
544
737
|
|
data/lib/gemstar/git_hub.rb
CHANGED
|
@@ -8,11 +8,12 @@ module Gemstar
|
|
|
8
8
|
uri = URI(url)
|
|
9
9
|
return url unless uri.host == "github.com"
|
|
10
10
|
|
|
11
|
-
owner, repo,
|
|
12
|
-
return url unless blob
|
|
11
|
+
owner, repo, view, *rest = uri.path.split("/")[1..]
|
|
12
|
+
return url unless %w[blob tree].include?(view)
|
|
13
13
|
|
|
14
14
|
ref = rest.shift
|
|
15
15
|
path = rest.join("/")
|
|
16
|
+
return url if path.empty?
|
|
16
17
|
|
|
17
18
|
ref_prefix = ref_is_tag ? "refs/tags/" : ""
|
|
18
19
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require "open-uri"
|
|
2
2
|
require "uri"
|
|
3
3
|
require "json"
|
|
4
|
+
require "date"
|
|
5
|
+
require "time"
|
|
4
6
|
require_relative "remote_repository"
|
|
5
7
|
|
|
6
8
|
module Gemstar
|
|
@@ -90,6 +92,28 @@ module Gemstar
|
|
|
90
92
|
Gemstar::ChangeLog.new(self).sections(cache_only: cache_only, force_refresh: force_refresh)
|
|
91
93
|
end
|
|
92
94
|
|
|
95
|
+
def registry_release_dates(cache_only: false, force_refresh: false)
|
|
96
|
+
cache_key = "rubygems-versions-#{gem_name}"
|
|
97
|
+
json = if cache_only
|
|
98
|
+
Cache.peek(cache_key)
|
|
99
|
+
else
|
|
100
|
+
url = "https://rubygems.org/api/v1/versions/#{URI.encode_www_form_component(gem_name)}.json"
|
|
101
|
+
Cache.fetch(cache_key, force: force_refresh) do
|
|
102
|
+
URI.open(url, read_timeout: 8).read
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Array(JSON.parse(json)).each_with_object({}) do |version, dates|
|
|
107
|
+
number = version["number"].to_s
|
|
108
|
+
created_at = version["created_at"].to_s
|
|
109
|
+
next if number.empty? || created_at.empty?
|
|
110
|
+
|
|
111
|
+
dates[number] = format_registry_release_date(created_at)
|
|
112
|
+
end.compact
|
|
113
|
+
rescue JSON::ParserError
|
|
114
|
+
{}
|
|
115
|
+
end
|
|
116
|
+
|
|
93
117
|
def warm_cache(versions: nil)
|
|
94
118
|
meta
|
|
95
119
|
repo_uri
|
|
@@ -146,5 +170,17 @@ module Gemstar
|
|
|
146
170
|
value.to_s.gsub("{gem_name}", gem_name)
|
|
147
171
|
end
|
|
148
172
|
|
|
173
|
+
def format_registry_release_date(datetime)
|
|
174
|
+
if datetime.to_s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
|
|
175
|
+
date = Date.strptime(datetime.to_s, "%Y-%m-%d")
|
|
176
|
+
return date.strftime("%b #{date.day}, %Y")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
time = Time.parse(datetime.to_s).utc
|
|
180
|
+
time.strftime("%b #{time.day}, %Y")
|
|
181
|
+
rescue ArgumentError
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
149
185
|
end
|
|
150
186
|
end
|
data/lib/gemstar/version.rb
CHANGED
data/lib/gemstar/web/app.rb
CHANGED
|
@@ -2,6 +2,7 @@ require "cgi"
|
|
|
2
2
|
require "erb"
|
|
3
3
|
require "uri"
|
|
4
4
|
require "kramdown"
|
|
5
|
+
require "nokogiri"
|
|
5
6
|
require "roda"
|
|
6
7
|
|
|
7
8
|
begin
|
|
@@ -13,7 +14,7 @@ module Gemstar
|
|
|
13
14
|
module Web
|
|
14
15
|
class App < Roda
|
|
15
16
|
MISSING_METADATA = Object.new
|
|
16
|
-
CACHE_VERSION = "
|
|
17
|
+
CACHE_VERSION = "v7"
|
|
17
18
|
|
|
18
19
|
class << self
|
|
19
20
|
def build(projects:, config_home:, cache_warmer: nil)
|
|
@@ -837,7 +838,10 @@ module Gemstar
|
|
|
837
838
|
<article class="revision-card revision-#{section[:kind]}#{status_class}">
|
|
838
839
|
<header class="revision-card-header">
|
|
839
840
|
<div class="revision-card-titlebar">
|
|
840
|
-
<
|
|
841
|
+
<div class="revision-card-heading">
|
|
842
|
+
<h5>#{h(section[:title] || section[:version])}</h5>
|
|
843
|
+
#{render_release_date(section[:release_date])}
|
|
844
|
+
</div>
|
|
841
845
|
<div class="revision-card-actions">
|
|
842
846
|
#{title_links.join}
|
|
843
847
|
</div>
|
|
@@ -850,6 +854,12 @@ module Gemstar
|
|
|
850
854
|
HTML
|
|
851
855
|
end
|
|
852
856
|
|
|
857
|
+
def render_release_date(release_date)
|
|
858
|
+
return "" if release_date.to_s.empty?
|
|
859
|
+
|
|
860
|
+
%(<span class="revision-release-date" title="Estimated release date">#{h(release_date)}</span>)
|
|
861
|
+
end
|
|
862
|
+
|
|
853
863
|
def grouped_change_sections(gem_state)
|
|
854
864
|
sections = change_sections(gem_state)
|
|
855
865
|
latest = sections.select { |section| section[:kind] == :future }
|
|
@@ -895,6 +905,7 @@ module Gemstar
|
|
|
895
905
|
return change_sections_cache[cache_key] = [] if sections.nil? || sections.empty?
|
|
896
906
|
|
|
897
907
|
previous_version = gem_state[:old_version]
|
|
908
|
+
release_dates = resolved_release_dates(metadata, sections.keys, gem_state)
|
|
898
909
|
|
|
899
910
|
rendered_sections = sections.keys.filter_map do |version|
|
|
900
911
|
kind = section_kind(version, previous_version, current_version, gem_state[:status])
|
|
@@ -906,6 +917,7 @@ module Gemstar
|
|
|
906
917
|
title: content[:title],
|
|
907
918
|
kind: kind,
|
|
908
919
|
previous_version: previous_section_version(sections.keys, version),
|
|
920
|
+
release_date: release_date_for(release_dates, version),
|
|
909
921
|
html: content[:html]
|
|
910
922
|
}
|
|
911
923
|
end
|
|
@@ -931,6 +943,38 @@ module Gemstar
|
|
|
931
943
|
cached_sections.merge(refreshed_sections || {})
|
|
932
944
|
end
|
|
933
945
|
|
|
946
|
+
def resolved_release_dates(metadata, versions, gem_state)
|
|
947
|
+
changelog = Gemstar::ChangeLog.new(metadata)
|
|
948
|
+
cached_dates = changelog.release_dates(versions: versions, cache_only: true) || {}
|
|
949
|
+
return cached_dates unless selected_gem_missing_release_dates?(gem_state, versions, cached_dates)
|
|
950
|
+
|
|
951
|
+
fetched_dates = changelog.release_dates(versions: versions, cache_only: false) || {}
|
|
952
|
+
cached_dates.merge(fetched_dates)
|
|
953
|
+
rescue StandardError
|
|
954
|
+
{}
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
def release_date_for(release_dates, version)
|
|
958
|
+
release_dates.find do |candidate_version, _date|
|
|
959
|
+
normalize_release_version_key(candidate_version) == normalize_release_version_key(version)
|
|
960
|
+
end&.last
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
def selected_gem_missing_release_dates?(gem_state, versions, release_dates)
|
|
964
|
+
return false unless @selected_gem && gem_state[:name] == @selected_gem[:name]
|
|
965
|
+
|
|
966
|
+
requested_versions = Array(versions).map { |version| normalize_release_version_key(version) }.compact
|
|
967
|
+
dated_versions = release_dates.keys.map { |version| normalize_release_version_key(version) }.compact
|
|
968
|
+
(requested_versions - dated_versions).any?
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
def normalize_release_version_key(version)
|
|
972
|
+
value = version.to_s.strip
|
|
973
|
+
return nil if value.empty?
|
|
974
|
+
|
|
975
|
+
value.sub(/\Av/i, "")
|
|
976
|
+
end
|
|
977
|
+
|
|
934
978
|
def relevant_package_versions(gem_state, metadata)
|
|
935
979
|
metadata_hash = metadata_for(gem_state) || {}
|
|
936
980
|
[
|
|
@@ -1034,7 +1078,7 @@ module Gemstar
|
|
|
1034
1078
|
|
|
1035
1079
|
def strip_leading_version_heading(text, heading_version)
|
|
1036
1080
|
stripped = text.sub(/\A\s*#+\s*v?#{Regexp.escape(heading_version)}\s*\n+/i, "")
|
|
1037
|
-
return
|
|
1081
|
+
return strip_leading_heading_separator(stripped) unless stripped == text
|
|
1038
1082
|
|
|
1039
1083
|
lines = text.lines
|
|
1040
1084
|
return text if lines.empty?
|
|
@@ -1048,11 +1092,11 @@ module Gemstar
|
|
|
1048
1092
|
|
|
1049
1093
|
remaining = lines.drop(1)
|
|
1050
1094
|
remaining.shift while remaining.first&.strip&.empty?
|
|
1051
|
-
|
|
1095
|
+
strip_leading_heading_separator(remaining.join)
|
|
1052
1096
|
end
|
|
1053
1097
|
|
|
1054
|
-
def
|
|
1055
|
-
text.sub(/\A\s
|
|
1098
|
+
def strip_leading_heading_separator(text)
|
|
1099
|
+
text.sub(/\A\s*(?:#{Regexp.escape("#")}{4,}|[-=]{3,})\s*\n+/, "")
|
|
1056
1100
|
end
|
|
1057
1101
|
|
|
1058
1102
|
def compare_versions(left, right)
|
|
@@ -566,6 +566,13 @@
|
|
|
566
566
|
flex: 0 0 auto;
|
|
567
567
|
margin-left: auto;
|
|
568
568
|
}
|
|
569
|
+
.revision-card-heading {
|
|
570
|
+
display: inline-flex;
|
|
571
|
+
align-items: baseline;
|
|
572
|
+
flex-wrap: wrap;
|
|
573
|
+
gap: 0.45rem;
|
|
574
|
+
min-width: 0;
|
|
575
|
+
}
|
|
569
576
|
.revision-card h5 {
|
|
570
577
|
margin: 0;
|
|
571
578
|
font-size: 1.55rem;
|
|
@@ -573,6 +580,12 @@
|
|
|
573
580
|
color: var(--ink);
|
|
574
581
|
min-width: 0;
|
|
575
582
|
}
|
|
583
|
+
.revision-release-date {
|
|
584
|
+
color: var(--grey);
|
|
585
|
+
font-size: 0.86rem;
|
|
586
|
+
font-weight: 600;
|
|
587
|
+
white-space: nowrap;
|
|
588
|
+
}
|
|
576
589
|
.revision-markup {
|
|
577
590
|
padding: 0.7rem;
|
|
578
591
|
}
|