metanorma-mko 1.1.0 → 1.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f884af7c676c4b65e7f157fc580605171ed9e35b857ea6ab6407cc158a6cfd5f
4
- data.tar.gz: c01a9a9cf36a2ba2c933007605e550e3354193516798f213ebc584522ff9e4f0
3
+ metadata.gz: 4e612f1678371d2e5f3ab58ef00e80918dcf419346ae7140244e5baa15b24c3e
4
+ data.tar.gz: 41519e76b5470d19f8823554386bbfac62c7e688ccaf5d263fe0f75a79f5a722
5
5
  SHA512:
6
- metadata.gz: 45c3f2a3c7226226a761c0684f8df548981c12ee2229f3f8ab8c37da42b1dc5c82613bda4c8ada865dceac397f550ba40d86fcc6330176c2f259bb1fd9ba1e81
7
- data.tar.gz: 3b9e6249e7c78fd40010ea691ff92a152d1923eb07267f8346871cfd25fa808d29b87fdc8c9995552a86f7ed246018e5e6c0d30502204af783feb22c307072de
6
+ metadata.gz: 84a2bb13c2ac8f215785e613cce14810c8873fc86d8de11c77e3dee9f7f419fe0943d8fb8597cf8af0d59f785d6e57a7fedeef514561e5b71ab782c072feef4f
7
+ data.tar.gz: c9c724166f850ed54b8ef85a744d7a22c7b51290dec4f39db72f4de349239f4958d2c11d9f2736030355d8678e37df9914a44f2293ee9e1884494375f62c0b20
@@ -68,7 +68,7 @@ module Metanorma
68
68
  def zip!
69
69
  require "zip"
70
70
  zip_path = "#{dir}.zip"
71
- Zip::File.open(zip_path, Zip::File::CREATE) do |zipfile|
71
+ Zip::File.open(zip_path, create: true) do |zipfile|
72
72
  Dir[File.join(dir, "**", "*")].each do |f|
73
73
  next if File.directory?(f)
