class GenerateDictTest(unittest.TestCase):
    def setUp(self):

        rospack = rospkg.RosPack()
        node_path = rospack.get_path('package_generator_templates')

        self.dir_template_spec = node_path + "/templates/cpp_node_update/config/"
        self.spec = TemplateSpec()

        # creating a temporary repo for trials
        self.dir_name = "/tmp/test_template_spec"
        if not os.path.exists(self.dir_name):
            print "Creating the repo {}".format(self.dir_name)
            os.makedirs(self.dir_name)

    def test_undef_files(self):
        self.assertFalse(self.spec.load_yaml_desc("unknown_file.yaml"))
        self.assertFalse(self.spec.load_functions("unknown_file.py"))

    def test_yaml_error(self):
        bad_file = """bad_content: ["name", "author","""
        filename = self.dir_name + "/bad_dico.yaml"
        with open(filename, 'w') as open_file:
            open_file.write(bad_file)

        self.assertFalse(self.spec.load_yaml_desc(filename))

    def test_correct_folder(self):
        self.assertTrue(self.spec.load_spec(self.dir_template_spec))
コード例 #2
0
ファイル: code_generator.py プロジェクト: ipa-jfh/ros_pkg_gen
def main():
    """main only defined for debugging purposes

    Returns:
        None: nothing
    """
    gen = CodeGenerator()
    xml_parser = PackageXMLParser()

    import rospkg
    rospack = rospkg.RosPack()
    node_path = rospack.get_path('package_generator_templates')

    dir_template_spec = node_path + "/templates/cpp_node_update/config"

    spec = TemplateSpec()

    if not spec.load_spec(dir_template_spec):
        print "Could not load the template spec"
        print "Bye"
        return

    if not xml_parser.set_template_spec(spec):
        print "template spec not compatible with parser requirements"
        print "Bye"
        return

    node_path = rospack.get_path('package_generator')
    filename = node_path + '/tests/data/demo.ros_package'

    if not xml_parser.load(filename):
        print "Error while parsing the xml file {}".format(filename)
        print "Bye"
        return

    xml_parser.set_active_node(0)
    gen.configure(xml_parser, spec)

    node_path = rospack.get_path('package_generator_templates')

    filename = node_path + '/templates/cpp_node_update/template/README.md'

    gen.reset_output_file()
    if gen.process_file(filename):
        output_file = "README.md"
        gen.write_output_file(output_file)
        print "Output written in file {}".format(output_file)
    else:
        print "Debug!"
コード例 #3
0
    def test_package_multi_nodes(self):
        """test generation of a package file involving multiple nodes spec.
        """

        spec = TemplateSpec()
        self.assertTrue(spec.load_spec(self.path_template_ + "config"))

        xml_parser = PackageXMLParser()
        self.assertTrue(xml_parser.set_template_spec(spec))
        self.assertTrue(xml_parser.load(self.multi_node_package_spec_))

        file_generator = CodeGenerator()
        self.assertTrue(file_generator.configure(xml_parser, spec))

        readme_file = self.path_template_ + "template/package.xml"
        output_file = self.dir_name + "/test_package.xml"

        self.assertTrue(file_generator.generate_disk_file(readme_file, output_file))
    def test_parsing(self):
        """
        check if a parsing goes well
        Mainly implemented as an example of use
        """

        node_path = self.rospack.get_path('package_generator_templates')

        # the current example only contains the dictionary
        dir_template_spec = node_path + "/templates/cpp_node_update/config/"
        spec = TemplateSpec()

        self.assertTrue(spec.load_spec(dir_template_spec))

        self.assertTrue(self.package_parser.set_template_spec(spec))

        node_path = self.rospack.get_path('package_generator')

        file_xml = node_path + '/tests/data/demo.ros_package'

        self.assertTrue(self.package_parser.load(file_xml))
