19
19
except ImportError :
20
20
exit ('[table-builder]: You must install PyYAML to build tables.' )
21
21
22
- # global variables
23
- columnwidths = [] # the column widths to use in get_rowline() and get_row() methods
24
- tempcolumnwidths = [] # working list to determine columnwidths global variable
25
- # tempcolumnwidths keeps the running max value for each cell
26
-
27
- ###################################
28
- #
29
- # Generating parts of the table.
30
-
31
- def get_row_line (delim = '-' ):
32
- """
33
- Prints lines to seperate rows in the column for restrucutred text
34
- format.
35
- """
36
- return '+' + delim + str (delim + '+' + delim ).join ([ delim * width for width in columnwidths ]) + delim + '+'
37
-
38
- def get_row (rowdata ):
39
- """
40
- Prints the contents of the row, seperated as needed for
41
- restructured text format.
42
- """
43
- rowlines = []
44
- for line in zip (* rowdata ):
45
- if len (rowlines ) > 0 :
46
- rowlines .append ('\n ' )
47
- rowlines .append ( '| ' + ' | ' .join ([ line [idx ].ljust (columnwidths [idx ]) for idx in range (len (line )) ]) + ' |' )
48
-
49
- return '' .join (rowlines )
50
-
51
- ###################################
52
- #
53
- # Flexible table building processing.
54
-
55
22
def normalize_cell_height (rowdata ):
56
23
"""
57
24
Normalize cells in the rowdata so that each cell has the same height;
58
25
more specifically, ensure that each cell data list spans the same number of
59
- lines when printed as others in the row.
26
+ lines when printed as others in the row. Mutates 'rowdata' in place by only
27
+ appeneding rather than resetting the reference.
60
28
"""
61
-
62
- # This function mutates the rowdata object, which is passed by
63
- # reference.. By appending to rowdata content as opposed to
64
- # resetting the reference (i.e. setting it equal to something
65
- # else) the method modifies whatever was referenced by rowdata and
66
- # does not need to explicitly return something
67
-
68
29
maxlines = max ([ len (cell ) for cell in rowdata ])
69
30
70
31
for cell in rowdata :
71
- for x in range (maxlines - len (cell )):
32
+ for x in range (maxlines - len (cell )):
72
33
cell .append ( ' ' )
73
34
74
- def check_column_width (rowdata ):
75
- """
76
- Compares the cell widths of the row with the curren max cell width
77
- in the global variables. Then updates the global values in the
78
- variable ``tempcolumnwidths``. ``tempcolumnwidths`` variable maintains
79
- the running max width of each column.
35
+ def get_default_outputfile (inputfile ):
36
+ return inputfile .rsplit ('.' )[0 ] + '.rst'
80
37
81
- By appending to the variable, the global
82
- scope of tempcolumnwidths is maintained.
83
- """
84
38
85
- thisrowwidths = [ max ([len (line ) for line in cell ]) for cell in rowdata ]
39
+ ###################################
40
+ #
41
+ # Generating parts of the table.
86
42
87
- if len ( tempcolumnwidths ) == 0 :
88
- tempcolumnwidths .append (thisrowwidths )
89
- else :
90
- currentmaxwidths = tempcolumnwidths .pop ()
91
- tempcolumnwidths .append ([ max (currentmaxwidths [i ], thisrowwidths [i ]) for i in range (len (currentmaxwidths ))])
43
+ class RstTable (object ):
44
+ def __init__ (self , inputfile ):
45
+ self .columnwidths = []
46
+ self .tempcolumnwidths = []
47
+ self .read_data (inputfile )
48
+ self .process_table_content ()
49
+
50
+ def read_data (self , datafile ):
51
+ with open (datafile , "rt" ) as f :
52
+ parsed = yaml .load_all (f )
53
+ layout = dict2obj (parsed .next ())
54
+ meta = dict2obj (parsed .next ())
55
+ content = dict2obj (parsed .next ())
56
+
57
+ if layout .section != 'layout' :
58
+ exit ('layout document in "' + datafile + '" is malformed.' )
59
+ elif meta .section != 'meta' :
60
+ exit ('meta document in "' + datafile + '" is malformed.' )
61
+ elif content .section != 'content' :
62
+ exit ('content document in "' + datafile + '" is malformed.' )
63
+
64
+ rows = { 'rows' : [] }
65
+
66
+ if layout .header :
67
+ header = []
68
+ for cell in layout .header :
69
+ header .append ([eval (cell )])
70
+ else :
71
+ header = None
72
+
73
+ for rownum in layout .rows :
74
+ parsed_cell = []
75
+ for cell in rownum .items ()[0 ][1 ]:
76
+ parsed_cell .append (eval (cell ))
77
+ rows ['rows' ].append ( dict (zip (rownum .keys (), [parsed_cell ])) )
78
+
79
+ # return header, rows
80
+ self .header = header
81
+ self .rows = rows
82
+
83
+ ###################################
84
+ #
85
+ # Flexibility for tables of different sizes.
86
+
87
+ def check_column_width (self , rowdata ):
88
+ """
89
+ Compares the cell widths of the row with the curren max cell
90
+ width in the global variables. Then updates
91
+ ``tempcolumnwidths``. ``tempcolumnwidths`` variable maintains
92
+ the running max width of each column.
93
+ """
94
+
95
+ thisrowwidths = [ max ([len (line ) for line in cell ]) for cell in rowdata ]
96
+
97
+ if len (self .tempcolumnwidths ) == 0 :
98
+ self .tempcolumnwidths .append (thisrowwidths )
99
+ else :
100
+ currentmaxwidths = self .tempcolumnwidths .pop ()
101
+ self .tempcolumnwidths .append ([ max (currentmaxwidths [i ], thisrowwidths [i ]) for i in range (len (currentmaxwidths ))])
102
+
103
+
104
+ ###################################
105
+ #
106
+ # Building the table representation
107
+
108
+ def get_row_line (self , delim = '-' ):
109
+ """
110
+ Produces and returns row deliminiters for restructured text tables.
111
+ """
112
+ return '+' + delim + str (delim + '+' + delim ).join ([ delim * width for width in self .columnwidths ]) + delim + '+'
113
+
114
+ def get_row (self , rowdata ):
115
+ """
116
+ Returns rows given ``rowdata`` properly formated for restructured text tables.
117
+ """
118
+ rowlines = []
119
+ for line in zip (* rowdata ):
120
+ if len (rowlines ) > 0 :
121
+ rowlines .append ('\n ' )
122
+ rowlines .append ( '| ' + ' | ' .join ([ line [idx ].ljust (self .columnwidths [idx ]) for idx in range (len (line )) ]) + ' |' )
123
+
124
+ return '' .join (rowlines )
125
+
126
+ def process_table_content (self ):
127
+ self .tabledata = []
128
+
129
+ # Compare cell widths of the header with the
130
+ # max cell widths stored in the global var tempcolumnwidths
131
+ # and swap out value(s) if necessary.
132
+ if self .header is not None :
133
+ self .check_column_width (self .header )
134
+
135
+ for index in range (len (self .rows ['rows' ])):
136
+ parsed_row = []
137
+
138
+ # Append each cell to the parsed_row list, breaking multi-line
139
+ # cell data as needed.
140
+ for cell in self .rows ['rows' ][index ][index + 1 ]:
141
+ parsed_row .append (cell .split ('\n ' ))
142
+
143
+ # process the data to ensure the table is big enough.
144
+ self .check_column_width (parsed_row )
145
+ normalize_cell_height (parsed_row )
146
+
147
+ # add the processed data to the table
148
+ self .tabledata .append (parsed_row )
149
+
150
+ # Set the global variable columnwidths to the flattened out
151
+ # tempcolumnwidths
152
+ for cellwidth in self .tempcolumnwidths .pop ():
153
+ self .columnwidths .append (cellwidth )
92
154
93
155
###################################
94
156
#
@@ -109,114 +171,31 @@ def __getattr__(self, key):
109
171
110
172
return value
111
173
112
- def check_input_data (datafile , layout , meta , content ):
113
- if layout != 'layout' :
114
- exit ('layout document in "' + datafile + '" is malformed.' )
115
- elif meta != 'meta' :
116
- exit ('meta document in "' + datafile + '" is malformed.' )
117
- elif content != 'content' :
118
- exit ('content document in "' + datafile + '" is malformed.' )
119
-
120
- def read_data (datafile ):
121
- with open (datafile , "rt" ) as f :
122
- parsed = yaml .load_all (f )
123
- layout = dict2obj (parsed .next ())
124
- meta = dict2obj (parsed .next ())
125
- content = dict2obj (parsed .next ())
126
-
127
- check_input_data (datafile , layout .section , meta .section , content .section )
128
-
129
- rows = { 'rows' : [] }
130
-
131
- if layout .header :
132
- header = []
133
- for cell in layout .header :
134
- header .append ([eval (cell )])
135
- else :
136
- header = None
137
-
138
- for rownum in layout .rows :
139
- parsed_cell = []
140
- for cell in rownum .items ()[0 ][1 ]:
141
- parsed_cell .append (eval (cell ))
142
- rows ['rows' ].append ( dict (zip (rownum .keys (), [parsed_cell ])) )
143
-
144
- return header , rows
145
-
146
- def render_table (header , rows ):
147
- tabledata = []
148
-
149
- # Compare cell widths of the header with the
150
- # max cell widths stored in the global var tempcolumnwidths
151
- # and swap out value(s) if necessary.
152
- if header is not None :
153
- check_column_width (header )
154
-
155
- for index in range (len (rows ['rows' ])):
156
- parsed_row = []
157
-
158
- # Append each cell to the parsed_row list, breaking multi-line
159
- # cell data as needed.
160
- for cell in rows ['rows' ][index ][index + 1 ]:
161
- parsed_row .append (cell .split ('\n ' ))
162
-
163
- # process the data to ensure the table is big enough.
164
- check_column_width (parsed_row )
165
- normalize_cell_height (parsed_row )
166
-
167
- # add the processed data to the table
168
- tabledata .append (parsed_row )
169
-
170
- # Set the global variable columnwidths to the flattened out
171
- # tempcolumnwidths
172
- for cellwidth in tempcolumnwidths .pop ():
173
- columnwidths .append (cellwidth )
174
-
175
- output = []
176
- output .append (get_row_line ())
177
-
178
- if header is not None :
179
- output .append (get_row (header ))
180
- output .append (get_row_line ('=' ))
181
-
182
- for row in tabledata :
183
- output .append (get_row (row ))
184
- output .append (get_row_line ())
185
-
186
- return output
187
-
188
174
###################################
189
175
#
190
176
# Interaction
191
177
192
- def get_default_outputfile (inputfile ):
193
- return inputfile .rsplit ('.' )[0 ] + '.rst'
178
+ class YamlTableBuilder (RstTable ):
179
+ def __init__ (self , inputfile ):
180
+ self .inputfile = inputfile
181
+ super (YamlTableBuilder , self ).__init__ (inputfile )
182
+ self .output = self .render_table ()
194
183
195
- def cli ():
196
- # this is a total hack to avoid argparse. first argument is input,
197
- # second is output. we'll have to break down and use argparse if
198
- # we want any other options, just for sanity.
184
+ def render_table (self ):
185
+ o = []
186
+ o .append (self .get_row_line ())
199
187
200
- inputfile = sys .argv [1 ]
188
+ if self .header is not None :
189
+ o .append (self .get_row (self .header ))
190
+ o .append (self .get_row_line ('=' ))
201
191
202
- try :
203
- outputfile = sys .argv [2 ]
204
- except IndexError :
205
- outputfile = get_default_outputfile (inputfile )
192
+ for self .row in self .tabledata :
193
+ o .append (self .get_row (self .row ))
194
+ o .append (self .get_row_line ())
206
195
207
- return inputfile , outputfile
196
+ return o
208
197
209
- ###################################
210
- #
211
- # Interfaces.
212
-
213
- class YamlTableBuilder (object ):
214
- def __init__ (self , inputfile ):
215
- self .inputfile = inputfile
216
- self .table_header , self .table_rows = read_data (inputfile )
217
- self .output = render_table (self .table_header , self .table_rows )
218
-
219
- def write_file (self , outputfile = None ):
198
+ def write (self , outputfile = None ):
220
199
if outputfile is None :
221
200
outputfile = get_default_outputfile (self .inputfile )
222
201
@@ -228,12 +207,24 @@ def print_table(self):
228
207
for line in self .output :
229
208
print (line )
230
209
210
+ ###################################
211
+ #
212
+ # Interface.
213
+
231
214
def main ():
232
- inputfile , outputfile = cli ()
215
+ # the following is a total hack to avoid argparse. first argument
216
+ # is input, second is output. we'll have to break down and use
217
+ # argparse if we want any other options, just for sanity.
233
218
234
- table = YamlTableBuilder (inputfile )
219
+ inputfile = sys .argv [1 ]
220
+
221
+ try :
222
+ outputfile = sys .argv [2 ]
223
+ except IndexError :
224
+ outputfile = get_default_outputfile (inputfile )
235
225
236
- table .write_file (outputfile )
226
+ table = YamlTableBuilder (inputfile )
227
+ table .write (outputfile )
237
228
238
229
if __name__ == '__main__' :
239
230
main ()
0 commit comments