MoinLight

Annotated moinformat/tree.py

17:6b231d75d301
2017-05-01 Paul Boddie Added emphasis and strong formatting support.
paul@11 1
#!/usr/bin/env python
paul@11 2
paul@11 3
"""
paul@11 4
Moin wiki format document tree nodes.
paul@11 5
paul@11 6
Copyright (C) 2017 Paul Boddie <paul@boddie.org.uk>
paul@11 7
paul@11 8
This program is free software; you can redistribute it and/or modify it under
paul@11 9
the terms of the GNU General Public License as published by the Free Software
paul@11 10
Foundation; either version 3 of the License, or (at your option) any later
paul@11 11
version.
paul@11 12
paul@11 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@11 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@11 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@11 16
details.
paul@11 17
paul@11 18
You should have received a copy of the GNU General Public License along with
paul@11 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@11 20
"""
paul@11 21
paul@11 22
class Container:
paul@11 23
paul@11 24
    "A container of document nodes."
paul@11 25
paul@11 26
    def __init__(self, nodes):
paul@11 27
        self.nodes = nodes
paul@11 28
paul@11 29
    def append(self, node):
paul@11 30
        self.nodes.append(node)
paul@11 31
paul@15 32
    add = append
paul@15 33
paul@17 34
    append_inline = append
paul@11 35
paul@11 36
    def empty(self):
paul@11 37
        return not self.nodes
paul@11 38
paul@15 39
    def node(self, index):
paul@15 40
        try:
paul@15 41
            return self.nodes[index]
paul@15 42
        except IndexError:
paul@15 43
            return None
paul@14 44
paul@11 45
    def normalise(self):
paul@11 46
paul@11 47
        "Combine adjacent text nodes."
paul@11 48
paul@11 49
        nodes = self.nodes
paul@11 50
        self.nodes = []
paul@11 51
        text = None
paul@11 52
paul@11 53
        for node in nodes:
paul@11 54
paul@11 55
            # Open a text node or merge text into an open node.
paul@11 56
paul@11 57
            if isinstance(node, Text):
paul@11 58
                if not text:
paul@11 59
                    text = node
paul@11 60
                else:
paul@11 61
                    text.merge(node)
paul@11 62
paul@11 63
            # Close any open text node and append the current node.
paul@11 64
paul@11 65
            else:
paul@11 66
                if text:
paul@11 67
                    self.append(text)
paul@11 68
                    text = None
paul@11 69
                self.append(node)
paul@11 70
paul@11 71
        # Add any open text node.
paul@11 72
paul@11 73
        if text:
paul@11 74
            self.append(text)
paul@11 75
paul@11 76
    def __str__(self):
paul@11 77
        return self.prettyprint()
paul@11 78
paul@16 79
    def _prettyprint(self, l, indent=""):
paul@16 80
        for node in self.nodes:
paul@16 81
            l.append(node.prettyprint(indent + "  "))
paul@16 82
        return "\n".join(l)
paul@16 83
paul@16 84
    def _to_string(self, out):
paul@16 85
        for node in self.nodes:
paul@16 86
            node.to_string(out)
paul@11 87
paul@11 88
class Region(Container):
paul@11 89
paul@11 90
    "A region of the page."
paul@11 91
paul@11 92
    transparent_region_types = ["wiki"]
paul@11 93
paul@11 94
    def __init__(self, nodes, level=0, indent=0, type=None):
paul@11 95
        Container.__init__(self, nodes)
paul@11 96
        self.level = level
paul@11 97
        self.indent = indent
paul@11 98
        self.type = type
paul@11 99
paul@15 100
    def add(self, node):
paul@15 101
        last = self.node(-1)
paul@11 102
        if last and last.empty():
paul@11 103
            self.nodes[-1] = node
paul@11 104
        else:
paul@11 105
            self.nodes.append(node)
paul@11 106
paul@17 107
    def append_inline(self, s):
paul@11 108
        if self.is_transparent():
paul@11 109
            self.nodes[-1].append(s)
paul@11 110
        else:
paul@11 111
            self.append(s)
paul@11 112
paul@11 113
    def have_end(self, s):
paul@11 114
        return self.level and s.startswith("}") and self.level == len(s)
paul@11 115
paul@11 116
    def is_transparent(self):
paul@11 117
        return not self.level or self.type in self.transparent_region_types
paul@11 118
paul@11 119
    def __repr__(self):
paul@11 120
        return "Region(%r, %r, %r, %r)" % (self.nodes, self.level, self.indent, self.type)
