mirror of
https://github.com/build-cpp/cmkr
synced 2026-06-08 13:22:55 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c76956450a | |||
| 1596a8143d | |||
| 9b0f18ee94 | |||
| 887086dc05 |
Generated
+1
-1
@@ -25,7 +25,7 @@ project(cmkr
|
|||||||
LANGUAGES
|
LANGUAGES
|
||||||
CXX
|
CXX
|
||||||
VERSION
|
VERSION
|
||||||
0.2.11
|
0.2.12
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
"CMakeLists generator from TOML"
|
"CMakeLists generator from TOML"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -69,3 +69,4 @@ arguments:
|
|||||||
- https://github.com/ToruNiina/toml11
|
- https://github.com/ToruNiina/toml11
|
||||||
- https://github.com/mpark/variant
|
- https://github.com/mpark/variant
|
||||||
- https://www.svgrepo.com/svg/192268/hammer
|
- https://www.svgrepo.com/svg/192268/hammer
|
||||||
|
- https://github.com/can1357 for buying `cmkr.build` ❤️
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ cmkr-include = false
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "cmkr"
|
name = "cmkr"
|
||||||
version = "0.2.11"
|
version = "0.2.12"
|
||||||
description = "CMakeLists generator from TOML"
|
description = "CMakeLists generator from TOML"
|
||||||
languages = ["CXX"]
|
languages = ["CXX"]
|
||||||
subdirs = ["third_party", "tests"]
|
subdirs = ["third_party", "tests"]
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ endif()
|
|||||||
|
|
||||||
message(STATUS "Version ${OLDVERSION} -> ${NEWVERSION}")
|
message(STATUS "Version ${OLDVERSION} -> ${NEWVERSION}")
|
||||||
|
|
||||||
find_program(CMKR_EXECUTABLE "cmkr" PATHS "${CMAKE_SOURCE_DIR}/build" NO_CACHE REQUIRED)
|
find_program(CMKR_EXECUTABLE "cmkr" PATHS "${CMAKE_SOURCE_DIR}/build" PATH_SUFFIXES Debug Release RelWithDebInfo MinSizeRel NO_CACHE REQUIRED)
|
||||||
message(STATUS "Found cmkr: ${CMKR_EXECUTABLE}")
|
message(STATUS "Found cmkr: ${CMKR_EXECUTABLE}")
|
||||||
|
|
||||||
# Replace version in cmake.toml
|
# Replace version in cmake.toml
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ include_guard()
|
|||||||
|
|
||||||
# Change these defaults to point to your infrastructure if desired
|
# Change these defaults to point to your infrastructure if desired
|
||||||
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
|
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
|
||||||
set(CMKR_TAG "v0.2.11" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
|
set(CMKR_TAG "v0.2.12" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
|
||||||
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
|
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
|
||||||
|
|
||||||
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake
|
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct Option {
|
|||||||
bool val = false;
|
bool val = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Package {
|
struct FindPackage {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string condition;
|
std::string condition;
|
||||||
std::string version;
|
std::string version;
|
||||||
@@ -151,6 +151,14 @@ struct Content {
|
|||||||
ConditionVector include_after;
|
ConditionVector include_after;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Package {
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::string> targets;
|
||||||
|
std::vector<std::string> headers;
|
||||||
|
std::vector<std::string> dependencies;
|
||||||
|
std::string namespace_name;
|
||||||
|
};
|
||||||
|
|
||||||
struct Project {
|
struct Project {
|
||||||
// This is the CMake version required to use all cmkr versions.
|
// This is the CMake version required to use all cmkr versions.
|
||||||
std::string cmake_version = "3.15";
|
std::string cmake_version = "3.15";
|
||||||
@@ -175,8 +183,9 @@ struct Project {
|
|||||||
ConditionVector include_after;
|
ConditionVector include_after;
|
||||||
std::vector<Setting> settings;
|
std::vector<Setting> settings;
|
||||||
std::vector<Option> options;
|
std::vector<Option> options;
|
||||||
std::vector<Package> packages;
|
std::vector<FindPackage> packages;
|
||||||
Vcpkg vcpkg;
|
Vcpkg vcpkg;
|
||||||
|
Package package;
|
||||||
std::vector<Content> contents;
|
std::vector<Content> contents;
|
||||||
std::vector<Template> templates;
|
std::vector<Template> templates;
|
||||||
std::vector<Target> targets;
|
std::vector<Target> targets;
|
||||||
|
|||||||
+115
-15
@@ -25,16 +25,16 @@ static std::string format(const char *format, tsl::ordered_map<std::string, std:
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string extract_suffix(const fs::path &base, const fs::path &full) {
|
||||||
|
auto fullpath = full.string();
|
||||||
|
auto base_len = base.string().length();
|
||||||
|
auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len);
|
||||||
|
return delet;
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool root_project) {
|
static std::vector<std::string> expand_cmake_path(const fs::path &name, const fs::path &toml_dir, bool root_project) {
|
||||||
std::vector<std::string> temp;
|
std::vector<std::string> temp;
|
||||||
|
|
||||||
auto extract_suffix = [](const fs::path &base, const fs::path &full) {
|
|
||||||
auto fullpath = full.string();
|
|
||||||
auto base_len = base.string().length();
|
|
||||||
auto delet = fullpath.substr(base_len + 1, fullpath.length() - base_len);
|
|
||||||
return delet;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto stem = name.filename().stem().string();
|
auto stem = name.filename().stem().string();
|
||||||
auto ext = name.extension();
|
auto ext = name.extension();
|
||||||
|
|
||||||
@@ -234,9 +234,16 @@ struct Command {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
static bool is_empty(const T &v) {
|
||||||
|
return v.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_empty(const char *v) { return *v == '\0'; }
|
||||||
|
|
||||||
template <class K, class V>
|
template <class K, class V>
|
||||||
bool print_arg(const std::pair<K, V> &kv) {
|
bool print_arg(const std::pair<K, V> &kv) {
|
||||||
if (kv.second.empty()) {
|
if (is_empty(kv.second)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +257,7 @@ struct Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool print_arg(const RawArg &arg) {
|
bool print_arg(const RawArg &arg) {
|
||||||
if (arg.arg.empty()) {
|
if (is_empty(arg.arg)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,6 +616,9 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
gen.conditional_cmake(project.cmake_after);
|
gen.conditional_cmake(project.cmake_after);
|
||||||
|
|
||||||
if (!project.vcpkg.packages.empty()) {
|
if (!project.vcpkg.packages.empty()) {
|
||||||
|
if (!root_project) {
|
||||||
|
throw std::runtime_error("[vcpkg] has to be in the project root");
|
||||||
|
}
|
||||||
// Allow the user to specify a url or derive it from the version
|
// Allow the user to specify a url or derive it from the version
|
||||||
auto url = project.vcpkg.url;
|
auto url = project.vcpkg.url;
|
||||||
auto version_name = url;
|
auto version_name = url;
|
||||||
@@ -718,11 +728,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
version_info = " (" + content.arguments.at("SVN_REVISION") + ")";
|
version_info = " (" + content.arguments.at("SVN_REVISION") + ")";
|
||||||
}
|
}
|
||||||
cmd("message")("STATUS", "Fetching " + content.name + version_info + "...");
|
cmd("message")("STATUS", "Fetching " + content.name + version_info + "...");
|
||||||
ss << "FetchContent_Declare(\n\t" << content.name << "\n";
|
cmd("FetchContent_Declare")(content.name, content.arguments);
|
||||||
for (const auto &arg : content.arguments) {
|
|
||||||
ss << "\t" << arg.first << "\n\t\t" << arg.second << "\n";
|
|
||||||
}
|
|
||||||
ss << ")\n";
|
|
||||||
cmd("FetchContent_MakeAvailable")(content.name).endl();
|
cmd("FetchContent_MakeAvailable")(content.name).endl();
|
||||||
|
|
||||||
gen.conditional_includes(content.include_after);
|
gen.conditional_includes(content.include_after);
|
||||||
@@ -959,7 +965,23 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
target_cmd("target_compile_options", t.compile_options, target_scope);
|
target_cmd("target_compile_options", t.compile_options, target_scope);
|
||||||
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
|
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_include_directories", t.include_directories, target_scope);
|
cmkr::parser::ConditionVector include_directories;
|
||||||
|
for (const auto &itr : t.include_directories) {
|
||||||
|
auto directories = itr.second;
|
||||||
|
if (target_scope == "PUBLIC" || target_scope == "INTERFACE") {
|
||||||
|
for (auto &directory : directories) {
|
||||||
|
// TODO: properly detect if this is a relative path
|
||||||
|
if (directory[0] != '$') {
|
||||||
|
directory = "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/" + directory + ">";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include_directories[itr.first] = directories;
|
||||||
|
}
|
||||||
|
// TODO: this needs to be done cleaner, maybe the install command has some magic?
|
||||||
|
include_directories[""].push_back("$<INSTALL_INTERFACE:include>");
|
||||||
|
|
||||||
|
target_cmd("target_include_directories", include_directories, target_scope);
|
||||||
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
|
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
|
||||||
|
|
||||||
target_cmd("target_link_directories", t.link_directories, target_scope);
|
target_cmd("target_link_directories", t.link_directories, target_scope);
|
||||||
@@ -1049,6 +1071,84 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!project.package.targets.empty()) {
|
||||||
|
//TODO: generate an option to enable/disable installation
|
||||||
|
|
||||||
|
const auto &package = project.package;
|
||||||
|
|
||||||
|
if (!root_project) {
|
||||||
|
throw std::runtime_error("[package] has to be in the project root");
|
||||||
|
}
|
||||||
|
comment("Project packaging");
|
||||||
|
cmd("include")("CMakePackageConfigHelpers").endl();
|
||||||
|
|
||||||
|
comment("Install headers");
|
||||||
|
tsl::ordered_map<std::string, std::vector<std::string>> header_tree;
|
||||||
|
for (const auto &pattern : package.headers) {
|
||||||
|
auto pattern_path = fs::path(pattern);
|
||||||
|
auto pattern_parent = pattern_path.parent_path();
|
||||||
|
auto headers = expand_cmake_path(pattern_path, path, root_project);
|
||||||
|
for (const auto &header : headers) {
|
||||||
|
auto install_path = extract_suffix(pattern_parent, header);
|
||||||
|
auto install_destination = "include/" + fs::path(install_path).parent_path().string();
|
||||||
|
header_tree[install_destination].push_back(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &itr : header_tree) {
|
||||||
|
cmd("install")(std::make_pair("FILES", itr.second), std::make_pair("DESTINATION", itr.first));
|
||||||
|
printf("Install destination: %s\n", itr.first.c_str());
|
||||||
|
for (const auto &header : itr.second) {
|
||||||
|
printf(" Header: %s\n", header.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endl();
|
||||||
|
|
||||||
|
comment("Install target outputs and generate target exports");
|
||||||
|
auto export_name = package.name + "Targets";
|
||||||
|
cmd("install")(std::make_pair("TARGETS", package.targets), std::make_pair("EXPORT", export_name), "LIBRARY", "ARCHIVE", "RUNTIME");
|
||||||
|
endl();
|
||||||
|
|
||||||
|
comment("Install generated target exports");
|
||||||
|
auto cmake_destination = "lib/cmake/" + package.name;
|
||||||
|
cmd("install")(std::make_pair("EXPORT", export_name), std::make_pair("FILE", export_name + ".cmake"),
|
||||||
|
std::make_pair("NAMESPACE", package.namespace_name), std::make_pair("DESTINATION", cmake_destination));
|
||||||
|
endl();
|
||||||
|
|
||||||
|
auto config_base = "${CMAKE_CURRENT_BINARY_DIR}/" + package.name;
|
||||||
|
std::vector<std::string> generated_scripts;
|
||||||
|
if (!project.project_version.empty()) {
|
||||||
|
comment("Generate package version scripts");
|
||||||
|
auto version_cmake = config_base + "ConfigVersion.cmake";
|
||||||
|
cmd("write_basic_package_version_file")(version_cmake, std::make_pair("VERSION", "${PROJECT_VERSION}"),
|
||||||
|
std::make_pair("COMPATIBILITY", "SameMajorVersion"));
|
||||||
|
endl();
|
||||||
|
|
||||||
|
generated_scripts.push_back(version_cmake);
|
||||||
|
}
|
||||||
|
|
||||||
|
comment("Generate package config scripts");
|
||||||
|
cmd("set")("PACKAGE_NAME", package.name);
|
||||||
|
// TODO: this needs some actual logic
|
||||||
|
cmd("set")("PACKAGE_DEPENDENCIES", package.dependencies);
|
||||||
|
auto config_cmake = config_base + "Config.cmake";
|
||||||
|
auto config_in = config_cmake + ".in";
|
||||||
|
cmd("file")("WRITE", config_in, RawArg(R"("include(CMakeFindDependencyMacro)\n\n")"));
|
||||||
|
cmd("file")("APPEND", config_in, RawArg(R"("string(REGEX MATCHALL \"[^;]+\" SEPARATE_DEPENDENCIES \"@PACKAGE_DEPENDENCIES@\")\n")"));
|
||||||
|
cmd("file")("APPEND", config_in, RawArg(R"("foreach(dependency ${SEPARATE_DEPENDENCIES})\n")"));
|
||||||
|
cmd("file")("APPEND", config_in, RawArg(R"(" string(REPLACE \" \" \";\" args \"\${dependency}\")\n")"));
|
||||||
|
cmd("file")("APPEND", config_in, RawArg(R"(" find_dependency(\${args})\n")"));
|
||||||
|
cmd("file")("APPEND", config_in, RawArg(R"("endforeach()\n\n")"));
|
||||||
|
cmd("file")("APPEND", config_in, RawArg(R"("include(\"\${CMAKE_CURRENT_LIST_DIR}/@PACKAGE_NAME@Targets.cmake\")\n")"));
|
||||||
|
cmd("configure_file")(config_in, config_cmake, "@ONLY");
|
||||||
|
generated_scripts.push_back(config_cmake);
|
||||||
|
endl();
|
||||||
|
|
||||||
|
comment("Install generated package scripts");
|
||||||
|
cmd("install")(std::make_pair("FILES", generated_scripts), std::make_pair("DESTINATION", cmake_destination));
|
||||||
|
endl();
|
||||||
|
}
|
||||||
|
|
||||||
// Generate CMakeLists.txt
|
// Generate CMakeLists.txt
|
||||||
auto list_path = fs::path(path) / "CMakeLists.txt";
|
auto list_path = fs::path(path) / "CMakeLists.txt";
|
||||||
|
|
||||||
|
|||||||
+32
-5
@@ -60,7 +60,8 @@ class TomlChecker {
|
|||||||
TomlChecker(TomlChecker &&) = delete;
|
TomlChecker(TomlChecker &&) = delete;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void optional(const toml::key &ky, Condition<T> &destination) {
|
bool optional(const toml::key &ky, Condition<T> &destination) {
|
||||||
|
auto found = false;
|
||||||
// TODO: this algorithm in O(n) over the amount of keys, kinda bad
|
// TODO: this algorithm in O(n) over the amount of keys, kinda bad
|
||||||
const auto &table = m_v.as_table();
|
const auto &table = m_v.as_table();
|
||||||
for (const auto &itr : table) {
|
for (const auto &itr : table) {
|
||||||
@@ -69,9 +70,11 @@ class TomlChecker {
|
|||||||
if (value.is_table()) {
|
if (value.is_table()) {
|
||||||
if (value.contains(ky)) {
|
if (value.contains(ky)) {
|
||||||
destination[key] = toml::find<T>(value, ky);
|
destination[key] = toml::find<T>(value, ky);
|
||||||
|
found = true;
|
||||||
}
|
}
|
||||||
} else if (key == ky) {
|
} else if (key == ky) {
|
||||||
destination[""] = toml::find<T>(m_v, ky);
|
destination[""] = toml::find<T>(m_v, ky);
|
||||||
|
found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,15 +85,19 @@ class TomlChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
visit(ky);
|
visit(ky);
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void optional(const toml::key &ky, T &destination) {
|
bool optional(const toml::key &ky, T &destination) {
|
||||||
|
auto found = false;
|
||||||
// TODO: this currently doesn't allow you to get an optional map<string, X>
|
// TODO: this currently doesn't allow you to get an optional map<string, X>
|
||||||
if (m_v.contains(ky)) {
|
if (m_v.contains(ky)) {
|
||||||
destination = toml::find<T>(m_v, ky);
|
destination = toml::find<T>(m_v, ky);
|
||||||
|
found = true;
|
||||||
}
|
}
|
||||||
visit(ky);
|
visit(ky);
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -339,7 +346,7 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
|||||||
using pkg_map = tsl::ordered_map<std::string, TomlBasicValue>;
|
using pkg_map = tsl::ordered_map<std::string, TomlBasicValue>;
|
||||||
const auto &pkgs = toml::find<pkg_map>(toml, "find-package");
|
const auto &pkgs = toml::find<pkg_map>(toml, "find-package");
|
||||||
for (const auto &itr : pkgs) {
|
for (const auto &itr : pkgs) {
|
||||||
Package p;
|
FindPackage p;
|
||||||
p.name = itr.first;
|
p.name = itr.first;
|
||||||
const auto &value = itr.second;
|
const auto &value = itr.second;
|
||||||
if (itr.second.is_string()) {
|
if (itr.second.is_string()) {
|
||||||
@@ -426,9 +433,12 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
|||||||
throw std::runtime_error(format_key_error("Unknown key '" + argItr.first + "'", argItr.first, argItr.second));
|
throw std::runtime_error(format_key_error("Unknown key '" + argItr.first + "'", argItr.first, argItr.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
c.visit(argItr.first);
|
// Make sure not to emit keys like "condition" in the FetchContent call
|
||||||
|
if (!c.visisted(key)) {
|
||||||
|
content.arguments.emplace(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
content.arguments.emplace(key, value);
|
c.visit(argItr.first);
|
||||||
}
|
}
|
||||||
contents.emplace_back(std::move(content));
|
contents.emplace_back(std::move(content));
|
||||||
}
|
}
|
||||||
@@ -646,6 +656,23 @@ Project::Project(const Project *parent, const std::string &path, bool build) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(checker.contains("package")) {
|
||||||
|
auto& p = checker.create(toml, "package");
|
||||||
|
if(!p.optional("name", package.name)) {
|
||||||
|
package.name = project_name;
|
||||||
|
}
|
||||||
|
p.required("targets", package.targets);
|
||||||
|
// TODO: should this be optional?
|
||||||
|
p.required("headers", package.headers);
|
||||||
|
// TODO: how to detect this should default to all dependencies vs no dependencies?
|
||||||
|
p.optional("dependencies", package.dependencies);
|
||||||
|
if(!p.optional("namespace", package.namespace_name)) {
|
||||||
|
package.namespace_name = project_name + "::";
|
||||||
|
} else {
|
||||||
|
// TODO: check if the namespace ends with "::"?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
checker.check(conditions, true);
|
checker.check(conditions, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,3 +45,9 @@ name = "templates"
|
|||||||
working-directory = "templates"
|
working-directory = "templates"
|
||||||
command = "$<TARGET_FILE:cmkr>"
|
command = "$<TARGET_FILE:cmkr>"
|
||||||
arguments = ["build"]
|
arguments = ["build"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "package"
|
||||||
|
working-directory = "package"
|
||||||
|
command = "$<TARGET_FILE:cmkr>"
|
||||||
|
arguments = ["build"]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[project]
|
||||||
|
name = "Utility"
|
||||||
|
version = "1.2"
|
||||||
|
|
||||||
|
[target.Utility]
|
||||||
|
type = "static"
|
||||||
|
alias = "Utility::Utility" # autogenerate this alias as ${PROJECT_NAME}::TargetName
|
||||||
|
sources = [
|
||||||
|
"src/Utility/*.cpp",
|
||||||
|
"include/Utility/*.hpp",
|
||||||
|
]
|
||||||
|
include-directories = ["include"]
|
||||||
|
compile-features = ["cxx_std_11"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "Utility" # defaults to ${PROJECT_NAME}
|
||||||
|
targets = ["Utility"] # required
|
||||||
|
headers = ["include/**.hpp", "include/**.h"] # required
|
||||||
|
dependencies = [] # defaults to all the required packages
|
||||||
|
namespace = "Utility::" # defaults to ${PROJECT_NAME}::
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace String
|
||||||
|
{
|
||||||
|
bool startsWith(const std::string& str, const std::string& prefix);
|
||||||
|
std::string reverse(const std::string& str);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#include <cctype>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <Utility/String.hpp>
|
||||||
|
|
||||||
|
namespace String
|
||||||
|
{
|
||||||
|
bool startsWith(const std::string& str, const std::string& prefix)
|
||||||
|
{
|
||||||
|
return str.find(prefix) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string reverse(const std::string& str)
|
||||||
|
{
|
||||||
|
auto result = str;
|
||||||
|
std::reverse(result.begin(), result.end());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user