Completed
Push — master ( bd0acc...9933b8 )
by James Ekow Abaka
04:06
created

ClearIce::input()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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 static 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 ClearIce
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 static $defaultOutputLevel = self::OUTPUT_LEVEL_1;
74
75
    /**
76
     * An array to hold the output level stack.
77
     * @var array
78
     */
79
    private static $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 static $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 static $streamUrls = array(
99
        'input' => 'php://stdin',
100
        'output' => 'php://stdout',
101
        'error' => 'php://stderr'
102
    );
103
104
    /**
105
     * An instance of the ArgumentParser class which is maintained as a singleton
106
     * for the purposes of parsing command line arguments.
107
     * 
108
     * @var \clearice\ArgumentParser
109
     */
110
    private static $parser = null;
111
112
    /**
113
     * A function for getting answers to questions from users interractively.
114
     * This function takes the question and an optional array of parameters. 
115
     * The question is a regular string and the array provides extra information
116
     * about the question being asked.
117
     * 
118
     * The array takes the following parameters
119
     * 
120
     * **answers**  
121
     * An array of posible answers to the question. Once this array is available
122
     * the user would be expected to provide an answer which is specifically in
123
     * the list. Any other answer would be rejected. The library would print
124
     * out all the possible answers so the user is aware of which answers
125
     * are valid.
126
     * 
127
     * **default**  
128
     * A default answer which should be used in case the user does not supply an
129
     * answer. The library would make the user aware of this default by placing
130
     * it in square brackets after the question.
131
     * 
132
     * **required**  
133
     * If this flag is set, the user would be required to provide an answer. A
134
     * blank answer would be rejected.
135
     * 
136
     * @param string $question The question you want to ask
137
     * @param array  $params   An array of options that this function takes.
138
     * @return string The response provided by the user to the prompt.
139
     */
140 9
    public static function getResponse($question, $params = array()) {
141 9
        self::cleanResponseParams($params);
142 9
        $prompt = $question;
143 9
        if (count($params['answers']) > 0) {
144 4
            $prompt .= " (" . implode("/", $params['answers']) . ")";
145
        }
146
147 9
        self::output($prompt . " [{$params['default']}]: ");
148 9
        $response = str_replace(array("\n", "\r"), array("", ""), self::input());
149
150 9
        if ($response == "" && $params['required'] === true && $params['default'] == '') {
151 1
            self::error("A value is required.\n");
152 1
            return self::getResponse($question, $params);
153 9
        } else if ($response == "" && $params['required'] === true && $params['default'] != '') {
154 1
            return $params['default'];
155 8
        } else if ($response == "") {
156 3
            return $params['default'];
157
        } else {
158 6
            if (count($params['answers']) == 0) {
159 4
                return $response;
160
            }
161 3
            foreach ($params['answers'] as $answer) {
162 3
                if (strtolower($answer) == strtolower($response)) {
163 3
                    return strtolower($answer);
164
                }
165
            }
166 2
            self::error("Please provide a valid answer.\n");
167 2
            return self::getResponse($question, $params);
168
        }
169
    }
170
171
    /**
172
     * Set the URL of any of the streams used by ClearIce.
173
     * ClearIce maintains three different streams for its I/O operations. The
174
     * `output` stream is used for output, the `error` stream is used for errors 
175
     * and the `input` stream is used for input. The `output` and `error` streams
176
     * are represented by the standard output and standard error streams 
177
     * respectively. The `input` stream on the other hand is represented by the 
178
     * standard input stream by default. 
179
     * 
180
     * Streams could be any valid PHP stream URL.
181
     * Example to write all output to a file you could set.
182
     * 
183
     * ````php
184
     * <?php
185
     * ClearIce::setStreamUrl('output', '/path/to/file');
186
     * ClearIce::setStreamUrl('error', '/path/to/file');
187
     * ````
188
     * 
189
     * Once a new URL is set, any old streams are closed and the new one is 
190
     * opened in its place immediately the stream is accessed.
191
     * 
192
     * @param string $type The type of stream to set a URL for. The value of this 
193
     *                     could either be 'error', 'input' or 'output'.
194
     * 
195
     * @param string $url  The URL of the stream. Based on the type of stream
196
     *                     being requested, the right kind of permissions must
197
     *                     be set. For instance 
198
     */
199 22
    public static function setStreamUrl($type, $url) {
200 22
        self::$streamUrls[$type] = $url;
201 22
        unset(self::$streams[$type]);
202 22
    }