paul@11 121
paul@11 122
    def prettyprint(self, indent=""):
paul@11 123
        l = ["%sRegion: level=%d indent=%d type=%s" % (indent, self.level, self.indent, self.type)]
paul@16 124
        return self._prettyprint(l, indent)
paul@11 125
paul@11 126
    def to_string(self, out):
paul@11 127
        out.start_region(self.level, self.indent, self.type)
paul@16 128
        self._to_string(out)
paul@11 129
        out.end_region(self.level, self.indent, self.type)
paul@11 130
paul@17 131
paul@17 132
paul@11 133
class Block(Container):
paul@11 134
paul@11 135
    "A block in the page."
paul@11 136
paul@11 137
    def __repr__(self):
paul@11 138
        return "Block(%r)" % self.nodes
paul@11 139
paul@11 140
    def prettyprint(self, indent=""):
paul@15 141
        l = ["%sBlock" % indent]
paul@16 142
        return self._prettyprint(l, indent)
paul@11 143
paul@11 144
    def to_string(self, out):
paul@15 145
        out.start_block()
paul@16 146
        self._to_string(out)
paul@15 147
        out.end_block()
paul@11 148
paul@16 149
class DefItem(Container):
paul@16 150
paul@16 151
    "A definition item."
paul@16 152
paul@16 153
    def __init__(self, nodes, pad, extra):
paul@16 154
        Container.__init__(self, nodes)
paul@16 155
        self.pad = pad
paul@16 156
        self.extra = extra
paul@16 157
paul@16 158
    def __repr__(self):
paul@16 159
        return "DefItem(%r, %r, %r)" % (self.nodes, self.pad, self.extra)
paul@16 160
paul@16 161
    def prettyprint(self, indent=""):
paul@16 162
        l = ["%sDefItem: pad=%r extra=%r" % (indent, self.pad, self.extra)]
paul@16 163
        return self._prettyprint(l, indent)
paul@16 164
paul@16 165
    def to_string(self, out):
paul@16 166
        out.start_defitem(self.pad, self.extra)
paul@16 167
        self._to_string(out)
paul@16 168
        out.end_defitem(self.pad, self.extra)
paul@16 169
paul@16 170
class DefTerm(Container):
paul@16 171
paul@16 172
    "A definition term."
paul@16 173
paul@16 174
    def __init__(self, nodes, pad):
paul@16 175
        Container.__init__(self, nodes)
paul@16 176
        self.pad = pad
paul@16 177
paul@16 178
    def __repr__(self):
paul@16 179
        return "DefTerm(%r, %r)" % (self.nodes, self.pad)
paul@16 180
paul@16 181
    def prettyprint(self, indent=""):
paul@16 182
        l = ["%sDefTerm: pad=%r" % (indent, self.pad)]
paul@16 183
        return self._prettyprint(l, indent)
paul@16 184
paul@16 185
    def to_string(self, out):
paul@16 186
        out.start_defterm(self.pad)
paul@16 187
        self._to_string(out)
paul@16 188
        out.end_defterm(self.pad)
paul@16 189
paul@17 190
class Emphasis(Container):
paul@17 191
paul@17 192
    "Emphasised text."
paul@17 193
paul@17 194
    def __repr__(self):
paul@17 195
        return "Emphasis(%r)" % self.nodes
paul@17 196
paul@17 197
    def prettyprint(self, indent=""):
paul@17 198
        l = ["%sEmphasis" % indent]
paul@17 199
        return self._prettyprint(l, indent)
paul@17 200
paul@17 201
    def to_string(self, out):
paul@17 202
        out.start_emphasis()
paul@17 203
        self._to_string(out)
paul@17 204
        out.end_emphasis()
paul@17 205
paul@13 206
class Heading(Container):
paul@13 207
paul@13 208
    "A heading."
paul@13 209
paul@13 210
    def __init__(self, nodes, level, start_extra="", start_pad="", end_pad="", end_extra=""):
paul@13 211
        Container.__init__(self, nodes)
paul@13 212
        self.level = level
paul@13 213
        self.start_extra = start_extra
paul@13 214
        self.start_pad = start_pad
paul@13 215
        self.end_pad = end_pad
paul@13 216
        self.end_extra = end_extra
paul@13 217
paul@13 218
    def __repr__(self):
paul@13 219
        return "Heading(%r, %d, %r, %r, %r, %r)" % (
paul@13 220
            self.nodes, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)
