metanorma-release 0.2.24 → 0.2.25

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +21 -319
  3. data/README.adoc +306 -91
  4. data/lib/metanorma/release/aggregation_pipeline.rb +65 -40
  5. data/lib/metanorma/release/asset_processor.rb +0 -2
  6. data/lib/metanorma/release/cache_store.rb +1 -0
  7. data/lib/metanorma/release/change_detector.rb +3 -2
  8. data/lib/metanorma/release/cli.rb +26 -2
  9. data/lib/metanorma/release/commands/aggregate.rb +0 -16
  10. data/lib/metanorma/release/commands/package.rb +9 -17
  11. data/lib/metanorma/release/commands/release_command.rb +19 -14
  12. data/lib/metanorma/release/config.rb +16 -7
  13. data/lib/metanorma/release/dependency_validation.rb +19 -0
  14. data/lib/metanorma/release/document_flattener.rb +173 -0
  15. data/lib/metanorma/release/index.rb +1 -1
  16. data/lib/metanorma/release/interfaces.rb +12 -0
  17. data/lib/metanorma/release/platform/github/manifest_reader.rb +3 -1
  18. data/lib/metanorma/release/platform/github/release_fetcher.rb +4 -10
  19. data/lib/metanorma/release/platform/github/topic_discoverer.rb +1 -1
  20. data/lib/metanorma/release/platform/github.rb +0 -4
  21. data/lib/metanorma/release/platform/local/fetcher.rb +5 -17
  22. data/lib/metanorma/release/platform/null/manifest_reader.rb +15 -0
  23. data/lib/metanorma/release/platform/null/publisher.rb +1 -1
  24. data/lib/metanorma/release/platform/null.rb +2 -0
  25. data/lib/metanorma/release/platform/static_discoverer.rb +19 -0
  26. data/lib/metanorma/release/platform.rb +1 -0
  27. data/lib/metanorma/release/platform_factory.rb +6 -21
  28. data/lib/metanorma/release/publication.rb +23 -161
  29. data/lib/metanorma/release/publication_serializer.rb +59 -0
  30. data/lib/metanorma/release/release_pipeline.rb +7 -15
  31. data/lib/metanorma/release/rxl_extractor.rb +106 -0
  32. data/lib/metanorma/release/site.rb +4 -164
  33. data/lib/metanorma/release/slug_strategy.rb +30 -15
  34. data/lib/metanorma/release/version.rb +1 -1
  35. data/lib/metanorma/release.rb +36 -19
  36. metadata +8 -2
@@ -13,7 +13,7 @@ module Metanorma
13
13
  @index = index
14
14
  @output_dir = output_dir
15
15
  @data_dir = data_dir
16
- @display_categories = display_categories || []
16
+ @flattener = DocumentFlattener.new(display_categories: display_categories)
17
17
  end
18
18
 
19
19
  def write!
@@ -50,12 +50,13 @@ module Metanorma
50
50
  index.publications.map do |pub|
51
51
  enrich_publication(pub)
52
52
  rescue StandardError => e
53
- warn " Skip #{pub.identifier}: #{e.message}"
53
+ Metanorma::Release.logger.warn "Skip #{pub.identifier}: #{e.message}"
54
54
  pub.to_h
55
55
  end
56
56
  end
57
57
 
58
58
  def enrich_publication(pub)
59
+ require "relaton/bib"
59
60
  rxl_file = pub.files.find { |f| f.format == "rxl" }
60
61
  return pub.to_h unless rxl_file
61
62
 
@@ -78,171 +79,10 @@ module Metanorma
78
79
 
79
80
  def write_data_file(documents)
80
81
  FileUtils.mkdir_p(@data_dir)
81
- items = documents.compact.map { |doc| flatten_for_site(doc) }
82
+ items = documents.compact.map { |doc| @flattener.flatten(doc) }
82
83
  File.write(File.join(@data_dir, "documents.json"),
83
84
  JSON.pretty_generate({ "items" => items }))
84
85
  end
