Skip to content

Commit

Permalink
correctly handle XDG_CONFIG_HOME as config dir
Browse files Browse the repository at this point in the history
  • Loading branch information
tfeldmann committed Mar 22, 2024
1 parent 5b882fe commit c3f9406
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 70 deletions.
36 changes: 13 additions & 23 deletions organize/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,18 @@
from yaml.scanner import ScannerError

from organize import Config, ConfigError
from organize.find_config import ConfigNotFound, find_config, list_configs
from organize.find_config import (
DOCS_RTD,
ConfigNotFound,
create_example_config,
find_config,
list_configs,
)
from organize.output import JSONL, Default, Output
from organize.utils import escape

from .__version__ import __version__

DOCS_RTD = "https://organize.readthedocs.io"
DOCS_GHPAGES = "https://tfeldmann.github.io/organize/"

EXAMPLE_CONFIG = f"""\
# organize configuration file
# {DOCS_RTD}
rules:
- locations:
filters:
actions:
- echo: "Hello, World!"
"""

Tags = Set[str]
OutputFormat = Annotated[
Literal["default", "jsonl", "errorsonly"], BeforeValidator(lambda v: v.lower())
Expand Down Expand Up @@ -154,18 +146,16 @@ def execute(

def new(config: Optional[str]) -> None:
try:
config_path = find_config(config)
new_path = create_example_config(name_or_path=config)
console.print(
f'Config "{escape(config_path)}" already exists.\n'
r'Use "organize new \[name]" to create a config in the default location.'
f'Config "{escape(new_path.name)}" created at "{escape(new_path.absolute())}"'
)
except ConfigNotFound as e:
assert e.init_path is not None
e.init_path.parent.mkdir(parents=True, exist_ok=True)
e.init_path.write_text(EXAMPLE_CONFIG, encoding="utf-8")
except FileExistsError as e:
console.print(
f'Config "{escape(e.init_path.stem)}" created at "{escape(e.init_path)}"'
f"{e}\n"
r'Use "organize new \[name]" to create a config in the default location.'
)
sys.exit(1)


def edit(config: Optional[str]) -> None:
Expand Down
2 changes: 0 additions & 2 deletions organize/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@ def __init__(
self,
config: str,
search_pathes: Iterable[Path] = tuple(),
init_path: Optional[Path] = None,
):
self.config = config
self.search_pathes = search_pathes
self.init_path = init_path

def __str__(self):
msg = f'Cannot find config "{self.config}".'
Expand Down
127 changes: 82 additions & 45 deletions organize/find_config.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import os
from itertools import chain
from itertools import chain, product
from pathlib import Path
from typing import Iterator, Optional, Tuple
from typing import Iterator, Optional

import platformdirs

from organize.utils import expandvars

from .errors import ConfigNotFound

DOCS_RTD = "https://organize.readthedocs.io"
DOCS_GHPAGES = "https://tfeldmann.github.io/organize/"

ENV_ORGANIZE_CONFIG = os.environ.get("ORGANIZE_CONFIG")
USER_CONFIG_DIR = platformdirs.user_config_path(appname="organize")
XDG_CONFIG_DIR = expandvars(os.environ.get("XDG_CONFIG_HOME", "~/.config")) / "organize"
USER_CONFIG_DIR = platformdirs.user_config_path(appname="organize")

SEARCH_DIRS = (
XDG_CONFIG_DIR,
USER_CONFIG_DIR,
)


def find_config_by_name(name: str) -> Path:
stem = Path(name).stem
filenames = (
f"{stem}.yaml",
f"{stem}.yml",
name,
)
search_dirs = (
Path("."),
*SEARCH_DIRS,
)

search_pathes = [d / f for d, f in product(search_dirs, filenames)]
for path in search_pathes:
if path.is_file():
return path

raise ConfigNotFound(config=stem, search_pathes=search_pathes)


def find_default_config() -> Path:
Expand All @@ -20,59 +48,68 @@ def find_default_config() -> Path:
result = expandvars(ENV_ORGANIZE_CONFIG)
if result.exists() and result.is_file():
return result
raise ConfigNotFound(str(result), init_path=result)
raise ConfigNotFound(str(result))

# no ORGANIZE_CONFIG env variable given:
# -> check the default config location
result = USER_CONFIG_DIR / "config.yaml"
if result.exists() and result.is_file():
return result
raise ConfigNotFound(str(result), init_path=result)
# otherwise we check all default locations for "config.y[a]ml"
return find_config_by_name("config")


def find_config(name_or_path: Optional[str] = None) -> Path:
# No config given? Find the default one.
if name_or_path is None:
return find_default_config()

# otherwise we try:
# 0. The full path if applicable
# 1.`$PWD`
# 2. the platform specifig config dir
# 3. `$XDG_CONFIG_HOME/organize`
# Maybe we are given the path to a config file?
as_path = expandvars(name_or_path)
if as_path.exists() and as_path.is_file():
if as_path.is_file():
return as_path

search_pathes: Tuple[Path, ...] = tuple()
if not as_path.is_absolute():
as_yml = Path(f"{as_path}.yml")
as_yaml = Path(f"{as_path}.yaml")
search_pathes = (
as_path,
as_yaml,
as_yml,
USER_CONFIG_DIR / as_path,
USER_CONFIG_DIR / as_yaml,
USER_CONFIG_DIR / as_yml,
XDG_CONFIG_DIR / as_path,
XDG_CONFIG_DIR / as_yaml,
XDG_CONFIG_DIR / as_yml,
)
for path in search_pathes:
if path.exists() and path.is_file():
return path

if str(as_path).lower().endswith((".yaml", ".yml")):
init_path = as_path
else:
init_path = USER_CONFIG_DIR / as_yaml
raise ConfigNotFound(
config=name_or_path,
search_pathes=search_pathes,
init_path=init_path,
)
# search the default locations for the given name
return find_config_by_name(name=name_or_path)


def list_configs() -> Iterator[Path]:
for loc in (USER_CONFIG_DIR, XDG_CONFIG_DIR):
for loc in SEARCH_DIRS:
yield from chain(loc.glob("*.yml"), loc.glob("*.yaml"))


EXAMPLE_CONFIG = f"""\
# organize configuration file
# {DOCS_RTD}
rules:
- locations:
filters:
actions:
- echo: "Hello, World!"
"""


def example_config_path(name_or_path: Optional[str]) -> Path:
# prefer "~/.config/organize" if it is already present on the system
preferred_dir = XDG_CONFIG_DIR if XDG_CONFIG_DIR.is_dir() else USER_CONFIG_DIR

if name_or_path is None:
if ENV_ORGANIZE_CONFIG is not None:
return expandvars(ENV_ORGANIZE_CONFIG)
return preferred_dir / "config.yaml"

# maybe we are given a path to create the config there?
if "/" in name_or_path or "\\" in name_or_path:
return expandvars(name_or_path)

# create at preferred dir -
# - keeping the extension
if name_or_path.lower().endswith((".yml", ".yaml")):
return preferred_dir / name_or_path
# - with .yaml extension
return preferred_dir / f"{name_or_path}.yaml"


def create_example_config(name_or_path: Optional[str] = None) -> Path:
path = example_config_path(name_or_path=name_or_path)
if path.is_file():
raise FileExistsError(f'Config "{path.absolute()}" already exists.')
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(EXAMPLE_CONFIG, encoding="utf-8")
return path

0 comments on commit c3f9406

Please sign in to comment.