Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
230853f
Replaced split(), startswith() and endswith() pystring usages
meimchu May 15, 2026
fc23cca
Add utils as include path for openfx build
meimchu May 15, 2026
ad324ce
Fix silly char issue
meimchu May 15, 2026
e906f5a
Update StringUtils Trim functionalities.
meimchu May 15, 2026
e57ce09
Add tests and Replace
meimchu May 15, 2026
4d6a859
Add more Replace tests.
meimchu May 15, 2026
c7505f1
Fix test
meimchu May 15, 2026
3339fa4
Fix test
meimchu May 15, 2026
68957b4
Fix replace test
meimchu May 16, 2026
8694cf7
Fix replace test
meimchu May 16, 2026
a0c02ec
More replace test fix
meimchu May 16, 2026
6a9f8bf
Replace pystring::replace in FileTransform
meimchu May 17, 2026
c277336
Test changed cicd
meimchu May 17, 2026
25f596e
Update CICD test
meimchu May 17, 2026
160c1ec
Integrate StringUtils::Multiply
meimchu May 19, 2026
f39f593
Small syntax update.
meimchu May 19, 2026
9417f9b
Revert workflow.yml to get ready for real fix.
meimchu May 21, 2026
f41b7eb
Test changed cicd
meimchu May 17, 2026
75ba8e1
Update CICD test
meimchu May 17, 2026
e84b2b4
Revert workflow.yml to get ready for real fix.
meimchu May 21, 2026
d2d5368
Address notes to use size_t instead of int for count parameter.
meimchu Jun 4, 2026
92e37dd
Add Repeat() function to prepare to replace Multiply().
meimchu Jun 4, 2026
0ef5d09
Use Repeat() in Op::SerializeOpVec. Add more tests
meimchu Jun 4, 2026
ddd0f48
Remove the limit test for Repeat.
meimchu Jun 4, 2026
c226670
Update Repeat and Multiply accordingly.
meimchu Jun 6, 2026
6be11f5
Add SerializeOpVec output prefix check.
meimchu Jun 6, 2026
42a38f5
Fix test
meimchu Jun 6, 2026
2048eb9
Fix test
meimchu Jun 6, 2026
e488851
Fix test
meimchu Jun 6, 2026
355aa32
Fix test
meimchu Jun 6, 2026
ac7647c
Fix test
meimchu Jun 6, 2026
27fd8bd
Fix test
meimchu Jun 6, 2026
989ac0e
Op::SerializeOpVec switch to size_t
meimchu Jun 6, 2026
b9d9493
Not changing ci_workflow.yml
meimchu Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/OpenColorIO/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2571,7 +2571,8 @@ const char * Config::getInactiveColorSpaces() const
bool Config::isInactiveColorSpace(const char * colorspace) const noexcept
{
StringUtils::StringVec svec;
pystring::split(getImpl()->m_inactiveColorSpaceNamesConf.c_str(), svec, ", ");
svec = StringUtils::Split(getImpl()->m_inactiveColorSpaceNamesConf.c_str(), ',');
StringUtils::Trim(svec);

for (size_t i = 0; i < svec.size(); i++)
{
Expand Down Expand Up @@ -6036,7 +6037,7 @@ bool Config::isArchivable() const
// 1) Path may not be absolute.
pystring::os::path::isabs(normPath) ||
// 2) Path may not start with double dot ".." (going above working directory).
pystring::startswith(normPath, "..") ||
StringUtils::StartsWith(normPath, "..") ||
// 3) A context variable may not be located at the start of the path.
(ContainsContextVariables(path) &&
(StringUtils::Find(path, "$") == 0 ||
Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/OCIOZArchive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ void addSupportedFiles(void * archiver, const char * path, const char * configWo
std::string root, ext;
pystring::os::path::splitext(root, ext, std::string(entry->d_name));
// Strip leading dot character in order to get the extension name only.
ext = pystring::lstrip(ext, ".");
ext = StringUtils::LeftTrim(ext, '.');

// Check if the extension is supported. Using logic from LoadFileUncached().
FormatRegistry & formatRegistry = FormatRegistry::GetInstance();
Expand Down
7 changes: 3 additions & 4 deletions src/OpenColorIO/Op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include <cstring>
#include <sstream>

#include <pystring.h>

#include <OpenColorIO/OpenColorIO.h>

#include "Logging.h"
Expand All @@ -23,6 +21,7 @@
#include "ops/lut1d/Lut1DOp.h"
#include "ops/lut3d/Lut3DOp.h"
#include "ops/range/RangeOp.h"
#include "utils/StringUtils.h"

namespace OCIO_NAMESPACE
{
Expand Down Expand Up @@ -470,15 +469,15 @@ std::ostream& operator<< (std::ostream & os, const Op & op)
return os;
}

std::string SerializeOpVec(const OpRcPtrVec & ops, int indent)
std::string SerializeOpVec(const OpRcPtrVec & ops, size_t indent)
{
std::ostringstream oss;

for (OpRcPtrVec::size_type idx = 0, size = ops.size(); idx < size; ++idx)
{
const OpRcPtr & op = ops[idx];

oss << pystring::mul(" ", indent);
oss << StringUtils::Repeat(" ", indent);
oss << "Op " << idx << ": " << *op << " ";
oss << op->getCacheID();

Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/Op.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ class OpRcPtrVec

};

std::string SerializeOpVec(const OpRcPtrVec & ops, int indent=0);
std::string SerializeOpVec(const OpRcPtrVec & ops, size_t indent=0);

void CreateOpVecFromOpData(OpRcPtrVec & ops,
const ConstOpDataRcPtr & opData,
Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/fileformats/FileFormatIridasLook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ class XMLParserHelper
if (pImpl->m_size)
{
std::string size_raw = std::string(s, len);
std::string size_clean = pystring::strip(size_raw, "'\" "); // strip quotes and space
std::string size_clean = StringUtils::Trim(size_raw, "'\" "); // strip quotes and space

long int size_3d{};

Expand Down
2 changes: 1 addition & 1 deletion src/OpenColorIO/transforms/FileTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ void LoadFileUncached(FileFormat * & returnFormat,
std::string root, extension;
pystring::os::path::splitext(root, extension, filepath);
// remove the leading '.'
extension = pystring::replace(extension,".","",1);
extension = StringUtils::Replace(extension, ".", "", 1);

FormatRegistry & formatRegistry = FormatRegistry::GetInstance();

Expand Down
105 changes: 90 additions & 15 deletions src/utils/StringUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ inline bool StartsWith(const std::string& str, char prefix)
}

// Starting from the left, trim the character.
inline std::string LeftTrim(std::string str, const std::string & prefix)
{
size_t first_good_char = str.find_first_not_of(prefix);
if (first_good_char == std::string::npos) { return str; }
str.erase(0, first_good_char);
return str;
}

inline std::string LeftTrim(std::string str, char c)
{
const auto it = std::find_if(str.begin(), str.end(), [&c](char ch) { return c!=ch; });
Expand All @@ -126,6 +134,14 @@ inline std::string LeftTrim(std::string str)
}

// Starting from the right, trim the character.
inline std::string RightTrim(std::string str, const std::string & suffix)
{
size_t last_good_char = str.find_last_not_of(suffix);
if (last_good_char == std::string::npos) { return str; }
str.erase(last_good_char + 1);
return str;
}

inline std::string RightTrim(std::string str, char c)
{
const auto it = std::find_if(str.rbegin(), str.rend(), [&c](char ch) { return c!=ch; });
Expand All @@ -149,6 +165,11 @@ inline std::string Trim(std::string str, char c)
}

// From the left and right, trim all the space characters i.e. space, tabulation, etc.
inline std::string Trim(std::string str, const std::string & chars)
{
return LeftTrim(RightTrim(str, chars), chars);
}

inline std::string Trim(std::string str)
{
return LeftTrim(RightTrim(str));
Expand Down Expand Up @@ -249,39 +270,54 @@ inline std::string::size_type ReverseFind(const std::string & subject, const std
return subject.rfind(search);
}

// In place replace the 'search' substring by the 'replace' string in 'str'.
inline bool ReplaceInPlace(std::string & subject, const std::string & search, const std::string & replace)
// In place replace the 'search' substring by the 'replace' string in 'subject'. Limited by 'count'.
inline bool ReplaceInPlace(std::string & subject, const std::string & search, const std::string & replace, size_t count)
{
if (search.empty()) return false;
if (search.empty() || count == 0) { return false; }

bool changed = false;

size_t pos = 0;
size_t pos = 0;
size_t iter = 0;
while ((pos = subject.find(search, pos)) != std::string::npos)
{
if (iter >= count) { break; }
subject.replace(pos, search.length(), replace);
pos += replace.length();
changed = true;
++iter;
}

return changed;
}

// Replace the 'search' substring by the 'replace' string in 'str'.
inline std::string Replace(const std::string & subject, const std::string & search, const std::string & replace)
// In place replace the 'search' substring by the 'replace' string in 'subject'.
inline bool ReplaceInPlace(std::string & subject, const std::string & search, const std::string & replace)
{
return ReplaceInPlace(subject, search, replace, std::string::npos);
}

// Replace the 'search' substring by the 'replace' string in 'subject'. Limited by 'count'.
inline std::string Replace(const std::string & subject, const std::string & search, const std::string & replace, size_t count)
{
std::string str{subject};
ReplaceInPlace(str, search, replace);
ReplaceInPlace(str, search, replace, count);
return str;
}

// Replace the 'search' substring by the 'replace' string in 'subject'.
inline std::string Replace(const std::string & subject, const std::string & search, const std::string & replace)
{
return Replace(subject, search, replace, std::string::npos);
}

// Check if the 'entry' is in the 'list' using a case insensitive comparison.
inline bool Contain(const StringVec & list, const std::string & entry)
{
const auto it = std::find_if(list.begin(), list.end(),
[entry](const std::string & ent)
{
return Compare(ent.c_str(), entry.c_str());
const auto it = std::find_if(list.begin(), list.end(),
[entry](const std::string & ent)
{
return Compare(ent.c_str(), entry.c_str());
});
return it!=list.end();
}
Expand All @@ -290,10 +326,10 @@ inline bool Contain(const StringVec & list, const std::string & entry)
// It returns true if found.
inline bool Remove(StringVec & list, const std::string & entry)
{
const auto it = std::find_if(list.begin(), list.end(),
[entry](const std::string & ent)
{
return Compare(ent.c_str(), entry.c_str());
const auto it = std::find_if(list.begin(), list.end(),
[entry](const std::string & ent)
{
return Compare(ent.c_str(), entry.c_str());
});
if (it!=list.end())
{
Expand All @@ -304,6 +340,45 @@ inline bool Remove(StringVec & list, const std::string & entry)
return false;
}

// Multiply the 'str' by the 'n' value.
inline std::string Multiply(const std::string & str, size_t n)
{
// Early exit and match pystring::mul behaviour.
if (n == 0) { return ""; }
if (n == 1) { return str; }

if (str.empty()) { return str; }

std::ostringstream os;

@KevinJW KevinJW Jun 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if something like the following might be 'better'/faster

std::string repeat_fast(const std::string& input, std::size_t n) {
    if (n == 0) {
        return {};
    }
    if (n == 1) {
        return input;
    }
    std::string result;
    const auto input_size = input.size();
    result.reserve(input_size * n);
    result = input;
    std::size_t current = 1;
    while (current * 2 <= n) {
        result += result;
        current *= 2;
    }
    const auto remaining = n - current;
    if (remaining > 0) {
        result += std::string_view(result.data(), input_size * remaining);
    }
    return result;
}

Edited because I nerd sniped myself and ran some performance tests
And again avoid an extra allocation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might want to call the function Repeat or Repeat_n rather than Multiply.

Note that reserve() can raise an exception if we would go over std::string::max_size() but likely we would run out of memory before that happens.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a wonderful suggestion. I just want to speak briefly on what I've done so far to get your eyes on it.

  1. Implemented your function above as Repeat(). Made some small changes for syntax consistency.
  2. I attempted to put a max_limit check as you've pointed out prior to reserve(). I chose not to throw an exception but I'm open to suggestions. It is a new behaviour compared to the old pystring::mul.
  3. I have replaced the single StringUtils::Multiply() usage found in the code base (which was using pystring::mul() before).
  4. I did do a quick benchmark testing between Multiply() and Repeat(). I got a string of 23 chars in length and repeated it 1000 times. The difference is significant and I like the pointer arithmetic in this implementation. I'm happy to remove Multiply() completely in favour of Repeat() if that is the desired decision.
Multiply() Total Time: 388.173 ms
Multiply() Avg Time:   0.0388174 ms/iter

Repeat() Total Time:   4.99192 ms
Repeat() Avg Time:     0.000499192 ms/iter

for(size_t i = 0; i < n; ++i) { os << str; }
return os.str();
}

// Repeat the 'str' by the 'n' value.
inline std::string Repeat(const std::string & str, size_t n)
{
// Early exit and match pystring::mul behaviour.
if (n == 0) { return {}; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not benchmarked it but it is possible that under some circumstances we might want to check || str.empty() to catch the case of appending an empty string many times. though the extra test probably slows down the function when n is small.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you that doing an early check on str.empty() for early return makes sense. I ran a benchmark on both Multiply() and Repeat() with an empty string input and 10,000 repeats. Both Multiply() and Repeat() will return itself and Repeat() just barely beat out Multiply(). I think at this point, I feel pretty good about just removing Multiply() in favour of Repeat() if you agree with it.

Multiply() Total Time: 0.129125 ms
Multiply() Avg Time:   1.29125e-05 ms/iter

Repeat() Total Time:   0.119125 ms
Repeat() Avg Time:     1.19125e-05 ms/iter

if (n == 1) { return str; }

if (str.empty()) { return str; }
const auto str_size = str.size();

std::string result;
result.reserve(str_size * n);
result = str;
size_t current = 1;
while (current * 2 <= n) {
result += result;
current *= 2;
}
const auto remaining = n - current;
if (remaining > 0) {
result += std::string_view(result.data(), str_size * remaining);
}
return result;
}

} // namespace StringUtils

#endif // INCLUDED_STRINGUTILS_H
4 changes: 4 additions & 0 deletions tests/cpu/Op_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -514,4 +514,8 @@ OCIO_ADD_TEST(OpRcPtrVec, serialize)

// Serialize not optimized OpVec i.e. contains some NoOps.
OCIO_CHECK_NO_THROW(OCIO::SerializeOpVec(ops));

// Check Serialize output's prefix for indentation.
std::string ops_serialized = OCIO::SerializeOpVec(ops, 4);
OCIO_CHECK_EQUAL(std::string_view(ops_serialized.data(), 7), " Op ");
}
Loading
Loading