74
74
 
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Metanorma
6
+ module Mko
7
+ # Collection export (MN 116 §collections): a metanorma
8
+ # collection.yml declares a document family (OIML R 60 parts,
9
+ # amendments, R 129/138/144). Every member document exports as its
10
+ # own bundle (the document bundle is the unit of truth); the
11
+ # collection adds one small bundle with the membership contract:
12
+ # collection.json and the cross-document part_of edges binding the
13
+ # members to the family. Member identity comes from each member's
14
+ # exported document.json — the collection yml's identifiers are
15
+ # reference labels only.
16
+ class Collection
17
+ Member = Struct.new(:fileref, :xml_path, :identifier,
18
+ :bundle_path, :result, :skipped,
19
+ keyword_init: true)
20
+
21
+ class Result
22
+ attr_reader :collection_bundle, :members
23
+
24
+ def initialize(collection_bundle:, members:)
25
+ @collection_bundle = collection_bundle
26
+ @members = members
27
+ end
28
+
29
+ def skipped
30
+ members.select(&:skipped)
31
+ end
32
+ end
33
+
34
+ class << self
35
+ def export(yml, to:, assets_from: nil, zip: false)
36
+ new(yml, to: to, assets_from: assets_from, zip: zip).export
37
+ end
38
+ end
39
+
40
+ def initialize(yml, to:, assets_from: nil, zip: false)
41
+ @yml = if yml.is_a?(Hash)
42
+ yml
43
+ elsif File.exist?(yml.to_s)
44
+ YAML.safe_load_file(yml)
45
+ else
46
+ YAML.safe_load(yml)
47
+ end
48
+ @base = File.exist?(yml.to_s) ? File.dirname(File.expand_path(yml.to_s)) : Dir.pwd
49
+ @to = to
50
+ @assets_from = assets_from
51
+ @zip = zip
52
+ end
53
+
54
+ def export
55
+ members = docrefs.filter_map do |ref|
56
+ member = resolve_member(ref)
57
+ next member if member.skipped
58
+
59
+ export = Mko.export(
60
+ File.read(member.xml_path), to: @to,
61
+ presentation_xml: presentation_for(member.xml_path),
62
+ assets_from: @assets_from || File.dirname(member.xml_path)
63
+ )
64
+ member.bundle_path = export.path
65
+ member.result = export.result
66
+ member
67
+ end
68
+ Result.new(collection_bundle: write_collection_bundle(members),
69
+ members: members)
70
+ end
71
+
72
+ private
73
+
74
+ def docrefs
75
+ manifest = @yml["manifest"]
76
+ manifest = manifest.first if manifest.is_a?(Array)
77
+ Array(manifest.is_a?(Hash) ? manifest["docref"] : nil)
78
+ rescue StandardError
79
+ []
80
+ end
81
+
82
+ # The yml points members at .adoc sources; the compiled semantic
83
+ # XML sits beside them. Direct .xml filerefs work too.
84
+ def resolve_member(ref)
85
+ fileref = ref["fileref"].to_s
86
+ xml = fileref.end_with?(".xml") ? fileref : fileref.sub(/\.adoc$/, ".xml")
87
+ path = File.expand_path(xml, @base)
88
+ if File.file?(path)
89
+ Member.new(fileref: fileref, xml_path: path,
90
+ identifier: ref["identifier"])
91
+ else
92
+ Member.new(fileref: fileref, xml_path: path,
93
+ identifier: ref["identifier"], skipped: true)
94
+ end
95
+ end
96
+
97
+ def presentation_for(xml_path)
98
+ pres = xml_path.sub(/\.xml$/, ".presentation.xml")
99
+ File.file?(pres) ? File.read(pres) : nil
100
+ end
101
+
102
+ def canonical
103
+ docid = @yml.dig("bibdata", "docid")
104
+ docid = docid.first if docid.is_a?(Array)
105
+ docid.is_a?(Hash) ? docid["id"].to_s : docid.to_s
106
+ end
107
+
108
+ def title
109
+ t = @yml.dig("bibdata", "title")
110
+ t = t.first if t.is_a?(Array)
111
+ t.is_a?(Hash) ? t["content"].to_s : t.to_s
112
+ end
113
+
114
+ def collection_short
115
+ Mko.slug(canonical.empty? ? "collection" : canonical)
116
+ end
117
+
118
+ # Member identity from the export's own Result — the same truth
119
+ # the bundle was written from, never a read-back of our output.
120
+ def member_data(member)
121
+ document = member.result.document
122
+ { "bundle" => "#{document.ids.short}.mko",
123
+ "docidentifier" => document.ids.canonical,
124
+ "identifier" => member.identifier,
125
+ "title" => document.titles.first&.text,
126
+ "edition" => document.edition }
127
+ rescue StandardError
128
+ nil
129
+ end
130
+
131
+ def collection_member(member)
132
+ data = member_data(member) or return nil
133
+
134
+ Schema::CollectionMember.new(
135
+ bundle: data["bundle"], docidentifier: data["docidentifier"],
136
+ identifier: data["identifier"], title: data["title"],
137
+ edition: data["edition"]
138
+ )
139
+ end
140
+
141
+ def write_collection_bundle(members)
142
+ collection = Schema::Collection.new(
143
+ canonical: canonical, short: collection_short, title: title,
144
+ edition: @yml.dig("bibdata", "edition"),
145
+ members: members.filter_map { |m| collection_member(m) }
146
+ )
147
+ # A member whose identity collides with the family (e.g. a
148
+ # stale amendment bibdata — mn-samples-oiml#66) would emit a
149
+ # self-edge; skip those, they say nothing.
150
+ edges = collection.members.reject do |m|
151
+ m.docidentifier.to_s.empty? || m.docidentifier == collection.canonical
152
+ end.map do |m|
153
+ { "from" => "doc:#{m.docidentifier}",
154
+ "to" => "doc:#{collection.canonical}",
155
+ "kind" => "part_of" }
156
+ end
157
+ bundle = Bundle.open(collection_short, @to)
158
+ bundle.flavor = nil
159
+ bundle.add_json("collection", "collection.json", collection)
160
+ bundle.add_lines("edges", "edges.jsonl", edges) do |edge|
161
+ JSON.generate(edge)
162
+ end
163
+ bundle.write_manifest
164
+ @zip ? bundle.zip! : bundle.dir
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ module Mko
5
+ # Registry-facing exporter: quacks like a renderer (generate_full_document)
6
+ # so the Core::Flavors format table and the compile pipeline can select
7
+ # mko like any output format.
8
+ class Exporter
9
+ def generate_full_document(document, **options)
10
+ output = options[:output] || options[:output_filename] || "."
11
+ to = File.directory?(output) ? output : File.dirname(output)
12
+ Mko.export(document, to: to,
13
+ presentation_xml: options[:presentation_xml])
14
+ end
15
+ end
16
+ end
17
+ end