Skip to content

Commit

Permalink
Improvements to embedSdf script
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Carroll <[email protected]>
  • Loading branch information
mjcarroll committed Feb 16, 2023
1 parent 07e9161 commit 09d69f4
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 63 deletions.
25 changes: 20 additions & 5 deletions sdf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,28 @@ add_subdirectory(1.10)
add_custom_target(schema)
add_dependencies(schema schema1_10)

# Optionally use the python script (default in sdf14+)
if (EMBEDSDF_PY)
if (NOT Python3_Interpreter_FOUND)
gz_build_error("Python is required to generate the C++ file with the SDF content")
endif()

# Generate the EmbeddedSdf.cc file, which contains all the supported SDF
# descriptions in a map of strings. The parser.cc file uses EmbeddedSdf.hh.
execute_process(
COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.rb
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
)
execute_process(
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.py
--output-file "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
)
else()
execute_process(
COMMAND ${RUBY} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.py
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
)
endif()


# Generate aggregated SDF description files for use by the sdformat.org
# website. If the description files change, the generated full*.sdf files need
Expand Down
168 changes: 110 additions & 58 deletions sdf/embedSdf.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
#!/usr/bin/env python3
"""
Script for generating a C++ file that contains the content from all SDF files
"""

from typing import List, Optional

import argparse
import inspect
from pathlib import Path, PurePosixPath
import sys
from pathlib import Path

""""Script for generating a C++ file that contains the content from all SDF files"""

# The list of supported SDF specification versions. This will let us drop
# versions without removing the directories.
SUPPORTED_SDF_VERSIONS = ['1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3', '1.2']
SUPPORTED_SDF_VERSIONS = [
"1.10",
"1.9",
"1.8",
"1.7",
"1.6",
"1.5",
"1.4",
"1.3",
"1.2",
]

# The list of supported SDF conversions. This list includes versions that
# a user can convert an existing SDF version to.
SUPPORTED_SDF_CONVERSIONS = ['1.10', '1.9', '1.8', '1.7', '1.6', '1.5', '1.4', '1.3']
SUPPORTED_SDF_CONVERSIONS = ["1.10", "1.9", "1.8", "1.7", "1.6", "1.5", "1.4", "1.3"]

# whitespace indentation for C++ code
INDENTATION = ' '
INDENTATION = " "

# newline character
NEWLINE = '\n'
NEWLINE = "\n"


def get_copyright_notice() -> str:
"""
Provides the copyrigt notice for the C++ file
Provides the copyright notice for the C++ file
:returns: copyright notice
"""
res = inspect.cleandoc("""
res = inspect.cleandoc(
"""
/*
* Copyright 2022 Open Source Robotics Foundation
*
Expand All @@ -40,8 +60,9 @@ def get_copyright_notice() -> str:
* limitations under the License.
*
*/
""")
return res + 2*NEWLINE
"""
)
return res + 2 * NEWLINE


