ctx_parser 0.2.4 → 0.2.5

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: 19faa09b189e01bb0d76ad334a769bebe9c54fb2cc05bea40e335f81f02d2764
4
- data.tar.gz: 1484eafe0166f0141d7dfdf3709364ef7cea19dca5cc56c4e2356711504f2876
3
+ metadata.gz: f5bf7a928b6287113d2db138fbd46ad11c22932b15ef99473caeedee6517dd07
4
+ data.tar.gz: 5acef4c71f501bd931309afec028334831b86bafe67fd3e03411887bfdb401df
5
5
  SHA512:
6
- metadata.gz: 8bbc87c894cfe9c6db52ebcacbce0c0ceda4c1e979a670d2c63b7b2f80402bc91a8afa83987b3b3945c4c2e02481ffea80cf7b9bc9dfc6704489afbd2f196a17
7
- data.tar.gz: 4d387bb4fb40ed03cd08b719064c33fa88978cccc1274d46236bc8b4a965cf07aa18aceab92f5f37d9cbce571852f202ad5fa08de532d98ea2f8fc082230c891
6
+ metadata.gz: 0ff61300ef0fc301eb315bf5d0e667534e996884ae6e83b2c08c0a630bf5f55da4a2faf2c7f97b23cba0520a71807c6393bb6a3fb8350b406632fd1930b1732d
7
+ data.tar.gz: 180f90050ea5f2794d638eb9e64efc680b1ce0d07c786318eba5c22d2b48c6177c26156678777c4ae3e5e33fe5b3611c9cb7b8d2e1fd48f1e66be9c7c7020694
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  Changes to the public contract are recorded here. Versions follow Semantic Versioning.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.2.5 (2026-10-19)
8
+
9
+ - Keep offline example facts and configuration benchmark inputs independent of Ruby's global input and output separators.
10
+ - Reuse validated dynamic command directories and membership indexes when their complete contents are frozen, avoiding repeated full-directory validation during discovered-query preparation. Mutable and replaced directories retain current lookup semantics.
11
+
5
12
  ## 0.2.4 (2026-10-18)
6
13
 
7
14
  - Clear Bundler bootstrap variables when verifying packages in an isolated gem installation, including calls made through bundle exec.
data/README-zh.md CHANGED
@@ -46,7 +46,7 @@ class ExampleStatusParser < CtxParser::Parser
46
46
  def parse_output(text, complete:)
47
47
  records = []
48
48
  issues = []
49
- text.each_line.with_index(1) do |line, number|
49
+ text.each_line("\n").with_index(1) do |line, number|
50
50
  next if line.strip.empty?
51
51
 
52
52
  match = /\A([A-Za-z0-9\/]+) (up|down)\z/.match(line.strip)
@@ -88,6 +88,8 @@ result.records.each { |row| puts "#{row[:name]}: #{row[:state]}" }
88
88
 
89
89
  `MODEL_PARSERS` 的型号键与请求型号一样去除两侧空白并转为小写,归一化冲突和无效覆盖均被拒绝。`Registry` 在注册时校验静态命令与解析器目录;动态目录在使用时仍需校验,缺失实现抛出 `ArgumentError`。
90
90
 
91
+ Profile 为深冻结的普通命令目录复用查询列表与成员索引,包括发现阶段产生的目录。替换目录后重建索引;可变条目仍在每次查找时校验。`bundle exec ruby -Ilib benchmark/profile.rb` 测量 32、128、512 条合成命令的准备开销,并校验选择顺序与未知命令拒绝行为。
92
+
91
93
  `Records` 持有自己的筛选数组,行对象仍属于调用方;使用期间应保持行不变,需要快照时传入 `Immutable.copy(rows)`。`Immutable.copy` 在逐层校验后可复用深冻结普通容器;浅冻结、特殊 Hash 行为及循环引用仍按原规则复制或拒绝。整数转换仅在 `min` 允许负数或为 `nil` 时接受负号,默认仍只接受非负数。
92
94
 
93
95
  ## 开发与测试
data/README.md CHANGED
@@ -46,7 +46,7 @@ class ExampleStatusParser < CtxParser::Parser
46
46
  def parse_output(text, complete:)
47
47
  records = []
48
48
  issues = []
49
- text.each_line.with_index(1) do |line, number|
49
+ text.each_line("\n").with_index(1) do |line, number|
50
50
  next if line.strip.empty?
51
51
 
52
52
  match = /\A([A-Za-z0-9\/]+) (up|down)\z/.match(line.strip)
