[{"id":30831,"web_url":"https://patchwork.libcamera.org/comment/30831/","msgid":"<Zr2BKPU1daEQugNR@pyrite.rasen.tech>","date":"2024-08-15T04:16:40","subject":"Re: [PATCH 07/10] utils: codegen: gen-controls.py: Convert to jinja2\n\ttemplates","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"On Fri, Aug 09, 2024 at 03:59:11AM +0300, Laurent Pinchart wrote:\n> Jinja2 templates help separating the logic related to the template from\n> the generation of the data. The python code gets much clearer as a\n> result.\n> \n> As an added bonus, we can use a single template file for both controls\n> and properties.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  include/libcamera/control_ids.h.in  |  40 +++-\n>  include/libcamera/meson.build       |   2 +-\n>  include/libcamera/property_ids.h.in |  34 ----\n>  src/libcamera/control_ids.cpp.in    | 101 ++++++++--\n>  src/libcamera/meson.build           |   5 +-\n>  src/libcamera/property_ids.cpp.in   |  48 -----\n>  utils/codegen/gen-controls.py       | 285 ++++++----------------------\n>  7 files changed, 176 insertions(+), 339 deletions(-)\n>  delete mode 100644 include/libcamera/property_ids.h.in\n>  delete mode 100644 src/libcamera/property_ids.cpp.in\n> \n> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in\n> index 293ba966fbc4..858ef872e9ee 100644\n> --- a/include/libcamera/control_ids.h.in\n> +++ b/include/libcamera/control_ids.h.in\n> @@ -2,7 +2,7 @@\n>  /*\n>   * Copyright (C) 2019, Google Inc.\n>   *\n> - * Control ID list\n> + * {{mode|capitalize}} ID list\n>   *\n>   * This file is auto-generated. Do not edit.\n>   */\n> @@ -18,18 +18,42 @@\n>  \n>  namespace libcamera {\n>  \n> -namespace controls {\n> +namespace {{mode}} {\n> +\n> +extern const ControlIdMap {{mode}};\n> +\n> +{%- for vendor, ctrls in controls -%}\n> +\n> +{% if vendor != 'libcamera' %}\n> +namespace {{vendor}} {\n> +\n> +#define LIBCAMERA_HAS_{{vendor|upper}}_VENDOR_{{mode|upper}}\n> +{%- endif %}\n>  \n>  enum {\n> -${ids}\n> +{%- for ctrl in ctrls %}\n> +\t{{ctrl.name|snake_case|upper}} = {{ctrl.id}},\n> +{%- endfor %}\n>  };\n>  \n> -${controls}\n> +{% for ctrl in ctrls -%}\n> +{% if ctrl.is_enum -%}\n> +enum {{ctrl.name}}Enum {\n> +{%- for enum in ctrl.enum_values %}\n> +\t{{enum.name}} = {{enum.value}},\n> +{%- endfor %}\n> +};\n> +extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values;\n> +extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap;\n> +{% endif -%}\n> +extern const Control<{{ctrl.type}}> {{ctrl.name}};\n> +{% endfor -%}\n>  \n> -extern const ControlIdMap controls;\n> +{% if vendor != 'libcamera' %}\n> +} /* namespace {{vendor}} */\n> +{% endif -%}\n>  \n> -${vendor_controls}\n> -\n> -} /* namespace controls */\n> +{% endfor %}\n> +} /* namespace {{mode}} */\n>  \n>  } /* namespace libcamera */\n> diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\n> index 87b9a9412fe7..d90a8615e52d 100644\n> --- a/include/libcamera/meson.build\n> +++ b/include/libcamera/meson.build\n> @@ -80,7 +80,7 @@ foreach mode, entry : controls_map\n>          properties_files_names += files_list\n>      endif\n>  \n> -    template_file = files(outfile + '.in')\n> +    template_file = files('control_ids.h.in')\n>      ranges_file = files('../../src/libcamera/control_ranges.yaml')\n>      control_headers += custom_target(header + '_h',\n>                                       input : input_files,\n> diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in\n> deleted file mode 100644\n> index e6edabca771f..000000000000\n> --- a/include/libcamera/property_ids.h.in\n> +++ /dev/null\n> @@ -1,34 +0,0 @@\n> -/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> -/*\n> - * Copyright (C) 2019, Google Inc.\n> - *\n> - * Property ID list\n> - *\n> - * This file is auto-generated. Do not edit.\n> - */\n> -\n> -#pragma once\n> -\n> -#include <map>\n> -#include <stdint.h>\n> -#include <string>\n> -\n> -#include <libcamera/controls.h>\n> -\n> -namespace libcamera {\n> -\n> -namespace properties {\n> -\n> -enum {\n> -${ids}\n> -};\n> -\n> -${controls}\n> -\n> -extern const ControlIdMap properties;\n> -\n> -${vendor_controls}\n> -\n> -} /* namespace properties */\n> -\n> -} /* namespace libcamera */\n> diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in\n> index 0b028c92d852..05c8fb385d20 100644\n> --- a/src/libcamera/control_ids.cpp.in\n> +++ b/src/libcamera/control_ids.cpp.in\n> @@ -2,51 +2,120 @@\n>  /*\n>   * Copyright (C) 2019, Google Inc.\n>   *\n> - * Control ID list\n> + * {{mode}} ID list\n>   *\n>   * This file is auto-generated. Do not edit.\n>   */\n>  \n> -#include <libcamera/control_ids.h>\n> +#include <libcamera/{{filename}}.h>\n>  #include <libcamera/controls.h>\n>  \n>  /**\n> - * \\file control_ids.h\n> - * \\brief Camera control identifiers\n> + * \\file {{filename}}.h\n> + * \\brief Camera {{mode}} identifiers\n>   */\n>  \n>  namespace libcamera {\n>  \n>  /**\n> - * \\brief Namespace for libcamera controls\n> + * \\brief Namespace for libcamera {{mode}}\n>   */\n> -namespace controls {\n> +namespace {{mode}} {\n>  \n> -${controls_doc}\n> +{%- for vendor, ctrls in controls -%}\n>  \n> -${vendor_controls_doc}\n> +{%- if vendor != 'libcamera' %}\n> +/**\n> + * \\brief Namespace for {{vendor}} {{mode}}\n> + */\n> +namespace {{vendor}} {\n> +{%- endif -%}\n> +\n> +{% for ctrl in ctrls %}\n> +\n> +{% if ctrl.is_enum -%}\n> +/**\n> + * \\enum {{ctrl.name}}Enum\n> + * \\brief Supported {{ctrl.name}} values\n> +{%- for enum in ctrl.enum_values %}\n> + *\n> + * \\var {{enum.name}}\n> + * \\brief {{enum.description|format_description}}\n> +{%- endfor %}\n> + */\n> +\n> +/**\n> + * \\var {{ctrl.name}}Values\n> + * \\brief List of all {{ctrl.name}} supported values\n> + */\n> +\n> +/**\n> + * \\var {{ctrl.name}}NameValueMap\n> + * \\brief Map of all {{ctrl.name}} supported value names (in std::string format) to value\n> + */\n> +\n> +{% endif -%}\n> +/**\n> + * \\var {{ctrl.name}}\n> + * \\brief {{ctrl.description|format_description}}\n> + */\n> +{%- endfor %}\n> +{% if vendor != 'libcamera' %}\n> +} /* namespace {{vendor}} */\n> +{% endif -%}\n> +\n> +{%- endfor %}\n>  \n>  #ifndef __DOXYGEN__\n>  /*\n> - * Keep the controls definitions hidden from doxygen as it incorrectly parses\n> + * Keep the {{mode}} definitions hidden from doxygen as it incorrectly parses\n>   * them as functions.\n>   */\n> -${controls_def}\n> +{% for vendor, ctrls in controls -%}\n>  \n> -${vendor_controls_def}\n> +{% if vendor != 'libcamera' %}\n> +namespace {{vendor}} {\n> +{% endif %}\n>  \n> -#endif\n> +{%- for ctrl in ctrls %}\n> +{% if ctrl.is_enum -%}\n> +extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values = {\n> +{%- for enum in ctrl.enum_values %}\n> +\tstatic_cast<{{ctrl.type}}>({{enum.name}}),\n> +{%- endfor %}\n> +};\n> +extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap = {\n> +{%- for enum in ctrl.enum_values %}\n> +\t{ \"{{enum.name}}\", {{enum.name}} },\n> +{%- endfor %}\n> +};\n> +{% endif -%}\n> +extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, \"{{ctrl.name}}\");\n> +{%- endfor %}\n> +\n> +{% if vendor != 'libcamera' %}\n> +} /* namespace {{vendor}} */\n> +{% endif -%}\n> +\n> +{%- endfor %}\n> +#endif /* __DOXYGEN__ */\n>  \n>  /**\n> - * \\brief List of all supported libcamera controls\n> + * \\brief List of all supported libcamera {{mode}}\n> +{%- if mode == 'controls' %}\n>   *\n>   * Unless otherwise stated, all controls are bi-directional, i.e. they can be\n>   * set through Request::controls() and returned out through Request::metadata().\n> +{%- endif %}\n>   */\n> -extern const ControlIdMap controls {\n> -${controls_map}\n> +extern const ControlIdMap {{mode}} {\n> +{%- for vendor, ctrls in controls -%}\n> +{%- for ctrl in ctrls %}\n> +\t{ {{ctrl.namespace}}{{ctrl.name|snake_case|upper}}, &{{ctrl.namespace}}{{ctrl.name}} },\n> +{%- endfor -%}\n> +{%- endfor %}\n>  };\n>  \n> -} /* namespace controls */\n> +} /* namespace {{mode}} */\n>  \n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index e5e959d9c7bd..3fd3a87e9f95 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -143,9 +143,10 @@ foreach mode, inout_files : controls_mode_files\n>      input_files = inout_files[0]\n>      output_file = inout_files[1]\n>  \n> -    template_file = files(output_file + '.in')\n> +    template_file = files('control_ids.cpp.in')\n>      ranges_file = files('control_ranges.yaml')\n> -    control_sources += custom_target(mode + '_cpp',\n> +\n> +    control_sources += custom_target(mode + '_ids_cpp',\n>                                       input : input_files,\n>                                       output : output_file,\n>                                       command : [gen_controls, '-o', '@OUTPUT@',\n> diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in\n> deleted file mode 100644\n> index 2d3f192eb6ef..000000000000\n> --- a/src/libcamera/property_ids.cpp.in\n> +++ /dev/null\n> @@ -1,48 +0,0 @@\n> -/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> -/*\n> - * Copyright (C) 2019, Google Inc.\n> - *\n> - * Property ID list\n> - *\n> - * This file is auto-generated. Do not edit.\n> - */\n> -\n> -#include <libcamera/property_ids.h>\n> -\n> -/**\n> - * \\file property_ids.h\n> - * \\brief Camera property identifiers\n> - */\n> -\n> -namespace libcamera {\n> -\n> -/**\n> - * \\brief Namespace for libcamera properties\n> - */\n> -namespace properties {\n> -\n> -${controls_doc}\n> -\n> -${vendor_controls_doc}\n> -\n> -#ifndef __DOXYGEN__\n> -/*\n> - * Keep the properties definitions hidden from doxygen as it incorrectly parses\n> - * them as functions.\n> - */\n> -${controls_def}\n> -\n> -${vendor_controls_def}\n> -\n> -#endif\n> -\n> -/**\n> - * \\brief List of all supported libcamera properties\n> - */\n> -extern const ControlIdMap properties {\n> -${controls_map}\n> -};\n> -\n> -} /* namespace properties */\n> -\n> -} /* namespace libcamera */\n> diff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py\n> index 56315f5089b4..685ef7a00d5f 100755\n> --- a/utils/codegen/gen-controls.py\n> +++ b/utils/codegen/gen-controls.py\n> @@ -7,12 +7,10 @@\n>  # Generate control definitions from YAML\n>  \n>  import argparse\n> -from functools import reduce\n> -import operator\n> -import string\n> +import jinja2\n> +import os\n>  import sys\n>  import yaml\n> -import os\n>  \n>  \n>  class ControlEnum(object):\n> @@ -81,6 +79,13 @@ class Control(object):\n>          for enum in self.__enum_values:\n>              yield enum\n>  \n> +    @property\n> +    def enum_values_count(self):\n> +        \"\"\"The number of enum values, if the control is an enumeration\"\"\"\n> +        if self.__enum_values is None:\n> +            return 0\n> +        return len(self.__enum_values)\n> +\n>      @property\n>      def is_enum(self):\n>          \"\"\"Is the control an enumeration\"\"\"\n> @@ -119,221 +124,23 @@ def snake_case(s):\n>  \n>  def format_description(description):\n>      description = description.strip('\\n').split('\\n')\n> -    description[0] = '\\\\brief ' + description[0]\n> -    return '\\n'.join([(line and ' * ' or ' *') + line for line in description])\n> +    for i in range(1, len(description)):\n> +        line = description[i]\n> +        description[i] = (line and ' * ' or ' *') + line\n> +    return '\\n'.join(description)\n>  \n>  \n> -def generate_cpp(controls):\n> -    enum_doc_start_template = string.Template('''/**\n> - * \\\\enum ${name}Enum\n> - * \\\\brief Supported ${name} values''')\n> -    enum_doc_value_template = string.Template(''' * \\\\var ${value}\n> -${description}''')\n> -    doc_template = string.Template('''/**\n> - * \\\\var ${name}\n> -${description}\n> - */''')\n> -    def_template = string.Template('extern const Control<${type}> ${name}(${id_name}, \"${name}\");')\n> -    enum_values_doc = string.Template('''/**\n> - * \\\\var ${name}Values\n> - * \\\\brief List of all $name supported values\n> - */''')\n> -    enum_values_start = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values = {''')\n> -    enum_values_values = string.Template('''\\tstatic_cast<int32_t>(${name}),''')\n> -    name_value_map_doc = string.Template('''/**\n> - * \\\\var ${name}NameValueMap\n> - * \\\\brief Map of all $name supported value names (in std::string format) to value\n> - */''')\n> -    name_value_map_start = string.Template('''extern const std::map<std::string, ${type}> ${name}NameValueMap = {''')\n> -    name_value_values = string.Template('''\\t{ \"${name}\", ${name} },''')\n> +def extend_control(ctrl, id, ranges):\n> +    ctrl.id = ranges[ctrl.vendor] + id + 1\n>  \n> -    ctrls_doc = {}\n> -    ctrls_def = {}\n> -    ctrls_map = []\n> +    if ctrl.vendor != 'libcamera':\n> +        ctrl.namespace = f'{ctrl.vendor}::'\n> +    else:\n> +        ctrl.namespace = ''\n>  \n> -    for ctrl in controls:\n> -        id_name = snake_case(ctrl.name).upper()\n> +    ctrl.documentation = format_description(ctrl.description)\n\nThis is unused.\n\nOther than that, looks good to me. I'm impressed at how easy it was to\nread (it still took a while though).\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n>  \n> -        vendor = ctrl.vendor\n> -        if vendor not in ctrls_doc:\n> -            ctrls_doc[vendor] = []\n> -            ctrls_def[vendor] = []\n> -\n> -        info = {\n> -            'name': ctrl.name,\n> -            'type': ctrl.type,\n> -            'description': format_description(ctrl.description),\n> -            'id_name': id_name,\n> -        }\n> -\n> -        target_doc = ctrls_doc[vendor]\n> -        target_def = ctrls_def[vendor]\n> -\n> -        if ctrl.is_enum:\n> -            enum_doc = []\n> -            enum_doc.append(enum_doc_start_template.substitute(info))\n> -\n> -            num_entries = 0\n> -            for enum in ctrl.enum_values:\n> -                value_info = {\n> -                    'name': ctrl.name,\n> -                    'value': enum.name,\n> -                    'description': format_description(enum.description),\n> -                }\n> -                enum_doc.append(enum_doc_value_template.substitute(value_info))\n> -                num_entries += 1\n> -\n> -            enum_doc = '\\n *\\n'.join(enum_doc)\n> -            enum_doc += '\\n */'\n> -            target_doc.append(enum_doc)\n> -\n> -            values_info = {\n> -                'name': info['name'],\n> -                'type': ctrl.type,\n> -                'size': num_entries,\n> -            }\n> -            target_doc.append(enum_values_doc.substitute(values_info))\n> -            target_def.append(enum_values_start.substitute(values_info))\n> -            for enum in ctrl.enum_values:\n> -                value_info = {\n> -                    'name': enum.name\n> -                }\n> -                target_def.append(enum_values_values.substitute(value_info))\n> -            target_def.append(\"};\")\n> -\n> -            target_doc.append(name_value_map_doc.substitute(values_info))\n> -            target_def.append(name_value_map_start.substitute(values_info))\n> -            for enum in ctrl.enum_values:\n> -                value_info = {\n> -                    'name': enum.name\n> -                }\n> -                target_def.append(name_value_values.substitute(value_info))\n> -            target_def.append(\"};\")\n> -\n> -        target_doc.append(doc_template.substitute(info))\n> -        target_def.append(def_template.substitute(info))\n> -\n> -        vendor_ns = vendor + '::' if vendor != \"libcamera\" else ''\n> -        ctrls_map.append('\\t{ ' + vendor_ns + id_name + ', &' + vendor_ns + ctrl.name + ' },')\n> -\n> -    vendor_ctrl_doc_sub = []\n> -    vendor_ctrl_template = string.Template('''\n> -/**\n> - * \\\\brief Namespace for ${vendor} controls\n> - */\n> -namespace ${vendor} {\n> -\n> -${vendor_controls_str}\n> -\n> -} /* namespace ${vendor} */''')\n> -\n> -    for vendor in [v for v in ctrls_doc.keys() if v not in ['libcamera']]:\n> -        vendor_ctrl_doc_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\\n\\n'.join(ctrls_doc[vendor])}))\n> -\n> -    vendor_ctrl_def_sub = []\n> -    for vendor in [v for v in ctrls_def.keys() if v not in ['libcamera']]:\n> -        vendor_ctrl_def_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\\n'.join(ctrls_def[vendor])}))\n> -\n> -    return {\n> -        'controls_doc': '\\n\\n'.join(ctrls_doc['libcamera']),\n> -        'controls_def': '\\n'.join(ctrls_def['libcamera']),\n> -        'controls_map': '\\n'.join(ctrls_map),\n> -        'vendor_controls_doc': '\\n'.join(vendor_ctrl_doc_sub),\n> -        'vendor_controls_def': '\\n'.join(vendor_ctrl_def_sub),\n> -    }\n> -\n> -\n> -def generate_h(controls, mode, ranges):\n> -    enum_template_start = string.Template('''enum ${name}Enum {''')\n> -    enum_value_template = string.Template('''\\t${name} = ${value},''')\n> -    enum_values_template = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values;''')\n> -    name_value_map_template = string.Template('''extern const std::map<std::string, ${type}> ${name}NameValueMap;''')\n> -    template = string.Template('''extern const Control<${type}> ${name};''')\n> -\n> -    ctrls = {}\n> -    ids = {}\n> -    id_value = {}\n> -\n> -    for ctrl in controls:\n> -        id_name = snake_case(ctrl.name).upper()\n> -\n> -        vendor = ctrl.vendor\n> -        if vendor not in ctrls:\n> -            if vendor not in ranges.keys():\n> -                raise RuntimeError(f'Control id range is not defined for vendor {vendor}')\n> -            id_value[vendor] = ranges[vendor] + 1\n> -            ids[vendor] = []\n> -            ctrls[vendor] = []\n> -\n> -        target_ids = ids[vendor]\n> -        target_ids.append('\\t' + id_name + ' = ' + str(id_value[vendor]) + ',')\n> -\n> -        info = {\n> -            'name': ctrl.name,\n> -            'type': ctrl.type,\n> -        }\n> -\n> -        target_ctrls = ctrls[vendor]\n> -\n> -        if ctrl.is_enum:\n> -            target_ctrls.append(enum_template_start.substitute(info))\n> -\n> -            num_entries = 0\n> -            for enum in ctrl.enum_values:\n> -                value_info = {\n> -                    'name': enum.name,\n> -                    'value': enum.value,\n> -                }\n> -                target_ctrls.append(enum_value_template.substitute(value_info))\n> -                num_entries += 1\n> -            target_ctrls.append(\"};\")\n> -\n> -            values_info = {\n> -                'name': info['name'],\n> -                'type': ctrl.type,\n> -                'size': num_entries,\n> -            }\n> -            target_ctrls.append(enum_values_template.substitute(values_info))\n> -            target_ctrls.append(name_value_map_template.substitute(values_info))\n> -\n> -        target_ctrls.append(template.substitute(info))\n> -        id_value[vendor] += 1\n> -\n> -    vendor_template = string.Template('''\n> -namespace ${vendor} {\n> -\n> -#define LIBCAMERA_HAS_${vendor_def}_VENDOR_${mode}\n> -\n> -enum {\n> -${vendor_enums}\n> -};\n> -\n> -${vendor_controls}\n> -\n> -} /* namespace ${vendor} */\n> -''')\n> -\n> -    vendor_sub = []\n> -    for vendor in [v for v in ctrls.keys() if v != 'libcamera']:\n> -        vendor_sub.append(vendor_template.substitute({'mode': mode.upper(),\n> -                                                      'vendor': vendor,\n> -                                                      'vendor_def': vendor.upper(),\n> -                                                      'vendor_enums': '\\n'.join(ids[vendor]),\n> -                                                      'vendor_controls': '\\n'.join(ctrls[vendor])}))\n> -\n> -    return {\n> -        'ids': '\\n'.join(ids['libcamera']),\n> -        'controls': '\\n'.join(ctrls['libcamera']),\n> -        'vendor_controls': '\\n'.join(vendor_sub)\n> -    }\n> -\n> -\n> -def fill_template(template, data):\n> -\n> -    template = open(template, 'rb').read()\n> -    template = template.decode('utf-8')\n> -    template = string.Template(template)\n> -    return template.substitute(data)\n> +    return ctrl\n>  \n>  \n>  def main(argv):\n> @@ -358,29 +165,47 @@ def main(argv):\n>          data = open(args.ranges, 'rb').read()\n>          ranges = yaml.safe_load(data)['ranges']\n>  \n> -    controls = []\n> +    controls = {}\n>      for input in args.input:\n> -        with open(input, 'rb') as f:\n> -            data = f.read()\n> -            vendor = yaml.safe_load(data)['vendor']\n> -            ctrls = yaml.safe_load(data)['controls']\n> -            controls = controls + [Control(*ctrl.popitem(), vendor) for ctrl in ctrls]\n> +        data = yaml.safe_load(open(input, 'rb').read())\n>  \n> -    if args.template.endswith('.cpp.in'):\n> -        data = generate_cpp(controls)\n> -    elif args.template.endswith('.h.in'):\n> -        data = generate_h(controls, args.mode, ranges)\n> -    else:\n> -        raise RuntimeError('Unknown template type')\n> +        vendor = data['vendor']\n> +        if vendor not in ranges.keys():\n> +            raise RuntimeError(f'Control id range is not defined for vendor {vendor}')\n>  \n> -    data = fill_template(args.template, data)\n> +        ctrls = controls.setdefault(vendor, [])\n> +\n> +        for i, ctrl in enumerate(data['controls']):\n> +            ctrl = Control(*ctrl.popitem(), vendor)\n> +            ctrls.append(extend_control(ctrl, i, ranges))\n> +\n> +    # Sort the vendors by range numerical value\n> +    controls = [[vendor, ctrls] for vendor, ctrls in controls.items()]\n> +    controls.sort(key=lambda item: ranges[item[0]])\n> +\n> +    filename = {\n> +        'controls': 'control_ids',\n> +        'properties': 'property_ids',\n> +    }[args.mode]\n> +\n> +    data = {\n> +        'filename': filename,\n> +        'mode': args.mode,\n> +        'controls': controls,\n> +    }\n> +\n> +    env = jinja2.Environment()\n> +    env.filters['format_description'] = format_description\n> +    env.filters['snake_case'] = snake_case\n> +    template = env.from_string(open(args.template, 'r', encoding='utf-8').read())\n> +    string = template.render(data)\n>  \n>      if args.output:\n> -        output = open(args.output, 'wb')\n> -        output.write(data.encode('utf-8'))\n> +        output = open(args.output, 'w', encoding='utf-8')\n> +        output.write(string)\n>          output.close()\n>      else:\n> -        sys.stdout.write(data)\n> +        sys.stdout.write(string)\n>  \n>      return 0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 75AA4C323E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 15 Aug 2024 04:16:50 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 32D5C633BD;\n\tThu, 15 Aug 2024 06:16:49 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 58E6763382\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 Aug 2024 06:16:47 +0200 (CEST)","from pyrite.rasen.tech (h175-177-049-024.catv02.itscom.jp\n\t[175.177.49.24])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 08431827;\n\tThu, 15 Aug 2024 06:15:47 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"R7EsPKWq\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1723695349;\n\tbh=DMz8Ib2IemqjdrDuZud8FUTKxQv1aeOwTHlvZMI+XXw=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=R7EsPKWqXe8T5Y0cH9RVrjkFqCIB4T4p724RJE/L2Yb+X3U++NU1YozQTipXuNnel\n\tFZHvFQ/4BrH4XDl0WfR/8ian2ytuJQjz2XcPwfuMubqHSjbm0+Oc+AvqnZD1F5tBZx\n\tYpnuI+wrbZYdSvuXitPzbCowDmtVRE2X1C0/lHSA=","Date":"Thu, 15 Aug 2024 13:16:40 +0900","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 07/10] utils: codegen: gen-controls.py: Convert to jinja2\n\ttemplates","Message-ID":"<Zr2BKPU1daEQugNR@pyrite.rasen.tech>","References":"<20240809005914.20662-1-laurent.pinchart@ideasonboard.com>\n\t<20240809005914.20662-8-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20240809005914.20662-8-laurent.pinchart@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":30835,"web_url":"https://patchwork.libcamera.org/comment/30835/","msgid":"<Zr2LoUzHJ9delTBo@pyrite.rasen.tech>","date":"2024-08-15T05:01:21","subject":"Re: [PATCH 07/10] utils: codegen: gen-controls.py: Convert to jinja2\n\ttemplates","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"On Fri, Aug 09, 2024 at 03:59:11AM +0300, Laurent Pinchart wrote:\n> Jinja2 templates help separating the logic related to the template from\n\nOh I just remembered,\n\ns/separating/separate/\n\n> the generation of the data. The python code gets much clearer as a\n> result.\n\ns/gets/becomes/\n\nSame for 10/10\n\nPaul\n\n<snip>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 4D4A2BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 15 Aug 2024 05:01:30 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7C2CC633BA;\n\tThu, 15 Aug 2024 07:01:29 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8418963394\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 Aug 2024 07:01:27 +0200 (CEST)","from pyrite.rasen.tech (h175-177-049-024.catv02.itscom.jp\n\t[175.177.49.24])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 344526CA;\n\tThu, 15 Aug 2024 07:00:27 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"kMy3+8Gx\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1723698029;\n\tbh=+z3apzQE/3upTKwbshxZ5f8FihTqTBVOXoLc0MhPTV8=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=kMy3+8GxrVAIEVI56SvIqLuC8ununJZSB5LRBfJ5CawjnbNodxMNRJajK3CPeWqVC\n\t9nHY1tIDBSukxE/5w15nKBrHvBXYG0PgqtPHy/FylrcMg4VD0RBnn6cz1TY1cS36Li\n\tpfjIO7lBiTmzf/KUXlGlc90OV74/lp8B6ki5/PjU=","Date":"Thu, 15 Aug 2024 14:01:21 +0900","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 07/10] utils: codegen: gen-controls.py: Convert to jinja2\n\ttemplates","Message-ID":"<Zr2LoUzHJ9delTBo@pyrite.rasen.tech>","References":"<20240809005914.20662-1-laurent.pinchart@ideasonboard.com>\n\t<20240809005914.20662-8-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20240809005914.20662-8-laurent.pinchart@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]