blob: 3ee309871ac9b04f6037bdca64dbed35d3527cae [file] [log] [blame]
Pavel Sedlák5ce5c032013-02-25 18:41:30 +01001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2013 OpenStack Foundation
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18"""Collection of utilities for parsing CLI clients output."""
19
20
Attila Fazekasc7eb6cc2013-07-04 10:25:54 +020021from tempest.common import log as logging
22
Pavel Sedlák5ce5c032013-02-25 18:41:30 +010023import re
24
25
26LOG = logging.getLogger(__name__)
27
28
29delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
30
31
32def details_multiple(output_lines, with_label=False):
33 """Return list of dicts with item details from cli output tables.
34
35 If with_label is True, key '__label' is added to each items dict.
36 For more about 'label' see OutputParser.tables().
37 """
38 items = []
39 tables_ = tables(output_lines)
40 for table_ in tables_:
41 if 'Property' not in table_['headers'] \
42 or 'Value' not in table_['headers']:
43 raise Exception('Invalid structure of table with details')
44 item = {}
45 for value in table_['values']:
46 item[value[0]] = value[1]
47 if with_label:
48 item['__label'] = table_['label']
49 items.append(item)
50 return items
51
52
53def details(output_lines, with_label=False):
54 """Return dict with details of first item (table) found in output."""
55 items = details_multiple(output_lines, with_label)
56 return items[0]
57
58
59def listing(output_lines):
60 """Return list of dicts with basic item info parsed from cli output.
61 """
62
63 items = []
64 table_ = table(output_lines)
65 for row in table_['values']:
66 item = {}
67 for col_idx, col_key in enumerate(table_['headers']):
68 item[col_key] = row[col_idx]
69 items.append(item)
70 return items
71
72
73def tables(output_lines):
74 """Find all ascii-tables in output and parse them.
75
76 Return list of tables parsed from cli output as dicts.
77 (see OutputParser.table())
78
79 And, if found, label key (separated line preceding the table)
80 is added to each tables dict.
81 """
82 tables_ = []
83
84 table_ = []
85 label = None
86
87 start = False
88 header = False
89
90 if not isinstance(output_lines, list):
91 output_lines = output_lines.split('\n')
92
93 for line in output_lines:
94 if delimiter_line.match(line):
95 if not start:
96 start = True
97 elif not header:
98 # we are after head area
99 header = True
100 else:
101 # table ends here
102 start = header = None
103 table_.append(line)
104
105 parsed = table(table_)
106 parsed['label'] = label
107 tables_.append(parsed)
108
109 table_ = []
110 label = None
111 continue
112 if start:
113 table_.append(line)
114 else:
115 if label is None:
116 label = line
117 else:
118 LOG.warn('Invalid line between tables: %s' % line)
119 if len(table_) > 0:
120 LOG.warn('Missing end of table')
121
122 return tables_
123
124
125def table(output_lines):
126 """Parse single table from cli output.
127
128 Return dict with list of column names in 'headers' key and
129 rows in 'values' key.
130 """
131 table_ = {'headers': [], 'values': []}
132 columns = None
133
134 if not isinstance(output_lines, list):
135 output_lines = output_lines.split('\n')
136
137 for line in output_lines:
138 if delimiter_line.match(line):
139 columns = _table_columns(line)
140 continue
141 if '|' not in line:
142 LOG.warn('skipping invalid table line: %s' % line)
143 continue
144 row = []
145 for col in columns:
146 row.append(line[col[0]:col[1]].strip())
147 if table_['headers']:
148 table_['values'].append(row)
149 else:
150 table_['headers'] = row
151
152 return table_
153
154
155def _table_columns(first_table_row):
156 """Find column ranges in output line.
157
158 Return list of touples (start,end) for each column
159 detected by plus (+) characters in delimiter line.
160 """
161 positions = []
162 start = 1 # there is '+' at 0
163 while start < len(first_table_row):
164 end = first_table_row.find('+', start)
165 if end == -1:
166 break
167 positions.append((start, end))
168 start = end + 1
169 return positions