From e15b6b2f241002245d9b33c7fc71c403c7125b51 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 13:11:45 -0500 Subject: [PATCH 01/58] add intro/conclusion, rework prose, follow styleguide, clean diffs --- .../tutorials/module-system/module-system.md | 881 +++++++++--------- 1 file changed, 456 insertions(+), 425 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 8aa0d4541..096deb309 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -1,80 +1,83 @@ -# Module system introduction +# The Module System +Much of the power in Nixpkgs comes from the module system, which provides mechanisms for automatically merging attribute sets, making it easy to compose configurations in a type-safe way. -Note: This tutorial was created from https://github.com/tweag/summer-of-nix-modules, as presented by @infinisil at Summer of Nix 2021, presentation can be seen here (might not work in browser, `mpv` should work): https://infinisil.com/modules.mp4 +In this tutorial, you'll write your first modules to interact with the Google Maps API, declaring options which represent map geometry, location pins, and more. + +You'll learn what a module is and how to define one, what options are and how to declare them, how to express dependencies between modules, and a practical way to use Nix to wrap external APIs. + +Be prepared to see some Nix errors: during the tutorial, you will first write some *incorrect* configurations, creating opportunities to discuss the resulting error messages and how to resolve them, particularly when discussing type checking. + +:::{note} +This tutorial follows [@infinisil's presentation](https://infinisil.com/modules.mp4) of the [Summer of Nix Modules](https://github.com/tweag/summer-of-nix-modules), during the 2021 Summer of Nix. +::: ## Empty module -The simplest module you can have is just an empty attribute set, which -as you might expect, doesn't do anything! +The simplest module is just an empty attribute set, which doesn't do anything! -```diff -diff --git a/default.nix b/default.nix -new file mode 100644 -index 0000000..1797133 ---- /dev/null -+++ b/default.nix -@@ -0,0 +1,3 @@ -+{ -+ -+} +Write the following into a file called `default.nix`: + +```nix +# default.nix +{ + +} ``` -## Module arguments: lib +## Module Arguments -Modules can be just an attribute set. But if you want access to some -arguments you need to change it into a function taking an attribute set -with an ellipsis (that's the "..."). In this case we only match on the -`lib` argument, which gives us access to nixpkgs library functions. +In order to make a module actually useful, you will need to write it as a *function*, which takes an attribute set as an argument. -Note that the ellipsis is necessary since arbitrary arguments can be -passed to modules. +Do this by adding the following new line to `default.nix`: ```diff -diff --git a/default.nix b/default.nix -index 1797133..f7569bd 100644 ---- a/default.nix -+++ b/default.nix -@@ -1,3 +1,3 @@ --{ -+{ lib, ... }: { - - } +# default.nix ++ { lib, ... }: +{ + +} ``` -## Declaring generate.script option +The addition of this line turns the expression in `default.nix` into a function which takes *at least* one argument, called `lib`, and may accept other arguments (expressed by the ellipsis `...`). + +Matching on the `lib` argument will make `nixpkgs` library functions available within the function body. + +:::{note} +The ellipsis `...` is necessary because arbitrary arguments can be passed to modules. +::: + +## Declaring Options -In order for modules to be useful, we need to have options, so let's -declare one. Options are declared by defining an attribute with -`lib.mkOption` under the `options` attribute. Here we're defining the -option `generate.script`. +One of the reasons for writing modules is to declare names which can be assigned values and used in other computations elsewhere. -While there are many attributes to customize options, the most -important one is `type`, which specifies what values are valid for an -option, and how/whether multiple values should be merged together. +For your new module to become useful, you will need to add some *options*, which define these named-values. -In the nixpkgs library there are a number of types available under -`lib.types`. Here we're using the `lines` type, which specifies that: -- Only strings are valid values -- Multiple strings are joined with newlines +Options are declared by defining an attribute under the top-level `options` attribute, using `lib.mkOption`. + +In this section, you will define the `generate.script` option. + +Change `default.nix` to include the following declaration: ```diff -diff --git a/default.nix b/default.nix -index f7569bd..cb423a9 100644 ---- a/default.nix -+++ b/default.nix -@@ -1,3 +1,9 @@ +# default.nix { lib, ... }: { -+ -+ options = { -+ generate.script = lib.mkOption { -+ type = lib.types.lines; -+ }; -+ }; - + ++ options = { ++ generate.script = lib.mkOption { ++ type = lib.types.lines; ++ }; ++ }; + } ``` -Let's try to evaluate this with this file: +While many attributes for customizing options are available, the most important one is `type`, which specifies which values are valid for an option, and how or whether multiple values should be merged together. + +There are several other types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the nixpkgs library. + +You have just declared `generate.script` with the `lines` type, which specifies that the only valid values are strings, and that multiple strings should be joined with newlines. + +Write a new file, `eval.nix`, which you will use to evaluate `default.nix`: ```nix # eval.nix @@ -85,90 +88,87 @@ Let's try to evaluate this with this file: } ``` -Then we run +Now execute the following command: + ```bash nix-instantiate --eval eval.nix -A config.generate.script ``` -Trying to evaluate the `generate.script` option however, we get an error -that the option is used but not defined, indicating that we need to -actually give a value to the option. +You will see an error message indicating that the `generate.script` option is used but not defined; you will need to assign a value to the option before using it. + +## Type Checking +As previously mentioned, the `lines` type only permits string values. +:::{warning} +In this section, you will make your first type error. Be prepared! +::: -## Type checking: Assigning integer to generate.script +What happens if you instead try to assign an integer to the option? -We can try to assign an integer to our option of type `lines`, but the -module system correctly throws an error saying that our definition -doesn't match the options type. +Add the following lines to `default.nix`: ```diff -diff --git a/default.nix b/default.nix -index cb423a9..0a9162e 100644 ---- a/default.nix -+++ b/default.nix -@@ -5,5 +5,9 @@ - type = lib.types.lines; - }; - }; -+ -+ config = { -+ generate.script = 42; -+ }; - +# default.nix + { lib, ... }: { + + options = { + generate.script = lib.mkOption { + type = lib.types.lines; + }; + }; + ++ config = { ++ generate.script = 42; ++ }; } ``` -## Successful evaluation: Assigning a string to generate.script +Now try to execute the previous command, and witness your first module error: + +```console +$ nix-instantiate --eval eval.nix -A config.generate.script +error: +... + error: A definition for option `generate.script' is not of type `strings concatenated with "\n"'. Definition values: + - In `/home/nix-user/default.nix': 42 +``` + +This assignment of `generate.script = 42;` caused a type error: integers are not strings concatenated with the newline character. -We can make type checking pass by assigning a string to the option, -giving us our first successful evaluation of the `generate.script` -option. +## Successful Type-checking -In this case, we assign a script which calls the Google Maps Static API -to generate a world map, then displaying the result using icat -(image-cat), both of which are helper scripts. +To make this module pass the type-checker and successfully evaluate the `generate.script` option, you will now assign a string to `generate.script`. + +In this case, you will assign a `map` script which first calls the Google Maps Static API to generate a world map, then displays the result using `icat` (image-cat), both of which are helper scripts. + +Update `default.nix` by changing the value of `generate.script` to the following string: ```diff -diff --git a/default.nix b/default.nix -index 0a9162e..24f9c34 100644 ---- a/default.nix -+++ b/default.nix -@@ -7,7 +7,9 @@ - }; - +# default.nix config = { - generate.script = 42; + generate.script = '' + map size=640x640 scale=2 | icat + ''; }; - - } ``` TODO: Create derivations to get these commands -## A new list option: Declaring generate.requestParams +## Declaring More Options +In this section, you will introduce another option: `generate.requestParams`. + +For its type, you should use `listOf `, which is a generic list type where each element must have the given nested type. -Let's introduce another option, generate.requestParams. For its type, -we'll use `listOf `, which is a generic list type where each -element has to match the given nested type. In our case we want `str` to -be the nested type, which is a generic string type. +Instead of `lines`, in this case you will want the nested type to be `str`, a generic string type. -Note that the difference between `str` and `lines` is in their merging -behavior: -- For `lines`, multiple definitions get merged by concatenation with - newlines -- For `str`, multiple definitions are not allowed. Which in this case is - mostly irrelevant however, since we can't really define a list element - multiple times. +The difference between `str` and `lines` is in their merging behavior: +- For `lines`, multiple definitions get merged by concatenation with newlines. +- For `str`, multiple definitions are not allowed. This is mostly irrelevant here however, since it is not really possible to define a list element multiple times. +Make the following additions to your `default.nix` file now: ```diff -diff --git a/default.nix b/default.nix -index 24f9c34..fd5027a 100644 ---- a/default.nix -+++ b/default.nix -@@ -4,12 +4,21 @@ +# default.nix generate.script = lib.mkOption { type = lib.types.lines; }; @@ -177,7 +177,7 @@ index 24f9c34..fd5027a 100644 + type = lib.types.listOf lib.types.str; + }; }; - + config = { generate.script = '' map size=640x640 scale=2 | icat @@ -188,39 +188,36 @@ index 24f9c34..fd5027a 100644 + "scale=2" + ]; }; - + } ``` -## Dependencies between options: Using generate.requestParams +## Dependencies Between Options -A collection of modules generally only has a single option that is meant -to be evaluated. That's the option that generates the final result we're -interested in, which in our case is `generate.script`. +A given module generally only declares a single option that is meant to be evaluated. -In order to build up abstractions on that, we have the ability for -options to depend on other options. In this case we want the -`generate.script` option to use the values of `generate.requestParams`. -We can access the values of options by adding the `config` argument to -the argument list at the top and using e.g. -`config.generate.requestParams` to access that options value. +This option generates the final result to be used elsewhere, which in this case is `generate.script`. -We're then using `lib.concatStringsSep " "` to join each list element -of the option together into an argument list. +Options have the ability to depend on other options, making it possible to build more useful abstractions. +Here, the plan is for the `generate.script` option to use the values of `generate.requestParams` as arguments to the `map` command. + +### Accessing Option Values +To make a declared option available, the argument attribute set of the module declaring it must include the `config` attribute. + +Update `default.nix` to add the `config` attribute: ```diff -diff --git a/default.nix b/default.nix -index fd5027a..050a98e 100644 ---- a/default.nix -+++ b/default.nix -@@ -1,4 +1,4 @@ +# default.nix -{ lib, ... }: { +{ lib, config, ... }: { - - options = { - generate.script = lib.mkOption { -@@ -12,7 +12,9 @@ - +``` + +When a module declaring an option is evaluated, values of the resulting option can be accessed by using attribute names to access the corresponding values. + +Now make the following changes to `default.nix`: + +```diff +# default.nix config = { generate.script = '' - map size=640x640 scale=2 | icat @@ -228,33 +225,32 @@ index fd5027a..050a98e 100644 + config.generate.requestParams + } | icat ''; - - generate.requestParams = [ ``` -## Conditional definitions: Introducing map.zoom option +Here, the value of the `config.generate.requestParams` attribute is substituted at its call site. -We now introduce the new option `map.zoom` in order to control the zoom -level of the map. We'll use a new type for it, `nullOr `, which -accepts the value `null`, but also values of its argument type. We're -using `null` here to mean an inferred zoom level. +`lib.concatStringsSep " "` is then used to join each list element from the value of `config.generate.requestParams` into a single string, with the list elements of `requestParams` separated by a space character. -For this option, we'll set a default value which should be used if it's -not defined otherwise, which we can do using `mkOption`'s `default` -argument. +The result of this represents the list of command line arguments to pass to `map`. -Now we want to use this option to define another element in -`generate.requestParams`, but we only want to add this element if its -value is non-null. We can do this using the `mkIf -` function, which only adds a definition if the condition -holds. +## Conditional Definitions and Default Values + +In this section, you will define a new option, `map.zoom`, to control the zoom level of the map. + +You will use a new type, `nullOr `, which can take as values either the values of its argument type or `null`. + +In this case, a `null` value will use the API's default behavior of inferring the zoom level. + +Here, you will also use `default` from `mkOption`](https://github.com/NixOS/nixpkgs/blob/master/lib/options.nix) to declare your first *default* value, which will be used if the option declaring it is not enabled. + +You will use this option to define another element in `generate.requestParams`, which will only be added if its value is non-null. + +To do this, you can use the `mkIf ` function, which only adds the definition if the condition holds. + +Add the `map` attribute set with the `zoom` option into the top-level `options` declaration, like so: ```diff -diff --git a/default.nix b/default.nix -index 050a98e..6b6e1e1 100644 ---- a/default.nix -+++ b/default.nix -@@ -8,6 +8,13 @@ +# default.nix generate.requestParams = lib.mkOption { type = lib.types.listOf lib.types.str; }; @@ -266,9 +262,14 @@ index 050a98e..6b6e1e1 100644 + }; + }; }; - + ``` + +Now make the following additions to the `generate.requestParams` list in the `config` block: + +```diff +# default.nix config = { -@@ -20,6 +27,8 @@ + ... generate.requestParams = [ "size=640x640" "scale=2" @@ -276,23 +277,15 @@ index 050a98e..6b6e1e1 100644 + "zoom=${toString config.map.zoom}") ]; }; - ``` -## Declaring map.center option +## Centering the Map -Similarly, let's declare a map.center option, declaring where the map -should be centered. - -We'll be using a small utility for geocoding location names, aka turning -them from names into coordinates +You have now declared options controlling the map dimensions and zoom level, but have not provided a way to specify where the map should be centered. +Add the `center` option now, possibly with your own location as default value: ```diff -diff --git a/default.nix b/default.nix -index 6b6e1e1..098d135 100644 ---- a/default.nix -+++ b/default.nix -@@ -14,6 +14,11 @@ +# default.nix type = lib.types.nullOr lib.types.int; default = 2; }; @@ -303,8 +296,14 @@ index 6b6e1e1..098d135 100644 + }; }; }; - -@@ -29,6 +34,10 @@ +``` + +To implement this behavior, you will use the `geocode` utility, which turns location names into coordinates. + +Add another `mkIf` call to the list of `requestParams` now: + +```diff +# default.nix "scale=2" (lib.mkIf (config.map.zoom != null) "zoom=${toString config.map.zoom}") @@ -314,63 +313,52 @@ index 6b6e1e1..098d135 100644 + })\"") ]; }; - ``` -## Splitting modules: importing marker.nix +This time, you've used `escapeShellArg` to pass the `config.map.center` value as a command-line argument to `geocode`, interpolating the result back into the `requestParams` string which sets the `center` value. + +Wrapping shell command execution in Nix modules is a powerful technique for controlling system changes using the ergnomic attributes and values interface. + +## Splitting Modules + +The module schema includes the `imports` attribute, which allows you to define further modules to import, enabling a *modular* approach where your configuration may be split into multiple files. -The module system allows you to split your config into multiple files -via the `imports` attribute, which can define further modules to import. -This allows us to logically separate options and config for different -parts. +In particular, this allows you to separate option declarations from their call-sites in your configuration. -Here we create a new module `marker.nix`, where we will declare options -for defining markers on the map +You should now create a new module, `marker.nix`, where you can declare options for defining location pins and other markers on the map. +```diff +# marker.nix ++{ lib, config, ... }: { ++ ++} +``` +Reference this new file in `default.nix` using the `imports` attribute: ```diff -diff --git a/default.nix b/default.nix -index 098d135..9d9f29d 100644 ---- a/default.nix -+++ b/default.nix -@@ -1,5 +1,9 @@ +# default.nix { lib, config, ... }: { - + + imports = [ + ./marker.nix + ]; + - options = { - generate.script = lib.mkOption { - type = lib.types.lines; -diff --git a/marker.nix b/marker.nix -new file mode 100644 -index 0000000..035c28d ---- /dev/null -+++ b/marker.nix -@@ -0,0 +1,3 @@ -+{ lib, config, ... }: { -+ -+} ``` -## Submodule types: Declaring map.markers option +## The `submodule` Type + +One of the most useful types included in the module system's type system is `submodule`. + +This type allows you to define nested modules with their own options. -One of the most useful types of the module system is the `submodule` -type. This type allows you to define a nested module system evaluation, -with its own options. Every value of such a type is then interpreted -(by default) as a `config` assignment of the nested module evaluation. +Every value of such a type is then interpreted (by default) as a `config` assignment of the nested module evaluation. -In this case we're defining a `map.markers` option, whose type is a list -of submodules with a nested `location` type, allowing us to define a -list of markers on the map, where each assignment is type checked -according to the submodule. +Here, you will define a new `map.markers` option whose type is a list of submodules, each with a nested `location` type, allowing you to define a list of markers on the map. +Each assignment of markers will be type-checked during evaluation of the top-level `config`. + +Make the following changes to `marker.nix` now: ```diff -diff --git a/marker.nix b/marker.nix -index 035c28d..6a6c686 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -1,3 +1,20 @@ +# marker.nix -{ lib, config, ... }: { +{ lib, config, ... }: +let @@ -390,26 +378,19 @@ index 035c28d..6a6c686 100644 + type = lib.types.listOf markerType; + }; + }; - } ``` -## Single option namespace: Defining markers in generate.requestParams +## Setting Option Values Within Other Modules + +Because of the way the module system composes option definitions, you can also freely assign values to options defined in other modules. -Since all modules in `imports` are treated the same, we can also freely -assign an option defined in our initial module. In this case we want to -add some request parameters derived from the `map.markers` option, so -that markers actually show up on the map. +In this case, you will use the `map.markers` option to derive and add new `requestParams`, making your declared markers appear on the returned map. +To implement this behavior, add the following `config` block to `marker.nix`: ```diff -diff --git a/marker.nix b/marker.nix -index 6a6c686..c2c5da2 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -17,4 +17,26 @@ in { - }; - }; - +# marker.nix + ... + config = { + + map.markers = [ @@ -431,24 +412,20 @@ index 6a6c686..c2c5da2 100644 + in map paramForMarker config.map.markers; + + }; -+ } ``` -## Set map.{center,zoom} for more than {1,2} markers +Here, you again used `escapeShellArg` and string interpolation to generate a Nix string, this time producing a pipe-separated list of geocoded location attributes. + +The `generate.requestParams` value was also set to the resulting list of strings, which gets appended to the `generate.requestParams` list defined in `default.nix`, thanks to the default behavior of the `list`-type module. + +## Multiple Markers -Let's let the API infer the center if we have more than one marker, and -let's let it infer the zoom as well if there's more than 2. +In case you define multiple markers, determining an appropriate center or zoom level for the map may be challenging; it's easier to let the API do this for you. +To do this, make the following additions to `marker.nix`, above the `generate.requestParams` declaration: ```diff -diff --git a/marker.nix b/marker.nix -index c2c5da2..b80ddd0 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -23,6 +23,14 @@ in { - { location = "new york"; } - ]; - +# marker.nix + map.center = lib.mkIf + (lib.length config.map.markers >= 1) + null; @@ -462,30 +439,24 @@ index c2c5da2..b80ddd0 100644 let ``` -## Nested submodules: Introducing users option +In this case, the default behavior of the Maps API when not passed a center or zoom level is to pick the geometric center of all the given markers, and to set a zoom level appropriate for viewing all markers at once. + +## Nested Submodules + +It's time to introduce the `users` option with the `lib.types.attrsOf ` type, which will allow you to define `users` as an attribute set with arbitrary keys, each value of which has type ``. -We will now introduce the option `users`, where we make use of a very -useful type, `lib.types.attrsOf `. This type lets us specify an -attribute set as a value, where the keys can be arbitrary, but each -value has to conform to the given . +Here, that subtype will be another submodule which allows declaring a departure marker, suitable for querying the API for the recommended route for a trip. -In this case, we'll use another submodule as the subtype, one that -allows declaring a departure marker, which notably also makes use of our -`markerType` submodule, giving us a nested structure of submodules. +This will also make use of the `markerType` submodule, giving a nested structure of submodules. -We're now propagating each of the users marker definitions to the -`map.markers` option. +To propagate marker definitions from `users` to the `map.markers` option, make the following changes now: + +- In the `let` block: ```diff -diff --git a/marker.nix b/marker.nix -index b80ddd0..3c54ad8 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -9,9 +9,24 @@ let - }; - }; - }; -+ +# marker.nix + let + ... + userType = lib.types.submodule { + options = { + departure = lib.mkOption { @@ -496,20 +467,24 @@ index b80ddd0..3c54ad8 100644 + }; + in { - +``` + +- In the `options` block, above `map.markers`: +```diff +# marker.nix options = { + + users = lib.mkOption { + type = lib.types.attrsOf userType; + }; + - map.markers = lib.mkOption { - type = lib.types.listOf markerType; - }; -@@ -19,9 +34,11 @@ in { - +``` + +- In the `config` block, above `map.center`: +```diff +# marker.nix config = { - + - map.markers = [ - { location = "new york"; } - ]; @@ -518,24 +493,36 @@ index b80ddd0..3c54ad8 100644 + (lib.concatMap (user: [ + user.departure + ]) (lib.attrValues config.users)); - + map.center = lib.mkIf (lib.length config.map.markers >= 1) ``` -## Introducing style.label and strMatching type +The `config.users` attribute set is passed to `attrValues`, which returns a list of values of each of the attributes in the set (here, the set of `config.users` you've defined), sorted alphabetically. + +The `departure` values of each of the `users` are then joined into a list, and this list is filtered for non-`null` locations. + +The resulting list is stored in `map.markers`. + +The resulting `map.markers` option then propagates to the `generate.requestParams` option, which in turn is used to generate arguments to the script which ultimately calls the Maps API. + +Defining the options in this way allows you to set multiple `users..departure.location` values and generate a map with the appropriate zoom and center, with pins corresponding to the set of `departure.location` values for *all* `users`. + +In the 2021 Summer of Nix, this formed the basis of an interactive multi-person map demo. + +## Labeling Markers + +Now that the map can be rendered with multiple markers, it's time to add some style customization. -Let's add an option to customize markers with a label. We can do so by -just adding another option in our markerType submodule. The API states -that this has to be an uppercase letter or a number, which we can -implement with the `strMatching ""` type. +To tell the markers apart, you should add another option to the `markerType` submodule, to allow labeling each marker pin. +The API [states](https://developers.google.com/maps/documentation/maps-static/start#MarkerStyles) that these labels must be either an uppercase letter or a number. + +You can implement this with the `strMatching ""` type, where `` is a regular expression used for the matching; this will reject any unacceptable (non-uppercase letter or number) values. + +- In the `let` block: ```diff -diff --git a/marker.nix b/marker.nix -index 3c54ad8..1c9a043 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -7,6 +7,12 @@ let +# marker.nix type = lib.types.nullOr lib.types.str; default = null; }; @@ -547,8 +534,11 @@ index 3c54ad8..1c9a043 100644 + }; }; }; - -@@ -52,7 +58,10 @@ in { +``` + +- In the `paramForMarker` function: +```diff +# marker.nix paramForMarker = marker: let attributes = @@ -562,27 +552,19 @@ index 3c54ad8..1c9a043 100644 })" ``` -## Using the attribute name in the submodule to define a default label +Here, the label for each `marker` is only propagated to the CLI parameters if `marker.style.label` is set. + +## Defining a Default Label + +In case you don't want to manually define a label for every marker, you can set a default value. -Let's set a default label by deriving it from the username. By -transforming the submodule's argument into a function, we can access -arguments within it. One special argument available to submodules is the -`name` argument, which when used in `attrsOf`, gives you the name of the -attribute the submodule is defined under. +The easiest value for a default label is the username, which will always also be set. -In this case, we don't easily have access to the name from the marker -submodules label option (where we could set a `default =`). Instead we -will use the `config` section of the user submodule to set a default. We -can do so using the `lib.mkDefault` modifier, which has lower precedence -than if no modifier were used. +This `firstUpperAlnum` function allows you to retrieve the first character of the username, with the correct type for passing to `departure.style.label`: ```diff -diff --git a/marker.nix b/marker.nix -index 1c9a043..53860f1 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -1,5 +1,11 @@ - { lib, config, ... }: +# marker.nix +{ lib, config, ... }: let + # Returns the uppercased first letter + # or number of a string @@ -590,13 +572,19 @@ index 1c9a043..53860f1 100644 + lib.mapNullable lib.head + (builtins.match "[^A-Z0-9]*([A-Z0-9]).*" + (lib.toUpper str)); - + markerType = lib.types.submodule { options = { -@@ -16,14 +22,19 @@ let - }; - }; - +``` + +By transforming the argument to `lib.types.submodule` into a function, you can access arguments within it. + +One special argument available to submodules is the `name` argument, which when used in `attrsOf`, gives you the name of the attribute the submodule is defined under. + +You can use this function argument to retrieve the `name` attribute for use elsewhere: + +```diff +# marker.nix - userType = lib.types.submodule { + userType = lib.types.submodule ({ name, ... }: { options = { @@ -606,34 +594,49 @@ index 1c9a043..53860f1 100644 }; }; - }; +``` + +In this case, you don't easily have access to the name from the marker submodules `label` option, where you otherwise could set a `default` value. + +Instead you can use the `config` section of the `user` submodule to set a default, like so: + +```diff +# marker.nix + + config = { + departure.style.label = lib.mkDefault + (firstUpperAlnum name); + }; + }); - + in { - + ``` -## Marker colors +:::{note} +Module options have a *precedence*, represented as an integer, which determines the priority of setting the option to a particular value. + +The `lib.mkDefault` modifier sets the precedence of its argument value to 1000, the lowest priority. + +This ensures that other values set for the same option will prevail. +::: + + +## Marker Styling: Color -Let's allow markers to change their color as well. We'll use some new -type functions for this, namely -- `either `: Takes two types as arguments, allows either of - them -- `enum [ ]`: Takes a list of allowed values +For better visual contrast, it would also be helpful to have a way to change the *color* of a marker. +Here you will use two new type-functions for this: +- `either `, which takes two types as arguments, allows either of them +- `enum [ ]`, which takes a list of allowed values + +In the `let` block, add the following `colorType` option, which can hold strings containing either some given color names or an RGB value: ```diff -diff --git a/marker.nix b/marker.nix -index 53860f1..df0d08b 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -7,6 +7,13 @@ let +# marker.nix + ... (builtins.match "[^A-Z0-9]*([A-Z0-9]).*" (lib.toUpper str)); - + + # Either a color name or `0xRRGGBB` + colorType = lib.types.either + (lib.types.strMatching "0x[0-9A-F]{6}") @@ -644,7 +647,11 @@ index 53860f1..df0d08b 100644 markerType = lib.types.submodule { options = { location = lib.mkOption { -@@ -19,6 +26,11 @@ let +``` + +At the bottom of the `let` block, add the `style.color` option: +```diff +# marker.nix (lib.types.strMatching "[A-Z0-9]"); default = null; }; @@ -655,8 +662,11 @@ index 53860f1..df0d08b 100644 + }; }; }; - -@@ -73,6 +85,7 @@ in { +``` + +Now add a line to the `paramForMarker` list which makes use of the new option: +```diff +# marker.nix (marker.style.label != null) "label:${marker.style.label}" ++ [ @@ -666,16 +676,14 @@ index 53860f1..df0d08b 100644 })" ``` -## Marker size +## Marker Styling: Size + +In case you set many different markers, it would be helpful to have the ability to change their size individually, further improving visual accessibility. -Let's also allow changing of marker sizes. +Add a new `style.size` option to `marker.nix`, allowing you to do so: ```diff -diff --git a/marker.nix b/marker.nix -index df0d08b..2c0c1a8 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -31,6 +31,12 @@ let +# marker.nix type = colorType; default = "red"; }; @@ -687,8 +695,11 @@ index df0d08b..2c0c1a8 100644 + }; }; }; - -@@ -80,10 +86,20 @@ in { +``` + +Now add a handler for the size parameter in `paramForMarker`, which selects an appropriate string to pass to the API: +```diff +# marker.nix generate.requestParams = let paramForMarker = marker: let @@ -699,6 +710,11 @@ index df0d08b..2c0c1a8 100644 + large = null; + }.${marker.style.size}; + +``` + +Finally, add another `lib.optional` call to the `attributes` string, making use of the selected size: +``` +# marker.nix attributes = lib.optional (marker.style.label != null) @@ -711,37 +727,16 @@ index df0d08b..2c0c1a8 100644 "$(geocode ${ ``` -## Initial path module +## The `pathType` Submodule + +So far, you've created an option for declaring a *destination* marker, as well as several options for configuring the marker's visual representation. -Let's introduce a new module for declaring paths on the map. We'll -import a new `path.nix` module from our `marker.nix` module. +The new option defined in the next section will allow you to set an *arrival* marker, which together with a destination allows you to draw *paths* on the map using the new module defined below. -In the path module we'll define an option for declaring paths, and will -use the same `generate.requestParams` to influence the API call to -include our defined paths +To start, create a new `path.nix` file with the following contents: ```diff -diff --git a/marker.nix b/marker.nix -index 2c0c1a8..ffb8185 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -56,6 +56,10 @@ let - - in { - -+ imports = [ -+ ./path.nix -+ ]; -+ - options = { - - users = lib.mkOption { -diff --git a/path.nix b/path.nix -new file mode 100644 -index 0000000..554a88b ---- /dev/null -+++ b/path.nix -@@ -0,0 +1,32 @@ +# path.nix +{ lib, config, ... }: +let + pathType = lib.types.submodule { @@ -776,18 +771,29 @@ index 0000000..554a88b +} ``` -## Arrival marker +The `path.nix` module defines an option for declaring paths, augmenting the API call by re-using the `generate.requestParams` option. -Now in order for users to be able to draw a path in their definitions, -we need to allow them to specify another marker. We will copy the -`departure` option declaration to a new `arrival` option for that. +Now import this new `path.nix` module from your `marker.nix` module: ```diff -diff --git a/marker.nix b/marker.nix -index ffb8185..940b8f8 100644 ---- a/marker.nix -+++ b/marker.nix -@@ -46,11 +46,18 @@ let +# marker.nix + in { + ++ imports = [ ++ ./path.nix ++ ]; ++ + options = { + + users = lib.mkOption { +``` + +## The Arrival Marker + +Now copy the `departure` option declaration to a new `arrival` option in `marker.nix`, to complete the initial path implementation: + +```diff +# marker.nix type = markerType; default = {}; }; @@ -797,7 +803,11 @@ index ffb8185..940b8f8 100644 + default = {}; + }; }; - +``` + +Next, add an `arrival.style.label` attribute to the `config` block, mirroring the `departure.style.label`: +```diff +# marker.nix config = { departure.style.label = lib.mkDefault (firstUpperAlnum name); @@ -805,31 +815,29 @@ index ffb8185..940b8f8 100644 + (firstUpperAlnum name); }; }); - -@@ -76,7 +83,7 @@ in { +``` + +Finally, update the return list in the function passed to `concatMap` in `map.markers`: +```diff +# marker.nix map.markers = lib.filter (marker: marker.location != null) (lib.concatMap (user: [ - user.departure + user.departure user.arrival ]) (lib.attrValues config.users)); - + map.center = lib.mkIf ``` -## Connecting user paths +You should now be able to define paths on the map, connecting pairs of departure and arrival points. + +## Connecting Markers by Paths -In our path module, we can now define a path spanning from every users -departure location to their arrival location. +In the path module, you can now define a path connecting every user's departure and arrival locations. ```diff -diff --git a/path.nix b/path.nix -index 554a88b..d4a3a84 100644 ---- a/path.nix -+++ b/path.nix -@@ -17,6 +17,17 @@ in { - }; - +# path.nix config = { + + map.paths = map (user: { @@ -847,24 +855,25 @@ index 554a88b..d4a3a84 100644 "$(geocode ${lib.escapeShellArg loc})"; ``` -## Introducing path weight option +:::{warning} +Don't confuse the `map` function with the `map` option or the `map` script! +::: + +The new `map.paths` attribute contains a list of all valid paths defined for all users. -Let's also allow some customization of path styles with a `weight` option. -As already done before, we'll declare a submodule for the path style. +A path is valid only if the `departure` and `arrival` attributes are set for that user. -While we could also directly define the style.weight option in this -case, we will use the submodule in a future change to reuse the path -style definitions. +## Path Styling: Weight -Note how we're using a new type for this, `ints.between -`, which allows integers in the given inclusive range. +Your users have spoken, and they demand the ability to customize the styles of their paths with a `weight` option. +As before, you'll now declare a new submodule for the path style. + +While you could also directly define the `style.weight` option, in this case, you should use the submodule in a future change to reuse the path style definitions. + +Add the `pathStyleType` submodule option to the `let` block in `path.nix`: ```diff -diff --git a/path.nix b/path.nix -index d4a3a84..88766a8 100644 ---- a/path.nix -+++ b/path.nix -@@ -1,11 +1,26 @@ +# path.nix { lib, config, ... }: let + @@ -878,7 +887,17 @@ index d4a3a84..88766a8 100644 + }; + pathType = lib.types.submodule { - +``` + +:::{note} +The `ints.between ` type allows integers in the given (inclusive) range. +::: + +The path weight will default to 5, but can be set to any integer value in the 1 to 20 range, with higher weights producing thicker paths on the map. + +Now add a `style` option to the `options` set further down the file: +```diff +# path.nix options = { locations = lib.mkOption { type = lib.types.listOf lib.types.str; @@ -889,9 +908,13 @@ index d4a3a84..88766a8 100644 + default = {}; + }; }; - + }; -@@ -34,7 +49,10 @@ in { +``` + +Finally, update the `attributes` list in `paramForPath`: +```diff +# path.nix paramForPath = path: let attributes = @@ -905,23 +928,16 @@ index d4a3a84..88766a8 100644 }"; ``` -## User path styles +## The `pathStyle` Submodule + +Users still can't actually customize the path style yet, so you should introduce a new `pathStyle` option for each user. -Now users can't actually customize the path style yet, so let's -introduce a new `pathStyle` option for each user. +The module system allows you to declare values for an option multiple times, and if the types permit doing so, takes care of merging each declaration's values together. -But wait! Didn't we already define the `user` option in the `marker.nix` -module? Yes we did, but the module system actually allows us to declare -an option multiple times, and the module system takes care of merging -each declarations types together (if possible). +This makes it possible to have a definition for the `user` option in the `marker.nix` module, as well as a `user` definition in `path.nix`, which you should add now: ```diff -diff --git a/path.nix b/path.nix -index 88766a8..8b56782 100644 ---- a/path.nix -+++ b/path.nix -@@ -26,6 +26,16 @@ let - }; +# path.nix in { options = { + @@ -937,7 +953,11 @@ index 88766a8..8b56782 100644 map.paths = lib.mkOption { type = lib.types.listOf pathType; }; -@@ -38,6 +48,7 @@ in { +``` + +Then add a line using the `user.pathStyle` option in `map.paths`: +```diff +# path.nix user.departure.location user.arrival.location ]; @@ -947,20 +967,18 @@ index 88766a8..8b56782 100644 && user.arrival.location != null ``` -## Introducing path color option +## Path Styling: Color + +As with markers, paths should have customizable colors. -Very similar to markers, let's allow customization of the path color, -using types we've seen before already. +You can accomplish this using types you've already seen by now. +Add a new `colorType` block to `path.nix`, specifying the allowed color names and RGB/RGBA values: ```diff -diff --git a/path.nix b/path.nix -index 8b56782..d2073fe 100644 ---- a/path.nix -+++ b/path.nix -@@ -1,12 +1,25 @@ +# path.nix { lib, config, ... }: let - + + # Either a color name, `0xRRGGBB` or `0xRRGGBBAA` + colorType = lib.types.either + (lib.types.strMatching "0x[0-9A-F]{6}[0-9A-F]{2}?") @@ -970,8 +988,11 @@ index 8b56782..d2073fe 100644 + ]); + pathStyleType = lib.types.submodule { - options = { - weight = lib.mkOption { +``` + +Under the `weight` option, add a new `color` option to use the new `colorType` value: +```diff +# path.nix type = lib.types.ints.between 1 20; default = 5; }; @@ -982,8 +1003,11 @@ index 8b56782..d2073fe 100644 + }; }; }; - -@@ -62,6 +75,7 @@ in { +``` + +Finally, add a line using the `color` option to the `attributes` list: +```diff +# path.nix attributes = [ "weight:${toString path.style.weight}" @@ -993,17 +1017,15 @@ index 8b56782..d2073fe 100644 in "path=${ ``` -## Introducing geodesic path option +## Further Styling + +To further improve the aesthetics of the rendered map, you should add another style option allowing paths to be drawn as *geodesics*, the shortest "as the crow flies" distance between two points on Earth. -Finally, another option for the path style, using a new but very simple -type, `bool`, which just allows `true` and `false`. +Since this feature can be turned on or off, you can do this using the `bool` type, which can be `true` or `false`. +Make the following changes to `path.nix` now: ```diff -diff --git a/path.nix b/path.nix -index d2073fe..ebd9561 100644 ---- a/path.nix -+++ b/path.nix -@@ -20,6 +20,11 @@ let +# path.nix type = colorType; default = "blue"; }; @@ -1014,8 +1036,11 @@ index d2073fe..ebd9561 100644 + }; }; }; - -@@ -76,6 +81,7 @@ in { +``` + +Make sure to also add a new line using this to the `attributes` list, so the option value is included in the API call: +```diff +# path.nix [ "weight:${toString path.style.weight}" "color:${path.style.color}" @@ -1025,3 +1050,9 @@ index d2073fe..ebd9561 100644 in "path=${ ``` +## Wrapping Up +In this tutorial, you've learned how to write custom Nix modules to bring external services under declarative control, with the help of several new utility functions from the Nixpkgs `lib`. + +You defined several modules in multiple files, each with separate submodules making use of the module system's type checking. + +These modules exposed features of the external API in a declarative way. From e98276e241b17c8cc5d5038fea5d31f283b2da06 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 16:24:18 -0500 Subject: [PATCH 02/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 096deb309..ac01540d9 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -1,5 +1,6 @@ # The Module System -Much of the power in Nixpkgs comes from the module system, which provides mechanisms for automatically merging attribute sets, making it easy to compose configurations in a type-safe way. +Much of the power in Nixpkgs and NixOS comes from the module system. +It provides mechanisms for conveniently declaring and automatically merging interdependent attribute sets that follow dynamic type constraints, making it easy to express modular configurations. In this tutorial, you'll write your first modules to interact with the Google Maps API, declaring options which represent map geometry, location pins, and more. From 01cab161ca32852d8bed2b6761d0040e4df37324 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 16:26:38 -0500 Subject: [PATCH 03/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index ac01540d9..4f740419e 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -2,9 +2,9 @@ Much of the power in Nixpkgs and NixOS comes from the module system. It provides mechanisms for conveniently declaring and automatically merging interdependent attribute sets that follow dynamic type constraints, making it easy to express modular configurations. -In this tutorial, you'll write your first modules to interact with the Google Maps API, declaring options which represent map geometry, location pins, and more. +In this tutorial you'll learn what a module is and how to define one, what options are and how to declare them, how to express dependencies between modules, and follow extensive demonstration of how to wrap an existing API with Nix modules. -You'll learn what a module is and how to define one, what options are and how to declare them, how to express dependencies between modules, and a practical way to use Nix to wrap external APIs. +Concretely, you'll write modules to interact with the Google Maps API, declaring options which represent map geometry, location pins, and more. Be prepared to see some Nix errors: during the tutorial, you will first write some *incorrect* configurations, creating opportunities to discuss the resulting error messages and how to resolve them, particularly when discussing type checking. From 512c264b960630983e26a4bcef242b9bfc89a9cc Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 16:28:24 -0500 Subject: [PATCH 04/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 4f740419e..9ecdc8966 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -9,7 +9,7 @@ Concretely, you'll write modules to interact with the Google Maps API, declaring Be prepared to see some Nix errors: during the tutorial, you will first write some *incorrect* configurations, creating opportunities to discuss the resulting error messages and how to resolve them, particularly when discussing type checking. :::{note} -This tutorial follows [@infinisil's presentation](https://infinisil.com/modules.mp4) of the [Summer of Nix Modules](https://github.com/tweag/summer-of-nix-modules), during the 2021 Summer of Nix. +This tutorial follows [@infinisil](https://github.com/infinisil)'s [presentation on modules](https://infinisil.com/modules.mp4) [(source)](https://github.com/tweag/summer-of-nix-modules) for 2021 Summer of Nix. ::: ## Empty module From 30e3f4528cc038ba547f3a864a4f737057f80b75 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 16:32:59 -0500 Subject: [PATCH 05/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 9ecdc8966..9ae7c35e0 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -14,7 +14,7 @@ This tutorial follows [@infinisil](https://github.com/infinisil)'s [presentation ## Empty module -The simplest module is just an empty attribute set, which doesn't do anything! +The simplest module is just a function that takes any attributes and returns empty attribute set. Write the following into a file called `default.nix`: From 08095679c4eefa0bfe5f2c6dd1139791916e04e2 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 16:33:17 -0500 Subject: [PATCH 06/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 9ae7c35e0..ec524a65a 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -1,4 +1,4 @@ -# The Module System +# Deep dive demo: Wrapping the World in Modules Much of the power in Nixpkgs and NixOS comes from the module system. It provides mechanisms for conveniently declaring and automatically merging interdependent attribute sets that follow dynamic type constraints, making it easy to express modular configurations. From 068224d0061629710a75c9d651689eaea05c2430 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 16:34:29 -0500 Subject: [PATCH 07/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 1 + 1 file changed, 1 insertion(+) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index ec524a65a..95c658446 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -20,6 +20,7 @@ Write the following into a file called `default.nix`: ```nix # default.nix +{ ... }: { } From 08ee13c37e1ca0d0a092efc56f81d584203127e6 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 17:40:12 -0500 Subject: [PATCH 08/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 95c658446..18a624100 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -50,7 +50,7 @@ The ellipsis `...` is necessary because arbitrary arguments can be passed to mod ## Declaring Options -One of the reasons for writing modules is to declare names which can be assigned values and used in other computations elsewhere. +Modules allow providing *options* that declare which values can be set and used elsewhere. For your new module to become useful, you will need to add some *options*, which define these named-values. From 26c62c976735f06b511c4ffeec90717555314ad0 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 17:40:29 -0500 Subject: [PATCH 09/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 1 - 1 file changed, 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 18a624100..93984a077 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -52,7 +52,6 @@ The ellipsis `...` is necessary because arbitrary arguments can be passed to mod Modules allow providing *options* that declare which values can be set and used elsewhere. -For your new module to become useful, you will need to add some *options*, which define these named-values. Options are declared by defining an attribute under the top-level `options` attribute, using `lib.mkOption`. From 8cf2e36b1e4ad6faf8cc1114fb18c41d55b34759 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 17:40:58 -0500 Subject: [PATCH 10/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 93984a077..0ac8b1006 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -72,7 +72,7 @@ Change `default.nix` to include the following declaration: } ``` -While many attributes for customizing options are available, the most important one is `type`, which specifies which values are valid for an option, and how or whether multiple values should be merged together. +While many attributes for customizing options are available, the most important one is `type`, which specifies which values are valid for an option. There are several other types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the nixpkgs library. From 68b82404d1841e4ec0b6e64fa3dd00a33244b0bd Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 17:41:14 -0500 Subject: [PATCH 11/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 0ac8b1006..05fa9a152 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -74,7 +74,7 @@ Change `default.nix` to include the following declaration: While many attributes for customizing options are available, the most important one is `type`, which specifies which values are valid for an option. -There are several other types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the nixpkgs library. +There are several other types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the Nixpkgs library. You have just declared `generate.script` with the `lines` type, which specifies that the only valid values are strings, and that multiple strings should be joined with newlines. From adfe0e5d0ca2e70ce530e4be610f4f9942cce203 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 17:42:21 -0500 Subject: [PATCH 12/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 05fa9a152..c96f100f2 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -89,7 +89,7 @@ Write a new file, `eval.nix`, which you will use to evaluate `default.nix`: } ``` -Now execute the following command: +Now run the following command: ```bash nix-instantiate --eval eval.nix -A config.generate.script From f0f53cb862cee0db89928bc97286d318a219224c Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:24:56 -0500 Subject: [PATCH 13/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index c96f100f2..37298e44f 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -101,7 +101,7 @@ You will see an error message indicating that the `generate.script` option is us As previously mentioned, the `lines` type only permits string values. :::{warning} -In this section, you will make your first type error. Be prepared! +In this section, you will set an invalid value and encounter a type error. ::: What happens if you instead try to assign an integer to the option? From 9fd98f1ceee454a3848a07f0a52ce2a8e766198f Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:25:32 -0500 Subject: [PATCH 14/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 37298e44f..2f6ce8107 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -159,7 +159,7 @@ TODO: Create derivations to get these commands ## Declaring More Options In this section, you will introduce another option: `generate.requestParams`. -For its type, you should use `listOf `, which is a generic list type where each element must have the given nested type. +For its type, you should use `listOf `, which is a list type where each element must have the specified type. Instead of `lines`, in this case you will want the nested type to be `str`, a generic string type. From c38373fb7a94dc266b4f2ab4c55620004855f4e5 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:25:49 -0500 Subject: [PATCH 15/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 1 + 1 file changed, 1 insertion(+) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 2f6ce8107..434977b77 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -164,6 +164,7 @@ For its type, you should use `listOf `, which is a list type where Instead of `lines`, in this case you will want the nested type to be `str`, a generic string type. The difference between `str` and `lines` is in their merging behavior: +Module option types not only check for valid values, but also specify how multiple definitions of an option are to be combined into one. - For `lines`, multiple definitions get merged by concatenation with newlines. - For `str`, multiple definitions are not allowed. This is mostly irrelevant here however, since it is not really possible to define a list element multiple times. From 820e63a0bddd60aebba8f28043da4f21fd114ad9 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:26:04 -0500 Subject: [PATCH 16/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 434977b77..649e57da5 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -166,7 +166,7 @@ Instead of `lines`, in this case you will want the nested type to be `str`, a ge The difference between `str` and `lines` is in their merging behavior: Module option types not only check for valid values, but also specify how multiple definitions of an option are to be combined into one. - For `lines`, multiple definitions get merged by concatenation with newlines. -- For `str`, multiple definitions are not allowed. This is mostly irrelevant here however, since it is not really possible to define a list element multiple times. +- For `str`, multiple definitions are not allowed. This is not a problem here, since one can't define a list element multiple times. Make the following additions to your `default.nix` file now: ```diff From 74c27ee697ef862cc26239739cca85e3f4c43d83 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:26:32 -0500 Subject: [PATCH 17/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 649e57da5..4d37536b5 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -200,7 +200,7 @@ A given module generally only declares a single option that is meant to be evalu This option generates the final result to be used elsewhere, which in this case is `generate.script`. -Options have the ability to depend on other options, making it possible to build more useful abstractions. +Options can depend on other options, making it possible to build more useful abstractions. Here, the plan is for the `generate.script` option to use the values of `generate.requestParams` as arguments to the `map` command. From 18854fe1c5f6242a6b3ea602d84f01192dc9af7c Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:26:51 -0500 Subject: [PATCH 18/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 4d37536b5..86112b10c 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -202,7 +202,7 @@ This option generates the final result to be used elsewhere, which in this case Options can depend on other options, making it possible to build more useful abstractions. -Here, the plan is for the `generate.script` option to use the values of `generate.requestParams` as arguments to the `map` command. +Here, we want the `generate.script` option to use the values of `generate.requestParams` as arguments to the `map` command. ### Accessing Option Values To make a declared option available, the argument attribute set of the module declaring it must include the `config` attribute. From 06b9df4b98d9c00e5a6daf8eee9b3a5da556f658 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:27:14 -0500 Subject: [PATCH 19/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 86112b10c..becdee78d 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -205,7 +205,7 @@ Options can depend on other options, making it possible to build more useful abs Here, we want the `generate.script` option to use the values of `generate.requestParams` as arguments to the `map` command. ### Accessing Option Values -To make a declared option available, the argument attribute set of the module declaring it must include the `config` attribute. +To make an option definition available, the argument of the module accessing it must include the `config` attribute. Update `default.nix` to add the `config` attribute: ```diff From 6f59e1c85ff5091deec9b2deea3f9d1a1b910d18 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:28:09 -0500 Subject: [PATCH 20/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index becdee78d..0d239cbe4 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -214,7 +214,7 @@ Update `default.nix` to add the `config` attribute: +{ lib, config, ... }: { ``` -When a module declaring an option is evaluated, values of the resulting option can be accessed by using attribute names to access the corresponding values. +When a module setting options is evaluated, these values can be accessed by their corresponding attribute names. Now make the following changes to `default.nix`: From f52d339dbbfcf4ca432db4afb1e27f0291661848 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:28:24 -0500 Subject: [PATCH 21/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 0d239cbe4..48e18e4c8 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -229,7 +229,8 @@ Now make the following changes to `default.nix`: ''; ``` -Here, the value of the `config.generate.requestParams` attribute is substituted at its call site. +Here, the value of the `config.generate.requestParams` attribute is populated by the module system based on the definitions in the same file. +This is possible due to lazy evaluation in the Nix language. `lib.concatStringsSep " "` is then used to join each list element from the value of `config.generate.requestParams` into a single string, with the list elements of `requestParams` separated by a space character. From 09c7720d3d6fdfb940293509f96688ba4bc6bc1d Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:28:40 -0500 Subject: [PATCH 22/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 48e18e4c8..035222dab 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -240,7 +240,7 @@ The result of this represents the list of command line arguments to pass to `map In this section, you will define a new option, `map.zoom`, to control the zoom level of the map. -You will use a new type, `nullOr `, which can take as values either the values of its argument type or `null`. +You will use a new type, `nullOr `, which can take either values of its argument type or `null`. In this case, a `null` value will use the API's default behavior of inferring the zoom level. From 581a064f419fa42a07e2604dd7b62961f947a008 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:28:56 -0500 Subject: [PATCH 23/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 035222dab..8d2270c13 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -248,7 +248,7 @@ Here, you will also use `default` from `mkOption`](https://github.com/NixOS/nixp You will use this option to define another element in `generate.requestParams`, which will only be added if its value is non-null. -To do this, you can use the `mkIf ` function, which only adds the definition if the condition holds. +To do this, you can use the `mkIf ` function, which only adds the definition if the condition evaluates to `true`. Add the `map` attribute set with the `zoom` option into the top-level `options` declaration, like so: From e7558e028fd10fe2c906a1ae197efc9a9f1371d6 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:29:20 -0500 Subject: [PATCH 24/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 8d2270c13..2362fe2ad 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -320,7 +320,7 @@ Add another `mkIf` call to the list of `requestParams` now: This time, you've used `escapeShellArg` to pass the `config.map.center` value as a command-line argument to `geocode`, interpolating the result back into the `requestParams` string which sets the `center` value. -Wrapping shell command execution in Nix modules is a powerful technique for controlling system changes using the ergnomic attributes and values interface. +Wrapping shell command execution in Nix modules is a helpful technique for controlling system changes, using the more ergonomic attributes and values interface rather than dealing with the peculiarities of escaping manually. ## Splitting Modules From 48f20b10f0ca72fb60bc51c27f86623829b4147a Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:29:59 -0500 Subject: [PATCH 25/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 2362fe2ad..91ab95c30 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -324,7 +324,7 @@ Wrapping shell command execution in Nix modules is a helpful technique for contr ## Splitting Modules -The module schema includes the `imports` attribute, which allows you to define further modules to import, enabling a *modular* approach where your configuration may be split into multiple files. +The module schema includes the `imports` attribute, which allows incorporating further modules, for example to split a large configuration into multiple files. In particular, this allows you to separate option declarations from their call-sites in your configuration. From 901fdd4cd7ccb33812d63413599ba4c1e2303341 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:30:28 -0500 Subject: [PATCH 26/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 91ab95c30..551704503 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -326,7 +326,7 @@ Wrapping shell command execution in Nix modules is a helpful technique for contr The module schema includes the `imports` attribute, which allows incorporating further modules, for example to split a large configuration into multiple files. -In particular, this allows you to separate option declarations from their call-sites in your configuration. +In particular, this allows you to separate option declarations from where they are used in your configuration. You should now create a new module, `marker.nix`, where you can declare options for defining location pins and other markers on the map. ```diff From c95a8aee25269605dc60977617c38182c517dd14 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 18:52:29 -0500 Subject: [PATCH 27/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 551704503..4066fb019 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -328,7 +328,7 @@ The module schema includes the `imports` attribute, which allows incorporating f In particular, this allows you to separate option declarations from where they are used in your configuration. -You should now create a new module, `marker.nix`, where you can declare options for defining location pins and other markers on the map. +Create a new module, `marker.nix`, where you can declare options for defining location pins and other markers on the map. ```diff # marker.nix +{ lib, config, ... }: { From dbbec651974fc519be4dd6c4c880ba01ea7c7981 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:35:47 -0500 Subject: [PATCH 28/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 1 - 1 file changed, 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 4066fb019..4a0416440 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -353,7 +353,6 @@ One of the most useful types included in the module system's type system is `sub This type allows you to define nested modules with their own options. -Every value of such a type is then interpreted (by default) as a `config` assignment of the nested module evaluation. Here, you will define a new `map.markers` option whose type is a list of submodules, each with a nested `location` type, allowing you to define a list of markers on the map. From 0e64d36ec5204723fc54484c9e6cbf4e47bcc1cc Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:36:19 -0500 Subject: [PATCH 29/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 4a0416440..0e9a5a9a8 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -680,7 +680,7 @@ Now add a line to the `paramForMarker` list which makes use of the new option: ## Marker Styling: Size -In case you set many different markers, it would be helpful to have the ability to change their size individually, further improving visual accessibility. +In case you set many different markers, it would be helpful to have the ability to change their size individually. Add a new `style.size` option to `marker.nix`, allowing you to do so: From ce44f03b089cf07a8b99ea36b0ad73880ff72a22 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:36:26 -0500 Subject: [PATCH 30/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 0e9a5a9a8..0f34933b3 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -699,7 +699,7 @@ Add a new `style.size` option to `marker.nix`, allowing you to do so: }; ``` -Now add a handler for the size parameter in `paramForMarker`, which selects an appropriate string to pass to the API: +Now add a mapping for the size parameter in `paramForMarker`, which selects an appropriate string to pass to the API: ```diff # marker.nix generate.requestParams = let From 286e473c5c840b5eee2277bfe6b85c7b68a819f9 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:37:34 -0500 Subject: [PATCH 31/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 0f34933b3..f4f03914e 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -387,7 +387,7 @@ Make the following changes to `marker.nix` now: Because of the way the module system composes option definitions, you can also freely assign values to options defined in other modules. -In this case, you will use the `map.markers` option to derive and add new `requestParams`, making your declared markers appear on the returned map. +In this case, you will use the `map.markers` option to produce and add new elements to the `requestParams` list, making your declared markers appear on the returned map. To implement this behavior, add the following `config` block to `marker.nix`: ```diff From f91e9bc8fb31b0c475477e968d0da7bb34644cf6 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:39:56 -0500 Subject: [PATCH 32/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index f4f03914e..f53288f72 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -1021,7 +1021,7 @@ Finally, add a line using the `color` option to the `attributes` list: ## Further Styling -To further improve the aesthetics of the rendered map, you should add another style option allowing paths to be drawn as *geodesics*, the shortest "as the crow flies" distance between two points on Earth. +Now that you've got this far, to further improve the aesthetics of the rendered map, you should add another style option allowing paths to be drawn as *geodesics*, the shortest "as the crow flies" distance between two points on Earth. Since this feature can be turned on or off, you can do this using the `bool` type, which can be `true` or `false`. From 60aa7615961d57b37a36573b42d3e26c0c693e3b Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:40:29 -0500 Subject: [PATCH 33/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index f53288f72..3621b02fc 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -419,7 +419,7 @@ To implement this behavior, add the following `config` block to `marker.nix`: Here, you again used `escapeShellArg` and string interpolation to generate a Nix string, this time producing a pipe-separated list of geocoded location attributes. -The `generate.requestParams` value was also set to the resulting list of strings, which gets appended to the `generate.requestParams` list defined in `default.nix`, thanks to the default behavior of the `list`-type module. +The `generate.requestParams` value was also set to the resulting list of strings, which gets appended to the `generate.requestParams` list defined in `default.nix`, thanks to the default merging behavior of the `list`-type module. ## Multiple Markers From 89662dbe6a232a795aa5c72643db7f434b17acfa Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:41:09 -0500 Subject: [PATCH 34/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 3621b02fc..434f13707 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -445,7 +445,7 @@ In this case, the default behavior of the Maps API when not passed a center or z ## Nested Submodules -It's time to introduce the `users` option with the `lib.types.attrsOf ` type, which will allow you to define `users` as an attribute set with arbitrary keys, each value of which has type ``. +It's time to introduce a `users` option with the `lib.types.attrsOf ` type, which will allow you to define `users` as an attribute set with arbitrary keys, each value of which has type ``. Here, that subtype will be another submodule which allows declaring a departure marker, suitable for querying the API for the recommended route for a trip. From 0a69bc8fd77e529c5815d616fec14f3987ae166b Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Thu, 14 Sep 2023 19:42:25 -0500 Subject: [PATCH 35/58] Update source/tutorials/module-system/module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 434f13707..f21c4c669 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -449,7 +449,7 @@ It's time to introduce a `users` option with the `lib.types.attrsOf ` t Here, that subtype will be another submodule which allows declaring a departure marker, suitable for querying the API for the recommended route for a trip. -This will also make use of the `markerType` submodule, giving a nested structure of submodules. +This will again make use of the `markerType` submodule, giving a nested structure of submodules. To propagate marker definitions from `users` to the `map.markers` option, make the following changes now: From bed58692aaaad807234698075e600bd46b95c8b3 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Sat, 16 Sep 2023 08:46:00 -0500 Subject: [PATCH 36/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index f21c4c669..fcdc7df24 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -506,7 +506,7 @@ The `departure` values of each of the `users` are then joined into a list, and t The resulting list is stored in `map.markers`. -The resulting `map.markers` option then propagates to the `generate.requestParams` option, which in turn is used to generate arguments to the script which ultimately calls the Maps API. +The resulting `map.markers` option then propagates to the `generate.requestParams` option, which in turn is used to generate arguments to the script which ultimately calls the Google Maps API. Defining the options in this way allows you to set multiple `users..departure.location` values and generate a map with the appropriate zoom and center, with pins corresponding to the set of `departure.location` values for *all* `users`. From e25d80d4f0ba140e01b0ee1accde6d4397368748 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Sat, 16 Sep 2023 08:46:13 -0500 Subject: [PATCH 37/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index fcdc7df24..bd5519766 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -518,7 +518,7 @@ Now that the map can be rendered with multiple markers, it's time to add some st To tell the markers apart, you should add another option to the `markerType` submodule, to allow labeling each marker pin. -The API [states](https://developers.google.com/maps/documentation/maps-static/start#MarkerStyles) that these labels must be either an uppercase letter or a number. +The API documentation states that [these labels must be either an uppercase letter or a number](https://developers.google.com/maps/documentation/maps-static/start#MarkerStyles). You can implement this with the `strMatching ""` type, where `` is a regular expression used for the matching; this will reject any unacceptable (non-uppercase letter or number) values. From 8f92b0141d4dc4391c433f87e0055c195556a14b Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Sat, 16 Sep 2023 08:46:31 -0500 Subject: [PATCH 38/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index bd5519766..9c8a72ee7 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -520,7 +520,7 @@ To tell the markers apart, you should add another option to the `markerType` sub The API documentation states that [these labels must be either an uppercase letter or a number](https://developers.google.com/maps/documentation/maps-static/start#MarkerStyles). -You can implement this with the `strMatching ""` type, where `` is a regular expression used for the matching; this will reject any unacceptable (non-uppercase letter or number) values. +You can implement this with the `strMatching ""` type, where `` is a regular expression that will accept any matching (uppercase letter or number) values. - In the `let` block: ```diff From b9a68dc543cfc49fa947d2048c191e9a9377a2d9 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Sat, 16 Sep 2023 08:46:43 -0500 Subject: [PATCH 39/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 9c8a72ee7..7889745ac 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -629,7 +629,7 @@ This ensures that other values set for the same option will prevail. For better visual contrast, it would also be helpful to have a way to change the *color* of a marker. Here you will use two new type-functions for this: -- `either `, which takes two types as arguments, allows either of them +- `either `, which takes two types as arguments, and allows either of them - `enum [ ]`, which takes a list of allowed values In the `let` block, add the following `colorType` option, which can hold strings containing either some given color names or an RGB value: From 352214db309c20d3410ee052fe3fc4902e856d4b Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Sat, 16 Sep 2023 08:46:55 -0500 Subject: [PATCH 40/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 7889745ac..ed9ed2f67 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -630,7 +630,7 @@ For better visual contrast, it would also be helpful to have a way to change the Here you will use two new type-functions for this: - `either `, which takes two types as arguments, and allows either of them -- `enum [ ]`, which takes a list of allowed values +- `enum [ ]`, which takes a list of allowed values, and allows any of them In the `let` block, add the following `colorType` option, which can hold strings containing either some given color names or an RGB value: ```diff From 7f556264163d716f19fe2d15228b4893b020e1f5 Mon Sep 17 00:00:00 2001 From: Alexander Groleau Date: Sat, 16 Sep 2023 08:47:23 -0500 Subject: [PATCH 41/58] Update module-system.md Co-authored-by: Valentin Gagarin --- source/tutorials/module-system/module-system.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index ed9ed2f67..25d6db34a 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -1058,3 +1058,5 @@ In this tutorial, you've learned how to write custom Nix modules to bring extern You defined several modules in multiple files, each with separate submodules making use of the module system's type checking. These modules exposed features of the external API in a declarative way. + +You can now conquer the world with Nix. From 5b07f134cfd90970bc927f0a6fd802dac2121c1e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 26 Oct 2023 21:04:15 +0200 Subject: [PATCH 42/58] Update source/tutorials/module-system/module-system.md Co-authored-by: asymmetric --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 25d6db34a..e999afafe 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -1,4 +1,4 @@ -# Deep dive demo: Wrapping the World in Modules +# Deep dive demo: Wrapping the world in modules Much of the power in Nixpkgs and NixOS comes from the module system. It provides mechanisms for conveniently declaring and automatically merging interdependent attribute sets that follow dynamic type constraints, making it easy to express modular configurations. From 46aa2713de632d1b8b0f146ed8a02ac6fd7933f5 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 26 Oct 2023 21:05:38 +0200 Subject: [PATCH 43/58] Update source/tutorials/module-system/module-system.md Co-authored-by: asymmetric --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index e999afafe..7cc8042a8 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -14,7 +14,7 @@ This tutorial follows [@infinisil](https://github.com/infinisil)'s [presentation ## Empty module -The simplest module is just a function that takes any attributes and returns empty attribute set. +The simplest module is just a function that takes any attributes and returns an empty attribute set. Write the following into a file called `default.nix`: From 544f3a0eb0df3d2ddc5658e41b9e9cba9f400caa Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 26 Oct 2023 21:05:46 +0200 Subject: [PATCH 44/58] Update source/tutorials/module-system/module-system.md --- source/tutorials/module-system/module-system.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 7cc8042a8..50d8f44b2 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -2,7 +2,13 @@ Much of the power in Nixpkgs and NixOS comes from the module system. It provides mechanisms for conveniently declaring and automatically merging interdependent attribute sets that follow dynamic type constraints, making it easy to express modular configurations. -In this tutorial you'll learn what a module is and how to define one, what options are and how to declare them, how to express dependencies between modules, and follow extensive demonstration of how to wrap an existing API with Nix modules. +In this tutorial you'll learn +- what a module is +- how to define one +- what options are +- how to declare them +- how to express dependencies between modules +and follow extensive demonstration of how to wrap an existing API with Nix modules. Concretely, you'll write modules to interact with the Google Maps API, declaring options which represent map geometry, location pins, and more. From e7a8c2116a3e5a0c8b9fc2c7ba63bea53a9a99e5 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 26 Oct 2023 21:18:25 +0200 Subject: [PATCH 45/58] Apply suggestions from code review Co-authored-by: asymmetric --- source/tutorials/module-system/module-system.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 50d8f44b2..56511b300 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -330,7 +330,7 @@ Wrapping shell command execution in Nix modules is a helpful technique for contr ## Splitting Modules -The module schema includes the `imports` attribute, which allows incorporating further modules, for example to split a large configuration into multiple files. +The [module schema](https://nixos.org/manual/nixos/stable/#sec-writing-modules) includes the `imports` attribute, which allows incorporating further modules, for example to split a large configuration into multiple files. In particular, this allows you to separate option declarations from where they are used in your configuration. @@ -447,11 +447,11 @@ To do this, make the following additions to `marker.nix`, above the `generate.re let ``` -In this case, the default behavior of the Maps API when not passed a center or zoom level is to pick the geometric center of all the given markers, and to set a zoom level appropriate for viewing all markers at once. +In this case, the default behavior of the Google Maps API when not passed a center or zoom level is to pick the geometric center of all the given markers, and to set a zoom level appropriate for viewing all markers at once. ## Nested Submodules -It's time to introduce a `users` option with the `lib.types.attrsOf ` type, which will allow you to define `users` as an attribute set with arbitrary keys, each value of which has type ``. +It's time to introduce a `users` option with type `lib.types.attrsOf `, which will allow you to define `users` as an attribute set, whose values have type ``. Here, that subtype will be another submodule which allows declaring a departure marker, suitable for querying the API for the recommended route for a trip. @@ -520,7 +520,7 @@ In the 2021 Summer of Nix, this formed the basis of an interactive multi-person ## Labeling Markers -Now that the map can be rendered with multiple markers, it's time to add some style customization. +Now that the map can be rendered with multiple markers, it's time to add some style customizations. To tell the markers apart, you should add another option to the `markerType` submodule, to allow labeling each marker pin. @@ -564,9 +564,9 @@ Here, the label for each `marker` is only propagated to the CLI parameters if `m ## Defining a Default Label -In case you don't want to manually define a label for every marker, you can set a default value. +In case you don't want to manually define a label for each marker, you can set a default value. -The easiest value for a default label is the username, which will always also be set. +The easiest value for a default label is the username, which will always be set. This `firstUpperAlnum` function allows you to retrieve the first character of the username, with the correct type for passing to `departure.style.label`: @@ -623,6 +623,7 @@ Instead you can use the `config` section of the `user` submodule to set a defaul :::{note} Module options have a *precedence*, represented as an integer, which determines the priority of setting the option to a particular value. +When merging values, the lowest precedence wins. The `lib.mkDefault` modifier sets the precedence of its argument value to 1000, the lowest priority. From 25435fc1aa134f3fdb7c086865bc7b935aa68af8 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 26 Oct 2023 21:31:56 +0200 Subject: [PATCH 46/58] Apply suggestions from code review Co-authored-by: asymmetric --- source/tutorials/module-system/module-system.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 56511b300..328a02018 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -80,7 +80,7 @@ Change `default.nix` to include the following declaration: While many attributes for customizing options are available, the most important one is `type`, which specifies which values are valid for an option. -There are several other types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the Nixpkgs library. +There are several types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the Nixpkgs library. You have just declared `generate.script` with the `lines` type, which specifies that the only valid values are strings, and that multiple strings should be joined with newlines. @@ -167,14 +167,14 @@ In this section, you will introduce another option: `generate.requestParams`. For its type, you should use `listOf `, which is a list type where each element must have the specified type. -Instead of `lines`, in this case you will want the nested type to be `str`, a generic string type. +Instead of `lines`, in this case you will want the type of the list elements to be `str`, a generic string type. The difference between `str` and `lines` is in their merging behavior: Module option types not only check for valid values, but also specify how multiple definitions of an option are to be combined into one. - For `lines`, multiple definitions get merged by concatenation with newlines. - For `str`, multiple definitions are not allowed. This is not a problem here, since one can't define a list element multiple times. -Make the following additions to your `default.nix` file now: +Make the following additions to your `default.nix` file: ```diff # default.nix generate.script = lib.mkOption { @@ -220,7 +220,7 @@ Update `default.nix` to add the `config` attribute: +{ lib, config, ... }: { ``` -When a module setting options is evaluated, these values can be accessed by their corresponding attribute names. +When a module that sets options is evaluated, the resulting values can be accessed by their corresponding attribute names. Now make the following changes to `default.nix`: @@ -236,7 +236,7 @@ Now make the following changes to `default.nix`: ``` Here, the value of the `config.generate.requestParams` attribute is populated by the module system based on the definitions in the same file. -This is possible due to lazy evaluation in the Nix language. +Lazy evaluation in the Nix language allows taking a value from the `config` argument passed to the module which defines the value. `lib.concatStringsSep " "` is then used to join each list element from the value of `config.generate.requestParams` into a single string, with the list elements of `requestParams` separated by a space character. From 98aafb901592717e5b83b6d480ff9e2c67413e63 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 21:22:02 +0200 Subject: [PATCH 47/58] fix whitespace --- source/tutorials/module-system/module-system.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 328a02018..416f8dc03 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -1,4 +1,5 @@ # Deep dive demo: Wrapping the world in modules + Much of the power in Nixpkgs and NixOS comes from the module system. It provides mechanisms for conveniently declaring and automatically merging interdependent attribute sets that follow dynamic type constraints, making it easy to express modular configurations. @@ -57,8 +58,6 @@ The ellipsis `...` is necessary because arbitrary arguments can be passed to mod ## Declaring Options Modules allow providing *options* that declare which values can be set and used elsewhere. - - Options are declared by defining an attribute under the top-level `options` attribute, using `lib.mkOption`. In this section, you will define the `generate.script` option. @@ -79,7 +78,6 @@ Change `default.nix` to include the following declaration: ``` While many attributes for customizing options are available, the most important one is `type`, which specifies which values are valid for an option. - There are several types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the Nixpkgs library. You have just declared `generate.script` with the `lines` type, which specifies that the only valid values are strings, and that multiple strings should be joined with newlines. @@ -163,6 +161,7 @@ Update `default.nix` by changing the value of `generate.script` to the following TODO: Create derivations to get these commands ## Declaring More Options + In this section, you will introduce another option: `generate.requestParams`. For its type, you should use `listOf `, which is a list type where each element must have the specified type. @@ -630,7 +629,6 @@ The `lib.mkDefault` modifier sets the precedence of its argument value to 1000, This ensures that other values set for the same option will prevail. ::: - ## Marker Styling: Color For better visual contrast, it would also be helpful to have a way to change the *color* of a marker. @@ -1060,6 +1058,7 @@ Make sure to also add a new line using this to the `attributes` list, so the opt ``` ## Wrapping Up + In this tutorial, you've learned how to write custom Nix modules to bring external services under declarative control, with the help of several new utility functions from the Nixpkgs `lib`. You defined several modules in multiple files, each with separate submodules making use of the module system's type checking. From 0ea1c14e9fd45aebbe83bd851e29a259f8486f18 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 21:32:11 +0200 Subject: [PATCH 48/58] add some more motivation the each section --- source/tutorials/module-system/module-system.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 416f8dc03..a6f9cf582 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -21,6 +21,7 @@ This tutorial follows [@infinisil](https://github.com/infinisil)'s [presentation ## Empty module +We have to start somewhere. The simplest module is just a function that takes any attributes and returns an empty attribute set. Write the following into a file called `default.nix`: @@ -49,7 +50,8 @@ Do this by adding the following new line to `default.nix`: The addition of this line turns the expression in `default.nix` into a function which takes *at least* one argument, called `lib`, and may accept other arguments (expressed by the ellipsis `...`). -Matching on the `lib` argument will make `nixpkgs` library functions available within the function body. +On NixOS, `lib` argument is passed automatically. +This will make Nixpkgs library functions available within the function body. :::{note} The ellipsis `...` is necessary because arbitrary arguments can be passed to modules. @@ -57,8 +59,10 @@ The ellipsis `...` is necessary because arbitrary arguments can be passed to mod ## Declaring Options -Modules allow providing *options* that declare which values can be set and used elsewhere. -Options are declared by defining an attribute under the top-level `options` attribute, using `lib.mkOption`. +To set any values, the module system first has to know which ones are allowed. + +This is done by declaring *options* that specify which values can be set and used elsewhere. +Options are declared by adding an attribute under the top-level `options` attribute, using `lib.mkOption`. In this section, you will define the `generate.script` option. @@ -102,6 +106,7 @@ nix-instantiate --eval eval.nix -A config.generate.script You will see an error message indicating that the `generate.script` option is used but not defined; you will need to assign a value to the option before using it. ## Type Checking + As previously mentioned, the `lines` type only permits string values. :::{warning} From 6f4d5e9b8fddaa8abddb06b0751fe7654bd3c9bf Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 21:56:45 +0200 Subject: [PATCH 49/58] make scripts downloadable --- source/conf.py | 2 +- source/tutorials/module-system/{ => files}/geocode | 0 source/tutorials/module-system/{ => files}/map | 0 source/tutorials/module-system/icat | 3 --- source/tutorials/module-system/module-system.md | 3 +++ 5 files changed, 4 insertions(+), 4 deletions(-) rename source/tutorials/module-system/{ => files}/geocode (100%) rename source/tutorials/module-system/{ => files}/map (100%) delete mode 100755 source/tutorials/module-system/icat diff --git a/source/conf.py b/source/conf.py index a59a52a66..0e4e15d81 100644 --- a/source/conf.py +++ b/source/conf.py @@ -169,7 +169,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ['_static', 'tutorials/module-system/files'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/source/tutorials/module-system/geocode b/source/tutorials/module-system/files/geocode similarity index 100% rename from source/tutorials/module-system/geocode rename to source/tutorials/module-system/files/geocode diff --git a/source/tutorials/module-system/map b/source/tutorials/module-system/files/map similarity index 100% rename from source/tutorials/module-system/map rename to source/tutorials/module-system/files/map diff --git a/source/tutorials/module-system/icat b/source/tutorials/module-system/icat deleted file mode 100755 index cf7109313..000000000 --- a/source/tutorials/module-system/icat +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -feh - diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index a6f9cf582..4d4bf31e0 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -19,6 +19,9 @@ Be prepared to see some Nix errors: during the tutorial, you will first write so This tutorial follows [@infinisil](https://github.com/infinisil)'s [presentation on modules](https://infinisil.com/modules.mp4) [(source)](https://github.com/tweag/summer-of-nix-modules) for 2021 Summer of Nix. ::: +You will use need two helper scripts for this exercise. +Download {download}`map ` and {download}`geocode ` into your working directory. + ## Empty module We have to start somewhere. From bc427858ecac32e67b19c2c3a6bcf647a3c005cd Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 21:57:14 +0200 Subject: [PATCH 50/58] address review comments --- .../tutorials/module-system/module-system.md | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 4d4bf31e0..d7247f948 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -39,19 +39,19 @@ Write the following into a file called `default.nix`: ## Module Arguments -In order to make a module actually useful, you will need to write it as a *function*, which takes an attribute set as an argument. - -Do this by adding the following new line to `default.nix`: +We will need some helper functions, which will come from the Nixpkgs library. +Start by changing the first line in `default.nix`: ```diff # default.nix +- { ... }: + { lib, ... }: { } ``` -The addition of this line turns the expression in `default.nix` into a function which takes *at least* one argument, called `lib`, and may accept other arguments (expressed by the ellipsis `...`). +Now the module is a function which takes *at least* one argument, called `lib`, and may accept other arguments (expressed by the ellipsis `...`). On NixOS, `lib` argument is passed automatically. This will make Nixpkgs library functions available within the function body. @@ -85,21 +85,28 @@ Change `default.nix` to include the following declaration: ``` While many attributes for customizing options are available, the most important one is `type`, which specifies which values are valid for an option. -There are several types available under [`lib.types`](https://github.com/NixOS/nixpkgs/blob/master/lib/types.nix) in the Nixpkgs library. +There are several types available under [`lib.types`](https://nixos.org/manual/nixos/stable/#sec-option-types-basic) in the Nixpkgs library. -You have just declared `generate.script` with the `lines` type, which specifies that the only valid values are strings, and that multiple strings should be joined with newlines. +You have just declared `generate.script` with the `lines` type, which specifies that the only valid values are strings, and that multiple definitions should be joined with newlines. Write a new file, `eval.nix`, which you will use to evaluate `default.nix`: ```nix # eval.nix -(import ).evalModules { +let + nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-22.11"; + pkgs = import nixpkgs { config = {}; overlays = []; }; +in +pkgs.lib.evalModules { modules = [ ./default.nix ]; } ``` +[`evalModules`] is the function that evaluates modules, applies type checking, and merges values into the final attribute set. +It expects a `modules` attribute that takes a list, where each element can be a path to a module or an expression that follows the [module schema](https://nixos.org/manual/nixos/stable/#sec-writing-modules). + Now run the following command: ```bash From f93878317139e79d983d41115a0663e546d26fd4 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 23:06:05 +0200 Subject: [PATCH 51/58] make script actually work ...hopefully. can't test it without Google API key --- .../tutorials/module-system/module-system.md | 73 ++++++++++++++++--- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index d7247f948..dfe79d09d 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -13,6 +13,10 @@ and follow extensive demonstration of how to wrap an existing API with Nix modul Concretely, you'll write modules to interact with the Google Maps API, declaring options which represent map geometry, location pins, and more. +:::{warning} +To run the examples in this tutorial, you will need a [Google API key](https://developers.google.com/maps/documentation/maps-static/start#before-you-begin) in `$XDG_DATA_HOME/google-api/key`. +::: + Be prepared to see some Nix errors: during the tutorial, you will first write some *incorrect* configurations, creating opportunities to discuss the resulting error messages and how to resolve them, particularly when discussing type checking. :::{note} @@ -168,15 +172,58 @@ Update `default.nix` by changing the value of `generate.script` to the following config = { - generate.script = 42; + generate.script = '' -+ map size=640x640 scale=2 | icat ++ ./map size=640x640 scale=2 | feh - + ''; }; ``` -TODO: Create derivations to get these commands +## Interlude: Reproducible scripts + +The simple script will likely not work as intended on your system, as it may lack the required dependencies. +We can solve this by packaging the raw {download}`map ` script with `pkgs.writeShellApplication`. + +First, make available a `pkgs` argument in your module evaluation by adding a module that sets `config._module.args`: + +```diff +# eval.nix + pkgs.lib.evalModules { + modules = [ ++ ({ config, ... }: { config._module.args = { inherit pkgs; }; }) + ./test.nix + ]; + } +``` + +Then change `default.nix` to have the following contents: + +```nix +# default.nix +{ pkgs, lib, ... }: { + + options = { + generate.script = lib.mkOption { + type = lib.types.package; + }; + }; + + config = { + generate.script = pkgs.writeShellApplication { + name = "map"; + runtimeInputs = with pkgs; [ curl feh ]; + text = '' + ${./map} size=640x640 scale=2 | feh - + ''; + }; + }; +} +``` + +This will access the previously added `pkgs` argument so we can use dependencies, and copy the `map` file in the current directory into the Nix store so it's available to the wrapped script, which will also live in the Nix store. ## Declaring More Options +Rather than setting all script parameters manually, we will to do that through the module system, as this will not just add some safety through type checking, but also allows to build abstractions in order to manage growing complexity and changing requirements. + In this section, you will introduce another option: `generate.requestParams`. For its type, you should use `listOf `, which is a list type where each element must have the specified type. @@ -189,10 +236,11 @@ Module option types not only check for valid values, but also specify how multip - For `str`, multiple definitions are not allowed. This is not a problem here, since one can't define a list element multiple times. Make the following additions to your `default.nix` file: + ```diff # default.nix generate.script = lib.mkOption { - type = lib.types.lines; + type = lib.types.package; }; + + generate.requestParams = lib.mkOption { @@ -200,17 +248,20 @@ Make the following additions to your `default.nix` file: + }; }; - config = { - generate.script = '' - map size=640x640 scale=2 | icat - ''; + config = { + generate.script = pkgs.writeShellApplication { + name = "map"; + runtimeInputs = with pkgs; [ curl feh ]; + text = '' + ${./map} size=640x640 scale=2 | feh - + ''; + }; + + generate.requestParams = [ + "size=640x640" + "scale=2" + ]; }; - } ``` @@ -225,13 +276,14 @@ Options can depend on other options, making it possible to build more useful abs Here, we want the `generate.script` option to use the values of `generate.requestParams` as arguments to the `map` command. ### Accessing Option Values + To make an option definition available, the argument of the module accessing it must include the `config` attribute. Update `default.nix` to add the `config` attribute: ```diff # default.nix --{ lib, ... }: { -+{ lib, config, ... }: { +-{ pkgs, lib, ... }: { ++{ pkgs, lib, config, ... }: { ``` When a module that sets options is evaluated, the resulting values can be accessed by their corresponding attribute names. @@ -307,6 +359,7 @@ Now make the following additions to the `generate.requestParams` list in the `co You have now declared options controlling the map dimensions and zoom level, but have not provided a way to specify where the map should be centered. Add the `center` option now, possibly with your own location as default value: + ```diff # default.nix type = lib.types.nullOr lib.types.int; From 77ca136fd557a7f2390632838e02283ddf1c1b05 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 23:41:44 +0200 Subject: [PATCH 52/58] add file watching yes, this looks scary, and yes, it works. --- .../tutorials/module-system/module-system.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index dfe79d09d..3c266cf52 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -220,6 +220,34 @@ Then change `default.nix` to have the following contents: This will access the previously added `pkgs` argument so we can use dependencies, and copy the `map` file in the current directory into the Nix store so it's available to the wrapped script, which will also live in the Nix store. +Run the script with: + +```console +nix-build eval.nix -A config.generate.script +./result/bin/map +``` + +To iterate more quickly, open a new terminal and set up [`entr`](https://github.com/eradman/entr) to re-run the script whenever any source file in the current directory changes: + +```console +nix-shell -p entr findutils bash --run \ + "ls *.nix | \ + entr -rs ' \ + nix-build eval.nix -A config.generate.script --no-out-link \ + | xargs printf -- \"%s/bin/map\" \ + | xargs bash \ + ' \ + " +``` + +This command does the following: +- List all `.nix` files +- Make `entr` watch them for changes. Terminate the invoked command on each change with `-r`. +- On each change: + - Run the `nix-build` invocation as above, but without adding a `./result` symlink + - Take the resulting store path and append `/bin/map` to it + - Run the executable at the path constructed this way + ## Declaring More Options Rather than setting all script parameters manually, we will to do that through the module system, as this will not just add some safety through type checking, but also allows to build abstractions in order to manage growing complexity and changing requirements. From 6a422dbf61d0cae4e93b3068e6b548dd6d96f6c1 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 23:48:21 +0200 Subject: [PATCH 53/58] update diff and wording --- .../tutorials/module-system/module-system.md | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 3c266cf52..5c0d6dcef 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -250,7 +250,8 @@ This command does the following: ## Declaring More Options -Rather than setting all script parameters manually, we will to do that through the module system, as this will not just add some safety through type checking, but also allows to build abstractions in order to manage growing complexity and changing requirements. +Rather than setting all script parameters directly, we will to do that through the module system. +This will not just add some safety through type checking, but also allows to build abstractions in order to manage growing complexity and changing requirements. In this section, you will introduce another option: `generate.requestParams`. @@ -308,29 +309,37 @@ Here, we want the `generate.script` option to use the values of `generate.reques To make an option definition available, the argument of the module accessing it must include the `config` attribute. Update `default.nix` to add the `config` attribute: + ```diff # default.nix -{ pkgs, lib, ... }: { +{ pkgs, lib, config, ... }: { ``` -When a module that sets options is evaluated, the resulting values can be accessed by their corresponding attribute names. +When a module that sets options is evaluated, the resulting values can be accessed by their corresponding attribute names under `config`. Now make the following changes to `default.nix`: ```diff # default.nix + config = { - generate.script = '' -- map size=640x640 scale=2 | icat -+ map ${lib.concatStringsSep " " -+ config.generate.requestParams -+ } | icat - ''; + generate.script = pkgs.writeShellApplication { + name = "map"; + runtimeInputs = with pkgs; [ curl feh ]; + text = '' +- ${./map} size=640x640 scale=2 | feh - ++ map ${lib.concatStringsSep " " ++ config.generate.requestParams ++ } | feh - + ''; ``` Here, the value of the `config.generate.requestParams` attribute is populated by the module system based on the definitions in the same file. -Lazy evaluation in the Nix language allows taking a value from the `config` argument passed to the module which defines the value. + +:::{note} +Lazy evaluation in the Nix language allows the module system to make a value available in the `config` argument passed to the module which defines that value. +::: `lib.concatStringsSep " "` is then used to join each list element from the value of `config.generate.requestParams` into a single string, with the list elements of `requestParams` separated by a space character. From ab22195c4d6af046b98527554eabaf28cf3ebaad Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Thu, 26 Oct 2023 23:59:53 +0200 Subject: [PATCH 54/58] more notes on potential pitfalls --- source/tutorials/module-system/module-system.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 5c0d6dcef..cfde0a79d 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -296,9 +296,7 @@ Make the following additions to your `default.nix` file: ## Dependencies Between Options -A given module generally only declares a single option that is meant to be evaluated. - -This option generates the final result to be used elsewhere, which in this case is `generate.script`. +A given module generally declares one option that produces a result to be used elsewhere, which in this case is `generate.script`. Options can depend on other options, making it possible to build more useful abstractions. @@ -318,6 +316,17 @@ Update `default.nix` to add the `config` attribute: When a module that sets options is evaluated, the resulting values can be accessed by their corresponding attribute names under `config`. +:::{note} +Option values can't be accessed directly from the same module. + +The module system evaluates all modules it receives, and any of them can define a particular option's value. +What happens when an option is set by multiple modules is determined by that option's type. + +The `config` argument is *not the same* as the `config` attribute where option values are set: +- The `config` argument holds the module system's evaluation result that takes into account all modules passed to `evalModules` and their `imports`. +- The `config` attribute of a module exposes that particular module's option values to the module system for evaluation. +::: + Now make the following changes to `default.nix`: ```diff From e02a9d1a66a6654dd7d71b6c751fd6c701050c1f Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Fri, 27 Oct 2023 00:02:10 +0200 Subject: [PATCH 55/58] be explicit which `map` we mean --- source/tutorials/module-system/module-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index cfde0a79d..51032e6ae 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -352,7 +352,7 @@ Lazy evaluation in the Nix language allows the module system to make a value ava `lib.concatStringsSep " "` is then used to join each list element from the value of `config.generate.requestParams` into a single string, with the list elements of `requestParams` separated by a space character. -The result of this represents the list of command line arguments to pass to `map`. +The result of this represents the list of command line arguments to pass to the `map` script. ## Conditional Definitions and Default Values From 58efbcc233a3521377102824f101e53cfd0d7bf1 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Fri, 27 Oct 2023 00:16:12 +0200 Subject: [PATCH 56/58] split nullable from default values --- .../tutorials/module-system/module-system.md | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 51032e6ae..8be99d61b 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -304,7 +304,7 @@ Here, we want the `generate.script` option to use the values of `generate.reques ### Accessing Option Values -To make an option definition available, the argument of the module accessing it must include the `config` attribute. +To make option values available, the argument of the module accessing them must include the `config` attribute. Update `default.nix` to add the `config` attribute: @@ -354,19 +354,12 @@ Lazy evaluation in the Nix language allows the module system to make a value ava The result of this represents the list of command line arguments to pass to the `map` script. -## Conditional Definitions and Default Values +## Conditional Definitions In this section, you will define a new option, `map.zoom`, to control the zoom level of the map. -You will use a new type, `nullOr `, which can take either values of its argument type or `null`. - -In this case, a `null` value will use the API's default behavior of inferring the zoom level. - -Here, you will also use `default` from `mkOption`](https://github.com/NixOS/nixpkgs/blob/master/lib/options.nix) to declare your first *default* value, which will be used if the option declaring it is not enabled. - -You will use this option to define another element in `generate.requestParams`, which will only be added if its value is non-null. - -To do this, you can use the `mkIf ` function, which only adds the definition if the condition evaluates to `true`. +Since the Google Maps API will infer a zoom level if no corresponding argument is passed, we want to represent that at the module level. +To do that, you will use a new type, `nullOr `, which can take either values of its argument type or `null`. Add the `map` attribute set with the `zoom` option into the top-level `options` declaration, like so: @@ -379,18 +372,16 @@ Add the `map` attribute set with the `zoom` option into the top-level `options` + map = { + zoom = lib.mkOption { + type = lib.types.nullOr lib.types.int; -+ default = 2; + }; + }; }; - ``` +``` -Now make the following additions to the `generate.requestParams` list in the `config` block: +To make use of this, use the `mkIf ` function, which only adds the definition if the condition evaluates to `true`. +Make the following additions to the `generate.requestParams` list in the `config` block: ```diff # default.nix - config = { - ... generate.requestParams = [ "size=640x640" "scale=2" @@ -400,6 +391,28 @@ Now make the following additions to the `generate.requestParams` list in the `co }; ``` +This will will only add a `zoom` parameter to the script call if the value is non-null. + +## Default values + +Let's say that in our application we want to have a different default behavior that sets the the zoom level to `2`, such that automatic zoom has to be enabled explicitly. + +This can be done with the `default` argument to [`mkOption`](https://github.com/NixOS/nixpkgs/blob/master/lib/options.nix). +Its value will be used if the value of the option declaring it is otherwise specified. + +Add the corresponding line: + +```diff +# default.nix + map = { + zoom = lib.mkOption { + type = lib.types.nullOr lib.types.int; ++ default = 2; + }; + }; + }; +``` + ## Centering the Map You have now declared options controlling the map dimensions and zoom level, but have not provided a way to specify where the map should be centered. From a1004e9b429bce3c68f82a0c04378a292e545506 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Fri, 27 Oct 2023 00:58:41 +0200 Subject: [PATCH 57/58] also wrap the geocode script --- .../tutorials/module-system/module-system.md | 111 ++++++++++++------ 1 file changed, 72 insertions(+), 39 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index 8be99d61b..dab50f771 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -433,9 +433,41 @@ Add the `center` option now, possibly with your own location as default value: }; ``` -To implement this behavior, you will use the `geocode` utility, which turns location names into coordinates. +To implement this behavior, you will use the {download}`geocode ` utility, which turns location names into coordinates. +There are multiple ways of making a new package accessible, but as an exercise, you will add it as an option in the module system. -Add another `mkIf` call to the list of `requestParams` now: +First, add a new option to accommodate the package: + + +```diff +# default.nix + options = { + generate.script = lib.mkOption { + type = lib.types.package; + }; ++ ++ helpers.geocode = lib.mkOption { ++ type = lib.types.package; ++ }; +``` + +Then define the value for that option where you make the raw script reproducible by wrapping a call to it in `writeShellApplication`: + +```diff +# default.nix + config = { ++ helpers.geocode = pkgs.writeShellApplication { ++ name = "geocode"; ++ runtimeInputs = with pkgs; [ curl jq ]; ++ text = "exec ${./geocode}"; ++ }; ++ + generate.script = pkgs.writeShellApplication { + name = "map"; + runtimeInputs = with pkgs; [ curl feh ]; +``` + +Add another `mkIf` call to the list of `requestParams` now where you access the wrapped package through `config.helpers.geocode`, and run the executable `/bin/geocode` inside: ```diff # default.nix @@ -443,16 +475,16 @@ Add another `mkIf` call to the list of `requestParams` now: (lib.mkIf (config.map.zoom != null) "zoom=${toString config.map.zoom}") + (lib.mkIf (config.map.center != null) -+ "center=\"$(geocode ${ ++ "center=\"$(${config.helpers.geocode}/bin/geocode ${ + lib.escapeShellArg config.map.center + })\"") ]; }; ``` -This time, you've used `escapeShellArg` to pass the `config.map.center` value as a command-line argument to `geocode`, interpolating the result back into the `requestParams` string which sets the `center` value. +This time, you've used `escapeShellArg` to pass the `config.map.center` value as a command-line argument to `geocode`, string interpolating the result back into the `requestParams` string which sets the `center` value. -Wrapping shell command execution in Nix modules is a helpful technique for controlling system changes, using the more ergonomic attributes and values interface rather than dealing with the peculiarities of escaping manually. +Wrapping shell command execution in Nix modules is a helpful technique for controlling system changes, as it uses the more ergonomic attributes and values interface rather than dealing with the peculiarities of escaping manually. ## Splitting Modules @@ -460,7 +492,8 @@ The [module schema](https://nixos.org/manual/nixos/stable/#sec-writing-modules) In particular, this allows you to separate option declarations from where they are used in your configuration. -Create a new module, `marker.nix`, where you can declare options for defining location pins and other markers on the map. +Create a new module, `marker.nix`, where you can declare options for defining location pins and other markers on the map: + ```diff # marker.nix +{ lib, config, ... }: { @@ -469,9 +502,10 @@ Create a new module, `marker.nix`, where you can declare options for defining lo ``` Reference this new file in `default.nix` using the `imports` attribute: + ```diff # default.nix - { lib, config, ... }: { + { pkgs, lib, config ... }: { + imports = [ + ./marker.nix @@ -481,22 +515,23 @@ Reference this new file in `default.nix` using the `imports` attribute: ## The `submodule` Type -One of the most useful types included in the module system's type system is `submodule`. +We want to set multiple markers on the map. +A marker is a complex type with multiple fields. +This is wher one of the most useful types included in the module system's type system comes into play: `submodule`. This type allows you to define nested modules with their own options. - Here, you will define a new `map.markers` option whose type is a list of submodules, each with a nested `location` type, allowing you to define a list of markers on the map. Each assignment of markers will be type-checked during evaluation of the top-level `config`. -Make the following changes to `marker.nix` now: +Make the following changes to `marker.nix`: + ```diff # marker.nix --{ lib, config, ... }: { -+{ lib, config, ... }: +-{ pkgs, lib, config, ... }: { ++{ pkgs, lib, config, ... }: +let -+ + markerType = lib.types.submodule { + options = { + location = lib.mkOption { @@ -512,19 +547,18 @@ Make the following changes to `marker.nix` now: + type = lib.types.listOf markerType; + }; + }; - } ``` ## Setting Option Values Within Other Modules -Because of the way the module system composes option definitions, you can also freely assign values to options defined in other modules. +Because of the way the module system composes option definitions, you can freely assign values to options defined in other modules. -In this case, you will use the `map.markers` option to produce and add new elements to the `requestParams` list, making your declared markers appear on the returned map. +In this case, you will use the `map.markers` option to produce and add new elements to the `requestParams` list, making your declared markers appear on the returned map – but from the module declared in `marker.nix`. To implement this behavior, add the following `config` block to `marker.nix`: + ```diff # marker.nix - ... + config = { + + map.markers = [ @@ -544,20 +578,18 @@ To implement this behavior, add the following `config` block to `marker.nix`: + lib.concatStringsSep "\\|" attributes + }"; + in map paramForMarker config.map.markers; -+ -+ }; - } ``` Here, you again used `escapeShellArg` and string interpolation to generate a Nix string, this time producing a pipe-separated list of geocoded location attributes. -The `generate.requestParams` value was also set to the resulting list of strings, which gets appended to the `generate.requestParams` list defined in `default.nix`, thanks to the default merging behavior of the `list`-type module. +The `generate.requestParams` value was also set to the resulting list of strings, which gets appended to the `generate.requestParams` list defined in `default.nix`, thanks to the default merging behavior of the `list` type. + +## Dealing with multiple markers -## Multiple Markers +When defining multiple markers, determining an appropriate center or zoom level for the map may be challenging; it's easier to let the API do this for you. -In case you define multiple markers, determining an appropriate center or zoom level for the map may be challenging; it's easier to let the API do this for you. +To achieve this, make the following additions to `marker.nix`, above the `generate.requestParams` declaration: -To do this, make the following additions to `marker.nix`, above the `generate.requestParams` declaration: ```diff # marker.nix + map.center = lib.mkIf @@ -577,20 +609,20 @@ In this case, the default behavior of the Google Maps API when not passed a cent ## Nested Submodules -It's time to introduce a `users` option with type `lib.types.attrsOf `, which will allow you to define `users` as an attribute set, whose values have type ``. +Next, we want to allow multiple named users to define a list of markers each. + +For that you'll add a `users` option with type `lib.types.attrsOf `, which will allow you to define `users` as an attribute set, whose values have type ``. Here, that subtype will be another submodule which allows declaring a departure marker, suitable for querying the API for the recommended route for a trip. This will again make use of the `markerType` submodule, giving a nested structure of submodules. -To propagate marker definitions from `users` to the `map.markers` option, make the following changes now: +To propagate marker definitions from `users` to the `map.markers` option, make the following changes. -- In the `let` block: +In the `let` block: ```diff # marker.nix - let - ... + userType = lib.types.submodule { + options = { + departure = lib.mkOption { @@ -603,18 +635,21 @@ To propagate marker definitions from `users` to the `map.markers` option, make in { ``` -- In the `options` block, above `map.markers`: +This defines a submodule type for a user, with a `departure` option of type `markerType`. + +In the `options` block, above `map.markers`: + ```diff # marker.nix - options = { -+ + users = lib.mkOption { + type = lib.types.attrsOf userType; + }; -+ ``` -- In the `config` block, above `map.center`: +That allows adding a `users` attribute set to `config` in any submodule that imports `marker.nix`, where each attribute will be of type `userType` as declared in the previous step. + +In the `config` block, above `map.center`: + ```diff # marker.nix config = { @@ -632,13 +667,11 @@ To propagate marker definitions from `users` to the `map.markers` option, make (lib.length config.map.markers >= 1) ``` -The `config.users` attribute set is passed to `attrValues`, which returns a list of values of each of the attributes in the set (here, the set of `config.users` you've defined), sorted alphabetically. - -The `departure` values of each of the `users` are then joined into a list, and this list is filtered for non-`null` locations. +This takes all the `departure` markers from all users in the `config` argument, and adds them to `map.markers` if their `location` attribute is not `null`. -The resulting list is stored in `map.markers`. +The `config.users` attribute set is passed to `attrValues`, which returns a list of values of each of the attributes in the set (here, the set of `config.users` you've defined), sorted alphabetically (this how attribute names are stored in the Nix language). -The resulting `map.markers` option then propagates to the `generate.requestParams` option, which in turn is used to generate arguments to the script which ultimately calls the Google Maps API. +Back in `default.nix`, the resulting `map.markers` option value is still accessed by `generate.requestParams`, which in turn is used to generate arguments to the script that ultimately calls the Google Maps API. Defining the options in this way allows you to set multiple `users..departure.location` values and generate a map with the appropriate zoom and center, with pins corresponding to the set of `departure.location` values for *all* `users`. From 648d7a1c552064efec8a64e949cf85762952bba0 Mon Sep 17 00:00:00 2001 From: fricklerhandwerk Date: Fri, 27 Oct 2023 01:38:58 +0200 Subject: [PATCH 58/58] work through the tutorial to the end --- .../tutorials/module-system/module-system.md | 173 ++++++++++-------- 1 file changed, 98 insertions(+), 75 deletions(-) diff --git a/source/tutorials/module-system/module-system.md b/source/tutorials/module-system/module-system.md index dab50f771..ac9beb85c 100644 --- a/source/tutorials/module-system/module-system.md +++ b/source/tutorials/module-system/module-system.md @@ -338,7 +338,7 @@ Now make the following changes to `default.nix`: runtimeInputs = with pkgs; [ curl feh ]; text = '' - ${./map} size=640x640 scale=2 | feh - -+ map ${lib.concatStringsSep " " ++ builtins.map ${lib.concatStringsSep " " + config.generate.requestParams + } | feh - ''; @@ -496,9 +496,9 @@ Create a new module, `marker.nix`, where you can declare options for defining lo ```diff # marker.nix -+{ lib, config, ... }: { -+ -+} +{ lib, config, ... }: { + +} ``` Reference this new file in `default.nix` using the `imports` attribute: @@ -577,9 +577,14 @@ To implement this behavior, add the following `config` block to `marker.nix`: + in "markers=${ + lib.concatStringsSep "\\|" attributes + }"; -+ in map paramForMarker config.map.markers; ++ in builtins.map paramForMarker config.map.markers; ``` +:::{warning} +To avoid confusion with the `map` option setting and the evaluated `config.map` configuration value, here we use the `map` function explicitly as `builtins.map`. +::: + + Here, you again used `escapeShellArg` and string interpolation to generate a Nix string, this time producing a pipe-separated list of geocoded location attributes. The `generate.requestParams` value was also set to the resulting list of strings, which gets appended to the `generate.requestParams` list defined in `default.nix`, thanks to the default merging behavior of the `list` type. @@ -681,13 +686,14 @@ In the 2021 Summer of Nix, this formed the basis of an interactive multi-person Now that the map can be rendered with multiple markers, it's time to add some style customizations. -To tell the markers apart, you should add another option to the `markerType` submodule, to allow labeling each marker pin. +To tell the markers apart, add another option to the `markerType` submodule, to allow labeling each marker pin. The API documentation states that [these labels must be either an uppercase letter or a number](https://developers.google.com/maps/documentation/maps-static/start#MarkerStyles). -You can implement this with the `strMatching ""` type, where `` is a regular expression that will accept any matching (uppercase letter or number) values. +You can implement this with the `strMatching ""` type, where `` is a regular expression that will accept any matching values, in this case an uppercase letter or number. + +In the `let` block: -- In the `let` block: ```diff # marker.nix type = lib.types.nullOr lib.types.str; @@ -703,7 +709,10 @@ You can implement this with the `strMatching ""` type, where `` is }; ``` -- In the `paramForMarker` function: +Again, `types.nullOr` allows for `null` values, and the default has been set to `null`. + +In the `paramForMarker` function: + ```diff # marker.nix paramForMarker = marker: @@ -723,9 +732,8 @@ Here, the label for each `marker` is only propagated to the CLI parameters if `m ## Defining a Default Label -In case you don't want to manually define a label for each marker, you can set a default value. - -The easiest value for a default label is the username, which will always be set. +Right now, if a label is not explicitly set, none will show up. +But since every `users` attribute has a name, we could use that as an automatic value instead. This `firstUpperAlnum` function allows you to retrieve the first character of the username, with the correct type for passing to `departure.style.label`: @@ -746,9 +754,7 @@ This `firstUpperAlnum` function allows you to retrieve the first character of th By transforming the argument to `lib.types.submodule` into a function, you can access arguments within it. -One special argument available to submodules is the `name` argument, which when used in `attrsOf`, gives you the name of the attribute the submodule is defined under. - -You can use this function argument to retrieve the `name` attribute for use elsewhere: +One special argument automatically available to submodules is `name`, which when used in `attrsOf`, gives you the name of the attribute the submodule is defined under: ```diff # marker.nix @@ -781,23 +787,24 @@ Instead you can use the `config` section of the `user` submodule to set a defaul ``` :::{note} -Module options have a *precedence*, represented as an integer, which determines the priority of setting the option to a particular value. -When merging values, the lowest precedence wins. +Module options have a *priority*, represented as an integer, which determines the precedence for setting the option to a particular value. +When merging values, the priority with lowest numeric value wins. -The `lib.mkDefault` modifier sets the precedence of its argument value to 1000, the lowest priority. +The `lib.mkDefault` modifier sets the priority of its argument value to 1000, the lowest precedence. This ensures that other values set for the same option will prevail. ::: ## Marker Styling: Color -For better visual contrast, it would also be helpful to have a way to change the *color* of a marker. +For better visual contrast, it would be helpful to have a way to change the *color* of a marker. Here you will use two new type-functions for this: - `either `, which takes two types as arguments, and allows either of them - `enum [ ]`, which takes a list of allowed values, and allows any of them -In the `let` block, add the following `colorType` option, which can hold strings containing either some given color names or an RGB value: +In the `let` block, add the following `colorType` option, which can hold strings containing either some given color names or an RGB value add the new compound type: + ```diff # marker.nix ... @@ -816,7 +823,10 @@ In the `let` block, add the following `colorType` option, which can hold strings location = lib.mkOption { ``` -At the bottom of the `let` block, add the `style.color` option: +This allows either strings that matche a 24-bit hexadecimal number or are equal to one of the specified color names. + +At the bottom of the `let` block, add the `style.color` option and specify a default value: + ```diff # marker.nix (lib.types.strMatching "[A-Z0-9]"); @@ -831,7 +841,8 @@ At the bottom of the `let` block, add the `style.color` option: }; ``` -Now add a line to the `paramForMarker` list which makes use of the new option: +Now add an entry to the `paramForMarker` list which makes use of the new option: + ```diff # marker.nix (marker.style.label != null) @@ -847,7 +858,7 @@ Now add a line to the `paramForMarker` list which makes use of the new option: In case you set many different markers, it would be helpful to have the ability to change their size individually. -Add a new `style.size` option to `marker.nix`, allowing you to do so: +Add a new `style.size` option to `marker.nix`, allowing you to choose from the set of pre-defined sizes: ```diff # marker.nix @@ -865,6 +876,7 @@ Add a new `style.size` option to `marker.nix`, allowing you to do so: ``` Now add a mapping for the size parameter in `paramForMarker`, which selects an appropriate string to pass to the API: + ```diff # marker.nix generate.requestParams = let @@ -880,6 +892,7 @@ Now add a mapping for the size parameter in `paramForMarker`, which selects an a ``` Finally, add another `lib.optional` call to the `attributes` string, making use of the selected size: + ``` # marker.nix attributes = @@ -898,47 +911,50 @@ Finally, add another `lib.optional` call to the `attributes` string, making use So far, you've created an option for declaring a *destination* marker, as well as several options for configuring the marker's visual representation. +Now we want to compute and display a route from the user's location to some destination. + The new option defined in the next section will allow you to set an *arrival* marker, which together with a destination allows you to draw *paths* on the map using the new module defined below. To start, create a new `path.nix` file with the following contents: ```diff # path.nix -+{ lib, config, ... }: -+let -+ pathType = lib.types.submodule { -+ -+ options = { -+ locations = lib.mkOption { -+ type = lib.types.listOf lib.types.str; -+ }; -+ }; -+ -+ }; -+in { -+ options = { -+ map.paths = lib.mkOption { -+ type = lib.types.listOf pathType; -+ }; -+ }; -+ -+ config = { -+ generate.requestParams = let -+ attrForLocation = loc: -+ "$(geocode ${lib.escapeShellArg loc})"; -+ paramForPath = path: -+ let -+ attributes = -+ map attrForLocation path.locations; -+ in "path=${ -+ lib.concatStringsSep "\\|" attributes -+ }"; -+ in map paramForPath config.map.paths; -+ }; -+} +{ lib, config, ... }: +let + pathType = lib.types.submodule { + options = { + locations = lib.mkOption { + type = lib.types.listOf lib.types.str; + }; + }; + }; +in { + options = { + map.paths = lib.mkOption { + type = lib.types.listOf pathType; + }; + }; + + config = { + generate.requestParams = let + attrForLocation = loc: + "$(geocode ${lib.escapeShellArg loc})"; + paramForPath = path: + let + attributes = + builtins.map attrForLocation path.locations; + in "path=${ + lib.concatStringsSep "\\|" attributes + }"; + in builtins.map paramForPath config.map.paths; + }; +} ``` -The `path.nix` module defines an option for declaring paths, augmenting the API call by re-using the `generate.requestParams` option. +The `path.nix` module defines an option for declaring a list of paths on our `map`, where each path is a list of strings for geographic locations. + + +In the `config` attribute we augment the API call by setting the `generate.requestParams` option value with the coordinates transformed appropriately, which will be concatenated with request paremeters set elsewhere. Now import this new `path.nix` module from your `marker.nix` module: @@ -957,7 +973,7 @@ Now import this new `path.nix` module from your `marker.nix` module: ## The Arrival Marker -Now copy the `departure` option declaration to a new `arrival` option in `marker.nix`, to complete the initial path implementation: +Copy the `departure` option declaration to a new `arrival` option in `marker.nix`, to complete the initial path implementation: ```diff # marker.nix @@ -973,6 +989,7 @@ Now copy the `departure` option declaration to a new `arrival` option in `marker ``` Next, add an `arrival.style.label` attribute to the `config` block, mirroring the `departure.style.label`: + ```diff # marker.nix config = { @@ -984,7 +1001,8 @@ Next, add an `arrival.style.label` attribute to the `config` block, mirroring th }); ``` -Finally, update the return list in the function passed to `concatMap` in `map.markers`: +Finally, update the return list in the function passed to `concatMap` in `map.markers` to also include the `arrival` marker for each user: + ```diff # marker.nix map.markers = lib.filter @@ -997,17 +1015,17 @@ Finally, update the return list in the function passed to `concatMap` in `map.ma map.center = lib.mkIf ``` -You should now be able to define paths on the map, connecting pairs of departure and arrival points. +Now you have the basesis to define paths on the map, connecting pairs of departure and arrival points. ## Connecting Markers by Paths -In the path module, you can now define a path connecting every user's departure and arrival locations. +In the path module, define a path connecting every user's departure and arrival locations: ```diff # path.nix config = { + -+ map.paths = map (user: { ++ map.paths = builtins.map (user: { + locations = [ + user.departure.location + user.arrival.location @@ -1022,10 +1040,6 @@ In the path module, you can now define a path connecting every user's departure "$(geocode ${lib.escapeShellArg loc})"; ``` -:::{warning} -Don't confuse the `map` function with the `map` option or the `map` script! -::: - The new `map.paths` attribute contains a list of all valid paths defined for all users. A path is valid only if the `departure` and `arrival` attributes are set for that user. @@ -1036,7 +1050,7 @@ Your users have spoken, and they demand the ability to customize the styles of t As before, you'll now declare a new submodule for the path style. -While you could also directly define the `style.weight` option, in this case, you should use the submodule in a future change to reuse the path style definitions. +While you could also directly declare the `style.weight` option, in this case you should use the submodule to be able reuse the path style type later. Add the `pathStyleType` submodule option to the `let` block in `path.nix`: ```diff @@ -1063,6 +1077,7 @@ The `ints.between ` type allows integers in the given (inclusive) The path weight will default to 5, but can be set to any integer value in the 1 to 20 range, with higher weights producing thicker paths on the map. Now add a `style` option to the `options` set further down the file: + ```diff # path.nix options = { @@ -1080,16 +1095,17 @@ Now add a `style` option to the `options` set further down the file: ``` Finally, update the `attributes` list in `paramForPath`: + ```diff # path.nix paramForPath = path: let attributes = -- map attrForLocation path.locations; +- builtins.map attrForLocation path.locations; + [ + "weight:${toString path.style.weight}" + ] -+ ++ map attrForLocation path.locations; ++ ++ builtins.map attrForLocation path.locations; in "path=${ lib.concatStringsSep "\\|" attributes }"; @@ -1097,11 +1113,12 @@ Finally, update the `attributes` list in `paramForPath`: ## The `pathStyle` Submodule -Users still can't actually customize the path style yet, so you should introduce a new `pathStyle` option for each user. +Users still can't actually customize the path style yet. +Introduce a new `pathStyle` option for each user. The module system allows you to declare values for an option multiple times, and if the types permit doing so, takes care of merging each declaration's values together. -This makes it possible to have a definition for the `user` option in the `marker.nix` module, as well as a `user` definition in `path.nix`, which you should add now: +This makes it possible to have a definition for the `user` option in the `marker.nix` module, as well as a `user` definition in `path.nix`: ```diff # path.nix @@ -1122,7 +1139,8 @@ This makes it possible to have a definition for the `user` option in the `marker }; ``` -Then add a line using the `user.pathStyle` option in `map.paths`: +Then add a line using the `user.pathStyle` option in `map.paths` where each user's paths are processed: + ```diff # path.nix user.departure.location @@ -1138,9 +1156,10 @@ Then add a line using the `user.pathStyle` option in `map.paths`: As with markers, paths should have customizable colors. -You can accomplish this using types you've already seen by now. +You can accomplish this using types you've already encountered by now. + +Add a new `colorType` block to `path.nix`, specifying the allowed color names and RGB/RGBA hexadecimal values: -Add a new `colorType` block to `path.nix`, specifying the allowed color names and RGB/RGBA values: ```diff # path.nix { lib, config, ... }: @@ -1158,6 +1177,7 @@ Add a new `colorType` block to `path.nix`, specifying the allowed color names an ``` Under the `weight` option, add a new `color` option to use the new `colorType` value: + ```diff # path.nix type = lib.types.ints.between 1 20; @@ -1173,6 +1193,7 @@ Under the `weight` option, add a new `color` option to use the new `colorType` v ``` Finally, add a line using the `color` option to the `attributes` list: + ```diff # path.nix attributes = @@ -1186,11 +1207,12 @@ Finally, add a line using the `color` option to the `attributes` list: ## Further Styling -Now that you've got this far, to further improve the aesthetics of the rendered map, you should add another style option allowing paths to be drawn as *geodesics*, the shortest "as the crow flies" distance between two points on Earth. +Now that you've got this far, to further improve the aesthetics of the rendered map, add another style option allowing paths to be drawn as *geodesics*, the shortest "as the crow flies" distance between two points on Earth. Since this feature can be turned on or off, you can do this using the `bool` type, which can be `true` or `false`. Make the following changes to `path.nix` now: + ```diff # path.nix type = colorType; @@ -1205,7 +1227,8 @@ Make the following changes to `path.nix` now: }; ``` -Make sure to also add a new line using this to the `attributes` list, so the option value is included in the API call: +Make sure to also add a line to use that value in `attributes` list, so the option value is included in the API call: + ```diff # path.nix [