问题
I have this old code using Sych
doing :
yaml_as "tag:yaml.org,2002:#{self}"
def to_yaml(opts = {})
YAML::quick_emit(self, opts) do |out|
out.map(taguri, to_yaml_style) do |map|
map.add('name', name)
map.add('address', full_address.upcase) if full_address?
end
end
end
which outputs something like that :
--- !Contact
name: SMOKE OIL
address: |-
SMOKE OIL
1 RUE DE LA PAIX
75002 PARIS
FRANCE
Now, I'm upgrading that old code, going to Psych
so, I read the doc and did :
yaml_as "tag:yaml.org,2002:#{self}"
def encode_with(coder)
coder['name'] = name
coder['address'] = full_address.upcase if full_address?
end
And that does :
--- !Contact
name: SMOKE OIL
address: ! "SMOKE OIL\n1 RUE DE LA PAIX\n75002 PARIS\nFRANCE"
It's nice YAML but, it's supposed to be the output of a whois server, and it's way less readable by humans…
So, I went back to the doc, and looked at the second way of doing things, that is, building an AST. Now, unless I'm not seeing something, nothing explains you how to take the AST you built, and plug it in a way Psych.dump(obj) would still work…
I tried doing (without much hope) :
a = Psych::Nodes::Scalar(full_address.upcase)
a.style = Psych::Nodes::LITTERAL
coder['address'] = a if full_address?
but, obviously, it did not do what I hoped it'd do… I also tried :
def encode_with(coder)
Psych::Nodes::Mapping.new.tap do |map|
map.children << Psych::Nodes::Scalar.new("name")
map.children << Psych::Nodes::Scalar.new(name)
map.children << Psych::Nodes::Scalar.new("address")
a = Psych::Nodes::Scalar.new(full_address.upcase)
a.style = 4
map.children << a
end
end
But, I could not see how to plug it into the coder…
Also, the answer needs to work when doing recursive things, this is a Contact
objet, but one can ask for a Domain
which will contain a few contacts and I want it as DRY as possible :-)
So, anyone has a hint on how to do this ?
回答1:
If you want to create your own AST then you can’t use Psych.dump
. Psych.dump
creates its own AST using the Psych defaults. In your case you want to customise the AST creation process.
Looking at the source of Psych.dump you can see it uses a Psych::Visitors::YAMLTree to create the AST. You can subclass this and customise how it handles your Contact
class to get the output you want. In particular you need to override the accept method.
Here’s a simple example that just special cases the Contact
class:
class MyYAMLTree < Psych::Visitors::YAMLTree
def accept target
return super unless target.is_a? Contact
@emitter.start_mapping(nil, "tag:yaml.org,2002:#{target.class}", false, Psych::Nodes::Mapping::BLOCK)
@emitter.scalar 'name', nil, nil, true, false, Psych::Nodes::Scalar::ANY
@emitter.scalar target.name, nil, nil, true, false, Psych::Nodes::Scalar::ANY
@emitter.scalar 'address', nil, nil, true, false, Psych::Nodes::Scalar::ANY
#this is the where we make the address string a literal
@emitter.scalar target.full_address, nil, nil, true, false, Psych::Nodes::Scalar::LITERAL
@emitter.end_mapping
end
end
Note that it’s the Psych::Visitors::YAMLTree
class that calls encode_with
, this will bypass it altogether for your class.
In order to use this, use something like (this is basically a simplified version of Psych.dump
using MyYAMLTree):
def my_yaml o
visitor = MyYAMLTree.new
visitor << o
visitor.tree.yaml
end
This is obviously just a simple example, but hopefully it’ll point you in the right direction.
来源:https://stackoverflow.com/questions/11798716/psych-doc-says-at-the-mid-level-is-building-an-ast-but-how-exactly