SimpleCommand::outputUsage()   B
last analyzed

Complexity

Conditions 6
Paths 10

Size

Total Lines 44
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 22
c 1
b 0
f 0
dl 0
loc 44
rs 8.9457
cc 6
nc 10
nop 3
1
<?php
2
namespace PortlandLabs\Slackbot\Command;
3
4
use Illuminate\Support\Str;
5
use League\CLImate\Argument\Argument;
6
use League\CLImate\Argument\Filter;
7
use League\CLImate\Argument\Summary;
8
use PortlandLabs\Slackbot\Bot;
9
use PortlandLabs\Slackbot\Command\Argument\Manager;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, PortlandLabs\Slackbot\Command\Manager. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use PortlandLabs\Slackbot\Permission\Checker;
11
use PortlandLabs\Slackbot\Permission\User;
12
use PortlandLabs\Slackbot\Slack\Rtm\Event\Message;
13
14
abstract class SimpleCommand implements ConsoleStyleCommand
15
{
16
17
    /** @var string */
18
    protected $signature = '';
19
20
    /** @var string */
21
    protected $description = '';
22
23
    /** @var Bot */
24
    protected $bot;
25
26
    /** @var Manager */
27
    protected $argumentManager;
28
29
    /** @var Checker */
30
    protected $checker;
31
32
    /** @var string The Role class required */
33
    protected $role = User::class;
34
35
    public function __construct(Bot $bot, Manager $argumentManager, Checker $checker)
36
    {
37
        $this->bot = $bot;
38
        $this->argumentManager = $argumentManager;
39
        $this->checker = $checker;
40
    }
41
42
    /**
43
     * Add arguments to the argument manager
44
     * This method gets called after we match the command to a request
45
     *
46
     * @param Manager $manager
47
     *
48
     * @return Manager
49
     * @throws \Exception
50
     */
51
    public function configure(Manager $manager): Manager
52
    {
53
        $manager = SignatureParser::parse($this->signature, $manager);
54
        $manager->setRole($this->role);
55
56
        return $manager;
57
    }
58
59
    /**
60
     * Determine whether we should handle this message
61
     *
62
     * @param Message $message
63
     *
64
     * @return bool
65
     * @throws \Exception
66
     */
67
    public function shouldHandle(Message $message): bool
68
    {
69
        $text = $message->getText();
70
        $userId = $this->bot->rtm()->getUserId();
71
72
        // Early return if the text doesn't start with "@slackbot "
73
        if (!Str::startsWith($text, "<@$userId>")) {
74
            return false;
75
        }
76
77
        // Determine our signature
78
        $command = $this->argumentManager->getCommand();
79
        if (!$command) {
80
            $this->argumentManager = $this->configure($this->argumentManager);
81
            $command = $this->argumentManager->getCommand();
82
        }
83
84
        // If we still don't have a command, let's just return false
85
        if (!$command) {
86
            return false;
87
        }
88
89
        $shouldRun = Str::startsWith($text, "<@$userId> $command ") || $text === "<@$userId> $command";
90
        if (!$shouldRun) {
91
            return false;
92
        }
93
94
        // Make sure the user have the right role
95
        $role = $this->checker->getRole($message);
96
        return ($role instanceof $this->role);
97
    }
98
99
    /**
100
     * Handle a simple command message
101
     *
102
     * @param Message $message
103
     * @throws \Exception
104
     */
105
    public function handle(Message $message)
106
    {
107
        $argumentManager = $this->argumentManager;
108
        if (!$argumentManager->getCommand()) {
109
            $argumentManager = $this->configure($argumentManager);
110
        }
111
112
        if (!$argumentManager->exists('help')) {
113
            $argumentManager->add([
114
                'help' => [
115
                    'prefix' => 'h',
116
                    'longPrefix' => 'help',
117
                    'description' => 'Output helpful information about a command',
118
                    'noValue' => true
119
                ]
120
            ]);
121
        }
122
123
        // Parse the actual message text
124
        $text = Str::replaceFirst(
125
            sprintf('<@%s> ', $this->bot->rtm()->getUserId()),
126
            '',
127
            $message->getText());
128
129
        // Pass in the actual split string
130
        $parsedManager = clone $argumentManager;
131
132
        // GT / LT signs are encoded in slack messages so we can safely use them here as a stand-in.
133
        $argv = \Clue\Arguments\split(str_replace("\n", "<>\n", $text));
134
135
        // Restore newlines
136
        $argv = array_map(function($arg) {
137
            return str_replace('<>', "\n", $arg);
138
        }, $argv);
139
140
        try {
141
            $parsedManager->parse($argv);
142
        } catch (\Exception $e) {
143
            if (!$parsedManager->get('help')) {
144
                $this->bot->feignTyping($message->getChannel(), "*Error:*\n> " . $e->getMessage());
0 ignored issues
show
Bug introduced by
It seems like $message->getChannel() can also be of type null; however, parameter $channel of PortlandLabs\Slackbot\Bot::feignTyping() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
                $this->bot->feignTyping(/** @scrutinizer ignore-type */ $message->getChannel(), "*Error:*\n> " . $e->getMessage());
Loading history...
145
                return;
146
            }
147
        }
148
149
        if ($parsedManager->get('help')) {
150
            // Output usage info
151
            $this->outputUsage($message, $parsedManager, $this);
152
        } else {
153
            // Run the command
154
            $this->run($message, $parsedManager);
155
        }
156
    }
157
158
    /**
159
     * Output the usage statement for this command
160
     *
161
     * @param Message $message
162
     * @param Manager $manager
163
     */
164
    protected function outputUsage(Message $message, Manager $manager, SimpleCommand $command)
165
    {
166
        $output = [];
167
        $channel = $message->getChannel();
168
169
        $filter = new Filter();
170
        $filter->setArguments($manager->all());
171
172
        $summary = new Summary();
173
174
        // Print the description if it's defined.
175
        if ($command->getDescription()) {
176
            $output[] = '*' . ucfirst($manager->getCommand()) . '*: ' . $command->getDescription();
177
            $output[] = '>>>';
178
        }
179
180
        // Output the simple usage statement
181
        $orderedArguments = array_merge($filter->withPrefix(), $filter->withoutPrefix());
182
        $output[] = "*Usage:* `{$manager->getCommand()} " . $summary->short($orderedArguments) . '`';
183
184
        // Output the detailed arguments
185
        foreach (['required', 'optional'] as $type) {
186
            /** @var Argument[] $filteredArguments */
187
            $filteredArguments = $filter->{$type}();
188
189
            if (count($filteredArguments) == 0) {
190
                continue;
191
            }
192
193
            $output[] = '';
194
            $output[] = '*' . mb_convert_case($type, MB_CASE_TITLE) . ' Arguments:*';
195
196
            foreach ($filteredArguments as $argument) {
197
                $argumentString = '`' .$summary->argument($argument) . '`';
198
199
                if ($description = $argument->description()) {
200
                    $argumentString .= " _{$description}_";
201
                }
202
203
                $output[] = $argumentString;
204
            }
205
        }
206
207
        $this->bot->feignTyping($channel, implode(PHP_EOL, $output));
0 ignored issues
show
Bug introduced by
It seems like $channel can also be of type null; however, parameter $channel of PortlandLabs\Slackbot\Bot::feignTyping() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

207
        $this->bot->feignTyping(/** @scrutinizer ignore-type */ $channel, implode(PHP_EOL, $output));
Loading history...
208
    }
209
210
    /**
211
     * Get the description associated with this command
212
     *
213
     * @return string
214
     */
215
    public function getDescription(): string
216
    {
217
        return $this->description;
218
    }
219
}