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