同じYAMLファイルの他の場所からYAMLの「設定」を参照する方法


145

次のYAMLがあります。

paths:
  patha: /path/to/root/a
  pathb: /path/to/root/b
  pathc: /path/to/root/c

/path/to/root/3つのパスから削除してこれを「正規化」し、それを独自の設定として使用するには、次のようにします。

paths:
  root: /path/to/root/
  patha: *root* + a
  pathb: *root* + b
  pathc: *root* + c

明らかにそれは無効です、私はそれを作りました。実際の構文は何ですか?できますか?


回答:


126

それは可能ではないと思います。「ノード」は再利用できますが、その一部を再利用することはできません。

bill-to: &id001
    given  : Chris
    family : Dumars
ship-to: *id001

これは完全に有効なYAMLとフィールドでgivenありfamilyship-toブロックで再利用されます。同じ方法でスカラーノードを再利用できますが、内部にあるものを変更して、パスの最後の部分をYAMLの内部から追加する方法はありません。

繰り返しがそれだけ気になる場合は、アプリケーションにrootプロパティを認識させ、絶対ではなく相対的に見えるすべてのパスに追加することをお勧めします。


1
わかりました、ええと、rootコードの先頭に追加する必要があります。大したことはありません。
Andrew Bullock 2010年

2
受け入れられた答えは正確ではありません。解決策については私の答えを参照してください。
クリスジョンソン

場合は、これを行う方法を請求先がどこにインポートした別のファイルにある 船-には定義されていますか?
Prateek Jain、2015

@PrateekJain:複数のファイルを扱う場合は、おそらくここにリストされているようなスタンドアロンのYAML拡張ライブラリを評価するのが最善です。github.com/dreftymac/dynamic.yaml/blob/master/...
dreftymac

1
yaml.org/spec/1.2/spec.htmlの例2.9を参照してください。素晴らしいスカラーを参照することもできます
akostadinov

72

はい、カスタムタグを使用します。Pythonでの例、!joinタグを配列内の文字列に結合する:

import yaml

## define custom tag handler
def join(loader, node):
    seq = loader.construct_sequence(node)
    return ''.join([str(i) for i in seq])

## register the tag handler
yaml.add_constructor('!join', join)

## using your sample data
yaml.load("""
paths:
    root: &BASE /path/to/root/
    patha: !join [*BASE, a]
    pathb: !join [*BASE, b]
    pathc: !join [*BASE, c]
""")

その結果:

{
    'paths': {
        'patha': '/path/to/root/a',
        'pathb': '/path/to/root/b',
        'pathc': '/path/to/root/c',
        'root': '/path/to/root/'
     }
}

への引数の配列は!join、それらが文字列に変換できる限り、任意のデータ型の任意の数の要素を持つことができます!join [*a, "/", *b, "/", *c]


2
私はあなたのソリューションが好きです。コーディングがより簡単で、YAMLが少し読みにくいという犠牲を払って採掘します。
Anthon

7
この回答は、より多くの投票に値します。技術的には、YAML仕様による最も正確な回答です。ただし、実際のYAML 実装によると1つの注意点があります。実際に完全なYAML仕様を実装しているものはほとんどありません。Pythonのpyyamlは、仕様との統一性の点で、他の多くのpyyamlを上回っています。
dreftymac

5
問題は、yamlファイル内の値を参照することに関するもののようです。その周りにコードの別のレイヤーを追加することは、私が好むソリューションではありません。
user2020056 2017

1
@ChrisJohnsonこの回答をありがとう、この構文をリストしたリファレンスドキュメントがあるかどうか疑問に思っていました。YAML仕様がウェブの複数の場所で説明されているのを見たので、私があなたと同じリファレンスを参照していることを確認したいだけです。ありがとう!
user5359531 2017年

3
この解決策は私にはうまくいきませんでした(python3?)が、上記の簡単な変更を加えると、期待どおりに機能します。具体的には、yaml.SafeLoader.add_constructor(tag='!join', constructor=join) yaml.load(open(fpth, mode='r'), Loader=yaml.SafeLoader)
benjaminmgross

20

これを見る別の方法は、単に別のフィールドを使用することです。

