-
Notifications
You must be signed in to change notification settings - Fork 1
/
default.nix
152 lines (133 loc) · 6.37 KB
/
default.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# _Contracts_ in Nix design and implementations details:
#
# * we try, for a better developer experience, to disambiguate with case:
# - `Types` that takes a value and return a Boolean
# - `builders` that takes one or several values and return a Type
# * when a function takes a type as argument, always implicitly pass it to the
# `def` method, `Type` is only made to describe `nixpkgs.lib.types` not to be
# used with `fn` construct!
#
# n.b. Have a look at the `README.md` for a getting start tutorial!
{ enable ? false, ... }: let
# TODO version = is types.SemVer "0.0.1";
/* Turn arbitrary data into a type (a fancy functor) */
declare = with types; args: type: {
__functor = self: self.check;
__toString = self: self.name;
check = e:
if Functor type || Lambda type then type e
else if Set type then
builtins.all (n: e?${n} && def type.${n} e.${n})
(builtins.attrNames type)
else if List type then
builtins.length e >= builtins.length type &&
builtins.all (n: def (builtins.elemAt type n) (builtins.elemAt e n))
(builtins.genList (i: i) (builtins.length type))
else e == type;
name =
if List type then "[ ${builtins.toString type} ]"
else if Print type then builtins.toString type
else if Set type then ''{ ${builtins.toString (
map (n: "${n} = ${def type.${n}};") (builtins.attrNames type)
)} }''
else "<UNNAMED>";
} // args;
/* Shortcut alias of `declare` function without argument */
def = declare {};
/* If a `value` respect a `type` return the `value` else a `default` value */
default = d: t: v: if def t v then v else d;
/* If a `value` respect a `type` return the `value` else throw an error */
contract = {
name ? "<UNNAMED>",
message ? s: "The following value don't respect the `${s.name}' contract:"
}: type: value:
if enable then let type' = def type;
isBool = value: assert types.Bool value || errors.INVALID_TYPE; value;
errors = {
INVALID_TYPE = builtins.throw ''
InvalidType: `${type'}' is not a function that return a `Boolean` ...
> n.b. Do you forget parenthesis around a type constructor?
'';
TYPE_ERROR = builtins.trace (message { inherit name; type = type'; })
builtins.trace value builtins.throw ''
TypeError: `check` function of the type `${type'}' return `false' ...
> n.b. This error comes from `github:yvan-sraka/contracts' library
'';
}; in assert isBool (type' value) || errors.TYPE_ERROR; value
else value;
/* Shortcut alias of `contract` function with default arguments */
is = contract { message = s: "Value should be of type `${s.type}':"; };
# TODO I'm not sure if this would be really handy:
# FunctionArgs = args: f: args == builtins.functionArgs f;
# ... because I would prefer something like:
fn = Args: f: x: f (is (def Args) x);
/* Force the concrete evaluation of a contract or any datatype */
strict = e: builtins.deepSeq e e;
#################################### TYPES ####################################
types = rec {
/* *** Top and Bottom types *** */
Any = e: true;
None = e: false;
/* *** Primitives types offered by Nix builtins *** */
Set = builtins.isAttrs;
Bool = builtins.isBool;
Float = builtins.isFloat;
Lambda = builtins.isFunction;
Int = builtins.isInt;
List = builtins.isList;
Path = builtins.isPath;
Str = builtins.isString;
Null = e: e == null;
Functor = e: Set e && e?__functor;
# Handy type fillers that throw an error if evaluated:
TODO = e: throw "Not implemented yet ...";
/* *** Types could be composed into new types *** */
Type = def { name = Str; check = enum [ Lambda Functor ]; };
# `Format` means "it could be turn into a string", with `"${x}"` syntax ...
Format = enum [ Str Path (e: Set e && (e?__toString || e?outPath)) ];
# ... and `Print` it could be with `toString x`, any better name idea?
Print = enum [ Bool Float Format Int List Null ];
# TODO choose types coming from `nixpkgs.lib.types`, e.g.:
# * Package = enum [ lib.isDerivation lib.isStorePath ];
# * NonEmptyStr = Str e && str != "";
/* *** Type builders could be parametrized with values like other types *** */
# n.b. `fn Type (args: ...)` ensure `Type` of given `args`!
length = fn prelude.Int (length:
declare { name = "length ${builtins.toString length}"; }
(fn prelude.List (xs: length == builtins.length xs)));
listOf = type: let type' = def type; in
declare { name = "listOf (${type'})"; }
(fn prelude.List (xs: builtins.all (x: type' x) xs));
setOf = type: let type' = def type; in
declare { name = "setOf (${type'})"; }
(fn prelude.Set (s: listOf type' (builtins.attrValues s)));
# Turn any type declared with `nixpkgs.lib.types` into a validator:
option = fn prelude.Type (type: with type;
declare { inherit check name; } None);
both = fn prelude.List (xs: let xs' = map (x: def x) xs; in
declare { name = "both [ ${builtins.toString xs'} ]"; }
(e: builtins.all (type: type e) xs'));
# TODO it would make more sense to name it `either` or to rename `both`:
enum = fn prelude.List (xs: let xs' = map (x: def x) xs; in
declare { name = "enum [ ${builtins.toString xs'} ]"; }
(e: builtins.any (type: type e) xs'));
not = type: let type' = def type; in
declare { name = "!(${type'})"; }
(e: !(type' e));
match = regex: # FIXME check if `regex` is of `Regex` type!
declare { name = "match /${regex}/ regex"; }
(fn prelude.Str (str: builtins.match regex str != null));
# From https://www.rfc-editor.org/rfc/rfc3986#page-50
Url = match ''^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?'';
/* *** Some type ideas for the future *** */
Hash = TODO; # FIXME SRI hashes used by Nix :)
Regex = TODO; # It would be nice to check if a RegEx is valid ...
Json = TODO; # ... or if JSON data could be parsed?
SemVer = TODO; # ... or if a software version is in SemVer format!
}; ############################################################################
# This is internal mixture that helps to have declared types available here:
prelude = (builtins.mapAttrs (n: _: declare { name = n; } types.${n}) types);
in prelude // {
# Explicitly choose what would be our library interface (and eventually not)
inherit declare def default contract is fn strict;
}