Cue Lang介绍

作者: 潘晓华Michael | 来源:发表于2021-06-11 17:27 被阅读0次
    Cue Lang

    Cue,是一种开源语言,用于定义,生成和验证各种数据:配置,API,数据库模式,代码......。它能够将数据的结构、约束、数值作为同一层级成员,从而简化配置文件的生成。
    Cue教程

    Cue格式说明

    1. 使用//进行单行注释
    2. 对象被称为结构体
    3. 对象成员称为结构字段
    4. 对于没有特殊字符的字段名,可以省略引号
    5. 结构字段后面无需,
    6. 在列表中的最后一个元素后放置,
    7. 最外层的{}可省略

    例子:

    str: "hello world"
    num: 42
    flt: 3.14
    
    // Special field name (and a comment)
    "k8s.io/annotation": "secure-me"
    
    // lists can have different element types
    list: [
        "a", "b", "c",
        1,
        2,
        3,
    ]
    
    obj: {
        foo: "bar"
        // reuse another field?!
        L: list
    }
    

    Cue 结构、约束、数据

    // 结构
    album: {
        title: string
        year: int
        live: bool
    }
    
    // 约束
    album: {
        title: string
        year: >1950
        live: false
    }
    
    // 数据
    album: {
        title: "Houses of the Holy"
        year: 1973
        live: false
    }
    

    Cue的最佳实践:从开放的结构模式开始,限制上下文可能性,最终具体到数据实例。
    Cue哲学:为了保证唯一性,Cue的数据不会被覆盖。

    Cue核心规则

    1. 数据可被重复定义,但必须值保持一致
    2. 结构字段可以被更强限制覆盖
    3. 结构的字段会被合并,如果是列表,必须严格匹配
    4. 规则可被递规应用
    hello: "world"
    hello: "world"
    
    // set a type
    s: { a: int }
    
    // set some data
    s: { a: 1, b: 2 }
    
    // set a nested field without curly braces
    s: c: d: 3
    
    // lists must have the same elements
    // and cannot change length
    l: ["abc", "123"]
    l: [
        "abc",
        "123"
    ]
    

    结构

    1. 结构并不会输出
    2. 它的值可能是不确认、不完整的
    3. 字段必须完全

    使用#mydef来定义结构,使用...来定义一个开放的结构体

    #Album: {
        artist: string
        title: string
        year: int
    
        // ...  uncomment to open, must be last
    }
    
    // This is a conjunction, it says "album" has to be "#Album"
    album: #Album & {
        artist: "Led Zeppelin"
        title: "Led Zeppelin I"
        year: 1969
    
        // studio: true  (uncomment to trigger error)
    }
    
    
    #Person: {
     name: string
    ... // open struct
    }
    
    Jim: #Person & {
     name: "jim"
     age: 12
    }
    

    约束

    约束与数值使用&字符进行连接时,会将值进行校验

    // conjunctions on a field
    n: int & >0 & <100
    n: 23
    
    // conjuction of schemas
    val: #Def1 & #Def2
    val: { foo: "bar", ans: 42 }
    
    #Def1: {
        foo: string
        ans: int
    }
    
    #Def2: {
        foo: =~ "[a-z]+"
        ans: >0
    }
    

    替换

    使用|可以实现支持多种结构。同时它也可以为出错值设置替换值

    // disjunction of values (like an enum)
    hello: "world" | "bob" | "mary"
    hello: "world"
    
    // disjunction of types
    port: string | int
    port: 5432
    
    // disjunction of schemas
    val: #Def1 | #Def2
    val: { foo: "bar", ans: 42 }
    
    #Def1: {
        foo: string
        ans: int
    }
    
    #Def2: {
        name: string
        port: int
    }
    

    默认值与可选

    使用*来设置默认值, ?设置可选字段

    s: {
        // field with a default
        hello: string | *"world" | "apple"
        // an optional integer
        count?: int
    }
    

    开放模式与封闭模式

    开放模式意味着结构可以扩展,关闭模式意味着不能扩展。 默认情况下,结构是开放模式,定义是封闭模式。 可以通过定义的最后添加...来申明开放模式定义;另外通过过close强制为结构体设置为关闭模式

    // Open definition
    #d: {
        foo: "bar"
        ... // must be last
    }
    
    // Closed struct
    s: close({
        foo: "bar"
    })
    
    jim: {
      name: "Jim"
    }
    jim: {
      age: 12
    }
    

    推荐从基础定义开始,复用定义

    在编写Cue时,推荐从基础定义开始,这样能够有更好的复用能力。

    #Base: {
        name: string
        kind: string
        ... // so it can be extended
    }
    #Meta: {
        // string and a semver regex
        version: string & =~"^v[0-9]+\\.[0-9]+\\.[0-9]+$"
        // list of strings
        labels: [...string]
    }
    
    #Permissions: {
        role: string
        public: bool | *false
    }
    
    // Building up a schema using a conjunction and embedding
    #Schema: #Base & {
        // "embed" meta and permissions
        #Meta
        #Permissions
        // with no '...' this is final
    }
    
    value: #Schema & {
        name: "app"
        kind: "deploy"
        version: "v1.0.42"
        labels: ["server", "prod"]
        role: "backend"
        // public: false  (by default)
    }
    

    使用"""来定义多行字符串

    str1: #"avoid using \ to "escape""#
    str2: """
    a nested multiline
    string goes here
    """
    

    List

    List 可被定义为开放模式,这样便可与其它数据进行合并,

    empty: []
    any: [...]
    ints: [...int]
    nested: [...[...string]]
    
    opened: ints & [1,2,...]
    closed: ints & [1,2,3]
    
    // list of for constrained ints
    ip: 4 * [uint8]
    // sets the first element
    tendot: ip & [10, ...uint8]
    // uses constraint as second element
    one72: ip & [172, >=16 & <=32, ...]
    
    mixed: any & [...] & ["a",1, { foo: "bar" }]
    join: [1,2] + [3,4]
    Join: opened & join
    

    Struct

    结构体是Cue的主要内容,也是最终数据的输出。如上介绍,默认情况下它是开放模式。除了使用Json类型形式进行设置值,还可通过级联:来设置,如a: hello: "world"

    // an open struct
    a: {
        foo: "bar"
    }
    
    // shorthand nested field
    a: hello: "world"
    
    // a closed struct
    b: close({
        left: "right"
    })
    

    模式匹配约束

    模式匹配允许您为与模式匹配的标签指定约束。可以将约束应用于字符串标签,并使用标识符来设置字段。

    #schema: {
        name: string
        ans: string
        num: int | *42
    }
    
    // match elem fields and alias labels to Name,
    // unify with schema, set name to Name by label
    elems: [Name=_]: #schema & { name: Name }
    
    elems: {
        one: {
            ans: "solo"
            num: 1
        }
        two: {
            ans: "life"
        }
    }
    
    elems: other: { ans: "id", num: 23 }
    

    表达式

    1. 引用字段,使用\(**)显用其它字段
    container: {
        repo: "docker.io/cuelang"
        image: "cue"
        version: "v0.3.0"
        full: "\(repo)/\(image):\(version)"
    }
    
    name: "Tony"
    msg: "Hello \(name)"
    // conver string to bytes
    b: '\(msg)'
    // convert bytes to string
    s: "\(b)"
    
    1. Cue也能够为通过\(**)来设置key
    apps: ["nginx", "express", "postgres"]
    #labels: [string]: string
    stack: {
        for i, app in apps {
            "\(app)": {
                name: app
                labels: #labels & {
                    app: "foo"
                    tier: "\(i)"
                }
            }
        }
    }
    
    1. List遍历
      遍历List数据格式如下:[ for key, val in <iterable> [condition] { production } ]
    nums: [1,2,3,4,5,6]
    sqrd: [ for _, n in nums { n*n } ]
    even: [ for _, n in nums if mod(n,2) == 0 { n } ]
    
    listOfStructs: [ for p, n in nums {
        pos: p
        val: n
    }]
    
    extractVals: [ for p, S in listOfStructs { S.val } ]
    
    1. 条件控制语句
      没有else,所有判断都会被执行
    app: {
        name: string
        tech: string
        mem: int
    
        if tech == "react" {
            tier: "frontend"
        }
        if tech != "react" {
            tier: "backend"
        }
    
        if mem < 1Gi {
            footprint: "small"
        }
        if mem >= 1Gi && mem < 4Gi {
            footprint: "medium"
        }
        if mem  >= 4Gi {
            footprint: "large"
        }
    }
    

    标准库

    Cue的标准库中包含了很多的帮助包(helper packages)。

    1. Encoding
    package stdlib
    
    import (
        "encoding/json"
    )
    
    data: """
    {
        "hello": "world",
        "list": [ 1, 2 ],
        "nested": {
            "foo": "bar"
        }
    }
    """
    
    jval: json.Unmarshal(data)
    
    val: {
        hello: "world"
        list: [1,2]
        nested: foo: "bar"
    }
    
    cjson: json.Marshal(val)
    
    1. Strings
    package stdlib
    
    import "strings"
    
    s: "HelloWorld"
    
    u: strings.ToUpper(s)
    l: strings.ToLower(s)
    
    line: "Cue stands for configure, unify, execute"
    words: strings.Split(line, " ")
    lined: strings.Join(words, " ")
    
    haspre: strings.HasPrefix(line, "Cue")
    index:  strings.Index(line, "unify")
    
    1. List
    package stdlib
    
    import "list"
    
    l1: [1,2,3,4,5]
    l2: ["c","b","a"]
    
    // constrain length
    l2: list.MinItems(1)
    l2: list.MaxItems(3)
    
    // slice a list
    l3: list.Slice(l1, 2,4)
    
    // get the sum and product
    sum: list.Sum(l1)
    prd: list.Product(l1)
    
    // linear search for list (no binary)
    lc: list.Contains(l1, 2)
    
    // sort a list
    ls: list.Sort(l2, list.Ascending)
    l2s: list.IsSorted(l2, list.Ascending)
    lss: list.IsSorted(ls, list.Ascending)
    
    // Flatten a list
    ll: [1,[2,3],[4,[5]]]
    lf: list.FlattenN(ll, 1)
    
    1. Constrain
    package stdlib
    
    import (
        "net"
        "time"
    )
    
    // string with ip format
    ip: net.IPv4
    ip: "10.1.2.3"
    
    // string with time format
    ts: time.Format(time.ANSIC)
    ts: "Mon Jan 2 15:04:05 2006"
    

    使用Cue制作脚本命令工具

    Cue 拥有制作脚本命令工具的功能,它有一个工具层,可用来执行脚本、读写文件以及网络访问等。
    规范:

    • 脚本文件以_tool.cue结尾
    • 执行命令为cue cmd <name> or cue <name>
      例子:
    1. 脚本文件名为ex_tool.cue
    package foo
    
    import (
        "tool/cli"
        "tool/exec"
        "tool/file"
    )
    
    // moved to the data.cue file to show how we can reference "pure" Cue files
    // city: "Amsterdam"
    
    // A command named "prompter"
    command: prompter: {
    
        // save transcript to this file
        var: file: *"out.txt" | string @tag(file) // you can use "-t flag=filename.txt" to change the output file, see "cue help injection" for more details
    
        // prompt the user for some input
        ask: cli.Ask & {
            prompt:   "What is your name?"
            response: string
        }
    
        // run an external command, starts after ask
        echo: exec.Run & {
            // note the reference to ask and city here
            cmd:    ["echo", "Hello", ask.response + "!", "Have you been to", city + "?"]
            stdout: string // capture stdout, don't print to the terminal
        }
    
        // append to a file, starts after echo
        append: file.Append & {
            filename: var.file
            contents: echo.stdout // becuase we reference the echo task
        }
    
        // also starts after echo, and concurrently with append
        print: cli.Print & {
            text: echo.stdout // write the output to the terminal since we captured it previously
        }
    }
    
    • prompter为命令名
    • ask/echo/append/print为唯一标识
    • cli.Ask/exec.Run/file.Append为函数,
    • &{...}为函数参数
    1. 创建data.cue
    package foo
    
    city: "Amsterdam"
    
    1. 运行:cue cmd prompter or cue prompter
    $ cue cmd prompter
    What is your name? he
    Hello he! Have you been to Amsterdam?
    $ cat out.txt
    Hello he! Have you been to Amsterdam?
    

    Tips

    • A & B === B & A
    • A === A
    • 路径短写:{a : {b: {c: 5}}} == a b c: 5
    • 多种类型:a | b | c
    • 默认值:number | *1
    • 算术: 4 + 5
    • 变量引用:"Hello (person)"
    • 列表遍历:[ x for x in y ]
    • cue 执行 当前目录下的cue文件及父目录下同一个package的cue文件
    • cue ./... 以上目录 + 遍历当前目录的子目录下的cue文件
    • _开头的变量不会在输出结果中显示,作为局部变量
    • [Name=_] 可用来定义一个模板,其中Name匹配任意字段。例如:
      application: [Name=_]: {
        name: string | *Name
      }
      
    • | 可判断是否存在。例如:if _variable != | { // ... }
    • 定义映射:map: [string]: string
    • 定义切片:slice: [...{name:string,value:string}]

    实践

    Go To Cue
    1. 使用 cue import 将已有的yaml转成Cue语言
    $ cue import ./... -p kube -l '"\(strings.ToCamel(kind))" "\(metadata.name)"' -fR 
    
    1. 引入k8s资源的模块
    $ go mod init main
    $ cue get go k8s.io/api/extensions/v1beta1 -v
    
    1. 导入k8s资源模块,并创建资源
    package kube
    import (
      "k8s.io/api/core/v1"
      "k8s.io/api/extensions/v1beta1"
    )
    service <Name>: v1.Service
    deployment <Name>: v1beta1.Deployment
    

    参考文档

    cue torials
    cue语法
    cue语言入门

    相关文章

      网友评论

        本文标题:Cue Lang介绍

        本文链接:https://www.haomeiwen.com/subject/bndveltx.html