metanorma-release 0.2.23 → 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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +21 -319
- data/README.adoc +306 -91
- data/lib/metanorma/release/aggregation_pipeline.rb +65 -40
- data/lib/metanorma/release/asset_processor.rb +0 -2
- data/lib/metanorma/release/cache_store.rb +1 -0
- data/lib/metanorma/release/change_detector.rb +3 -2
- data/lib/metanorma/release/cli.rb +26 -2
- data/lib/metanorma/release/commands/aggregate.rb +0 -16
- 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/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 +4 -10
- 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 +4 -154
- data/lib/metanorma/release/slug_strategy.rb +30 -15
- data/lib/metanorma/release/version.rb +1 -1
- data/lib/metanorma/release.rb +36 -19
- metadata +8 -2
|
@@ -8,12 +8,13 @@ module Metanorma
|
|
|
8
8
|
class ContentHashChangeDetector
|
|
9
9
|
include ChangeDetector
|
|
10
10
|
|
|
11
|
-
def initialize(previous_releases:)
|
|
11
|
+
def initialize(previous_releases:, output_dir: ".")
|
|
12
12
|
@previous_releases = previous_releases
|
|
13
|
+
@output_dir = output_dir
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def detect(publication, tag, force: false)
|
|
16
|
-
current = publication.content_hash
|
|
17
|
+
current = publication.content_hash(from_directory: @output_dir)
|
|
17
18
|
previous = @previous_releases[tag.to_s]
|
|
18
19
|
changed = force || previous.nil? || !current.eql?(previous)
|
|
19
20
|
ChangeResult.new(changed?: changed, current_hash: current,
|
|
@@ -11,6 +11,17 @@ module Metanorma
|
|
|
11
11
|
|
|
12
12
|
class PipelineError < Thor::Error; end
|
|
13
13
|
|
|
14
|
+
desc "version", "Print version"
|
|
15
|
+
def version
|
|
16
|
+
puts "metanorma-release #{VERSION}"
|
|
17
|
+
end
|
|
18
|
+
map %w[--version -v] => :version
|
|
19
|
+
|
|
20
|
+
class_option :verbose, type: :boolean, default: false,
|
|
21
|
+
desc: "Verbose output"
|
|
22
|
+
class_option :quiet, type: :boolean, default: false,
|
|
23
|
+
desc: "Suppress warnings"
|
|
24
|
+
|
|
14
25
|
desc "package", "Package compiled documents"
|
|
15
26
|
option :output_dir, type: :string, default: "_site",
|
|
16
27
|
desc: "Compiled docs directory"
|
|
@@ -21,6 +32,7 @@ module Metanorma
|
|
|
21
32
|
option :config, type: :string, desc: "Config file"
|
|
22
33
|
|
|
23
34
|
def package
|
|
35
|
+
configure_logging
|
|
24
36
|
config = PackageCommand::Config.new(
|
|
25
37
|
output_dir: options[:output_dir],
|
|
26
38
|
dest: options[:dest],
|
|
@@ -49,6 +61,7 @@ module Metanorma
|
|
|
49
61
|
option :config, type: :string, desc: "Config file"
|
|
50
62
|
|
|
51
63
|
def release
|
|
64
|
+
configure_logging
|
|
52
65
|
config = ReleaseCommand::Config.new(
|
|
53
66
|
output_dir: options[:output_dir],
|
|
54
67
|
platform: options[:platform],
|
|
@@ -80,16 +93,19 @@ module Metanorma
|
|
|
80
93
|
option :file_routing, type: :string,
|
|
81
94
|
desc: "File routing (by-document|flat|by-format)"
|
|
82
95
|
option :cache_dir, type: :string, desc: "Cache directory"
|
|
83
|
-
option :data_dir, type: :string,
|
|
96
|
+
option :data_dir, type: :string,
|
|
97
|
+
desc: "Write flattened documents.json for site generators"
|
|
84
98
|
option :include_drafts, type: :boolean, default: false,
|
|
85
99
|
desc: "Include draft releases"
|
|
86
100
|
option :concurrency, type: :numeric, default: 4
|
|
87
101
|
option :min_documents, type: :numeric, default: 0,
|
|
88
102
|
desc: "Minimum required documents"
|
|
89
103
|
option :token, type: :string, desc: "Platform auth token"
|
|
90
|
-
option :config, type: :string,
|
|
104
|
+
option :config, type: :string,
|
|
105
|
+
desc: "Config file (default: metanorma.aggregate.yml)"
|
|
91
106
|
|
|
92
107
|
def aggregate
|
|
108
|
+
configure_logging
|
|
93
109
|
config = AggregateCommand.build_config(
|
|
94
110
|
source: options[:source],
|
|
95
111
|
organizations: options[:organizations],
|
|
@@ -123,6 +139,14 @@ module Metanorma
|
|
|
123
139
|
|
|
124
140
|
private
|
|
125
141
|
|
|
142
|
+
def configure_logging
|
|
143
|
+
if options[:verbose]
|
|
144
|
+
Metanorma::Release.logger.level = Logger::DEBUG
|
|
145
|
+
elsif options[:quiet]
|
|
146
|
+
Metanorma::Release.logger.level = Logger::ERROR
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
126
150
|
def print_package_result(result, dest)
|
|
127
151
|
released = result.released
|
|
128
152
|
puts "Packaged #{released.length} documents → #{dest}/"
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "base64"
|
|
4
3
|
require "yaml"
|
|
5
4
|
|
|
6
5
|
module Metanorma
|
|
@@ -33,7 +32,6 @@ module Metanorma
|
|
|
33
32
|
site.enrich!
|
|
34
33
|
site.package! if @config.create_zip
|
|
35
34
|
|
|
36
|
-
stamp_primary_identifiers(index)
|
|
37
35
|
result
|
|
38
36
|
end
|
|
39
37
|
|
|
@@ -151,20 +149,6 @@ module Metanorma
|
|
|
151
149
|
output_dir: @config.output_dir,
|
|
152
150
|
)
|
|
153
151
|
end
|
|
154
|
-
|
|
155
|
-
def stamp_primary_identifiers(index)
|
|
156
|
-
index.publications.each do |pub|
|
|
157
|
-
next unless pub.to_h["bibliographic"]
|
|
158
|
-
|
|
159
|
-
ids = pub.to_h.dig("bibliographic", "docidentifier")
|
|
160
|
-
next unless ids&.any?
|
|
161
|
-
|
|
162
|
-
primary = ids.find { |di| di["primary"] == true } || ids.first
|
|
163
|
-
pub.to_h.merge("primary_identifier" => primary["content"])
|
|
164
|
-
end
|
|
165
|
-
rescue LoadError
|
|
166
|
-
warn " (relaton gem not available — bibliography skipped)"
|
|
167
|
-
end
|
|
168
152
|
end
|
|
169
153
|
end
|
|
170
154
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Metanorma
|
|
4
4
|
module Release
|
|
5
5
|
class PackageCommand
|
|
6
|
+
extend ConfigLoader
|
|
7
|
+
|
|
6
8
|
Config = Struct.new(
|
|
7
9
|
:output_dir, :dest, :manifest, :config_source,
|
|
8
10
|
keyword_init: true
|
|
@@ -13,11 +15,15 @@ module Metanorma
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def call
|
|
16
|
-
config = load_config
|
|
18
|
+
config = self.class.load_config(
|
|
19
|
+
config_source: @config.config_source,
|
|
20
|
+
manifest: @config.manifest,
|
|
21
|
+
)
|
|
17
22
|
deps = ReleasePipeline::Dependencies.new(
|
|
18
|
-
extractor:
|
|
23
|
+
extractor: RxlExtractor,
|
|
19
24
|
filters: [],
|
|
20
|
-
change_detector: ContentHashChangeDetector.new(previous_releases: {}
|
|
25
|
+
change_detector: ContentHashChangeDetector.new(previous_releases: {},
|
|
26
|
+
output_dir: @config.output_dir),
|
|
21
27
|
packager: ZipPackager.new(output_dir: @config.output_dir),
|
|
22
28
|
publisher: PlatformFactory.build_publisher("null", {}),
|
|
23
29
|
slug_registry: SlugRegistry.from_config(config),
|
|
@@ -28,27 +34,13 @@ module Metanorma
|
|
|
28
34
|
|
|
29
35
|
pipeline_config = ReleasePipeline::Config.new(
|
|
30
36
|
output_dir: @config.output_dir,
|
|
31
|
-
manifest_path: @config.manifest,
|
|
32
37
|
force: false,
|
|
33
38
|
force_replace_patterns: nil,
|
|
34
39
|
concurrency: 4,
|
|
35
|
-
default_visibility: "public",
|
|
36
40
|
)
|
|
37
41
|
|
|
38
42
|
ReleasePipeline.new(deps).run(pipeline_config)
|
|
39
43
|
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def load_config
|
|
44
|
-
if @config.config_source && File.exist?(@config.config_source)
|
|
45
|
-
Metanorma::Release::Config.from_file(@config.config_source)
|
|
46
|
-
elsif @config.manifest && File.exist?(@config.manifest)
|
|
47
|
-
Metanorma::Release::Config.from_file(@config.manifest)
|
|
48
|
-
else
|
|
49
|
-
Metanorma::Release::Config.defaults
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
44
|
end
|
|
53
45
|
end
|
|
54
46
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "yaml"
|
|
4
|
-
|
|
5
3
|
module Metanorma
|
|
6
4
|
module Release
|
|
7
5
|
class ReleaseCommand
|
|
6
|
+
extend ConfigLoader
|
|
7
|
+
|
|
8
8
|
Config = Struct.new(
|
|
9
9
|
:output_dir, :platform, :manifest, :force,
|
|
10
10
|
:force_replace, :channels, :concurrency, :token, :config_source,
|
|
@@ -16,15 +16,20 @@ module Metanorma
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def call
|
|
19
|
-
config = load_config
|
|
19
|
+
config = self.class.load_config(
|
|
20
|
+
config_source: @config.config_source,
|
|
21
|
+
manifest: @config.manifest,
|
|
22
|
+
)
|
|
20
23
|
options = { token: @config.token }
|
|
21
24
|
publisher = PlatformFactory.build_publisher(@config.platform, options)
|
|
22
25
|
channel_override = Channel.parse_list(@config.channels) if @config.channels
|
|
23
26
|
|
|
27
|
+
previous_releases = build_previous_releases
|
|
24
28
|
deps = ReleasePipeline::Dependencies.new(
|
|
25
|
-
extractor:
|
|
29
|
+
extractor: RxlExtractor,
|
|
26
30
|
filters: [],
|
|
27
|
-
change_detector: ContentHashChangeDetector.new(previous_releases:
|
|
31
|
+
change_detector: ContentHashChangeDetector.new(previous_releases: previous_releases,
|
|
32
|
+
output_dir: @config.output_dir),
|
|
28
33
|
packager: ZipPackager.new(output_dir: @config.output_dir),
|
|
29
34
|
publisher: publisher,
|
|
30
35
|
slug_registry: SlugRegistry.from_config(config),
|
|
@@ -35,11 +40,9 @@ module Metanorma
|
|
|
35
40
|
|
|
36
41
|
pipeline_config = ReleasePipeline::Config.new(
|
|
37
42
|
output_dir: @config.output_dir,
|
|
38
|
-
manifest_path: @config.manifest,
|
|
39
43
|
force: @config.force,
|
|
40
44
|
force_replace_patterns: @config.force_replace && !@config.force_replace.empty? ? @config.force_replace : nil,
|
|
41
45
|
concurrency: @config.concurrency,
|
|
42
|
-
default_visibility: "public",
|
|
43
46
|
)
|
|
44
47
|
|
|
45
48
|
ReleasePipeline.new(deps).run(pipeline_config)
|
|
@@ -47,13 +50,15 @@ module Metanorma
|
|
|
47
50
|
|
|
48
51
|
private
|
|
49
52
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
def build_previous_releases
|
|
54
|
+
pubs = RxlExtractor.discover(@config.output_dir)
|
|
55
|
+
pubs.each_with_object({}) do |pub, map|
|
|
56
|
+
pub.slug
|
|
57
|
+
registry = SlugRegistry.default
|
|
58
|
+
strategy = registry.resolve(SlugStrategy.publisher_from_identifier(pub.identifier))
|
|
59
|
+
tag_info = strategy.compute_tag(pub)
|
|
60
|
+
content_hash = pub.content_hash(from_directory: @config.output_dir)
|
|
61
|
+
map[tag_info[:tag]] = content_hash if content_hash
|
|
57
62
|
end
|
|
58
63
|
end
|
|
59
64
|
end
|
|
@@ -60,13 +60,26 @@ module Metanorma
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
#
|
|
63
|
+
# Shared config resolution logic for CLI commands.
|
|
64
|
+
module ConfigLoader
|
|
65
|
+
def load_config(config_source:, manifest:)
|
|
66
|
+
if config_source && File.exist?(config_source)
|
|
67
|
+
Config.from_file(config_source)
|
|
68
|
+
elsif manifest && File.exist?(manifest)
|
|
69
|
+
Config.from_file(manifest)
|
|
70
|
+
else
|
|
71
|
+
Config.defaults
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Single routing entry — matches by any combination of pattern,
|
|
64
77
|
# stage, and doctype. An entry with no criteria matches everything (catch-all).
|
|
65
|
-
DocumentEntry = Struct.new(:pattern, :
|
|
78
|
+
DocumentEntry = Struct.new(:pattern, :stages, :doctypes,
|
|
79
|
+
:channels, keyword_init: true) do
|
|
66
80
|
def initialize(data)
|
|
67
81
|
super(
|
|
68
82
|
pattern: data["pattern"],
|
|
69
|
-
source: data["source"],
|
|
70
83
|
stages: Array(data["stage"]).map(&:to_s),
|
|
71
84
|
doctypes: Array(data["doctype"]).map(&:to_s),
|
|
72
85
|
channels: Array(data["channels"]).map(&:to_s),
|
|
@@ -80,10 +93,6 @@ module Metanorma
|
|
|
80
93
|
return false
|
|
81
94
|
end
|
|
82
95
|
|
|
83
|
-
if source && !(publication.source_path&.end_with?(source) || false)
|
|
84
|
-
return false
|
|
85
|
-
end
|
|
86
|
-
|
|
87
96
|
if !stages.empty? && !stages.include?(publication.stage.to_s)
|
|
88
97
|
return false
|
|
89
98
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module DependencyValidation
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def validate_interface!(obj, mod, name)
|
|
9
|
+
return if obj.is_a?(mod) || begin
|
|
10
|
+
obj.class.ancestors.include?(mod)
|
|
11
|
+
rescue StandardError
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
raise ArgumentError, "#{name} must include #{mod}, got #{obj.class}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -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
|
|
|
@@ -43,21 +37,21 @@ module Metanorma
|
|
|
43
37
|
end
|
|
44
38
|
all
|
|
45
39
|
rescue StandardError => e
|
|
46
|
-
warn "
|
|
40
|
+
Metanorma::Release.logger.warn "Failed to fetch releases for #{repo_slug}: #{e.message}"
|
|
47
41
|
[]
|
|
48
42
|
end
|
|
49
43
|
|
|
50
44
|
def parse_release(r)
|
|
51
45
|
assets = (r[:assets] || []).map do |a|
|
|
52
46
|
data = download_asset(a[:url]) if a[:name].end_with?(".zip")
|
|
53
|
-
|
|
47
|
+
Asset.new(
|
|
54
48
|
name: a[:name],
|
|
55
49
|
browser_download_url: a[:browser_download_url],
|
|
56
50
|
size: a[:size],
|
|
57
51
|
data: data,
|
|
58
52
|
)
|
|
59
53
|
end
|
|
60
|
-
|
|
54
|
+
Release.new(
|
|
61
55
|
tag_name: r[:tag_name],
|
|
62
56
|
body: r[:body],
|
|
63
57
|
prerelease: r[:prerelease],
|
|
@@ -82,7 +76,7 @@ module Metanorma
|
|
|
82
76
|
end
|
|
83
77
|
data
|
|
84
78
|
rescue StandardError => e
|
|
85
|
-
warn "
|
|
79
|
+
Metanorma::Release.logger.warn "Failed to download asset #{url}: #{e.message}"
|
|
86
80
|
nil
|
|
87
81
|
end
|
|
88
82
|
|
|
@@ -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
|