paul@13 221
paul@13 222
    def prettyprint(self, indent=""):
paul@13 223
        l = ["%sHeading: level=%d start_extra=%r start_pad=%r end_pad=%r end_extra=%r" % (
paul@13 224
                indent, self.level, self.start_extra, self.start_pad, self.end_pad, self.end_extra)]
paul@16 225
        return self._prettyprint(l, indent)
paul@13 226
paul@13 227
    def to_string(self, out):
paul@13 228
        out.start_heading(self.level, self.start_extra, self.start_pad)
paul@16 229
        self._to_string(out)
paul@13 230
        out.end_heading(self.level, self.end_pad, self.end_extra)
paul@13 231
paul@11 232
class ListItem(Container):
paul@11 233
paul@11 234
    "A list item."
paul@11 235
paul@14 236
    def __init__(self, nodes, indent, marker, space):
paul@14 237
        Container.__init__(self, nodes)
paul@14 238
        self.indent = indent
paul@14 239
        self.marker = marker
paul@14 240
        self.space = space
paul@14 241
paul@11 242
    def __repr__(self):
paul@14 243
        return "ListItem(%r, %r, %r, %r)" % (self.nodes, self.indent, self.marker, self.space)
paul@11 244
paul@11 245
    def prettyprint(self, indent=""):
paul@14 246
        l = ["%sListItem: indent=%d marker=%r space=%r" % (indent, self.indent, self.marker, self.space)]
paul@16 247
        return self._prettyprint(l, indent)
paul@11 248
paul@11 249
    def to_string(self, out):
paul@14 250
        out.start_listitem(self.indent, self.marker, self.space)
paul@16 251
        self._to_string(out)
paul@14 252
        out.end_listitem(self.indent, self.marker)
paul@11 253
paul@17 254
class Strong(Container):
paul@17 255
paul@17 256
    "Emboldened text."
paul@17 257
paul@17 258
    def __repr__(self):
paul@17 259
        return "Strong(%r)" % self.nodes
paul@17 260
paul@17 261
    def prettyprint(self, indent=""):
paul@17 262
        l = ["%sStrong" % indent]
paul@17 263
        return self._prettyprint(l, indent)
paul@17 264
paul@17 265
    def to_string(self, out):
paul@17 266
        out.start_strong()
paul@17 267
        self._to_string(out)
paul@17 268
        out.end_strong()
paul@17 269
paul@12 270
paul@12 271
paul@12 272
class Node:
paul@12 273
paul@12 274
    "A document node without children."
paul@12 275
paul@12 276
    def empty(self):
paul@12 277
        return False
paul@12 278
paul@15 279
class Break(Node):
paul@15 280
paul@15 281
    "A paragraph break."
paul@15 282
paul@15 283
    def __repr__(self):
paul@15 284
        return "Break()"
paul@15 285
paul@15 286
    def prettyprint(self, indent=""):
paul@15 287
        return "%sBreak" % indent
paul@15 288
paul@15 289
    def to_string(self, out):
paul@15 290
        out.break_()
paul@15 291
paul@12 292
class Rule(Node):
paul@12 293
paul@12 294
    "A horizontal rule."
paul@12 295
paul@12 296
    def __init__(self, length):
paul@12 297
        self.length = length
paul@12 298
paul@12 299
    def __repr__(self):
paul@12 300
        return "Rule(%d)" % self.length
paul@12 301
paul@12 302
    def prettyprint(self, indent=""):
paul@12 303
        return "%sRule: %d" % (indent, self.length)
paul@12 304
paul@12 305
    def to_string(self, out):
paul@12 306
        out.rule(self.length)
paul@12 307
paul@12 308
class Text(Node):
paul@11 309
paul@11 310
    "A text node."
paul@11 311
paul@11 312
    def __init__(self, s):
paul@11 313
        self.s = s
paul@11 314
paul@11 315
    def empty(self):
paul@11 316
        return not self.s
paul@11 317
paul@11 318
    def merge(self, text):
paul@11 319
        self.s += text.s
paul@11 320
paul@11 321
    def __repr__(self):
paul@11 322
        return "Text(%r)" % self.s
paul@11 323
paul@11 324
    def prettyprint(self, indent=""):
paul@11 325
        return "%sText: %r" % (indent, self.s)
paul@11 326
paul@11 327
    def to_string(self, out):
paul@11 328
        out.text(self.s)
paul@11 329
paul@11 330
# vim: tabstop=4 expandtab shiftwidth=4