blob: 1c83fd7a7c0d7693f6e6a152067b1674d9300c24 [file] [edit]
#!/usr/bin/env python3
#
# Copyright (C) 2025 The Android Open Source Project
#
# 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
#
# http://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.
#
import os
import subprocess
import textwrap
from typing import List, Tuple, Any, Optional
from jinja2 import Template
def run_subprocess_cmd(
command: List[str],
ignore_error_and_return=False,
timeout=30,
) -> subprocess.CompletedProcess[Any]:
"""Runs command in a subprocess and returns the CompletedProcess."""
result = subprocess.run(
command,
capture_output=True,
text=True,
check=False,
encoding="utf-8",
timeout=timeout,
)
if not ignore_error_and_return and result.returncode:
joined_command = " ".join(command)
error_message = (
f"\n\n========= Issue calling command =========\n\n"
f"Cmd: `{joined_command}`\n\n"
f"stderr: {result.stderr}\n\n")
raise RuntimeError(error_message)
return result
def make_and_write_file(
directory: str,
file_name: str,
data: str,
) -> None:
"""Recursively creates any parent directories and then the file."""
os.makedirs(directory, exist_ok=True)
with open(
os.path.join(directory, file_name),
"w",
encoding="UTF-8",
) as f:
f.write(data)
def list_to_markdown_table(
data: List[Tuple[Any, ...]],
headers: Optional[List[str]] = None,
) -> str:
"""Creates a Markdown table using Jinja2."""
str_data = [
[
f"{item:,.0f}"
if isinstance(item, (int, float))
else str(item)
for item in row
]
for row in data
]
if headers:
num_columns = len(headers)
elif data:
num_columns = len(data[0])
else:
return ""
is_numeric_col = [True] * num_columns
for row in data:
for i, item in enumerate(row):
if i < num_columns and not isinstance(item, (int, float)):
is_numeric_col[i] = False
col_widths = [3] * num_columns
if headers:
for i, header in enumerate(headers):
col_widths[i] = max(col_widths[i], len(header))
for row in str_data:
for i, item in enumerate(row):
# Guard against rows that might be longer than headers
if i < num_columns:
col_widths[i] = max(col_widths[i], len(item))
template_string = textwrap.dedent("""\
{%- if headers -%}
|{% for header in headers %} {{ "{:<{}}".format(header, widths[loop.index0]) }} |{% endfor %}
|{% for i in range(widths|length) %}{% if is_numeric[i] %} {{ "-" * (widths[i]) }}:|{% else %} :{{ "-" * (widths[i]) }}|{% endif %}{% endfor %}
{% endif -%}
{%- for row in data -%}
|{% for item in row %}{% if is_numeric[loop.index0] %} {{ "{:>{}}".format(item, widths[loop.index0]) }} |{% else %} {{ "{:<{}}".format(item, widths[loop.index0]) }} |{% endif %}{% endfor %}
{% endfor -%}
""")
template = Template(template_string)
return template.render(
data=str_data,
headers=headers,
widths=col_widths,
is_numeric=is_numeric_col
).strip()