textfsm 0.2.0
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 +7 -0
- data/LICENSE +202 -0
- data/NOTICE +15 -0
- data/README.md +289 -0
- data/examples/cisco_bgp_summary_example +15 -0
- data/examples/cisco_bgp_summary_template +16 -0
- data/examples/cisco_ipv6_interface_example +53 -0
- data/examples/cisco_ipv6_interface_template +27 -0
- data/examples/cisco_version_example +40 -0
- data/examples/cisco_version_template +17 -0
- data/examples/f10_ip_bgp_summary_example +16 -0
- data/examples/f10_ip_bgp_summary_template +15 -0
- data/examples/f10_version_example +25 -0
- data/examples/f10_version_template +10 -0
- data/examples/index +14 -0
- data/examples/juniper_bgp_summary_example +12 -0
- data/examples/juniper_bgp_summary_template +22 -0
- data/examples/juniper_version_example +10 -0
- data/examples/juniper_version_template +21 -0
- data/examples/unix_ifcfg_example +16 -0
- data/examples/unix_ifcfg_template +18 -0
- data/exe/textfsm +6 -0
- data/lib/textfsm/cli.rb +79 -0
- data/lib/textfsm/cli_table.rb +92 -0
- data/lib/textfsm/data.rb +23 -0
- data/lib/textfsm/errors.rb +8 -0
- data/lib/textfsm/field.rb +83 -0
- data/lib/textfsm/index_table.rb +88 -0
- data/lib/textfsm/options.rb +93 -0
- data/lib/textfsm/parser.rb +233 -0
- data/lib/textfsm/pattern.rb +322 -0
- data/lib/textfsm/rule.rb +72 -0
- data/lib/textfsm/table.rb +96 -0
- data/lib/textfsm/version.rb +5 -0
- data/lib/textfsm.rb +5 -0
- metadata +105 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f381555ee4b666d74fb0fb9f8ed82d3881089872c9ce69d69bdb20e6febd1335
|
|
4
|
+
data.tar.gz: 9edc61cf09c9e24a494a0fb15aba98cc57162afba32bafa6dd8ccee5c00b414c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7bb5d2d0907d9f1dee50c6db10cc3ad62681cbddb6d188a9053164502c37cbb3cb908d3538d3923f796ece79e311974fce95b63499595a69c5ab2a19901dea23
|
|
7
|
+
data.tar.gz: ba2cc24808ac78daad55cbd6bf7bb9c228b9f3965d865ad0801bca3c19c5d406873fc189a6627ea2187e1366c3367b69750c2957a2d728d4d2e62bcafd390c89
|
data/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
202
|
+
|
data/NOTICE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
TextFSM Ruby
|
|
2
|
+
|
|
3
|
+
This project is a Ruby port of the parsing engine and CLI template selection
|
|
4
|
+
from Google TextFSM (https://github.com/google/textfsm).
|
|
5
|
+
|
|
6
|
+
Original TextFSM: Copyright 2010 Google Inc. All Rights Reserved.
|
|
7
|
+
Original CLI tables: Copyright 2022 Google Inc. All Rights Reserved.
|
|
8
|
+
Licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
9
|
+
|
|
10
|
+
Reference: TextFSM 2.1.0, commit f80bbb459c55ff5f21651e48d2529722d667af97.
|
|
11
|
+
The Ruby source is a reimplementation, modified in 2026.
|
|
12
|
+
Files in examples/ and test/fixtures/upstream/ are copied from that reference.
|
|
13
|
+
test/fixtures/python_conformance.json contains results generated from the
|
|
14
|
+
upstream parser tests and examples. The generator is in script/.
|
|
15
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# TextFSM Ruby
|
|
2
|
+
|
|
3
|
+
使用 Ruby 实现的模板驱动文本解析状态机。读取 TextFSM 模板和半结构化文本,输出二维数组或 Hash 数组,适合解析网络设备 CLI 输出、系统命令输出等。
|
|
4
|
+
|
|
5
|
+
解析语义参考 [Google TextFSM 2.1.0](https://github.com/google/textfsm/tree/f80bbb459c55ff5f21651e48d2529722d667af97),运行时使用 Ruby 自身的正则引擎,不需要 Python 或外部进程;运行时依赖 Ruby 标准库 gem `json` 和 `strscan`。要求 Ruby 3.1 或以上。
|
|
6
|
+
|
|
7
|
+
0.2 使用 `TextFSM` 模块组织 `Parser`、`Field`、`Rule`、`Table` 和 `CliTable`;公开方法与选项扩展接口按 Ruby 风格重新设计,不保留 0.1 的接口别名。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
本项目尚未发布到 RubyGems。在其他项目的 `Gemfile` 中使用本地路径:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem "textfsm", path: "/path/to/textfsm"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
也可以构建并安装:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bundle install
|
|
21
|
+
bundle exec rake build
|
|
22
|
+
gem install ./pkg/textfsm-0.2.0.gem
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
源码仓库:[gatework/textfsm](https://github.com/gatework/textfsm)。
|
|
26
|
+
|
|
27
|
+
## 快速使用
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "textfsm"
|
|
31
|
+
|
|
32
|
+
template = <<~'FSM'
|
|
33
|
+
Value Required INTERFACE (\S+)
|
|
34
|
+
Value ADDRESS (\S+)
|
|
35
|
+
Value STATUS (up|down|administratively down)
|
|
36
|
+
Value PROTOCOL (up|down)
|
|
37
|
+
|
|
38
|
+
Start
|
|
39
|
+
^${INTERFACE}\s+${ADDRESS}\s+${STATUS}\s+${PROTOCOL}$$ -> Record
|
|
40
|
+
FSM
|
|
41
|
+
|
|
42
|
+
text = <<~TEXT
|
|
43
|
+
GigabitEthernet0/0 192.0.2.1 up up
|
|
44
|
+
GigabitEthernet0/1 unassigned administratively down down
|
|
45
|
+
TEXT
|
|
46
|
+
|
|
47
|
+
fsm = TextFSM::Parser.new(template)
|
|
48
|
+
p fsm.header
|
|
49
|
+
# => ["INTERFACE", "ADDRESS", "STATUS", "PROTOCOL"]
|
|
50
|
+
p fsm.parse(text)
|
|
51
|
+
# => [["GigabitEthernet0/0", "192.0.2.1", "up", "up"],
|
|
52
|
+
# ["GigabitEthernet0/1", "unassigned", "administratively down", "down"]]
|
|
53
|
+
|
|
54
|
+
fsm.reset
|
|
55
|
+
p fsm.parse_hashes(text)
|
|
56
|
+
# => [{"INTERFACE"=>"GigabitEthernet0/0", "ADDRESS"=>"192.0.2.1",
|
|
57
|
+
# "STATUS"=>"up", "PROTOCOL"=>"up"}, ...]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
模板使用单引号 heredoc(`<<~'FSM'`),避免 Ruby 提前处理正则中的反斜杠。`TextFSM::Parser.new` 的字符串参数是**模板内容**;从文件读取请用 `TextFSM::Parser.from_file("template.textfsm")`,也可以传入已打开的文件或 `StringIO`。IO 从当前位置读取并保持在读完的位置,不会自动 `rewind`。
|
|
61
|
+
|
|
62
|
+
## 模板语法
|
|
63
|
+
|
|
64
|
+
模板先声明连续的 `Value`,用空行分隔状态。初始状态必须叫 `Start`。状态之间用空行分隔;规则以一个空格、两个空格或一个制表符缩进,随后必须是 `^`。以 `#` 开始的注释行会被忽略。
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
Value [选项,选项] NAME (正则表达式)
|
|
68
|
+
|
|
69
|
+
Start
|
|
70
|
+
^匹配规则 -> 行操作.记录操作 下一个状态
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
规则中的 `$NAME` 或 `${NAME}` 会替换为对应字段的捕获组。规则需要正则行尾锚点 `$` 时写 `$$`;需要匹配字面量美元符号时写 `\$$`。字段名和状态名最长 48 个字符。
|
|
74
|
+
|
|
75
|
+
字段选项:
|
|
76
|
+
|
|
77
|
+
| 选项 | 行为 |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `Required` | 字段为空时丢弃当前记录 |
|
|
80
|
+
| `Filldown` | 保存字段值,后续记录沿用,直到重新赋值或 `Clearall` |
|
|
81
|
+
| `Fillup` | 新值向上填充此前连续的空字段,遇到非空值停止 |
|
|
82
|
+
| `List` | 将多次匹配累积为数组 |
|
|
83
|
+
| `Key` | 标记多模板表合并所使用的键 |
|
|
84
|
+
|
|
85
|
+
选项按照声明顺序执行,保留 Python 的顺序语义,例如 `List,Required` 与 `Required,List` 在可选匹配或空值时可能有不同结果。
|
|
86
|
+
|
|
87
|
+
行操作和记录操作可以省略;默认是 `Next.NoRecord`:
|
|
88
|
+
|
|
89
|
+
| 操作 | 行为 |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `Next` | 消耗当前行,从当前或目标状态的第一条规则处理下一行 |
|
|
92
|
+
| `Continue` | 保留当前行,继续当前状态的下一条规则;不能同时指定目标状态 |
|
|
93
|
+
| `Error ["消息"]` | 抛出 `TextFSM::ParseError`,包含模板规则行号和输入行 |
|
|
94
|
+
| `NoRecord` | 保留当前字段,不输出记录 |
|
|
95
|
+
| `Record` | 输出符合条件的记录,清除普通字段,保留 `Filldown` |
|
|
96
|
+
| `Clear` | 清除普通字段,保留 `Filldown` |
|
|
97
|
+
| `Clearall` | 清除全部字段,包括 `Filldown` 和列表缓存 |
|
|
98
|
+
|
|
99
|
+
记录操作先于行操作执行。未匹配的行会被跳过,可添加 `^.* -> Error "Unexpected input"` 拒绝未识别的输入。缺失的普通字段输出 `""`,未匹配的列表输出 `[]`;纯未赋值的记录不会输出,实际匹配到的空字符串可以输出。
|
|
100
|
+
|
|
101
|
+
输入结束时默认执行一次 `Record`。定义空的 `EOF` 状态可以关闭这一行为。跳转到 `End` 会停止解析并跳过最终记录;需要保留当前记录时使用 `Record End`。`EOF` 和 `End` 的显式状态定义必须为空。
|
|
102
|
+
|
|
103
|
+
`Filldown` 本身也会让最终记录非空,因此可能产生尾部记录。通常给每条业务记录的标识字段设置 `Required`,或根据模板含义定义空 `EOF`。
|
|
104
|
+
|
|
105
|
+
### 列表中的结构化数据
|
|
106
|
+
|
|
107
|
+
`List` 字段包含 Python 风格的命名子捕获组时,每次匹配会产生一个 Hash:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
Value List PERSON ((?P<name>\w+)\s+(?P<age>\d+))
|
|
111
|
+
|
|
112
|
+
Start
|
|
113
|
+
^${PERSON}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
解析 `Bob 32\nAlice 27\n` 后得到:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
[[[{ "name" => "Bob", "age" => "32" }, { "name" => "Alice", "age" => "27" }]]]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
所有捕获值保持字符串;可选命名子组未匹配时为 `nil`。同一条规则里不能出现重复的命名捕获组,包括重复引用同一个 Value。
|
|
123
|
+
|
|
124
|
+
## 分段解析与复用
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
fsm = TextFSM::Parser.from_file("template.textfsm")
|
|
128
|
+
fsm.parse(first_chunk, eof: false)
|
|
129
|
+
rows = fsm.parse(last_chunk)
|
|
130
|
+
|
|
131
|
+
fsm.reset
|
|
132
|
+
rows = fsm.parse(another_command_output)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`parse` 和 `rows` 返回累计结果的深度冻结快照;它们不会自动重置解析器。需要可修改的数据时使用 `to_a`、`to_hashes` 或 `parse_hashes`,这些接口返回独立的深拷贝。分段必须按完整行切分,`eof: false` 仅抑制最终 `Record`,不缓存半行。`parse` 也接受可读取的 IO,但会一次读取内容。不同设备输出应先 `reset` 或使用新实例;有状态的实例不应由多个线程并发共享。`Fillup` 会更新解析器内部的历史行;此前返回的快照不变,后续 `parse`/`rows` 会包含回填后的结果。
|
|
136
|
+
|
|
137
|
+
可用接口:`header`、`rows`、`current_state`、`states`、`state_names`、`fields_with_option("Key")`、`to_a`、`to_hashes`、`to_s`(重建去除注释的模板)。表头、状态规则及编译后的模式只读;字段的运行时状态保留在解析器内部。`fields_with_option` 也接受 `:Key` 等 Symbol。
|
|
138
|
+
|
|
139
|
+
## 根据设备与命令选择模板
|
|
140
|
+
|
|
141
|
+
`CliTable` 读取与 Python TextFSM 相同的 index 文件格式:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
require "textfsm/cli_table"
|
|
145
|
+
|
|
146
|
+
table = TextFSM::CliTable.new(index: "index", template_dir: "/path/to/templates")
|
|
147
|
+
table.parse(output, attributes: { "Vendor" => "Cisco", "Command" => "sh ver" })
|
|
148
|
+
p table.header
|
|
149
|
+
p table.to_a
|
|
150
|
+
p table.to_hashes
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
index 示例:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
Template, Vendor, Command
|
|
157
|
+
cisco_version_template, Cisco, sh[[ow]] ve[[rsion]]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
按文件顺序选择第一条匹配记录;匹配从属性字符串开头开始,空单元格是通配条件,index 未定义的属性会被忽略。`Command` 中 `sh[[ow]]` 支持 `sh`、`sho`、`show`。index 遵循上游简单逗号分隔格式,不处理带引号的 CSV 字段。
|
|
161
|
+
|
|
162
|
+
可绕过 index 显式传入模板:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
table = TextFSM::CliTable.new(template_dir: "/path/to/templates")
|
|
166
|
+
table.parse(output, templates: "first.textfsm:second.textfsm")
|
|
167
|
+
# templates 也接受文件名数组。
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
多个模板重复解析同一份输入,只合入新增列。有 `Key` 时按键匹配首条记录,否则按行位置合并。保留第一张表的行数,缺少匹配的新增列填 `""`。表格支持 Enumerable,行号从零开始。`IndexTable#match(attributes)` 返回首条匹配的只读 Hash,未命中返回 `nil`,无需换算行号。
|
|
171
|
+
|
|
172
|
+
`Table#merge(other, keys: ["ID"])` 返回合入新列的新表,保留原表;`merge!` 更新当前表并返回自身。新表保留接收者的类型,因此 `CliTable#merge` 也保留索引、输入和键。表头和 `rows`/`table[index]` 只读;`to_a` 和 `to_hashes` 返回独立副本,嵌套的列表与 Hash 也不会共享可变数据。构造表格时拒绝重复列名和宽度不一致的行。
|
|
173
|
+
|
|
174
|
+
`CliTable#keys` 返回模板声明的键,使用 `table.keys = ["ID"]` 显式设置,或 `table.keys += ["NAME"]` 增补。`key_for(row)` 返回键值数组,没有键时返回 `[]`。排序遵循 Ruby 自身的比较规则:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
table.sort! # 按行比较
|
|
178
|
+
table.sort_by! { |row| table.key_for(row) } # 按声明的键排序
|
|
179
|
+
table.sort_by! { |row| row[0].to_i } # 明确按数值排序
|
|
180
|
+
table.sort! { |left, right| right <=> left } # 倒序
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`load_index("another_index")` 可替换索引。多模板解析、合并或索引加载失败时保留上次成功的表头、数据、键及输入。
|
|
184
|
+
|
|
185
|
+
## 命令行
|
|
186
|
+
|
|
187
|
+
```sh
|
|
188
|
+
# 本地源码运行;安装 gem 后可直接使用 textfsm 命令
|
|
189
|
+
ruby exe/textfsm examples/cisco_version_template examples/cisco_version_example
|
|
190
|
+
cat examples/cisco_version_example | ruby exe/textfsm examples/cisco_version_template
|
|
191
|
+
ruby exe/textfsm --rows examples/cisco_version_template examples/cisco_version_example
|
|
192
|
+
ruby exe/textfsm --format table examples/cisco_version_template examples/cisco_version_example
|
|
193
|
+
ruby exe/textfsm --validate examples/cisco_version_template
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
默认输出 JSON 对象数组;`--rows` 输出包含 `header` 和 `rows` 的 JSON;`--format table` 输出制表符分隔的可读表格。成功退出码为 `0`,参数、文件、模板或解析错误为 `2`。`--validate` 只验证模板,不读取标准输入。
|
|
197
|
+
|
|
198
|
+
## 扩展字段选项
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
class Uppercase < TextFSM::Options::Base
|
|
202
|
+
def after_assign
|
|
203
|
+
field.value = field.value&.upcase
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
parser = TextFSM::Parser.new(template, options: { "Uppercase" => Uppercase })
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
模板中可以声明 `Value Uppercase NAME (...)`。显式传入的选项与五种内置选项合并,只对该解析器生效;内置注册表不可修改。选项类必须继承 `TextFSM::Options::Base`。
|
|
211
|
+
|
|
212
|
+
可实现 `after_initialize`、`after_assign`、`after_clear`、`after_reset`、`before_record` 钩子,通过 `field.value` 读写当前值。在 `before_record` 中执行 `throw :skip_record` 可以丢弃当前记录;字段仍会按正常规则清除。选项的 `visible?` 返回 `false` 可以隐藏字段,输出列在模板加载时确定,`Fillup` 使用同一份列映射。隐藏字段仍执行选项钩子,可用于控制记录是否输出。
|
|
213
|
+
|
|
214
|
+
## 工程约定
|
|
215
|
+
|
|
216
|
+
- `lib/textfsm.rb` 是统一入口,各组件也可单独 `require "textfsm/parser"`、`require "textfsm/pattern"` 等。CLI 位于 `exe/textfsm`。
|
|
217
|
+
- `exe/` 遵循 Bundler 的 gem 可执行文件约定,存放安装给使用者的命令;`script/` 存放项目维护脚本。`Gemfile.lock` 纳入 Git,固定开发和发布验证使用的依赖,不会打进 gem;使用者的依赖范围由 gemspec 声明。
|
|
218
|
+
- 类和模块使用 `CamelCase`,方法、参数和实例变量使用 `snake_case`;查询使用 `?`,属性赋值使用 `=`。类方法写成 `def self.method_name`,解析状态属于实例,固定映射使用冻结常量。
|
|
219
|
+
- 集合通过 `Enumerable`、`each`、`[]`、`size` 和 `empty?` 提供 Ruby 接口;需要代码块的迭代方法在未传块时返回 `Enumerator`。`merge`/`merge!` 区分创建新表和修改当前表,排序复用 Ruby 比较器。
|
|
220
|
+
- 数据转换使用 `map`、`filter_map`、`transform_values`、`reduce` 等集合方法;简单转换使用单行 `{ ... }`,多行逻辑和有副作用的迭代使用 `do ... end`。简短取值使用条件表达式,复杂状态分支使用 `if`/`case`。
|
|
221
|
+
- 构造方法组织对象初始化,声明解析等细节放入私有方法。字段选项通过明确的生命周期钩子扩展;错误使用 `TextFSM::Error` 的子类,预期的跳过记录使用 `catch`/`throw`。
|
|
222
|
+
- 内部动作使用 Symbol,省略目标状态使用 `nil`;模板文本继续使用标准的 `Next`、`Record`、`Clearall` 等语法。
|
|
223
|
+
- 正则编译使用 `StringScanner`,编译器的游标和分组栈不保留在最终 `Pattern` 对象中。模式始终从输入开头匹配。
|
|
224
|
+
- `bundle exec rake` 执行 Minitest 和 RuboCop;`rake test`、`rake lint` 可分别运行。代码规则见 `.rubocop.yml`。
|
|
225
|
+
- `bundle exec rake verify` 进一步构建 gem,在临时目录安装后逐文件核对源码内容,并验证独立加载与 CLI 输出、标准输入和退出码。生成的包位于 `pkg/`,不会发布到 RubyGems。
|
|
226
|
+
|
|
227
|
+
## 发布到 RubyGems
|
|
228
|
+
|
|
229
|
+
发布入口是 `script/release.rb`,也可通过 Rake 运行。直接执行脚本默认只检查和构建:
|
|
230
|
+
|
|
231
|
+
```sh
|
|
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
|
+
```
|
|
249
|
+
|
|
250
|
+
凭据由 RubyGems 管理;自动化环境可通过密钥管理服务注入 `GEM_HOST_API_KEY`,密钥需有 `push_rubygem` 权限。需要 MFA 时由 `gem push` 处理交互。不要把密钥放进仓库或发布脚本。详见 [RubyGems 发布指南](https://guides.rubygems.org/publishing/)和 [API key scopes](https://guides.rubygems.org/api-key-scopes/)。
|
|
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
|
+
```
|
|
260
|
+
|
|
261
|
+
## 兼容范围与验证
|
|
262
|
+
|
|
263
|
+
本项目实现 TextFSM 模板解析、状态机、全部五种内置字段选项、列表命名子组、字典输出,以及命令索引与多模板表合并。官方示例保存在 `examples/`。
|
|
264
|
+
|
|
265
|
+
Ruby 使用 Onigmo 正则引擎。本项目转换 Python 命名组 `(?P<name>...)`、命名与数字反向引用、贪婪/懒惰/占有量词、常用内联标志 `i/m/s/x/a/u`、Unicode 字符类及锚点语义。**这不是完整 Python `re` 引擎的替代实现**:Unicode 名称转义 `\N{...}`、条件组等不支持的扩展会报 `TemplateError`;两种引擎的大小写折叠、复杂后行断言等细节仍可能不同。使用额外模板库时,应以实际模板与输入执行对照测试。
|
|
266
|
+
|
|
267
|
+
Ruby 的 `End` / `EOF` 跳转在后续调用中继续保持终止状态,重新解析需 `reset`;不会复现 Python 在后续调用中再次处理一行的边界行为。CLI JSON 格式和错误文案采用 Ruby 接口;未移植 Python `terminal` 的终端控制功能或 `texttable` 的完整展示/编辑 API。模板 IO 不自动回卷;结果采用快照语义;输入必须是字符串或可读取的 IO。
|
|
268
|
+
|
|
269
|
+
运行测试:
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
bundle install
|
|
273
|
+
bundle exec rake
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
测试包含单元测试及离线 Python 对照数据,正常运行无需 Python 或网络。`test/fixtures/python_conformance.json` 来自固定上游提交的 33 项原测试、8 个官方示例,以及 140 组确定性的选项/状态组合场景,共 203 组实例轨迹。对照校验表头、模板重建、选项元数据、累计结果、重置和目标状态。CI 覆盖 Ruby 3.1、3.2、3.3、3.4 和 4.0,每个版本执行 `rake verify`。额外回归覆盖结果隔离、自定义选项、隐藏列回填、索引事务、CLI 错误和正则语法边界。
|
|
277
|
+
|
|
278
|
+
需要重新生成对照数据时:
|
|
279
|
+
|
|
280
|
+
```sh
|
|
281
|
+
git clone https://github.com/google/textfsm.git /tmp/google-textfsm
|
|
282
|
+
git -C /tmp/google-textfsm checkout f80bbb459c55ff5f21651e48d2529722d667af97
|
|
283
|
+
python3 script/generate_conformance.py /tmp/google-textfsm
|
|
284
|
+
bundle exec rake
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## 许可证
|
|
288
|
+
|
|
289
|
+
Apache-2.0。原项目归属、参考版本及复制文件说明见 [NOTICE](NOTICE),许可证全文见 [LICENSE](LICENSE)。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
BGP router identifier 192.0.2.70, local AS number 65550
|
|
2
|
+
BGP table version is 9, main routing table version 9
|
|
3
|
+
4 network entries using 468 bytes of memory
|
|
4
|
+
4 path entries using 208 bytes of memory
|
|
5
|
+
3/2 BGP path/bestpath attribute entries using 420 bytes of memory
|
|
6
|
+
1 BGP AS-PATH entries using 24 bytes of memory
|
|
7
|
+
1 BGP community entries using 24 bytes of memory
|
|
8
|
+
0 BGP route-map cache entries using 0 bytes of memory
|
|
9
|
+
0 BGP filter-list cache entries using 0 bytes of memory
|
|
10
|
+
BGP using 1144 total bytes of memory
|
|
11
|
+
BGP activity 12/4 prefixes, 12/4 paths, scan interval 5 secs
|
|
12
|
+
|
|
13
|
+
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
|
|
14
|
+
192.0.2.77 4 65551 6965 1766 9 0 0 5w4d 1
|
|
15
|
+
192.0.2.78 4 65552 6965 1766 9 0 0 5w4d 10
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Carry down the local end information so that it is present on each row item.
|
|
2
|
+
Value Filldown RouterID (\S+)
|
|
3
|
+
Value Filldown LocalAS (\d+)
|
|
4
|
+
Value RemoteAS (\d+)
|
|
5
|
+
Value Required RemoteIP (\d+(\.\d+){3})
|
|
6
|
+
Value Uptime (\d+\S+)
|
|
7
|
+
Value Received_V4 (\d+)
|
|
8
|
+
Value Status (\D.*)
|
|
9
|
+
|
|
10
|
+
Start
|
|
11
|
+
^BGP router identifier ${RouterID}, local AS number ${LocalAS}
|
|
12
|
+
^${RemoteIP}\s+\d+\s+${RemoteAS}(\s+\S+){5}\s+${Uptime}\s+${Received_V4} -> Record
|
|
13
|
+
^${RemoteIP}\s+\d+\s+${RemoteAS}(\s+\S+){5}\s+${Uptime}\s+${Status} -> Record
|
|
14
|
+
|
|
15
|
+
# Last record is already recorded then skip doing so here.
|
|
16
|
+
EOF
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Dialer0 is up, line protocol is up
|
|
2
|
+
IPv6 is enabled, link-local address is FE80::21B:2BFF:FECE:4EE3
|
|
3
|
+
No Virtual link-local address(es):
|
|
4
|
+
Description: PPP Dialer
|
|
5
|
+
Stateless address autoconfig enabled
|
|
6
|
+
General-prefix in use for addressing
|
|
7
|
+
Global unicast address(es):
|
|
8
|
+
2001:4567:1212:B2:21B:2BFF:FECE:4EE3, subnet is 2001:4567:1212:B2::/64 [EUI/CAL/PRE]
|
|
9
|
+
valid lifetime 5041 preferred lifetime 5041
|
|
10
|
+
2001:4567:1111:56FF::1, subnet is 2001:4567:1111:56FF::1/128 [CAL/PRE]
|
|
11
|
+
valid lifetime 5945 preferred lifetime 2344
|
|
12
|
+
Joined group address(es):
|
|
13
|
+
FF02::1
|
|
14
|
+
FF02::2
|
|
15
|
+
FF02::1:FF00:1
|
|
16
|
+
FF02::1:FFCE:4EE3
|
|
17
|
+
MTU is 1500 bytes
|
|
18
|
+
ICMP error messages limited to one every 100 milliseconds
|
|
19
|
+
ICMP redirects are enabled
|
|
20
|
+
ICMP unreachables are sent
|
|
21
|
+
Input features: Access List
|
|
22
|
+
Inbound access list IPV6-IN
|
|
23
|
+
ND DAD is enabled, number of DAD attempts: 1
|
|
24
|
+
ND reachable time is 30000 milliseconds (using 21397)
|
|
25
|
+
Hosts use stateless autoconfig for addresses.
|
|
26
|
+
Vlan1 is up, line protocol is up
|
|
27
|
+
IPv6 is enabled, link-local address is FE80::21B:2BFF:FECE:4EE3
|
|
28
|
+
No Virtual link-local address(es):
|
|
29
|
+
Description: Local VLAN
|
|
30
|
+
General-prefix in use for addressing
|
|
31
|
+
Global unicast address(es):
|
|
32
|
+
2001:4567:1212:5600::1, subnet is 2001:4567:1212:5600::/64 [CAL/PRE]
|
|
33
|
+
valid lifetime 5943 preferred lifetime 2342
|
|
34
|
+
Joined group address(es):
|
|
35
|
+
FF02::1
|
|
36
|
+
FF02::2
|
|
37
|
+
FF02::1:2
|
|
38
|
+
FF02::1:FF00:1
|
|
39
|
+
FF02::1:FFCE:4EE3
|
|
40
|
+
FF05::1:3
|
|
41
|
+
MTU is 1500 bytes
|
|
42
|
+
ICMP error messages limited to one every 100 milliseconds
|
|
43
|
+
ICMP redirects are enabled
|
|
44
|
+
ICMP unreachables are sent
|
|
45
|
+
ND DAD is enabled, number of DAD attempts: 1
|
|
46
|
+
ND reachable time is 30000 milliseconds (using 26371)
|
|
47
|
+
ND advertised reachable time is 0 (unspecified)
|
|
48
|
+
ND advertised retransmit interval is 0 (unspecified)
|
|
49
|
+
ND router advertisements are sent every 200 seconds
|
|
50
|
+
ND router advertisements live for 1800 seconds
|
|
51
|
+
ND advertised default router preference is Medium
|
|
52
|
+
Hosts use stateless autoconfig for addresses.
|
|
53
|
+
Hosts use DHCP to obtain other configuration.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Value Interface (\S+)
|
|
2
|
+
Value Admin (\S+)
|
|
3
|
+
Value Oper (\S+)
|
|
4
|
+
Value Description (.*)
|
|
5
|
+
Value LinkLocal (\S+)
|
|
6
|
+
Value List Addresses (\S+)
|
|
7
|
+
Value List Subnets (\S+)
|
|
8
|
+
Value List GroupAddresses (\S+)
|
|
9
|
+
Value Mtu (\d+)
|
|
10
|
+
|
|
11
|
+
Start
|
|
12
|
+
^${Interface} is ${Admin}, line protocol is ${Oper}
|
|
13
|
+
^.*link-local address is ${LinkLocal}
|
|
14
|
+
^ Description: ${Description}
|
|
15
|
+
^ Global unicast address -> Unicast
|
|
16
|
+
^ Joined group address -> Multicast
|
|
17
|
+
^ MTU is ${Mtu} bytes -> Record
|
|
18
|
+
|
|
19
|
+
Unicast
|
|
20
|
+
^ ${Addresses}, subnet is ${Subnets}
|
|
21
|
+
^ Joined group address -> Multicast
|
|
22
|
+
^ \S -> Start
|
|
23
|
+
|
|
24
|
+
Multicast
|
|
25
|
+
^ ${GroupAddresses}
|
|
26
|
+
^ MTU is ${Mtu} bytes -> Record
|
|
27
|
+
^ \S -> Start
|