1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
'''
Copyright (C) 2020 by Jeremy Tan
Distributed under the original FontForge BSD 3-clause license.
Based on the docutils table code, which is released into the public domain.
-------------------------------------------------------------------------------
Adds (HTML only) support for a flex-grid directive that allows writing
out elements in a generic grid fashion.
Example:
.. flex-grid::
:class: custom_class
* - Row 1 Col 1
- Row 1 Col 2
- Row 1 col 3
* :flex-widths: 1 2
- Row 2 Col 1
- Row 2 Col 2
The syntax is very similar to the docutils list-table directive. However the
number of columns per row does *not* need to be homogeneous.
The flex-widths parameter is specified on a per-row basis. It should be a list
of numbers whose length is equal to the number of columns in that row. It
represents the ratio of space taken by that column. For example, with two
columns and a flex-widths of '1 2', the proportion of space taken is 1:2
between the first and second columns. A value of 0 disables flexing that
column.
'''
from docutils import nodes, statemachine
from docutils.utils import SystemMessagePropagation
from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives
class flex_grid(nodes.General, nodes.Element): pass
class flex_row(nodes.Part, nodes.Element): pass
class flex_col(nodes.Part, nodes.Element): pass
class FlexGrid(Directive):
final_argument_whitespace = True
option_spec = {'class': directives.class_option, 'name': directives.unchanged}
has_content = True
valid_flexes = set((0, 1, 2, 3, 4, 5))
def run(self):
if not self.content:
error = self.state_machine.reporter.error(
'The "%s" directive is empty; content required.' % self.name,
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno)
return [error]
node = nodes.Element() # anonymous container for parsing
self.state.nested_parse(self.content, self.content_offset, node)
try:
grid_data = self.get_grid(node)
except SystemMessagePropagation as detail:
return [detail.args[0]]
grid_node = self.build_grid_node(grid_data)
return [grid_node]
def get_grid(self, node):
if len(node) != 1 or not isinstance(node[0], nodes.bullet_list):
error = self.state_machine.reporter.error(
'Error parsing content block for the "%s" directive: '
'exactly one bullet list expected.' % self.name,
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno)
raise SystemMessagePropagation(error)
grid = []
list_node = node[0]
for item_index in range(len(list_node)):
item = list_node[item_index]
nitems = len(item)
if not nitems or nitems > 2 or not isinstance(item[-1], nodes.bullet_list):
error = self.state_machine.reporter.error(
'Error parsing content block for the "%s" directive: '
'two-level bullet list expected, but row %s does not '
'contain a second-level bullet list.'
% (self.name, item_index + 1), nodes.literal_block(
self.block_text, self.block_text), line=self.lineno)
raise SystemMessagePropagation(error)
elif nitems == 2 and not isinstance(item[0], nodes.field_list):
error = self.state_machine.reporter.error(
'Error parsing content block for the "%s" directive: '
'expected options as a field list, but row %s does not '
'start with a field list.'
% (self.name, item_index + 1), nodes.literal_block(
self.block_text, self.block_text), line=self.lineno)
raise SystemMessagePropagation(error)
if nitems == 1:
row = ({}, [col.children for col in item[0]])
else:
opts = {}
for opt in item[0]:
key = opt[0].rawsource
value = opt[1].rawsource.strip()
if key != 'flex-widths':
error = self.state_machine.reporter.error(
'Error parsing content block for the "%s" directive: '
'received "%s" but only "flex-widths" is supported as '
'an option on row %s.'
% (self.name, key, item_index + 1), nodes.literal_block(
self.block_text, self.block_text), line=self.lineno)
raise SystemMessagePropagation(error)
try:
sep = ',' if ',' in value else ' '
fwidths = [int(x) for x in value.split(sep)]
if any(x not in self.valid_flexes for x in fwidths) or len(fwidths) != len(item[1]):
raise ValueError('invalid flex widths')
opts['flex-widths'] = fwidths
except:
error = self.state_machine.reporter.error(
'Error parsing content block for the "%s" directive: '
'"flex-widths" expects a list of %s widths with possible values of (1,2,3,4,5).'
'The input "%s" for row %s is malformed.'
% (self.name, len(item[1]), value, item_index + 1), nodes.literal_block(
self.block_text, self.block_text), line=self.lineno)
raise SystemMessagePropagation(error)
row = (opts, [col.children for col in item[1]])
grid.append(row)
return grid
def build_grid_node(self, grid_data):
grid = flex_grid()
self.add_name(grid)
grid['classes'] += ['flex-container']
grid['classes'] += self.options.get('class', [])
for row_opts, row in grid_data:
row_node = flex_row()
row_node['classes'] += ['flex-row']
widths = row_opts.get('flex-widths')
for i, cell in enumerate(row):
entry = flex_col()
entry['classes'] += ['flex-col']
if widths:
width = widths[i]
if width == 0:
entry['classes'] += ['flex-none']
elif width != 1:
entry['classes'] += ['flex-{}'.format(width)]
entry += cell
row_node += entry
grid += row_node
return grid
def visit_flex_node(self, node):
self.body.append(self.starttag(node, 'div'))
def depart_flex_node(self, node):
self.body.append('</div>\n')
def setup(app):
app.add_directive('flex-grid', FlexGrid)
app.add_node(flex_grid, html=(visit_flex_node, depart_flex_node))
app.add_node(flex_row, html=(visit_flex_node, depart_flex_node))
app.add_node(flex_col, html=(visit_flex_node, depart_flex_node))
return {
'version': '1.0.0',
'env_version': 1,
'parallel_read_safe': True
}
|