85
-
86
- def flatten_for_site(doc)
87
- bib = doc["bibliographic"] || {}
88
- doctype = extract_doctype(bib) || doc.fetch("doctype", "")
89
- formats = doc["formats"] || []
90
- base = {
91
- "slug" => doc["id"],
92
- "id" => resolve_doc_id(bib, doc),
93
- "title" => doc["title"].to_s,
94
- "abstract" => extract_abstract(bib),
95
- "stage" => (doc["stage"] || "published").to_s.downcase,
96
- "doctype" => doctype,
97
- "edition" => doc["edition"],
98
- "date" => extract_date(doc),
99
- "channels" => doc["channels"] || [],
100
- "formats" => formats,
101
- "files" => doc["files"] || [],
102
- "bibliographic" => bib,
103
- }
104
- add_format_flags(base, formats)
105
- add_display_category(base, doctype)
106
- add_contributors(base, bib)
107
- base
108
- end
109
-
110
- def add_format_flags(hash, formats)
111
- hash["has_html"] = formats.include?("html")
112
- hash["has_pdf"] = formats.include?("pdf")
113
- hash["has_xml"] = formats.include?("xml")
114
- hash["has_rxl"] = formats.include?("rxl")
115
-
116
- files = hash["files"]
117
- html_file = files.find { |f| f["format"] == "html" }
118
- hash["html_path"] = html_file["path"] if html_file
119
- pdf_file = files.find { |f| f["format"] == "pdf" }
120
- hash["pdf_path"] = pdf_file["path"] if pdf_file
121
- xml_file = files.find { |f| f["format"] == "xml" }
122
- hash["xml_path"] = xml_file["path"] if xml_file
123
- rxl_file = files.find { |f| f["format"] == "rxl" }
124
- hash["rxl_path"] = rxl_file["path"] if rxl_file
125
- end
126
-
127
- def add_display_category(hash, doctype)
128
- cat = resolve_display_category(doctype)
129
- hash["stage_css"] = hash["stage"].gsub(/\s+/, "-")
130
- hash["doctype_class"] = "type-#{doctype.downcase}"
131
- hash["display_category"] = cat&.fetch("name", nil)
132
- hash["display_category_slug"] = cat&.fetch("slug", nil)
133
- end
134
-
135
- def resolve_doc_id(bib, doc)
136
- extract_primary_id(bib) || doc["identifier"] || doc["id"]
137
- end
138
-
139
- def extract_primary_id(bib)
140
- ids = bib["docidentifier"]
141
- return nil unless ids&.any?
142
-
143
- primary = ids.find { |di| di["primary"] == true } || ids.first
144
- primary["content"]
145
- end
146
-
147
- def extract_doctype(bib)
148
- bib.dig("ext", "doctype", "content")
149
- end
150
-
151
- def extract_abstract(bib)
152
- abstracts = bib["abstract"]
153
- return nil unless abstracts&.any?
154
-
155
- abstracts.first["content"]
156
- end
157
-
158
- def extract_date(doc)
159
- bib_date = extract_bib_date(doc["bibliographic"])
160
- return bib_date if bib_date
161
-
162
- release_date = doc.dig("source", "releaseDate")
163
- return nil unless release_date
164
-
165
- release_date.to_s.split(/[T ]/).first
166
- end
167
-
168
- def extract_bib_date(bib)
169
- return nil unless bib
170
-
171
- dates = bib["date"]
172
- return nil if dates.nil? || dates.empty?
173
-
174
- published = dates.find { |d| d["type"] == "published" }
175
- entry = published || dates.first
176
- at = entry["at"]
177
- at ? at.to_s.split(/[T ]/).first : nil
178
- end
179
-
180
- def resolve_display_category(doctype)
181
- return nil if doctype.nil? || doctype.empty?
182
-
183
- @display_categories.each do |cat|
184
- doctypes = cat["doctypes"] || []
185
- return { "name" => cat["name"], "slug" => cat["slug"] } if doctypes.include?(doctype)
186
- end
187
- nil
188
- end
189
-
190
- def add_contributors(hash, bib)
191
- contribs = bib["contributor"] || []
192
- persons, committees = partition_contributors(contribs)
193
- hash["authors"] = persons
194
- hash["committee"] = committees.first
195
- end
196
-
197
- def partition_contributors(contribs)
198
- persons = contribs.filter_map { |c| parse_person(c) }
199
- committees = contribs.filter_map { |c| parse_committee(c) }
200
- [persons, committees]
201
- end
202
-
203
- def parse_person(contrib)
204
- return nil unless contrib["person"]
205
-
206
- name = extract_person_name(contrib["person"])
207
- return nil unless name
208
-
209
- role = (contrib["role"] || []).first&.fetch("type", nil)
210
- { "name" => name, "role" => role }
211
- end
212
-
213
- def parse_committee(contrib)
214
- return nil unless contrib["organization"]
215
-
216
- extract_org_subdivision(contrib["organization"])
217
- end
218
-
219
- def extract_person_name(person)
220
- n = person["name"] || {}
221
- complete = n["completename"]
222
- return complete["content"] if complete.is_a?(Hash) && complete["content"]
223
- return complete if complete.is_a?(String)
224
-
225
- surname = n["surname"]
226
- given = n["given"]
227
- given_str = given.is_a?(Hash) ? given["content"].to_s : given.to_s
228
- parts = [given_str, surname].compact
229
- parts.empty? ? nil : parts.join(" ")
230
- end
231
-
232
- def extract_org_subdivision(org)
233
- subs = org["subdivision"]
234
- return nil unless subs&.any?
235
-
236
- sd = subs.first
237
- sd_name = sd["name"]
238
- if sd_name.is_a?(Array)
239
- sd_name.first&.dig("content")
240
- elsif sd_name.is_a?(Hash)
241
- sd_name["content"]
242
- else
243
- sd_name.to_s
244
- end
245
- end
246
86
  end
