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

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

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

""" 

:py:mod:`sysdirs.installer` 

 

Copyright (C) 2018 Mathias Stelzer 

 

sysdirs is free software: you can redistribute it and/or modify 

it under the terms of the GNU General Public License as published by 

the Free Software Foundation, either version 3 of the License, or 

(at your option) any later version. 

 

sysdirs is distributed in the hope that it will be useful, 

but WITHOUT ANY WARRANTY; without even the implied warranty of 

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

GNU General Public License for more details. 

 

You should have received a copy of the GNU General Public License 

along with this program. If not, see <http://www.gnu.org/licenses/>. 

""" 

from __future__ import absolute_import, unicode_literals, print_function 

 

import os 

import sys 

 

from sysdirs.interfaces import TerminalInterface 

from sysdirs.specifications import DirectorySpecification, XDG 

 

 

str_type = str 

if sys.version_info[0] < 3: 

str_type = unicode # noqa (flake warns about unicode usage) 

 

 

class NotInstalled(Exception): 

pass 

 

 

class InstallationError(Exception): 

pass 

 

 

class BaseInstaller(object): 

""" 

Base class to install and access system directories. 

 

Subclass it to configure your project:: 

 

>>> from sysdirs import BaseInstaller, FHS, XDG, DevDir 

>>> class Installer(BaseInstaller): 

... project_name = 'myproject' 

... specifications = [ 

... FHS(force=True), 

... XDG, 

... DevDir(__file__) 

... ] 

... 

>>> installer = Installer() 

>>> dirs = installer.install() 

>>> dirs = installer.load() 

""" 

 

#: :class:`str` - Name of your project 

project_name = None 

 

#: :class:`list` - Directory specifications your project supports 

specifications = [] 

 

#: :class:`bool` - Allow multiple installations, set to False to raise if already installed 

multiple_installations = True 

 

#: :class:`` - The interface class to display questions 

interface_class = TerminalInterface 

 

#: :class:`int` - Seconds to wait for user input before using default values. 

#: Required in automated installations (e.g. debian packages)! This ensures the 

#: installation won't halt in case the interface is shown where it shouldn't. 

input_timeout = None 

 

config_mode = 0o750 

data_mode = 0o755 

cache_mode = 0o770 

var_mode = 0o770 

run_mode = 0o770 

log_mode = 0o770 

 

def __init__(self): 

specifications = [] 

for specification in self.get_specifications(): 

is_inst = isinstance(specification, DirectorySpecification) 

if not is_inst and issubclass(specification, DirectorySpecification): 

specification = specification() 

specification.init(self) 

specifications.append(specification) 

self.specifications = specifications 

 

def get_specifications(self): 

return self.specifications 

 

def find_specification_by_name(self, name): 

for specification in self.get_specifications(): 

if specification.name.lower() == name.lower(): 

return specification 

 

def find_installation_specification(self): 

available_specs = [] 

root_available = False 

for specification in self.get_specifications(): 

if specification.hidden: 

continue 

if specification.requires_root: 

root_available = True 

if specification.is_available_for_installation(): 

available_specs.append(specification) 

 

if len(available_specs) > 1: 

for specification in available_specs: 

if specification.force: 

return specification 

interface = self.get_interface() 

return interface.ask_choices('Select a directory specification to use for this installation:', 

choices=available_specs, 

format_callback=self.specification_format_callback) 

 

if not available_specs: 

msg = 'No available installation methods!' 

if root_available: 

msg += ' Try running as root.' 

raise InstallationError(msg) 

 

return available_specs[0] 

 

def specification_format_callback(self, specification): 

formatted = '{} ({})'.format(specification.name, specification.description) 

if specification.is_installed(): 

formatted += ' INSTALLED' 

return formatted 

 

def get_interface_class(self): 

return self.interface_class 

 

def get_interface(self): 

return self.get_interface_class()(self) 

 

def get_uninstall_directories(self): 

for specification in self.specifications: 

for directory in specification.all: 

if os.path.exists(directory): 

yield directory 

 

########################################################################### 

# API 

########################################################################### 

 

def load(self, require=True): 

for specification in self.get_specifications(): 

if specification.is_installed(): 

return specification 

if require: 

raise NotInstalled() 

 

def is_installed(self): 

return self.load(require=False) is not None 

 

def install(self, specification=None): 

if not self.multiple_installations and self.is_installed(): 

raise InstallationError('Already installed!') 

if specification is None: 

specification = self.find_installation_specification() 

elif isinstance(specification, str_type): 

specification = self.find_specification_by_name(specification) 

if specification is None: 

raise InstallationError('Unknown specification: {}'.format(specification)) 

specification.init_install() 

return specification 

 

def uninstall(self): 

directories = set(self.get_uninstall_directories()) 

if not directories: 

raise NotInstalled() 

interface = self.get_interface() 

prompt = 'The following directories will be removed:\n' 

prompt += '\n'.join(directories) 

prompt += '\nContinue?' 

if not interface.ask_yesno(prompt): 

return False 

for specification in self.specifications: 

specification.uninstall() 

return True 

 

def setup_subparser(self, subparsers): 

install_parser = subparsers.add_parser('install') 

self.setup_argument_parser(install_parser) 

 

def setup_argument_parser(self, parser): 

choices = [s.name.lower() for s in self.get_specifications()] 

parser.add_argument('-s', '--specification', choices=choices) 

 

 

class Installer(BaseInstaller): 

""" 

Class to install and access system directories. 

 

Usage:: 

 

>>> from sysdirs import Installer, FHS, XDG 

>>> installer = Installer('myproject', specifications=[FHS, XDG], config=True) 

>>> dirs = installer.install() 

""" 

 

specifications = [XDG] 

 

def __init__(self, project_name, specifications=None, 

config=False, data=False, cache=False, var=False, run=False, log=False): 

"""Initialize system directories.""" 

self.project_name = project_name 

 

if specifications is not None: 

self.specifications = specifications 

 

self.create_config = config 

self.create_data = data 

self.create_cache = cache 

self.create_var = var 

self.create_run = run 

self.create_log = log 

 

super(Installer, self).__init__() 

 

def install(self, specification=None): 

specification = super(Installer, self).install(specification=specification) 

if self.create_config: 

specification.install_config() 

if self.create_data: 

specification.install_data() 

if self.create_cache: 

specification.install_cache() 

if self.create_var: 

specification.install_var() 

if self.create_run: 

specification.install_run() 

if self.create_log: 

specification.install_log() 

return specification