7444: Delete containers as soon as they stop.
[arvados.git] / crunch_scripts / crunchutil / subst.py
1 import glob
2 import os
3 import re
4 import stat
5
6 BACKSLASH_ESCAPE_RE = re.compile(r'\\(.)')
7
8 class SubstitutionError(Exception):
9     pass
10
11 def search(c):
12     DEFAULT = 0
13     DOLLAR = 1
14
15     i = 0
16     state = DEFAULT
17     start = None
18     depth = 0
19     while i < len(c):
20         if c[i] == '\\':
21             i += 1
22         elif state == DEFAULT:
23             if c[i] == '$':
24                 state = DOLLAR
25                 if depth == 0:
26                     start = i
27             elif c[i] == ')':
28                 if depth == 1:
29                     return [start, i]
30                 if depth > 0:
31                     depth -= 1
32         elif state == DOLLAR:
33             if c[i] == '(':
34                 depth += 1
35             state = DEFAULT
36         i += 1
37     if depth != 0:
38         raise SubstitutionError("Substitution error, mismatched parentheses {}".format(c))
39     return None
40
41 def sub_file(v):
42     path = os.path.join(os.environ['TASK_KEEPMOUNT'], v)
43     st = os.stat(path)
44     if st and stat.S_ISREG(st.st_mode):
45         return path
46     else:
47         raise SubstitutionError("$(file {}) is not accessible or is not a regular file".format(path))
48
49 def sub_dir(v):
50     d = os.path.dirname(v)
51     if d == '':
52         d = v
53     path = os.path.join(os.environ['TASK_KEEPMOUNT'], d)
54     st = os.stat(path)
55     if st and stat.S_ISDIR(st.st_mode):
56         return path
57     else:
58         raise SubstitutionError("$(dir {}) is not accessible or is not a directory".format(path))
59
60 def sub_basename(v):
61     return os.path.splitext(os.path.basename(v))[0]
62
63 def sub_glob(v):
64     l = glob.glob(v)
65     if len(l) == 0:
66         raise SubstitutionError("$(glob {}) no match fonud".format(v))
67     else:
68         return l[0]
69
70 default_subs = {"file ": sub_file,
71                 "dir ": sub_dir,
72                 "basename ": sub_basename,
73                 "glob ": sub_glob}
74
75 def do_substitution(p, c, subs=default_subs):
76     while True:
77         m = search(c)
78         if m is None:
79             return BACKSLASH_ESCAPE_RE.sub(r'\1', c)
80
81         v = do_substitution(p, c[m[0]+2 : m[1]])
82         var = True
83         for sub in subs:
84             if v.startswith(sub):
85                 r = subs[sub](v[len(sub):])
86                 var = False
87                 break
88         if var:
89             if v in p:
90                 r = p[v]
91             else:
92                 raise SubstitutionError("Unknown variable or function '%s' while performing substitution on '%s'" % (v, c))
93             if r is None:
94                 raise SubstitutionError("Substitution for '%s' is null while performing substitution on '%s'" % (v, c))
95             if not isinstance(r, basestring):
96                 raise SubstitutionError("Substitution for '%s' must be a string while performing substitution on '%s'" % (v, c))
97
98         c = c[:m[0]] + r + c[m[1]+1:]