From dd327d31b751b8d2e7097a55c09b26ab9c2037ac Mon Sep 17 00:00:00 2001 From: jparisu <69341543+jparisu@users.noreply.github.com> Date: Wed, 30 Nov 2022 07:47:41 +0100 Subject: [PATCH] Build Enumeration Dev Utils (#17) * Implement dev-utils Build Enumeration Signed-off-by: jparisu * Add vector to custom enumeration builder Signed-off-by: jparisu * Add fixes to enumeration builder Signed-off-by: jparisu * apply suggestions Signed-off-by: jparisu * remove ... in file path Signed-off-by: jparisu Signed-off-by: jparisu --- .../cpp_utils/macros/custom_enumeration.hpp | 73 +++-- dev_utils/README.md | 14 + .../cpp/enumeration_builder/README.md | 19 ++ .../enumeration_builder.py | 303 ++++++++++++++++++ 4 files changed, 372 insertions(+), 37 deletions(-) create mode 100644 dev_utils/README.md create mode 100644 dev_utils/src/automatic_code_generator/cpp/enumeration_builder/README.md create mode 100644 dev_utils/src/automatic_code_generator/cpp/enumeration_builder/enumeration_builder.py diff --git a/cpp_utils/include/cpp_utils/macros/custom_enumeration.hpp b/cpp_utils/include/cpp_utils/macros/custom_enumeration.hpp index defa2f61..c617ecea 100644 --- a/cpp_utils/include/cpp_utils/macros/custom_enumeration.hpp +++ b/cpp_utils/include/cpp_utils/macros/custom_enumeration.hpp @@ -35,9 +35,9 @@ namespace utils { /** * @brief This macro creates a Custom Enumeration with auxiliary functions and variables. * - * An enumeration built with ENUEMERATION_BUILDER has: + * An enumeration built with ENUMERATION_BUILDER has: * - enum class with name \c enumeration_name and N values, one for each extra argument, and with that exact name. - * - array called \c nammes_ with the names of each element of the enumeration + * - array called \c NAMES_ with the names of each element of the enumeration * as strings of the enum value. * - \c to_string method to get the string associated with an enumeration value. * - \c from_string_ method that gives enumeration value from string name. @@ -55,42 +55,41 @@ namespace utils { * my_value = from_string_CustomEnum("el2"); // Set my_value as el2 = 1 * to_string(my_value); // = "el2" */ -#define ENUMERATION_BUILDER(enumeration_name, ...) \ - \ - /* Forbid empty enumerations */ \ - static_assert( COUNT_ARGUMENTS(__VA_ARGS__), "Empty Enumerations are not allowed."); \ - \ - /* Declare enumeration */ \ - enum class enumeration_name {__VA_ARGS__ \ - }; \ - \ - /* Initialize name arrays */ \ - const std::array names_ ## enumeration_name = \ - { APPLY_MACRO_FOR_EACH(STRINGIFY_WITH_COMMA, __VA_ARGS__) }; \ - \ - /* To string method */ \ - inline const std::string& to_string(const enumeration_name& e) \ - { return names_ ## enumeration_name[static_cast(e)]; } \ - \ - inline std::vector string_vector_ ## enumeration_name() \ - { return std::vector (names_ ## enumeration_name.begin(), names_ ## enumeration_name.end()); } \ - \ - /* From string */ \ - inline enumeration_name from_string_ ## enumeration_name(const std::string& s) \ - { \ - for (int i = 0; i < COUNT_ARGUMENTS(__VA_ARGS__); i++) \ - if (names_ ## enumeration_name[i] == s)return static_cast(i); \ - throw eprosima::utils::InitializationException( \ - STR_ENTRY << "Not correct name " << s << " for Enum " << STRINGIFY(enumeration_name) << "."); \ - } \ - \ - /* Serialization operation */ \ - inline std::ostream& operator <<(std::ostream& os, const enumeration_name& e) \ - { os << to_string(e); return os; } \ - \ - /* Number of elements in enumeration */ \ +#define ENUMERATION_BUILDER(enumeration_name, ...) \ + \ + /* Forbid empty enumerations */ \ + static_assert( COUNT_ARGUMENTS(__VA_ARGS__), "Empty Enumerations are not allowed."); \ + \ + /* Declare enumeration */ \ + enum class enumeration_name {__VA_ARGS__ \ + }; \ + \ + /* Initialize name arrays */ \ + const std::array NAMES_ ## enumeration_name = \ + { APPLY_MACRO_FOR_EACH(STRINGIFY_WITH_COMMA, __VA_ARGS__) }; \ + \ + /* To string method */ \ + inline const std::string& to_string(const enumeration_name& e) \ + { return NAMES_ ## enumeration_name[static_cast(e)]; } \ + \ + inline std::vector string_vector_ ## enumeration_name() \ + { return std::vector (NAMES_ ## enumeration_name.begin(), NAMES_ ## enumeration_name.end()); } \ + \ + /* From string */ \ + inline enumeration_name from_string_ ## enumeration_name(const std::string& s) \ + { \ + for (int i = 0; i < COUNT_ARGUMENTS(__VA_ARGS__); i++) \ + if (NAMES_ ## enumeration_name[i] == s)return static_cast(i); \ + throw eprosima::utils::InitializationException( \ + STR_ENTRY << "Not correct name " << s << " for Enum " << STRINGIFY(enumeration_name) << "."); \ + } \ + \ + /* Serialization operation */ \ + inline std::ostream& operator <<(std::ostream& os, const enumeration_name& e) \ + { os << to_string(e); return os; } \ + \ + /* Number of elements in enumeration */ \ constexpr const unsigned int N_VALUES_ ## enumeration_name = COUNT_ARGUMENTS(__VA_ARGS__) - } /* namespace utils */ } /* namespace eprosima */ diff --git a/dev_utils/README.md b/dev_utils/README.md new file mode 100644 index 00000000..b9dc469f --- /dev/null +++ b/dev_utils/README.md @@ -0,0 +1,14 @@ +# eProsima Developers Utils Module + +This is a package that contains certain scripts to help in the implementation of new projects. +These scripts are meant to generate code or certain utils in a fast and optimal way. + +> :warning: This is not a linkable package, it is only for utils focus on developing faster or better code. + +## Automatic Code Generator + +These tools are meant to autogenerate general code in different languages. + +### Enumeration builder + +Extend in a new file the whole `ENUMERATION_BUILDER` macro. diff --git a/dev_utils/src/automatic_code_generator/cpp/enumeration_builder/README.md b/dev_utils/src/automatic_code_generator/cpp/enumeration_builder/README.md new file mode 100644 index 00000000..2010bbe7 --- /dev/null +++ b/dev_utils/src/automatic_code_generator/cpp/enumeration_builder/README.md @@ -0,0 +1,19 @@ +# ENUMERATION BUILDER + +This auto generates the code that would be generated by macro `ENUMERATION_BUILDER` in +`dev-utils/cpp_utils/include/cpp_utils/macros/custom_enumeration.hpp`. + +## Motivation + +This script is useful because the use of this macro could have problems depending on its use and/or architecture: + +- Windows does extend macros in a different order. +- SWIG does not create classes or enums that are extended from a macro. + +## Usage + +This is a standalone script. In order to execute it: + +```sh +python3 enumeration_builder.py --output include/output.hpp --enum CustomEnum --values "value1;value2" --namespaces "eprosima;utils" +``` diff --git a/dev_utils/src/automatic_code_generator/cpp/enumeration_builder/enumeration_builder.py b/dev_utils/src/automatic_code_generator/cpp/enumeration_builder/enumeration_builder.py new file mode 100644 index 00000000..6e6c8699 --- /dev/null +++ b/dev_utils/src/automatic_code_generator/cpp/enumeration_builder/enumeration_builder.py @@ -0,0 +1,303 @@ +# Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# 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. + +""" +Script to generate code for ENUMERATION_BUILDER in C++. + +NOTE: This script is meant to be a standalone, without requiring any other script, file or path. +""" + +import argparse +import datetime +import logging +import os +import sys + +DESCRIPTION = """Script to generate code for ENUMERATION_BUILDER in C++.""" +USAGE = ( + 'python3 enumeration_builder.py' + ' --enum CustomEnum' + ' --values "value1;value2"' + ' --output include/output.hpp' + ' --namespaces "eprosima;utils"' +) + +TEMPLATE_TEXT = """// Copyright %year% Proyectos y Sistemas de Mantenimiento SL (eProsima). +// +// 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. + +/** + * @file %output_file% + * + * This is an autogenerated file from command: + * %command% + */ + +#pragma once + +#include +#include +#include +#include + +%start_namespace% + +enum class %enum_name% +{ +%values% +}; + +const std::array NAMES_%enum_name% = +{ +%quotes_values% +}; + +inline const std::string& to_string( + const %enum_name%& e) +{ + return NAMES_%enum_name%[static_cast(e)]; +} + +inline std::vector string_vector_%enum_name%() +{ + return std::vector ( + NAMES_%enum_name%.begin(), + NAMES_%enum_name%.end()); +} + +inline %enum_name% from_string_%enum_name%( + const std::string& s) +{ + for (int i = 0; i < %n_values%; i++) + { + if (NAMES_%enum_name%[i] == s) + { + return static_cast<%enum_name%>(i); + } + } + throw std::invalid_argument("Incorrect name for enum %enum_name%."); +} + +inline std::ostream& operator <<( + std::ostream& os, + const %enum_name%& e) +{ + os << to_string(e); + return os; +} + +constexpr const unsigned int N_VALUES_%enum_name% = %n_values%; + +%end_namespace% +""" + + +def parse_options(): + """ + Parse arguments. + + :return: The arguments parsed. + """ + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + add_help=True, + description=(DESCRIPTION), + usage=(USAGE) + ) + + required_args = parser.add_argument_group('required arguments') + required_args.add_argument( + '-e', + '--enum', + type=str, + required=True, + help='Name of the Enumeration.' + ) + required_args.add_argument( + '-v', + '--values', + type=str, + required=True, + help="Name of values separated by ';' (blank spaces would be stripped)." + ) + + parser.add_argument( + '-o', + '--output', + type=str, + default=None, + help='Path to hpp output file. [Default: .hpp' + ) + parser.add_argument( + '-n', + '--namespaces', + type=str, + default='eprosima', + help="Namespaces separated by ';' (blank spaces would be stripped)." + ) + parser.add_argument( + '-d', + '--debug', + action='store_true', + help="Namespaces separated by ';' (blank spaces would be stripped)." + ) + + return parser.parse_args() + + +def get_file_name_from_path( + path_str: str): + """Get name of the file from a file path.""" + return path_str.split('/')[-1].split('\\')[-1].strip() + + +def get_command_executed(): + """Convert string arguments into a list of strings.""" + # Get flags and their args (it has a default first empty string to apply join to first case) + flag_and_args = [''] + for arg in sys.argv[1:]: + if '-' in arg: + flag_and_args.append(arg) + else: + # If it is a file path short it + arg = get_file_name_from_path(arg) + flag_and_args[-1] += f' {arg}' + # Return string with format + return f'{os.path.basename(__file__)}' + '\n * '.join(flag_and_args) + + +def parse_values( + values_str: str): + """Convert string arguments into a list of strings.""" + separator = (';' if ';' in values_str else ',') + return [x.strip().replace(' ', '') for x in values_str.split(separator)] + + +def replace_in_text( + text_to_replace: str, + original_str: str, + replacement_str: str) -> str: + """Replace every occurrence of %original_str% for replacement_str.""" + return text_to_replace.replace(f'%{original_str}%', replacement_str) + + +def get_year() -> str: + """Return a string with the current year.""" + return str(datetime.date.today().year) + + +def generate_file_text( + template_text: str, + enumeration_name: str, + enumeration_values, + namespaces, + output_file) -> str: + """Generate text for file from template.""" + # Get text + text = template_text + + # Parse year + text = replace_in_text(text, 'year', get_year()) + + # Parse output file + text = replace_in_text(text, 'output_file', get_file_name_from_path(output_file)) + + # Parse command + text = replace_in_text(text, 'command', get_command_executed()) + + # Parse start_namespace + text = replace_in_text( + text, + 'start_namespace', + '\n'.join([f'namespace {x} {{' for x in namespaces])) + + # Parse end_namespace + text = replace_in_text( + text, + 'end_namespace', + '\n'.join([f'}} /* namespace {x} */' for x in reversed(namespaces)])) + + # Parse enum_name + text = replace_in_text(text, 'enum_name', enumeration_name) + + # Parse values + text = replace_in_text( + text, + 'values', + ',\n'.join([f' {x}' for x in enumeration_values])) + + # Parse between quotes values + text = replace_in_text( + text, + 'quotes_values', + ',\n'.join([f' "{x}"' for x in enumeration_values])) + + # Parse n_values + text = replace_in_text(text, 'n_values', str(len(enumeration_values))) + + return text + + +def generate_file( + output_file: str, + text: str) -> int: + """Write down text in target file.""" + # Check path exists and creates it + path_without_file = os.path.dirname(os.path.abspath(output_file)) + os.makedirs(path_without_file, exist_ok=True) + + # Write down file + with open(output_file, 'w') as f: + f.write(text) + + return 0 + + +def main(): + """Run main routine.""" + # Parse arguments + args = parse_options() + + # Set log level + if args.debug: + logging.basicConfig(level=logging.DEBUG) + + # Set output file + output_file = (args.output if args.output else f'{args.enum}.hpp') + + logging.debug(f'Command executed {get_command_executed()}') + logging.debug(f'Writing file {output_file}') + + return generate_file( + output_file=output_file, + text=generate_file_text( + template_text=TEMPLATE_TEXT, + enumeration_name=args.enum, + enumeration_values=parse_values(args.values), + namespaces=parse_values(args.namespaces), + output_file=output_file)) + + +if __name__ == '__main__': + main()