Passed
Push — master ( 72fc34...cfd30c )
by Tom
02:20
created

EnvResolver::addLines()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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