В продолжение темы про конфигурационные файлы, взглянем на более распространённый формат YAML. YAML (YAML Ain’t Markup Language) - это надмножество над JSON с упрощенным синтаксисом.

Синтаксис

Основные типы

Аналогично TOML, про который я писал ранее, в YAML можно использовать различные типы данных:

    # Комментарии начинаются со знака "#"
    
    # Пары ключ-значение разделяются через ":"
    # Строки, как значения, могут быть заключены в кавычки (одинарные, двойные), 
    # но не обязаны
    key: значение
    another_key: еще одно значение
    
    # Числа можно использовать в экспоненциальной форме
    a_number_value: 100
    scientific_notation: 1e+12
    
    # Логические значения требуется записывать как true или false.
    # 1 и 0 будут считаться в этом контексте как строки.
    boolean: true
    
    # Значение может быть null.
    null_value: null
    
    # Ключ может содержать пробелы.
    key with spaces: value
    
    # Многострочные строки могут быть записаны двумя способами: 
    # 'literal block' (используя символ '|'),
    # или 'folded block' (используя символ '>').
    literal_block: |
        Весь этот блок текста - значение 'literal_block'. 
        Переносы строк сохранятся.
    
        Литерал считается значением текущего ключа до тех пор, 
        пока отступ слева не будет уменьшен до уровня ключа. 
        Ведущие пробелы в итоговой строке будут опущены.
    
            Все строки, отступ у которых больше обычного (как в этой, например), 
            сохранят отступы. У этой строки отступ будет равен четырём.
    folded_style: >
        Этот блок текста - значение 'folded_style'. 
        Но здесь переносы строк будут заменены пробелами.
        Пустые строки, как предыдущая строка, заменяются переносами строк.
    
            Строки с бОльшим отступом сохраняют переносы строк как в литералах - 
            этот текст будет располагаться на двух строках.

Коллекции

    # Вложенность достигается за счёт отступов.
    # Этот фрагмент эквивалентен следующему JSON:
    # { "a_nested_map: { 
    #       "key": "value",
    #       "another_key: "Another Value", 
    #       "another_nested_map: {
    #           "hello": "hello"
    #       }
    #   } 
    # }
    a_nested_map:
        key: value
        another_key: Another Value
        another_nested_map:
            hello: hello
    
    # Ключом словаря может быть не только строка:
    0.25: a float key
    
    # Ключ может быть сложным. Например, многострочными.
    # Для этого необходимо использовать символ "?" с последующим пробелом 
    # для обозначения начала сложного ключа:
    ? |
        Многострочный
        ключ из двух строк
    : значение, соответствующее сложному ключу
    
    # YAML позволяет создавать словари с составным ключом и значением-перечислением.
    # Однако, не все парсеры поддерживают данный синтаксис.
    # Пример:
    ? - Manchester United
      - Real Madrid
    : [ 2001-01-01, 2002-02-02 ]
    # Эквивалентный JSON: 
    #  'Manchester United,Real Madrid': 
    # [ Mon Jan 01 2001 04:00:00 GMT+0400 (Russia TZ 3 Standard Time),
    # Sat Feb 02 2002 04:00:00 GMT+0400 (Russia TZ 3 Standard Time) ] }
    
    # Массивы или списки:
    a_sequence:
        - Item 1
        - Item 2
        - 0.5 # Последовательность может содержать различные типы данных.
        - Item 4
        - key: value
          another_key: another_value
        -
            - This is a sequence # Последовательность можно содержать подпоследовательности
            - inside another sequence
    
    # Так как YAML - это надмножество JSON, 
    # то можно использовать синтаксис JSON для словарей и массивов:
    json_map: {"key": "value"}
    json_seq: [3, 2, 1, "takeoff"]

Дополнительные возможности

    # YAML поддерживает возможности использования anchor-ов. 
    # Anchor похож на переменную: оба ключа в примере ниже будут иметь одно значение:
    anchored_content: **&anchor_name** Эта строка будет значением для двух ключей.
    other_anchor: ***anchor_name**
    
    # Anchor можно использовать для дублирования/наследования свойств:
    base: **&base**
        name: Все имеют одинаковые имена
    
    foo: &foo
        **<<: *base**
        age: 10
    
    bar: &bar
        **<<: *base**
        age: 20
    
    # foo и bar будут иметь ключ "name" со значением "Все имеют одинаковые имена"
    
    # YAML позволяет использовать теги, которые позволяют явно указывать типы:
    explicit_string: **!!str** 0.5
    
    # Некоторые парсеры имеют собственные теги.
    # Например, тег в Python для комплексных чисел:
    python_complex_number: !!python/complex 1+2j

