packages feed

autodocodec-nix 0.0.1.5 → 0.1.0.0

raw patch · 6 files changed

+558/−420 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Autodocodec.Nix: append :: [Text] -> Text -> [Text]
- Autodocodec.Nix: appendWith :: Text -> [Text] -> Text -> [Text]
- Autodocodec.Nix: apply :: [Text] -> [Text] -> [Text]
- Autodocodec.Nix: data Expr
- Autodocodec.Nix: indent :: [Text] -> [Text]
- Autodocodec.Nix: instance GHC.Classes.Eq Autodocodec.Nix.Expr
- Autodocodec.Nix: instance GHC.Classes.Eq Autodocodec.Nix.Option
- Autodocodec.Nix: instance GHC.Classes.Eq Autodocodec.Nix.OptionType
- Autodocodec.Nix: instance GHC.Classes.Ord Autodocodec.Nix.Expr
- Autodocodec.Nix: instance GHC.Classes.Ord Autodocodec.Nix.Option
- Autodocodec.Nix: instance GHC.Classes.Ord Autodocodec.Nix.OptionType
- Autodocodec.Nix: instance GHC.Show.Show Autodocodec.Nix.Expr
- Autodocodec.Nix: instance GHC.Show.Show Autodocodec.Nix.Option
- Autodocodec.Nix: instance GHC.Show.Show Autodocodec.Nix.OptionType
- Autodocodec.Nix: jsonValueExpr :: Value -> Expr
- Autodocodec.Nix: parens :: [Text] -> [Text]
- Autodocodec.Nix: prepend :: Text -> [Text] -> [Text]
- Autodocodec.Nix: prependWith :: Text -> Text -> [Text] -> [Text]
- Autodocodec.Nix: renderOptions :: Map Text Option -> Text
- Autodocodec.Nix: simplifyOptions :: Map Text Option -> Map Text Option
- Autodocodec.Nix: surround :: Text -> Text -> [Text] -> [Text]
- Autodocodec.Nix: surroundWith :: Text -> Text -> Text -> [Text] -> [Text]
+ Autodocodec.Nix: data Expression
+ Autodocodec.Nix: objectToNixExpressionVia :: ObjectCodec a void -> a -> Map Text Expression
+ Autodocodec.Nix: objectToNixExpressionViaCodec :: HasObjectCodec a => a -> Map Text Expression
+ Autodocodec.Nix: optionExpression :: Option -> Expression
+ Autodocodec.Nix: optionTypeExpression :: OptionType -> Expression
+ Autodocodec.Nix: optionsExpression :: Map Text Option -> Expression
+ Autodocodec.Nix: renderExpression :: Expr -> Text
+ Autodocodec.Nix: toNixExpressionVia :: ValueCodec a void -> a -> Expression
+ Autodocodec.Nix: toNixExpressionViaCodec :: HasCodec a => a -> Expression
+ Autodocodec.Nix: type Expr = Expression
+ Autodocodec.Nix.Expression: ExprAp :: !Expression -> !Expression -> Expression
+ Autodocodec.Nix.Expression: ExprAttrSet :: !Map Text Expression -> Expression
+ Autodocodec.Nix.Expression: ExprFun :: ![Text] -> !Expression -> Expression
+ Autodocodec.Nix.Expression: ExprLitBool :: !Bool -> Expression
+ Autodocodec.Nix.Expression: ExprLitList :: ![Expression] -> Expression
+ Autodocodec.Nix.Expression: ExprLitNumber :: !Scientific -> Expression
+ Autodocodec.Nix.Expression: ExprLitString :: !Text -> Expression
+ Autodocodec.Nix.Expression: ExprNull :: Expression
+ Autodocodec.Nix.Expression: ExprVar :: !Text -> Expression
+ Autodocodec.Nix.Expression: ExprWith :: !Text -> !Expression -> Expression
+ Autodocodec.Nix.Expression: data Expression
+ Autodocodec.Nix.Expression: instance GHC.Classes.Eq Autodocodec.Nix.Expression.Expression
+ Autodocodec.Nix.Expression: instance GHC.Classes.Ord Autodocodec.Nix.Expression.Expression
+ Autodocodec.Nix.Expression: instance GHC.Show.Show Autodocodec.Nix.Expression.Expression
+ Autodocodec.Nix.Expression: jsonObjectExpression :: Object -> Map Text Expression
+ Autodocodec.Nix.Expression: jsonValueExpression :: Value -> Expression
+ Autodocodec.Nix.Expression: renderExpr :: Expr -> Text
+ Autodocodec.Nix.Expression: renderExpression :: Expr -> Text
+ Autodocodec.Nix.Expression: type Expr = Expression
+ Autodocodec.Nix.Options: Option :: !Maybe OptionType -> !Maybe Text -> !Maybe Value -> Option
+ Autodocodec.Nix.Options: OptionTypeAttrsOf :: !OptionType -> OptionType
+ Autodocodec.Nix.Options: OptionTypeEnum :: ![Expression] -> OptionType
+ Autodocodec.Nix.Options: OptionTypeListOf :: !OptionType -> OptionType
+ Autodocodec.Nix.Options: OptionTypeNull :: OptionType
+ Autodocodec.Nix.Options: OptionTypeNullOr :: !OptionType -> OptionType
+ Autodocodec.Nix.Options: OptionTypeOneOf :: ![OptionType] -> OptionType
+ Autodocodec.Nix.Options: OptionTypeSimple :: !Text -> OptionType
+ Autodocodec.Nix.Options: OptionTypeSubmodule :: !Map Text Option -> OptionType
+ Autodocodec.Nix.Options: [optionDefault] :: Option -> !Maybe Value
+ Autodocodec.Nix.Options: [optionDescription] :: Option -> !Maybe Text
+ Autodocodec.Nix.Options: [optionType] :: Option -> !Maybe OptionType
+ Autodocodec.Nix.Options: data Option
+ Autodocodec.Nix.Options: data OptionType
+ Autodocodec.Nix.Options: emptyOption :: Option
+ Autodocodec.Nix.Options: instance GHC.Classes.Eq Autodocodec.Nix.Options.Option
+ Autodocodec.Nix.Options: instance GHC.Classes.Eq Autodocodec.Nix.Options.OptionType
+ Autodocodec.Nix.Options: instance GHC.Classes.Ord Autodocodec.Nix.Options.Option
+ Autodocodec.Nix.Options: instance GHC.Classes.Ord Autodocodec.Nix.Options.OptionType
+ Autodocodec.Nix.Options: instance GHC.Show.Show Autodocodec.Nix.Options.Option
+ Autodocodec.Nix.Options: instance GHC.Show.Show Autodocodec.Nix.Options.OptionType
+ Autodocodec.Nix.Options: objectCodecNixOptions :: ObjectCodec input output -> Map Text Option
+ Autodocodec.Nix.Options: optionExpr :: Option -> Expression
+ Autodocodec.Nix.Options: optionExpression :: Option -> Expression
+ Autodocodec.Nix.Options: optionTypeExpr :: OptionType -> Expression
+ Autodocodec.Nix.Options: optionTypeExpression :: OptionType -> Expression
+ Autodocodec.Nix.Options: optionsExpr :: Map Text Option -> Expression
+ Autodocodec.Nix.Options: optionsExpression :: Map Text Option -> Expression
+ Autodocodec.Nix.Options: renderExpression :: Expr -> Text
+ Autodocodec.Nix.Options: renderNixOptionTypeVia :: ValueCodec input output -> Text
+ Autodocodec.Nix.Options: renderNixOptionTypeViaCodec :: forall a. HasCodec a => Text
+ Autodocodec.Nix.Options: renderNixOptionsVia :: ObjectCodec input output -> Text
+ Autodocodec.Nix.Options: renderNixOptionsViaCodec :: forall a. HasObjectCodec a => Text
+ Autodocodec.Nix.Options: renderOption :: Option -> Text
+ Autodocodec.Nix.Options: renderOptionType :: OptionType -> Text
+ Autodocodec.Nix.Options: simplifyOption :: Option -> Option
+ Autodocodec.Nix.Options: simplifyOptionType :: OptionType -> OptionType
+ Autodocodec.Nix.Options: valueCodecNixOptionType :: ValueCodec input output -> Maybe OptionType
+ Autodocodec.Nix.Options: withNixArgs :: Expression -> Expression
+ Autodocodec.Nix.Render: objectToNixExpressionVia :: ObjectCodec a void -> a -> Map Text Expression
+ Autodocodec.Nix.Render: objectToNixExpressionViaCodec :: HasObjectCodec a => a -> Map Text Expression
+ Autodocodec.Nix.Render: renderExpression :: Expr -> Text
+ Autodocodec.Nix.Render: toNixExpressionVia :: ValueCodec a void -> a -> Expression
+ Autodocodec.Nix.Render: toNixExpressionViaCodec :: HasCodec a => a -> Expression
- Autodocodec.Nix: ExprAp :: !Expr -> !Expr -> Expr
+ Autodocodec.Nix: ExprAp :: !Expression -> !Expression -> Expression
- Autodocodec.Nix: ExprAttrSet :: !Map Text Expr -> Expr
+ Autodocodec.Nix: ExprAttrSet :: !Map Text Expression -> Expression
- Autodocodec.Nix: ExprFun :: ![Text] -> !Expr -> Expr
+ Autodocodec.Nix: ExprFun :: ![Text] -> !Expression -> Expression
- Autodocodec.Nix: ExprLitBool :: !Bool -> Expr
+ Autodocodec.Nix: ExprLitBool :: !Bool -> Expression
- Autodocodec.Nix: ExprLitList :: ![Expr] -> Expr
+ Autodocodec.Nix: ExprLitList :: ![Expression] -> Expression
- Autodocodec.Nix: ExprLitNumber :: !Scientific -> Expr
+ Autodocodec.Nix: ExprLitNumber :: !Scientific -> Expression
- Autodocodec.Nix: ExprLitString :: !Text -> Expr
+ Autodocodec.Nix: ExprLitString :: !Text -> Expression
- Autodocodec.Nix: ExprNull :: Expr
+ Autodocodec.Nix: ExprNull :: Expression
- Autodocodec.Nix: ExprVar :: !Text -> Expr
+ Autodocodec.Nix: ExprVar :: !Text -> Expression
- Autodocodec.Nix: ExprWith :: !Text -> !Expr -> Expr
+ Autodocodec.Nix: ExprWith :: !Text -> !Expression -> Expression
- Autodocodec.Nix: OptionTypeEnum :: ![Expr] -> OptionType
+ Autodocodec.Nix: OptionTypeEnum :: ![Expression] -> OptionType
- Autodocodec.Nix: optionExpr :: Option -> Expr
+ Autodocodec.Nix: optionExpr :: Option -> Expression
- Autodocodec.Nix: optionTypeExpr :: OptionType -> Expr
+ Autodocodec.Nix: optionTypeExpr :: OptionType -> Expression
- Autodocodec.Nix: optionsExpr :: Map Text Option -> Expr
+ Autodocodec.Nix: optionsExpr :: Map Text Option -> Expression
- Autodocodec.Nix: withNixArgs :: Expr -> Expr
+ Autodocodec.Nix: withNixArgs :: Expression -> Expression

