Pergunta

Então, eu tenho dois arquivos YAML, "A" e "B" e eu quero o conteúdo de A para ser inserido dentro B, seja emendado na estrutura de dados existente, como um array, ou como filho de um elemento, como o valor para uma determinada chave hash.

Isto é possível em tudo? Quão? Se não, qualquer ponteiros para uma referência normativa?

Foi útil?

Solução

Não, YAML não inclui qualquer tipo de "importação" ou "incluir" declaração.

Outras dicas

A sua questão não pede uma solução Python, mas aqui está um usando PyYAML .

PyYAML permite anexar construtores personalizados (tais como !include) para o carregador YAML. Eu incluí um diretório raiz que pode ser configurado de modo a que esta solução suporta referências relativas e absolutas de arquivo.

Class-Based Solution

Aqui é uma solução baseada em classes, que evita a variável raiz mundial da minha resposta original.

Veja este essência para uma mais robusta solução semelhante, Python 3 que usa um metaclass a registar o construtor personalizado.

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)

Um exemplo:

foo.yaml

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

bar.yaml

- 3.6
- [1, 2, 3]

Agora, os arquivos podem ser carregados usando:

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

Se você estiver usando a versão do Symfony de YAML , isso é possível, como este:

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

Inclui não são suportados diretamente no YAML, tanto quanto eu sei, você terá que fornecer um mecanismo de si mesmo no entanto, esta é geralmente fácil de fazer.

Eu tenho usado YAML como uma linguagem de configuração em meus aplicativos Python, e neste caso, muitas vezes definir uma convenção como esta:

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

Em seguida, no meu código (Python) eu faço:

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

A única desvantagem é que as variáveis ??no inclui sempre substituirá as variáveis ??no principal, e não há nenhuma maneira de mudar isso precedência alterando onde o "inclui:. Aparece instrução no arquivo main.yml

Em um ponto ligeiramente diferente, YAML não suporta inclui como não é realmente concebido como como exclusivamente como uma marca baseada em arquivo para cima. O que seria um incluem dizer, se você tem isso em resposta a um pedido de AJAX?

Expandindo resposta da @ Josh_Bode, aqui está a minha própria solução PyYAML, que tem a vantagem de ser uma subclasse independente de yaml.Loader. Ele não depende de qualquer globals de nível de módulo, ou modificar o estado global do módulo 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                                                      

Para usuários de Python, você pode tentar PyYAML-incluem .

Instalar

pip install pyyaml-include

Uso

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)

Considere que temos tais YAML arquivos:

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • 1.yaml de conteúdo:
name: "1"
  • 2.yaml de conteúdo:
name: "2"

Incluir arquivos por nome

  • No nível superior:

    Se 0.yaml foi:

!include include.d/1.yaml

Nós vamos chegar:

{"name": "1"}
  • No mapeamento:

    Se 0.yaml foi:

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

Nós vamos chegar:

  file1:
    name: "1"
  file2:
    name: "2"
  • Na sequência:

    Se 0.yaml foi:

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

Nós vamos chegar:

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

? Nota :

O nome do arquivo pode ser absoluto (como /usr/conf/1.5/Make.yml) ou relativa (como ../../cfg/img.yml).

Incluir arquivos por wildcards

O nome do arquivo pode conter wildcards de estilo concha. Os dados carregados a partir do ficheiro (s) encontrada por caracteres especiais serão estabelecidos numa sequência.

Se 0.yaml foi:

files: !include include.d/*.yaml

Nós vamos chegar:

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

? Nota :

  • Para Python>=3.5, se o argumento recursive de !include YAML tag é true, o “**” padrão irá corresponder a todos os arquivos e Zero ou mais diretórios e subdiretórios.
  • Usando o padrão “**” em grandes árvores de diretório pode consumir uma quantidade excessiva de tempo por causa de pesquisa recursiva.

A fim de permitir argumento recursive, vamos escrever a tag !include no modo Mapping ou Sequence:

  • Os argumentos em modo Sequence:
!include [tests/data/include.d/**/*.yaml, true]
  • Os argumentos em modo Mapping:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

Infelizmente YAML não fornece isso em seu padrão.

Mas se você estiver usando Ruby, há uma jóia fornecer a funcionalidade que você está pedindo, alargando a biblioteca Ruby YAML: https://github.com/entwanderer/yaml_extend

Eu acho que a solução usada por @ maxy-B parece ótimo. No entanto, ele não teve sucesso para mim, com inclusões aninhados. Por exemplo, se config_1.yaml inclui config_2.yaml, que inclui config_3.yaml havia um problema com o carregador. No entanto, se você simplesmente apontar a nova classe carregador para si sobre a carga, ele funciona! Especificamente, se substituirmos a função _Include velho com a versão ligeiramente modificada:

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

Após a reflexão Concordo com os outros comentários, que o carregamento aninhada não é apropriado para yaml em geral, como o fluxo de entrada não pode ser um arquivo, mas é muito útil!

Talvez isso pode inspirá-lo, tente alinhar a convenções 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

Padrão YAML 1.2 não inclui nativamente este recurso. No entanto muitas implementações fornece alguma extensão para fazê-lo.

Eu apresento uma maneira de alcançá-la com Java e snakeyaml:1.24 (biblioteca Java para emitir arquivos de análise / YAML), que permite a criação de um costume YAML tag para alcançar o seguinte objetivo (você vai ver que eu estou usando-o para suites de teste de carga definidos em vários arquivos YAML e que fez funcionar como uma lista de inclui um nó de destino test:):

# ... yaml prev stuff

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

# ... more yaml document

Aqui está o Java de uma classe que permite o processamento da tag !include. Os arquivos são carregados a partir de classpath (diretório de recursos do 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));
                }
            }
        }

    }

}

Com Symfony , sua manipulação de yaml, indiretamente permitem arquivos YAML ninho. O truque é fazer uso da opção parameters. por exemplo:

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

O resultado será o mesmo que:

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

Provavelmente não foi suportada quando pergunta foi feita, mas você pode importar outro arquivo YAML em um:

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

Embora eu não tenho qualquer referência online, mas isso funciona para mim.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top