Психолог говорит: «На среднем уровне строится АСТ…», но как именно?

У меня есть этот старый код, использующий Sych:

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

который выводит что-то вроде этого:

--- !Contact
name: SMOKE OIL
address: |-
  SMOKE OIL
  1 RUE DE LA PAIX
  75002 PARIS
  FRANCE

Теперь я обновляю этот старый код, переходя на Psych, поэтому я прочитал документ и сделал:

yaml_as "tag:yaml.org,2002:#{self}"
def encode_with(coder)
  coder['name'] = name
  coder['address'] = full_address.upcase if full_address?
end

И это делает:

--- !Contact
name: SMOKE OIL
address: ! "SMOKE OIL\n1 RUE DE LA PAIX\n75002 PARIS\nFRANCE"

Это хороший YAML, но он должен быть результатом сервера whois, и он менее удобочитаем для людей…

Итак, я вернулся к документу и рассмотрел второй способ ведения дел, то есть создание AST. Теперь, если я чего-то не вижу, ничто не объясняет вам, как взять созданный вами AST и подключить его так, чтобы Psych.dump(obj) все еще работал…

Я пытался сделать (без особой надежды):

a = Psych::Nodes::Scalar(full_address.upcase)
a.style = Psych::Nodes::LITTERAL
coder['address'] = a if full_address?

но, очевидно, он не сделал того, на что я надеялся… Я также пробовал:

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

Но я не мог понять, как подключить его к кодеру…

Кроме того, ответ должен работать при выполнении рекурсивных действий, это объект Contact, но можно попросить Domain, который будет содержать несколько контактов, и я хочу, чтобы он был как можно СУХИМ :-)

Итак, у кого-нибудь есть намек на то, как это сделать?


person mat    schedule 03.08.2012    source источник
comment
Вы имеете в виду Syck, а не Sych, как оригинальную библиотеку, верно?   -  person Andrew Grimm    schedule 02.10.2012


Ответы (1)


Если вы хотите создать свой собственный AST, вы не можете использовать Psych.dump. Psych.dump создает свой собственный AST, используя значения по умолчанию Psych. В вашем случае вы хотите настроить процесс создания AST.

Глядя на источник Psych.dump, вы можно увидеть, что он использует Psych::Visitors::YAMLTree для создать АСТ. Вы можете подклассировать это и настроить, как он обрабатывает ваш класс Contact, чтобы получить желаемый результат. В частности, вам необходимо переопределить метод accept .

Вот простой пример, в котором используются только частные случаи класса Contact:

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

Обратите внимание, что класс Psych::Visitors::YAMLTree вызывает encode_with, это полностью обойдет его для вашего класса.

Чтобы использовать это, используйте что-то вроде (это в основном упрощенная версия Psych.dump с использованием MyYAMLTree):

def my_yaml o
  visitor = MyYAMLTree.new
  visitor << o
  visitor.tree.yaml
end

Очевидно, это всего лишь простой пример, но, надеюсь, он укажет вам правильное направление.

person matt    schedule 30.09.2012