Дополнительные типы данных

    # В YAML можно использовать даты в ISO-формате:
    datetime: 2001-12-15T02:59:43.1Z
    datetime_with_spaces: 2001-12-14 21:59:43.10 -5
    date: 2002-12-14
    
    # Тег **!!binary **говорит о том, что следующая за ним строка - 
    # это закодированные в base64 бинарные данные.
    gif_file: !!binary |
        R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
        OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+
        +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC
        AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=
    
    # YAML содержит тип set:
    set:
        ? item1
        ? item2
        ? item3
    
    # Аналогично Python, set - это словарь, в котором значения всех ключей - null:
    set2:
        item1: null
        item2: null
        item3: null

Работа с YAML в Go

Все возможности YAML поддерживает только один парсер: go-yaml.

API

Простейший пример выглядит элементарно:

    package main
    
    import (
           "gopkg.in/yaml.v2"
           "log"
           "fmt"
           "time"
    )
    
    var data = `
    a: Easy!
    b:
      c: 2
      d: [3, 4]
    m: [ "2001-01-01T15:04:05Z", "2002-02-02T15:04:05Z" ]
    
    `
    
    type T struct {
           A string
           B struct {
                  RenamedC int   `yaml:"c"`
                  D        []int `yaml:",flow"`
           }
           M []time.Time
    }
    
    func main() {
           t := T{}
    
           err := yaml.Unmarshal([]byte(data), &t)
           if err != nil {
                  log.Fatalf("error: %v", err)
           }
           fmt.Printf("--- t:\n%v\n\n", t)
    }

Из коробки есть возможность распарисить строку в структуру (этим уже не удивишь). Документация утверждает, что библиотека поддерживает anchor-ы и теги. Проверим на практике:

    type T1 struct {
        A string `yaml:"a"`
        B string `yaml:"b"`
        I string `yaml:"i"`
        Base struct{
            Name string
        }
        Child struct {
            Name string
            Address string
        }
    }
    
    func anchorsAndTags() {
     var data = `
    a: &x Simple Value
    b: *x
    i: !!binary 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
    base: &base
     name: Одно имя на всех
    child:
     <

В общем-то, всё работает как надо:

  • после выполнения этой программы в корне проекта появляется файл img.png с закодированным изображением,
  • значения свойств A и B структуры - совпадают и равны Simple Value,
  • свойства Base.Name равно свойству Child.Name, а свойство Child.Address равно Мой адрес не дом и не улица.

Скорость на больших конфигурационных файлах

Для анализа возьму файл, который использовал в примерах для TOML. Размер полученного файла - примерно 1Мб. Не bigdata, но сейчас этого будет достаточно. Для профилирования использую библиотеку [stopwatch](https://github.com/fatih/stopwatch), которую так же использовал в предыдущий раз для профилирования парсинга TOML.

    sw := stopwatch.New()
    sw.Start(0)
    singleYaml() 
    log.Printf("Yaml simple decode: %s", sw.ElapsedTime().String())
    
    sw.Reset()
    sw.Start(0)
    for i := 0; i < 10; i++ {
           largeYaml() 
    }
    log.Printf("Large yaml 10 times: %s", sw.ElapsedTime().String())

Замерил скорость работы для маленького и для большого YAML-файлов. Результаты получились очень привлекательные: 3.0017ms для первого и 186.0448ms для второго случаев. Для сравнения, при разборе TOML лучший результат для аналогичного файла был 2.49 секунды.

Итого

YAML - более распространённый на данный момент формат конфигурационных файлов. Возможно, это, в том числе, является причиной более производительной работы. Так же радуют интересные фичи YAML, anchor-ы и теги, позволяющие удобнее организовывать данные. Все примеры можно найти в репозитории на bitbucket.

Ссылки

  1. http://yaml.org/
  2. http://docs.ansible.com/ansible/YAMLSyntax.html
  3. https://learnxinyminutes.com/docs/yaml/
  4. http://nodeca.github.io/js-yaml/
  5. https://github.com/Animosity/CraftIRC/wiki/Complete-idiot’s-introduction-to-yaml