# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A formatter for code templates. Use the format_template function to render a code template. """ import collections import re import string def format_template(template, **kwargs): """format_template acts like str.format, but uses ${name} instead of {name}. format_template acts like a str.format, except that instead of using { and } to delimit substitutions, format_template uses ${name}. This simplifies templates of source code in most languages, which frequently use "{" and "}", but very rarely use "$". See the documentation for string.Template for details about template strings and the format of substitutions. Arguments: template: A template to format. **kwargs: Keyword arguments for string.Template.substitute. Returns: A formatted string. """ return template.substitute(**kwargs) def parse_templates(text): """Parses text into a namedtuple of templates. parse_templates will split its argument into templates by searching for lines of the form: [punctuation] " ** " [name] " ** " [punctuation] e.g.: // ** struct_field_accessor ** //////// Leading and trailing punctuation is ignored, and [name] is used as the name of the template. [name] should match [A-Za-z][A-Za-z0-9_]* -- that is, it should be a valid ASCII Python identifier. Additionally any `//` style comment without leading space of the form: ```C++ // This is an emboss developer related comment, it's useful internally // but not relevant to end-users of generated code. ``` will be stripped out of the generated code. If a template wants to define a comment that will be included in the generated code a C-style comment is recommended: ```C++ /** This will be included in the generated source. */ /** * So will this! */ ``` Arguments: text: The text to parse into templates. Returns: A namedtuple object whose attributes are the templates from text. """ delimiter_re = re.compile(r"^\W*\*\* ([A-Za-z][A-Za-z0-9_]*) \*\*\W*$") comment_re = re.compile(r"^\s*//.*$") templates = {} name = None template = [] def finish_template(template): return string.Template("\n".join(template)) for line in text.splitlines(): if delimiter_re.match(line): if name: templates[name] = finish_template(template) name = delimiter_re.match(line).group(1) template = [] else: if not comment_re.match(line): template.append(line) if name: templates[name] = finish_template(template) return collections.namedtuple("Templates", list(templates.keys()))(**templates)