1
1
mirror of https://github.com/tweag/nickel.git synced 2024-09-11 03:35:37 +03:00
Better configuration for less
Go to file
Jeremy Schlatter 090984d0e2
clean up outdated primitive operator names (#1992)
* clean up outdated primitive operator names

PR #1937 recently renamed many primitive operators. It missed some
instances of the previous names, though. In particular:

- error messages
- comments
- an unused function "apply_contract" in core/src/term/mod.rs

While most of these names were only made obsolete in #1937, some of them
have been incorrect for longer, eg "%array_access%" in
core/src/term/pattern/compile.rs and "recordMap" in core/src/term/mod.rs.

I caught as many as I could find. However it's hard to be sure I got all
of them, given that some of the previous names are very general terms
like "value", "fields", "length", and "map".

The full list of renames I identifiend are as follows, formatted as
"<old name> <new name>".

First, the easy cases:

    chng_pol label/flip_polarity
    record_map record/map
    str_trim string/trim
    str_chars string/chars
    str_uppercase string/uppercase
    str_lowercase string/lowercase
    str_length string/length
    str_from to_string
    num_from number/from_string
    enum_from enum/from_string
    str_is_match string/is_match
    str_find string/find
    str_find_all string/find_all
    record_empty_with_tail record/empty_with_tail
    label_push_diag label/push_diag
    enum_unwrap_variant enum/unwrap_variant
    enum_is_variant enum/is_variant
    enum_get_tag enum/get_tag
    apply_contract contract/apply
    array_lazy_app_ctr contract/array_lazy_app
    record_lazy_app_ctr contract/record_lazy_app
    elem_at array/at
    str_split string/split
    str_contains string/contains
    record_insert record/insert
    record_insert_with_opts record/insert_with_opts
    record_remove record/remove
    record_remove_with_opts record/remove_with_opts
    label_with_message label/with_message
    label_with_notes label/with_notes
    label_append_note label/append_note
    str_replace string/replace
    str_replace_regex string/replace_regex
    str_substr string/substr
    record_seal_tail record/seal_tail
    record_unseal_tail record/unseal_tail
    array_slice array/slice

Then the harder cases:

    polarity label/polarity
    go_dom label/go_dom
    go_codom label/go_codom
    go_array label/go_array
    go_dict label/go_dict
    embed enum/embed
    map array/map
    generate array/generate
    length array/length
    fields record/fields
    fields_with_opts record/fields_with_opts
    values record/values
    go_field label/go_field
    has_field record/has_field
    has_field_with_opts record/has_field_with_opts
    field_is_defined record/field_is_defined
    field_is_defined_with_opts record/field_is_defined_with_opts
    lookup_type_variable label/lookup_type_variable
    insert_type_variable label/insert_type_variable

Finally, two cases that I didn't understand and seem to be unused:

    rec_force_op op rec_force
    rec_default_op op rec_default

* address code review comments
2024-07-10 09:04:42 +00:00
.github chore(deps): bump DeterminateSystems/update-flake-lock from 22 to 23 (#1981) 2024-07-01 08:58:56 +00:00
.vscode Fix problem with the client not starting up 2021-09-23 19:40:03 +03:00
cli clean up outdated primitive operator names (#1992) 2024-07-10 09:04:42 +00:00
core clean up outdated primitive operator names (#1992) 2024-07-10 09:04:42 +00:00
doc/manual Split contracts into an immediate part and a delayed part (#1975) 2024-07-03 09:38:00 +00:00
examples Split contracts into an immediate part and a delayed part (#1975) 2024-07-03 09:38:00 +00:00
infra Update release infrastructure NixOS configuration (#1863) 2024-03-22 13:21:10 +00:00
lsp More aggressive type/contract deduplication on hover (#1984) 2024-07-03 18:56:20 +00:00
notes clean up outdated primitive operator names (#1992) 2024-07-10 09:04:42 +00:00
pyckel Add linker directive for *-darwin to fix PyO3 (#1454) 2023-07-31 16:16:42 +00:00
rfcs Fixes typos 2023-02-02 11:20:09 +01:00
scripts Update the release.sh script (#1952) 2024-06-11 14:12:45 +00:00
spec Use long names for string and numbers 2023-03-08 16:36:13 +01:00
utils Allow other formats for singleton input (#1901) (#1902) 2024-05-07 08:33:28 +00:00
wasm-repl [release.sh] update to 1.7.0 (#1951) 2024-06-11 14:19:21 +00:00
.envrc direnv: init .envrc file 2023-02-16 21:05:09 +01:00
.gitignore Convert paths to absolute before normalization (#1489) 2023-07-31 12:52:20 +00:00
.markdownlint.json flake.lock: Update (#1733) 2024-05-18 08:13:26 +00:00
.vscodeignore Move lsp to subfolder 2021-09-23 17:21:45 +03:00
Cargo.lock Bump comrak (#1961) 2024-06-18 12:40:43 +00:00
Cargo.toml Bump comrak (#1961) 2024-06-18 12:40:43 +00:00
CONTRIBUTING.md Add discord link to READMEs (#1742) 2023-12-20 09:30:58 +00:00
default.nix Make it easier to build the project without using flakes 2021-11-03 08:57:55 +01:00
flake.lock flake: remove redundant transitive dependencies (#1986) 2024-07-04 14:55:02 +00:00
flake.nix flake: remove redundant transitive dependencies (#1986) 2024-07-04 14:55:02 +00:00
HACKING.md move Assert and check to the stdlib (#1914) 2024-05-24 22:39:26 +00:00
LICENSE Update the copyright holder (#1422) 2023-06-30 11:03:25 +00:00
RATIONALE.md Fix swapped links in "Turing completeness" section (#1741) 2023-12-19 04:06:09 +00:00
README.md use Topiary's published crates over git (#1919) 2024-06-06 16:58:44 +00:00
RELEASES.md [release.sh] update to 1.7.0 (#1951) 2024-06-11 14:19:21 +00:00
RELEASING.md Backport 1.4 release to master (#1755) 2024-01-11 09:15:16 +00:00
shell.nix Add flake.nix 2020-07-03 14:22:21 +02:00

Nickel

Continuous integration Website Discord

Nickel is the cheap configuration language.

Its purpose is to automate the generation of static configuration files - think JSON, YAML, XML, or your favorite data representation language - that are then fed to another system. It is designed to have a simple, well-understood core: it is in essence JSON with functions.

Nickel's salient traits are:

  • Lightweight: Nickel is easy to embed. An interpreter should be simple to implement. The reference interpreter can be called from many programming languages.
  • Composable code: the basic building blocks for computing are functions. They are first-class citizens, which can be passed around, called and composed.
  • Composable data: the basic building blocks for data are records (called objects in JSON). In Nickel, records can be merged at will, including associated metadata (documentation, default values, type contracts, etc).
  • Typed, but only when it helps: static types improve code quality, serve as documentation and eliminate bugs early. But application-specific self-contained code will always evaluate to the same value, so type errors will show up at runtime anyway. Some JSON is hard to type. There, types are only a burden. Whereas reusable code - that is, functions - is evaluated on potentially infinitely many different inputs, and is impossible to test exhaustively. There, types are precious. Nickel has types, but you get to choose when you want it or not, and it handles safely the interaction between the typed and the untyped world.
  • Design by contract: complementary to the type system, contracts are a principled approach to checking assertions. The interpreter automatically inserts assertions at the boundary between typed and untyped code. Nickel lets users add arbitrary assertions of their own and easily understand why when assertions fail.

The motto guiding Nickel's design is:

Great defaults, design for extensibility

There should be a standard, clear path for common things. There should be no arbitrary restrictions that limit what you can do you the one day you need to go beyond.

Use cases

Nickel is a good fit in any situation where you need to generate a complex configuration, be it for a single app, a machine, whole infrastructure, or a build system.

The motivating use cases are in particular:

  • The Nix package manager: Nix is a declarative package manager using its own language for specifying packages. Nickel is an evolution of the Nix language, while trying to overcome some of its limitations.
  • Infrastructure as code: infrastructure is becoming increasingly complex, requiring a rigorous approach to deployment, modification and configuration. This is where a declarative approach also shines, as adopted by Terraform, NixOps or Kubernetes, all requiring potentially complex generation of configuration.
  • Build systems: build systems (like Bazel) need a specification of the dependency graph.

Most aforementioned projects have their own bespoke configuration language. See Comparison. In general, application-specific languages might suffer from feature creep, lack of abstractions or just feel ad hoc. Nickel buys you more for less.

The Nickel ecosystem

Related projects that are part of the Nickel ecosystem:

  • Terraform-Nickel: write Terraform configuration with Nickel
  • Organist: batteries included environments with Nickel inside
  • json-schema-to-nickel: generate Nickel contracts from JSON schema specifications.
  • rules_nickel: generate configuration files using Nickel during a Bazel build
  • The nickel-lang organization hosts various smaller projects, including a tree-sitter grammar definition for Nickel and editor plugins.

Getting started

Please follow the getting started guide for Nickel users on the nickel-lang website. The instructions below are either reproduced for this document to be self-contained or because they are aimed toward hacking on the Nickel interpreter itself (e.g. building the nickel-lang-core crate documentation).

Run

  1. Get a Nickel binary:

    • With flake-enabled Nix, run Nickel directly with nix run github:tweag/nickel. You can use our binary cache to prevent rebuilding a lot of packages. Pass arguments to Nickel with an extra -- as in nix run github:tweag/nickel -- repl,
    • Again with flake-enabled Nix, you can install Nickel in your profile with nix profile install github:tweag/nickel. The nickel command is then in your $PATH and is available anywhere.
    • If you're running macOS you can use Homebrew to install the Nickel binary with brew install nickel.
    • Without Nix, you can use cargo run --bin nickel after building, passing arguments with an extra -- as in cargo run --bin nickel -- eval program.ncl.
  2. Run your first program:

    $ nickel eval <<< '["hello", "world"] |> std.string.join ", "'
    "hello, world"
    

    Or load it from a file:

    $ echo 'let s = "world" in "hello, %{s}"' > program.ncl
    $ nickel eval program.ncl
    "hello, world"
    
  3. Start a REPL:

    $ nickel repl
    nickel> {"hello" = true, "world" = true, "universe" = false}
      |> std.record.to_array
      |> std.array.filter (fun {field, value} => value)
      |> std.array.map (fun {field, value} => field)
      |> std.string.join ", "
    
    "hello, world"
    

    Use :help for a list of available commands.

  4. Export your configuration to JSON, YAML or TOML:

$ nickel export --format json <<< '{content = "hello, world"}'
{
  "content": "hello, world"
}

Use nickel help for a list of subcommands, and nickel help <subcommand> for help about a specific subcommand.

To get in touch, you can join our Discord server.

Editor Setup

Nickel has syntax highlighting plugins for Vim/Neovim, and VSCode. In-editor diagnostics, type hints, and auto-completion are provided by the Nickel Language Server. Please follow the LSP guide to set up syntax highlighting and NLS.

Formatting

To format one or several Nickel source files, use nickel format:

nickel format network.ncl container.ncl api.ncl

Nickel uses Topiary to format Nickel code under the hood.

Please follow the Formatting Capabilities section of the LSP documentation to know how to hook up the Nickel LSP and Topiary in order to enable formatting inside your code editor.

Build

  1. Download build dependencies:

    • With Nix: If you have Nix installed:

      nix-shell # if you don't use Nix flakes
      nix develop # if you use Nix flakes
      

      You will be dropped in a shell, ready to build. You can use our binary cache to prevent rebuilding a lot of packages.

    • Without Nix: otherwise, follow this guide to install Rust and Cargo first.

  2. Build Nickel:

    cargo build -p nickel-lang-cli --release
    

    And voilà! Generated files are placed in target/release.

    You can directly build and run the Nickel binary and pass argument after -- by using cargo run:

    cargo run --bin nickel -- eval foo.ncl
    

Test

Run tests with

cargo test

Documentation

The user manual is available on the nickel-lang.org website, and in this repository as a collection of Markdown files in doc/manual.

To get the documentation of the nickel-lang codebase itself:

  1. Build the doc:

    cargo doc --no-deps
    
  2. Open the file target/doc/nickel/index.html in your browser.

Examples

You can find examples in the ./examples directory.

Current state and roadmap

Since version 1.0 released in May 2023, the core design of the language is stable and Nickel is useful for real-world applications. The next steps we plan to work on are:

The next steps we plan to work on are:

  • Nix integration: being able to seamlessly use Nickel to write packages and shells (Organist)
  • Custom merge functions (second part of the overriding proposal)
  • Incremental evaluation: design an incremental evaluation model and a caching mechanism in order to perform fast re-evaluation upon small changes to a configuration.
  • Performance improvements

Comparison

  • CUE is a configuration language with a focus on data validation. It introduces a new constraint system backed by a solid theory which ensures strong guarantees about your code. It allows for very elegant schema specifications. In return, the cost to pay is to abandon functions and Turing-completeness. Nickel's merge system is inspired by the one of CUE, even if since Nickel does have general functions and is Turing-complete, they are necessarily different.
  • Nix: The Nix language, or Nix expressions, is one of the main inspirations for Nickel. It is a very simple yet powerful lazy functional language. We strive to retain this simplicity, while adding typing capabilities, modularity, and detaching the language from the Nix package manager.
  • Dhall is a statically typed configuration language. It is also inspired by Nix, to which it adds a powerful static type system. However, this forces the programmer to annotate all of their code with types.
  • Jsonnet is another language which could be dubbed as "JSON with functions" (and others things as well). It is a lazy functional language with object-oriented features, among which inheritance is similar to Nickel's merge system. One big difference with Nickel is the absence of typing.
  • KCL is a gradually typed configuration language whose validation is based on object-oriented schemas that can be extended through inheritance. Unlike the languages above, its evaluation is strict.
  • Pulumi is not a language in itself, but a cloud tool (like Terraform) where you can use your preferred language for describing your infrastructure. This is a different approach to the problem, with different trade-offs.
  • Starlark is the language of Bazel, which is a dialect of Python. It does not have types and recursion is forbidden, making it not Turing-complete.

See RATIONALE.md for the design rationale and a more detailed comparison with these languages.

Comparison with other configuration languages

Language Typing Recursion Evaluation Side-effects
Nickel Gradual (dynamic + static) Yes Lazy Yes (constrained, planned)
Starlark Dynamic No Strict No
Nix Dynamic Yes Lazy Predefined and specialized to package management
Dhall Static (requires annotations) Restricted Lazy No
CUE Static (everything is a type) No Lazy No, but allowed in the separated scripting layer
Jsonnet Dynamic Yes Lazy No
KCL Gradual (dynamic + static) Yes Strict No
JSON None No Strict No
YAML None No N/A No
TOML None No N/A No