at dev 4.7 kB view raw
1{ self, lib, config, ... }: let 2 cfg = config.modularizer; 3in { 4 options.modularizer = { 5 paths = lib.mkOption { 6 type = lib.types.listOf lib.types.path; 7 default = []; 8 description = "List of paths to import"; 9 }; 10 11 modules = lib.mkOption { 12 # ./path/name/type.nix or ./path/name/type/default.nix 13 # --> becomes --> 14 # { "type" = { "name" = f: import ./path/name/type.nix; }; } or 15 # { "type" = { "name" = f: import ./path/name/type/default.nix; }; } 16 # ./path/ can be longer, like ./path/path/path/name/type.nix 17 # the recursive search will stop in any directory with a default.nix, 18 # allowing for either single .nix files for each type or an entire directory. 19 type = with lib.types; attrsOf (attrsOf (functionTo attrs)); 20 21 internal = true; 22 readOnly = true; 23 24 default = let 25 26 inherit (builtins) 27 elemAt 28 length 29 isFunction 30 groupBy 31 mapAttrs 32 listToAttrs 33 readFileType 34 ; 35 36 inherit (lib) 37 concatMapAttrs 38 hasSuffix 39 removeSuffix 40 pathExists 41 splitString 42 attrsToList 43 concatMap 44 ; 45 46 importFromDirectoryRecursive = 47 directory: 48 let 49 processDir = 50 { 51 basePath, 52 localPath ? "", # should be emtpy or start with "/" 53 }: 54 let 55 fullPathDir = basePath + localPath; 56 in 57 concatMapAttrs ( 58 name: type: 59 let 60 fullPath = fullPathDir + "/" + name; 61 isFile = type == "regular"; 62 isDir = type == "directory"; 63 isImportableDir = 64 isDir && 65 pathExists (fullPath + "/" + "default.nix") && 66 readFileType (fullPath + "/" + "default.nix") == "regular"; 67 isSupported = 68 isFile || # regular file 69 (isDir && !isImportableDir) || # dir with no default.nix file 70 isImportableDir # dir where default.nix is a regular file 71 ; 72 in 73 74 # unknown type 75 if !isSupported then 76 throw '' 77 modulesFromDirectoryRecursive: Unsupported file type ${type} at path ${toString fullPath} 78 '' 79 80 # duplicate importable files with the same name, 81 # directory/default.nix and directory.nix both exist 82 else if 83 isImportableDir && 84 pathExists (fullPath + ".nix") 85 then 86 throw '' 87 modulesFromDirectoryRecursive: Found both a directory ${fullPath} and file ${fullPath}/default.nix which conflict (same module name). 88 Suggested action: Delete the ${name}.nix file and move its contents into ${name}/default.nix or vice versa. 89 '' 90 91 # .nix file or directory with default.nix 92 else if 93 (isFile && hasSuffix ".nix" name) || 94 isImportableDir 95 then 96 { 97 # FIXME: importing an empty file or otherwise a non-nix file will freeze execution... 98 "${localPath + "/" + (removeSuffix ".nix" name)}" = import fullPath; 99 } 100 101 # directory without default.nix 102 else if isDir && !isImportableDir then 103 processDir 104 { 105 inherit basePath; 106 localPath = localPath + "/" + name; 107 } 108 109 # non-nix file 110 else 111 { } 112 ) (builtins.readDir fullPathDir); 113 in processDir { basePath = directory; }; 114 115 processImportedModule = m: 116 let 117 pathParts = splitString "/" m.name; 118 type = elemAt pathParts (length pathParts - 1); 119 # first element is an empty string because of the root slash 120 # [ "" "moduleName" ... "type" ] 121 name = if length pathParts >= 3 then elemAt pathParts 1 else "default"; 122 value = if isFunction m.value then m.value else _: m.value; 123 in { 124 inherit type name value; 125 }; 126 127 allDirs = map importFromDirectoryRecursive cfg.paths; 128 allImports = concatMap attrsToList allDirs; 129 allModules = map processImportedModule allImports; 130 131 byType = groupBy (e: e.type) allModules; 132 finalize = mapAttrs (_: v: listToAttrs v) byType; 133 134 in finalize; 135 }; 136 137 }; 138}