gachi-cli-0.1.0.0: src/Gachi/CLI/Options.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Module: Gachi.CLI.Options
-- Description: Command-line option parsing and data structures for gachi
--
-- This module defines the complete command hierarchy, flags, execution modes,
-- and applicative parsers for the unified @gachi@ command-line interface.
--
-- = Architectural Context & Invariants
--
-- Per Note [CLI Ergonomics and Non-Destructive Initialization]
-- [ref:note-cli-init-ergonomics], the command-line interface balances declarative
-- precision (loading typed Dhall schemas) with rapid-prototyping convenience
-- (CLI flags and interactive prompts).
--
-- == Command Hierarchy and Strategic Roadmap
--
-- The Gachi CLI establishes five primary operational subcommands designed to
-- govern the entire repository lifecycle across the ecosystem:
--
-- 1. __Workspace Initialization ('init'):__
-- Bootstraps a complete, standardized multi-package repository skeleton adhering to
-- all five verification gates immediately upon creation (Milestone 2.3).
-- Supports declarative Dhall files (@--config FILE@), CLI flag overrides, and
-- an interactive step-by-step terminal wizard (@--interactive@ / @-i@).
--
-- 2. __Harmonic Configuration Propagation ('sync'):__
-- Propagates unified toolchain pins, linters, pre-commit hooks, and verification
-- harnesses across heterogeneous repositories (Phase 3). Evaluates differential
-- changes using a bidirectional merge strategy without manual editing.
--
-- 3. __Non-Destructive Codebase Retrofitting ('retrofit'):__
-- Migrates legacy or non-standard Haskell repositories into the canonical Gachi
-- multi-package workspace layout (Phase 4). Preserves existing business logic while
-- inserting the universal verification infrastructure.
--
-- 4. __Roadmap & Snapshot Governance ('plan'):__
-- Interacts with detached Git storage and immutable ledger checkpoints (Phase 5).
-- Maintains atomic session states, branch histories, and multi-step rollbacks.
--
-- 5. __Version & Toolchain Diagnostics ('version'):__
-- Emits detailed build metadata, including active GHC compiler pinning (GHC 9.14.1),
-- Cabal specification target (Cabal 3.18), and licensing status (EUPL-1.2).
--
-- == Safety & Verification Invariants
--
-- * __Fail-Safe Defaults:__ All filesystem mutations default to 'FailOnExisting'
-- mode, preventing accidental clobbering of uncommitted user files or directories.
-- * __Dry-Run Capability:__ Every mutating subcommand provides a @--dry-run@ flag
-- enabling developers to inspect planned actions and file trees prior to execution.
-- * __Total Failure Modes:__ Option parsers fail cleanly with informative usage
-- diagnostics on invalid inputs rather than terminating with runtime exceptions.
--
-- = Worked Example
--
-- >>> import Options.Applicative
-- >>> let parseArgs = execParserPure defaultPrefs (info (commandParser "0.1.0.0") idm)
-- >>> case parseArgs ["init", "my-project", "--no-git"] of { Success (CmdInit opts) -> initNoGit opts; _ -> False }
-- True
module Gachi.CLI.Options
( -- * High-Level CLI Command Types
Command(..)
, InitOptions(..)
, SyncOptions(..)
, DoctorOptions(..)
, RetrofitOptions(..)
, PlanOptions(..)
-- * Mode and List Parsers
, parseMode
, parsePackageList
-- * Optparse-Applicative Parsers
, commandParser
, initOptionsParser
, syncOptionsParser
, doctorOptionsParser
, retrofitOptionsParser
, planOptionsParser
) where
import Data.Char (toLower)
import Data.Text (Text)
import qualified Data.Text as T
import Options.Applicative
( Parser
, ParserInfo
, command
, eitherReader
, fullDesc
, header
, help
, helper
, hsubparser
, info
, long
, many
, metavar
, option
, optional
, progDesc
, short
, strArgument
, strOption
, switch
, value
)
import Gachi.Scaffold (ScaffoldMode(..))
--------------------------------------------------------------------------------
-- 1. Command-Line AST & Option Types
--------------------------------------------------------------------------------
-- | Top-level command dispatcher covering all ecosystem operational subcommands.
data Command
= CmdInit !InitOptions
-- ^ Initialize a new standardized multi-package workspace ('gachi init').
| CmdSync !SyncOptions
-- ^ Harmonically synchronize configuration across repositories ('gachi sync').
| CmdDoctor !DoctorOptions
-- ^ Diagnose workspace health, toolchain binaries, and 1-D cellular sheaf coherence ('gachi doctor').
| CmdRetrofit !RetrofitOptions
-- ^ Non-destructively retrofit an existing codebase ('gachi retrofit').
| CmdPlan !PlanOptions
-- ^ Manage ecosystem strategic roadmaps and rollback ('gachi plan').
| CmdVersion
-- ^ Print toolchain version, GHC pinning, and licensing stanzas.
deriving stock (Eq, Show)
-- | Configuration parameters controlling the 'gachi init' scaffolding engine.
--
-- Governs pure specification resolution, terminal interaction, target destination paths,
-- filesystem write modes, and automated Git governance bootstrapping.
data InitOptions = InitOptions
{ initProjectName :: !(Maybe Text)
-- ^ Positional project name (e.g., @"gongju"@). When supplied, defines the canonical
-- project identifier, directory basename, Cabal workspace name, and verification harness slug.
, initConfigFile :: !(Maybe FilePath)
-- ^ Optional path to declarative Dhall configuration file (@--config FILE@ / @-c FILE@).
-- When specified, the schema is hermetically evaluated from disk, superseding positional defaults.
, initTargetDir :: !(Maybe FilePath)
-- ^ Destination directory path (@--target-dir DIR@ / @-d DIR@).
-- Defaults to @./<project-name>@ when omitted. Created recursively if non-existent.
, initMode :: !ScaffoldMode
-- ^ Filesystem write mode: 'FailOnExisting' (abort on collision), 'SkipExisting' (preserve
-- pre-existing files untouched), or 'OverwriteExisting' (clobber existing destination files).
, initAuthorName :: !(Maybe Text)
-- ^ Override for author full personal or organizational name (@--author-name NAME@).
-- Overrides values detected from ambient Git configuration (@user.name@).
, initAuthorEmail :: !(Maybe Text)
-- ^ Override for author contact email address (@--author-email EMAIL@).
-- Overrides values detected from ambient Git configuration (@user.email@).
, initDescription :: !(Maybe Text)
-- ^ Override for high-level project synopsis or mission description (@--desc TEXT@).
-- Embedded directly in generated README.md, cabal files, and package documentation.
, initPackages :: ![Text]
-- ^ Optional comma-separated list of satellite packages to instantiate in the workspace.
-- Each package receives a dedicated cabal manifest, test suite, and module hierarchy.
, initNoGit :: !Bool
-- ^ Skip hermetic Git repository initialization and .githooks configuration (@--no-git@).
-- Intended for generating sub-trees inside existing Git working copies or container sandboxes.
, initDryRun :: !Bool
-- ^ Preview file tree generation and permissions without writing to disk (@--dry-run@).
-- Prints an audit table containing relative paths, character counts, and executable marks.
, initInteractive :: !Bool
-- ^ Interactively prompt for missing project parameters (@--interactive@ / @-i@).
-- Launches terminal wizard offering intelligent defaults drawn from ambient Git configuration.
} deriving stock (Eq, Show)
-- | Configuration parameters controlling Phase 3 'gachi sync'.
--
-- Used to synchronize standardized ecosystem configuration across disparate repositories.
data SyncOptions = SyncOptions
{ syncTargetDir :: !(Maybe FilePath)
-- ^ Target repository checkout directory to synchronize (@--target DIR@ / @-t DIR@).
-- Defaults to current working directory when omitted.
, syncDryRun :: !Bool
-- ^ Preview differential synchronization changes without mutating filesystem (@--dry-run@).
-- Displays unified diffs and file permission updates planned by the synchronization engine.
, syncAll :: !Bool
-- ^ Discover and synchronize all sibling repositories across the ecosystem parent directory (@--all@ / @-a@).
, syncRemotes :: !Bool
-- ^ Audit and synchronize git remote replication topology (@--remotes@ / @-r@).
} deriving stock (Eq, Show)
-- | Configuration parameters controlling the 'gachi doctor' diagnostic command.
--
-- Evaluates ambient system toolchains, repository health, and executes 1-D cellular
-- sheaf cohomology audits to detect and localize cross-repository contract drift.
data DoctorOptions = DoctorOptions
{ doctorTargetDir :: !(Maybe FilePath)
-- ^ Target repository directory to inspect (@--target DIR@ / @-t DIR@).
-- Defaults to current working directory when omitted.
, doctorSheaf :: !Bool
-- ^ Run 1-D cellular sheaf cohomology coherence audit (@--sheaf@ / @-s@).
, doctorAll :: !Bool
-- ^ Discover and audit all sibling repositories across ecosystem parent directory (@--all@ / @-a@).
} deriving stock (Eq, Show)
-- | Configuration parameters controlling Phase 4 'gachi retrofit'.
--
-- Used to migrate legacy or ad-hoc Haskell codebases into the standardized ecosystem hierarchy.
data RetrofitOptions = RetrofitOptions
{ retrofitTargetDir :: !(Maybe FilePath)
-- ^ Legacy repository checkout directory to inspect and overlay (@--target DIR@ / @-t DIR@).
-- Defaults to current working directory when omitted.
, retrofitDryRun :: !Bool
-- ^ Preview retrofitting actions without modifying existing source files (@--dry-run@).
-- Reports missing verification harnesses, hooks, and documentation skeletons.
, retrofitMarkdown :: !Bool
-- ^ Output diagnostic report in GitHub-Flavored Markdown format (@--markdown@ / @-m@).
} deriving stock (Eq, Show)
-- | Configuration parameters controlling Phase 5 'gachi plan'.
--
-- Interacts with immutable ledger checkpoints and atomic rollback via detached git storage.
data PlanOptions = PlanOptions
{ planSubcommand :: !Text
-- ^ Plan governance subcommand (e.g. @"checkpoint"@, @"diff"@, @"log"@, @"rollback"@).
-- Dictates state store transitions and audit trail queries.
, planArgs :: ![Text]
-- ^ Additional positional or keyword arguments supplied to the plan action.
-- Passed verbatim to the underlying detached Git storage engine.
} deriving stock (Eq, Show)
--------------------------------------------------------------------------------
-- 2. Parsing Helpers
--------------------------------------------------------------------------------
-- | Parses a textual write mode into a canonical 'ScaffoldMode'.
--
-- Accepts case-insensitive strings: @"fail"@, @"skip"@, or @"overwrite"@.
parseMode :: String -> Either String ScaffoldMode
parseMode raw = case map toLower raw of
"fail" -> Right FailOnExisting
"skip" -> Right SkipExisting
"overwrite" -> Right OverwriteExisting
other -> Left ("Invalid write mode '" ++ other ++ "'. Expected 'fail', 'skip', or 'overwrite'.")
-- | Parses a comma-delimited string of package names into a cleaned list.
parsePackageList :: String -> Either String [Text]
parsePackageList raw =
let pieces = map T.strip (T.splitOn "," (T.pack raw))
filtered = filter (not . T.null) pieces
in Right filtered
--------------------------------------------------------------------------------
-- 3. Top-Level Optparse Parser
--------------------------------------------------------------------------------
-- | Constructs the top-level 'ParserInfo' for the @gachi@ executable.
commandParser :: Text -> ParserInfo Command
commandParser ver =
info (helper <*> mainParser)
( fullDesc
<> header ("gachi v" ++ T.unpack ver ++ " — Ecosystem Skeleton Generator & Chattering Synchronizer")
<> progDesc "Declarative Haskell project scaffolding, configuration propagation, and plan governance."
)
-- | Dispatches across CLI subcommands.
mainParser :: Parser Command
mainParser = hsubparser
( command "init"
(info (CmdInit <$> initOptionsParser)
(progDesc "Initialize a new standardized multi-package Haskell workspace or satellite package"))
<> command "sync"
(info (CmdSync <$> syncOptionsParser)
(progDesc "Harmonically synchronize configuration and governance stanzas across repositories (Phase 3)"))
<> command "doctor"
(info (CmdDoctor <$> doctorOptionsParser)
(progDesc "Diagnose workspace health, toolchain binaries, and 1-D cellular sheaf coherence (Milestone 3.5)"))
<> command "retrofit"
(info (CmdRetrofit <$> retrofitOptionsParser)
(progDesc "Non-destructively retrofit an existing codebase into the canonical ecosystem skeleton (Phase 4)"))
<> command "plan"
(info (CmdPlan <$> planOptionsParser)
(progDesc "Manage ecosystem strategic roadmaps and atomic rollback via detached git storage (Phase 5)"))
<> command "version"
(info (pure CmdVersion)
(progDesc "Display gachi version, compiler pinning, and licensing stanzas"))
)
--------------------------------------------------------------------------------
-- 4. Subcommand Parsers
--------------------------------------------------------------------------------
-- | Applicative parser for 'InitOptions'.
--
-- Assembles options governing the 'gachi init' subcommand:
-- * Positional argument: Optional project identifier slug (e.g. @"gongju"@).
-- * Configuration flag (@--config@ / @-c@): Path to declarative Dhall schema file.
-- * Target directory (@--target-dir@ / @-d@): Output path (defaults to @./<project-name>@).
-- * Write mode (@--mode@ / @-m@): Safety policy ('FailOnExisting', 'SkipExisting', 'OverwriteExisting').
-- * Metadata overrides: @--author-name@, @--author-email@, and @--desc@.
-- * Satellite packages (@--packages@): Comma-delimited list of packages to instantiate.
-- * Operational switches: @--no-git@ (skip VCS setup), @--dry-run@ (simulation), @-i@ (interactive wizard).
initOptionsParser :: Parser InitOptions
initOptionsParser = InitOptions
<$> optional
( strArgument
( metavar "PROJECT_NAME"
<> help "Name of the project to initialize (e.g., 'gongju')"
)
)
<*> optional
( strOption
( long "config"
<> short 'c'
<> metavar "FILE"
<> help "Path to declarative Dhall project specification file (.dhall)"
)
)
<*> optional
( strOption
( long "target-dir"
<> short 'd'
<> metavar "DIR"
<> help "Destination directory for generated workspace (defaults to ./<project-name>)"
)
)
<*> option (eitherReader parseMode)
( long "mode"
<> short 'm'
<> metavar "MODE"
<> value FailOnExisting
<> help "Filesystem write mode: 'fail', 'skip', or 'overwrite' (default: 'fail')"
)
<*> optional
( strOption
( long "author-name"
<> metavar "NAME"
<> help "Author full name override"
)
)
<*> optional
( strOption
( long "author-email"
<> metavar "EMAIL"
<> help "Author email address override"
)
)
<*> optional
( strOption
( long "desc"
<> metavar "TEXT"
<> help "High-level project synopsis or description override"
)
)
<*> option (eitherReader parsePackageList)
( long "packages"
<> metavar "PKG1,PKG2,..."
<> value []
<> help "Comma-separated list of satellite packages to instantiate"
)
<*> switch
( long "no-git"
<> help "Skip Git repository initialization and .githooks configuration"
)
<*> switch
( long "dry-run"
<> help "Simulate scaffolding generation and print summary without writing to disk"
)
<*> switch
( long "interactive"
<> short 'i'
<> help "Interactively prompt for missing configuration values"
)
-- | Applicative parser for 'SyncOptions'.
--
-- Parses flags for cross-repository configuration synchronization (Phase 3):
-- * Target repository directory (@--target@ / @-t@).
-- * Dry-run simulation switch (@--dry-run@).
-- * All repositories discovery switch (@--all@ / @-a@).
syncOptionsParser :: Parser SyncOptions
syncOptionsParser = SyncOptions
<$> optional
( strOption
( long "target"
<> short 't'
<> metavar "DIR"
<> help "Target repository directory to synchronize"
)
)
<*> switch
( long "dry-run"
<> help "Preview synchronization diffs without mutating filesystem"
)
<*> switch
( long "all"
<> short 'a'
<> help "Discover and synchronize all repositories across the ecosystem parent directory"
)
<*> switch
( long "remotes"
<> short 'r'
<> help "Audit and synchronize git remote replication topology"
)
-- | Applicative parser for 'DoctorOptions'.
--
-- Parses flags for workspace diagnostics and cellular sheaf coherence auditing (Milestone 3.5):
-- * Target repository directory (@--target@ / @-t@).
-- * Cellular sheaf coherence audit switch (@--sheaf@ / @-s@).
-- * All ecosystem repositories switch (@--all@ / @-a@).
doctorOptionsParser :: Parser DoctorOptions
doctorOptionsParser = DoctorOptions
<$> optional
( strOption
( long "target"
<> short 't'
<> metavar "DIR"
<> help "Target repository directory to diagnose (defaults to current directory)"
)
)
<*> switch
( long "sheaf"
<> short 's'
<> help "Evaluate 1-D cellular sheaf coherence and localize H^1 boundary obstructions"
)
<*> switch
( long "all"
<> short 'a'
<> help "Audit all discovered ecosystem repositories across the parent directory"
)
-- | Applicative parser for 'RetrofitOptions'.
--
-- Parses flags for non-destructive legacy codebase migration (Phase 4):
-- * Target codebase directory (@--target@ / @-t@).
-- * Dry-run simulation switch (@--dry-run@).
retrofitOptionsParser :: Parser RetrofitOptions
retrofitOptionsParser = RetrofitOptions
<$> optional
( strOption
( long "target"
<> short 't'
<> metavar "DIR"
<> help "Legacy repository directory to inspect and overlay"
)
)
<*> switch
( long "dry-run"
<> help "Preview retrofitting actions without modifying existing files"
)
<*> switch
( long "markdown"
<> short 'm'
<> help "Format diagnostic inspection report as GitHub-Flavored Markdown"
)
-- | Applicative parser for 'PlanOptions'.
--
-- Parses commands and arguments for detached Git state store management (Phase 5):
-- * Action subcommand (@checkpoint@, @diff@, @log@, @rollback@).
-- * Trailing positional arguments supplied directly to the plan engine.
planOptionsParser :: Parser PlanOptions
planOptionsParser = PlanOptions
<$> strArgument
( metavar "SUBCOMMAND"
<> help "Plan action: checkpoint, diff, log, or rollback"
)
<*> many
( strArgument
( metavar "ARGS..."
<> help "Arguments supplied to the plan action"
)
)