Completed
Push — master ( 58b0ef...9aa801 )
by Greg
01:27
created

HubphCommands::getProjectWithOrfFromUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
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
    public function prClose($projectWithOrg = '', $number = '', $options = ['as' => 'default'])
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);
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);
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
            'file|F' => '',
108
            'base' => '',
109
            'head' => '',
110
            'as' => 'default',
111
            'format' => 'yaml',
112
            'idempotent' => false
113
        ]
114
    ) {
115
        $projectWithOrg = $this->projectWithOrg();
116
117
        // Get the commit message from --message or --file
118
        $message = $this->getMessage($options);
119
120
        // Determine all of the vid/vval pairs if idempotent
121
        $vids = $this->getVids($options, $message);
122
123
        $api = $this->api($options['as']);
124
        list($status, $result) = $api->prCheck($projectWithOrg, $vids);
125
126
        if ($status) {
127
            return new CommandError($result, $status);
128
        }
129
130
        // Go ahead and create the requested PR
131
132
        // If $result is an array, it will contain
133
        // all of the pull request numbers to close.
134
        // TODO: We should make a wrapper object for $result
135
        if (is_array($result)) {
136
            list($org, $project) = explode('/', $projectWithOrg, 2);
137
            $api->prClose($org, $project, $result);
138
        }
139
    }
140
141
    protected function getMessage($options)
142
    {
143
        if (!empty($options['message'])) {
144
            return $options['message'];
145
        }
146
        if (!empty($options['file'])) {
147
            return file_get_contents($options['file']);
148
        }
149
        return '';
150
    }
151
152
    protected function getVids($options, $message)
153
    {
154
        $vids = new VersionIdentifiers();
155
156
        //if (empty($options['idempotent'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
85% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
157
        //    return $vids;
158
        //}
159
160
        // Allow the caller to define more specific vid / vval patterns
161
        if (!empty($options['vid'])) {
162
            $vids->setVidPattern($options['vid']);
163
        }
164
        if (!empty($options['vval'])) {
165
            $vids->setVvalPattern($options['vval']);
166
        }
167
168
        $vids->addVidsFromMessage($message);
169
        return $vids;
170
    }
171
172
    protected function projectWithOrg($projectWithOrg = '')
173
    {
174
        if (!empty($projectWithOrg)) {
175
            return $projectWithOrg;
176
        }
177
178
        return $this->getProjectWithOrgFromRemote();
179
    }
180
181
    protected function getProjectWithOrgFromRemote($remote = 'origin', $cwd = '')
182
    {
183
        $remote = $this->getRemote($remote, $cwd);
184
185
        return $this->getProjectWithOrfFromUrl($remote);
186
    }
187
188
    protected function getProjectWithOrfFromUrl($remote)
189
    {
190
        $remote = preg_replace('#^git@[^:]*:#', '', $remote);
191
        $remote = preg_replace('#^[^:]*://[^/]/#', '', $remote);
192
        $remote = preg_replace('#\.git$#', '', $remote);
193
194
        return $remote;
195
    }
196
197
    protected function getRemote($remote = 'origin', $cwd = '')
198
    {
199
        if (!empty($cwd)) {
200
            $cwd = "-C $cwd";
201
        }
202
        return exec("git {$cwd} config --get remote.{$remote}.url");
203
    }
204
205
    /**
206
     * @command pr:find
207
     * @param $projectWithOrg The project to work on, e.g. org/project
208
     * @option $q Query term
209
     * @filter-output
210
     * @field-labels
211
     *   url: Url
212
     *   id: ID
213
     *   node_id: Node ID
214
     *   html_url: HTML Url
215
     *   diff_url: Diff Url
216
     *   patch_url: Patch Url
217
     *   issue_url: Issue Url
218
     *   number: Number
219
     *   state: State
220
     *   locked: Locked
221
     *   title: Title
222
     *   user: User
223
     *   body: Boday
224
     *   created_at: Created
225
     *   updated_at: Updated
226
     *   closed_at: Closed
227
     *   merged_at: Merged
228
     *   merge_commit_sha: Merge Commit
229
     *   assignee: Assignee
230
     *   assignees: Assignees
231
     *   requested_reviewers: Requested Reviewers
232
     *   requested_teams: Requested Teams
233
     *   labels: Labels
234
     *   milestone: Milestone
235
     *   commits_url: Commit Url
236
     *   review_comments_url: Review Comments Url
237
     *   review_comment_url: Review Comment Url
238
     *   comments_url: Comments Url
239
     *   statuses_url: Statuses Url
240
     *   head: Head
241
     *   base: Base
242
     *   _links: Links
243
     * @default-fields number,user,title
244
     * @default-string-field number
245
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
246
    */
247
    public function prFind($projectWithOrg = '', $options = ['as' => 'default', 'format' => 'yaml', 'q' => ''])
248
    {
249
        $api = $this->api($options['as']);
250
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
251
        $q = $options['q'];
252
253
        if (!empty($q)) {
254
            $q = $q . ' ';
255
        }
256
        $q = $q . 'repo:' . $projectWithOrg;
257
        $searchResults = $api->gitHubAPI()->api('search')->issues($q);
258
        $pullRequests = $searchResults['items'];
259
260
        $pullRequests = $this->keyById($pullRequests, 'number');
261
        $result = new RowsOfFields($pullRequests);
262
        $this->alterPRTables($result);
263
264
        return $result;
265
    }
