diff --git a/.gitignore b/.gitignore index 245d8f1d..918d674d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ +########## +# This file is autogenerated by Organist, don't edit +########## +.editorconfig /examples/*/result /result 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/git.ncl b/lib/git.ncl new file mode 100644 index 00000000..d9aeb7dc --- /dev/null +++ b/lib/git.ncl @@ -0,0 +1,52 @@ +# let nix = import "./nix-interop/nix.ncl" in +let filegen = import "./files.ncl" in +let Gitignorable = { + gitignore + : Bool + | doc "Whether to gitignore the file" + | default + = false, + .. +} +in +let header : String = m%" + ########## + # This file is autogenerated by Organist, don't edit + ########## + + "% + in +let ignored_files | { _ : Gitignorable } -> { _ : Bool } = fun files => + files + |> std.record.to_array + |> std.array.filter (fun { value = { gitignore, .. }, .. } => gitignore) + |> std.array.map (fun { value = { target, .. }, .. } => { field = target, value = true }) + |> std.record.from_array + in +let gitignore_content | { _ : Bool } -> String = fun ignores => + ignores + |> std.record.map + ( + fun pattern is_ignored => + "%{if is_ignored then "" else "!"}%{pattern}" + ) + |> std.record.values + |> std.string.join "\n" + |> (fun c => header ++ c ++ "\n") + in +{ + Schema = + filegen.Schema + & { + + files | filegen.Files, + files | { _ : Gitignorable }, + + git.ignore + | { _ : Bool } + = ignored_files files, + + files.".gitignore".materialisation_method = 'Copy, + files.".gitignore".content = gitignore_content git.ignore, + } +} 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..22b0f801 100644 --- a/lib/schema.ncl +++ b/lib/schema.ncl @@ -1,4 +1,6 @@ let nix = import "./nix-interop/nix.ncl" in +let filegen = import "files.ncl" in +let git = import "git.ncl" in { OrganistShells = { dev | nix.derivation.NickelDerivation = build, @@ -20,11 +22,14 @@ 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 + & git.Schema, } diff --git a/project.ncl b/project.ncl index a03c1c94..073b215e 100644 --- a/project.ncl +++ b/project.ncl @@ -73,5 +73,22 @@ let import_nix = organist.nix.import_nix in }, flake.checks = import "tests/main.ncl", + + # Simple config file. It will be gitignore and regenerated on the fly by the + # `#regenerate-files` command. + files.".editorconfig".content = m%" + # Auto-generated by Organist, don't edit + + root = true + [*.{nix,nickel}] + indent_style = spaces + indent_size = 2 + + "%, + files.".editorconfig".gitignore = true, + + # Extra gitignored files + git.ignore."/result" = true, + git.ignore."/examples/*/result" = true, } | organist.OrganistExpression