@@ -88,6 +88,8 @@ Address conversion uses `Values.ip_address`, `Values.ip_address?`, and `Values.m
88
88
 
89
89
  `MODEL_PARSERS` model names are stripped and lowercased just like the requested model. Normalization collisions and invalid overrides are rejected. `Registry` validates static command/parser declarations when registering a profile; dynamic directory lookup remains validated when used and reports missing implementations as `ArgumentError`.
90
90
 
91
+ Profiles reuse query lists and membership indexes for fully frozen plain command directories, including directories created by discovery. Replacing a directory rebuilds its index; mutable entries are validated on each lookup. `bundle exec ruby -Ilib benchmark/profile.rb` measures preparation of 32, 128 and 512 synthetic commands and checks selection order and rejection of unknown commands.
92
+
91
93
  `Records` owns selection arrays, while row objects remain caller-owned: treat those rows as stable during use, or pass `Immutable.copy(rows)` when a snapshot is needed. `Immutable.copy` can reuse fully frozen plain trees after validating all descendants; shallow-frozen containers, custom Hash behavior, and cycles receive the existing copy or rejection behavior. Integer conversion accepts a minus sign only when `min` permits negative values (or is `nil`); the default remains nonnegative.
92
94
 
93
95
  ## Development and tests
data/examples/offline.rb CHANGED
@@ -10,7 +10,7 @@ class ExampleStatusParser < CtxParser::Parser
10
10
  def parse_output(text, complete:)
11
11
  records = []
12
12
  issues = []
13
- text.each_line.with_index(1) do |line, number|
13
+ text.each_line("\n").with_index(1) do |line, number|
14
14
  next if line.strip.empty?
15
15
 
16
16
  match = /\A([A-Za-z0-9\/]+) (up|down)\z/.match(line.strip)
@@ -17,8 +17,7 @@ module CtxParser
17
17
 
18
18
  @model = model&.strip&.downcase&.freeze
19
19
  @model_parsers = model_parser_overrides
20
- @static_commands = self.class::COMMANDS
21
- @command_queries = compile_commands(@static_commands)
20
+ cache_commands(self.class::COMMANDS, strict: true)
22
21
  end
23
22
 
24
23
  # 注册时核对静态目录;动态实例仍在实际查询时重新校验。
@@ -73,7 +72,8 @@ module CtxParser
73
72
  def commands_for(name)
74
73
  directory = commands
75
74
  declaration = directory.fetch(name) { raise ArgumentError, "Unsupported parser dimension" }
76
- return @command_queries.fetch(name) if @command_queries && directory.equal?(@static_commands)
75
+ cached = cache_commands(directory)
76
+ return cached[:queries].fetch(name) if cached
77
77
 
78
78
  validated_queries(declaration)
79
79
  end
@@ -111,12 +111,32 @@ module CtxParser
111
111
 
112
112
  private
113
113
 
114
- # 仅为深冻结的静态目录建立快照;可变或被替换的目录仍走实时校验。
114
+ # 发现阶段可替换实例目录;只缓存当前深冻结目录,不继承旧目录的选择结果。
115
+ def cache_commands(directory, strict: false)
116
+ cached = @command_cache
117
+ return cached if cached && directory.equal?(cached[:directory])
118
+ return if frozen?
119
+
120
+ queries = compile_commands(directory)
121
+ return unless queries
122
+
123
+ membership = queries.transform_values { |entries| entries.to_h { |query| [ query, true ] }.freeze }.freeze
124
+ # 一次发布完整索引;并发查找只读取自己取得的快照,不混合两份目录。
125
+ @command_cache = { directory: directory, queries: queries, membership: membership }.freeze
126
+ rescue ArgumentError
127
+ # 动态目录仍按请求维度报错,未使用的无效声明不能阻断其他维度。
128
+ raise if strict
129
+
130
+ nil
131
+ end
132
+
133
+ # 可变容器或文本仍逐次校验;普通深冻结目录允许共享验证后的顺序与成员索引。
115
134
  def compile_commands(directory)
116
135
  return unless directory.instance_of?(Hash) && directory.frozen? && !directory.compare_by_identity? &&
117
136
  directory.keys.all? { |name| name.is_a?(Symbol) && !name.empty? } && directory.values.all? do |value|
118
137
  entries = value.is_a?(Array) ? value : [ value ]
119
- (!value.is_a?(Array) || value.frozen?) && entries.all? { |query| query.is_a?(String) && query.frozen? }
138
+ (!value.is_a?(Array) || value.instance_of?(Array) && value.frozen?) &&
139
+ entries.all? { |query| query.instance_of?(String) && query.frozen? }
120
140
  end
121
141
 
122
142
  directory.to_h { |name, declaration| [ name, validated_queries(declaration) ] }.freeze
@@ -134,13 +154,26 @@ module CtxParser
134
154
 
135
155
  # 仅当存在唯一公共查询时允许推断;显式命令必须覆盖全部请求视图。
136
156
  def select_command(dimensions, selected)
137
- candidates = dimensions.map { |name| commands_for(name) }.reduce { |common, queries| common & queries }
157
+ declarations = dimensions.map do |name|
158
+ queries = commands_for(name)
159
+ cached = @command_cache
160
+ membership = cached[:membership][name] if cached && queries.equal?(cached[:queries][name])
161
+ [ queries, membership ]
162
+ end
138
163
  if selected.nil?
164
+ candidates = declarations.map(&:first).reduce { |common, queries| common & queries }
139
165
  raise ArgumentError, "One CLI output must belong to one command" unless candidates.one?
140
166
 
141
167
  candidates.first
142
168
  else
143
- raise ArgumentError, "Selected command does not provide all parser dimensions" unless candidates.include?(selected)
169
+ supported = declarations.all? do |queries, membership|
170
+ if selected.instance_of?(String) && membership
171
+ membership.key?(selected)
172
+ else
173
+ queries.include?(selected)
174
+ end
175
+ end
176
+ raise ArgumentError, "Selected command does not provide all parser dimensions" unless supported
144
177
 
145
178
  selected
146
179
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module CtxParser
4
4
  # 公开包版本,供 RubyGems 元数据和运行时调用方共用。
5
- VERSION = "0.2.4"
5
+ VERSION = "0.2.5"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ctx_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gatework contributors