def __init__(self, ctx): self.ctx = ctx scripts = set("postrotate prerotate firstaction lastaction preremove".split()) Stanza = Forward() Spaces = Many(WSChar) Bare = String(set(string.printable) - (set(string.whitespace) | set("#{}'\""))) Num = Number & (WSChar | LineEnd) Comment = OneLineComment("#").map(lambda x: None) ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS ScriptEnd = Literal("endscript") Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: "".join(x)) Lines = Line.until(ScriptEnd | EOF).map(lambda x: "\n".join(x)) Script = ScriptStart + Lines << Opt(ScriptEnd) Script = Script.map(lambda x: [x[0], [x[1]], None]) BeginBlock = WS >> LeftCurly << WS EndBlock = WS >> RightCurly First = PosMarker((Bare | QuotedString)) << Spaces Attr = Spaces >> (Num | Bare | QuotedString) << Spaces Rest = Many(Attr) Block = BeginBlock >> Many(Stanza).map(skip_none).map(self.to_entries) << EndBlock Stmt = WS >> (Script | (First + Rest + Opt(Block))) << WS Stanza <= WS >> (Stmt | Comment) << WS Doc = Many(Stanza).map(skip_none).map(self.to_entries) self.Top = Doc << EOF
def parse_doc(content, ctx): def to_directive(x): name, rest = x rest = [rest] if rest is not None else [] return Directive(name=name.value.strip(), attrs=rest, lineno=name.lineno, src=ctx) def to_section(name, rest): return Section(name=name.value.strip(), children=rest, lineno=name.lineno, src=ctx) def apply_defaults(cfg): if "DEFAULT" not in cfg: return cfg defaults = cfg["DEFAULT"] not_defaults = cfg[~eq("DEFAULT")] for c in not_defaults: for d in defaults.grandchildren: if d.name not in c: c.children.append(d) cfg.children = list(not_defaults) return cfg header_chars = (set(string.printable) - set(string.whitespace) - set("[]")) | set(" ") sep_chars = set("=:") key_chars = header_chars - sep_chars value_chars = set(string.printable) - set("\n\r") Yes = Literal("yes", True, ignore_case=True) No = Literal("no", False, ignore_case=True) Tru = Literal("true", True, ignore_case=True) Fals = Literal("false", False, ignore_case=True) Boolean = ((Yes | No | Tru | Fals) & (WSChar | LineEnd)) % "Boolean" LeftEnd = (WS + LeftBracket + WS) RightEnd = (WS + RightBracket + WS) Header = ( LeftEnd >> PosMarker(String(header_chars)) << RightEnd) % "Header" Key = WS >> PosMarker(String(key_chars)) << WS Sep = InSet(sep_chars, "Sep") Value = WS >> (Boolean | HangingString(value_chars)) KVPair = WithIndent(Key + Opt(Sep >> Value)) % "KVPair" Comment = (WS >> (OneLineComment("#") | OneLineComment(";")).map(lambda x: None)) Line = Comment | KVPair.map(to_directive) Sect = Lift(to_section) * Header * Many(Line).map(skip_none) Doc = Many(Comment | Sect).map(skip_none) Top = Doc + EOF res = Entry(children=Top(content)[0], src=ctx) return apply_defaults(res)
def __init__(self, sep_chars="=:", comment_chars="#;"): eol_chars = set("\n\r") sep_chars = set(sep_chars) comment_chars = set(comment_chars) key_chars = set(string.printable) - (sep_chars | eol_chars | comment_chars) value_chars = set(string.printable) - (eol_chars | comment_chars) OLC = reduce(operator.__or__, [OneLineComment(c) for c in comment_chars]) Comment = (WS >> OLC).map(lambda x: None) Num = Number & (WSChar | LineEnd) Key = WS >> PosMarker(String(key_chars).map(lambda x: x.strip())) << WS Sep = InSet(sep_chars) Value = WS >> (Num | String(value_chars).map(lambda x: x.strip())) KVPair = (Key + Opt(Sep + Value, default=[None, None])).map(lambda a: (a[0], a[1][1])) Line = Comment | KVPair | EOL.map(lambda x: None) Doc = Many(Line).map(skip_none).map(to_entry) self.Top = Doc + EOF
def parse_doc(f, ctx=None, line_end="\n"): def to_entry(name, rest): rest = [] if not rest else [rest] return Directive(name=name.value, attrs=rest, lineno=name.lineno, src=ctx) Sep = Char("=") Stmt = Forward() Num = Number & (WSChar | LineEnd) Comment = (WS >> OneLineComment("#").map(lambda x: None)) Bare = String(set(string.printable) - (set("#\n\r"))) Name = WS >> PosMarker(String(string.ascii_letters + "_-" + string.digits)) << WS Value = WS >> (Num | Bare) << WS Stanza = (Lift(to_entry) * Name * (Opt(Sep >> Value))) | Comment Stmt <= WS >> Stanza << WS Doc = Many(Stmt).map(skip_none) Top = Doc + EOF return Entry(children=Top(f)[0], src=ctx)
def parse_doc(self, content): try: def to_directive(x): name, rest = x rest = [rest] if rest is not None else [] return Directive(name=name.value.strip(), attrs=rest, lineno=name.lineno, src=self) def to_section(name, rest): return Section(name=name.value.strip(), children=rest, lineno=name.lineno, src=self) def apply_defaults(cfg): if "DEFAULT" not in cfg: return cfg defaults = cfg["DEFAULT"] not_defaults = cfg[~eq("DEFAULT")] for c in not_defaults: for d in defaults.grandchildren: if d.name not in c: c.children.append(d) cfg.children = list(not_defaults) return cfg def make_bytes(number, char_multiple): if char_multiple.lower() == 'k': return number * 2**10 if char_multiple.lower() == 'm': return number * 2**20 if char_multiple.lower() == 'g': return number * 2**30 content = "\n".join(content) header_chars = (set(string.printable) - set(string.whitespace) - set("[]")) | set(" ") sep_chars = set("=") key_chars = header_chars - sep_chars value_chars = set(string.printable) - set("\n\r") On = Literal("on", True, ignore_case=True) Off = Literal("off", False, ignore_case=True) Tru = Literal("true", True, ignore_case=True) Fals = Literal("false", False, ignore_case=True) Boolean = ((On | Off | Tru | Fals) & (WSChar | LineEnd)) % "Boolean" Num = Number & (WSChar | LineEnd) QuoStr = QuotedString & (WSChar | LineEnd) # Handle php.ini shorthand notation for memory limits: 1G, 8M, 50K # https://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes MemNum = (Lift(make_bytes) * Number * (Char('K') | Char('M') | Char('G'))) & (WSChar | LineEnd) LeftEnd = (WS + LeftBracket + WS) RightEnd = (WS + RightBracket + WS) Header = (LeftEnd >> PosMarker(String(header_chars)) << RightEnd) % "Header" Key = WS >> PosMarker(String(key_chars)) << WS Sep = InSet(sep_chars, "Sep") Value = WS >> (Boolean | MemNum | Num | QuoStr | HangingString(value_chars)) KVPair = WithIndent(Key + Opt(Sep >> Value)) % "KVPair" Comment = (WS >> (OneLineComment(";")).map(lambda x: None)) Line = Comment | KVPair.map(to_directive) Sect = Lift(to_section) * Header * Many(Line).map(skip_none) Doc = Many(Comment | Sect).map(skip_none) Top = Doc << WS << EOF res = Entry(children=Top(content), src=self) return apply_defaults(res) except SkipException: raise except: raise ParseException(ParseException("Could not parse content: '{0}'". format(content)))
OneLineComment, Opt, skip_none, String, WithIndent, WS) header_chars = (set(string.printable) - set(string.whitespace) - set("[]")) | set(" ") sep_chars = set(":=") key_chars = header_chars - sep_chars - set(" ") value_chars = set(string.printable) - set("\n\r") LeftEnd = WS >> Char("[") << WS RightEnd = WS >> Char("]") << WS Header = LeftEnd >> String(header_chars) << RightEnd Key = WS >> String(key_chars) << WS Sep = InSet(sep_chars) Value = WS >> HangingString(value_chars) KVPair = WithIndent(Key + Opt(Sep >> Value)) Comment = WS >> (OneLineComment("#") | OneLineComment(";")).map(lambda x: None) Line = Comment | KVPair.map(tuple) Sect = (Header + Many(Line).map(skip_none).map(dict)).map(tuple) Doc = Many(Comment | Sect).map(skip_none).map(dict) Top = Doc << WS << EOF def parse_yum_repos(content): doc = Top(content) for k, v in doc.items(): for special in ("baseurl", "gpgkey"): if special in v: v[special] = [i.strip() for i in re.split(",| ", v[special])] return doc
return Not(p) if op else p def oper(args): left, rest = args for op, right in rest: if op == "&": left = And(left, right) if op in ",|": left = Or(left, right) return left expr = Forward() bare = String(set(string.printable) - (set(string.whitespace) | set(")&,|"))) tag = (QuotedString | bare).map(Eq) regex_body = QuotedString | String( set(string.printable) - set(string.whitespace)) regex = Char("/") >> regex_body.map(Regex) factor_body = ((Char("(") >> expr << Char(")")) | regex | tag) factor = (WS >> Opt(Char("!")) + factor_body << WS).map(negate) term = (factor + Many(Char("&") + factor)).map(oper) expr <= (term + Many(InSet(",|") + term)).map(oper) parse = expr << EOF
ret.append(Section(name=n, children=body, lineno=name.lineno)) else: attrs = [attrs] if not isinstance(attrs, list) else attrs ret.append( Directive(name=name.value, attrs=attrs, lineno=name.lineno)) return ret scripts = set("postrotate prerotate firstaction lastaction preremove".split()) Stanza = Forward() Spaces = Many(WSChar) Bare = String(set(string.printable) - (set(string.whitespace) | set("#{}'\""))) Num = Number & (WSChar | LineEnd) Comment = OneLineComment("#").map(lambda x: None) ScriptStart = WS >> PosMarker(Choice([Literal(s) for s in scripts])) << WS ScriptEnd = Literal("endscript") Line = (WS >> AnyChar.until(EOL) << WS).map(lambda x: "".join(x)) Lines = Line.until(ScriptEnd).map(lambda x: "\n".join(x)) Script = ScriptStart + Lines << ScriptEnd Script = Script.map(lambda x: [x[0], x[1], None]) BeginBlock = WS >> LeftCurly << WS EndBlock = WS >> RightCurly First = PosMarker((Bare | QuotedString)) << Spaces Attr = Spaces >> (Num | Bare | QuotedString) << Spaces Rest = Many(Attr) Block = BeginBlock >> Many(Stanza).map(skip_none).map(to_entries) << EndBlock Stmt = WS >> (Script | (First + Rest + Opt(Block))) << WS Stanza <= WS >> (Stmt | Comment) << WS Doc = Many(Stanza).map(skip_none).map(to_entries) Top = Doc + EOF
def test_opt_default(): a = Opt(Char("a"), "Default") assert a("b") == "Default"
def test_opt(): a = Opt(Char("a")) assert a("a") == "a" assert a("b") is None