summaryrefslogtreecommitdiff
path: root/src/tox/config/source/discover.py
blob: d9e8e507de01b661d24d31f012b36d20a8ce764f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from __future__ import annotations

import logging
from itertools import chain
from pathlib import Path

from tox.report import HandledError

from .api import Source
from .legacy_toml import LegacyToml
from .setup_cfg import SetupCfg
from .tox_ini import ToxIni

SOURCE_TYPES: tuple[type[Source], ...] = (ToxIni, SetupCfg, LegacyToml)


def discover_source(config_file: Path | None, root_dir: Path | None) -> Source:
    """
    Discover a source for configuration.

    :param config_file: the file storing the source
    :param root_dir: the root directory as set by the user (None means not set)
    :return: the source of the config
    """
    if config_file is None:
        src = _locate_source()
        if src is None:
            src = _create_default_source(root_dir)
    elif config_file.is_dir():
        src = None
        for src_type in SOURCE_TYPES:
            candidate: Path = config_file / src_type.FILENAME
            try:
                src = src_type(candidate)
                break
            except ValueError:
                continue
        if src is None:
            raise HandledError(f"could not find any config file in {config_file}")
    else:
        src = _load_exact_source(config_file)
    return src


def _locate_source() -> Source | None:
    folder = Path.cwd()
    for base in chain([folder], folder.parents):
        for src_type in SOURCE_TYPES:
            candidate: Path = base / src_type.FILENAME
            try:
                return src_type(candidate)
            except ValueError:
                pass
    return None


def _load_exact_source(config_file: Path) -> Source:
    # if the filename matches to the letter some config file name do not fallback to other source types
    exact_match = next((s for s in SOURCE_TYPES if config_file.name == s.FILENAME), None)  # pragma: no cover
    for src_type in (exact_match,) if exact_match is not None else SOURCE_TYPES:  # pragma: no branch
        try:
            return src_type(config_file)
        except ValueError:
            pass
    raise HandledError(f"could not recognize config file {config_file}")


def _create_default_source(root_dir: Path | None) -> Source:
    if root_dir is None:  # if set use that
        empty = Path.cwd()
        for base in chain([empty], empty.parents):
            if (base / "pyproject.toml").exists():
                empty = base
                break
    else:  # if not set use where we find pyproject.toml in the tree or cwd
        empty = root_dir
    logging.warning(f"No {' or '.join(i.FILENAME for i in SOURCE_TYPES)} found, assuming empty tox.ini at {empty}")
    src = ToxIni(empty / "tox.ini", content="")
    return src


__all__ = ("discover_source",)