在YAML中使用占位符。

95

有没有办法在YAML中像这样使用占位符:

foo: &FOO
    <<propname>>: 
        type: number 
        default: <<default>>

bar:
    - *FOO 
       propname: "some_prop"
       default: "some default" 

参见:https://stackoverflow.com/questions/30905103/yaml-reusable-variables-with-case-specific-values - dreftymac
4个回答

191

背景

  • YAML版本1.2
  • 用户希望
    • 在YAML中包含变量占位符
    • yaml.load时将占位符替换为计算出的值
    • 能够在YAML映射键和值中使用占位符

问题

  • YAML不支持本地变量占位符。
  • 锚点和别名几乎提供所需的功能,但这些不起作用作为可插入到YAML文本任意区域的变量占位符。它们必须作为单独的YAML节点放置。
  • 有一些附加库支持任意变量占位符,但它们不是本机YAML规范的一部分。

示例

考虑以下示例YAML。它是格式良好的YAML语法,但它使用(非标准)大括号占位符和嵌入表达式。

嵌入表达式在YAML中不能产生期望的结果,因为它们不是本机YAML规范的一部分。然而,在此示例中仅使用它们来帮助说明标准YAML中的可用内容和不可用内容。

part01_customer_info:
  cust_fname:   "Homer"
  cust_lname:   "Himpson"
  cust_motto:   "I love donuts!"
  cust_email:   homer@himpson.org

part01_government_info:
  govt_sales_taxrate: 1.15

part01_purchase_info:
  prch_unit_label:    "Bacon-Wrapped Fancy Glazed Donut"
  prch_unit_price:    3.00
  prch_unit_quant:    7
  prch_product_cost:  "{{prch_unit_price * prch_unit_quant}}"
  prch_total_cost:    "{{prch_product_cost * govt_sales_taxrate}}"   

part02_shipping_info:
  cust_fname:   "{{cust_fname}}"
  cust_lname:   "{{cust_lname}}"
  ship_city:    Houston
  ship_state:   Hexas    

part03_email_info:
  cust_email:     "{{cust_email}}"
  mail_subject:   Thanks for your DoughNutz order!
  mail_notes: |
    We want the mail_greeting to have all the expected values
    with filled-in placeholders (and not curly-braces).
  mail_greeting: |
    Greetings {{cust_fname}} {{cust_lname}}!
    
    We love your motto "{{cust_motto}}" and we agree with you!
    
    Your total purchase price is {{prch_total_cost}}
    

说明

  • 下面是一个内联图片,用绿色、黄色和红色的区域说明了示例。

  • 标记为绿色的替换在标准YAML中很容易使用锚点、别名和合并键实现。

  • 标记为黄色的替换在标准YAML中技术上可用,但需要自定义类型声明或其他绑定机制。

  • 标记为红色的替换在标准YAML中不可用。然而,有一些变通方法和替代方案;例如通过字符串格式化或字符串模板引擎(如Python的str.format)。

Image explaining the different types of variable substitution in YAML

详细信息

使用变量占位符的模板是一个经常被请求的YAML功能。

开发人员通常希望在同一YAML文件或转入的YAML文件中交叉引用内容。

YAML支持锚点和别名,但此功能不支持在YAML文本中任意放置占位符和表达式。它们只适用于YAML节点。

YAML还支持自定义类型声明, 但这些不太常见,并且如果您从潜在的不受信任的来源接受YAML内容,则存在安全风险。

YAML附加库

有YAML扩展库,但这些不是本地YAML规范的一部分。

解决方案

  • 与模板系统(如Jinja2或Twig)一起使用YAML
  • 使用YAML扩展库
  • 从托管语言中使用sprintfstr.format样式功能

替代方案

  • YTT YAML Templating 本质上是一个基于YAML的分支,具有额外的功能,可能更接近于OP指定的目标。
  • Jsonnet 与YAML有些相似之处,但具有额外的功能,可能更接近于OP指定的目标。

另请参阅

这里在SO上

