usfmtc 0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
usfmtc/reference.py ADDED
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Optional, List, Tuple
4
+ from dataclasses import dataclass
5
+ import re
6
+
7
+ @dataclass
8
+ class MarkerRef:
9
+ mrkr: str
10
+ index: Optional[int] = None
11
+ word: Optional[int] = None
12
+ char: Optional[str] = None
13
+
14
+ def __eq__(self, context:'MarkerRef'):
15
+ if self.mrkr != context.mrkr:
16
+ return False
17
+ elif self.index != context.index:
18
+ return False
19
+ elif self.word != context.word:
20
+ return False
21
+ elif self.char != context.char:
22
+ return False
23
+
24
+ def __str__(self):
25
+ return self.str(force=True)
26
+
27
+ def str(self, context: Optional['MarkerRef']=None, force: bool=False):
28
+ res = []
29
+ if force or context is None or context.mrkr != self.mrkr or context.index != self.index:
30
+ res.append("!"+self.mrkr)
31
+ if self.index is not None:
32
+ res.append("[" + str(self.index) + "]")
33
+ if self.word is not None and (force or context is None or context.word != self.word):
34
+ if len(res):
35
+ res.append("!")
36
+ res.append(str(self.word))
37
+ if self.char is not None and (force or context is None or context.char != self.char):
38
+ if len(res):
39
+ res.append("+")
40
+ res.append(str(self.char))
41
+ return "".join(res)
42
+
43
+
44
+ _regexes = {
45
+ "book": r"""(?P<transid>(?:[a-z0-9_-]*[+])*)
46
+ (?P<book>(?:[A-Z][A-Z0-9][A-Z0-9]|[0-9](?:[A-Z][A-Z]|[0-9][A-Z]|[A-Z][0-9])))
47
+ \s*{chap}""",
48
+ "id": r"(?:[a-z][a-z0-9_-]*[a-z0-9]?)",
49
+ "charref": r"(?:(?:[0-9]|end)[+]?)",
50
+ "wordrefanon": r"",
51
+ "mrkref": r"(?:\!{id}(?:\[[0-9]\])?(?:\!(?:[0-9|end)(?:[+]{charref})?)?)",
52
+ "wordrefonly": r"(?P<word>[0-9]|end)(?P<char>[+]{charref})?",
53
+ "wordref1": r"""(?:(?: \!(?P<word1>[0-9]|end)(?:[+](?P<char1>{charref}))?
54
+ |(?P<mrk1>{mrkref}))
55
+ (?P<mrkn1>{mrkref}*))""",
56
+ "wordref2": r"""(?:(?: \!(?P<word2>[0-9]|end)(?:[+](?P<char2>{charref}))?
57
+ |(?P<mrk2>{mrkref}))
58
+ (?P<mrkn2>{mrkref}*))""",
59
+ "verse1": r"(?P<verse1>[0-9]+)(?P<subv1>[a-z]?|end)",
60
+ "verse2": r"(?P<verse2>[0-9]+)(?P<subv2>[a-z]?|end)",
61
+ "chapter": r"(?:(?P<chap>[0-9]+))",
62
+ "chap": r"{chapter}(?:[:.]{verse1})?{wordref1}?",
63
+ "context": r"""(?:{chap}
64
+ | {verse2}{wordref2}? | {wordrefonly}(?P<mrkn3>{mrkref}*)
65
+ | (?P<char3>{charref})(?P<mrkn4>{mrkref}*))(?=[-;,\s]|$)"""
66
+ }
67
+
68
+ regexes = _regexes
69
+ for i in range(3):
70
+ regexes = {k: v.format(**regexes) for k, v in regexes.items()}
71
+
72
+ def intend(s: str) -> Optional[int]:
73
+ if not s:
74
+ return None
75
+ elif s == "end":
76
+ return -1
77
+ return int(s)
78
+
79
+ def strend(s: int) -> str:
80
+ if s == -1:
81
+ return "end"
82
+ else:
83
+ return str(s)
84
+
85
+ def parse_wordref(s: str) -> Tuple[int, Optional[str]]:
86
+ b = s.split("+", 1)
87
+ word = int(b[0])
88
+ char = "+" + b[1] if len(b) > 1 else None
89
+ return (word, char)
90
+
91
+ _reindex = re.compile(r"([0-9a-z_-]+)(?:\[([0-9]+\]))?")
92
+ def asmarkers(s: str, t: str) -> List[MarkerRef]:
93
+ res = []
94
+ if s is None or not len(s):
95
+ return res
96
+ b = s.split("!")
97
+ b += t.split("!")
98
+ i = 0
99
+ while i < len(b):
100
+ if not len(b[i]):
101
+ i += 1
102
+ continue
103
+ if not (m := _reindex.match(b[i])):
104
+ raise SyntaxError("Badly formed marker reference {} in {}".format(b, s))
105
+ mrkr = m.group(1)
106
+ ind = int(m.group(2)) if m.group(2) else None
107
+ if i < len(b) - 1 and len(b[i+1]) and b[i+1][0] in "0123456789":
108
+ word, charref = parse_wordref(b[i+1])
109
+ i += 1
110
+ else:
111
+ word = None
112
+ charref = None
113
+ res.append(MarkerRef(mrkr, index=ind, word=word, char=charref))
114
+ i += 1
115
+ return res if len(res) else None
116
+
117
+
118
+ class Ref:
119
+ rebook = re.compile(regexes["book"], re.X)
120
+ recontext = re.compile(regexes["context"], re.X)
121
+
122
+ def __init__(self, string: Optional[str] = None,
123
+ context: Optional['Ref'] = None, start: int = 0, **kw):
124
+ parmlist = ('product', 'book', 'chapter', 'verse', 'subverse', 'word', 'char', 'mrkrs')
125
+ if string is not None:
126
+ self.parse(string, context=context, start=start)
127
+ elif context is not None:
128
+ for a in parmlist:
129
+ setattr(self, a, kw.get(a, getattr(context, a, None)))
130
+ else:
131
+ for a in parmlist:
132
+ setattr(self, a, kw.get(a, None))
133
+
134
+ def parse(self, s: str, context: Optional['Ref'] = None, start: int = 0):
135
+ p = {}
136
+ if m := self.rebook.match(s, pos=start):
137
+ p['product'] = m.group('transid') or None
138
+ p['book'] = m.group('book')
139
+ p['chapter'] = int(m.group('chap')) if m.group('chap') else None
140
+ p['verse'] = intend(m.group('verse1'))
141
+ p['subverse'] = m.group('subv1') or None
142
+ p['word'] = intend(m.group('word1'))
143
+ p['char'] = intend(m.group('char1'))
144
+ p['mrkrs'] = asmarkers(m.group("mrk1"), m.group("mrkn1"))
145
+ elif not (m:= self.recontext.match(s, pos=start)):
146
+ raise SyntaxError("Cannot parse {}".format(s))
147
+ elif m.group('verse1'): # we know we have a chapter and verse
148
+ p['chapter'] = int(m.group('chap'))
149
+ p['verse'] = intend(m.group('verse1'))
150
+ p['subverse'] = m.group('subv1') or None
151
+ p['word'] = intend(m.group('word1'))
152
+ p['char'] = intend(m.group('char1'))
153
+ p['mrkrs'] = asmarkers(m.group("mrk1"), m.group("mrkn1"))
154
+ elif m.group('subv2'):
155
+ p['verse'] = intend(m.group('verse2'))
156
+ p['subverse'] = m.group('subv2') or None
157
+ p['word'] = intend(m.group('word2'))
158
+ p['char'] = intend(m.group('char2'))
159
+ p['mrkrs'] = asmarkers(m.group("mrk2"), m.group("mrkn2"))
160
+ elif m.group('word'):
161
+ p['word'] = intend(m.group('word'))
162
+ p['char'] = intend(m.group('char'))
163
+ p['mrkrs'] = asmarkers("", m.group('mrkn3'))
164
+ elif m.group('char3'):
165
+ p['char'] = intend(m.group('char3'))
166
+ p['mrkrs'] = asmarkers("", m.group('mrkn4'))
167
+ elif not m.group('chap') and m.group('word1'):
168
+ p['word'] = intend(m.group('word1'))
169
+ p['char'] = intend(m.group('char1'))
170
+ p['mrkrs'] = asmarkers(m.group('mrk1'), m.group('mrkn1'))
171
+ elif context is not None and context.char:
172
+ p['char'] = intend(m.group('chap'))
173
+ elif context is not None and context.word:
174
+ p['word'] = intend(m.group('chap'))
175
+ p['char'] = intend(m.group('char1'))
176
+ elif context is not None and context.verse:
177
+ p['verse'] = intend(m.group('chap'))
178
+ p['word'] = intend(m.group('word1'))
179
+ p['char'] = intend(m.group('char1'))
180
+ else:
181
+ p['chapter'] = int(m.group('chap'))
182
+ p['word'] = intend(m.group('word1'))
183
+ p['char'] = intend(m.group('char1'))
184
+ self.strend = m.end(0)
185
+ self.__init__(None, context, **p)
186
+
187
+ def __str__(self):
188
+ return self.str()
189
+
190
+ def str(self, context: Optional['Ref'] = None, force: bool = False):
191
+ if context is None:
192
+ context = Ref()
193
+ res = []
194
+ sep = ''
195
+ if context.product != self.product:
196
+ res.append(self.product)
197
+ res.append('.')
198
+ force = True
199
+ if force or context.book != self.book:
200
+ res.append(self.book)
201
+ res.append(' ')
202
+ force = True
203
+ if force or context.chapter != self.chapter:
204
+ res.append(str(self.chapter))
205
+ sep = ':'
206
+ if self.verse is not None and (force or context.verse != self.verse):
207
+ if len(res):
208
+ res.append(sep)
209
+ res.append(strend(self.verse))
210
+ res.append(self.subverse or "")
211
+ sep = "!"
212
+ if self.word is not None and (force or context.word != self.word):
213
+ if len(res):
214
+ res.append(sep)
215
+ res.append(strend(self.word))
216
+ sep = "+"
217
+ if self.char is not None and (force or context.char != self.char):
218
+ if len(res):
219
+ res.append(sep)
220
+ res.append(strend(self.char))
221
+ sep = "!"
222
+ if self.mrkrs is not None and len(self.mrkrs):
223
+ forcemkr = False
224
+ for i, m in enumerate(self.mrkrs):
225
+ if not forcemkr and context is not None and i < len(context.mrkrs):
226
+ if context.mrkrs[i] != m:
227
+ res.append(m.str(context=contex.mrkrs[i], force=force or len(res)))
228
+ forcemkr = True
229
+ elif force or forcemkr:
230
+ res.append(m.str(force=True))
231
+ return "".join(res)
232
+
233
+ @property
234
+ def first(self):
235
+ return self
236
+
237
+ @property
238
+ def last(self):
239
+ return self
240
+
241
+ class RefRange:
242
+ def __init__(self, first: Optional[Ref] = None, last: Optional[Ref] = None):
243
+ self.first = first
244
+ self.last = last
245
+
246
+ def str(self, context: Optional[Ref] = None, force: bool = False):
247
+ res = [self.first.str(context, force=force)]
248
+ res.append("-")
249
+ res.append(self.last.str(self.first, force=force))
250
+ return "".join(res)
251
+
252
+ def __str__(self):
253
+ return self.str()
254
+
255
+
256
+ class RefList(List):
257
+ def __init__(self, content: str | List[Ref | RefRange], context: Optional[Ref] = None, start: int = 0):
258
+ if isinstance(content, list):
259
+ super().__init__(content)
260
+ else:
261
+ self.parse(content, context, start=start)
262
+
263
+ def parse(self, s: str, context: Optional[Ref] = None, start: int = 0):
264
+ bits = re.split(r"\s*[,;]\s*", s[start:])
265
+ res = []
266
+ for b in bits:
267
+ first = Ref(b, context=context)
268
+ nextstart = first.strend
269
+ if not (m := re.match(r"\s*-\s*", s[nextstart:])):
270
+ if nextstart < len(b):
271
+ raise SyntaxError("Bad range in {}".format(s[start:]))
272
+ else:
273
+ res.append(first)
274
+ context = first
275
+ else:
276
+ nextstart += m.end()
277
+ last = Ref(b, context=first, start=nextstart)
278
+ res.append(RefRange(first, last))
279
+ context = last
280
+ self.__init__(res)
281
+
282
+ def __str__(self):
283
+ return "; ".join(str(s) for s in self)
284
+
285
+ def str(self, context: Optional[Ref] = None, force: bool = False):
286
+ res = []
287
+ for r in self:
288
+ res.append(r.str(context, force=force))
289
+ context = r.last
290
+ return "; ".join(res)
291
+
292
+
293
+ def main():
294
+ import sys
295
+
296
+ if len(sys.argv) > 1:
297
+ s = " ".join(sys.argv[1:])
298
+ res = RefList(s)
299
+ print(res.str(force=True))
300
+
301
+ if __name__ == "__main__":
302
+ main()
@@ -0,0 +1,13 @@
1
+ from . import parser, serializer
2
+
3
+ def load(f):
4
+ return parser.parse(f=f)
5
+
6
+ def loads(src):
7
+ return parser.parse(src)
8
+
9
+ def dump(root, f, indent=None):
10
+ f.write(serializer.XMLSerializer(indent).toxml(root))
11
+
12
+ def dumps(root, indent=None):
13
+ return serializer.XMLSerializer(indent).toxml(root)