metanorma-release 0.2.1
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +504 -0
- data/CHANGELOG.md +15 -0
- data/PROMPT.md +282 -0
- data/README.adoc +430 -0
- data/Rakefile +8 -0
- data/exe/mn-release +6 -0
- data/lib/metanorma/release/aggregation_interfaces.rb +33 -0
- data/lib/metanorma/release/aggregation_pipeline.rb +155 -0
- data/lib/metanorma/release/asset_processor.rb +58 -0
- data/lib/metanorma/release/cache_store.rb +86 -0
- data/lib/metanorma/release/change_detector.rb +20 -0
- data/lib/metanorma/release/channel.rb +64 -0
- data/lib/metanorma/release/channel_audience.rb +24 -0
- data/lib/metanorma/release/channel_config.rb +55 -0
- data/lib/metanorma/release/channel_filter.rb +26 -0
- data/lib/metanorma/release/channel_manifest.rb +192 -0
- data/lib/metanorma/release/channel_registry.rb +60 -0
- data/lib/metanorma/release/cli.rb +129 -0
- data/lib/metanorma/release/commands/aggregate.rb +126 -0
- data/lib/metanorma/release/commands/package.rb +46 -0
- data/lib/metanorma/release/commands/publish.rb +51 -0
- data/lib/metanorma/release/config_fetcher.rb +11 -0
- data/lib/metanorma/release/config_locator.rb +37 -0
- data/lib/metanorma/release/config_resolver.rb +37 -0
- data/lib/metanorma/release/content_hash.rb +51 -0
- data/lib/metanorma/release/delta_state.rb +108 -0
- data/lib/metanorma/release/document_id.rb +45 -0
- data/lib/metanorma/release/document_index.rb +183 -0
- data/lib/metanorma/release/document_metadata.rb +39 -0
- data/lib/metanorma/release/document_stage.rb +86 -0
- data/lib/metanorma/release/document_type.rb +55 -0
- data/lib/metanorma/release/document_version.rb +50 -0
- data/lib/metanorma/release/file_routing.rb +51 -0
- data/lib/metanorma/release/interfaces.rb +47 -0
- data/lib/metanorma/release/naming_strategy.rb +158 -0
- data/lib/metanorma/release/platform/github/config_fetcher.rb +40 -0
- data/lib/metanorma/release/platform/github/manifest_reader.rb +32 -0
- data/lib/metanorma/release/platform/github/publisher.rb +73 -0
- data/lib/metanorma/release/platform/github/release_fetcher.rb +52 -0
- data/lib/metanorma/release/platform/github/topic_discoverer.rb +29 -0
- data/lib/metanorma/release/platform/github.rb +25 -0
- data/lib/metanorma/release/platform/local/config_fetcher.rb +20 -0
- data/lib/metanorma/release/platform/local/directory_discoverer.rb +26 -0
- data/lib/metanorma/release/platform/local/fetcher.rb +76 -0
- data/lib/metanorma/release/platform/local/publisher.rb +44 -0
- data/lib/metanorma/release/platform/local.rb +14 -0
- data/lib/metanorma/release/platform/null/publisher.rb +17 -0
- data/lib/metanorma/release/platform/null.rb +11 -0
- data/lib/metanorma/release/platform.rb +11 -0
- data/lib/metanorma/release/platform_factory.rb +78 -0
- data/lib/metanorma/release/rake_tasks.rb +71 -0
- data/lib/metanorma/release/relaton_enricher.rb +138 -0
- data/lib/metanorma/release/release_metadata.rb +79 -0
- data/lib/metanorma/release/release_pipeline.rb +115 -0
- data/lib/metanorma/release/release_tag.rb +49 -0
- data/lib/metanorma/release/repo_ref.rb +34 -0
- data/lib/metanorma/release/rxl_extractor.rb +115 -0
- data/lib/metanorma/release/stage_filter.rb +18 -0
- data/lib/metanorma/release/version.rb +7 -0
- data/lib/metanorma/release/zip_packager.rb +37 -0
- data/lib/metanorma/release.rb +116 -0
- metadata +156 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
module GitHub
|
|
7
|
+
class TopicDiscoverer
|
|
8
|
+
include Metanorma::Release::RepoDiscoverer
|
|
9
|
+
|
|
10
|
+
def initialize(client:, organizations:, topic:)
|
|
11
|
+
@client = client
|
|
12
|
+
@organizations = organizations
|
|
13
|
+
@topic = topic
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def discover
|
|
17
|
+
@organizations.flat_map do |org|
|
|
18
|
+
query = "topic:#{@topic} org:#{org}"
|
|
19
|
+
results = @client.search_repositories(query)
|
|
20
|
+
results[:items].map do |repo|
|
|
21
|
+
RepoRef.new(owner: org, repo: repo[:name])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'octokit'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
raise LoadError, "The octokit gem is required for GitHub adapters. Add `gem 'octokit'` to your Gemfile."
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Metanorma
|
|
10
|
+
module Release
|
|
11
|
+
module Platform
|
|
12
|
+
module GitHub
|
|
13
|
+
autoload :Publisher, 'metanorma/release/platform/github/publisher'
|
|
14
|
+
autoload :TopicDiscoverer, 'metanorma/release/platform/github/topic_discoverer'
|
|
15
|
+
autoload :ReleaseFetcher, 'metanorma/release/platform/github/release_fetcher'
|
|
16
|
+
autoload :ManifestReader, 'metanorma/release/platform/github/manifest_reader'
|
|
17
|
+
autoload :ConfigFetcher, 'metanorma/release/platform/github/config_fetcher'
|
|
18
|
+
|
|
19
|
+
def self.cache_store(cache_dir:)
|
|
20
|
+
FileCacheStore.new(cache_dir)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
module Local
|
|
7
|
+
class ConfigFetcher
|
|
8
|
+
include Metanorma::Release::ConfigFetcher
|
|
9
|
+
|
|
10
|
+
def fetch(source)
|
|
11
|
+
path = source.sub(/\Alocal:/, '')
|
|
12
|
+
return nil unless File.exist?(path)
|
|
13
|
+
|
|
14
|
+
ChannelConfig.from_file(path)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
module Local
|
|
7
|
+
class DirectoryDiscoverer
|
|
8
|
+
include Metanorma::Release::RepoDiscoverer
|
|
9
|
+
|
|
10
|
+
def initialize(base_path:)
|
|
11
|
+
@base_path = base_path
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def discover
|
|
15
|
+
return [] unless Dir.exist?(@base_path)
|
|
16
|
+
|
|
17
|
+
Dir.children(@base_path).filter_map do |entry|
|
|
18
|
+
full = File.join(@base_path, entry)
|
|
19
|
+
RepoRef.new(owner: 'local', repo: entry) if File.directory?(full)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Metanorma
|
|
6
|
+
module Release
|
|
7
|
+
module Platform
|
|
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
|
+
class Fetcher
|
|
16
|
+
include Metanorma::Release::ReleaseFetcher
|
|
17
|
+
|
|
18
|
+
def initialize(base_path:)
|
|
19
|
+
@base_path = base_path
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def fetch(repo, etag: nil)
|
|
23
|
+
dir = File.join(@base_path, repo.repo)
|
|
24
|
+
return FetchResult.new(releases: [], etag: nil, unchanged?: false) unless Dir.exist?(dir)
|
|
25
|
+
|
|
26
|
+
releases = Dir.glob(File.join(dir, '*.meta.json')).filter_map do |meta_path|
|
|
27
|
+
build_release(dir, meta_path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
FetchResult.new(releases: releases, etag: nil, unchanged?: false)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def build_release(dir, meta_path)
|
|
36
|
+
data = JSON.parse(File.read(meta_path))
|
|
37
|
+
base = File.basename(meta_path, '.meta.json')
|
|
38
|
+
zip_path = File.join(dir, "#{base}.zip")
|
|
39
|
+
|
|
40
|
+
unless File.exist?(zip_path)
|
|
41
|
+
warn "Warning: Missing zip for #{meta_path}, skipping"
|
|
42
|
+
return nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
metadata = ReleaseMetadata.new(data)
|
|
46
|
+
asset = LocalAsset.new(
|
|
47
|
+
name: "#{base}.zip",
|
|
48
|
+
browser_download_url: "file://#{File.expand_path(zip_path)}",
|
|
49
|
+
size: File.size(zip_path),
|
|
50
|
+
data: File.binread(zip_path)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
LocalRelease.new(
|
|
54
|
+
tag_name: "#{data['id']}/#{data.fetch('edition', '1')}",
|
|
55
|
+
body: metadata.to_release_body,
|
|
56
|
+
prerelease: prerelease?(data),
|
|
57
|
+
draft: false,
|
|
58
|
+
html_url: "file://#{File.expand_path(dir)}",
|
|
59
|
+
published_at: File.mtime(zip_path).iso8601,
|
|
60
|
+
created_at: File.mtime(zip_path).iso8601,
|
|
61
|
+
assets: [asset]
|
|
62
|
+
)
|
|
63
|
+
rescue JSON::ParserError
|
|
64
|
+
warn "Warning: Invalid metadata JSON in #{meta_path}, skipping"
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def prerelease?(data)
|
|
69
|
+
stage = data['stage'].to_s
|
|
70
|
+
%w[working-draft committee-draft draft-standard final-draft].include?(stage)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Metanorma
|
|
7
|
+
module Release
|
|
8
|
+
module Platform
|
|
9
|
+
module Local
|
|
10
|
+
class Publisher
|
|
11
|
+
include Metanorma::Release::Publisher
|
|
12
|
+
|
|
13
|
+
def initialize(output_dir:)
|
|
14
|
+
@output_dir = output_dir
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def publish(tag, artifact, metadata, channels:, force_replace: false)
|
|
18
|
+
FileUtils.mkdir_p(@output_dir)
|
|
19
|
+
|
|
20
|
+
zip_dest = File.join(@output_dir, artifact.asset_name)
|
|
21
|
+
meta_dest = File.join(@output_dir, meta_file_name(artifact.asset_name))
|
|
22
|
+
|
|
23
|
+
if force_replace
|
|
24
|
+
File.delete(zip_dest) if File.exist?(zip_dest)
|
|
25
|
+
File.delete(meta_dest) if File.exist?(meta_dest)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
FileUtils.cp(artifact.zip_path, zip_dest)
|
|
29
|
+
File.write(meta_dest, metadata.to_json)
|
|
30
|
+
|
|
31
|
+
PublishResult.new(tag: tag.to_s, url: "file://#{File.expand_path(zip_dest)}", created?: true)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def meta_file_name(asset_name)
|
|
37
|
+
base = asset_name.sub(/\.zip$/, '')
|
|
38
|
+
"#{base}.meta.json"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
module Local
|
|
7
|
+
autoload :Publisher, 'metanorma/release/platform/local/publisher'
|
|
8
|
+
autoload :DirectoryDiscoverer, 'metanorma/release/platform/local/directory_discoverer'
|
|
9
|
+
autoload :Fetcher, 'metanorma/release/platform/local/fetcher'
|
|
10
|
+
autoload :ConfigFetcher, 'metanorma/release/platform/local/config_fetcher'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
module Null
|
|
7
|
+
class Publisher
|
|
8
|
+
include Metanorma::Release::Publisher
|
|
9
|
+
|
|
10
|
+
def publish(tag, _artifact, _metadata, channels:, force_replace: false)
|
|
11
|
+
PublishResult.new(tag: tag.to_s, url: 'null://', created?: true)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
autoload :GitHub, 'metanorma/release/platform/github'
|
|
7
|
+
autoload :Local, 'metanorma/release/platform/local'
|
|
8
|
+
autoload :Null, 'metanorma/release/platform/null'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module PlatformFactory
|
|
6
|
+
PUBLISHER_REGISTRY = {
|
|
7
|
+
'null' => ->(_opts) { Platform::Null::Publisher.new },
|
|
8
|
+
'local' => ->(opts) { Platform::Local::Publisher.new(output_dir: opts[:output_dir]) }
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
AGGREGATION_REGISTRY = {
|
|
12
|
+
'local' => lambda { |opts, _token|
|
|
13
|
+
path = opts[:source].sub('local:', '')
|
|
14
|
+
{
|
|
15
|
+
discoverer: Platform::Local::DirectoryDiscoverer.new(base_path: path),
|
|
16
|
+
fetcher: Platform::Local::Fetcher.new(base_path: path)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
def self.build_publisher(platform, options)
|
|
22
|
+
factory = PUBLISHER_REGISTRY[platform]
|
|
23
|
+
raise ArgumentError, "Unknown platform: #{platform}. Available: #{PUBLISHER_REGISTRY.keys.join(', ')}" unless factory
|
|
24
|
+
|
|
25
|
+
factory.call(options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.build_aggregation_adapters(options)
|
|
29
|
+
source = options[:source]
|
|
30
|
+
if source.start_with?('local:')
|
|
31
|
+
adapters = AGGREGATION_REGISTRY['local'].call(options, options[:token])
|
|
32
|
+
adapters[:manifest_reader] = NullManifestReader.new
|
|
33
|
+
return adapters
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
require 'octokit'
|
|
37
|
+
client = build_github_client(options[:token])
|
|
38
|
+
|
|
39
|
+
discoverer = if options[:repos]
|
|
40
|
+
repos = options[:repos].map { |r| RepoRef.from_string(r) }
|
|
41
|
+
StaticDiscoverer.new(repos: repos)
|
|
42
|
+
else
|
|
43
|
+
Platform::GitHub::TopicDiscoverer.new(
|
|
44
|
+
client: client, organizations: options[:organizations], topic: options[:topic]
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
discoverer: discoverer,
|
|
50
|
+
fetcher: Platform::GitHub::ReleaseFetcher.new(client: client),
|
|
51
|
+
manifest_reader: Platform::GitHub::ManifestReader.new(client: client)
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.build_github_client(token)
|
|
56
|
+
require 'octokit'
|
|
57
|
+
token ? Octokit::Client.new(access_token: token) : Octokit::Client.new
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class StaticDiscoverer
|
|
61
|
+
include RepoDiscoverer
|
|
62
|
+
|
|
63
|
+
def initialize(repos:)
|
|
64
|
+
@repos = repos
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def discover
|
|
68
|
+
@repos
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class NullManifestReader
|
|
73
|
+
include Metanorma::Release::ManifestReader
|
|
74
|
+
def read(_repo) = nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rake'
|
|
4
|
+
require 'ostruct'
|
|
5
|
+
|
|
6
|
+
module Metanorma
|
|
7
|
+
module Release
|
|
8
|
+
class RakeTasks
|
|
9
|
+
include Rake::DSL
|
|
10
|
+
|
|
11
|
+
def self.install(&block)
|
|
12
|
+
new(&block).install
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(&block)
|
|
16
|
+
@config = OpenStruct.new(
|
|
17
|
+
output_dir: '_site',
|
|
18
|
+
manifest: 'metanorma.release.yml',
|
|
19
|
+
platform: 'github',
|
|
20
|
+
concurrency: 4,
|
|
21
|
+
dest: 'dist',
|
|
22
|
+
source: 'github',
|
|
23
|
+
organizations: [],
|
|
24
|
+
topic: 'metanorma-release'
|
|
25
|
+
)
|
|
26
|
+
block&.call(@config)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def install
|
|
30
|
+
install_package_task
|
|
31
|
+
install_publish_task
|
|
32
|
+
install_aggregate_task
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def install_package_task
|
|
38
|
+
desc 'Package compiled documents'
|
|
39
|
+
task :"mn:package" do
|
|
40
|
+
argv = ['--output-dir', @config.output_dir,
|
|
41
|
+
'--dest', @config.dest,
|
|
42
|
+
'--manifest', @config.manifest]
|
|
43
|
+
CLI.run_package(argv)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def install_publish_task
|
|
48
|
+
desc 'Package and publish documents'
|
|
49
|
+
task :"mn:publish" do
|
|
50
|
+
argv = ['--platform', @config.platform,
|
|
51
|
+
'--output-dir', @config.output_dir,
|
|
52
|
+
'--manifest', @config.manifest,
|
|
53
|
+
'--concurrency', @config.concurrency.to_s]
|
|
54
|
+
CLI.run_publish(argv)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def install_aggregate_task
|
|
59
|
+
desc 'Aggregate released documents'
|
|
60
|
+
task :"mn:aggregate" do
|
|
61
|
+
argv = ['--source', @config.source,
|
|
62
|
+
'--output-dir', @config.output_dir,
|
|
63
|
+
'--concurrency', @config.concurrency.to_s]
|
|
64
|
+
argv += ['--organizations', @config.organizations.join(',')] if @config.organizations.any?
|
|
65
|
+
argv += ['--topic', @config.topic] if @config.topic
|
|
66
|
+
CLI.run_aggregate(argv)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'relaton/bib'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
|
|
8
|
+
module Metanorma
|
|
9
|
+
module Release
|
|
10
|
+
class RelatonEnricher
|
|
11
|
+
EnrichResult = Struct.new(
|
|
12
|
+
:item_count, :output_dir, :documents,
|
|
13
|
+
keyword_init: true
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
@flavor_registry = {}
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def register_flavor(name, &loader)
|
|
20
|
+
@flavor_registry[name.to_s] = loader
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :flavor_registry
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
register_flavor('calconnect') do
|
|
27
|
+
require 'relaton/calconnect'
|
|
28
|
+
Relaton::Calconnect::Item
|
|
29
|
+
end
|
|
30
|
+
register_flavor('cc') do
|
|
31
|
+
require 'relaton/calconnect'
|
|
32
|
+
Relaton::Calconnect::Item
|
|
33
|
+
end
|
|
34
|
+
register_flavor('iso') do
|
|
35
|
+
require 'relaton/iso'
|
|
36
|
+
Relaton::Iso::Item
|
|
37
|
+
end
|
|
38
|
+
register_flavor('iec') do
|
|
39
|
+
require 'relaton/iec'
|
|
40
|
+
Relaton::Iec::BibliographicItem
|
|
41
|
+
end
|
|
42
|
+
register_flavor('ogc') do
|
|
43
|
+
require 'relaton/ogc'
|
|
44
|
+
Relaton::Ogc::BibliographicItem
|
|
45
|
+
end
|
|
46
|
+
register_flavor('ietf') do
|
|
47
|
+
require 'relaton/ietf'
|
|
48
|
+
Relaton::Ietf::BibliographicItem
|
|
49
|
+
end
|
|
50
|
+
register_flavor('bipm') do
|
|
51
|
+
require 'relaton/bipm'
|
|
52
|
+
Relaton::Bipm::BibliographicItem
|
|
53
|
+
end
|
|
54
|
+
register_flavor('itu') do
|
|
55
|
+
require 'relaton/itu'
|
|
56
|
+
Relaton::Itu::BibliographicItem
|
|
57
|
+
end
|
|
58
|
+
register_flavor('nist') do
|
|
59
|
+
require 'relaton/nist'
|
|
60
|
+
Relaton::Nist::BibliographicItem
|
|
61
|
+
end
|
|
62
|
+
register_flavor('un') do
|
|
63
|
+
require 'relaton/un'
|
|
64
|
+
Relaton::Un::BibliographicItem
|
|
65
|
+
end
|
|
66
|
+
register_flavor('bsi') do
|
|
67
|
+
require 'relaton/bsi'
|
|
68
|
+
Relaton::Bsi::BibliographicItem
|
|
69
|
+
end
|
|
70
|
+
register_flavor('ribose') do
|
|
71
|
+
require 'relaton/ribose'
|
|
72
|
+
Relaton::Ribose::Item
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def initialize(flavor: nil, registry_name: 'Document Registry')
|
|
76
|
+
@flavor = flavor
|
|
77
|
+
@registry_name = registry_name
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def enrich(document_index, output_dir, bib_dir: 'relaton')
|
|
81
|
+
return nil if document_index.empty?
|
|
82
|
+
|
|
83
|
+
flavor = resolve_flavor(document_index)
|
|
84
|
+
klass = resolve_class(flavor)
|
|
85
|
+
documents = enrich_documents(document_index, output_dir, klass)
|
|
86
|
+
return nil if documents.empty?
|
|
87
|
+
|
|
88
|
+
dest = File.join(output_dir, bib_dir)
|
|
89
|
+
write_index(documents, dest)
|
|
90
|
+
|
|
91
|
+
EnrichResult.new(item_count: documents.length, output_dir: dest,
|
|
92
|
+
documents: documents)
|
|
93
|
+
rescue LoadError
|
|
94
|
+
warn ' (relaton gem not available — bibliography skipped)'
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def resolve_flavor(document_index)
|
|
101
|
+
@flavor || document_index.documents.first&.flavor
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def resolve_class(flavor)
|
|
105
|
+
loader = self.class.flavor_registry[flavor.to_s]
|
|
106
|
+
return loader.call if loader
|
|
107
|
+
|
|
108
|
+
Relaton::Bib::Item
|
|
109
|
+
rescue LoadError
|
|
110
|
+
warn " (relaton-#{flavor} gem not available — using base Relaton::Bib::Item)"
|
|
111
|
+
Relaton::Bib::Item
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def enrich_documents(document_index, output_dir, klass)
|
|
115
|
+
document_index.documents.map do |doc|
|
|
116
|
+
rxl = doc.files.find { |f| f.extension == 'rxl' }
|
|
117
|
+
path = rxl && File.join(output_dir, rxl.path)
|
|
118
|
+
|
|
119
|
+
bib = (klass.from_xml(File.read(path)) if path && File.exist?(path))
|
|
120
|
+
|
|
121
|
+
enriched = doc.to_h
|
|
122
|
+
enriched['bibliographic'] = bib.to_h if bib
|
|
123
|
+
enriched
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
warn " Skip #{File.basename(path)}: #{e.message}"
|
|
126
|
+
doc.to_h
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def write_index(documents, dest)
|
|
131
|
+
FileUtils.mkdir_p(dest)
|
|
132
|
+
index = { 'root' => { 'title' => @registry_name, 'items' => documents } }
|
|
133
|
+
File.write(File.join(dest, 'index.json'), JSON.pretty_generate(index))
|
|
134
|
+
File.write(File.join(dest, 'index.yaml'), YAML.dump(index))
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Metanorma
|
|
6
|
+
module Release
|
|
7
|
+
class ReleaseMetadata
|
|
8
|
+
SCHEMA_VERSION = 1
|
|
9
|
+
|
|
10
|
+
def self.from_document(metadata, channels:)
|
|
11
|
+
data = {
|
|
12
|
+
'version' => SCHEMA_VERSION,
|
|
13
|
+
'id' => metadata.id.to_s,
|
|
14
|
+
'title' => metadata.title,
|
|
15
|
+
'edition' => metadata.version.edition,
|
|
16
|
+
'stage' => metadata.version.stage.to_s,
|
|
17
|
+
'doctype' => metadata.doctype.to_s,
|
|
18
|
+
'revdate' => metadata.revdate,
|
|
19
|
+
'formats' => metadata.formats,
|
|
20
|
+
'channels' => channels.map(&:to_s),
|
|
21
|
+
'flavor' => metadata.flavor,
|
|
22
|
+
'sourcePath' => metadata.source_path
|
|
23
|
+
}
|
|
24
|
+
new(data)
|
|
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
|
+
raise ArgumentError, 'Missing required field: title' unless data['title']
|
|
31
|
+
|
|
32
|
+
new(data)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.from_release_body(body)
|
|
36
|
+
return nil if body.nil? || body.empty?
|
|
37
|
+
|
|
38
|
+
match = body.match(/<!--\s*mn-release-metadata\s*\n(.*?)\n-->/m)
|
|
39
|
+
return nil unless match
|
|
40
|
+
|
|
41
|
+
json_str = match[1]
|
|
42
|
+
begin
|
|
43
|
+
from_json(json_str)
|
|
44
|
+
rescue JSON::ParserError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def initialize(data)
|
|
50
|
+
@data = data
|
|
51
|
+
freeze
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_json(*_args)
|
|
55
|
+
JSON.generate(@data)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_release_body
|
|
59
|
+
json_str = JSON.generate(@data)
|
|
60
|
+
"<!-- mn-release-metadata\n#{json_str}\n-->"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_h
|
|
64
|
+
@data.dup
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def id = @data['id']
|
|
68
|
+
def title = @data['title']
|
|
69
|
+
def edition = @data['edition']
|
|
70
|
+
def stage = @data['stage']
|
|
71
|
+
def doctype = @data['doctype']
|
|
72
|
+
def revdate = @data['revdate']
|
|
73
|
+
def formats = @data['formats'] || []
|
|
74
|
+
def channels = @data['channels'] || []
|
|
75
|
+
def flavor = @data['flavor']
|
|
76
|
+
def source_path = @data['sourcePath']
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|