Author SHA1 Message Date
Supan Roy 1922c8f2b4 Add additional replace overlap edge case tests (#63)
* add additional replace overlap edge case tests

* Add additional Python compatibility edge case tests
2026-06-13 16:01:32 -07:00
Danny Greenstein 266d23c9b4 Add permanent header-only and compiled library targets with CMake export support (#62)
* updating version numbers

* no need for meson options now that we are building both compiled and header only together and providing them as options

* no need for header only ci tasks, each ci task now builds and tests two different targets, regular pystring and pystring_header_only

* no need for header only ci tasks, each ci task now builds and tests two different targets, regular pystring and pystring_header_only

* updated meson and cmake build systems to have two available targets for the user to pick from compiled and header only

* adding cmake configuration file

* removing empty ci target

* adding bit in pystringConfig to prevent double definition error

* adding newline in file end for consistency
2026-06-13 15:38:34 -07:00
Danny Greenstein 381829c2c1 Match our isupper with python behavior (#55)
* tightening compatbility with python's behavior in edge cases

* a couple of more edge cases
2026-02-23 17:04:14 -08:00
Danny Greenstein f64d7e3130 enforcing min c++ 11 to match the new meson build system (#56) 2026-02-22 16:33:16 -08:00
Danny Greenstein 64a428f622 header only build option (#54)
* new header only build option

* removed extra semi colon

* remaing marcos in new header file with a PYSTRING prefix so that they don't clash with other definitions when used in different contexts
2026-02-22 15:30:28 -08:00
Zephyr Lykos a9df4d1bb3 ci: only bypass b_lundef if using clang+sanitize (#53) 2025-12-04 19:32:22 -08:00
Larry Gritz c195f66709 Install locally by default to avoid naively overwriting /usr/local (#52)
Use `-DCMAKE_INSTALL_PREFIX=...` to set a non-local install
destination.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2025-11-11 18:41:00 -08:00
Zephyr Lykos a09708a487 Add meson build system (#50)
* Add meson build system

* Sync cmake's installation layout to Makefile and Meson builds

* meson: remove cmake config target generation

this pulls in an extra dependency on the cmake binary,
CMake users can use the PkgConfig module instead.

* ci: add meson tests

* build: default to c++17

* ci: fix cmake builds

* tests: fix memory leak in tests

* ci: workaround clang sanitizer linking bug

* build: install headers into a subdir
2025-10-05 21:30:32 -07:00
Danny Greenstein 02ef1186d6 Simplify github actions CI yaml file to use stock github runners and latest versions of windows/mac/ubuntu (#48)
* simplified github actions to include just 8 tasks, 4 linux, 2 macos, 2 windows, always on the latest version of the os/runners that github gives us

* using specific sha when pointing to the actions/checkout repo instead of a tag, a tag can be moved around, but a sha can't so it's more stable
2025-06-22 19:44:53 -07:00
Danny Greenstein 962fd25b25 Merge pull request #42 from svenstaro/patch-1
Proper installation paths
2025-05-13 23:48:06 -07:00
Danny Greenstein f7cdd17584 Merge pull request #47 from tgurr/cmake-4.0
cmake: fix build with cmake 4.0.0
2025-05-13 23:37:13 -07:00
Timo Gurr e5df7dd77f cmake: fix build with cmake 4.0.0 2025-05-13 16:30:22 +02:00
Danny Greenstein c5ca4f569d Merge pull request #43 from marza-sergey/cmake-improvements
Added option to build static lib and installing header using cmake
2024-08-18 13:45:46 -07:00
Danny Greenstein 76a2024e13 Merge pull request #30 from niclasr/removestarfix
Add removeprefix and removesuffix to pystring
2023-07-22 19:37:05 -07:00
Morozov Sergey 4b8029a55c Added option to build static lib and installing header 2023-05-15 15:15:31 +09:00
Sven-Hendrik Haase 74700b2c04 Proper installation paths
It's Makefile convention to be able to install to `DESTDIR`. This is especially important for packaging on Linux distributions but might be convenient for other users as well. Also this introduces `PREFIX`, another convention.
2023-05-04 03:42:21 +02:00
Danny Greenstein 7d16bc814c Merge pull request #34 from jayvdb/readme-shield
README: Add repology shield
2022-09-26 17:28:46 -07:00
Danny Greenstein 1f56d0f745 Merge pull request #37 from imageworks/grdanny-ci-tests
adding ci.yml with cross platform tests.
2022-09-25 16:58:33 -07:00
John Vandenberg 22839311d1 README: Add repology shield 2022-06-13 12:08:27 +08:00
Niclas Rosenvik e8e34d02a6 Add tests for removeprefix and removesuffix 2022-03-04 13:39:47 +01:00
Niclas Rosenvik 5a0840a737 Add removeprefix and removesuffix to pystring
Add removeprefix and removesuffix to pystring.
Python 3.9 added removeprefix and removesuffix
methods to Pythons string class. Add them to
pystring to match the interfaces.
2022-03-04 13:21:02 +01:00
13 changed files with 2836 additions and 2054 deletions
+69 -325
View File
@@ -1,6 +1,6 @@
# borrowed this from:
# https://raw.githubusercontent.com/AcademySoftwareFoundation/Imath/main/.github/workflows/ci_workflow.yml
# with some slight simplification
# simple ci config the idea is to use the latest ubuntu, macos, and windows that github runners give us
# test a few different compilers (gcc/clang) in debug and release
# and run our tests via ctest
name: CI
@@ -14,219 +14,73 @@ on:
- '**.md'
jobs:
# Linux jobs run in Docker containers, so the latest OS version is OK. macOS
# and Windows jobs need to be locked to specific virtual environment
# versions to mitigate issues from OS updates, and will require maintenance
# as OS versions are retired.
#
# GH Actions (Free plan) supports 20 concurrent jobs, with 5 concurrent macOS
# jobs. This workflow tries to utilize (but not exceed) that budget to
# promote timely CI.
# ---------------------------------------------------------------------------
# Linux
# ---------------------------------------------------------------------------
# TODO: Add ARM build. Add sanitize build.
linux:
name: 'Linux CentOS 7 VFX CY${{ matrix.vfx-cy }}
<${{ matrix.compiler-desc }} ,
config=${{ matrix.build-type }},
shared=${{ matrix.build-shared }},
cxx=${{ matrix.cxx-standard }}>'
# GH-hosted VM. The build runs in CentOS 7 'container' defined below.
name: '${{ matrix.os }} / ${{ matrix.build-type }} / ${{ matrix.compiler-desc }} '
runs-on: ubuntu-latest
container:
# DockerHub: https://hub.docker.com/u/aswf
# Source: https://github.com/AcademySoftwareFoundation/aswf-docker
image: aswf/ci-openexr:${{ matrix.vfx-cy }}
strategy:
matrix:
build: [1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 20, 21, 22]
build: [1, 2, 3, 4]
include:
# -------------------------------------------------------------------
# GCC, VFX CY2022
# CLANG, Release
# -------------------------------------------------------------------
# C++17, Python 3.9
- build: 1
build-type: Release
build-shared: 'ON'
cxx-standard: 17
cxx-compiler: g++
cc-compiler: gcc
compiler-desc: gcc9.3.1
vfx-cy: 2022
cxx-compiler: clang++
cxx-flags: ''
cc-compiler: clang
compiler-desc: clang
os: ubuntu-latest
# C++17, Python 3.9.7, Debug
# -------------------------------------------------------------------
# CLANG, Debug
# -------------------------------------------------------------------
- build: 2
build-type: Debug
build-shared: 'ON'
cxx-standard: 17
cxx-compiler: g++
cc-compiler: gcc
compiler-desc: gcc9.3.1
vfx-cy: 2022
cxx-compiler: clang++
cxx-flags: ''
cc-compiler: clang
compiler-desc: clang
os: ubuntu-latest
# C++17, Python 3.9.7, Static
# -------------------------------------------------------------------
# gcc, Release
# -------------------------------------------------------------------
- build: 3
build-type: Release
build-shared: 'OFF'
build-shared: 'ON'
cxx-standard: 17
cxx-compiler: g++
cxx-flags: ''
cc-compiler: gcc
compiler-desc: gcc9.3.1
vfx-cy: 2022
compiler-desc: gcc
os: ubuntu-latest
# C++14, Python 3.9.7, Static
# -------------------------------------------------------------------
# gcc, Debug
# -------------------------------------------------------------------
- build: 4
build-type: Release
build-shared: 'ON'
cxx-standard: 14
cxx-compiler: g++
cc-compiler: gcc
compiler-desc: gcc9.3.1
vfx-cy: 2022
# C++17, no Python
# - build: 5
# build-type: Release
# build-shared: 'ON'
# cxx-standard: 17
# cxx-compiler: g++
# cc-compiler: gcc
# compiler-desc: gcc9.3.1
# python: 'OFF'
# python-desc: no python
# vfx-cy: 2022
# -------------------------------------------------------------------
# GCC, VFX CY2021
# -------------------------------------------------------------------
# C++17, Python 3.7.9
- build: 9
build-type: Release
build-shared: 'ON'
cxx-standard: 17
cxx-compiler: g++
cc-compiler: gcc
compiler-desc: gcc9.3.1
python: 'ON'
vfx-cy: 2021
# -------------------------------------------------------------------
# GCC, VFX CY2020
# -------------------------------------------------------------------
# C++14, Python 3.7
- build: 10
build-type: Release
build-shared: 'ON'
cxx-standard: 14
cxx-compiler: g++
cc-compiler: gcc
compiler-desc: gcc6.3.1
vfx-cy: 2020
# -------------------------------------------------------------------
# GCC, VFX CY2019
# -------------------------------------------------------------------
# C++11, Python 2.7
- build: 11
build-type: Release
build-shared: 'ON'
cxx-standard: 11
cxx-compiler: g++
cc-compiler: gcc
compiler-desc: gcc6.3.1
vfx-cy: 2019
# -------------------------------------------------------------------
# Clang, VFX CY2022
# -------------------------------------------------------------------
# C++17, Python 3.9
- build: 12
build-type: Release
build-shared: 'ON'
cxx-standard: 17
cxx-compiler: clang++
cc-compiler: clang
compiler-desc: clang10.4
vfx-cy: 2022
# C++17, Python 3.9.7, Debug
- build: 13
build-type: Debug
build-shared: 'ON'
cxx-standard: 17
cxx-compiler: clang++
cc-compiler: clang
compiler-desc: clang10.4
vfx-cy: 2022
# C++17, Python 3.9.7, Static
- build: 14
build-type: Release
build-shared: 'OFF'
cxx-standard: 17
cxx-compiler: clang++
cc-compiler: clang
compiler-desc: clang10.4
vfx-cy: 2022
# C++14, Python 3.9.7, Static
- build: 15
build-type: Release
build-shared: 'ON'
cxx-standard: 14
cxx-compiler: clang++
cc-compiler: clang
compiler-desc: clang10.4
vfx-cy: 2022
# -------------------------------------------------------------------
# Clang, VFX CY2021
# -------------------------------------------------------------------
# C++17, Python 3.7.9
- build: 20
build-type: Release
build-shared: 'ON'
cxx-standard: 17
cxx-compiler: clang++
cc-compiler: clang
compiler-desc: clang10.4
vfx-cy: 2021
# -------------------------------------------------------------------
# Clang, VFX CY2020
# -------------------------------------------------------------------
# C++14, Python 3.7
- build: 21
build-type: Release
build-shared: 'ON'
cxx-standard: 14
cxx-compiler: clang++
cc-compiler: clang
compiler-desc: clang7.8
vfx-cy: 2020
# -------------------------------------------------------------------
# Clang, VFX CY2019
# -------------------------------------------------------------------
# C++11, Python 2.7
- build: 22
build-type: Release
build-shared: 'ON'
cxx-standard: 11
cxx-compiler: clang++
cc-compiler: clang
compiler-desc: clang7.8
vfx-cy: 2019
cxx-compiler: g++
cxx-flags: ''
cc-compiler: gcc
compiler-desc: gcc
os: ubuntu-latest
env:
CXX: ${{ matrix.cxx-compiler }}
CC: ${{ matrix.cc-compiler }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Create build directories
run: |
mkdir _install
@@ -234,7 +88,7 @@ jobs:
- name: Configure
run: |
cmake .. \
-DCMAKE_INSTALL_PREFIX=../_install \
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/_install \
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \
-DCMAKE_CXX_FLAGS=${{ matrix.cxx-flags }} \
@@ -244,37 +98,32 @@ jobs:
- name: Build
run: |
cmake --build . \
--target install \
--config ${{ matrix.build-type }}
working-directory: _build
- name: Test
run: |
ctest -C ${{ matrix.build_type }}
ctest --build-config ${{ matrix.build-type }} --verbose
working-directory: _build
# ---------------------------------------------------------------------------
# macOS
# ---------------------------------------------------------------------------
macos_no_python:
name: 'macOS 10.15
<AppleClang 11.0
config=${{ matrix.build-type }},
shared=${{ matrix.build-shared }},
cxx=${{ matrix.cxx-standard }},
docs=${{ matrix.build-docs }}>'
runs-on: macos-10.15
macos:
name: '${{ matrix.os }} / ${{ matrix.build-type }}'
runs-on: macos-latest
strategy:
matrix:
build: [1, 2, 3, 4, 5]
build: [1, 2]
include:
# C++11
# Release
- build: 1
build-type: Release
build-shared: 'ON'
build-docs: 'ON'
cxx-standard: 17
exclude-tests:
cxx-flags: ''
os: macos-latest
# Debug
- build: 2
@@ -282,34 +131,12 @@ jobs:
build-shared: 'ON'
build-docs: 'OFF'
cxx-standard: 17
exclude-tests:
cxx-flags: ''
os: macos-latest
# Static
- build: 3
build-type: Debug
build-shared: 'OFF'
build-docs: 'OFF'
cxx-standard: 17
exclude-tests:
# C++14
- build: 4
build-type: Release
build-shared: 'ON'
build-docs: 'OFF'
cxx-standard: 14
exclude-tests:
# C++11
- build: 5
build-type: Release
build-shared: 'ON'
build-docs: 'OFF'
cxx-standard: 11
exclude-tests:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Create build directories
run: |
mkdir _install
@@ -318,23 +145,21 @@ jobs:
- name: Configure
run: |
cmake ../. \
-DCMAKE_INSTALL_PREFIX=../_install \
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/_install \
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \
-DCMAKE_CXX_FLAGS=${{ matrix.cxx-flags }} \
-DCMAKE_VERBOSE_MAKEFILE:BOOL='OFF' \
-DBUILD_SHARED_LIBS=${{ matrix.build-shared }}
-DBUILD_SHARED_LIBS=${{ matrix.build-shared }} \
working-directory: _build
- name: Build
run: |
cmake --build . \
--target install \
--config ${{ matrix.build-type }} \
-- -j2
working-directory: _build
- name: Test
run: |
ctest -C ${{ matrix.build_type }}
ctest --build-config ${{ matrix.build-type }} --verbose
working-directory: _build
# ---------------------------------------------------------------------------
@@ -342,113 +167,33 @@ jobs:
# ---------------------------------------------------------------------------
windows:
name: 'Windows 2019
<${{ matrix.compiler-desc }},
config=${{ matrix.build-type }},
shared=${{ matrix.build-shared }},
cxx=${{ matrix.cxx-standard }},
docs=${{ matrix.build-docs }}>'
runs-on: windows-${{ matrix.osver }}
name: '${{ matrix.os }} / ${{ matrix.build-type }}'
runs-on: windows-latest
strategy:
matrix:
build: [1, 3, 4, 6, 7, 8]
build: [1, 2]
include:
# -------------------------------------------------------------------
# VFX CY2022 - C++17 - Windows 2019
# -------------------------------------------------------------------
# C++17, Release Shared
# Release
- build: 1
build-type: Release
build-shared: 'ON'
build-docs: 'ON'
compiler-desc: msvc16.11
cxx-standard: 17
vfx-cy: 2022
exclude-tests:
osver: 2019
cxx-flags: ''
os: windows-latest
# C++17, Debug -
## - build: 2
## build-type: Debug
## build-shared: 'ON'
## build-docs: 'OFF'
## cxx-standard: 17
## exclude-tests:
# C++17, Release Static
- build: 3
build-type: Release
build-shared: 'OFF'
build-docs: 'OFF'
compiler-desc: msvc16.11
cxx-standard: 17
vfx-cy: 2022
exclude-tests:
osver: 2019
# -------------------------------------------------------------------
# VFX CY2022 - C++17 - Windows 2022
# -------------------------------------------------------------------
# C++17, Release Shared
- build: 4
build-type: Release
# Debug
- build: 2
build-type: Debug
build-shared: 'ON'
build-docs: 'ON'
compiler-desc: msvc17.1
cxx-standard: 17
vfx-cy: 2022
exclude-tests:
osver: 2022
# C++17, Debug -
## - build: 5
## build-type: Debug
## build-shared: 'ON'
## build-docs: 'OFF'
## cxx-standard: 17
## exclude-tests:
# C++17, Release Static
- build: 6
build-type: Release
build-shared: 'OFF'
build-docs: 'OFF'
compiler-desc: msvc17.1
cxx-standard: 17
vfx-cy: 2022
exclude-tests:
osver: 2022
# -------------------------------------------------------------------
# VFX CY2020 - C++14 - Windows 2019
# -------------------------------------------------------------------
# C++14, Release Shared
- build: 7
build-type: Release
build-shared: 'ON'
build-docs: 'OFF'
compiler-desc: msvc16.11
cxx-standard: 14
vfx-cy: 2020
exclude-tests:
osver: 2019
# -------------------------------------------------------------------
# VFX CY2019 - C++11 - Windows 2019
# -------------------------------------------------------------------
# C++11, Release Shared
- build: 8
build-type: Release
build-shared: 'ON'
build-docs: 'OFF'
compiler-desc: msvc16.11
cxx-standard: 11
exclude-tests:
osver: 2019
cxx-flags: ''
os: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Create build directories
run: |
mkdir _install
@@ -458,19 +203,18 @@ jobs:
# the windows build needs the -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS to work
run: |
cmake ../. \
-DCMAKE_INSTALL_PREFIX=../_install \
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/_install \
-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS='ON'\
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \
-DCMAKE_CXX_FLAGS=${{ matrix.cxx-flags }} \
-DCMAKE_VERBOSE_MAKEFILE:BOOL='OFF' \
-DBUILD_SHARED_LIBS=${{ matrix.build-shared }}
-DBUILD_SHARED_LIBS=${{ matrix.build-shared }} \
shell: bash
working-directory: _build
- name: Build
run: |
cmake --build . \
--target install \
--config ${{ matrix.build-type }}
shell: bash
working-directory: _build
@@ -478,4 +222,4 @@ jobs:
run: |
ctest -C ${{ matrix.build-type }}
shell: bash
working-directory: _build
working-directory: _build
+145
View File
@@ -0,0 +1,145 @@
name: Meson
on:
pull_request:
push:
jobs:
meson-build-and-tests:
runs-on: ${{ matrix.platform }}
name: ${{ matrix.platform }}, ${{ matrix.mode.name }} ${{ matrix.flavor }}
strategy:
fail-fast: false
matrix:
flavor:
- debug
- release
mode:
- name: default
extra_envs: {}
# Alternative compiler setups
- name: gcc
extra_envs:
CC: gcc
CXX: g++
- name: clang
extra_envs:
CC: clang
CXX: clang++
- name: sanitize
args: >-
"-Db_sanitize=address,undefined"
extra_envs: {}
# This is for MSVC, which only supports AddressSanitizer.
# https://learn.microsoft.com/en-us/cpp/sanitizers/
- name: sanitize+asanonly
args: -Db_sanitize=address
extra_envs:
ASAN_OPTIONS: report_globals=0:halt_on_error=1:abort_on_error=1:print_summary=1
- name: clang+sanitize
args: >-
-Db_lundef=false "-Db_sanitize=address,undefined"
extra_envs:
CC: clang
CXX: clang++
# default clang on GitHub hosted runners is from MSYS2.
# Use Visual Studio supplied clang-cl instead.
- name: clang-cl+sanitize
args: >-
"-Db_sanitize=address,undefined"
extra_envs:
CC: clang-cl
CXX: clang-cl
platform:
- ubuntu-22.04
- windows-2022
- macos-latest
exclude:
# clang-cl only makes sense on windows.
- platform: ubuntu-22.04
mode:
name: clang-cl+sanitize
- platform: macos-latest
mode:
name: clang-cl+sanitize
# Use clang-cl instead of MSYS2 clang.
#
# we already tested clang+sanitize on linux,
# if this doesn't work, it should be an issue for MSYS2 team to consider.
- platform: windows-2022
mode:
name: clang
- platform: windows-2022
mode:
name: clang+sanitize
# MSVC-only sanitizers
- platform: ubuntu-22.04
mode:
name: sanitize+asanonly
- platform: macos-latest
mode:
name: sanitize+asanonly
- platform: windows-2022
mode:
name: sanitize
# clang is the default on macos
# also gcc is an alias to clang
- platform: macos-latest
mode:
name: clang
- platform: macos-latest
mode:
name: gcc
# gcc is the default on linux
- platform: ubuntu-22.04
mode:
name: gcc
# only run sanitizer tests on linux
#
# gcc/clang's codegen shouldn't massively change across platforms,
# and linux supports most of the sanitizers.
- platform: macos-latest
mode:
name: clang+sanitize
- platform: macos-latest
mode:
name: sanitize
steps:
- name: Setup meson
run: |
pipx install meson ninja
- name: Checkout
uses: actions/checkout@v4
- name: Activate MSVC and Configure
if: ${{ matrix.platform == 'windows-2022' }}
env: ${{ matrix.mode.extra_envs }}
run: |
meson setup build-${{ matrix.flavor }} --buildtype=${{ matrix.flavor }} -Ddefault_library=static ${{ matrix.mode.args }} --vsenv
- name: Configuring
if: ${{ matrix.platform != 'windows-2022' }}
env: ${{ matrix.mode.extra_envs }}
run: |
meson setup build-${{ matrix.flavor }} --buildtype=${{ matrix.flavor }} ${{ matrix.mode.args }}
- name: Building
run: |
meson compile -C build-${{ matrix.flavor }}
- name: Running tests
env: ${{ matrix.mode.extra_envs }}
run: |
meson test -C build-${{ matrix.flavor }} --timeout-multiplier 0
- uses: actions/upload-artifact@v4
if: failure()
with:
name: ${{ matrix.platform }}-${{ matrix.mode.name }}-${{ matrix.flavor }}-logs
path: build-${{ matrix.flavor }}/meson-logs
+93 -12
View File
@@ -1,22 +1,103 @@
cmake_minimum_required(VERSION 3.2)
project(pystring CXX)
cmake_minimum_required(VERSION 3.10)
project(pystring LANGUAGES CXX VERSION 1.2.0)
set(BUILD_SHARED_LIBS YES)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
option (BUILD_SHARED_LIBS "Build shared libraries (set to OFF to build static libs)" ON)
# If the user hasn't configured cmake with an explicit
# -DCMAKE_INSTALL_PREFIX=..., then set it to safely install into ./dist, to
# help prevent the user from accidentally writing over /usr/local or whatever.
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT
AND PROJECT_IS_TOP_LEVEL)
set (CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/dist" CACHE PATH
"Installation location" FORCE)
endif()
message (STATUS "Installation path will be ${CMAKE_INSTALL_PREFIX}")
include(GNUInstallDirs)
# --- Compiled library target: pystring::pystring ---
add_library(pystring
pystring.cpp
pystring.h
)
add_executable (pystring_test test.cpp)
TARGET_LINK_LIBRARIES (pystring_test pystring)
set_target_properties(pystring PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
target_include_directories(pystring PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
install(TARGETS pystring
EXPORT pystringTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
# --- Header-only target: pystring::pystring_header_only ---
add_library(pystring_header_only INTERFACE)
target_compile_definitions(pystring_header_only INTERFACE PYSTRING_HEADER_ONLY)
target_include_directories(pystring_header_only INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
install(TARGETS pystring_header_only
EXPORT pystringTargets
)
install(FILES pystring.h pystring_impl.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
)
# --- Export & package config ---
install(EXPORT pystringTargets
FILE pystringTargets.cmake
NAMESPACE pystring::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/pystring
)
include(CMakePackageConfigHelpers)
configure_package_config_file(
cmake/pystringConfig.cmake.in
pystringConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/pystring
)
write_basic_package_version_file(
pystringConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/pystringConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/pystringConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/pystring
)
# --- Tests ---
add_executable(pystring_test test.cpp)
target_link_libraries(pystring_test pystring)
add_executable(pystring_test_header_only test.cpp)
target_link_libraries(pystring_test_header_only pystring_header_only)
# Compile-time check that PYSTRING_HEADER_ONLY propagates correctly
add_executable(pystring_test_header_only_define test_header_only_define.cpp)
target_link_libraries(pystring_test_header_only_define pystring_header_only)
enable_testing()
add_test(NAME PyStringTest COMMAND pystring_test)
include(GNUInstallDirs)
install(TARGETS pystring
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
add_test(NAME PyStringTestHeaderOnly COMMAND pystring_test_header_only)
+5 -2
View File
@@ -1,5 +1,7 @@
LIBTOOL ?= libtool
LIBDIR ?= /usr/lib
PREFIX ?= /usr
INCLUDEDIR ?= ${PREFIX}/include/pystring
LIBDIR ?= ${PREFIX}/lib
CXX ?= g++
CXXFLAGS ?= -g -O3 -Wall -Wextra -Wshadow -Wconversion -Wcast-qual -Wformat=2
@@ -12,7 +14,8 @@ libpystring.la: pystring.lo
$(LIBTOOL) --mode=link --tag=CXX $(CXX) -o $@ $< -rpath $(LIBDIR)
install: libpystring.la
$(LIBTOOL) --mode=install install -c $< $(LIBDIR)/$<
$(LIBTOOL) --mode=install install -Dm755 $< $(DESTDIR)$(LIBDIR)/$<
$(LIBTOOL) --mode=install install -Dm644 pystring.h $(DESTDIR)$(INCLUDEDIR)/pystring.h
clean:
$(RM) -fr pystring.lo pystring.o libpystring.la .libs
+2
View File
@@ -1,3 +1,5 @@
[![Repology](https://img.shields.io/repology/repositories/pystring)](https://repology.org/project/pystring/versions)
Pystring is a collection of C++ functions which match the interface and
behavior of python's string class methods using std::string. Implemented in
C++, it does not require or make use of a python interpreter. It provides
+8
View File
@@ -0,0 +1,8 @@
@PACKAGE_INIT@
# Prevent double-definition errors
if(NOT TARGET pystring::pystring AND NOT TARGET pystring::pystring_header_only)
include("${CMAKE_CURRENT_LIST_DIR}/pystringTargets.cmake")
endif()
check_required_components(pystring)
+75
View File
@@ -0,0 +1,75 @@
project(
'pystring',
'cpp',
version: '1.2.0',
license: 'BSD-3-Clause',
license_files: 'LICENSE',
meson_version: '>=1.3',
default_options: ['cpp_std=c++17,c++11', 'warning_level=3'],
)
inc = include_directories('.')
hdrs = files('pystring.h')
# --- Compiled library target ---
pystring_lib = library(
'pystring',
files('pystring.cpp'),
implicit_include_directories: false,
include_directories: inc,
version: meson.project_version(),
install: true,
)
pystring_dep = declare_dependency(
link_with: pystring_lib,
include_directories: inc,
)
install_headers(hdrs, subdir: 'pystring')
pkgconfig = import('pkgconfig')
pkgconfig.generate(
pystring_lib,
description: 'C++ functions matching the interface and behavior of python string methods with std::string',
)
# --- Header-only target ---
pystring_header_only_dep = declare_dependency(
include_directories: inc,
compile_args: ['-DPYSTRING_HEADER_ONLY'],
)
install_headers(hdrs, files('pystring_impl.h'), subdir: 'pystring')
# --- Override default dependency ---
meson.override_dependency('pystring', pystring_dep)
# --- Tests ---
test(
'PyStringTest',
executable(
'pystring_test',
'test.cpp',
dependencies: pystring_dep,
build_by_default: false,
),
)
test(
'PyStringTestHeaderOnly',
executable(
'pystring_test_header_only',
'test.cpp',
dependencies: pystring_header_only_dep,
build_by_default: false,
),
)
# Compile-time check that PYSTRING_HEADER_ONLY propagates correctly
executable(
'pystring_test_header_only_define',
'test_header_only_define.cpp',
dependencies: pystring_header_only_dep,
build_by_default: true,
)
+4 -1640
View File
File diff suppressed because it is too large Load Diff
+89 -67
View File
@@ -6,6 +6,12 @@
#ifndef INCLUDED_PYSTRING_H
#define INCLUDED_PYSTRING_H
#ifdef PYSTRING_HEADER_ONLY
#define PYSTRING_INLINE inline
#else
#define PYSTRING_INLINE
#endif
#include <string>
#include <vector>
@@ -32,102 +38,102 @@ namespace pystring
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string with only its first character capitalized.
///
std::string capitalize( const std::string & str );
PYSTRING_INLINE std::string capitalize( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return centered in a string of length width. Padding is done using spaces.
///
std::string center( const std::string & str, int width );
PYSTRING_INLINE std::string center( const std::string & str, int width );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return the number of occurrences of substring sub in string S[start:end]. Optional
/// arguments start and end are interpreted as in slice notation.
///
int count( const std::string & str, const std::string & substr, int start = 0, int end = MAX_32BIT_INT);
PYSTRING_INLINE int count( const std::string & str, const std::string & substr, int start = 0, int end = MAX_32BIT_INT);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return True if the string ends with the specified suffix, otherwise return False. With
/// optional start, test beginning at that position. With optional end, stop comparing at that position.
///
bool endswith( const std::string & str, const std::string & suffix, int start = 0, int end = MAX_32BIT_INT );
PYSTRING_INLINE bool endswith( const std::string & str, const std::string & suffix, int start = 0, int end = MAX_32BIT_INT );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string where all tab characters are expanded using spaces. If tabsize
/// is not given, a tab size of 8 characters is assumed.
///
std::string expandtabs( const std::string & str, int tabsize = 8);
PYSTRING_INLINE std::string expandtabs( const std::string & str, int tabsize = 8);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return the lowest index in the string where substring sub is found, such that sub is
/// contained in the range [start, end). Optional arguments start and end are interpreted as
/// in slice notation. Return -1 if sub is not found.
///
int find( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
PYSTRING_INLINE int find( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Synonym of find right now. Python version throws exceptions. This one currently doesn't
///
int index( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
PYSTRING_INLINE int index( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return true if all characters in the string are alphanumeric and there is at least one
/// character, false otherwise.
///
bool isalnum( const std::string & str );
PYSTRING_INLINE bool isalnum( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return true if all characters in the string are alphabetic and there is at least one
/// character, false otherwise
///
bool isalpha( const std::string & str );
PYSTRING_INLINE bool isalpha( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return true if all characters in the string are digits and there is at least one
/// character, false otherwise.
///
bool isdigit( const std::string & str );
PYSTRING_INLINE bool isdigit( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return true if all cased characters in the string are lowercase and there is at least one
/// cased character, false otherwise.
///
bool islower( const std::string & str );
PYSTRING_INLINE bool islower( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return true if there are only whitespace characters in the string and there is at least
/// one character, false otherwise.
///
bool isspace( const std::string & str );
PYSTRING_INLINE bool isspace( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return true if the string is a titlecased string and there is at least one character,
/// i.e. uppercase characters may only follow uncased characters and lowercase characters only
/// cased ones. Return false otherwise.
///
bool istitle( const std::string & str );
PYSTRING_INLINE bool istitle( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return true if all cased characters in the string are uppercase and there is at least one
/// cased character, false otherwise.
///
bool isupper( const std::string & str );
PYSTRING_INLINE bool isupper( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a string which is the concatenation of the strings in the sequence seq.
/// The separator between elements is the str argument
///
std::string join( const std::string & str, const std::vector< std::string > & seq );
PYSTRING_INLINE std::string join( const std::string & str, const std::vector< std::string > & seq );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return the string left justified in a string of length width. Padding is done using
/// spaces. The original string is returned if width is less than str.size().
///
std::string ljust( const std::string & str, int width );
PYSTRING_INLINE std::string ljust( const std::string & str, int width );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string converted to lowercase.
///
std::string lower( const std::string & str );
PYSTRING_INLINE std::string lower( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string with leading characters removed. If chars is omitted or None,
@@ -135,13 +141,13 @@ namespace pystring
/// characters in the string will be stripped from the beginning of the string this method
/// is called on (argument "str" ).
///
std::string lstrip( const std::string & str, const std::string & chars = "" );
PYSTRING_INLINE std::string lstrip( const std::string & str, const std::string & chars = "" );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string, concatenated N times, together.
/// Corresponds to the __mul__ operator.
///
std::string mul( const std::string & str, int n);
PYSTRING_INLINE std::string mul( const std::string & str, int n);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Split the string around first occurance of sep.
@@ -149,7 +155,7 @@ namespace pystring
/// be the text before sep, sep itself, and the remaining text. If sep is
/// not found, the original string will be returned with two empty strings.
///
void partition( const std::string & str, const std::string & sep, std::vector< std::string > & result );
PYSTRING_INLINE void partition( const std::string & str, const std::string & sep, std::vector< std::string > & result );
inline std::vector< std::string > partition( const std::string & str, const std::string & sep )
{
std::vector< std::string > result;
@@ -157,30 +163,42 @@ namespace pystring
return result;
}
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief If str starts with prefix return a copy of the string with prefix at the start
/// removed otherwise return an unmodified copy of the string.
///
PYSTRING_INLINE std::string removeprefix( const std::string & str, const std::string & prefix );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief If str ends with suffix return a copy of the string with suffix at the end removed
/// otherwise return an unmodified copy of the string.
///
PYSTRING_INLINE std::string removesuffix( const std::string & str, const std::string & suffix );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string with all occurrences of substring old replaced by new. If
/// the optional argument count is given, only the first count occurrences are replaced.
///
std::string replace( const std::string & str, const std::string & oldstr, const std::string & newstr, int count = -1);
PYSTRING_INLINE std::string replace( const std::string & str, const std::string & oldstr, const std::string & newstr, int count = -1);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return the highest index in the string where substring sub is found, such that sub is
/// contained within s[start,end]. Optional arguments start and end are interpreted as in
/// slice notation. Return -1 on failure.
///
int rfind( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
PYSTRING_INLINE int rfind( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Currently a synonym of rfind. The python version raises exceptions. This one currently
/// does not
///
int rindex( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
PYSTRING_INLINE int rindex( const std::string & str, const std::string & sub, int start = 0, int end = MAX_32BIT_INT );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return the string right justified in a string of length width. Padding is done using
/// spaces. The original string is returned if width is less than str.size().
///
std::string rjust( const std::string & str, int width);
PYSTRING_INLINE std::string rjust( const std::string & str, int width);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Split the string around last occurance of sep.
@@ -188,7 +206,7 @@ namespace pystring
/// be the text before sep, sep itself, and the remaining text. If sep is
/// not found, the original string will be returned with two empty strings.
///
void rpartition( const std::string & str, const std::string & sep, std::vector< std::string > & result );
PYSTRING_INLINE void rpartition( const std::string & str, const std::string & sep, std::vector< std::string > & result );
inline std::vector< std::string > rpartition ( const std::string & str, const std::string & sep )
{
std::vector< std::string > result;
@@ -201,14 +219,14 @@ namespace pystring
/// characters are removed. If not "", the characters in the string will be stripped from the
/// end of the string this method is called on.
///
std::string rstrip( const std::string & str, const std::string & chars = "" );
PYSTRING_INLINE std::string rstrip( const std::string & str, const std::string & chars = "" );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Fills the "result" list with the words in the string, using sep as the delimiter string.
/// If maxsplit is > -1, at most maxsplit splits are done. If sep is "",
/// any whitespace string is a separator.
///
void split( const std::string & str, std::vector< std::string > & result, const std::string & sep = "", int maxsplit = -1);
PYSTRING_INLINE void split( const std::string & str, std::vector< std::string > & result, const std::string & sep = "", int maxsplit = -1);
inline std::vector< std::string > split( const std::string & str, const std::string & sep = "", int maxsplit = -1)
{
std::vector< std::string > result;
@@ -223,7 +241,7 @@ namespace pystring
/// If maxsplit is > -1, at most maxsplit splits are done. If sep is "",
/// any whitespace string is a separator.
///
void rsplit( const std::string & str, std::vector< std::string > & result, const std::string & sep = "", int maxsplit = -1);
PYSTRING_INLINE void rsplit( const std::string & str, std::vector< std::string > & result, const std::string & sep = "", int maxsplit = -1);
inline std::vector< std::string > rsplit( const std::string & str, const std::string & sep = "", int maxsplit = -1)
{
std::vector< std::string > result;
@@ -235,7 +253,7 @@ namespace pystring
/// @brief Return a list of the lines in the string, breaking at line boundaries. Line breaks
/// are not included in the resulting list unless keepends is given and true.
///
void splitlines( const std::string & str, std::vector< std::string > & result, bool keepends = false );
PYSTRING_INLINE void splitlines( const std::string & str, std::vector< std::string > & result, bool keepends = false );
inline std::vector< std::string > splitlines( const std::string & str, bool keepends = false )
{
std::vector< std::string > result;
@@ -248,48 +266,48 @@ namespace pystring
/// test string beginning at that position. With optional end, stop comparing string at that
/// position
///
bool startswith( const std::string & str, const std::string & prefix, int start = 0, int end = MAX_32BIT_INT );
PYSTRING_INLINE bool startswith( const std::string & str, const std::string & prefix, int start = 0, int end = MAX_32BIT_INT );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string with leading and trailing characters removed. If chars is "",
/// whitespace characters are removed. If given not "", the characters in the string will be
/// stripped from the both ends of the string this method is called on.
///
std::string strip( const std::string & str, const std::string & chars = "" );
PYSTRING_INLINE std::string strip( const std::string & str, const std::string & chars = "" );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string with uppercase characters converted to lowercase and vice versa.
///
std::string swapcase( const std::string & str );
PYSTRING_INLINE std::string swapcase( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a titlecased version of the string: words start with uppercase characters,
/// all remaining cased characters are lowercase.
///
std::string title( const std::string & str );
PYSTRING_INLINE std::string title( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string where all characters occurring in the optional argument
/// deletechars are removed, and the remaining characters have been mapped through the given
/// translation table, which must be a string of length 256.
///
std::string translate( const std::string & str, const std::string & table, const std::string & deletechars = "");
PYSTRING_INLINE std::string translate( const std::string & str, const std::string & table, const std::string & deletechars = "");
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a copy of the string converted to uppercase.
///
std::string upper( const std::string & str );
PYSTRING_INLINE std::string upper( const std::string & str );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return the numeric string left filled with zeros in a string of length width. The original
/// string is returned if width is less than str.size().
///
std::string zfill( const std::string & str, int width );
PYSTRING_INLINE std::string zfill( const std::string & str, int width );
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief function matching python's slice functionality.
///
std::string slice( const std::string & str, int start = 0, int end = MAX_32BIT_INT);
PYSTRING_INLINE std::string slice( const std::string & str, int start = 0, int end = MAX_32BIT_INT);
///
/// @ }
@@ -327,26 +345,26 @@ namespace path
/// program; where basename for '/foo/bar/' returns 'bar', the basename() function returns an
/// empty string ('').
std::string basename(const std::string & path);
std::string basename_nt(const std::string & path);
std::string basename_posix(const std::string & path);
PYSTRING_INLINE std::string basename(const std::string & path);
PYSTRING_INLINE std::string basename_nt(const std::string & path);
PYSTRING_INLINE std::string basename_posix(const std::string & path);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return the directory name of pathname path. This is the first half of the pair
/// returned by split(path).
std::string dirname(const std::string & path);
std::string dirname_nt(const std::string & path);
std::string dirname_posix(const std::string & path);
PYSTRING_INLINE std::string dirname(const std::string & path);
PYSTRING_INLINE std::string dirname_nt(const std::string & path);
PYSTRING_INLINE std::string dirname_posix(const std::string & path);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return True if path is an absolute pathname. On Unix, that means it begins with a
/// slash, on Windows that it begins with a (back)slash after chopping off a potential drive
/// letter.
bool isabs(const std::string & path);
bool isabs_nt(const std::string & path);
bool isabs_posix(const std::string & s);
PYSTRING_INLINE bool isabs(const std::string & path);
PYSTRING_INLINE bool isabs_nt(const std::string & path);
PYSTRING_INLINE bool isabs_posix(const std::string & s);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Return a normalized absolutized version of the pathname path.
@@ -354,9 +372,9 @@ namespace path
/// NOTE: This differs from the interface of the python equivalent in that it requires you
/// to pass in the current working directory as an argument.
std::string abspath(const std::string & path, const std::string & cwd);
std::string abspath_nt(const std::string & path, const std::string & cwd);
std::string abspath_posix(const std::string & path, const std::string & cwd);
PYSTRING_INLINE std::string abspath(const std::string & path, const std::string & cwd);
PYSTRING_INLINE std::string abspath_nt(const std::string & path, const std::string & cwd);
PYSTRING_INLINE std::string abspath_posix(const std::string & path, const std::string & cwd);
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -369,13 +387,13 @@ namespace path
/// current directory on drive C: (c:foo), not c:\foo.
/// This dispatches based on the compilation OS
std::string join(const std::string & path1, const std::string & path2);
std::string join_nt(const std::string & path1, const std::string & path2);
std::string join_posix(const std::string & path1, const std::string & path2);
PYSTRING_INLINE std::string join(const std::string & path1, const std::string & path2);
PYSTRING_INLINE std::string join_nt(const std::string & path1, const std::string & path2);
PYSTRING_INLINE std::string join_posix(const std::string & path1, const std::string & path2);
std::string join(const std::vector< std::string > & paths);
std::string join_nt(const std::vector< std::string > & paths);
std::string join_posix(const std::vector< std::string > & paths);
PYSTRING_INLINE std::string join(const std::vector< std::string > & paths);
PYSTRING_INLINE std::string join_nt(const std::vector< std::string > & paths);
PYSTRING_INLINE std::string join_posix(const std::vector< std::string > & paths);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Normalize a pathname. This collapses redundant separators and up-level references
@@ -384,9 +402,9 @@ namespace path
/// It should be understood that this may change the meaning of the path if it contains
/// symbolic links!
std::string normpath(const std::string & path);
std::string normpath_nt(const std::string & path);
std::string normpath_posix(const std::string & path);
PYSTRING_INLINE std::string normpath(const std::string & path);
PYSTRING_INLINE std::string normpath_nt(const std::string & path);
PYSTRING_INLINE std::string normpath_posix(const std::string & path);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Split the pathname path into a pair, (head, tail) where tail is the last pathname
@@ -397,9 +415,9 @@ namespace path
/// join(head, tail) returns a path to the same location as path (but the strings may
/// differ).
void split(std::string & head, std::string & tail, const std::string & path);
void split_nt(std::string & head, std::string & tail, const std::string & path);
void split_posix(std::string & head, std::string & tail, const std::string & path);
PYSTRING_INLINE void split(std::string & head, std::string & tail, const std::string & path);
PYSTRING_INLINE void split_nt(std::string & head, std::string & tail, const std::string & path);
PYSTRING_INLINE void split_posix(std::string & head, std::string & tail, const std::string & path);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Split the pathname path into a pair (drive, tail) where drive is either a drive
@@ -407,18 +425,18 @@ namespace path
/// drive will always be the empty string. In all cases, drive + tail will be the same as
/// path.
void splitdrive(std::string & drivespec, std::string & pathspec, const std::string & path);
void splitdrive_nt(std::string & drivespec, std::string & pathspec, const std::string & p);
void splitdrive_posix(std::string & drivespec, std::string & pathspec, const std::string & path);
PYSTRING_INLINE void splitdrive(std::string & drivespec, std::string & pathspec, const std::string & path);
PYSTRING_INLINE void splitdrive_nt(std::string & drivespec, std::string & pathspec, const std::string & p);
PYSTRING_INLINE void splitdrive_posix(std::string & drivespec, std::string & pathspec, const std::string & path);
//////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Split the pathname path into a pair (root, ext) such that root + ext == path, and
/// ext is empty or begins with a period and contains at most one period. Leading periods on
/// the basename are ignored; splitext('.cshrc') returns ('.cshrc', '').
void splitext(std::string & root, std::string & ext, const std::string & path);
void splitext_nt(std::string & root, std::string & ext, const std::string & path);
void splitext_posix(std::string & root, std::string & ext, const std::string & path);
PYSTRING_INLINE void splitext(std::string & root, std::string & ext, const std::string & path);
PYSTRING_INLINE void splitext_nt(std::string & root, std::string & ext, const std::string & path);
PYSTRING_INLINE void splitext_posix(std::string & root, std::string & ext, const std::string & path);
///
/// @ }
@@ -428,4 +446,8 @@ namespace path
} // namespace pystring
#if PYSTRING_HEADER_ONLY
#include "pystring_impl.h"
#endif
#endif
+1693
View File
File diff suppressed because it is too large Load Diff
+640
View File
@@ -7,6 +7,25 @@
#include "pystring.h"
#include "unittest.h"
// Helper wrappers (for pystring functions that don't have direct return values)
namespace {
static std::vector<std::string> pystring_split(const std::string& s, int maxsplit)
{ std::vector<std::string> r; pystring::split(s, r, "", maxsplit); return r; }
static std::vector<std::string> pystring_split(const std::string& s, const std::string& sep, int maxsplit)
{ std::vector<std::string> r; pystring::split(s, r, sep, maxsplit); return r; }
static std::vector<std::string> pystring_rsplit(const std::string& s, int maxsplit)
{ std::vector<std::string> r; pystring::rsplit(s, r, "", maxsplit); return r; }
static std::vector<std::string> pystring_rsplit(const std::string& s, const std::string& sep, int maxsplit)
{ std::vector<std::string> r; pystring::rsplit(s, r, sep, maxsplit); return r; }
static std::vector<std::string> pystring_partition(const std::string& s, const std::string& sep)
{ std::vector<std::string> r; pystring::partition(s, sep, r); return r; }
static std::vector<std::string> pystring_rpartition(const std::string& s, const std::string& sep)
{ std::vector<std::string> r; pystring::rpartition(s, sep, r); return r; }
static std::vector<std::string> pystring_splitlines(const std::string& s, bool keepends)
{ std::vector<std::string> r; pystring::splitlines(s, r, keepends); return r; }
} // namespace
PYSTRING_TEST_APP(PyStringUnitTests)
PYSTRING_ADD_TEST(pystring, endswith)
@@ -33,6 +52,17 @@ PYSTRING_ADD_TEST(pystring, endswith)
PYSTRING_CHECK_EQUAL(pystring::endswith("abcdef", "cdef", -10), true);
}
PYSTRING_ADD_TEST(pystring, isupper)
{
PYSTRING_CHECK_EQUAL(pystring::isupper("ABC"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("abc"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("ABc"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("AB-C"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("HELLO 123"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("123"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("HELLO!"), true);
}
PYSTRING_ADD_TEST(pystring, find)
{
PYSTRING_CHECK_EQUAL(pystring::find("", ""), 0);
@@ -98,6 +128,18 @@ PYSTRING_ADD_TEST(pystring, rfind)
PYSTRING_CHECK_EQUAL(pystring::rfind("abcabcabc", "abc", 6, 8), -1);
}
PYSTRING_ADD_TEST(pystring, removeprefix)
{
PYSTRING_CHECK_EQUAL(pystring::removeprefix("abcdef", "abc"), "def");
PYSTRING_CHECK_EQUAL(pystring::removeprefix("abcdef", "bcd"), "abcdef");
}
PYSTRING_ADD_TEST(pystring, removesuffix)
{
PYSTRING_CHECK_EQUAL(pystring::removesuffix("abcdef", "def"), "abc");
PYSTRING_CHECK_EQUAL(pystring::removesuffix("abcdef", "cde"), "abcdef");
}
PYSTRING_ADD_TEST(pystring, replace)
{
PYSTRING_CHECK_EQUAL(pystring::replace("abcdef", "foo", "bar"), "abcdef");
@@ -658,3 +700,601 @@ PYSTRING_ADD_TEST(pystring_os_path, splitext)
splitext_nt(root, ext, "c:\\a.b.c"); PYSTRING_CHECK_EQUAL(root, "c:\\a.b"); PYSTRING_CHECK_EQUAL(ext, ".c");
splitext_nt(root, ext, "c:\\a_b.c"); PYSTRING_CHECK_EQUAL(root, "c:\\a_b"); PYSTRING_CHECK_EQUAL(ext, ".c");
}
// the python3_compat tests were auto generated from python
// to make sure that our functions exactly match the values that are being returned in python 3.11
PYSTRING_ADD_TEST(python3_compat, capitalize)
{
PYSTRING_CHECK_EQUAL(pystring::capitalize(""), "");
PYSTRING_CHECK_EQUAL(pystring::capitalize("hello"), "Hello");
PYSTRING_CHECK_EQUAL(pystring::capitalize("HELLO"), "Hello");
PYSTRING_CHECK_EQUAL(pystring::capitalize("Hello World"), "Hello world");
PYSTRING_CHECK_EQUAL(pystring::capitalize("hello123"), "Hello123");
PYSTRING_CHECK_EQUAL(pystring::capitalize("123"), "123");
PYSTRING_CHECK_EQUAL(pystring::capitalize("hElLo"), "Hello");
PYSTRING_CHECK_EQUAL(pystring::capitalize("hello world!"), "Hello world!");
}
PYSTRING_ADD_TEST(python3_compat, center)
{
PYSTRING_CHECK_EQUAL(pystring::center("hello", 10), " hello ");
PYSTRING_CHECK_EQUAL(pystring::center("hello", 3), "hello");
PYSTRING_CHECK_EQUAL(pystring::center("", 5), " ");
PYSTRING_CHECK_EQUAL(pystring::center("hello", 5), "hello");
}
PYSTRING_ADD_TEST(python3_compat, count_python3)
{
PYSTRING_CHECK_EQUAL(pystring::count("", "", 0), 1);
PYSTRING_CHECK_EQUAL(pystring::count("bob", ""), 4);
PYSTRING_CHECK_EQUAL(pystring::count("", "bob"), 0);
PYSTRING_CHECK_EQUAL(pystring::count("hello world", "o", 0), 2);
PYSTRING_CHECK_EQUAL(pystring::count("hello world", "l", 0), 3);
PYSTRING_CHECK_EQUAL(pystring::count("hello world", "xyz", 0), 0);
PYSTRING_CHECK_EQUAL(pystring::count("hello world", "", 0), 12);
PYSTRING_CHECK_EQUAL(pystring::count("aaa", "a", 0), 3);
PYSTRING_CHECK_EQUAL(pystring::count("aaa", "aa", 0), 1);
PYSTRING_CHECK_EQUAL(pystring::count("hello", "l", 2, 5), 2);
}
PYSTRING_ADD_TEST(python3_compat, endswith_python3)
{
PYSTRING_CHECK_EQUAL(pystring::endswith("hello world", "hello", 0), false);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello world", "world", 0), true);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello world", "", 0), true);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello world", "hello", 1), false);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello world", "ello", 1), false);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello", "hello world", 0), false);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello world", "world", 0, 5), false);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello world", "hello", 0, 5), true);
PYSTRING_CHECK_EQUAL(pystring::endswith("", "", 0), true);
PYSTRING_CHECK_EQUAL(pystring::endswith("hello", "hello", 0), true);
}
PYSTRING_ADD_TEST(python3_compat, expandtabs)
{
PYSTRING_CHECK_EQUAL(pystring::expandtabs("hello\tworld", 8), "hello world");
PYSTRING_CHECK_EQUAL(pystring::expandtabs("hello\tworld", 4), "hello world");
PYSTRING_CHECK_EQUAL(pystring::expandtabs("\t\t", 4), " ");
PYSTRING_CHECK_EQUAL(pystring::expandtabs("no tabs", 4), "no tabs");
PYSTRING_CHECK_EQUAL(pystring::expandtabs("a\tb\tc", 4), "a b c");
}
PYSTRING_ADD_TEST(python3_compat, find_python3)
{
PYSTRING_CHECK_EQUAL(pystring::find("hello world", "world", 0), 6);
PYSTRING_CHECK_EQUAL(pystring::find("hello world", "hello", 0), 0);
PYSTRING_CHECK_EQUAL(pystring::find("hello world", "xyz", 0), -1);
PYSTRING_CHECK_EQUAL(pystring::find("hello world", "", 0), 0);
PYSTRING_CHECK_EQUAL(pystring::find("hello world", "o", 0), 4);
PYSTRING_CHECK_EQUAL(pystring::find("hello world", "o", 5), 7);
PYSTRING_CHECK_EQUAL(pystring::find("hello world", "o", 0, 5), 4);
PYSTRING_CHECK_EQUAL(pystring::find("", "hello", 0), -1);
PYSTRING_CHECK_EQUAL(pystring::find("hello", "", 0), 0);
PYSTRING_CHECK_EQUAL(pystring::find("abcabc", "bc", 0), 1);
PYSTRING_CHECK_EQUAL(pystring::find("abcabc", "bc", 2), 4);
}
PYSTRING_ADD_TEST(python3_compat, isalnum)
{
PYSTRING_CHECK_EQUAL(pystring::isalnum(""), false);
PYSTRING_CHECK_EQUAL(pystring::isalnum("abc"), true);
PYSTRING_CHECK_EQUAL(pystring::isalnum("123"), true);
PYSTRING_CHECK_EQUAL(pystring::isalnum("abc123"), true);
PYSTRING_CHECK_EQUAL(pystring::isalnum("abc!"), false);
PYSTRING_CHECK_EQUAL(pystring::isalnum(" "), false);
PYSTRING_CHECK_EQUAL(pystring::isalnum("abc 123"), false);
}
PYSTRING_ADD_TEST(python3_compat, isalpha)
{
PYSTRING_CHECK_EQUAL(pystring::isalpha(""), false);
PYSTRING_CHECK_EQUAL(pystring::isalpha("abc"), true);
PYSTRING_CHECK_EQUAL(pystring::isalpha("ABC"), true);
PYSTRING_CHECK_EQUAL(pystring::isalpha("abcABC"), true);
PYSTRING_CHECK_EQUAL(pystring::isalpha("abc1"), false);
PYSTRING_CHECK_EQUAL(pystring::isalpha("abc!"), false);
PYSTRING_CHECK_EQUAL(pystring::isalpha(" "), false);
PYSTRING_CHECK_EQUAL(pystring::isalpha("a b"), false);
}
PYSTRING_ADD_TEST(python3_compat, isdigit)
{
PYSTRING_CHECK_EQUAL(pystring::isdigit(""), false);
PYSTRING_CHECK_EQUAL(pystring::isdigit("0"), true);
PYSTRING_CHECK_EQUAL(pystring::isdigit("123"), true);
PYSTRING_CHECK_EQUAL(pystring::isdigit("12.3"), false);
PYSTRING_CHECK_EQUAL(pystring::isdigit("12 3"), false);
PYSTRING_CHECK_EQUAL(pystring::isdigit("abc"), false);
PYSTRING_CHECK_EQUAL(pystring::isdigit("123abc"), false);
PYSTRING_CHECK_EQUAL(pystring::isdigit(" "), false);
}
PYSTRING_ADD_TEST(python3_compat, islower)
{
PYSTRING_CHECK_EQUAL(pystring::islower(""), false);
PYSTRING_CHECK_EQUAL(pystring::islower("a"), true);
PYSTRING_CHECK_EQUAL(pystring::islower("A"), false);
PYSTRING_CHECK_EQUAL(pystring::islower("abc"), true);
PYSTRING_CHECK_EQUAL(pystring::islower("ABC"), false);
PYSTRING_CHECK_EQUAL(pystring::islower("abC"), false);
PYSTRING_CHECK_EQUAL(pystring::islower("abc123"), true);
PYSTRING_CHECK_EQUAL(pystring::islower("123"), false);
PYSTRING_CHECK_EQUAL(pystring::islower("abc 123"), true);
PYSTRING_CHECK_EQUAL(pystring::islower("hello!"), true);
PYSTRING_CHECK_EQUAL(pystring::islower("HELLO!"), false);
PYSTRING_CHECK_EQUAL(pystring::islower(" "), false);
PYSTRING_CHECK_EQUAL(pystring::islower("abc\n"), true);
PYSTRING_CHECK_EQUAL(pystring::islower("!@#$"), false);
PYSTRING_CHECK_EQUAL(pystring::islower("a!b"), true);
PYSTRING_CHECK_EQUAL(pystring::islower("1a"), true);
}
PYSTRING_ADD_TEST(python3_compat, isspace)
{
PYSTRING_CHECK_EQUAL(pystring::isspace(""), false);
PYSTRING_CHECK_EQUAL(pystring::isspace(" "), true);
PYSTRING_CHECK_EQUAL(pystring::isspace(" "), true);
PYSTRING_CHECK_EQUAL(pystring::isspace("\t"), true);
PYSTRING_CHECK_EQUAL(pystring::isspace("\n"), true);
PYSTRING_CHECK_EQUAL(pystring::isspace("\r"), true);
PYSTRING_CHECK_EQUAL(pystring::isspace(" \t\n"), true);
PYSTRING_CHECK_EQUAL(pystring::isspace("a"), false);
PYSTRING_CHECK_EQUAL(pystring::isspace(" a "), false);
PYSTRING_CHECK_EQUAL(pystring::isspace(" a"), false);
}
PYSTRING_ADD_TEST(python3_compat, istitle)
{
PYSTRING_CHECK_EQUAL(pystring::istitle(""), false);
PYSTRING_CHECK_EQUAL(pystring::istitle("Title"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("Title Case"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("title"), false);
PYSTRING_CHECK_EQUAL(pystring::istitle("TITLE"), false);
PYSTRING_CHECK_EQUAL(pystring::istitle("Title123"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("A"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("a"), false);
PYSTRING_CHECK_EQUAL(pystring::istitle("Title Case Here"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("Title Case"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("123 Title"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("Already A Title"), true);
PYSTRING_CHECK_EQUAL(pystring::istitle("not A Title"), false);
}
PYSTRING_ADD_TEST(python3_compat, isupper)
{
PYSTRING_CHECK_EQUAL(pystring::isupper(""), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("A"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("a"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("ABC"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("abc"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("ABc"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("ABC123"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("123"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("ABC 123"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("HELLO!"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("hello!"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper(" "), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("ABC\n"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("ABC\tDEF"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("!@#$"), false);
PYSTRING_CHECK_EQUAL(pystring::isupper("A!B"), true);
PYSTRING_CHECK_EQUAL(pystring::isupper("1A"), true);
}
PYSTRING_ADD_TEST(python3_compat, join_python3)
{
PYSTRING_CHECK_EQUAL(pystring::join(",", {}), "");
PYSTRING_CHECK_EQUAL(pystring::join(",", {""}), "");
PYSTRING_CHECK_EQUAL(pystring::join(",", {"a"}), "a");
PYSTRING_CHECK_EQUAL(pystring::join(",", {"a", "b", "c"}), "a,b,c");
PYSTRING_CHECK_EQUAL(pystring::join("", {"a", "b", "c"}), "abc");
PYSTRING_CHECK_EQUAL(pystring::join(", ", {"hello", "world"}), "hello, world");
PYSTRING_CHECK_EQUAL(pystring::join("-", {"one", "two", "three"}), "one-two-three");
PYSTRING_CHECK_EQUAL(pystring::join("/", {"path", "to", "file"}), "path/to/file");
}
PYSTRING_ADD_TEST(python3_compat, ljust)
{
PYSTRING_CHECK_EQUAL(pystring::ljust("hello", 10), "hello ");
PYSTRING_CHECK_EQUAL(pystring::ljust("hello", 3), "hello");
PYSTRING_CHECK_EQUAL(pystring::ljust("", 5), " ");
PYSTRING_CHECK_EQUAL(pystring::ljust("hello", 5), "hello");
}
PYSTRING_ADD_TEST(python3_compat, lower)
{
PYSTRING_CHECK_EQUAL(pystring::lower(""), "");
PYSTRING_CHECK_EQUAL(pystring::lower("hello"), "hello");
PYSTRING_CHECK_EQUAL(pystring::lower("HELLO"), "hello");
PYSTRING_CHECK_EQUAL(pystring::lower("Hello World"), "hello world");
PYSTRING_CHECK_EQUAL(pystring::lower("hello123"), "hello123");
PYSTRING_CHECK_EQUAL(pystring::lower("123"), "123");
PYSTRING_CHECK_EQUAL(pystring::lower("hElLo"), "hello");
PYSTRING_CHECK_EQUAL(pystring::lower("hello world!"), "hello world!");
}
PYSTRING_ADD_TEST(python3_compat, lstrip)
{
PYSTRING_CHECK_EQUAL(pystring::lstrip(""), "");
PYSTRING_CHECK_EQUAL(pystring::lstrip(" hello "), "hello ");
PYSTRING_CHECK_EQUAL(pystring::lstrip(" hello ", " "), "hello ");
PYSTRING_CHECK_EQUAL(pystring::lstrip("xxhelloxx", "x"), "helloxx");
PYSTRING_CHECK_EQUAL(pystring::lstrip("\t\nhello\t\n"), "hello\t\n");
PYSTRING_CHECK_EQUAL(pystring::lstrip("hello"), "hello");
PYSTRING_CHECK_EQUAL(pystring::lstrip(" "), "");
PYSTRING_CHECK_EQUAL(pystring::lstrip("aabbcc", "ac"), "bbcc");
PYSTRING_CHECK_EQUAL(pystring::lstrip("hello world "), "hello world ");
}
PYSTRING_ADD_TEST(python3_compat, mul)
{
PYSTRING_CHECK_EQUAL(pystring::mul("ab", 3), "ababab");
PYSTRING_CHECK_EQUAL(pystring::mul("ab", 0), "");
PYSTRING_CHECK_EQUAL(pystring::mul("ab", 1), "ab");
PYSTRING_CHECK_EQUAL(pystring::mul("", 5), "");
PYSTRING_CHECK_EQUAL(pystring::mul("x", 5), "xxxxx");
}
PYSTRING_ADD_TEST(python3_compat, removeprefix_python3)
{
PYSTRING_CHECK_EQUAL(pystring::removeprefix("hello world", "hello "), "world");
PYSTRING_CHECK_EQUAL(pystring::removeprefix("hello world", "xyz"), "hello world");
PYSTRING_CHECK_EQUAL(pystring::removeprefix("hello world", ""), "hello world");
PYSTRING_CHECK_EQUAL(pystring::removeprefix("hello", "hello"), "");
PYSTRING_CHECK_EQUAL(pystring::removeprefix("", "x"), "");
}
PYSTRING_ADD_TEST(python3_compat, removesuffix_python3)
{
PYSTRING_CHECK_EQUAL(pystring::removesuffix("hello world", "hello "), "hello world");
PYSTRING_CHECK_EQUAL(pystring::removesuffix("hello world", "xyz"), "hello world");
PYSTRING_CHECK_EQUAL(pystring::removesuffix("hello world", ""), "hello world");
PYSTRING_CHECK_EQUAL(pystring::removesuffix("hello", "hello"), "");
PYSTRING_CHECK_EQUAL(pystring::removesuffix("", "x"), "");
}
PYSTRING_ADD_TEST(python3_compat, replace_python3)
{
PYSTRING_CHECK_EQUAL(pystring::replace("hello world", "world", "python", -1), "hello python");
PYSTRING_CHECK_EQUAL(pystring::replace("hello world", "o", "0", -1), "hell0 w0rld");
PYSTRING_CHECK_EQUAL(pystring::replace("hello world", "o", "0", 1), "hell0 world");
PYSTRING_CHECK_EQUAL(pystring::replace("hello world", "xyz", "abc", -1), "hello world");
PYSTRING_CHECK_EQUAL(pystring::replace("hello world", "", "_", -1), "_h_e_l_l_o_ _w_o_r_l_d_");
PYSTRING_CHECK_EQUAL(pystring::replace("aaa", "a", "bb", -1), "bbbbbb");
PYSTRING_CHECK_EQUAL(pystring::replace("aaa", "a", "bb", 2), "bbbba");
PYSTRING_CHECK_EQUAL(pystring::replace("", "a", "b", -1), "");
PYSTRING_CHECK_EQUAL(pystring::replace("hello", "hello", "", -1), "");
}
PYSTRING_ADD_TEST(python3_compat, replace_overlap)
{
PYSTRING_CHECK_EQUAL(pystring::replace("aaaaa", "aa", "b", -1), "bba");
PYSTRING_CHECK_EQUAL(pystring::replace("aaaa", "aa", "b", -1), "bb");
PYSTRING_CHECK_EQUAL(pystring::replace("aaa", "aa", "b", -1), "ba");
PYSTRING_CHECK_EQUAL(pystring::replace("abababa", "aba", "X", -1), "XbX");
PYSTRING_CHECK_EQUAL(pystring::replace("aaaaaa", "aaa", "x", -1), "xx");
PYSTRING_CHECK_EQUAL(pystring::replace("abcabcabc", "abc", "x", 2), "xxabc");
PYSTRING_CHECK_EQUAL(pystring::replace("abcabcabc", "abc", "x", 1), "xabcabc");
PYSTRING_CHECK_EQUAL(pystring::replace("aaaa", "a", "", -1), "");
PYSTRING_CHECK_EQUAL(pystring::replace("aaaa", "aa", "", -1), "");
PYSTRING_CHECK_EQUAL(pystring::replace("abc", "d", "x", -1), "abc");
PYSTRING_CHECK_EQUAL(pystring::replace("", "a", "x", -1), "");
PYSTRING_CHECK_EQUAL(pystring::replace("", "", "x", -1), "x");
PYSTRING_CHECK_EQUAL(pystring::replace("a", "", "-", -1), "-a-");
PYSTRING_CHECK_EQUAL(pystring::replace("mississippi", "issi", "X", -1), "mXssippi");
PYSTRING_CHECK_EQUAL(pystring::replace("11111", "11", "x", -1), "xx1");
}
PYSTRING_ADD_TEST(python3_compat, rfind_python3)
{
PYSTRING_CHECK_EQUAL(pystring::rfind("hello world", "world", 0), 6);
PYSTRING_CHECK_EQUAL(pystring::rfind("hello world", "hello", 0), 0);
PYSTRING_CHECK_EQUAL(pystring::rfind("hello world", "xyz", 0), -1);
PYSTRING_CHECK_EQUAL(pystring::rfind("hello world", "", 0), 11);
PYSTRING_CHECK_EQUAL(pystring::rfind("hello world", "o", 0), 7);
PYSTRING_CHECK_EQUAL(pystring::rfind("hello world", "o", 5), 7);
PYSTRING_CHECK_EQUAL(pystring::rfind("hello world", "o", 0, 5), 4);
PYSTRING_CHECK_EQUAL(pystring::rfind("", "hello", 0), -1);
PYSTRING_CHECK_EQUAL(pystring::rfind("hello", "", 0), 5);
PYSTRING_CHECK_EQUAL(pystring::rfind("abcabc", "bc", 0), 4);
PYSTRING_CHECK_EQUAL(pystring::rfind("abcabc", "bc", 2), 4);
}
PYSTRING_ADD_TEST(python3_compat, rjust)
{
PYSTRING_CHECK_EQUAL(pystring::rjust("hello", 10), " hello");
PYSTRING_CHECK_EQUAL(pystring::rjust("hello", 3), "hello");
PYSTRING_CHECK_EQUAL(pystring::rjust("", 5), " ");
PYSTRING_CHECK_EQUAL(pystring::rjust("hello", 5), "hello");
}
PYSTRING_ADD_TEST(python3_compat, rstrip)
{
PYSTRING_CHECK_EQUAL(pystring::rstrip(""), "");
PYSTRING_CHECK_EQUAL(pystring::rstrip(" hello "), " hello");
PYSTRING_CHECK_EQUAL(pystring::rstrip(" hello ", " "), " hello");
PYSTRING_CHECK_EQUAL(pystring::rstrip("xxhelloxx", "x"), "xxhello");
PYSTRING_CHECK_EQUAL(pystring::rstrip("\t\nhello\t\n"), "\t\nhello");
PYSTRING_CHECK_EQUAL(pystring::rstrip("hello"), "hello");
PYSTRING_CHECK_EQUAL(pystring::rstrip(" "), "");
PYSTRING_CHECK_EQUAL(pystring::rstrip("aabbcc", "ac"), "aabb");
PYSTRING_CHECK_EQUAL(pystring::rstrip("hello world "), "hello world");
}
PYSTRING_ADD_TEST(python3_compat, slice_python3)
{
PYSTRING_CHECK_EQUAL(pystring::slice("hello", 0, 5), "hello");
PYSTRING_CHECK_EQUAL(pystring::slice("hello", 1, 4), "ell");
PYSTRING_CHECK_EQUAL(pystring::slice("hello", 0, -1), "hell");
PYSTRING_CHECK_EQUAL(pystring::slice("hello", -3, -1), "ll");
PYSTRING_CHECK_EQUAL(pystring::slice("hello", 2, 2), "");
PYSTRING_CHECK_EQUAL(pystring::slice("hello", 3, 1), "");
PYSTRING_CHECK_EQUAL(pystring::slice("", 0, 0), "");
PYSTRING_CHECK_EQUAL(pystring::slice("hello world", 6, 11), "world");
}
PYSTRING_ADD_TEST(python3_compat, startswith_python3)
{
PYSTRING_CHECK_EQUAL(pystring::startswith("hello world", "hello", 0), true);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello world", "world", 0), false);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello world", "", 0), true);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello world", "hello", 1), false);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello world", "ello", 1), true);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello", "hello world", 0), false);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello world", "world", 0, 5), false);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello world", "hello", 0, 5), true);
PYSTRING_CHECK_EQUAL(pystring::startswith("", "", 0), true);
PYSTRING_CHECK_EQUAL(pystring::startswith("hello", "hello", 0), true);
}
PYSTRING_ADD_TEST(python3_compat, strip)
{
PYSTRING_CHECK_EQUAL(pystring::strip(""), "");
PYSTRING_CHECK_EQUAL(pystring::strip(" hello "), "hello");
PYSTRING_CHECK_EQUAL(pystring::strip(" hello ", " "), "hello");
PYSTRING_CHECK_EQUAL(pystring::strip("xxhelloxx", "x"), "hello");
PYSTRING_CHECK_EQUAL(pystring::strip("\t\nhello\t\n"), "hello");
PYSTRING_CHECK_EQUAL(pystring::strip("hello"), "hello");
PYSTRING_CHECK_EQUAL(pystring::strip(" "), "");
PYSTRING_CHECK_EQUAL(pystring::strip("aabbcc", "ac"), "bb");
PYSTRING_CHECK_EQUAL(pystring::strip("hello world "), "hello world");
}
PYSTRING_ADD_TEST(python3_compat, swapcase)
{
PYSTRING_CHECK_EQUAL(pystring::swapcase(""), "");
PYSTRING_CHECK_EQUAL(pystring::swapcase("hello"), "HELLO");
PYSTRING_CHECK_EQUAL(pystring::swapcase("HELLO"), "hello");
PYSTRING_CHECK_EQUAL(pystring::swapcase("Hello World"), "hELLO wORLD");
PYSTRING_CHECK_EQUAL(pystring::swapcase("hello123"), "HELLO123");
PYSTRING_CHECK_EQUAL(pystring::swapcase("123"), "123");
PYSTRING_CHECK_EQUAL(pystring::swapcase("hElLo"), "HeLlO");
PYSTRING_CHECK_EQUAL(pystring::swapcase("hello world!"), "HELLO WORLD!");
}
PYSTRING_ADD_TEST(python3_compat, title)
{
PYSTRING_CHECK_EQUAL(pystring::title(""), "");
PYSTRING_CHECK_EQUAL(pystring::title("hello world"), "Hello World");
PYSTRING_CHECK_EQUAL(pystring::title("HELLO WORLD"), "Hello World");
PYSTRING_CHECK_EQUAL(pystring::title("hello"), "Hello");
PYSTRING_CHECK_EQUAL(pystring::title("it's a test"), "It'S A Test");
PYSTRING_CHECK_EQUAL(pystring::title("they're bill's friends from the UK"), "They'Re Bill'S Friends From The Uk");
PYSTRING_CHECK_EQUAL(pystring::title("hello123world"), "Hello123World");
}
PYSTRING_ADD_TEST(python3_compat, upper)
{
PYSTRING_CHECK_EQUAL(pystring::upper(""), "");
PYSTRING_CHECK_EQUAL(pystring::upper("hello"), "HELLO");
PYSTRING_CHECK_EQUAL(pystring::upper("HELLO"), "HELLO");
PYSTRING_CHECK_EQUAL(pystring::upper("Hello World"), "HELLO WORLD");
PYSTRING_CHECK_EQUAL(pystring::upper("hello123"), "HELLO123");
PYSTRING_CHECK_EQUAL(pystring::upper("123"), "123");
PYSTRING_CHECK_EQUAL(pystring::upper("hElLo"), "HELLO");
PYSTRING_CHECK_EQUAL(pystring::upper("hello world!"), "HELLO WORLD!");
}
PYSTRING_ADD_TEST(python3_compat, zfill)
{
PYSTRING_CHECK_EQUAL(pystring::zfill("42", 5), "00042");
PYSTRING_CHECK_EQUAL(pystring::zfill("42", 2), "42");
PYSTRING_CHECK_EQUAL(pystring::zfill("-42", 5), "-0042");
PYSTRING_CHECK_EQUAL(pystring::zfill("+42", 5), "+0042");
PYSTRING_CHECK_EQUAL(pystring::zfill("hello", 8), "000hello");
PYSTRING_CHECK_EQUAL(pystring::zfill("", 3), "000");
}
PYSTRING_ADD_TEST(python3_compat, rpartition_python3)
{
{
std::vector<std::string> _e17 = {"hello", " ", "world"};
PYSTRING_CHECK_ASSERT((pystring_rpartition("hello world", " ")) == _e17);
}
{
std::vector<std::string> _e19 = {"", "", "hello world"};
PYSTRING_CHECK_ASSERT((pystring_rpartition("hello world", "xyz")) == _e19);
}
{
std::vector<std::string> _e21 = {"", "hello", ""};
PYSTRING_CHECK_ASSERT((pystring_rpartition("hello", "hello")) == _e21);
}
{
std::vector<std::string> _e23 = {"", "", ""};
PYSTRING_CHECK_ASSERT((pystring_rpartition("", "x")) == _e23);
}
{
std::vector<std::string> _e25 = {"aXb", "X", "c"};
PYSTRING_CHECK_ASSERT((pystring_rpartition("aXbXc", "X")) == _e25);
}
{
std::vector<std::string> _e27 = {"hello ", "world", ""};
PYSTRING_CHECK_ASSERT((pystring_rpartition("hello world", "world")) == _e27);
}
}
PYSTRING_ADD_TEST(python3_compat, rsplit_python3)
{
{
std::vector<std::string> _e12 = {"hello world", "foo"};
PYSTRING_CHECK_ASSERT((pystring_rsplit("hello world foo", " ", 1)) == _e12);
}
{
std::vector<std::string> _e13 = {"a,b", "c", "d"};
PYSTRING_CHECK_ASSERT((pystring_rsplit("a,b,c,d", ",", 2)) == _e13);
}
{
std::vector<std::string> _e14 = {" hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_rsplit(" hello world ", 1)) == _e14);
}
{
std::vector<std::string> _e15 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_rsplit("hello world", -1)) == _e15);
}
}
PYSTRING_ADD_TEST(python3_compat, split_python3)
{
{
std::vector<std::string> _e0 = {};
PYSTRING_CHECK_ASSERT((pystring_split("", -1)) == _e0);
}
{
std::vector<std::string> _e1 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_split("hello world", -1)) == _e1);
}
{
std::vector<std::string> _e2 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_split(" hello world ", -1)) == _e2);
}
{
std::vector<std::string> _e3 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_split("hello world", " ", -1)) == _e3);
}
{
std::vector<std::string> _e4 = {"hello world"};
PYSTRING_CHECK_ASSERT((pystring_split("hello world", " ", 0)) == _e4);
}
{
std::vector<std::string> _e5 = {"hello", "world foo"};
PYSTRING_CHECK_ASSERT((pystring_split("hello world foo", " ", 1)) == _e5);
}
{
std::vector<std::string> _e6 = {"a", "b", "c", "d"};
PYSTRING_CHECK_ASSERT((pystring_split("a,b,c,d", ",", -1)) == _e6);
}
{
std::vector<std::string> _e7 = {"a", "b", "c,d"};
PYSTRING_CHECK_ASSERT((pystring_split("a,b,c,d", ",", 2)) == _e7);
}
{
std::vector<std::string> _e8 = {"a", "b", "c"};
PYSTRING_CHECK_ASSERT((pystring_split("aXXbXXc", "XX", -1)) == _e8);
}
{
std::vector<std::string> _e9 = {"hello"};
PYSTRING_CHECK_ASSERT((pystring_split("hello", "x", -1)) == _e9);
}
{
std::vector<std::string> _e10 = {};
PYSTRING_CHECK_ASSERT((pystring_split(" ", -1)) == _e10);
}
{
std::vector<std::string> _e11 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_split("\thello\tworld\t", -1)) == _e11);
}
}
PYSTRING_ADD_TEST(python3_compat, splitlines)
{
{
std::vector<std::string> _e28 = {};
PYSTRING_CHECK_ASSERT((pystring_splitlines("", false)) == _e28);
}
{
std::vector<std::string> _e29 = {};
PYSTRING_CHECK_ASSERT((pystring_splitlines("", true)) == _e29);
}
{
std::vector<std::string> _e30 = {"hello"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello", false)) == _e30);
}
{
std::vector<std::string> _e31 = {"hello"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello", true)) == _e31);
}
{
std::vector<std::string> _e32 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\nworld", false)) == _e32);
}
{
std::vector<std::string> _e33 = {"hello\n", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\nworld", true)) == _e33);
}
{
std::vector<std::string> _e34 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\r\nworld", false)) == _e34);
}
{
std::vector<std::string> _e35 = {"hello\r\n", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\r\nworld", true)) == _e35);
}
{
std::vector<std::string> _e36 = {"hello", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\rworld", false)) == _e36);
}
{
std::vector<std::string> _e37 = {"hello\r", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\rworld", true)) == _e37);
}
{
std::vector<std::string> _e38 = {"line1", "line2", "line3"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("line1\nline2\nline3", false)) == _e38);
}
{
std::vector<std::string> _e39 = {"line1\n", "line2\n", "line3"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("line1\nline2\nline3", true)) == _e39);
}
{
std::vector<std::string> _e40 = {"hello"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\n", false)) == _e40);
}
{
std::vector<std::string> _e41 = {"hello\n"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\n", true)) == _e41);
}
{
std::vector<std::string> _e42 = {"", "hello"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("\nhello", false)) == _e42);
}
{
std::vector<std::string> _e43 = {"\n", "hello"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("\nhello", true)) == _e43);
}
{
std::vector<std::string> _e44 = {"hello", "", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\n\nworld", false)) == _e44);
}
{
std::vector<std::string> _e45 = {"hello\n", "\n", "world"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\n\nworld", true)) == _e45);
}
{
std::vector<std::string> _e46 = {"hello", ""};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\n\n", false)) == _e46);
}
{
std::vector<std::string> _e47 = {"hello\n", "\n"};
PYSTRING_CHECK_ASSERT((pystring_splitlines("hello\n\n", true)) == _e47);
}
}
+5
View File
@@ -0,0 +1,5 @@
// test_header_only_mode.cpp
#ifndef PYSTRING_HEADER_ONLY
#error "PYSTRING_HEADER_ONLY must be defined when using the header-only target"
#endif
int main() { return 0; }
+8 -8
View File
@@ -23,11 +23,11 @@ struct PYSTRINGTest
PYSTRINGTestFunc function;
};
typedef std::vector<PYSTRINGTest*> UnitTests;
typedef std::vector<PYSTRINGTest> UnitTests;
UnitTests& GetUnitTests();
struct AddTest { AddTest(PYSTRINGTest* test); };
struct AddTest { AddTest(PYSTRINGTest&& test); };
/// PYSTRING_CHECK_* macros checks if the conditions is met, and if not,
/// prints an error message indicating the module and line where the
@@ -102,22 +102,22 @@ struct AddTest { AddTest(PYSTRINGTest* test); };
#define PYSTRING_ADD_TEST(group, name) \
static void pystringtest_##group##_##name(); \
AddTest pystringaddtest_##group##_##name(new PYSTRINGTest(#group, #name, pystringtest_##group##_##name)); \
AddTest pystringaddtest_##group##_##name(PYSTRINGTest(#group, #name, pystringtest_##group##_##name)); \
static void pystringtest_##group##_##name()
#define PYSTRING_TEST_SETUP() \
int unit_test_failures = 0
#define PYSTRING_TEST_APP(app) \
std::vector<PYSTRINGTest*>& GetUnitTests() { \
static std::vector<PYSTRINGTest*> pystring_unit_tests; \
std::vector<PYSTRINGTest>& GetUnitTests() { \
static std::vector<PYSTRINGTest> pystring_unit_tests; \
return pystring_unit_tests; } \
AddTest::AddTest(PYSTRINGTest* test){GetUnitTests().push_back(test);}; \
AddTest::AddTest(PYSTRINGTest&& test){GetUnitTests().emplace_back(test);} \
PYSTRING_TEST_SETUP(); \
int main(int, char **) { std::cerr << "\n" << #app <<"\n\n"; \
for(size_t i = 0; i < GetUnitTests().size(); ++i) { \
int _tmp = unit_test_failures; GetUnitTests()[i]->function(); \
std::cerr << "Test [" << GetUnitTests()[i]->group << "] [" << GetUnitTests()[i]->name << "] - "; \
int _tmp = unit_test_failures; GetUnitTests()[i].function(); \
std::cerr << "Test [" << GetUnitTests()[i].group << "] [" << GetUnitTests()[i].name << "] - "; \
std::cerr << (_tmp == unit_test_failures ? "PASSED" : "FAILED") << "\n"; } \
std::cerr << "\n" << unit_test_failures << " tests failed\n\n"; \
return unit_test_failures; }