paths:
  root_path: &root
     val: /path/to/root/
  patha: &a
    root_path: *root
    rel_path: a
  pathb: &b
    root_path: *root
    rel_path: b
  pathc: &c
    root_path: *root
    rel_path: c

5

YML定義:

dir:
  default: /home/data/in/
  proj1: ${dir.default}p1
  proj2: ${dir.default}p2
  proj3: ${dir.default}p3 

胸腺のどこかに

<p th:utext='${@environment.getProperty("dir.default")}' />
<p th:utext='${@environment.getProperty("dir.proj1")}' /> 

出力: / home / data / in / / home / data / in / p1


@AndrewBullockあなたの問題を正確に解決するので、これは受け入れられる答えであると思います。
Honza Zidek 2017年

5
いいえ、これはYAMLでの変数のネイティブ使用ではなく、どの仕様バージョンでも指定されていません。いくつかのテストの後、これは機能しません。
アーサーラコステ2017年

2
これはおそらく、yamlを前処理したもの(つまり、maven-resources-pluginフィルタリング)を使用してPavolで機能しました
DeezCashews 2017年

1
標準のYamlではない
Dan Niero

3

この機能を実行する、Packagistで利用可能なライブラリを作成しました。https://packagist.org/packages/grasmash/yaml-expander

YAMLファイルの例:

type: book
book:
  title: Dune
  author: Frank Herbert
  copyright: ${book.author} 1965
  protaganist: ${characters.0.name}
  media:
    - hardcover
characters:
  - name: Paul Atreides
    occupation: Kwisatz Haderach
    aliases:
      - Usul
      - Muad'Dib
      - The Preacher
  - name: Duncan Idaho
    occupation: Swordmaster
summary: ${book.title} by ${book.author}
product-name: ${${type}.title}

ロジックの例:

// Parse a yaml string directly, expanding internal property references.
$yaml_string = file_get_contents("dune.yml");
$expanded = \Grasmash\YamlExpander\Expander::parse($yaml_string);
print_r($expanded);

結果の配列:

array (
  'type' => 'book',
  'book' => 
  array (
    'title' => 'Dune',
    'author' => 'Frank Herbert',
    'copyright' => 'Frank Herbert 1965',
    'protaganist' => 'Paul Atreides',
    'media' => 
    array (
      0 => 'hardcover',
    ),
  ),
  'characters' => 
  array (
    0 => 
    array (
      'name' => 'Paul Atreides',
      'occupation' => 'Kwisatz Haderach',
      'aliases' => 
      array (
        0 => 'Usul',
        1 => 'Muad\'Dib',
        2 => 'The Preacher',
      ),
    ),
    1 => 
    array (
      'name' => 'Duncan Idaho',
      'occupation' => 'Swordmaster',
    ),
  ),
  'summary' => 'Dune by Frank Herbert',
);

エキスパンダーのコンセプトが大好き!
ギヨームロデリック2018年

2

一部の言語では、代替ライブラリを使用できます。たとえば、tampaxはYAML処理変数の実装です。

const tampax = require('tampax');

const yamlString = `
dude:
  name: Arthur
weapon:
  favorite: Excalibur
  useless: knife
sentence: "{{dude.name}} use {{weapon.favorite}}. The goal is {{goal}}."`;

const r = tampax.yamlParseString(yamlString, { goal: 'to kill Mordred' });
console.log(r.sentence);

// output : "Arthur use Excalibur. The goal is to kill Mordred."

1

あなたの例では、無効であるということはある唯一のあなたとあなたのスカラーを開始するための予約文字を選んだので。を*他の予約されていない文字に置き換えると(一部の仕様の一部として使用されることはほとんどないため、ASCII以外の文字を使用する傾向があります)、完全に正当なYAMLになります。

paths:
  root: /path/to/root/
  patha: ♦root♦ + a
  pathb: ♦root♦ + b
  pathc: ♦root♦ + c

これは、パーサーが使用する言語のマッピングの標準表現に読み込まれ、魔法のように何も拡張しません。
これを行うには、次のPythonプログラムのように、ローカルにデフォルトのオブジェクトタイプを使用します。

