14

According to http://yaml.org/spec/current.html#id2509980 comments in YAML files are a presentational detail and must not be in the serialization/representation graph ( http://yaml.org/spec/current.html#representation/). It looks like Psych is parsing according to spec and loses the comments which means that it is not possible to parse a YAML file and serialize it again exactly the same way when the file contains comments. Which in my opinion is very strange because comments do matter in such file (e.g. configs).

Does anyone know if it possible to parse comments with an existing library or is the only way to go to do it all by myself?

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
hjuskewycz
  • 1,437
  • 1
  • 14
  • 20
  • 1
    It's a stretch, but you could clone it into a temporary file before loading it in and then merge the two files after serialization (using diff?)... as long as the overall structure was the same... or... is that kind of a crazy idea...? – Josh Voigts Aug 24 '12 at 19:51
  • [This blog post](https://kev.inburke.com/kevin/more-comment-preserving-configuration-parsers/) suggests the only comment-preserving YAML parser is ruamel, which is for Python. – Wilfred Hughes Dec 20 '17 at 15:17

3 Answers3

2

We can do some thing like this also, which would change the key value and also keep the comments.

require 'yaml'
thing = YAML.load_file('/opt/database.yml')
hostname = thing["common"]["host_name"]
appname = thing["common"]["app_name"]
motdobj = IO.readlines('/opt/database.yml')
motdobj = motdobj.map {|s| s.gsub(hostname, "mrigesh")}
motdobj = motdobj.map {|s| s.gsub(appname, "abc")}

File.open('/opt/database.yml', "w" ) do | file1 |
    file1.puts motdobj
    file1.close
end
1

You could iterate over the nodes on a lower level keeping the comments when emitting. Also, you could see if syck engine gives you the result you are looking for.

Bjoern Rennhak
  • 6,766
  • 1
  • 16
  • 21
0

I liked @josh-voigts crazy idea. Here's a crazy implementation. Comments can be interspersed almost anywhere and it works!

require 'tempfile'

def yaml_conf_edit(fn, &block)
  conf = File.open(fn) {|f| YAML.load(f.read)}

  before = Tempfile.new('before')
  before.write(conf.to_yaml)
  before.close

  yield conf

  after = Tempfile.new('after')
  after.write(conf.to_yaml)
  after.close

  `merge #{fn} #{before.path} #{after.path}`
  before.unlink; after.unlink

  conf
end

Which you can use in something like:

yaml_conf_edit('conf/database.yml') do |conf| 
  conf['development']['database'] = db_timestamped
end
Tombart
  • 30,520
  • 16
  • 123
  • 136
TKH
  • 828
  • 6
  • 11