Как я могу включить файл YAML в другой?

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

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

Ответ 1

Нет, YAML не содержит никаких выражений "import" или "include".

Ответ 2

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

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

Основанное на классе решение

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

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

import yaml
import os

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]]}

Ответ 3

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

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

Ответ 4

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

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

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

Затем в моем (python) коде я делаю:

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

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

С другой стороны, YAML не поддерживает include, так как он на самом деле не разработан так же эксклюзивно, как разметка на основе файлов. Что будет означать включение, если вы получите его в ответ на запрос AJAX?

Ответ 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 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                                                      

Ответ 6

Для пользователей Python вы можете попробовать pyyaml-include.

Установить

pip install pyyaml-include

Usage

Usage
import yaml
from yamlinclude import YamlIncludeConstructor

YamlIncludeConstructor.add_to_loader_class(loader_class=yaml.FullLoader, base_dir='/your/conf/dir')

with open('0.yaml') as f:
    data = yaml.load(f, Loader=yaml.FullLoader)

print(data)

Предположим, у нас есть такие файлы YAML :

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • 1.yaml содержание:
name: "1"
  • 2.yaml содержание:
name: "2"

Включить файлы по имени

  • На верхнем уровне:

    Если 0.yaml было:

!include include.d/1.yaml

Мы получим:

{"name": "1"}
  • В картировании:

    Если 0.yaml было:

file1: !include include.d/1.yaml
file2: !include include.d/2.yaml

Мы получим:

  file1:
    name: "1"
  file2:
    name: "2"
  • В последовательности:

    Если 0.yaml было:

files:
  - !include include.d/1.yaml
  - !include include.d/2.yaml

Мы получим:

files:
  - name: "1"
  - name: "2"

Note:

Имя файла может быть абсолютным (например, /usr/conf/1.5/Make.yml) или относительным (например, ../../cfg/img.yml).

Включить файлы по групповым символам

Имя файла может содержать подстановочные знаки в стиле оболочки. Данные, загруженные из файла (-ов), найденных с помощью подстановочных знаков, будут заданы в последовательности.

Если 0.yaml было:

files: !include include.d/*.yaml

Мы получим:

files:
  - name: "1"
  - name: "2"

Note:

  • Для Python>=3.5, если аргумент recursive тега !include YAML равен true, шаблон '**' будет сопоставлять любые файлы и ноль или более каталогов и подкаталогов.
  • Использование шаблона '**' в больших деревьях каталогов может потребовать чрезмерного количества времени из-за рекурсивного поиска.

Чтобы включить аргумент recursive, мы напишем тег !include в режиме Mapping или Sequence:

  • Аргументы в режиме Sequence:
!include [tests/data/include.d/**/*.yaml, true]
  • Аргументы в режиме Mapping:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

Ответ 7

К сожалению, YAML не предоставляет это в своем стандарте.

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

Ответ 8

Я думаю, что решение, используемое @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 в целом, поскольку входной поток может быть не файлом, но это очень полезно!

Ответ 10

Стандарт YAML 1.2 изначально не включает эту функцию. Тем не менее, многие реализации предоставляют некоторое расширение для этого.

Я представляю способ достижения этого с помощью Java и snakeyaml:1.24 (библиотеки Java для синтаксического анализа/передачи файлов YAML), которая позволяет создать собственный тег YAML для достижения следующей цели (вы увидите, что я использую его для загрузки наборов тестов, определенных в несколько файлов YAML, которые я включил в список включений для целевого узла test:):

# ... yaml prev stuff

tests: !include
  - '1.hello-test-suite.yaml'
  - '3.foo-test-suite.yaml'
  - '2.bar-test-suite.yaml'

# ... more yaml document

Здесь представлен одноклассный Java, который позволяет обрабатывать тег !include. Файлы загружаются из classpath (каталог ресурсов Maven):

/**
 * Custom YAML loader. It adds support to the custom !include tag which allows splitting a YAML file across several
 * files for a better organization of YAML tests.
 */
@Slf4j   // <-- This is a Lombok annotation to auto-generate logger
public class MyYamlLoader {

    private static final Constructor CUSTOM_CONSTRUCTOR = new MyYamlConstructor();

    private MyYamlLoader() {
    }

    /**
     * Parse the only YAML document in a stream and produce the Java Map. It provides support for the custom !include
     * YAML tag to split YAML contents across several files.
     */
    public static Map<String, Object> load(InputStream inputStream) {
        return new Yaml(CUSTOM_CONSTRUCTOR)
                .load(inputStream);
    }


    /**
     * Custom SnakeYAML constructor that registers custom tags.
     */
    private static class MyYamlConstructor extends Constructor {

        private static final String TAG_INCLUDE = "!include";

        MyYamlConstructor() {
            // Register custom tags
            yamlConstructors.put(new Tag(TAG_INCLUDE), new IncludeConstruct());
        }

        /**
         * The actual include tag construct.
         */
        private static class IncludeConstruct implements Construct {

            @Override
            public Object construct(Node node) {
                List<Node> inclusions = castToSequenceNode(node);
                return parseInclusions(inclusions);
            }

            @Override
            public void construct2ndStep(Node node, Object object) {
                // do nothing
            }

            private List<Node> castToSequenceNode(Node node) {
                try {
                    return ((SequenceNode) node).getValue();

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The !import value must be a sequence node, but " +
                            "'%s' found.", node));
                }
            }

            private Object parseInclusions(List<Node> inclusions) {

                List<InputStream> inputStreams = inputStreams(inclusions);

                try (final SequenceInputStream sequencedInputStream =
                             new SequenceInputStream(Collections.enumeration(inputStreams))) {

                    return new Yaml(CUSTOM_CONSTRUCTOR)
                            .load(sequencedInputStream);

                } catch (IOException e) {
                    log.error("Error closing the stream.", e);
                    return null;
                }
            }

            private List<InputStream> inputStreams(List<Node> scalarNodes) {
                return scalarNodes.stream()
                        .map(this::inputStream)
                        .collect(toList());
            }

            private InputStream inputStream(Node scalarNode) {
                String filePath = castToScalarNode(scalarNode).getValue();
                final InputStream is = getClass().getClassLoader().getResourceAsStream(filePath);
                Assert.notNull(is, String.format("Resource file %s not found.", filePath));
                return is;
            }

            private ScalarNode castToScalarNode(Node scalarNode) {
                try {
                    return ((ScalarNode) scalarNode);

                } catch (ClassCastException e) {
                    throw new IllegalArgumentException(String.format("The value must be a scalar node, but '%s' found" +
                            ".", scalarNode));
                }
            }
        }

    }

}

Ответ 11

С 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"

Ответ 12

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

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

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

Ответ 13

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

a.txt

это верхняя часть файла a. <% include file = 'b.txt'/" >
это нижняя часть файла

b.txt

это файл b

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

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

 это файл b
 это нижняя часть файла