Skip to content

Commit

Permalink
Add NixOS support
Browse files Browse the repository at this point in the history
NixOS 23.11 received support for creating dconf databases in NixOS configuration.
The GVariant library in Nixpkgs has slightly different syntax, though,
so let’s make the output style configurable and add support for that.
https://www.github.com/NixOS/nixpkgs/pull/234615
  • Loading branch information
jtojnar committed May 20, 2024
1 parent 63c7eab commit 153a3e9
Show file tree
Hide file tree
Showing 27 changed files with 994 additions and 53 deletions.
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CI Status](https://github.com/nix-commmunity/dconf2nix/workflows/Haskell%20CI/badge.svg)](https://github.com/nix-commmunity/dconf2nix/actions)

A convenient converter of [dconf](https://gitlab.gnome.org/GNOME/dconf) files to Nix, as expected by [Home Manager's dconf settings](https://rycee.gitlab.io/home-manager/options.xhtml#opt-dconf.settings). So you can Nixify your [GNOME Shell](https://gitlab.gnome.org/GNOME/gnome-shell) configuration :wink:
A convenient converter of [dconf](https://gitlab.gnome.org/GNOME/dconf) files to Nix, as expected by [Home Manager’s](https://rycee.gitlab.io/home-manager/options.xhtml#opt-dconf.settings) or [NixOS](https://nixos.org/manual/nixos/unstable/options.html#opt-programs.dconf.profiles) dconf settings. So you can Nixify your [GNOME Shell](https://gitlab.gnome.org/GNOME/gnome-shell) configuration :wink:

<!--ts-->
* [Benchmarks](#benchmarks)
Expand Down Expand Up @@ -46,12 +46,14 @@ xkb-options=['terminate:ctrl_alt_bksp', 'lv3:ralt_switch', 'caps:ctrl_modifier']
picture-uri='file:///home/gvolpe/Pictures/nixos.png'
```

You will get the following output when running `dconf2nix`:
You will get the following output when running `dconf2nix --home-manager`:

```nix
# Generated via dconf2nix: https://github.com/nix-commmunity/dconf2nix
{ lib, ... }:
with lib.hm.gvariant;
{
dconf.settings = {
"org/gnome/desktop/peripherals/mouse" = {
Expand All @@ -73,8 +75,8 @@ with lib.hm.gvariant;
"org/gnome/desktop/screensaver" = {
picture-uri = "file:///home/gvolpe/Pictures/nixos.png";
};
};
};
}
```

Expand All @@ -91,29 +93,37 @@ dconf dump / > dconf.settings
The easiest way is to pipe the standard input to `dconf2nix` and expect the result in the standard output:

```shell
dconf dump / | dconf2nix > dconf.nix
dconf dump / | dconf2nix --home-manager > dconf.nix
```

If you want to use the result in a NixOS configuration, use `--nixos` instead of `--home-manager`:

```shell
dconf dump / | dconf2nix --nixos > dconf.nix
```

If you have an input file instead, you can run the following command:

```shell
dconf2nix -i data/dconf.settings -o output/dconf.nix
dconf2nix --home-manager -i data/dconf.settings -o output/dconf.nix
```

Type `--help` for some more information.

```shell
dconf2nix - Nixify dconf configuration files

Usage: dconf2nix [-v|--version] [-r|--root ARG] [--verbose]
[(-i|--input ARG) (-o|--output ARG)]
Usage: dconf2nix [-v|--version] [-r|--root ARG] (--home-manager | --nixos)
[--verbose] [(-i|--input ARG) (-o|--output ARG)]

Convert a dconf file into a Nix file, as expected by Home Manager.

Available options:
-h,--help Show this help text
-v,--version Show the current version
-r,--root ARG Custom root path. e.g.: system/locale/
--home-manager Use NixOS syntax
--nixos Use home-manager syntax
--verbose Verbose mode (debug)
-i,--input ARG Path to the dconf file (input)
-o,--output ARG Path to the Nix output file (to be created)
Expand All @@ -124,7 +134,7 @@ Available options:
By default, `dconf2nix` expects the root to be `/`. If you want to create a dump of a custom root, you can use the `--root` flag. For example:

```shell
dconf dump /system/locale/ | dconf2nix --root system/locale > dconf.nix
dconf dump /system/locale/ | dconf2nix --home-manager --root system/locale > dconf.nix
```

This will generate an output similar to the one below.
Expand Down
8 changes: 4 additions & 4 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import DConf2Nix ( dconf2nixFile

main :: IO ()
main = runArgs >>= \case
Args r v (FileInput (FileArgs i o)) ->
dconf2nixFile i o r v
Args r v StdinInput ->
dconf2nixStdin r v
Args r s v (FileInput (FileArgs i o)) ->
dconf2nixFile i o r s v
Args r s v StdinInput ->
dconf2nixStdin r s v
4 changes: 2 additions & 2 deletions dconf2nix.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ cabal-version: 2.4

name: dconf2nix
version: 0.1.1
synopsis: Convert dconf files to Nix, as expected by Home Manager.
description: A convenient converter of dconf files to Nix, as expected by Home Manager’s dconf settings. So you can Nixify your GNOME Shell configuration ;)
synopsis: Convert dconf files to Nix, as expected by Home Manager or NixOS.
description: A convenient converter of dconf files to Nix, as expected by Home Manager or NixOS’s dconf settings. So you can Nixify your GNOME Shell configuration ;)
bug-reports: https://github.com/nix-commmunity/dconf2nix/issues
license: Apache-2.0
license-file: LICENSE
Expand Down
8 changes: 7 additions & 1 deletion src/CommandLine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Paths_dconf2nix ( version )

data Args = Args
{ argsRoot :: Root
, argsStyle :: Style
, argsVerbosity :: Verbosity
, argsInput :: Input
}
Expand All @@ -25,6 +26,11 @@ data FileArgs = FileArgs
, fileOutput :: OutputFilePath
}

styleArgs :: Parser Style
styleArgs =
flag' HomeManager (long "home-manager" <> help "Use NixOS syntax")
<|> flag' NixOS (long "nixos" <> help "Use home-manager syntax")

verbosityArgs :: Parser Verbosity
verbosityArgs =
flag Normal Verbose (long "verbose" <> help "Verbose mode (debug)")
Expand Down Expand Up @@ -71,7 +77,7 @@ versionOpt = infoOption versionInfo
runArgs :: IO Args
runArgs =
let
args = Args <$> rootArgs <*> verbosityArgs <*> (stdinArgs <|> fileArgs)
args = Args <$> rootArgs <*> styleArgs <*> verbosityArgs <*> (stdinArgs <|> fileArgs)

opts = info
(helper <*> versionOpt <*> args)
Expand Down
3 changes: 3 additions & 0 deletions src/DConf/Data.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import Data.Word ( Word8 )
newtype InputFilePath = InputFilePath FilePath deriving Show
newtype OutputFilePath = OutputFilePath FilePath deriving Show

data Style = HomeManager | NixOS
deriving Eq

data Verbosity = Normal | Verbose

newtype Nix = Nix { unNix :: Text } deriving Show
Expand Down
21 changes: 11 additions & 10 deletions src/DConf2Nix.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,28 @@ import Text.Parsec ( ParseError
, runParser
)

dconf2nixFile :: InputFilePath -> OutputFilePath -> Root -> Verbosity -> IO ()
dconf2nixFile (InputFilePath input) (OutputFilePath output) root v =
let run = handler (T.writeFile output) (T.appendFile output) root
dconf2nixFile :: InputFilePath -> OutputFilePath -> Root -> Style -> Verbosity -> IO ()
dconf2nixFile (InputFilePath input) (OutputFilePath output) root s v =
let run = handler (T.writeFile output) (T.appendFile output) root s
parse = parseFromFile (dconfParser v) input
in run =<< parse

dconf2nixStdin :: Root -> Verbosity -> IO ()
dconf2nixStdin root v =
let run = handler T.putStr T.putStr root
dconf2nixStdin :: Root -> Style -> Verbosity -> IO ()
dconf2nixStdin root s v =
let run = handler T.putStr T.putStr root s
parse = runParser (dconfParser v) () "<stdin>"
in run . parse =<< T.getContents

handler
:: (T.Text -> IO ())
-> (T.Text -> IO ())
-> Root
-> Style
-> Either ParseError [Entry]
-> IO ()
handler writer appender root = \case
handler writer appender root s = \case
Left err -> error $ show err
Right xs -> do
writer Nix.renderHeader
traverse_ (\e -> appender (unNix $ Nix.renderEntry e root)) xs
appender Nix.renderFooter
writer (Nix.renderHeader s)
traverse_ (\e -> appender (unNix $ Nix.renderEntry s e root)) xs
appender (Nix.renderFooter s)
70 changes: 55 additions & 15 deletions src/Nix.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import qualified Data.Text as T
import Data.Word ( Word8 )
import DConf.Data

renderHeader :: Header
renderHeader = T.unlines
renderHeader :: Style -> Header
renderHeader HomeManager = renderHeaderHm
renderHeader NixOS = renderHeaderNixOS

renderHeaderHm :: Header
renderHeaderHm = T.unlines
[ "# Generated via dconf2nix: https://github.com/nix-commmunity/dconf2nix"
, "{ lib, ... }:"
, ""
Expand All @@ -27,8 +31,33 @@ renderHeader = T.unlines
, " dconf.settings = {"
]

renderFooter :: Header
renderFooter = T.unlines [" };", "}"]
renderHeaderNixOS :: Header
renderHeaderNixOS = T.unlines
[ "# Generated via dconf2nix: https://github.com/nix-commmunity/dconf2nix"
, "{ lib, ... }:"
, ""
, "with lib.gvariant;"
, ""
, "{"
, " programs.dconf.profiles.user.databases = ["
, " {"
, " settings = {"
]

renderFooter :: Style -> Header
renderFooter HomeManager = renderFooterHm
renderFooter NixOS = renderFooterNixOS

renderFooterHm :: Header
renderFooterHm = T.unlines [" };", "}"]

renderFooterNixOS :: Header
renderFooterNixOS = T.unlines
[ " };"
, " }"
, " ];"
, "}"
]

normalizeRoot :: T.Text -> T.Text
normalizeRoot r | T.null r = r
Expand All @@ -43,15 +72,20 @@ normalizeHeader h (Root r) = normalizeRoot r <> T.replace "." "/" h
mkSpaces :: Int -> T.Text
mkSpaces = T.pack . flip replicate ' '

renderEntry :: Entry -> Root -> Nix
renderEntry (Entry h c) root =
let header = mkSpaces 4 <> "\"" <> normalizeHeader h root <> "\" = {\n"
indentBase :: Style -> Int
indentBase HomeManager = 4
indentBase NixOS = 8

renderEntry :: Style -> Entry -> Root -> Nix
renderEntry s (Entry h c) root =
let base = indentBase s
header = mkSpaces base <> "\"" <> normalizeHeader h root <> "\" = {\n"
body = Map.toList c >>= \(Key k, v) ->
T.unpack $ mkSpaces 6 <> k <> " = " <> unNix (renderValue v) <> "\n"
close = mkSpaces 4 <> "};\n\n"
T.unpack $ mkSpaces (base + 2) <> k <> " = " <> unNix (renderValue s v) <> "\n"
close = mkSpaces base <> "};\n\n"
in Nix $ header <> T.pack body <> close

-- | Converts type to home-manager constructor function name.
-- | Converts type to NixOS/home-manager constructor function name.
-- | Most constructors will prefix the value with type annotation, we have to avoid those that don’t or we might get ambiguous expressions.
constrName :: Ty -> Maybe String
constrName TyObjectpath = Just "mkObjectpath"
Expand All @@ -72,10 +106,10 @@ constrName _ = Nothing
-- arguments or list items (e.g. function application).
data NixExpr = NeedsParens T.Text | Atomic T.Text

renderValue :: Value -> Nix
renderValue raw = Nix $ unparens (renderValue' initialIndent raw) <> ";"
renderValue :: Style -> Value -> Nix
renderValue s raw = Nix $ unparens (renderValue' initialIndent raw) <> ";"
where
initialIndent = 6
initialIndent = indentBase s + 2

parens :: NixExpr -> T.Text
parens (NeedsParens v) = "(" <> v <> ")"
Expand All @@ -95,7 +129,10 @@ renderValue raw = Nix $ unparens (renderValue' initialIndent raw) <> ";"
renderValue' _indent (S v) = renderString v
renderValue' _indent (B v) = Atomic $ T.toLower . T.pack $ show v
renderValue' _indent (No ) = error "Standalone nothing not supported"
renderValue' _indent (I v) = (if v < 0 then NeedsParens else Atomic) (T.pack $ show v)
renderValue' _indent (I v) =
case s of
HomeManager -> (if v < 0 then NeedsParens else Atomic) (T.pack $ show v)
NixOS -> NeedsParens $ "mkInt32 " <> parens ((if v < 0 then NeedsParens else Atomic) (T.pack $ show v))
renderValue' _indent (D v) = NeedsParens $ "mkDouble \"" <> (T.pack $ show v) <> "\""
renderValue' indent (C ty v) =
NeedsParens $ case constrName ty of
Expand All @@ -117,7 +154,10 @@ renderValue raw = Nix $ unparens (renderValue' initialIndent raw) <> ";"
Atomic $ "''\n" <> mkSpaces (indent + 2) <> T.strip v <> "\n" <> mkSpaces indent <> "''"
renderValue' indent (R kvs) =
Atomic $ "[\n" <> mconcat (fmap (\(k,v) -> mkSpaces (indent + 2) <> renderItem (indent + 2) (DE k v) <> "\n") kvs) <> mkSpaces indent <> "]"
renderValue' indent (DE k v) = NeedsParens $ "mkDictionaryEntry [" <> renderItem indent k <> " " <> renderItem indent v <> "]"
renderValue' indent (DE k v) =
case s of
HomeManager -> NeedsParens $ "mkDictionaryEntry [" <> renderItem indent k <> " " <> renderItem indent v <> "]"
NixOS -> NeedsParens $ "mkDictionaryEntry " <> renderItem indent k <> " " <> renderItem indent v

renderString :: T.Text -> NixExpr
renderString text = Atomic $ "\"" <> escaped <> "\""
Expand Down
30 changes: 19 additions & 11 deletions test/DConf2NixTest.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{-# LANGUAGE OverloadedStrings #-}

module DConf2NixTest
( dconf2nixTests
( dconf2nixHmTests
, dconf2nixNixOSTests
)
where

Expand All @@ -15,9 +16,13 @@ import DConf.Data
import Hedgehog
import Text.Parsec ( runParser )

dconf2nixTests :: Group
dconf2nixTests = Group "Tests"
[(fromString name, runOnce (inputTestProperty (fromString name) root)) | InputTest name root <- inputTests]
dconf2nixHmTests :: Group
dconf2nixHmTests = Group "Home Manager tests"
[(fromString name, runOnce (inputTestProperty HomeManager (fromString name) root)) | InputTest name root <- inputTests]

dconf2nixNixOSTests :: Group
dconf2nixNixOSTests = Group "NixOS tests"
[(fromString name, runOnce (inputTestProperty NixOS (fromString name) root)) | InputTest name root <- inputTests]

inputTests :: [InputTest]
inputTests =
Expand Down Expand Up @@ -53,11 +58,11 @@ it name = InputTest { itName = name, itRoot = (Root T.empty) }
runOnce :: Property -> Property
runOnce = withTests 1

baseProperty :: FilePath -> FilePath -> Root -> Property
baseProperty i o root = property $ do
baseProperty :: Style -> FilePath -> FilePath -> Root -> Property
baseProperty s i o root = property $ do
input <- evalIO $ T.readFile i
ref <- evalIO $ newIORef T.empty
evalIO $ handler (writer ref) (writer ref) root (entries input)
evalIO $ handler (writer ref) (writer ref) root s (entries input)
result <- evalIO $ readIORef ref
-- Uncomment to overwrite the output files.
-- evalIO $ T.writeFile o result
Expand All @@ -67,8 +72,11 @@ baseProperty i o root = property $ do
entries = runParser (dconfParser Normal) () "<test>"
writer ref x = modifyIORef ref (`T.append` x)

inputTestProperty :: FilePath -> Root -> Property
inputTestProperty name root =
inputTestProperty :: Style -> FilePath -> Root -> Property
inputTestProperty s name root =
let input = "test/data/" <> name <> ".settings"
output = "test/output/" <> name <> ".nix"
in baseProperty input output root
output = "test/output/" <> name <> suffix <> ".nix"
suffix = case s of
HomeManager -> ""
NixOS -> "-nixos"
in baseProperty s input output root
6 changes: 4 additions & 2 deletions test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ module Main where

import Control.Monad ( unless )
import DConfTest ( dconfParserTests )
import DConf2NixTest ( dconf2nixTests )
import DConf2NixTest ( dconf2nixHmTests, dconf2nixNixOSTests )
import Hedgehog
import System.Exit

main :: IO ()
main = do
results <- sequence
[checkParallel dconfParserTests, checkParallel dconf2nixTests]
[ checkParallel dconfParserTests
, checkParallel dconf2nixHmTests
, checkParallel dconf2nixNixOSTests]
unless (and results) exitFailure
Loading

0 comments on commit 153a3e9

Please sign in to comment.