Ruby: Write escaped string to YAML

2019-02-05 02:47发布

问题:

The following...

require 'yaml'
test = "I'm a b&d string"
File.open('test.yaml', 'w') do |out|
  out.write(test.to_yaml)
end

...outputs ...

--- this is a b&d string

How can I get it to output

--- 'this is a b&d string'

???

回答1:

If you want to store an escaped string in YAML, escape it using #inspect before you convert it to YAML:

irb> require 'yaml'
=> true
irb> str = %{This string's a little complicated, but it "does the job" (man, I hate scare quotes)}
=> "This string's a little complicated, but it \"does the job\" (man, I hate scare quotes)"
irb> puts str
This string's a little complicated, but it "does the job" (man, I hate scare quotes)
=> nil
irb> puts str.inspect
"This string's a little complicated, but it \"does the job\" (man, I hate scare quotes)"
=> nil
irb> puts str.to_yaml
--- This string's a little complicated, but it "does the job" (man, I hate scare quotes)
=> nil
irb> puts str.inspect.to_yaml
--- "\"This string's a little complicated, but it \\\"does the job\\\" (man, I hate scare quotes)\""
=> nil

YAML doesn't quote strings unless it has to. It quotes strings if they include things that it would miss if it stored it unquoted - like surrounding quote characters or trailing or leading spaces:

irb> puts (str + " ").to_yaml
--- "This string's a little complicated, but it \"does the job\" (man, I hate scare quotes) "
=> nil
irb> puts %{"#{str}"}.to_yaml
--- "\"This string's a little complicated, but it \"does the job\" (man, I hate scare quotes)\""
=> nil
irb> puts (" " + str).to_yaml
--- " This string's a little complicated, but it \"does the job\" (man, I hate scare quotes)"
=> nil

However, as a YAML consumer, whether the string is quoted shouldn't matter to you. You should never be parsing the YAML text yourself - leave that to the libraries. If you need the string to be quoted in the YAML file, that smells bad to me.

It doesn't matter whether your strings have '&'s in them, YAML will preserve the string:

irb> test = "I'm a b&d string"
=> "I'm a b&d string"
irb> YAML::load(YAML::dump(test))
=> "I'm a b&d string"
irb> YAML::load(YAML::dump(test)) == test
=> true


回答2:

As per YAML 1.2 Specification, JSON document is a valid YAML document.

The primary objective of this revision is to bring YAML into compliance with JSON as an official subset.

Therefore, a valid JSON string is a valid YAML string.

require 'json'
'my string'.to_json  # => produces a valid YAML string

This is extremely usable for YAML files enriched with ERB syntax where string.to_yaml won't work in many cases.

Example:

# openvpn.yml.erb
openvpn:
  cert: <%= ENV.fetch('OPENVPN_CERT').to_json %>
  key: <%= ENV.fetch('OPENVPN_KEY').to_json %>