python-fhs
Python module for using the FHS and XDG basedir paths.
fhs.py
Go to the documentation of this file.
1# This module implements fhs directory support in Python.
2# vim: set fileencoding=utf-8 foldmethod=marker :
3
4# {{{ Copyright 2013-2019 Bas Wijnen <wijnen@debian.org>
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as
7# published by the Free Software Foundation, either version 3 of the
8# License, or(at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# }}}
18
19# File documentation. {{{
20
28
29'''@file
30This module makes it easy to find files in the locations that are defined for
31them by the FHS. Some locations are not defined there. This module chooses a
32location for those.
33
34It also defines a configuration file format which is used automatically when
35initializing this module.
36'''
37
38'''@package fhs Module for using paths as described in the FHS.
39This module makes it easy to find files in the locations that are defined for
40them by the FHS. Some locations are not defined there. This module chooses a
41location for those.
42
43It also defines a configuration file format which is used automatically when
44initializing this module.
45'''
46# }}}
47
48# Paths and how they are handled by this module: {{{
49# /etc configfile
50# /run runtimefile
51# /tmp tempfile
52# /usr/lib/package datafile
53# /usr/local datafile
54# /usr/share/package datafile
55# /var/cache cachefile
56# /var/games datafile
57# /var/lib/package datafile
58# /var/lock lockfile
59# /var/log logfile
60# /var/spool spoolfile
61# /var/tmp tempfile?
62
63# /home (xdgbasedir)
64# /root (xdgbasedir)
65# /bin -
66# /boot -
67# /dev -
68# /lib -
69# /lib<qual> -
70# /media -
71# /mnt -
72# /opt -
73# /sbin -
74# /srv -
75# /usr/bin -
76# /usr/include -
77# /usr/libexec -
78# /usr/lib<qual> -
79# /usr/sbin -
80# /usr/src -
81# /var/lib -
82# /var/opt -
83# /var/run -
84
85# FHS: http://www.linuxbase.org/betaspecs/fhs/fhs.html
86# XDG basedir: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
87
88# So: configfile, runtimefile, tempfile, datafile, cachefile, lockfile, logfile, spoolfile
89# }}}
90
91# Imports. {{{
92import os
93import sys
94import shutil
95import argparse
96import tempfile
97import atexit
98# }}}
99
100# Globals. {{{
101
102initialized = False
103
104is_system = False
105
106is_game = False
107
108pname = os.getenv('PACKAGE_NAME', os.path.basename(sys.argv[0]))
109
110HOME = os.path.expanduser('~')
111# Internal variables.
112
115_tempfiles = []
116
119_options = {}
120
123_option_order = []
124
127_module_info = {}
128
131_module_config = {}
132
135_module_values = {}
136
139_module_present = {}
140
143_base = os.path.abspath(os.path.dirname(sys.argv[0]))
144# }}}
145
146# Configuration files. {{{
147
148XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config'))
149
150XDG_CONFIG_DIRS = tuple([XDG_CONFIG_HOME] + os.getenv('XDG_CONFIG_DIRS', '/etc/xdg').split(':'))
151
152
160def write_config(name = None, text = True, dir = False, opened = True, packagename = None):
161 assert initialized
162 if name is None:
163 if dir:
164 filename = packagename or pname
165 else:
166 filename = (packagename or pname) + os.extsep + 'cfg'
167 else:
168 filename = name if is_system else os.path.join(packagename or pname, name)
169 if is_system:
170 if packagename and packagename != pname:
171 d = os.path.join('/etc/xdg', pname, packagename)
172 else:
173 d = os.path.join('/etc/xdg', pname)
174 else:
175 d = XDG_CONFIG_HOME
176 target = os.path.join(d, filename)
177 if dir:
178 if opened and not os.path.exists(target):
179 os.makedirs(target)
180 return target
181 else:
182 d = os.path.dirname(target)
183 if opened and not os.path.exists(d):
184 os.makedirs(d)
185 return open(target, 'w+' if text else 'w+b') if opened else target
186# }}}
187
188
196def read_config(name = None, text = True, dir = False, multiple = False, opened = True, packagename = None):
197 assert initialized
198 if name is None:
199 if dir:
200 filename = packagename or pname
201 else:
202 filename = (packagename or pname) + os.extsep + 'cfg'
203 else:
204 filename = name
205 seen = set()
206 target = []
207 if not is_system:
208 t = os.path.join(XDG_CONFIG_HOME, filename if name is None else os.path.join(packagename or pname, name))
209 if os.path.realpath(t) not in seen and os.path.exists(t) and (dir if os.path.isdir(t) else not dir):
210 r = t if dir or not opened else open(t, 'r' if text else 'rb')
211 if not multiple:
212 return r
213 seen.add(os.path.realpath(t))
214 target.append(r)
215 dirs = ['/etc/xdg', '/usr/local/etc/xdg']
216 if not is_system:
217 for d in XDG_CONFIG_DIRS:
218 dirs.insert(0, d)
219 if packagename and packagename != pname:
220 dirs = [os.path.join(x, pname, packagename) for x in dirs] + [os.path.join(x, packagename) for x in dirs]
221 else:
222 dirs = [os.path.join(x, pname) for x in dirs]
223 if not is_system:
224 dirs.insert(0, packagename or pname)
225 dirs.insert(0, os.path.curdir)
226 dirs.insert(0, _base)
227 for d in dirs:
228 t = os.path.join(d, filename)
229 if os.path.realpath(t) not in seen and os.path.exists(t) and (dir if os.path.isdir(t) else not dir):
230 r = t if dir or not opened else open(t, 'r' if text else 'rb')
231 if not multiple:
232 return r
233 seen.add(os.path.realpath(t))
234 target.append(r)
235 if multiple:
236 return target
237 else:
238 return None
239# }}}
240
241
247def remove_config(name = None, dir = False, packagename = None):
248 assert initialized
249 if dir:
250 shutil.rmtree(read_config(name, False, True, False, False, packagename), ignore_errors = False)
251 else:
252 os.unlink(read_config(name, False, False, False, False, packagename))
253# }}}
254
255# Config file helper functions. {{{
256def _protect(data, extra = ''):
257 ret = ''
258 extra += '\\'
259 for x in str(data):
260 o = ord(x)
261 if o < 32 or o >= 127 or x in extra:
262 ret += '\\%x;' % o
263 else:
264 ret += x
265 return ret
266# }}}
267
268def _unprotect(data):
269 ret = ''
270 while len(data) > 0:
271 if data[0] == '%':
272 l = data.index(';')
273 ret += chr(int(data[1:l], 16))
274 data = data[l + 1:]
275 else:
276 if 32 <= ord(data[0]) < 127:
277 # newlines can happen; only this range is valid.
278 ret += data[0]
279 data = data[1:]
280 return ret
281# }}}
282
283
288def decode_value(value, argtype):
289 if value == 'None':
290 return None
291 if argtype is str:
292 if len(value) < 2 or not value.startswith("'") or not value.endswith("'"):
293 raise ValueError('str value without quotes')
294 return value[1:-1].replace(r"'\''", "'")
295 if argtype is bool:
296 if value not in ('False', 'True'):
297 raise ValueError('incorrect bool value %s' % value)
298 return value == 'True'
299 return argtype(value)
300# }}}
301
302
305def encode_value(value):
306 if value is None:
307 return 'None'
308 if isinstance(value, str):
309 return "'" + value.replace("'", r"'\''") + "'"
310 return str(value)
311# }}}
312
313def help_text(main, options, option_order):
314 if _info['help']:
315 print(_info['help'], file = sys.stderr)
316 else:
317 if _info['version']:
318 print('this is %s version %s\n' % (pname, _info['version']), file = sys.stderr)
319 else:
320 print('this is %s\n' % pname, file = sys.stderr)
321
322 print('\nSupported option arguments:', file = sys.stderr)
323 for module in (False, True):
324 for opt in option_order:
325 option = options[opt]
326 if (option['module'] is not None) != module:
327 continue
328 m = ' (This option can be passed multiple times)' if option['multiple'] else ''
329 if option['argtype'] is bool:
330 optname = '--' + opt
331 if option['short'] is not None:
332 optname += ', -' + option['short']
333 print('\t%s\n\t\t%s%s' % (optname, option['help'], m), file = sys.stderr)
334 elif option['optional']:
335 optname = '--' + opt + '[=<value>]'
336 if option['short'] is not None:
337 optname += ', -' + option['short'] + '[<value>]'
338 default = ' Default: %s' % str(option['default']) if option['default'] is not None else ''
339 print('\t%s\n\t\t%s%s%s' % (optname, option['help'], default, m), file = sys.stderr)
340 else:
341 optname = '--' + opt + '=<value>'
342 if option['short'] is not None:
343 optname += ', -' + option['short'] + '<value>'
344 default = ' Default: %s' % str(option['default']) if option['default'] is not None else ''
345 print('\t%s\n\t\t%s%s%s' % (optname, option['help'], default, m), file = sys.stderr)
346
347 if _info['contact'] is not None:
348 print('\nPlease send feedback and bug reports to %s' % _info['contact'], file = sys.stderr)
349# }}}
350
352 if _info['version']:
353 print('%s version %s' % (pname, _info['version']), file = sys.stderr)
354 else:
355 print('%s' % pname, file = sys.stderr)
356 if _info['contact']:
357 print('\tPlease send feedback and bug reports to %s' % _info['contact'], file = sys.stderr)
358
359 if len(_module_info) > 0:
360 print('\nUsing modules:', file = sys.stderr)
361 for mod in _module_info:
362 print('\t%s version %s\n\t\t%s' % (mod, _module_info[mod]['version'], _module_info[mod]['desc']), file = sys.stderr)
363 if _module_info[mod]['contact'] is not None:
364 print('\t\tPlease send feedback and bug reports for %s to %s' % (mod, _module_info[mod]['contact']), file = sys.stderr)
365# }}}
366
367def load_config(filename, values = None, present = None, options = None):
368 if present is None:
369 present = {}
370 if values is None:
371 new_values = True
372 values = {}
373 else:
374 new_values = False
375 config = read_config(filename + os.extsep + 'ini')
376 if config is None:
377 return {}
378 for cfg in config:
379 if cfg.strip() == '' or cfg.strip().startswith('#'):
380 continue
381 if '=' not in cfg:
382 print('invalid line in config file %s: %s' % (filename, cfg), file = sys.stderr)
383 key, value = cfg.split('=', 1)
384 key = _unprotect(key)
385 if not new_values and key not in values:
386 print('invalid key %s in config file' % key, file = sys.stderr)
387 continue
388 if key in present and present[key]:
389 continue
390 try:
391 if options is not None and options[key]['multiple']:
392 values[key] = [decode_value(_unprotect(v), options[key]['argtype']) for v in value.split(',')]
393 else:
394 values[key] = _unprotect(value) if options is None else decode_value(_unprotect(value), options[key]['argtype'])
395 except ValueError:
396 print('Warning: error loading value for %s; ignoring' % key, file = sys.stderr)
397 continue
398 if present is not None:
399 present[key] = True
400 return values
401# }}}
402
403
412def save_config(config, name = None, packagename = None):
413 assert initialized
414 if name is None:
415 filename = 'commandline' + os.extsep + 'ini'
416 else:
417 filename = name + os.extsep + 'ini'
418 keys = list(config.keys())
419 keys.sort()
420 with write_config(filename) as f:
421 for key in keys:
422 if isinstance(config[key], list):
423 value = ','.join(_protect(encode_value(x), ',') for x in config[key])
424 else:
425 value = _protect(encode_value(config[key]))
426 f.write('%s=%s\n' % (_protect(key, '='), value))
427# }}}
428# }}}
429
430# Commandline argument handling. {{{
431
448def option(name, help, short = None, multiple = False, optional = False, default = None, noarg = None, argtype = None, module = None, options = None, option_order = None):
449 if options is None:
450 assert not initialized
451 options = _options
452 if option_order is None:
453 option_order = _option_order
454 if name in options:
455 raise ValueError('duplicate registration of argument name %s' % name)
456 if not isinstance(name, str) or len(name) == 0 or name.startswith('-'):
457 raise ValueError('argument must not start with "-": %s' % name)
458 if short is not None:
459 if any(options[x]['short'] == short for x in options):
460 raise ValueError('duplicate short option %s defined' % short)
461 if len(short) != 1:
462 raise ValueError('length of short option %s for %s must be 1' % (short, name))
463 if short == '-':
464 raise ValueError('short option for %s cannot be "-"' % name)
465 if argtype is None:
466 if default is not None:
467 argtype = type(default)
468 else:
469 argtype = str
470 if argtype is bool:
471 if default is None and noarg is None:
472 default, noarg = False, True
473 if optional:
474 if argtype is bool:
475 if not isinstance(noarg, bool):
476 raise ValueError('noarg value for %s must be of type bool if argtype is bool' % name)
477 else:
478 try:
479 # Testing suggests that this works for floats, but can rounding errors cause a false positive here?
480 if decode_value(encode_value(noarg), argtype) != noarg:
481 raise ValueError('noarg value %s for %s changes when saving to config file' % (str(noarg), name))
482 except:
483 raise ValueError('noarg value %s for %s cannot be restored from config file' % (str(noarg), name))
484 options[name] = {'help': help, 'short': short, 'multiple': multiple, 'optional': optional, 'default': default, 'noarg': noarg, 'argtype': argtype, 'module': module}
485 option_order.append(name)
486 return options[name]
487# }}}
488
489def parse_args(argv = None, options = None, extra = False):
490 if argv is None:
491 argv = sys.argv
492 if options is None:
493 options = _options
494 shorts = {options[name]['short']: name for name in options}
495 values = {name: [] if options[name]['multiple'] else options[name]['default'] for name in options}
496 present = {name: False for name in options}
497 pos = 1
498 while pos < len(argv):
499 current = argv[pos]
500 nextarg = argv[pos + 1] if pos + 1 < len(argv) else None
501 if current == '--':
502 argv.pop(pos)
503 break
504 if len(current) < 2 or not current.startswith('-'):
505 pos += 1
506 continue
507 if current.startswith('--'):
508 # This is a long option.
509 if '=' in current:
510 optname, arg = current.split('=', 1)
511 else:
512 optname, arg = current, None
513 optname = optname[2:]
514 if optname not in options:
515 print('Warning: ignoring unrecognized option %s' % optname)
516 argv.pop(pos)
517 continue
518 opt = options[optname]
519 argtype = opt['argtype']
520 if argtype is bool:
521 # This option takes no argument.
522 value = opt['noarg']
523 elif opt['optional']:
524 # This option takes an optional argument.
525 if arg is not None:
526 value = opt['argtype'](arg)
527 else:
528 value = opt['noarg']
529 else:
530 # This option requires an argument.
531 if arg is not None:
532 value = opt['argtype'](arg)
533 else:
534 argv.pop(pos)
535 if pos >= len(argv):
536 print('Warning: option %s requires an argument' % optname, file = sys.stderr)
537 continue
538 value = opt['argtype'](argv[pos])
539 if opt['multiple']:
540 values[optname].append(value)
541 else:
542 if present[optname]:
543 print('Warning: option %s must only be passed once' % optname, file = sys.stderr)
544 values[optname] = value
545 present[optname] = True
546 else:
547 # This is a short options argument.
548 optpos = 1
549 while optpos < len(current):
550 o = current[optpos]
551 optpos += 1
552 if o not in shorts:
553 print('Warning: short option %s is not recognized' % o, file = sys.stderr)
554 continue
555 optname = shorts[o]
556 opt = options[optname]
557 argtype = opt['argtype']
558 if argtype is bool:
559 # This option takes no argument.
560 value = opt['noarg']
561 elif opt['optional']:
562 # This option takes an optional argument.
563 if optpos < len(current):
564 value = opt['argtype'](current[optpos:])
565 else:
566 value = opt['noarg']
567 optpos = len(current)
568 else:
569 # This option requires an argument.
570 if optpos < len(current):
571 value = opt['argtype'](current[optpos:])
572 else:
573 argv.pop(pos)
574 if pos >= len(argv):
575 print('Warning: option %s (%s) requires an argument' % (o, optname), file = sys.stderr)
576 continue
577 value = opt['argtype'](argv[pos])
578 optpos = len(current)
579 if opt['multiple']:
580 values[optname].append(value)
581 else:
582 if present[optname]:
583 print('Warning: option %s (%s) must only be passed once' % (o, optname), file = sys.stderr)
584 values[optname] = value
585 present[optname] = True
586 argv.pop(pos)
587 if extra:
588 return values, present
589 else:
590 return values
591# }}}
592
593
615def init(config = None, help = None, version = None, contact = None, packagename = None, system = None, game = False):
616 global initialized
617 assert not initialized
618 global pname
619 if packagename is not None:
620 pname = packagename
621 global is_system
622 global is_game
623 is_game = game
624 if config is not None:
625 print('Warning: using the config parameter for fhs.init() is DEPRECATED! Use option() instead.', file = sys.stderr)
626 for key in config:
627 option(key, 'no help for this option', default = config[key])
628 global XDG_RUNTIME_DIR
629 global _values, _present
630 global _info
631
634 _info = {'help': help, 'version': version, 'contact': contact}
635 # If these default options are passed by the user, this will raise an exception.
636 first_options = {}
637 option_order = []
638 option('help', 'Show this help text', short = None if any(_options[o]['short'] == 'h' for o in _options) else 'h', argtype = bool, options = first_options, option_order = option_order)
639 option('version', 'Show version information', short = None if any(_options[o]['short'] == 'v' for o in _options) else 'v', argtype = bool, options = first_options, option_order = option_order)
640 option('configfile', 'Use this file for loading and/or saving commandline configuration', default = 'commandline', options = first_options, option_order = option_order)
641 option('saveconfig', 'Save active commandline configuration as default or to the named file', optional = True, default = None, noarg = '', argtype = str, options = first_options, option_order = option_order)
642 if system is None:
643 option('system', 'Use only system paths', argtype = bool, options = first_options, option_order = option_order)
644 else:
645 is_system = system
646 options = first_options.copy()
647 options.update(_options)
648 option_order += _option_order
649 try:
650 _values, _present = parse_args(sys.argv, options, extra = True)
651 except ValueError as err:
652 # Error parsing options.
653 print('Error parsing arguments: %s' % str(err))
654 help_text(help, options, option_order)
655 sys.exit(1)
656 if _values['help']:
657 help_text(help, options, option_order)
658 sys.exit(1)
659 _values.pop('help')
660 if _values['version']:
662 sys.exit(1)
663 _values.pop('version')
664 configfile = _values.pop('configfile')
665 saveconfig = _values.pop('saveconfig')
666 if system is None:
667 is_system = _values['system']
668
669 initialized = True
670 if saveconfig == '':
671 saveconfig = configfile
672 load_config(configfile, _values, _present, options)
673 if saveconfig is not None:
674 save_config({key: _values[key] for key in _values if _present[key]}, saveconfig, packagename)
675 # Split out the module options into their own object.
676 for module in _module_config:
677 _module_values[module] = {key: _values.pop(module + '-' + key) for key in _module_config[module]}
678 _module_present[module] = {key: _present.pop(module + '-' + key) for key in _module_config[module]}
679 # system may have been updated. Record the new value. Do this after
680 # save_config, because it should save in the location where read_config
681 # searches for it.
682 if system is None:
683 is_system = _values.pop('system')
684 @atexit.register
685 def clean_temps():
686 for f in _tempfiles:
687 try:
688 os.unlink(f)
689 except:
690 shutil.rmtree(f, ignore_errors = True)
691 if XDG_RUNTIME_DIR is None:
692 XDG_RUNTIME_DIR = write_temp(dir = True)
693 return _values
694# }}}
695
696
707def get_config(extra = False):
708 if not initialized:
709 print('Warning: init() should be called before get_config() to set program information', file = sys.stderr)
710 init()
711 if extra:
712 return _values, _present;
713 else:
714 return _values;
715# }}}
716# }}}
717
718# Module commandline argument handling. {{{
719
726def module_info(modulename, desc, version, contact):
727 assert not initialized
728 if modulename in _module_info:
729 print('Warning: duplicate registration of information for module %s' % modulename, file = sys.stderr)
730 return
731 _module_info[modulename] = {'desc': desc, 'version': version, 'contact': contact}
732 _module_config[modulename] = set()
733# }}}
734
735
740def module_option(modulename, name, help, short = None, multiple = False, optional = False, default = None, noarg = None, argtype = None, options = None, option_order = None):
741 assert not initialized
742 assert modulename in _module_info
743 _module_config[modulename].add(name)
744 return option(modulename + '-' + name, help, short, multiple, optional, default, noarg, argtype, modulename, options, option_order)
745# }}}
746
747
757def module_init(modulename, config):
758 print('Warning: module %s uses module_init() which is DEPRECATED! It should use module_option() instead.' % modulename, file = sys.stderr)
759 assert not initialized
760 module_info(modulename, 'no information about this module available', 'unknown', None)
761 for key in config:
762 module_option(modulename, key, 'no help for this module option', default = config[key])
763# }}}
764
765
776def module_get_config(modulename, extra = False):
777 if not initialized:
778 init()
779 if extra:
780 return _module_values[modulename], _module_present[modulename];
781 else:
782 return _module_values[modulename];
783# }}}
784# }}}
785# }}}
786
787# Runtime files. {{{
788
789XDG_RUNTIME_DIR = os.getenv('XDG_RUNTIME_DIR')
790def _runtime_get(name, packagename, dir):
791 assert initialized
792 if name is None:
793 if dir:
794 name = packagename or pname
795 else:
796 name = (packagename or pname) + os.extsep + 'txt'
797 else:
798 name = os.path.join(packagename or pname, name)
799 d = '/run' if is_system else XDG_RUNTIME_DIR
800 target = os.path.join(d, name)
801 d = target if dir else os.path.dirname(target)
802 return d, target
803
804
812def write_runtime(name = None, text = True, dir = False, opened = True, packagename = None):
813 d, target = _runtime_get(name, packagename, dir)
814 if opened and not os.path.exists(d):
815 os.makedirs(d)
816 return open(target, 'w+' if text else 'w+b') if opened and not dir else target
817
818
826def read_runtime(name = None, text = True, dir = False, opened = True, packagename = None):
827 d, target = _runtime_get(name, packagename, dir)
828 if os.path.exists(target) and (dir if os.path.isdir(target) else not dir):
829 return open(target, 'r' if text else 'rb') if opened and not dir else target
830 return None
831
832
839def remove_runtime(name = None, dir = False, packagename = None):
840 assert initialized
841 if dir:
842 shutil.rmtree(read_runtime(name, False, True, False, packagename), ignore_errors = False)
843 else:
844 os.unlink(read_runtime(name, False, False, False, packagename))
845# }}}
846
847# Temp files. {{{
848class _TempFile:
849 def __init__(self, f, name):
850 # Avoid calling file.__setattr__.
851 super().__setattr__('_file', f)
852 super().__setattr__('filename', name)
853 def remove(self):
854 assert initialized
855 assert self.filename is not None
856 self.close()
857 os.unlink(self.filename)
858 _tempfiles.remove(self.filename)
859 super().__setattr__('_file', None)
860 super().__setattr__('filename', None)
861 def __getattr__(self, k):
862 return getattr(self._file, k)
863 def __setattr__(self, k, v):
864 return setattr(self._file, k, v)
865 def __enter__(self):
866 return self
867 def __exit__(self, exc_type, exc_value, traceback):
868 self.remove()
869 return False
870
871
892def write_temp(dir = False, text = True, packagename = None):
893 assert initialized
894 if dir:
895 ret = tempfile.mkdtemp(prefix = (packagename or pname) + '-')
896 _tempfiles.append(ret)
897 else:
898 f = tempfile.mkstemp(text = text, prefix = (packagename or pname) + '-')
899 _tempfiles.append(f[1])
900 ret = _TempFile(os.fdopen(f[0], 'w+' if text else 'w+b'), f[1])
901 return ret
902
903
910def remove_temp(name):
911 assert initialized
912 assert name in _tempfiles
913 _tempfiles.remove(name)
914 shutil.rmtree(name, ignore_errors = False)
915# }}}
916
917# Data files. {{{
918
919XDG_DATA_HOME = os.getenv('XDG_DATA_HOME', os.path.join(HOME, '.local', 'share'))
920
921XDG_DATA_DIRS = os.getenv('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
922
923
931def write_data(name = None, text = True, dir = False, opened = True, packagename = None):
932 assert initialized
933 if name is None:
934 if dir:
935 filename = packagename or pname
936 else:
937 filename = (packagename or pname) + os.extsep + 'dat'
938 else:
939 filename = name if is_system else os.path.join(packagename or pname, name)
940 if is_system:
941 if is_game:
942 if packagename and packagename != pname:
943 d = os.path.join('/var/games', pname, packagename)
944 else:
945 d = os.path.join('/var/games', pname)
946 else:
947 if packagename and packagename != pname:
948 d = os.path.join('/var/lib', pname, packagename)
949 else:
950 d = os.path.join('/var/lib', pname)
951 else:
952 d = XDG_DATA_HOME
953 target = os.path.join(d, filename)
954 if dir:
955 if opened and not os.path.exists(target):
956 os.makedirs(target)
957 return target
958 else:
959 d = os.path.dirname(target)
960 if opened and not os.path.exists(d):
961 os.makedirs(d)
962 return open(target, 'w+' if text else 'w+b') if opened else target
963
964
972def read_data(name = None, text = True, dir = False, multiple = False, opened = True, packagename = None):
973 assert initialized
974 if name is None:
975 if dir:
976 filename = packagename or pname
977 else:
978 filename = (packagename or pname) + os.extsep + 'dat'
979 else:
980 filename = name
981 seen = set()
982 target = []
983 if not is_system:
984 t = os.path.join(XDG_DATA_HOME, filename if name is None else os.path.join(packagename or pname, name))
985 if os.path.realpath(t) not in seen and os.path.exists(t) and (dir if os.path.isdir(t) else not dir):
986 r = t if dir or not opened else open(t, 'r' if text else 'rb')
987 if not multiple:
988 return r
989 seen.add(os.path.realpath(t))
990 target.append(r)
991 dirs = ['/var/local/lib', '/var/lib', '/usr/local/lib', '/usr/lib', '/usr/local/share', '/usr/share']
992 if is_game:
993 dirs = ['/var/local/games', '/var/games', '/usr/local/lib/games', '/usr/lib/games', '/usr/local/share/games', '/usr/share/games'] + dirs
994 if not is_system:
995 for d in XDG_DATA_DIRS[::-1]:
996 dirs.insert(0, d)
997 if packagename and packagename != pname:
998 dirs = [os.path.join(x, pname, packagename) for x in dirs] + [os.path.join(x, packagename) for x in dirs]
999 else:
1000 dirs = [os.path.join(x, pname) for x in dirs]
1001 if not is_system:
1002 dirs.insert(0, packagename or pname)
1003 dirs.insert(0, os.path.curdir)
1004 dirs.insert(0, _base)
1005 for d in dirs:
1006 t = os.path.join(d, filename)
1007 if os.path.realpath(t) not in seen and os.path.exists(t) and (dir if os.path.isdir(t) else not dir):
1008 r = t if dir or not opened else open(t, 'r' if text else 'rb')
1009 if not multiple:
1010 return r
1011 seen.add(os.path.realpath(t))
1012 target.append(r)
1013 if multiple:
1014 return target
1015 else:
1016 return None
1017
1018
1024def remove_data(name = None, dir = False, packagename = None):
1025 assert initialized
1026 if dir:
1027 shutil.rmtree(read_data(name, False, True, False, False, packagename), ignore_errors = False)
1028 else:
1029 os.unlink(read_data(name, False, False, False, False, packagename))
1030# }}}
1031
1032# Cache files. {{{
1033
1034XDG_CACHE_HOME = os.getenv('XDG_CACHE_HOME', os.path.join(HOME, '.cache'))
1035
1036
1044def write_cache(name = None, text = True, dir = False, opened = True, packagename = None):
1045 assert initialized
1046 if name is None:
1047 if dir:
1048 filename = packagename or pname
1049 else:
1050 filename = (packagename or pname) + os.extsep + 'dat'
1051 else:
1052 filename = name if is_system else os.path.join(packagename or pname, name)
1053 d = os.path.join('/var/cache', packagename or pname) if is_system else XDG_CACHE_HOME
1054 target = os.path.join(d, filename)
1055 if dir:
1056 if opened and not os.path.exists(target):
1057 os.makedirs(target)
1058 return target
1059 else:
1060 d = os.path.dirname(target)
1061 if opened and not os.path.exists(d):
1062 os.makedirs(d)
1063 return open(target, 'w+' if text else 'w+b') if opened and not dir else target
1064
1065
1073def read_cache(name = None, text = True, dir = False, opened = True, packagename = None):
1074 assert initialized
1075 if name is None:
1076 if dir:
1077 filename = packagename or pname
1078 else:
1079 filename = (packagename or pname) + os.extsep + 'dat'
1080 else:
1081 filename = os.path.join(packagename or pname, name)
1082 target = os.path.join(XDG_CACHE_HOME, filename)
1083 if not os.path.exists(target):
1084 if name is None:
1085 filename = os.path.join(packagename or pname, packagename or pname + os.extsep + 'dat')
1086 d = '/var/cache'
1087 target = os.path.join(d, filename)
1088 if not os.path.exists(target):
1089 return None
1090 return open(target, 'r' if text else 'rb') if opened and not dir else target
1091
1092
1098def remove_cache(name = None, dir = False, packagename = None):
1099 assert initialized
1100 if dir:
1101 shutil.rmtree(read_cache(name, False, True, False, packagename), ignore_errors = False)
1102 else:
1103 os.unlink(read_cache(name, False, False, False, packagename))
1104# }}}
1105
1106# Log files. {{{
1107
1115def write_log(name = None, packagename = None):
1116 assert initialized
1117 if not is_system:
1118 return sys.stderr
1119 if name is None:
1120 filename = (packagename or pname) + os.extsep + 'log'
1121 else:
1122 filename = os.path.join(packagename or pname, name)
1123 target = os.path.join('/var/log', filename)
1124 d = os.path.dirname(target)
1125 if not os.path.exists(d):
1126 os.makedirs(d)
1127 return open(target, 'a')
1128# }}}
1129
1130# Spool files. {{{
1131
1141def write_spool(name = None, text = True, dir = False, opened = True, packagename = None):
1142 assert initialized
1143 if name is None:
1144 if dir:
1145 filename = packagename or pname
1146 else:
1147 filename = (packagename or pname) + os.extsep + 'dat'
1148 else:
1149 filename = os.path.join(packagename or pname, name)
1150 target = os.path.join('/var/spool' if is_system else os.path.join(XDG_CACHE_HOME, 'spool'), filename)
1151 d = os.path.dirname(target)
1152 if opened and not os.path.exists(d):
1153 os.makedirs(d)
1154 return open(target, 'w+' if text else 'w+b') if opened and not dir else target
1155
1156
1164def read_spool(name = None, text = True, dir = False, opened = True, packagename = None):
1165 assert initialized
1166 if name is None:
1167 if dir:
1168 filename = packagename or pname
1169 else:
1170 filename = (packagename or pname) + os.extsep + 'dat'
1171 else:
1172 filename = os.path.join(packagename or pname, name)
1173 target = os.path.join('/var/spool' if is_system else os.path.join(XDG_CACHE_HOME, 'spool'), filename)
1174 if not os.path.exists(target):
1175 return None
1176 return open(target, 'r' if text else 'rb') if opened and not dir else target
1177
1178
1184def remove_spool(name = None, dir = False, packagename = None):
1185 assert initialized
1186 if name is None:
1187 if dir:
1188 filename = packagename or pname
1189 else:
1190 filename = (packagename or pname) + os.extsep + 'dat'
1191 if dir:
1192 shutil.rmtree(read_spool(name, False, True, False, packagename), ignore_errors = False)
1193 else:
1194 os.unlink(read_spool(name, False, False, False, packagename))
1195# }}}
1196
1197# Locks. {{{
1198
1201def lock(name = None, info = '', packagename = None):
1202 assert initialized
1203 # TODO
1204
1205
1208def unlock(name = None, packagename = None):
1209 assert initialized
1210 # TODO
1211# }}}
def write_data(name=None, text=True, dir=False, opened=True, packagename=None)
Open a data file for writing.
Definition: fhs.py:931
def read_cache(name=None, text=True, dir=False, opened=True, packagename=None)
Open a cache file for reading.
Definition: fhs.py:1073
def read_config(name=None, text=True, dir=False, multiple=False, opened=True, packagename=None)
Open a config file for reading.
Definition: fhs.py:196
def read_spool(name=None, text=True, dir=False, opened=True, packagename=None)
Open a spool file for reading.
Definition: fhs.py:1164
def module_init(modulename, config)
Add configuration for a module.
Definition: fhs.py:757
def parse_args(argv=None, options=None, extra=False)
Definition: fhs.py:489
def write_temp(dir=False, text=True, packagename=None)
Open a temporary file for writing.
Definition: fhs.py:892
def save_config(config, name=None, packagename=None)
Save a dict as a configuration file.
Definition: fhs.py:412
def read_runtime(name=None, text=True, dir=False, opened=True, packagename=None)
Open a runtime file for reading.
Definition: fhs.py:826
def write_config(name=None, text=True, dir=False, opened=True, packagename=None)
Open a config file for writing.
Definition: fhs.py:160
def write_cache(name=None, text=True, dir=False, opened=True, packagename=None)
Open a cache file for writing.
Definition: fhs.py:1044
def remove_cache(name=None, dir=False, packagename=None)
Remove a cache file.
Definition: fhs.py:1098
def unlock(name=None, packagename=None)
Release a lock.
Definition: fhs.py:1208
def remove_data(name=None, dir=False, packagename=None)
Remove a data file.
Definition: fhs.py:1024
def read_data(name=None, text=True, dir=False, multiple=False, opened=True, packagename=None)
Open a data file for reading.
Definition: fhs.py:972
def write_runtime(name=None, text=True, dir=False, opened=True, packagename=None)
Open a runtime file for writing.
Definition: fhs.py:812
def init(config=None, help=None, version=None, contact=None, packagename=None, system=None, game=False)
Initialize the module.
Definition: fhs.py:615
def version_text()
Definition: fhs.py:351
def load_config(filename, values=None, present=None, options=None)
Definition: fhs.py:367
def remove_temp(name)
Remove a temporary directory.
Definition: fhs.py:910
def decode_value(value, argtype)
Parse a string value into its proper type.
Definition: fhs.py:288
def module_option(modulename, name, help, short=None, multiple=False, optional=False, default=None, noarg=None, argtype=None, options=None, option_order=None)
Register a commandline option for a module.
Definition: fhs.py:740
def write_log(name=None, packagename=None)
Open a log file for writing.
Definition: fhs.py:1115
def lock(name=None, info='', packagename=None)
Acquire a lock.
Definition: fhs.py:1201
def module_info(modulename, desc, version, contact)
Register information about a module.
Definition: fhs.py:726
def help_text(main, options, option_order)
Definition: fhs.py:313
def module_get_config(modulename, extra=False)
Retrieve module configuration.
Definition: fhs.py:776
def get_config(extra=False)
Retrieve commandline configuration.
Definition: fhs.py:707
def write_spool(name=None, text=True, dir=False, opened=True, packagename=None)
Open a spool file for writing.
Definition: fhs.py:1141
def remove_runtime(name=None, dir=False, packagename=None)
Remove a reuntime file or directory.
Definition: fhs.py:839
def remove_config(name=None, dir=False, packagename=None)
Remove a config file.
Definition: fhs.py:247
def remove_spool(name=None, dir=False, packagename=None)
Remove a spool file.
Definition: fhs.py:1184
def encode_value(value)
Encode a value into a string which can be stored in a config file.
Definition: fhs.py:305
def option(name, help, short=None, multiple=False, optional=False, default=None, noarg=None, argtype=None, module=None, options=None, option_order=None)
Register commandline argument.
Definition: fhs.py:448