万物皆可生成



CUE 在配置管理方面非常出色,而且解决了大量目前我们其他选择的根本问题。

虽然这是大多数人使用 CUE 的主要使用场景,但是实际上可以用 CUE 生成任何东西。

怎么可能?可以用 text/template 包来渲染任何我们想要的文件。

用 CUE 生成

first.cue

package gen

import "text/template"

data: {
	// meta info
	name: *"tasks" | string @tag(name)
	msg:  "Hello \(name), welcome to Cuetorials"

	// task list
	tasks: [
		{name: "t1", effort: 1, complete: true},
		{name: "t2", effort: 4, complete: true},
		{name: "t3", effort: 3, complete: false},
		{name: "t4", effort: 2, complete: true},
		{name: "t5", effort: 3, complete: false},
	]

	// grouped tasks
	complete: [ for t in tasks if t.complete == true {t}]
	incomplete: [ for t in tasks if t.complete == false {t}]
}

// Templates which use Go's template engine
templates: [
	{
		filename: "\(data.name)-todo.txt"
		contents: """
			{{ .msg }}

			--- TODO ---
			{{ range $T := .incomplete -}}
			{{ printf "%-4s%v" $T.name $T.effort }}
			{{ end }}
			"""
	}, {
		filename: "\(data.name)-done.txt"
		contents: """
		Here's what you have finished \(data.name). Good job!

		--- DONE ---
		{{ range $T := .complete -}}
		{{ $T.name }}
		{{ end }}
		"""
	},
]

// The rendered output from data+templates
rendered: [ for T in templates {
	filename: T.filename
	contents: template.Execute(T.contents, data)
}]

first_tool.cue

package gen

import (
	"tool/file"
)

command: "gen": {
	for i, R in rendered {
		// make a unique name when comprehending
		"write-\(i)": file.Create & {
			filename: R.filename
			contents: R.contents
		}
	}
}
cue cmd -t "name=bob" gen

CUE 的模板系统使用的 Go 的模板系统,所以所有的兼容性和规则都是一样的。

我们使用了 list 和 field 推导分别渲染和写入我们的模板,使用复杂的 CUE 定义、值以及模板你可以生成任何输出。

使用 Hof 生成

在 Hofstadter,我们使用 hof 用于复杂的代码生成, 我们希望可以通过唯一的来源来生成数据库、后端以及前端的代码。

我们认为对于开发这来说是,这是 high codelow code)的解决方案,CUE 被选为 UX/DX 来写输入(设计)和生成器。

之前系统的两个问题是:

  1. 你总会在生成的代码中编写自定义的代码
  2. 设计发展了,但是你已经生成了样板代码

解决方案实际上非常简单,保留最初生成代码代码的副本,然后使用 3-way diff 工具来合并设计更新和自定义的代码。

将下面两个文件放到同一个文件夹,然后运行 hof mod vendor cuehof gen

cue.mods

module cuetorials.com/gen-with-hof

cue 0.4.0

require (
	github.com/hofstadter-io/hof v0.5.17
)

hof.cue

package gen

import (
	// import hof's generator schema
	"github.com/hofstadter-io/hof/schema/gen"
)

// A schema for our generator's input
#Input: {
	name: string
	todos: [...{
		name:     string
		effort:   int
		complete: bool
	}]
}

// create a generator
#Gen: gen.#HofGenerator & {
	// We often have some input values for the user to provide.
	// Use a Cue definition to enforce a schema
	Input: #Input

	// Required filed for generator definitions, details can be found in the hof docs
	PackageName: "dummy"

	// Required field for a generator to work, the list of files to generate
	Out: [...gen.#HofGeneratorFile] & [
		todo,
		done,
		debug,
	]

	// In is supplied as the root data object to every template
	// pass user inputs to the tempaltes here, possibly modified, enhanced, or transformed
	In: {
		INPUT:      Input
		Completed:  _C
		Incomplete: _I
	}

	// calculate some internal data from the input
	_C: [ for t in Input.todos if t.complete == true {t}]
	_I: [ for t in Input.todos if t.complete == false {t}]

	// the template files
	todo: {
		Template: """
			Hello {{ .INPUT.name }}.
			
			The items still on your todo list:

			{{ range $T := .Incomplete -}}
			{{ printf "%-4s%v" $T.name $T.effort }}
			{{ end }}
			"""
		// The output filename, using string interpolation
		Filepath: "\(Input.name)-todo.txt"
	}
	done: {
		Template: """
			Here's what you have finished {{ .INPUT.name }}. Good job!

			{{ range $T := .Completed -}}
			{{ $T.name }}
			{{ end }}
			"""
		Filepath: "\(Input.name)-done.txt"
	}

	// useful helper
	debug: {
		Template: """
			{{ yaml . }}
			"""
		Filepath: "debug.yaml"
	}
}

// Add the @gen(<name>,<name>,...) to denote usage of a generator
Gen: _ @gen(todos)
// Construct the generator
Gen: #Gen & {
	Input: {
		name: "tasks"
		todos: [
			{name: "t1", effort: 1, complete: true},
			{name: "t2", effort: 4, complete: true},
			{name: "t3", effort: 3, complete: false},
			{name: "t4", effort: 2, complete: true},
			{name: "t5", effort: 3, complete: false},
		]
	}
}

你可以通过下面的链接了解更多 hof 相关的内容:

也有许多 hofmod 的仓库用于生成 CLI、API 以及更多其他内容,

hofmodhof 工具的分离意味着你可以创建你自己的而不需要修改我们的代码。 因为它们也是 CUE 的模块,所以你可以使用 CUE 的所有功能,也可以引用它们来重用并扩展你自己的生成器和项目。

我们绝不会将你的邮箱分享给任何人。
2022 Hofstadter, Inc