blob: bb3368f610c3c63c611b0b14636c68d868ab3d04 [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
Pavel Sedlák5ce5c032013-02-25 18:41:30 +010020import re
21
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040022from tempest.openstack.common import log as logging
23
Pavel Sedlák5ce5c032013-02-25 18:41:30 +010024
25LOG = logging.getLogger(__name__)
26
27
28delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
29
30
31def details_multiple(output_lines, with_label=False):
32 """Return list of dicts with item details from cli output tables.
33
34 If with_label is True, key '__label' is added to each items dict.
35 For more about 'label' see OutputParser.tables().
36 """
37 items = []
38 tables_ = tables(output_lines)
39 for table_ in tables_:
40 if 'Property' not in table_['headers'] \
41 or 'Value' not in table_['headers']:
42 raise Exception('Invalid structure of table with details')
43 item = {}
44 for value in table_['values']:
45 item[value[0]] = value[1]
46 if with_label:
47 item['__label'] = table_['label']
48 items.append(item)
49 return items
50
51
52def details(output_lines, with_label=False):
53 """Return dict with details of first item (table) found in output."""
54 items = details_multiple(output_lines, with_label)
55 return items[0]
56
57
58def listing(output_lines):
59 """Return list of dicts with basic item info parsed from cli output.
60 """
61
62 items = []
63 table_ = table(output_lines)
64 for row in table_['values']:
65 item = {}
66 for col_idx, col_key in enumerate(table_['headers']):
67 item[col_key] = row[col_idx]
68 items.append(item)
69 return items
70
71
72def tables(output_lines):
73 """Find all ascii-tables in output and parse them.
74
75 Return list of tables parsed from cli output as dicts.
76 (see OutputParser.table())
77
78 And, if found, label key (separated line preceding the table)
79 is added to each tables dict.
80 """
81 tables_ = []
82
83 table_ = []
84 label = None
85
86 start = False
87 header = False
88
89 if not isinstance(output_lines, list):
90 output_lines = output_lines.split('\n')
91
92 for line in output_lines:
93 if delimiter_line.match(line):
94 if not start:
95 start = True
96 elif not header:
97 # we are after head area
98 header = True
99 else:
100 # table ends here
101 start = header = None
102 table_.append(line)
103
104 parsed = table(table_)
105 parsed['label'] = label
106 tables_.append(parsed)
107
108 table_ = []
109 label = None
110 continue
111 if start:
112 table_.append(line)
113 else:
114 if label is None:
115 label = line
116 else:
117 LOG.warn('Invalid line between tables: %s' % line)
118 if len(table_) > 0:
119 LOG.warn('Missing end of table')
120
121 return tables_
122
123
124def table(output_lines):
125 """Parse single table from cli output.
126
127 Return dict with list of column names in 'headers' key and
128 rows in 'values' key.
129 """
130 table_ = {'headers': [], 'values': []}
131 columns = None
132
133 if not isinstance(output_lines, list):
134 output_lines = output_lines.split('\n')
135
Pavel Sedlák0d9a84f2013-08-27 19:09:26 +0200136 if not output_lines[-1]:
137 # skip last line if empty (just newline at the end)
138 output_lines = output_lines[:-1]
139
Pavel Sedlák5ce5c032013-02-25 18:41:30 +0100140 for line in output_lines:
141 if delimiter_line.match(line):
142 columns = _table_columns(line)
143 continue
144 if '|' not in line:
145 LOG.warn('skipping invalid table line: %s' % line)
146 continue
147 row = []
148 for col in columns:
149 row.append(line[col[0]:col[1]].strip())
150 if table_['headers']:
151 table_['values'].append(row)
152 else:
153 table_['headers'] = row
154
155 return table_
156
157
158def _table_columns(first_table_row):
159 """Find column ranges in output line.
160
Chang Bo Guocc1623c2013-09-13 20:11:27 -0700161 Return list of tuples (start,end) for each column
Pavel Sedlák5ce5c032013-02-25 18:41:30 +0100162 detected by plus (+) characters in delimiter line.
163 """
164 positions = []
165 start = 1 # there is '+' at 0
166 while start < len(first_table_row):
167 end = first_table_row.find('+', start)
168 if end == -1:
169 break
170 positions.append((start, end))
171 start = end + 1
172 return positions