From a8b283bd318f7140f0846c5f324b33cd55f20147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Fri, 29 Sep 2023 17:59:45 +0200 Subject: [PATCH] Add a crude file generation mechanism Running `nix run .\#regenerate-files` now generates files locally according to the `files` option in `project.ncl`. This is still rather crude in a number of ways, but it is already working (and used to generate this project's own `.gitignore`). Fixes https://github.com/nickel-lang/organist/issues/144 --- .gitignore | 2 +- lib/files.ncl | 84 +++++++++++++++++++++++++++++++++++++++ lib/nix-interop/nix.ncl | 1 + lib/nix-interop/utils.ncl | 12 ++++++ lib/schema.ncl | 17 ++++---- project.ncl | 8 ++++ 6 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 lib/files.ncl create mode 100644 lib/nix-interop/utils.ncl diff --git a/.gitignore b/.gitignore index 245d8f1d..f1724153 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /examples/*/result -/result +/result \ No newline at end of file diff --git a/lib/files.ncl b/lib/files.ncl new file mode 100644 index 00000000..1605c2db --- /dev/null +++ b/lib/files.ncl @@ -0,0 +1,84 @@ +let nix = import "./nix-interop/nix.ncl" in +{ + File = { + target + | doc m%" + The file to write to. + If null, defaults to the attribute name of the file. + "% + | String + | optional, + content + | doc m%" + The content of the file. + "% + | nix.derivation.NixString, + materialisation_method + : [| 'Symlink, 'Copy |] + | doc m%" + How the file should be materialized on-disk. + + Symlinking makes it easier to track where the files are coming from, + but their target only exists after a first call to Organist, which + might be undesirable. + "% + | default + = 'Copy, + .. + }, + Files = { _ : File }, + NormaliseTargets = fun label files => + files + |> std.record.map (fun name file_descr => file_descr & { target | default = name }), + + Schema = { + files + | Files + | NormaliseTargets + | doc m%" + Set of files that should be generated in the project's directory. + "% + = {}, + flake.apps.regenerate-files.program = nix-s%"%{regenerate_files files}/bin/regenerate-files"%, + }, + + regenerate_files | Files -> nix.derivation.Derivation = fun files_to_generate => + let regnerate_one | String -> File -> nix.derivation.NixString = fun key file_descr => + let file_content = file_descr.content in + let target = file_descr.target in + let copy_command = + match { + 'Symlink => "ln -s", + 'Copy => "cp", + } + file_descr.materialisation_method + in + let file_in_store = + nix.builtins.to_file + (nix.utils.escape_drv_name key) + file_content + in + nix-s%" + rm -f %{target} + echo "Regenerating %{target}" + %{copy_command} %{file_in_store} %{target} + "% + in + { + name = "regenerate-files", + content.text = + files_to_generate + |> std.record.to_array + |> std.array.map (fun { field, value } => regnerate_one field value) + |> std.array.fold_left + ( + fun acc elt => + nix-s%" + %{acc} + %{elt} + "% + ) + "", + } + | nix.builders.ShellApplication, +} diff --git a/lib/nix-interop/nix.ncl b/lib/nix-interop/nix.ncl index 18bfa233..62f4cbc8 100644 --- a/lib/nix-interop/nix.ncl +++ b/lib/nix-interop/nix.ncl @@ -3,6 +3,7 @@ builders = import "builders.ncl", shells = import "shells.ncl", builtins = import "builtins.ncl", + utils = import "utils.ncl", import_nix = builtins.import_nix, } diff --git a/lib/nix-interop/utils.ncl b/lib/nix-interop/utils.ncl new file mode 100644 index 00000000..d6d03aeb --- /dev/null +++ b/lib/nix-interop/utils.ncl @@ -0,0 +1,12 @@ +{ + escape_drv_name + : String -> String + | doc m%" + Escape the given string to make it an allowed derivation name + "% + = fun str => + if std.string.is_match "^\\." str then + "-" ++ str + else + str +} diff --git a/lib/schema.ncl b/lib/schema.ncl index d227178d..63574813 100644 --- a/lib/schema.ncl +++ b/lib/schema.ncl @@ -1,4 +1,5 @@ let nix = import "./nix-interop/nix.ncl" in +let filegen = import "files.ncl" in { OrganistShells = { dev | nix.derivation.NickelDerivation = build, @@ -20,11 +21,13 @@ let nix = import "./nix-interop/nix.ncl" in # # The contract must be: what the Nix side of the code can "parse" without # erroring out. - OrganistExpression = { - shells - | OrganistShells - | optional, - flake | FlakeOutputs | optional, - .. - }, + OrganistExpression = + { + shells + | OrganistShells + | optional, + flake | FlakeOutputs = {}, + } + & filegen.Schema } + diff --git a/project.ncl b/project.ncl index a03c1c94..cc08504e 100644 --- a/project.ncl +++ b/project.ncl @@ -73,5 +73,13 @@ let import_nix = organist.nix.import_nix in }, flake.checks = import "tests/main.ncl", + + files.".gitignore".materialisation_method = 'Copy, + files.".gitignore".content = m%" + # This file is generated by Organist, please don't edit directly + /examples/*/result + /result + + "%, } | organist.OrganistExpression