Skip to content

Commit

Permalink
Add underscore_to_space option to String#titleize (crystal-lang#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
Blacksmoke16 authored Aug 8, 2024
1 parent c0cdaa2 commit 94bdba1
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 11 deletions.
10 changes: 10 additions & 0 deletions spec/std/string_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,10 @@ describe "String" do
it { assert_prints " spáçes before".titleize, " Spáçes Before" }
it { assert_prints "testá-se múitô".titleize, "Testá-se Múitô" }
it { assert_prints "iO iO".titleize(Unicode::CaseOptions::Turkic), "İo İo" }
it { assert_prints "foo_Bar".titleize, "Foo_bar" }
it { assert_prints "foo_bar".titleize, "Foo_bar" }
it { assert_prints "testá_se múitô".titleize(underscore_to_space: true), "Testá Se Múitô" }
it { assert_prints "foo_bar".titleize(underscore_to_space: true), "Foo Bar" }

it "handles multi-character mappings correctly (#13533)" do
assert_prints "fflİ İffl dz DZ".titleize, "Ffli̇ İffl Dz Dz"
Expand All @@ -735,6 +739,12 @@ describe "String" do
String.build { |io| "\xB5!\xE0\xC1\xB5?".titleize(io) }.should eq("\xB5!\xE0\xC1\xB5?".scrub)
String.build { |io| "a\xA0b".titleize(io) }.should eq("A\xA0b".scrub)
end

describe "with IO" do
it { String.build { |io| "foo_Bar".titleize io }.should eq "Foo_bar" }
it { String.build { |io| "foo_bar".titleize io }.should eq "Foo_bar" }
it { String.build { |io| "foo_bar".titleize(io, underscore_to_space: true) }.should eq "Foo Bar" }
end
end

describe "chomp" do
Expand Down
35 changes: 24 additions & 11 deletions src/string.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1506,15 +1506,17 @@ class String
end
end

# Returns a new `String` with the first letter after any space converted to uppercase and every
# other letter converted to lowercase.
# Returns a new `String` with the first letter after any space converted to uppercase and every other letter converted to lowercase.
# Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase.
#
# ```
# "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld"
# " spaces before".titleize # => " Spaces Before"
# "x-men: the last stand".titleize # => "X-men: The Last Stand"
# "hEllO tAb\tworld".titleize # => "Hello Tab\tWorld"
# " spaces before".titleize # => " Spaces Before"
# "x-men: the last stand".titleize # => "X-men: The Last Stand"
# "foo_bar".titleize # => "Foo_bar"
# "foo_bar".titleize(underscore_to_space: true) # => "Foo Bar"
# ```
def titleize(options : Unicode::CaseOptions = :none) : String
def titleize(options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : String
return self if empty?

if single_byte_optimizable? && (options.none? || options.ascii?)
Expand All @@ -1525,9 +1527,15 @@ class String
byte = to_unsafe[i]
if byte < 0x80
char = byte.unsafe_chr
replaced_char = upcase_next ? char.upcase : char.downcase
replaced_char, upcase_next = if upcase_next
{char.upcase, false}
elsif underscore_to_space && '_' == char
{' ', true}
else
{char.downcase, char.ascii_whitespace?}
end

buffer[i] = replaced_char.ord.to_u8!
upcase_next = char.ascii_whitespace?
else
buffer[i] = byte
upcase_next = false
Expand All @@ -1537,26 +1545,31 @@ class String
end
end

String.build(bytesize) { |io| titleize io, options }
String.build(bytesize) { |io| titleize io, options, underscore_to_space: underscore_to_space }
end

# Writes a titleized version of `self` to the given *io*.
# Optionally, if *underscore_to_space* is `true`, underscores (`_`) will be converted to a space and the following letter converted to uppercase.
#
# ```
# io = IO::Memory.new
# "x-men: the last stand".titleize io
# io.to_s # => "X-men: The Last Stand"
# ```
def titleize(io : IO, options : Unicode::CaseOptions = :none) : Nil
def titleize(io : IO, options : Unicode::CaseOptions = :none, *, underscore_to_space : Bool = false) : Nil
upcase_next = true

each_char_with_index do |char, i|
if upcase_next
upcase_next = false
char.titlecase(options) { |c| io << c }
elsif underscore_to_space && '_' == char
upcase_next = true
io << ' '
else
upcase_next = char.whitespace?
char.downcase(options) { |c| io << c }
end
upcase_next = char.whitespace?
end
end

Expand Down

0 comments on commit 94bdba1

Please sign in to comment.