diff --git a/CMakeLists.txt b/CMakeLists.txt index d117c21..8bf1629 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.11) # FetchContent requires CMake 3.11 project(chaiscript_extras) # MINGW does not yet support C++11's concurrency features @@ -10,7 +10,6 @@ endif() option(BUILD_IN_CPP17_MODE "Build with C++17 flags" FALSE) - if(CMAKE_COMPILER_IS_GNUCC) option(ENABLE_COVERAGE "Enable Coverage Reporting in GCC" FALSE) @@ -71,8 +70,6 @@ endif() include(CTest) enable_testing() - - if(CMAKE_COMPILER_IS_GNUCC) execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) @@ -137,7 +134,6 @@ if(MINGW OR CYGWIN) add_definitions(-O3) endif() - if(NOT MULTITHREAD_SUPPORT_ENABLED) add_definitions(-DCHAISCRIPT_NO_THREADS) endif() @@ -161,75 +157,11 @@ if(CMAKE_HOST_UNIX) add_definitions(-pthread) endif() - endif() -list(APPEND LIBS ${READLINE_LIB}) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") -# ChaiScript -set(CHAISCRIPT_BRANCH v5.8.6) -set(CHAISCRIPT_VERSION 5.8.6) -file(DOWNLOAD https://github.com/ChaiScript/ChaiScript/archive/${CHAISCRIPT_BRANCH}.tar.gz "${CMAKE_BINARY_DIR}/chaiscript/chaiscript-${CHAISCRIPT_BRANCH}.tar.gz" - INACTIVITY_TIMEOUT 180 TIMEOUT 180 TLS_VERIFY off) -execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf "${CMAKE_BINARY_DIR}/chaiscript/chaiscript-${CHAISCRIPT_BRANCH}.tar.gz" "${CMAKE_BINARY_DIR}/chaiscript" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/chaiscript") -include_directories("${CMAKE_BINARY_DIR}/chaiscript/ChaiScript-${CHAISCRIPT_VERSION}/include") - -# String ID -set(STRING_ID_VERSION 674527b0dab0cca9cf846f3084e986d2783357eb) -file(DOWNLOAD https://github.com/foonathan/string_id/archive/${STRING_ID_VERSION}.tar.gz "${CMAKE_BINARY_DIR}/string_id/string_id-${STRING_ID_VERSION}.tar.gz" - INACTIVITY_TIMEOUT 180 TIMEOUT 180 TLS_VERIFY off) -execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf "${CMAKE_BINARY_DIR}/string_id/string_id-${STRING_ID_VERSION}.tar.gz" "${CMAKE_BINARY_DIR}/string_id" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/string_id") -file(RENAME "${CMAKE_BINARY_DIR}/string_id/string_id-${STRING_ID_VERSION}" "${CMAKE_BINARY_DIR}/string_id/string_id") -include_directories("${CMAKE_BINARY_DIR}/string_id") -include_directories("${CMAKE_SOURCE_DIR}/tests") - -# Add catch tests macro -macro(ADD_CATCH_TESTS executable) - if (MSVC) - file(TO_NATIVE_PATH "${QT_LIBRARY_DIR}" QT_LIB_PATH) - set(NEWPATH "${QT_LIB_PATH};$ENV{PATH}") - else() - set(NEWPATH $ENV{PATH}) - endif() - - get_target_property(target_files ${executable} SOURCES) - - message("Files: ${target_files}") - - foreach(source ${target_files}) - if(NOT "${source}" MATCHES "/moc_.*cxx") - string(REGEX MATCH .*cpp source "${source}") - if(source) - file(READ "${source}" contents) - string(REGEX MATCHALL "TEST_CASE\\([ ]*\"[^\"]+\"" found_tests ${contents}) - foreach(hit ${found_tests}) - message("Found Test: ${hit}") - string(REGEX REPLACE "TEST_CASE\\([ ]*(\"[^\"]+\").*" "\\1" test_name ${hit}) - add_test(${test_name} "${executable}" ${test_name}) - set_tests_properties(${test_name} PROPERTIES TIMEOUT 660 ENVIRONMENT "PATH=${NEWPATH}") - endforeach() - endif() - endif() - endforeach() -endmacro() - - - -add_executable(math_test tests/math.cpp) -target_link_libraries(math_test ${LIBS}) -ADD_CATCH_TESTS(math_test) - -# TODO: Fix String ID Tests -#add_executable(string_id_test tests/string_id.cpp) -#target_link_libraries(string_id_test ${LIBS}) -#ADD_CATCH_TESTS(string_id_test) - -add_executable(string_methods_test tests/string_methods.cpp) -target_link_libraries(string_methods_test ${LIBS}) -ADD_CATCH_TESTS(string_methods_test) +add_subdirectory(cmake) +add_subdirectory(tests) diff --git a/README.md b/README.md index 1421906..e3459de 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,12 @@ chai.add(string_idlib); Adds various string methods to extend how strings can be used in ChaiScript: - `string::replace(string, string)` - `string::trim()` +- `string::trimStart()` +- `string::trimEnd()` - `string::split(string)` - `string::toLowerCase()` - `string::toUpperCase()` +- `string::includes()` ### Install diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 0000000..4d9daaa --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1,4 @@ +include(chaiscript.cmake) + +# TODO: Fix string_id_test. +#include(foonathan_string_id.cmake) diff --git a/cmake/chaiscript.cmake b/cmake/chaiscript.cmake new file mode 100644 index 0000000..5fbdf1c --- /dev/null +++ b/cmake/chaiscript.cmake @@ -0,0 +1,25 @@ +set(CHAISCRIPT_VERSION 5.8.6) +find_package(chaiscript ${CHAISCRIPT_VERSION} QUIET) + +if (NOT chaiscript_FOUND) + include(FetchContent) + + FetchContent_Declare( + chaiscript + GIT_REPOSITORY https://github.com/ChaiScript/ChaiScript.git + GIT_TAG v${CHAISCRIPT_VERSION} + ) + + FetchContent_GetProperties(chaiscript) + if (NOT chaiscript_POPULATED) + set(FETCHCONTENT_QUIET NO) + FetchContent_Populate(chaiscript) + + set(BUILD_SAMPLES OFF CACHE BOOL "" FORCE) + set(BUILD_MODULES ON CACHE BOOL "" FORCE) + set(BUILD_TESTING OFF CACHE BOOL "" FORCE) + set(BUILD_LIBFUZZ_TESTER OFF CACHE BOOL "" FORCE) + + add_subdirectory(${chaiscript_SOURCE_DIR} ${chaiscript_BINARY_DIR}) + endif() +endif() diff --git a/cmake/foonathan_string_id.cmake b/cmake/foonathan_string_id.cmake new file mode 100644 index 0000000..46fcc30 --- /dev/null +++ b/cmake/foonathan_string_id.cmake @@ -0,0 +1,20 @@ +set(STRING_ID_VERSION 6e2e5c48ee4a3ac0c54ba505f0a573561f2979ec) +find_package(foonathan_string_id 2.0.3 QUIET) + +if (NOT foonathan_string_id_FOUND) + include(FetchContent) + + FetchContent_Declare( + foonathan_string_id + GIT_REPOSITORY https://github.com/foonathan/string_id.git + GIT_TAG ${STRING_ID_VERSION} + ) + + FetchContent_GetProperties(foonathan_string_id) + if (NOT foonathan_string_id_POPULATED) + set(FETCHCONTENT_QUIET NO) + FetchContent_Populate(foonathan_string_id) + + add_subdirectory(${foonathan_string_id_SOURCE_DIR} ${foonathan_string_id_BINARY_DIR}) + endif() +endif() diff --git a/include/chaiscript/extras/string_id.hpp b/include/chaiscript/extras/string_id.hpp index 3461a73..241a52f 100644 --- a/include/chaiscript/extras/string_id.hpp +++ b/include/chaiscript/extras/string_id.hpp @@ -9,8 +9,6 @@ #include #include - - namespace chaiscript { namespace extras { namespace string_id { diff --git a/include/chaiscript/extras/string_methods.hpp b/include/chaiscript/extras/string_methods.hpp index 6abcaf7..23b75da 100644 --- a/include/chaiscript/extras/string_methods.hpp +++ b/include/chaiscript/extras/string_methods.hpp @@ -1,18 +1,21 @@ /** * @file ChaiScript String Methods * - * Adds some additional string methods to ChaiScript strings. + * Adds some additional string methods to ChaiScript strings: * - * string::replace(string search, string replace) - * string::replace(char search, char replace) - * string::trim() - * string::split(string token) - * string::toLowerCase() - * string::toUpperCase() + * string::replace(string search, string replace) + * string::replace(char search, char replace) + * string::trim() + * string::trimStart() + * string::trimEnd() + * string::split(string token) + * string::toLowerCase() + * string::toUpperCase() + * string::includes(string search) * - * To allow selecting indexes from split(), ensure VectorString type is added: + * To allow selecting indexes from split(), ensure the vector of strings type is added: * - * chai.add(chaiscript::bootstrap::standard_library::vector_type>("VectorString")); + * chai.add(chaiscript::bootstrap::standard_library::vector_type>("VectorString")); */ #ifndef CHAISCRIPT_EXTRAS_STRING_METHODS_HPP_ @@ -21,78 +24,158 @@ #include #include #include - #include namespace chaiscript { namespace extras { namespace string_methods { - ModulePtr bootstrap(ModulePtr m = std::make_shared()) - { - // string::replace(std::string search, std::string replace) - m->add(fun([](const std::string& subject, const std::string& search, const std::string& replace) { - std::string result(subject); - size_t pos = 0; - while ((pos = result.find(search, pos)) != std::string::npos) { - result.replace(pos, search.length(), replace); - pos += replace.length(); - } - return result; - }), "replace"); - - // string::replace(char, char) - m->add(fun([](const std::string& subject, char search, char replace) { - std::string result(subject); - std::replace(result.begin(), result.end(), search, replace); - return result; - }), "replace"); - - // string::trim() - m->add(fun([](const std::string& subject) { - std::string result(subject); - std::string chars = "\t\n\v\f\r "; - result.erase(0, result.find_first_not_of(chars)); - result.erase(0, result.find_last_not_of(chars)); - return result; - }), "trim"); - - // string::split(string) - m->add(fun([](const std::string& subject, const std::string& token) { - std::string str(subject); - std::vector result; - while (str.size()) { - size_t index = str.find(token); - if (index != std::string::npos) { - result.push_back(str.substr(0, index)); - str = str.substr(index + token.size()); - if (str.size() == 0) { - result.push_back(str); - } - } else { + + /** + * Replaces all occurances of a string within the given string. + * + * @code + * var hello = "Hello World" + * hello.replace("Hello", "Goodbye") + * // => "Goodbye World" + * @endcode + * + * @see replaceChar + */ + std::string replaceString(const std::string& subject, const std::string& search, const std::string& replace) { + std::string result(subject); + size_t pos = 0; + while ((pos = result.find(search, pos)) != std::string::npos) { + result.replace(pos, search.length(), replace); + pos += replace.length(); + } + return result; + } + + /** + * Replaces all occurances of a character within the given character. + * + * @see replaceString + */ + std::string replaceChar(const std::string& subject, char search, char replace) { + std::string result(subject); + std::replace(result.begin(), result.end(), search, replace); + return result; + } + + /** + * Trims the given string. + */ + std::string trim(const std::string& subject) { + std::string result(subject); + std::string delimiters = "\t\n\v\f\r "; + result.erase(0, result.find_first_not_of(delimiters)); + result.erase(0, result.find_last_not_of(delimiters)); + return result; + } + + /** + * Trims the beginning of the given string. + */ + std::string trimStart(const std::string& subject) { + std::string result(subject); + std::string delimiters = "\t\n\v\f\r "; + result.erase(0, result.find_first_not_of(delimiters)); + return result; + } + + /** + * Trims the end of the given string. + */ + std::string trimEnd(const std::string& subject) { + std::string result(subject); + std::string delimiters = "\t\n\v\f\r "; + result.erase(result.find_last_not_of(delimiters) + 1); + return result; + } + + /** + * Splits the given string into a vector of strings. + * + * @code + * var input = "Hello|World|How|Are|You" + * var words = input.split("|") + * words[1] + * // => "World" + */ + std::vector split(const std::string& subject, const std::string& token) { + std::string str(subject); + std::vector result; + while (str.size()) { + size_t index = str.find(token); + if (index != std::string::npos) { + result.push_back(str.substr(0, index)); + str = str.substr(index + token.size()); + if (str.size() == 0) { result.push_back(str); - str = ""; } + } else { + result.push_back(str); + str = ""; } - return result; - }), "split"); - - // string::toLowerCase() - m->add(fun([](const std::string& subject) { - std::string result(subject); - std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { - return std::tolower(c); - }); - return result; - }), "toLowerCase"); - - // string::toUpperCase - m->add(fun([](const std::string& subject) { - std::string result(subject); - std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { - return std::toupper(c); - }); - return result; - }), "toUpperCase"); + } + return result; + } + + /** + * Convert the given string to lowercase letters. + */ + std::string toLowerCase(const std::string& subject) { + std::string result(subject); + std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { + return std::tolower(c); + }); + return result; + } + + /** + * Convert the given string to uppercase letters. + */ + std::string toUpperCase(const std::string& subject) { + std::string result(subject); + std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { + return std::toupper(c); + }); + return result; + } + + /** + * Checks if a string includes the given string. + * + * @see includesChar + */ + bool includes(const std::string& subject, const std::string& search) { + return subject.find(search) != std::string::npos; + } + + /** + * Checks if a string includes the given character. + * + * @see includes + */ + bool includesChar(const std::string& subject, char search) { + return subject.find(search) != std::string::npos; + } + + /** + * Adds the String Methods to the given ChaiScript module. + */ + ModulePtr bootstrap(ModulePtr m = std::make_shared()) + { + m->add(fun(replaceString), "replace"); + m->add(fun(replaceChar), "replace"); + m->add(fun(trim), "trim"); + m->add(fun(split), "split"); + m->add(fun(toLowerCase), "toLowerCase"); + m->add(fun(toUpperCase), "toUpperCase"); + m->add(fun(includes), "includes"); + m->add(fun(includesChar), "includes"); + m->add(fun(trimStart), "trimStart"); + m->add(fun(trimEnd), "trimEnd"); return m; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8416adb --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,52 @@ +list(APPEND LIBS ${READLINE_LIB}) + +# Add catch tests macro +macro(ADD_CATCH_TESTS executable) + if (MSVC) + file(TO_NATIVE_PATH "${QT_LIBRARY_DIR}" QT_LIB_PATH) + set(NEWPATH "${QT_LIB_PATH};$ENV{PATH}") + else() + set(NEWPATH $ENV{PATH}) + endif() + + get_target_property(target_files ${executable} SOURCES) + + message("Files: ${target_files}") + + foreach(source ${target_files}) + if(NOT "${source}" MATCHES "/moc_.*cxx") + string(REGEX MATCH .*cpp source "${source}") + if(source) + file(READ "${source}" contents) + string(REGEX MATCHALL "TEST_CASE\\([ ]*\"[^\"]+\"" found_tests ${contents}) + foreach(hit ${found_tests}) + message("Found Test: ${hit}") + string(REGEX REPLACE "TEST_CASE\\([ ]*(\"[^\"]+\").*" "\\1" test_name ${hit}) + add_test(${test_name} "${executable}" ${test_name}) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 660 ENVIRONMENT "PATH=${NEWPATH}") + endforeach() + endif() + endif() + endforeach() +endmacro() + +# Math +add_executable(math_test math.cpp) +target_link_libraries(math_test ${LIBS}) +target_include_directories(math_test PUBLIC "${chaiscript_SOURCE_DIR}/include") +ADD_CATCH_TESTS(math_test) + +# String ID +#add_executable(string_id_test string_id.cpp) +#target_link_libraries(string_id_test ${LIBS} foonathan_string_id) +#target_include_directories(string_id_test PUBLIC +# "${chaiscript_SOURCE_DIR}/include" +# "${foonathan_string_id_SOURCE_DIR}" +#) +#ADD_CATCH_TESTS(string_id_test) + +# String Methods +add_executable(string_methods_test string_methods.cpp) +target_link_libraries(string_methods_test ${LIBS}) +target_include_directories(string_methods_test PUBLIC "${chaiscript_SOURCE_DIR}/include") +ADD_CATCH_TESTS(string_methods_test) diff --git a/tests/string_methods.cpp b/tests/string_methods.cpp index b943999..07efc3f 100644 --- a/tests/string_methods.cpp +++ b/tests/string_methods.cpp @@ -7,6 +7,7 @@ #include "../include/chaiscript/extras/string_methods.hpp" TEST_CASE( "string_methods functions work", "[string_methods]" ) { + // Create the ChaiScript environment with stdlib available. auto stdlib = chaiscript::Std_Lib::library(); chaiscript::ChaiScript chai(stdlib); @@ -19,17 +20,26 @@ TEST_CASE( "string_methods functions work", "[string_methods]" ) { CHECK(chai.eval("\"Hello World!\".replace(\"Hello\", \"Goodbye\")") == "Goodbye World!"); // replace(char, char) - CHECK(chai.eval("\"Hello World!\".replace('e', 'i')") == "Hillo World!"); + CHECK(chai.eval("\"Hello World!\".replace('l', 'r')") == "Herro Worrd!"); // trim() CHECK(chai.eval("\" Hello World! \".trim()") == "Hello World!"); + CHECK(chai.eval("\" Hello World! \".trimStart()") == "Hello World! "); + CHECK(chai.eval("\" Hello World! \".trimEnd()") == " Hello World!"); // split() CHECK(chai.eval("\"Hello,World,How,Are,You\".split(\",\")[1]") == "World"); + CHECK(chai.eval("split(\"Hello,World,How,Are,You\", \",\")[1]") == "World"); // toLowerCase() CHECK(chai.eval("\"HeLLO WoRLD!\".toLowerCase()") == "hello world!"); // toUpperCase() CHECK(chai.eval("\"Hello World!\".toUpperCase()") == "HELLO WORLD!"); + + // includes() + CHECK(chai.eval("\"Hello World!\".includes(\"orl\")") == true); + CHECK(chai.eval("\"Hello World!\".includes(\"Not Included\")") == false); + CHECK(chai.eval("\"Hello World!\".includes('l')") == true); + CHECK(chai.eval("\"Hello World!\".includes('a')") == false); }