Completed
Push — master ( 2ee5bc...295d99 )
by Greg
01:21
created

HubphCommands::prStatuses()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16

Duplication

Lines 16
Ratio 100 %

Importance

Changes 0
Metric Value
dl 16
loc 16
rs 9.7333
c 0
b 0
f 0
cc 3
nc 2
nop 3
1
<?php
2
3
namespace Hubph\Cli;
4
5
use Consolidation\AnnotatedCommand\CommandData;
6
use Consolidation\Filter\FilterOutputData;
7
use Consolidation\Filter\LogicalOpFactory;
8
use Consolidation\OutputFormatters\Options\FormatterOptions;
9
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
10
use Consolidation\OutputFormatters\StructuredData\PropertyList;
11
use Psr\Log\LoggerAwareInterface;
12
use Psr\Log\LoggerAwareTrait;
13
use Robo\Common\ConfigAwareTrait;
14
use Robo\Contract\ConfigAwareInterface;
15
use Consolidation\AnnotatedCommand\CommandError;
16
use Hubph\HubphAPI;
17
use Hubph\VersionIdentifiers;
18
use Hubph\PullRequests;
19
20
class HubphCommands extends \Robo\Tasks implements ConfigAwareInterface, LoggerAwareInterface
21
{
22
    use ConfigAwareTrait;
23
    use LoggerAwareTrait;
24
25
    /**
26
     * Report who we have authenticated as
27
     *
28
     * @command whoami
29
     */
30
    public function whoami($options = ['as' => 'default'])
31
    {
32
        $api = $this->api($options['as']);
33
        $authenticated = $api->whoami();
34
        $authenticatedUser = $authenticated['login'];
35
36
        $this->say("Authenticated as $authenticatedUser.");
37
    }
38
39
    /**
40
     * @command pr:close
41
     */
42 View Code Duplication
    public function prClose($projectWithOrg = '', $number = '', $options = ['as' => 'default'])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
43
    {
44
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
45
            $number = $projectWithOrg;
46
            $projectWithOrg = '';
47
        }
48
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
49
        list($org, $project) = explode('/', $projectWithOrg, 2);
50
51
        $api = $this->api($options['as']);
52
        $api->prClose($org, $project, $number);
0 ignored issues
show
Documentation introduced by
$number is of type string, but the function expects a object<Hubph\PullRequests>.

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...
53
    }
54
55
    /*
56
     * hubph pr:check --vid=php-7.0./31 --vid=php-7.1./20
57
     *
58
     * status 0 and csv with PR numbers to close
59
     *
60
     *  - or -
61
     *
62
     * status 1 if all vid/vvals exist and nothing more needs to be done
63
     */
64
65
    /**
66
     * @command pr:check
67
     */
68
    public function prCheck(
69
        $options = [
70
            'message|m' => '',
71
            'file|F' => '',
72
            'base' => '',
73
            'head' => '',
74
            'as' => 'default',
75
            'format' => 'yaml',
76
            'idempotent' => false
77
        ]
78
    ) {
79
        $projectWithOrg = $this->projectWithOrg();
80
81
        // Get the commit message from --message or --file
82
        $message = $this->getMessage($options);
83
84
        // Determine all of the vid/vval pairs if idempotent
85
        $vids = $this->getVids($options, $message);
86
87
        $api = $this->api($options['as']);
88
        list($status, $result) = $api->prCheck($projectWithOrg, $vids);
89
90
        if ($status) {
91
            return new CommandError($result, $status);
0 ignored issues
show
Documentation introduced by
$status is of type boolean, but the function expects a integer.

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...
92
        }
93
        if (is_string($result)) {
94
            $this->logger->notice("No open pull requests that need to be closed.");
95
            return;
96
        }
97
        return implode(',', (array)$result);
98
    }
99
100
    /**
101
     * @command pr:create
102
     * @aliases pull-request
103
     */
