packages feed

cutesetup-0.1.0.0: src/NixGen.hs

module NixGen
  ( GenerateConfig (..)
  , generateFlake
  , generateEnvrc
  , generateBoth
  ) where

import Data.List (intercalate, nub)
import Languages (Language (..), LangPackage (..))
import NixSearch (Channel (..))
import Control.Exception (try, SomeException)
import System.Directory (doesFileExist, createDirectoryIfMissing)
import System.FilePath ((</>))

data GenerateConfig = GenerateConfig
  { gcLang         :: Language
  , gcPackages     :: [LangPackage]   -- ^ selected packages
  , gcExtraAttrs   :: [(String, Channel)] -- ^ extra nixpkgs attr names with their channel from search
  , gcTargetDir    :: FilePath
  , gcSystemArch   :: String          -- ^ e.g. "x86_64-linux"
  , gcPythonVenv   :: Bool            -- ^ toggle to enter virtualenv on shell hook
  , gcNixpkgsStable :: String         -- ^ stable nixpkgs branch/tag (e.g. "nixos-25.11")
  } deriving (Show)

-- Indent a string by n spaces
indent :: Int -> String -> String
indent n s = replicate n ' ' ++ s

-- Generate flake.nix content
generateFlake :: GenerateConfig -> String
generateFlake cfg =
  let baseAttrs = nub $ map pkgName (gcPackages cfg)
      unstableExtras = nub [attr | (attr, Unstable) <- gcExtraAttrs cfg]
      stableExtras = nub [attr | (attr, Stable) <- gcExtraAttrs cfg]
      
      unstableLine = map (\a -> indent 12 ("pkgs." ++ a)) (baseAttrs ++ unstableExtras)
      stableLine   = map (\a -> indent 12 ("pkgs-stable." ++ a)) stableExtras
      pkgsLines    = intercalate "\n" (unstableLine ++ stableLine)

      shellHookStr = if gcPythonVenv cfg
        then unlines
             [ "          shellHook = ''"
             , "            echo \"🚀  " ++ show (gcLang cfg) ++ " devshell ready!\""
             , "            if [ ! -d .venv ]; then"
             , "              echo \"Creating virtual environment in .venv...\""
             , "              python3 -m venv .venv"
             , "            fi"
             , "            echo \"Activating virtual environment...\""
             , "            source .venv/bin/activate"
             , "          '';"
             ]
        else unlines
             [ "          shellHook = ''"
             , "            echo \"🚀  " ++ show (gcLang cfg) ++ " devshell ready!\""
             , "          '';"
             ]
  in unlines
  [ "{"
  , "  description = \"" ++ show (gcLang cfg) ++ " development environment\";"
  , ""
  , "  inputs = {"
  , "    nixpkgs.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";"
  , "    nixpkgs-stable.url = \"github:NixOS/nixpkgs/" ++ gcNixpkgsStable cfg ++ "\";"
  , "    flake-utils.url = \"github:numtide/flake-utils\";"
  , "  };"
  , ""
  , "  outputs = { self, nixpkgs, nixpkgs-stable, flake-utils, ... }:"
  , "    flake-utils.lib.eachDefaultSystem (system:"
  , "      let"
  , "        pkgs = nixpkgs.legacyPackages.${system};"
  , "        pkgs-stable = nixpkgs-stable.legacyPackages.${system};"
  , "      in {"
  , "        devShells.default = pkgs.mkShell {"
  , "          nativeBuildInputs = ["
  , pkgsLines
  , "          ];"
  , ""
  , init (shellHookStr) -- remove trailing newline for better formatting inside unlines
  , "        };"
  , "      }"
  , "    );"
  , "}"
  ]

-- Generate .envrc content
generateEnvrc :: GenerateConfig -> String
generateEnvrc _ = unlines
  [ "# Generated by cutesetup"
  , "use flake"
  ]

-- Write both files to disk, backing up existing ones
generateBoth :: GenerateConfig -> IO (Either String String)
generateBoth cfg = do
  let dir      = gcTargetDir cfg
      flakePath = dir </> "flake.nix"
      envrcPath = dir </> ".envrc"

  res <- try @SomeException $ do
    createDirectoryIfMissing True dir
    backupIfExists flakePath
    backupIfExists envrcPath
    writeFile flakePath (generateFlake cfg)
    writeFile envrcPath (generateEnvrc cfg)

  return $ case res of
    Left err -> Left ("File write failed: " ++ show err)
    Right _  -> Right $ "✅  Written:\n  " ++ flakePath ++ "\n  " ++ envrcPath

backupIfExists :: FilePath -> IO ()
backupIfExists fp = do
  exists <- doesFileExist fp
  if exists
    then do
      let backup = fp ++ ".bak"
      content <- readFile fp
      writeFile backup content
    else return ()