metanorma-plugin-lutaml 0.7.61 → 0.7.63

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: e10152f9146a2100063d02e244208ed4acc105b8a105b6a15604a14e8522e9ab
4
- data.tar.gz: 8c453ee0ba82bc78d20c78e7bf31d34c048874301a12bbd13a63810791db3a8e
3
+ metadata.gz: 9087a3bdc384a47318affb8b8fc243d2bd7817d3c726b3b5cf27f9c229068a4a
4
+ data.tar.gz: 7685b187bbf7d47e771b2276d42f62b3a4ed849abdec9ddd2154ff48f393864d
5
5
  SHA512:
6
- metadata.gz: aa862500cb613b0cfab7ce3816f197a23cab52a44d778fc6cc1945b0e7e48c3a466cec55a1d7601e439256deb9b327e15e840368174896371dadbd457aeb3fb3
7
- data.tar.gz: 5cdcea37e490d21fcf555a952c836350570ab8e60499f2a1b98ed54a754af497832a2b749c052a2048a86c0be689f1c2be173e5f0ad74612d1363a4e6ada2cba
6
+ metadata.gz: cc8c7e181551079b69bb24d41c6bacea72bfa7e64cb6bce41ea3c6bc676f3883f7372a40d97d032e9dbafa0e2fab92c4e3783f280b579eeb15dd1aa675f68067
7
+ data.tar.gz: 90a1af9e236cf1a855b4042e459951da82d477e9180f0e3192819ef60c9e157e0236e9fc96d2343e30fe70e053154b88ff53f436f0b79b59262d54b86bef513a
@@ -17,43 +17,25 @@ module Metanorma
17
17
  )
18
18
  resolved = document.path_resolver
19
19
  .system_path(file_path, docfile_directory)
20
+ # The decorator expands emitted paths against this base: relative
21
+ # prefixes alone get re-anchored by asciidoctor's imagesdir
22
+ # handling (images/../../schemas collapses a level), so the
23
+ # decoration must produce paths that resolve from the docfile.
24
+ options["relative_path_base"] =
25
+ File.expand_path(docfile_directory)
20
26
  # The prefix lands inside emitted image macros, so it must not
21
27
  # carry the absolute build path: two builds of the same tree in
22
28
  # two directories produced different image/@src values
23
29
  # (metanorma/metanorma-plugin-lutaml#325). A path relative to the
24
30
  # docfile resolves to the same schema file at render time and is
25
- # byte-identical across build directories; the decorator joins it
26
- # as written rather than expanding it.
27
- #
28
- # The render resolves image targets against the imagesdir joined
29
- # onto the docfile directory, not the bare docfile: a target
30
- # relative to the bare docfile is re-anchored through the
31
- # imagesdir prefix and every decorated diagram misses by its
32
- # depth (metanorma/iso-10303 smol-all FATAL: emitted
33
- # `../schemas/…`, resolved as `images/../schemas/…`). Compute the
34
- # prefix against the directory the render resolves from.
31
+ # byte-identical across build directories.
35
32
  # The docfile directory may be relative ("."): relative_path_from
36
33
  # refuses mixed prefixes, so both sides expand first
37
34
  Pathname.new(File.expand_path(resolved))
38
- .relative_path_from(
39
- Pathname.new(image_resolve_base(document, docfile_directory)),
40
- )
35
+ .relative_path_from(Pathname.new(File.expand_path(docfile_directory)))
41
36
  .to_s
42
37
  end
43
38
 
44
- # The directory image targets resolve against: the docfile
45
- # directory with the imagesdir joined on. A web or absolute
46
- # imagesdir bypasses the join; the bare docfile directory is the
47
- # closest approximation.
48
- def self.image_resolve_base(document, docfile_directory)
49
- base = File.expand_path(docfile_directory)
50
- imagesdir = document.attributes["imagesdir"]
51
- return base if imagesdir.nil? || imagesdir.empty?
52
- return base if imagesdir.match?(%r{\A(?:[a-z]+:|/)})
53
-
54
- File.expand_path(imagesdir, base)
55
- end
56
-
57
39
  def self.for(model)
58
40
  case model
59
41
  when Expressir::Model::Cache
@@ -68,15 +68,19 @@ module Metanorma
68
68
  return line if path_prefix && line.include?(path_prefix)
69
69
 
70
70
  line.gsub(RELATIVE_PREFIX_MACRO_REGEXP) do |_match|
