Passed
Push — master ( f4f165...130fdd )
by Charlotte
02:12
created

ArgumentCollector::obtainNext()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 35
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 24
nc 17
nop 6
dl 0
loc 35
c 0
b 0
f 0
cc 8
rs 8.4444
1
<?php
2
/**
3
 * Livia
4
 * Copyright 2017-2018 Charlotte Dunois, All Rights Reserved
5
 *
6
 * Website: https://charuru.moe
7
 * License: https://github.com/CharlotteDunois/Livia/blob/master/LICENSE
8
*/
9
10
namespace CharlotteDunois\Livia\Arguments;
11
12
/**
13
 * Obtains, validates, and prompts for argument values.
14
 *
15
 * @property \CharlotteDunois\Livia\LiviaClient           $client       The client which initiated the instance.
16
 * @property \CharlotteDunois\Livia\Arguments\Argument[]  $args         Arguments for the collector.
17
 * @property int|float                                    $promptLimit  Maximum number of times to prompt for a single argument.
18
 */
19
class ArgumentCollector implements \Serializable {
20
    /**
21
     * The client which initiated the instance.
22
     * @var \CharlotteDunois\Livia\LiviaClient
23
     */
24
    protected $client;
25
    
26
    /**
27
     * Arguments for the collector.
28
     * @var \CharlotteDunois\Livia\Arguments\Argument[]
29
     */
30
    protected $args = array();
31
    
32
    /**
33
     * The num of arguments.
34
     * @var int
35
     */
36
    protected $argsCount;
37
    
38
    /**
39
     * Maximum number of times to prompt for a single argument.
40
     * @var int|float
41
     */
42
    protected $promptLimit;
43
    
44
    /**
45
     * Constructs a new Argument Collector.
46
     * @param \CharlotteDunois\Livia\LiviaClient    $client
47
     * @param array                                 $args
48
     * @param int|float                             $promptLimit
49
     * @throws \InvalidArgumentException
50
     */
51
    function __construct(\CharlotteDunois\Livia\LiviaClient $client, array $args, $promptLimit = \INF) {
52
        $this->client = $client;
53
        
54
        $hasInfinite = false;
55
        $hasOptional = false;
56
        
57
        foreach($args as $key => $arg) {
58
            if(!empty($arg['infinite'])) {
59
                $hasInfinite = true;
60
            } elseif($hasInfinite) {
61
                throw new \InvalidArgumentException('No other argument may come after an infinite argument');
62
            }
63
            
64
            if(($arg['default'] ?? null) !== null) {
65
                $hasOptional = true;
66
            } elseif($hasOptional) {
67
                throw new \InvalidArgumentException('Required arguments may not come after optional arguments');
68
            }
69
            
70
            if(empty($arg['key']) && !empty($key)) {
71
                $arg['key'] = $key;
72
            }
73
            
74
            $this->args[] = new \CharlotteDunois\Livia\Arguments\Argument($this->client, $arg);
75
        }
76
        
77
        $this->argsCount = \count($this->args);
78
        $this->promptLimit = $promptLimit;
79
    }
80
    
81
    /**
82
     * @param string  $name
83
     * @return bool
84
     * @throws \Exception
85
     * @internal
86
     */
87
    function __isset($name) {
88
        try {
89
            return $this->$name !== null;
90
        } catch (\RuntimeException $e) {
91
            if($e->getTrace()[0]['function'] === '__get') {
92
                return false;
93
            }
94
            
95
            throw $e;
96
        }
97
    }
98
    
99
    /**
100
     * @param string  $name
101
     * @return mixed
102
     * @throws \RuntimeException
103
     * @internal
104
     */
105
    function __get($name) {
106
        if(\property_exists($this, $name)) {
107
            return $this->$name;
108
        }
109
        
110
        throw new \RuntimeException('Unknown property '.\get_class($this).'::$'.$name);
111
    }
112
    
113
    /**
114
     * @return string
115
     * @internal
116
     */
117
    function serialize() {
118
        $vars = \get_object_vars($this);
119
        
120
        unset($vars['client']);
121
        
122
        return \serialize($vars);
123
    }
124
    
125
    /**
126
     * @return void
127
     * @internal
128
     */
129
    function unserialize($vars) {
130
        if(\CharlotteDunois\Yasmin\Models\ClientBase::$serializeClient === null) {
131
            throw new \Exception('Unable to unserialize a class without ClientBase::$serializeClient being set');
132
        }
133
        
134
        $vars = \unserialize($vars);
135
        
136
        foreach($vars as $name => $val) {
137
            $this->$name = $val;
138
        }
139
        
140
        $this->client = \CharlotteDunois\Yasmin\Models\ClientBase::$serializeClient;
141
    }
142
    
143
    /**
144
     * Obtains values for the arguments, prompting if necessary.
145
     * @param \CharlotteDunois\Livia\CommandMessage  $message
146
     * @param array                                  $provided
147
     * @param int|float                              $promptLimit
148
     * @return \React\Promise\ExtendedPromiseInterface
149
     */
150
    function obtain(\CharlotteDunois\Livia\CommandMessage $message, $provided = array(), $promptLimit = null) {
151
        if($promptLimit === null) {
152
            $promptLimit = $this->promptLimit;
153
        }
154
        
155
        $this->client->dispatcher->setAwaiting($message);
156
        
157
        $values = array();
158
        $results = array();
159
        
160
        try {
161
            return $this->obtainNext($message, $provided, $promptLimit, $values, $results, 0)->then(function ($result = null) use ($message, &$values, &$results) {
162
                $this->client->dispatcher->unsetAwaiting($message);
163
                
164
                if($result !== null) {
165
                    return $result;
166
                }
167
                
168
                return array(
169
                    'values' => $values,
170
                    'cancelled' => null,
171
                    'prompts' => \array_merge(array(), ...\array_map(function (\CharlotteDunois\Livia\Arguments\ArgumentBag $res) {
172
                        return $res->prompts;
173
                    }, $results)),
174
                    'answers' => \array_merge(array(), ...\array_map(function (\CharlotteDunois\Livia\Arguments\ArgumentBag $res) {
175
                        return $res->answers;
176
                    }, $results))
177
                );
178
            }, function ($error) use ($message) {
179
                $this->client->dispatcher->unsetAwaiting($message);
180
                
181
                throw $error;
182
            });
183
        } catch (\Throwable $error) {
184
            $this->client->dispatcher->unsetAwaiting($message);
185
            
186
            throw $error;
187
        }
188
    }
189
    
190
    /**
191
     * Obtains and collects the next argument.
192
     * @param \CharlotteDunois\Livia\CommandMessage           $message
193
     * @param array                                           $provided
194
     * @param int|float                                       $promptLimit
195
     * @param array                                           $values
196
     * @param array                                           $results
197
     * @param int                                             $current
198
     * @return \React\Promise\ExtendedPromiseInterface
199
     */
200
    protected function obtainNext(\CharlotteDunois\Livia\CommandMessage $message, array &$provided, $promptLimit, array &$values, array &$results, int $current) {
201
        if(empty($this->args[$current])) {
202
            return \React\Promise\resolve();
203
        }
204
        
205
        $bag = new \CharlotteDunois\Livia\Arguments\ArgumentBag($this->args[$current], $promptLimit);
206
        
207
        $providedArg = (isset($provided[$current]) ?
208
            ($this->args[$current]->infinite ?
209
                \array_slice($provided, $current) :
210
                ($this->argsCount < \count($provided) && ($this->argsCount - 1) === $current ? \implode(' ', \array_slice($provided, $current)) : $provided[$current])
211
            ) : null
212
        );
213
        
214
        return $this->args[$current]->obtain($message, $providedArg, $bag)
215
            ->then(function (\CharlotteDunois\Livia\Arguments\ArgumentBag $result) use ($message, &$provided, $bag, $promptLimit, &$values, &$results, $current) {
216
                $results[] = $result;
217
                
218
                if($bag->cancelled) {
219
                    return array(
220
                        'values' => null,
221
                        'cancelled' => $result->cancelled,
222
                        'prompts' => \array_merge(array(), ...\array_map(function (\CharlotteDunois\Livia\Arguments\ArgumentBag $res) {
223
                            return $res->prompts;
224
                        }, $results)),
225
                        'answers' => \array_merge(array(), ...\array_map(function (\CharlotteDunois\Livia\Arguments\ArgumentBag $res) {
226
                            return $res->answers;
227
                        }, $results))
228
                    );
229
                }
230
                
231
                $values[$this->args[$current]->key] = ($this->args[$current]->infinite ? $result->values : $result->values[0]);
232
                $current++;
233
                
234
                return $this->obtainNext($message, $provided, $promptLimit, $values, $results, $current);
235
            });
236
    }
237
}
238