Skip to content

Commit

Permalink
Allow --output to be templatized when generating multiple files. (#263)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsuereth authored Feb 15, 2024
1 parent fa298d7 commit bc6ee42
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 11 deletions.
25 changes: 24 additions & 1 deletion semantic-conventions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,37 @@ By default, all models are fed into the specified template at once, i.e. only a
This is helpful to generate constants for the semantic attributes, [example from opentelemetry-java](https://github.com/open-telemetry/opentelemetry-java/tree/main/buildscripts/semantic-convention).

If the parameter `--file-per-group {pattern}` is set, a single yaml model is fed into the template
and the value of `pattern` is resolved from the model and attached as prefix to the output argument.
and the value of `pattern` is resolved from the model and may be used in the output argument.
This way, multiple files are generated. The value of `pattern` can be one of the following:

- `semconv_id`: The id of the semantic convention.
- `prefix`: The prefix with which all attributes starts with.
- `extends`: The id of the parent semantic convention.
- `root_namespace`: The root namespace of attribute to group by.

The `--output` parameter, when `--file-per-group` is used is evaluated as a template. The following variables are provided to output:

- `prefix`: A prefix name for files, determined from the grouping. e.g. `http`, `database`, `user-agent`.
- `pascal_prefix`: A Pascal-case prefix name for files. e.g. `Http`, `Database`, `UserAgent`.
- `camel_prefix`: A camel-case prefix name for files. e.g. `http`, `database`, `userAgent`.
- `snake_prefix`: A snake-case prefix name for files. e.g. `http`, `database`, `user_agent`.

For example, you could do the following:

```bash
docker run --rm \
-v ${SCRIPT_DIR}/opentelemetry-specification/semantic_conventions/trace:/source \
-v ${SCRIPT_DIR}/templates:/templates \
-v ${ROOT_DIR}/semconv/src/main/java/io/opentelemetry/semconv/trace/attributes/:/output \
otel/semconvgen:$GENERATOR_VERSION \
--yaml-root /source \
code \
--template /templates/SemanticAttributes.java.j2 \
--file-per-group root_namespace \
--output "/output/{{pascal_prefix}}Attributes.java" \
...other parameters...
```

Finally, additional value can be passed to the template in form of `key=value` pairs separated by
comma using the `--parameters [{key=value},]+` or `-D` flag.

Expand Down
31 changes: 25 additions & 6 deletions semantic-conventions/src/opentelemetry/semconv/templating/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ def to_camelcase(name: str, first_upper=False) -> str:
return first + "".join(word.capitalize() for word in rest)


def to_snake_case(name):
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
name = re.sub("__([A-Z])", r"_\1", name)
name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name)
return name.lower()


def first_up(name: str) -> str:
return name[0].upper() + name[1:]

Expand Down Expand Up @@ -264,10 +271,22 @@ def setup_environment(env: Environment, trim_whitespace: bool):
env.lstrip_blocks = trim_whitespace

@staticmethod
def prefix_output_file(file_name, prefix):
basename = os.path.basename(file_name)
dirname = os.path.dirname(file_name)
return os.path.join(dirname, to_camelcase(prefix, True) + basename)
def prefix_output_file(env, file_name, prefix):
# We treat incoming file names as a pattern.
# We allow will give them access to the same jinja model as file creation
# and we'll make sure a few things are available there, specifically:
# pascal case, camel case and snake case
data = {
"prefix": prefix,
"pascal_prefix": to_camelcase(prefix, True),
"camel_prefix": to_camelcase(prefix, False),
"snake_prefix": to_snake_case(prefix),
}
template = env.from_string(file_name)
full_name = template.render(data)
dirname = os.path.dirname(full_name)
basename = os.path.basename(full_name)
return os.path.join(dirname, basename)

def render(
self,
Expand Down Expand Up @@ -307,7 +326,7 @@ def _render_by_pattern(
):
for semconv in semconvset.models.values():
prefix = getattr(semconv, pattern)
output_name = self.prefix_output_file(output_file, prefix)
output_name = self.prefix_output_file(env, output_file, prefix)
data = self.get_data_multiple_files(semconv, template_path)
template = env.get_template(file_name, globals=data)
self._write_template_to_file(template, data, output_name)
Expand All @@ -324,7 +343,7 @@ def _render_group_by_root_namespace(
metrics = self._grouped_metric_definitions(semconvset)
for ns, attribute_and_templates in attribute_and_templates.items():
sanitized_ns = ns if ns != "" else "other"
output_name = self.prefix_output_file(output_file, sanitized_ns)
output_name = self.prefix_output_file(env, output_file, sanitized_ns)

data = {
"template": template_path,
Expand Down
39 changes: 35 additions & 4 deletions semantic-conventions/src/tests/semconv/templating/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_codegen_attribute_root_ns(test_file_path, read_test_file):
renderer.render(
semconv,
template_path,
os.path.join(tmppath, "Attributes.java"),
os.path.join(tmppath, "{{pascal_prefix}}Attributes.java"),
"root_namespace",
)

Expand All @@ -93,6 +93,34 @@ def test_codegen_attribute_root_ns(test_file_path, read_test_file):
check_file(tmppath, "ThirdAttributes.java", third)


def test_codegen_attribute_root_ns_snake_case_file(test_file_path, read_test_file):
semconv = SemanticConventionSet(debug=False)

semconv.parse(test_file_path("jinja", "group_by_root_namespace", "attributes.yml"))
semconv.finish()

template_path = test_file_path("jinja", "group_by_root_namespace", "template_all")
renderer = CodeRenderer({}, trim_whitespace=True)

test_path = os.path.join("group_by_root_namespace", "all")
tmppath = tempfile.mkdtemp()
renderer.render(
semconv,
template_path,
os.path.join(tmppath, "{{snake_prefix}}_attributes.java"),
"root_namespace",
)

first = read_test_file("jinja", test_path, "FirstAttributes.java")
check_file(tmppath, "first_attributes.java", first)

second = read_test_file("jinja", test_path, "SecondAttributes.java")
check_file(tmppath, "second_attributes.java", second)

third = read_test_file("jinja", test_path, "ThirdAttributes.java")
check_file(tmppath, "third_attributes.java", third)


def test_codegen_attribute_root_ns_stable(test_file_path, read_test_file):
semconv = SemanticConventionSet(debug=False)
semconv.parse(test_file_path("jinja", "group_by_root_namespace", "attributes.yml"))
Expand All @@ -106,7 +134,7 @@ def test_codegen_attribute_root_ns_stable(test_file_path, read_test_file):
renderer.render(
semconv,
template_path,
os.path.join(tmppath, "Attributes.java"),
os.path.join(tmppath, "{{pascal_prefix}}Attributes.java"),
"root_namespace",
)

Expand All @@ -130,7 +158,7 @@ def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_fil
renderer.render(
semconv,
template_path,
os.path.join(tmppath, "Attributes.java"),
os.path.join(tmppath, "{{pascal_prefix}}Attributes.java"),
"root_namespace",
)

Expand Down Expand Up @@ -170,7 +198,10 @@ def test_codegen_attribute_root_ns_metrics(test_file_path, read_test_file):

tmppath = tempfile.mkdtemp()
renderer.render(
semconv, template_path, os.path.join(tmppath, ".java"), "root_namespace"
semconv,
template_path,
os.path.join(tmppath, "{{pascal_prefix}}.java"),
"root_namespace",
)

first = read_test_file("jinja", test_path, "First.java")
Expand Down

0 comments on commit bc6ee42

Please sign in to comment.