Hide keyboard shortcuts

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""" 

20Locate standard data. 

21 

22Read: 

23 - standard xdg-base locations 

24 - current directory and ancestors 

25 - custom location 

26 

27""" 

28 

29import os 

30import sys 

31from pathlib import Path 

32from typing import List 

33 

34 

35def _is_mount(path: Path): 

36 """ 

37 Check across platform if path is mountpoint or drive. 

38 

39 Args: 

40 path: path to be checked 

41 """ 

42 try: 

43 if path.is_mount(): 

44 return True 

45 return False 

46 except NotImplementedError: 

47 if path.resolve().drive + '\\' == str(path): 

48 return True 

49 return False 

50 

51 

52def ancestral_data(child_dir: Path) -> List[Path]: 

53 """ 

54 Walk up to nearest mountpoint or project root. 

55 

56 - collect all directories containing __init__.py 

57 (assumed to be source directories) 

58 - project root is directory that contains ``setup.cfg`` or ``setup.py`` 

59 - mountpoint is a unix mountpoint or windows drive root 

60 - I am **NOT** my ancestor 

61 

62 Args: 

63 child_dir: walk ancestry of `this` directory 

64 

65 Returns: 

66 List of Paths to ancestral source directories: 

67 First directory is most dominant 

68 """ 

69 data_heir: List[Path] = [] 

70 

71 while not _is_mount(child_dir): 

72 if (child_dir / '__init__.py').is_file(): 

73 data_heir.append(child_dir) 

74 if any((child_dir / setup).is_file() 

75 for setup in ('setup.cfg', 'setup.py')): 

76 # project directory 

77 data_heir.append(child_dir) 

78 break 

79 child_dir = child_dir.parent 

80 return data_heir 

81 

82 

83def xdg_data() -> List[Path]: 

84 """ 

85 Get XDG_DATA_HOME locations. 

86 

87 `specifications 

88 <https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html>`__ 

89 

90 Returns: 

91 List of xdg-data Paths 

92 First directory is most dominant 

93 """ 

94 xdg_heir: List[Path] = [] 

95 # environment 

96 if sys.platform.startswith('win'): # pragma: no cover 

97 # windows 

98 user_home = Path(os.environ['USERPROFILE']) 

99 root_data = Path(os.environ['APPDATA']) 

100 xdg_data_home = Path( 

101 os.environ.get('LOCALAPPDATA', user_home / 'AppData/Local')) 

102 xdg_heir.append(xdg_data_home) 

103 xdg_heir.append(root_data) 

104 else: 

105 # assume POSIX 

106 user_home = Path(os.environ['HOME']) 

107 xdg_data_home = Path( 

108 os.environ.get('XDG_DATA_HOME', user_home / '.data')) 

109 xdg_heir.append(xdg_data_home) 

110 xdg_data_dirs = os.environ.get('XDG_DATA_DIRS', 

111 '/usr/local/share/:/usr/share/') 

112 for xdg_dirs in xdg_data_dirs.split(':'): 

113 xdg_heir.append(Path(xdg_dirs)) 

114 return xdg_heir 

115 

116 

117def locate_data(project: str, 

118 custom: os.PathLike = None, 

119 ancestors: bool = False) -> List[Path]: 

120 """ 

121 Locate data at standard locations. 

122 

123 Args: 

124 project: name of project whose data is being fetched 

125 custom: custom location for data 

126 ancestors: inherit ancestor directories that contain __init__.py 

127 dname: name of data file 

128 

129 Returns: 

130 List of all possible data paths: 

131 Existing and non-existing 

132 First directory is most dominant 

133 

134 """ 

135 # Preference of data location *Most dominant first* 

136 data_heir: List[Path] = [] 

137 

138 # custom 

139 if custom is not None: 

140 if not Path(custom).is_dir(): 

141 raise FileNotFoundError( 

142 f'Custom data directory: {custom} not found') 

143 data_heir.append(Path(custom)) 

144 

145 # environment variable 

146 env_val = os.environ.get(project.upper() + '_DATA') 

147 if env_val is not None: 

148 if not Path(env_val).is_dir(): 

149 raise FileNotFoundError(f'RC data directory: {env_val} not found') 

150 data_heir.append(Path(env_val)) 

151 

152 # Current directory 

153 current_dir = Path('.').resolve() 

154 data_heir.append(current_dir) 

155 

156 if ancestors: 

157 # ancestral directories 

158 data_heir.extend(ancestral_data(current_dir)) 

159 

160 # xdg locations 

161 xdg_heir = xdg_data() 

162 for heir in xdg_heir: 

163 data_heir.append(heir / project) 

164 

165 # Shipped location 

166 data_heir.append((Path(__file__).parent)) 

167 

168 return data_heir