Làm thế nào để tham khảo một cài đặt YAML vào từ nơi khác trong cùng một tệp YAML?


145

Tôi có YAML sau:

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

Làm cách nào tôi có thể "bình thường hóa" điều này, bằng cách xóa /path/to/root/khỏi ba đường dẫn và đặt nó làm cài đặt riêng, đại loại như:

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

Rõ ràng đó là không hợp lệ, tôi chỉ cần làm cho nó lên. Cú pháp thực sự là gì? Nó có thể được thực hiện?


Câu trả lời:


126

Tôi không nghĩ rằng nó có thể. Bạn có thể sử dụng lại "nút" nhưng không phải là một phần của nó.

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

Điều này là hoàn toàn hợp lệ YAML và các trường givenfamilyđược sử dụng lại trong ship-tokhối. Bạn có thể sử dụng lại một nút vô hướng theo cách tương tự nhưng không có cách nào bạn có thể thay đổi những gì bên trong và thêm phần cuối cùng của đường dẫn đến nó từ bên trong YAML.

Nếu sự lặp lại làm phiền bạn nhiều đến mức tôi đề nghị làm cho ứng dụng của bạn nhận biết thuộc roottính và thêm nó vào mọi đường dẫn có vẻ tương đối không tuyệt đối.


1
Ok cảm ơn, vâng tôi phải đăng ký rootmã. không có vấn đề gì
Andrew Bullock

2
Câu trả lời được chấp nhận là không chính xác. Xem câu trả lời của tôi cho một giải pháp.
Chris Johnson

Làm thế nào để làm điều này, nếu bill-to nằm trong một tệp khác, mà chúng ta đã nhập vào nơi xác định ship-to ?
Prateek Jain

@PrateekJain: nếu bạn đang xử lý nhiều tệp, có lẽ bạn sẽ làm tốt nhất để đánh giá thư viện nâng cao YAML độc lập, chẳng hạn như một tệp được liệt kê ở đây. github.com/dreftymac/dynamic.yaml/blob/master/NH
dreftymac

1
Xem ví dụ 2.9 trong yaml.org/spec/1.2/spec.html ; người ta cũng có thể tham khảo các số vô hướng tuyệt vời
akostadinov

72

