├── .gitignore
├── icon.png
├── icons
├── clone.png
├── file.png
├── fork.png
├── gists.png
├── issue.png
├── pulse.png
├── repo.png
├── stars.png
├── user.png
├── wiki.png
├── branch.png
├── commits.png
├── graphs.png
├── logout.png
├── mirror.png
├── project.png
├── search.png
├── update.png
├── dashboard.png
├── milestone.png
├── releases.png
├── settings.png
├── organization.png
├── private-fork.png
├── private-repo.png
├── pull-request.png
├── notifications.png
└── private-mirror.png
├── screenshot.png
├── .php_cs.dist
├── LICENSE
├── bin
├── build
└── create_icons.php
├── server.php
├── CHANGELOG.md
├── README.md
├── action.php
├── curl.php
├── item.php
├── workflow.php
├── info.plist
└── search.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .php_cs.cache
2 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icon.png
--------------------------------------------------------------------------------
/icons/clone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/clone.png
--------------------------------------------------------------------------------
/icons/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/file.png
--------------------------------------------------------------------------------
/icons/fork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/fork.png
--------------------------------------------------------------------------------
/icons/gists.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/gists.png
--------------------------------------------------------------------------------
/icons/issue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/issue.png
--------------------------------------------------------------------------------
/icons/pulse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/pulse.png
--------------------------------------------------------------------------------
/icons/repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/repo.png
--------------------------------------------------------------------------------
/icons/stars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/stars.png
--------------------------------------------------------------------------------
/icons/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/user.png
--------------------------------------------------------------------------------
/icons/wiki.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/wiki.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/screenshot.png
--------------------------------------------------------------------------------
/icons/branch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/branch.png
--------------------------------------------------------------------------------
/icons/commits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/commits.png
--------------------------------------------------------------------------------
/icons/graphs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/graphs.png
--------------------------------------------------------------------------------
/icons/logout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/logout.png
--------------------------------------------------------------------------------
/icons/mirror.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/mirror.png
--------------------------------------------------------------------------------
/icons/project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/project.png
--------------------------------------------------------------------------------
/icons/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/search.png
--------------------------------------------------------------------------------
/icons/update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/update.png
--------------------------------------------------------------------------------
/icons/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/dashboard.png
--------------------------------------------------------------------------------
/icons/milestone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/milestone.png
--------------------------------------------------------------------------------
/icons/releases.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/releases.png
--------------------------------------------------------------------------------
/icons/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/settings.png
--------------------------------------------------------------------------------
/icons/organization.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/organization.png
--------------------------------------------------------------------------------
/icons/private-fork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/private-fork.png
--------------------------------------------------------------------------------
/icons/private-repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/private-repo.png
--------------------------------------------------------------------------------
/icons/pull-request.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/pull-request.png
--------------------------------------------------------------------------------
/icons/notifications.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/notifications.png
--------------------------------------------------------------------------------
/icons/private-mirror.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bs/alfred-github-workflow/master/icons/private-mirror.png
--------------------------------------------------------------------------------
/.php_cs.dist:
--------------------------------------------------------------------------------
1 | setRiskyAllowed(true)
14 | ->setRules([
15 | '@Symfony' => true,
16 | '@Symfony:risky' => true,
17 | 'array_syntax' => ['syntax' => 'long'],
18 | 'blank_line_before_return' => false,
19 | 'combine_consecutive_unsets' => true,
20 | 'header_comment' => ['header' => $header],
21 | 'method_argument_space' => false,
22 | 'no_useless_else' => true,
23 | ])
24 | ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__))
25 | ;
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Gregor Harlan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/bin/build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | addFile($dir.'/'.$file, $file);
39 | }
40 | foreach (glob($dir.'/icons/*.png') as $path) {
41 | $zip->addFile($path, 'icons/'.basename($path));
42 | }
43 |
44 | $zip->compressFiles(Phar::GZ);
45 |
46 | $workflow = $dir.'/github.alfredworkflow';
47 | if (file_exists($workflow)) {
48 | unlink($workflow);
49 | }
50 | rename($zipFile, $workflow);
51 |
--------------------------------------------------------------------------------
/server.php:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 | alfred-github-workflow
25 |
56 |
57 |
58 |
59 |

