Configuration management framework as presented on my blog's "Dotfiles" series.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

config-manage 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. import argparse
  5. import configparser
  6. import contextlib
  7. import subprocess
  8. def eprint(*args, **kwargs):
  9. print(*args, file=sys.stderr, **kwargs)
  10. @contextlib.contextmanager
  11. def change_cwd(cwd):
  12. """A simple context manager which changes the CWD to the specified one and
  13. when it ends, changes it back to the current one."""
  14. current = os.getcwd()
  15. os.chdir(cwd)
  16. yield
  17. os.chdir(current)
  18. def escape(s):
  19. return s.replace('$', '$$')
  20. def execute(target, cfg, cmd_name):
  21. target, _, posargs = target.partition(':')
  22. try:
  23. # if there's no custom configuration, create a dummy section, which will
  24. # use [DEFAULT] settings
  25. cfg.add_section(target)
  26. except configparser.DuplicateSectionError:
  27. pass
  28. cfg.set(target, 'posargs', posargs)
  29. cfg.set(target, 'targetname', escape(target))
  30. cmd = cfg.get(target, cmd_name)
  31. cmds = cmd.split('\n')
  32. for i, cmd in enumerate(cmds):
  33. eprint("%s[%s-%d]: %s" % (cmd_name, target, i, cmd))
  34. if cmd.startswith('-'):
  35. subprocess.call(cmd[1:], shell=True)
  36. else:
  37. try:
  38. subprocess.check_call(cmd, shell=True)
  39. except subprocess.CalledProcessError as err:
  40. eprint("Command failed with status: %d" % err.returncode)
  41. sys.exit(1)
  42. def install(target, cfg):
  43. execute(target, cfg, 'install_cmd')
  44. def uninstall(target, cfg):
  45. execute(target, cfg, 'uninstall_cmd')
  46. def parse_args():
  47. parser = argparse.ArgumentParser()
  48. subp = parser.add_subparsers()
  49. subp.required = True
  50. subp.dest = 'subcommand'
  51. install_cmd = subp.add_parser('install',
  52. help='install given targets')
  53. install_cmd.add_argument('targets', nargs='+',
  54. help='list of targets. Accepts target names or \
  55. targets in form "name:target_posargs"')
  56. install_cmd.set_defaults(func=install)
  57. uninstall_cmd = subp.add_parser('uninstall',
  58. help='uninstall given targets')
  59. uninstall_cmd.add_argument('targets', nargs='+')
  60. uninstall_cmd.set_defaults(func=uninstall)
  61. return parser.parse_args()
  62. def main():
  63. args = parse_args()
  64. script_dir = os.path.dirname(os.path.realpath(__file__))
  65. venv_dir = os.path.join(script_dir, '.venv')
  66. cfg = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
  67. cfg.optionxform = str # disable case-insensitivity
  68. # some convienient variables
  69. cfg.add_section('var')
  70. cfg.set('var', 'home', escape(os.environ['HOME']))
  71. cfg.set('var', 'workdir', escape(script_dir))
  72. cfg.set('var', 'venv', escape(venv_dir))
  73. # Add environment variables to a special section: 'env'.
  74. # Because environment variables can contain any string, we must escape
  75. # ExtendedInterpolation's special '$' sign.
  76. cfg.add_section('env')
  77. for key, val in os.environ.items():
  78. cfg.set('env', key, escape(val))
  79. with change_cwd(script_dir):
  80. cfg.read('config.ini')
  81. for target in args.targets:
  82. args.func(target, cfg)
  83. return 0
  84. sys.exit(main())