2009-02-09 2 views
167

Итак, у меня есть два файла YAML «A» и «B», и я хочу, чтобы содержимое A было вставлено внутри B, либо сплайсировано в существующую структуру данных, например массив, либо как дочерний элемент элемента, например значение для определенного хэш-ключа.Как я могу добавить файл YAML внутри другого?

Возможно ли это вообще? Как? Если нет, указатели на нормативную ссылку?

+0

http://therightstuff.de/2010/01/30/Rake-YAML-And-Inherited-Build-Configuration.aspx – Darragh

+1

Я недавно столкнулся с [HiYaPyCo] (https://github.com/zerwes/hiyapyco) для Python, который делает именно это. Вы можете объединить разные файлы YAML. Это очень хороший модуль Python, который стоит знать. – nowox

ответ

208

Нет, YAML не содержит никаких инструкций «импорт» или «включение».

+6

Вы можете создать обработчик включать !. – clarkevans

+4

@clarkevans уверен, но эта конструкция будет «вне» языка YAML. – jameshfisher

+0

Теперь это возможно. Я добавил ответ ниже ... надеюсь, что это поможет. – daveaspinall

-16

Здесь я использовал Мако

a.txt

это верхняя часть файла
<% включаемого файла = 'b.txt' />
это нижняя часть файла

b.txt

это файл б

test.py

from mako.template import Template 
from mako.lookup import TemplateLookup 
import os 

directory = os.path.dirname(os.path.abspath(__file__)) 
mylookup = TemplateLookup(directories=[directory]) 
mytemplate = Template(filename="a.txt", lookup=mylookup) 
finalsrc = mytemplate.render() 
# finalsrc can be treated as yaml or whatever you like 

$ питон test.py
это верхняя часть файла
это файл б
это нижняя часть файла

+5

ОП спрашивал о включении в YAML, а не о Мако. – jjmontes

7

включает в себя в ямле, как я знаю, напрямую не поддерживаются в ямле, вам придется самим обеспечить механизм, но это, как правило, легко сделать.

Я использовал YAML как язык конфигурации в моих питона приложений, и в этом случае часто определяют convension так:

>>> main.yml <<< 
includes: [ wibble.yml, wobble.yml] 

Тогда в моей (питон) код, который я сделать:

import yaml 
cfg = yaml.load(open("main.yml")) 
for inc in cfg.get("includes", []): 
    cfg.update(yaml.load(open(inc))) 

Единственная нижняя сторона состоит в том, что переменные в include всегда будут переопределять переменные в основном, и нет способа изменить это приоритет, изменив, где в файле main.yml появляется оператор include:

В немного другой точке yaml does not support включает в себя, поскольку ее не спроектировано так же, как и только на основе файла. Что будет включать среднее значение, если вы получили его в ответ на запрос ajax?

67

Ваш вопрос не требует решения Python, но здесь используется PyYAML.

PyYAML позволяет подключать пользовательские конструкторы (например, !include) к загрузчику YAML. Я включил корневой каталог, который можно установить таким образом, чтобы это решение поддерживало относительные и абсолютные ссылки на файлы.

Class-Based Solution

Вот класс на основе решения, что позволяет избежать глобальной корневой переменной моего первоначального ответа.

См. Это gist для аналогичного, более надежного решения Python 3, которое использует метакласс для регистрации пользовательского конструктора.

import yaml 
import os.path 

class Loader(yaml.SafeLoader): 

    def __init__(self, stream): 

     self._root = os.path.split(stream.name)[0] 

     super(Loader, self).__init__(stream) 

    def include(self, node): 

     filename = os.path.join(self._root, self.construct_scalar(node)) 

     with open(filename, 'r') as f: 
      return yaml.load(f, Loader) 

Loader.add_constructor('!include', Loader.include) 

Пример:

foo.yaml

a: 1 
b: 
    - 1.43 
    - 543.55 
c: !include bar.yaml 

bar.yaml

- 3.6 
- [1, 2, 3] 

Теперь файлы могут быть загружены с помощью:

>>> with open('foo.yaml', 'r') as f: 
>>> data = yaml.load(f, Loader) 
>>> data 
{'a': 1, 'b': [1.43, 543.55], 'c': [3.6, [1, 2, 3]]} 
+0

Это интересная функция, спасибо. Но какова цель всех этих манипуляций с root/old_root? Я полагаю, что код 'include' функции можно упростить: ' Защиты включает (загрузчик, узел): "Включите другой файл YAML" "" "" имя_файла = loader.construct_scalar (узел) данных = yaml.load (open (filename)) ' –

+0

Корневой глобальный, так что относительный включает работу на любой глубине, например когда включенные файлы, находящиеся в другом каталоге, содержат файл, относящийся к этому каталогу. Абсолютные также должны работать. Вероятно, существует более чистый способ сделать это без глобальной переменной, возможно, используя собственный класс yaml.Loader. –

+1

Можно ли также иметь что-то вроде этого: foo.yaml: 'а: bla' bar.yaml: ' включают foo.yaml б: blubb' Так что результат будет: '{'a': bla, 'b': blubb} –

5

Расширяя @ ответ Josh_Bode, вот мое собственное решение PyYAML, который имеет преимущество в том, самодостаточным подкласс yaml.Loader. Он не зависит от каких-либо глобальных уровней модуля или от изменения глобального состояния модуля yaml.

import yaml, os 

class IncludeLoader(yaml.Loader):             
    """                   
    yaml.Loader subclass handles "!include path/to/foo.yml" directives in config 
    files. When constructed with a file object, the root path for includes  
    defaults to the directory containing the file, otherwise to the current  
    working directory. In either case, the root path can be overridden by the  
    `root` keyword argument.              

    When an included file F contain its own !include directive, the path is  
    relative to F's location.              

    Example:                  
     YAML file /home/frodo/one-ring.yml:          
      ---                 
      Name: The One Ring              
      Specials:                
       - resize-to-wearer             
      Effects: 
       - !include path/to/invisibility.yml        

     YAML file /home/frodo/path/to/invisibility.yml:       
      ---                 
      Name: invisibility              
      Message: Suddenly you disappear!          

     Loading:                 
      data = IncludeLoader(open('/home/frodo/one-ring.yml', 'r')).get_data() 

     Result:                 
      {'Effects': [{'Message': 'Suddenly you disappear!', 'Name':    
       'invisibility'}], 'Name': 'The One Ring', 'Specials':    
       ['resize-to-wearer']}            
    """                   
    def __init__(self, *args, **kwargs):           
     super(IncludeLoader, self).__init__(*args, **kwargs)      
     self.add_constructor('!include', self._include)       
     if 'root' in kwargs:              
      self.root = kwargs['root']            
     elif isinstance(self.stream, file):          
      self.root = os.path.dirname(self.stream.name)       
     else:                  
      self.root = os.path.curdir            

    def _include(self, loader, node):          
     oldRoot = self.root            
     filename = os.path.join(self.root, loader.construct_scalar(node)) 
     self.root = os.path.dirname(filename)       
     data = yaml.load(open(filename, 'r'))        
     self.root = oldRoot            
     return data              
+2

Наконец-то обзавелся добавлением подхода, основанного на классе, к моему ответу, но вы избили меня до удара :) Примечание. Если вы используете 'yaml.load (f, IncludeLoader)' в '_include' вы можете избежать необходимости замены корня. Кроме того, если вы этого не сделаете, решение не будет работать более чем на один уровень, поскольку включенные данные используют обычный класс 'yaml.Loader'. –