60 |
61 | alfred-github-workflow is ready.
62 | Have fun.
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/bin/create_icons.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | array(
6 | 'mark-github' => 'github',
7 |
8 | 'repo' => 'repo',
9 | 'repo-forked' => 'fork',
10 | 'mirror' => 'mirror',
11 |
12 | 'person' => 'user',
13 | 'organization' => 'organization',
14 | 'star' => 'stars',
15 | 'gist' => 'gists',
16 |
17 | 'issue-opened' => 'issue',
18 | 'git-pull-request' => 'pull-request',
19 | 'milestone' => 'milestone',
20 | 'file' => 'file',
21 | 'graph' => 'graphs',
22 | 'pulse' => 'pulse',
23 | 'project' => 'project',
24 | 'book' => 'wiki',
25 | 'git-commit' => 'commits',
26 | 'git-branch' => 'branch',
27 | 'repo-clone' => 'clone',
28 | 'tag' => 'releases',
29 |
30 | 'dashboard' => 'dashboard',
31 | 'gear' => 'settings',
32 | 'bell' => 'notifications',
33 |
34 | 'search' => 'search',
35 |
36 | 'cloud-download' => 'update',
37 | 'sign-out' => 'logout',
38 | ),
39 | '#e9dba5' => array(
40 | 'repo' => 'private-repo',
41 | 'repo-forked' => 'private-fork',
42 | 'mirror' => 'private-mirror',
43 | ),
44 | );
45 |
46 | $dir = __DIR__.'/../icons/';
47 |
48 | $baseImg = new Imagick();
49 | $baseImg->newImage(256, 256, new ImagickPixel('transparent'));
50 | $baseImg->setImageFormat('png');
51 |
52 | $draw = new ImagickDraw();
53 | $draw->setFillColor('#444444');
54 | $draw->roundRectangle(0, 0, 256, 256, 50, 50);
55 | $baseImg->drawImage($draw);
56 |
57 | foreach ($icons as $color => $set) {
58 | foreach ($set as $svgName => $name) {
59 | $img = clone $baseImg;
60 |
61 | $svg = new Imagick();
62 | $svg->setBackgroundColor(new ImagickPixel('transparent'));
63 | $svg->setResolution(1020, 1020);
64 |
65 | $file = file_get_contents(__DIR__.'/../node_modules/octicons/build/svg/'.$svgName.'.svg');
66 | $file = str_replace(''."\n".$file;
68 |
69 | $svg->readImageBlob($file);
70 |
71 | $x = (256 - $svg->getImageWidth()) / 2;
72 | $y = (256 - $svg->getImageHeight()) / 2;
73 |
74 | $img->compositeImage($svg, Imagick::COMPOSITE_DEFAULT, $x, $y);
75 | $img->writeImage($dir.$name.'.png');
76 | }
77 | }
78 |
79 | rename($dir.'github.png', __DIR__.'/../icon.png');
80 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | Version 1.6.2 – 2018-02-13
5 | --------------------------
6 |
7 | ### Bugfixes
8 |
9 | * Api pagination didn't work correctly any more (missing results from page > 2)
10 |
11 |
12 | Version 1.6.1 – 2017-09-23
13 | --------------------------
14 |
15 | ### Bugfixes
16 |
17 | * Support for macOS 10.13 High Sierra
18 | * Commit search results had wrong urls on GitHub Enterprise (@beparker)
19 |
20 |
21 | Version 1.6 – 2017-05-07
22 | ------------------------
23 |
24 | ### Features
25 |
26 | * new command `gh user/repo projects` (@dagio)
27 | * new command `gh my pulls review requested` (@AeroEchelon)
28 | * better sorting for issues (most recently updated on top) and commits (most recent on top) (@danielma)
29 |
30 | ### Bugfixes
31 |
32 | * On macOS 10.12.5 Beta URLs didn't opened in browser anymore
33 |
34 |
35 | Version 1.5 – 2016-12-13
36 | ------------------------
37 |
38 | ### Features
39 |
40 | * new commands for searching repos and users globally in GitHub (`gh s repo` and `gh s @user`)
41 | * new command `gh my repos` (@jacobkossman)
42 | * new command `gh > delete database`
43 | * source repos with higher priority than forks
44 |
45 | ### Bugfixes
46 |
47 | * in some situations private repos were missing (@lxynox)
48 | * after saving GitHub Enterprise url the workflow didn't reopen correctly
49 | * updated user sub commands ("Activity" tab does not exist any more on GitHub)
50 |
51 |
52 | Version 1.4.1 – 2016-22-07
53 | --------------------------
54 |
55 | * fixed reading environment variables (important for hotkey support)
56 |
57 |
58 | Version 1.4 – 2016-22-07
59 | ------------------------
60 |
61 | * Hotkey support
62 | * use native update mechanism of Alfred (to keep your hotkeys)
63 | * new command `gh user/repo releases` (@altern8tif)
64 | * cache warmup after login
65 | * lower cpu usage in multi curl
66 | * fixed autocomplete values in GitHub Enterprise
67 |
68 |
69 | Version 1.3 – 2016-17-07
70 | ------------------------
71 |
72 | **Important:** This is the last version for Alfred 2.
73 |
74 | * Disabled updates in Alfred 2
75 | * Updates in Alfred 3 are loaded from new location (GitHub releases)
76 |
77 |
78 | Version 1.2 – 2016-04-17
79 | ------------------------
80 |
81 | ### Features
82 |
83 | * New sub commands for `gh my issues/pull`: `created`, `assigned` and `mentioned`
84 | * New help command: `gh > help`
85 | * Longer cache lifetime
86 |
87 |
88 | Version 1.1 – 2015-01-10
89 | ------------------------
90 |
91 | ### Features
92 |
93 | * GitHub Enterprise support (use `ghe`)
94 | * Commit search (`gh user/repo *hash`)
95 |
96 | ### Bugfixes
97 |
98 | * A space after `gh` is required to avoid confusion when using commands of other workflows like `ghost`
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | GitHub Workflow for [Alfred 3](http://www.alfredapp.com)
2 | ==============================
3 |
4 | [](https://gitter.im/gharlan/alfred-github-workflow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
5 |
6 | You can search through GitHub (`gh`) and your GitHub Enterprise instance (`ghe`).
7 |
8 | You have to login (`gh > login`) before you can use the workflow. The login uses OAuth, so you do not have to enter your credentials.
9 |
10 | **[DOWNLOAD](https://github.com/gharlan/alfred-github-workflow/releases)**
11 |
12 | 
13 |
14 | Setup
15 | -----
16 |
17 | ### For github.com
18 |
19 | In Alfred type (`gh > login`) to authenticate against your account. The login uses OAuth, so you do not have to enter your credentials.
20 |
21 | ### For github enterprise
22 |
23 | 1. In Alfred type (`ghe > url https://github.mycompany.com`)
24 | 2. Create a new Personal Access Token (`ghe > generate token` or `https://github.mycompany.com/settings/applications`). It only needs access to your repos. Copy this token to your clipboard.
25 | 3. In Alfred type (`ghe > login `)
26 | 4. You can now `ghe your_enterprise_repo_name`
27 |
28 | Key Combinations
29 | ----------------
30 |
31 | Key Combination | Action
32 | ---------------------- | ------
33 | `enter` | Open entry in default browser
34 | `cmd` + `c` | Copy URL of the entry
35 | `cmd` + `enter` | Paste URL to front most app
36 | `shift` or `cmd` + `y` | Open URL in QuickLook
37 |
38 | Commands
39 | --------
40 |
41 | To search through your GitHub Enterprise instance replace `gh` by `ghe`.
42 |
43 | ### Repo commands
44 |
45 | * `gh user/repo`
46 | * `gh user/repo #123`
47 | * `gh user/repo @branch`
48 | * `gh user/repo *commit`
49 | * `gh user/repo /path/to/file`
50 | * `gh user/repo admin`
51 | * `gh user/repo clone`
52 | * `gh user/repo graphs`
53 | * `gh user/repo issues`
54 | * `gh user/repo milestones`
55 | * `gh user/repo network`
56 | * `gh user/repo new issue`
57 | * `gh user/repo new pull`
58 | * `gh user/repo projects`
59 | * `gh user/repo pulls`
60 | * `gh user/repo pulse`
61 | * `gh user/repo releases`
62 | * `gh user/repo wiki`
63 | * `gh user/repo projects`
64 |
65 | ### User commands
66 |
67 | * `gh @user`
68 | * `gh @user overview`
69 | * `gh @user repositories`
70 | * `gh @user stars`
71 | * `gh @user gists`
72 |
73 | ### Search commands
74 |
75 | * `gh s repo`
76 | * `gh s @user`
77 |
78 | ### "My" commands
79 |
80 | * `gh my dashboard`
81 | * `gh my notifications`
82 | * `gh my profile`
83 | * `gh my issues`
84 | * `gh my issues created`
85 | * `gh my issues assigned`
86 | * `gh my issues mentioned`
87 | * `gh my pulls`
88 | * `gh my pulls created`
89 | * `gh my pulls assigned`
90 | * `gh my pulls mentioned`
91 | * `gh my pulls review requested`
92 | * `gh my repos`
93 | * `gh my settings`
94 | * `gh my stars`
95 | * `gh my gists`
96 |
97 | ### Workflow commands
98 |
99 | * `gh > login`
100 | * `gh > logout`
101 | * `gh > delete cache`
102 | * `gh > delete database`
103 | * `gh > update`
104 | * `gh > activate autoupdate`
105 | * `gh > deactivate autoupdate`
106 | * `gh > help`
107 | * `gh > changelog`
108 | * `ghe > url` (GitHub Enterprise only)
109 | * `ghe > generate token` (GitHub Enterprise only)
110 | * `ghe > enterprise reset` (GitHub Enterprise only)
111 |
112 |
113 |
--------------------------------------------------------------------------------
/action.php:
--------------------------------------------------------------------------------
1 | ' !== $query[0] && 0 !== strpos($query, 'e >')) {
17 | if ('.git' == substr($query, -4)) {
18 | $query = 'github-mac://openRepo/'.substr($query, 0, -4);
19 | }
20 | exec('open '.$query);
21 | return;
22 | }
23 |
24 | $enterprise = 0 === strpos($query, 'e ');
25 | if ($enterprise) {
26 | $query = substr($query, 2);
27 | }
28 | $parts = explode(' ', $query);
29 |
30 | Workflow::init($enterprise);
31 |
32 | switch ($parts[1]) {
33 | case 'enterprise-url':
34 | Workflow::setConfig('enterprise_url', rtrim($parts[2], '/'));
35 | exec('osascript -e "tell application \"Alfred 3\" to search \"ghe \""');
36 | break;
37 |
38 | case 'enterprise-reset':
39 | Workflow::removeConfig('enterprise_url');
40 | Workflow::removeConfig('enterprise_access_token');
41 | Workflow::deleteCache();
42 | break;
43 |
44 | case 'login':
45 | if (isset($parts[2]) && $parts[2]) {
46 | Workflow::setAccessToken($parts[2]);
47 | echo 'Successfully logged in';
48 | } elseif (!$enterprise) {
49 | Workflow::startServer();
50 | $state = version_compare(PHP_VERSION, '5.4', '<') ? 'm' : '';
51 | $url = Workflow::getBaseUrl().'/login/oauth/authorize?client_id=2d4f43826cb68e11c17c&scope=repo&state='.$state;
52 | exec('open '.escapeshellarg($url));
53 | }
54 | break;
55 |
56 | case 'logout':
57 | Workflow::removeAccessToken();
58 | Workflow::deleteCache();
59 | echo 'Successfully logged out';
60 | break;
61 |
62 | case 'delete-cache':
63 | Workflow::deleteCache();
64 | echo 'Successfully deleted cache';
65 | break;
66 |
67 | case 'delete-database':
68 | Workflow::deleteDatabase();
69 | echo 'Successfully deleted database';
70 | break;
71 |
72 | case 'refresh-cache':
73 | $curl = new Curl();
74 | foreach (explode(',', $parts[2]) as $url) {
75 | Workflow::requestCache($url, $curl, null, false, 0, false);
76 | }
77 | $curl->execute();
78 | Workflow::cleanCache();
79 | break;
80 |
81 | case 'activate-autoupdate':
82 | Workflow::setConfig('autoupdate', 1);
83 | echo 'Activated auto updating';
84 | break;
85 |
86 | case 'deactivate-autoupdate':
87 | Workflow::setConfig('autoupdate', 0);
88 | echo 'Deactivated auto updating';
89 | break;
90 |
91 | case 'update':
92 | $release = json_decode(Workflow::request('https://api.github.com/repos/gharlan/alfred-github-workflow/releases/latest'));
93 | if (!isset($release->assets[0]->browser_download_url)) {
94 | echo 'Update failed';
95 | exit;
96 | }
97 | $response = Workflow::request($release->assets[0]->browser_download_url, null, null, false);
98 | if (!$response) {
99 | echo 'Update failed';
100 | exit;
101 | }
102 | $path = getenv('alfred_workflow_data').'/github.alfredworkflow';
103 | file_put_contents($path, $response);
104 | exec('open '.escapeshellarg($path));
105 | break;
106 | }
107 |
--------------------------------------------------------------------------------
/curl.php:
--------------------------------------------------------------------------------
1 | requests[$request->url] = $request;
24 | if ($this->running) {
25 | $this->addHandle($request);
26 | }
27 | }
28 |
29 | public function execute()
30 | {
31 | $this->running = true;
32 | if (!is_resource(self::$multiHandle)) {
33 | self::$multiHandle = curl_multi_init();
34 | }
35 |
36 | foreach ($this->requests as $request) {
37 | $this->addHandle($request);
38 | }
39 |
40 | $finish = false;
41 | $running = true;
42 | do {
43 | $finish = !$running;
44 | while (CURLM_CALL_MULTI_PERFORM == $execrun = curl_multi_exec(self::$multiHandle, $running));
45 | if ($execrun != CURLM_OK) {
46 | break;
47 | }
48 | while ($done = curl_multi_info_read(self::$multiHandle)) {
49 | $ch = $done['handle'];
50 | $info = curl_getinfo($ch);
51 | $url = self::getHeader($info['request_header'], 'X-Url');
52 | $request = $this->requests[$url];
53 | $rawResponse = curl_multi_getcontent($ch);
54 | if (preg_match("@^HTTP/\\d\\.\\d 200 Connection established\r\n\r\n@i", $rawResponse)) {
55 | list(, $header, $body) = explode("\r\n\r\n", $rawResponse, 3);
56 | } else {
57 | list($header, $body) = explode("\r\n\r\n", $rawResponse, 2);
58 | }
59 | $response = new CurlResponse();
60 | $response->request = $request;
61 | $response->status = $info['http_code'];
62 | $headerNames = array(
63 | 'etag' => 'ETag',
64 | 'contentType' => 'Content-Type',
65 | 'link' => 'Link',
66 | );
67 | foreach ($headerNames as $key => $name) {
68 | $response->$key = self::getHeader($header, $name);
69 | }
70 | if (200 == $response->status) {
71 | $response->content = $body;
72 | }
73 | $callback = $request->callback;
74 | $callback($response);
75 | curl_multi_remove_handle(self::$multiHandle, $ch);
76 | curl_close($ch);
77 | }
78 | if ($running || !$finish) {
79 | if (curl_multi_select(self::$multiHandle, 1) === -1) {
80 | usleep(250);
81 | }
82 | }
83 | } while ($running || !$finish);
84 |
85 | $this->running = false;
86 | return true;
87 | }
88 |
89 | private function addHandle(CurlRequest $request)
90 | {
91 | $defaultOptions = array(
92 | CURLOPT_HEADER => true,
93 | CURLOPT_RETURNTRANSFER => true,
94 | CURLOPT_USERAGENT => 'alfred-github-workflow',
95 | CURLOPT_FOLLOWLOCATION => true,
96 | CURLOPT_MAXREDIRS => 5,
97 | CURLINFO_HEADER_OUT => true,
98 | );
99 | if ($this->debug) {
100 | $defaultOptions[CURLOPT_PROXY] = 'localhost';
101 | $defaultOptions[CURLOPT_PROXYPORT] = 8888;
102 | $defaultOptions[CURLOPT_SSL_VERIFYPEER] = 0;
103 | }
104 |
105 | $ch = curl_init();
106 | $options = $defaultOptions;
107 | $options[CURLOPT_URL] = $request->url;
108 | $header = array();
109 | $header[] = 'X-Url: '.$request->url;
110 | if ($request->token) {
111 | $header[] = 'Authorization: token '.$request->token;
112 | }
113 | if ($request->etag) {
114 | $header[] = 'If-None-Match: '.$request->etag;
115 | }
116 | $options[CURLOPT_HTTPHEADER] = $header;
117 | curl_setopt_array($ch, $options);
118 | curl_multi_add_handle(self::$multiHandle, $ch);
119 | }
120 |
121 | public static function getHeader($header, $key)
122 | {
123 | if (preg_match('/^'.preg_quote($key, '/').': (\V*)/mi', $header, $match)) {
124 | return $match[1];
125 | }
126 | return null;
127 | }
128 | }
129 |
130 | class CurlRequest
131 | {
132 | public $url;
133 | public $etag;
134 | public $token;
135 | public $callback;
136 |
137 | public function __construct($url, $etag, $token, $callback)
138 | {
139 | $this->url = $url;
140 | $this->etag = $etag;
141 | $this->token = $token;
142 | $this->callback = $callback;
143 | }
144 | }
145 |
146 | class CurlResponse
147 | {
148 | /** @var CurlRequest */
149 | public $request;
150 | public $status;
151 | public $contentType;
152 | public $etag;
153 | public $link;
154 | public $content;
155 | }
156 |
--------------------------------------------------------------------------------
/item.php:
--------------------------------------------------------------------------------
1 | randomUid = true;
37 | return $this;
38 | }
39 |
40 | public function prefix($prefix, $onlyTitle = true)
41 | {
42 | $this->prefix = $prefix;
43 | $this->prefixOnlyTitle = $onlyTitle;
44 | return $this;
45 | }
46 |
47 | public function title($title)
48 | {
49 | $this->title = $title;
50 | return $this;
51 | }
52 |
53 | public function comparator($comparator)
54 | {
55 | $this->comparator = $comparator;
56 | return $this;
57 | }
58 |
59 | public function subtitle($subtitle)
60 | {
61 | $this->subtitle = $subtitle;
62 | return $this;
63 | }
64 |
65 | public function icon($icon)
66 | {
67 | $this->icon = $icon;
68 | return $this;
69 | }
70 |
71 | public function arg($arg)
72 | {
73 | $this->arg = $arg;
74 | return $this;
75 | }
76 |
77 | public function valid($valid, $add = '…')
78 | {
79 | $this->valid = (bool) $valid;
80 | $this->add = $add;
81 | return $this;
82 | }
83 |
84 | public function autocomplete($autocomplete = true)
85 | {
86 | $this->autocomplete = $autocomplete;
87 | return $this;
88 | }
89 |
90 | public function prio($prio)
91 | {
92 | $this->prio = $prio;
93 | return $this;
94 | }
95 |
96 | public function match($query)
97 | {
98 | $comparator = strtolower($this->comparator ?: $this->title);
99 | $query = strtolower($query);
100 | if (!$this->prefixOnlyTitle && stripos($query, $this->prefix) === 0) {
101 | $query = substr($query, strlen($this->prefix));
102 | }
103 | $this->sameChars = 0;
104 | $queryLength = strlen($query);
105 | for ($i = 0, $k = 0; $i < $queryLength; ++$i, $k++) {
106 | for (; isset($comparator[$k]) && $comparator[$k] !== $query[$i]; ++$k);
107 | if (!isset($comparator[$k])) {
108 | return false;
109 | }
110 | if ($i === $k) {
111 | ++$this->sameChars;
112 | }
113 | }
114 | $this->missingChars = strlen($comparator) - $queryLength;
115 | return true;
116 | }
117 |
118 | public function compare(self $another)
119 | {
120 | if ($this->sameChars != $another->sameChars) {
121 | return $this->sameChars < $another->sameChars ? 1 : -1;
122 | }
123 | if ($this->prio != $another->prio) {
124 | return $this->prio < $another->prio ? 1 : -1;
125 | }
126 | return $this->missingChars > $another->missingChars ? 1 : -1;
127 | }
128 |
129 | /**
130 | * @param self[] $items
131 | *
132 | * @return string
133 | */
134 | public static function toXml(array $items, $enterprise, $hotkey, $baseUrl)
135 | {
136 | $xml = new SimpleXMLElement('');
137 | $prefix = $hotkey ? '' : ' ';
138 | foreach ($items as $item) {
139 | $c = $xml->addChild('item');
140 | $title = $item->prefix.$item->title;
141 | $c->addAttribute('uid', $item->randomUid ? md5(time().$title) : md5($title));
142 | if ($item->icon && file_exists(__DIR__.'/icons/'.$item->icon.'.png')) {
143 | $c->addChild('icon', 'icons/'.$item->icon.'.png');
144 | } else {
145 | $c->addChild('icon', 'icon.png');
146 | }
147 | if ($item->arg) {
148 | $arg = $item->arg;
149 | if ('/' === $arg[0]) {
150 | $arg = $baseUrl.$arg;
151 | } elseif (false === strpos($arg, '://')) {
152 | $arg = ($enterprise ? 'e ' : '').$arg;
153 | }
154 | $c->addAttribute('arg', $arg);
155 | }
156 | if ($item->autocomplete) {
157 | if (is_string($item->autocomplete)) {
158 | $autocomplete = $item->autocomplete;
159 | } elseif (null !== $item->comparator) {
160 | $autocomplete = $item->comparator;
161 | } else {
162 | $autocomplete = $item->title;
163 | }
164 | $c->addAttribute('autocomplete', $prefix.($item->prefixOnlyTitle ? $autocomplete : $item->prefix.$autocomplete));
165 | }
166 | if (!$item->valid) {
167 | $c->addAttribute('valid', 'no');
168 | $title .= $item->add;
169 | }
170 | $c->addChild('title', htmlspecialchars($title));
171 | if ($item->subtitle) {
172 | $c->addChild('subtitle', htmlspecialchars($item->subtitle));
173 | }
174 | }
175 | return $xml->asXML();
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/workflow.php:
--------------------------------------------------------------------------------
1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
65 |
66 | if (!$exists) {
67 | self::createTables();
68 | }
69 |
70 | if (self::$enterprise) {
71 | self::$baseUrl = self::getConfig('enterprise_url');
72 | self::$apiUrl = self::$baseUrl ? self::$baseUrl.'/api/v3' : null;
73 | self::$gistUrl = self::$baseUrl ? self::$baseUrl.'/gist' : null;
74 | }
75 |
76 | self::$debug = getenv('alfred_debug') && defined('STDERR');
77 |
78 | register_shutdown_function(array(__CLASS__, 'shutdown'));
79 | }
80 |
81 | public static function shutdown()
82 | {
83 | if (self::$refreshUrls) {
84 | $urls = implode(',', array_keys(self::$refreshUrls));
85 | exec('php action.php "> refresh-cache '.$urls.'" > /dev/null 2>&1 &');
86 | self::log('refreshing cache in background for %s', $urls);
87 | }
88 | }
89 |
90 | public static function setConfig($key, $value)
91 | {
92 | self::getStatement('REPLACE INTO config VALUES(?, ?)')->execute(array($key, $value));
93 | }
94 |
95 | public static function getConfig($key, $default = null)
96 | {
97 | $stmt = self::getStatement('SELECT value FROM config WHERE key = ?');
98 | $stmt->execute(array($key));
99 | $value = $stmt->fetchColumn();
100 | return false !== $value ? $value : $default;
101 | }
102 |
103 | public static function removeConfig($key)
104 | {
105 | self::getStatement('DELETE FROM config WHERE key = ?')->execute(array($key));
106 | }
107 |
108 | public static function getBaseUrl()
109 | {
110 | return self::$baseUrl;
111 | }
112 |
113 | public static function getApiUrl($path = null)
114 | {
115 | $url = self::$apiUrl;
116 |
117 | if ($path) {
118 | $paramStart = false === strpos($path, '?') ? '?' : '&';
119 | $url .= $path.$paramStart.'per_page=100';
120 | }
121 |
122 | return $url;
123 | }
124 |
125 | public static function getGistUrl()
126 | {
127 | return self::$gistUrl;
128 | }
129 |
130 | public static function setAccessToken($token)
131 | {
132 | self::setConfig(self::$enterprise ? 'enterprise_access_token' : 'access_token', $token);
133 | }
134 |
135 | public static function getAccessToken()
136 | {
137 | return self::getConfig(self::$enterprise ? 'enterprise_access_token' : 'access_token');
138 | }
139 |
140 | public static function removeAccessToken()
141 | {
142 | self::removeConfig(self::$enterprise ? 'enterprise_access_token' : 'access_token');
143 | }
144 |
145 | public static function request($url, Curl $curl = null, $callback = null, $withAuthorization = true)
146 | {
147 | self::log('loading content for %s', $url);
148 |
149 | $return = false;
150 | $returnValue = null;
151 | if (!$curl) {
152 | $curl = new Curl();
153 | $return = true;
154 | $callback = function ($content) use (&$returnValue) {
155 | $returnValue = $content;
156 | };
157 | }
158 |
159 | $token = $withAuthorization ? self::getAccessToken() : null;
160 | $curl->add(new CurlRequest($url, null, $token, function (CurlResponse $response) use ($callback) {
161 | if (is_callable($callback) && isset($response->content)) {
162 | $callback($response->content);
163 | }
164 | }));
165 |
166 | if ($return) {
167 | $curl->execute();
168 | }
169 | return $returnValue;
170 | }
171 |
172 | /**
173 | * @param string $url
174 | * @param Curl $curl
175 | * @param callable $callback
176 | * @param bool $firstPageOnly
177 | * @param int $maxAge
178 | * @param bool $refreshInBackground
179 | *
180 | * @return mixed
181 | */
182 | public static function requestCache($url, Curl $curl = null, $callback = null, $firstPageOnly = false, $maxAge = self::DEFAULT_CACHE_MAX_AGE, $refreshInBackground = true)
183 | {
184 | $return = false;
185 | $returnValue = null;
186 | if (!$curl) {
187 | $curl = new Curl();
188 | $return = true;
189 | $callback = function ($content) use (&$returnValue) {
190 | $returnValue = $content;
191 | };
192 | }
193 |
194 | $stmt = self::getStatement('SELECT * FROM request_cache WHERE url = ?');
195 | $stmt->execute(array($url));
196 | $stmt->bindColumn('timestamp', $timestamp);
197 | $stmt->bindColumn('etag', $etag);
198 | $stmt->bindColumn('content', $content);
199 | $stmt->bindColumn('refresh', $refresh);
200 | $stmt->fetch(PDO::FETCH_BOUND);
201 |
202 | $shouldRefresh = $timestamp < time() - 60 * $maxAge;
203 | $refreshInBackground = $refreshInBackground && !is_null($content);
204 |
205 | if ($shouldRefresh && $refreshInBackground && $refresh < time() - 3 * 60) {
206 | self::getStatement('UPDATE request_cache SET refresh = ? WHERE url = ?')->execute(array(time(), $url));
207 | self::$refreshUrls[$url] = true;
208 | }
209 |
210 | if (!$shouldRefresh || $refreshInBackground) {
211 | self::log('using cached content for %s', $url);
212 | $content = json_decode($content);
213 |
214 | if (!$firstPageOnly) {
215 | $stmt = self::getStatement('SELECT url, content FROM request_cache WHERE parent = ? ORDER BY `timestamp` DESC');
216 | while ($stmt->execute(array($url)) && $data = $stmt->fetchObject()) {
217 | $content = array_merge($content, json_decode($data->content));
218 | $url = $data->url;
219 | }
220 | }
221 |
222 | if (is_callable($callback)) {
223 | $callback($content);
224 | }
225 |
226 | return $returnValue;
227 | }
228 |
229 | $responses = array();
230 |
231 | $handleResponse = function (CurlResponse $response, $content, $parent = null) use (&$handleResponse, $curl, &$responses, $stmt, $callback, $firstPageOnly) {
232 | $url = $response->request->url;
233 | if ($response && in_array($response->status, array(200, 304))) {
234 | $checkNext = false;
235 | if (304 == $response->status) {
236 | $response->content = $content;
237 | $checkNext = true;
238 | } elseif (false === stripos($response->contentType, 'json')) {
239 | $response->content = json_encode($response->content);
240 | }
241 | $response->content = json_decode($response->content);
242 | if (isset($response->content->items)) {
243 | $response->content = $response->content->items;
244 | }
245 | $responses[] = $response->content;
246 | Workflow::getStatement('REPLACE INTO request_cache VALUES(?, ?, ?, ?, 0, ?)')
247 | ->execute(array($url, time(), $response->etag, json_encode($response->content), $parent));
248 |
249 | if ($firstPageOnly) {
250 | // do nothing
251 | } elseif ($checkNext || $response->link && preg_match('/<([^<>]+)>; rel="next"/U', $response->link, $match)) {
252 | $stmt = Workflow::getStatement('SELECT * FROM request_cache WHERE parent = ?');
253 | $stmt->execute(array($url));
254 | if ($checkNext) {
255 | $stmt->bindColumn('url', $nextUrl);
256 | } else {
257 | $nextUrl = $match[1];
258 | }
259 | $stmt->bindColumn('etag', $etag);
260 | $stmt->bindColumn('content', $content);
261 | $stmt->fetch(PDO::FETCH_BOUND);
262 | if ($nextUrl) {
263 | $curl->add(new CurlRequest($nextUrl, $etag, Workflow::getAccessToken(), function (CurlResponse $response) use ($handleResponse, $url, $content) {
264 | $handleResponse($response, $content, $url);
265 | }));
266 | return;
267 | }
268 | } else {
269 | Workflow::getStatement('DELETE FROM request_cache WHERE parent = ?')->execute(array($url));
270 | }
271 | } else {
272 | Workflow::getStatement('DELETE FROM request_cache WHERE url = ?')->execute(array($url));
273 | $url = null;
274 | }
275 |
276 | if (is_callable($callback)) {
277 | if (empty($responses)) {
278 | $callback(array());
279 | return;
280 | }
281 | if (1 === count($responses)) {
282 | $callback($responses[0]);
283 | return;
284 | }
285 | $callback(array_reduce($responses, function ($content, $response) {
286 | return array_merge($content, $response);
287 | }, array()));
288 | }
289 | };
290 |
291 | self::log('loading content for %s', $url);
292 | $curl->add(new CurlRequest($url, $etag, self::getAccessToken(), function (CurlResponse $response) use (&$responses, $handleResponse, $callback, $content) {
293 | $handleResponse($response, $content);
294 | }));
295 |
296 | if ($return) {
297 | $curl->execute();
298 | }
299 | return $returnValue;
300 | }
301 |
302 | public static function requestApi($url, Curl $curl = null, $callback = null, $firstPageOnly = false, $maxAge = self::DEFAULT_CACHE_MAX_AGE)
303 | {
304 | $url = self::getApiUrl($url);
305 | return self::requestCache($url, $curl, $callback, $firstPageOnly, $maxAge);
306 | }
307 |
308 | public static function cleanCache()
309 | {
310 | self::$db->exec('DELETE FROM request_cache WHERE timestamp < '.(time() - 100 * 24 * 60 * 60));
311 | }
312 |
313 | public static function deleteCache()
314 | {
315 | self::$db->exec('DELETE FROM request_cache');
316 | }
317 |
318 | public static function cacheWarmup()
319 | {
320 | $paths = array('/user', '/user/orgs', '/user/starred', '/user/subscriptions', '/user/repos', '/user/following');
321 | foreach ($paths as $path) {
322 | self::$refreshUrls[self::getApiUrl($path)] = true;
323 | }
324 | }
325 |
326 | public static function startServer()
327 | {
328 | if (version_compare(PHP_VERSION, '5.4', '>=')) {
329 | self::stopServer();
330 | shell_exec(sprintf(
331 | 'alfred_workflow_data=%s php -d variables_order=EGPCS -S localhost:2233 server.php > /dev/null 2>&1 & echo $! >> %s',
332 | escapeshellarg(getenv('alfred_workflow_data')),
333 | escapeshellarg(self::$filePids)
334 | ));
335 | }
336 | }
337 |
338 | public static function stopServer()
339 | {
340 | if (file_exists(self::$filePids)) {
341 | $pids = file(self::$filePids);
342 | foreach ($pids as $pid) {
343 | shell_exec('kill -9 '.$pid);
344 | }
345 | unlink(self::$filePids);
346 | }
347 | }
348 |
349 | public static function checkUpdate()
350 | {
351 | if (self::getConfig('version') !== self::VERSION) {
352 | self::setConfig('version', self::VERSION);
353 | }
354 | if (!self::getConfig('autoupdate', 1)) {
355 | return false;
356 | }
357 | $release = self::requestCache('https://api.github.com/repos/gharlan/alfred-github-workflow/releases/latest', null, null, true, 1440);
358 | if (!$release) {
359 | return false;
360 | }
361 | $version = ltrim($release->tag_name, 'v');
362 | return version_compare($version, self::VERSION) > 0;
363 | }
364 |
365 | private static function createTables()
366 | {
367 | self::$db->exec('
368 | CREATE TABLE config (
369 | key TEXT PRIMARY KEY NOT NULL,
370 | value TEXT
371 | ) WITHOUT ROWID
372 | ');
373 |
374 | self::$db->exec('
375 | CREATE TABLE request_cache (
376 | url TEXT PRIMARY KEY NOT NULL,
377 | timestamp INTEGER NOT NULL,
378 | etag TEXT,
379 | content TEXT,
380 | refresh INTEGER,
381 | parent TEXT
382 | ) WITHOUT ROWID
383 | ');
384 | self::$db->exec('CREATE INDEX parent_url ON request_cache(parent) WHERE parent IS NOT NULL');
385 | }
386 |
387 | public static function deleteDatabase()
388 | {
389 | self::closeCursors();
390 | self::$db = null;
391 | unlink(self::$fileDb);
392 | }
393 |
394 | public static function addItem(Item $item, $check = true)
395 | {
396 | if (!$check || $item->match(self::$query)) {
397 | self::$items[] = $item;
398 | }
399 | }
400 |
401 | public static function sortItems()
402 | {
403 | usort(self::$items, function (Item $a, Item $b) {
404 | return $a->compare($b);
405 | });
406 | }
407 |
408 | public static function getItemsAsXml()
409 | {
410 | return Item::toXml(self::$items, self::$enterprise, self::$hotkey, self::getBaseUrl());
411 | }
412 |
413 | public static function log($msg)
414 | {
415 | if (self::$debug) {
416 | fwrite(STDERR, "\n".call_user_func_array('sprintf', func_get_args()));
417 | }
418 | }
419 |
420 | /**
421 | * @param string $query
422 | *
423 | * @return PDOStatement
424 | */
425 | public static function getStatement($query)
426 | {
427 | if (!isset(self::$statements[$query])) {
428 | self::$statements[$query] = self::$db->prepare($query);
429 | }
430 | return self::$statements[$query];
431 | }
432 |
433 | protected static function closeCursors()
434 | {
435 | foreach (self::$statements as $statement) {
436 | $statement->closeCursor();
437 | }
438 | self::$statements = array();
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | bundleid
6 | de.gh01.alfred.github
7 | category
8 | Internet
9 | connections
10 |
11 | 14CA19E2-6328-4201-905E-A751E2BA47B5
12 |
13 |
14 | destinationuid
15 | ABE8DD6E-CD29-4E70-87CF-1382A0446009
16 | modifiers
17 | 0
18 | modifiersubtext
19 |
20 | vitoclose
21 |
22 |
23 |
24 | 29045171-6618-4FA4-BAB0-39C10422CF31
25 |
26 |
27 | destinationuid
28 | 67ADBB8D-C705-4981-BB9B-7C9238BEFF2E
29 | modifiers
30 | 0
31 | modifiersubtext
32 |
33 | vitoclose
34 |
35 |
36 |
37 | 94B48881-907F-4752-AAA0-234EA30CFC18
38 |
39 |
40 | destinationuid
41 | FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F
42 | modifiers
43 | 0
44 | modifiersubtext
45 |
46 | vitoclose
47 |
48 |
49 |
50 | 95CD1A63-A0C6-4458-9817-9C6B1A90C827
51 |
52 |
53 | destinationuid
54 | 29045171-6618-4FA4-BAB0-39C10422CF31
55 | modifiers
56 | 0
57 | modifiersubtext
58 |
59 | vitoclose
60 |
61 |
62 |
63 | destinationuid
64 | 9715D8FA-0199-450F-99B8-64796B5AC6CB
65 | modifiers
66 | 1048576
67 | modifiersubtext
68 | Copy and paste URL
69 | vitoclose
70 |
71 |
72 |
73 | 9715D8FA-0199-450F-99B8-64796B5AC6CB
74 |
75 |
76 | destinationuid
77 | 87F10E6D-697C-47CE-BD78-E7174F5DF815
78 | modifiers
79 | 0
80 | modifiersubtext
81 |
82 | vitoclose
83 |
84 |
85 |
86 | ABE8DD6E-CD29-4E70-87CF-1382A0446009
87 |
88 |
89 | destinationuid
90 | 95CD1A63-A0C6-4458-9817-9C6B1A90C827
91 | modifiers
92 | 0
93 | modifiersubtext
94 |
95 | vitoclose
96 |
97 |
98 |
99 | DAA505B9-F86C-4AF8-818B-8F614F01485E
100 |
101 |
102 | destinationuid
103 | 94B48881-907F-4752-AAA0-234EA30CFC18
104 | modifiers
105 | 0
106 | modifiersubtext
107 |
108 | vitoclose
109 |
110 |
111 |
112 | FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F
113 |
114 |
115 | destinationuid
116 | 29045171-6618-4FA4-BAB0-39C10422CF31
117 | modifiers
118 | 0
119 | modifiersubtext
120 |
121 | vitoclose
122 |
123 |
124 |
125 | destinationuid
126 | 9715D8FA-0199-450F-99B8-64796B5AC6CB
127 | modifiers
128 | 1048576
129 | modifiersubtext
130 | Copy and paste URL
131 | vitoclose
132 |
133 |
134 |
135 |
136 | createdby
137 | Gregor Harlan
138 | description
139 | GitHub for Alfred
140 | disabled
141 |
142 | name
143 | GitHub
144 | objects
145 |
146 |
147 | config
148 |
149 | action
150 | 0
151 | argument
152 | 0
153 | hotkey
154 | 0
155 | hotmod
156 | 0
157 | hotstring
158 |
159 | leftcursor
160 |
161 | modsmode
162 | 2
163 | relatedAppsMode
164 | 0
165 |
166 | type
167 | alfred.workflow.trigger.hotkey
168 | uid
169 | DAA505B9-F86C-4AF8-818B-8F614F01485E
170 | version
171 | 2
172 |
173 |
174 | config
175 |
176 | alfredfiltersresults
177 |
178 | argumenttype
179 | 1
180 | escaping
181 | 36
182 | keyword
183 | gh
184 | queuedelaycustom
185 | 3
186 | queuedelayimmediatelyinitially
187 |
188 | queuedelaymode
189 | 0
190 | queuemode
191 | 2
192 | runningsubtext
193 | Loading results…
194 | script
195 | php -f search.php -- github "{query}"
196 | scriptargtype
197 | 0
198 | scriptfile
199 |
200 | subtext
201 | Search or type a command
202 | title
203 | gh...
204 | type
205 | 0
206 | withspace
207 |
208 |
209 | type
210 | alfred.workflow.input.scriptfilter
211 | uid
212 | FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F
213 | version
214 | 2
215 |
216 |
217 | config
218 |
219 | concurrently
220 |
221 | escaping
222 | 36
223 | script
224 | php -f action.php -- "{query}"
225 | scriptargtype
226 | 0
227 | scriptfile
228 |
229 | type
230 | 0
231 |
232 | type
233 | alfred.workflow.action.script
234 | uid
235 | 29045171-6618-4FA4-BAB0-39C10422CF31
236 | version
237 | 2
238 |
239 |
240 | config
241 |
242 | lastpathcomponent
243 |
244 | onlyshowifquerypopulated
245 |
246 | removeextension
247 |
248 | text
249 | {query}
250 | title
251 | GitHub
252 |
253 | type
254 | alfred.workflow.output.notification
255 | uid
256 | 67ADBB8D-C705-4981-BB9B-7C9238BEFF2E
257 | version
258 | 1
259 |
260 |
261 | config
262 |
263 | argument
264 | {query}
265 | variables
266 |
267 | hotkey
268 | 1
269 |
270 |
271 | type
272 | alfred.workflow.utility.argument
273 | uid
274 | 94B48881-907F-4752-AAA0-234EA30CFC18
275 | version
276 | 1
277 |
278 |
279 | config
280 |
281 | action
282 | 0
283 | argument
284 | 0
285 | hotkey
286 | 0
287 | hotmod
288 | 0
289 | hotstring
290 |
291 | leftcursor
292 |
293 | modsmode
294 | 2
295 | relatedAppsMode
296 | 0
297 |
298 | type
299 | alfred.workflow.trigger.hotkey
300 | uid
301 | 14CA19E2-6328-4201-905E-A751E2BA47B5
302 | version
303 | 2
304 |
305 |
306 | config
307 |
308 | alfredfiltersresults
309 |
310 | argumenttype
311 | 1
312 | escaping
313 | 36
314 | keyword
315 | ghe
316 | queuedelaycustom
317 | 3
318 | queuedelayimmediatelyinitially
319 |
320 | queuedelaymode
321 | 0
322 | queuemode
323 | 2
324 | runningsubtext
325 | Loading results…
326 | script
327 | php -f search.php -- enterprise "{query}"
328 | scriptargtype
329 | 0
330 | scriptfile
331 |
332 | subtext
333 | Search or type a command (GitHub Enterprise)
334 | title
335 | ghe...
336 | type
337 | 0
338 | withspace
339 |
340 |
341 | type
342 | alfred.workflow.input.scriptfilter
343 | uid
344 | 95CD1A63-A0C6-4458-9817-9C6B1A90C827
345 | version
346 | 2
347 |
348 |
349 | config
350 |
351 | autopaste
352 |
353 | clipboardtext
354 | {query}
355 | transient
356 |
357 |
358 | type
359 | alfred.workflow.output.clipboard
360 | uid
361 | 87F10E6D-697C-47CE-BD78-E7174F5DF815
362 | version
363 | 2
364 |
365 |
366 | config
367 |
368 | argument
369 | {query}
370 | variables
371 |
372 | hotkey
373 | 1
374 |
375 |
376 | type
377 | alfred.workflow.utility.argument
378 | uid
379 | ABE8DD6E-CD29-4E70-87CF-1382A0446009
380 | version
381 | 1
382 |
383 |
384 | config
385 |
386 | inputstring
387 | {query}
388 | matchcasesensitive
389 |
390 | matchmode
391 | 2
392 | matchstring
393 | ^\w+://
394 |
395 | type
396 | alfred.workflow.utility.filter
397 | uid
398 | 9715D8FA-0199-450F-99B8-64796B5AC6CB
399 | version
400 | 1
401 |
402 |
403 | readme
404 | Changelog
405 | =========
406 |
407 | Version 1.6.2 – 2018-02-13
408 | --------------------------
409 |
410 | ### Bugfixes
411 |
412 | * Api pagination didnt work correctly any more (missing results from page > 2)
413 |
414 |
415 | Version 1.6.1 – 2017-09-23
416 | --------------------------
417 |
418 | ### Bugfixes
419 |
420 | * Support for macOS 10.13 High Sierra
421 | * Commit search results had wrong urls on GitHub Enterprise (@beparker)
422 |
423 |
424 | Version 1.6 – 2017-05-07
425 | ------------------------
426 |
427 | ### Features
428 |
429 | * new command `gh user/repo projects` (@dagio)
430 | * new command `gh my pulls review requested` (@AeroEchelon)
431 | * better sorting for issues (most recently updated on top) and commits (most recent on top) (@danielma)
432 |
433 | ### Bugfixes
434 |
435 | * On macOS 10.12.5 Beta URLs didnt opened in browser anymore
436 |
437 |
438 | Version 1.5 – 2016-12-13
439 | ------------------------
440 |
441 | ### Features
442 |
443 | * new commands for searching repos and users globally in GitHub (`gh s repo` and `gh s @user`)
444 | * new command `gh my repos` (@jacobkossman)
445 | * new command `gh > delete database`
446 | * source repos with higher priority than forks
447 |
448 | ### Bugfixes
449 |
450 | * in some situations private repos were missing (@lxynox)
451 | * after saving GitHub Enterprise url the workflow didn't reopen correctly
452 | * updated user sub commands (Activity tab does not exist any more on GitHub)
453 |
454 |
455 | Version 1.4.1 – 2016-22-07
456 | --------------------------
457 |
458 | * fixed reading environment variables (important for hotkey support)
459 |
460 |
461 | Version 1.4 – 2016-22-07
462 | ------------------------
463 |
464 | * Hotkey support
465 | * use native update mechanism of Alfred (to keep your hotkeys)
466 | * new command `gh user/repo releases` (@altern8tif)
467 | * cache warmup after login
468 | * lower cpu usage in multi curl
469 | * fixed autocomplete values in GitHub Enterprise
470 |
471 |
472 | Version 1.3 – 2016-17-07
473 | ------------------------
474 |
475 | **Important:** This is the last version for Alfred 2.
476 |
477 | * Disabled updates in Alfred 2
478 | * Updates in Alfred 3 are loaded from new location (GitHub releases)
479 |
480 |
481 | Version 1.2 – 2016-04-17
482 | ------------------------
483 |
484 | ### Features
485 |
486 | * New sub commands for `gh my issues/pull`: `created`, `assigned` and `mentioned`
487 | * New help command: `gh > help`
488 | * Longer cache lifetime
489 |
490 |
491 | Version 1.1 – 2015-01-10
492 | ------------------------
493 |
494 | ### Features
495 |
496 | * GitHub Enterprise support (use `ghe`)
497 | * Commit search (`gh user/repo *hash`)
498 |
499 | ### Bugfixes
500 |
501 | * A space after `gh` is required to avoid confusion when using commands of other workflows like `ghost`
502 |
503 | uidata
504 |
505 | 14CA19E2-6328-4201-905E-A751E2BA47B5
506 |
507 | colorindex
508 | 9
509 | note
510 | GitHub Enterprise
511 | xpos
512 | 30
513 | ypos
514 | 230
515 |
516 | 29045171-6618-4FA4-BAB0-39C10422CF31
517 |
518 | xpos
519 | 600
520 | ypos
521 | 40
522 |
523 | 67ADBB8D-C705-4981-BB9B-7C9238BEFF2E
524 |
525 | xpos
526 | 790
527 | ypos
528 | 40
529 |
530 | 87F10E6D-697C-47CE-BD78-E7174F5DF815
531 |
532 | xpos
533 | 790
534 | ypos
535 | 230
536 |
537 | 94B48881-907F-4752-AAA0-234EA30CFC18
538 |
539 | colorindex
540 | 12
541 | xpos
542 | 220
543 | ypos
544 | 70
545 |
546 | 95CD1A63-A0C6-4458-9817-9C6B1A90C827
547 |
548 | colorindex
549 | 9
550 | note
551 | GitHub Enterprise
552 | xpos
553 | 330
554 | ypos
555 | 230
556 |
557 | 9715D8FA-0199-450F-99B8-64796B5AC6CB
558 |
559 | xpos
560 | 680
561 | ypos
562 | 260
563 |
564 | ABE8DD6E-CD29-4E70-87CF-1382A0446009
565 |
566 | colorindex
567 | 9
568 | xpos
569 | 220
570 | ypos
571 | 260
572 |
573 | DAA505B9-F86C-4AF8-818B-8F614F01485E
574 |
575 | colorindex
576 | 12
577 | note
578 | github.com
579 | xpos
580 | 30
581 | ypos
582 | 40
583 |
584 | FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F
585 |
586 | colorindex
587 | 12
588 | note
589 | github.com
590 | xpos
591 | 330
592 | ypos
593 | 40
594 |
595 |
596 | variables
597 |
598 | hotkey
599 | 0
600 |
601 | version
602 | 1.6.2
603 | webaddress
604 | https://github.com/gharlan/alfred-github-workflow
605 |
606 |
607 |
--------------------------------------------------------------------------------
/search.php:
--------------------------------------------------------------------------------
1 | ') {
59 | self::addSystemCommands();
60 | Workflow::sortItems();
61 | return;
62 | }
63 |
64 | $isSearch = 's' === $parts[0] && isset($parts[1]);
65 | $isUser = isset($query[0]) && $query[0] == '@';
66 | $isRepo = false;
67 | $queryUser = null;
68 | if ($isUser) {
69 | $queryUser = ltrim($parts[0], '@');
70 | } elseif (!$isSearch && false !== $pos = strpos($parts[0], '/')) {
71 | $queryUser = substr($parts[0], 0, $pos);
72 | $isRepo = true;
73 | }
74 |
75 | if ('my' == $parts[0] && isset($parts[1])) {
76 | self::addMyCommands();
77 | } elseif ($isSearch && strlen($query) > 5 && '@' !== substr($parts[1], 0, 1)) {
78 | self::addRepoSearchCommands();
79 | } elseif ($isSearch && strlen($query) > 6 && '@' === substr($parts[1], 0, 1)) {
80 | self::addUserSearchCommands();
81 | } elseif ($isUser && isset($parts[1])) {
82 | self::addUserSubCommands($queryUser);
83 | } elseif (!$isUser && $isRepo && isset($parts[1])) {
84 | self::addRepoSubCommands();
85 | } else {
86 | self::addDefaultCommands($isSearch, $isUser, $isRepo, $queryUser);
87 | }
88 |
89 | Workflow::sortItems();
90 |
91 | if (!$query) {
92 | return;
93 | }
94 |
95 | if (!$isSearch && !$isUser && !isset($parts[1])) {
96 | Workflow::addItem(Item::create()
97 | ->title('s '.$query)
98 | ->subtitle('Search repo (in alfred workflow)')
99 | ->comparator($query)
100 | ->autocomplete('s '.$query)
101 | ->icon('repo')
102 | ->valid(false)
103 | , false);
104 | }
105 |
106 | if (!$isSearch && !$isRepo && !isset($parts[1])) {
107 | $title = 's @'.ltrim($query, '@');
108 | Workflow::addItem(Item::create()
109 | ->title($title)
110 | ->subtitle('Search user (in alfred workflow)')
111 | ->comparator($query)
112 | ->autocomplete($title)
113 | ->icon('user')
114 | ->valid(false)
115 | , false);
116 | }
117 |
118 | if (!$isUser && $isRepo && isset($parts[1])) {
119 | $repoQuery = substr($query, strlen($parts[0]) + 1);
120 | Workflow::addItem(Item::create()
121 | ->title("Search '$parts[0]' for '$repoQuery'")
122 | ->icon('search')
123 | ->arg('/'.$parts[0].'/search?q='.urlencode($repoQuery))
124 | ->autocomplete(false)
125 | , false);
126 | }
127 |
128 | $path = $isUser ? $queryUser : 'search?q='.urlencode($query);
129 | $name = self::$enterprise ? 'GitHub Enterprise' : 'GitHub';
130 | Workflow::addItem(Item::create()
131 | ->title("Search $name for '$query'")
132 | ->icon('search')
133 | ->arg('/'.$path)
134 | ->autocomplete(false)
135 | , false);
136 | }
137 |
138 | private static function addEmptyQueryCommand()
139 | {
140 | Workflow::addItem(Item::create()
141 | ->title(self::$enterprise ? 'ghe' : 'gh')
142 | ->subtitle('Search or type a command'.(self::$enterprise ? ' (GitHub Enterprise)' : ''))
143 | ->comparator('')
144 | ->valid(false)
145 | , false);
146 | }
147 |
148 | private static function addUpdateCommands()
149 | {
150 | $cmds = array(
151 | 'update' => 'There is an update for this Alfred workflow',
152 | 'deactivate autoupdate' => 'Deactivate auto updating this Alfred Workflow',
153 | );
154 | foreach ($cmds as $cmd => $desc) {
155 | Workflow::addItem(Item::create()
156 | ->title('> '.$cmd)
157 | ->subtitle($desc)
158 | ->icon($cmd)
159 | ->arg('> '.str_replace(' ', '-', $cmd))
160 | ->randomUid()
161 | , false);
162 | }
163 |
164 | Workflow::addItem(Item::create()
165 | ->title('> changelog')
166 | ->subtitle('View the changelog')
167 | ->icon('file')
168 | ->arg('https://github.com/gharlan/alfred-github-workflow/blob/master/CHANGELOG.md')
169 | ->randomUid()
170 | , false);
171 | }
172 |
173 | private static function addEnterpriseUrlCommand()
174 | {
175 | $url = null;
176 | if (count(self::$parts) > 1 && self::$parts[0] == '>' && self::$parts[1] == 'url' && isset(self::$parts[2])) {
177 | $url = self::$parts[2];
178 | }
179 | Workflow::addItem(Item::create()
180 | ->title('> url '.$url)
181 | ->subtitle('Set the GitHub Enterprise URL')
182 | ->arg('> enterprise-url '.$url)
183 | ->valid((bool) $url, '')
184 | ->randomUid()
185 | , false);
186 | }
187 |
188 | private static function addLoginCommands()
189 | {
190 | Workflow::removeAccessToken();
191 | $token = null;
192 | if (count(self::$parts) > 1 && self::$parts[0] == '>' && self::$parts[1] == 'login' && isset(self::$parts[2])) {
193 | $token = self::$parts[2];
194 | }
195 | if (!$token && !self::$enterprise) {
196 | Workflow::addItem(Item::create()
197 | ->title('> login')
198 | ->subtitle('Generate OAuth access token')
199 | ->arg('> login')
200 | ->randomUid()
201 | , false);
202 | }
203 | Workflow::addItem(Item::create()
204 | ->title('> login '.$token)
205 | ->subtitle('Save access token')
206 | ->arg('> login '.$token)
207 | ->valid((bool) $token, '')
208 | ->randomUid()
209 | , false);
210 | if (!$token && self::$enterprise) {
211 | Workflow::addItem(Item::create()
212 | ->title('> generate token')
213 | ->subtitle('Generate a new access token')
214 | ->arg('/settings/applications')
215 | ->randomUid()
216 | , false);
217 | Workflow::addItem(Item::create()
218 | ->title('> enterprise reset')
219 | ->subtitle('Reset the GitHub Enterprise URL')
220 | ->arg('> enterprise-reset')
221 | ->randomUid()
222 | , false);
223 | }
224 | }
225 |
226 | private static function addDefaultCommands($isSearch, $isUser, $isRepo, $queryUser)
227 | {
228 | $users = array();
229 | $repos = array();
230 |
231 | $curl = new Curl();
232 |
233 | if (!$isSearch && !$isUser) {
234 | $getRepos = function ($url, $prio) use ($curl, &$repos) {
235 | Workflow::requestApi($url, $curl, function ($urlRepos) use (&$repos, $prio) {
236 | foreach ($urlRepos as $repo) {
237 | $repo->score = 300 + $prio + ($repo->fork ? 0 : 10);
238 | $repos[$repo->id] = $repo;
239 | }
240 | });
241 | };
242 | if ($isRepo) {
243 | if ($queryUser != self::$user->login) {
244 | $urls = array('/users/'.$queryUser.'/repos', '/orgs/'.$queryUser.'/repos');
245 | } else {
246 | $urls = array('/user/repos');
247 | }
248 | } else {
249 | Workflow::requestApi('/user/orgs', $curl, function ($orgs) use ($getRepos) {
250 | foreach ($orgs as $org) {
251 | $getRepos('/orgs/'.$org->login.'/repos', 0);
252 | }
253 | });
254 | $urls = array('/user/starred', '/user/subscriptions', '/user/repos');
255 | }
256 | foreach ($urls as $prio => $url) {
257 | $getRepos($url, $prio + 1);
258 | }
259 | }
260 |
261 | if (!$isSearch && !$isRepo) {
262 | Workflow::requestApi('/user/following', $curl, function ($urlUsers) use (&$users) {
263 | $users = $urlUsers;
264 | });
265 | }
266 |
267 | $curl->execute();
268 |
269 | self::addRepos($repos);
270 |
271 | foreach ($users as $user) {
272 | Workflow::addItem(Item::create()
273 | ->prefix('@', false)
274 | ->title($user->login.' ')
275 | ->subtitle($user->type)
276 | ->arg($user->html_url)
277 | ->icon(lcfirst($user->type))
278 | ->prio(200)
279 | );
280 | }
281 |
282 | Workflow::addItem(Item::create()
283 | ->title('s '.substr(self::$query, 2, 4))
284 | ->subtitle('Search repo or @user (min 4 chars)')
285 | ->prio(110)
286 | ->valid(false)
287 | );
288 |
289 | Workflow::addItem(Item::create()
290 | ->title('my ')
291 | ->subtitle('Dashboard, settings, and more')
292 | ->prio(100)
293 | ->valid(false)
294 | );
295 | }
296 |
297 | private static function addRepoSearchCommands()
298 | {
299 | $q = substr(self::$query, 2);
300 | $repos = Workflow::requestApi('/search/repositories?q='.urlencode($q), null, null, true);
301 |
302 | self::addRepos($repos, 's ');
303 | }
304 |
305 | private static function addUserSearchCommands()
306 | {
307 | $q = substr(self::$query, 3);
308 | $users = Workflow::requestApi('/search/users?q='.urlencode($q), null, null, true);
309 |
310 | self::addUsers($users, 's @');
311 | }
312 |
313 | private static function addRepos($repos, $comparatorPrefix = '')
314 | {
315 | foreach ($repos as $repo) {
316 | $icon = 'repo';
317 | if ($repo->fork) {
318 | $icon = 'fork';
319 | } elseif ($repo->mirror_url) {
320 | $icon = 'mirror';
321 | }
322 | if ($repo->private) {
323 | $icon = 'private-'.$icon;
324 | }
325 | Workflow::addItem(Item::create()
326 | ->title($repo->full_name.' ')
327 | ->comparator($comparatorPrefix.$repo->full_name)
328 | ->autocomplete($repo->full_name.' ')
329 | ->subtitle($repo->description)
330 | ->icon($icon)
331 | ->arg('/'.$repo->full_name)
332 | ->prio($repo->score)
333 | );
334 | }
335 | }
336 |
337 | private static function addUsers($users, $comparatorPrefix = '')
338 | {
339 | foreach ($users as $user) {
340 | Workflow::addItem(Item::create()
341 | ->prefix('@', false)
342 | ->title($user->login.' ')
343 | ->comparator($comparatorPrefix.$user->login)
344 | ->autocomplete($user->login.' ')
345 | ->subtitle($user->type)
346 | ->arg($user->html_url)
347 | ->icon(lcfirst($user->type))
348 | ->prio(isset($user->score) ? $user->score : 200)
349 | );
350 | }
351 | }
352 |
353 | private static function addRepoSubCommands()
354 | {
355 | $parts = self::$parts;
356 | if (isset($parts[1][0]) && in_array($parts[1][0], array('#', '@', '*', '/'))) {
357 | switch ($parts[1][0]) {
358 | case '*':
359 | $commits = Workflow::requestApi('/repos/'.$parts[0].'/commits');
360 | foreach ($commits as $commit) {
361 | Workflow::addItem(Item::create()
362 | ->title($commit->commit->message)
363 | ->comparator($parts[0].' *'.$commit->sha)
364 | ->subtitle($commit->commit->author->date.' ('.$commit->sha.')')
365 | ->icon('commits')
366 | ->arg('/'.$parts[0].'/commit/'.$commit->sha)
367 | ->prio(strtotime($commit->commit->author->date))
368 | );
369 | }
370 | break;
371 | case '@':
372 | $branches = Workflow::requestApi('/repos/'.$parts[0].'/branches');
373 | foreach ($branches as $branch) {
374 | Workflow::addItem(Item::create()
375 | ->title('@'.$branch->name)
376 | ->comparator($parts[0].' @'.$branch->name)
377 | ->subtitle($branch->commit->sha)
378 | ->icon('branch')
379 | ->arg('/'.$parts[0].'/tree/'.$branch->name)
380 | );
381 | }
382 | break;
383 | case '/':
384 | $repo = Workflow::requestApi('/repos/'.$parts[0]);
385 | $files = Workflow::requestApi('/repos/'.$parts[0].'/git/trees/'.$repo->default_branch.'?recursive=1');
386 | foreach ($files->tree as $file) {
387 | if ('blob' === $file->type) {
388 | Workflow::addItem(Item::create()
389 | ->title(basename($file->path))
390 | ->subtitle('/'.$file->path)
391 | ->comparator($parts[0].' /'.$file->path)
392 | ->icon('file')
393 | ->arg('/'.$parts[0].'/blob/'.$repo->default_branch.'/'.$file->path)
394 | );
395 | }
396 | }
397 | break;
398 | case '#':
399 | $issues = Workflow::requestApi('/repos/'.$parts[0].'/issues?sort=updated&state=all');
400 | foreach ($issues as $issue) {
401 | Workflow::addItem(Item::create()
402 | ->title('#'.$issue->number)
403 | ->comparator($parts[0].' #'.$issue->number)
404 | ->subtitle($issue->title)
405 | ->icon($issue->pull_request ? 'pull-request' : 'issue')
406 | ->arg($issue->html_url)
407 | ->prio(strtotime($issue->updated_at))
408 | );
409 | }
410 | break;
411 | }
412 | } else {
413 | $subs = array(
414 | 'admin' => array('Manage this repo', 'settings'),
415 | 'graphs' => array('All the graphs'),
416 | 'issues ' => array('List, show and create issues', 'issue'),
417 | 'milestones' => array('View milestones', 'milestone'),
418 | 'network' => array('See the network', 'graphs'),
419 | 'projects' => array('View projects', 'project'),
420 | 'pulls' => array('Show open pull requests', 'pull-request'),
421 | 'pulse' => array('See recent activity'),
422 | 'wiki' => array('Pull up the wiki'),
423 | 'commits' => array('View commit history'),
424 | 'releases' => array('See latest releases'),
425 | );
426 | foreach ($subs as $key => $sub) {
427 | Workflow::addItem(Item::create()
428 | ->title($parts[0].' '.$key)
429 | ->subtitle($sub[0])
430 | ->icon(isset($sub[1]) ? $sub[1] : $key)
431 | ->arg('/'.$parts[0].'/'.$key)
432 | );
433 | }
434 | Workflow::addItem(Item::create()
435 | ->title($parts[0].' new issue')
436 | ->subtitle('Create new issue')
437 | ->icon('issue')
438 | ->arg('/'.$parts[0].'/issues/new?source=c')
439 | );
440 | Workflow::addItem(Item::create()
441 | ->title($parts[0].' new pull')
442 | ->subtitle('Create new pull request')
443 | ->icon('pull-request')
444 | ->arg('/'.$parts[0].'/pull/new?source=c')
445 | );
446 | if (empty($parts[1])) {
447 | $subs = array(
448 | '#' => array('Show a specific issue by number', 'issue'),
449 | '@' => array('Show a specific branch', 'branch'),
450 | '*' => array('Show a specific commit', 'commits'),
451 | '/' => array('Show a blob', 'file'),
452 | );
453 | foreach ($subs as $key => $sub) {
454 | Workflow::addItem(Item::create()
455 | ->title($parts[0].' '.$key)
456 | ->subtitle($sub[0])
457 | ->icon($sub[1])
458 | ->arg($key.' '.$parts[0])
459 | ->valid(false)
460 | );
461 | }
462 | }
463 | Workflow::addItem(Item::create()
464 | ->title($parts[0].' clone')
465 | ->subtitle('Clone this repo')
466 | ->icon('clone')
467 | ->arg('/'.$parts[0].'.git')
468 | );
469 | }
470 | }
471 |
472 | private static function addUserSubCommands($queryUser)
473 | {
474 | $subs = array(
475 | 'overview' => array($queryUser, "View $queryUser's overview", 'user'),
476 | 'repositories' => array($queryUser.'?tab=repositories', "View $queryUser's repositories", 'repo'),
477 | 'stars' => array($queryUser.'?tab=stars', "View $queryUser's stars"),
478 | );
479 | $prio = count($subs) + 2;
480 | foreach ($subs as $key => $sub) {
481 | Workflow::addItem(Item::create()
482 | ->title('@'.$queryUser.' '.$key)
483 | ->subtitle($sub[1])
484 | ->icon(isset($sub[2]) ? $sub[2] : $key)
485 | ->arg('/'.$sub[0])
486 | ->prio($prio--)
487 | );
488 | }
489 | Workflow::addItem(Item::create()
490 | ->title('@'.$queryUser.' gists')
491 | ->subtitle("View $queryUser's' gists")
492 | ->icon('gists')
493 | ->arg(Workflow::getGistUrl().'/'.$queryUser)
494 | ->prio(2)
495 | );
496 |
497 | Workflow::addItem(Item::create()
498 | ->title($queryUser.'/')
499 | ->comparator('@'.$queryUser.' ')
500 | ->autocomplete($queryUser.'/')
501 | ->subtitle("View $queryUser's' repositories (in alfred workflow)")
502 | ->icon('repo')
503 | ->prio(1)
504 | ->valid(false)
505 | );
506 | }
507 |
508 | private static function addMyCommands()
509 | {
510 | $parts = self::$parts;
511 | if (isset($parts[2]) && in_array($parts[1], array('pulls', 'issues'))) {
512 | $icon = $parts[1] === 'pulls' ? 'pull-request' : 'issue';
513 | $items = $icon.'s';
514 | $subs = array(
515 | 'created' => array($parts[1], 'View your '.$items),
516 | 'assigned' => array($parts[1].'/assigned', 'View your assigned '.$items),
517 | 'mentioned' => array($parts[1].'/mentioned', 'View '.$items.' that mentioned you'),
518 | );
519 | if ('pulls' === $parts[1]) {
520 | $subs['review requested'] = array($parts[1].'/review-requested', 'View '.$items.' that require review');
521 | }
522 | foreach ($subs as $key => $sub) {
523 | Workflow::addItem(Item::create()
524 | ->title('my '.$parts[1].' '.$key)
525 | ->subtitle($sub[1])
526 | ->icon($icon)
527 | ->arg('/'.$sub[0])
528 | ->prio(1)
529 | );
530 | }
531 | return;
532 | }
533 |
534 | $myPages = array(
535 | 'dashboard' => array('', 'View your dashboard'),
536 | 'pulls ' => array('pulls', 'View your pull requests', 'pull-request'),
537 | 'issues ' => array('issues', 'View your issues', 'issue'),
538 | 'stars' => array('stars', 'View your starred repositories'),
539 | 'profile' => array(self::$user->login, 'View your public user profile', 'user'),
540 | 'settings' => array('settings', 'View or edit your account settings'),
541 | 'notifications' => array('notifications', 'View all your notifications'),
542 | );
543 | foreach ($myPages as $key => $my) {
544 | Workflow::addItem(Item::create()
545 | ->title('my '.$key)
546 | ->subtitle($my[1])
547 | ->icon(isset($my[2]) ? $my[2] : rtrim($key))
548 | ->arg('/'.$my[0])
549 | ->prio(1)
550 | );
551 | }
552 | Workflow::addItem(Item::create()
553 | ->title('my gists')
554 | ->subtitle('View your gists')
555 | ->icon('gists')
556 | ->arg(Workflow::getGistUrl().'/'.self::$user->login)
557 | ->prio(1)
558 | );
559 |
560 | Workflow::addItem(Item::create()
561 | ->title('my repos')
562 | ->subtitle('View your repos')
563 | ->icon('repo')
564 | ->arg('/'.self::$user->login.'?tab=repositories')
565 | ->prio(1)
566 | );
567 | }
568 |
569 | private static function addSystemCommands()
570 | {
571 | $cmds = array(
572 | 'delete cache' => 'Delete GitHub Cache',
573 | 'logout' => 'Log out this workflow',
574 | 'delete database' => 'Delete database (contains login, config and cache)',
575 | 'update' => 'Update this Alfred workflow',
576 | );
577 | if (Workflow::getConfig('autoupdate', true)) {
578 | $cmds['deactivate autoupdate'] = 'Deactivate auto updating this Alfred Workflow';
579 | } else {
580 | $cmds['activate autoupdate'] = 'Activate auto updating this Alfred Workflow';
581 | }
582 | if (self::$enterprise) {
583 | $cmds['enterprise reset'] = 'Reset the GitHub Enterprise URL';
584 | }
585 | foreach ($cmds as $cmd => $desc) {
586 | Workflow::addItem(Item::create()
587 | ->title('> '.$cmd)
588 | ->subtitle($desc)
589 | ->icon($cmd)
590 | ->arg('> '.str_replace(' ', '-', $cmd))
591 | );
592 | }
593 |
594 | $cmds = array(
595 | 'help' => 'readme',
596 | 'changelog' => 'changelog',
597 | );
598 | foreach ($cmds as $cmd => $file) {
599 | Workflow::addItem(Item::create()
600 | ->title('> '.$cmd)
601 | ->subtitle('View the '.$file)
602 | ->icon('file')
603 | ->arg('https://github.com/gharlan/alfred-github-workflow/blob/master/'.strtoupper($file).'.md')
604 | );
605 | }
606 | }
607 | }
608 |
609 | Search::run($argv[1], $argv[2], getenv('hotkey'));
610 | echo Workflow::getItemsAsXml();
611 |
--------------------------------------------------------------------------------