Как я могу сортировать файлы YAML?

Я пытаюсь сортировать файл YAML для i18n с Ruby, чтобы лучше управлять и упорядочивать новые переводы, но мне было интересно, есть ли что-то, чтобы облегчить задачу.

Я нашел запись файла YAML, поэтому я могу написать хэш в файл, но моя проблема заключается в правильном сортировке хэша. Если я получил hash h, h.sort возвращает массив, и я до сих пор не понял простой способ сделать это.

У меня есть файлы YAML, подобные этому:

pt-br:    
  global:
    misc:
      total: "Total"
      all: "Todos"
      close: "Fechar"
      cancel: "Cancelar"

    crud:
      access: "Acessar"
      back: "Voltar"
      edit: "Editar"
      confirm: "Confirmar"
      send: "Enviar"

...

(Файлы больше, чем это)

Но я хочу отсортировать их следующим образом:

pt-br:    
  global:
    crud:
      access: "Acessar"
      back: "Voltar"
      confirm: "Confirmar"
      edit: "Editar"
      send: "Enviar"

    misc:
      all: "Todos"
      cancel: "Cancelar"
      close: "Fechar"          
      total: "Total"

Я думал, что какой-то простой рекурсивный метод может мне помочь:

def translation_sort(h)
  if h.class == Hash
    h = h.sort
    h.each{|item| translation_sort(item)}
  end
  h
end

require "yaml"
h=YAML.load_file(File.open("~/pt-br.sample.yml"))
translation_sort(h)

Ответы

Ответ 1

На самом деле это хороший вопрос. Вы хотите глубоко сортировать хэши. Поэтому мне не нравится изобретать колесо, а затем я искал хорошую реализацию, и я нашел тот, который мне нравится. Посмотрите на него https://gist.github.com/1083930. Он работает нормально.

Ответ 2

Вы не должны использовать библиотеку YAML, как предложено в других ответах. Это испортит форматирование длинных строковых значений, удалит ваши комментарии и выплюнет нечитаемые char экраны при использовании акцентов и специальных символов (что вы будете делать, так как вы делаете i18n). Используйте этот камень, который я создал:

https://github.com/redealumni/i18n_yaml_sorter

Он будет сортировать строки только в файле, поэтому все останется таким же, как на исходном yaml (ваши акценты, конструкция YAML, которую вы использовали для ввода строк, отступов и т.д.). Он будет работать с глубоко вложенными ямлами, и результаты довольно прочные. Драгоценный камень включает тесты, и он хорош для рубина 1,8 или 1,9.

Он поставляется с пакетом TextMate Bundle (Shift + Command + S) и Rails, поэтому вы можете сортировать файлы легко и мгновенно в своем редакторе. Это очень быстро.

Чтобы проиллюстрировать разницу:

Оригинал:

  pt-BR:
    # Note how this is a nice way of inputing
    # paragraphs of text in YAML. 
    apples: >
      Maçãs são boas,
      só não coma 
      seus iPods!
    grapes: Não comemos elas.
    bananas: |
      Bananas são "legais":
        - Elas são <b> doces </b>.
        isto: não é chave

      Por isto todos gostam de bananas!

Результаты по YAML:: dump:

  pt-BR: 
    apples: "Ma\xC3\xA7\xC3\xA3s s\xC3\xA3o boas, s\xC3\xB3 n\xC3\xA3o coma  seus iPods!\n"
    bananas: "Bananas s\xC3\xA3o \"legais\":\n  - Elas s\xC3\xA3o <b> doces </b>.\n  isto: n\xC3\xA3o \xC3\xA9 chave\n\n\ Por isto todos gostam de bananas!\n"
    grapes: "N\xC3\xA3o comemos elas."

Результаты по i18n_yaml_sorter:

  pt-BR:
    # Note how this is a nice way of inputing
    # paragraphs of text in YAML. 
    apples: >
      Maçãs são boas,
      só não coma 
      seus iPods!
    bananas: |
      Bananas são "legais":
        - Elas são <b> doces </b>.
        isto: não é chave

      Por isto todos gostam de bananas!
    grapes: Não comemos elas.