コード例 #5
0
class PackageGenerator(EnhancedObject):
    """Handle the genration of a whole package

    Attributes:
        file_generator_ (CodeGenerator): custom generator
        jinja_generator_ (JinjaGenerator): generator based on jinja
        package_path_ (str): base location of the package to create
        path_pkg_backup_ (str): if the package already existed, location of the package backup
        spec_ (TemplateSpec): configuration of the template model
        template_path_ (str): path to the template to use
        xml_parser_ (PackageXMLParser): parser of the package description
    """
    def __init__(self, name="PackageGenerator"):
        """Intialisation of the object

        Args:
            name (str, optional): Name of the component, for printing aspect
        """
        #  call super class constructor
        super(PackageGenerator, self).__init__(name)

        # path to the template to use
        self.template_path_ = None
        # base location of the package to create
        self.package_path_ = None
        # parser of the package description
        self.xml_parser_ = None
        # config parameter provide with the template
        self.spec_ = None
        # generic file generator
        self.file_generator_ = None
        # jinja-based generator
        self.jinja_generator_ = None
        # if the package already existed, location of the package backup
        self.path_pkg_backup_ = None

    def check_template_structure(self, template_path):
        """Check a provided path refers to a valid template structure

        Args:
            template_path (str): path to the package template

        Returns:
            Bool: True if basic sanity checks are successful
        """
        if not os.path.exists(template_path):
            msg = "Template path ({}) is incorrect ".format(template_path)
            self.log_error(msg)
            return False

        if not os.path.isdir(template_path):
            msg = "Template path ({}) is not a directory ".format(
                template_path)
            self.log_error(msg)
            return False

        # check if minimum information is present.

        details = """A template should contain:
    * config/dictionary.yaml : the dictionary to be used
    * config/functions.py [optional] : additional functions used in the generation
    * config/generator.py [optional] : generator list (custom, jinja) default is custom
    * template/* set of elements to be generated
Revise the template, and compare to examples
        """

        is_ok = True
        # check for directories
        required_folders = ["config", "template"]
        for item in required_folders:
            req_folder = template_path + "/" + item
            if not os.path.isdir(req_folder):
                msg_err = "Error \n Expecting to have folder " + item
                msg_err += " in " + template_path
                self.log_error(msg_err)
                is_ok = False

        # check for files
        required_files = ["config/dictionary.yaml"]
        for item in required_files:
            req_file = template_path + "/" + item
            if not os.path.isfile(req_file):
                msg_err = "Error.\n Expecting to have file " + item
                msg_err += " in " + template_path
                self.log_error(msg_err)
                is_ok = False

        if not is_ok:
            self.log_error("\n{}".format(details))
            return False

        return True

    def get_template_info(self):
        """Get information about the available package templates

        Returns:
            list: tuple with [absolute package path, list of package names]
        """
        rospack = rospkg.RosPack()
        path_template = rospack.get_path('package_generator_templates')
        path_template += "/templates/"
        template_names = os.listdir(path_template)

        return [path_template, template_names]

    def generate_package(self, package_desc, output_path):
        """launches the package generation

        Args:
            package_desc (str): xml file containing the package description
            output_path (str): directory into which the package is created

        Returns:
            Bool: True if the operation succeeded
        """
        if not os.path.exists(output_path):
            msg_err = "Incorrect desired package path ({})".format(output_path)
            self.log_error(msg_err)
            return False

        if not os.path.isdir(output_path):
            msg_err = "Desired package path ({}) not a directory ".format(
                output_path)
            self.log_error(msg_err)
            return False

        # Initialising needed components
        # todo bring it to the constructor?
        self.spec_ = TemplateSpec()
        self.xml_parser_ = PackageXMLParser()
        self.file_generator_ = CodeGenerator()
        self.jinja_generator_ = JinjaGenerator()

        # Start finding the template

        template = self.xml_parser_.get_template(package_desc)
        if template is None:
            return False

        # Locate template location
        try:
            [all_template_path, template_names] = self.get_template_info()
        except rospkg.common.ResourceNotFound as error:
            msg = "Package package_generator_templates not found in rospack"
            self.log_error(msg)
            self.log_error(error)
            return False
        except OSError as error:
            msg = "No template dounf in package_generator_templates"
            self.log_error(msg)
            self.log_error(error)
            return False

        if template not in template_names:
            msg = "Template requested: {} unknown".format(template)
            self.log_error(msg)
            msg = "Available templates: {}".format(template_names)
            self.log_error(msg)
            return False

        template_path = all_template_path + "/" + template

        # confirm this is a template...
        if not self.check_template_structure(template_path):
            msg = "Please revise template structure"
            self.log_error(msg)
            return False

        # template localized, ready to work!
        self.template_path_ = template_path
        self.path_pkg_backup_ = None

        dir_template_spec = self.template_path_ + "/config/"
        if not self.spec_.load_spec(dir_template_spec):
            self.log_error("Could not load the template spec")
            return False

        if not self.xml_parser_.set_template_spec(self.spec_):
            msg_err = "Package spec not compatible with xml parser expectations"
            self.log_error(msg_err)
            return False

        if not self.xml_parser_.load(package_desc):
            msg_err = "Prb while parsing xml file {}".format(package_desc)
            self.log_error(msg_err)
            return False

        # todo why only the custom generator is configured?
        if not self.file_generator_.configure(self.xml_parser_, self.spec_):
            return False

        package_name = self.xml_parser_.get_package_spec()["name"]

        self.package_path_ = output_path + "/" + package_name

        if os.path.exists(self.package_path_):
            self.log_warn("Package {} already exists".format(
                self.package_path_))
            # moving preexisting code.
            # generating dir name using date
            now = datetime.datetime.now()
            str_date = now.strftime("%Y_%m_%d_%H_%M_%S")
            self.path_pkg_backup_ = "/tmp/{}_{}".format(
                os.path.basename(self.package_path_), str_date)
            self.log_warn("Original package temporally stored in {}".format(
                self.path_pkg_backup_))
            # TODO check if the move succeeded
            shutil.move(self.package_path_, self.path_pkg_backup_)
        else:
            self.log("Package to be created in {}".format(self.package_path_))
        os.makedirs(self.package_path_)

        nb_comp = self.xml_parser_.get_number_comps()
        self.log("Number of components defined: {}".format(nb_comp))

        if not self.generate_content():
            return False

        # we store the model into the directory model
        path = self.package_path_ + "/model"
        if not os.path.exists(path):
            os.makedirs(path)
        path += "/" + package_name + ".ros_package"

        if self.xml_parser_.is_dependency_complete_:
            try:
                if os.path.abspath(package_desc) == os.path.abspath(path):
                    # self.log_warn("Using generated model...")
                    stored_desc = self.path_pkg_backup_ + "/model/" + package_name + ".ros_package"
                    # self.log("check {} is absolute: {}".format(package_desc, os.path.abspath(package_desc)))
                    shutil.copyfile(stored_desc, path)
                else:
                    shutil.copyfile(package_desc, path)
                self.log("Package model saved in: {}".format(path))
            except IOError as error:
                self.log_error("Could not store model file: {}".format(error))
        else:
            # some dependencies were automatically added
            # model needs to be rewritten
            try:
                self.xml_parser_.write_xml(path)
                self.log("Package model updated & saved in: {}".format(path))
            except IOError as error:
                self.log_error("Could not store model file: {}".format(error))

        is_ok = self.handle_maintained_files()

        return is_ok

    def generate_one_file(self, template_file, result_file, force_write):
        """Generate a template file, depending on the generators to be used

        Args:
            template_file (str): template filename
            result_file (str): filename to store the result (unless is None)
            force_write (str): force the writting of empty files (if not, files is not written)

        Returns:
            Bool: True on success
        """

        generator = dict()
        generator["custom"] = self.file_generator_
        generator["jinja"] = self.jinja_generator_

        if len(self.spec_.generators_) == 1:
            return generator[self.spec_.generators_[0]].generate_disk_file(
                template_file, result_file, force_write)
        # two generators are to be used
        gen_one = generator[self.spec_.generators_[0]]
        gen_two = generator[self.spec_.generators_[1]]

        is_ok = gen_one.generate_disk_file(template_file)

        if not is_ok:
            return False

        return gen_two.generate_open_file(gen_one.rendered_, result_file,
                                          force_write)

    def check_template_file(self, template_file):
        """Generate a template file, depending on the generators to be used

        Args:
            template_file (str): template filename
            result_file (str): filename to store the result (unless is None)
            force_write (str): force the writting of empty files (if not, files is not written)

        Returns:
            Bool: True on success
        """

        generator = dict()
        generator["custom"] = self.file_generator_
        generator["jinja"] = self.jinja_generator_

        if len(self.spec_.generators_) == 1:
            # self.log("Check with Generator {}".format(self.spec_.generators_[0]))
            return generator[self.spec_.generators_[0]].check_template_file(
                template_file)

        # two generators are to be used
        gen_one = generator[self.spec_.generators_[0]]
        gen_two = generator[self.spec_.generators_[1]]

        # self.log("Check with Generator {}".format(self.spec_.generators_[0]))

        is_ok = gen_one.check_template_file(template_file)

        if not is_ok:
            return False

        # self.log("Check with Generator {}".format(self.spec_.generators_[1]))

        if self.spec_.generators_[1] == "jinja":
            return gen_two.check_template_file(gen_one.rendered_,
                                               is_filename=False)
        return gen_two.check_template_file(template_file)

    def write_generated_file(self, result_file):
        """Write a generated file

        Args:
            result_file (str): filename to store the file.

        Returns:
            Bool: True on success
        """
        generator = dict()
        generator["custom"] = self.file_generator_
        generator["jinja"] = self.jinja_generator_

        return generator[self.spec_.generators_[-1]].write_rendered_file(
            result_file)

    def get_generated_file(self):
        """Get the generated files

        Returns:
            list: list of of each line of the generated file
        """
        generator = dict()
        generator["custom"] = self.file_generator_
        generator["jinja"] = self.jinja_generator_

        return generator[self.spec_.generators_[-1]].rendered_

    def set_generated_file(self, l_file):
        """set the generated file

        Args:
            l_file (list): list of of each line of the generated file
        """
        generator = dict()
        generator["custom"] = self.file_generator_
        generator["jinja"] = self.jinja_generator_

        generator[self.spec_.generators_[-1]].rendered_ = l_file

    def handle_maintained_files(self):
        """Restore file Developer requests to maintain
        Assuming these patterns are defined in file .gen_maintain

        Returns:
            Bool: True on sucess
        """
        # check for files to be maintained
        if self.path_pkg_backup_ is None:
            # package just created, no maintained file
            return True

        filename_rel = ".gen_maintain"
        filename_abs = self.path_pkg_backup_ + "/" + filename_rel
        if os.path.exists(filename_abs) and os.path.isfile(filename_abs):
            self.log("Checking content to maintain after update")
        else:
            self.log("no maintained file defined in previous package version")
            return True

        with open(filename_abs) as open_file:
            for line in open_file:
                line = line.rstrip('\n')
                if not line:
                    continue
                path_abs = self.path_pkg_backup_ + "/" + line

                if not os.path.exists(path_abs):
                    msg = "Content {} not found. Revise {} content"
                    self.log_error(msg.format(line, filename_abs))
                    continue
                new_path = self.package_path_ + "/" + line

                if os.path.isfile(path_abs):
                    try:
                        self.log("Restoring file {}".format(line))
                        # check if directories needs to be created
                        dirname = os.path.dirname(line)
                        # self.log("dirname is : {}".format(dirname))
                        if dirname:
                            path_abs_dir = self.package_path_ + "/" + dirname
                            if not os.path.isdir(path_abs_dir):
                                os.makedirs(path_abs_dir)

                        shutil.copyfile(path_abs, new_path)
                    except IOError as error:
                        msg = "Could not restore a file: {}"
                        self.log_error(msg.format(error))
                    continue

                if os.path.isdir(path_abs):
                    try:
                        self.log("Restoring folder {}".format(line))
                        shutil.copytree(path_abs, new_path)
                    except IOError as error:
                        msg = "Could not restore folder: {}"
                        self.log_error(msg.format(error))
                    continue
                self.log_error("Unkown statement {}".format(line))

        # restoring the maintained content file
        try:
            self.log("Restoring file {}".format(filename_rel))
            new_path = self.package_path_ + "/" + filename_rel
            shutil.copyfile(filename_abs, new_path)
        except IOError as error:
            msg = "Could not restore file: {}"
            self.log_error(msg.format(error))
        return True

    def handle_status_and_advise(self, input_file, output_file, gen_flag):
        """Depending on the file generation process outcome,
           Adjust file status and inform user

        Args:
            input_file (str): path  of the template file used
            output_file (str): path of the generated file
            gen_flag (Bool): Success of the generation process

        Returns:
            Bool: True on success of the file generation
        """
        if not gen_flag:
            msg = "Prb while generating file {}".format(output_file)
            self.log_error(msg)
            return False
        # so the file generation went well
        if self.file_generator_.get_len_gen_file() == 0:
            # Only file __init__.py is kept empty
            if os.path.basename(output_file) != '__init__.py':
                msg = "File {} not written since empty".format(output_file)
                self.log_warn(msg)
                self.log_warn("Check: {}".format(
                    os.path.basename(output_file)))
                return True
        # file has content
        file_status = os.stat(input_file)
        os.chmod(output_file, file_status.st_mode)
        # self.log("File {} handled".format(input_file))
        self.log("File handled")
        self.log("*********************************")
        return True

    def generate_content(self):
        """Generation and storage of all content

        Returns:
            Bool -- True on success
        """
        # Extracting all components from the template
        file_list = list()
        dir_list = list()

        path_root_template = self.template_path_ + "/template"

        for (root, dirs, files) in os.walk(path_root_template):
            # print "check {}: dir {}, files: {}".format(root, dirs, files)

            if os.path.samefile(root, path_root_template):
                for item in files:
                    file_list.append(item)
                for item in dirs:
                    dir_list.append(item)
            else:
                rel_path = os.path.relpath(root, path_root_template)
                for item in files:
                    file_list.append(rel_path + "/" + item)
                for item in dirs:
                    dir_list.append(rel_path + "/" + item)

        # Looking at final directory and filenames
        package_name = self.xml_parser_.get_package_spec()["name"]
        nb_comp = self.xml_parser_.get_number_comps()
        comps_name = [
            self.xml_parser_.data_comp_[id_comp]["attributes"]["name"]
            for id_comp in range(nb_comp)
        ]

        self.log("Generating all folders")

        tmp = list()
        for item in dir_list:
            item = item.replace('package_name', package_name)
            if 'component' in item:
                for one_name in comps_name:
                    tmp.append(item.replace('component', one_name))
            else:
                tmp.append(item)
        dir_list = tmp

        for item in dir_list:
            path_folder = self.package_path_ + "/" + item
            if not os.path.exists(path_folder):
                os.makedirs(path_folder)

        generation_list = list()
        # File preparation: storing [template filename, new filename, comp id]
        for item in file_list:

            new_item = item.replace('package_name', package_name)
            if 'component' in item:
                for num, one_name in enumerate(comps_name):
                    generation_list.append(
                        [item,
                         new_item.replace('component', one_name), num])
            else:
                # todo if no component active I should not set one
                generation_list.append([item, new_item, 0])

        is_ok = True
        # self.log("\nFiles generation plan: ")
        for item in generation_list:
            [template_file, result_file, comp_id] = item
            self.log("{} --> {}".format(template_file, result_file))

            if not self.xml_parser_.set_active_comp(comp_id):
                return False

            # reconfiguring the generator to adjust to the new active component
            # todo configure already called in generate_package function. Check why
            if not self.file_generator_.configure(self.xml_parser_,
                                                  self.spec_):
                return False
            if not self.jinja_generator_.configure(self.xml_parser_,
                                                   self.spec_):
                return False

            # Normally an empty file should not be written
            # The exception is currently only for the special python file __init__.py
            is_write_forced = (os.path.basename(result_file) == '__init__.py')

            result_file = self.package_path_ + "/" + result_file
            template_file = self.template_path_ + '/template/' + template_file

            if self.path_pkg_backup_ is None:
                self.log("Generating file {}".format(result_file))

                is_ok = self.generate_one_file(template_file, result_file,
                                               is_write_forced)

                if self.handle_status_and_advise(template_file, result_file,
                                                 is_ok):
                    continue
                else:
                    return False

            # A previous version of the package exists
            # Checking if an update is necessary
            rel_path = os.path.relpath(result_file, package_name)
            previous_filename = os.path.join(self.path_pkg_backup_, rel_path)

            # Check 1: does this file exist?
            if not os.path.isfile(previous_filename):
                msg = "File {} not previously existing. Just write it"
                self.log_warn(msg.format(rel_path))

                is_ok = self.generate_one_file(template_file, result_file,
                                               is_write_forced)
                if self.handle_status_and_advise(template_file, result_file,
                                                 is_ok):
                    continue
                else:
                    return False
            # File already existing. Processing previous version
            is_update_needed = False
            file_analyzor = GeneratedFileAnalysis()
            is_ok = file_analyzor.extract_protected_region(previous_filename)
            if is_ok:
                # Check if Developer inserted any contribution
                if file_analyzor.extracted_areas_:
                    # contribution found, merge needed
                    is_update_needed = True
                else:
                    self.log("No Developer contribution found")
            else:
                msg = "prb while extracting protected area in {}"
                self.log_error(msg.format(previous_filename))
                self.log_error("Previous file to be manually merged, sorry")

            # now we know if an update is needed
            if is_ok and is_update_needed:
                # self.log("Updating file {} in {}".format(rel_path, output_item))
                self.log("Updating file {}".format(rel_path))

                is_ok = self.generate_one_file(template_file, None, None)
                if not is_ok:
                    return False

                # todo handle this in case jinja is involved.
                l_gen = self.get_generated_file()
                if not l_gen:
                    msg = "New generated file empty. No code maintained from previous version"
                    self.log_warn(msg)
                    # we write it if forced
                    if is_write_forced:
                        is_ok = self.write_generated_file(result_file)
                else:
                    self.log("Merging with previous version")
                    l_gen = file_analyzor.update_file(l_gen)
                    self.set_generated_file(l_gen)
                    is_ok = self.write_generated_file(result_file)

                if self.handle_status_and_advise(template_file, result_file,
                                                 is_ok):
                    continue
                else:
                    return False

            # Although the file existed before, we do not have to maintain it
            is_ok = self.generate_one_file(template_file, result_file,
                                           is_write_forced)
            if self.handle_status_and_advise(template_file, result_file,
                                             is_ok):
                continue
            else:
                return False
        return True

    def template_sanity_check(self, template):
        """Perform the package sanity check

        Returns:
            Bool: True on success
        """

        # Locate template location
        try:
            [all_template_path, template_names] = self.get_template_info()
        except rospkg.common.ResourceNotFound as error:
            msg = "Package package_generator_templates not found in rospack"
            self.log_error(msg)
            self.log_error(error)
            return False
        except OSError as error:
            msg = "No template found in package_generator_templates"
            self.log_error(msg)
            self.log_error(error)
            return False

        is_template_found = False
        template_path = None

        if template in template_names:
            is_template_found = True
            template_path = all_template_path + "/" + template
        else:
            self.log("Could not find template {} in {}".format(
                template, all_template_path))
            # check if the template provided is a relative path, and not a package in the repo
            if os.path.isabs(template):
                self.log(
                    "Loading template from absolute path {}".format(template))
                is_template_found = True
                template_path = template
            else:
                # relative path ?
                template_path = os.getcwd() + "/" + template

                if os.path.isdir(template_path):
                    self.log(
                        "Loading template from path {}".format(template_path))
                    is_template_found = True

        if not is_template_found:
            msg = "Template requested: {} unknown".format(template)
            self.log_error(msg)
            msg = "Available templates: {}".format(template_names)
            self.log_error(msg)
            return False

        # confirm this is a template...
        if not self.check_template_structure(template_path):
            msg = "Please revise template structure"
            self.log_error(msg)
            return False

        # TODO list number of files in template
        # Extracting all components from the template
        file_list = list()
        dir_list = list()

        path_root_template = template_path + "/template"

        for (root, dirs, files) in os.walk(path_root_template):
            # print "check {}: dir {}, files: {}".format(root, dirs, files)

            if os.path.samefile(root, path_root_template):
                for item in files:
                    file_list.append(item)
                for item in dirs:
                    dir_list.append(item)
            else:
                rel_path = os.path.relpath(root, path_root_template)
                for item in files:
                    file_list.append(rel_path + "/" + item)
                for item in dirs:
                    dir_list.append(rel_path + "/" + item)

        # print ("Dirs: ")
        # print("\n".join(dir_list))
        # print("Files: ")
        # print("\n".join(file_list))

        # setting the needed component.
        self.spec_ = TemplateSpec()
        self.xml_parser_ = PackageXMLParser()
        self.file_generator_ = CodeGenerator()
        self.jinja_generator_ = JinjaGenerator()

        dir_template_spec = template_path + "/config/"
        if not self.spec_.load_spec(dir_template_spec):
            self.log_error("Could not load the template spec")
            return False

        if not self.xml_parser_.set_template_spec(self.spec_):
            msg_err = "Package spec not compatible with xml parser expectations"
            self.log_error(msg_err)
            return False

        if not self.xml_parser_.set_empty_spec():
            msg_err = "Failed generating empty spec"
            self.log_error(msg_err)
            return False

        if not self.file_generator_.configure(self.xml_parser_, self.spec_):
            return False

        if not self.jinja_generator_.configure(self.xml_parser_, self.spec_):
            return False

        is_ok = True

        for item in file_list:
            self.log("Checking file: {}".format(item))
            item_abs = path_root_template + "/" + item
            is_ok = self.check_template_file(item_abs)
            if not is_ok:
                break
        if is_ok:
            self.log("No error detected")
        else:
            self.log_error("Revise the template")
        return is_ok
