1from __future__ import division, absolute_import, print_function 2 3import os 4import sys 5import argparse 6import optparse 7import platform 8import pkgutil 9 10 11PY3 = sys.version_info[0] == 3 12STRING = str if PY3 else unicode # noqa: F821 13BASESTRING = str if PY3 else basestring # noqa: F821 14NUMERIC_TYPES = (int, float) if PY3 else (int, float, long) # noqa: F821 15 16 17UNIX_DIR_FALLBACK = '~/.config' 18WINDOWS_DIR_VAR = 'APPDATA' 19WINDOWS_DIR_FALLBACK = '~\\AppData\\Roaming' 20MAC_DIR = '~/Library/Application Support' 21 22 23def iter_first(sequence): 24 """Get the first element from an iterable or raise a ValueError if 25 the iterator generates no values. 26 """ 27 it = iter(sequence) 28 try: 29 return next(it) 30 except StopIteration: 31 raise ValueError() 32 33 34def namespace_to_dict(obj): 35 """If obj is argparse.Namespace or optparse.Values we'll return 36 a dict representation of it, else return the original object. 37 38 Redefine this method if using other parsers. 39 40 :param obj: * 41 :return: 42 :rtype: dict or * 43 """ 44 if isinstance(obj, (argparse.Namespace, optparse.Values)): 45 return vars(obj) 46 return obj 47 48 49def build_dict(obj, sep='', keep_none=False): 50 """Recursively builds a dictionary from an argparse.Namespace, 51 optparse.Values, or dict object. 52 53 Additionally, if `sep` is a non-empty string, the keys will be split 54 by `sep` and expanded into a nested dict. Keys with a `None` value 55 are dropped by default to avoid unsetting options but can be kept 56 by setting `keep_none` to `True`. 57 58 :param obj: Namespace, Values, or dict to iterate over. Other 59 values will simply be returned. 60 :type obj: argparse.Namespace or optparse.Values or dict or * 61 :param sep: Separator to use for splitting properties/keys of `obj` 62 for expansion into nested dictionaries. 63 :type sep: str 64 :param keep_none: Whether to keep keys whose value is `None`. 65 :type keep_none: bool 66 :return: A new dictionary or the value passed if obj was not a 67 dict, Namespace, or Values. 68 :rtype: dict or * 69 """ 70 # We expect our root object to be a dict, but it may come in as 71 # a namespace 72 obj = namespace_to_dict(obj) 73 # We only deal with dictionaries 74 if not isinstance(obj, dict): 75 return obj 76 77 # Get keys iterator 78 keys = obj.keys() if PY3 else obj.iterkeys() 79 if sep: 80 # Splitting keys by `sep` needs sorted keys to prevent parents 81 # from clobbering children 82 keys = sorted(list(keys)) 83 84 output = {} 85 for key in keys: 86 value = obj[key] 87 if value is None and not keep_none: # Avoid unset options. 88 continue 89 90 save_to = output 91 result = build_dict(value, sep, keep_none) 92 if sep: 93 # Split keys by `sep` as this signifies nesting 94 split = key.split(sep) 95 if len(split) > 1: 96 # The last index will be the key we assign result to 97 key = split.pop() 98 # Build the dict tree if needed and change where 99 # we're saving to 100 for child_key in split: 101 if child_key in save_to and \ 102 isinstance(save_to[child_key], dict): 103 save_to = save_to[child_key] 104 else: 105 # Clobber or create 106 save_to[child_key] = {} 107 save_to = save_to[child_key] 108 109 # Save 110 if key in save_to: 111 save_to[key].update(result) 112 else: 113 save_to[key] = result 114 return output 115 116 117# Config file paths, including platform-specific paths and in-package 118# defaults. 119 120def find_package_path(name): 121 """Returns the path to the package containing the named module or 122 None if the path could not be identified (e.g., if 123 ``name == "__main__"``). 124 """ 125 # Based on get_root_path from Flask by Armin Ronacher. 126 loader = pkgutil.get_loader(name) 127 if loader is None or name == '__main__': 128 return None 129 130 if hasattr(loader, 'get_filename'): 131 filepath = loader.get_filename(name) 132 else: 133 # Fall back to importing the specified module. 134 __import__(name) 135 filepath = sys.modules[name].__file__ 136 137 return os.path.dirname(os.path.abspath(filepath)) 138 139 140def xdg_config_dirs(): 141 """Returns a list of paths taken from the XDG_CONFIG_DIRS 142 and XDG_CONFIG_HOME environment varibables if they exist 143 """ 144 paths = [] 145 if 'XDG_CONFIG_HOME' in os.environ: 146 paths.append(os.environ['XDG_CONFIG_HOME']) 147 if 'XDG_CONFIG_DIRS' in os.environ: 148 paths.extend(os.environ['XDG_CONFIG_DIRS'].split(':')) 149 else: 150 paths.append('/etc/xdg') 151 paths.append('/etc') 152 return paths 153 154 155def config_dirs(): 156 """Return a platform-specific list of candidates for user 157 configuration directories on the system. 158 159 The candidates are in order of priority, from highest to lowest. The 160 last element is the "fallback" location to be used when no 161 higher-priority config file exists. 162 """ 163 paths = [] 164 165 if platform.system() == 'Darwin': 166 paths.append(UNIX_DIR_FALLBACK) 167 paths.append(MAC_DIR) 168 paths.extend(xdg_config_dirs()) 169 170 elif platform.system() == 'Windows': 171 paths.append(WINDOWS_DIR_FALLBACK) 172 if WINDOWS_DIR_VAR in os.environ: 173 paths.append(os.environ[WINDOWS_DIR_VAR]) 174 175 else: 176 # Assume Unix. 177 paths.append(UNIX_DIR_FALLBACK) 178 paths.extend(xdg_config_dirs()) 179 180 # Expand and deduplicate paths. 181 out = [] 182 for path in paths: 183 path = os.path.abspath(os.path.expanduser(path)) 184 if path not in out: 185 out.append(path) 186 return out 187