1import configparser
2import json
3import os
4
5conf = {}
6default_conf_dir = os.path.join(os.path.expanduser("~"), ".config/fsspec")
7conf_dir = os.environ.get("FSSPEC_CONFIG_DIR", default_conf_dir)
8
9
10def set_conf_env(conf_dict, envdict=os.environ):
11    """Set config values from environment variables
12
13    Looks for variable of the form ``FSSPEC_<protocol>_<kwarg>``.
14    There is no attempt to convert strings, but the kwarg keys will
15    be lower-cased.
16
17    Parameters
18    ----------
19    conf_dict : dict(str, dict)
20        This dict will be mutated
21    envdict : dict-like(str, str)
22        Source for the values - usually the real environment
23    """
24    for key in envdict:
25        if key.startswith("FSSPEC"):
26            if key.count("_") < 2:
27                continue
28            _, proto, kwarg = key.split("_", 2)
29            conf_dict.setdefault(proto.lower(), {})[kwarg.lower()] = envdict[key]
30
31
32def set_conf_files(cdir, conf_dict):
33    """Set config values from files
34
35    Scans for INI and JSON files in the given dictionary, and uses their
36    contents to set the config. In case of repeated values, later values
37    win.
38
39    In the case of INI files, all values are strings, and these will not
40    be converted.
41
42    Parameters
43    ----------
44    cdir : str
45        Directory to search
46    conf_dict : dict(str, dict)
47        This dict will be mutated
48    """
49    if not os.path.isdir(cdir):
50        return
51    allfiles = sorted(os.listdir(cdir))
52    for fn in allfiles:
53        if fn.endswith(".ini"):
54            ini = configparser.ConfigParser()
55            ini.read(os.path.join(cdir, fn))
56            for key in ini:
57                if key == "DEFAULT":
58                    continue
59                conf_dict.setdefault(key, {}).update(dict(ini[key]))
60        if fn.endswith(".json"):
61            js = json.load(open(os.path.join(cdir, fn)))
62            for key in js:
63                conf_dict.setdefault(key, {}).update(dict(js[key]))
64
65
66def apply_config(cls, kwargs, conf_dict=None):
67    """Supply default values for kwargs when instantiating class
68
69    Augments the passed kwargs, by finding entries in the config dict
70    which match the classes ``.protocol`` attribute (one or more str)
71
72    Parameters
73    ----------
74    cls : file system implementation
75    kwargs : dict
76    conf_dict : dict of dict
77        Typically this is the global configuration
78
79    Returns
80    -------
81    dict : the modified set of kwargs
82    """
83    if conf_dict is None:
84        conf_dict = conf
85    protos = cls.protocol if isinstance(cls.protocol, (tuple, list)) else [cls.protocol]
86    kw = {}
87    for proto in protos:
88        # default kwargs from the current state of the config
89        if proto in conf_dict:
90            kw.update(conf_dict[proto])
91    # explicit kwargs always win
92    kw.update(**kwargs)
93    kwargs = kw
94    return kwargs
95
96
97set_conf_files(conf_dir, conf)
98set_conf_env(conf)
99