+0

Мне пришлось удалить ключевое слово 'root' из' kwargs' после установки 'self.root', чтобы заставить его работать со строками. Я переместил блок if-else над вызовом 'super'. Может быть, кто-то еще может подтвердить мое открытие или показать мне, как использовать класс со строками и параметр «root». – Woltan

+0

К сожалению, это не работает со ссылками, такие как '' ' включены: & ВКЛЮЧЕНО включают inner.yaml слияния: <<: * ВКЛЮЧЕНО ' '' – antony

-6

Вероятно, это не было поддержано, когда был задан вопрос, но вы можете импортировать другой файл YAML в один:

imports: [/your_location_to_yaml_file/Util.area.yaml] 

Хотя у меня нет какой-либо интернет-ссылку, но это работает для меня.

+3

Это вообще не делает. Он создает сопоставление с последовательностью, состоящей из одной строки «/your_location_to_yaml_file/Util.area.yaml», как значение для ключа «import». – Anthon

18

Если вы используете Symfony's version of YAML, это возможно, например:

imports: 
    - { resource: sub-directory/file.yml } 
    - { resource: sub-directory/another-file.yml } 
+10

Это относится к тому, как Symfony интерпретирует YAML, а не часть самого YAML. – jameshfisher

+3

Да, поэтому я разместил ссылку на документы Symfony. Вопрос спрашивает: «Это вообще возможно? Как?» ... вот как. Не вижу причин для понижения. – daveaspinall

+2

Я не спустил вас вниз; Я просто указываю, что это характерно для Symfony YAML. – jameshfisher

1

К сожалению YAML не обеспечивает этого в стандарте.

Но если вы используете Ruby, есть камень обеспечения функциональности вы просите, расширив библиотеку рубин YAML: https://github.com/entwanderer/yaml_extend

0

С Symfony, его обращение YAML косвенно позволит вам гнездо yaml файлов. Фокус в том, чтобы использовать опцию parameters. например:

common.yml

parameters: 
    yaml_to_repeat: 
     option: "value" 
     foo: 
      - "bar" 
      - "baz" 

config.yml

imports: 
    - { resource: common.yml } 
whatever: 
    thing: "%yaml_to_repeat%" 
    other_thing: "%yaml_to_repeat%" 

Результат будет таким же, как:

whatever: 
    thing: 
     option: "value" 
     foo: 
      - "bar" 
      - "baz" 
    other_thing: 
     option: "value" 
     foo: 
      - "bar" 
      - "baz" 
0

Я думаю, что решение используется by @ maxy-B выглядит великолепно. Однако мне это не удавалось с вложенными включениями. Например, если config_1.yaml включает config_2.yaml, который включает config_3.yaml, возникла проблема с загрузчиком. Однако, если вы просто укажете новый класс загрузчика на себя при загрузке, он будет работать!В частности, если заменить старую _include функции с очень немного измененной версией:

def _include(self, loader, node):          
    oldRoot = self.root            
    filename = os.path.join(self.root, loader.construct_scalar(node)) 
    self.root = os.path.dirname(filename)       
    data = yaml.load(open(filename, 'r'), loader = IncludeLoader)        
    self.root = oldRoot            
    return data 

Поразмыслив, я согласен с другими комментариями, что вложенная нагрузка не подходит для YAML в общем, как входной поток не может быть файл, но это очень полезно!

Смежные вопросы