Merge pull request #2096 from helderco/coding-style
docs: Package coding style
This commit is contained in:
commit
732ef96a06
383
docs/guides/1226-coding-style.md
Normal file
383
docs/guides/1226-coding-style.md
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
---
|
||||||
|
slug: /1226/coding-style
|
||||||
|
displayed_sidebar: europa
|
||||||
|
---
|
||||||
|
|
||||||
|
# Package Coding Style
|
||||||
|
|
||||||
|
Please follow these guidelines when contributing CUE packages to keep consistency,
|
||||||
|
improve clarity and avoid issues.
|
||||||
|
|
||||||
|
## Public names
|
||||||
|
|
||||||
|
Choose `PascalCase` for "public" definitions (importable outside of package).
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// good
|
||||||
|
#WriteFile: {}
|
||||||
|
|
||||||
|
// bad
|
||||||
|
#writeFile: {}
|
||||||
|
#write_file: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose `camelCase` for "public" fields, instead of `snake_case` or `"kebab-case"`.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// good
|
||||||
|
sshKey: ...
|
||||||
|
|
||||||
|
// bad
|
||||||
|
ssh_key: ...
|
||||||
|
"ssh-key": ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Private names
|
||||||
|
|
||||||
|
Choose `_#camelCase` for private definitions that should only be used in current package.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// good
|
||||||
|
_#mergeStructs: { ... }
|
||||||
|
|
||||||
|
// bad, starts with lower case but not private
|
||||||
|
#mergeStructs: { ... }
|
||||||
|
|
||||||
|
// bad, starts with upper case but private
|
||||||
|
_#MergeStructs: { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose `_camelCase` for private fields (implementation details).
|
||||||
|
|
||||||
|
```cue
|
||||||
|
#Copy: {
|
||||||
|
// good, indicates internal implementation
|
||||||
|
_copy: core.#Copy
|
||||||
|
|
||||||
|
// bad, indicates it can be used directly
|
||||||
|
copy: core.#Copy
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Definitions for *schemas*, fields for concrete *implementations*
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// good, defines a schema
|
||||||
|
#Copy: {
|
||||||
|
// good, implements a schema
|
||||||
|
input: #Image
|
||||||
|
_copy: core.#Copy & { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad, not a definition
|
||||||
|
Copy: {
|
||||||
|
// bad, not using a visible or hidden field (according to intent)
|
||||||
|
#input: #Image
|
||||||
|
_#copy: core.#Copy & { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok for mixin
|
||||||
|
Mixin: { ... }
|
||||||
|
copy: #Copy & Mixin & { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## All public definitions and fields must be documented
|
||||||
|
|
||||||
|
Documenting other fields is optional. The main idea is that public fields should be documented for someone
|
||||||
|
reading Dagger’s documentation in the browser. Everything else is for someone looking at the code.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// Copy files into an image
|
||||||
|
#Copy: {
|
||||||
|
// Image to receive copied files
|
||||||
|
input: #Image
|
||||||
|
|
||||||
|
// Files to copy
|
||||||
|
contents: dagger.#FS
|
||||||
|
|
||||||
|
_copy: ...
|
||||||
|
|
||||||
|
// Resulting image with added files
|
||||||
|
output: ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## One definition per file
|
||||||
|
|
||||||
|
Split action definitions into their own file as much as possible (unless very tightly related).
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// copy.cue
|
||||||
|
#Copy: ...
|
||||||
|
|
||||||
|
// push.cue
|
||||||
|
#Push: ...
|
||||||
|
|
||||||
|
// exec.cue
|
||||||
|
#Exec: ...
|
||||||
|
#Mount: ...
|
||||||
|
#CacheDir: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use verbs for action names, nouns otherwise
|
||||||
|
|
||||||
|
Action definition names should be verbs (e.g., `#Build`, `#Run`, `#Push`).
|
||||||
|
Other types of definitions should be nouns as much as possible (e.g., `#Image`, `#Secret`, `#Socket`).
|
||||||
|
|
||||||
|
## No field abbreviations
|
||||||
|
|
||||||
|
CUE fields are more explicit and intuitive without abbreviations (e.g, `target` or
|
||||||
|
`destination` instead of `dest`).
|
||||||
|
|
||||||
|
## Avoid interpolation
|
||||||
|
|
||||||
|
There's no need to interpolate a variable if it's already
|
||||||
|
a string.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
let greet = "hello world"
|
||||||
|
|
||||||
|
// bad
|
||||||
|
args: ["echo", "\(greet)"]
|
||||||
|
read: contents: "\(greet)"
|
||||||
|
|
||||||
|
// good
|
||||||
|
args: ["echo", greet]
|
||||||
|
read: contents: greet
|
||||||
|
```
|
||||||
|
|
||||||
|
Even for field names there's a better way:
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
client: filesystem: "\(path)": read: contents: string
|
||||||
|
files: "\(path)": output.contents
|
||||||
|
|
||||||
|
// good
|
||||||
|
client: filesystem: (path): read: contents: string
|
||||||
|
files: (path): output.contents
|
||||||
|
```
|
||||||
|
|
||||||
|
It's ok if you need to convert something into a string.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
exit: int
|
||||||
|
|
||||||
|
env: [string]: string
|
||||||
|
env: EXIT: "\(exit)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Interpolating a really short script is actually harmless,
|
||||||
|
but not if it discourages from splitting the script into
|
||||||
|
its own file.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
let name = "world"
|
||||||
|
run: bash.#Run & {
|
||||||
|
script: contents: "echo hello \(world)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
run: bash.#Run & {
|
||||||
|
env: NAME: name
|
||||||
|
script: contents: "echo hello $NAME"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Don’t inline scripts
|
||||||
|
|
||||||
|
Avoid inlining scripts (e.g., *sh*, *py*, etc). Instead, put them in their own files
|
||||||
|
with proper extension, and use `core.#Source` to import into CUE. This allows linting
|
||||||
|
and avoids some limitations (script size, escaping).
|
||||||
|
|
||||||
|
Some exceptions may apply (e.g., the script is really short or interpolation is
|
||||||
|
actually necessary).
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
run: bash.#Run & {
|
||||||
|
script: contents: """
|
||||||
|
...
|
||||||
|
...
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
src: core.#Source & {
|
||||||
|
path: "./src"
|
||||||
|
}
|
||||||
|
run: bash.#Run & {
|
||||||
|
script: {
|
||||||
|
directory: src.output
|
||||||
|
filename: "setup.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok
|
||||||
|
run: bash.#Run & {
|
||||||
|
script: contents: "echo hello world"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Avoid raw strings
|
||||||
|
|
||||||
|
Don’t use `#"""` unless you actually need it. Most of the time you won't.
|
||||||
|
The regular `"""` is simpler to read and less scary for beginners.
|
||||||
|
|
||||||
|
<!-- TODO: Add example where it actually helps or is ok. -->
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
run: python.#Run & {
|
||||||
|
script: contents: #"""
|
||||||
|
print("Hello World!")
|
||||||
|
"""#
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
run: python.#Run & {
|
||||||
|
script: contents: """
|
||||||
|
print("Hello World!")
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad
|
||||||
|
run: python.#Run & {
|
||||||
|
script: contents: #"print("Hello World!")"#
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
run: python.#Run & {
|
||||||
|
script: contents: "print('Hello World')"
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
run: python.#Run & {
|
||||||
|
script: contents: 'print("Hello World")'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Favor disjunctions over *if* conditions
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
type: string
|
||||||
|
if type == "fs" {
|
||||||
|
contents: dagger.#FS
|
||||||
|
ro?: true | *false
|
||||||
|
}
|
||||||
|
if type == "cache" {
|
||||||
|
contents: dagger.#Secret
|
||||||
|
mask: int
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
{
|
||||||
|
contents: dagger.#FS
|
||||||
|
ro?: true | *false
|
||||||
|
} | {
|
||||||
|
contents: dagger.#Secret
|
||||||
|
mask: int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Favor templates over *for* loops
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
files: ["stdout.log", "stderr.log"]
|
||||||
|
export: {
|
||||||
|
for path in files {
|
||||||
|
(path): #Read & {
|
||||||
|
"path": path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
export: [path=string]: #Read & {
|
||||||
|
"path": path
|
||||||
|
}
|
||||||
|
export: {
|
||||||
|
"stdout.log": _
|
||||||
|
"stderr.log": _
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scope conditions and loops as locally as possible
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
if host != _|_ {
|
||||||
|
env: HOST: host
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
env: {
|
||||||
|
if host != _|_ {
|
||||||
|
HOST: host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad
|
||||||
|
for path, output in _files {
|
||||||
|
files: (path): output.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
// good
|
||||||
|
files: {
|
||||||
|
for path, output in _files {
|
||||||
|
(path): output.contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use *top* to match anything
|
||||||
|
|
||||||
|
From [CUE](https://cuelang.org/docs/references/spec/#values-1):
|
||||||
|
|
||||||
|
> At the top of the lattice is the single ancestor of all values, called *top*, denoted `_` in CUE. Every value is an instance of top.
|
||||||
|
|
||||||
|
There's a recurring theme when you have a template and need to create instances from it:
|
||||||
|
|
||||||
|
```cue
|
||||||
|
// bad
|
||||||
|
files: [path=string]: {
|
||||||
|
"path": path
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad
|
||||||
|
files: "/output": {}
|
||||||
|
|
||||||
|
// good
|
||||||
|
files: "/output": _
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
And also when you need to reference something that's being added implicitly.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
actions: build: go.#Run & {
|
||||||
|
// these values are being added implicitly to the plan
|
||||||
|
// but we need something to reference
|
||||||
|
os: client.platform.os
|
||||||
|
arch: client.platform.arch
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad, no need to redefine these fields
|
||||||
|
client: platform: {
|
||||||
|
os: string
|
||||||
|
arch: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// good, we only need the `client` field to exist
|
||||||
|
client: _
|
||||||
|
|
||||||
|
|
||||||
|
// ok if it improves understanding
|
||||||
|
client: platform: _
|
||||||
|
```
|
@ -1,32 +1,11 @@
|
|||||||
# Europa Universe
|
# Europa Universe
|
||||||
|
|
||||||
## About this directory
|
|
||||||
|
|
||||||
`europa-universe/` is a staging area for the upcoming `universe.dagger.io` package namespace,
|
|
||||||
which will be shipped as part of the [Europa release](https://github.com/dagger/dagger/issues/1088).
|
|
||||||
|
|
||||||
## What is Universe?
|
## What is Universe?
|
||||||
|
|
||||||
The Dagger Universe is a catalog of reusable Cue packages, curated by Dagger but possibly authored by third parties. Most packages in Universe contain reusable actions; some may also contain entire configuration templates.
|
The Dagger Universe is a catalog of reusable CUE packages, curated by Dagger but possibly authored by third parties.
|
||||||
|
Most packages in Universe contain reusable actions; some may also contain entire configuration templates.
|
||||||
|
|
||||||
The import domain for Universe will be `universe.dagger.io`. It will deprecate the current domain `alpha.dagger.io`.
|
The import domain for Universe will be `universe.dagger.io`.
|
||||||
|
|
||||||
## Where is the `dagger` package?
|
|
||||||
|
|
||||||
Europa will also introduce a new package for the Dagger Core API: `dagger.io/dagger`.
|
|
||||||
This is a core package, and is *not* part of Universe (note the import domain).
|
|
||||||
|
|
||||||
The development version of the Europa core API can be imported as [alpha.dagger.io/europa/dagger](../stdlib/europa/dagger).
|
|
||||||
|
|
||||||
## Where is the `dagger/engine` package?
|
|
||||||
|
|
||||||
Europa will also introduce a new package for the Low-Level Dagger Engine API : `dagger.io/dagger/engine`.
|
|
||||||
This is a core package, and is *not* part of Universe (note the import domain).
|
|
||||||
|
|
||||||
The development version of the Europa Low-Level Engine API can be imported as either:
|
|
||||||
|
|
||||||
* [alpha.dagger.io/europa/dagger/engine/spec/engine](../stdlib/europa/dagger/engine/spec/engine) for the full spec
|
|
||||||
* [alpha.dagger.io/dagger/europa/engine](../stdlib/europa/dagger/engine) for the implemented subset of the spec
|
|
||||||
|
|
||||||
## Universe vs other packages
|
## Universe vs other packages
|
||||||
|
|
||||||
@ -41,7 +20,6 @@ This table compares Dagger core packages, Dagger Universe packages, and the over
|
|||||||
| Size | Small | Large | Very large |
|
| Size | Small | Large | Very large |
|
||||||
| Growth rate | Grows slowly, with engine features | Grows fast, with Dagger community | Grows even faster, with CUE ecosystem |
|
| Growth rate | Grows slowly, with engine features | Grows fast, with Dagger community | Grows even faster, with CUE ecosystem |
|
||||||
|
|
||||||
|
|
||||||
## Notable packages
|
## Notable packages
|
||||||
|
|
||||||
### Docker API
|
### Docker API
|
||||||
@ -66,11 +44,13 @@ This package contains examples of complete Dagger configurations, including the
|
|||||||
|
|
||||||
For example, [the todoapp example](./examples/todoapp) corresponds to the [Getting Started tutorial](https://docs.dagger.io/1003/get-started/)
|
For example, [the todoapp example](./examples/todoapp) corresponds to the [Getting Started tutorial](https://docs.dagger.io/1003/get-started/)
|
||||||
|
|
||||||
|
## Coding Style
|
||||||
|
|
||||||
|
When contributing, please follow the guidelines from the [Package Coding Style](https://docs.dagger.io/1226/coding-style).
|
||||||
|
|
||||||
## TODO LIST
|
## TODO LIST
|
||||||
|
|
||||||
* Support native language dev in `docker.#Run` with good DX (Python, Go, Typescript etc.)
|
* Support native language dev in `docker.#Run` with good DX (Python, Go, Typescript etc.)
|
||||||
* Coding style. When to use verbs vs. nouns?
|
|
||||||
* Easy file injection API (`container.#Image.files` ?)
|
* Easy file injection API (`container.#Image.files` ?)
|
||||||
* Use file injection instead of inline for `#Command.script` (to avoid hitting arg character limits)
|
* Use file injection instead of inline for `#Command.script` (to avoid hitting arg character limits)
|
||||||
* Organize universe packages in sub-categories?
|
* Organize universe packages in sub-categories?
|
||||||
|
@ -115,6 +115,7 @@ module.exports = {
|
|||||||
"guides/custom-buildkit",
|
"guides/custom-buildkit",
|
||||||
"guides/self-signed-certificates",
|
"guides/self-signed-certificates",
|
||||||
"guides/pushing-plan-dependencies",
|
"guides/pushing-plan-dependencies",
|
||||||
|
"guides/coding-style",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user