docs: Package coding style

This is just to start. We’ll keep iterating on it.

Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com>
This commit is contained in:
Helder Correia 2022-04-08 16:24:18 +00:00
parent 6fe34c7693
commit 42fc1d14d2
No known key found for this signature in database
GPG Key ID: C6490D872EF1DCA7
3 changed files with 390 additions and 26 deletions

View 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 Daggers 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"
}
```
## Dont 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
Dont 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: _
```

View File

@ -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?

View File

@ -115,6 +115,7 @@ module.exports = {
"guides/custom-buildkit",
"guides/self-signed-certificates",
"guides/pushing-plan-dependencies",
"guides/coding-style",
],
},
{