summaryrefslogtreecommitdiffstats
path: root/func/yaml/stream.py
blob: dcd65c3429c7fe23ad2703d90ab3befee3b8ef58 (plain)
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
"""

pyyaml legacy

Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved

(see open source license information in docs/ directory)

"""

import re
import string

def indentLevel(line):
    n = 0
    while n < len(line) and line[n] == ' ':
        n = n + 1
    return n

class LineNumberStream:
    def __init__(self, filename=None):
        self.curLine = 0
        self.filename = filename

    def get(self):
        line = self.getLine()
        self.curLine += 1 # used by subclass

        if line:
            line = noLineFeed(line)
        return line

    def lastLineRead(self):
        return self.curLine

class FileStream(LineNumberStream):
    def __init__(self, filename):
        self.fp = open(filename)
        LineNumberStream.__init__(self, filename)

    def getLine(self):
        line = self.fp.readline()
        if line == '': line = None
        return line

class StringStream(LineNumberStream):
    def __init__(self, text):
        self.lines = split(text)
        self.numLines = len(self.lines)
        LineNumberStream.__init__(self)

    def getLine(self):
        if self.curLine < self.numLines:
            return self.lines[self.curLine]

def split(text):
    lines = string.split(text, '\n')
    if lines[-1] == '':
        lines.pop()
    return lines

def eatNewLines(stream):
    while 1:
       line = stream.get()
       if line is None or len(string.strip(line)):
           return line

COMMENT_LINE_REGEX = re.compile(R"\s*#")
def isComment(line):
    return line is not None and COMMENT_LINE_REGEX.match(line)

class CommentEater:
    def __init__(self, stream):
        self.stream = stream
        self.peeked = 1
        self.line = eatNewLines(stream)
        self.eatComments()

    def eatComments(self):
        while isComment(self.line):
            self.line = self.stream.get()

    def peek(self):
        if self.peeked:
            return self.line
        self.peeked = 1
        self.line = self.stream.get()
        self.eatComments()
        return self.line

    def lastLineRead(self):
        return self.stream.lastLineRead()

    def pop(self):
        data = self.peek()
        self.peeked = 0
        return data

class NestedText:
    def __init__(self, stream):
        self.commentEater = CommentEater(stream)
        self.reset()

    def lastLineRead(self):
        return self.commentEater.lastLineRead()

    def reset(self):
        self.indentLevel = 0
        self.oldIndents = [0]

    def peek(self):
        nextLine = self.commentEater.peek()
        if nextLine is not None:
            if indentLevel(nextLine) >= self.indentLevel:
                return nextLine[self.indentLevel:]
            elif nextLine == '':
                return ''                

    def pop(self):
        line = self.peek()
        if line is None:
            self.indentLevel = self.oldIndents.pop()
            return
        self.commentEater.pop()
        return line

    def popNestedLines(self):
        nextLine = self.peek()
        if nextLine is None or nextLine == '' or nextLine[0] != ' ':
            return []
        self.nestToNextLine()
        lines = []
        while 1:
            line = self.pop()
            if line is None:
                break
            lines.append(line)
        return lines

    def nestToNextLine(self):
        line = self.commentEater.peek()
        indentation = indentLevel(line)
        if len(self.oldIndents) > 1 and indentation <= self.indentLevel:
            self.error("Inadequate indentation", line)
        self.setNewIndent(indentation)

    def nestBySpecificAmount(self, adjust):
        self.setNewIndent(self.indentLevel + adjust)
        
    def setNewIndent(self, indentLevel):
        self.oldIndents.append(self.indentLevel)
        self.indentLevel = indentLevel    

class YamlLoaderException(Exception):
    def __init__(self, *args):
        (self.msg, self.lineNum, self.line, self.filename) = args

    def __str__(self):
        msg = """\

%(msg)s:

near line %(lineNum)d:

%(line)s

""" % self.__dict__
        if self.filename:
            msg += "file: " + self.filename
        return msg

class NestedDocs(NestedText):
    def __init__(self, stream):
        self.filename = stream.filename
        NestedText.__init__(self,stream)
        line = NestedText.peek(self)
        self.sep = '---'
        if self.startsWithSep(line):
            self.eatenDocSep = NestedText.pop(self)
        else:
            self.eatenDocSep = self.sep

    def startsWithSep(self,line):
        if line and self.sep == line[:3]: return 1
        return 0

    def popDocSep(self):
        line = self.eatenDocSep
        self.eatenDocSep = None
        self.reset()
        return line

    def pop(self):
        if self.eatenDocSep is not None:
            raise "error"
        line = self.commentEater.peek()
        if line and self.startsWithSep(line):
            self.eatenDocSep = NestedText.pop(self)
            return None
        return NestedText.pop(self)

    def error(self, msg, line):
        raise YamlLoaderException(msg, self.lastLineRead(), line, self.filename)

def noLineFeed(s):
    while s[-1:] in ('\n', '\r'):
        s = s[:-1]
    return s