Case Converter — Ruby 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 Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# frozen_string_literal: true
# case-converter — pure, deterministic text-case transformations.
#
# Language: Ruby (3.2, 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. Ruby's upcase / downcase are full-Unicode, matching JavaScript's
# toUpperCase / toLowerCase.
module CaseConverter
# A camelCase seam: a lowercase letter or digit immediately followed by an
# uppercase letter. We splice a space in here before splitting.
CAMEL_BOUNDARY = /([a-z0-9])([A-Z])/
# Word delimiters: any run of whitespace (\p{Space} is the Unicode
# whitespace property, like the Python and Rust ports), underscore, hyphen,
# or dot.
SEPARATORS = /[\p{Space}_\-.]+/
# Anything that is not an ASCII letter or digit — stripped from each token.
NON_ALNUM = /[^a-zA-Z0-9]/
# A title "word": a maximal run that starts with an ASCII word character
# (Ruby's \w is ASCII-only by default, exactly like JavaScript's) and
# continues with non-whitespace.
TITLE_WORD = /\w\S*/
module_function
# Tokenize +s+ into word units.
#
# The pipeline reproduces the TypeScript reference step for step:
# 1. Insert a space at every camelCase boundary.
# 2. Split on runs of whitespace / underscore / hyphen / dot.
# 3. Strip remaining non-alphanumerics and drop empty tokens.
def words(s)
spaced = s.gsub(CAMEL_BOUNDARY, '\1 \2')
spaced.split(SEPARATORS)
.map { |part| part.gsub(NON_ALNUM, '') }
.reject(&:empty?) # filter(Boolean) in the TS source
end
# Capitalize the first character and lowercase the rest.
def cap(s)
s.empty? ? '' : s[0].upcase + s[1..].downcase
end
# Title case: capitalize the first character of each maximal word-run.
# Characters between runs (spacing, punctuation) are left untouched.
def title(s)
s.gsub(TITLE_WORD) { |run| cap(run) }
end
# Sentence case: lowercase + trim the whole string, then uppercase only the
# first character. Empty input yields empty output.
def sentence(s)
t = s.downcase.strip
t.empty? ? '' : t[0].upcase + t[1..]
end
def upper(s)
s.upcase
end
def lower(s)
s.downcase
end
# camelCase: the first word stays lowercase, every later word is capitalized.
def camel(s)
words(s).each_with_index.map { |w, i| i.zero? ? w.downcase : cap(w) }.join
end
# PascalCase: every word capitalized and concatenated.
def pascal(s)
words(s).map { |w| cap(w) }.join
end
def snake(s)
words(s).join('_').downcase
end
def kebab(s)
words(s).join('-').downcase
end
def constant(s)
words(s).join('_').upcase
end
def slug(s)
words(s).join('-').downcase
end
# The case-transform registry: CaseId -> proc. Keys mirror the TypeScript
# caseTransforms object 1:1 (insertion order preserved, frozen).
CASE_TRANSFORMS = {
'upper' => ->(s) { upper(s) },
'lower' => ->(s) { lower(s) },
'title' => ->(s) { title(s) },
'sentence' => ->(s) { sentence(s) },
'camel' => ->(s) { camel(s) },
'pascal' => ->(s) { pascal(s) },
'snake' => ->(s) { snake(s) },
'kebab' => ->(s) { kebab(s) },
'constant' => ->(s) { constant(s) },
'slug' => ->(s) { slug(s) }
}.freeze
# Valid case identifiers, in registry order.
CASE_IDS = CASE_TRANSFORMS.keys.freeze
# Apply a named case transform.
#
# Raises ArgumentError if +case_id+ is not a known case.
def convert(case_id, s)
fn = CASE_TRANSFORMS[case_id]
raise ArgumentError, "Unknown case: #{case_id}" if fn.nil?
fn.call(s)
end
# Like +convert+ but returns nil for an unknown case id.
def try_convert(case_id, s)
CASE_TRANSFORMS[case_id]&.call(s)
end
end
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 →