Case Converter — C++ source
Convert text between UPPER, lower, Title, Sentence, camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE and slug. Live, one-click copy.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// case-converter — pure, deterministic text-case transformations.
//
// Language: C++17 (standard library only)
// Source: CosmoDev polyglot showcase port of the Case Converter tool.
// Canonical: src/lib/case.ts (the live TypeScript) plus this
// tool's python.py / rust.rs reference ports.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Split any input into word tokens and re-emit it in upper / lower / title /
// sentence / camel / pascal / snake / kebab / constant / slug case — same
// inputs produce the same outputs as the TypeScript original, including edge
// cases.
//
// The C++ standard library has no locale-independent Unicode case mapping
// (std::towupper is locale-dependent), so this port maps ASCII letters — the
// tool's token alphabet is [a-zA-Z0-9] — and passes every other byte through
// verbatim. Single-byte scans are safe for UTF-8 payload: every ASCII test
// below only ever matches single-byte characters.
#include <cctype>
#include <functional>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace case_converter {
namespace {
inline bool isAsciiLower(unsigned char c) { return c >= 'a' && c <= 'z'; }
inline bool isAsciiUpper(unsigned char c) { return c >= 'A' && c <= 'Z'; }
inline bool isAsciiDigit(unsigned char c) { return c >= '0' && c <= '9'; }
inline bool isAsciiAlnum(unsigned char c) {
return isAsciiLower(c) || isAsciiUpper(c) || isAsciiDigit(c);
}
// JavaScript's \w = [A-Za-z0-9_].
inline bool isWordChar(unsigned char c) { return isAsciiAlnum(c) || c == '_'; }
} // namespace
/// Tokenize `s` into word units.
///
/// Mirrors the TypeScript reference exactly, as one fused forward pass:
/// 1. Break at every camelCase boundary (a lowercase letter or digit
/// immediately followed by an uppercase letter).
/// 2. Break on runs of whitespace / underscore / hyphen / dot.
/// 3. Strip any remaining non-alphanumeric characters and drop empty tokens.
std::vector<std::string> words(std::string_view s) {
std::vector<std::string> out;
std::string current;
// Close the current token, if any, pushing it into the output.
auto flush = [&] {
if (!current.empty()) out.push_back(std::exchange(current, std::string{}));
};
for (std::size_t i = 0; i < s.size(); ++i) {
const unsigned char c = static_cast<unsigned char>(s[i]);
// 1. camelCase boundary: the previous char was lowercase-or-digit and
// this one is uppercase — close the current token.
if (i > 0) {
const unsigned char prev = static_cast<unsigned char>(s[i - 1]);
if ((isAsciiLower(prev) || isAsciiDigit(prev)) && isAsciiUpper(c)) flush();
}
if (std::isspace(c) || c == '_' || c == '-' || c == '.') {
// 2. delimiter — end the current token.
flush();
} else if (isAsciiAlnum(c)) {
// 3a. keep ASCII letters and digits.
current.push_back(static_cast<char>(c));
}
// 3b. anything else (punctuation, symbols) is silently dropped,
// matching the reference's `[^a-zA-Z0-9]` stripping.
}
flush();
return out;
}
namespace {
// Map ASCII letters through `map`; copy every other byte unchanged.
std::string mapAscii(std::string_view s, int (*map)(int)) {
std::string out;
out.reserve(s.size());
for (const char ch : s)
out.push_back(static_cast<char>(map(static_cast<unsigned char>(ch))));
return out;
}
// Join the tokens with `sep` into one string.
std::string joinWords(const std::vector<std::string>& ws, char sep) {
std::string out;
for (std::size_t i = 0; i < ws.size(); ++i) {
if (i > 0) out.push_back(sep);
out += ws[i];
}
return out;
}
} // namespace
/// Upper-case every ASCII letter.
std::string upper(std::string_view s) { return mapAscii(s, std::toupper); }
/// Lower-case every ASCII letter.
std::string lower(std::string_view s) { return mapAscii(s, std::tolower); }
/// Capitalize the first character and lowercase the rest.
std::string cap(std::string_view s) {
if (s.empty()) return std::string{};
std::string out;
out.reserve(s.size());
out.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(s[0]))));
for (std::size_t i = 1; i < s.size(); ++i)
out.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(s[i]))));
return out;
}
/// Title case: capitalize the first character of each maximal word-run.
///
/// A "word-run" starts at an ASCII word character ([A-Za-z0-9_]) and runs to
/// the next whitespace, mirroring JavaScript's /\w\S*/g. Characters between
/// runs (spacing, punctuation) are preserved verbatim — only each run's first
/// character is touched.
std::string title(std::string_view s) {
std::string out;
out.reserve(s.size());
std::size_t i = 0;
while (i < s.size()) {
if (isWordChar(static_cast<unsigned char>(s[i]))) {
// Consume the whole `\w\S*` run up to the next whitespace.
const std::size_t start = i;
++i;
while (i < s.size() && !std::isspace(static_cast<unsigned char>(s[i]))) ++i;
out += cap(s.substr(start, i - start));
} else {
// Not the start of a word-run: copy verbatim.
out.push_back(s[i]);
++i;
}
}
return out;
}
/// Sentence case: lowercase + trim the whole string, then uppercase the first
/// character. Empty (or all-whitespace) input yields empty output.
std::string sentence(std::string_view s) {
std::string t = lower(s);
const std::size_t b = t.find_first_not_of(" \t\n\v\f\r");
if (b == std::string::npos) return std::string{};
const std::size_t e = t.find_last_not_of(" \t\n\v\f\r");
t = t.substr(b, e - b + 1);
t[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(t[0])));
return t;
}
/// camelCase: the first word stays lowercase, every later word is capitalized.
std::string camel(std::string_view s) {
std::string out;
const std::vector<std::string> ws = words(s);
for (std::size_t i = 0; i < ws.size(); ++i)
out += (i == 0) ? lower(ws[i]) : cap(ws[i]);
return out;
}
/// PascalCase: every word capitalized and concatenated.
std::string pascal(std::string_view s) {
std::string out;
for (const auto& w : words(s)) out += cap(w);
return out;
}
/// snake_case — "_".join(words).lower() in the reference.
std::string snake(std::string_view s) { return lower(joinWords(words(s), '_')); }
/// kebab-case — "-".join(words).lower() in the reference.
std::string kebab(std::string_view s) { return lower(joinWords(words(s), '-')); }
/// CONSTANT_CASE — "_".join(words).upper() in the reference.
std::string constant(std::string_view s) { return upper(joinWords(words(s), '_')); }
/// slug (kebab's URL-safe twin) — "-".join(words).lower() in the reference.
std::string slug(std::string_view s) { return lower(joinWords(words(s), '-')); }
/// The case-transform registry: CaseId -> pure function.
///
/// The std::map lookup table is the C++ analogue of the TypeScript
/// `caseTransforms` object. Keys are sorted rather than insertion-ordered
/// (std::map), which does not affect dispatch — only enumeration order.
const std::map<std::string, std::function<std::string(const std::string&)>>&
caseTransforms() {
static const std::map<std::string, std::function<std::string(const std::string&)>>
transforms{
{"upper", [](const std::string& s) { return upper(s); }},
{"lower", [](const std::string& s) { return lower(s); }},
{"title", [](const std::string& s) { return title(s); }},
{"sentence", [](const std::string& s) { return sentence(s); }},
{"camel", [](const std::string& s) { return camel(s); }},
{"pascal", [](const std::string& s) { return pascal(s); }},
{"snake", [](const std::string& s) { return snake(s); }},
{"kebab", [](const std::string& s) { return kebab(s); }},
{"constant", [](const std::string& s) { return constant(s); }},
{"slug", [](const std::string& s) { return slug(s); }},
};
return transforms;
}
/// Apply a named case transform.
///
/// Throws std::invalid_argument if `caseId` is not a known case — the C++
/// equivalent of the Python port's ValueError.
std::string convert(const std::string& caseId, const std::string& s) {
const auto& transforms = caseTransforms();
const auto it = transforms.find(caseId);
if (it == transforms.end()) throw std::invalid_argument("Unknown case: " + caseId);
return it->second(s);
}
/// Like `convert` but returns std::nullopt for an unknown case id.
std::optional<std::string> tryConvert(const std::string& caseId, const std::string& s) {
const auto& transforms = caseTransforms();
const auto it = transforms.find(caseId);
if (it == transforms.end()) return std::nullopt;
return it->second(s);
}
} // namespace case_converter
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →