metanorma-plugin-lutaml 0.7.62 → 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: 102085caacd4951ba78acd47b10eecec2fd50c6646270bd15c70444d4a54bb3e
4
- data.tar.gz: 42939df5ca71fe3e838277864054f215d5b3432310792d75384983eb4a35b7cb
3
+ metadata.gz: 9087a3bdc384a47318affb8b8fc243d2bd7817d3c726b3b5cf27f9c229068a4a
4
+ data.tar.gz: 7685b187bbf7d47e771b2276d42f62b3a4ed849abdec9ddd2154ff48f393864d
5
5
  SHA512:
6
- metadata.gz: 550a516ed78801a0a5359a114edac8258bc1c94e0f72384a2d0f338ab38c8476f729de6fb2d4876c21729569badbdfdce17b13e7a6bd0d4164a1a860b319c388
7
- data.tar.gz: a641b9efc125a5fce7432c43dd1e49fd673abef9f8991e73a3e182f5a44e7a987e40a741164e8be75f9198497d9063e29d44972a1a500f9cc1c90573d7fe9472
6
+ metadata.gz: cc8c7e181551079b69bb24d41c6bacea72bfa7e64cb6bce41ea3c6bc676f3883f7372a40d97d032e9dbafa0e2fab92c4e3783f280b579eeb15dd1aa675f68067
7
+ data.tar.gz: 90a1af9e236cf1a855b4042e459951da82d477e9180f0e3192819ef60c9e157e0236e9fc96d2343e30fe70e053154b88ff53f436f0b79b59262d54b86bef513a
@@ -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.62".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.62
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