104
    public function prCreate(
105
        $options = [
106
            'message|m' => '',
107
            'body' => '',
108
            'file|F' => '',
109
            'base' => 'master',
110
            'head' => '',
111
            'as' => 'default',
112
            'format' => 'yaml',
113
            'idempotent' => false
114
        ]
115
    ) {
116
        $projectWithOrg = $this->projectWithOrg();
117
118
        // Get the commit message from --message or --file
119
        $message = $this->getMessage($options);
120
        list($org, $project) = explode('/', $projectWithOrg, 2);
121
122
        // Determine all of the vid/vval pairs if idempotent
123
        $vids = $this->getVids($options, $message);
124
125
        $api = $this->api($options['as']);
126
        list($status, $result) = $api->prCheck($projectWithOrg, $vids);
127
128
        if ($status) {
129
            return new CommandError($result, $status);
0 ignored issues
show
Documentation introduced by
$status is of type boolean, but the function expects a integer.

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...
130
        }
131
132
        // TODO: We could look up 'head' if it is not provided.
133
        if (empty($options['head'])) {
134
            throw new \Exceptions('Must provide --head');
135
        }
136
137
        // Create the requested PR
138
        $api->prCreate($org, $project, $message, $options['body'], $options['base'], $options['head']);
139
140
        // If $result is an array, it will contain
141
        // all of the pull request numbers to close.
142
        // TODO: We should make a wrapper object for $result
143
        if (is_array($result)) {
144
            list($org, $project) = explode('/', $projectWithOrg, 2);
145
            $api->prClose($org, $project, $result);
0 ignored issues
show
Documentation introduced by
$result is of type array, but the function expects a object<Hubph\PullRequests>.

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...
146
        }
147
    }
148
149
    protected function getMessage($options)
150
    {
151
        if (!empty($options['message'])) {
152
            return $options['message'];
153
        }
154
        if (!empty($options['file'])) {
155
            return file_get_contents($options['file']);
156
        }
157
        return '';
158
    }
159
160
    protected function getVids($options, $message)
161
    {
162
        $vids = new VersionIdentifiers();
163
164
        //if (empty($options['idempotent'])) {
165
        //    return $vids;
166
        //}
167
168
        // Allow the caller to define more specific vid / vval patterns
169
        if (!empty($options['vid'])) {
170
            $vids->setVidPattern($options['vid']);
171
        }
172
        if (!empty($options['vval'])) {
173
            $vids->setVvalPattern($options['vval']);
174
        }
175
176
        $vids->addVidsFromMessage($message);
177
        return $vids;
178
    }
179
180
    protected function projectWithOrg($projectWithOrg = '')
181
    {
182
        if (!empty($projectWithOrg)) {
183
            return $projectWithOrg;
184
        }
185
186
        return $this->getProjectWithOrgFromRemote();
187
    }
188
189
    protected function getProjectWithOrgFromRemote($remote = 'origin', $cwd = '')
190
    {
191
        $remote = $this->getRemote($remote, $cwd);
192
193
        return $this->getProjectWithOrfFromUrl($remote);
194
    }
195
196 View Code Duplication
    protected function getProjectWithOrfFromUrl($remote)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
    {
198
        $remote = preg_replace('#^git@[^:]*:#', '', $remote);
199
        $remote = preg_replace('#^[^:]*://[^/]/#', '', $remote);
200
        $remote = preg_replace('#\.git$#', '', $remote);
201
202
        return $remote;
203
    }
204
205
    protected function getRemote($remote = 'origin', $cwd = '')
206
    {
207
        if (!empty($cwd)) {
208
            $cwd = "-C $cwd";
209
        }
210
        return exec("git {$cwd} config --get remote.{$remote}.url");
211
    }
212
213
    /**
214
     * @command pr:find
215
     * @param $projectWithOrg The project to work on, e.g. org/project
216
     * @option $q Query term
217
     * @filter-output
218
     * @field-labels
219
     *   url: Url
220
     *   id: ID
221
     *   node_id: Node ID
222
     *   html_url: HTML Url
223
     *   diff_url: Diff Url
224
     *   patch_url: Patch Url
225
     *   issue_url: Issue Url
226
     *   number: Number
227
     *   state: State
228
     *   locked: Locked
229
     *   title: Title
230
     *   user: User
231
     *   body: Boday
232
     *   created_at: Created
233
     *   updated_at: Updated
234
     *   closed_at: Closed
235
     *   merged_at: Merged
236
     *   merge_commit_sha: Merge Commit
237
     *   assignee: Assignee
238
     *   assignees: Assignees
239
     *   requested_reviewers: Requested Reviewers
240
     *   requested_teams: Requested Teams
241
     *   labels: Labels
242
     *   milestone: Milestone
243
     *   commits_url: Commit Url
244
     *   review_comments_url: Review Comments Url
245
     *   review_comment_url: Review Comment Url
246
     *   comments_url: Comments Url
247
     *   statuses_url: Statuses Url
248
     *   head: Head
249
     *   base: Base
250
     *   _links: Links
251
     * @default-fields number,user,title
252
     * @default-string-field number
253
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
254
    */