266
267
    /**
268
     * @command pr:show
269
     * @field-labels
270
     *   url: Url
271
     *   id: ID
272
     *   node_id: Node ID
273
     *   html_url: HTML Url
274
     *   diff_url: Diff Url
275
     *   patch_url: Patch Url
276
     *   issue_url: Issue Url
277
     *   number: Number
278
     *   state: State
279
     *   locked: Locked
280
     *   title: Title
281
     *   user: User
282
     *   body: Boday
283
     *   created_at: Created
284
     *   updated_at: Updated
285
     *   closed_at: Closed
286
     *   merged_at: Merged
287
     *   merge_commit_sha: Merge Commit
288
     *   assignee: Assignee
289
     *   assignees: Assignees
290
     *   requested_reviewers: Requested Reviewers
291
     *   requested_teams: Requested Teams
292
     *   labels: Labels
293
     *   milestone: Milestone
294
     *   commits_url: Commit Url
295
     *   review_comments_url: Review Comments Url
296
     *   review_comment_url: Review Comment Url
297
     *   comments_url: Comments Url
298
     *   statuses_url: Statuses Url
299
     *   head: Head
300
     *   base: Base
301
     *   _links: Links
302
     * @return Consolidation\OutputFormatters\StructuredData\PropertyList
303
     */
304
    public function prShow($projectWithOrg = '', $number = '', $options = ['as' => 'default', 'format' => 'table'])
305
    {
306
        if (empty($number) && preg_match('#^[0-9]*$#', $projectWithOrg)) {
307
            $number = $projectWithOrg;
308
            $projectWithOrg = '';
309
        }
310
        $api = $this->api($options['as']);
311
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
312
313
        list($org, $project) = explode('/', $projectWithOrg, 2);
314
315
        $pullRequests = $api->gitHubAPI()->api('pull_request')->show($org, $project, $number);
316
317
        $result = new PropertyList($pullRequests);
318
        $this->alterPRTables($result);
319
320
        return $result;
321
    }
322
323
    /**
324
     * @command pr:list
325
     * @param $projectWithOrg The project to work on, e.g. org/project
326
     * @filter-output
327
     * @field-labels
328
     *   url: Url
329
     *   id: ID
330
     *   node_id: Node ID
331
     *   html_url: HTML Url
332
     *   diff_url: Diff Url
333
     *   patch_url: Patch Url
334
     *   issue_url: Issue Url
335
     *   number: Number
336
     *   state: State
337
     *   locked: Locked
338
     *   title: Title
339
     *   user: User
340
     *   body: Boday
341
     *   created_at: Created
342
     *   updated_at: Updated
343
     *   closed_at: Closed
344
     *   merged_at: Merged
345
     *   merge_commit_sha: Merge Commit
346
     *   assignee: Assignee
347
     *   assignees: Assignees
348
     *   requested_reviewers: Requested Reviewers
349
     *   requested_teams: Requested Teams
350
     *   labels: Labels
351
     *   milestone: Milestone
352
     *   commits_url: Commit Url
353
     *   review_comments_url: Review Comments Url
354
     *   review_comment_url: Review Comment Url
355
     *   comments_url: Comments Url
356
     *   statuses_url: Statuses Url
357
     *   head: Head
358
     *   base: Base
359
     *   _links: Links
360
     * @default-fields number,user,title
361
     * @default-string-field number
362
     * @return Consolidation\OutputFormatters\StructuredData\RowsOfFields
363
     */
364
    public function prList($projectWithOrg = '', $options = ['state' => 'open', 'as' => 'default', 'format' => 'table'])
365
    {
366
        $api = $this->api($options['as']);
367
        $projectWithOrg = $this->projectWithOrg($projectWithOrg);
368
369
        list($org, $project) = explode('/', $projectWithOrg, 2);
370
371
        $pullRequests = $api->gitHubAPI()->api('pull_request')->all($org, $project, ['state' => $options['state']]);
372
373
        $pullRequests = $this->keyById($pullRequests, 'number');
374
375
        $result = new RowsOfFields($pullRequests);
376
        $this->alterPRTables($result);
377
378
        return $result;
379
    }
380
381
    protected function alterPRTables($data)
382
    {
383
        $data->addRendererFunction(
384
            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...
385
                if (is_array($cellData)) {
386
                    if (empty($cellData)) {
387
                        return '';
388
                    }
389
                    foreach (['login', 'label'] as $k) {
390
                        if (isset($cellData[$k])) {
391
                            return $cellData[$k];
392
                        }
393
                    }
394
                    // TODO: simplify
395
                    //   assignees
396
                    //   requested_reviewers
397
                    //   requested_teams
398
                    //   labels
399
                    //   _links
400
                    return json_encode($cellData, true);
401
                }
402
                if (!is_string($cellData)) {
403
                    return var_export($cellData, true);
404
                }
405
                return $cellData;
406
            }
407
        );
408
    }
409
410
    protected function keyById($data, $field)
411
    {
412
        return
413
            array_column(
414
                array_map(
415
                    function ($k) use ($data, $field) {
416
                        return [$data[$k][$field], $data[$k]];
417
                    },
418
                    array_keys($data)
419
                ),
420
                1,
421
                0
422
            );
423
    }
424
425
    /**
426
     * @hook alter @filter-output
427
     * @option $filter Filter output based on provided expression
428
     * @default $filter ''
429
     */
430
    public function filterOutput($result, CommandData $commandData)
431
    {
432
        $expr = $commandData->input()->getOption('filter');
433
        if (!empty($expr)) {
434
            $factory = LogicalOpFactory::get();
435
            $op = $factory->evaluate($expr);
436
            $filter = new FilterOutputData();
437
            $result = $filter->filter($result, $op);
438
        }
439
440
        return $result;
441
    }
442
443
    protected function api($as = 'default')
444
    {
445
        $api = new HubphAPI($this->getConfig());
446
        $api->setAs($as);
447
448
        return $api;
449
    }
450
}
451