diff --git a/organize/cli.py b/organize/cli.py index 26d54b4f..fd23001c 100644 --- a/organize/cli.py +++ b/organize/cli.py @@ -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()) @@ -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: diff --git a/organize/errors.py b/organize/errors.py index 59a3bc19..133f9f67 100644 --- a/organize/errors.py +++ b/organize/errors.py @@ -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}".' diff --git a/organize/find_config.py b/organize/find_config.py index 3909bd7f..d45a8e8a 100644 --- a/organize/find_config.py +++ b/organize/find_config.py @@ -1,7 +1,7 @@ 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 @@ -9,9 +9,37 @@ 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: @@ -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