Skip to content

Commit

Permalink
Make String#center spec-compliant
Browse files Browse the repository at this point in the history
  • Loading branch information
ryangjchandler committed Feb 28, 2022
1 parent 754e3e7 commit 80ec8a1
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 2 deletions.
1 change: 1 addition & 0 deletions include/natalie/string_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class StringObject : public Object {
Value add(Env *, Value) const;
Value b(Env *) const;
Value bytes(Env *, Block *);
Value center(Env *, Value, Value);
Value chr(Env *);
Value cmp(Env *, Value) const;
Value concat(Env *env, size_t argc, Value *args);
Expand Down
1 change: 1 addition & 0 deletions lib/natalie/compiler/binding_gen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ def generate_name
gen.binding('String', 'b', 'StringObject', 'b', argc: 0, pass_env: true, pass_block: false, return_type: :Object)
gen.binding('String', 'bytes', 'StringObject', 'bytes', argc: 0, pass_env: true, pass_block: true, return_type: :Object)
gen.binding('String', 'bytesize', 'StringObject', 'bytesize', argc: 0, pass_env: false, pass_block: false, return_type: :size_t)
gen.binding('String', 'center', 'StringObject', 'center', argc: 1..2, pass_env: true, pass_block: false, return_type: :Object)
gen.binding('String', 'chars', 'StringObject', 'chars', argc: 0, pass_env: true, pass_block: false, return_type: :Object)
gen.binding('String', 'chr', 'StringObject', 'chr', argc: 0, pass_env: true, pass_block: false, return_type: :Object)
gen.binding('String', 'concat', 'StringObject', 'concat', argc: :any, pass_env: true, pass_block: false, return_type: :Object)
Expand Down
6 changes: 4 additions & 2 deletions spec/core/string/center_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@
end
end

describe "with width" do
# NATFIXME: Add back after adding encodings.
xdescribe "with width" do
it "returns a String in the same encoding as the original" do
str = "abc".force_encoding Encoding::IBM437
result = str.center 6
Expand All @@ -130,7 +131,8 @@
end
end

describe "with width, pattern" do
# NATFIXME: Add back after adding encodings.
xdescribe "with width, pattern" do
it "returns a String in the compatible encoding" do
str = "abc".force_encoding Encoding::IBM437
result = str.center 6, "あ"
Expand Down
68 changes: 68 additions & 0 deletions src/string_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,74 @@ ArrayObject *StringObject::chars(Env *env) {
return ary;
}

String create_padding(String padding, size_t length) {
size_t quotient = ::floor(length / padding.size());
size_t remainder = length % padding.size();
auto buffer = new String { "" };

for (size_t i = 0; i < quotient; ++i) {
buffer->append(padding);
}

for (size_t j = 0; j < remainder; ++j) {
buffer->append_char(padding[j]);
}

return buffer;
}

Value StringObject::center(Env *env, Value length, Value padstr) {
nat_int_t length_i;
auto to_int = "to_int"_s;

if (length->is_integer()) {
length_i = length->as_integer()->to_nat_int_t();
} else if (length->respond_to(env, to_int)) {
auto result = length->send(env, to_int);

if (!result->is_integer())
env->raise("TypeError", "length can't be converted to an integer");

length_i = result->as_integer()->to_nat_int_t();
} else {
env->raise("TypeError", "length can't be converted to an integer.");
}

auto to_str = "to_str"_s;
String pad;

if (! padstr) {
pad = new String { " " };
} else if (padstr->is_string()) {
pad = padstr->as_string()->string();
} else if (padstr->respond_to(env, to_str)) {
auto result = padstr->send(env, to_str);

if (! result->is_string())
env->raise("TypeError", "padstr can't be converted to a string");

pad = result->as_string()->string();
} else {
env->raise("TypeError", "padstr can't be converted to a string");
}

if (pad.is_empty())
env->raise("ArgumentError", "padstr can't be empty");

if (length_i <= (nat_int_t)m_string.size())
return this;

double split = (length_i - m_string.size()) / 2.0;
auto left_split = ::floor(split);
auto right_split = ::ceil(split);

auto result = new String { m_string };
result->prepend(create_padding(pad, left_split));
result->append(create_padding(pad, right_split));

return new StringObject { result, m_encoding };
}

Value StringObject::chr(Env *env) {
if (this->is_empty()) {
return new StringObject { "", m_encoding };
Expand Down

0 comments on commit 80ec8a1

Please sign in to comment.