This repository has been archived by the owner on Jan 7, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse_model.py
171 lines (161 loc) · 6.22 KB
/
parse_model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""Attempt to use pyang like a library.
Mostly extracted from bin/pyang.
Should probably contribute this back. :)
Does not handle deviations or hello.
"""
import pdb
import os
import json
import logging
from pyang import Context, FileRepository
from pyang.yang_parser import YangParser
from pyang import syntax
from pyang import statements
from pyang import error
def get_type(node):
"""Gets the immediate, top-level type of the node.
"""
type_obj = node.search_one('type')
# Return type value if exists
return getattr(type_obj, 'arg', None)
def get_fq_type(node):
"""Gets the fully qualified, top-level type of the node.
This enters the typedef if defined instead of using the prefix
to ensure absolute distinction.
"""
type_obj = node.search_one('type')
fq_type_name = None
if type_obj is not None:
if getattr(type_obj, 'i_typedef', None):
# If type_obj has typedef, substitute.
# Absolute module:type instead of prefix:type
type_obj = type_obj.i_typedef
type_name = type_obj.arg
if getattr(type_obj, 'i_type_spec', None):
# i_type_spec appears to indicate primitive type
# Make sure it isn't there and just null, though.
# It doesn't make sense to qualify a primitive type..
# ...........................................I think.
fq_type_name = type_name
else:
type_module = type_obj.i_orig_module.arg
fq_type_name = '%s:%s' % (type_module, type_name)
return fq_type_name
def get_primitive_type(node):
"""Recurses through the typedefs and returns
the most primitive YANG type defined.
"""
type_obj = node.search_one('type')
type_name = getattr(type_obj, 'arg', None)
typedef_obj = getattr(type_obj, 'i_typedef', None)
if typedef_obj:
type_name = get_primitive_type(typedef_obj)
return type_name
def get_description(node):
"""Retrieves the description of the node if present.
"""
description_obj = node.search_one('description')
# Return description value if exists
return getattr(description_obj, 'arg', None)
def recurse_children(node, module):
"""Recurses through module levels and extracts
xpaths, fully qualified type, primitive type, description, and children.
Keep module name and fully qualified xpath intact, and also parse out the
Cisco xpaths which are cleaner albeit less absolute.
"""
if not hasattr(node, 'i_children'):
return {}
children = (child for child in node.i_children if child.keyword in statements.data_definition_keywords)
parsed_children = {}
for child in children:
module_name = module.arg
prefixed_xpath = statements.mk_path_str(child, with_prefixes=True)
no_prefix_xpath = statements.mk_path_str(child, with_prefixes=False)
cisco_xpath = '%s:%s' % (module_name, no_prefix_xpath[1:])
parsed_children[cisco_xpath] = {
'module_name': module_name,
'xpath': prefixed_xpath,
'cisco_xpath': cisco_xpath,
'type': get_fq_type(child),
'primitive_type': get_primitive_type(child),
'description': get_description(child),
'children': recurse_children(child, module)
}
return parsed_children
# Let's begin..
base_data_dir = 'testdata/'
model_filenames = ['Cisco-IOS-XR-ipv4-bgp-oper.yang']
# Context has a dependency on Repository
# Repository is abstract, only FileRepository exists
file_repository = FileRepository(path=base_data_dir, use_env=False, no_path_recurse=False)
# Contexts are like the base controller for parsing in pyang
context = Context(repository=file_repository)
parser = YangParser()
# YangParser has a dependency on Context
# Context also has a dependency on YangParser
# Parse all the models in to modules
modules = []
for filename in model_filenames:
model_file_path = os.path.join(base_data_dir, filename)
model_data = None
with open(model_file_path, 'r') as model_fd:
model_data = model_fd.read()
# Some model filenames indicate revision, etc.
filename_attributes = syntax.re_filename.search(filename)
module = None
if filename_attributes is None:
module = context.add_module(filename, model_data)
else:
(name, revision, model_format) = filename_attributes.groups()
module = context.add_module(
filename, model_data, name, revision, model_format,
expect_failure_error=False
)
modules.append(module)
# Some kind of validation step?
context.validate()
# Validating features?
for module in modules:
if module.arg in context.features:
for feature in context.features[module.arg]:
if feature not in module.i_features:
raise Exception('FEATURE %s MISSING IN %s' % (feature, module.arg))
# Now check for errors?
# Sort our errors.
def keyfun(e):
if e[0].ref == model_filenames[0]:
return 0
else:
return 1
context.errors.sort(key=lambda e: (e[0].ref, e[0].line))
if len(model_filenames) > 0:
# Print errors in filename order.
# TODO: Pretty sure this doesn't make sense.
context.errors.sort(key=keyfun)
# Parse out all the module names for error checking.
module_names = []
for module in modules:
module_names.append(module.arg)
for included_module in module.search('include'):
module_names.append(included_module.arg)
# Now actually do error parsing
for (epos, etag, eargs) in context.errors:
if (context.implicit_errors == False and
hasattr(epos.top, 'i_modulename') and
epos.top.arg not in module_names and
epos.top.i_modulename not in module_names and
epos.ref not in model_filenames):
# this module was added implicitly (by import); skip this error
# the code includes submodules
continue
elevel = error.err_level(etag)
if error.is_warning(elevel):
kind = "error"
exit_code = 1
logging.error('%s: %s: %s', str(epos), kind, etag)
# Now actually do whatever we want.
parsed_modules = [recurse_children(module, module) for module in modules]
if len(parsed_modules) == 1:
parsed_modules = parsed_modules[0]
with open('testout.json', 'w') as testout_fd:
json.dump(parsed_modules, testout_fd)