在SO之外


3
非常好的答案,非常详细和解释清晰,谢谢!不幸的是,标记为绿色的那些对于Jekyll用户来说不可用,至少在Jekyll v3.8.5上是这样。我亲自测试过。 - Antônio Medeiros
3
在Jekyll上有效的是 &hello(别名)和 *hello(锚点),如此解释:https://idratherbewriting.com/documentation-theme-jekyll/mydoc_yaml_tutorial#example-6-variables - Antônio Medeiros

10

通过 Yglu 结构化模板,您的示例可以这样编写:

foo: !()
  !? $.propname: 
     type: number 
     default: !? $.default
    
bar:
  !apply .foo: 
    propname: "some_prop"
    default: "some default"

免责声明:我是Yglu的作者。


我本想喜欢Yglu,但是我在使用konverter时更加顺手。虽然它使用了jinja,不太纯净,但我无法完全理解Yglu。 - Aaron McMillin

4

1

我想在yaml文件中实现模板化,我发现dreftymac的答案是一个很好的起点。经过几个小时的研究和编码,这是我的答案,请告诉我如何改进。

我没有做什么特别的事情,我尝试利用Python的字符串模板语法,并稍微滥用字符串格式方法。所以这里的魔法都是由Python的字符串模板和替换完成的。我修改了dreftymac的答案中模板化yaml文件的方式,以作为示例。

YAML:

part01_customer_info:
  cust_fname: "Homer"
  cust_lname: "Himpson"
  cust_motto: "I love donuts!"
  cust_email: homer@himpson.org

part01_government_info:
  govt_sales_taxrate: 1.15

part01_purchase_info:
  prch_unit_label: "Bacon-Wrapped Fancy Glazed Donut"
  prch_unit_price: 3.00
  prch_unit_quant: 7
  prch_product_cost: "eval!#{part01_purchase_info[prch_unit_price]} * {part01_purchase_info[prch_unit_quant]}"
  prch_total_cost: "eval!#{part01_purchase_info[prch_product_cost]} * {part01_government_info[govt_sales_taxrate]}"

part02_shipping_info:
  cust_fname: "{part01_customer_info[cust_fname]}"
  cust_lname: "{part01_customer_info[cust_lname]}"
  ship_city: Houston
  ship_state: Hexas

part03_email_info:
  cust_email: "{part01_customer_info[cust_email]}"
  mail_subject: Thanks for your DoughNutz order!
  mail_notes: |
    We want the mail_greeting to have all the expected values
    with filled-in placeholders (and not curly-braces).
  mail_greeting: |
    Greetings {part01_customer_info[cust_fname]} {part01_customer_info[cust_lname]}!

    We love your motto "{part01_customer_info[cust_motto]}" and we agree with you!

    Your total purchase price is {part01_purchase_info[prch_total_cost]}

我已经将{{}}更改为{}并添加了一个名为eval!#的标识符。

Python:

from pprint import pprint
import yaml

EVAL_IDENTIFIER = "eval!#"


def eval_math_expr(val):
    if val.startswith(EVAL_IDENTIFIER):
        val = val.replace(EVAL_IDENTIFIER, "")
        val = eval(val)
    return val


def str_template_substitute(full, val=None, initial=True):
    val = val or full if initial else val
    if isinstance(val, dict):
        for k, v in val.items():
            val[k] = str_template_substitute(full, v, False)
    elif isinstance(val, list):
        for idx, i in enumerate(val):
            val[idx] = str_template_substitute(full, i, False)
    elif isinstance(val, str):
        # NOTE:
        # Templating shouldn't be confused or tasked with extra work.
        # I am attaching evaluation to string substitution here,
        # just to prove this can be done.
        val = eval_math_expr(val.format(**full))
    return val


data = yaml.load(open('./data.yml'))
str_template_substitute(data)

pprint(data)


注意:这个函数非常强大,因为它可以处理字典,而 JSON/YAML 和许多其他格式在 Python 中将其转换为字典。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接