Completed
Pull Request — master (#2)
by James Ekow Abaka
01:13
created

ConsoleIO::output()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 3
crap 2
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 8
    private function cleanParamsAndPrintPrompt($question, $params)
105
    {
106 8
        $prompt = $question;
107
        
108 8
        $params = ['answers' => $params['answers'] ?? [], 'default' => $params['default'] ?? '', 'required' => $params['required'] ?? false];
109
        
110 8
        if (count($params['answers']) > 0) {
111 4
            $prompt .= " (" . implode("/", $params['answers']) . ")";
112
        }
113
114 8
        $this->output($prompt . " [{$params['default']}]: ");
115 8
        return $params;
116
    }
117
    
118 8
    private function validateResponse($response, $question, $params)
119
    {
120 8
        if ($response == "" && $params['required'] === true) {
121 1
            $this->error("A value is required.\n");
122 1
            return $this->getResponse($question, $params);
123 8
        } else if ($response == "" && $params['default'] != '') {
124 2
            return $params['default'];
125
        } else {
126 6
            if (count($params['answers']) == 0) {
127 4
                return $response;
128
            }
129 3
            foreach ($params['answers'] as $answer) {
130 3
                if (strtolower($answer) == strtolower($response)) {
131 3
                    return $answer;
132
                }
133
            }
134 2
            $this->error("Please provide a valid answer.\n");
135 2
            return $this->getResponse($question, $params);
136
        }
137
        
138
    }
139
140
    /**
141
     * A function for getting answers to questions from users interractively.
142
     * This function takes the question and an optional array of parameters. 
143
     * The question is a regular string and the array provides extra information
144
     * about the question being asked.
145
     * 
146
     * The array takes the following parameters
147
     * 
148
     * **answers**  
149
     * An array of posible answers to the question. Once this array is available
150
     * the user would be expected to provide an answer which is specifically in
151
     * the list. Any other answer would be rejected. The library would print
152
     * out all the possible answers so the user is aware of which answers
153
     * are valid.
154
     * 
155
     * **default**  
156
     * A default answer which should be used in case the user does not supply an
157
     * answer. The library would make the user aware of this default by placing
158
     * it in square brackets after the question.
159
     * 
160
     * **required**  
161
     * If this flag is set, the user would be required to provide an answer. A
162
     * blank answer would be rejected.
163
     * 
164
     * @param string $question The question you want to ask
165
     * @param array  $params   An array of options that this function takes.
166
     * @return string The response provided by the user to the prompt.
167
     */
168 8
    public function getResponse($question, $params = array())
169
    {
170 8
        $params = $this->cleanParamsAndPrintPrompt($question, $params);
171 8
        $response = str_replace(array("\n", "\r"), array("", ""), $this->input());
172 8
        return $this->validateResponse($response, $question, $params);
173
    }
174
175
    /**
176
     * Set the URL of any of the streams used by ClearIce.
177
     * ClearIce maintains three different streams for its I/O operations. The
178
     * `output` stream is used for output, the `error` stream is used for errors 
179
     * and the `input` stream is used for input. The `output` and `error` streams
180
     * are represented by the standard output and standard error streams 
181
     * respectively. The `input` stream on the other hand is represented by the 
182
     * standard input stream by default. 
183
     * 
184
     * Streams could be any valid PHP stream URL.
185
     * Example to write all output to a file you could set.
186
     * 
187
     * ````php
188
     * <?php
189
     * ClearIce::setStreamUrl('output', '/path/to/file');
190
     * ClearIce::setStreamUrl('error', '/path/to/file');
191
     * ````
192
     * 
193
     * Once a new URL is set, any old streams are closed and the new one is 
194
     * opened in its place immediately the stream is accessed.
195
     * 
196
     * @param string $type The type of stream to set a URL for. The value of this 
197
     *                     could either be 'error', 'input' or 'output'.
198
     * 
199
     * @param string $url  The URL of the stream. Based on the type of stream
200
     *                     being requested, the right kind of permissions must
201
     *                     be set. For instance 
202
     */
203 18
    public function setStreamUrl($type, $url)
204
    {
205 18
        $this->streamUrls[$type] = $url;
206 18
        unset($this->streams[$type]);
207 18
    }
208
209
    /**
210
     * Write a string to the output stream. 
211
     * If an output stream is not defined this method writes to the standard 
212
     * output (the console) by default.
213
     * 
214
     * @param string $string
215
     */
216 16
    public function output($string, $outputLevel = self::OUTPUT_LEVEL_1, $stream = 'output')
217
    {
218 16
        if ($outputLevel <= $this->defaultOutputLevel) {
219 16
            fputs($this->getStream($stream), $string);
220
        }
221 16
    }
222
223
    /**
224
     * Write a string to the error stream. 
225
     * If an error stream is not defined this method writes to the standard 
226
     * error (the console) by default.
227
     * 
228
     * @param string $string
229
     */
230 5
    public function error($string, $outputLevel = self::OUTPUT_LEVEL_1)
231
    {
232 5
        $this->output($string, $outputLevel, 'error');
233 5
    }
234
235
    /**
236
     * Set the output level of the ClearIce output streams (including the error)
237
     * stream. 
238
     * 
239
     * @param int $outputLevel
240
     */
241 3
    public function setOutputLevel($outputLevel)
242
    {
243 3
        $this->defaultOutputLevel = $outputLevel;
244 3
    }
245
246
    /**
247
     * Returns the current output level of the CliearIce library.
248
     * @return int
249
     */
250 2
    public function getOutputLevel()
251
    {
252 2
        return $this->defaultOutputLevel;
253
    }
254
255
    /**
256
     * Push an output level unto the output level stack.
257
     * The output level pushed becomes the new output level with which ClearIce
258
     * would work. The previous level pushed would be automatically restored
259
     * when the ClearIce::popOutputLevel() method is called. Using the output
260
     * level stack gives you a convenient way to change the output level 
261
     * temporarily without having to keep a record of the previous output level.
262
     * 
263
     * @param int $outputLevel
264
     */
265 1
    public function pushOutputLevel($outputLevel)
266
    {
267 1
        $this->outputLevelStack[] = $this->getOutputLevel();
268 1
        $this->setOutputLevel($outputLevel);
269 1
    }
270
271
    /**
272
     * Pop the last output level which was pushed unto the output level stack.
273
     * This restores the previous output level which was active before the last
274
     * call to the ClearIce::pushOutputLevel() method. Using the output
275
     * level stack gives you a convenient way to change the output level 
276
     * temporarily without having to keep a record of the previous output level.
277
     * 
278
     */
279 1
    public function popOutputLevel()
280
    {
281 1
        $this->setOutputLevel(array_pop($this->outputLevelStack));
282 1
    }
283
284
    /**
285
     * Resets the output level stack.
286
     * This method clears all items off the output level stack leaving only the
287
     * current output level.
288
     */
289 1
    public function resetOutputLevel()
290
    {
291 1
        if (count($this->outputLevelStack) > 0) {
292 1
            $this->setOutputLevel(reset($this->outputLevelStack));
293 1
            $this->outputLevelStack = array();
294
        }
295 1
    }
296
297
    /**
298
     * Reads a line of string from the input stream. 
299
     * If an input stream is not defined this method reads an input from the 
300
     * standard input (usually a keyboard) by default.
301
     * 
302
     * @todo look into using readline for this in cases where it's available
303
     * @return string
304
     */
305 8
    public function input()
306
    {
307 8
        return fgets($this->getStream('input'));
308
    }
309
310
    /**
311
     * Returns a stream resource for a given stream type. 
312
     * If the stream has not been opened this method opens the stream before 
313
     * returning the asociated resource. This ensures that there is only one 
314
     * resource handle to any stream at any given time.
315
     * 
316
     * @param string $type
317
     * @return resource
318
     */
319 16
    private function getStream($type)
320
    {
321 16
        if (!isset($this->streams[$type])) {
322 16
            $this->streams[$type] = fopen($this->streamUrls[$type], $type == 'input' ? 'r' : 'w');
323
        }
324 16
        return $this->streams[$type];
325
    }
326
327
}
328