diff --git a/docs/guides/1226-coding-style.md b/docs/guides/1226-coding-style.md new file mode 100644 index 00000000..8855f48e --- /dev/null +++ b/docs/guides/1226-coding-style.md @@ -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. + + + +```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: _ +``` diff --git a/pkg/universe.dagger.io/README.md b/pkg/universe.dagger.io/README.md index 81e66b96..19765ca6 100644 --- a/pkg/universe.dagger.io/README.md +++ b/pkg/universe.dagger.io/README.md @@ -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? diff --git a/website/sidebars.js b/website/sidebars.js index f6b3868d..c7989658 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -115,6 +115,7 @@ module.exports = { "guides/custom-buildkit", "guides/self-signed-certificates", "guides/pushing-plan-dependencies", + "guides/coding-style", ], }, {