Ответ 3

https://gist.github.com/1083930 работает не так, как я ожидал. Он глубоко сортирует НЕ ТОЛЬКО хеш-ключи, НО ТАКЖЕ хэш-значения. В моих случаях использования, когда необходима глубокая сортировка хэша, хеш всегда является деревом, где ключи являются метками, а значения - (под) деревьями (если хэши) или листьями (в противном случае). Мне нужно глубоко сортировать только метки деревьев.

Я получил это

before: {"a":[2,10,{"5":null,"1":null,"3":null}],"x":{"5":null,"1":null,"3":null},"a2":{"5":[2,10,5],"1":null,"3":null}}
after:  {"a":[2,10,{"5":null,"1":null,"3":null}],"a2":{"1":null,"3":null,"5":[2,10,5]},"x":{"1":null,"3":null,"5":null}}

с этим

require 'active_support'

def deeply_sort_hash(object)
  return object unless object.is_a?(Hash)
  hash = RUBY_VERSION >= '1.9' ? Hash.new : ActiveSupport::OrderedHash.new
  object.each { |k, v| hash[k] = deeply_sort_hash(v) }
  sorted = hash.sort { |a, b| a[0].to_s <=> b[0].to_s }
  hash.class[sorted]
end

Ответ 4

ОБНОВЛЕНИЕ Апрель 2014:

Использование Rails 3.2.13, Ruby 1.9.3p489:

Я только использовал жемчуг i18n_yaml_sorter (https://github.com/redealumni/i18n_yaml_sorter).

Просто добавьте в свой Gemfile:

gem 'i18n_yaml_sorter', group: :development

Затем запустите задачу rake для сортировки файлов локалей:

rake i18n:sort

Работал отлично, хотя этот камень был последним автором 2 года назад. Это заняло 5 минут максимум.

Ответ 5

В Ruby 1.8 хеши не имеют определенного порядка, поэтому вы не можете просто сортировать их.

Вы можете обезвредить патч/перезаписать метод to_yaml Hash следующим образом:

#!/usr/local/bin/ruby -w

require 'yaml'

class Hash
  def to_yaml(opts = {})
    YAML::quick_emit(self, opts) do |out|
      out.map(taguri, to_yaml_style) do |map|
        keys.sort.each do |k|
          v = self[k]
          map.add(k, v)
        end
      end
    end
  end
end

dict = YAML.load($<.read)

puts dict.to_yaml

Конечно, детали могут зависеть от вашей версии YAML/Ruby. Пример выше для Ruby 1.8.6.

Ответ 6

Вот еще одна альтернатива для всех, кто сталкивается с этим.

require 'yaml'

yaml = YAML.load(IO.read(File.join(File.dirname(__FILE__), 'example.yml')))

@yml_string = "---\n"

def recursive_hash_to_yml_string(hash, depth=0)
  spacer = ""
  depth.times { spacer += "  "}
  hash.keys.sort.each do |sorted_key|
    @yml_string += spacer + sorted_key + ": "
    if hash[sorted_key].is_a?(Hash)
      @yml_string += "\n"
      recursive_hash_to_yml_string(hash[sorted_key], depth+1)
    else
      @yml_string += "#{hash[sorted_key].to_s}\n"
    end
  end
end

recursive_hash_to_yml_string(yaml)

open(File.join(File.dirname(__FILE__), 'example.yml'), 'w') { |f|
  f.write @yml_string
}

Ответ 9

К сожалению, YAML::quick_emit устарел и больше не доступен в новых сборках самоцвета Psych. Если вы хотите, чтобы ваши ключи хеша сортировались при сериализации в yaml, вам придется использовать следующий патч обезьяны:

class Hash
    def to_yaml opts={}
        return Psych.dump(self.clone.sort.to_h)
    end
end