コード例 #6
0
class CodeGeneratorTest(unittest.TestCase):
    """
    tests for the code generator
    """
    def setUp(self):
        """
        Common initialization for all tester
        """
        file_content = (
            '<?xml version="1.0" encoding="UTF-8"?>'
            '\n'
            '<package name="great_package" author="anthony" author_email="*****@*****.**"'
            '\n'
            '         description="The extended package" license="TODO" copyright="TRI">'
            '\n'
            '   <component name="node_extended" frequency="100.0">'
            '\n'
            '       <publisher name="pub" type="std_msgs::Bool" description="a description"/>'
            '\n'
            '       <publisher name="pub_second" type="std_msgs::String" description="a description"/>'
            '\n'
            '       <subscriber name="sub_in" type="std_msgs::String" description="a description" />'
            '\n'
            '       <serviceClient name="service_client" type="std_srvs::Trigger" description="a description"/>'
            '\n'
            '       <serviceServer name="service_server" type="std_srvs::SetBool" description="a description"/>'
            '\n'
            '       <parameter name="param_one" type="std::string" value="Empty" description="a description"/>'
            '\n'
            '       <parameter name="param_two" type="bool" value="1" description="a description"/>'
            '\n'
            '       <actionServer name="action_server" type="bride_tutorials::TriggerPublish"'
            '\n'
            '                     description="a description"/>'
            '\n'
            '       <actionClient name="action_client" type="bride_tutorials::TriggerPublish"'
            '\n'
            '                     description="a description"/>'
            '\n'
            '   </component>'
            '\n'
            '<depend>std_msgs</depend>'
            '\n'
            '<depend>std_srvs</depend>'
            '\n'
            '<depend>bride_tutorials</depend>'
            '\n'
            '<depend>roscpp</depend>'
            '\n'
            '<depend>actionlib</depend>'
            '\n'
            '<depend>actionlib_msgs</depend>'
            '\n'
            '</package>'
            '\n')

        # print "File content: \n{}".format(file_content)

        self.dir_name = "/tmp/test_package_generator"
        if not os.path.exists(self.dir_name):
            print "Creating the repo {}".format(self.dir_name)
            os.makedirs(self.dir_name)

        file_xml = self.dir_name + "/node_spec.ros_package"
        with open(file_xml, 'w') as open_file:
            open_file.write(file_content)

        rospack = rospkg.RosPack()
        self.template_path_ = rospack.get_path('package_generator_templates')
        self.template_path_ += "/templates/cpp_node_update/"

        self.spec = TemplateSpec()
        self.assertTrue(self.spec.load_spec(self.template_path_ + "config"))

        self.xml_parser = PackageXMLParser()
        self.assertTrue(self.xml_parser.set_template_spec(self.spec))
        self.assertTrue(self.xml_parser.load(file_xml))

        self.generator_ = CodeGenerator()
        self.assertTrue(self.generator_.configure(self.xml_parser, self.spec))
        self.generator_.reset_output_file()

    def test_apply_function(self):
        """
        To apply a function on a tag
        """
        filename = self.dir_name + "/template_test_apply_function.cpp"
        file_content = (
            '{forallpublisher}'
            '\n'
            'name={name}'
            '\n'
            'pkg={apply-get_package_type} '
            '\n'
            'type={apply-get_class_type}'
            '\n'
            'in one line: {name}: {apply-get_python_type}'
            '\n'
            'cpp path: {name}: {apply-get_cpp_path}'
            '\n'
            'more tricky: {name}: {apply-get_cpp_path} {unknowntag}'
            '\n'
            'more tricky: {name}: {apply-get_cpp_path} {apply-unknownname}'
            '\n'
            '\n'
            '{endforallpublisher}')

        expected_output = (
            "name=pub"
            "\n"
            "pkg=std_msgs "
            "\n"
            "type=Bool"
            "\n"
            "in one line: pub: std_msgs.Bool"
            "\n"
            "cpp path: pub: std_msgs/Bool"
            "\n"
            "more tricky: pub: std_msgs/Bool {unknowntag}"
            "\n"
            "more tricky: pub: std_msgs/Bool {apply-unknownname}"
            "\n"
            ""
            "\n"
            "name=pub_second"
            "\n"
            "pkg=std_msgs "
            "\n"
            "type=String"
            "\n"
            "in one line: pub_second: std_msgs.String"
            "\n"
            "cpp path: pub_second: std_msgs/String"
            "\n"
            "more tricky: pub_second: std_msgs/String {unknowntag}"
            "\n"
            "more tricky: pub_second: std_msgs/String {apply-unknownname}"
            "\n")

        with open(filename, 'w') as openfile:
            openfile.write(file_content)

        self.assertTrue(self.generator_.process_file(filename))

        for generated, groundtruth in zip(self.generator_.rendered_,
                                          expected_output.splitlines()):
            # print "Comparing |{}| with |{}|".format(generated, groundtruth)
            self.assertEqual(generated, groundtruth)

    def test_multi_line(self):
        """
        Test  the conversion of multi-line
        """
        filename = self.dir_name + "/template_test_multiple_line.cpp"
        file_content = ('hello {componentName}'
                        '\n'
                        '{forallpublisher}'
                        '\n'
                        'if (component_data_.{name}_active)'
                        '\n'
                        '  pub_.publish(component_data_.{name});'
                        '\n'
                        '{endforallpublisher}'
                        '\n')
        expected_output = ("hello node_extended"
                           "\n"
                           "if (component_data_.pub_active)"
                           "\n"
                           "  pub_.publish(component_data_.pub);"
                           "\n"
                           "if (component_data_.pub_second_active)"
                           "\n"
                           "  pub_.publish(component_data_.pub_second);"
                           "\n")

        with open(filename, 'w') as open_file:
            open_file.write(file_content)

        self.assertTrue(self.generator_.process_file(filename))
        for generated, groundtruth in zip(self.generator_.rendered_,
                                          expected_output.splitlines()):
            self.assertEqual(generated, groundtruth)

    def test_multi_line_bracket(self):
        """
        Test correct management of multi-line with bracket
        """
        filename = self.dir_name + "/template_test_multiple_line.cpp"
        file_content = ('hello {componentName}'
                        '\n'
                        '{forallpublisher}'
                        '\n'
                        'if (component_data_.{name}_active)'
                        '\n'
                        '{'
                        '\n'
                        '   pub_.publish(component_data_.{name});'
                        '\n'
                        '}'
                        '\n'
                        '{endforallpublisher}'
                        '\n')
        expected_output = ("hello node_extended"
                           "\n"
                           "if (component_data_.pub_active)"
                           "\n"
                           "{"
                           "\n"
                           "   pub_.publish(component_data_.pub);"
                           "\n"
                           "}"
                           "\n"
                           "if (component_data_.pub_second_active)"
                           "\n"
                           "{"
                           "\n"
                           "   pub_.publish(component_data_.pub_second);"
                           "\n"
                           "}"
                           "\n")
        with open(filename, 'w') as open_file:
            open_file.write(file_content)

        self.assertTrue(self.generator_.process_file(filename))
        for generated, groundtruth in zip(self.generator_.rendered_,
                                          expected_output.splitlines()):
            self.assertEqual(generated, groundtruth)

    def test_multi_tag_single_line(self):
        """
        todo An interesting reference to look at if needed
        http://stackoverflow.com/questions/17215400/python-format-string-unused-named-arguments
        """
        filename = self.dir_name + "/template_test_multiple_tag.cpp"

        file_content = (
            'Let us start !!!!'
            '\n'
            'hello {componentName}'
            '\n'
            '    void update({componentName}_data &data, {componentName}_config config)'
            '\n'
            'Bye Bye'
            '\n')
        expected_output = (
            "Let us start !!!!"
            "\n"
            "hello node_extended"
            "\n"
            "    void update(node_extended_data &data, node_extended_config config)"
            "\n"
            "Bye Bye"
            "\n")

        with open(filename, 'w') as openfile:
            openfile.write(file_content)

        self.assertTrue(self.generator_.process_file(filename))
        for generated, groundtruth in zip(self.generator_.rendered_,
                                          expected_output.splitlines()):
            self.assertEqual(generated, groundtruth)

    def test_if_condition(self):
        """
        @brief Test the if condition
        @param      self The object
        @return nothing
        """
        filename = self.dir_name + "/template_test_multiple_line.cpp"
        file_content = ('Let us start !!!!'
                        '\n'
                        '{ifparameter}'
                        '\n'
                        '<build_depend>dynamic_reconfigure</build_depend>'
                        '\n'
                        '<run_depend>dynamic_reconfigure</run_depend>'
                        '\n'
                        '{endifparameter}'
                        '\n'
                        '{ifactionServer}'
                        '\n'
                        '<build_depend>actionlib</build_depend>'
                        '\n'
                        '<run_depend>actionlib</run_depend>'
                        '\n'
                        '{endifactionServer}'
                        '\n')
        expected_output = ("Let us start !!!!"
                           "\n"
                           "<build_depend>dynamic_reconfigure</build_depend>"
                           "\n"
                           "<run_depend>dynamic_reconfigure</run_depend>"
                           "\n"
                           "<build_depend>actionlib</build_depend>"
                           "\n"
                           "<run_depend>actionlib</run_depend>"
                           "\n")

        with open(filename, 'w') as openfile:
            openfile.write(file_content)

        self.assertTrue(self.generator_.process_file(filename))
        for generated, groundtruth in zip(self.generator_.rendered_,
                                          expected_output.splitlines()):
            self.assertEqual(generated, groundtruth)

    def test_complete_ros(self):
        """
        @brief Test the generation of a complete cpp ros file

        @param      self The object
        @return nothing
        """
        filename = self.template_path_ + 'template/ros/src/component_ros.cpp'

        self.assertTrue(self.generator_.process_file(filename))

        output_file = self.dir_name + "/component_ros.cpp"
        self.assertTrue(self.generator_.write_rendered_file(output_file))

    def test_complete_cmake(self):
        """
        @brief Test generation of a complete cmake

        @param      self The object

        @return { description_of_the_return_value }
        """
        filename = self.template_path_ + 'template/CMakeLists.txt'

        self.assertTrue(self.generator_.process_file(filename))

        output_file = self.dir_name + "/CMakeLists.txt"
        self.assertTrue(self.generator_.write_rendered_file(output_file))

    def test_complete_readme(self):
        """
        @brief Test generation of a complete readme

        @param      self The object

        @return { description_of_the_return_value }
        """
        filename = self.template_path_ + 'template/README.md'

        self.assertTrue(self.generator_.process_file(filename))

        output_file = self.dir_name + "/README.md"
        self.assertTrue(self.generator_.write_rendered_file(output_file))

    def test_complete_package(self):
        """
        @brief Test the generation of a complete package

        @param      self The object

        @return nothing
        """
        filename = self.template_path_ + 'template/package.xml'

        self.assertTrue(self.generator_.process_file(filename))

        output_file = self.dir_name + "/package.xml"
        self.assertTrue(self.generator_.write_rendered_file(output_file))
