Question

J'ai donc deux fichiers YAML, « A » et « B » et je veux le contenu de A à insérer à l'intérieur de B, soit épissés dans la structure de données existantes, comme un tableau, ou comme un enfant d'un élément, comme la valeur d'une certaine clé de hachage.

Est-ce possible? Comment? Dans le cas contraire, tous les pointeurs à une référence normative?

Était-ce utile?

La solution

Non, YAML ne comprend aucune sorte de déclaration « importation » ou « inclure ».

Autres conseils

Votre question ne demande pas une solution Python, mais voici une en utilisant PyYAML .

PyYAML vous permet d'attacher des constructeurs personnalisés (tels que !include) au chargeur YAML. J'ai inclus un répertoire racine qui peut être réglé de telle sorte que cette solution prend en charge les références de fichiers relatifs et absolus.

Solution Class-Based

Voici une solution de classe, qui évite la variable globale racine de ma réponse originale.

Voir cette essentiel pour un similaire, Python plus robuste 3 solution qui utilise un métaclasse pour vous inscrire le constructeur personnalisé.

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)

Un exemple:

foo.yaml

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

bar.yaml

- 3.6
- [1, 2, 3]

Maintenant, les fichiers peuvent être chargés à l'aide:

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

Comprend ne sont pas directement pris en charge dans YAML pour autant que je sache, vous devrez fournir un mécanisme vous cependant, ce qui est généralement facile à faire.

Je l'ai utilisé YAML comme langue de configuration dans mes applications de python, et dans ce cas définissent souvent une convention comme ceci:

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

Alors dans mon code (python) Je fais:

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

Le seul inconvénient est que les variables includes toujours passer outre les variables principales, et il n'y a aucun moyen de changer cette priorité en changeant où « comprend: la déclaration apparaît dans le fichier main.yml

.

Sur un point légèrement différent, YAML ne prend pas en charge inclut comme pas vraiment conçu comme comme exclusivement comme une marque à base de fichiers vers le haut. Que serait un moyen inclure si vous avez obtenu une réponse à une requête AJAX?

Développant @ réponse de Josh_Bode, voici ma propre solution PyYAML, qui a l'avantage d'être une sous-classe autonome de yaml.Loader. Il ne dépend pas de tout niveau de GLOBALS module ou sur la modification de l'état global du module 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                                                      

Pour les utilisateurs de Python, vous pouvez essayer PyYAML-include .

Installer

pip install pyyaml-include

Utilisation

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)

Tenez compte que nous avons des fichiers YAML:

├── 0.yaml
└── include.d
    ├── 1.yaml
    └── 2.yaml
  • Contenu de l » 1.yaml:
name: "1"
  • Contenu de l » 2.yaml:
name: "2"

Inclure les fichiers par nom

  • Le haut niveau:

    Si 0.yaml était:

!include include.d/1.yaml

Nous allons obtenir:

{"name": "1"}
  • mapping:

    Si 0.yaml était:

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

Nous allons obtenir:

  file1:
    name: "1"
  file2:
    name: "2"
  • Dans la séquence:

    Si 0.yaml était:

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

Nous allons obtenir:

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

Remarque :

     

Nom du fichier peut être absolu (comme /usr/conf/1.5/Make.yml) ou d'un parent (comme ../../cfg/img.yml).

Inclure des fichiers par des jokers

Nom du fichier peut contenir des caractères génériques de style shell. Les données chargées à partir du fichier (s) trouvé par des jokers seront définies dans une séquence.

Si 0.yaml était:

files: !include include.d/*.yaml

Nous allons obtenir:

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

Remarque :

     

Pour permettre argument recursive, nous écrirons la balise !include en mode Mapping ou Sequence:

  • Arguments en mode Sequence:
!include [tests/data/include.d/**/*.yaml, true]
  • Arguments en mode Mapping:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}

Je pense que la solution utilisée par @ Maxy-B ressemble beaucoup. Cependant, il n'a pas réussi pour moi avec des inclusions imbriquées. Par exemple, si config_1.yaml comprend config_2.yaml, qui comprend config_3.yaml il y avait un problème avec le chargeur. Toutefois, si vous pointez simplement la nouvelle classe de chargement à la charge elle-même, ça marche! Plus précisément, si l'on remplace l'ancienne fonction _include avec la version très légèrement modifiée:

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

À la réflexion, je suis d'accord avec les autres commentaires, que le chargement imbriqué ne convient pas à yaml en général que le flux d'entrée ne peut pas être un fichier, mais il est très utile!

Peut-être que cela pourrait vous inspirer, essayer d'aligner les conventions de 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

Standard YAML 1.2 ne comprend pas nativement cette fonctionnalité. Néanmoins, de nombreuses implémentations fournit une extension de le faire.

Je vous présente une façon d'y parvenir avec Java et snakeyaml:1.24 (bibliothèque Java pour analyser / émettre des fichiers YAML) qui permet de créer une étiquette de YAML personnalisé pour atteindre l'objectif suivant (vous verrez que je l'utilise pour charger des suites de test définies dans plusieurs fichiers YAML et que je l'ai fait travailler comme une liste de comprend un noeud test: cible):

# ... yaml prev stuff

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

# ... more yaml document

Voici le Java d'une classe qui permet le traitement de la balise !include. Les fichiers sont chargés à partir classpath (répertoire des ressources 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));
                }
            }
        }

    }

}

Symfony , sa gestion de YAML indirectement vous permettra d'imbriquer les fichiers YAML. L'astuce consiste à utiliser l'option parameters. par exemple:

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

Le résultat sera le même que:

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

Probablement, il n'a pas été soutenu quand a posé la question, mais vous pouvez importer un autre fichier YAML en un seul:

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

Bien que je n'ai aucune référence en ligne, mais cela fonctionne pour moi.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top