203
204
    /**
205
     * Safely EXIT the app. 
206
     * Usefull if testing so that the termination doesn't kill the test 
207
     * environment. 
208
     */
209 7
    public static function terminate() {
210 7
        if (!defined('TESTING'))
211
            die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method terminate() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
212 7
    }
213
214
    /**
215
     * Write a string to the output stream. 
216
     * If an output stream is not defined this method writes to the standard 
217
     * output (the console) by default.
218
     * 
219
     * @param string $string
220
     */
221 19
    public static function output($string, $outputLevel = self::OUTPUT_LEVEL_1, $stream = 'output') {
222 19
        if ($outputLevel <= self::$defaultOutputLevel) {
223 19
            fputs(self::getStream($stream), $string);
224
        }
225 19
    }
226
227
    /**
228
     * Write a string to the error stream. 
229
     * If an error stream is not defined this method writes to the standard 
230
     * error (the console) by default.
231
     * 
232
     * @param string $string
233
     */
234 5
    public static function error($string, $outputLevel = self::OUTPUT_LEVEL_1) {
235 5
        self::output($string, $outputLevel, 'error');
236 5
    }
237
238
    /**
239
     * Set the output level of the ClearIce output streams (including the error)
240
     * stream. 
241
     * 
242
     * @param int $outputLevel
243
     */
244 3
    public static function setOutputLevel($outputLevel) {
245 3
        self::$defaultOutputLevel = $outputLevel;
246 3
    }
247
248
    /**
249
     * Returns the current output level of the CliearIce library.
250
     * @return int
251
     */
252 2
    public static function getOutputLevel() {
253 2
        return self::$defaultOutputLevel;
254
    }
255
256
    /**
257
     * Push an output level unto the output level stack.
258
     * The output level pushed becomes the new output level with which ClearIce
259
     * would work. The previous level pushed would be automatically restored
260
     * when the ClearIce::popOutputLevel() method is called. Using the output
261
     * level stack gives you a convenient way to change the output level 
262
     * temporarily without having to keep a record of the previous output level.
263
     * 
264
     * @param int $outputLevel
265
     */