Files

CHANGELOG.md view
@@ -1,5 +1,16 @@ # Changelog +## [0.1.0.0] - 2025-02-13++### Added++* The `Autodocodec.Nix.Render` module for rendering values as Nix expressions.++### Changed++* Moved the options code into `Autodocodec.Nix.Options`.+* Moved the expressions code into `Autodocodec.Nix.Expression`.+ ## [0.0.1.5] - 2024-11-04  ### Changed
autodocodec-nix.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           autodocodec-nix-version:        0.0.1.5+version:        0.1.0.0 synopsis:       Autodocodec interpreters for nix homepage:       https://github.com/NorfairKing/autodocodec#readme bug-reports:    https://github.com/NorfairKing/autodocodec/issues@@ -26,6 +26,9 @@ library   exposed-modules:       Autodocodec.Nix+      Autodocodec.Nix.Expression+      Autodocodec.Nix.Options+      Autodocodec.Nix.Render   other-modules:       Paths_autodocodec_nix   hs-source-dirs:
src/Autodocodec/Nix.hs view
@@ -1,11 +1,4 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# OPTIONS_GHC -fno-warn-dodgy-exports -fno-warn-duplicate-exports #-}+{-# OPTIONS_GHC -fno-warn-dodgy-exports #-}  module Autodocodec.Nix   ( -- * Producing a Nixos module type@@ -16,424 +9,34 @@     valueCodecNixOptionType,     objectCodecNixOptions,     Option (..),+    emptyOption,+    simplifyOption,     OptionType (..),+    simplifyOptionType,     renderOption,     renderOptionType,+    withNixArgs,+    optionExpression,     optionExpr,+    optionsExpression,     optionsExpr,+    optionTypeExpression,     optionTypeExpr,-    renderExpr, -    -- * To makes sure we definitely export everything.-    module Autodocodec.Nix,+    -- * Rendering Haskell values as Nix values+    toNixExpressionViaCodec,+    toNixExpressionVia,+    objectToNixExpressionViaCodec,+    objectToNixExpressionVia,++    -- * Expressions+    Expression (..),+    Expr,+    renderExpression,+    renderExpr,   ) where -import Autodocodec-import Control.Applicative-import Data.Aeson as JSON-import qualified Data.Aeson.Key as Key-import qualified Data.Aeson.KeyMap as KeyMap-import Data.Containers.ListUtils-import qualified Data.HashMap.Strict as HM-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as M-import Data.Maybe-import Data.Scientific-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Vector as V--renderNixOptionTypeViaCodec :: forall a. (HasCodec a) => Text-renderNixOptionTypeViaCodec = renderNixOptionTypeVia (codec @a)--renderNixOptionsViaCodec :: forall a. (HasObjectCodec a) => Text-renderNixOptionsViaCodec = renderNixOptionsVia (objectCodec @a)--renderNixOptionTypeVia :: ValueCodec input output -> Text-renderNixOptionTypeVia =-  renderOptionType-    . fromMaybe (OptionTypeSimple "lib.types.anything")-    . valueCodecNixOptionType--renderNixOptionsVia :: ObjectCodec input output -> Text-renderNixOptionsVia =-  renderOptions-    . objectCodecNixOptions--valueCodecNixOptionType :: ValueCodec input output -> Maybe OptionType-valueCodecNixOptionType = fmap simplifyOptionType . go-  where-    mTyp = fromMaybe $ OptionTypeSimple "lib.types.anything"-    go :: ValueCodec input output -> Maybe OptionType-    go = \case-      NullCodec -> Just OptionTypeNull-      BoolCodec _ -> Just $ OptionTypeSimple "lib.types.bool"-      StringCodec _ -> Just $ OptionTypeSimple "lib.types.str"-      IntegerCodec _ bounds -> Just $-        OptionTypeSimple $-          case guessIntegerBoundsSymbolic bounds of-            BitUInt w -> case w of-              64 -> "lib.types.ints.unsigned"-              32 -> "lib.types.ints.u32"-              16 -> "lib.types.ints.u16"-              8 -> "lib.types.ints.u8"-              _ -> "lib.types.int" -- TODO bounds?-            BitSInt w -> case w of-              64 -> "lib.types.int"-              32 -> "lib.types.ints.s32"-              16 -> "lib.types.ints.s16"-              8 -> "lib.types.ints.s8"-              _ -> "lib.types.int" -- TODO bounds?-            OtherIntegerBounds _ _ -> "lib.types.int" -- TODO bounds?-      NumberCodec _ _ -> Just $ OptionTypeSimple "lib.types.number"-      HashMapCodec c -> Just $ OptionTypeAttrsOf $ mTyp $ go c-      MapCodec c -> Just $ OptionTypeAttrsOf $ mTyp $ go c-      ValueCodec -> Just (OptionTypeSimple "lib.types.unspecified")-      ArrayOfCodec _ c -> Just $ OptionTypeListOf $ mTyp $ go c-      ObjectOfCodec _ oc -> Just (OptionTypeSubmodule (objectCodecNixOptions oc))-      EqCodec v c -> Just $ OptionTypeEnum [jsonValueExpr $ toJSONVia c v]-      BimapCodec _ _ c -> go c-      EitherCodec _ c1 c2 -> Just $ OptionTypeOneOf (map mTyp [go c1, go c2])-      CommentCodec _ c -> go c-      ReferenceCodec {} -> Nothing -- TODO: let-binding?---- [tag:NixOptionNullable]--- Note about nullable options:--- It's not technically accurate to represent optional fields as the 'null' value in Nix,--- but Nix isn't very good at optional values at all, so we use 'null' for both--- optional fields and nullable fields.--- If Nix options ever figure out how to do optional fields, we'll use that--- instead.-objectCodecNixOptions :: ObjectCodec input output -> Map Text Option-objectCodecNixOptions = simplifyOptions . go False-  where-    -- The bool means 'force optional'-    go :: Bool -> ObjectCodec input output -> Map Text Option-    go b = \case-      DiscriminatedUnionCodec k _ m ->-        M.insert-          k-          ( Option-              { optionType = Just $ OptionTypeOneOf $ map (OptionTypeSimple . T.pack . show) $ HM.keys m,-                optionDescription = Nothing,-                optionDefault = Nothing-              }-          )-          $ M.unionsWith-            ( \t1 t2 ->-                Option-                  { optionType = Just $ OptionTypeOneOf $ map (fromMaybe (OptionTypeSimple "lib.types.anything") . optionType) [t1, t2],-                    optionDescription = optionDescription t1 <|> optionDescription t2, -- TODO-                    optionDefault = Nothing-                  }-            )-          $ map (go b . snd)-          $ HM.elems m-      RequiredKeyCodec key o mDesc ->-        M.singleton key $-          Option-            { optionType =-                ( if b-                    then fmap OptionTypeNullOr-                    else id-                )-                  $ valueCodecNixOptionType o,-              optionDescription = mDesc,-              optionDefault =-                if b-                  then Just JSON.Null-                  else Nothing -- [ref:NixOptionNullable]-            }-      OptionalKeyCodec key o mDesc ->-        M.singleton key $-          Option-            { optionType = OptionTypeNullOr <$> valueCodecNixOptionType o,-              optionDescription = mDesc,-              optionDefault = Just JSON.Null -- [ref:NixOptionNullable]-            }-      OptionalKeyWithDefaultCodec key c defaultValue mDesc ->-        M.singleton-          key-          Option-            { optionType = valueCodecNixOptionType c,-              optionDescription = mDesc,-              optionDefault = Just $ toJSONVia c defaultValue-            }-      OptionalKeyWithOmittedDefaultCodec key c defaultValue mDesc ->-        M.singleton-          key-          Option-            { optionType = valueCodecNixOptionType c,-              optionDescription = mDesc,-              optionDefault = Just $ toJSONVia c defaultValue-            }-      PureCodec _ -> M.empty-      ApCodec c1 c2 -> M.unionWith mergeOption (go b c1) (go b c2)-      BimapCodec _ _ c -> go b c-      EitherCodec _ c1 c2 -> M.unionWith mergeOption (go True c1) (go True c2)-    -- This throwing away of the description and the default is not ideal but-    -- better than just taking the first option.-    mergeOption :: Option -> Option -> Option-    mergeOption o1 o2 =-      o1-        { optionType =-            ( \ot1 ot2 ->-                simplifyOptionType $-                  OptionTypeOneOf-                    [ ot1,-                      ot2-                    ]-            )-              <$> optionType o1-              <*> optionType o2-        }--data Option = Option-  { optionType :: !(Maybe OptionType),-    optionDescription :: !(Maybe Text),-    optionDefault :: !(Maybe JSON.Value)-  }-  deriving (Show, Eq, Ord)--emptyOption :: Option-emptyOption =-  Option-    { optionType = Nothing,-      optionDescription = Nothing,-      optionDefault = Nothing-    }--simplifyOption :: Option -> Option-simplifyOption o = o {optionType = simplifyOptionType <$> optionType o}--data OptionType-  = OptionTypeNull-  | OptionTypeSimple !Text-  | OptionTypeEnum ![Expr]-  | OptionTypeNullOr !OptionType-  | OptionTypeListOf !OptionType-  | OptionTypeAttrsOf !OptionType-  | OptionTypeOneOf ![OptionType]-  | OptionTypeSubmodule !(Map Text Option)-  deriving (Show, Eq, Ord)--simplifyOptionType :: OptionType -> OptionType-simplifyOptionType = go-  where-    go = \case-      OptionTypeNull -> OptionTypeNull-      OptionTypeSimple t -> OptionTypeSimple t-      OptionTypeEnum es -> OptionTypeEnum es-      OptionTypeNullOr t -> case t of-        OptionTypeNull -> OptionTypeNull-        OptionTypeNullOr t' -> go $ OptionTypeNullOr t'-        OptionTypeOneOf os -> OptionTypeNullOr $ go $ OptionTypeOneOf $ filter (/= OptionTypeNull) $ map go os-        _ -> OptionTypeNullOr $ go t-      OptionTypeListOf o -> OptionTypeListOf $ go o-      OptionTypeAttrsOf o -> OptionTypeAttrsOf $ go o-      OptionTypeOneOf os -> case goEnums $ nubOrd $ concatMap goOr os of-        [ot] -> ot-        os' ->-          if any canBeNull os'-            then go $ OptionTypeNullOr $ case mapMaybe stripNull os' of-              [t] -> t-              ts' -> OptionTypeOneOf ts'-            else OptionTypeOneOf os'-      OptionTypeSubmodule m -> OptionTypeSubmodule $ M.map goOpt m--    canBeNull :: OptionType -> Bool-    canBeNull = \case-      OptionTypeNull -> True-      OptionTypeNullOr _ -> True-      _ -> False--    stripNull :: OptionType -> Maybe OptionType-    stripNull = \case-      OptionTypeNull -> Nothing-      OptionTypeNullOr t -> Just t-      t -> Just t--    goEnums :: [OptionType] -> [OptionType]-    goEnums = goEnum []-      where-        goEnum :: [Expr] -> [OptionType] -> [OptionType]-        goEnum es = \case-          [] -> case es of-            [] -> []-            _ -> [OptionTypeEnum es]-          (t : rest) -> case t of-            OptionTypeEnum es' -> goEnum (es ++ es') rest-            _ -> t : goEnum es rest--    goOpt o = o {optionType = go <$> optionType o}--    goOr = \case-      OptionTypeOneOf os -> concatMap goOr os-      o -> [o]--simplifyOptions :: Map Text Option -> Map Text Option-simplifyOptions = M.map simplifyOption--renderOption :: Option -> Text-renderOption = renderExpr . withNixArgs . optionExpr--renderOptions :: Map Text Option -> Text-renderOptions = renderExpr . withNixArgs . optionsExpr--renderOptionType :: OptionType -> Text-renderOptionType = renderExpr . withNixArgs . optionTypeExpr--withNixArgs :: Expr -> Expr-withNixArgs = ExprFun ["lib"]--optionExpr :: Option -> Expr-optionExpr Option {..} =-  ExprAp-    (ExprVar "lib.mkOption")-    ( ExprAttrSet $-        M.fromList $-          concat-            [ [("type", optionTypeExpr typ) | typ <- maybeToList optionType],-              [("description", ExprLitString d) | d <- maybeToList optionDescription],-              case optionDefault of-                Nothing -> case optionType of-                  -- Automatically give submodule options a default of the empty set.-                  Just (OptionTypeSubmodule _) -> [("default", ExprAttrSet M.empty)]-                  _ -> []-                Just d -> [("default", jsonValueExpr d)]-            ]-    )--optionsExpr :: Map Text Option -> Expr-optionsExpr = ExprAttrSet . M.map optionExpr--optionTypeExpr :: OptionType -> Expr-optionTypeExpr = go-  where-    go = \case-      OptionTypeNull -> ExprAp (ExprVar "lib.types.enum") (ExprLitList [ExprNull])-      OptionTypeSimple s -> ExprVar s-      OptionTypeEnum es -> ExprAp (ExprVar "lib.types.enum") (ExprLitList es)-      OptionTypeNullOr ot -> ExprAp (ExprVar "lib.types.nullOr") (go ot)-      OptionTypeListOf ot ->-        ExprAp-          (ExprVar "lib.types.listOf")-          (go ot)-      OptionTypeAttrsOf ot ->-        ExprAp-          (ExprVar "lib.types.attrsOf")-          (go ot)-      OptionTypeOneOf os ->-        ExprAp-          (ExprVar "lib.types.oneOf")-          (ExprLitList (map go os))-      OptionTypeSubmodule os ->-        ExprAp-          (ExprVar "lib.types.submodule")-          (ExprAttrSet (M.singleton "options" (optionsExpr os)))--jsonValueExpr :: JSON.Value -> Expr-jsonValueExpr = \case-  JSON.Null -> ExprNull-  JSON.Bool b -> ExprLitBool b-  JSON.String s -> ExprLitString s-  JSON.Number n -> ExprLitNumber n-  JSON.Array v -> ExprLitList $ map jsonValueExpr $ V.toList v-  JSON.Object vs -> ExprAttrSet $ M.mapKeysMonotonic Key.toText $ KeyMap.toMap $ KeyMap.map jsonValueExpr vs--data Expr-  = ExprNull-  | ExprLitBool !Bool-  | ExprLitString !Text-  | ExprLitNumber !Scientific-  | ExprLitList ![Expr]-  | ExprVar !Text-  | ExprAttrSet !(Map Text Expr)-  | ExprAp !Expr !Expr-  | ExprFun ![Text] !Expr-  | ExprWith !Text !Expr-  deriving (Show, Eq, Ord)--renderExpr :: Expr -> Text-renderExpr = T.unlines . go 0-  where-    parensWhen b ts = if b then parens ts else ts-    go :: Int -> Expr -> [Text]-    go d = \case-      ExprNull -> ["null"]-      ExprLitBool b -> [if b then "true" else "false"]-      ExprLitString s -> [T.pack $ show $ T.unpack s]-      ExprLitNumber s ->-        [ case floatingOrInteger s of-            Left f -> T.pack $ show (f :: Double)-            Right i -> T.pack $ show (i :: Integer)-        ]-      ExprLitList es -> case es of-        [] -> ["[]"]-        [e] -> surround "[" "]" $ go 0 e-        _ ->-          -- If there is more than one list element, put them on separate lines.-          "[" : indent (concatMap (go 11) es) ++ ["]"]-      ExprVar s -> [s]-      ExprAttrSet m | null m -> ["{ }"]-      ExprAttrSet m ->-        -- We always put "{" and "}" on separate lines.-        "{" : indent (concatMap (uncurry goBind) (M.toList m)) ++ ["}"]-      ExprAp e1 e2 ->-        parensWhen (d > 10) $-          go 11 e1 `apply` go 11 e2-      ExprFun args e ->-        parensWhen (d > 10) $-          surroundWith " " "{" "}:" [T.intercalate ", " args]-            ++ go 0 e-      ExprWith t e ->-        parensWhen (d > 10) $-          ("with " <> t <> ";") : go 0 e-    goBind key e =-      prependWith " " (key <> " =") $-        (`append` ";") $-          go 0 e--indent :: [Text] -> [Text]-indent = map ("  " <>)--prepend :: Text -> [Text] -> [Text]-prepend = prependWith T.empty--prependWith :: Text -> Text -> [Text] -> [Text]-prependWith spacer t = \case-  [] -> [t]-  (u : us) -> (t <> spacer <> u) : us--append :: [Text] -> Text -> [Text]-append = appendWith T.empty--appendWith :: Text -> [Text] -> Text -> [Text]-appendWith spacer ts u = go ts-  where-    go = \case-      [] -> [u]-      [t] -> [t <> spacer <> u]-      (t : ts') -> t : go ts'--apply :: [Text] -> [Text] -> [Text]-apply ts1 ts2 = case (ts1, ts2) of-  ([t1], [t2]) -> [t1 <> " " <> t2]-  ([t1], _) -> prependWith " " t1 ts2-  (_, [t2]) -> ts1 `append` t2-  _ -> go ts1-    where-      go = \case-        [] -> ts2-        [t] -> prependWith " " t ts2-        (t : ts) -> t : go ts--parens :: [Text] -> [Text]-parens = surround "(" ")"--surround :: Text -> Text -> [Text] -> [Text]-surround = surroundWith T.empty--surroundWith :: Text -> Text -> Text -> [Text] -> [Text]-surroundWith spacer open close = prependWith spacer open . (\t -> appendWith spacer t close)+import Autodocodec.Nix.Expression+import Autodocodec.Nix.Options+import Autodocodec.Nix.Render
+ src/Autodocodec/Nix/Expression.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-dodgy-exports -fno-warn-duplicate-exports #-}++module Autodocodec.Nix.Expression+  ( Expression (..),+    Expr,+    renderExpression,+    renderExpr,+    jsonValueExpression,+    jsonObjectExpression,+  )+where++import Data.Aeson as JSON+import qualified Data.Aeson.Key as Key+import qualified Data.Aeson.KeyMap as KeyMap+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as M+import Data.Scientific+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Vector as V++-- For backward compatibility+-- {-# DEPRECATED "Use Expression instead" #-}+type Expr = Expression++data Expression+  = ExprNull+  | ExprLitBool !Bool+  | ExprLitString !Text+  | ExprLitNumber !Scientific+  | ExprLitList ![Expression]+  | ExprVar !Text+  | ExprAttrSet !(Map Text Expression)+  | ExprAp !Expression !Expression+  | ExprFun ![Text] !Expression+  | ExprWith !Text !Expression+  deriving (Show, Eq, Ord)++-- {-# DEPRECATED renderExpr "Use renderExpression instead" #-}+renderExpr :: Expr -> Text+renderExpr = renderExpression++renderExpression :: Expr -> Text+renderExpression = T.unlines . go 0+  where+    parensWhen b ts = if b then parens ts else ts+    go :: Int -> Expr -> [Text]+    go d = \case+      ExprNull -> ["null"]+      ExprLitBool b -> [if b then "true" else "false"]+      ExprLitString s -> [T.pack $ show $ T.unpack s]+      ExprLitNumber s ->+        [ case floatingOrInteger s of+            Left f -> T.pack $ show (f :: Double)+            Right i -> T.pack $ show (i :: Integer)+        ]+      ExprLitList es -> case es of+        [] -> ["[]"]+        [e] -> surround "[" "]" $ go 0 e+        _ ->+          -- If there is more than one list element, put them on separate lines.+          "[" : indent (concatMap (go 11) es) ++ ["]"]+      ExprVar s -> [s]+      ExprAttrSet m | null m -> ["{ }"]+      ExprAttrSet m ->+        -- We always put "{" and "}" on separate lines.+        "{" : indent (concatMap (uncurry goBind) (M.toList m)) ++ ["}"]+      ExprAp e1 e2 ->+        parensWhen (d > 10) $+          go 11 e1 `apply` go 11 e2+      ExprFun args e ->+        parensWhen (d > 10) $+          surroundWith " " "{" "}:" [T.intercalate ", " args]+            ++ go 0 e+      ExprWith t e ->+        parensWhen (d > 10) $+          ("with " <> t <> ";") : go 0 e+    goBind key e =+      prependWith " " (key <> " =") $+        (`append` ";") $+          go 0 e++indent :: [Text] -> [Text]+indent = map ("  " <>)++prependWith :: Text -> Text -> [Text] -> [Text]+prependWith spacer t = \case+  [] -> [t]+  (u : us) -> (t <> spacer <> u) : us++append :: [Text] -> Text -> [Text]+append = appendWith T.empty++appendWith :: Text -> [Text] -> Text -> [Text]+appendWith spacer ts u = go ts+  where+    go = \case+      [] -> [u]+      [t] -> [t <> spacer <> u]+      (t : ts') -> t : go ts'++apply :: [Text] -> [Text] -> [Text]+apply ts1 ts2 = case (ts1, ts2) of+  ([t1], [t2]) -> [t1 <> " " <> t2]+  ([t1], _) -> prependWith " " t1 ts2+  (_, [t2]) -> ts1 `append` t2+  _ -> go ts1+    where+      go = \case+        [] -> ts2+        [t] -> prependWith " " t ts2+        (t : ts) -> t : go ts++parens :: [Text] -> [Text]+parens = surround "(" ")"++surround :: Text -> Text -> [Text] -> [Text]+surround = surroundWith T.empty++surroundWith :: Text -> Text -> Text -> [Text] -> [Text]+surroundWith spacer open close = prependWith spacer open . (\t -> appendWith spacer t close)++jsonValueExpression :: JSON.Value -> Expression+jsonValueExpression = go+  where+    go = \case+      JSON.Null -> ExprNull+      JSON.Bool b -> ExprLitBool b+      JSON.String s -> ExprLitString s+      JSON.Number n -> ExprLitNumber n+      JSON.Array v -> ExprLitList $ map go $ V.toList v+      JSON.Object vs -> ExprAttrSet $ jsonObjectExpression vs++jsonObjectExpression :: JSON.Object -> Map Text Expression+jsonObjectExpression = M.mapKeysMonotonic Key.toText . KeyMap.toMap . KeyMap.map jsonValueExpression
+ src/Autodocodec/Nix/Options.hs view
@@ -0,0 +1,348 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -fno-warn-dodgy-exports -fno-warn-duplicate-exports #-}++module Autodocodec.Nix.Options+  ( -- * Producing a Nixos module type+    renderNixOptionTypeViaCodec,+    renderNixOptionsViaCodec,+    renderNixOptionTypeVia,+    renderNixOptionsVia,+    valueCodecNixOptionType,+    objectCodecNixOptions,+    Option (..),+    emptyOption,+    simplifyOption,+    OptionType (..),+    simplifyOptionType,+    renderOption,+    renderOptionType,+    withNixArgs,+    optionExpression,+    optionExpr,+    optionsExpression,+    optionsExpr,+    optionTypeExpression,+    optionTypeExpr,+    renderExpression,+  )+where++import Autodocodec+import Autodocodec.Nix.Expression+import Autodocodec.Nix.Render+import Control.Applicative+import Data.Aeson as JSON+import Data.Containers.ListUtils+import qualified Data.HashMap.Strict as HM+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as M+import Data.Maybe+import Data.Text (Text)+import qualified Data.Text as T++renderNixOptionTypeViaCodec :: forall a. (HasCodec a) => Text+renderNixOptionTypeViaCodec = renderNixOptionTypeVia (codec @a)++renderNixOptionsViaCodec :: forall a. (HasObjectCodec a) => Text+renderNixOptionsViaCodec = renderNixOptionsVia (objectCodec @a)++renderNixOptionTypeVia :: ValueCodec input output -> Text+renderNixOptionTypeVia =+  renderOptionType+    . fromMaybe (OptionTypeSimple "lib.types.anything")+    . valueCodecNixOptionType++renderNixOptionsVia :: ObjectCodec input output -> Text+renderNixOptionsVia =+  renderOptions+    . objectCodecNixOptions++valueCodecNixOptionType :: ValueCodec input output -> Maybe OptionType+valueCodecNixOptionType = fmap simplifyOptionType . go+  where+    mTyp = fromMaybe $ OptionTypeSimple "lib.types.anything"+    go :: ValueCodec input output -> Maybe OptionType+    go = \case+      NullCodec -> Just OptionTypeNull+      BoolCodec _ -> Just $ OptionTypeSimple "lib.types.bool"+      StringCodec _ -> Just $ OptionTypeSimple "lib.types.str"+      IntegerCodec _ bounds -> Just $+        OptionTypeSimple $+          case guessIntegerBoundsSymbolic bounds of+            BitUInt w -> case w of+              64 -> "lib.types.ints.unsigned"+              32 -> "lib.types.ints.u32"+              16 -> "lib.types.ints.u16"+              8 -> "lib.types.ints.u8"+              _ -> "lib.types.int" -- TODO bounds?+            BitSInt w -> case w of+              64 -> "lib.types.int"+              32 -> "lib.types.ints.s32"+              16 -> "lib.types.ints.s16"+              8 -> "lib.types.ints.s8"+              _ -> "lib.types.int" -- TODO bounds?+            OtherIntegerBounds _ _ -> "lib.types.int" -- TODO bounds?+      NumberCodec _ _ -> Just $ OptionTypeSimple "lib.types.number"+      HashMapCodec c -> Just $ OptionTypeAttrsOf $ mTyp $ go c+      MapCodec c -> Just $ OptionTypeAttrsOf $ mTyp $ go c+      ValueCodec -> Just (OptionTypeSimple "lib.types.unspecified")+      ArrayOfCodec _ c -> Just $ OptionTypeListOf $ mTyp $ go c+      ObjectOfCodec _ oc -> Just (OptionTypeSubmodule (objectCodecNixOptions oc))+      EqCodec v c -> Just $ OptionTypeEnum [toNixExpressionVia c v]+      BimapCodec _ _ c -> go c+      EitherCodec _ c1 c2 -> Just $ OptionTypeOneOf (map mTyp [go c1, go c2])+      CommentCodec _ c -> go c+      ReferenceCodec {} -> Nothing -- TODO: let-binding?++-- [tag:NixOptionNullable]+-- Note about nullable options:+-- It's not technically accurate to represent optional fields as the 'null' value in Nix,+-- but Nix isn't very good at optional values at all, so we use 'null' for both+-- optional fields and nullable fields.+-- If Nix options ever figure out how to do optional fields, we'll use that+-- instead.+objectCodecNixOptions :: ObjectCodec input output -> Map Text Option+objectCodecNixOptions = simplifyOptions . go False+  where+    -- The bool means 'force optional'+    go :: Bool -> ObjectCodec input output -> Map Text Option+    go b = \case+      DiscriminatedUnionCodec k _ m ->+        M.insert+          k+          ( Option+              { optionType = Just $ OptionTypeOneOf $ map (OptionTypeSimple . T.pack . show) $ HM.keys m,+                optionDescription = Nothing,+                optionDefault = Nothing+              }+          )+          $ M.unionsWith+            ( \t1 t2 ->+                Option+                  { optionType = Just $ OptionTypeOneOf $ map (fromMaybe (OptionTypeSimple "lib.types.anything") . optionType) [t1, t2],+                    optionDescription = optionDescription t1 <|> optionDescription t2, -- TODO+                    optionDefault = Nothing+                  }+            )+          $ map (go b . snd)+          $ HM.elems m+      RequiredKeyCodec key o mDesc ->+        M.singleton key $+          Option+            { optionType =+                ( if b+                    then fmap OptionTypeNullOr+                    else id+                )+                  $ valueCodecNixOptionType o,+              optionDescription = mDesc,+              optionDefault =+                if b+                  then Just JSON.Null+                  else Nothing -- [ref:NixOptionNullable]+            }+      OptionalKeyCodec key o mDesc ->+        M.singleton key $+          Option+            { optionType = OptionTypeNullOr <$> valueCodecNixOptionType o,+              optionDescription = mDesc,+              optionDefault = Just JSON.Null -- [ref:NixOptionNullable]+            }+      OptionalKeyWithDefaultCodec key c defaultValue mDesc ->+        M.singleton+          key+          Option+            { optionType = valueCodecNixOptionType c,+              optionDescription = mDesc,+              optionDefault = Just $ toJSONVia c defaultValue+            }+      OptionalKeyWithOmittedDefaultCodec key c defaultValue mDesc ->+        M.singleton+          key+          Option+            { optionType = valueCodecNixOptionType c,+              optionDescription = mDesc,+              optionDefault = Just $ toJSONVia c defaultValue+            }+      PureCodec _ -> M.empty+      ApCodec c1 c2 -> M.unionWith mergeOption (go b c1) (go b c2)+      BimapCodec _ _ c -> go b c+      EitherCodec _ c1 c2 -> M.unionWith mergeOption (go True c1) (go True c2)+    -- This throwing away of the description and the default is not ideal but+    -- better than just taking the first option.+    mergeOption :: Option -> Option -> Option+    mergeOption o1 o2 =+      o1+        { optionType =+            ( \ot1 ot2 ->+                simplifyOptionType $+                  OptionTypeOneOf+                    [ ot1,+                      ot2+                    ]+            )+              <$> optionType o1+              <*> optionType o2+        }++data Option = Option+  { optionType :: !(Maybe OptionType),+    optionDescription :: !(Maybe Text),+    optionDefault :: !(Maybe JSON.Value)+  }+  deriving (Show, Eq, Ord)++emptyOption :: Option+emptyOption =+  Option+    { optionType = Nothing,+      optionDescription = Nothing,+      optionDefault = Nothing+    }++simplifyOption :: Option -> Option+simplifyOption o = o {optionType = simplifyOptionType <$> optionType o}++data OptionType+  = OptionTypeNull+  | OptionTypeSimple !Text+  | OptionTypeEnum ![Expression]+  | OptionTypeNullOr !OptionType+  | OptionTypeListOf !OptionType+  | OptionTypeAttrsOf !OptionType+  | OptionTypeOneOf ![OptionType]+  | OptionTypeSubmodule !(Map Text Option)+  deriving (Show, Eq, Ord)++simplifyOptionType :: OptionType -> OptionType+simplifyOptionType = go+  where+    go = \case+      OptionTypeNull -> OptionTypeNull+      OptionTypeSimple t -> OptionTypeSimple t+      OptionTypeEnum es -> OptionTypeEnum es+      OptionTypeNullOr t -> case t of+        OptionTypeNull -> OptionTypeNull+        OptionTypeNullOr t' -> go $ OptionTypeNullOr t'+        OptionTypeOneOf os -> OptionTypeNullOr $ go $ OptionTypeOneOf $ filter (/= OptionTypeNull) $ map go os+        _ -> OptionTypeNullOr $ go t+      OptionTypeListOf o -> OptionTypeListOf $ go o+      OptionTypeAttrsOf o -> OptionTypeAttrsOf $ go o+      OptionTypeOneOf os -> case goEnums $ nubOrd $ concatMap goOr os of+        [ot] -> ot+        os' ->+          if any canBeNull os'+            then go $ OptionTypeNullOr $ case mapMaybe stripNull os' of+              [t] -> t+              ts' -> OptionTypeOneOf ts'+            else OptionTypeOneOf os'+      OptionTypeSubmodule m -> OptionTypeSubmodule $ M.map goOpt m++    canBeNull :: OptionType -> Bool+    canBeNull = \case+      OptionTypeNull -> True+      OptionTypeNullOr _ -> True+      _ -> False++    stripNull :: OptionType -> Maybe OptionType+    stripNull = \case+      OptionTypeNull -> Nothing+      OptionTypeNullOr t -> Just t+      t -> Just t++    goEnums :: [OptionType] -> [OptionType]+    goEnums = goEnum []+      where+        goEnum :: [Expression] -> [OptionType] -> [OptionType]+        goEnum es = \case+          [] -> case es of+            [] -> []+            _ -> [OptionTypeEnum es]+          (t : rest) -> case t of+            OptionTypeEnum es' -> goEnum (es ++ es') rest+            _ -> t : goEnum es rest++    goOpt o = o {optionType = go <$> optionType o}++    goOr = \case+      OptionTypeOneOf os -> concatMap goOr os+      o -> [o]++simplifyOptions :: Map Text Option -> Map Text Option+simplifyOptions = M.map simplifyOption++renderOption :: Option -> Text+renderOption = renderExpression . withNixArgs . optionExpression++renderOptions :: Map Text Option -> Text+renderOptions = renderExpression . withNixArgs . optionsExpression++renderOptionType :: OptionType -> Text+renderOptionType = renderExpression . withNixArgs . optionTypeExpression++withNixArgs :: Expression -> Expression+withNixArgs = ExprFun ["lib"]++-- {-# DEPRECATED optionExpr "Use optionExpression instead" #-}+optionExpr :: Option -> Expression+optionExpr = optionExpression++optionExpression :: Option -> Expression+optionExpression Option {..} =+  ExprAp+    (ExprVar "lib.mkOption")+    ( ExprAttrSet $+        M.fromList $+          concat+            [ [("type", optionTypeExpression typ) | typ <- maybeToList optionType],+              [("description", ExprLitString d) | d <- maybeToList optionDescription],+              case optionDefault of+                Nothing -> case optionType of+                  -- Automatically give submodule options a default of the empty set.+                  Just (OptionTypeSubmodule _) -> [("default", ExprAttrSet M.empty)]+                  _ -> []+                Just d -> [("default", toNixExpressionViaCodec d)]+            ]+    )++-- {-# DEPRECATED optionsExpr "Use optionsExpression instead" #-}+optionsExpr :: Map Text Option -> Expression+optionsExpr = optionsExpression++optionsExpression :: Map Text Option -> Expression+optionsExpression = ExprAttrSet . M.map optionExpression++-- {-# DEPRECATED optionTypeExpr "Use optionTypeExpression instead" #-}+optionTypeExpr :: OptionType -> Expression+optionTypeExpr = optionTypeExpression++optionTypeExpression :: OptionType -> Expression+optionTypeExpression = go+  where+    go = \case+      OptionTypeNull -> ExprAp (ExprVar "lib.types.enum") (ExprLitList [ExprNull])+      OptionTypeSimple s -> ExprVar s+      OptionTypeEnum es -> ExprAp (ExprVar "lib.types.enum") (ExprLitList es)+      OptionTypeNullOr ot -> ExprAp (ExprVar "lib.types.nullOr") (go ot)+      OptionTypeListOf ot ->+        ExprAp+          (ExprVar "lib.types.listOf")+          (go ot)+      OptionTypeAttrsOf ot ->+        ExprAp+          (ExprVar "lib.types.attrsOf")+          (go ot)+      OptionTypeOneOf os ->+        ExprAp+          (ExprVar "lib.types.oneOf")+          (ExprLitList (map go os))+      OptionTypeSubmodule os ->+        ExprAp+          (ExprVar "lib.types.submodule")+          (ExprAttrSet (M.singleton "options" (optionsExpression os)))
+ src/Autodocodec/Nix/Render.hs view
@@ -0,0 +1,32 @@+module Autodocodec.Nix.Render+  ( toNixExpressionViaCodec,+    toNixExpressionVia,+    objectToNixExpressionViaCodec,+    objectToNixExpressionVia,+    renderExpression,+  )+where++import Autodocodec+import Autodocodec.Nix.Expression as Nix+import Data.Map (Map)+import Data.Text (Text)++toNixExpressionViaCodec :: (HasCodec a) => a -> Nix.Expression+toNixExpressionViaCodec = toNixExpressionVia codec++-- | Render a value as a nix expression via the given codec.+toNixExpressionVia :: ValueCodec a void -> a -> Nix.Expression+toNixExpressionVia c = jsonValueExpression . toJSONVia c++-- | Render an object as a nix attribute set via its 'HasObjectCodec' instance+--+-- Attrset+objectToNixExpressionViaCodec :: (HasObjectCodec a) => a -> Map Text Nix.Expression+objectToNixExpressionViaCodec = objectToNixExpressionVia objectCodec++-- | Render an object as a nix attribute set via the given object codec.+--+-- Attrset+objectToNixExpressionVia :: ObjectCodec a void -> a -> Map Text Nix.Expression+objectToNixExpressionVia c = jsonObjectExpression . toJSONObjectVia c