247
87
  end
248
88
  end
@@ -11,6 +11,21 @@ module Metanorma
11
11
  raise NotImplementedError,
12
12
  "#{self.class} must implement #compute_asset_name"
13
13
  end
14
+
15
+ def self.slug_from_identifier(identifier)
16
+ identifier.to_s.strip
17
+ .gsub(/\s+/, "-")
18
+ .gsub(/:+/, "-")
19
+ .downcase
20
+ .gsub(/--+/, "-")
21
+ .gsub(/[-.]+$/, "")
22
+ end
23
+
24
+ def self.publisher_from_identifier(identifier)
25
+ return nil if identifier.nil? || identifier.strip.empty?
26
+
27
+ identifier.strip.split(/[\s-]/).first&.downcase
28
+ end
14
29
  end
15
30
 
16
31
  class EditionSlug
@@ -18,18 +33,12 @@ module Metanorma
18
33
 
19
34
  def compute_tag(publication)
20
35
  tag = "#{publication.slug}/ed#{publication.edition}"
21
- { tag: tag, pre_release: draft?(publication) }
36
+ { tag: tag, pre_release: publication.draft? }
22
37
  end
23
38
 
24
39
  def compute_asset_name(publication)
25
40
  "#{publication.slug}-ed#{publication.edition}.zip"
26
41
  end
27
-
28
- private
29
-
30
- def draft?(publication)
31
- %w[20 30 40 50].include?(publication.stage.to_s)
32
- end
33
42
  end
34
43
 
35
44
  class VersionSlug
@@ -37,18 +46,12 @@ module Metanorma
37
46
 
38
47
  def compute_tag(publication)
39
48
  tag = "#{publication.slug}/v#{publication.edition}"
40
- { tag: tag, pre_release: draft?(publication) }
49
+ { tag: tag, pre_release: publication.draft? }
41
50
  end
42
51
 
43
52
  def compute_asset_name(publication)
44
53
  "#{publication.slug}-v#{publication.edition}.zip"
45
54
  end
46
-
47
- private
48
-
49
- def draft?(publication)
50
- %w[20 30 40 50].include?(publication.stage.to_s)
51
- end
52
55
  end
53
56
 
54
57
  class InternetDraftSlug
@@ -129,6 +132,18 @@ module Metanorma
129
132
  @strategies.fetch(publisher.to_s, @default)
130
133
  end
131
134
 
135
+ def with_default(strategy)
136
+ registry = new
137
+ @strategies.each { |pub, s| registry.register(pub, s) }
138
+ registry.set_default(strategy)
139
+ registry
140
+ end
141
+
142
+ def set_default(strategy)
143
+ @default = strategy
144
+ self
145
+ end
146
+
132
147
  def self.from_config(config)
133
148
  registry = new
134
149
  config.slug_strategies.each do |publisher, strategy_name|
@@ -136,7 +151,7 @@ module Metanorma
136
151
  registry.register(publisher, strategy) if strategy
137
152
  end
138
153
  default = build_strategy(config.slug_default_strategy) || EditionSlug.new
139
- registry.instance_variable_set(:@default, default)
154
+ registry.set_default(default)
140
155
  registry
141
156
  end
142
157
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Metanorma
4
4
  module Release
5
- VERSION = "0.2.24"
5
+ VERSION = "0.2.25"
6
6
  end
7
7
  end
@@ -4,18 +4,32 @@ module Metanorma
4
4
  module Release
5
5
  require_relative "release/version"
6
6
 
7
- # Domain models
8
- autoload :Publication, "metanorma/release/publication"
9
- autoload :PublicationFile, "metanorma/release/publication"
10
- autoload :PublicationSource, "metanorma/release/publication"
11
- autoload :Index, "metanorma/release/index"
12
- autoload :Site, "metanorma/release/site"
13
- autoload :Channel, "metanorma/release/channel"
14
- autoload :Config, "metanorma/release/config"
15
- autoload :DocumentEntry, "metanorma/release/config"
16
- autoload :ChannelResolver, "metanorma/release/config"
17
- autoload :ContentHash, "metanorma/release/content_hash"
7
+ require "logger"
8
+
9
+ class << self
10
+ attr_writer :logger
18
11
 
