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

HelpMessage   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 12
Bugs 1 Features 2
Metric Value
wmc 38
c 12
b 1
f 2
lcom 1
cbo 0
dl 0
loc 295
ccs 143
cts 143
cp 1
rs 8.4

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 2
A getCommandsHelp() 0 10 2
A getOptionsHelp() 0 20 4
A formatValue() 0 7 3
B formatArgument() 0 23 5
A wrapHelp() 0 11 2
A formatOptionHelp() 0 14 2
A formatCommandHelp() 0 11 2
A getDescriptionMessage() 0 6 2
B getOptionsMessage() 0 27 6
C getUsageMessage() 0 41 7
A __toString() 0 4 1
1
<?php
2
/**
3
 * ClearIce CLI Argument Parser
4
 * Copyright (c) 2012-2014 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
 * Class responsible for generating the help messages. 
34
 * This class reads all the options passed on to the ClearIce library and generates 
35
 * help messages. The help messages could either be displayed automatically when 
36
 * the passes the --help option (on an app with the automatic help feature enabled) 
37
 * is passed or when the app explicitly calls the ClearIce::getHelpMessage() method.
38
 * 
39
 * @internal
40
 */
41
class HelpMessage
42
{
43
    /**
44
     * The message which is usually generated by the constructor.
45
     * @var string
46
     */
47
    private $message = '';
48
    
49
    /**
50
     * The constructor for the HelpMessage class. This constructor does the work
51
     * of generating the help message. This means that nothing can be done to
52
     * change the help message after the class has been instantiated.
53
     * 
54
     * @param array $params An associative array which contains the details needed
55
     *                      to help generate the help message.s
56
     */
57 8
    public function __construct($params)
58
    {
59 8
        $sections = [];
60
        
61
        // Build up sections and use them as the basis for the help message
62 8
        $this->getDescriptionMessage($params, $sections, 'description');
63 8
        $this->getUsageMessage($params, $sections);
64 8
        $this->getOptionsMessage($params, $sections);
65 8
        $this->getDescriptionMessage($params, $sections, 'footnote');
66
        
67
        // Glue up all sections with newline characters to build the help
68
        // message
69 8
        foreach($sections as $i => $section)
70
        {
71 8
            $sections[$i] = implode("\n", $section);
72 8
        }
73
        
74 8
        $this->message = implode("\n", $sections);        
75 8
    }
76
    
77
    /**
78
     * The method runs through all the commands and generates formatted lines
79
     * of text to be used as the help message for each commands.
80
     * 
81
     * @param array $commands An array of associative arrays with infomation 
82
     *                        about all commands configured into ClearIce.
83
     * @return array
84
     */
85 1
    private function getCommandsHelp($commands)
86
    {
87 1
        $commandsHelp = array('Commands:');
88 1
        foreach ($commands as $command)
89
        {
90 1
            $commandsHelp[] = implode("\n", $this->formatCommandHelp($command));
91 1
        }
92 1
        $commandsHelp[] = '';    
93 1
        return $commandsHelp;
94
    }
95
    
96
    /**
97
     * The method runs through all the commands and generates formatted lines
98
     * of text to be used as the help message each option.
99
     * 
100
     * @param array $options    An array of associative arrays with infomation 
101
     *                          about all options configured into ClearIce.
102
     * @param string $command   All options returned would belong to the command 
103
     *                          stated in this argument.
104
     * @param string $title     A descriptive title for the header of this set 
105
     *                          of options.
106
     * @return array
107
     */    
108 8
    private function getOptionsHelp($options, $groups, $command = '', $title = 'Options:')
109
    {
110
        
111 8
        $prevGroup = null;
112 8
        $optionHelp = array($title);
113 8
        foreach ($options as $option)
114
        {
115 8
            if($option['command'] != $command) {
116 4
                continue;
117
            }
118 8
            if($prevGroup != $option['group']) {
119 1
                $optionHelp[] = '';
120 1
                $optionHelp[] = "{$groups[$option['group']]['help']}:";
121 1
                $prevGroup = $option['group'];
122 1
            }
123 8
            $optionHelp[] = implode("\n", $this->formatOptionHelp($option));
124 8
        }      
125 8
        $optionHelp[] = '';
126 8
        return $optionHelp;
127
    }
128
    
129
    /**
130
     * Formats the help line of a value which is accepted by an option. If a 
131
     * value type is provided in the option, it is used if not it uses a generic 
132
     * "VALUE" to show that an option can accept a value.
133
     * 
134
     * @param array $option
135
     * @return string
136
     */
137 8
    private function formatValue($option)
138
    {
139 8
        if($option['has_value'])
140 8
        {
141 7
            return "=" . (isset($option['value']) ? $option['value'] : "VALUE");
142
        }
143 8
    }
144
    
145
    /**
146
     * Formats the argument parts of the help line. If an option has both a long
147
     * and a short form both forms are put together, if it has either a short or
148
     * a long form, the respective form is formatted. The formatting includes
149
     * placing a comma between the two forms and padding the output with the
150
     * appropriate spaces.
151
     * 
152
     * @param array $option
153
     * @return string
154
     */
155 8
    private function formatArgument($option)
156
    {
157 8
        $valueHelp = $this->formatValue($option);
158 8
        if(isset($option['long']) && isset($option['short']))            
159 8
        {
160 8
            $argumentHelp = sprintf(
161 8
                "  %s, %-22s ", "-{$option['short']}", "--{$option['long']}$valueHelp"
162 8
            );
163 8
        }
164 4
        else if(isset($option['long']))
165 4
        {
166 4
            $argumentHelp = sprintf(
167 4
                "  %-27s", "--{$option['long']}$valueHelp"
168 4
            );
169 4
        }
170 4
        else if(isset($option['short']))
171 4
        {
172 4
            $argumentHelp = sprintf(
173 4
                "  %-27s", "-{$option['short']}"
174 4
            );                
175 4
        }      
176 8
        return $argumentHelp;
0 ignored issues
show
Bug introduced by
The variable $argumentHelp does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
177
    }
178
    
179
    /**
180
     * Wraps the help message arround the argument by producing two different
181
     * columns. The argument is placed in the first column and the help message
182
     * is placed in the second column.
183
     * 
184
     * @param string $argumentPart
185
     * @param string $help
186
     * @param integer $minSize
187
     * @return array
188
     */
189 8
    private function wrapHelp($argumentPart, &$help, $minSize = 29)
190
    {
191 8
        if(strlen($argumentPart) <= $minSize)
192 8
        {
193 8
            return $argumentPart . array_shift($help);
194
        }
195
        else
196
        {
197 4
            return $argumentPart;
198
        }        
199
    }
200
    
201
    /**
202
     * Format the help message for an option. This would involve generating a 
203
     * sring with your option and and wrapping the help message around it.
204
     * 
205
     * @param type $option
206
     * @return string
207
     */
208 8
    private function formatOptionHelp($option)
209
    {
210 8
        $optionHelp = array();
211 8
        $help = explode("\n", wordwrap($option['help'], 50));
212 8
        $argumentPart = $this->formatArgument($option);
213
214 8
        $optionHelp[] = $this->wrapHelp($argumentPart, $help);
0 ignored issues
show
Documentation introduced by
$help is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
215
216 8
        foreach($help as $helpLine)
0 ignored issues
show
Bug introduced by
The expression $help of type string is not traversable.
Loading history...
217
        {  
218 4
            $optionHelp[] = str_repeat(' ', 29) . "$helpLine" ;
219 8
        }        
220 8
        return $optionHelp;
221
    }  
222
    
223
    /**
224
     * Format the help message for a command. This would involve wrapping the
225
     * help message for a given command around the command.
226
     * 
227
     * @param type $command
228
     * @return string
229
     */
230 1
    private function formatCommandHelp($command)
231
    {
232 1
        $commandHelp = array();
233 1
        $help = explode("\n", wordwrap($command['help'], 59));
234 1
        $commandHelp[] = $this->wrapHelp(sprintf("% -20s", $command['command']), $help, 20);
0 ignored issues
show
Documentation introduced by
$help is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
235 1
        foreach($help as $helpLine)
0 ignored issues
show
Bug introduced by
The expression $help of type string is not traversable.
Loading history...
236
        {
237 1
            $commandHelp[] = str_repeat(' ', 20) . $helpLine;
238 1
        }
239 1
        return $commandHelp;
240
    }
241
    
242 8
    private function getDescriptionMessage(array $params, array &$sections, $section)
243
    {
244 8
        if(isset($params[$section])) {
245 7
            $sections[$section] = [wordwrap($params[$section]), ''];
246 7
        }
247 8
    }
248
    
249 8
    private function getOptionsMessage(array $params, array &$sections)
250
    {
251 8
        $options = $params['options']->getArray();
252 8
        if(count($params['groups'])) {
253 1
            $groups = [];
254 1
            foreach($options as $option) {
255 1
                $groups[] = $option['group'];
256 1
            }
257 1
            array_multisort($groups, SORT_ASC, $options);
258 1
        }        
259 8
        $optionHelp = $this->getOptionsHelp($options, $params['groups']);
260 8
        if(count($params['commands']) > 0 && $params['command'] == '') 
261 8
        {
262 1
            $sections['commands'] = $this->getCommandsHelp($params['commands']); 
263 1
        }
264 7
        else if($params['command'] != '')
265 7
        {
266 3
            $sections['command_options'] = $this->getOptionsHelp(
267 3
                $options, 
268 3
                $params['groups'],
269 3
                $params['command'], 
270 3
                "Options for {$params['command']} command:"
271 3
            );
272 3
        }
273
        
274 8
        $sections['options'] = $optionHelp;        
275 8
    }
276
277
278
    /**
279
     * Returns the usage message for either the command or the main script
280
     * depending on the state in which the HelpMessage class currently is.
281
     * 
282
     * @global array $argv The arguments passed to the 
283
     * @param array $params A copy of the parameters passed to the HelpMessage class
284
     */
285 8
    private function getUsageMessage(array $params, array &$sections)
286
    {
287 8
        global $argv;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
288 8
        $usageMessage = [];
289 8
        $usageSet = false;
290
        
291 8
        if($params['command'] != '')
292 8
        {
293 3
            if(isset($params['commands'][$params['command']]['usage']))
294 3
            {
295 1
                $usage = $params['commands'][$params['command']]['usage'];
296 1
            }
297
            else
298
            {
299 2
                $usage = "{$params['command']} [options]..";
300
            }
301 3
        }
302
        else
303
        {
304 5
            $usage = $params['usage'];
305
        }
306
        
307 8
        if(is_string($usage))
308 8
        {
309 6
            $usageMessage[] = "Usage:\n  {$argv[0]} " . $usage;
310 6
            $usageSet = true;
311 6
        }
312 2
        elseif (is_array($usage)) 
313
        {
314 1
            $usageMessage[] = "Usage:";
315 1
            foreach($usage as $usage)
316
            {
317 1
                $usageMessage[] = "  {$argv[0]} {$usage}";
318 1
            }
319 1
            $usageSet = true;
320 1
        }
321 8
        if($usageSet){
322 7
            $usageMessage[] = "";
323 7
            $sections['usage'] = $usageMessage;
324 7
        }
325 8
    }    
326
    
327
    /**
328
     * Returns the message as a string.
329
     * @return string
330
     */
331 8
    public function __toString()
332
    {
333 8
        return $this->message;
334
    }
335
}
336
337