def get_file_header_prolog() -> str:
Expand All @@ -50,7 +71,8 @@ def get_file_header_prolog() -> str:
:returns: prolog of the C++ file
"""
res = inspect.cleandoc("""
res = inspect.cleandoc(
"""
#include "EmbeddedSdf.hh"
namespace sdf
Expand All @@ -61,60 +83,39 @@ def get_file_header_prolog() -> str:
const std::map<std::string, std::string> &GetEmbeddedSdf()
{
static const std::map<std::string, std::string> result {
""")
"""
)
return res + NEWLINE


def embed_sdf_content(arg_path: str, arg_file_content: str) -> str:
"""
Generates a string pair with the folder and filename as well as the content of the file
Generates a string pair with the folder and filename
as well as the content of the file
:param arg_path: Foldername and filename of the SDF
:param arg_file_content: Content of the provided file
:returns: raw string literal mapping pair for the std::map
"""
res = []
res.append('// NOLINT')
res.append('{')
res.append("// NOLINT")
res.append("{")
res.append(f'"{arg_path}",')
res.append('R"__sdf_literal__(')
res.append(f'{arg_file_content}')
res.append(f"{arg_file_content}")
res.append(')__sdf_literal__"')
res.append('},')
res.append('')
res.append("}")
return NEWLINE.join(res)


def get_map_content(arg_pathlist: Path) -> str:
"""
Generates a string pair with the folder and filename as well as the content
of the file in ascending order
:param arg_pathlist: Foldername and all filenames inside it
:returns: mapping pairs for the std::map
"""
map_str = ''
files = []
for path in arg_pathlist:
# dir separator is hardcoded to '/' in C++ mapping
posix_path = PurePosixPath(path)
files.append(str(posix_path))
# get ascending order
files.sort()
for file in files:
with Path(file).open() as f:
content = f.read()
map_str += embed_sdf_content(file, content)
return map_str


def get_file_header_epilog() -> str:
"""
Provides the return statement and the closing brackets of the C++ file
:returns: epilog of the C++ file
"""
res = inspect.cleandoc("""
res = inspect.cleandoc(
"""
};
return result;
Expand All @@ -123,29 +124,80 @@ def get_file_header_epilog() -> str:
}
} // namespace sdf
""")
"""
)
return NEWLINE + res


if __name__ == "__main__":
copyright = get_copyright_notice()
def write_output(file_content: str, output_filename: str) -> None:
"""
Print the content of the EmbeddedSdf.cc to a file
"""
copyright_notice = get_copyright_notice()
prolog = get_file_header_prolog()
epilog = get_file_header_epilog()
output_content = copyright_notice + prolog + file_content + epilog

map_str = ""
for sdf_version in SUPPORTED_SDF_VERSIONS:
pathlist = Path(sdf_version).glob('*.sdf')
map_str += get_map_content(pathlist)
with open(output_filename, "w", encoding="utf8") as output_file:
output_file.write(output_content)

for sdf_conversion in SUPPORTED_SDF_CONVERSIONS:
pathlist = Path(sdf_conversion).glob('*.convert')
map_str += get_map_content(pathlist)

# remove the last comma
map_str = map_str[:-2]
def collect_file_locations() -> List[Path]:
paths: List[Path] = []

epilog = get_file_header_epilog()
for sdf_version in SUPPORTED_SDF_VERSIONS:
paths.extend(Path(sdf_version).glob("*.sdf"))
for sdf_conversion in SUPPORTED_SDF_CONVERSIONS:
paths.extend(Path(sdf_conversion).glob("*.convert"))
return paths


def generate_map_content(paths: List[Path], relative_to: Optional[str] = None) -> str:
'''
Generate the EmbeddedSdf.cc content
'''
content = []
for path in paths:
with open(path, "r", encoding="utf8") as input_sdf:
file_content = input_sdf.read()

# Strip relative path if requested
if relative_to is not None:
path = path.relative_to(relative_to)
content.append(embed_sdf_content(str(path), file_content))
return ",".join(content)


def main(args=None) -> int:
'''
Main entrypoint
'''
if args is None:
args = sys.argv[1:]

parser = argparse.ArgumentParser()

parser.add_argument(
"--sdf-root",
default=None,
help="Directory containing sdf description files for each version",
)
parser.add_argument(
"--input-files", nargs="*", help="List of input files to be embedded"
)
parser.add_argument(
"--output-file", help="File to output embeddedsdf.cc content to"
)

args = parser.parse_args(args)
if not args.input_files or len(args.input_files) == 0:
paths = collect_file_locations()
else:
paths = [Path(f) for f in args.input_files]
content = generate_map_content(paths, args.sdf_root)
write_output(content, args.output_file)
return 0

output = copyright + prolog + map_str + epilog

# output to stdin so that CMake can read it and create the appropriate file
print(output)
if __name__ == "__main__":
sys.exit(main())

0 comments on commit 09d69f4

Please sign in to comment.