1# Copyright 2019 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""A formatter for code templates. 16 17Use the format_template function to render a code template. 18""" 19 20import collections 21import re 22import string 23 24 25def format_template(template, **kwargs): 26 """format_template acts like str.format, but uses ${name} instead of {name}. 27 28 format_template acts like a str.format, except that instead of using { and } 29 to delimit substitutions, format_template uses ${name}. This simplifies 30 templates of source code in most languages, which frequently use "{" and "}", 31 but very rarely use "$". 32 33 See the documentation for string.Template for details about 34 template strings and the format of substitutions. 35 36 Arguments: 37 template: A template to format. 38 **kwargs: Keyword arguments for string.Template.substitute. 39 40 Returns: 41 A formatted string. 42 """ 43 return template.substitute(**kwargs) 44 45 46def parse_templates(text): 47 """Parses text into a namedtuple of templates. 48 49 parse_templates will split its argument into templates by searching for lines 50 of the form: 51 52 [punctuation] " ** " [name] " ** " [punctuation] 53 54 e.g.: 55 56 // ** struct_field_accessor ** //////// 57 58 Leading and trailing punctuation is ignored, and [name] is used as the name 59 of the template. [name] should match [A-Za-z][A-Za-z0-9_]* -- that is, it 60 should be a valid ASCII Python identifier. 61 62 Additionally any `//` style comment without leading space of the form: 63 ```C++ 64 // This is an emboss developer related comment, it's useful internally 65 // but not relevant to end-users of generated code. 66 ``` 67 will be stripped out of the generated code. 68 69 If a template wants to define a comment that will be included in the 70 generated code a C-style comment is recommended: 71 ```C++ 72 /** This will be included in the generated source. */ 73 74 /** 75 * So will this! 76 */ 77 ``` 78 79 Arguments: 80 text: The text to parse into templates. 81 82 Returns: 83 A namedtuple object whose attributes are the templates from text. 84 """ 85 delimiter_re = re.compile(r"^\W*\*\* ([A-Za-z][A-Za-z0-9_]*) \*\*\W*$") 86 comment_re = re.compile(r"^\s*//.*$") 87 templates = {} 88 name = None 89 template = [] 90 def finish_template(template): 91 return string.Template("\n".join(template)) 92 93 for line in text.splitlines(): 94 if delimiter_re.match(line): 95 if name: 96 templates[name] = finish_template(template) 97 name = delimiter_re.match(line).group(1) 98 template = [] 99 else: 100 if not comment_re.match(line): 101 template.append(line) 102 if name: 103 templates[name] = finish_template(template) 104 return collections.namedtuple("Templates", 105 list(templates.keys()))(**templates) 106