blob: 519fb9935bc94e53a9bb301607029643c91d3bb4 [file] [log] [blame]
# 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)