Coverage for /home/pradyumna/Languages/python/packages/xdgpspconf/xdgpspconf/common.py : 100%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python3
2# -*- coding: utf-8; mode: python; -*-
3# Copyright © 2020-2021 Pradyumna Paranjape
4#
5# This file is part of xdgpspconf.
6#
7# xdgpspconf is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Lesser General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# xdgpspconf is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with xdgpspconf. If not, see <https://www.gnu.org/licenses/>. #
19"""
20Common filesystem discovery functions.
21"""
23import os
24import sys
25from dataclasses import dataclass
26from pathlib import Path
27from typing import List, Union
30@dataclass
31class XdgVar():
32 win_root: str
33 win_var: str
34 win_default: str
35 unix_root: Union[str, None]
36 unix_var: str
37 unix_vars: Union[str, None]
38 unix_default: str
41XDG_BASES = {
42 'CACHE':
43 XdgVar('TEMP', 'TEMP', 'AppData/Local/Temp', None, 'XDG_CACHE_HOME', None,
44 '.cache'),
45 'CONFIG':
46 XdgVar('APPDATA', 'LOCALAPPDATA', 'AppData/Local', '/etc/xdg',
47 'XDG_CONFIG_HOME', 'XDG_CONFIG_DIRS', '.config'),
48 'DATA':
49 XdgVar('APPDATA', 'LOCALAPPDATA', 'AppData/Local', '/local/share',
50 'XDG_DATA_HOME', 'XDG_DATA_DIRS', '.local/share'),
51 'STATE':
52 XdgVar('APPDATA', 'LOCALAPPDATA', 'AppData/Local', '/local/share',
53 'XDG_STATE_HOME', 'XDG_STATE_DIRS', '.local/state')
54}
57def is_mount(path: Path):
58 """
59 Check across platform if path is mountpoint or drive.
61 Args:
62 path: path to be checked
63 """
64 try:
65 if path.is_mount():
66 return True
67 return False
68 except NotImplementedError: # pragma: no cover
69 if path.resolve().drive + '\\' == str(path):
70 return True
71 return False
74def walk_ancestors(child_dir: Path) -> List[Path]:
75 """
76 Walk up to nearest mountpoint or project root.
78 - collect all directories containing __init__.py
79 (assumed to be source directories)
80 - project root is directory that contains ``setup.cfg`` or ``setup.py``
81 - mountpoint is a unix mountpoint or windows drive root
82 - I am **NOT** my ancestor
84 Args:
85 child_dir: walk ancestry of `this` directory
87 Returns:
88 List of Paths to parents of ancestral configurations:
89 First directory is most dominant
90 """
91 config_heir: List[Path] = []
93 while not is_mount(child_dir):
94 if (child_dir / '__init__.py').is_file():
95 config_heir.append(child_dir)
96 if any((child_dir / setup).is_file()
97 for setup in ('setup.cfg', 'setup.py')):
98 # project directory
99 config_heir.append(child_dir)
100 break
101 child_dir = child_dir.parent
102 return config_heir
105def xdg_base(base: str = 'CONFIG') -> List[Path]:
106 """
107 Get XDG_<BASE>_HOME locations.
109 `specifications
110 <https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html>`__
112 Args:
113 base: xdg base to fetch {CACHE,CONFIG,DATA,STATE}
115 Returns:
116 List of xdg-<base> Paths
117 First directory is most dominant
118 Raises:
119 KeyError: bad variable name
121 """
122 xdgbase = XDG_BASES[base.upper()]
123 xdg_heir: List[Path] = []
124 # environment
125 if sys.platform.startswith('win'): # pragma: no cover
126 # windows
127 user_home = Path(os.environ['USERPROFILE'])
128 root_var = Path(os.environ[xdgbase.win_root])
129 xdg_base_home = Path(
130 os.environ.get(xdgbase.win_var, user_home / xdgbase.win_default))
131 xdg_heir.append(xdg_base_home)
132 xdg_heir.append(root_var)
133 else:
134 # assume POSIX
135 user_home = Path(os.environ['HOME'])
136 xdg_base_home = Path(
137 os.environ.get(xdgbase.unix_var, user_home / xdgbase.unix_default))
138 xdg_heir.append(xdg_base_home)
139 if xdgbase.unix_vars:
140 xdg_base_dirs = os.environ.get(xdgbase.unix_vars,
141 xdgbase.unix_root)
142 else:
143 xdg_base_dirs = xdgbase.unix_root
144 if xdg_base_dirs:
145 for xdg_dirs in xdg_base_dirs.split(':'):
146 xdg_heir.append(Path(xdg_dirs))
147 return xdg_heir
150def locate_base(project: str,
151 custom: os.PathLike = None,
152 ancestors: bool = False,
153 base_type: str = 'CONFIG',
154 py_bin: os.PathLike = None) -> List[Path]:
155 """
156 Locate base (data/base) directories at standard locations.
158 Args:
159 project: name of project whose base is being fetched
160 custom: custom location (directory)
161 ancestors: inherit ancestor directories that contain __init__.py
162 base_type: type of xdg base {CACHE,CONFIG,DATA,STATE}
163 py_bin: namespace.__file__ that imports this function
165 Returns:
166 List of all possible base directory paths:
167 Existing and non-existing
168 First directory is most dominant
170 """
171 # Preference of base *Most dominant first*
172 base_heir: List[Path] = []
174 # custom
175 if custom is not None:
176 if not Path(custom).is_dir():
177 raise FileNotFoundError(f'Custom base: {custom} not found')
178 base_heir.append(Path(custom))
180 # Current directory
181 current_dir = Path('.').resolve()
182 base_heir.append(current_dir)
184 if ancestors:
185 # ancestral directories
186 ancestor_parents = walk_ancestors(current_dir)
187 base_heir.extend(ancestor_parents)
189 # xdg locations
190 xdg_heir = xdg_base(base_type)
191 for heir in xdg_heir:
192 base_heir.append(heir / project)
194 # Shipped location
195 if py_bin:
196 base_heir.append(Path(py_bin).parent)
197 return base_heir