Có, sử dụng thẻ tùy chỉnh. Ví dụ trong Python, làm cho các !joinchuỗi tham gia chuỗi trong một mảng:

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]
""")

Kết quả nào trong:

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

Mảng đối số !joincó thể có bất kỳ số lượng phần tử thuộc bất kỳ loại dữ liệu nào, miễn là chúng có thể được chuyển đổi thành chuỗi, !join [*a, "/", *b, "/", *c]điều bạn mong đợi cũng vậy.


2
Tôi thích giải pháp của bạn, đơn giản hơn trong mã hóa, sau đó khai thác với chi phí YAML ít đọc hơn một chút.
Anthon

7
Câu trả lời này xứng đáng được nhiều phiếu bầu hơn. Đây là câu trả lời chính xác nhất theo thông số kỹ thuật YAML. Tuy nhiên, có một cảnh báo, theo các triển khai YAML thực tế , có một số ít thực sự thực hiện đầy đủ thông số kỹ thuật YAML. Pyyaml ​​của Python là trên và vượt trên nhiều người khác về tính đồng nhất của nó với đặc điểm kỹ thuật.
dreftymac

5
Câu hỏi dường như là về việc tham chiếu một giá trị IN một tệp yaml. Thêm một lớp mã khác xung quanh nó sẽ không phải là giải pháp ưa thích của tôi.
dùng2020056

1
@ChrisJohnson Cảm ơn câu trả lời này, tôi đã tự hỏi nếu bạn có một tài liệu tham khảo liệt kê cú pháp này. Tôi đã thấy thông số kỹ thuật YAML được giải thích ở nhiều nơi trên web vì vậy tôi chỉ muốn chắc chắn rằng tôi đang xem cùng một tài liệu tham khảo. Cảm ơn!
dùng5359531

3
Giải pháp này không hiệu quả với tôi ( python3?) Tuy nhiên với một sửa đổi đơn giản ở trên, nó hoạt động như mong đợi. Cụ thể:yaml.SafeLoader.add_constructor(tag='!join', constructor=join) yaml.load(open(fpth, mode='r'), Loader=yaml.SafeLoader)
benjaminmg trong

20

Một cách khác để xem xét điều này là chỉ cần sử dụng một lĩnh vực khác.

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

Định nghĩa YML:

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

Một nơi nào đó trong tuyến ức

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

Đầu ra: / home / data / in / / home / data / in / p1


@AndrewBullock Tôi nghĩ rằng đây nên là câu trả lời được chấp nhận, vì nó giải quyết chính xác vấn đề của bạn.
Honza Zidek

5
Không, nó không phải là cách sử dụng biến riêng trong YAML và nó không được chỉ định trong bất kỳ phiên bản thông số kỹ thuật nào. Sau một số thử nghiệm, điều này không hoạt động.
Arthur Lacoste

2
Điều này có thể đã làm việc cho Pavol bằng cách sử dụng cái gì đó đã xử lý trước yaml (tức là lọc maven-resource-plugin)
DeezCashews 17/11/17

1
Không chuẩn Yaml
Dan Niero

3

Tôi đã tạo một thư viện, có sẵn trên Packagist, thực hiện chức năng này: https://packagist.org/packages/grasmash/yaml-Exander

Ví dụ tệp 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}

Ví dụ logic:

// 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);

Mảng kết quả:

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',
);

Yêu khái niệm mở rộng!
Guillaume Roderick

2

Trong một số ngôn ngữ, bạn có thể sử dụng một thư viện thay thế, Ví dụ: tampax là một triển khai của các biến xử lý 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

Rằng ví dụ của bạn không hợp lệ chỉ vì bạn đã chọn một ký tự dành riêng để bắt đầu vô hướng. Nếu bạn thay thế *bằng một số ký tự không dành riêng khác (tôi có xu hướng sử dụng các ký tự không phải ASCII cho chúng vì chúng hiếm khi được sử dụng như một phần của một số đặc điểm kỹ thuật), bạn sẽ kết thúc với YAML hoàn toàn hợp pháp:

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

Điều này sẽ tải vào biểu diễn tiêu chuẩn cho ánh xạ trong ngôn ngữ mà trình phân tích cú pháp của bạn sử dụng và không mở rộng một cách kỳ diệu bất cứ điều gì.
Để làm điều đó, hãy sử dụng loại đối tượng mặc định cục bộ như trong chương trình Python sau:

# 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]))

sẽ in:

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

Việc mở rộng được thực hiện nhanh chóng và xử lý các định nghĩa lồng nhau, nhưng bạn phải cẩn thận về việc không gọi đệ quy vô hạn.

Bằng cách chỉ định máy xúc lật, bạn có thể kết xuất YAML ban đầu từ dữ liệu được tải vào, do việc mở rộng nhanh chóng:

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

điều này sẽ thay đổi thứ tự khóa ánh xạ. Nếu đó là một vấn đề bạn cần phải thực hiện self.dmột CommentedMap(nhập khẩu từ ruamel.yaml.comments.py)


0

Tôi đã viết thư viện của riêng mình trên Python để mở rộng các biến đang được tải từ các thư mục với hệ thống phân cấp như:

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

Sự khác biệt chính ở đây là việc mở rộng phải được áp dụng chỉ sau khi tất cả các config.yamltệp được tải, trong đó các biến từ tệp tiếp theo có thể ghi đè các biến từ trước đó, vì vậy mã giả sẽ trông như thế này:

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

Là một tùy chọn bổ sung, xonshtập lệnh có thể xuất các biến kết quả thành các biến môi trường (xem yaml_update_global_varshàm).

Các kịch bản:

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

Ưu điểm :

  • đơn giản, không hỗ trợ đệ quy và các biến lồng nhau
  • có thể thay thế một biến không xác định thành một trình giữ chỗ ( ${MYUNDEFINEDVAR}-> *$/{MYUNDEFINEDVAR})
  • có thể mở rộng một tham chiếu từ biến môi trường ( ${env:MYVAR})
  • có thể thay thế tất cả \\thành /một biến đường dẫn ( ${env:MYVAR:path})

Nhược điểm :

  • không hỗ trợ các biến lồng nhau, vì vậy không thể mở rộng giá trị trong từ điển lồng nhau (một cái gì đó giống như ${MYSCOPE.MYVAR}không được thực hiện)
  • không phát hiện đệ quy mở rộng, bao gồm đệ quy sau khi đặt chỗ

0

Với Yglu , bạn có thể viết ví dụ của mình dưới dạng:

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

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của Yglu.


Thật tốt khi biết một thư viện có thêm chức năng này trên đầu YAML
Dhiraj
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.