Passed
Push — master ( 74010f...cd30de )
by Tom
03:00
created

EnvResolver   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 49
dl 0
loc 200
ccs 58
cts 58
cp 1
rs 10
c 1
b 0
f 1
wmc 22

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getValue() 0 5 2
A addArguments() 0 10 3
A addFile() 0 10 2
A addLines() 0 5 2
A addFileIfExists() 0 9 2
A __invoke() 0 8 2
A addDefinition() 0 19 4
A getVariables() 0 3 1
A resolveString() 0 16 3
1
<?php
2
3
/* this file is part of pipelines */
4
5
namespace Ktomk\Pipelines\Runner;
6
7
use InvalidArgumentException;
8
use Ktomk\Pipelines\Cli\Args\Args;
9
use Ktomk\Pipelines\Cli\Args\OptionFilterIterator;
10
use Ktomk\Pipelines\LibFs;
11
use UnexpectedValueException;
12
13
/**
14
 * resolve environment variables against docker --env & --env-file arguments
15
 *
16
 * some string values in the bitbucket-pipelines.yml file might need resolution
17
 * against a part of the current host environment but only if set for the
18
 * container as well (--env-file, -e, --env)
19
 *
20
 * @package Ktomk\Pipelines\Runner\Runner
21
 */
22
class EnvResolver
23
{
24
    /**
25
     * @var array host environment (that exports)
26
     */
27
    private $environment;
28
29
    /**
30
     * @var array container environment (partial w/o bitbucket environment)
31
     */
32
    private $variables;
33
34
    /**
35
     * EnvResolver constructor.
36
     *
37
     * @param array|string[] $environment host environment variables (name => string)
38
     */
39 11
    public function __construct(array $environment)
40
    {
41 11
        $this->environment = array_filter($environment, 'is_string');
42 11
    }
43
44
    /**
45
     * resolve a string or an array of strings
46
     *
47
     * @param array|string $stringOrArray
48
     *
49
     * @throws UnexpectedValueException
50
     *
51
     * @return array|string
52
     *
53
     * @see resolveString
54
     */
55 1
    public function __invoke($stringOrArray)
56
    {
57
        // TODO(tk): provide full environment (string) on NULL parameter
58 1
        if (is_array($stringOrArray)) {
59 1
            return array_map(array($this, 'resolveString'), $stringOrArray);
60
        }
61
62 1
        return $this->resolveString($stringOrArray);
63
    }
64
65
    /**
66
     * @param Args $args
67
     *
68
     * @throws InvalidArgumentException
69
     *
70
     * @return void
71
     */
72 1
    public function addArguments(Args $args)
73
    {
74 1
        $files = new OptionFilterIterator($args, 'env-file');
75 1
        foreach ($files->getArguments() as $file) {
76 1
            $this->addFile($file);
77
        }
78
79 1
        $definitions = new OptionFilterIterator($args, array('e', 'env'));
80 1
        foreach ($definitions->getArguments() as $definition) {
81 1
            $this->addDefinition($definition);
82
        }
83 1
    }
84
85
    /**
86
     * add a file (--env-file option)
87
     *
88
     * @param string $file path to file
89
     *
90
     * @throws InvalidArgumentException
91
     *
92
     * @return void
93
     */
94 4
    public function addFile($file)
95
    {
96 4
        $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
97 4
        if (false === $lines) {
98 1
            throw new InvalidArgumentException(sprintf(
99 1
                "File read error: '%s'",
100
                $file
101
            ));
102
        }
103 3
        $this->addLines($lines);
104 3
    }
105
106
    /**
107
     * add a file but only if it exists (similar to --env-file option)
108
     *
109
     * @param string $file path to (potentially existing) file
110
     *
111
     * @throws InvalidArgumentException
112
     *
113
     * @return bool file was added
114
     */
115 2
    public function addFileIfExists($file)
116
    {
117 2
        if (!LibFs::isReadableFile($file)) {
118 1
            return false;
119
        }
120
121 1
        $this->addFile($file);
122
123 1
        return true;
124
    }
125
126
    /**
127
     * @param array $lines
128
     *
129
     * @throws InvalidArgumentException
130
     *
131
     * @return void
132
     */
133 4
    public function addLines(array $lines)
134
    {
135 4
        $definitions = preg_grep('~^(\s*#.*|\s*)$~', $lines, PREG_GREP_INVERT);
136 4
        foreach ($definitions as $definition) {
137 4
            $this->addDefinition($definition);
138
        }
139 4
    }
140
141
    /**
142
     * add a variable definition (-e, --env option)
143
     *
144
     * @param string $definition variable definition, either name only or w/ equal sign
145
     *
146
     * @throws InvalidArgumentException
147
     *
148
     * @return void
149
     */
150 7
    public function addDefinition($definition)
151
    {
152 7
        $pattern = '~^([^$={}\\x0-\\x20\\x7f-\\xff-]+)(?:=(.*))?$~';
153
154 7
        $result = preg_match($pattern, $definition, $matches);
155 7
        if (0 === $result) {
156 1
            throw new InvalidArgumentException(sprintf(
157 1
                "Variable definition error: '%s'",
158
                $definition
159
            ));
160
        }
161
162 6
        list(, $name, $value) = $matches + array(2 => null);
163
164 6
        if (null === $value && isset($this->environment[$name])) {
165 5
            $value = $this->environment[$name];
166
        }
167
168 6
        $this->variables[$name] = $value;
169 6
    }
170
171
    /**
172
     * get value of variable
173
     *
174
     * @param string $name of variable to obtain value from
175
     *
176
     * @return null|string value, null if unset
177
     */
178 8
    public function getValue($name)
179
    {
180 8
        return isset($this->variables[$name])
181 5
            ? $this->variables[$name]
182 8
            : null;
183
    }
184
185
    /**
186
     * @return array
187
     */
188 1
    public function getVariables()
189
    {
190 1
        return $this->variables;
191
    }
192
193
    /**
194
     * replace variable with its content if it is a portable, Shell and
195
     * Utilities variable name (see POSIX).
196
     *
197
     * zero-length string if the variable is undefined in the resolver
198
     * context.
199
     *
200
     * @param string $string
201
     *
202
     * @throws UnexpectedValueException
203
     *
204
     * @return string
205
     */
206 2
    public function resolveString($string)
207
    {
208 2
        $pattern = '~^\$([A-Z_]+[0-9A-Z_]*)$~';
209 2
        $result = preg_match($pattern, $string, $matches);
210 2
        if (false === $result) {
211
            throw new UnexpectedValueException('regex pattern error'); // @codeCoverageIgnore
212
        }
213
214 2
        if (0 === $result) {
215 2
            return $string;
216
        }
217
218 2
        list(, $name) = $matches;
219 2
        $value = $this->getValue($name);
220
221 2
        return (string)$value;
222
    }
223
}
224