12
+ def logger
13
+ @logger ||= Logger.new($stderr, level: Logger::WARN)
14
+ end
15
+ end
16
+
17
+ # Domain models
18
+ autoload :Publication, "metanorma/release/publication"
19
+ autoload :PublicationFile, "metanorma/release/publication"
20
+ autoload :PublicationSource, "metanorma/release/publication"
21
+ autoload :PublicationSerializer, "metanorma/release/publication_serializer"
22
+ autoload :RxlExtractor, "metanorma/release/rxl_extractor"
23
+ autoload :Index, "metanorma/release/index"
24
+ autoload :Site, "metanorma/release/site"
25
+ autoload :Channel, "metanorma/release/channel"
26
+ autoload :Config, "metanorma/release/config"
27
+ autoload :ConfigLoader, "metanorma/release/config"
28
+ autoload :DocumentEntry, "metanorma/release/config"
29
+ autoload :ChannelResolver, "metanorma/release/config"
30
+ autoload :ContentHash, "metanorma/release/content_hash"
31
+ autoload :DependencyValidation, "metanorma/release/dependency_validation"
32
+ autoload :DocumentFlattener, "metanorma/release/document_flattener"
19
33
  # Strategies
20
34
  autoload :SlugStrategy, "metanorma/release/slug_strategy"
21
35
  autoload :EditionSlug, "metanorma/release/slug_strategy"
@@ -25,14 +39,17 @@ module Metanorma
25
39
  autoload :DraftSuffixSlug, "metanorma/release/slug_strategy"
26
40
  autoload :SlugRegistry, "metanorma/release/slug_strategy"
27
41
 
28
- # Interfaces
29
- autoload :Filter, "metanorma/release/interfaces"
30
- autoload :ChangeDetector, "metanorma/release/interfaces"
31
- autoload :Packager, "metanorma/release/interfaces"
32
- autoload :Publisher, "metanorma/release/interfaces"
33
- autoload :RepoDiscoverer, "metanorma/release/interfaces"
34
- autoload :ReleaseFetcher, "metanorma/release/interfaces"
35
- autoload :ManifestReader, "metanorma/release/interfaces"
42
+ # Interfaces & shared types
43
+ autoload :Extractor, "metanorma/release/interfaces"
44
+ autoload :Release, "metanorma/release/interfaces"
45
+ autoload :Asset, "metanorma/release/interfaces"
46
+ autoload :Filter, "metanorma/release/interfaces"
47
+ autoload :ChangeDetector, "metanorma/release/interfaces"
48
+ autoload :Packager, "metanorma/release/interfaces"
49
+ autoload :Publisher, "metanorma/release/interfaces"
50
+ autoload :RepoDiscoverer, "metanorma/release/interfaces"
51
+ autoload :ReleaseFetcher, "metanorma/release/interfaces"
52
+ autoload :ManifestReader, "metanorma/release/interfaces"
36
53
 
37
54
  # Pipeline components
38
55
  autoload :RepoRef, "metanorma/release/repo_ref"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma-release
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.24
4
+ version: 0.2.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-23 00:00:00.000000000 Z
11
+ date: 2026-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: relaton-bib
@@ -104,6 +104,8 @@ files:
104
104
  - lib/metanorma/release/config.rb
105
105
  - lib/metanorma/release/content_hash.rb
106
106
  - lib/metanorma/release/delta_state.rb
107
+ - lib/metanorma/release/dependency_validation.rb
108
+ - lib/metanorma/release/document_flattener.rb
107
109
  - lib/metanorma/release/file_routing.rb
108
110
  - lib/metanorma/release/index.rb
109
111
  - lib/metanorma/release/interfaces.rb
@@ -118,11 +120,15 @@ files:
118
120
  - lib/metanorma/release/platform/local/fetcher.rb
119
121
  - lib/metanorma/release/platform/local/publisher.rb
120
122
  - lib/metanorma/release/platform/null.rb
123
+ - lib/metanorma/release/platform/null/manifest_reader.rb
121
124
  - lib/metanorma/release/platform/null/publisher.rb
125
+ - lib/metanorma/release/platform/static_discoverer.rb
122
126
  - lib/metanorma/release/platform_factory.rb
123
127
  - lib/metanorma/release/publication.rb
128
+ - lib/metanorma/release/publication_serializer.rb
124
129
  - lib/metanorma/release/release_pipeline.rb
125
130
  - lib/metanorma/release/repo_ref.rb
131
+ - lib/metanorma/release/rxl_extractor.rb
126
132
  - lib/metanorma/release/site.rb
127
133
  - lib/metanorma/release/slug_strategy.rb
128
134
  - lib/metanorma/release/version.rb