71
- # The prefix is already docfile-relative (see
72
- # ExpressRemarkAdapter.relative_path_prefix), so joining it
73
- # keeps the emitted path relative to the docfile too. Expanding
74
- # to an absolute path embedded the build directory in the
75
- # output, making semantic XML, presentation XML and HTML differ
76
- # between two builds of the same tree (#325). Resolution is the
77
- # renderer's job: a relative target joined with imagesdir still
78
- # resolves against the docfile at render time.
79
- "#{$1}#{$2}#{File.join(path_prefix, $3.strip)}#{$4}"
71
+ # Expand against the docfile directory (passed by the adapter
72
+ # as relative_path_base), NOT the process CWD: a CWD expansion
73
+ # produced absolute image paths under whatever directory the
74
+ # build was launched from (e.g. <repo>/../../schemas/...).
75
+ # The absolute result is deliberate: relative targets are
76
+ # re-anchored by asciidoctor's imagesdir handling
77
+ # (images/../../schemas collapses a level), which made every
78
+ # decorated diagram unresolvable at render time.
79
+ full_path = File.expand_path(
80
+ File.join(path_prefix, $3.strip),
81
+ options["relative_path_base"] || Dir.pwd,
82
+ )
83
+ "#{$1}#{$2}#{full_path}#{$4}"
80
84
  end
81
85
  end
82
86
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "expressir"
5
+
6
+ module Metanorma
7
+ module Plugin
8
+ module Lutaml
9
+ # Process-wide, content-keyed store of parsed Expressir repositories.
10
+ #
11
+ # A Metanorma collection compiles many documents against the same
12
+ # schema set, and every document compile used to re-parse the whole
13
+ # EXPRESS corpus (measured on ISO 10303-104: 125 schemas, 8.29 MB,
14
+ # ~12.5 s wall and ~1.1 GB of allocations per document). This cache
15
+ # parses each distinct source once per process: the key is derived
16
+ # from the schema sources' content digests, so an edited source
17
+ # produces a new key and re-parses instead of serving a stale
18
+ # repository.
19
+ #
20
+ # Deliberately NOT registered in CacheRegistry: the metanorma
21
+ # toolchain flushes CacheRegistry after every document compile to
22
+ # release the render-time XMI/XSD graphs, and a flush here would
23
+ # restore the per-document re-parse this cache exists to remove
24
+ # (metanorma/metanorma-plugin-lutaml#341). The repositories are
25
+ # collection-lifetime state; the LRU bound below caps how many
26
+ # corpora stay resident.
27
+ class ExpressRepositoryCache
28
+ # Key digest version: bump when the key composition changes so
29
+ # keys from different compositions can never collide.
30
+ KEY_VERSION = "metanorma-plugin-lutaml/express-repository-cache/v1"
31
+
32
+ MAX_SIZE = 4
33
+
34
+ class << self
35
+ # Content key for a set of EXPRESS source files: the key version,
36
+ # the Expressir version, then every file's path and content
37
+ # digest, in the order given (the order shapes the repository,
38
+ # and the parsed model embeds file paths, so both belong in the
39
+ # key). A changed source yields a new key.
40
+ def source_key(paths)
41
+ digest = Digest::SHA256.new
42
+ digest.update(KEY_VERSION)
43
+ digest.update(Expressir::Version::VERSION.to_s)
44
+ paths.each do |path|
45
+ digest.update(path)
46
+ digest.update(Digest::SHA256.file(path).digest)
47
+ end
48
+ digest.hexdigest
49
+ end
50
+
51
+ # Identity key for one prebuilt file (a compiled-set artifact or
52
+ # an Expressir cache file): expanded path plus mtime and size.
53
+ # The file is not read for the key — compiled sets hydrate lazily
54
+ # and re-reading a whole artifact per document would defeat the
55
+ # sharing; path + mtime + size detects a rebuilt file.
56
+ def file_key(path)
57
+ [
58
+ KEY_VERSION,
59
+ File.expand_path(path),
60
+ File.mtime(path).to_f,
61
+ File.size(path),
62
+ ]
63
+ end
64
+
65
+ def fetch_or_store(key, &block)
66
+ store.fetch_or_store(key, &block)
67
+ end
68
+
69
+ def fetch(key)
70
+ store.fetch(key)
71
+ end
72
+
73
+ def clear
74
+ store.clear
75
+ end
76
+
77
+ def size
78
+ store.size
79
+ end
80
+
81
+ private
82
+
83
+ def store
84
+ @store ||= CacheStore.new(max_size: MAX_SIZE)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -27,10 +27,16 @@ module Metanorma
27
27
  def load_lutaml_file(document, file_path, _options)
28
28
  full_path = Utils.relative_file_path(document, file_path)
29
29
 
30
- file = File.new(full_path, encoding: "UTF-8")
31
30
  if full_path.end_with?(".exp")
