Ugly code - suggestions?

Hello!

I'm writing an ANSI color codes parser which gets a string with text and ANSI color codes and outputs an array with substrings and numbers of the color codes.
A color code is something like "\e[31m". Generally, I assume (am I wrong?) they match /\e\[(\d+)(;\d+)*m/.

Here is what I came up with:

module ANSIParser
   def self.ansi_to_array(str)
     str = str.to_s
     ret = []
     while ((m = /\e\[(\d+)(;\d+)*m/.match(str)))
       ret << m.pre_match if m.pre_match != ""
       ret += m[0].scan(/\d+/).map { |c| c.to_i }
       str = m.post_match
     end
     ret << str if str != ""
     return ret
   end
end

# TEST:
ansistr = "\e[31mred,\e[32;1m\e[32;1mbold green\e[33;32;31mred anyway, still bold\e[33;32;31m"

puts ansistr # you should see colors if your terminal supports ANSI escapes
puts ANSIParser::ansi_to_array(ansistr).inspect

It works, but it doesn't look much elegant. In fact, it looks much more like perl than ruby to me :stuck_out_tongue:
That while over there is awful. Is there a way to make it more elegant? Is there some kind of iterator which does what my while does? If not, would you split it somehow?

I'm looking for suggestions :slight_smile:
Thanks!

couple of ideas for you- didn't this out though
nil.to_s #=> ""
no need to re-scan a match if you can use $1, $2, etc

module ANSIParser
  ANSI_MATCH = /\e\[(\d+);(\d+)*m/

  def self.ansi_to_array(str)
    str = str.to_s
    ret = []

    str.scan(ANSI_MATCH) do |match|
      ret << $`.to_s << $1.to_i << $2.to_i
    end

    ret << $'.to_s
  end
end

sorry for using the Perl variables :slight_smile: if you want you can use
Regexp.last_match.post_match

Hello!

I'm writing an ANSI color codes parser which gets a string with text and ANSI color codes and outputs an array with substrings and numbers of the color codes.
A color code is something like "\e[31m". Generally, I assume (am I wrong?) they match /\e\[(\d+)(;\d+)*m/.

Here is what I came up with:

module ANSIParser
  def self.ansi_to_array(str)
    str = str.to_s
    ret =
    while ((m = /\e\[(\d+)(;\d+)*m/.match(str)))
      ret << m.pre_match if m.pre_match != ""
      ret += m[0].scan(/\d+/).map { |c| c.to_i }
      str = m.post_match
    end
    ret << str if str != ""
    return ret
  end
end

# TEST:
ansistr = "\e[31mred,\e[32;1m\e[32;1mbold green\e[33;32;31mred anyway, still bold\e[33;32;31m"

puts ansistr # you should see colors if your terminal supports ANSI escapes
puts ANSIParser::ansi_to_array(ansistr).inspect

>> ansistr = "\e[31mred,\e[32;1m\e[32;1mbold green\e[33;32;31mred anyway, still bold\e[33;32;31m"
=> "\e[31mred,\e[32;1m\e[32;1mbold green\e[33;32;31mred anyway, still bold\e[33;32;31m"

>> ansistr.split(%r{(\e\[\d+(?:;\d+)*m)}).inject() do |arr, s|
?> case s
>> when ""
>> arr
>> when %r{^\e}
>> arr.concat( s.scan( /\d+/ ).map {|x| x.to_i} )
>> else
?> arr << s
>> end
>> end
=> [31, "red,", 32, 1, 32, 1, "bold green", 33, 32, 31, "red anyway, still bold", 33, 32, 31]

Kind regards

  robert

···

On 05.11.2006 22:13, Gabriele Marrone wrote:

HI --

couple of ideas for you- didn't this out though
nil.to_s #=> ""
no need to re-scan a match if you can use $1, $2, etc

The reason is that you might have more than one ;\d+ sequence, and the
scan operation with captures will only give you the last one:

   >> p "abc;31;32;33".scan(/abc(;\d+)*/)
   => [[";33"]]

I don't know whether the named captures in oniguruma might help
here....

David

···

On Mon, 6 Nov 2006, greg wrote:

--
                   David A. Black | dblack@wobblini.net
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1] Ruby for Rails | [3] http://www.rubypowerandlight.com
[2] http://dablog.rubypal.com | [4] http://www.rubycentral.org

Cool!
Thanks :smiley:

···

Il giorno 06/nov/06, alle ore 10:35, Robert Klemme ha scritto:

>> ansistr = "\e[31mred,\e[32;1m\e[32;1mbold green\e[33;32;31mred anyway, still bold\e[33;32;31m"
=> "\e[31mred,\e[32;1m\e[32;1mbold green\e[33;32;31mred anyway, still bold\e[33;32;31m"

>> ansistr.split(%r{(\e\[\d+(?:;\d+)*m)}).inject() do |arr, s|
?> case s
>> when ""
>> arr
>> when %r{^\e}
>> arr.concat( s.scan( /\d+/ ).map {|x| x.to_i} )
>> else
?> arr << s
>> end
>> end
=> [31, "red,", 32, 1, 32, 1, "bold green", 33, 32, 31, "red anyway, still bold", 33, 32, 31]

Kind regards

  robert