# coding=utf8"""Fluent AST helpers.The functions defined in this module offer a shorthand for defining common ASTnodes.They take a string argument and immediately return a corresponding AST node.(As opposed to Transforms which are AST nodes on their own and only return themigrated AST nodes when they are evaluated by a MergeContext.) """from__future__importunicode_literalsfrom__future__importabsolute_importfromfluent.syntaximportFluentParser,astasFTLfrom.transformsimportTransform,CONCAT,COPYfrom.errorsimportNotSupportedError,InvalidTransformErrordefVARIABLE_REFERENCE(name):"""Create an ExternalArgument expression."""returnFTL.VariableReference(id=FTL.Identifier(name))defMESSAGE_REFERENCE(name):"""Create a MessageReference expression."""returnFTL.MessageReference(id=FTL.Identifier(name))defTERM_REFERENCE(name):"""Create a TermReference expression."""returnFTL.TermReference(id=FTL.Identifier(name))deftransforms_from(ftl,**substitutions):"""Parse FTL code into a list of Message nodes with Transforms. The FTL may use a fabricated COPY function inside of placeables which will be converted into actual COPY migration transform. new-key = Hardcoded text { COPY("filepath.dtd", "string.key") } For convenience, COPY may also refer to transforms_from's keyword arguments via the MessageReference syntax: transforms_from(\"""new-key=Hardcodedtext{COPY(file_dtd,"string.key")} \""", file_dtd="very/long/path/to/a/file.dtd") """IMPLICIT_TRANSFORMS=("CONCAT",)FORBIDDEN_TRANSFORMS=("PLURALS","REPLACE","REPLACE_IN_TEXT")definto_argument(node):"""Convert AST node into an argument to migration transforms."""ifisinstance(node,FTL.StringLiteral):# Special cases for booleans which don't exist in Fluent.ifnode.value=="True":returnTrueifnode.value=="False":returnFalsereturnnode.valueifisinstance(node,FTL.MessageReference):try:returnsubstitutions[node.id.name]exceptKeyError:raiseInvalidTransformError("Unknown substitution in COPY: {}".format(node.id.name))else:raiseInvalidTransformError("Invalid argument passed to COPY: {}".format(type(node).__name__))definto_transforms(node):"""Convert AST node into a migration transform."""ifisinstance(node,FTL.Junk):anno=node.annotations[0]raiseInvalidTransformError("Transform contains parse error: {}, at {}".format(anno.message,anno.span.start))ifisinstance(node,FTL.CallExpression):name=node.callee.id.nameifname=="COPY":args=(into_argument(arg)forarginnode.positional)kwargs={arg.name.name:into_argument(arg.value)forarginnode.named}returnCOPY(*args,**kwargs)ifnameinIMPLICIT_TRANSFORMS:raiseNotSupportedError("{} may not be used with transforms_from(). It runs ""implicitly on all Patterns anyways.".format(name))ifnameinFORBIDDEN_TRANSFORMS:raiseNotSupportedError("{} may not be used with transforms_from(). It requires ""additional logic in Python code.".format(name))if(isinstance(node,FTL.Placeable)andisinstance(node.expression,Transform)):# Replace the placeable with the transform it's holding.# Transforms evaluate to Patterns which aren't valid Placeable# expressions.returnnode.expressionifisinstance(node,FTL.Pattern):# Replace the Pattern with CONCAT which is more accepting of its# elements. CONCAT takes PatternElements, Expressions and other# Patterns (e.g. returned from evaluating transforms).returnCONCAT(*node.elements)returnnodeparser=FluentParser(with_spans=False)resource=parser.parse(ftl)returnresource.traverse(into_transforms).body