metanorma-release 0.2.4 → 0.2.6
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/README.adoc +24 -3
- data/lib/metanorma/release/cli.rb +7 -3
- data/lib/metanorma/release/commands/aggregate.rb +59 -2
- data/lib/metanorma/release/platform_factory.rb +2 -1
- data/lib/metanorma/release/site.rb +89 -21
- data/lib/metanorma/release/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fd5a3df2d9c64ed9d7d70d8f4c3fd3646d8c932a4cbffc39c07490e0276e37d9
|
|
4
|
+
data.tar.gz: f688bc55c1dff3751cdc1903e420e64c7503f7716710655c123e034dd0a3a3fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8127051648875b3ea36b988542c9ae82954bf68caf4ad91b785543d8ff6518ef95e1caa7d55334a777c22567806fbfa6cd32014812e6b2885388f42997f881d1
|
|
7
|
+
data.tar.gz: 77c9e3f867de101f3e962d674c8ddcabc630b9ba45557afe4c6338af0669a572dee37432b8231887cde7964f143817ea4ee1b50c9549bf2875dcdc76542152fe
|
data/README.adoc
CHANGED
|
@@ -133,6 +133,8 @@ metanorma-release release [options]
|
|
|
133
133
|
=== `metanorma-release aggregate`
|
|
134
134
|
|
|
135
135
|
Aggregate published releases from multiple repositories into a unified file tree.
|
|
136
|
+
Reads config from `metanorma.aggregate.yml` if present (auto-detected).
|
|
137
|
+
Idempotent: uses cached delta state (`.cache/aggregate/`) to skip unchanged repos.
|
|
136
138
|
|
|
137
139
|
[source,sh]
|
|
138
140
|
----
|
|
@@ -142,21 +144,40 @@ metanorma-release aggregate [options]
|
|
|
142
144
|
[cols="1m,3",options="header"]
|
|
143
145
|
|===
|
|
144
146
|
|Option |Description
|
|
147
|
+
|`--config FILE` |Config file (default: `metanorma.aggregate.yml`)
|
|
145
148
|
|`--source SOURCE` |Discovery source: `github`, `local:PATH` (default: `github`)
|
|
146
|
-
|`--organizations ORGS` |Organization list
|
|
149
|
+
|`--organizations ORGS` |Organization list (overrides config)
|
|
147
150
|
|`--topic TOPIC` |Repository topic filter (default: `metanorma-release`)
|
|
148
151
|
|`--repos REPOS` |Explicit repo list
|
|
149
152
|
|`--channels CHANS` |Filter channels
|
|
150
153
|
|`--stages STAGES` |Filter stages
|
|
151
154
|
|`--output-dir DIR` |Output directory (default: `_site/cc`)
|
|
152
155
|
|`--file-routing MODE` |File layout: `by-document`, `flat`, `by-format` (default: `by-document`)
|
|
153
|
-
|`--cache-dir DIR` |Cache directory for delta state
|
|
154
156
|
|`--[no-]include-drafts` |Include draft releases
|
|
155
157
|
|`--concurrency N` |Parallel repos (default: 4)
|
|
156
158
|
|`--min-documents N` |Fail if fewer documents found (default: 0)
|
|
157
|
-
|`--token TOKEN` |Platform auth token
|
|
159
|
+
|`--token TOKEN` |Platform auth token (falls back to `GITHUB_TOKEN` env)
|
|
158
160
|
|===
|
|
159
161
|
|
|
162
|
+
==== Aggregate config file
|
|
163
|
+
|
|
164
|
+
Create `metanorma.aggregate.yml` in your project root:
|
|
165
|
+
|
|
166
|
+
[source,yaml]
|
|
167
|
+
----
|
|
168
|
+
source: github
|
|
169
|
+
output_dir: _site/cc
|
|
170
|
+
file_routing: flat
|
|
171
|
+
cache_dir: .cache/aggregate
|
|
172
|
+
|
|
173
|
+
github:
|
|
174
|
+
organizations:
|
|
175
|
+
- MyOrg
|
|
176
|
+
topic: metanorma-release
|
|
177
|
+
----
|
|
178
|
+
|
|
179
|
+
CLI flags override config file values. Cache is always enabled (defaults to `.cache/aggregate/`).
|
|
180
|
+
|
|
160
181
|
== Concepts
|
|
161
182
|
|
|
162
183
|
=== Publication
|
|
@@ -82,15 +82,17 @@ module Metanorma
|
|
|
82
82
|
option :file_routing, type: :string, default: "by-document",
|
|
83
83
|
desc: "File routing (by-document|flat|by-format)"
|
|
84
84
|
option :cache_dir, type: :string, desc: "Cache directory"
|
|
85
|
+
option :data_dir, type: :string, desc: "Write flattened documents.json for site generators"
|
|
85
86
|
option :include_drafts, type: :boolean, default: false,
|
|
86
87
|
desc: "Include draft releases"
|
|
87
88
|
option :concurrency, type: :numeric, default: 4
|
|
88
89
|
option :min_documents, type: :numeric, default: 0,
|
|
89
90
|
desc: "Minimum required documents"
|
|
90
91
|
option :token, type: :string, desc: "Platform auth token"
|
|
92
|
+
option :config, type: :string, desc: "Config file (default: metanorma.aggregate.yml)"
|
|
91
93
|
|
|
92
94
|
def aggregate
|
|
93
|
-
config = AggregateCommand
|
|
95
|
+
config = AggregateCommand.build_config(
|
|
94
96
|
source: options[:source],
|
|
95
97
|
organizations: options[:organizations],
|
|
96
98
|
topic: options[:topic],
|
|
@@ -100,18 +102,20 @@ module Metanorma
|
|
|
100
102
|
output_dir: options[:output_dir],
|
|
101
103
|
file_routing: options[:file_routing],
|
|
102
104
|
cache_dir: options[:cache_dir],
|
|
105
|
+
data_dir: options[:data_dir],
|
|
103
106
|
include_drafts: options[:include_drafts],
|
|
104
107
|
concurrency: options[:concurrency],
|
|
105
108
|
min_documents: options[:min_documents],
|
|
106
109
|
token: options[:token],
|
|
107
110
|
create_zip: nil,
|
|
111
|
+
config: options[:config],
|
|
108
112
|
)
|
|
109
113
|
result = AggregateCommand.new(config).call
|
|
110
114
|
print_aggregate_result(result)
|
|
111
115
|
|
|
112
|
-
if
|
|
116
|
+
if config.min_documents.positive? && result.publications.length < config.min_documents
|
|
113
117
|
raise PipelineError,
|
|
114
|
-
"Found #{result.publications.length} documents, minimum is #{
|
|
118
|
+
"Found #{result.publications.length} documents, minimum is #{config.min_documents}"
|
|
115
119
|
end
|
|
116
120
|
|
|
117
121
|
unless result.failed_repos.empty?
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
3
5
|
module Metanorma
|
|
4
6
|
module Release
|
|
5
7
|
class AggregateCommand
|
|
6
8
|
Config = Struct.new(
|
|
7
9
|
:source, :organizations, :topic, :repos, :repo_pattern, :local_path,
|
|
8
10
|
:channels, :stages, :output_dir, :file_routing, :cache_dir,
|
|
9
|
-
:include_drafts, :concurrency, :min_documents, :token,
|
|
11
|
+
:data_dir, :include_drafts, :concurrency, :min_documents, :token,
|
|
12
|
+
:create_zip,
|
|
10
13
|
keyword_init: true
|
|
11
14
|
)
|
|
12
15
|
|
|
16
|
+
DEFAULT_CONFIG_FILE = "metanorma.aggregate.yml"
|
|
17
|
+
DEFAULT_CACHE_DIR = ".cache/aggregate"
|
|
18
|
+
|
|
13
19
|
def initialize(config)
|
|
14
20
|
@config = config
|
|
15
21
|
end
|
|
@@ -19,7 +25,8 @@ module Metanorma
|
|
|
19
25
|
return result unless result.publications.any?
|
|
20
26
|
|
|
21
27
|
index = build_index(result)
|
|
22
|
-
site = Site.new(index: index, output_dir: @config.output_dir
|
|
28
|
+
site = Site.new(index: index, output_dir: @config.output_dir,
|
|
29
|
+
data_dir: @config.data_dir)
|
|
23
30
|
site.write!
|
|
24
31
|
site.enrich!
|
|
25
32
|
site.package! if @config.create_zip
|
|
@@ -28,6 +35,56 @@ module Metanorma
|
|
|
28
35
|
result
|
|
29
36
|
end
|
|
30
37
|
|
|
38
|
+
def self.build_config(cli_options)
|
|
39
|
+
file_data = load_config_file(cli_options[:config])
|
|
40
|
+
merged = merge_config(file_data, cli_options)
|
|
41
|
+
Config.new(
|
|
42
|
+
source: merged[:source],
|
|
43
|
+
organizations: merged[:organizations],
|
|
44
|
+
topic: merged[:topic],
|
|
45
|
+
repos: merged[:repos],
|
|
46
|
+
channels: merged[:channels],
|
|
47
|
+
stages: merged[:stages],
|
|
48
|
+
output_dir: merged[:output_dir],
|
|
49
|
+
file_routing: merged[:file_routing],
|
|
50
|
+
cache_dir: merged[:cache_dir] || DEFAULT_CACHE_DIR,
|
|
51
|
+
data_dir: merged[:data_dir],
|
|
52
|
+
include_drafts: merged[:include_drafts],
|
|
53
|
+
concurrency: merged[:concurrency],
|
|
54
|
+
min_documents: merged[:min_documents],
|
|
55
|
+
token: merged[:token],
|
|
56
|
+
create_zip: merged[:create_zip],
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.load_config_file(path)
|
|
61
|
+
path ||= DEFAULT_CONFIG_FILE
|
|
62
|
+
return {} unless File.exist?(path)
|
|
63
|
+
|
|
64
|
+
YAML.safe_load_file(path, permitted_classes: [Symbol]) || {}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.merge_config(file_data, cli_options)
|
|
68
|
+
gh = file_data["github"] || {}
|
|
69
|
+
{
|
|
70
|
+
source: cli_options[:source] || file_data["source"],
|
|
71
|
+
organizations: cli_options[:organizations].any? ? cli_options[:organizations] : Array(gh["organizations"]),
|
|
72
|
+
topic: cli_options[:topic] || gh["topic"],
|
|
73
|
+
repos: cli_options[:repos] || file_data["repos"],
|
|
74
|
+
channels: cli_options[:channels].any? ? cli_options[:channels] : Array(file_data["channels"]),
|
|
75
|
+
stages: cli_options[:stages].any? ? cli_options[:stages] : Array(file_data["stages"]),
|
|
76
|
+
output_dir: cli_options[:output_dir] || file_data["output_dir"],
|
|
77
|
+
file_routing: cli_options[:file_routing] || file_data["file_routing"],
|
|
78
|
+
cache_dir: cli_options[:cache_dir] || file_data["cache_dir"],
|
|
79
|
+
data_dir: cli_options[:data_dir] || file_data["data_dir"],
|
|
80
|
+
include_drafts: cli_options[:include_drafts] || file_data["include_drafts"],
|
|
81
|
+
concurrency: cli_options[:concurrency] || file_data["concurrency"],
|
|
82
|
+
min_documents: cli_options[:min_documents] || file_data["min_documents"],
|
|
83
|
+
token: cli_options[:token],
|
|
84
|
+
create_zip: cli_options[:create_zip],
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
31
88
|
private
|
|
32
89
|
|
|
33
90
|
def run_aggregation
|
|
@@ -64,7 +64,8 @@ module Metanorma
|
|
|
64
64
|
|
|
65
65
|
def self.build_github_client(token)
|
|
66
66
|
require "octokit"
|
|
67
|
-
token
|
|
67
|
+
access_token = token || ENV.fetch("GITHUB_TOKEN", nil)
|
|
68
|
+
access_token ? Octokit::Client.new(access_token: access_token) : Octokit::Client.new
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
def self.register_publisher(name, factory)
|
|
@@ -9,9 +9,10 @@ module Metanorma
|
|
|
9
9
|
class Site
|
|
10
10
|
attr_reader :index, :output_dir
|
|
11
11
|
|
|
12
|
-
def initialize(index:, output_dir:)
|
|
12
|
+
def initialize(index:, output_dir:, data_dir: nil)
|
|
13
13
|
@index = index
|
|
14
14
|
@output_dir = output_dir
|
|
15
|
+
@data_dir = data_dir
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def write!
|
|
@@ -22,22 +23,49 @@ module Metanorma
|
|
|
22
23
|
def enrich!
|
|
23
24
|
return if index.empty?
|
|
24
25
|
|
|
25
|
-
documents =
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
documents = enrich_documents
|
|
27
|
+
write_relaton_index(documents)
|
|
28
|
+
write_data_file(documents) if @data_dir
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def package!(zip_path: nil)
|
|
32
|
+
require "zip"
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
path = zip_path || "#{output_dir}.zip"
|
|
35
|
+
Zip::File.open(path, Zip::File::CREATE) do |zipfile|
|
|
36
|
+
Dir.glob("#{output_dir}/**/*").each do |file|
|
|
37
|
+
next if File.directory?(file)
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
entry_name = file.sub("#{File.dirname(output_dir)}/", "")
|
|
40
|
+
zipfile.add(entry_name, file)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
path
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def enrich_documents
|
|
49
|
+
index.publications.map do |pub|
|
|
50
|
+
enrich_publication(pub)
|
|
36
51
|
rescue StandardError => e
|
|
37
52
|
warn " Skip #{pub.identifier}: #{e.message}"
|
|
38
53
|
pub.to_h
|
|
39
54
|
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def enrich_publication(pub)
|
|
58
|
+
rxl_file = pub.files.find { |f| f.format == "rxl" }
|
|
59
|
+
return pub.to_h unless rxl_file
|
|
40
60
|
|
|
61
|
+
rxl_path = File.join(output_dir, rxl_file.path)
|
|
62
|
+
return pub.to_h unless File.exist?(rxl_path)
|
|
63
|
+
|
|
64
|
+
bib = Relaton::Bib::Item.from_xml(File.read(rxl_path))
|
|
65
|
+
pub.to_h.merge("bibliographic" => bib.to_h)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def write_relaton_index(documents)
|
|
41
69
|
dest = File.join(output_dir, "relaton")
|
|
42
70
|
FileUtils.mkdir_p(dest)
|
|
43
71
|
index_data = { "root" => { "title" => "Document Registry",
|
|
@@ -47,19 +75,59 @@ module Metanorma
|
|
|
47
75
|
File.write(File.join(dest, "index.yaml"), YAML.dump(index_data))
|
|
48
76
|
end
|
|
49
77
|
|
|
50
|
-
def
|
|
51
|
-
|
|
78
|
+
def write_data_file(documents)
|
|
79
|
+
FileUtils.mkdir_p(@data_dir)
|
|
80
|
+
items = documents.compact.map { |doc| flatten_for_site(doc) }
|
|
81
|
+
File.write(File.join(@data_dir, "documents.json"),
|
|
82
|
+
JSON.pretty_generate({ "items" => items }))
|
|
83
|
+
end
|
|
52
84
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
85
|
+
def flatten_for_site(doc)
|
|
86
|
+
bib = doc["bibliographic"] || {}
|
|
87
|
+
doc_id = resolve_doc_id(bib, doc)
|
|
88
|
+
{
|
|
89
|
+
"slug" => doc["id"],
|
|
90
|
+
"id" => doc_id,
|
|
91
|
+
"title" => doc["title"].to_s,
|
|
92
|
+
"abstract" => extract_abstract(bib),
|
|
93
|
+
"stage" => (doc["stage"] || "published").to_s.downcase,
|
|
94
|
+
"doctype" => extract_doctype(bib) || doc.fetch("doctype", ""),
|
|
95
|
+
"edition" => doc["edition"],
|
|
96
|
+
"date" => extract_date(doc),
|
|
97
|
+
"channels" => doc["channels"] || [],
|
|
98
|
+
"formats" => doc["formats"] || [],
|
|
99
|
+
"files" => doc["files"] || [],
|
|
100
|
+
}
|
|
101
|
+
end
|
|
57
102
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
def resolve_doc_id(bib, doc)
|
|
104
|
+
extract_primary_id(bib) || doc["identifier"] || doc["id"]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def extract_primary_id(bib)
|
|
108
|
+
ids = bib["docidentifier"]
|
|
109
|
+
return nil unless ids&.any?
|
|
110
|
+
|
|
111
|
+
primary = ids.find { |di| di["primary"] == true } || ids.first
|
|
112
|
+
primary["content"]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def extract_doctype(bib)
|
|
116
|
+
bib.dig("ext", "doctype", "content")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def extract_abstract(bib)
|
|
120
|
+
abstracts = bib["abstract"]
|
|
121
|
+
return nil unless abstracts&.any?
|
|
122
|
+
|
|
123
|
+
abstracts.first["content"]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def extract_date(doc)
|
|
127
|
+
release_date = doc.dig("source", "releaseDate")
|
|
128
|
+
return nil unless release_date
|
|
129
|
+
|
|
130
|
+
release_date.to_s.split(/[T ]/).first
|
|
63
131
|
end
|
|
64
132
|
end
|
|
65
133
|
end
|
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.
|
|
4
|
+
version: 0.2.6
|
|
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-
|
|
11
|
+
date: 2026-05-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: relaton-bib
|