Come posso inserire un file YAML all'interno di un altro?
-
22-08-2019 - |
Domanda
Così ho due file YAML, "A" e "B" e voglio che il contenuto di Un essere inserita all'interno di B, sia impiombato nella struttura di dati esistenti, come un array, o come un bambino di un elemento, come il valore di un determinato tasto cancelletto.
Questo è possibile a tutti?Come?Se non, tutti i puntatori di un riferimento normativo?
Soluzione
No, YAML non include alcun tipo di "importazione" o "include" dichiarazioni.
Altri suggerimenti
La tua domanda non chiede una soluzione Python, ma qui è uno che utilizza PyYAML .
PyYAML permette di collegare i costruttori personalizzata (ad esempio !include
) per il caricatore YAML. Ho incluso una directory principale che può essere impostato in modo tale che questa soluzione supporta riferimenti ai file relativi e assoluti.
Classe-Based Solution
Ecco una soluzione di classe, che evita la variabile radice globale della mia risposta originale.
Gist per una simile, più robusto Python 3 soluzione che utilizza un metaclasse per registrare il costruttore personalizzato.
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 esempio:
foo.yaml
a: 1
b:
- 1.43
- 543.55
c: !include bar.yaml
bar.yaml
- 3.6
- [1, 2, 3]
Ora i file possono essere caricati utilizzando:
>>> 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 si utilizza la versione di Symfony di YAML , questo è possibile, in questo modo:
imports:
- { resource: sub-directory/file.yml }
- { resource: sub-directory/another-file.yml }
Include non sono supportati direttamente in YAML per quanto ne so, si dovrà fornire un meccanismo da soli però, che in genere è facile da fare.
Ho usato YAML come linguaggio di configurazione in mie applicazioni python, e in questo caso spesso di definire una convenzione in questo modo:
>>> main.yml <<<
includes: [ wibble.yml, wobble.yml]
Poi, nel mio codice (Python) che faccio:
import yaml
cfg = yaml.load(open("main.yml"))
for inc in cfg.get("includes", []):
cfg.update(yaml.load(open(inc)))
L'unico lato negativo è che le variabili nel include sempre ignorare le variabili principali, e non v'è alcun modo per cambiare la situazione modificando la precedenza dove il "include: dichiarazione compare nel file main.yml
.Su un punto leggermente diverso, YAML non supporta comprende come la sua non realmente progettato come come esclusivamente come un file in base mark up. Cosa sarebbe un include media se avete capito bene in una risposta ad una richiesta AJAX?
Ampliando @ risposta di Josh_Bode, ecco la mia propria soluzione PyYAML, che ha il vantaggio di essere una sottoclasse autonomo di yaml.Loader
. Essa non dipende da alcuna globali a livello di modulo, o modificare lo stato globale del modulo 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
Per gli utenti di Python, si può provare pyyaml-includono.
Installare
pip install pyyaml-include
Utilizzo
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)
Considerare l'abbiamo YAML file:
├── 0.yaml
└── include.d
├── 1.yaml
└── 2.yaml
1.yaml
contenuti:
name: "1"
2.yaml
contenuti:
name: "2"
Includere i file per nome
Sul livello superiore:
Se
0.yaml
stato:
!include include.d/1.yaml
Otteniamo:
{"name": "1"}
Nel mapping:
Se
0.yaml
stato:
file1: !include include.d/1.yaml
file2: !include include.d/2.yaml
Otteniamo:
file1:
name: "1"
file2:
name: "2"
In sequenza:
Se
0.yaml
stato:
files:
- !include include.d/1.yaml
- !include include.d/2.yaml
Otteniamo:
files:
- name: "1"
- name: "2"
ℹ Nota:
Il nome del File può essere assoluto (come
/usr/conf/1.5/Make.yml
) o relativo (come../../cfg/img.yml
).
Includere i file con caratteri jolly
Il nome del File può contenere wildcard in stile shell.I dati caricati dal file(s) trovato da caratteri jolly, sarà ambientato in una sequenza.
Se 0.yaml
stato:
files: !include include.d/*.yaml
Otteniamo:
files:
- name: "1"
- name: "2"
ℹ Nota:
- Per
Python>=3.5
, serecursive
argomento di!include
YAML tagtrue
, il modello“**”
si adattano a qualsiasi file e zero o più directory e sottodirectory.- Utilizzando il
“**”
modello in grandi alberi di directory può consumare una quantità eccessiva di tempo a causa della ricerca ricorsiva.
Al fine di consentire recursive
argomento, si deve scrivere il !include
tag Mapping
o Sequence
modalità:
- Argomenti
Sequence
modalità:
!include [tests/data/include.d/**/*.yaml, true]
- Argomenti
Mapping
modalità:
!include {pathname: tests/data/include.d/**/*.yaml, recursive: true}
Purtroppo YAML non fornisce questo nel suo standard.
Ma se stai usando Ruby, c'è un gioiello che fornisce la funzionalità che si sta chiedendo per estendendo la ruby libreria YAML:https://github.com/entwanderer/yaml_extend
Credo che la soluzione utilizzata da @ maxy-B sembra grande. Tuttavia, non è riuscito per me con inclusioni nidificate. Ad esempio, se config_1.yaml include config_2.yaml, che comprende config_3.yaml c'è stato un problema con il caricatore. Tuttavia, se semplicemente puntare la nuova classe loader a se stesso il carico, funziona! In particolare, se si sostituisce la vecchia funzione _Include con la versione leggermente modificata:
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
Al momento di riflessione Concordo con gli altri commenti, che il carico nidificato non è appropriato per YAML, in generale, come il flusso di input non può essere un file, ma è molto utile!
Forse questo potrebbe ispirarvi, cercare di allineare alle convenzioni 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 non include nativamente questa funzione. Tuttavia molte implementazioni fornisce qualche estensione di farlo.
vi presento un modo per raggiungere con Java e snakeyaml:1.24
(libreria Java per analizzare / emettere file YAML) che permette di creare un tag YAML personalizzato per raggiungere il seguente obiettivo (si vedrà che sto usando per caricare suite di test definiti in diversi file YAML e che ho fatto funzionare come una lista di comprende un nodo di destinazione test:
):
# ... yaml prev stuff
tests: !include
- '1.hello-test-suite.yaml'
- '3.foo-test-suite.yaml'
- '2.bar-test-suite.yaml'
# ... more yaml document
Ecco il Java una classe che consente l'elaborazione del tag !include
. I file vengono caricati dal percorso di classe (Maven risorse di directory):
/**
* 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 , la sua gestione della YAML permetterai indirettamente a file YAML nido. Il trucco è quello di utilizzare l'opzione parameters
. ad esempio:
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%"
Il risultato sarà lo stesso di:
whatever:
thing:
option: "value"
foo:
- "bar"
- "baz"
other_thing:
option: "value"
foo:
- "bar"
- "baz"
Probabilmente non è stato supportato quando domanda è stato chiesto, ma è possibile importare altri file YAML in uno:
imports: [/your_location_to_yaml_file/Util.area.yaml]
Anche se non ho alcun riferimento online, ma questo funziona per me.