ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

五分钟学会 CUE:learnxinyminutes-docs 配置语言实战指南

五分钟学会 CUE:learnxinyminutes-docs 配置语言实战指南 文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载CUE 是一种可导出为 JSON 或 YAML 的 JSON 超集配置语言以其类型即值与统一unification引擎著称。本文以 learnxinyminutes-docs 仓库中的 cue.md 教学文档为骨架从跨文件合并、冲突检测、默认值与析取到定义、模板、包与模块带你完整走一遍 CUE 的核心机制。读完本文你将能够独立编写可校验、可复用的 CUE 配置文件并用cue eval与cue export完成从 CUE 到 JSON/YAML 的实战转换。CUE 是什么表达力强但非图灵完备的 JSON 超集CUE 是一种富有表现力、但非图灵完备not Turing-complete的 JSON 超集可以导出为 JSON 或 YAML。它支持可选类型以及许多面向大规模配置集的处理便利特性。其统一引擎根植于逻辑编程logic programming因此为现代配置管理问题提供了现成的解决方案。这一设计定位非常关键CUE 有意限制计算能力以换取配置的可合并、可校验与可追溯这正是它区别于通用编程语言的地方。在 learnxinyminutes-docs 仓库中cue.md 是 CUE 语言的完整速览文档本文所有示例均继承自该文档读者可在仓库根目录直接打开对照阅读。跨文件统一多个文件合并成一个对象CUE 最核心的机制是统一unification当 CUE 导出为 JSON 时每个被处理文件中的值都会被统一进一个巨大的顶层对象。考虑下面两个文件//name.cue name: Daniel//disposition.cue disposition: oblivious现在统一并导出为 JSON% cue export name.cue disposition.cue { name: Daniel, disposition: oblivious }也可以导出为 YAML% cue export --out yaml name.cue disposition.cue name: Daniel disposition: oblivious从这个最小示例可以观察到三个细节C 风格的注释//不会出现在输出中CUE 语法中的键key通常不需要加引号某些特殊字符仍然需要引号包裹例如works_fine: true needs-quotes: true也就是说只要键名符合普通标识符规则就可以裸写一旦包含-等特殊字符就必须用双引号括起来。全局合并类型冲突与值冲突都会报错统一不仅发生在文件之间它同时也是所有类型和值的全局合并global merge。下面这个例子会失败因为类型不同//string_value.cue foo: baz//integer_value.cue foo: 100% cue export string_value.cue integer_value.cue foo: conflicting values baz and 100 (mismatched types string and int): integer_value.cue:1:6 string_value.cue:1:6注意错误信息末尾给出了文件名:行号:列号的精确位置这在大规模配置中极具诊断价值。即使把整数用引号包成字符串仍然会失败因为值冲突且无法将所有内容统一进一个顶层对象//string_value.cue foo: baz//integer_value.cue foo: 100 // a string now% cue export string_value.cue integer_value.cue foo: conflicting values 100 and baz: integer_value.cue:1:6 string_value.cue:1:6可见统一引擎对类型不一致和具体值不一致两种冲突都会给出明确的错误。这正是 CUE 能充当配置校验器的底层原因任何两处对同一路径的矛盾定义都无法悄悄通过。类型即值schema、默认值与数据同源CUE 中的类型本身就是值统一引擎知道某些特殊类型的取值行为。在统一过程中引擎要求具体值必须匹配指定类型而当需要具体值concrete value却只有一个类型时就会报错。所以下面这样是合法的street: 1 Infinite Loop street: string1 Infinite Loop满足string约束二者统一成功。cue export产出 YAML 或 JSON而cue eval产出 CUE。这很适合把 YAML/JSON 转成 CUE或直接用 CUE 检查统一后的输出。CUE 允许缺少具体值在同时存在类型与匹配的具体值时eval会优先输出具体值//type-only.cue amount: float% cue eval type-only.cue amount: float但如果想导出就必须有具体值或者用-c参数让eval强制要求具体值% cue export type-only.cue amount: incomplete value floatincomplete value是 CUE 中只有类型没有具体值时的典型报错。给这个类型统一一个具体值一切就正常了//concrete-value.cue amount: 3.14% cue export type-only.cue concrete-value.cue { amount: 3.14 }这种用共享语法把具体值与类型统一起来的方式非常强大而且比 JSON Schema 紧凑得多。由此schema、默认值和数据都可以用同一种语言——CUE——来表达避免了传统方案中定义与数据分离、易漂移的问题。默认值与析取Disjunction类型可以附带默认值用星号*标记// default-port.cue port: int | *8080% cue eval default-port.cue port: 8080int | *8080读作整数默认值 8080|表示析取disjunction*标记其中的默认分支。当没有其他约束时eval会选择默认值输出。枚举式的选项也是析取用|分隔//severity-enum.cue severity: high | medium | low severity: unknown% cue eval severity-enum.cue severity: 3 errors in empty disjunction: severity: conflicting values high and unknown: ./severity-enum.cue:1:11 ./severity-enum.cue:1:48 severity: conflicting values low and unknown: ./severity-enum.cue:1:31 ./severity-enum.cue:1:48 severity: conflicting values medium and unknown: ./severity-enum.cue:1:20 ./severity-enum.cue:1:48unknown不在high | medium | low三个分支中因此每个分支都与之冲突最终得到 empty disjunction空析取错误。析取同样可以作用于结构体struct其行为与直觉一致——即结构体层面也可以做多选一的约束。定义Definitions与运算符CUE 有定义definitions机制可以像其他语言中的变量声明那样使用也可以用来定义结构体类型。用可以把一个结构体类型的定义应用到某些具体值上。还可以用[...#Whatever]表示元素类型为 #Whatever 的列表// definitions.cue #DashboardPort: 1337 configs: { host: localhost port: #DashboardPort } #Address: { street: string city: string zip?: int // ? makes zip optional } some_address: #Address { street: 1 Rocket Rd city: Hawthorne } more_addresses: [...#Address] [ {street: 1600 Amphitheatre Parkway, city: Mountain View, zip: 94043}, {street: 1 Hacker Way, city: Menlo Park} ]导出结果% cue export --out yaml definitions.cue configs: host: localhost port: 1337 some_address: street: 1 Rocket Rd city: Hawthorne more_addresses: - street: 1600 Amphitheatre Parkway city: Mountain View zip: 94043 - street: 1 Hacker Way city: Menlo Park要点拆解以#开头的标识符如#DashboardPort、#Address是定义#DashboardPort: 1337定义了一个常量configs.port: #DashboardPort通过引用复用它#Address定义了结构体类型street、city必须为字符串zip用?标记为可选#Address {...}表示把#Address的约束应用到具体值上——some_address因此获得了类型校验[...#Address] [...]表示列表中的每个元素都必须满足#Address——注意第二个元素没有zip字段但这合法因为zip是可选的。复杂值与更精细的校验CUE 支持更复杂的值与校验比如正则匹配与数值范围#Country: { name: ~^\\p{Lu} // Must start with an upper-case letter pop: 800 9_000_000_000 // More than 800, fewer than 9 billion } vatican_city: #Country { name: Vatican City pop: 825 }~^\\p{Lu}表示name必须以大写字母Unicode 属性Lu开头800 9_000_000_000表示pop必须大于 800 且小于 90 亿数字中的下划线9_000_000_000只是可读性分隔符会被正常解析梵蒂冈城人口 825、首字母大写恰好满足全部约束因此统一成功。如果某个值不满足这些约束统一引擎会像前面一样给出精确的冲突错误——配置的安全网由此建立。路径语法糖嵌套路径与集合约束CUE 在纯 JSON 之上提供了大量语法糖能省去不少样板代码。下面这段仅用三行就完成了对嵌套结构的定义、修改与校验//paths.cue // path-value pairs outer: middle1: inner: 3 outer: middle2: inner: 7 // collection-constraint pair outer: [string]: inner: int% cue export paths.cue { outer: { middle1: { inner: 3 }, middle2: { inner: 7 } } }这里有两个值得注意的点outer: middle1: inner: 3是路径-值对的简写等价于嵌套的三层结构免去了逐层书写花括号outer: [string]: inner: int是集合-约束对[string]中的string被方括号包裹是向引擎声明这是一个类型约束而不是字符串字面量。它表示outer下任意字符串键对应的结构其inner字段都必须是整数。正是这第三条约束让middle1和middle2下的inner获得了类型校验——路径简写与约束校验在同一份文件里共存。模板Templates单参数函数与集合约束同源的还有模板templates它有点像一个单参数函数。下面Name被绑定到container下的每个字符串键而键下对应的结构体随之被求值//templates.cue container: [Name_]: { name: Name replicas: uint | *1 command: string } container: sidecar: command: envoy container: service: { command: fibonacci replicas: 2 }% cue eval templates.cue container: { sidecar: { name: sidecar replicas: 1 command: envoy } service: { name: service command: fibonacci replicas: 2 } }模板[Name_]:的语义是对container下的每一个键Name应用该结构体约束。于是sidecar键被绑定为Name sidecarname字段自动填入sidecarreplicas未指定时取默认值1service键的replicas显式设为2覆盖了默认值。这是用一条规则约束所有同类条目的典型场景——新增一个容器只需一行具体值模板自动补齐其余字段与校验。作用域与引用Scoped ReferencesCUE 支持作用域化引用。看这个例子//scopes-and-references.cue v: top-level v b: v // a reference a: { b: v // matches the top-level v } let V v a: { v: as inner v c: v // matches the inner v d: V // matches the top-level v now shadowed by a.v } av: a.v // matches as v% cue eval --out yaml scopes-and-references.cuev: top-level v b: top-level v a: b: top-level v v: as inner v c: as inner v d: top-level v av: as inner v逐条解读b: v在顶层引用顶层的v得到top-level va.b: v虽然在a内部但此时a内还没有自己的v所以仍解析到顶层vlet V v在顶层把v的值快照进局部常量V之后a内定义了a.v: as inner v于是a.c: v就近解析到a内层的v即as inner v而a.d: V仍引用被a.v遮蔽前的顶层值av: a.v显式写出路径匹配a的v。作者在文档中调整了输出键的顺序以方便阅读。键的顺序在 CUE 中并不重要同时要注意同一层级出现的重复键全部会被统一——这正是冲突检测与合并语义的体现。隐藏字段Hidden Fields以_为前缀的字段会被隐藏。如果需要导出的字段名真的以_开头就用引号包起来//hiddens.cue _foo: 2 _foo: 3 foo: 4 _#foo: 5 #foo : 6% cue eval hiddens.cue _foo: 2 foo: 4 #foo: 6 % cue export hiddens.cue { _foo: 2, foo: 4 }对比eval与export的输出可以看清隐藏规则的分层_foo: 3是隐藏字段eval和export都不输出_foo: 2被引号包裹是真实字段名因此被输出_#foo: 5是隐藏的定义eval中也不出现#foo: 6是普通定义eval会显示以便继续参与统一而export不输出——因为导出的是面向消费者的数据定义本身不属于数据。插值Interpolation值与字段名都能插CUE 支持对值和字段名做插值//interpolation.cue #expense: 90 #revenue: 100 message: Your profit was $\( #revenue - #expense) cat: { type: Cuddly is\(type): true }% cue export interpolation.cue { message: Your profit was $10, cat: { type: Cuddly, isCuddly: true } }字符串内使用$\(表达式)进行值插值#revenue - #expense被求值为10嵌入message字段名同样支持插值is\(type)中type是cat内的字段其值Cuddly被拼进字段名生成isCuddly字段。操作符、条件、列表推导与导入CUE 还提供操作符、条件、列表推导以及导入等能力//getting-out-of-hand-now.cue import strings // well come back to this // operators are nice g: 5 / 3 // CUE can do math h: 3 * blah // and Python-like string repetition i: 3 * [1, 2, 3] // with lists too j: 8 10 // and supports boolean ops // conditionals are also nice price: number // Require a justification if price is too high if price 100 { justification: string } price: 200 justification: impulse buy // list comprehensions are powerful and compact #items: [ 1, 2, 3, 4, 5, 6, 7, 8, 9] comp: [ for x in #items if x rem 2 0 {x*x}] // and... well you can do this too #a: [ Apple, Google, SpaceX] for k, v in #a { \( strings.ToLower(v) ): { pos: k 1 name: v nameLen: len(v) } }% cue export getting-out-of-hand-now.cue{ g: 1.66666666666666666666667, h: blahblahblah, i: [1, 2, 3, 1, 2, 3, 1, 2, 3], j: true, apple: { pos: 1, name: Apple, nameLen: 5 }, google: { pos: 2, name: Google, nameLen: 6 }, price: 200, justification: impulse buy, comp: [ 4, 16, 36, 64 ], spacex: { pos: 3, name: SpaceX, nameLen: 6 } }逐个拆解操作符g: 5 / 3做精确除法输出高精度小数h: 3 * blah是类似 Python 的字符串重复i: 3 * [1, 2, 3]对列表同样适用j: 8 10是布尔比较条件if price 100 { justification: string }声明如果价格过高则必须有理由字段随后price: 200触发条件justification: impulse buy满足约束列表推导for x in #items if x rem 2 0 {x*x}遍历 1~9筛选偶数并求平方得到[4, 16, 36, 64]导入与遍历生成字段import strings后for k, v in #a遍历公司名单用strings.ToLower(v)生成小写键名并用k 1与len(v)填充pos、nameLen——配置可以长出来而不是手写。需要提醒的是正如文档作者所言CUE 虽然不是图灵完备的但它足够强大到让你搬起石头砸自己的脚。如果滥用条件、遍历与推导配置会变得比手写更难维护。因此要保持配置清晰、善用注释这也是 cue.md 全文反复强调的工程纪律。包Packages同包文件自动统一CUE 文件默认是**独立standalone**的但如果文件顶部写上package子句就表示该文件与同一个包内的其他文件可以互相统一//a.cue package config foo: 100 bar: int//b.cue package config bar: 200把这两个文件放进一个新目录后运行不带参数的cue eval它们会如你所料被统一CUE 会搜索当前目录下的.cue文件若它们的包名相同就统一求值。模块Modules最大的组织单元包的概念在模块modules的语境下更清晰。模块是最大的组织单元基本上只要一个项目跨越多个文件就应该创建一个模块并用类似 URL 域名加路径的形式命名例如example.com/something。任何从该模块导入的内容——哪怕是模块内部导入——都必须使用完整的模块路径且以模块名为前缀。创建新模块mkdir mymodule cd mymodule cue mod init example.com/mymodule该命令会在mymodule目录下创建cue.mod/子目录其中包含module.cue定义模块名本例为module: example.com/mymodulepkg/gen/usr/关于cue.mod内各目录的详细用途CUE 官方文档的 packages 概念篇有更系统的说明就日常使用而言你几乎不需要关心该目录的内部细节只需记住模块名将成为模块内所有导入路径的前缀。模块的文件层级都根植于mymodule/即同时包含cue.mod/的那个目录。要导入某个包就用example.com/mymodule加上相对于mymodule/的路径作前缀。来看一个具体布局mymodule ├── config │ ├── a.cue │ └── b.cue ├── cue.mod │ ├── module.cue │ ├── pkg │ └── usr └── main.cuecue.mod/及其下的文件由cue mod init example.com/mymodule生成随后手工创建config/子目录放入a.cue、b.cue再创建顶层文件main.cue。不带参数运行cue eval时CUE 会检查当前目录下所有.cue文件是否属于同一个包——本例中只有main.cue声明了package mainmain 这个名字并无特殊含义只是恰如其分因此统一的就是它% cue eval configuredBar: 200main.cue的内容是//main.cue package main import example.com/mymodule/config configuredBar: config.bar而config/a.cue与config/b.cue就是前文那两个文件只不过都加上了package config//a.cue package config foo: 100 bar: int//b.cue package config bar: 200导入语句import example.com/mymodule/config使用了完整模块路径config.bar引用包导出的字段。若想验证config/下两个文件确实被统一了可以把a.cue中的bar: int改成bar: string再运行cue eval就会得到清晰的类型错误cue eval 2022-01-06 17:51:24 configuredBar: conflicting values string and 200 (mismatched types string and int): ./config/a.cue:4:6 ./config/b.cue:3:6 ./main.cue:5:16错误信息同时点名了三个参与冲突的位置类型定义处a.cue、具体值处b.cue以及引用处main.cue排查路径一目了然。文档作者还提到cue.mod的设计为未来更多的包管理特性预留了空间。内置模块开箱即用的标准库最后CUE 自带功能强大的内置模块built-in modules。前文import strings并调用strings.ToLower就是其中一例。不带完整模块名的导入默认视为内置模块。除strings外内置模块还涵盖数学、正则、时间、编码等多种领域各包完整清单与文档可在 CUE 官方的 Go 包文档站查阅对应源码组织在 cuelang.org 的go/pkg目录下。在仓库中继续研读本文是对 learnxinyminutes-docs 仓库 cue.md 的完整展开。该仓库是一个whirlwind tours旋风式速览系列教学库如 README.md 所述所有教程都以可运行的带注释代码形式呈现边看代码边讲解仓库的贡献规范也明确了这类文档的编写风格。若想深入 CUE 的官方教程与概念文档可直接从 cue.md 文末的指引出发其对应 CUE 官方 tutorials 与 packages 概念系列。掌握了统一、类型即值、定义、模板、包与模块这些核心概念后你便已具备把 CUE 用于真实项目配置管理的基础能力。赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载相关推荐五分钟速通 C 语言learnxinyminutes-docs 官方速查指南全解析五分钟速通 C 语言learnxinyminutes docs 官方速查指南全解析 本篇技术指南以 learnxinyminutes docs 仓库中的 C文档教程终极文言文编程入门5分钟用古汉语写出你的第一个Hello World程序终极文言文编程入门5分钟用古汉语写出你的第一个Hello World程序 wenyan lang文言文编程语言是一个独特的开源项目它让你能用古汉语语法编编程语言编译器终极指南5分钟学会tts-server-android多语言语音合成终极指南5分钟学会tts server android多语言语音合成 tts server android是一款功能强大的Android文本转语音应用它支持语音后端音频上一篇Linux tar 命令完全指南打包、压缩与解压实战手册下一篇Authelia 可观测性实战Prometheus 指标导出telemetry.metrics配置与指标详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表