blob: 840839b2bc4c7f4afb7fd677ee47128cba64474c [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
21import logging
22import re
23
24
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
136 for line in output_lines:
137 if delimiter_line.match(line):
138 columns = _table_columns(line)
139 continue
140 if '|' not in line:
141 LOG.warn('skipping invalid table line: %s' % line)
142 continue
143 row = []
144 for col in columns:
145 row.append(line[col[0]:col[1]].strip())
146 if table_['headers']:
147 table_['values'].append(row)
148 else:
149 table_['headers'] = row
150
151 return table_
152
153
154def _table_columns(first_table_row):
155 """Find column ranges in output line.
156
157 Return list of touples (start,end) for each column
158 detected by plus (+) characters in delimiter line.
159 """
160 positions = []
161 start = 1 # there is '+' at 0
162 while start < len(first_table_row):
163 end = first_table_row.find('+', start)
164 if end == -1:
165 break
166 positions.append((start, end))
167 start = end + 1
168 return positions