metanorma-release 0.2.24 → 0.2.26
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/.rubocop_todo.yml +21 -319
- data/README.adoc +319 -92
- data/lib/metanorma/release/aggregation_pipeline.rb +128 -45
- data/lib/metanorma/release/asset_processor.rb +3 -3
- data/lib/metanorma/release/cache_store.rb +1 -0
- data/lib/metanorma/release/change_detector.rb +3 -2
- data/lib/metanorma/release/cli.rb +28 -4
- data/lib/metanorma/release/commands/aggregate.rb +1 -17
- data/lib/metanorma/release/commands/package.rb +9 -17
- data/lib/metanorma/release/commands/release_command.rb +19 -14
- data/lib/metanorma/release/config.rb +16 -7
- data/lib/metanorma/release/delta_state.rb +15 -1
- data/lib/metanorma/release/dependency_validation.rb +19 -0
- data/lib/metanorma/release/document_flattener.rb +173 -0
- data/lib/metanorma/release/index.rb +1 -1
- data/lib/metanorma/release/interfaces.rb +12 -0
- data/lib/metanorma/release/platform/github/manifest_reader.rb +3 -1
- data/lib/metanorma/release/platform/github/release_fetcher.rb +10 -14
- data/lib/metanorma/release/platform/github/topic_discoverer.rb +1 -1
- data/lib/metanorma/release/platform/github.rb +0 -4
- data/lib/metanorma/release/platform/local/fetcher.rb +5 -17
- data/lib/metanorma/release/platform/null/manifest_reader.rb +15 -0
- data/lib/metanorma/release/platform/null/publisher.rb +1 -1
- data/lib/metanorma/release/platform/null.rb +2 -0
- data/lib/metanorma/release/platform/static_discoverer.rb +19 -0
- data/lib/metanorma/release/platform.rb +1 -0
- data/lib/metanorma/release/platform_factory.rb +6 -21
- data/lib/metanorma/release/publication.rb +23 -161
- data/lib/metanorma/release/publication_serializer.rb +59 -0
- data/lib/metanorma/release/release_pipeline.rb +7 -15
- data/lib/metanorma/release/rxl_extractor.rb +106 -0
- data/lib/metanorma/release/site.rb +5 -165
- data/lib/metanorma/release/slug_strategy.rb +30 -15
- data/lib/metanorma/release/version.rb +1 -1
- data/lib/metanorma/release/zip_packager.rb +1 -1
- data/lib/metanorma/release.rb +36 -19
- metadata +11 -5
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
class DocumentFlattener
|
|
6
|
+
def initialize(display_categories: [])
|
|
7
|
+
@display_categories = display_categories || []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def flatten(doc)
|
|
11
|
+
bib = doc["bibliographic"] || {}
|
|
12
|
+
doctype = extract_doctype(bib) || doc.fetch("doctype", "")
|
|
13
|
+
formats = doc["formats"] || []
|
|
14
|
+
base = {
|
|
15
|
+
"slug" => doc["id"],
|
|
16
|
+
"id" => resolve_doc_id(bib, doc),
|
|
17
|
+
"title" => doc["title"].to_s,
|
|
18
|
+
"abstract" => extract_abstract(bib),
|
|
19
|
+
"stage" => (doc["stage"] || "published").to_s.downcase,
|
|
20
|
+
"doctype" => doctype,
|
|
21
|
+
"edition" => doc["edition"],
|
|
22
|
+
"date" => extract_date(doc),
|
|
23
|
+
"channels" => doc["channels"] || [],
|
|
24
|
+
"formats" => formats,
|
|
25
|
+
"files" => doc["files"] || [],
|
|
26
|
+
"bibliographic" => bib,
|
|
27
|
+
}
|
|
28
|
+
add_format_flags(base, formats)
|
|
29
|
+
add_display_category(base, doctype)
|
|
30
|
+
add_contributors(base, bib)
|
|
31
|
+
base
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def add_format_flags(hash, formats)
|
|
37
|
+
hash["has_html"] = formats.include?("html")
|
|
38
|
+
hash["has_pdf"] = formats.include?("pdf")
|
|
39
|
+
hash["has_xml"] = formats.include?("xml")
|
|
40
|
+
hash["has_rxl"] = formats.include?("rxl")
|
|
41
|
+
|
|
42
|
+
files = hash["files"]
|
|
43
|
+
%w[html pdf xml rxl].each do |fmt|
|
|
44
|
+
file = files.find { |f| f["format"] == fmt }
|
|
45
|
+
hash["#{fmt}_path"] = file["path"] if file
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def add_display_category(hash, doctype)
|
|
50
|
+
cat = resolve_display_category(doctype)
|
|
51
|
+
hash["stage_css"] = hash["stage"].gsub(/\s+/, "-")
|
|
52
|
+
hash["doctype_class"] = "type-#{doctype.downcase}"
|
|
53
|
+
hash["display_category"] = cat&.fetch("name", nil)
|
|
54
|
+
hash["display_category_slug"] = cat&.fetch("slug", nil)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def resolve_display_category(doctype)
|
|
58
|
+
return nil if doctype.nil? || doctype.empty?
|
|
59
|
+
|
|
60
|
+
@display_categories.each do |cat|
|
|
61
|
+
doctypes = cat["doctypes"] || []
|
|
62
|
+
if doctypes.include?(doctype)
|
|
63
|
+
return { "name" => cat["name"],
|
|
64
|
+
"slug" => cat["slug"] }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolve_doc_id(bib, doc)
|
|
71
|
+
extract_primary_id(bib) || doc["identifier"] || doc["id"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def extract_primary_id(bib)
|
|
75
|
+
ids = bib["docidentifier"]
|
|
76
|
+
return nil unless ids&.any?
|
|
77
|
+
|
|
78
|
+
primary = ids.find { |di| di["primary"] == true } || ids.first
|
|
79
|
+
primary["content"]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def extract_doctype(bib)
|
|
83
|
+
bib.dig("ext", "doctype", "content")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def extract_abstract(bib)
|
|
87
|
+
abstracts = bib["abstract"]
|
|
88
|
+
return nil unless abstracts&.any?
|
|
89
|
+
|
|
90
|
+
abstracts.first["content"]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def extract_date(doc)
|
|
94
|
+
bib_date = extract_bib_date(doc["bibliographic"])
|
|
95
|
+
return bib_date if bib_date
|
|
96
|
+
|
|
97
|
+
release_date = doc.dig("source", "releaseDate")
|
|
98
|
+
return nil unless release_date
|
|
99
|
+
|
|
100
|
+
release_date.to_s.split(/[T ]/).first
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def extract_bib_date(bib)
|
|
104
|
+
return nil unless bib
|
|
105
|
+
|
|
106
|
+
dates = bib["date"]
|
|
107
|
+
return nil if dates.nil? || dates.empty?
|
|
108
|
+
|
|
109
|
+
published = dates.find { |d| d["type"] == "published" }
|
|
110
|
+
entry = published || dates.first
|
|
111
|
+
at = entry["at"]
|
|
112
|
+
at ? at.to_s.split(/[T ]/).first : nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def add_contributors(hash, bib)
|
|
116
|
+
contribs = bib["contributor"] || []
|
|
117
|
+
persons, committees = partition_contributors(contribs)
|
|
118
|
+
hash["authors"] = persons
|
|
119
|
+
hash["committee"] = committees.first
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def partition_contributors(contribs)
|
|
123
|
+
persons = contribs.filter_map { |c| parse_person(c) }
|
|
124
|
+
committees = contribs.filter_map { |c| parse_committee(c) }
|
|
125
|
+
[persons, committees]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def parse_person(contrib)
|
|
129
|
+
return nil unless contrib["person"]
|
|
130
|
+
|
|
131
|
+
name = extract_person_name(contrib["person"])
|
|
132
|
+
return nil unless name
|
|
133
|
+
|
|
134
|
+
role = (contrib["role"] || []).first&.fetch("type", nil)
|
|
135
|
+
{ "name" => name, "role" => role }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_committee(contrib)
|
|
139
|
+
return nil unless contrib["organization"]
|
|
140
|
+
|
|
141
|
+
extract_org_subdivision(contrib["organization"])
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def extract_person_name(person)
|
|
145
|
+
n = person["name"] || {}
|
|
146
|
+
complete = n["completename"]
|
|
147
|
+
return complete["content"] if complete.is_a?(Hash) && complete["content"]
|
|
148
|
+
return complete if complete.is_a?(String)
|
|
149
|
+
|
|
150
|
+
surname = n["surname"]
|
|
151
|
+
given = n["given"]
|
|
152
|
+
given_str = given.is_a?(Hash) ? given["content"].to_s : given.to_s
|
|
153
|
+
parts = [given_str, surname].compact
|
|
154
|
+
parts.empty? ? nil : parts.join(" ")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def extract_org_subdivision(org)
|
|
158
|
+
subs = org["subdivision"]
|
|
159
|
+
return nil unless subs&.any?
|
|
160
|
+
|
|
161
|
+
sd = subs.first
|
|
162
|
+
sd_name = sd["name"]
|
|
163
|
+
if sd_name.is_a?(Array)
|
|
164
|
+
sd_name.first&.dig("content")
|
|
165
|
+
elsif sd_name.is_a?(Hash)
|
|
166
|
+
sd_name["content"]
|
|
167
|
+
else
|
|
168
|
+
sd_name.to_s
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Metanorma
|
|
4
4
|
module Release
|
|
5
|
+
module Extractor
|
|
6
|
+
def discover(output_dir)
|
|
7
|
+
raise NotImplementedError, "#{self} must implement .discover"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Release = Struct.new(:tag_name, :body, :prerelease, :draft,
|
|
12
|
+
:html_url, :published_at, :created_at,
|
|
13
|
+
:assets, keyword_init: true)
|
|
14
|
+
Asset = Struct.new(:name, :browser_download_url, :size, :data,
|
|
15
|
+
keyword_init: true)
|
|
16
|
+
|
|
5
17
|
module Filter
|
|
6
18
|
def apply(documents)
|
|
7
19
|
raise NotImplementedError, "#{self.class} must implement #apply"
|
|
@@ -22,7 +22,9 @@ module Metanorma
|
|
|
22
22
|
return nil unless parsed.is_a?(Hash)
|
|
23
23
|
|
|
24
24
|
channels = Array(parsed["channels"])
|
|
25
|
-
Array(parsed["documents"]).each
|
|
25
|
+
Array(parsed["documents"]).each do |doc|
|
|
26
|
+
channels.concat(Array(doc["channels"]))
|
|
27
|
+
end
|
|
26
28
|
channels.map(&:to_s).uniq
|
|
27
29
|
rescue StandardError
|
|
28
30
|
nil
|
|
@@ -6,12 +6,6 @@ module Metanorma
|
|
|
6
6
|
module Release
|
|
7
7
|
module Platform
|
|
8
8
|
module GitHub
|
|
9
|
-
GitHubRelease = Struct.new(:tag_name, :body, :prerelease, :draft,
|
|
10
|
-
:html_url, :published_at, :created_at,
|
|
11
|
-
:assets, keyword_init: true)
|
|
12
|
-
GitHubAsset = Struct.new(:name, :browser_download_url, :size, :data,
|
|
13
|
-
keyword_init: true)
|
|
14
|
-
|
|
15
9
|
class ReleaseFetcher
|
|
16
10
|
include Metanorma::Release::ReleaseFetcher
|
|
17
11
|
|
|
@@ -21,14 +15,16 @@ module Metanorma
|
|
|
21
15
|
end
|
|
22
16
|
|
|
23
17
|
def fetch(repo, etag: nil)
|
|
24
|
-
releases = paginate_releases(repo.to_s)
|
|
18
|
+
releases, failed = paginate_releases(repo.to_s)
|
|
25
19
|
parsed = releases.map { |r| parse_release(r) }
|
|
26
20
|
FetchResult.new(releases: parsed, etag: "etag-#{repo}",
|
|
27
|
-
unchanged?: false)
|
|
21
|
+
unchanged?: false, failed?: failed)
|
|
28
22
|
end
|
|
29
23
|
|
|
30
24
|
private
|
|
31
25
|
|
|
26
|
+
# Returns [releases, failed] — failed is true when the listing
|
|
27
|
+
# errored, which callers must not treat as an empty release set.
|
|
32
28
|
def paginate_releases(repo_slug)
|
|
33
29
|
all = []
|
|
34
30
|
page = 1
|
|
@@ -41,23 +37,23 @@ module Metanorma
|
|
|
41
37
|
|
|
42
38
|
page += 1
|
|
43
39
|
end
|
|
44
|
-
all
|
|
40
|
+
[all, false]
|
|
45
41
|
rescue StandardError => e
|
|
46
|
-
warn "
|
|
47
|
-
[]
|
|
42
|
+
Metanorma::Release.logger.warn "Failed to fetch releases for #{repo_slug}: #{e.message}"
|
|
43
|
+
[[], true]
|
|
48
44
|
end
|
|
49
45
|
|
|
50
46
|
def parse_release(r)
|
|
51
47
|
assets = (r[:assets] || []).map do |a|
|
|
52
48
|
data = download_asset(a[:url]) if a[:name].end_with?(".zip")
|
|
53
|
-
|
|
49
|
+
Asset.new(
|
|
54
50
|
name: a[:name],
|
|
55
51
|
browser_download_url: a[:browser_download_url],
|
|
56
52
|
size: a[:size],
|
|
57
53
|
data: data,
|
|
58
54
|
)
|
|
59
55
|
end
|
|
60
|
-
|
|
56
|
+
Release.new(
|
|
61
57
|
tag_name: r[:tag_name],
|
|
62
58
|
body: r[:body],
|
|
63
59
|
prerelease: r[:prerelease],
|
|
@@ -82,7 +78,7 @@ module Metanorma
|
|
|
82
78
|
end
|
|
83
79
|
data
|
|
84
80
|
rescue StandardError => e
|
|
85
|
-
warn "
|
|
81
|
+
Metanorma::Release.logger.warn "Failed to download asset #{url}: #{e.message}"
|
|
86
82
|
nil
|
|
87
83
|
end
|
|
88
84
|
|
|
@@ -18,10 +18,6 @@ module Metanorma
|
|
|
18
18
|
"metanorma/release/platform/github/release_fetcher"
|
|
19
19
|
autoload :ManifestReader,
|
|
20
20
|
"metanorma/release/platform/github/manifest_reader"
|
|
21
|
-
|
|
22
|
-
def self.cache_store(cache_dir:)
|
|
23
|
-
FileCacheStore.new(cache_dir)
|
|
24
|
-
end
|
|
25
21
|
end
|
|
26
22
|
end
|
|
27
23
|
end
|
|
@@ -6,12 +6,6 @@ module Metanorma
|
|
|
6
6
|
module Release
|
|
7
7
|
module Platform
|
|
8
8
|
module Local
|
|
9
|
-
LocalRelease = Struct.new(:tag_name, :body, :prerelease, :draft,
|
|
10
|
-
:html_url, :published_at, :created_at,
|
|
11
|
-
:assets, keyword_init: true)
|
|
12
|
-
LocalAsset = Struct.new(:name, :browser_download_url, :size, :data,
|
|
13
|
-
keyword_init: true)
|
|
14
|
-
|
|
15
9
|
class Fetcher
|
|
16
10
|
include Metanorma::Release::ReleaseFetcher
|
|
17
11
|
|
|
@@ -42,22 +36,22 @@ module Metanorma
|
|
|
42
36
|
zip_path = File.join(dir, "#{base}.zip")
|
|
43
37
|
|
|
44
38
|
unless File.exist?(zip_path)
|
|
45
|
-
warn "
|
|
39
|
+
Metanorma::Release.logger.warn "Missing zip for #{meta_path}, skipping"
|
|
46
40
|
return nil
|
|
47
41
|
end
|
|
48
42
|
|
|
49
43
|
metadata = Publication.from_metadata_hash(data)
|
|
50
|
-
asset =
|
|
44
|
+
asset = Asset.new(
|
|
51
45
|
name: "#{base}.zip",
|
|
52
46
|
browser_download_url: "file://#{File.expand_path(zip_path)}",
|
|
53
47
|
size: File.size(zip_path),
|
|
54
48
|
data: File.binread(zip_path),
|
|
55
49
|
)
|
|
56
50
|
|
|
57
|
-
|
|
51
|
+
Release.new(
|
|
58
52
|
tag_name: "#{metadata.slug}/#{metadata.edition || '1'}",
|
|
59
53
|
body: metadata.to_release_body,
|
|
60
|
-
prerelease:
|
|
54
|
+
prerelease: metadata.draft?,
|
|
61
55
|
draft: false,
|
|
62
56
|
html_url: "file://#{File.expand_path(dir)}",
|
|
63
57
|
published_at: File.mtime(zip_path).iso8601,
|
|
@@ -65,15 +59,9 @@ module Metanorma
|
|
|
65
59
|
assets: [asset],
|
|
66
60
|
)
|
|
67
61
|
rescue JSON::ParserError
|
|
68
|
-
warn "
|
|
62
|
+
Metanorma::Release.logger.warn "Invalid metadata JSON in #{meta_path}, skipping"
|
|
69
63
|
nil
|
|
70
64
|
end
|
|
71
|
-
|
|
72
|
-
def prerelease?(metadata)
|
|
73
|
-
stage = metadata.stage.to_s
|
|
74
|
-
%w[working-draft committee-draft draft-standard
|
|
75
|
-
final-draft].include?(stage)
|
|
76
|
-
end
|
|
77
65
|
end
|
|
78
66
|
end
|
|
79
67
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
class StaticDiscoverer
|
|
7
|
+
include RepoDiscoverer
|
|
8
|
+
|
|
9
|
+
def initialize(repos:)
|
|
10
|
+
@repos = repos
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def discover
|
|
14
|
+
@repos
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -6,6 +6,7 @@ module Metanorma
|
|
|
6
6
|
autoload :GitHub, "metanorma/release/platform/github"
|
|
7
7
|
autoload :Local, "metanorma/release/platform/local"
|
|
8
8
|
autoload :Null, "metanorma/release/platform/null"
|
|
9
|
+
autoload :StaticDiscoverer, "metanorma/release/platform/static_discoverer"
|
|
9
10
|
end
|
|
10
11
|
end
|
|
11
12
|
end
|
|
@@ -24,14 +24,17 @@ module Metanorma
|
|
|
24
24
|
|
|
25
25
|
discoverer = if opts[:repos]
|
|
26
26
|
repos = opts[:repos].map { |r| RepoRef.from_string(r) }
|
|
27
|
-
StaticDiscoverer.new(repos: repos)
|
|
27
|
+
Platform::StaticDiscoverer.new(repos: repos)
|
|
28
28
|
else
|
|
29
29
|
Platform::GitHub::TopicDiscoverer.new(
|
|
30
30
|
client: client, organizations: opts[:organizations], topic: opts[:topic],
|
|
31
31
|
)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
download_cache =
|
|
34
|
+
download_cache = if opts[:cache_dir]
|
|
35
|
+
File.join(opts[:cache_dir],
|
|
36
|
+
"downloads")
|
|
37
|
+
end
|
|
35
38
|
|
|
36
39
|
{
|
|
37
40
|
discoverer: discoverer,
|
|
@@ -58,7 +61,7 @@ module Metanorma
|
|
|
58
61
|
if source.start_with?("local:")
|
|
59
62
|
adapters = AGGREGATION_REGISTRY["local"].call(options,
|
|
60
63
|
options[:token])
|
|
61
|
-
adapters[:manifest_reader] =
|
|
64
|
+
adapters[:manifest_reader] = Platform::Null::ManifestReader.new
|
|
62
65
|
return adapters
|
|
63
66
|
end
|
|
64
67
|
|
|
@@ -78,24 +81,6 @@ module Metanorma
|
|
|
78
81
|
def self.register_aggregation(name, factory)
|
|
79
82
|
AGGREGATION_REGISTRY[name] = factory
|
|
80
83
|
end
|
|
81
|
-
|
|
82
|
-
class StaticDiscoverer
|
|
83
|
-
include RepoDiscoverer
|
|
84
|
-
|
|
85
|
-
def initialize(repos:)
|
|
86
|
-
@repos = repos
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def discover
|
|
90
|
-
@repos
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
class NullManifestReader
|
|
95
|
-
include Metanorma::Release::ManifestReader
|
|
96
|
-
|
|
97
|
-
def read(_repo) = nil
|
|
98
|
-
end
|
|
99
84
|
end
|
|
100
85
|
end
|
|
101
86
|
end
|