├── LICENSE ├── README.md ├── default.nix ├── flake.nix └── src ├── _test.nix ├── asserts.nix ├── builtins.txt ├── default.nix ├── fetchers.nix ├── formats ├── default.nix ├── json.nix ├── nix.nix └── toml.nix ├── imports.nix ├── impure.nix ├── nix.nix ├── systems.nix ├── types ├── attrs.nix ├── bools.nix ├── default.nix ├── floats.nix ├── ints.nix ├── lambdas.nix ├── lists.nix ├── nulls.nix ├── paths.nix ├── strings.nix └── values.nix └── versions.nix /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Numtide 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nix-stdlib - experimental nix prelude 2 | 3 | A batteries-included standard library for Nix. 4 | 5 | The Nix `builtins` and accompanied functions in `` have grown 6 | organically, without too much consideration for their taxonomy or re-use 7 | capabilities. 8 | 9 | This is an effort to re-think the basic tools made available to the user. 10 | 11 | Compatible with nix 1.12+ only. 12 | 13 | ## Usage 14 | 15 | ### Overlay 16 | 17 | ### Lib 18 | 19 | ### Documentation 20 | 21 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | import ./src 2 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "experimental nix prelude"; 3 | 4 | outputs = { self }: { 5 | lib = import ./src; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /src/_test.nix: -------------------------------------------------------------------------------- 1 | let 2 | stdlib = import ./.; 3 | in 4 | stdlib.asserts.isEqual 2 2 5 | -------------------------------------------------------------------------------- /src/asserts.nix: -------------------------------------------------------------------------------- 1 | { values, ... }: 2 | { 3 | isEqual = a: b: 4 | if a != b then throw "expected ${toString a} == ${toString b}" else true; 5 | 6 | isTrue = cond: 7 | if !cond then throw "expected true, got ${toString cond}" else true; 8 | 9 | isType = type: val: 10 | let 11 | t = values.type val; 12 | in 13 | if t != type then 14 | throw "expected type to be ${type}, not ${t}" 15 | else true; 16 | } 17 | -------------------------------------------------------------------------------- /src/builtins.txt: -------------------------------------------------------------------------------- 1 | 2 | nix-repl> map (x: builtins.trace x x) (builtins.attrNames builtins) 3 | 4 | # Remaining builtins 5 | trace: abort 6 | trace: addErrorContext 7 | trace: appendContext 8 | trace: deepSeq 9 | trace: derivation 10 | trace: derivationStrict 11 | trace: genericClosure 12 | trace: getContext 13 | trace: hasContext 14 | trace: parseDrvName 15 | trace: partition 16 | trace: placeholder 17 | trace: scopedImport 18 | trace: seq 19 | trace: splitVersion 20 | trace: throw 21 | trace: toXML 22 | trace: trace 23 | trace: tryEval 24 | trace: typeOf 25 | trace: unsafeDiscardOutputDependency 26 | trace: unsafeDiscardStringContext 27 | trace: unsafeGetAttrPos 28 | trace: valueSize 29 | 30 | -------------------------------------------------------------------------------- /src/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | # The first layer is pure types 3 | types = import ./types; 4 | 5 | # Then come more utility on top 6 | stdlib = types // { 7 | inherit types; 8 | asserts = import ./asserts.nix stdlib; 9 | fetchers = import ./fetchers.nix stdlib; 10 | formats = import ./formats stdlib; 11 | 12 | impure = import ./impure.nix stdlib; 13 | nix = import ./nix.nix; 14 | systems = import ./systems.nix stdlib; 15 | versions = import ./versions.nix stdlib; 16 | }; 17 | in 18 | stdlib 19 | -------------------------------------------------------------------------------- /src/fetchers.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | # TODO: wrap the fetchers so they look like build-time fetchers. 3 | # TODO: add mappings for github and friends? 4 | { 5 | git = builtins.fetchGit; 6 | mercurial = builtins.fetchMercurial; 7 | tarball = builtins.fetchTarball; 8 | url = builtins.fetchurl; 9 | tree = builtins.fetchTree; 10 | } 11 | -------------------------------------------------------------------------------- /src/formats/default.nix: -------------------------------------------------------------------------------- 1 | { ... }@stdlib: 2 | { 3 | json = import ./json.nix; 4 | nix = import ./nix.nix stdlib; 5 | toml = import ./toml.nix; 6 | } -------------------------------------------------------------------------------- /src/formats/json.nix: -------------------------------------------------------------------------------- 1 | rec { 2 | decode = builtins.fromJSON; 3 | encode = builtins.toJSON; 4 | import = path: decode (builtins.readFile path); 5 | } -------------------------------------------------------------------------------- /src/formats/nix.nix: -------------------------------------------------------------------------------- 1 | { 2 | encode = 3 | } 4 | 5 | { sets, strings, lists, values, ... }: 6 | let 7 | # Escape Nix strings 8 | stringToNix = str: 9 | "\"" + ( 10 | strings.replace 11 | [ "\\" "\"" "\n" "\r" "\t" ] 12 | [ "\\\\" "\\" "\\n" "\\r" "\\t" ] 13 | str 14 | ) 15 | + "\""; 16 | 17 | attrsToNix = attrs: 18 | strings.concatSep " " ( 19 | [ "{" ] ++ (sets.mapToList (k: v: "${k} = ${toNix v};") attrs) 20 | ++ [ "}" ] 21 | ); 22 | 23 | listToNix = list: 24 | strings.concatSep " " ( 25 | [ "[" ] ++ (lists.map toNix list) 26 | ++ [ "]" ] 27 | ); 28 | 29 | table = { 30 | "bool" = (x: if x then "true" else "false"); 31 | "int" = toString; 32 | "list" = listToNix; 33 | "null" = (x: "null"); 34 | "path" = toString; 35 | "set" = attrsToNix; 36 | "string" = stringToNix; 37 | }; 38 | 39 | # Like builtins.JSON but outputs Nix code instead 40 | # TODO: 41 | # * support floats 42 | # * escape attrs keys 43 | # * formatting options? 44 | toNix = value: 45 | let 46 | t = values.type value; 47 | in 48 | table.${t} or (x: throw "type '${t}' not supported") 49 | value 50 | ; 51 | in 52 | { 53 | encode = toNix; 54 | # FIXME: use the same type signature as the other decoders. 55 | decode = builtins.tryEval; 56 | 57 | import = builtins.import; 58 | } -------------------------------------------------------------------------------- /src/formats/toml.nix: -------------------------------------------------------------------------------- 1 | rec { 2 | decode = builtins.fromTOML; 3 | encode = throw "TODO: format.toml.encode is not implemented"; 4 | import = path: decode (builtins.readFile path); 5 | } -------------------------------------------------------------------------------- /src/imports.nix: -------------------------------------------------------------------------------- 1 | { attrs, lists, lambdas, paths, ... }: 2 | { 3 | # Loads all the sub-folders in the baseDir using the importFn 4 | subdirs = importFn: baseDir: 5 | let 6 | dirEntries = 7 | attrs.keys 8 | ( 9 | attrs.filter 10 | (k: v: v == "directory") 11 | (paths.readDir baseDir) 12 | ); 13 | 14 | absDirs = lists.map (dir: baseDir + "/${dir}") dirEntries; 15 | 16 | imports = 17 | lists.map 18 | (dir: { name = paths.basename dir; value = importFn dir; }) 19 | absDirs; 20 | in 21 | lists.toAttrs imports; 22 | 23 | # Loads a JSON file 24 | # 25 | # path -> a 26 | JSON = lambdas.compose builtins.fromJSON builtins.readFile; 27 | 28 | TOML = lambdas.compose builtins.fromTOML builtins.readFile; 29 | 30 | scoped = builtins.scopedImport; 31 | } 32 | -------------------------------------------------------------------------------- /src/impure.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | inherit (builtins) 4 | getEnv 5 | currentSystem 6 | currentTime 7 | ; 8 | 9 | NIX_PATH = builtins.nixPath; 10 | } 11 | -------------------------------------------------------------------------------- /src/nix.nix: -------------------------------------------------------------------------------- 1 | { 2 | inherit (builtins) 3 | langVersion 4 | 5 | # returns the configured nix store root directory 6 | # (eg: /nix/store) 7 | # 8 | # string 9 | storeDir 10 | ; 11 | 12 | # returns the version of nix 13 | version = builtins.nixVersion; 14 | } 15 | -------------------------------------------------------------------------------- /src/systems.nix: -------------------------------------------------------------------------------- 1 | { attrs, ... }: 2 | rec { 3 | # contains the list of systems that are currently supported 4 | # by the NixOS project. 5 | default = [ 6 | "x86_64-linux" 7 | "i686-linux" 8 | "x86_64-darwin" 9 | "aarch64-linux" 10 | ]; 11 | 12 | # creates a generator for the given systems 13 | mkForAll = systems: f: attrs.gen systems f; 14 | 15 | # a generator for all the default systems 16 | defaultForAll = mkForAll default; 17 | } 18 | -------------------------------------------------------------------------------- /src/types/attrs.nix: -------------------------------------------------------------------------------- 1 | { lists, ... }: 2 | rec { 3 | isType = builtins.isAttrs; 4 | 5 | concat = a: b: a // b; 6 | 7 | size = set: builtins.length (keys set); 8 | 9 | optional = cond: x: if cond then x else empty; 10 | 11 | empty = { }; 12 | 13 | isEmpty = x: x == empty; 14 | 15 | get = builtins.getAttr; 16 | 17 | has = builtins.hasAttr; 18 | 19 | map = builtins.mapAttrs; 20 | 21 | # NOTE: putting the attrs as the last argument 22 | remove = keys: attrs: builtins.removeAttrs attrs keys; 23 | 24 | keys = builtins.attrNames; 25 | 26 | values = builtins.attrValues; 27 | 28 | # key:string -> [{ key = value; }] -> [value] 29 | cat = builtins.catAttrs; 30 | 31 | intersect = builtins.intersectAttrs; 32 | 33 | nameValuePair = name: value: { inherit name value; }; 34 | 35 | gen = names: f: 36 | lists.toAttrs (map (n: nameValuePair n (f n)) names); 37 | 38 | # filter = pred: set: 39 | # lists.toAttrs 40 | # (lists.concatMap 41 | # (name: let v = set.${name}; in if pred name v then [(nameValuePair name v)] else []) (keys set)); 42 | 43 | /* Call a function for each attribute in the given set and return 44 | the result in a list. 45 | 46 | Example: 47 | mapAttrsToList (name: value: name + value) 48 | { x = "a"; y = "b"; } 49 | => [ "xa" "yb" ] 50 | */ 51 | mapToList = f: attrs: 52 | builtins.map (name: f name attrs.${name}) (keys attrs); 53 | } 54 | -------------------------------------------------------------------------------- /src/types/bools.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | isType = builtins.isBool; 4 | 5 | # TODO: add boolean logic 6 | } 7 | -------------------------------------------------------------------------------- /src/types/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | types = { 3 | attrs = import ./attrs.nix types; 4 | bools = import ./bools.nix types; 5 | floats = import ./floats.nix types; 6 | ints = import ./ints.nix types; 7 | lambdas = import ./lambdas.nix types; 8 | lists = import ./lists.nix types; 9 | nulls = import ./nulls.nix types; 10 | paths = import ./paths.nix types; 11 | strings = import ./strings.nix types; 12 | 13 | # "sets" is used for the generic type dispatcher 14 | sets = types.attrs; 15 | 16 | # generic values 17 | values = import ./values.nix types; 18 | }; 19 | in 20 | types 21 | -------------------------------------------------------------------------------- /src/types/floats.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | isType = builtins.isFloat; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/ints.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | rec { 3 | isType = builtins.isInt; 4 | 5 | even = a: (mod a 2) == 0; 6 | odd = a: (mod a 2) != 0; 7 | 8 | lessThan = builtins.lessThan; 9 | 10 | # C-style comparisons 11 | compare = a: b: 12 | if a < b then 13 | -1 14 | else if a > b then 15 | 1 16 | else 17 | 0; 18 | 19 | min = x: y: if x < y then x else y; 20 | max = x: y: if x > y then x else y; 21 | mod = base: int: base - (int * (div base int)); 22 | 23 | add = builtins.add; 24 | sub = builtins.sub; 25 | mul = builtins.mul; 26 | div = builtins.div; 27 | 28 | bitAnd = builtins.bitAnd; 29 | bitOr = builtins.bitOr; 30 | bitXor = builtins.bitXor; 31 | } 32 | -------------------------------------------------------------------------------- /src/types/lambdas.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | # Returns true for user-defined lambdas 4 | # 5 | # a -> Bool 6 | isType = builtins.isFunction; 7 | 8 | # (b -> c) -> (a -> b) -> a -> c 9 | compose = f: g: x: f (g x); 10 | 11 | # (a -> b) -> a -> b 12 | apply = f: a: f a; 13 | 14 | # (a -> a) -> a 15 | fix = f: 16 | let x = f x; in x; 17 | 18 | # a -> a 19 | id = x: x; 20 | 21 | # a -> b -> a 22 | const = a: b: a; 23 | 24 | # Reverses the two first arguments. 25 | # (a -> b -> c) -> b -> a -> c 26 | flip = fn: a: b: fn b a; 27 | 28 | # Returns the set of attributes and defaults for functions that 29 | # take keyword arguments. 30 | # 31 | # fn -> Set 32 | args = builtins.functionArgs; 33 | 34 | # builtins.genericClosure 35 | } 36 | -------------------------------------------------------------------------------- /src/types/lists.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | rec { 3 | isType = builtins.isList; 4 | 5 | inherit (builtins) 6 | all 7 | any 8 | elem 9 | elemAt 10 | map 11 | filter 12 | foldl' 13 | head 14 | isList 15 | length 16 | tail 17 | ; 18 | 19 | # [a] -> [a] -> [a] 20 | append = a: b: a ++ b; 21 | 22 | # [[a]] -> [a] 23 | concat = builtins.concatLists; 24 | 25 | # [a] -> str -> str 26 | join = builtins.concatStringsSet; 27 | 28 | concatMap = builtins.concatMap; 29 | 30 | optional = cond: x: if cond then x else empty; 31 | 32 | empty = [ ]; 33 | 34 | isEmpty = x: x == empty; 35 | 36 | singleton = x: [ x ]; 37 | 38 | slice = start: count: list: 39 | let 40 | len = length list; 41 | in 42 | gen 43 | (n: elemAt list (n + start)) 44 | ( 45 | if start >= len then 0 46 | else if start + count > len then len - start 47 | else count 48 | ); 49 | 50 | take = count: slice 0 count; 51 | 52 | drop = count: list: slice count (length list) list; 53 | 54 | toAttrs = builtins.listToAttrs; 55 | 56 | # sort: (a -> a -> bool) -> [a] -> [a] 57 | sort = builtins.sort; 58 | 59 | # 60 | gen = builtins.genList; 61 | 62 | replace = builtins.replaceStrings; 63 | } 64 | -------------------------------------------------------------------------------- /src/types/nulls.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | isType = builtins.isNull; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/paths.nix: -------------------------------------------------------------------------------- 1 | { lambdas, strings, ... }: 2 | { 3 | isType = builtins.isPath; 4 | 5 | # FIXME: is this correct? 6 | toType = str: /. + toString str; 7 | 8 | # path -> bool 9 | exists = builtins.pathExists; 10 | 11 | # path -> string 12 | read = builtins.readFile; 13 | 14 | # creates a new entry in the /nix/store 15 | # 16 | # name:string -> content:string -> path 17 | # 18 | # NOTE: the string cannot have context 19 | write = builtins.toFile; 20 | 21 | # path -> set{ name = filetype } 22 | readDir = builtins.readDir; 23 | 24 | dirname = builtins.dirOf; 25 | 26 | basename = builtins.baseNameOf; 27 | 28 | # resolves a given path to a /nix/store entry 29 | # 30 | # path -> string 31 | storePath = builtins.storePath; 32 | 33 | import = builtins.import; 34 | 35 | # Find a file in the Nix search path. Used to implement paths, 36 | # which are desugared to 'findFile __nixPath "x"'. */ 37 | findFile = builtins.findFile; 38 | 39 | # A proper source filter 40 | filter = { path, name ? "source", allow ? [ ], deny ? [ ] }: 41 | let 42 | # If an argument to allow or deny is a path, transform it to a matcher. 43 | # 44 | # This probably needs more work, I don't think that it works on 45 | # sub-folders. 46 | toMatcher = f: 47 | let 48 | path_ = toString f; 49 | in 50 | if lambdas.isType f then f 51 | else 52 | (path: type: path_ == toString path); 53 | 54 | allow_ = builtins.map toMatcher allow; 55 | deny_ = builtins.map toMatcher deny; 56 | in 57 | builtins.path { 58 | inherit name path; 59 | filter = path: type: 60 | (builtins.any (f: f path type) allow_) && 61 | (!builtins.any (f: f path type) deny_); 62 | }; 63 | 64 | # Match paths with the given extension 65 | matchExt = ext: 66 | path: type: 67 | (strings.hasSuffix ".${ext}" path); 68 | 69 | # Returns a hash of the file. 70 | # 71 | # Valid algos are: 72 | # md5 73 | # sha1 74 | # sha256 75 | # sha512 76 | # 77 | # algo:string -> path -> hash:string 78 | hash = builtins.hashFile; 79 | } 80 | -------------------------------------------------------------------------------- /src/types/strings.nix: -------------------------------------------------------------------------------- 1 | { lists, ... }: 2 | rec { 3 | isType = builtins.isString; 4 | toType = builtins.toString; 5 | 6 | sub = builtins.substring; 7 | 8 | append = a: b: a + b; 9 | 10 | # [string] -> string 11 | concat = lists.join ""; 12 | 13 | # 14 | length = builtins.stringLength; 15 | 16 | optional = cond: x: if cond then x else empty; 17 | 18 | empty = ""; 19 | 20 | isEmpty = x: x == empty; 21 | 22 | # start:int -> length:int -> [T] -> [T] 23 | slice = builtins.substring; 24 | 25 | # split regex str 26 | split = builtins.split; 27 | 28 | take = count: slice 0 count; 29 | 30 | drop = count: str: slice count (length str) str; 31 | 32 | replace = builtins.replaceStrings; 33 | 34 | /* Determine whether a string has given suffix. 35 | 36 | Type: hasSuffix :: string -> string -> bool 37 | 38 | Example: 39 | hasSuffix "foo" "foobar" 40 | => false 41 | hasSuffix "foo" "barfoo" 42 | => true 43 | */ 44 | hasSuffix = 45 | # Suffix to check for 46 | suffix: 47 | # Input string 48 | content: 49 | let 50 | lenContent = length content; 51 | lenSuffix = length suffix; 52 | in 53 | lenContent >= lenSuffix && 54 | slice (lenContent - lenSuffix) lenContent content == suffix; 55 | 56 | # Returns a hash of the content. 57 | # 58 | # Valid algos are: 59 | # md5 60 | # sha1 61 | # sha256 62 | # sha512 63 | # 64 | # algo:string -> content:string -> hash:string 65 | hash = builtins.hashString; 66 | 67 | # regexp match 68 | match = builtins.match; 69 | } 70 | -------------------------------------------------------------------------------- /src/types/values.nix: -------------------------------------------------------------------------------- 1 | types: 2 | rec { 3 | type = builtins.typeOf; 4 | 5 | optional = cond: x: 6 | let 7 | t = type x; 8 | in 9 | if cond then x else types."${t}s".empty; 10 | 11 | # assuming a and b are of the same type 12 | append = a: b: 13 | let 14 | t = type a; 15 | tb = type b; 16 | in 17 | assert t == tb; 18 | types."${t}s".append a b; 19 | 20 | # returns the underlying size consumption 21 | # ??? not sure if it's worth exporting that 22 | size = builtins.valueSize; 23 | } 24 | -------------------------------------------------------------------------------- /src/versions.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | # Compares two semver-abiding versions. 4 | # 5 | # -1: the first argument is lower than the second 6 | # 0: both arguments are the same 7 | # 1: the first argument is higher than the second 8 | # 9 | # str -> str -> int 10 | compare = builtins.compareVersions; 11 | 12 | # Splits a semver version according to its components 13 | # 14 | # Example: 15 | # > builtins.splitVersion "1.2.3-rc1" 16 | # [ "1" "2" "3" "rc" "1" ] 17 | # 18 | split = builtins.splitVersion; 19 | 20 | # Returns the version of the current Nix interpreter 21 | nix = builtins.nixVersion; 22 | 23 | # Returns the version of the language 24 | lang = builtins.langVersion; 25 | } 26 | --------------------------------------------------------------------------------