Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port embedSdf script from Ruby to Python3 and provide unittests #884

Merged
merged 15 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion sdf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ add_subdirectory(1.10)
add_custom_target(schema)
add_dependencies(schema schema1_10)

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
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/sdf/embedSdf.py
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/sdf"
OUTPUT_FILE "${PROJECT_BINARY_DIR}/src/EmbeddedSdf.cc"
)
Expand Down
151 changes: 151 additions & 0 deletions sdf/embedSdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
import inspect
from pathlib import Path, PurePosixPath

""""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']

# 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']

# whitespace indentation for C++ code
INDENTATION = ' '
# newline character
NEWLINE = '\n'

def get_copyright_notice() -> str:
"""
Provides the copyrigt notice for the C++ file

:returns: copyright notice
"""
res = inspect.cleandoc("""
/*
* Copyright 2022 Open Source Robotics Foundation
*
* 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.
*
*/
""")
return res + 2*NEWLINE


def get_file_header_prolog() -> str:
"""
Provides the include statement, namespace and variable declaration of the C++ file

:returns: prolog of the C++ file
"""
res = inspect.cleandoc("""
#include "EmbeddedSdf.hh"

namespace sdf
{
inline namespace SDF_VERSION_NAMESPACE
{
/////////////////////////////////////////////////
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

: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(f'"{arg_path}",')
res.append('R"__sdf_literal__(')
res.append(f'{arg_file_content}')
res.append(')__sdf_literal__"')
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("""
};

return result;
}

}
} // namespace sdf

""")
return NEWLINE + res


if __name__ == "__main__":
copyright = get_copyright_notice()
prolog = get_file_header_prolog()

map_str = ""
for sdf_version in SUPPORTED_SDF_VERSIONS:
pathlist = Path(sdf_version).glob('*.sdf')
map_str += get_map_content(pathlist)

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]

epilog = get_file_header_epilog()

output = copyright + prolog + map_str + epilog

# output to stdin so that CMake can read it and create the appropriate file
print(output)
45 changes: 0 additions & 45 deletions sdf/embedSdf.rb

This file was deleted.

56 changes: 56 additions & 0 deletions src/SDF_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#include <gz/utils/Environment.hh>
#include <gz/utils/SuppressWarning.hh>

#include "test_config.hh"
#include "test_utils.hh"

#include "sdf/sdf.hh"

class SDFUpdateFixture
Expand Down Expand Up @@ -559,6 +562,59 @@ TEST(SDF, Version)
EXPECT_STREQ(SDF_VERSION, sdf::SDF::Version().c_str());
}

/////////////////////////////////////////////////
TEST(SDF, EmbeddedSpec)
{
std::string result;

result = sdf::SDF::EmbeddedSpec("actor.sdf", false);
EXPECT_NE(result.find("<!-- Actor -->"), std::string::npos);
EXPECT_NE(result.find("<element name=\"actor\" required=\"*\">"),
std::string::npos);
result = sdf::SDF::EmbeddedSpec("actor.sdf", true);
EXPECT_NE(result.find("<!-- Actor -->"), std::string::npos);
EXPECT_NE(result.find("<element name=\"actor\" required=\"*\">"),
std::string::npos);

result = sdf::SDF::EmbeddedSpec("root.sdf", false);
EXPECT_NE(result.find("SDFormat base element"), std::string::npos);
EXPECT_NE(result.find("name=\"version\" type=\"string\""), std::string::npos);
result = sdf::SDF::EmbeddedSpec("root.sdf", true);
EXPECT_NE(result.find("SDFormat base element"), std::string::npos);
EXPECT_NE(result.find("name=\"version\" type=\"string\""), std::string::npos);
}

TEST(SDF, EmbeddedSpecNonExistent)
{
std::string result;

// Capture sdferr output
std::stringstream stderr_buffer;
sdf::testing::RedirectConsoleStream redir(
sdf::Console::Instance()->GetMsgStream(), &stderr_buffer);
#ifdef _WIN32
sdf::Console::Instance()->SetQuiet(false);
sdf::testing::ScopeExit revertSetQuiet(
[]
{
sdf::Console::Instance()->SetQuiet(true);
});
#endif

result = sdf::SDF::EmbeddedSpec("unavailable.sdf", false);
EXPECT_NE(stderr_buffer.str().find("Unable to find SDF filename"),
std::string::npos);
EXPECT_NE(stderr_buffer.str().find("with version"), std::string::npos);
EXPECT_TRUE(result.empty());

// clear the contents of the buffer
stderr_buffer.str("");

result = sdf::SDF::EmbeddedSpec("unavailable.sdf", true);
EXPECT_TRUE(stderr_buffer.str().empty());
EXPECT_TRUE(result.empty());
}

/////////////////////////////////////////////////
TEST(SDF, FilePath)
{
Expand Down