textfsm 0.2.0 → 0.2.2
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 +23 -0
- data/README.md +25 -38
- data/lib/textfsm/cli.rb +1 -1
- data/lib/textfsm/cli_table.rb +1 -1
- data/lib/textfsm/index_table.rb +2 -1
- data/lib/textfsm/parser.rb +8 -4
- data/lib/textfsm/table.rb +5 -4
- data/lib/textfsm/version.rb +1 -1
- metadata +25 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 32964a356a1628ef07df499f93d1e9fdc52f7884826d3a80e7ee339aefe3c51b
|
|
4
|
+
data.tar.gz: 63572a7dcab089698adcaf10c216f3867513edae6f49d4197b8a9aa426b238c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 430368496eb8b805cad3f944a7d9678286d69a34bb1b2c625c89cc326463df86e7e6861142a7cc9fbf9849777320bd203ca2d996212a8c84f6b9dafdc871a6d2
|
|
7
|
+
data.tar.gz: 1447c9de34de499d88ae5a9f8906148353710b410312103777d43a1691345987e112603530a236eb441acf83ea384bda2973753deb9a767519257fc768d672d3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 变更记录
|
|
2
|
+
|
|
3
|
+
## 0.2.2 (2026-10-05)
|
|
4
|
+
|
|
5
|
+
- 启用标签触发的 RubyGems OIDC 可信发布;完整 CI 通过后复用同一产物并下载核对。
|
|
6
|
+
|
|
7
|
+
## 0.2.1(2026-10-01)
|
|
8
|
+
|
|
9
|
+
- 解析器复用深度冻结的记录快照,`Fillup` 通过替换记录回填,减少分段解析和读取结果的重复复制。
|
|
10
|
+
- 表合并只复制新增列,索引匹配每次查询只转换一次属性键,保持嵌套数据隔离与首条匹配语义。
|
|
11
|
+
- 显式传入 `templates: false` 时报告参数错误并保留上次结果,不再意外使用索引选取模板。
|
|
12
|
+
- CLI 遇到不兼容的输入编码时按错误契约返回退出码 `2`。
|
|
13
|
+
- 安装验证隔离全局与开发依赖,从本地缓存安装实际运行时依赖,防止漏声明的依赖被环境掩盖。
|
|
14
|
+
- 补齐 CLI 的 `optparse` 运行时依赖,保持 Ruby 3.1 及以上支持。
|
|
15
|
+
- 运行时依赖仅声明最低版本,不限制主版本;开发验证仍使用 `Gemfile.lock`。
|
|
16
|
+
- 收紧 gem 的可执行文件与示例文件清单,补充文档、变更记录链接和 RubyGems MFA 元数据。
|
|
17
|
+
|
|
18
|
+
## 0.2.0(2026-09-12)
|
|
19
|
+
|
|
20
|
+
- 提供 `TextFSM::Parser`、`Field`、`Rule`、`Table` 和 `CliTable` Ruby API。
|
|
21
|
+
- 实现五种字段选项、状态动作、命令索引、多模板合并及 JSON/表格 CLI 输出。
|
|
22
|
+
- 结果使用独立快照;提供可修改的深拷贝、选项扩展钩子与 Python TextFSM 对照测试。
|
|
23
|
+
- 提供构建、隔离安装验证与 RubyGems 发布检查脚本。
|
data/README.md
CHANGED
|
@@ -2,13 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
使用 Ruby 实现的模板驱动文本解析状态机。读取 TextFSM 模板和半结构化文本,输出二维数组或 Hash 数组,适合解析网络设备 CLI 输出、系统命令输出等。
|
|
4
4
|
|
|
5
|
-
解析语义参考 [Google TextFSM 2.1.0](https://github.com/google/textfsm/tree/f80bbb459c55ff5f21651e48d2529722d667af97),运行时使用 Ruby 自身的正则引擎,不需要 Python 或外部进程;运行时依赖 Ruby 标准库 gem `json` 和 `strscan`。要求 Ruby 3.1 或以上。
|
|
5
|
+
解析语义参考 [Google TextFSM 2.1.0](https://github.com/google/textfsm/tree/f80bbb459c55ff5f21651e48d2529722d667af97),运行时使用 Ruby 自身的正则引擎,不需要 Python 或外部进程;运行时依赖 Ruby 标准库 gem `json`、`optparse` 和 `strscan`。要求 Ruby 3.1 或以上。
|
|
6
6
|
|
|
7
7
|
0.2 使用 `TextFSM` 模块组织 `Parser`、`Field`、`Rule`、`Table` 和 `CliTable`;公开方法与选项扩展接口按 Ruby 风格重新设计,不保留 0.1 的接口别名。
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
在项目的 `Gemfile` 中添加:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "textfsm"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
然后运行 `bundle install`。也可以直接安装命令行工具:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
gem install textfsm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
开发时可使用本地路径:
|
|
12
24
|
|
|
13
25
|
```ruby
|
|
14
26
|
gem "textfsm", path: "/path/to/textfsm"
|
|
@@ -19,7 +31,7 @@ gem "textfsm", path: "/path/to/textfsm"
|
|
|
19
31
|
```sh
|
|
20
32
|
bundle install
|
|
21
33
|
bundle exec rake build
|
|
22
|
-
gem install ./pkg/textfsm-0.2.
|
|
34
|
+
gem install ./pkg/textfsm-0.2.2.gem
|
|
23
35
|
```
|
|
24
36
|
|
|
25
37
|
源码仓库:[gatework/textfsm](https://github.com/gatework/textfsm)。
|
|
@@ -40,7 +52,7 @@ template = <<~'FSM'
|
|
|
40
52
|
FSM
|
|
41
53
|
|
|
42
54
|
text = <<~TEXT
|
|
43
|
-
GigabitEthernet0/0 192.0.2.
|
|
55
|
+
GigabitEthernet0/0 192.0.2.2 up up
|
|
44
56
|
GigabitEthernet0/1 unassigned administratively down down
|
|
45
57
|
TEXT
|
|
46
58
|
|
|
@@ -48,12 +60,12 @@ fsm = TextFSM::Parser.new(template)
|
|
|
48
60
|
p fsm.header
|
|
49
61
|
# => ["INTERFACE", "ADDRESS", "STATUS", "PROTOCOL"]
|
|
50
62
|
p fsm.parse(text)
|
|
51
|
-
# => [["GigabitEthernet0/0", "192.0.2.
|
|
63
|
+
# => [["GigabitEthernet0/0", "192.0.2.2", "up", "up"],
|
|
52
64
|
# ["GigabitEthernet0/1", "unassigned", "administratively down", "down"]]
|
|
53
65
|
|
|
54
66
|
fsm.reset
|
|
55
67
|
p fsm.parse_hashes(text)
|
|
56
|
-
# => [{"INTERFACE"=>"GigabitEthernet0/0", "ADDRESS"=>"192.0.2.
|
|
68
|
+
# => [{"INTERFACE"=>"GigabitEthernet0/0", "ADDRESS"=>"192.0.2.2",
|
|
57
69
|
# "STATUS"=>"up", "PROTOCOL"=>"up"}, ...]
|
|
58
70
|
```
|
|
59
71
|
|
|
@@ -214,7 +226,7 @@ parser = TextFSM::Parser.new(template, options: { "Uppercase" => Uppercase })
|
|
|
214
226
|
## 工程约定
|
|
215
227
|
|
|
216
228
|
- `lib/textfsm.rb` 是统一入口,各组件也可单独 `require "textfsm/parser"`、`require "textfsm/pattern"` 等。CLI 位于 `exe/textfsm`。
|
|
217
|
-
- `exe/` 遵循 Bundler 的 gem 可执行文件约定,存放安装给使用者的命令;`script/` 存放项目维护脚本。`Gemfile.lock` 纳入 Git,固定开发和发布验证使用的依赖,不会打进 gem
|
|
229
|
+
- `exe/` 遵循 Bundler 的 gem 可执行文件约定,存放安装给使用者的命令;`script/` 存放项目维护脚本。`Gemfile.lock` 纳入 Git,固定开发和发布验证使用的依赖,不会打进 gem;gemspec 中的运行时依赖仅声明最低版本,不限制主版本。
|
|
218
230
|
- 类和模块使用 `CamelCase`,方法、参数和实例变量使用 `snake_case`;查询使用 `?`,属性赋值使用 `=`。类方法写成 `def self.method_name`,解析状态属于实例,固定映射使用冻结常量。
|
|
219
231
|
- 集合通过 `Enumerable`、`each`、`[]`、`size` 和 `empty?` 提供 Ruby 接口;需要代码块的迭代方法在未传块时返回 `Enumerator`。`merge`/`merge!` 区分创建新表和修改当前表,排序复用 Ruby 比较器。
|
|
220
232
|
- 数据转换使用 `map`、`filter_map`、`transform_values`、`reduce` 等集合方法;简单转换使用单行 `{ ... }`,多行逻辑和有副作用的迭代使用 `do ... end`。简短取值使用条件表达式,复杂状态分支使用 `if`/`case`。
|
|
@@ -222,41 +234,16 @@ parser = TextFSM::Parser.new(template, options: { "Uppercase" => Uppercase })
|
|
|
222
234
|
- 内部动作使用 Symbol,省略目标状态使用 `nil`;模板文本继续使用标准的 `Next`、`Record`、`Clearall` 等语法。
|
|
223
235
|
- 正则编译使用 `StringScanner`,编译器的游标和分组栈不保留在最终 `Pattern` 对象中。模式始终从输入开头匹配。
|
|
224
236
|
- `bundle exec rake` 执行 Minitest 和 RuboCop;`rake test`、`rake lint` 可分别运行。代码规则见 `.rubocop.yml`。
|
|
225
|
-
- `bundle exec rake verify` 进一步构建 gem
|
|
237
|
+
- `bundle exec rake verify` 进一步构建 gem,从 `bundle install` 准备的本地缓存离线安装运行时依赖和候选包;安装环境仅使用临时目录的 gem 与 Ruby 自带的默认 gem。逐文件核对源码内容,并验证独立加载与 CLI 输出、标准输入和退出码,避免开发依赖掩盖缺失的运行时依赖。生成的包位于 `pkg/`,不会发布到 RubyGems。
|
|
226
238
|
|
|
227
239
|
## 发布到 RubyGems
|
|
228
240
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
bundle install
|
|
233
|
-
bundle exec rake release:check
|
|
234
|
-
# 等价于 bundle exec ruby script/release.rb --dry-run
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
检查模式可以在尚未提交的工作区运行,不访问 RubyGems,也不会创建提交或标签。正式发布需要工作区干净,所有入包文件已提交到 Git:
|
|
238
|
-
|
|
239
|
-
1. 修改 `lib/textfsm/version.rb` 中的版本号。新版本使用 RubyGems 版本格式,例如 `0.2.1` 或 `0.3.0.pre.1`。
|
|
240
|
-
2. 运行 `bundle install` 更新 `Gemfile.lock`,再运行 `bundle exec rake release:check`。
|
|
241
|
-
3. 提交并推送本次版本的源码与锁文件。
|
|
242
|
-
4. 使用自己的 RubyGems 账号发布:
|
|
243
|
-
|
|
244
|
-
```sh
|
|
245
|
-
gem signin --host https://rubygems.org
|
|
246
|
-
bundle exec rake release
|
|
247
|
-
# 等价于 bundle exec ruby script/release.rb --push
|
|
248
|
-
```
|
|
241
|
+
发布使用标签触发的 OIDC Trusted Publishing,不保存长期 API key。完整 Ruby CI 矩阵通过后,
|
|
242
|
+
工作流验证并上传同次 CI 的 gem,再核对 RubyGems 下载和 GitHub Release 的 SHA256。
|
|
243
|
+
流程、账号绑定及失败恢复见 [发布说明](docs/RELEASING.md)。
|
|
249
244
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
正式发布复用 `rake verify` 的完整检查:执行测试、RuboCop 和构建,再将 gem 复制到独立临时目录,由 `script/verify_package.rb` 验证安装后的库、CLI 和文件内容。候选包在验证前后必须保持同一 SHA256,HEAD 和工作区也必须保持不变,随后上传该候选包到 `https://rubygems.org`,读取版本元数据核对 SHA256。输出包含源码提交号和包校验值;相同版本、相同校验值已发布时直接成功,相同版本内容不同时拒绝覆盖。构建使用提交时间作为 `SOURCE_DATE_EPOCH`,便于同一提交重复构建。
|
|
253
|
-
|
|
254
|
-
上传后只重试查询,不自动重复上传。若提示结果未确认,先检查 RubyGems 上该版本及其校验值。脚本不创建或推送 Git 提交和标签;RubyGems 核对成功后,可按已发布版本标记源码,例如:
|
|
255
|
-
|
|
256
|
-
```sh
|
|
257
|
-
git tag -a v0.2.0 -m "Release v0.2.0"
|
|
258
|
-
git push origin v0.2.0
|
|
259
|
-
```
|
|
245
|
+
本地预检可执行 `bundle exec ruby script/release.rb --dry-run --artifact pkg/textfsm-0.2.2.gem`;
|
|
246
|
+
已有产物路径不会重新构建。更新版本及 CHANGELOG、提交并确认远端 CI 后推送对应版本标签。
|
|
260
247
|
|
|
261
248
|
## 兼容范围与验证
|
|
262
249
|
|
data/lib/textfsm/cli.rb
CHANGED
|
@@ -36,7 +36,7 @@ module TextFSM
|
|
|
36
36
|
rows = parser.parse(text)
|
|
37
37
|
write_result(parser.header, rows, settings)
|
|
38
38
|
0
|
|
39
|
-
rescue Error, OptionParser::ParseError, SystemCallError, IOError, ArgumentError => e
|
|
39
|
+
rescue Error, OptionParser::ParseError, SystemCallError, IOError, ArgumentError, EncodingError => e
|
|
40
40
|
@error.puts("textfsm: #{e.message}")
|
|
41
41
|
2
|
|
42
42
|
end
|
data/lib/textfsm/cli_table.rb
CHANGED
|
@@ -30,7 +30,7 @@ module TextFSM
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def parse(text, attributes: {}, templates: nil)
|
|
33
|
-
names = template_names(templates
|
|
33
|
+
names = template_names(templates.nil? ? find_templates(attributes) : templates)
|
|
34
34
|
text = text.read if text.respond_to?(:read)
|
|
35
35
|
raise ArgumentError, "text must be a String or readable IO" unless text.is_a?(String)
|
|
36
36
|
|
data/lib/textfsm/index_table.rb
CHANGED
|
@@ -46,9 +46,10 @@ module TextFSM
|
|
|
46
46
|
|
|
47
47
|
# Empty cells are wildcards. Attributes absent from the index are ignored.
|
|
48
48
|
def match(attributes)
|
|
49
|
+
attributes = attributes.map { |key, value| [key.to_s, value] }
|
|
49
50
|
index = @patterns.index do |patterns|
|
|
50
51
|
attributes.all? do |key, value|
|
|
51
|
-
pattern = patterns[key
|
|
52
|
+
pattern = patterns[key]
|
|
52
53
|
pattern.nil? || pattern.match?(value.to_s)
|
|
53
54
|
end
|
|
54
55
|
end
|
data/lib/textfsm/parser.rb
CHANGED
|
@@ -38,7 +38,7 @@ module TextFSM
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def rows
|
|
41
|
-
|
|
41
|
+
@rows.dup.freeze
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def to_a
|
|
@@ -80,11 +80,15 @@ module TextFSM
|
|
|
80
80
|
column = @output_fields.index(field)
|
|
81
81
|
return unless column
|
|
82
82
|
|
|
83
|
-
@rows.
|
|
83
|
+
(@rows.length - 1).downto(0) do |index|
|
|
84
|
+
row = @rows[index]
|
|
84
85
|
value = row[column]
|
|
85
86
|
break if value && !(value.respond_to?(:empty?) && value.empty?)
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
# Records are immutable so earlier snapshots can safely share them.
|
|
89
|
+
replacement = row.dup
|
|
90
|
+
replacement[column] = Data.copy(field.value, immutable: true)
|
|
91
|
+
@rows[index] = replacement.freeze
|
|
88
92
|
end
|
|
89
93
|
end
|
|
90
94
|
|
|
@@ -224,7 +228,7 @@ module TextFSM
|
|
|
224
228
|
@fields.each_value(&:prepare_record)
|
|
225
229
|
record = @output_fields.map(&:value)
|
|
226
230
|
unless record.all? { |value| value.nil? || value == [] }
|
|
227
|
-
@rows <<
|
|
231
|
+
@rows << record.map! { |value| Data.copy(value.nil? ? "" : value, immutable: true) }.freeze
|
|
228
232
|
end
|
|
229
233
|
end
|
|
230
234
|
@fields.each_value(&:clear)
|
data/lib/textfsm/table.rb
CHANGED
|
@@ -72,12 +72,13 @@ module TextFSM
|
|
|
72
72
|
end
|
|
73
73
|
rows = @rows.each_with_index.map do |row, index|
|
|
74
74
|
right = keys.empty? ? other[index] : lookup[row.values_at(*left_keys)]
|
|
75
|
-
|
|
75
|
+
extra = right ? Data.copy(right.values_at(*right_columns), immutable: true) : Array.new(columns.size, "")
|
|
76
|
+
# Existing cells are immutable; only incoming values need an owned copy.
|
|
77
|
+
(row + extra).freeze
|
|
76
78
|
end
|
|
77
|
-
header =
|
|
78
|
-
rows = Data.copy(rows, immutable: true)
|
|
79
|
+
header = (@header + Data.copy(columns, immutable: true)).freeze
|
|
79
80
|
@header = header
|
|
80
|
-
@rows = rows
|
|
81
|
+
@rows = rows.freeze
|
|
81
82
|
self
|
|
82
83
|
end
|
|
83
84
|
|
data/lib/textfsm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,40 +1,54 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: textfsm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TextFSM Ruby contributors
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: json
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '2.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: optparse
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.1'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: strscan
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
29
43
|
requirements:
|
|
30
|
-
- - "
|
|
44
|
+
- - ">="
|
|
31
45
|
- !ruby/object:Gem::Version
|
|
32
46
|
version: '3.0'
|
|
33
47
|
type: :runtime
|
|
34
48
|
prerelease: false
|
|
35
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
50
|
requirements:
|
|
37
|
-
- - "
|
|
51
|
+
- - ">="
|
|
38
52
|
- !ruby/object:Gem::Version
|
|
39
53
|
version: '3.0'
|
|
40
54
|
description: Parses semi-structured text using Google TextFSM template syntax, with
|
|
@@ -44,6 +58,7 @@ executables:
|
|
|
44
58
|
extensions: []
|
|
45
59
|
extra_rdoc_files: []
|
|
46
60
|
files:
|
|
61
|
+
- CHANGELOG.md
|
|
47
62
|
- LICENSE
|
|
48
63
|
- NOTICE
|
|
49
64
|
- README.md
|
|
@@ -84,6 +99,9 @@ licenses:
|
|
|
84
99
|
metadata:
|
|
85
100
|
source_code_uri: https://github.com/gatework/textfsm
|
|
86
101
|
bug_tracker_uri: https://github.com/gatework/textfsm/issues
|
|
102
|
+
documentation_uri: https://github.com/gatework/textfsm/blob/main/README.md
|
|
103
|
+
changelog_uri: https://github.com/gatework/textfsm/blob/main/CHANGELOG.md
|
|
104
|
+
rubygems_mfa_required: 'true'
|
|
87
105
|
allowed_push_host: https://rubygems.org
|
|
88
106
|
rdoc_options: []
|
|
89
107
|
require_paths:
|
|
@@ -99,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
99
117
|
- !ruby/object:Gem::Version
|
|
100
118
|
version: '0'
|
|
101
119
|
requirements: []
|
|
102
|
-
rubygems_version: 4.0.
|
|
120
|
+
rubygems_version: 4.0.20
|
|
103
121
|
specification_version: 4
|
|
104
122
|
summary: A Ruby state machine for parsing text with TextFSM templates
|
|
105
123
|
test_files: []
|