summaryrefslogtreecommitdiff
path: root/iniherit/interpolation.py
blob: d368f44c1c8328effd05e64997b5edfd7529ea6c (plain)
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
# -*- coding: utf-8 -*-
#------------------------------------------------------------------------------
# file: $Id$
# auth: Philip J Grabner <grabner@cadit.com>
# date: 2016/12/14
# copy: (C) Copyright 2016-EOT Cadit Inc., All Rights Reserved.
#------------------------------------------------------------------------------

import re
import os

import six
from six.moves import configparser as CP

#------------------------------------------------------------------------------

if six.PY3:
  _real_BasicInterpolation              = CP.BasicInterpolation
  _real_BasicInterpolation_before_get   = CP.BasicInterpolation.before_get
else:
  _real_BasicInterpolation              = object
  _real_BasicInterpolation_before_get   = None

#------------------------------------------------------------------------------
class InterpolationMissingEnvError(CP.InterpolationMissingOptionError): pass
class InterpolationMissingSuperError(CP.InterpolationMissingOptionError): pass


#------------------------------------------------------------------------------
class BasicInterpolationMixin(object):
  def before_get(self, parser, section, option, value, defaults):
    def base_interpolate(*args, **kw):
      return _real_BasicInterpolation_before_get(parser._interpolation, *args, **kw)
    return interpolate(parser, base_interpolate, section, option, value, defaults)


#------------------------------------------------------------------------------
class IniheritInterpolation(BasicInterpolationMixin, _real_BasicInterpolation):
  # todo: rewrite this to use a more PY3-oriented approach...
  pass


#------------------------------------------------------------------------------
_env_cre = re.compile(r'%\(ENV:([^:)]+)(:-([^)]*))?\)s', flags=re.DOTALL)
_super_cre = re.compile(r'%\(SUPER(:-([^)]*))?\)s', flags=re.DOTALL)
def interpolate(parser, base_interpolate, section, option, rawval, vars):
  # todo: ugh. this should be rewritten so that it uses
  #       `BasicInterpolationMixin` so as to be more "future-proof"...
  value = rawval
  depth = CP.MAX_INTERPOLATION_DEPTH
  erepl = lambda match: _env_replace(
    match, parser, base_interpolate, section, option, rawval, vars)
  srepl = lambda match: _super_replace(
    match, parser, parser, None, section, option, rawval, vars)
  while depth and ( _env_cre.search(value) or _super_cre.search(value) ):
    depth -= 1
    value = _env_cre.sub(erepl, value)
    value = _super_cre.sub(srepl, value)
  if not depth and ( _env_cre.search(value) or _super_cre.search(value) ):
    raise CP.InterpolationDepthError(option, section, rawval)
  if '%(SUPER)s' in value:
    raise InterpolationMissingSuperError(option, section, rawval, 'SUPER')
  if base_interpolate is None:
    return value
  vars = dict(vars)
  while True:
    # ok, this is... uh... "tricky"... basically, we don't want to
    # pre-emptively expand SUPER & ENV expressions because then we may
    # trip invalid expressions that aren't actually used. thus, we
    # only expand keys that are actually requested, and we detect by
    # catching InterpolationMissingOptionError's...
    try:
      return base_interpolate(parser, section, option, value, vars)
    except CP.InterpolationMissingOptionError as err:
      for key, val in list(vars.items()):
        if err.reference.lower() in val.lower():
          newval = interpolate(parser, None, section, key, val, vars)
          if newval != val:
            vars[key] = newval
            break
      else:
        raise

#------------------------------------------------------------------------------
def interpolate_super(parser, src, dst, section, option, value):
  srepl = lambda match: _super_replace(
    match, parser, src, dst, section, option, value, None)
  value = _super_cre.sub(srepl, value)
  return value

#------------------------------------------------------------------------------
def _env_replace(match, parser, base_interpolate, section, option, rawval, vars):
  if match.group(1) in os.environ:
    return os.environ.get(match.group(1))
  if match.group(2):
    return match.group(3)
  raise InterpolationMissingEnvError(option, section, rawval, match.group(1))

#------------------------------------------------------------------------------
def _super_replace(match, parser, src, dst, section, option, rawval, vars):
  if dst \
     and ( section == parser.IM_DEFAULTSECT or dst.has_section(section) ) \
     and dst.has_option(section, option):
    try:
      return dst.get(section, option, raw=True, vars=vars)
    except TypeError:
      return dst.get(section, option)
  if dst:
    return match.group(0)
  if match.group(1):
    return match.group(2)
  raise InterpolationMissingSuperError(option, section, rawval, 'SUPER')

#------------------------------------------------------------------------------
# end of $Id$
# $ChangeLog$
#------------------------------------------------------------------------------