コード例 #7
0
def main_generate_xml():
    """Generate a xml package description based on a template spec

    Returns:
        int: negative value on error
    """
    rospack = rospkg.RosPack()

    try:
        node_path = rospack.get_path('package_generator_templates')
        default_templates_path = node_path + "/templates/"
    except rospkg.common.ResourceNotFound as error:
        msg = "Package package_generator_templates not found in rospack"
        print colored(msg, "yellow")
        print colored("{}".format(error), "yellow")
        default_templates_path = None

    available_templates = None
    # look for the templates available
    if default_templates_path is not None:
        available_templates = os.listdir(default_templates_path)

    if len(sys.argv) != 3:
        print colored("Wrong input parameters !", "red")
        print colored(USAGE, "yellow")
        if available_templates is not None:
            msg = "Available templates are: {}"
            print colored(msg.format(available_templates), 'yellow')
        print "Bye bye"
        return -1

    path_template = sys.argv[1]
    package_spec = sys.argv[2]

    # check first if the provided filename is correct
    # we do not accept the name to be an existing file

    if os.path.isfile(package_spec):
        print colored("file {} already exists".format(package_spec), "red")
        print colored("Please select another filename, or remove the file")
        print "Bye Bye"
        return -1

    # if relative path or absolute path, check the containing folder exists
    folder = os.path.dirname(package_spec)

    if folder and not os.path.isdir(folder):
        msg_err = "file {} cannot be created in {}".format(
            package_spec, folder)
        print colored(msg_err, "red")
        print colored("Please adjust parameter")
        print "Bye Bye"
        return -1

    # searching for the template location
    if os.path.isabs(path_template):
        print "Loading model from absolute path {}".format(path_template)
    else:
        # relative path.
        # either from the current path, or from the template package
        path_current = os.getcwd()
        path_attempt = path_current + "/" + path_template

        if os.path.isdir(path_attempt):
            path_template = path_attempt
            print "Loading template from path {}".format(path_template)
        else:
            if path_template in available_templates:
                path_template = default_templates_path + path_template
                msg = "Loading template from template package: {}"
                print msg.format(path_template)
            else:
                msg = "Template name not found in package_generator_templates"
                print colored(msg, "red")
                print colored("Please verify your setup", "red")
                return -1

    package_parser = PackageXMLParser()

    dir_template_spec = path_template + "/config/"
    spec = TemplateSpec()

    if not spec.load_spec(dir_template_spec):
        print colored("Could not load the template spec", "red")
        return -1

    # print "Setting the template spec to \n {}".format(spec.dico_)
    if not package_parser.set_template_spec(spec):
        print colored("Prb while setting the parser dictionary", "red")
        return -1

    package_parser.generate_xml_from_spec(package_spec)

    print colored("Bye bye", "green")
    return 0
