Completed
Pull Request — master (#2)
by James Ekow Abaka
08:00
created

ConsoleIO::setOutputLevel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * ClearIce CLI Argument Parser
5
 * Copyright (c) 2012-2015 James Ekow Abaka Ainooson
6
 * 
7
 * Permission is hereby granted, free of charge, to any person obtaining
8
 * a copy of this software and associated documentation files (the
9
 * "Software"), to deal in the Software without restriction, including
10
 * without limitation the rights to use, copy, modify, merge, publish,
11
 * distribute, sublicense, and/or sell copies of the Software, and to
12
 * permit persons to whom the Software is furnished to do so, subject to
13
 * the following conditions:
14
 * 
15
 * The above copyright notice and this permission notice shall be
16
 * included in all copies or substantial portions of the Software.
17
 * 
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
25
 * 
26
 * @author James Ainooson <[email protected]>
27
 * @copyright Copyright 2012-2014 James Ekow Abaka Ainooson
28
 * @license MIT
29
 */
30
31
namespace clearice;
32
33
/**
34
 * The ClearIce class forms the entry for the entire library. 
35
 * All operations of the library are done through this class. Being static, 
36
 * the class contains sigleton objects with which it performs all its operations.
37
 */
38
class ConsoleIO
39
{
40
41
    /**
42
     * Least output level.
43
     * At this level clearice is expected to be mute. Nothing would be outputted
44
     * to any of the streams.
45
     * @var int
46
     */
47
    const OUTPUT_LEVEL_0 = 0;
48
49
    /**
50
     * Output level 1
51
     * @var int
52
     */
53
    const OUTPUT_LEVEL_1 = 1;
54
55
    /**
56
     * Output level 2
57
     * @var int
58
     */
59
    const OUTPUT_LEVEL_2 = 2;
60
61
    /**
62
     * Output level 3.
63
     * At this level clearice is expected not to filter any output. Everything
64
     * that is sent to ClearIce would be outputted to the streams.
65
     * @var int
66
     */
67
    const OUTPUT_LEVEL_3 = 3;
68
69
    /**
70
     * The default output level of the ClearIce library.
71
     * @var int
72
     */
73
    private $defaultOutputLevel = self::OUTPUT_LEVEL_1;
74
75
    /**
76
     * An array to hold the output level stack.
77
     * @var array
78
     */
79
    private $outputLevelStack = array();
80
81
    /**
82
     * An array of the three streams used primarily for I/O. These are the
83
     * standard output stream, the standard input stream and the error stream.
84
     * Being an associative array, this property presents the three streams
85
     * through its output, input and error keys.
86
     * 
87
     * @var array
88
     */
89
    private $streams = array();
90
91
    /**
92
     * The URLs of the various streams used for I/O. This variable stores these
93
     * URLs under the input, output and error streams respectively. 
94
     * 
95
     * @see ClearIce::$streams
96
     * @var array
97
     */
98
    private $streamUrls = array(
99
        'input' => 'php://stdin',
100
        'output' => 'php://stdout',
101
        'error' => 'php://stderr'
102
    );
103
104
    /**
105
     * A function for getting answers to questions from users interractively.
106
     * This function takes the question and an optional array of parameters. 
107
     * The question is a regular string and the array provides extra information
108
     * about the question being asked.
109
     * 
110
     * The array takes the following parameters
111
     * 
112
     * **answers**  
113
     * An array of posible answers to the question. Once this array is available
114
     * the user would be expected to provide an answer which is specifically in
115
     * the list. Any other answer would be rejected. The library would print
116
     * out all the possible answers so the user is aware of which answers
117
     * are valid.
118
     * 
119
     * **default**  
120
     * A default answer which should be used in case the user does not supply an
121
     * answer. The library would make the user aware of this default by placing
122
     * it in square brackets after the question.
123
     * 
124
     * **required**  
125
     * If this flag is set, the user would be required to provide an answer. A
126
     * blank answer would be rejected.
127
     * 
128
     * @param string $question The question you want to ask
129
     * @param array  $params   An array of options that this function takes.
130
     * @return string The response provided by the user to the prompt.
131
     */
132 9
    public function getResponse($question, $params = array())
133
    {
134 9
        $prompt = $question;
135
        
136 9
        $answers = $params['answers'] ?? [];
137 9
        $default = $params['default'] ?? '';
138 9
        $required = $params['required'] ?? false;
139
        
140 9
        if (count($answers ?? []) > 0) {
141 4
            $prompt .= " (" . implode("/", $answers) . ")";
142
        }
143
144 9
        $this->output($prompt . " [{$default}]: ");
145 9
        $response = str_replace(array("\n", "\r"), array("", ""), $this->input());
146
147 9
        if ($response == "" && $required === true && $default == '') {
148 1
            $this->error("A value is required.\n");
149 1
            return $this->getResponse($question, $params);
150 9
        } else if ($response == "" && $required === true && $default != '') {
151 1
            return $default;
152 8
        } else if ($response == "") {
153 3
            return $default;
154
        } else {
155 6
            if (count($answers) == 0) {
156 4
                return $response;
157
            }
158 3
            foreach ($answers as $answer) {
159 3
                if (strtolower($answer) == strtolower($response)) {
160 3
                    return strtolower($answer);
161
                }
162
            }
163 2
            $this->error("Please provide a valid answer.\n");
164 2
            return $this->getResponse($question, $params);
165
        }
166
    }
167
168
    /**
169
     * Set the URL of any of the streams used by ClearIce.
170
     * ClearIce maintains three different streams for its I/O operations. The
171
     * `output` stream is used for output, the `error` stream is used for errors 
172
     * and the `input` stream is used for input. The `output` and `error` streams
173
     * are represented by the standard output and standard error streams 
174
     * respectively. The `input` stream on the other hand is represented by the 
175
     * standard input stream by default. 
176
     * 
177
     * Streams could be any valid PHP stream URL.
178
     * Example to write all output to a file you could set.
179
     * 
180
     * ````php
181
     * <?php
182
     * ClearIce::setStreamUrl('output', '/path/to/file');
183
     * ClearIce::setStreamUrl('error', '/path/to/file');
184
     * ````
185
     * 
186
     * Once a new URL is set, any old streams are closed and the new one is 
187
     * opened in its place immediately the stream is accessed.
188
     * 
189
     * @param string $type The type of stream to set a URL for. The value of this 
190
     *                     could either be 'error', 'input' or 'output'.
191
     * 
192
     * @param string $url  The URL of the stream. Based on the type of stream
193
     *                     being requested, the right kind of permissions must
194
     *                     be set. For instance 
195
     */
196 19
    public function setStreamUrl($type, $url)
197
    {
198 19
        $this->streamUrls[$type] = $url;
199 19
        unset($this->streams[$type]);
200 19
    }
201
202
    /**
203
     * Write a string to the output stream. 
204
     * If an output stream is not defined this method writes to the standard 
205
     * output (the console) by default.
206
     * 
207
     * @param string $string
208
     */
209 17
    public function output($string, $outputLevel = self::OUTPUT_LEVEL_1, $stream = 'output')
210
    {
211 17
        if ($outputLevel <= $this->defaultOutputLevel) {
212 17
            fputs($this->getStream($stream), $string);
213
        }
214 17
    }
215
216
    /**
217
     * Write a string to the error stream. 
218
     * If an error stream is not defined this method writes to the standard 
219
     * error (the console) by default.
220
     * 
221
     * @param string $string
222
     */
223 5
    public function error($string, $outputLevel = self::OUTPUT_LEVEL_1)
224
    {
225 5
        $this->output($string, $outputLevel, 'error');
226 5
    }
227
228
    /**
229
     * Set the output level of the ClearIce output streams (including the error)
230
     * stream. 
231
     * 
232
     * @param int $outputLevel
233
     */
234 3
    public function setOutputLevel($outputLevel)
235
    {
236 3
        $this->defaultOutputLevel = $outputLevel;
237 3
    }
238
239
    /**
240
     * Returns the current output level of the CliearIce library.
241
     * @return int
242
     */
243 2
    public function getOutputLevel()
244
    {
245 2
        return $this->defaultOutputLevel;
246
    }
247
248
    /**
249
     * Push an output level unto the output level stack.
250
     * The output level pushed becomes the new output level with which ClearIce
251
     * would work. The previous level pushed would be automatically restored
252
     * when the ClearIce::popOutputLevel() method is called. Using the output
253
     * level stack gives you a convenient way to change the output level 
254
     * temporarily without having to keep a record of the previous output level.
255
     * 
256
     * @param int $outputLevel
257
     */
258 1
    public function pushOutputLevel($outputLevel)
259
    {
260 1
        $this->outputLevelStack[] = $this->getOutputLevel();
261 1
        $this->setOutputLevel($outputLevel);
262 1
    }
263
264
    /**
265
     * Pop the last output level which was pushed unto the output level stack.
266
     * This restores the previous output level which was active before the last
267
     * call to the ClearIce::pushOutputLevel() method. Using the output
268
     * level stack gives you a convenient way to change the output level 
269
     * temporarily without having to keep a record of the previous output level.
270
     * 
271
     */
272 1
    public function popOutputLevel()
273
    {
274 1
        $this->setOutputLevel(array_pop($this->outputLevelStack));
275 1
    }
276
277
    /**
278
     * Resets the output level stack.
279
     * This method clears all items off the output level stack leaving only the
280
     * current output level.
281
     */
282 1
    public function resetOutputLevel()
283
    {
284 1
        if (count($this->outputLevelStack) > 0) {
285 1
            $this->setOutputLevel(reset($this->outputLevelStack));
286 1
            $this->outputLevelStack = array();
287
        }
288 1
    }
289
290
    /**
291
     * Reads a line of string from the input stream. 
292
     * If an input stream is not defined this method reads an input from the 
293
     * standard input (usually a keyboard) by default.
294
     * 
295
     * @todo look into using readline for this in cases where it's available
296
     * @return string
297
     */
298 9
    public function input()
299
    {
300 9
        return fgets($this->getStream('input'));
301
    }
302
303
    /**
304
     * Returns a stream resource for a given stream type. 
305
     * If the stream has not been opened this method opens the stream before 
306
     * returning the asociated resource. This ensures that there is only one 
307
     * resource handle to any stream at any given time.
308
     * 
309
     * @param string $type
310
     * @return resource
311
     */
312 17
    private function getStream($type)
313
    {
314 17
        if (!isset($this->streams[$type])) {
315 17
            $this->streams[$type] = fopen($this->streamUrls[$type], $type == 'input' ? 'r' : 'w');
316
        }
317 17
        return $this->streams[$type];
318
    }
319
320
}
321