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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README-zh.md +3 -1
- data/README.md +3 -1
- data/examples/offline.rb +1 -1
- data/lib/ctx_parser/profile.rb +40 -7
- data/lib/ctx_parser/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5bf7a928b6287113d2db138fbd46ad11c22932b15ef99473caeedee6517dd07
|
|
4
|
+
data.tar.gz: 5acef4c71f501bd931309afec028334831b86bafe67fd3e03411887bfdb401df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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)
|
data/lib/ctx_parser/profile.rb
CHANGED
|
@@ -17,8 +17,7 @@ module CtxParser
|
|
|
17
17
|
|
|
18
18
|
@model = model&.strip&.downcase&.freeze
|
|
19
19
|
@model_parsers = model_parser_overrides
|
|
20
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/ctx_parser/version.rb
CHANGED