class JinjaGeneratorTest(unittest.TestCase):
    """
    tests for the code generator
    """

    def setUp(self):
        """
        Common initialization for all tester
        """
        file_content = (
            '<?xml version="1.0" encoding="UTF-8"?>' '\n'
            '<package name="great_package" author="anthony" author_email="*****@*****.**"' '\n'
            '         description="The extended package" license="TODO" copyright="TRI">' '\n'
            '   <component name="node_extended" frequency="100.0">' '\n'
            '       <publisher name="pub" type="std_msgs::Bool" description="a description"/>' '\n'
            '       <publisher name="pub_second" type="std_msgs::String" description="a description"/>' '\n'
            '       <subscriber name="sub_in" type="std_msgs::String" description="a description" />' '\n'
            '       <serviceClient name="service_client" type="std_srvs::Trigger" description="a description"/>' '\n'
            '       <serviceServer name="service_server" type="std_srvs::SetBool" description="a description"/>' '\n'
            '       <parameter name="param_one" type="std::string" value="Empty" description="a description"/>'  '\n'
            '       <parameter name="param_two" type="bool" value="1" description="a description"/>'  '\n'
            '       <actionServer name="action_server" type="bride_tutorials::TriggerPublish"' '\n'
            '                     description="a description"/>' '\n'
            '       <actionClient name="action_client" type="bride_tutorials::TriggerPublish"' '\n'
            '                     description="a description"/>' '\n'
            '   </component>' '\n'
            '<depend>std_msgs</depend>' '\n'
            '<depend>std_srvs</depend>' '\n'
            '<depend>bride_tutorials</depend>' '\n'
            '<depend>actionlib</depend>' '\n'
            '<depend>actionlib_msgs</depend>' '\n'
            '<depend>roscpp</depend>' '\n'
            '</package>' '\n')

        # print "File content: \n{}".format(file_content)

        self.dir_name = "/tmp/test_package_generator"
        if not os.path.exists(self.dir_name):
            print "Creating the repo {}".format(self.dir_name)
            os.makedirs(self.dir_name)

        file_xml = self.dir_name + "/node_spec.ros_package"
        with open(file_xml, 'w') as open_file:
            open_file.write(file_content)

        rospack = rospkg.RosPack()
        self.template_path_ = rospack.get_path('package_generator_templates')
        self.template_path_ += "/templates/cpp_node_update/"

        self.spec = TemplateSpec()
        self.assertTrue(self.spec.load_spec(self.template_path_ + "config"))

        self.xml_parser = PackageXMLParser()
        self.assertTrue(self.xml_parser.set_template_spec(self.spec))
        self.assertTrue(self.xml_parser.load(file_xml))

        self.generator_ = JinjaGenerator()
        self.assertTrue(self.generator_.configure(self.xml_parser, self.spec))

    def test_is_launching(self):
        """
        Confirm that Jinja does not interfere with custom tags
        """
        filename = self.dir_name + "/template_test_apply_function.cpp"
        file_content = (
            '{forallpublisher}' '\n'
            'name={name}' '\n'
            'pkg={apply-get_package_type} ' '\n'
            'type={apply-get_class_type}' '\n'
            'in one line: {name}: {apply-get_python_type}' '\n'
            'cpp path: {name}: {apply-get_cpp_path}' '\n'
            'more tricky: {name}: {apply-get_cpp_path} {unknowntag}' '\n'
            'more tricky: {name}: {apply-get_cpp_path} {apply-unknown}' '\n'
            '\n'
            '{endforallpublisher}')

        with open(filename, 'w') as openfile:
            openfile.write(file_content)

        self.assertTrue(self.generator_.generate_disk_file(filename))

        for generated, groundtruth in zip(self.generator_.rendered_,
                                          file_content.splitlines()):
            # print "Comparing |{}| with |{}|".format(generated, groundtruth)
            self.assertEqual(generated, groundtruth)

    def test_jinja(self):
        """
        General test
        """
        filename = self.dir_name + "/template_test_apply_function.cpp"

        file_content = (
            '{forallpublisher}' '\n'
            'name={{active_comp}}' '\n'
            'pkg={{package.name}}' '\n'
            '{% for item in components[0].interface.publisher %}' '\n'
            'publisher: {{ item.name }}' '\n'
            '{% endfor %}' '\n'
            '{% if components[0].interface.publisher %}' '\n'
            'At least one publisher ' '\n'
            '{% endif %}' '\n'
            '{% if components[0].interface.directPublisher %}' '\n'
            'At least one direct publisher ' '\n'
            '{% else %}' '\n'
            'No direct publisher ' '\n'
            '{% endif %}' '\n'
            '{endforallpublisher}' '\n')

        expected_output = (
            '{forallpublisher}' '\n'
            'name=0' '\n'
            'pkg=great_package' '\n'
            'publisher: pub' '\n'
            'publisher: pub_second' '\n'
            'At least one publisher ' '\n'
            'No direct publisher ' '\n'
            '{endforallpublisher}')

        with open(filename, 'w') as openfile:
            openfile.write(file_content)

        self.assertTrue(self.generator_.generate_disk_file(filename))

        for generated, groundtruth in zip(self.generator_.rendered_,
                                          expected_output.splitlines()):
            self.assertEqual(generated, groundtruth)

    def test_custom_on_jinja(self):
        """to be completed
        """
        filename = self.dir_name + "/template_test_apply_function.cpp"

        file_content = (
            '{ifpublisher}' '\n'
            'name={{active_comp}}' '\n'
            'pkg={{package.name}}' '\n'
            '{% for item in components[0].interface.publisher %}' '\n'
            'publisher: {{ item.name }}' '\n'
            '{% endfor %}' '\n'
            '' '\n'
            '{% if components[0].interface.publisher %}' '\n'
            'At least one publisher ' '\n'
            '{% endif %}' '\n'
            '{% if components[0].interface.directPublisher %}' '\n'
            'At least one direct publisher ' '\n'
            '{% else %}' '\n'
            'No direct publisher ' '\n'
            '{% endif %}' '\n'
            '{endifpublisher}' '\n')

        expected_output = (
            'name=0' '\n'
            'pkg=great_package' '\n'
            'publisher: pub' '\n'
            'publisher: pub_second' '\n'
            '' '\n'
            'At least one publisher ' '\n'
            'No direct publisher ' '\n')

        custom_generator = CodeGenerator()
        self.assertTrue(custom_generator.configure(self.xml_parser, self.spec))
        custom_generator.reset_output_file()

        with open(filename, 'w') as openfile:
            openfile.write(file_content)

        self.assertTrue(custom_generator.process_file(filename))

        # print "Custom generation \n"
        # for line in custom_generator.rendered_:
        #    print line

        self.assertTrue(self.generator_.generate_open_file(custom_generator.rendered_))

        # print "Rendered file \n"
        # print self.generator_.rendered_

        for generated, groundtruth in zip(self.generator_.rendered_,
                                          expected_output.splitlines()):
            # print "Comparing |{}| with |{}|".format(generated, groundtruth)

            self.assertEqual(generated, groundtruth)

    def test_write_file(self):
        file_content = (
            'name={{active_comp}}' '\n'
            'pkg={{package.name}}' '\n'
            '{% for item in components[0].interface.publisher %}' '\n'
            'publisher: {{ item.name }}' '\n'
            '{% endfor %}' '\n'
            '{% if components[0].interface.publisher %}' '\n'
            'At least one publisher ' '\n'
            '{% endif %}' '\n'
            '{% if components[0].interface.directPublisher %}' '\n'
            'At least one direct publisher ' '\n'
            '{% else %}' '\n'
            'No direct publisher ' '\n'
            '{% endif %}')
        expected_output = (
            'name=0' '\n'
            'pkg=great_package' '\n'
            'publisher: pub' '\n'
            'publisher: pub_second' '\n'
            'At least one publisher ' '\n'
            'No direct publisher ' '\n')
        template_name = self.dir_name + "/jinja_template.txt"
        with open(template_name, 'w') as openfile:
            openfile.write(file_content)
        rendered_name = self.dir_name + "/jinja_rendered.txt"

        self.assertTrue(self.generator_.generate_disk_file(template_name, rendered_name))
        self.assertTrue(os.path.isfile(rendered_name))

        lines = list()
        with open(rendered_name) as input_file:
            for line in input_file:
                lines.append(line.rstrip('\n'))

        for generated, groundtruth in zip(lines,
                                          expected_output.splitlines()):
            # print "Comparing |{}| with |{}|".format(generated, groundtruth)

            self.assertEqual(generated, groundtruth)
