2009-02-09 5 views
167

J'ai donc deux fichiers YAML, "A" et "B" et je veux que le contenu de A soit inséré dans B, soit épissé dans la structure de données existante, comme un tableau, soit comme enfant d'un élément, comme la valeur d'une certaine clé de hachage.Comment puis-je inclure un fichier YAML dans un autre?

Est-ce possible? Comment? Si non, des pointeurs vers une référence normative?

+0

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

+1

Je suis récemment tombé sur [HiYaPyCo] (https://github.com/zerwes/hiyapyco) pour Python qui fait exactement cela. Vous pouvez fusionner différents fichiers YAML ensemble. C'est un très bon module Python qui mérite d'être connu. – nowox

Répondre

208

Non, YAML n'inclut aucun type d'instruction "import" ou "include".

+6

Vous pouvez créer un gestionnaire comprennent !. – clarkevans

+4

@clarkevans bien sûr, mais cette construction serait "en dehors" du langage YAML. – jameshfisher

+0

C'est maintenant possible. J'ai ajouté une réponse ci-dessous ... espérons que ça aide. – daveaspinall

-16

Ici, je l'ai utilisé mako

a.txt

cela fait partie haut de fichier un
<% include file = 'b.txt' />
ceci est la partie inférieure du fichier un

b.txt

ce fichier est 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
cela fait partie haut de fichier un
ce fichier est b
ceci est la partie inférieure du fichier un

+5

OP demandait à propos de YAML, pas Mako. – jjmontes

7

comprend ne sont pas directement pris en charge dans yaml autant que je sache, vous devrez fournir un mécanisme vous-même cependant, c'est généralement facile à faire.

J'ai utilisé yaml comme langue de configuration dans mes applications de python, et dans ce cas définissent souvent un convension comme ceci:

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

Puis 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 supporte pas, car il n'est pas vraiment conçu aussi exclusivement comme un fichier basé sur le balisage. Que voudrait dire un moyen si vous l'avez dans une réponse à une requête ajax?

67

Votre question ne demande pas de solution Python, mais en voici une qui utilise PyYAML.

PyYAML vous permet de joindre des constructeurs personnalisés (tels que !include) au chargeur YAML. J'ai inclus un répertoire racine qui peut être défini pour que cette solution prenne en charge les références de fichiers relatives et absolues.

Solution

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

Voir cette gist pour une solution Python 3 similaire, plus robuste qui utilise une métaclasse pour enregistrer le constructeur personnalisé.

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) 

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

Ceci est une fonctionnalité intéressante, merci. Mais quel est le but de toutes ces manipulations avec root/old_root? Je suppose que le code de la fonction 'include' peut simplifiée: ' comprennent def (chargeur, nœud): "Inclure un fichier YAML" "" "" filename = loader.construct_scalar (noeud) data = yaml.load (open (nomfichier)) ' –

+0

La racine globale est là de sorte que relative comprend travail à n'importe quelle profondeur, par exemple Lorsque les fichiers inclus dans un répertoire différent incluent un fichier relatif à ce répertoire. Les inclusions absolues devraient aussi fonctionner. Il y a probablement une façon plus simple de le faire sans une variable globale, peut-être en utilisant une classe yaml.Loader personnalisée. –

+1

Est-il également possible d'avoir quelque chose comme ceci: foo.yaml: 'a: bla' bar.yaml: ' comprennent foo.yaml b: blubb' Alors que le résultat serait: '{'a': bla, 'b': blubb} –

5

expansion sur @ la 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 des globals au niveau du module, ni de 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              
+2

Finalement, je me suis mis à ajouter l'approche basée sur les classes à ma réponse, mais vous m'avez battu au punch :) Note: Si vous utilisez 'yaml.load (f, IncludeLoader)' dans '_include' vous pouvez éviter d'avoir à remplacer la racine. En outre, à moins que vous ne le fassiez, la solution ne fonctionnera pas à plus d'un niveau car les données incluses utilisent la classe régulière yaml.Loader. –

+0

J'ai dû supprimer le mot clé 'root' de' kwargs' après avoir défini 'self.root' pour qu'il fonctionne avec les chaînes. J'ai déplacé le bloc if-else au-dessus de l'appel 'super'. Peut-être que quelqu'un d'autre peut confirmer ma découverte ou me montrer comment utiliser la classe avec des chaînes et le paramètre 'root'. – Woltan

+0

Malheureusement, cela ne fonctionne pas avec des références telles que '' ' inclus: & INCLUS comprennent inner.yaml fusion: <<: * VISÉES ' '' – antony

-6

Probablement il n'a pas été pris en charge lorsque la question a été posée, 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.

+3

Cela n'en comprend pas du tout. Il crée un mappage avec une séquence constituée d'une seule chaîne "/your_location_to_yaml_file/Util.area.yaml", en tant que valeur pour la clé 'imports'. – Anthon

18

Si vous utilisez Symfony's version of YAML, cela est possible, comme ceci:

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

Ceci est spécifique à la façon dont Symfony interprète YAML, plutôt qu'une partie de YAML lui-même. – jameshfisher

+3

Oui, c'est pourquoi j'ai posté le lien vers les documents Symfony. La question demande «Est-ce possible du tout? Comment?» ... Voici comment. Voir aucune raison pour une downvote. – daveaspinall

+2

Je ne vous ai pas déprécié; Je signale simplement que ceci est spécifique à Symfony YAML. – jameshfisher

0

Avec Symfony, sa gestion de YAML indirectement vous permettre de nid 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" 
0

Je pense que la solution utilisée par @ maxy-B est super. Cependant, il n'a pas réussi pour moi avec des inclusions imbriquées. Par exemple, si config_1.yaml inclut config_2.yaml, ce qui inclut config_3.yaml, il y a eu un problème avec le chargeur. Cependant, si vous pointez simplement la nouvelle classe loader sur elle-même, cela fonctionne!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 

Après 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 fichier, mais c'est très utile!

Questions connexes