diff --git a/README.md b/README.md index 5f688b7..b31663f 100644 --- a/README.md +++ b/README.md @@ -6,41 +6,47 @@ Converge notifies the user via libnotify / `notify-send` whenever a merge confli ## Usage -### Single repository +### Configuration + +Converge looks for its config file at `$XDG_CONFIG_HOME/converge/config.yaml` (typically `~/.config/converge/config.yaml`). If found, it uses that file automatically. ```bash -# Watch the current directory (defaults) +# Run with the default XDG config file ./scripts/run - -# Watch a specific repo -./scripts/run -- --repo /path/to/repo --branch master --debounce 10000 ``` -### Multiple repositories (config file) +You can also specify a config file explicitly: ```bash -# Create a config file -cp converge.example.yaml converge.yaml -# Edit to list your repos -vim converge.yaml +./scripts/run -- --config /path/to/converge.yaml +``` -# Run with config -./scripts/run -- --config converge.yaml +### Single repository (no config file) + +If no config file exists, converge falls back to watching a single repository: + +```bash +# Watch the current directory +./scripts/run + +# Watch a specific repo with options +./scripts/run -- --repo /path/to/repo --branch master --debounce 10000 ``` ### Options | Option | Default | Description | |--------|---------|-------------| -| `-c`, `--config FILE` | — | Path to YAML config file listing repositories | -| `-r`, `--repo PATH` | `.` (cwd) | Path to a single git repository | -| `-d`, `--debounce MS` | `5000` | Debounce window in milliseconds (overrides config) | -| `--remote NAME` | `origin` | Remote name (single-repo mode) | -| `-b`, `--branch NAME` | `main` | Branch to pull (single-repo mode) | +| `-c`, `--config FILE` | `$XDG_CONFIG_HOME/converge/config.yaml` | Path to YAML config file listing repositories | +| `-r`, `--repo PATH` | `.` (cwd) | Path to a single git repository (single-repo fallback mode) | +| `-d`, `--debounce MS` | `5000` | Debounce window in milliseconds (overrides config file) | +| `--remote NAME` | `origin` | Remote name (single-repo fallback mode) | +| `-b`, `--branch NAME` | `main` | Branch to pull (single-repo fallback mode) | ### Config file format ```yaml +# ~/.config/converge/config.yaml (XDG default) repos: - path: /absolute/or/relative/path remote: origin # optional, defaults to "origin" @@ -74,5 +80,5 @@ All development tools run inside Docker via the `hs` wrapper script. - **File watching**: [fsnotify](https://hackage.haskell.org/package/fsnotify) - **Concurrency**: one watcher thread per repository via `async` - **Notifications**: shell out to `notify-send` (libnotify) -- **Configuration**: YAML config file for multi-repo, CLI flags for single-repo +- **Configuration**: YAML config file at XDG config path for multi-repo, CLI flags for single-repo fallback - **Daemonization**: TBD (likely systemd service) diff --git a/app/Main.hs b/app/Main.hs index 1f09554..249b3c7 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -76,27 +76,40 @@ main :: IO () main = do cli <- execParser parserInfo let mDebounceOverride = cliDebounce cli - options <- case cliConfig cli of + (options, configSource) <- case cliConfig cli of Just cfgPath -> do cfg <- loadConfig cfgPath - let opts = configToOptions cfg - pure $ case mDebounceOverride of - Just ms -> opts{optDebounceMs = ms} - Nothing -> opts - Nothing -> - pure $ - defaultOptions - { optRepos = - [ defaultRepoConfig - { rcPath = fromMaybe "." (cliRepo cli) - , rcRemote = cliRemote cli - , rcBranch = cliBranch cli + let opts = applyDebounce mDebounceOverride (configToOptions cfg) + pure (opts, Just cfgPath) + Nothing -> do + exists <- configFileExists + if exists + then do + cfgPath <- defaultConfigPath + cfg <- loadConfig cfgPath + let opts = applyDebounce mDebounceOverride (configToOptions cfg) + pure (opts, Just cfgPath) + else + pure + ( defaultOptions + { optRepos = + [ defaultRepoConfig + { rcPath = fromMaybe "." (cliRepo cli) + , rcRemote = cliRemote cli + , rcBranch = cliBranch cli + } + ] + , optDebounceMs = fromMaybe 5000 mDebounceOverride } - ] - , optDebounceMs = fromMaybe 5000 mDebounceOverride - } + , Nothing + ) + case configSource of + Just path -> + hPutStrLn stderr ("Using config: " <> path) + Nothing -> + hPutStrLn stderr "No config file found, using single-repo mode" hPutStrLn stderr $ - "Converge watching " + "Watching " <> show (length (optRepos options)) <> " repo(s)" <> " (debounce: " @@ -104,3 +117,7 @@ main = do <> "ms)" mapM_ (\r -> hPutStrLn stderr (" - " <> rcPath r)) (optRepos options) runConverge options + +applyDebounce :: Maybe Int -> Options -> Options +applyDebounce (Just ms) opts = opts{optDebounceMs = ms} +applyDebounce Nothing opts = opts diff --git a/converge.example.yaml b/converge.example.yaml index d3f3c05..518a87f 100644 --- a/converge.example.yaml +++ b/converge.example.yaml @@ -1,5 +1,6 @@ # Example converge configuration file. -# Usage: converge --config converge.yaml +# Place at ~/.config/converge/config.yaml to use by default, +# or pass explicitly with: converge --config converge.yaml repos: - path: /home/jbrechtel/org diff --git a/src/Converge.hs b/src/Converge.hs index af4f561..3476df9 100644 --- a/src/Converge.hs +++ b/src/Converge.hs @@ -9,6 +9,8 @@ module Converge ( Config (..), loadConfig, configToOptions, + defaultConfigPath, + configFileExists, -- * Running runConverge, @@ -31,7 +33,7 @@ import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Yaml (FromJSON (..), decodeFileThrow, withObject, (.:), (.:?)) -import System.Directory (getCurrentDirectory) +import System.Directory (XdgDirectory (XdgConfig), doesFileExist, getCurrentDirectory, getXdgDirectory) import System.Exit (ExitCode (..)) import System.FSNotify import System.FilePath (isAbsolute, ()) @@ -114,6 +116,14 @@ configToOptions cfg = , optDebounceMs = fromMaybe 5000 (cfgDebounceMs cfg) } +-- | The default XDG config file path: @$XDG_CONFIG_HOME/converge/config.yaml@. +defaultConfigPath :: IO FilePath +defaultConfigPath = ( "converge/config.yaml") <$> getXdgDirectory XdgConfig "" + +-- | Check whether the default config file exists. +configFileExists :: IO Bool +configFileExists = defaultConfigPath >>= doesFileExist + ---------------------------------------------------------------------- -- Running ----------------------------------------------------------------------