コード例 #9
0
class PackageGenerator(EnhancedObject):
    """Handle the genration of a whole package

    Attributes:
        file_generator_ (TYPE): Description
        package_path_ (TYPE): Description
        path_pkg_backup_ (TYPE): Description
        spec_ (TemplateSpec): configuration of the template model
        template_path_ (TYPE): Description
        xml_parser_ (TYPE): Description
    """
    def __init__(self, name="PackageGenerator"):
        """ Intialisation of the object

        Args:
            name (str, optional): Name of the component, for printing aspect
        """
        #  call super class constructor
        super(PackageGenerator, self).__init__(name)

        # path to the template to use
        self.template_path_ = None
        # base location of the package to create
        self.package_path_ = None
        # parser of the package description
        self.xml_parser_ = None
        # config parameter provide with the template
        self.spec_ = None
        # generic file generator
        self.file_generator_ = None
        # if the package already existed, location of the package backup
        self.path_pkg_backup_ = None

    def set_package_template(self, template_path):
        """set the package template

        Args:
            template_path (str): path to the package template

        Returns:
            Bool: True if basic sanity checks are done with success
        """
        if not os.path.exists(template_path):
            msg = "Template path ({}) is incorrect ".format(template_path)
            self.log_error(msg)
            return False

        if not os.path.isdir(template_path):
            msg = "Template path ({}) is not a directory ".format(
                template_path)
            self.log_error(msg)
            return False

        # check if minimum information is present.

        details = """A template should contain:
    * config/dictionary.yaml : the dictionary to be used
    * [optional] config/functions.py : additional functions used in the generation
    * template/* set of elements to be generated
Revise the template, and compare to examples
        """

        is_ok = True
        # check for directories
        required_folders = ["config", "template"]
        for item in required_folders:
            req_folder = template_path + "/" + item
            if not os.path.isdir(req_folder):
                msg_err = "Error \n Expecting to have folder " + item
                msg_err += " in " + template_path
                self.log_error(msg_err)
                is_ok = False

        # check for files
        required_files = ["config/dictionary.yaml"]
        for item in required_files:
            req_file = template_path + "/" + item
            if not os.path.isfile(req_file):
                msg_err = "Error.\n Expecting to have file " + item
                msg_err += " in " + template_path
                self.log_error(msg_err)
                is_ok = False

        if not is_ok:
            self.log_error("\n{}".format(details))
            return False

        self.template_path_ = template_path
        return True

    def generate_package(self, package_desc, output_path):
        """launches the package generation

        Args:
            package_desc (str): xml file containing the package description
            output_path (str): directory into which the package is created

        Returns:
            Bool: True if the operation succeeded
        """
        self.path_pkg_backup_ = None

        if not os.path.exists(output_path):
            msg_err = "Template path ({}) is incorrect ".format(output_path)
            self.log_error(msg_err)
            return False

        if not os.path.isdir(output_path):
            msg_err = "Template path ({}) not a directory ".format(output_path)
            self.log_error(msg_err)
            return False

        self.spec_ = TemplateSpec()
        self.xml_parser_ = PackageXMLParser()
        self.file_generator_ = CodeGenerator()

        dir_template_spec = self.template_path_ + "/config/"
        if not self.spec_.load_spec(dir_template_spec):
            self.log_error("Could not load the template spec")
            return False

        if not self.xml_parser_.set_template_spec(self.spec_):
            msg_err = "Package spec not compatible with xml parser expectations"
            self.log_error(msg_err)
            return False

        if not self.xml_parser_.load(package_desc):
            msg_err = "Prb while parsing xml file {}".format(package_desc)
            self.log_error(msg_err)
            return False

        self.file_generator_.configure(self.xml_parser_, self.spec_)

        package_name = self.xml_parser_.get_package_spec()["name"]

        self.package_path_ = output_path + "/" + package_name

        if os.path.exists(self.package_path_):
            self.log_warn("Package {} already exists".format(
                self.package_path_))
            # moving preexisting code.
            # generating dir name using date
            now = datetime.datetime.now()
            str_date = now.strftime("%Y_%m_%d_%H_%M_%S")
            self.path_pkg_backup_ = "/tmp/{}_{}".format(
                os.path.basename(self.package_path_), str_date)
            self.log_warn("Original package temporally stored in {}".format(
                self.path_pkg_backup_))
            # TODO check if the move succeeded
            shutil.move(self.package_path_, self.path_pkg_backup_)
        else:
            self.log("Package to be created in {}".format(self.package_path_))
            os.makedirs(self.package_path_)

        package_content = os.listdir(self.template_path_ + "/template")
        nb_node = self.xml_parser_.get_number_nodes()
        self.log("Number of nodes defined: {}".format(nb_node))

        # launching the file generation, except common files
        self.log("Generating files specific to nodes")
        is_ok = True
        for id_node in range(nb_node):
            # self.log_error("Handling node {}".format(id_node))
            if not self.xml_parser_.set_active_node(id_node):
                is_ok = False
                break
            node_name = self.xml_parser_.data_node_[id_node]["attributes"][
                "name"]
            self.log_warn("Handling files for node {}".format(node_name))

            # reconfiguring the generator to adjust to the new active node
            self.file_generator_.configure(self.xml_parser_, self.spec_)

            for item in package_content:
                file_generic = self.template_path_ + '/template/' + item
                is_ok = self.generate_content(file_generic, False)
                if not is_ok:
                    break
        if not is_ok:
            return False

        self.log("Generating files common to all nodes")
        is_ok = True
        # todo should we do something with the active_node file?
        for item in package_content:
            file_generic = self.template_path_ + '/template/' + item
            is_ok = self.generate_content(file_generic, True)
            if not is_ok:
                break

        if not is_ok:
            return False

        # we store the model into the directory model
        path = self.package_path_ + "/model"
        if not os.path.exists(path):
            os.makedirs(path)
        path += "/" + package_name + ".ros_package"

        if self.xml_parser_.is_dependency_complete_:
            try:
                if os.path.abspath(package_desc) == os.path.abspath(path):
                    # self.log_warn("Using generated model...")
                    stored_desc = self.path_pkg_backup_ + "/model/" + package_name + ".ros_package"
                    # self.log("check {} is absolute: {}".format(package_desc, os.path.abspath(package_desc)))
                    shutil.copyfile(stored_desc, path)
                else:
                    shutil.copyfile(package_desc, path)
                self.log("Package model saved in: {}".format(path))
            except IOError as error:
                self.log_error("Could not store model file: {}".format(error))
        else:
            # some dependencies were automatically added
            # model needs to be rewritten
            try:
                self.xml_parser_.write_xml(path)
                self.log("Package model updated & saved in: {}".format(path))
            except IOError as error:
                self.log_error("Could not store model file: {}".format(error))

        is_ok = self.handle_maintained_files()

        return is_ok

    def handle_maintained_files(self):
        """ Restore file Developer requests to maintain
        Assuming these patterns are defined in file .gen_maintain
        Returns:
            Bool: True on sucess
        """
        # check for files to be maintained
        if self.path_pkg_backup_ is None:
            # package just created, no maintained file
            return True

        filename_rel = ".gen_maintain"
        filename_abs = self.path_pkg_backup_ + "/" + filename_rel
        if os.path.exists(filename_abs) and os.path.isfile(filename_abs):
            self.log("Checking content to maintain after update")
        else:
            self.log("no maintained file defined in previous package version")
            return True

        with open(filename_abs) as open_file:
            for line in open_file:
                line = line.rstrip('\n')
                if not line:
                    continue
                path_abs = self.path_pkg_backup_ + "/" + line

                if not os.path.exists(path_abs):
                    msg = "Content {} not found. Revise {} content"
                    self.log_error(msg.format(line, filename_abs))
                    continue
                new_path = self.package_path_ + "/" + line

                if os.path.isfile(path_abs):
                    try:
                        self.log("Restoring file {}".format(line))
                        # check if directories needs to be created
                        dirname = os.path.dirname(line)
                        if dirname:
                            if not os.path.isdir(path_abs):
                                os.makedirs(self.package_path_ + "/" + dirname)

                        shutil.copyfile(path_abs, new_path)
                    except IOError as error:
                        msg = "Could not restore a file: {}"
                        self.log_error(msg.format(error))
                    continue

                if os.path.isdir(path_abs):
                    try:
                        self.log("Restoring folder {}".format(line))
                        shutil.copytree(path_abs, new_path)
                    except IOError as error:
                        msg = "Could not restore folder: {}"
                        self.log_error(msg.format(error))
                    continue
                self.log_error("Unkown statement {}".format(line))

        # restoring the maintained content file
        try:
            self.log("Restoring file {}".format(filename_rel))
            new_path = self.package_path_ + "/" + filename_rel
            shutil.copyfile(filename_abs, new_path)
        except IOError as error:
            msg = "Could not restore file: {}"
            self.log_error(msg.format(error))
        return True

    def generate_content(self, input_item, is_generic):
        """recursive function enabling the generation and storage of a file
            or all content of a directory

        Args:
            input_item (str): path to a file or a directory to be generated
            is_generic (Bool): specify if we handle generic files or node specific ones

        Returns:
            Todo: how to handle the file name, when several nodes are defined.
        """
        is_ok = True
        # get the package name
        package_name = self.xml_parser_.get_package_spec()["name"]

        rel_path = os.path.relpath(input_item,
                                   self.template_path_ + "/template")
        # check if the name 'package_name is present in the name'
        rel_path = rel_path.replace('package_name', package_name)
        output_item = self.package_path_ + '/' + rel_path

        if os.path.isdir(input_item):
            # self.log("Handling folder {}".format(rel_path))
            if os.path.exists(output_item):
                # self.log_warn("Directory {} already exists".format(output_item))
                pass
            else:
                # self.log("Creating directory {}".format(rel_path))
                os.makedirs(output_item)

            for item in os.listdir(input_item):
                is_ok = self.generate_content(input_item + '/' + item,
                                              is_generic)
                if not is_ok:
                    break
            return is_ok

        # file to be generated.
        # we check if "node" is in the filename (ie not a generic file).
        basename = os.path.basename(rel_path)

        if 'node' in basename:
            # this is a node specific file. Flag is_generic defines our strategy
            if is_generic:
                # nothing to be done, we just skip it
                return True
        else:
            # this is a generic file, common to all nodes
            if not is_generic:
                # we do nothing
                return True

        self.log("Handling template file {}".format(rel_path))
        # The only difference between generic and no generic is that
        # in the generic case we have to instanciate the node name
        if 'node' in basename:
            # This is a node specific file. We adjust the filename
            basename = basename.replace(
                'node',
                self.xml_parser_.get_active_node_spec()["attributes"]["name"])
            # the rest is common

        rel_path = os.path.join(os.path.dirname(rel_path), basename)
        output_item = os.path.join(os.path.dirname(output_item), basename)

        # Normally an empty file should not be written
        # The exception is currently only for the special python file __init__.py
        is_write_forced = (os.path.basename(output_item) == '__init__.py')

        if self.path_pkg_backup_ is None:
            self.log("Generating file {} in {}".format(rel_path, output_item))
            is_ok = self.file_generator_.generate_file(
                input_item, output_item, force_write=is_write_forced)

            return self.handle_status_and_advise(input_item, output_item,
                                                 is_ok)

        # A previous version of the package exists
        # Checking if an update is necessary
        previous_filename = os.path.join(self.path_pkg_backup_, rel_path)

        # Check 1: does this file exist?
        if not os.path.isfile(previous_filename):
            msg = "File {} not previously existing. Just write it"
            self.log_warn(msg.format(rel_path))

            is_ok = self.file_generator_.generate_file(input_item, output_item,
                                                       is_write_forced)
            return self.handle_status_and_advise(input_item, output_item,
                                                 is_ok)

        # File already existing. Processing previous version
        is_update_needed = False
        file_analyzor = GeneratedFileAnalysis()
        is_ok = file_analyzor.extract_protected_region(previous_filename)
        if is_ok:
            # Check if Developer inserted any contribution
            if file_analyzor.extracted_areas_:
                # contribution found, merge needed
                is_update_needed = True
            else:
                self.log("No Developer contribution found")
        else:
            msg = "prb while extracting protected area in {}"
            self.log_error(msg.format(previous_filename))
            self.log_error("Previous file to be manually merged, sorry")

        # now we know if an update is needed
        if is_ok and is_update_needed:
            # self.log("Updating file {} in {}".format(rel_path, output_item))
            self.log("Updating file {}".format(rel_path))

            is_ok = self.file_generator_.generate_file(input_item)
            if is_ok:
                if self.file_generator_.get_len_gen_file() == 0:
                    msg = "New generated file empty. No code maintained from previous version"
                    self.log_warn(msg)
                    # we write it if forced
                    if is_write_forced:
                        is_ok = self.file_generator_.write_output_file(
                            output_item)
                else:
                    self.log("Merging with previous version")
                    self.file_generator_.tmp_buffer_ = file_analyzor.update_file(
                        self.file_generator_.tmp_buffer_)
                    is_ok = self.file_generator_.write_output_file(output_item)
                return self.handle_status_and_advise(input_item, output_item,
                                                     is_ok)

        # Although the file existed before, we do not have to maintain it
        is_ok = self.file_generator_.generate_file(input_item, output_item,
                                                   is_write_forced)
        return self.handle_status_and_advise(input_item, output_item, is_ok)

    def handle_status_and_advise(self, input_file, output_file, gen_flag):
        """Depending on the file generation process outcome,
           Adjust file status and inform user

        Args:
            input_file (str): path  of the template file used
            output_file (TYPE): path of the generated file
            gen_flag (Bool): Success of the generation process

        Returns:
            Bool: True on success of the file generation
        """
        if not gen_flag:
            msg = "Prb while generating file {}".format(output_file)
            self.log_error(msg)
            return False
        # so the file generation went well
        if self.file_generator_.get_len_gen_file() == 0:
            # Only file __init__.py is kept empty
            if os.path.basename(output_file) != '__init__.py':
                msg = "File {} not written since empty".format(output_file)
                self.log_warn(msg)
                self.log_warn("Check: {}".format(
                    os.path.basename(output_file)))
                return True
        # file has content
        file_status = os.stat(input_file)
        os.chmod(output_file, file_status.st_mode)
        self.log("File {} handled".format(input_file))
        self.log("*********************************")
        return True