简体   繁体   中英

How best to get all the format sequence keys from a string in ruby?

When given a string that is intended to be formatted with a hash of values to write into the string, is there a clean way to get all the keys that string is expecting values for?

I'm putting together text in a situation where there is a lot of room for customization, and several options for dynamic values to insert into the text. Some of the values are more expensive to get than others, so I'd like to be able to prepare my hash to send in to % to only include the values that are needed in the string.

Ideally I'd be able to query the system that performs the formatting on the string, but I'm not seeing any documentation of such an interface. What I'd like is something like:

"Your request for %{item} is at position %<pos>d".formatting_keys
>>> [:item, :pos]

When passing a hash to String#% , it will call the hash's default proc if a key is missing. You could utilize this behavior and make the proc sneakily collect the passed keys:

def format_keys(format_string)
  keys = []
  format_string % Hash.new { |_, k| keys << k ; 0 }
  keys
end

format_keys("Your request for %{item} is at position %<pos>d")
#=> [:item, :pos]

Note that the proc's return value has to be a valid object for the various field types. I'm using 0 here which seems to work fine.

I'd like to be able to prepare my hash to send in to % to only include the values that are needed in the string.

Instead of a Hash, use an object that does the calculation on demand. That will be useful everywhere.

Use string interpolation to call the methods instead of format sequences.

class Whatever
  def item
    @item ||= calculate_item
  end

  def pos
    @pos ||= calculate_pos
  end

  private

  def calculate_item
    # do something expensive
  end

  def calculate_pos
    # do something expensive
  end
end

obj = Whatever.new
puts "Your request for #{obj.item} is at position #{obj.pos.to_i}"

Using Ruby's own sequence parsing as per https://stackoverflow.com/a/74728162 is ideal, but you can also do your own:

class String
  def format_keys
    scan(
      /
        (?<!%) # don't match escaped sequence starts, e.g. "%%{"
        (?:
          (?<=%\{) [^\}]+ (?=\}) # contents of %{...}
          |                      # OR
          (?<=%\<) [^\>]+ (?=\>) # contents of %<...>
        )
      /x
    )
  end
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM