133 lines
4.4 KiB
Nix
133 lines
4.4 KiB
Nix
{ lib, pkgs }:
|
|
let
|
|
inherit (lib) types;
|
|
inherit (types) attrsOf oneOf coercedTo str bool int float package;
|
|
in
|
|
{
|
|
javaProperties = { comment ? "Generated with Nix", boolToString ? lib.boolToString }: {
|
|
|
|
# Design note:
|
|
# A nested representation of inevitably leads to bad UX:
|
|
# 1. keys like "a.b" must be disallowed, or
|
|
# the addition of options in a freeformType module
|
|
# become breaking changes
|
|
# 2. adding a value for "a" after "a"."b" was already
|
|
# defined leads to a somewhat hard to understand
|
|
# Nix error, because that's not something you can
|
|
# do with attrset syntax. Workaround: "a"."", but
|
|
# that's too little too late. Another workaround:
|
|
# mkMerge [ { a = ...; } { a.b = ...; } ].
|
|
#
|
|
# Choosing a non-nested representation does mean that
|
|
# we sacrifice the ability to override at the (conceptual)
|
|
# hierarchical levels, _if_ an application exhibits those.
|
|
#
|
|
# Some apps just use periods instead of spaces in an odd
|
|
# mix of attempted categorization and natural language,
|
|
# with no meaningful hierarchy.
|
|
#
|
|
# We _can_ choose to support hierarchical config files
|
|
# via nested attrsets, but the module author should
|
|
# make sure that problem (2) does not occur.
|
|
type = let
|
|
elemType =
|
|
oneOf ([
|
|
# `package` isn't generalized to `path` because path values
|
|
# are ambiguous. Are they host path strings (toString /foo/bar)
|
|
# or should they be added to the store? ("${/foo/bar}")
|
|
# The user must decide.
|
|
(coercedTo package toString str)
|
|
|
|
(coercedTo bool boolToString str)
|
|
(coercedTo int toString str)
|
|
(coercedTo float toString str)
|
|
])
|
|
// { description = "string, package, bool, int or float"; };
|
|
in attrsOf elemType;
|
|
|
|
generate = name: value:
|
|
pkgs.runCommandLocal name
|
|
{
|
|
# Requirements
|
|
# ============
|
|
#
|
|
# 1. Strings in Nix carry over to the same
|
|
# strings in Java => need proper escapes
|
|
# 2. Generate files quickly
|
|
# - A JVM would have to match the app's
|
|
# JVM to avoid build closure bloat
|
|
# - Even then, JVM startup would slow
|
|
# down config generation.
|
|
#
|
|
#
|
|
# Implementation
|
|
# ==============
|
|
#
|
|
# Escaping has two steps
|
|
#
|
|
# 1. jq
|
|
# Escape known separators, in order not
|
|
# to break up the keys and values.
|
|
# This handles typical whitespace correctly,
|
|
# but may produce garbage for other control
|
|
# characters.
|
|
#
|
|
# 2. iconv
|
|
# Escape >ascii code points to java escapes,
|
|
# as .properties files are supposed to be
|
|
# encoded in ISO 8859-1. It's an old format.
|
|
# UTF-8 behavior may exist in some apps and
|
|
# libraries, but we can't rely on this in
|
|
# general.
|
|
|
|
passAsFile = [ "value" ];
|
|
value = builtins.toJSON value;
|
|
nativeBuildInputs = [
|
|
pkgs.jq
|
|
pkgs.libiconvReal
|
|
];
|
|
|
|
jqCode =
|
|
let
|
|
main = ''
|
|
to_entries
|
|
| .[]
|
|
| "\(
|
|
.key
|
|
| ${commonEscapes}
|
|
| gsub(" "; "\\ ")
|
|
| gsub("="; "\\=")
|
|
) = \(
|
|
.value
|
|
| ${commonEscapes}
|
|
| gsub("^ "; "\\ ")
|
|
| gsub("\\n "; "\n\\ ")
|
|
)"
|
|
'';
|
|
# Most escapes are equal for both keys and values.
|
|
commonEscapes = ''
|
|
gsub("\\\\"; "\\\\")
|
|
| gsub("\\n"; "\\n\\\n")
|
|
| gsub("#"; "\\#")
|
|
| gsub("!"; "\\!")
|
|
| gsub("\\t"; "\\t")
|
|
| gsub("\r"; "\\r")
|
|
'';
|
|
in
|
|
main;
|
|
|
|
inputEncoding = "UTF-8";
|
|
|
|
inherit comment;
|
|
|
|
} ''
|
|
(
|
|
echo "$comment" | while read -r ln; do echo "# $ln"; done
|
|
echo
|
|
jq -r --arg hash '#' "$jqCode" "$valuePath" \
|
|
| iconv --from-code "$inputEncoding" --to-code JAVA \
|
|
) > "$out"
|
|
'';
|
|
};
|
|
}
|