266 1
    public static function pushOutputLevel($outputLevel) {
267 1
        self::$outputLevelStack[] = self::getOutputLevel();
268 1
        self::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 static function popOutputLevel() {
280 1
        self::setOutputLevel(array_pop(self::$outputLevelStack));
281 1
    }
282
283
    /**
284
     * Resets the output level stack.
285
     * This method clears all items off the output level stack leaving only the
286
     * current output level.
287
     */
288 1
    public static function resetOutputLevel() {
289 1
        if (count(self::$outputLevelStack) > 0) {
290 1
            self::setOutputLevel(reset(self::$outputLevelStack));
291 1
            self::$outputLevelStack = array();
292
        }
293 1
    }
294
295
    /**
296
     * Reads a line of string from the input stream. 
297
     * If an input stream is not defined this method reads an input from the 
298
     * standard input (usually a keyboard) by default.
299
     * 
300
     * @todo look into using readline for this in cases where it's available
301
     * @return string
302
     */
303 9
    public static function input() {
304 9
        return fgets(self::getStream('input'));
305
    }
306
307
    /**
308
     * Returns a stream resource for a given stream type. 
309
     * If the stream has not been opened this method opens the stream before 
310
     * returning the asociated resource. This ensures that there is only one 
311
     * resource handle to any stream at any given time.
312
     * 
313
     * @param string $type
314
     * @return resource
315
     */
316 19
    private static function getStream($type) {
317 19
        if (!isset(self::$streams[$type])) {
318 19
            self::$streams[$type] = fopen(self::$streamUrls[$type], $type == 'input' ? 'r' : 'w');
319
        }
320 19
        return self::$streams[$type];
321
    }
322
323
    /**
324
     * Adds commands which are to be recognized during argument parsing.
325
     * Commands to be added could be passed as strings or structured arrays. 
326
     * This method takes as many arguments as possible. 
327
     * 
328
     * For example you could add commands with ...
329
     * 
330
     * ````php
331
     * <?php
332
     * ClearIce::addCommands('start', 'stop', 'restart');
333
     * ````
334
     * 
335
     * ... or more expressively ...
336
     * 
337
     * ````php
338
     * <?php
339
     * ClearIce::addCommands(
340
     *     array(
341
     *         'command' => 'start',
342
     *         'help' => 'start a new instance of the server'
343
     *     ),
344
     *     array(
345
     *         'command' => 'stop',
346
     *         'help' => 'stop the current instance of the server'
347
     *     ),
348
     *     array(
349
     *         'command' => 'restart',
350
     *         'help' => 'restart the current instance of the server'
351
     *     )
352
     * );
353
     * ````
354
     * 
355
     * A string argument would be taken as the text for the command. An 
356
     * array argument could have a combination of `command`, `help`, `class` and 
357
     * `usage` keys to help provide a more detailed command description. 
358
     * The `command` key stores the command text, the `help` key
359
     * stores the help message (which would be displayed in cases where
360
     * ClearIce's automatic help feature is used), the `usage` key specifies a 
361
     * usage syntax (which would also be displayed when the automatic help feature
362
     * is used) and the `class` key is the name of a class which implements the 
363
     * `Command` interface. The class specified in the `class` key would 
364
     * automatically be inistantiated when ClearIce is parsing the command line 
365
     * arguments. The options parsed on the command line would be passed on to 
366
     * the new object created.
367
     * 
368
     * @param string|array $command The command to be added for parsing.
369
     */
370 9
    public static function addCommands($command) {
0 ignored issues
show
Unused Code introduced by
The parameter $command is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
371 9
        self::callParserMethod('addCommands', func_get_args());
372 9
    }
373
374
    /**
375
     * Add an option to be recognized by the ClearIce parser.
376
     * Options added could be passed as strings or structured arrays. This method
377
     * takes as many arguments as needed. Options can also be tied to 
378
     * specific commands so they remain valid only when those commands
379
     * are specified.
380
     * 
381
     * Options can be added with
382
     * 
383
     * ````php
384
     * <?php
385
     * ClearIce::addOptions('input', 'output', 'format')
386
     * ````
387
     * 
388
     * ... or more expressively ...
389
     * 
390
     * ````php
391
     * <?php
392
     * ClearIce::addOptions(
393
     *     array(
394
     *         'short' => 'i',
395
     *         'long' => 'input',
396
     *         'has_value' => true,
397
     *         'help' => "specifies where the input files for the wiki are found.",
398
     *         'command' => 'generate'
399
     *     ),
400
     *     array(
401
     *         'short' => 'o',
402
     *         'long' => 'output',
403
     *         'has_value' => true,
404
     *         "help" => "specifies where the wiki should be written to",
405
     *         'command' => 'generate'
406
     *     )
407
     * );
408
     * ````
409
     * 
410
     * A string argument would be taken as the long version of the option. An
411
     * array argument must either have a `short` key, a `long` key or both. The
412
     * `long` key represents the long version of the option and the `short` key 
413
     * would hold a single character which represents a short form of the long
414
     * option. In addition to the `long` and `short` keys, you can also pass
415
     * a combination of any of the `has_value`, `help`, `command`, 'group' or `value`
416
     * keys.
417
     * 
418
     * The `has_value` key tells the argument parser that the option takes
419
     * a value. The `help` key is a short help message which (whoudl be displayed
420
     * when the automatic help feature is used). The `command` key specifies the 
421
     * name of a command to which the option should be associated. The `value`
422
     * key is a short description used, when generating help messages to give
423
     * the user an idea of the kind of value a given option takes. It is also
424
     * possible to group options on the automatic help page by passing a group 
425
     * name through the `group` key on the array.
426
     * 
427
     * @see ClearIce::addGroups
428
     * @see ClearIce::addCommands
429
     * 
430
     */
431 29
    public static function addOptions() {
432 29
        self::callParserMethod('addOptions', func_get_args());
433 29
    }
434
435
    /**
436
     * Add a group under which various options can be put.
437
     * Groups provide a nice way of grouping options when generating automatic
438
     * help messages. Groups are passed as arguments to this method as 
439
     * strutured arrays. The array has two keys: `group` and `help`. The `group`
440
     * key holds a unique key that identifies the group. The `help` key holds a 
441
     * description that will be displayed on the help message.
442
     */
443 1
    public static function addGroups() {
444 1
        self::callParserMethod('addGroups', func_get_args());
445 1
    }
446
447
    /**
448
     * Parse the command line arguments passed to the app.
449
     * This method parses the command line argumens and returns array which 
450
     * represents the options that were detected. The parse method also
451
     * instantiates and executes classes for commands which have specified
452
     * a `Command` class.
453
     * 
454
     * @return array A structured array which contains options as keys and the
455
     *       values assigned to the options as array values. Options which do not 
456
     *       accept value would have true assigned.
457
     */
458 27
    public static function parse() {
459 27
        return self::getParserInstance()->parse();
460
    }
461
462
    /**
463
     * Set a usage hint for your application.
464
     * The usage hint specified would be displayed when the automatic help 
465
     * feature of the library is used. Either a single line string or an array
466
     * of strings could be passed to this method. Usage strings passed as arrays
467
     * would be properly formatted into a multi-line usage hint format.
468
     * 
469
     * @param string|array $usage A short usage hint for the application.
470
     */
471 7
    public static function setUsage($usage) {
472 7
        self::getParserInstance()->setUsage($usage);
473 7
    }
474
475
    /**
476
     * Set a description for your application.
477
     * The description specified would be displayed when the automatic help
478
     * feature of the library is used. This description text is described before
479
     * the help message and as such can include anything from the name of the
480
     * application, copyright information to ACII art graphics.
481
     * 
482
     * @param string $description Text for the description message.
483
     */
484 7
    public static function setDescription($description) {
485 7
        self::getParserInstance()->setDescription($description);
486 7
    }
487
488
    /**
489
     * Set a foonote for the help message of your application.
490
     * The footnote specified would be displayed at the bottom of the automatic
491
     * help message generated. This text could contain information about where
492
     * to find more help, how to report bugs or you could also put some awesome
493
     * ASCII art here.
494
     * 
495
     * @param string $footnote Text for the footnote message
496
     */
497 7
    public static function setFootnote($footnote) {
498 7
        self::getParserInstance()->setFootnote($footnote);
499 7
    }
500
501
    /**
502
     * Add the automatic help options.
503
     * This would add the `-h` and `--help` options to your application. When
504
     * these options are passed, the library would automatically display a help
505
     * message to the user. The help options are also added to commands so
506
     * users can get help which are specific to commands.
507
     */
508 8
    public static function addHelp() {
509 8
        self::getParserInstance()->addHelp();
510 8
    }
511
512
    /**
513
     * Generate and return a help message.
514
     * This method generates and returns a help message based on the various
515
     * options and commands passed to the library. This method is used internally
516
     * to generate and display the automatic help message. Note however that this
517
     * message would still be active even when the automatic help message has
518
     * not been activated.
519
     * 
520
     * @param string $command Generate the help message for the command specified 
521
     *     or null to generate help for the application.
522
     * @return string
523
     */
524 3
    public static function getHelpMessage($command = '') {
525 3
        return self::getParserInstance()->getHelpMessage($command);
526
    }
527
528
    /**
529
     * Sets the parser into strict mode.
530
     * A strict parser would terminate the application if it doesn't understand 
531
     * any options. A not-strict parser would just return the unknown options it 
532
     * encountered and expect the application to deal with it appropriately. 
533
     * When a parser is in strict mode, ClearIce prints a descriptive help 
534
     * message which advises the user about the unknown options. In cases where 
535
     * the help feature has been enabled, ClearIce would go ahead to advice the 
536
     * user to request for help.
537
     * 
538
     * @param bool $strict
539
     */
540 2
    public static function setStrict($strict) {
541 2
        self::getParserInstance()->setStrict($strict);
542 2
    }
543
544
    /**
545
     * Returns a singleton instance of the argument parser.
546
     * 
547
     * @return \clearice\ArgumentParser
548
     */
549 30
    private static function getParserInstance() {
550 30
        if (self::$parser === null) {
551 29
            self::$parser = new ArgumentParser();
552
        }
553 30
        return self::$parser;
554
    }
555
556
    /**
557
     * @param string $name The name of the method called
558
     * @param array $arguments An array of arguments passed to the method
559
     * @return mixed
560
     * @throws \Exception
561
     */
562 30
    private static function callParserMethod($name, $arguments) {
563 30
        $parser = self::getParserInstance();
564 30
        $method = new \ReflectionMethod($parser, $name);
565 30
        return $method->invokeArgs($parser, $arguments);
566
    }
567
568
    /**
569
     * Reset the library. Deletes all singletons and provides you with a fresh
570
     * class ... sort of! This method is primarily used during testing to 
571
     * refresh the class in between tests.
572
     */
573 30
    public static function reset() {
574 30
        self::$parser = null;
575 30
    }
576
577 9
    private static function cleanResponseParams(&$params) {
578 9
        $params['answers'] = isset($params['answers']) ? $params['answers'] : [];
579 9
        $params['default'] = isset($params['default']) ? $params['default'] : '';
580 9
        $params['required'] = isset($params['required']) ? $params['required'] : false;
581 9
    }
582
    
583
    public static function setContainer($container) {
584
        self::getParserInstance()->setContainer($container);
585
    }
586
587
}
588