开始第一个pylon工程,我们就跟yaml配置文件开始了不解之缘。yaml是什么?它有哪些规则?
大IBM的文章如是说:YAML 是一种比 XML 更为简单易读的序列化语言。Structure通过空格来展示,Sequence里的项用"-"来代表,Map里的键值对用":"分隔. 这几乎就是所有的语法了.
真的是这样简单吗?发布上线项目rigger却告诉我们mysql host没配置,配好再上线发现DOMAIN_PREFIX没配置,更一头雾水的是yaml里的各种符号" ! & * << ",看不懂,只能小心翼翼如履薄冰Ctrl+c , Ctrl+v,Ctrl+c , Ctrl+v ....
本文试图让你完全理解如下配置sample.yaml,并能通过一个小程序解析这个配置。需要大家动手跑一跑python程序,10分钟后,就再也不怕yaml的BT标记了。
5分钟workshop
sample.yaml:
reusable: - !R.vars &common_setting REDIS_PORT: 16379 BETA_REDIS: 6 ONLINE_REDIS: 7 GIT_PATH: "/usr/local/bin/git" PHP_ERROR: "E_ERROR" SDK_PATH: "/home/q/php/plato_sdk/" CONFSVC_URL: "plato.svc.1360.com" XHPROF: "OFF" - !R.vars &sdk_deploy pkg : "pkg" name : "plato_sdk" root : "./sdk" __env: cache: !R.env res: - !R.vars debug: !R.vars &debug_support PHP_ERROR: "E_ALL & ~E_NOTICE" DEBUG: "ON" PYL_LOG_MODE: "DEBUG" dev: !R.env res: - !R.using refs: - *common_setting - *debug_support - !R.vars ENV: "dev" DOMAIN_PREFIX: "${USER}."beta_sdk: !P.publish <<: *sdk_deploy host: "127.0.0.1" |
现在看到了这样一个文件,怎么读懂它?处理它?
基本语法:
- 列表: 哈希对& 表示一个"锚点标记",其它节点可以使用"*"或"<<: *"来引用它的值* 引用,指node4的内容与node3完全一致<<: * 的作用,指node5的内容包含但不完全相同于node3的值。! tag标记,可以注册自己指定的类型 |
语法知道了,怎么解析?下面来一个简单易懂小例子, 为了省事儿,yaml.add_multi_constructor(u"!R.env", construct_object) 用map指代 !R.xxx 代表的自定义类
yaml_simple.py :
import sysimport yamlfrom yaml import load, dumpdef construct_object(loader, suffix, node): return loader.construct_yaml_map(node)yaml.add_multi_constructor(u"!R.env", construct_object)yaml.add_multi_constructor(u"!P.publish", construct_object)yaml.add_multi_constructor(u"!R.using", construct_object)yaml.add_multi_constructor(u"!P.pkg", construct_object)yaml.add_multi_constructor(u"!R.vars", construct_object)txt = file("sample.yaml").read()data = load(txt)print data print data['__env']['dev']['res'][0]['refs'][0]['SDK_PATH'] |
找台测试服务器,把两个文件放上去,命令行执行:python yaml_simple.py
输出:
{'beta_sdk': {'host': '127.0.0.1', 'root': './sdk', 'pkg': 'pkg', 'name': 'plato_sdk'}, '__env': {'debug': {'DEBUG': 'ON', 'PHP_ERROR': 'E_ALL & ~E_NOTICE', 'PYL_LOG_MODE': 'DEBUG'}, 'cache': {'res': [{'API_PROXY': 'http://127.0.0.1:8086'}]}, 'dev': {'res': [{'refs': [{'SDK_PATH': '/home/q/php/plato_sdk/', 'ONLINE_REDIS': 7, 'BETA_REDIS': 6, 'GIT_PATH': '/usr/local/bin/git', 'CONFSVC_URL': 'plato.svc.1360.com', 'PHP_ERROR': 'E_ERROR', 'REDIS_PORT': 16379, 'XHPROF': 'OFF'}, {'DEBUG': 'ON', 'PHP_ERROR': 'E_ALL & ~E_NOTICE', 'PYL_LOG_MODE': 'DEBUG'}]}, {'DOMAIN_PREFIX': '${USER}.', 'ENV': 'dev'}]}}, 'reusable': [{'SDK_PATH': '/home/q/php/plato_sdk/', 'ONLINE_REDIS': 7, 'BETA_REDIS': 6, 'GIT_PATH': '/usr/local/bin/git', 'CONFSVC_URL': 'plato.svc.1360.com', 'PHP_ERROR': 'E_ERROR', 'REDIS_PORT': 16379, 'XHPROF': 'OFF'}, {'root': './sdk', 'pkg': 'pkg', 'name': 'plato_sdk'}]}/home/q/php/plato_sdk/ |
5分钟进阶
YAML(IPA: /ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达资料序列的格式。
YAML是"YAML Ain't a Markup Language"(YAML不是一种置标语言)的递回缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种置标语言),但为了强调这种语言以数据做为中心,而不是以置标语言为重点,而用返璞词重新命名。
以下为可能用到的各种符号的解释和示例:
---## > 的作用,以缩进对齐来判断是否为一段文字,也就是说,一旦缩进与上一行不一致,则认为是一个新行。# node1的例子中,第一行"Ther... door",# 第二行" "Please... floor"",# 第三行"So...So2"node1: > Ther once was a man from Darjeeling Who got on a bus bound for Ealing It said on the door "Please don't spit on the floor" So he carefully spat on the ceiling So2# | 的作用,它表示之后的文字,每一行均为一个新行。node2: | Ther once was a man from Darjeeling Who got on a bus bound for Ealing It said on the door "Please don't spit on the floor" So he carefully spat on the ceiling# & 的作用,它表示一个"锚点标记",其它节点可以使用"*"或"<<: *"来引用它的值node3: &node3 a: 001 b: 002# * 的作用,指node4的内容与node3完全一致node4: *node3 # <<: * 的作用,指node5的内容包含但不完全相同于node3的值。node5: <<: *node3 c: 003# !! 的作用,强迫转换类型。#输出:#{"node6"=>{# "a"=>#<YAML::PrivateType:0x9df6d40 @value="123", @type_id="float">,# "b"=>#<YAML::PrivateType:0x9df6ae8 @value="true", @type_id="str">,# "c"=>true#}#注意:c的值为布尔型。node6: a: !!float 123 b: !!str true c: True# 二进制内容的表示node7: !!binary | xxxxxxxxxxxxx xxxxxxxxx xxxxxnode8_value: &node8_value {id: 10000, code: item_manager, name: 项目经理}#自定解析类型,YAML某Key的Value一般为Array或Hash,但如果需要将Value解析为其它的自定义类型,可以使用该方法。#步骤:# 1、首先定义 MyCustClass 类,如:# class MyCustClass# attr_accessor :id# attr_accessor :code# def initialize v_hash# @id = v_hash["id"]# @code = v_hash["code"]# end# end# 2、向YAML注册解释类型,如:# YAML::add_domain_type("yaml.org,2002", 'MyCustClass') do |type, val|# MyCustClass.new(val)# end# 3、OK,当YAML文件加载时,YAML将自动将"node8"的值解析为MyCustClass类型。# 4、测试一下,x["node8"] >> #<MyCustClass:0x9df1c88 @code="item_manager", @id=10000># x["node8"].code >> "item_manager"node8: !MyCustClass <<: *node8_value# ? 的作用,用来明确的表示多个词汇组成的键值# a["node9"] => {{"a"=>1, "b"=>2}=>[1, 2], "c"=>3}node9: ? {a: 01, b: 02} : [1, 2] c: 3 |
want more?
有兴趣的同学可以进一步了解rigger源码,会发现诸如!P.publish、!R.using,其实都是写的一个python类,会有自定义的属性,所以某些属性未定义,就会报错了。更有兴趣的可以把 yaml_simple.py 扩展为小工具,结合rigger,随时检查自己的配置文件是否有错误。
推荐yaml在线验证工具 https://yaml-online-parser.appspot.com/ 可以提前发现格式问题,but 要翻墙,翻墙设置见附录。
yaml提示不够友好,对齐错误有可能提示为“yaml.parser.ParserError: while parsing a block mapping“ ” expected <block end>, but found '<block mapping start>' 需要小心注意
总结
为什么不是XML呢?因为:
- YAML的可读性好。
- YAML和脚本语言的交互性好。
- YAML使用实现语言的数据类型。
- YAML有一个一致的信息模型。
- YAML易于实现。
上面5条也就是XML不足的地方。同时,YAML也有XML的下列优点:
- YAML可以基于流来处理;
- YAML表达能力强,扩展性好。
总之,YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务。当然简单的工具用深入之后就成了开篇那种样子,门槛有点高,但也体现深入研究一个小玩意的有趣之处。
参考文章:
http://zh.wikipedia.org/wiki/YAML
http://www.ibm.com/developerworks/cn/xml/x-cn-yamlintro/
http://www.yaml.org/spec/1.2/spec.html