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
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
begin
|
|
4
|
-
require "relaton/bib"
|
|
5
|
-
rescue LoadError
|
|
6
|
-
raise LoadError,
|
|
7
|
-
"The relaton-bib gem is required. Add `gem 'relaton-bib'` to your Gemfile."
|
|
8
|
-
end
|
|
9
|
-
|
|
10
3
|
require "json"
|
|
11
4
|
|
|
12
5
|
module Metanorma
|
|
@@ -67,6 +60,11 @@ module Metanorma
|
|
|
67
60
|
class Publication
|
|
68
61
|
METADATA_VERSION = 1
|
|
69
62
|
|
|
63
|
+
DRAFT_STAGES = %w[
|
|
64
|
+
20 30 40 50
|
|
65
|
+
working-draft committee-draft draft-standard final-draft
|
|
66
|
+
].freeze
|
|
67
|
+
|
|
70
68
|
attr_reader :identifier, :slug, :title, :edition, :stage, :doctype,
|
|
71
69
|
:revdate, :files, :channels, :source
|
|
72
70
|
|
|
@@ -94,14 +92,23 @@ module Metanorma
|
|
|
94
92
|
files.any? ? File.dirname(files.first.path) : "."
|
|
95
93
|
end
|
|
96
94
|
|
|
97
|
-
def content_hash
|
|
98
|
-
|
|
95
|
+
def content_hash(from_directory: nil)
|
|
96
|
+
dir = if from_directory && base_dir == "."
|
|
97
|
+
from_directory
|
|
98
|
+
else
|
|
99
|
+
base_dir
|
|
100
|
+
end
|
|
101
|
+
ContentHash.of_directory(dir, base: slug)
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
def file?(format)
|
|
102
105
|
formats.include?(format)
|
|
103
106
|
end
|
|
104
107
|
|
|
108
|
+
def draft?
|
|
109
|
+
DRAFT_STAGES.include?(stage.to_s)
|
|
110
|
+
end
|
|
111
|
+
|
|
105
112
|
def eql?(other)
|
|
106
113
|
other.is_a?(self.class) && identifier == other.identifier &&
|
|
107
114
|
edition == other.edition && stage == other.stage
|
|
@@ -154,40 +161,26 @@ module Metanorma
|
|
|
154
161
|
end
|
|
155
162
|
|
|
156
163
|
def to_release_body
|
|
157
|
-
|
|
164
|
+
PublicationSerializer.to_release_body(self)
|
|
158
165
|
end
|
|
159
166
|
|
|
160
167
|
def to_json(*_args)
|
|
161
|
-
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def self.from_json(json_string)
|
|
165
|
-
data = JSON.parse(json_string)
|
|
166
|
-
raise ArgumentError, "Missing required field: id" unless data["id"]
|
|
167
|
-
unless data["title"]
|
|
168
|
-
raise ArgumentError,
|
|
169
|
-
"Missing required field: title"
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
from_metadata_hash(data)
|
|
168
|
+
PublicationSerializer.to_json(self)
|
|
173
169
|
end
|
|
174
170
|
|
|
175
171
|
def self.from_release_body(body)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
match = body.match(/<!--\s*mn-release-metadata\s*\n(.*?)\n-->/m)
|
|
179
|
-
return nil unless match
|
|
172
|
+
PublicationSerializer.from_release_body(body)
|
|
173
|
+
end
|
|
180
174
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
nil
|
|
175
|
+
def self.from_json(json_string)
|
|
176
|
+
PublicationSerializer.from_json(json_string)
|
|
184
177
|
end
|
|
185
178
|
|
|
186
179
|
def self.from_metadata_hash(data)
|
|
187
180
|
ident = data["identifier"] || data["id"]
|
|
188
181
|
new(
|
|
189
182
|
identifier: ident,
|
|
190
|
-
slug: slug_from_identifier(ident),
|
|
183
|
+
slug: SlugStrategy.slug_from_identifier(ident),
|
|
191
184
|
title: data["title"],
|
|
192
185
|
edition: data["edition"],
|
|
193
186
|
stage: data["stage"],
|
|
@@ -199,137 +192,6 @@ module Metanorma
|
|
|
199
192
|
metadata_formats: data["formats"],
|
|
200
193
|
)
|
|
201
194
|
end
|
|
202
|
-
|
|
203
|
-
private
|
|
204
|
-
|
|
205
|
-
def metadata_hash
|
|
206
|
-
{
|
|
207
|
-
"version" => METADATA_VERSION,
|
|
208
|
-
"id" => slug,
|
|
209
|
-
"identifier" => identifier,
|
|
210
|
-
"title" => title,
|
|
211
|
-
"edition" => edition,
|
|
212
|
-
"stage" => stage,
|
|
213
|
-
"doctype" => doctype,
|
|
214
|
-
"revdate" => revdate,
|
|
215
|
-
"formats" => formats,
|
|
216
|
-
"channels" => channels,
|
|
217
|
-
"publisher" => Publication.publisher_from_identifier(identifier),
|
|
218
|
-
}
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def self.slug_from_identifier(identifier)
|
|
222
|
-
identifier.to_s.strip
|
|
223
|
-
.gsub(/\s+/, "-")
|
|
224
|
-
.gsub(/:+/, "-")
|
|
225
|
-
.downcase
|
|
226
|
-
.gsub(/--+/, "-")
|
|
227
|
-
.gsub(/[-.]+$/, "")
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def self.publisher_from_identifier(identifier)
|
|
231
|
-
return nil if identifier.nil? || identifier.strip.empty?
|
|
232
|
-
|
|
233
|
-
identifier.strip.split(/[\s-]/).first&.downcase
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# -- RXL extraction --
|
|
237
|
-
|
|
238
|
-
def self.discover(output_dir)
|
|
239
|
-
Dir.glob(File.join(output_dir, "**", "*.rxl")).filter_map do |path|
|
|
240
|
-
from_rxl(path)
|
|
241
|
-
rescue StandardError => e
|
|
242
|
-
warn "Warning: Skipping #{path}: #{e.message}"
|
|
243
|
-
nil
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def self.from_rxl(rxl_path)
|
|
248
|
-
unless File.exist?(rxl_path)
|
|
249
|
-
raise ArgumentError,
|
|
250
|
-
"RXL file not found: #{rxl_path}"
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
content = File.read(rxl_path)
|
|
254
|
-
bib = Relaton::Bib::Item.from_xml(content)
|
|
255
|
-
build_from_bib(bib, rxl_path)
|
|
256
|
-
rescue StandardError => e
|
|
257
|
-
warn "Warning: Failed to parse RXL #{rxl_path}: #{e.message}"
|
|
258
|
-
fallback_from_rxl(rxl_path)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
class << self
|
|
262
|
-
private
|
|
263
|
-
|
|
264
|
-
def build_from_bib(bib, rxl_path)
|
|
265
|
-
identifier = bib.docidentifier&.first&.content || ""
|
|
266
|
-
slug = slug_from_identifier(identifier)
|
|
267
|
-
output_dir = File.dirname(rxl_path)
|
|
268
|
-
base_name = File.basename(rxl_path, ".rxl")
|
|
269
|
-
|
|
270
|
-
new(
|
|
271
|
-
identifier: identifier, slug: slug,
|
|
272
|
-
title: bib.title&.first&.content || "",
|
|
273
|
-
edition: extract_edition(bib),
|
|
274
|
-
stage: extract_stage(bib),
|
|
275
|
-
doctype: extract_doctype(bib),
|
|
276
|
-
revdate: extract_revdate(bib),
|
|
277
|
-
files: discover_files(output_dir, base_name),
|
|
278
|
-
channels: [], source: nil
|
|
279
|
-
)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def extract_edition(bib)
|
|
283
|
-
ed = bib.edition
|
|
284
|
-
return "1" unless ed
|
|
285
|
-
|
|
286
|
-
ed.respond_to?(:content) ? ed.content.to_s : ed.to_s
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def extract_stage(bib)
|
|
290
|
-
stage = bib.status&.stage
|
|
291
|
-
return "" unless stage
|
|
292
|
-
|
|
293
|
-
stage.respond_to?(:content) ? stage.content.to_s : stage.to_s
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
def extract_doctype(bib)
|
|
297
|
-
doctype = bib.ext&.doctype
|
|
298
|
-
return "" unless doctype
|
|
299
|
-
|
|
300
|
-
doctype.respond_to?(:content) ? doctype.content.to_s : doctype.to_s
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def extract_revdate(bib)
|
|
304
|
-
date = bib.date&.find { |d| d.type == "published" } || bib.date&.first
|
|
305
|
-
return nil unless date
|
|
306
|
-
|
|
307
|
-
val = date.at
|
|
308
|
-
val ? val.to_s : nil
|
|
309
|
-
rescue StandardError
|
|
310
|
-
nil
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
def discover_files(output_dir, base_name)
|
|
314
|
-
Dir.glob(File.join(output_dir, "#{base_name}.*")).filter_map do |path|
|
|
315
|
-
next if File.directory?(path)
|
|
316
|
-
|
|
317
|
-
name = File.basename(path)
|
|
318
|
-
ext = File.extname(name).delete_prefix(".")
|
|
319
|
-
PublicationFile.new(format: ext, name: name, path: name)
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
def fallback_from_rxl(rxl_path)
|
|
324
|
-
base_name = File.basename(rxl_path, ".rxl")
|
|
325
|
-
slug = slug_from_identifier(base_name)
|
|
326
|
-
new(
|
|
327
|
-
identifier: base_name, slug: slug, title: "",
|
|
328
|
-
edition: "0", stage: "", doctype: "",
|
|
329
|
-
revdate: nil, files: [], channels: [], source: nil
|
|
330
|
-
)
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
195
|
end
|
|
334
196
|
end
|
|
335
197
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module PublicationSerializer
|
|
6
|
+
METADATA_COMMENT_PATTERN = /<!--\s*mn-release-metadata\s*\n(.*?)\n-->/m
|
|
7
|
+
|
|
8
|
+
def self.to_release_body(publication)
|
|
9
|
+
"<!-- mn-release-metadata\n#{JSON.generate(metadata_hash(publication))}\n-->"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.from_release_body(body)
|
|
13
|
+
return nil if body.nil? || body.empty?
|
|
14
|
+
|
|
15
|
+
match = body.match(METADATA_COMMENT_PATTERN)
|
|
16
|
+
return nil unless match
|
|
17
|
+
|
|
18
|
+
from_json(match[1])
|
|
19
|
+
rescue JSON::ParserError
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.to_json(publication)
|
|
24
|
+
JSON.generate(metadata_hash(publication))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.from_json(json_string)
|
|
28
|
+
data = JSON.parse(json_string)
|
|
29
|
+
raise ArgumentError, "Missing required field: id" unless data["id"]
|
|
30
|
+
unless data["title"]
|
|
31
|
+
raise ArgumentError,
|
|
32
|
+
"Missing required field: title"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Publication.from_metadata_hash(data)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def metadata_hash(publication)
|
|
42
|
+
{
|
|
43
|
+
"version" => Publication::METADATA_VERSION,
|
|
44
|
+
"id" => publication.slug,
|
|
45
|
+
"identifier" => publication.identifier,
|
|
46
|
+
"title" => publication.title,
|
|
47
|
+
"edition" => publication.edition,
|
|
48
|
+
"stage" => publication.stage,
|
|
49
|
+
"doctype" => publication.doctype,
|
|
50
|
+
"revdate" => publication.revdate,
|
|
51
|
+
"formats" => publication.formats,
|
|
52
|
+
"channels" => publication.channels,
|
|
53
|
+
"publisher" => SlugStrategy.publisher_from_identifier(publication.identifier),
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -15,6 +15,8 @@ module Metanorma
|
|
|
15
15
|
:manifest, :channel_override, :config,
|
|
16
16
|
keyword_init: true
|
|
17
17
|
) do
|
|
18
|
+
include DependencyValidation
|
|
19
|
+
|
|
18
20
|
def initialize(**kwargs)
|
|
19
21
|
super
|
|
20
22
|
validate_types!
|
|
@@ -23,9 +25,9 @@ module Metanorma
|
|
|
23
25
|
private
|
|
24
26
|
|
|
25
27
|
def validate_types!
|
|
26
|
-
unless extractor.
|
|
28
|
+
unless extractor.is_a?(Class) && extractor.singleton_class.ancestors.include?(Extractor)
|
|
27
29
|
raise ArgumentError,
|
|
28
|
-
"extractor must
|
|
30
|
+
"extractor must extend Extractor, got #{extractor}"
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
validate_interface!(change_detector, ChangeDetector,
|
|
@@ -33,21 +35,11 @@ module Metanorma
|
|
|
33
35
|
validate_interface!(packager, Packager, "packager")
|
|
34
36
|
validate_interface!(publisher, Publisher, "publisher")
|
|
35
37
|
end
|
|
36
|
-
|
|
37
|
-
def validate_interface!(obj, mod, name)
|
|
38
|
-
return if obj.is_a?(mod) || begin
|
|
39
|
-
obj.class.ancestors.include?(mod)
|
|
40
|
-
rescue StandardError
|
|
41
|
-
false
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
raise ArgumentError, "#{name} must include #{mod}, got #{obj.class}"
|
|
45
|
-
end
|
|
46
38
|
end
|
|
47
39
|
|
|
48
40
|
Config = Struct.new(
|
|
49
|
-
:output_dir, :
|
|
50
|
-
:concurrency,
|
|
41
|
+
:output_dir, :force, :force_replace_patterns,
|
|
42
|
+
:concurrency,
|
|
51
43
|
keyword_init: true
|
|
52
44
|
)
|
|
53
45
|
|
|
@@ -72,7 +64,7 @@ module Metanorma
|
|
|
72
64
|
|
|
73
65
|
def phase_one(publications, config)
|
|
74
66
|
publications.map do |pub|
|
|
75
|
-
publisher =
|
|
67
|
+
publisher = SlugStrategy.publisher_from_identifier(pub.identifier)
|
|
76
68
|
strategy = @deps.slug_registry.resolve(publisher)
|
|
77
69
|
tag_info = strategy.compute_tag(pub)
|
|
78
70
|
canonical_base = strategy.compute_asset_name(pub).sub(/\.zip$/, "")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
class RxlExtractor
|
|
6
|
+
extend Extractor
|
|
7
|
+
|
|
8
|
+
def self.discover(output_dir)
|
|
9
|
+
require "relaton/bib"
|
|
10
|
+
Dir.glob(File.join(output_dir, "**", "*.rxl")).filter_map do |path|
|
|
11
|
+
from_rxl(path)
|
|
12
|
+
rescue StandardError => e
|
|
13
|
+
Metanorma::Release.logger.warn "Skipping #{path}: #{e.message}"
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.from_rxl(rxl_path)
|
|
19
|
+
unless File.exist?(rxl_path)
|
|
20
|
+
raise ArgumentError,
|
|
21
|
+
"RXL file not found: #{rxl_path}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
content = File.read(rxl_path)
|
|
25
|
+
bib = Relaton::Bib::Item.from_xml(content)
|
|
26
|
+
build_from_bib(bib, rxl_path)
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
Metanorma::Release.logger.warn "Failed to parse RXL #{rxl_path}: #{e.message}"
|
|
29
|
+
fallback_from_rxl(rxl_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def build_from_bib(bib, rxl_path)
|
|
36
|
+
identifier = bib.docidentifier&.first&.content || ""
|
|
37
|
+
slug = SlugStrategy.slug_from_identifier(identifier)
|
|
38
|
+
output_dir = File.dirname(rxl_path)
|
|
39
|
+
base_name = File.basename(rxl_path, ".rxl")
|
|
40
|
+
|
|
41
|
+
Publication.new(
|
|
42
|
+
identifier: identifier, slug: slug,
|
|
43
|
+
title: bib.title&.first&.content || "",
|
|
44
|
+
edition: extract_edition(bib),
|
|
45
|
+
stage: extract_stage(bib),
|
|
46
|
+
doctype: extract_doctype(bib),
|
|
47
|
+
revdate: extract_revdate(bib),
|
|
48
|
+
files: discover_files(output_dir, base_name),
|
|
49
|
+
channels: [], source: nil
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def extract_edition(bib)
|
|
54
|
+
ed = bib.edition
|
|
55
|
+
return "1" unless ed
|
|
56
|
+
|
|
57
|
+
ed.is_a?(String) ? ed : ed.content.to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def extract_stage(bib)
|
|
61
|
+
stage = bib.status&.stage
|
|
62
|
+
return "" unless stage
|
|
63
|
+
|
|
64
|
+
stage.is_a?(String) ? stage : stage.content.to_s
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def extract_doctype(bib)
|
|
68
|
+
doctype = bib.ext&.doctype
|
|
69
|
+
return "" unless doctype
|
|
70
|
+
|
|
71
|
+
doctype.is_a?(String) ? doctype : doctype.content.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def extract_revdate(bib)
|
|
75
|
+
date = bib.date&.find { |d| d.type == "published" } || bib.date&.first
|
|
76
|
+
return nil unless date
|
|
77
|
+
|
|
78
|
+
val = date.at
|
|
79
|
+
val&.to_s
|
|
80
|
+
rescue StandardError
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def discover_files(output_dir, base_name)
|
|
85
|
+
Dir.glob(File.join(output_dir, "#{base_name}.*")).filter_map do |path|
|
|
86
|
+
next if File.directory?(path)
|
|
87
|
+
|
|
88
|
+
name = File.basename(path)
|
|
89
|
+
ext = File.extname(name).delete_prefix(".")
|
|
90
|
+
PublicationFile.new(format: ext, name: name, path: name)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def fallback_from_rxl(rxl_path)
|
|
95
|
+
base_name = File.basename(rxl_path, ".rxl")
|
|
96
|
+
slug = SlugStrategy.slug_from_identifier(base_name)
|
|
97
|
+
Publication.new(
|
|
98
|
+
identifier: base_name, slug: slug, title: "",
|
|
99
|
+
edition: "0", stage: "", doctype: "",
|
|
100
|
+
revdate: nil, files: [], channels: [], source: nil
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -13,7 +13,7 @@ module Metanorma
|
|
|
13
13
|
@index = index
|
|
14
14
|
@output_dir = output_dir
|
|
15
15
|
@data_dir = data_dir
|
|
16
|
-
@
|
|
16
|
+
@flattener = DocumentFlattener.new(display_categories: display_categories)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def write!
|
|
@@ -33,7 +33,7 @@ module Metanorma
|
|
|
33
33
|
require "zip"
|
|
34
34
|
|
|
35
35
|
path = zip_path || "#{output_dir}.zip"
|
|
36
|
-
Zip::File.open(path,
|
|
36
|
+
Zip::File.open(path, create: true) do |zipfile|
|
|
37
37
|
Dir.glob("#{output_dir}/**/*").each do |file|
|
|
38
38
|
next if File.directory?(file)
|
|
39
39
|
|
|
@@ -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 "
|
|
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|
|
|
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
|