Completed
Push — master ( cf67fe...913d24 )
by Greg
01:23
created

HubphCommands::addTableRenderFunction()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.4906
c 0
b 0
f 0
cc 7
nc 1
nop 1
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->addTableRenderFunction($result);
271
272
        return $result;
273
    }
274
275
    /**
276
     * @command org:repos
277
     * @param $org The org to list
278
     * @filter-output
279
     * @field-labels
280
     *   url: Url
281
     *   id: ID
282
     *   owner: Owner
283
     *   name: Shortname
284
     *   full_name: Name
285
     *   private: Private
286
     *   fork: Fork
287
     *   created_at: Created
288
     *   updated_at: Updated
289
     *   pushed_at: Pushed
290
     *   git_url: Git URL
291
     *   ssh_url: SSH URL
292
     *   svn_url: SVN URL
293
     *   homepage: Homepage
294
     *   size: Size
295
     *   stargazers_count: Stargazers
296
     *   watchers_count: Watchers
297
     *   language: Language
298
     *   has_issues: Has Issues
299
     *   has_projects: Has Projects
300
     *   has_downloads: Has Downloads
301
     *   has_wiki: Has Wiki
302
     *   has_pages: Has Pages
303
     *   forks_count: Forks
304
     *   archived: Archived
305
     *   disabled: Disabled
306
     *   open_issues_count: Open Issues
307
     *   default_branch: Default Branch
308
     *   license: License
309
     *   permissions: Permissions
310
     * @default-fields full_name,language,default_branch
311
     * @default-string-field full_name
312
     *
313
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
314
     */
315
    public function orgRepos($org, $options = ['as' => 'default', 'format' => 'table'])
316
    {
317
        $api = $this->api($options['as']);
318
319
        $repos = $api->gitHubAPI()->api('organization')->repositories($org);
320
321
        $data = new \Consolidation\OutputFormatters\StructuredData\RowsOfFields($repos);
322
        $this->addTableRenderFunction($data);
323
324
        return $data;
325
    }
326
327
    /**
328
     * @command pr:show
329
     * @field-labels
330
     *   url: Url
331
     *   id: ID
332
     *   node_id: Node ID
333
     *   html_url: HTML Url
334
     *   diff_url: Diff Url
335
     *   patch_url: Patch Url
336
     *   issue_url: Issue Url
337
     *   number: Number
338
     *   state: State
339
     *   locked: Locked
340
     *   title: Title
341
     *   user: User
342
     *   body: Boday
343
     *   created_at: Created
344
     *   updated_at: Updated
345
     *   closed_at: Closed
346
     *   mergeable: Mergeable
347
     *   mergeable_state: Mergable State
348
     *   merged_at: Merged
349
     *   merge_commit_sha: Merge Commit
350
     *   assignee: Assignee
351
     *   assignees: Assignees
352
     *   requested_reviewers: Requested Reviewers
353
     *   requested_teams: Requested Teams
354
     *   labels: Labels
355
     *   milestone: Milestone
356
     *   commits_url: Commit Url
357
     *   review_comments_url: Review Comments Url
358
     *   review_comment_url: Review Comment Url
359
     *   comments_url: Comments Url
360
     *   statuses_url: Statuses Url
361
     *   head: Head
362
     *   base: Base
363
     *   _links: Links
364
     * @return Consolidation\OutputFormatters\StructuredData\PropertyList
365
     */
366
    public function prShow($projectWithOrg = '', $number = '', $options = ['as' => 'default', 'format' => 'table'])
367
    {
368
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
369
            $number = $projectWithOrg;
370
            $projectWithOrg = '';
371
        }
372
        $api = $this->api($options['as']);
373
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
374
375
        list($org, $project) = explode('/', $projectWithOrg, 2);
376
377
        $pullRequest = $api->gitHubAPI()->api('pull_request')->show($org, $project, $number);
378
379
        $result = new PropertyList($pullRequest);
380
        $this->addTableRenderFunction($result);
381
382
        return $result;
383
    }