# coding: utf-8

from __future__ import print_function

import ruamel.yaml as yaml

class Paths:
    def __init__(self):
        self.d = {}

    def __repr__(self):
        return repr(self.d).replace('ordereddict', 'Paths')

    @staticmethod
    def __yaml_in__(loader, data):
        result = Paths()
        loader.construct_mapping(data, result.d)
        return result

    @staticmethod
    def __yaml_out__(dumper, self):
        return dumper.represent_mapping('!Paths', self.d)

    def __getitem__(self, key):
        res = self.d[key]
        return self.expand(res)

    def expand(self, res):
        try:
            before, rest = res.split(u'♦', 1)
            kw, rest = rest.split(u'♦ +', 1)
            rest = rest.lstrip() # strip any spaces after "+"
            # the lookup will throw the correct keyerror if kw is not found
            # recursive call expand() on the tail if there are multiple
            # parts to replace
            return before + self.d[kw] + self.expand(rest)
        except ValueError:
            return res

yaml_str = """\
paths: !Paths
  root: /path/to/root/
  patha: ♦root♦ + a
  pathb: ♦root♦ + b
  pathc: ♦root♦ + c
"""

loader = yaml.RoundTripLoader
loader.add_constructor('!Paths', Paths.__yaml_in__)

paths = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)['paths']

for k in ['root', 'pathc']:
    print(u'{} -> {}'.format(k, paths[k]))

印刷されます:

root -> /path/to/root/
pathc -> /path/to/root/c

拡張はオンザフライで行われ、ネストされた定義を処理しますが、無限再帰を呼び出さないように注意する必要があります。

ダンパーを指定すると、オンザフライで拡張されるため、ロードされたデータから元のYAMLをダンプできます。

dumper = yaml.RoundTripDumper
dumper.add_representer(Paths, Paths.__yaml_out__)
print(yaml.dump(paths, Dumper=dumper, allow_unicode=True))

これにより、マッピングキーの順序が変更されます。それが問題である場合self.dCommentedMap(からインポートされたruamel.yaml.comments.py)を作成する必要があります。


0

次のような階層を持つディレクトリからロードされる変数を拡張するために、Pythonで独自のライブラリを作成しました。

/root
 |
 +- /proj1
     |
     +- config.yaml
     |
     +- /proj2
         |
         +- config.yaml
         |
         ... and so on ...

ここでの主な違いは、すべてのconfig.yamlファイルがロードされた後にのみ拡張を適用する必要があることです。この場合、次のファイルの変数が前の変数をオーバーライドできるため、疑似コードは次のようになります。

env = YamlEnv()
env.load('/root/proj1/config.yaml')
env.load('/root/proj1/proj2/config.yaml')
...
env.expand()

追加オプションとして、xonshスクリプトは結果の変数を環境変数にエクスポートできます(yaml_update_global_vars関数を参照)。

スクリプト:

https://sourceforge.net/p/contools/contools/HEAD/tree/trunk/Scripts/Tools/cmdoplib.yaml.py https://sourceforge.net/p/contools/contools/HEAD/tree/trunk/Scripts /Tools/cmdoplib.yaml.xsh

長所

  • 単純、再帰およびネストされた変数をサポートしていません
  • 未定義の変数をプレースホルダーに置き換えることができます(${MYUNDEFINEDVAR}-> *$/{MYUNDEFINEDVAR}
  • 環境変数から参照を展開できます(${env:MYVAR}
  • パス変数のすべての\\to を置き換えることができ/ます(${env:MYVAR:path}

短所

  • ネストされた変数をサポートしないため、ネストされた辞書の値を拡張できません(${MYSCOPE.MYVAR}実装されていないようなもの)
  • プレースホルダーの配置後の再帰を含む、拡張再帰を検出しません

0

ではYglu、あなたはあなたの例をとして書くことができます。

paths:
  root: /path/to/root/
  patha: !? .paths.root + a
  pathb: !? .paths.root + b
  pathc: !? .paths.root + c

免責事項:私はYgluの作者です。


YAMLの上にこの機能を追加するライブラリを知っておくのは良いことです
Dhiraj
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.