{-# language MultiWayIf #-} {-# language TypeFamilies #-} {-# language RecordWildCards #-} module Main ( main ) where import Nix.Prelude import Relude as Prelude ( force ) import Control.Comonad ( extract ) import qualified Control.Exception as Exception import GHC.Err ( errorWithoutStackTrace ) import Control.Monad.Free import Control.Monad.Ref ( MonadRef(readRef) ) import Control.Monad.Catch import System.IO ( hPutStrLn ) import qualified Data.HashMap.Lazy as M import qualified Data.Map as Map import Data.Time import qualified Data.Text.IO as Text import Text.Show.Pretty ( ppShow ) import Nix hiding ( force ) import Nix.Convert import Nix.Json import Nix.Options.Parser import Nix.Standard import Nix.Thunk.Basic import Nix.Type.Env ( Env(..) ) import Nix.Type.Type ( Scheme ) import qualified Nix.Type.Infer as HM import Nix.Value.Monad import Options.Applicative hiding ( ParserResult(..) ) import Prettyprinter hiding ( list ) import Prettyprinter.Render.Text ( renderIO ) import qualified Repl import Nix.Eval main :: IO () main = do currentTime <- getCurrentTime opts <- execParser $ nixOptionsInfo currentTime main' opts main' :: Options -> IO () main' opts@Options{..} = runWithBasicEffectsIO opts execContentsFilesOrRepl where -- 2021-07-15: NOTE: This logic should be weaved stronger through CLI options logic (OptParse-Applicative code) -- As this logic is not stated in the CLI documentation, for example. So user has no knowledge of these. execContentsFilesOrRepl :: StdIO execContentsFilesOrRepl = fromMaybe loadFromCliFilePathList $ loadBinaryCacheFile <|> loadLiteralExpression <|> loadExpressionFromFile where -- | The base case: read expressions from the last CLI directive (@[FILE]@) listed on the command line. loadFromCliFilePathList :: StdIO loadFromCliFilePathList = case getFilePaths of [] -> runRepl ["-"] -> readExpressionFromStdin _paths -> processSeveralFiles (coerce _paths) where -- | Fall back to running the REPL runRepl = withEmptyNixContext Repl.main readExpressionFromStdin = processExpr =<< liftIO Text.getContents processSeveralFiles :: [Path] -> StdIO processSeveralFiles = traverse_ processFile where processFile path = handleResult (pure path) =<< parseNixFileLoc path -- | The `--read` option: load expression from a serialized file. loadBinaryCacheFile :: Maybe StdIO loadBinaryCacheFile = (\ (binaryCacheFile :: Path) -> do let file = replaceExtension binaryCacheFile "nixc" processCLIOptions (pure file) =<< liftIO (readCache binaryCacheFile) ) <$> getReadFrom -- | The `--expr` option: read expression from the argument string loadLiteralExpression :: Maybe StdIO loadLiteralExpression = processExpr <$> getExpression -- | The `--file` argument: read expressions from the files listed in the argument file loadExpressionFromFile :: Maybe StdIO loadExpressionFromFile = -- We can start use Text as in the base case, requires changing Path -> Text -- But that is a gradual process: -- https://github.com/haskell-nix/hnix/issues/912 (processSeveralFiles . (coerce . toString <$>) . lines <=< liftIO) . (\case "-" -> Text.getContents _fp -> readFile _fp ) <$> getFromFile processExpr :: Text -> StdIO processExpr = handleResult mempty . parseNixTextLoc withEmptyNixContext = withNixContext mempty -- 2021-07-15: NOTE: @handleResult@ & @process@ - have atrocious size & compexity, they need to be decomposed & refactored. handleResult mpath = either (\ err -> bool errorWithoutStackTrace (liftIO . hPutStrLn stderr) isIgnoreErrors $ "Parse failed: " <> show err ) (\ expr -> do when isCheck $ do expr' <- liftIO $ reduceExpr mpath expr either (\ err -> errorWithoutStackTrace $ "Type error: " <> ppShow err) (liftIO . putStrLn . (<>) "Type of expression: " . ppShow . maybeToMonoid . Map.lookup @VarName @[Scheme] "it" . coerce ) $ HM.inferTop mempty $ curry one "it" $ stripAnnotation expr' -- liftIO $ putStrLn $ runST $ -- runLintM opts . renderSymbolic =<< lint opts expr catch (processCLIOptions mpath expr) $ \case NixException frames -> errorWithoutStackTrace . show =<< renderFrames @StdVal @StdThun frames when isRepl $ withEmptyNixContext $ bool Repl.main ((Repl.main' . pure) =<< nixEvalExprLoc (coerce mpath) expr) isEvaluate ) -- 2021-07-15: NOTE: Logic of CLI Option processing is scattered over several functions, needs to be consolicated. processCLIOptions :: Maybe Path -> NExprLoc -> StdIO processCLIOptions mpath expr | isEvaluate = if | isTrace -> evaluateExprWith nixTracingEvalExprLoc expr | Just path <- getReduce -> evaluateExprWith (reduction path . coerce) expr | null getArg || null getArgstr -> evaluateExprWith nixEvalExprLoc expr | otherwise -> processResult printer <=< nixEvalExprLoc (coerce mpath) $ expr | isXml = fail "Rendering expression trees to XML is not yet implemented" | isJson = fail "Rendering expression trees to JSON is not implemented" | getVerbosity >= DebugInfo = liftIO . putStr . ppShow . stripAnnotation $ expr | isCache , Just path <- mpath = liftIO . writeCache (replaceExtension path "nixc") $ expr | isParseOnly = void . liftIO . Exception.evaluate . force $ expr | otherwise = liftIO . renderIO stdout . layoutPretty (LayoutOptions $ AvailablePerLine 80 0.4) . prettyNix . stripAnnotation $ expr where evaluateExprWith evaluator = evaluateExpression (coerce mpath) evaluator printer printer :: StdVal -> StdIO printer | isFinder = findAttrs <=< fromValue @(AttrSet StdVal) | otherwise = printer' where -- 2021-05-27: NOTE: With naive fix of the #941 -- This is overall a naive printer implementation, as options should interact/respect one another. -- A nice question: "Should respect one another to what degree?": Go full combinator way, for which -- old Nix CLI is nototrious for (and that would mean to reimplement the old Nix CLI), -- OR: https://github.com/haskell-nix/hnix/issues/172 and have some sane standart/default behaviour for (most) keys. printer' | isXml = out (ignoreContext . toXML) normalForm | isJson = out (ignoreContext . mempty . toJSONNixString) normalForm | isStrict = out (show . prettyNValue) normalForm | isValues = out (show . prettyNValueProv) removeEffects | otherwise = out (show . prettyNValue) removeEffects where out :: (b -> Text) -> (a -> StandardIO b) -> a -> StdIO out transform val = liftIO . Text.putStrLn . transform <=< val findAttrs :: AttrSet StdVal -> StdIO findAttrs = go mempty where go :: Text -> AttrSet StdVal -> StdIO go prefix s = traverse_ (\ (k, mv) -> do let path = prefix <> k (report, descend) = filterEntry path k when report $ do liftIO $ Text.putStrLn path when descend $ maybe stub (\case NVSet _ s' -> go (path <> ".") s' _ -> stub ) mv ) =<< traverse (\ (k, nv) -> (k, ) <$> free (\ (StdThunk (extract -> Thunk _ _ ref)) -> do let path = prefix <> k (_, descend) = filterEntry path k val <- readRef @StandardIO ref bool (pure Nothing) (forceEntry path nv) (descend && deferred (const False) (const True) val ) ) (pure . pure . Free) nv ) (sortWith fst $ M.toList $ M.mapKeys coerce s) where filterEntry path k = case (path, k) of ("stdenv", "stdenv" ) -> (True , True ) (_ , "stdenv" ) -> (False, False) (_ , "out" ) -> (True , False) (_ , "src" ) -> (True , False) (_ , "mirrorsFile" ) -> (True , False) (_ , "buildPhase" ) -> (True , False) (_ , "builder" ) -> (False, False) (_ , "drvPath" ) -> (False, False) (_ , "outPath" ) -> (False, False) (_ , "__impureHostDeps") -> (False, False) (_ , "__sandboxProfile") -> (False, False) ("pkgs" , "pkgs" ) -> (True , True ) (_ , "pkgs" ) -> (False, False) (_ , "drvAttrs" ) -> (False, False) _ -> (True , True ) forceEntry :: MonadValue a StandardIO => Text -> a -> StandardIO (Maybe a) forceEntry k v = catch (pure <$> demand v) fun where fun :: NixException -> StandardIO (Maybe a) fun (coerce -> frames) = do liftIO . Text.putStrLn . (("Exception forcing " <> k <> ": ") <>) . show =<< renderFrames @StdVal @StdThun frames pure Nothing reduction path mpathToContext annExpr = do eres <- withNixContext mpathToContext $ reducingEvalExpr evalContent mpathToContext annExpr handleReduced path eres handleReduced :: (MonadThrow m, MonadIO m) => Path -> (NExprLoc, Either SomeException (NValue t f m)) -> m (NValue t f m) handleReduced (coerce -> path) (expr', eres) = do liftIO $ do putStrLn $ "Wrote sifted expression tree to " <> path writeFile path $ show $ prettyNix $ stripAnnotation expr' either throwM pure eres