32
- ::Expressir::Express::Parser.from_file(file.path)
31
+ # Shared per process (ExpressRepositoryCache): a collection's
32
+ # documents re-parse the same schema file otherwise.
33
+ ExpressRepositoryCache.fetch_or_store(
34
+ ExpressRepositoryCache.source_key([full_path]),
35
+ ) do
36
+ ::Expressir::Express::Parser.from_file(full_path)
37
+ end
33
38
  else
39
+ file = File.new(full_path, encoding: "UTF-8")
34
40
  ::Lutaml::Lml::Parser.parse(file)
35
41
  end
36
42
  end
@@ -200,7 +200,11 @@ module Metanorma
200
200
  end
201
201
 
202
202
  def load_express_repo_from_cache(path)
203
- ::Expressir::Express::Cache.from_file(path)
203
+ ExpressRepositoryCache.fetch_or_store(
204
+ ExpressRepositoryCache.file_key(path),
205
+ ) do
206
+ ::Expressir::Express::Cache.from_file(path)
207
+ end
204
208
  end
205
209
 
206
210
  def save_express_repo_to_cache(path, repository, document)
@@ -225,14 +229,32 @@ module Metanorma
225
229
  # Compiled-set artifacts (EXSCS1) hydrate per file on first
226
230
  # touch: rendering one schema's page costs one hydration, not
227
231
  # the whole repository. The artifact already carries content
228
- # digests, so the expressir Cache layer does not apply.
232
+ # digests, so the expressir Cache layer does not apply. The
233
+ # repository handle itself is shared per process, so a second
234
+ # document compile reuses the first one's hydrated files.
229
235
  def load_express_from_artifact(path)
230
- Expressir::Express::LazyRepository.new(path.to_s)
236
+ ExpressRepositoryCache.fetch_or_store(
237
+ ExpressRepositoryCache.file_key(path),
238
+ ) do
239
+ Expressir::Express::LazyRepository.new(path.to_s)
240
+ end
231
241
  end
232
242
 
233
243
  def load_express_from_folder(folder)
234
- file_paths = Dir["#{folder}/*.exp"]
235
- ::Expressir::Express::Parser.from_files(file_paths)
244
+ parse_express_files(Dir["#{folder}/*.exp"])
245
+ end
246
+
247
+ # Parse a set of EXPRESS sources once per process: the parsed
248
+ # repository is shared through ExpressRepositoryCache, keyed by
249
+ # the sources' content digests, so the documents of a collection
250
+ # reuse the first compile's parse instead of each re-parsing the
251
+ # whole schema set (#341).
252
+ def parse_express_files(file_paths)
253
+ ExpressRepositoryCache.fetch_or_store(
254
+ ExpressRepositoryCache.source_key(file_paths),
255
+ ) do
256
+ ::Expressir::Express::Parser.from_files(file_paths)
257
+ end
236
258
  end
237
259
 
238
260
  # TODO: Refactor this using Suma::SchemaConfig
@@ -252,7 +274,7 @@ module Metanorma
252
274
  (schema_yaml_base_path + schema_path).cleanpath.to_s
253
275
  end
254
276
 
255
- ::Expressir::Express::Parser.from_files(file_paths)
277
+ parse_express_files(file_paths)
256
278
  end
257
279
 
258
280
  def parse_document_express_indexes(document, input_lines) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
@@ -1,7 +1,7 @@
1
1
  module Metanorma
2
2
  module Plugin
3
3
  module Lutaml
4
- VERSION = "0.7.61".freeze
4
+ VERSION = "0.7.63".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -24,6 +24,8 @@ module Metanorma
24
24
  "metanorma/plugin/lutaml/express_remarks_decorator"
25
25
  autoload :ExpressRemarkAdapter,
26
26
  "metanorma/plugin/lutaml/express_remark_adapter"
27
+ autoload :ExpressRepositoryCache,
28
+ "metanorma/plugin/lutaml/express_repository_cache"
27
29
  autoload :FileNotFoundError,
28
30
  "metanorma/plugin/lutaml/file_not_found_error"
29
31
  autoload :Json2TextPreprocessor,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metanorma-plugin-lutaml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.61
4
+ version: 0.7.63
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-11-03 00:00:00.000000000 Z
11
+ date: 2026-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -464,6 +464,7 @@ files:
464
464
  - lib/metanorma/plugin/lutaml/data2_text_preprocessor.rb
465
465
  - lib/metanorma/plugin/lutaml/express_remark_adapter.rb
466
466
  - lib/metanorma/plugin/lutaml/express_remarks_decorator.rb
467
+ - lib/metanorma/plugin/lutaml/express_repository_cache.rb
467
468
  - lib/metanorma/plugin/lutaml/file_not_found_error.rb
468
469
  - lib/metanorma/plugin/lutaml/gc_budget.rb
469
470
  - lib/metanorma/plugin/lutaml/json2_text_preprocessor.rb