textfsm 0.5.0 → 0.5.1
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.md +7 -4
- data/lib/textfsm/data.rb +4 -0
- data/lib/textfsm/field.rb +2 -1
- data/lib/textfsm/index_table.rb +9 -7
- data/lib/textfsm/parser.rb +4 -3
- data/lib/textfsm/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: e9aef491cc7a64406840450bdb59d12d767c905796686061866a1ec213a4163a
|
|
4
|
+
data.tar.gz: 396eb22fdc6af17a7f717703729ba336a8105da09d45c24fef7bee25ba9e6d1f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1dfc58294466d8a5bca5aafa38d05d72803a3195eeb9983202b9bb92e4794fef5bfe8b89f5eded6219580020d70c899c29f60e4fb6140c5514c13f21debeac45
|
|
7
|
+
data.tar.gz: 9d30f8b89367e81303280c86cc60efb3e26cbc041cd3748909e2e8ad8ff02c30484d08b08692dc03a25c6a416ec054bd3e648742f79aefd0ac85a82a223685df
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.5.1(2026-10-11)
|
|
4
|
+
|
|
5
|
+
- 拒绝缺少 `Value` 声明的模板,防止库与 CLI 验证静默接受无字段模板;多模板加载失败时保留之前的结果。
|
|
6
|
+
- 自定义选项产生的 `false` 按有效值处理,不再被 `Required` 丢弃或被 `Fillup` 覆盖,保持回填、继承与清除的生命周期契约。
|
|
7
|
+
- 索引属性转换取得逐属性冻结快照,允许不同属性的 `to_s` 复用缓冲区而不污染本次查询。
|
|
8
|
+
- 索引预计算列位置并用数组保存编译模式,减少加载时的临时对象与逐行扫描的列名查找;增加可复现基准。
|
|
9
|
+
|
|
3
10
|
## 0.5.0(2026-10-11)
|
|
4
11
|
|
|
5
12
|
- 修复索引转换回调复用缓冲区时覆盖此前单元格的问题,逐单元格取得独立冻结快照。
|
data/README.md
CHANGED
|
@@ -31,7 +31,7 @@ gem "textfsm", path: "/path/to/textfsm"
|
|
|
31
31
|
```sh
|
|
32
32
|
bundle install
|
|
33
33
|
bundle exec rake build
|
|
34
|
-
gem install ./pkg/textfsm-0.5.
|
|
34
|
+
gem install ./pkg/textfsm-0.5.1.gem
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
源码仓库:[gatework/textfsm](https://github.com/gatework/textfsm)。
|
|
@@ -73,7 +73,7 @@ p fsm.parse_hashes(text)
|
|
|
73
73
|
|
|
74
74
|
## 模板语法
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
模板至少声明一个 `Value`,连续的声明与状态之间用空行分隔。初始状态必须叫 `Start`。状态之间用空行分隔;规则以一个空格、两个空格或一个制表符缩进,随后必须是 `^`。以 `#` 开始的注释行会被忽略。
|
|
77
77
|
|
|
78
78
|
```text
|
|
79
79
|
Value [选项,选项] NAME (正则表达式)
|
|
@@ -185,7 +185,7 @@ cisco_version_template, Cisco, sh[[ow]] ve[[rsion]]
|
|
|
185
185
|
|
|
186
186
|
按文件顺序选择第一条匹配记录;匹配从属性字符串开头开始,空单元格是通配条件,index 未定义的属性会被忽略。`Command` 中 `sh[[ow]]` 支持 `sh`、`sho`、`show`。index 遵循上游简单逗号分隔格式,不处理带引号的 CSV 字段。
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
每次查询按需将参与匹配的属性值转换为字符串,并在本次扫描中复用冻结快照;不同属性的 `to_s` 可以复用可变缓冲区,已转换的值不会被后续转换覆盖。未知属性和仅遇到通配条件的值不会触发转换。Symbol 与同名 String 属性分别参与匹配,传入的属性集合不会被修改。
|
|
189
189
|
|
|
190
190
|
加载索引时,相同模式在当前索引内只编译一次;`IndexTable` 的 `transform:` 和 `compile:` 回调仍按文件顺序逐单元格执行,回调返回值不会被原地修改。
|
|
191
191
|
|
|
@@ -205,6 +205,8 @@ table.parse(output, templates: "first.textfsm:second.textfsm")
|
|
|
205
205
|
|
|
206
206
|
记录格式以字符串内容、数值和集合/Struct 成员为准;不要在字符串或 Struct 的额外实例变量中附加可变业务状态,这些附加状态不属于深拷贝契约。
|
|
207
207
|
|
|
208
|
+
自定义选项产生的 `false` 是有效记录值,能满足 `Required`,也能参与 `Fillup`/`Filldown`;后续 `Fillup` 遇到已记录的 `false` 时停止。只有 `nil` 或响应 `empty?` 且返回真值的对象被视为空值,例如空字符串、Array 和 Hash。
|
|
209
|
+
|
|
208
210
|
`CliTable#keys` 返回模板声明的键,使用 `table.keys = ["ID"]` 显式设置,或 `table.keys += ["NAME"]` 增补。`key_for(row)` 返回键值数组,没有键时返回 `[]`。排序遵循 Ruby 自身的比较规则:
|
|
209
211
|
|
|
210
212
|
```ruby
|
|
@@ -260,6 +262,7 @@ parser = TextFSM::Parser.new(template, options: { "Uppercase" => Uppercase })
|
|
|
260
262
|
- 内部动作使用 Symbol,省略目标状态使用 `nil`;模板文本继续使用标准的 `Next`、`Record`、`Clearall` 等语法。
|
|
261
263
|
- 正则编译使用 `StringScanner`,编译器的游标和分组栈不保留在最终 `Pattern` 对象中。模式始终从输入开头匹配。
|
|
262
264
|
- 多对一键合并按右行缓存新增列的冻结快照,缓存仅保留在本次合并中。可用 `ruby script/benchmark_merge.rb` 测量这一场景的耗时和对象分配;`ROWS`、`ITEMS` 分别控制左行数和嵌套成员数。此基准不代表所有模板或合并场景。
|
|
265
|
+
- 索引将列名预计算为位置,内部编译模式按表头顺序保存在数组中,查询保持属性顺序与短路规则。可用 `ruby script/benchmark_index.rb` 测量较晚命中的加载、扫描耗时和对象分配;`ROWS`、`QUERIES` 控制索引行数与查询次数,此基准不代表所有查询分布。
|
|
263
266
|
- `bundle exec rake` 执行 Minitest 和 RuboCop;`rake test`、`rake lint` 可分别运行。代码规则见 `.rubocop.yml`。
|
|
264
267
|
- `bundle exec rake verify` 进一步构建 gem,从 `bundle install` 准备的本地缓存离线安装运行时依赖和候选包;安装环境仅使用临时目录的 gem 与 Ruby 自带的默认 gem。逐文件核对源码内容,并验证独立加载与 CLI 输出、标准输入和退出码,避免开发依赖掩盖缺失的运行时依赖。生成的包位于 `pkg/`,不会发布到 RubyGems。
|
|
265
268
|
|
|
@@ -269,7 +272,7 @@ parser = TextFSM::Parser.new(template, options: { "Uppercase" => Uppercase })
|
|
|
269
272
|
工作流验证并上传同次 CI 的 gem,再核对 RubyGems 下载和 GitHub Release 的 SHA256。
|
|
270
273
|
流程、账号绑定及失败恢复见 [发布说明](docs/RELEASING.md)。
|
|
271
274
|
|
|
272
|
-
本地预检可执行 `bundle exec ruby script/release.rb --dry-run --artifact pkg/textfsm-0.5.
|
|
275
|
+
本地预检可执行 `bundle exec ruby script/release.rb --dry-run --artifact pkg/textfsm-0.5.1.gem`;
|
|
273
276
|
已有产物路径不会重新构建。更新版本及 CHANGELOG、提交并确认远端 CI 后推送对应版本标签。
|
|
274
277
|
|
|
275
278
|
## 兼容范围与验证
|
data/lib/textfsm/data.rb
CHANGED
|
@@ -4,6 +4,10 @@ module TextFSM
|
|
|
4
4
|
# 记录可包含嵌套列表与命名捕获;所有权边界复制这些值,
|
|
5
5
|
# 需要只读结果时返回深度冻结的快照。
|
|
6
6
|
module Data
|
|
7
|
+
def self.empty?(value)
|
|
8
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
def self.copy(value, immutable: false)
|
|
8
12
|
# 只有整个值树都不可变时才共享,冻结外层容器并不代表成员已冻结。
|
|
9
13
|
return value if immutable && immutable?(value)
|
data/lib/textfsm/field.rb
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
require_relative "errors"
|
|
6
6
|
require_relative "options"
|
|
7
7
|
require_relative "pattern"
|
|
8
|
+
require_relative "data"
|
|
8
9
|
|
|
9
10
|
module TextFSM
|
|
10
11
|
class Field
|
|
@@ -31,7 +32,7 @@ module TextFSM
|
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def empty?
|
|
34
|
-
|
|
35
|
+
Data.empty?(@value)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def assign(value)
|
data/lib/textfsm/index_table.rb
CHANGED
|
@@ -11,9 +11,10 @@ module TextFSM
|
|
|
11
11
|
include Enumerable
|
|
12
12
|
|
|
13
13
|
# 转换结果仅属于当前查询;通配条件或未使用的属性不会触发转换。
|
|
14
|
-
Attribute = Struct.new(:
|
|
14
|
+
Attribute = Struct.new(:column, :value) do
|
|
15
15
|
def text
|
|
16
|
-
|
|
16
|
+
# 不同属性的 to_s 可复用同一缓冲区,缓存必须持有本次转换的快照。
|
|
17
|
+
@text ||= Data.copy(value.to_s, immutable: true)
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
private_constant :Attribute
|
|
@@ -22,6 +23,7 @@ module TextFSM
|
|
|
22
23
|
|
|
23
24
|
def initialize(path, transform: nil, compile: nil)
|
|
24
25
|
@header, @rows = Data.copy(read_table(path, transform), immutable: true)
|
|
26
|
+
@columns = @header.each_with_index.to_h.freeze
|
|
25
27
|
@patterns = compile_patterns(compile)
|
|
26
28
|
freeze
|
|
27
29
|
rescue TemplateError => e
|
|
@@ -50,12 +52,12 @@ module TextFSM
|
|
|
50
52
|
# 空单元格为通配条件;索引中不存在的属性忽略。
|
|
51
53
|
def match(attributes)
|
|
52
54
|
attributes = attributes.filter_map do |key, value|
|
|
53
|
-
|
|
54
|
-
Attribute.new(
|
|
55
|
+
column = @columns[key.to_s]
|
|
56
|
+
Attribute.new(column, value) if column
|
|
55
57
|
end
|
|
56
58
|
index = @patterns.index do |patterns|
|
|
57
59
|
attributes.all? do |attribute|
|
|
58
|
-
pattern = patterns[attribute.
|
|
60
|
+
pattern = patterns[attribute.column]
|
|
59
61
|
pattern.nil? || pattern.match?(attribute.text)
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -68,13 +70,13 @@ module TextFSM
|
|
|
68
70
|
# 仅在本次构建中共享不可变模式;回调仍逐单元格执行,缓存不跨索引保留。
|
|
69
71
|
compiled = {}
|
|
70
72
|
@rows.map do |row|
|
|
71
|
-
row.
|
|
73
|
+
row.map do |key, value|
|
|
72
74
|
value = compile.call(key, value) if compile
|
|
73
75
|
unless value.nil? || value.is_a?(String)
|
|
74
76
|
raise TemplateError, "Index pattern for column #{key.inspect} must be a String or nil, got #{value.class}"
|
|
75
77
|
end
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
value.nil? || value.empty? ? nil : (compiled[value] ||= Pattern.new(value))
|
|
78
80
|
end.freeze
|
|
79
81
|
end.freeze
|
|
80
82
|
end
|
data/lib/textfsm/parser.rb
CHANGED
|
@@ -90,13 +90,13 @@ module TextFSM
|
|
|
90
90
|
snapshot = nil
|
|
91
91
|
(@rows.length - 1).downto(0) do |index|
|
|
92
92
|
row = @rows[index]
|
|
93
|
-
|
|
94
|
-
break if value && !(value.respond_to?(:empty?) && value.empty?)
|
|
93
|
+
break unless Data.empty?(row[column])
|
|
95
94
|
|
|
96
95
|
# 记录不可变,既有快照可安全共享;回填通过替换整行完成。
|
|
97
96
|
replacement = row.dup
|
|
98
97
|
# Fillup 接收非空值,同一次回填共享冻结快照,导出时仍各自复制。
|
|
99
|
-
|
|
98
|
+
snapshot = Data.copy(field.value, immutable: true) if snapshot.nil?
|
|
99
|
+
replacement[column] = snapshot
|
|
100
100
|
@rows[index] = replacement.freeze
|
|
101
101
|
end
|
|
102
102
|
end
|
|
@@ -197,6 +197,7 @@ module TextFSM
|
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
def validate_states
|
|
200
|
+
raise TemplateError, "No Value definitions found" if @fields.empty?
|
|
200
201
|
raise TemplateError, "Missing state 'Start'." unless @states.key?("Start")
|
|
201
202
|
|
|
202
203
|
%w[End EOF].each do |state|
|
data/lib/textfsm/version.rb
CHANGED