255
    public function prFind($projectWithOrg = '', $options = ['as' => 'default', 'format' => 'yaml', 'q' => ''])
256
    {
257
        $api = $this->api($options['as']);
258
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
259
        $q = $options['q'];
260
261
        if (!empty($q)) {
262
            $q = $q . ' ';
263
        }
264
        $q = $q . 'repo:' . $projectWithOrg;
265
        $searchResults = $api->gitHubAPI()->api('search')->issues($q);
266
        $pullRequests = $searchResults['items'];
267
268
        $pullRequests = $this->keyById($pullRequests, 'number');
269
        $result = new RowsOfFields($pullRequests);
270
        $this->alterPRTables($result);
271
272
        return $result;
273
    }
274
275
    /**
276
     * @command pr:show
277
     * @field-labels
278
     *   url: Url
279
     *   id: ID
280
     *   node_id: Node ID
281
     *   html_url: HTML Url
282
     *   diff_url: Diff Url
283
     *   patch_url: Patch Url
284
     *   issue_url: Issue Url
285
     *   number: Number
286
     *   state: State
287
     *   locked: Locked
288
     *   title: Title
289
     *   user: User
290
     *   body: Boday
291
     *   created_at: Created
292
     *   updated_at: Updated
293
     *   closed_at: Closed
294
     *   mergeable: Mergeable
295
     *   mergeable_state: Mergable State
296
     *   merged_at: Merged
297
     *   merge_commit_sha: Merge Commit
298
     *   assignee: Assignee
299
     *   assignees: Assignees
300
     *   requested_reviewers: Requested Reviewers
301
     *   requested_teams: Requested Teams
302
     *   labels: Labels
303
     *   milestone: Milestone
304
     *   commits_url: Commit Url
305
     *   review_comments_url: Review Comments Url
306
     *   review_comment_url: Review Comment Url
307
     *   comments_url: Comments Url
308
     *   statuses_url: Statuses Url
309
     *   head: Head
310
     *   base: Base
311
     *   _links: Links
312
     * @return Consolidation\OutputFormatters\StructuredData\PropertyList
313
     */
314
    public function prShow($projectWithOrg = '', $number = '', $options = ['as' => 'default', 'format' => 'table'])
315
    {
316
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
317
            $number = $projectWithOrg;
318
            $projectWithOrg = '';
319
        }
320
        $api = $this->api($options['as']);
321
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
322
323
        list($org, $project) = explode('/', $projectWithOrg, 2);
324
325
        $pullRequest = $api->gitHubAPI()->api('pull_request')->show($org, $project, $number);
326
327
        $result = new PropertyList($pullRequest);
328
        $this->alterPRTables($result);
329
330
        return $result;
331
    }
332
333
    /**
334
     * @command pr:statuses
335
     * @field-labels
336
     *   url: Url
337
     *   id: ID
338
     *   state: State
339
     *   description: Description
340
     *   node_id: Node ID
341
     *   context: Context
342
     *   avatar_url: Avatar URL
343
     *   target_url: Target URL
344
     *   creator: Creator
345
     *   created_at: Created
346
     *   updated_at: Updated
347
     * @default-fields id,creator,state,description
348
     * @default-string-field description
349
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
350
     */
351 View Code Duplication
    public function prStatuses($projectWithOrg = '', $number = '', $options = ['as' => 'default', 'format' => 'yaml'])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
352
    {
353
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
354
            $number = $projectWithOrg;
355
            $projectWithOrg = '';
356
        }
357
        $api = $this->api($options['as']);
358
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
359
360
        $pullRequestStatus = $api->prStatuses($projectWithOrg, $number);
