Completed
Push — master ( 8bde3f...f8d0b5 )
by James Ekow Abaka
02:09
created

ClearIce::addGroups()   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 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * ClearIce CLI Argument Parser
4
 * Copyright (c) 2012-2015 James Ekow Abaka Ainooson
5
 * 
6
 * Permission is hereby granted, free of charge, to any person obtaining
7
 * a copy of this software and associated documentation files (the
8
 * "Software"), to deal in the Software without restriction, including
9
 * without limitation the rights to use, copy, modify, merge, publish,
10
 * distribute, sublicense, and/or sell copies of the Software, and to
11
 * permit persons to whom the Software is furnished to do so, subject to
12
 * the following conditions:
13
 * 
14
 * The above copyright notice and this permission notice shall be
15
 * included in all copies or substantial portions of the Software.
16
 * 
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
24
 * 
25
 * @author James Ainooson <[email protected]>
26
 * @copyright Copyright 2012-2014 James Ekow Abaka Ainooson
27
 * @license MIT
28
 */
29
30
namespace clearice;
31
32
/**
33
 * The ClearIce class forms the static entry for the entire library. 
34
 * All operations of the library are done through this class. Being static, 
35
 * the class contains sigleton objects with which it performs all its operations.
36
 */
37
class ClearIce
38
{
39
    /**
40
     * Least output level.
41
     * At this level clearice is expected to be mute. Nothing would be outputted
42
     * to any of the streams.
43
     * @var int
44
     */
45
    const OUTPUT_LEVEL_0 = 0;
46
    
47
    /**
48
     * Output level 1
49
     * @var int
50
     */
51
    const OUTPUT_LEVEL_1 = 1;
52
    
53
    /**
54
     * Output level 2
55
     * @var int
56
     */
57
    const OUTPUT_LEVEL_2 = 2;
58
    
59
    /**
60
     * Output level 3.
61
     * At this level clearice is expected not to filter any output. Everything
62
     * that is sent to ClearIce would be outputted to the streams.
63
     * @var int
64
     */
65
    const OUTPUT_LEVEL_3 = 3;
66
    
67
    /**
68
     * The default output level of the ClearIce library.
69
     * @var int
70
     */
71
    private static $defaultOutputLevel = self::OUTPUT_LEVEL_1;
72
    
73
    /**
74
     * An array to hold the output level stack.
75
     * @var array
76
     */
77
    private static $outputLevelStack = array();
78
    
79
    /**
80
     * An array of the three streams used primarily for I/O. These are the
81
     * standard output stream, the standard input stream and the error stream.
82
     * Being an associative array, this property presents the three streams
83
     * through its output, input and error keys.
84
     * 
85
     * @var array
86
     */
87
    private static $streams = array();
88
    
89
    /**
90
     * The URLs of the various streams used for I/O. This variable stores these
91
     * URLs under the input, output and error streams respectively. 
92
     * 
93
     * @see ClearIce::$streams
94
     * @var array
95
     */
96
    private static $streamUrls = array(
97
        'input' => 'php://stdin',
98
        'output' => 'php://stdout',
99
        'error' => 'php://stderr'
100
    );
101
    
102
    /**
103
     * An instance of the ArgumentParser class which is maintained as a singleton
104
     * for the purposes of parsing command line arguments.
105
     * 
106
     * @var \clearice\ArgumentParser
107
     */
108
    private static $parser = null;
109
    
110
    /**
111
     * A function for getting answers to questions from users interractively.
112
     * This function takes the question and an optional array of parameters. 
113
     * The question is a regular string and the array provides extra information
114
     * about the question being asked.
115
     * 
116
     * The array takes the following parameters
117
     * 
118
     * **answers**  
119
     * An array of posible answers to the question. Once this array is available
120
     * the user would be expected to provide an answer which is specifically in
121
     * the list. Any other answer would be rejected. The library would print
122
     * out all the possible answers so the user is aware of which answers
123
     * are valid.
124
     * 
125
     * **default**  
126
     * A default answer which should be used in case the user does not supply an
127
     * answer. The library would make the user aware of this default by placing
128
     * it in square brackets after the question.
129
     * 
130
     * **required**  
131
     * If this flag is set, the user would be required to provide an answer. A
132
     * blank answer would be rejected.
133
     * 
134
     * @param string $question The question you want to ask
135
     * @param array  $params   An array of options that this function takes.
136
     * @return string The response provided by the user to the prompt.
137
     */
138 9
    public static function getResponse($question, $params = array())
139
    {
140 9
        self::cleanResponseParams($params);
141 9
        $prompt = $question;
142 9
        if(count($params['answers']) > 0) {
143 4
            $prompt .= " (" . implode("/", $params['answers']) . ")";
144 4
        }
145
146 9
        self::output($prompt . " [{$params['default']}]: ");
147 9
        $response = str_replace(array("\n", "\r"),array("",""), self::input());
148
149 9
        if($response == "" && $params['required'] === true && $params['default'] == '')
150 9
        {
151 1
            self::error("A value is required.\n");
152 1
            return self::getResponse($question, $params);
153
        }
154 9
        else if($response == "" && $params['required'] === true && $params['default'] != '')
155 9
        {
156 1
            return $params['default'];
157
        }
158 8
        else if($response == "")
159 8
        {
160 3
            return $params['default'];
161
        }
162
        else
163
        {
164 6
            if(count($params['answers']) == 0)
165 6
            {
166 4
                return $response;
167
            }
168 3
            foreach($params['answers'] as $answer)
169
            {
170 3
                if(strtolower($answer) == strtolower($response))
171 3
                {
172 2
                    return strtolower($answer);
173
                }
174 2
            }
175 2
            self::error("Please provide a valid answer.\n");
176 2
            return self::getResponse($question, $params);
177
        }
178
    } 
179
    
180
    /**
181
     * Set the URL of any of the streams used by ClearIce.
182
     * ClearIce maintains three different streams for its I/O operations. The
183
     * `output` stream is used for output, the `error` stream is used for errors 
184
     * and the `input` stream is used for input. The `output` and `error` streams
185
     * are represented by the standard output and standard error streams 
186
     * respectively. The `input` stream on the other hand is represented by the 
187
     * standard input stream by default. 
188
     * 
189
     * Streams could be any valid PHP stream URL.
190
     * Example to write all output to a file you could set.
191
     * 
192
     * ````php
193
     * <?php
194
     * ClearIce::setStreamUrl('output', '/path/to/file');
195
     * ClearIce::setStreamUrl('error', '/path/to/file');
196
     * ````
197
     * 
198
     * Once a new URL is set, any old streams are closed and the new one is 
199
     * opened in its place immediately the stream is accessed.
200
     * 
201
     * @param string $type The type of stream to set a URL for. The value of this 
202
     *                     could either be 'error', 'input' or 'output'.
203
     * 
204
     * @param string $url  The URL of the stream. Based on the type of stream
205
     *                     being requested, the right kind of permissions must
206
     *                     be set. For instance 
207
     */
208 19
    public static function setStreamUrl($type, $url)
209
    {
210 19
        self::$streamUrls[$type] = $url;
211 19
        unset(self::$streams[$type]);
212 19
    }
213
    
214
    /**
215
     * Safely EXIT the app. 
216
     * Usefull if testing so that the termination doesn't kill the test 
217
     * environment. 
218
     */
219 7
    public static function terminate()
220
    {
221 7
        if(!defined('TESTING')) 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...
222 7
    }
223
    
224
    /**
225
     * Write a string to the output stream. 
226
     * If an output stream is not defined this method writes to the standard 
227
     * output (the console) by default.
228
     * 
229
     * @param string $string
230
     */
231 18
    public static function output($string, $outputLevel = self::OUTPUT_LEVEL_1, $stream = 'output')
232
    {
233 18
        if($outputLevel <= self::$defaultOutputLevel)
234 18
        {
235 18
            fputs(self::getStream($stream), $string);
236 18
        }
237 18
    }
238
    
239
    /**
240
     * Write a string to the error stream. 
241
     * If an error stream is not defined this method writes to the standard 
242
     * error (the console) by default.
243
     * 
244
     * @param string $string
245
     */    
246 5
    public static function error($string, $outputLevel = self::OUTPUT_LEVEL_1)
247
    {
248 5
        self::output($string, $outputLevel, 'error');
249 5
    }    
250
    
251
    /**
252
     * Set the output level of the ClearIce output streams (including the error)
253
     * stream. 
254
     * 
255
     * @param int $outputLevel
256
     */
257 2
    public static function setOutputLevel($outputLevel)
258
    {
259 2
        self::$defaultOutputLevel = $outputLevel;
260 2
    }
261
    
262
    /**
263
     * Returns the current output level of the CliearIce library.
264
     * @return int
265
     */
266 1
    public static function getOutputLevel()
267
    {
268 1
        return self::$defaultOutputLevel;
269
    }
270
    
271
    /**
272
     * Push an output level unto the output level stack.
273
     * The output level pushed becomes the new output level with which ClearIce
274
     * would work. The previous level pushed would be automatically restored
275
     * when the ClearIce::popOutputLevel() method is called. Using the output
276
     * level stack gives you a convenient way to change the output level 
277
     * temporarily without having to keep a record of the previous output level.
278
     * 
279
     * @param int $outputLevel
280
     */
281
    public static function pushOutputLevel($outputLevel)
282
    {
283
        self::$outputLevelStack[] = self::getOutputLevel();
284
        self::setOutputLevel($outputLevel);
285
    }
286
    
287
    /**
288
     * Pop the last output level which was pushed unto the output level stack.
289
     * This restores the previous output level which was active before the last
290
     * call to the ClearIce::pushOutputLevel() method. Using the output
291
     * level stack gives you a convenient way to change the output level 
292
     * temporarily without having to keep a record of the previous output level.
293
     * 
294
     */
295
    public static function popOutputLevel()
296
    {
297
        self::setOutputLevel(array_pop(self::$outputLevelStack));
298
    }
299
    
300
    /**
301
     * Resets the output level stack.
302
     * This method clears all items off the output level stack leaving only the
303
     * current output level.
304
     */
305
    public static function resetOutputLevel()
306
    {
307
        if(count(self::$outputLevelStack) > 0)
308
        {
309
            self::setOutputLevel(reset(self::$outputLevelStack));
310
            self::$outputLevelStack = array();
311
        }
312
    }
313
314
    /**
315
     * Reads a line of string from the input stream. 
316
     * If an input stream is not defined this method reads an input from the 
317
     * standard input (usually a keyboard) by default.
318
     * 
319
     * @todo look into using readline for this in cases where it's available
320
     * @return string
321
     */
322 9
    public static function input()
323
    {
324 9
        return fgets(self::getStream('input'));
325
    }
326
    
327
    /**
328
     * Returns a stream resource for a given stream type. 
329
     * If the stream has not been opened this method opens the stream before 
330
     * returning the asociated resource. This ensures that there is only one 
331
     * resource handle to any stream at any given time.
332
     * 
333
     * @param string $type
334
     * @return resource
335
     */
336 18
    private static function getStream($type)
337
    {
338 18
        if(!isset(self::$streams[$type]))
339 18
        {
340 18
            self::$streams[$type] = fopen(self::$streamUrls[$type], $type == 'input' ? 'r' : 'w');
341 18
        }
342 18
        return self::$streams[$type];
343
    }
344
    
345
    /**
346
     * Adds commands which are to be recognized during argument parsing.
347
     * Commands to be added could be passed as strings or structured arrays. 
348
     * This method takes as many arguments as possible. 
349
     * 
350
     * For example you could add commands with ...
351
     * 
352
     * ````php
353
     * <?php
354
     * ClearIce::addCommands('start', 'stop', 'restart');
355
     * ````
356
     * 
357
     * ... or more expressively ...
358
     * 
359
     * ````php
360
     * <?php
361
     * ClearIce::addCommands(
362
     *     array(
363
     *         'command' => 'start',
364
     *         'help' => 'start a new instance of the server'
365
     *     ),
366
     *     array(
367
     *         'command' => 'stop',
368
     *         'help' => 'stop the current instance of the server'
369
     *     ),
370
     *     array(
371
     *         'command' => 'restart',
372
     *         'help' => 'restart the current instance of the server'
373
     *     )
374
     * );
375
     * ````
376
     * 
377
     * A string argument would be taken as the text for the command. An 
378
     * array argument could have a combination of `command`, `help`, `class` and 
379
     * `usage` keys to help provide a more detailed command description. 
380
     * The `command` key stores the command text, the `help` key
381
     * stores the help message (which would be displayed in cases where
382
     * ClearIce's automatic help feature is used), the `usage` key specifies a 
383
     * usage syntax (which would also be displayed when the automatic help feature
384
     * is used) and the `class` key is the name of a class which implements the 
385
     * `Command` interface. The class specified in the `class` key would 
386
     * automatically be inistantiated when ClearIce is parsing the command line 
387
     * arguments. The options parsed on the command line would be passed on to 
388
     * the new object created.
389
     * 
390
     * @param string|array $command The command to be added for parsing.
391
     */
392 7
    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...
393
    {
394 7
        self::callParserMethod('addCommands', func_get_args());
395 7
    }
396
    
397
    /**
398
     * Add an option to be recognized by the ClearIce parser.
399
     * Options added could be passed as strings or structured arrays. This method
400
     * takes as many arguments as needed. Options can also be tied to 
401
     * specific commands so they remain valid only when those commands
402
     * are specified.
403
     * 
404
     * Options can be added with
405
     * 
406
     * ````php
407
     * <?php
408
     * ClearIce::addOptions('input', 'output', 'format')
409
     * ````
410
     * 
411
     * ... or more expressively ...
412
     * 
413
     * ````php
414
     * <?php
415
     * ClearIce::addOptions(
416
     *     array(
417
     *         'short' => 'i',
418
     *         'long' => 'input',
419
     *         'has_value' => true,
420
     *         'help' => "specifies where the input files for the wiki are found.",
421
     *         'command' => 'generate'
422
     *     ),
423
     *     array(
424
     *         'short' => 'o',
425
     *         'long' => 'output',
426
     *         'has_value' => true,
427
     *         "help" => "specifies where the wiki should be written to",
428
     *         'command' => 'generate'
429
     *     )
430
     * );
431
     * ````
432
     * 
433
     * A string argument would be taken as the long version of the option. An
434
     * array argument must either have a `short` key, a `long` key or both. The
435
     * `long` key represents the long version of the option and the `short` key 
436
     * would hold a single character which represents a short form of the long
437
     * option. In addition to the `long` and `short` keys, you can also pass
438
     * a combination of any of the `has_value`, `help`, `command` or `value`
439
     * keys.
440
     * 
441
     * The `has_value` key tells the argument parser that the option takes
442
     * a value. The `help` key is a short help message which (whoudl be displayed
443
     * when the automatic help feature is used). The `command` key specifies the 
444
     * name of a command to which the option should be associated. The `value`
445
     * key is a short description used when generating help messages to give
446
     * the user an idea of the kind of value the option takes.
447
     * 
448
     */
449 27
    public static function addOptions()
450
    {
451 27
        self::callParserMethod('addOptions', func_get_args());
452 27
    }
453
    
454
    /**
455
     * Parse the command line arguments passed to the app.
456
     * This method parses the command line argumens and returns array which 
457
     * represents the options that were detected. The parse method also
458
     * instantiates and executes classes for commands which have specified
459
     * a `Command` class.
460
     * 
461
     * @return array A structured array which contains options as keys and the
462
     *       values assigned to the options as array values. Options which do not 
463
     *       accept value would have true assigned.
464
     */
465 24
    public static function parse()
466
    {
467 24
        return self::getParserInstance()->parse();
468
    }
469
    
470
    /**
471
     * Set a usage hint for your application.
472
     * The usage hint specified would be displayed when the automatic help 
473
     * feature of the library is used. Either a single line string or an array
474
     * of strings could be passed to this method. Usage strings passed as arrays
475
     * would be properly formatted into a multi-line usage hint format.
476
     * 
477
     * @param string|array $usage A short usage hint for the application.
478
     */
479 7
    public static function setUsage($usage)
480
    {
481 7
        self::getParserInstance()->setUsage($usage);
482 7
    }
483
    
484
    /**
485
     * Set a description for your application.
486
     * The description specified would be displayed when the automatic help
487
     * feature of the library is used. This description text is described before
488
     * the help message and as such can include anything from the name of the
489
     * application, copyright information to ACII art graphics.
490
    * 
491
     * @param string $description Text for the description message.
492
     */
493 7
    public static function setDescription($description)
494
    {
495 7
        self::getParserInstance()->setDescription($description);
496 7
    }
497
    
498
    /**
499
     * Set a foonote for the help message of your application.
500
     * The footnote specified would be displayed at the bottom of the automatic
501
     * help message generated. This text could contain information about where
502
     * to find more help, how to report bugs or you could also put some awesome
503
     * ASCII art here.
504
     * 
505
     * @param string $footnote Text for the footnote message
506
     */
507 7
    public static function setFootnote($footnote)
508
    {
509 7
        self::getParserInstance()->setFootnote($footnote);
510 7
    }
511
    
512
    /**
513
     * Add the automatic help options.
514
     * This would add the `-h` and `--help` options to your application. When
515
     * these options are passed, the library would automatically display a help
516
     * message to the user. The help options are also added to commands so
517
     * users can get help which are specific to commands.
518
     */
519 8
    public static function addHelp()
520
    {
521 8
        self::getParserInstance()->addHelp();
522 8
    }
523
    
524
    /**
525
     * Generate and return a help message.
526
     * This method generates and returns a help message based on the various
527
     * options and commands passed to the library. This method is used internally
528
     * to generate and display the automatic help message. Note however that this
529
     * message would still be active even when the automatic help message has
530
     * not been activated.
531
     * 
532
     * @param string $command Generate the help message for the command specified 
533
     *     or null to generate help for the application.
534
     * @return string
535
     */
536 3
    public static function getHelpMessage($command = '')
537
    {
538 3
        return self::getParserInstance()->getHelpMessage($command);
539
    }
540
    
541
    /**
542
     * Sets the parser into strict mode.
543
     * A strict parser would terminate the application if it doesn't understand 
544
     * any options. A not-strict parser would just return the unknown options it 
545
     * encountered and expect the application to deal with it appropriately. 
546
     * When a parser is in strict mode, ClearIce prints a descriptive help 
547
     * message which advises the user about the unknown options. In cases where 
548
     * the help feature has been enabled, ClearIce would go ahead to advice the 
549
     * user to request for help.
550
     * 
551
     * @param bool $strict
552
     */
553 2
    public static function setStrict($strict)
554
    {
555 2
        self::getParserInstance()->setStrict($strict);
556 2
    }
557
    
558
    /**
559
     * Returns a singleton instance of the argument parser.
560
     * 
561
     * @return \clearice\ArgumentParser
562
     */
563 27
    private static function getParserInstance()
564
    {
565 27
        if(self::$parser === null)
566 27
        {
567 27
            self::$parser = new ArgumentParser();
568 27
        }
569 27
        return self::$parser;
570
    }
571
    
572
    /**
573
     * @param string $name The name of the method called
574
     * @param array $arguments An array of arguments passed to the method
575
     * @return mixed
576
     * @throws \Exception
577
     */
578 27
    private static function callParserMethod($name, $arguments)
579
    {
580 27
        $parser = self::getParserInstance();
581 27
        $method = new \ReflectionMethod($parser, $name);
582 27
        return $method->invokeArgs($parser, $arguments);            
583
    }
584
    
585
    /**
586
     * Reset the library. Deletes all singletons and provides you with a fresh
587
     * class ... sort of! This method is primarily used during testing to 
588
     * refresh the class in between tests.
589
     */
590 27
    public static function reset()
591
    {
592 27
        self::$parser = null;
593 27
    }
594
    
595 9
    private static function cleanResponseParams(&$params)
596
    {
597 9
        $params['answers'] = isset($params['answers']) ? $params['answers'] : [];
598 9
        $params['default'] = isset($params['default']) ? $params['default'] : '';
599 9
        $params['required'] = isset($params['required']) ? $params['required'] : false;
600 9
    }
601
    
602 1
    public static function addGroups()
603
    {
604 1
        self::callParserMethod('addGroups', func_get_args());
605 1
    }
606
}
607
608