Вопрос

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

Это вообще возможно?Как?Если нет, есть ли какие-нибудь ссылки на нормативную ссылку?

Это было полезно?

Решение

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

Другие советы

Ваш вопрос не требует решения Python, но вот одно из них, использующее ПиЯМЛ.

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

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

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

Насколько мне известно, включения не поддерживаются напрямую в 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)))

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

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

Продолжая ответ @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                                                      

Пользователи Python могут попробовать pyyaml-включить.

Установить

pip install pyyaml-include

Применение

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)

Считайте, что у нас есть такие ЯМЛ файлы:

├── 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"

Примечание:

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

Включить файлы по подстановочным знакам

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

Если 0.yaml был:

files: !include include.d/*.yaml

Мы получим:

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

Примечание:

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

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

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

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

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

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

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

https://docs.openstack.org/infra/jenkins-job-builder/definition.html#inclusion-tags

- job: name: test-job-include-raw-1 builders: - shell: !include-raw: include-raw001-hello-world.sh

Стандартный 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 ярлык.Файлы загружаются из пути к классам (каталог ресурсов 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));
                }
            }
        }

    }

}

С Симфония, его обработка 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"

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top