Use XDG config directory as default config path

- Look for config at $XDG_CONFIG_HOME/converge/config.yaml by default
- --config still overrides to an explicit path
- Falls back to single-repo --repo mode if no config file found
- New defaultConfigPath and configFileExists helpers in Converge module
This commit is contained in:
2026-05-11 10:11:34 -04:00
parent d6e15864f4
commit 97e129a855
4 changed files with 71 additions and 37 deletions
+24 -18
View File
@@ -6,41 +6,47 @@ Converge notifies the user via libnotify / `notify-send` whenever a merge confli
## Usage ## 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 ```bash
# Watch the current directory (defaults) # Run with the default XDG config file
./scripts/run ./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 ```bash
# Create a config file ./scripts/run -- --config /path/to/converge.yaml
cp converge.example.yaml converge.yaml ```
# Edit to list your repos
vim converge.yaml
# Run with config ### Single repository (no config file)
./scripts/run -- --config converge.yaml
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 ### Options
| Option | Default | Description | | Option | Default | Description |
|--------|---------|-------------| |--------|---------|-------------|
| `-c`, `--config FILE` | | Path to YAML config file listing repositories | | `-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 | | `-r`, `--repo PATH` | `.` (cwd) | Path to a single git repository (single-repo fallback mode) |
| `-d`, `--debounce MS` | `5000` | Debounce window in milliseconds (overrides config) | | `-d`, `--debounce MS` | `5000` | Debounce window in milliseconds (overrides config file) |
| `--remote NAME` | `origin` | Remote name (single-repo mode) | | `--remote NAME` | `origin` | Remote name (single-repo fallback mode) |
| `-b`, `--branch NAME` | `main` | Branch to pull (single-repo mode) | | `-b`, `--branch NAME` | `main` | Branch to pull (single-repo fallback mode) |
### Config file format ### Config file format
```yaml ```yaml
# ~/.config/converge/config.yaml (XDG default)
repos: repos:
- path: /absolute/or/relative/path - path: /absolute/or/relative/path
remote: origin # optional, defaults to "origin" 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) - **File watching**: [fsnotify](https://hackage.haskell.org/package/fsnotify)
- **Concurrency**: one watcher thread per repository via `async` - **Concurrency**: one watcher thread per repository via `async`
- **Notifications**: shell out to `notify-send` (libnotify) - **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) - **Daemonization**: TBD (likely systemd service)
+34 -17
View File
@@ -76,27 +76,40 @@ main :: IO ()
main = do main = do
cli <- execParser parserInfo cli <- execParser parserInfo
let mDebounceOverride = cliDebounce cli let mDebounceOverride = cliDebounce cli
options <- case cliConfig cli of (options, configSource) <- case cliConfig cli of
Just cfgPath -> do Just cfgPath -> do
cfg <- loadConfig cfgPath cfg <- loadConfig cfgPath
let opts = configToOptions cfg let opts = applyDebounce mDebounceOverride (configToOptions cfg)
pure $ case mDebounceOverride of pure (opts, Just cfgPath)
Just ms -> opts{optDebounceMs = ms} Nothing -> do
Nothing -> opts exists <- configFileExists
Nothing -> if exists
pure $ then do
defaultOptions cfgPath <- defaultConfigPath
{ optRepos = cfg <- loadConfig cfgPath
[ defaultRepoConfig let opts = applyDebounce mDebounceOverride (configToOptions cfg)
{ rcPath = fromMaybe "." (cliRepo cli) pure (opts, Just cfgPath)
, rcRemote = cliRemote cli else
, rcBranch = cliBranch cli pure
( defaultOptions
{ optRepos =
[ defaultRepoConfig
{ rcPath = fromMaybe "." (cliRepo cli)
, rcRemote = cliRemote cli
, rcBranch = cliBranch cli
}
]
, optDebounceMs = fromMaybe 5000 mDebounceOverride
} }
] , Nothing
, optDebounceMs = fromMaybe 5000 mDebounceOverride )
} case configSource of
Just path ->
hPutStrLn stderr ("Using config: " <> path)
Nothing ->
hPutStrLn stderr "No config file found, using single-repo mode"
hPutStrLn stderr $ hPutStrLn stderr $
"Converge watching " "Watching "
<> show (length (optRepos options)) <> show (length (optRepos options))
<> " repo(s)" <> " repo(s)"
<> " (debounce: " <> " (debounce: "
@@ -104,3 +117,7 @@ main = do
<> "ms)" <> "ms)"
mapM_ (\r -> hPutStrLn stderr (" - " <> rcPath r)) (optRepos options) mapM_ (\r -> hPutStrLn stderr (" - " <> rcPath r)) (optRepos options)
runConverge options runConverge options
applyDebounce :: Maybe Int -> Options -> Options
applyDebounce (Just ms) opts = opts{optDebounceMs = ms}
applyDebounce Nothing opts = opts
+2 -1
View File
@@ -1,5 +1,6 @@
# Example converge configuration file. # 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: repos:
- path: /home/jbrechtel/org - path: /home/jbrechtel/org
+11 -1
View File
@@ -9,6 +9,8 @@ module Converge (
Config (..), Config (..),
loadConfig, loadConfig,
configToOptions, configToOptions,
defaultConfigPath,
configFileExists,
-- * Running -- * Running
runConverge, runConverge,
@@ -31,7 +33,7 @@ import Data.Maybe (fromMaybe)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Yaml (FromJSON (..), decodeFileThrow, withObject, (.:), (.:?)) import Data.Yaml (FromJSON (..), decodeFileThrow, withObject, (.:), (.:?))
import System.Directory (getCurrentDirectory) import System.Directory (XdgDirectory (XdgConfig), doesFileExist, getCurrentDirectory, getXdgDirectory)
import System.Exit (ExitCode (..)) import System.Exit (ExitCode (..))
import System.FSNotify import System.FSNotify
import System.FilePath (isAbsolute, (</>)) import System.FilePath (isAbsolute, (</>))
@@ -114,6 +116,14 @@ configToOptions cfg =
, optDebounceMs = fromMaybe 5000 (cfgDebounceMs 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 -- Running
---------------------------------------------------------------------- ----------------------------------------------------------------------