at 24.05-pre 4.5 kB view raw
1{ lib, pkgs }: 2let 3 inherit (lib) types; 4 inherit (types) attrsOf oneOf coercedTo str bool int float package; 5in 6{ 7 javaProperties = { comment ? "Generated with Nix", boolToString ? lib.boolToString }: { 8 9 # Design note: 10 # A nested representation of inevitably leads to bad UX: 11 # 1. keys like "a.b" must be disallowed, or 12 # the addition of options in a freeformType module 13 # become breaking changes 14 # 2. adding a value for "a" after "a"."b" was already 15 # defined leads to a somewhat hard to understand 16 # Nix error, because that's not something you can 17 # do with attrset syntax. Workaround: "a"."", but 18 # that's too little too late. Another workaround: 19 # mkMerge [ { a = ...; } { a.b = ...; } ]. 20 # 21 # Choosing a non-nested representation does mean that 22 # we sacrifice the ability to override at the (conceptual) 23 # hierarchical levels, _if_ an application exhibits those. 24 # 25 # Some apps just use periods instead of spaces in an odd 26 # mix of attempted categorization and natural language, 27 # with no meaningful hierarchy. 28 # 29 # We _can_ choose to support hierarchical config files 30 # via nested attrsets, but the module author should 31 # make sure that problem (2) does not occur. 32 type = let 33 elemType = 34 oneOf ([ 35 # `package` isn't generalized to `path` because path values 36 # are ambiguous. Are they host path strings (toString /foo/bar) 37 # or should they be added to the store? ("${/foo/bar}") 38 # The user must decide. 39 (coercedTo package toString str) 40 41 (coercedTo bool boolToString str) 42 (coercedTo int toString str) 43 (coercedTo float toString str) 44 ]) 45 // { description = "string, package, bool, int or float"; }; 46 in attrsOf elemType; 47 48 generate = name: value: 49 pkgs.runCommandLocal name 50 { 51 # Requirements 52 # ============ 53 # 54 # 1. Strings in Nix carry over to the same 55 # strings in Java => need proper escapes 56 # 2. Generate files quickly 57 # - A JVM would have to match the app's 58 # JVM to avoid build closure bloat 59 # - Even then, JVM startup would slow 60 # down config generation. 61 # 62 # 63 # Implementation 64 # ============== 65 # 66 # Escaping has two steps 67 # 68 # 1. jq 69 # Escape known separators, in order not 70 # to break up the keys and values. 71 # This handles typical whitespace correctly, 72 # but may produce garbage for other control 73 # characters. 74 # 75 # 2. iconv 76 # Escape >ascii code points to java escapes, 77 # as .properties files are supposed to be 78 # encoded in ISO 8859-1. It's an old format. 79 # UTF-8 behavior may exist in some apps and 80 # libraries, but we can't rely on this in 81 # general. 82 83 passAsFile = [ "value" ]; 84 value = builtins.toJSON value; 85 nativeBuildInputs = [ 86 pkgs.jq 87 pkgs.libiconvReal 88 ]; 89 90 jqCode = 91 let 92 main = '' 93 to_entries 94 | .[] 95 | "\( 96 .key 97 | ${commonEscapes} 98 | gsub(" "; "\\ ") 99 | gsub("="; "\\=") 100 ) = \( 101 .value 102 | ${commonEscapes} 103 | gsub("^ "; "\\ ") 104 | gsub("\\n "; "\n\\ ") 105 )" 106 ''; 107 # Most escapes are equal for both keys and values. 108 commonEscapes = '' 109 gsub("\\\\"; "\\\\") 110 | gsub("\\n"; "\\n\\\n") 111 | gsub("#"; "\\#") 112 | gsub("!"; "\\!") 113 | gsub("\\t"; "\\t") 114 | gsub("\r"; "\\r") 115 ''; 116 in 117 main; 118 119 inputEncoding = "UTF-8"; 120 121 inherit comment; 122 123 } '' 124 ( 125 echo "$comment" | while read -r ln; do echo "# $ln"; done 126 echo 127 jq -r --arg hash '#' "$jqCode" "$valuePath" \ 128 | iconv --from-code "$inputEncoding" --to-code JAVA \ 129 ) > "$out" 130 ''; 131 }; 132}