384
385
    /**
386
     * @command pr:statuses
387
     * @field-labels
388
     *   url: Url
389
     *   id: ID
390
     *   state: State
391
     *   description: Description
392
     *   node_id: Node ID
393
     *   context: Context
394
     *   avatar_url: Avatar URL
395
     *   target_url: Target URL
396
     *   creator: Creator
397
     *   created_at: Created
398
     *   updated_at: Updated
399
     * @default-fields id,creator,state,description
400
     * @default-string-field description
401
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
402
     */
403 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...
404
    {
405
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
406
            $number = $projectWithOrg;
407
            $projectWithOrg = '';
408
        }
409
        $api = $this->api($options['as']);
410
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
411
412
        $pullRequestStatus = $api->prStatuses($projectWithOrg, $number);
413
414
        $result = new RowsOfFields($pullRequestStatus);
415
        $this->addTableRenderFunction($result);
416
417
        return $result;
418
    }
419
420
    /**
421
     * @command pr:list
422
     * @param $projectWithOrg The project to work on, e.g. org/project
423
     * @filter-output
424
     * @field-labels
425
     *   url: Url
426
     *   id: ID
427
     *   node_id: Node ID
428
     *   html_url: HTML Url
429
     *   diff_url: Diff Url
430
     *   patch_url: Patch Url
431
     *   issue_url: Issue Url
432
     *   number: Number
433
     *   state: State
434
     *   locked: Locked
435
     *   title: Title
436
     *   user: User
437
     *   body: Boday
438
     *   created_at: Created
439
     *   updated_at: Updated
440
     *   closed_at: Closed
441
     *   merged_at: Merged
442
     *   merge_commit_sha: Merge Commit
443
     *   assignee: Assignee
444
     *   assignees: Assignees
445
     *   requested_reviewers: Requested Reviewers
446
     *   requested_teams: Requested Teams
447
     *   labels: Labels
448
     *   milestone: Milestone
449
     *   commits_url: Commit Url
450
     *   review_comments_url: Review Comments Url
451
     *   review_comment_url: Review Comment Url
452
     *   comments_url: Comments Url
453
     *   statuses_url: Statuses Url
454
     *   head: Head
455
     *   base: Base
456
     *   _links: Links
457
     * @default-fields number,user,title
458
     * @default-string-field number
459
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
460
     */
461
    public function prList($projectWithOrg = '', $options = ['state' => 'open', 'as' => 'default', 'format' => 'table'])
462
    {
463
        $api = $this->api($options['as']);
464
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
465
466
        list($org, $project) = explode('/', $projectWithOrg, 2);
467
468
        $pullRequests = $api->gitHubAPI()->api('pull_request')->all($org, $project, ['state' => $options['state']]);
469
470
        $pullRequests = $this->keyById($pullRequests, 'number');
471
472
        $result = new RowsOfFields($pullRequests);
473
        $this->addTableRenderFunction($result);
474
475
        return $result;
476
    }
477
478
    protected function addTableRenderFunction($data)
479
    {
480
        $data->addRendererFunction(
481
            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...
482
                if (empty($cellData)) {
483
                    return '';
484
                }
485
                if (is_array($cellData)) {
486
                    if ($key == 'permissions') {
487
                        return implode(',', array_filter(array_keys($cellData)));
488
                    }
489
                    foreach (['login', 'label', 'name'] as $k) {
490
                        if (isset($cellData[$k])) {
491
                            return $cellData[$k];
492
                        }
493
                    }
494
                    // TODO: simplify
495
                    //   assignees
496
                    //   requested_reviewers
497
                    //   requested_teams
498
                    //   labels
499
                    //   _links
500
                    return json_encode($cellData, true);
501
                }
502
                if (!is_string($cellData)) {
503
                    return var_export($cellData, true);
504
                }
505
                return $cellData;
506
            }
507
        );
508
    }
509
510
    protected function keyById($data, $field)
511
    {
512
        return
513
            array_column(
514
                array_map(
515
                    function ($k) use ($data, $field) {
516
                        return [$data[$k][$field], $data[$k]];
517
                    },
518
                    array_keys($data)
519
                ),
520
                1,
521
                0
522
            );
523
    }
524
525
    protected function api($as = 'default')
526
    {
527
        $api = new HubphAPI($this->getConfig());
528
        $api->setAs($as);
529
530
        return $api;
531
    }
532
}
533