From 6cd98c24fe9aeea3829ac3d554a277f053cec0be Mon Sep 17 00:00:00 2001 From: YO4 Date: Thu, 4 Sep 2025 20:34:15 +0900 Subject: [PATCH 1/2] Allow IO#each_codepoint to work with unetc even when encoding conversion active Using IO#each_codepoint together with IO#ungetc causes an unwanted exception when encoding conversion is active. C:\>ruby -e "open('NUL', 'rt') { |f| f.ungetc('aa'); f.each_codepoint { |c| p c }}" 97 -e:1:in 'IO#each_codepoint': byte oriented read for character buffered IO (IOError) from -e:1:in 'block in
' from -e:1:in 'Kernel#open' from -e:1:in '
' Fixes [Bug #21131] --- io.c | 2 +- test/ruby/test_io.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/io.c b/io.c index 4ee45c13442f7c..f6daa793990899 100644 --- a/io.c +++ b/io.c @@ -4947,7 +4947,7 @@ rb_io_each_codepoint(VALUE io) fptr->cbuf.off += n; fptr->cbuf.len -= n; rb_yield(UINT2NUM(c)); - rb_io_check_byte_readable(fptr); + rb_io_check_char_readable(fptr); } } NEED_NEWLINE_DECORATOR_ON_READ_CHECK(fptr); diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 3ec8726b5e45ce..0025aa5a7dd502 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -467,6 +467,24 @@ def test_each_codepoint_closed } end + def test_each_codepoint_with_ungetc + bug21562 = '[ruby-core:123176] [Bug #21562]' + with_read_pipe("") {|p| + p.binmode + p.ungetc("aa") + a = "" + p.each_codepoint { |c| a << c } + assert_equal("aa", a, bug21562) + } + with_read_pipe("") {|p| + p.set_encoding("ascii-8bit", universal_newline: true) + p.ungetc("aa") + a = "" + p.each_codepoint { |c| a << c } + assert_equal("aa", a, bug21562) + } + end + def test_rubydev33072 t = make_tempfile path = t.path From 1e7ee6a4ba3ee626d9fb99be4a35365bce74b0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= <2887858+deivid-rodriguez@users.noreply.github.com> Date: Sun, 7 Sep 2025 17:41:59 +0200 Subject: [PATCH 2/2] [DOC] Improve format specification docs One example to describe how `*` works actually prints a warning: ``` $ ruby -we "sprintf('%d', 20, 14)" => -e:1: warning: too many arguments for format string ``` I think it's better to not use examples that print warnings, so I propose to merge `*` docs with "width" specifier docs, and only include the "correct" example. After I believe `*` is not an actual flag, but a special value that the width specifier can take. Mention `*` special value in initial summary as well. --- doc/format_specifications.rdoc | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/doc/format_specifications.rdoc b/doc/format_specifications.rdoc index bdfdc249536b36..a2755256df3994 100644 --- a/doc/format_specifications.rdoc +++ b/doc/format_specifications.rdoc @@ -30,8 +30,9 @@ It consists of: - A leading percent character. - Zero or more _flags_ (each is a character). -- An optional _width_ _specifier_ (an integer). -- An optional _precision_ _specifier_ (a period followed by a non-negative integer). +- An optional _width_ _specifier_ (an integer, or *). +- An optional _precision_ _specifier_ (a period followed by a non-negative + integer, or *). - A _type_ _specifier_ (a character). Except for the leading percent character, @@ -125,13 +126,6 @@ Left-pad with zeros instead of spaces: sprintf('%6d', 100) # => " 100" sprintf('%06d', 100) # => "000100" -=== '*' Flag - -Use the next argument as the field width: - - sprintf('%d', 20, 14) # => "20" - sprintf('%*d', 20, 14) # => " 14" - === 'n$' Flag Format the (1-based) nth argument into this field: @@ -152,6 +146,11 @@ of the formatted field: # Ignore if too small. sprintf('%1d', 100) # => "100" +If the width specifier is '*' instead of an integer, the actual minimum +width is taken from the argument list: + + sprintf('%*d', 20, 14) # => " 14" + == Precision Specifier A precision specifier is a decimal point followed by zero or more @@ -194,6 +193,11 @@ the number of characters to write: sprintf('%s', Time.now) # => "2022-05-04 11:59:16 -0400" sprintf('%.10s', Time.now) # => "2022-05-04" +If the precision specifier is '*' instead of a non-negative integer, +the actual precision is taken from the argument list: + + sprintf('%.*d', 20, 1) # => "00000000000000000001" + == Type Specifier Details and Examples === Specifiers +a+ and +A+