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,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
class DocumentMetadata
|
|
6
|
+
attr_reader :id, :title, :version, :doctype, :document_type,
|
|
7
|
+
:flavor, :revdate, :source_path, :output_dir,
|
|
8
|
+
:formats, :file_base_name
|
|
9
|
+
|
|
10
|
+
def initialize(id:, title:, version:, doctype:, document_type:,
|
|
11
|
+
flavor:, revdate:, source_path:, output_dir:,
|
|
12
|
+
formats:, file_base_name:)
|
|
13
|
+
@id = id
|
|
14
|
+
@title = title
|
|
15
|
+
@version = version
|
|
16
|
+
@doctype = doctype
|
|
17
|
+
@document_type = document_type
|
|
18
|
+
@flavor = flavor
|
|
19
|
+
@revdate = revdate
|
|
20
|
+
@source_path = source_path
|
|
21
|
+
@output_dir = output_dir
|
|
22
|
+
@formats = formats.freeze
|
|
23
|
+
@file_base_name = file_base_name
|
|
24
|
+
@lookup = {
|
|
25
|
+
'id' => @id, 'title' => @title, 'doctype' => @doctype,
|
|
26
|
+
'document_type' => @document_type, 'flavor' => @flavor,
|
|
27
|
+
'revdate' => @revdate, 'source_path' => @source_path,
|
|
28
|
+
'output_dir' => @output_dir, 'formats' => @formats,
|
|
29
|
+
'file_base_name' => @file_base_name
|
|
30
|
+
}.freeze
|
|
31
|
+
freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def [](key)
|
|
35
|
+
@lookup[key]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
class DocumentStage
|
|
6
|
+
PUBLISHED_NAMES = %w[published in-force approved standard].freeze
|
|
7
|
+
|
|
8
|
+
STAGE_ABBREVS = {
|
|
9
|
+
'working-draft' => 'wd',
|
|
10
|
+
'committee-draft' => 'cd',
|
|
11
|
+
'draft-standard' => 'ds',
|
|
12
|
+
'final-draft' => 'fd',
|
|
13
|
+
'proposal' => 'proposal',
|
|
14
|
+
'informational' => 'info',
|
|
15
|
+
'withdrawn' => 'withdrawn',
|
|
16
|
+
'cancelled' => 'cancelled'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
ISO_STAGE_MAP = {
|
|
20
|
+
20 => 'working-draft',
|
|
21
|
+
30 => 'committee-draft',
|
|
22
|
+
40 => 'draft-standard',
|
|
23
|
+
50 => 'final-draft',
|
|
24
|
+
60 => 'published',
|
|
25
|
+
95 => 'withdrawn'
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
def self.from_status(status_string)
|
|
29
|
+
raise ArgumentError, 'Stage cannot be empty' if status_string.nil? || status_string.strip.empty?
|
|
30
|
+
|
|
31
|
+
normalized = status_string.to_s.downcase.strip.gsub(/\s+/, '-')
|
|
32
|
+
new(normalized)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.from_iso_stage(stage, _substage = nil)
|
|
36
|
+
name = ISO_STAGE_MAP[stage.to_i] || ISO_STAGE_MAP.values.first
|
|
37
|
+
new(name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.published
|
|
41
|
+
new('published')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.working_draft
|
|
45
|
+
new('working-draft')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(name)
|
|
49
|
+
@name = name
|
|
50
|
+
freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_s
|
|
54
|
+
@name
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def published?
|
|
58
|
+
PUBLISHED_NAMES.include?(@name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def draft?
|
|
62
|
+
!published? && @name != 'withdrawn' && @name != 'cancelled'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def withdrawn?
|
|
66
|
+
@name == 'withdrawn'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def cancelled?
|
|
70
|
+
@name == 'cancelled'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def tag_suffix
|
|
74
|
+
STAGE_ABBREVS[@name].to_s
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def eql?(other)
|
|
78
|
+
other.is_a?(self.class) && @name == other.to_s
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def hash
|
|
82
|
+
@name.hash
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module DocumentType
|
|
6
|
+
STANDARD = 'standard'
|
|
7
|
+
IETF_DRAFT = 'ietf-draft'
|
|
8
|
+
IETF_RFC = 'ietf-rfc'
|
|
9
|
+
ISO = 'iso'
|
|
10
|
+
IEC = 'iec'
|
|
11
|
+
IEEE = 'ieee'
|
|
12
|
+
ITU = 'itu'
|
|
13
|
+
BIPM = 'bipm'
|
|
14
|
+
IHO = 'iho'
|
|
15
|
+
OGC = 'ogc'
|
|
16
|
+
OIML = 'oiml'
|
|
17
|
+
UN = 'un'
|
|
18
|
+
CSA = 'csa'
|
|
19
|
+
PDFA = 'pdfa'
|
|
20
|
+
MPFA = 'mpfa'
|
|
21
|
+
M3AAWG = 'm3aawg'
|
|
22
|
+
RIBOSE = 'ribose'
|
|
23
|
+
|
|
24
|
+
DETECTION_RULES = [
|
|
25
|
+
[/^RFC\s/i, IETF_RFC],
|
|
26
|
+
[/^draft-/i, IETF_DRAFT],
|
|
27
|
+
[/^ISO/i, ISO],
|
|
28
|
+
[/^IEC/i, IEC],
|
|
29
|
+
[/^IEEE/i, IEEE],
|
|
30
|
+
[/^ITU-/i, ITU],
|
|
31
|
+
[/^BIPM/i, BIPM],
|
|
32
|
+
[/^[A-Z]-\d/i, IHO],
|
|
33
|
+
[/^\d{2}-\d{2,3}/, OGC],
|
|
34
|
+
[/^OIML/i, OIML],
|
|
35
|
+
[/^GE\./i, UN],
|
|
36
|
+
[/^csa-/i, CSA],
|
|
37
|
+
[/^(AN|BPG|TN)\s/i, PDFA],
|
|
38
|
+
[%r{^SU/}i, MPFA],
|
|
39
|
+
[/^M3AAWG/i, M3AAWG],
|
|
40
|
+
[/^Ribose/i, RIBOSE]
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
def self.from_identifier(raw_id)
|
|
44
|
+
id = raw_id.to_s.strip
|
|
45
|
+
return STANDARD if id.empty?
|
|
46
|
+
|
|
47
|
+
DETECTION_RULES.each do |pattern, type|
|
|
48
|
+
return type if id.match?(pattern)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
STANDARD
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
class DocumentVersion
|
|
6
|
+
attr_reader :edition, :stage
|
|
7
|
+
|
|
8
|
+
def self.from(edition, stage)
|
|
9
|
+
ed = edition.to_s.strip
|
|
10
|
+
ed = '0' if ed.empty?
|
|
11
|
+
new(edition: ed, stage: stage)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.published(edition:)
|
|
15
|
+
new(edition: edition.to_s.strip, stage: DocumentStage.published)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(edition:, stage:)
|
|
19
|
+
@edition = edition
|
|
20
|
+
@stage = stage
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def tag_component
|
|
25
|
+
base = "ed#{edition}"
|
|
26
|
+
return base if stage.published?
|
|
27
|
+
|
|
28
|
+
suffix = stage.tag_suffix
|
|
29
|
+
suffix.empty? ? base : "#{base}-#{suffix}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def pre_release?
|
|
33
|
+
stage.draft?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def file_name(doc_id)
|
|
37
|
+
base = "#{doc_id}-#{tag_component}"
|
|
38
|
+
"#{base}.zip"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def eql?(other)
|
|
42
|
+
other.is_a?(self.class) && edition == other.edition && stage.eql?(other.stage)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def hash
|
|
46
|
+
[edition, stage].hash
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module FileRouting
|
|
6
|
+
def compute_path(file_name, metadata)
|
|
7
|
+
raise NotImplementedError, "#{self.class} must implement #compute_path"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class ByDocument
|
|
12
|
+
include FileRouting
|
|
13
|
+
|
|
14
|
+
def compute_path(file_name, metadata)
|
|
15
|
+
"#{metadata['id']}/#{file_name}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Flat
|
|
20
|
+
include FileRouting
|
|
21
|
+
|
|
22
|
+
def compute_path(file_name, _metadata)
|
|
23
|
+
file_name
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class ByFormat
|
|
28
|
+
include FileRouting
|
|
29
|
+
|
|
30
|
+
def compute_path(file_name, _metadata)
|
|
31
|
+
ext = File.extname(file_name).delete_prefix('.')
|
|
32
|
+
"#{ext}/#{file_name}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module FileRoutingFactory
|
|
37
|
+
ROUTING_MAP = {
|
|
38
|
+
'by-document' => ByDocument,
|
|
39
|
+
'flat' => Flat,
|
|
40
|
+
'by-format' => ByFormat
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
def self.from_name(name)
|
|
44
|
+
klass = ROUTING_MAP[name]
|
|
45
|
+
raise ArgumentError, "Unknown routing mode: #{name}. Available: #{ROUTING_MAP.keys.join(', ')}" unless klass
|
|
46
|
+
|
|
47
|
+
klass.new
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Extractor
|
|
6
|
+
def discover(output_dir)
|
|
7
|
+
raise NotImplementedError, "#{self.class} must implement #discover"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def extract(rxl_path)
|
|
11
|
+
raise NotImplementedError, "#{self.class} must implement #extract"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module Filter
|
|
16
|
+
def apply(documents)
|
|
17
|
+
raise NotImplementedError, "#{self.class} must implement #apply"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module ChangeDetector
|
|
22
|
+
def detect(metadata, tag, force: false)
|
|
23
|
+
raise NotImplementedError, "#{self.class} must implement #detect"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module Packager
|
|
28
|
+
def package(metadata, canonical_base:)
|
|
29
|
+
raise NotImplementedError, "#{self.class} must implement #package"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module Publisher
|
|
34
|
+
def publish(tag, artifact, metadata, channels:, force_replace: false)
|
|
35
|
+
raise NotImplementedError, "#{self.class} must implement #publish"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
ChangeResult = Struct.new(:changed?, :current_hash, :previous_hash, keyword_init: true)
|
|
40
|
+
Artifact = Struct.new(:zip_path, :asset_name, :size, keyword_init: true)
|
|
41
|
+
PublishResult = Struct.new(:tag, :url, :created?, keyword_init: true)
|
|
42
|
+
|
|
43
|
+
ReleasedArtifact = Struct.new(:id, :tag, :url, :channels, keyword_init: true)
|
|
44
|
+
|
|
45
|
+
ReleaseResult = Struct.new(:released, :skipped, :failed, :released_artifacts, keyword_init: true)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module NamingStrategy
|
|
6
|
+
def compute_tag(id, version)
|
|
7
|
+
raise NotImplementedError, "#{self.class} must implement #compute_tag"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def compute_asset_name(id, version)
|
|
11
|
+
raise NotImplementedError, "#{self.class} must implement #compute_asset_name"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def compute_canonical_base(id, version)
|
|
15
|
+
raise NotImplementedError, "#{self.class} must implement #compute_canonical_base"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class EditionNaming
|
|
20
|
+
include NamingStrategy
|
|
21
|
+
|
|
22
|
+
def compute_tag(id, version)
|
|
23
|
+
tag = "#{id}/#{version.tag_component}"
|
|
24
|
+
ReleaseTag.create(tag, pre_release: version.pre_release?)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def compute_asset_name(id, version)
|
|
28
|
+
"#{id}-#{version.tag_component}.zip"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def compute_canonical_base(id, version)
|
|
32
|
+
"#{id}-#{version.tag_component}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class VersionNaming
|
|
37
|
+
include NamingStrategy
|
|
38
|
+
|
|
39
|
+
def compute_tag(id, version)
|
|
40
|
+
tag = "#{id}/v#{version.edition}"
|
|
41
|
+
ReleaseTag.create(tag, pre_release: version.pre_release?)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def compute_asset_name(id, version)
|
|
45
|
+
"#{id}-v#{version.edition}.zip"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def compute_canonical_base(id, version)
|
|
49
|
+
"#{id}-v#{version.edition}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class InternetDraftNaming
|
|
54
|
+
include NamingStrategy
|
|
55
|
+
|
|
56
|
+
DRAFT_PATTERN = /\Adraft-ietf-([a-z0-9-]+?)-(\d+)\z/i
|
|
57
|
+
|
|
58
|
+
def compute_tag(id, version)
|
|
59
|
+
match = id.match(DRAFT_PATTERN)
|
|
60
|
+
return fallback_tag(id, version) unless match
|
|
61
|
+
|
|
62
|
+
name = match[1]
|
|
63
|
+
num = match[2]
|
|
64
|
+
ReleaseTag.create("id-#{name}/#{num}", pre_release: true)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def compute_asset_name(id, _version)
|
|
68
|
+
"#{id}.zip"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def compute_canonical_base(id, _version)
|
|
72
|
+
id.to_s
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def fallback_tag(id, _version)
|
|
78
|
+
tag = "#{id}/draft"
|
|
79
|
+
ReleaseTag.create(tag, pre_release: true)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class RfcNaming
|
|
84
|
+
include NamingStrategy
|
|
85
|
+
|
|
86
|
+
def compute_tag(id, version)
|
|
87
|
+
tag = "#{id}/ed#{version.edition}"
|
|
88
|
+
ReleaseTag.create(tag, pre_release: version.pre_release?)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def compute_asset_name(id, _version)
|
|
92
|
+
"#{id}.zip"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def compute_canonical_base(id, version)
|
|
96
|
+
"#{id}-ed#{version.edition}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class DraftSuffixNaming
|
|
101
|
+
include NamingStrategy
|
|
102
|
+
|
|
103
|
+
DRAFT_SUFFIX = /-d(\d+)\z/
|
|
104
|
+
|
|
105
|
+
def compute_tag(id, version)
|
|
106
|
+
match = id.to_s.match(DRAFT_SUFFIX)
|
|
107
|
+
return @fallback.compute_tag(id, version) unless match
|
|
108
|
+
|
|
109
|
+
base = id.to_s.sub(DRAFT_SUFFIX, '')
|
|
110
|
+
num = match[1]
|
|
111
|
+
ReleaseTag.create("#{base}/#{num}", pre_release: true)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def compute_asset_name(id, version)
|
|
115
|
+
match = id.to_s.match(DRAFT_SUFFIX)
|
|
116
|
+
return @fallback.compute_asset_name(id, version) unless match
|
|
117
|
+
|
|
118
|
+
"#{id}.zip"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def compute_canonical_base(id, version)
|
|
122
|
+
match = id.to_s.match(DRAFT_SUFFIX)
|
|
123
|
+
return @fallback.compute_canonical_base(id, version) unless match
|
|
124
|
+
|
|
125
|
+
id.to_s
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def initialize
|
|
129
|
+
@fallback = EditionNaming.new
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class NamingRegistry
|
|
134
|
+
def initialize(default: EditionNaming.new)
|
|
135
|
+
@default = default
|
|
136
|
+
@strategies = {}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def register(document_type, strategy)
|
|
140
|
+
@strategies[document_type] = strategy
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def resolve(document_type)
|
|
144
|
+
@strategies.fetch(document_type, @default)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.default_registry
|
|
148
|
+
registry = new
|
|
149
|
+
registry.register(DocumentType::IETF_DRAFT, InternetDraftNaming.new)
|
|
150
|
+
registry.register(DocumentType::IETF_RFC, RfcNaming.new)
|
|
151
|
+
registry.register(DocumentType::IEEE, DraftSuffixNaming.new)
|
|
152
|
+
registry.register(DocumentType::IHO, VersionNaming.new)
|
|
153
|
+
registry.register(DocumentType::OGC, VersionNaming.new)
|
|
154
|
+
registry
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Metanorma
|
|
6
|
+
module Release
|
|
7
|
+
module Platform
|
|
8
|
+
module GitHub
|
|
9
|
+
class ConfigFetcher
|
|
10
|
+
include Metanorma::Release::ConfigFetcher
|
|
11
|
+
|
|
12
|
+
def initialize(client:)
|
|
13
|
+
@client = client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fetch(source)
|
|
17
|
+
repo, path = parse_source(source)
|
|
18
|
+
content = @client.contents(repo, path: path)
|
|
19
|
+
return nil unless content
|
|
20
|
+
|
|
21
|
+
ChannelConfig.from_yaml(content['content'].unpack1('m0'))
|
|
22
|
+
rescue StandardError
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def parse_source(source)
|
|
29
|
+
if source.include?('#')
|
|
30
|
+
parts = source.split('#', 2)
|
|
31
|
+
[parts[0], parts[1]]
|
|
32
|
+
else
|
|
33
|
+
[source, 'channels.yml']
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Metanorma
|
|
6
|
+
module Release
|
|
7
|
+
module Platform
|
|
8
|
+
module GitHub
|
|
9
|
+
class ManifestReader
|
|
10
|
+
include Metanorma::Release::ManifestReader
|
|
11
|
+
|
|
12
|
+
def initialize(client:)
|
|
13
|
+
@client = client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read(repo)
|
|
17
|
+
content = @client.contents(repo.to_s, path: 'metanorma.release.yml')
|
|
18
|
+
return nil unless content
|
|
19
|
+
|
|
20
|
+
yaml = content['content'].unpack1('m0')
|
|
21
|
+
parsed = YAML.safe_load(yaml, permitted_classes: [Symbol])
|
|
22
|
+
return nil unless parsed.is_a?(Hash)
|
|
23
|
+
|
|
24
|
+
(parsed['channels'] || []).map(&:to_s)
|
|
25
|
+
rescue StandardError
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
module GitHub
|
|
7
|
+
class Publisher
|
|
8
|
+
include Metanorma::Release::Publisher
|
|
9
|
+
|
|
10
|
+
def initialize(client:, repo:)
|
|
11
|
+
@client = client
|
|
12
|
+
@repo = repo
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def publish(tag, artifact, metadata, channels:, force_replace: false)
|
|
16
|
+
tag_name = tag.to_s
|
|
17
|
+
|
|
18
|
+
if force_replace
|
|
19
|
+
delete_existing_release(tag_name)
|
|
20
|
+
return create_release(tag_name, metadata, artifact)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
existing = find_release(tag_name)
|
|
24
|
+
|
|
25
|
+
if existing
|
|
26
|
+
update_release(existing, metadata)
|
|
27
|
+
else
|
|
28
|
+
create_release(tag_name, metadata, artifact)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def find_release(tag_name)
|
|
35
|
+
@client.releases(@repo).find { |r| r['tag_name'] == tag_name }
|
|
36
|
+
rescue StandardError
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_release(tag_name, metadata, artifact)
|
|
41
|
+
release = @client.create_release(
|
|
42
|
+
@repo, tag_name,
|
|
43
|
+
name: tag_name,
|
|
44
|
+
body: metadata.to_release_body,
|
|
45
|
+
prerelease: tag_name.match?(/-(wd|cd|ds|fd|proposal)$/)
|
|
46
|
+
)
|
|
47
|
+
upload_asset(release['id'], artifact)
|
|
48
|
+
PublishResult.new(tag: tag_name, url: release['html_url'], created?: true)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update_release(release, metadata)
|
|
52
|
+
@client.update_release(release['url'], body: metadata.to_release_body)
|
|
53
|
+
PublishResult.new(tag: release['tag_name'], url: release['html_url'], created?: false)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def upload_asset(release_id, artifact)
|
|
57
|
+
@client.upload_asset(release_id, artifact.zip_path, content_type: 'application/zip')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete_existing_release(tag_name)
|
|
61
|
+
release = find_release(tag_name)
|
|
62
|
+
@client.delete_release(release['url']) if release
|
|
63
|
+
begin
|
|
64
|
+
@client.delete_ref(@repo, "tags/#{tag_name}")
|
|
65
|
+
rescue StandardError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Release
|
|
5
|
+
module Platform
|
|
6
|
+
module GitHub
|
|
7
|
+
GitHubRelease = Struct.new(:tag_name, :body, :prerelease, :draft,
|
|
8
|
+
:html_url, :published_at, :created_at,
|
|
9
|
+
:assets, keyword_init: true)
|
|
10
|
+
GitHubAsset = Struct.new(:name, :browser_download_url, :size, :data,
|
|
11
|
+
keyword_init: true)
|
|
12
|
+
|
|
13
|
+
class ReleaseFetcher
|
|
14
|
+
include Metanorma::Release::ReleaseFetcher
|
|
15
|
+
|
|
16
|
+
def initialize(client:)
|
|
17
|
+
@client = client
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def fetch(repo, etag: nil)
|
|
21
|
+
releases = @client.releases(repo.to_s)
|
|
22
|
+
parsed = releases.map { |r| parse_release(r) }
|
|
23
|
+
FetchResult.new(releases: parsed, etag: "etag-#{repo}", unchanged?: false)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def parse_release(r)
|
|
29
|
+
assets = (r[:assets] || []).map do |a|
|
|
30
|
+
GitHubAsset.new(
|
|
31
|
+
name: a[:name],
|
|
32
|
+
browser_download_url: a[:browser_download_url],
|
|
33
|
+
size: a[:size],
|
|
34
|
+
data: nil
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
GitHubRelease.new(
|
|
38
|
+
tag_name: r[:tag_name],
|
|
39
|
+
body: r[:body],
|
|
40
|
+
prerelease: r[:prerelease],
|
|
41
|
+
draft: r[:draft],
|
|
42
|
+
html_url: r[:html_url],
|
|
43
|
+
published_at: r[:published_at],
|
|
44
|
+
created_at: r[:created_at],
|
|
45
|
+
assets: assets
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|