361
362
        $result = new RowsOfFields($pullRequestStatus);
363
        $this->alterPRTables($result);
364
365
        return $result;
366
    }
367
368
    /**
369
     * @command pr:list
370
     * @param $projectWithOrg The project to work on, e.g. org/project
371
     * @filter-output
372
     * @field-labels
373
     *   url: Url
374
     *   id: ID
375
     *   node_id: Node ID
376
     *   html_url: HTML Url
377
     *   diff_url: Diff Url
378
     *   patch_url: Patch Url
379
     *   issue_url: Issue Url
380
     *   number: Number
381
     *   state: State
382
     *   locked: Locked
383
     *   title: Title
384
     *   user: User
385
     *   body: Boday
386
     *   created_at: Created
387
     *   updated_at: Updated
388
     *   closed_at: Closed
389
     *   merged_at: Merged
390
     *   merge_commit_sha: Merge Commit
391
     *   assignee: Assignee
392
     *   assignees: Assignees
393
     *   requested_reviewers: Requested Reviewers
394
     *   requested_teams: Requested Teams
395
     *   labels: Labels
396
     *   milestone: Milestone
397
     *   commits_url: Commit Url
398
     *   review_comments_url: Review Comments Url
399
     *   review_comment_url: Review Comment Url
400
     *   comments_url: Comments Url
401
     *   statuses_url: Statuses Url
402
     *   head: Head
403
     *   base: Base
404
     *   _links: Links
405
     * @default-fields number,user,title
406
     * @default-string-field number
407
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
408
     */
409
    public function prList($projectWithOrg = '', $options = ['state' => 'open', 'as' => 'default', 'format' => 'table'])
410
    {
411
        $api = $this->api($options['as']);
412
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
413
414
        list($org, $project) = explode('/', $projectWithOrg, 2);
415
416
        $pullRequests = $api->gitHubAPI()->api('pull_request')->all($org, $project, ['state' => $options['state']]);
417
418
        $pullRequests = $this->keyById($pullRequests, 'number');
419
420
        $result = new RowsOfFields($pullRequests);
421
        $this->alterPRTables($result);
422
423
        return $result;
424
    }
425
426
    protected function alterPRTables($data)
427
    {
428
        $data->addRendererFunction(
429
            function ($key, $cellData, FormatterOptions $options, $rowData) {
0 ignored issues
show
Unused Code introduced by
The parameter $options 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...
Unused Code introduced by
The parameter $rowData 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...
430
                if (is_array($cellData)) {
431
                    if (empty($cellData)) {
432
                        return '';
433
                    }
434
                    foreach (['login', 'label'] as $k) {
435
                        if (isset($cellData[$k])) {
436
                            return $cellData[$k];
437
                        }
438
                    }
439
                    // TODO: simplify
440
                    //   assignees
441
                    //   requested_reviewers
442
                    //   requested_teams
443
                    //   labels
444
                    //   _links
445
                    return json_encode($cellData, true);
446
                }
447
                if (!is_string($cellData)) {
448
                    return var_export($cellData, true);
449
                }
450
                return $cellData;
451
            }
452
        );
453
    }
454
455
    protected function keyById($data, $field)
456
    {
457
        return
458
            array_column(
459
                array_map(
460
                    function ($k) use ($data, $field) {
461
                        return [$data[$k][$field], $data[$k]];
462
                    },
463
                    array_keys($data)
464
                ),
465
                1,
466
                0
467
            );
468
    }
469
470
    /**
471
     * @hook alter @filter-output
472
     * @option $filter Filter output based on provided expression
473
     * @default $filter ''
474
     */
475
    public function filterOutput($result, CommandData $commandData)
476
    {
477
        $expr = $commandData->input()->getOption('filter');
478
        if (!empty($expr)) {
479
            $factory = LogicalOpFactory::get();
480
            $op = $factory->evaluate($expr);
481
            $filter = new FilterOutputData();
482
            $result = $filter->filter($result, $op);
483
        }
484
485
        return $result;
486
    }
487
488
    protected function api($as = 'default')
489
    {
490
        $api = new HubphAPI($this->getConfig());
491
        $api->setAs($as);
492
493
        return $api;
494
    }
495
}
496