225 lines
8.7 KiB
Python
225 lines
8.7 KiB
Python
#------------------------------------------------------------------------------
|
|
# Copyright (c) 2022, Oracle and/or its affiliates.
|
|
#
|
|
# This software is dual-licensed to you under the Universal Permissive License
|
|
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
|
|
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
|
|
# either license.
|
|
#
|
|
# If you elect to accept the software under the Apache License, Version 2.0,
|
|
# the following applies:
|
|
#
|
|
# 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.
|
|
#------------------------------------------------------------------------------
|
|
|
|
#------------------------------------------------------------------------------
|
|
# build_from_template.py
|
|
#
|
|
# Builds the parameter file from the template supplied on the command line.
|
|
# The generated file is written to the src/oracledb directory. The following
|
|
# template keys are recognized:
|
|
#
|
|
# #{{ args_help_with_defaults }}
|
|
# is replaced by the arguments help string with field defaults
|
|
# #{{ args_help_without_defaults }}
|
|
# is replaced by the arguments help string without field defaults
|
|
# #{{ args_with_defaults }}
|
|
# is replaced by the arguments with field defaults included
|
|
# #{{ args_without_defaults }}
|
|
# is replaced by the arguments without field defaults (all set to None)
|
|
# #{{ generated_notice }}
|
|
# is replaced by a notice that the file is generated and should not be
|
|
# modified directly
|
|
# #{{ params_properties }}
|
|
# is replaced by generated property getter and setter methods
|
|
# #{{ params_repr_parts }}
|
|
# is replaced by generated parts for the repr() function
|
|
#
|
|
# All of these could be accomplished by decorators, but doing so would
|
|
# eliminate the usefulness of static analyzers such as those used within Visual
|
|
# Studio Code.
|
|
#------------------------------------------------------------------------------
|
|
|
|
import argparse
|
|
import configparser
|
|
import dataclasses
|
|
import os
|
|
import sys
|
|
import textwrap
|
|
|
|
TEXT_WIDTH = 79
|
|
|
|
@dataclasses.dataclass
|
|
class Field:
|
|
name: str = ""
|
|
typ: str = ""
|
|
default: str = ""
|
|
hidden: bool = False
|
|
pool_only: bool = False
|
|
description: str = ""
|
|
decorator: str = None
|
|
|
|
# parse command line
|
|
parser = argparse.ArgumentParser(description="build module from template")
|
|
parser.add_argument("name", help="the name of the module to generate")
|
|
args = parser.parse_args()
|
|
|
|
# determine location of template and source and validate template
|
|
base_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
|
target_dir = os.path.join(os.path.dirname(base_dir), "src", "oracledb")
|
|
template_name = os.path.join(base_dir, "templates", f"{args.name}.py")
|
|
config_name = os.path.join(base_dir, "fields.cfg")
|
|
target_name = os.path.join(target_dir, f"{args.name}.py")
|
|
if not os.path.exists(template_name):
|
|
raise Exception(f"template {template_name} does not exist!")
|
|
if not os.path.exists(config_name):
|
|
raise Exception(f"configuration {config_name} does not exist!")
|
|
code = open(template_name).read()
|
|
pool_only = "pool" in args.name
|
|
|
|
# acquire the fields from the configuration file
|
|
fields = []
|
|
config = configparser.ConfigParser()
|
|
config.read(config_name)
|
|
for section in config.sections():
|
|
field = Field()
|
|
field.name = section
|
|
field.typ = config.get(section, "type")
|
|
field.default = config.get(section, "default", fallback="None")
|
|
field.hidden = config.getboolean(section, "hidden", fallback=False)
|
|
field.pool_only = config.getboolean(section, "pool_only", fallback=False)
|
|
field.description = config.get(section, "description", fallback="").strip()
|
|
field.decorator = config.get(section, "decorator", fallback="")
|
|
if not field.pool_only or pool_only:
|
|
fields.append(field)
|
|
|
|
def replace_tag(tag, content_generator):
|
|
"""
|
|
Replaces a template tag with content generated by a function. The content
|
|
found before the tag is passed to the generator function.
|
|
"""
|
|
global code
|
|
search_value = "#{{ " + tag + " }}"
|
|
while True:
|
|
pos = code.find(search_value)
|
|
if pos < 0:
|
|
break
|
|
prev_line_pos = code[:pos].rfind("\n")
|
|
indent = code[prev_line_pos + 1:pos]
|
|
content = content_generator(indent)
|
|
code = code[:pos] + content + code[pos + len(search_value):]
|
|
|
|
def args_help_with_defaults_content(indent):
|
|
"""
|
|
Generates the content for the args_help_with_defaults template tag.
|
|
"""
|
|
raw_descriptions = [f"- {f.name}: {f.description} (default: {f.default})" \
|
|
for f in fields if f.description]
|
|
descriptions = [textwrap.fill(d, initial_indent=indent,
|
|
subsequent_indent=indent + " ",
|
|
width=TEXT_WIDTH) \
|
|
for d in raw_descriptions]
|
|
return "\n\n".join(descriptions).strip()
|
|
|
|
def args_help_without_defaults_content(indent):
|
|
"""
|
|
Generates the content for the args_help_without_defaults template tag.
|
|
"""
|
|
raw_descriptions = [f"- {f.name}: {f.description}" \
|
|
for f in fields if f.description]
|
|
descriptions = [textwrap.fill(d, initial_indent=indent,
|
|
subsequent_indent=indent + " ",
|
|
width=TEXT_WIDTH) \
|
|
for d in raw_descriptions]
|
|
return "\n\n".join(descriptions).strip()
|
|
|
|
def args_with_defaults_content(indent):
|
|
"""
|
|
Generates the content for the args_with_defaults template tag.
|
|
"""
|
|
args_joiner = ",\n" + indent
|
|
args = [f"{f.name}: {f.typ}={f.default}" for f in fields]
|
|
return args_joiner.join(args)
|
|
|
|
def args_without_defaults_content(indent):
|
|
"""
|
|
Generates the content for the args_without_defaults template tag.
|
|
"""
|
|
args_joiner = ",\n" + indent
|
|
args = [f"{f.name}: {f.typ}=None" for f in fields]
|
|
return args_joiner.join(args)
|
|
|
|
def generated_notice_content(indent):
|
|
"""
|
|
Generates the content for the generated_notice template tag.
|
|
"""
|
|
notice = """
|
|
*** NOTICE *** This file is generated from a template and should
|
|
not be modified directly. See build_from_template.py in the utils
|
|
subdirectory for more information."""
|
|
return textwrap.fill(textwrap.dedent(notice).strip(),
|
|
subsequent_indent=indent, width=TEXT_WIDTH)
|
|
|
|
def params_properties_content(indent):
|
|
"""
|
|
Generates the content for the params_properties template tag.
|
|
"""
|
|
functions = []
|
|
for field in sorted(fields, key=lambda f: f.name.upper()):
|
|
if field.hidden:
|
|
continue
|
|
if field.pool_only != pool_only:
|
|
continue
|
|
description = f"{field.description[0].upper()}{field.description[1:]}."
|
|
doc_string = textwrap.fill(description, initial_indent=" ",
|
|
subsequent_indent=" ",
|
|
width=TEXT_WIDTH - len(indent))
|
|
return_type = f"Union[list, {field.typ}]" if field.decorator \
|
|
else field.typ
|
|
|
|
body_lines = [
|
|
f'@property',
|
|
f'@{field.decorator}' if field.decorator else '',
|
|
f'def {field.name}(self) -> {return_type}:',
|
|
f' """'
|
|
] + doc_string.splitlines() + [
|
|
f' """',
|
|
f' return self._impl.{field.name}'
|
|
]
|
|
joiner = "\n" + indent
|
|
functions.append(joiner.join(s for s in body_lines if s))
|
|
joiner = '\n\n' + indent
|
|
return joiner.join(functions)
|
|
|
|
def params_repr_parts_content(indent):
|
|
"""
|
|
Generates the content for the params_repr_parts template tag.
|
|
"""
|
|
parts = [f'f"{field.name}={{self.{field.name}!r}}, "' \
|
|
for field in fields if not field.hidden]
|
|
parts[-1] = parts[-1][:-3] + '"'
|
|
joiner = ' + \\\n' + indent
|
|
return joiner.join(parts)
|
|
|
|
# replace generated_notice template tag
|
|
replace_tag("args_help_with_defaults", args_help_with_defaults_content)
|
|
replace_tag("args_help_without_defaults", args_help_without_defaults_content)
|
|
replace_tag("args_with_defaults", args_with_defaults_content)
|
|
replace_tag("args_without_defaults", args_without_defaults_content)
|
|
replace_tag("generated_notice", generated_notice_content)
|
|
replace_tag("params_properties", params_properties_content)
|
|
replace_tag("params_repr_parts", params_repr_parts_content)
|
|
|
|
# write the final code to the target location
|
|
open(target_name, "w").write(code)
|