File: //snap/google-cloud-cli/current/platform/gsutil/third_party/pyparsing/examples/dfmparse.py
"""
This module can parse a Delphi Form (dfm) file.
The main is used in experimenting (to find which files fail
to parse, and where), but isn't useful for anything else.
"""
__version__ = "1.0"
__author__ = "Daniel 'Dang' Griffith <pythondev - dang at lazytwinacres . net>"
from pyparsing import (
Literal,
CaselessLiteral,
Word,
delimitedList,
Optional,
Combine,
Group,
alphas,
nums,
alphanums,
Forward,
oneOf,
OneOrMore,
ZeroOrMore,
CharsNotIn,
)
# This converts DFM character constants into Python string (unicode) values.
def to_chr(x):
"""chr(x) if 0 < x < 128 ; unicode(x) if x > 127."""
return 0 < x < 128 and chr(x) or eval("u'\\u%d'" % x)
#################
# BEGIN GRAMMAR
#################
COLON = Literal(":").suppress()
CONCAT = Literal("+").suppress()
EQUALS = Literal("=").suppress()
LANGLE = Literal("<").suppress()
LBRACE = Literal("[").suppress()
LPAREN = Literal("(").suppress()
PERIOD = Literal(".").suppress()
RANGLE = Literal(">").suppress()
RBRACE = Literal("]").suppress()
RPAREN = Literal(")").suppress()
CATEGORIES = CaselessLiteral("categories").suppress()
END = CaselessLiteral("end").suppress()
FONT = CaselessLiteral("font").suppress()
HINT = CaselessLiteral("hint").suppress()
ITEM = CaselessLiteral("item").suppress()
OBJECT = CaselessLiteral("object").suppress()
attribute_value_pair = Forward() # this is recursed in item_list_entry
simple_identifier = Word(alphas, alphanums + "_")
identifier = Combine(simple_identifier + ZeroOrMore(Literal(".") + simple_identifier))
object_name = identifier
object_type = identifier
# Integer and floating point values are converted to Python longs and floats, respectively.
int_value = Combine(Optional("-") + Word(nums)).setParseAction(
lambda s, l, t: [int(t[0])]
)
float_value = Combine(
Optional("-") + Optional(Word(nums)) + "." + Word(nums)
).setParseAction(lambda s, l, t: [float(t[0])])
number_value = float_value | int_value
# Base16 constants are left in string form, including the surrounding braces.
base16_value = Combine(
Literal("{") + OneOrMore(Word("0123456789ABCDEFabcdef")) + Literal("}"),
adjacent=False,
)
# This is the first part of a hack to convert the various delphi partial sglQuotedStrings
# into a single sglQuotedString equivalent. The gist of it is to combine
# all sglQuotedStrings (with their surrounding quotes removed (suppressed))
# with sequences of #xyz character constants, with "strings" concatenated
# with a '+' sign.
unquoted_sglQuotedString = Combine(
Literal("'").suppress() + ZeroOrMore(CharsNotIn("'\n\r")) + Literal("'").suppress()
)
# The parse action on this production converts repetitions of constants into a single string.
pound_char = Combine(
OneOrMore(
(Literal("#").suppress() + Word(nums)).setParseAction(
lambda s, l, t: to_chr(int(t[0]))
)
)
)
# This is the second part of the hack. It combines the various "unquoted"
# partial strings into a single one. Then, the parse action puts
# a single matched pair of quotes around it.
delphi_string = Combine(
OneOrMore(CONCAT | pound_char | unquoted_sglQuotedString), adjacent=False
).setParseAction(lambda s, l, t: f"'{t[0]}'")
string_value = delphi_string | base16_value
list_value = (
LBRACE
+ Optional(Group(delimitedList(identifier | number_value | string_value)))
+ RBRACE
)
paren_list_value = (
LPAREN + ZeroOrMore(identifier | number_value | string_value) + RPAREN
)
item_list_entry = ITEM + ZeroOrMore(attribute_value_pair) + END
item_list = LANGLE + ZeroOrMore(item_list_entry) + RANGLE
generic_value = identifier
value = (
item_list
| number_value
| string_value
| list_value
| paren_list_value
| generic_value
)
category_attribute = CATEGORIES + PERIOD + oneOf("strings itemsvisibles visibles", True)
event_attribute = oneOf(
"onactivate onclosequery onclose oncreate ondeactivate onhide onshow", True
)
font_attribute = FONT + PERIOD + oneOf("charset color height name style", True)
hint_attribute = HINT
layout_attribute = oneOf("left top width height", True)
generic_attribute = identifier
attribute = (
category_attribute
| event_attribute
| font_attribute
| hint_attribute
| layout_attribute
| generic_attribute
)
category_attribute_value_pair = category_attribute + EQUALS + paren_list_value
event_attribute_value_pair = event_attribute + EQUALS + value
font_attribute_value_pair = font_attribute + EQUALS + value
hint_attribute_value_pair = hint_attribute + EQUALS + value
layout_attribute_value_pair = layout_attribute + EQUALS + value
generic_attribute_value_pair = attribute + EQUALS + value
attribute_value_pair << Group(
category_attribute_value_pair
| event_attribute_value_pair
| font_attribute_value_pair
| hint_attribute_value_pair
| layout_attribute_value_pair
| generic_attribute_value_pair
)
object_declaration = Group(OBJECT + object_name + COLON + object_type)
object_attributes = Group(ZeroOrMore(attribute_value_pair))
nested_object = Forward()
object_definition = (
object_declaration + object_attributes + ZeroOrMore(nested_object) + END
)
nested_object << Group(object_definition)
#################
# END GRAMMAR
#################
def printer(s, loc, tok):
print(tok, end=" ")
return tok
def get_filename_list(tf):
import sys, glob
if tf == None:
if len(sys.argv) > 1:
tf = sys.argv[1:]
else:
tf = glob.glob("*.dfm")
elif type(tf) == str:
tf = [tf]
testfiles = []
for arg in tf:
testfiles.extend(glob.glob(arg))
return testfiles
def main(testfiles=None, action=printer):
"""testfiles can be None, in which case the command line arguments are used as filenames.
testfiles can be a string, in which case that file is parsed.
testfiles can be a list.
In all cases, the filenames will be globbed.
If more than one file is parsed successfully, a dictionary of ParseResults is returned.
Otherwise, a simple ParseResults is returned.
"""
testfiles = get_filename_list(testfiles)
print(testfiles)
if action:
for i in (simple_identifier, value, item_list):
i.setParseAction(action)
success = 0
failures = []
retval = {}
for f in testfiles:
try:
retval[f] = object_definition.parseFile(f)
success += 1
except Exception:
failures.append(f)
nl = "\n"
if failures:
print(f"{nl}failed while processing {', '.join(failures)}")
print(f"{nl}succeeded on {success} of {len(testfiles)} files")
if len(retval) == 1 and len(testfiles) == 1:
# if only one file is parsed, return the parseResults directly
return retval[list(retval.keys())[0]]
# else, return a dictionary of parseResults
return retval
if __name__ == "__main__":
main()