Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for automatically gitignoring generated files #157

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
##########
# This file is autogenerated by Organist, don't edit
##########
.editorconfig
/examples/*/result
/result
84 changes: 84 additions & 0 deletions lib/files.ncl
Original file line number Diff line number Diff line change
@@ -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,
}
52 changes: 52 additions & 0 deletions lib/git.ncl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# let nix = import "./nix-interop/nix.ncl" in
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# let nix = import "./nix-interop/nix.ncl" in

let filegen = import "./files.ncl" in
let Gitignorable = {
gitignore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be nested like git.ignore in case we want to add, for example, .gitattributes support.

: Bool
| doc "Whether to gitignore the file"
| default
= false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that each generated file will be added to .gitignore in one way or another. I think we should have a default option here that doesn't add it to .gitignore at all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No they aren't, the ones with gitignore = true are filtered out here.

But that indeed makes some slightly confusing semantics as it means that files.foo.gitignore has a different semantics than git.ignore."/foo".

..
}
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 =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these functions should be in the final record so that users can add more .gitignore files to their repos.

ignores
|> std.record.map
(
fun pattern is_ignored =>
"%{if is_ignored then "" else "!"}%{pattern}"
)
|> std.record.values
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should somehow provide a way to order these lines, because order is important in .gitignore

|> 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,
}
}
1 change: 1 addition & 0 deletions lib/nix-interop/nix.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
12 changes: 12 additions & 0 deletions lib/nix-interop/utils.ncl
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 12 additions & 7 deletions lib/schema.ncl
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
}
17 changes: 17 additions & 0 deletions project.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -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