วิธีการอ้างอิง "การตั้งค่า" ของ YAML จากที่อื่นในไฟล์ YAML เดียวกัน


145

ฉันมี YAML ต่อไปนี้:

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

ฉันจะ "ทำให้ปกติ" เป็นแบบนี้ได้อย่างไรโดยลบออก/path/to/root/จากสามเส้นทางและตั้งเป็นของตัวเองดังนี้:

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

เห็นได้ชัดว่ามันไม่ถูกต้องฉันเพิ่งทำมันขึ้นมา ไวยากรณ์ที่แท้จริงคืออะไร มันสามารถทำได้?


1
ดูเพิ่มเติมที่: stackoverflow.com/a/41620747/42223
dreftymac

คำตอบ:


127

ฉันไม่คิดว่ามันเป็นไปได้ คุณสามารถนำ "node" กลับมาใช้ใหม่ได้ แต่ไม่สามารถเป็นส่วนหนึ่งของมันได้

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

นี่คือ YAML และฟิลด์ที่ถูกต้องสมบูรณ์givenและfamilyนำมาใช้ซ้ำในship-toบล็อก คุณสามารถนำสเกลาร์กลับมาใช้ใหม่ได้ในลักษณะเดียวกัน แต่ไม่มีทางที่คุณจะสามารถเปลี่ยนแปลงสิ่งที่อยู่ข้างในและเพิ่มส่วนสุดท้ายของเส้นทางจากภายใน YAML

หากการทำซ้ำรบกวนคุณมากฉันแนะนำให้แอปพลิเคชันของคุณรับรู้ถึงrootคุณสมบัติและเพิ่มไปยังทุกเส้นทางที่ดูไม่สัมพันธ์กัน


1
ตกลงขอบคุณใช่ป่วยต้องเตรียมrootรหัส ไม่มีปัญหา
Andrew Bullock

2
คำตอบที่ยอมรับนั้นไม่ถูกต้อง ดูคำตอบของฉันสำหรับการแก้ปัญหา
Chris Johnson

วิธีการทำเช่นนี้ถ้าเรียกเก็บเงินอยู่ในไฟล์อื่นซึ่งเราได้นำเข้าที่ เรือจะถูกกำหนด?
Prateek Jain

@PrateekJain: หากคุณกำลังจัดการกับไฟล์หลายไฟล์คุณอาจจะดีที่สุดในการประเมินไลบรารีเพิ่มประสิทธิภาพ YAML แบบสแตนด์อโลนเช่นที่อยู่ในรายการที่นี่ github.com/dreftymac/dynamic.yaml/blob/master/…
dreftymac

1
ดูตัวอย่าง 2.9 ในyaml.org/spec/1.2/spec.html ; เราสามารถอ้างอิง scalars ที่ยอดเยี่ยมได้ด้วย
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 จริงมีเพียงไม่กี่ที่ที่ใช้ข้อมูลจำเพาะของ YAML แบบเต็ม Pyyaml ​​ของ Python อยู่เหนือกว่าสิ่งอื่น ๆ อีกมากมายในแง่ของความสม่ำเสมอด้วยสเปค
dreftymac

5
คำถามดูเหมือนจะเกี่ยวกับการอ้างอิงค่าในไฟล์ yaml การเพิ่มเลเยอร์ของรหัสอื่นรอบ ๆ มันจะไม่ใช่โซลูชันที่ฉันต้องการ
user2020056

1
@ChrisJohnson ขอบคุณสำหรับคำตอบนี้ฉันสงสัยว่าคุณมีเอกสารอ้างอิงที่ระบุไวยากรณ์นี้หรือไม่ ฉันเห็นสเปคของ YAML อธิบายในหลาย ๆ ที่บนเว็บดังนั้นฉันแค่ต้องการให้แน่ใจว่าฉันกำลังดูการอ้างอิงเดียวกันกับคุณ ขอบคุณ!
user5359531

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 

ที่ไหนสักแห่งใน thymeleaf

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

เอาต์พุต: / home / data / in / / home / data / in / p1


@ AndrewBullock ฉันคิดว่านี่น่าจะเป็นคำตอบที่ได้รับการยอมรับเพราะมันช่วยแก้ปัญหาของคุณได้
Honza Zidek

5
ไม่มันไม่ใช่การใช้งานดั้งเดิมของตัวแปรใน YAML และไม่ได้ระบุไว้ในข้อกำหนดรุ่นใด ๆ หลังจากการทดสอบบางอย่างมันไม่ทำงาน
Arthur Lacoste

2
สิ่งนี้อาจใช้ได้กับ Pavol โดยใช้บางสิ่งที่ดำเนินการกับ yaml (เช่นการกรอง maven-resources-plugin)
DeezCashews

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

ชอบแนวคิดของเครื่องขยาย
Guillaume Roderick

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

สิ่งนี้จะโหลดเข้าสู่การเป็นตัวแทนมาตรฐานสำหรับการแมปในภาษาที่ parser ของคุณใช้และไม่ขยายอะไรอย่างน่าอัศจรรย์
ในการทำเช่นนั้นให้ใช้ประเภทวัตถุเริ่มต้นในเครื่องเช่นเดียวกับในโปรแกรม 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 ดั้งเดิมจากข้อมูลที่โหลดได้เนื่องจากการขยายแบบ on-the-fly:

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

สิ่งนี้จะเปลี่ยนการจัดลำดับคีย์การแมป ถ้าเป็นปัญหาที่คุณต้องให้(นำเข้าจาก)self.dCommentedMapruamel.yaml.comments.py


0

ฉันได้เขียนไลบรารี่ของฉันเองบน Python เพื่อขยายตัวแปรที่โหลดจากไดเรกทอรีที่มีลำดับขั้นดังนี้

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

ข้อแตกต่างที่สำคัญคือการขยายต้องใช้หลังจากconfig.yamlโหลดไฟล์ทั้งหมดเท่านั้นซึ่งตัวแปรจากไฟล์ถัดไปสามารถลบล้างตัวแปรจากก่อนหน้าดังนั้น pseudocode ควรมีลักษณะดังนี้:

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})
  • สามารถแทนที่ทั้งหมด\\ไป/ในตัวแปรเส้นทาง ( ${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.