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
|
||||
|
||||
## 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?
|
||||
|
||||
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`.
|
||||
|
||||
## 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
|
||||
The import domain for Universe will be `universe.dagger.io`.
|
||||
|
||||
## 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 |
|
||||
| Growth rate | Grows slowly, with engine features | Grows fast, with Dagger community | Grows even faster, with CUE ecosystem |
|
||||
|
||||
|
||||
## Notable packages
|
||||
|
||||
### 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/)
|
||||
|
||||
## Coding Style
|
||||
|
||||
When contributing, please follow the guidelines from the [Package Coding Style](https://docs.dagger.io/1226/coding-style).
|
||||
|
||||
## TODO LIST
|
||||
|
||||
* 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` ?)
|
||||
* Use file injection instead of inline for `#Command.script` (to avoid hitting arg character limits)
|
||||
* Organize universe packages in sub-categories?
|
||||
|
@ -115,6 +115,7 @@ module.exports = {
|
||||
"guides/custom-buildkit",
|
||||
"guides/self-signed-certificates",
|
||||
"guides/pushing-plan-dependencies",
|
||||
"guides/coding-style",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
Reference in New Issue
Block a user