diff --git a/alfred/Alfred.alfredpreferences/workflows/alfred-mvns b/alfred/Alfred.alfredpreferences/workflows/alfred-mvns deleted file mode 120000 index edcc215..0000000 --- a/alfred/Alfred.alfredpreferences/workflows/alfred-mvns +++ /dev/null @@ -1 +0,0 @@ -/Users/junwei.zhao/.nvm/versions/node/v10.20.1/lib/node_modules/alfred-mvns \ No newline at end of file diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/CHANGELOG.md b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/CHANGELOG.md new file mode 100644 index 0000000..49d5cd8 --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/CHANGELOG.md @@ -0,0 +1,98 @@ +Changelog +========= + +Version 1.6.2 – 2018-02-13 +-------------------------- + +### Bugfixes + +* Api pagination didn't work correctly any more (missing results from page > 2) + + +Version 1.6.1 – 2017-09-23 +-------------------------- + +### Bugfixes + +* Support for macOS 10.13 High Sierra +* Commit search results had wrong urls on GitHub Enterprise (@beparker) + + +Version 1.6 – 2017-05-07 +------------------------ + +### Features + +* new command `gh user/repo projects` (@dagio) +* new command `gh my pulls review requested` (@AeroEchelon) +* better sorting for issues (most recently updated on top) and commits (most recent on top) (@danielma) + +### Bugfixes + +* On macOS 10.12.5 Beta URLs didn't opened in browser anymore + + +Version 1.5 – 2016-12-13 +------------------------ + +### Features + +* new commands for searching repos and users globally in GitHub (`gh s repo` and `gh s @user`) +* new command `gh my repos` (@jacobkossman) +* new command `gh > delete database` +* source repos with higher priority than forks + +### Bugfixes + +* in some situations private repos were missing (@lxynox) +* after saving GitHub Enterprise url the workflow didn't reopen correctly +* updated user sub commands ("Activity" tab does not exist any more on GitHub) + + +Version 1.4.1 – 2016-22-07 +-------------------------- + +* fixed reading environment variables (important for hotkey support) + + +Version 1.4 – 2016-22-07 +------------------------ + +* Hotkey support +* use native update mechanism of Alfred (to keep your hotkeys) +* new command `gh user/repo releases` (@altern8tif) +* cache warmup after login +* lower cpu usage in multi curl +* fixed autocomplete values in GitHub Enterprise + + +Version 1.3 – 2016-17-07 +------------------------ + +**Important:** This is the last version for Alfred 2. + +* Disabled updates in Alfred 2 +* Updates in Alfred 3 are loaded from new location (GitHub releases) + + +Version 1.2 – 2016-04-17 +------------------------ + +### Features + +* New sub commands for `gh my issues/pull`: `created`, `assigned` and `mentioned` +* New help command: `gh > help` +* Longer cache lifetime + + +Version 1.1 – 2015-01-10 +------------------------ + +### Features + +* GitHub Enterprise support (use `ghe`) +* Commit search (`gh user/repo *hash`) + +### Bugfixes + +* A space after `gh` is required to avoid confusion when using commands of other workflows like `ghost` diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/LICENSE b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/LICENSE new file mode 100644 index 0000000..410a945 --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Gregor Harlan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/README.md b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/README.md new file mode 100644 index 0000000..4ff8bc8 --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/README.md @@ -0,0 +1,112 @@ +GitHub Workflow for [Alfred 3](http://www.alfredapp.com) +============================== + +[![Gitter](https://badges.gitter.im/gharlan/alfred-github-workflow.svg)](https://gitter.im/gharlan/alfred-github-workflow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +You can search through GitHub (`gh`) and your GitHub Enterprise instance (`ghe`). + +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. + +**[DOWNLOAD](https://github.com/gharlan/alfred-github-workflow/releases)** + +![Workflow Screenshot](screenshot.png) + +Setup +----- + +### For github.com + +In Alfred type (`gh > login`) to authenticate against your account. The login uses OAuth, so you do not have to enter your credentials. + +### For github enterprise + +1. In Alfred type (`ghe > url https://github.mycompany.com`) +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. +3. In Alfred type (`ghe > login `) +4. You can now `ghe your_enterprise_repo_name` + +Key Combinations +---------------- + +Key Combination | Action +---------------------- | ------ +`enter` | Open entry in default browser +`cmd` + `c` | Copy URL of the entry +`cmd` + `enter` | Paste URL to front most app +`shift` or `cmd` + `y` | Open URL in QuickLook + +Commands +-------- + +To search through your GitHub Enterprise instance replace `gh` by `ghe`. + +### Repo commands + +* `gh user/repo` +* `gh user/repo #123` +* `gh user/repo @branch` +* `gh user/repo *commit` +* `gh user/repo /path/to/file` +* `gh user/repo admin` +* `gh user/repo clone` +* `gh user/repo graphs` +* `gh user/repo issues` +* `gh user/repo milestones` +* `gh user/repo network` +* `gh user/repo new issue` +* `gh user/repo new pull` +* `gh user/repo projects` +* `gh user/repo pulls` +* `gh user/repo pulse` +* `gh user/repo releases` +* `gh user/repo wiki` +* `gh user/repo projects` + +### User commands + +* `gh @user` +* `gh @user overview` +* `gh @user repositories` +* `gh @user stars` +* `gh @user gists` + +### Search commands + +* `gh s repo` +* `gh s @user` + +### "My" commands + +* `gh my dashboard` +* `gh my notifications` +* `gh my profile` +* `gh my issues` +* `gh my issues created` +* `gh my issues assigned` +* `gh my issues mentioned` +* `gh my pulls` +* `gh my pulls created` +* `gh my pulls assigned` +* `gh my pulls mentioned` +* `gh my pulls review requested` +* `gh my repos` +* `gh my settings` +* `gh my stars` +* `gh my gists` + +### Workflow commands + +* `gh > login` +* `gh > logout` +* `gh > delete cache` +* `gh > delete database` +* `gh > update` +* `gh > activate autoupdate` +* `gh > deactivate autoupdate` +* `gh > help` +* `gh > changelog` +* `ghe > url` (GitHub Enterprise only) +* `ghe > generate token` (GitHub Enterprise only) +* `ghe > enterprise reset` (GitHub Enterprise only) + + diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/action.php b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/action.php new file mode 100644 index 0000000..9bf66ff --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/action.php @@ -0,0 +1,106 @@ +' !== $query[0] && 0 !== strpos($query, 'e >')) { + if ('.git' == substr($query, -4)) { + $query = 'github-mac://openRepo/'.substr($query, 0, -4); + } + exec('open '.$query); + return; +} + +$enterprise = 0 === strpos($query, 'e '); +if ($enterprise) { + $query = substr($query, 2); +} +$parts = explode(' ', $query); + +Workflow::init($enterprise); + +switch ($parts[1]) { + case 'enterprise-url': + Workflow::setConfig('enterprise_url', rtrim($parts[2], '/')); + exec('osascript -e "tell application \"Alfred 3\" to search \"ghe \""'); + break; + + case 'enterprise-reset': + Workflow::removeConfig('enterprise_url'); + Workflow::removeConfig('enterprise_access_token'); + Workflow::deleteCache(); + break; + + case 'login': + if (isset($parts[2]) && $parts[2]) { + Workflow::setAccessToken($parts[2]); + echo 'Successfully logged in'; + } elseif (!$enterprise) { + Workflow::startServer(); + $state = version_compare(PHP_VERSION, '5.4', '<') ? 'm' : ''; + $url = Workflow::getBaseUrl().'/login/oauth/authorize?client_id=2d4f43826cb68e11c17c&scope=repo&state='.$state; + exec('open '.escapeshellarg($url)); + } + break; + + case 'logout': + Workflow::removeAccessToken(); + Workflow::deleteCache(); + echo 'Successfully logged out'; + break; + + case 'delete-cache': + Workflow::deleteCache(); + echo 'Successfully deleted cache'; + break; + + case 'delete-database': + Workflow::deleteDatabase(); + echo 'Successfully deleted database'; + break; + + case 'refresh-cache': + $curl = new Curl(); + foreach (explode(',', $parts[2]) as $url) { + Workflow::requestCache($url, $curl, null, false, 0, false); + } + $curl->execute(); + Workflow::cleanCache(); + break; + + case 'activate-autoupdate': + Workflow::setConfig('autoupdate', 1); + echo 'Activated auto updating'; + break; + + case 'deactivate-autoupdate': + Workflow::setConfig('autoupdate', 0); + echo 'Deactivated auto updating'; + break; + + case 'update': + $release = json_decode(Workflow::request('https://api.github.com/repos/gharlan/alfred-github-workflow/releases/latest')); + if (!isset($release->assets[0]->browser_download_url)) { + echo 'Update failed'; + exit; + } + $response = Workflow::request($release->assets[0]->browser_download_url, null, null, false); + if (!$response) { + echo 'Update failed'; + exit; + } + $path = getenv('alfred_workflow_data').'/github.alfredworkflow'; + file_put_contents($path, $response); + exec('open '.escapeshellarg($path)); + break; +} diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/curl.php b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/curl.php new file mode 100644 index 0000000..8a08857 --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/curl.php @@ -0,0 +1,155 @@ +requests[$request->url] = $request; + if ($this->running) { + $this->addHandle($request); + } + } + + public function execute() + { + $this->running = true; + if (!is_resource(self::$multiHandle)) { + self::$multiHandle = curl_multi_init(); + } + + foreach ($this->requests as $request) { + $this->addHandle($request); + } + + $finish = false; + $running = true; + do { + $finish = !$running; + while (CURLM_CALL_MULTI_PERFORM == $execrun = curl_multi_exec(self::$multiHandle, $running)); + if ($execrun != CURLM_OK) { + break; + } + while ($done = curl_multi_info_read(self::$multiHandle)) { + $ch = $done['handle']; + $info = curl_getinfo($ch); + $url = self::getHeader($info['request_header'], 'X-Url'); + $request = $this->requests[$url]; + $rawResponse = curl_multi_getcontent($ch); + if (preg_match("@^HTTP/\\d\\.\\d 200 Connection established\r\n\r\n@i", $rawResponse)) { + list(, $header, $body) = explode("\r\n\r\n", $rawResponse, 3); + } else { + list($header, $body) = explode("\r\n\r\n", $rawResponse, 2); + } + $response = new CurlResponse(); + $response->request = $request; + $response->status = $info['http_code']; + $headerNames = array( + 'etag' => 'ETag', + 'contentType' => 'Content-Type', + 'link' => 'Link', + ); + foreach ($headerNames as $key => $name) { + $response->$key = self::getHeader($header, $name); + } + if (200 == $response->status) { + $response->content = $body; + } + $callback = $request->callback; + $callback($response); + curl_multi_remove_handle(self::$multiHandle, $ch); + curl_close($ch); + } + if ($running || !$finish) { + if (curl_multi_select(self::$multiHandle, 1) === -1) { + usleep(250); + } + } + } while ($running || !$finish); + + $this->running = false; + return true; + } + + private function addHandle(CurlRequest $request) + { + $defaultOptions = array( + CURLOPT_HEADER => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_USERAGENT => 'alfred-github-workflow', + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 5, + CURLINFO_HEADER_OUT => true, + ); + if ($this->debug) { + $defaultOptions[CURLOPT_PROXY] = 'localhost'; + $defaultOptions[CURLOPT_PROXYPORT] = 8888; + $defaultOptions[CURLOPT_SSL_VERIFYPEER] = 0; + } + + $ch = curl_init(); + $options = $defaultOptions; + $options[CURLOPT_URL] = $request->url; + $header = array(); + $header[] = 'X-Url: '.$request->url; + if ($request->token) { + $header[] = 'Authorization: token '.$request->token; + } + if ($request->etag) { + $header[] = 'If-None-Match: '.$request->etag; + } + $options[CURLOPT_HTTPHEADER] = $header; + curl_setopt_array($ch, $options); + curl_multi_add_handle(self::$multiHandle, $ch); + } + + public static function getHeader($header, $key) + { + if (preg_match('/^'.preg_quote($key, '/').': (\V*)/mi', $header, $match)) { + return $match[1]; + } + return null; + } +} + +class CurlRequest +{ + public $url; + public $etag; + public $token; + public $callback; + + public function __construct($url, $etag, $token, $callback) + { + $this->url = $url; + $this->etag = $etag; + $this->token = $token; + $this->callback = $callback; + } +} + +class CurlResponse +{ + /** @var CurlRequest */ + public $request; + public $status; + public $contentType; + public $etag; + public $link; + public $content; +} diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icon.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icon.png new file mode 100644 index 0000000..fc150e3 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icon.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/branch.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/branch.png new file mode 100644 index 0000000..89c4994 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/branch.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/clone.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/clone.png new file mode 100644 index 0000000..060e340 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/clone.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/commits.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/commits.png new file mode 100644 index 0000000..51edcc8 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/commits.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/dashboard.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/dashboard.png new file mode 100644 index 0000000..502b4c0 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/dashboard.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/file.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/file.png new file mode 100644 index 0000000..1494b2d Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/file.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/fork.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/fork.png new file mode 100644 index 0000000..e15fe23 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/fork.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/gists.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/gists.png new file mode 100644 index 0000000..a2d1de7 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/gists.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/graphs.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/graphs.png new file mode 100644 index 0000000..000d94f Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/graphs.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/issue.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/issue.png new file mode 100644 index 0000000..50da295 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/issue.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/logout.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/logout.png new file mode 100644 index 0000000..14dbf25 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/logout.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/milestone.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/milestone.png new file mode 100644 index 0000000..3c013e7 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/milestone.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/mirror.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/mirror.png new file mode 100644 index 0000000..a3b5779 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/mirror.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/notifications.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/notifications.png new file mode 100644 index 0000000..298cc77 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/notifications.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/organization.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/organization.png new file mode 100644 index 0000000..30bb704 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/organization.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-fork.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-fork.png new file mode 100644 index 0000000..f2f5254 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-fork.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-mirror.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-mirror.png new file mode 100644 index 0000000..218c7d5 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-mirror.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-repo.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-repo.png new file mode 100644 index 0000000..fa78739 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/private-repo.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/project.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/project.png new file mode 100644 index 0000000..aa87421 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/project.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/pull-request.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/pull-request.png new file mode 100644 index 0000000..45dfd0f Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/pull-request.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/pulse.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/pulse.png new file mode 100644 index 0000000..bc9b6f1 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/pulse.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/releases.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/releases.png new file mode 100644 index 0000000..127bda3 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/releases.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/repo.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/repo.png new file mode 100644 index 0000000..bd214f1 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/repo.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/search.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/search.png new file mode 100644 index 0000000..6f26b49 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/search.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/settings.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/settings.png new file mode 100644 index 0000000..0eac96f Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/settings.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/stars.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/stars.png new file mode 100644 index 0000000..73f65a5 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/stars.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/update.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/update.png new file mode 100644 index 0000000..8ce8e58 Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/update.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/user.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/user.png new file mode 100644 index 0000000..d82375f Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/user.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/wiki.png b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/wiki.png new file mode 100644 index 0000000..1dcfcec Binary files /dev/null and b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/icons/wiki.png differ diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/info.plist b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/info.plist new file mode 100644 index 0000000..2bfedc3 --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/info.plist @@ -0,0 +1,602 @@ + + + + + bundleid + de.gh01.alfred.github + category + Tools + connections + + 14CA19E2-6328-4201-905E-A751E2BA47B5 + + + destinationuid + ABE8DD6E-CD29-4E70-87CF-1382A0446009 + modifiers + 0 + modifiersubtext + + vitoclose + + + + 29045171-6618-4FA4-BAB0-39C10422CF31 + + + destinationuid + 67ADBB8D-C705-4981-BB9B-7C9238BEFF2E + modifiers + 0 + modifiersubtext + + vitoclose + + + + 94B48881-907F-4752-AAA0-234EA30CFC18 + + + destinationuid + FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F + modifiers + 0 + modifiersubtext + + vitoclose + + + + 95CD1A63-A0C6-4458-9817-9C6B1A90C827 + + + destinationuid + 29045171-6618-4FA4-BAB0-39C10422CF31 + modifiers + 0 + modifiersubtext + + vitoclose + + + + destinationuid + 9715D8FA-0199-450F-99B8-64796B5AC6CB + modifiers + 1048576 + modifiersubtext + Copy and paste URL + vitoclose + + + + 9715D8FA-0199-450F-99B8-64796B5AC6CB + + + destinationuid + 87F10E6D-697C-47CE-BD78-E7174F5DF815 + modifiers + 0 + modifiersubtext + + vitoclose + + + + ABE8DD6E-CD29-4E70-87CF-1382A0446009 + + + destinationuid + 95CD1A63-A0C6-4458-9817-9C6B1A90C827 + modifiers + 0 + modifiersubtext + + vitoclose + + + + DAA505B9-F86C-4AF8-818B-8F614F01485E + + + destinationuid + 94B48881-907F-4752-AAA0-234EA30CFC18 + modifiers + 0 + modifiersubtext + + vitoclose + + + + FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F + + + destinationuid + 29045171-6618-4FA4-BAB0-39C10422CF31 + modifiers + 0 + modifiersubtext + + vitoclose + + + + destinationuid + 9715D8FA-0199-450F-99B8-64796B5AC6CB + modifiers + 1048576 + modifiersubtext + Copy and paste URL + vitoclose + + + + + createdby + Gregor Harlan + description + GitHub for Alfred + disabled + + name + GitHub + objects + + + config + + action + 0 + argument + 0 + focusedappvariable + + focusedappvariablename + + leftcursor + + modsmode + 2 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + DAA505B9-F86C-4AF8-818B-8F614F01485E + version + 2 + + + config + + alfredfiltersresults + + argumenttype + 1 + escaping + 36 + keyword + gh + queuedelaycustom + 3 + queuedelayimmediatelyinitially + + queuedelaymode + 0 + queuemode + 2 + runningsubtext + Loading results… + script + php -f search.php -- github "{query}" + scriptargtype + 0 + scriptfile + + subtext + Search or type a command + title + gh... + type + 0 + withspace + + + type + alfred.workflow.input.scriptfilter + uid + FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F + version + 2 + + + config + + concurrently + + escaping + 36 + script + php -f action.php -- "{query}" + scriptargtype + 0 + scriptfile + + type + 0 + + type + alfred.workflow.action.script + uid + 29045171-6618-4FA4-BAB0-39C10422CF31 + version + 2 + + + config + + lastpathcomponent + + onlyshowifquerypopulated + + removeextension + + text + {query} + title + GitHub + + type + alfred.workflow.output.notification + uid + 67ADBB8D-C705-4981-BB9B-7C9238BEFF2E + version + 1 + + + config + + argument + {query} + variables + + hotkey + 1 + + + type + alfred.workflow.utility.argument + uid + 94B48881-907F-4752-AAA0-234EA30CFC18 + version + 1 + + + config + + action + 0 + argument + 0 + focusedappvariable + + focusedappvariablename + + leftcursor + + modsmode + 2 + relatedAppsMode + 0 + + type + alfred.workflow.trigger.hotkey + uid + 14CA19E2-6328-4201-905E-A751E2BA47B5 + version + 2 + + + config + + alfredfiltersresults + + argumenttype + 1 + escaping + 36 + keyword + ghe + queuedelaycustom + 3 + queuedelayimmediatelyinitially + + queuedelaymode + 0 + queuemode + 2 + runningsubtext + Loading results… + script + php -f search.php -- enterprise "{query}" + scriptargtype + 0 + scriptfile + + subtext + Search or type a command (GitHub Enterprise) + title + ghe... + type + 0 + withspace + + + type + alfred.workflow.input.scriptfilter + uid + 95CD1A63-A0C6-4458-9817-9C6B1A90C827 + version + 2 + + + config + + autopaste + + clipboardtext + {query} + transient + + + type + alfred.workflow.output.clipboard + uid + 87F10E6D-697C-47CE-BD78-E7174F5DF815 + version + 2 + + + config + + argument + {query} + variables + + hotkey + 1 + + + type + alfred.workflow.utility.argument + uid + ABE8DD6E-CD29-4E70-87CF-1382A0446009 + version + 1 + + + config + + inputstring + {query} + matchcasesensitive + + matchmode + 2 + matchstring + ^\w+:// + + type + alfred.workflow.utility.filter + uid + 9715D8FA-0199-450F-99B8-64796B5AC6CB + version + 1 + + + readme + Changelog +========= + +Version 1.6.2 – 2018-02-13 +-------------------------- + +### Bugfixes + +* Api pagination didnt work correctly any more (missing results from page > 2) + + +Version 1.6.1 – 2017-09-23 +-------------------------- + +### Bugfixes + +* Support for macOS 10.13 High Sierra +* Commit search results had wrong urls on GitHub Enterprise (@beparker) + + +Version 1.6 – 2017-05-07 +------------------------ + +### Features + +* new command `gh user/repo projects` (@dagio) +* new command `gh my pulls review requested` (@AeroEchelon) +* better sorting for issues (most recently updated on top) and commits (most recent on top) (@danielma) + +### Bugfixes + +* On macOS 10.12.5 Beta URLs didnt opened in browser anymore + + +Version 1.5 – 2016-12-13 +------------------------ + +### Features + +* new commands for searching repos and users globally in GitHub (`gh s repo` and `gh s @user`) +* new command `gh my repos` (@jacobkossman) +* new command `gh > delete database` +* source repos with higher priority than forks + +### Bugfixes + +* in some situations private repos were missing (@lxynox) +* after saving GitHub Enterprise url the workflow didn't reopen correctly +* updated user sub commands (Activity tab does not exist any more on GitHub) + + +Version 1.4.1 – 2016-22-07 +-------------------------- + +* fixed reading environment variables (important for hotkey support) + + +Version 1.4 – 2016-22-07 +------------------------ + +* Hotkey support +* use native update mechanism of Alfred (to keep your hotkeys) +* new command `gh user/repo releases` (@altern8tif) +* cache warmup after login +* lower cpu usage in multi curl +* fixed autocomplete values in GitHub Enterprise + + +Version 1.3 – 2016-17-07 +------------------------ + +**Important:** This is the last version for Alfred 2. + +* Disabled updates in Alfred 2 +* Updates in Alfred 3 are loaded from new location (GitHub releases) + + +Version 1.2 – 2016-04-17 +------------------------ + +### Features + +* New sub commands for `gh my issues/pull`: `created`, `assigned` and `mentioned` +* New help command: `gh > help` +* Longer cache lifetime + + +Version 1.1 – 2015-01-10 +------------------------ + +### Features + +* GitHub Enterprise support (use `ghe`) +* Commit search (`gh user/repo *hash`) + +### Bugfixes + +* A space after `gh` is required to avoid confusion when using commands of other workflows like `ghost` + + uidata + + 14CA19E2-6328-4201-905E-A751E2BA47B5 + + colorindex + 9 + note + GitHub Enterprise + xpos + 30 + ypos + 230 + + 29045171-6618-4FA4-BAB0-39C10422CF31 + + xpos + 600 + ypos + 40 + + 67ADBB8D-C705-4981-BB9B-7C9238BEFF2E + + xpos + 790 + ypos + 40 + + 87F10E6D-697C-47CE-BD78-E7174F5DF815 + + xpos + 790 + ypos + 230 + + 94B48881-907F-4752-AAA0-234EA30CFC18 + + colorindex + 12 + xpos + 220 + ypos + 70 + + 95CD1A63-A0C6-4458-9817-9C6B1A90C827 + + colorindex + 9 + note + GitHub Enterprise + xpos + 330 + ypos + 230 + + 9715D8FA-0199-450F-99B8-64796B5AC6CB + + xpos + 680 + ypos + 260 + + ABE8DD6E-CD29-4E70-87CF-1382A0446009 + + colorindex + 9 + xpos + 220 + ypos + 260 + + DAA505B9-F86C-4AF8-818B-8F614F01485E + + colorindex + 12 + note + github.com + xpos + 30 + ypos + 40 + + FC76BC27-8BCB-4BEE-AD42-EAC2D9B01F0F + + colorindex + 12 + note + github.com + xpos + 330 + ypos + 40 + + + variables + + hotkey + 0 + + version + 1.6.2 + webaddress + https://github.com/gharlan/alfred-github-workflow + + diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/item.php b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/item.php new file mode 100644 index 0000000..c16655f --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/item.php @@ -0,0 +1,177 @@ +randomUid = true; + return $this; + } + + public function prefix($prefix, $onlyTitle = true) + { + $this->prefix = $prefix; + $this->prefixOnlyTitle = $onlyTitle; + return $this; + } + + public function title($title) + { + $this->title = $title; + return $this; + } + + public function comparator($comparator) + { + $this->comparator = $comparator; + return $this; + } + + public function subtitle($subtitle) + { + $this->subtitle = $subtitle; + return $this; + } + + public function icon($icon) + { + $this->icon = $icon; + return $this; + } + + public function arg($arg) + { + $this->arg = $arg; + return $this; + } + + public function valid($valid, $add = '…') + { + $this->valid = (bool) $valid; + $this->add = $add; + return $this; + } + + public function autocomplete($autocomplete = true) + { + $this->autocomplete = $autocomplete; + return $this; + } + + public function prio($prio) + { + $this->prio = $prio; + return $this; + } + + public function match($query) + { + $comparator = strtolower($this->comparator ?: $this->title); + $query = strtolower($query); + if (!$this->prefixOnlyTitle && stripos($query, $this->prefix) === 0) { + $query = substr($query, strlen($this->prefix)); + } + $this->sameChars = 0; + $queryLength = strlen($query); + for ($i = 0, $k = 0; $i < $queryLength; ++$i, $k++) { + for (; isset($comparator[$k]) && $comparator[$k] !== $query[$i]; ++$k); + if (!isset($comparator[$k])) { + return false; + } + if ($i === $k) { + ++$this->sameChars; + } + } + $this->missingChars = strlen($comparator) - $queryLength; + return true; + } + + public function compare(self $another) + { + if ($this->sameChars != $another->sameChars) { + return $this->sameChars < $another->sameChars ? 1 : -1; + } + if ($this->prio != $another->prio) { + return $this->prio < $another->prio ? 1 : -1; + } + return $this->missingChars > $another->missingChars ? 1 : -1; + } + + /** + * @param self[] $items + * + * @return string + */ + public static function toXml(array $items, $enterprise, $hotkey, $baseUrl) + { + $xml = new SimpleXMLElement(''); + $prefix = $hotkey ? '' : ' '; + foreach ($items as $item) { + $c = $xml->addChild('item'); + $title = $item->prefix.$item->title; + $c->addAttribute('uid', $item->randomUid ? md5(time().$title) : md5($title)); + if ($item->icon && file_exists(__DIR__.'/icons/'.$item->icon.'.png')) { + $c->addChild('icon', 'icons/'.$item->icon.'.png'); + } else { + $c->addChild('icon', 'icon.png'); + } + if ($item->arg) { + $arg = $item->arg; + if ('/' === $arg[0]) { + $arg = $baseUrl.$arg; + } elseif (false === strpos($arg, '://')) { + $arg = ($enterprise ? 'e ' : '').$arg; + } + $c->addAttribute('arg', $arg); + } + if ($item->autocomplete) { + if (is_string($item->autocomplete)) { + $autocomplete = $item->autocomplete; + } elseif (null !== $item->comparator) { + $autocomplete = $item->comparator; + } else { + $autocomplete = $item->title; + } + $c->addAttribute('autocomplete', $prefix.($item->prefixOnlyTitle ? $autocomplete : $item->prefix.$autocomplete)); + } + if (!$item->valid) { + $c->addAttribute('valid', 'no'); + $title .= $item->add; + } + $c->addChild('title', htmlspecialchars($title)); + if ($item->subtitle) { + $c->addChild('subtitle', htmlspecialchars($item->subtitle)); + } + } + return $xml->asXML(); + } +} diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/search.php b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/search.php new file mode 100644 index 0000000..1c04a8c --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/search.php @@ -0,0 +1,610 @@ +') { + self::addSystemCommands(); + Workflow::sortItems(); + return; + } + + $isSearch = 's' === $parts[0] && isset($parts[1]); + $isUser = isset($query[0]) && $query[0] == '@'; + $isRepo = false; + $queryUser = null; + if ($isUser) { + $queryUser = ltrim($parts[0], '@'); + } elseif (!$isSearch && false !== $pos = strpos($parts[0], '/')) { + $queryUser = substr($parts[0], 0, $pos); + $isRepo = true; + } + + if ('my' == $parts[0] && isset($parts[1])) { + self::addMyCommands(); + } elseif ($isSearch && strlen($query) > 5 && '@' !== substr($parts[1], 0, 1)) { + self::addRepoSearchCommands(); + } elseif ($isSearch && strlen($query) > 6 && '@' === substr($parts[1], 0, 1)) { + self::addUserSearchCommands(); + } elseif ($isUser && isset($parts[1])) { + self::addUserSubCommands($queryUser); + } elseif (!$isUser && $isRepo && isset($parts[1])) { + self::addRepoSubCommands(); + } else { + self::addDefaultCommands($isSearch, $isUser, $isRepo, $queryUser); + } + + Workflow::sortItems(); + + if (!$query) { + return; + } + + if (!$isSearch && !$isUser && !isset($parts[1])) { + Workflow::addItem(Item::create() + ->title('s '.$query) + ->subtitle('Search repo (in alfred workflow)') + ->comparator($query) + ->autocomplete('s '.$query) + ->icon('repo') + ->valid(false) + , false); + } + + if (!$isSearch && !$isRepo && !isset($parts[1])) { + $title = 's @'.ltrim($query, '@'); + Workflow::addItem(Item::create() + ->title($title) + ->subtitle('Search user (in alfred workflow)') + ->comparator($query) + ->autocomplete($title) + ->icon('user') + ->valid(false) + , false); + } + + if (!$isUser && $isRepo && isset($parts[1])) { + $repoQuery = substr($query, strlen($parts[0]) + 1); + Workflow::addItem(Item::create() + ->title("Search '$parts[0]' for '$repoQuery'") + ->icon('search') + ->arg('/'.$parts[0].'/search?q='.urlencode($repoQuery)) + ->autocomplete(false) + , false); + } + + $path = $isUser ? $queryUser : 'search?q='.urlencode($query); + $name = self::$enterprise ? 'GitHub Enterprise' : 'GitHub'; + Workflow::addItem(Item::create() + ->title("Search $name for '$query'") + ->icon('search') + ->arg('/'.$path) + ->autocomplete(false) + , false); + } + + private static function addEmptyQueryCommand() + { + Workflow::addItem(Item::create() + ->title(self::$enterprise ? 'ghe' : 'gh') + ->subtitle('Search or type a command'.(self::$enterprise ? ' (GitHub Enterprise)' : '')) + ->comparator('') + ->valid(false) + , false); + } + + private static function addUpdateCommands() + { + $cmds = array( + 'update' => 'There is an update for this Alfred workflow', + 'deactivate autoupdate' => 'Deactivate auto updating this Alfred Workflow', + ); + foreach ($cmds as $cmd => $desc) { + Workflow::addItem(Item::create() + ->title('> '.$cmd) + ->subtitle($desc) + ->icon($cmd) + ->arg('> '.str_replace(' ', '-', $cmd)) + ->randomUid() + , false); + } + + Workflow::addItem(Item::create() + ->title('> changelog') + ->subtitle('View the changelog') + ->icon('file') + ->arg('https://github.com/gharlan/alfred-github-workflow/blob/master/CHANGELOG.md') + ->randomUid() + , false); + } + + private static function addEnterpriseUrlCommand() + { + $url = null; + if (count(self::$parts) > 1 && self::$parts[0] == '>' && self::$parts[1] == 'url' && isset(self::$parts[2])) { + $url = self::$parts[2]; + } + Workflow::addItem(Item::create() + ->title('> url '.$url) + ->subtitle('Set the GitHub Enterprise URL') + ->arg('> enterprise-url '.$url) + ->valid((bool) $url, '') + ->randomUid() + , false); + } + + private static function addLoginCommands() + { + Workflow::removeAccessToken(); + $token = null; + if (count(self::$parts) > 1 && self::$parts[0] == '>' && self::$parts[1] == 'login' && isset(self::$parts[2])) { + $token = self::$parts[2]; + } + if (!$token && !self::$enterprise) { + Workflow::addItem(Item::create() + ->title('> login') + ->subtitle('Generate OAuth access token') + ->arg('> login') + ->randomUid() + , false); + } + Workflow::addItem(Item::create() + ->title('> login '.$token) + ->subtitle('Save access token') + ->arg('> login '.$token) + ->valid((bool) $token, '') + ->randomUid() + , false); + if (!$token && self::$enterprise) { + Workflow::addItem(Item::create() + ->title('> generate token') + ->subtitle('Generate a new access token') + ->arg('/settings/applications') + ->randomUid() + , false); + Workflow::addItem(Item::create() + ->title('> enterprise reset') + ->subtitle('Reset the GitHub Enterprise URL') + ->arg('> enterprise-reset') + ->randomUid() + , false); + } + } + + private static function addDefaultCommands($isSearch, $isUser, $isRepo, $queryUser) + { + $users = array(); + $repos = array(); + + $curl = new Curl(); + + if (!$isSearch && !$isUser) { + $getRepos = function ($url, $prio) use ($curl, &$repos) { + Workflow::requestApi($url, $curl, function ($urlRepos) use (&$repos, $prio) { + foreach ($urlRepos as $repo) { + $repo->score = 300 + $prio + ($repo->fork ? 0 : 10); + $repos[$repo->id] = $repo; + } + }); + }; + if ($isRepo) { + if ($queryUser != self::$user->login) { + $urls = array('/users/'.$queryUser.'/repos', '/orgs/'.$queryUser.'/repos'); + } else { + $urls = array('/user/repos'); + } + } else { + Workflow::requestApi('/user/orgs', $curl, function ($orgs) use ($getRepos) { + foreach ($orgs as $org) { + $getRepos('/orgs/'.$org->login.'/repos', 0); + } + }); + $urls = array('/user/starred', '/user/subscriptions', '/user/repos'); + } + foreach ($urls as $prio => $url) { + $getRepos($url, $prio + 1); + } + } + + if (!$isSearch && !$isRepo) { + Workflow::requestApi('/user/following', $curl, function ($urlUsers) use (&$users) { + $users = $urlUsers; + }); + } + + $curl->execute(); + + self::addRepos($repos); + + foreach ($users as $user) { + Workflow::addItem(Item::create() + ->prefix('@', false) + ->title($user->login.' ') + ->subtitle($user->type) + ->arg($user->html_url) + ->icon(lcfirst($user->type)) + ->prio(200) + ); + } + + Workflow::addItem(Item::create() + ->title('s '.substr(self::$query, 2, 4)) + ->subtitle('Search repo or @user (min 4 chars)') + ->prio(110) + ->valid(false) + ); + + Workflow::addItem(Item::create() + ->title('my ') + ->subtitle('Dashboard, settings, and more') + ->prio(100) + ->valid(false) + ); + } + + private static function addRepoSearchCommands() + { + $q = substr(self::$query, 2); + $repos = Workflow::requestApi('/search/repositories?q='.urlencode($q), null, null, true); + + self::addRepos($repos, 's '); + } + + private static function addUserSearchCommands() + { + $q = substr(self::$query, 3); + $users = Workflow::requestApi('/search/users?q='.urlencode($q), null, null, true); + + self::addUsers($users, 's @'); + } + + private static function addRepos($repos, $comparatorPrefix = '') + { + foreach ($repos as $repo) { + $icon = 'repo'; + if ($repo->fork) { + $icon = 'fork'; + } elseif ($repo->mirror_url) { + $icon = 'mirror'; + } + if ($repo->private) { + $icon = 'private-'.$icon; + } + Workflow::addItem(Item::create() + ->title($repo->full_name.' ') + ->comparator($comparatorPrefix.$repo->full_name) + ->autocomplete($repo->full_name.' ') + ->subtitle($repo->description) + ->icon($icon) + ->arg('/'.$repo->full_name) + ->prio($repo->score) + ); + } + } + + private static function addUsers($users, $comparatorPrefix = '') + { + foreach ($users as $user) { + Workflow::addItem(Item::create() + ->prefix('@', false) + ->title($user->login.' ') + ->comparator($comparatorPrefix.$user->login) + ->autocomplete($user->login.' ') + ->subtitle($user->type) + ->arg($user->html_url) + ->icon(lcfirst($user->type)) + ->prio(isset($user->score) ? $user->score : 200) + ); + } + } + + private static function addRepoSubCommands() + { + $parts = self::$parts; + if (isset($parts[1][0]) && in_array($parts[1][0], array('#', '@', '*', '/'))) { + switch ($parts[1][0]) { + case '*': + $commits = Workflow::requestApi('/repos/'.$parts[0].'/commits'); + foreach ($commits as $commit) { + Workflow::addItem(Item::create() + ->title($commit->commit->message) + ->comparator($parts[0].' *'.$commit->sha) + ->subtitle($commit->commit->author->date.' ('.$commit->sha.')') + ->icon('commits') + ->arg('/'.$parts[0].'/commit/'.$commit->sha) + ->prio(strtotime($commit->commit->author->date)) + ); + } + break; + case '@': + $branches = Workflow::requestApi('/repos/'.$parts[0].'/branches'); + foreach ($branches as $branch) { + Workflow::addItem(Item::create() + ->title('@'.$branch->name) + ->comparator($parts[0].' @'.$branch->name) + ->subtitle($branch->commit->sha) + ->icon('branch') + ->arg('/'.$parts[0].'/tree/'.$branch->name) + ); + } + break; + case '/': + $repo = Workflow::requestApi('/repos/'.$parts[0]); + $files = Workflow::requestApi('/repos/'.$parts[0].'/git/trees/'.$repo->default_branch.'?recursive=1'); + foreach ($files->tree as $file) { + if ('blob' === $file->type) { + Workflow::addItem(Item::create() + ->title(basename($file->path)) + ->subtitle('/'.$file->path) + ->comparator($parts[0].' /'.$file->path) + ->icon('file') + ->arg('/'.$parts[0].'/blob/'.$repo->default_branch.'/'.$file->path) + ); + } + } + break; + case '#': + $issues = Workflow::requestApi('/repos/'.$parts[0].'/issues?sort=updated&state=all'); + foreach ($issues as $issue) { + Workflow::addItem(Item::create() + ->title('#'.$issue->number) + ->comparator($parts[0].' #'.$issue->number) + ->subtitle($issue->title) + ->icon($issue->pull_request ? 'pull-request' : 'issue') + ->arg($issue->html_url) + ->prio(strtotime($issue->updated_at)) + ); + } + break; + } + } else { + $subs = array( + 'admin' => array('Manage this repo', 'settings'), + 'graphs' => array('All the graphs'), + 'issues ' => array('List, show and create issues', 'issue'), + 'milestones' => array('View milestones', 'milestone'), + 'network' => array('See the network', 'graphs'), + 'projects' => array('View projects', 'project'), + 'pulls' => array('Show open pull requests', 'pull-request'), + 'pulse' => array('See recent activity'), + 'wiki' => array('Pull up the wiki'), + 'commits' => array('View commit history'), + 'releases' => array('See latest releases'), + ); + foreach ($subs as $key => $sub) { + Workflow::addItem(Item::create() + ->title($parts[0].' '.$key) + ->subtitle($sub[0]) + ->icon(isset($sub[1]) ? $sub[1] : $key) + ->arg('/'.$parts[0].'/'.$key) + ); + } + Workflow::addItem(Item::create() + ->title($parts[0].' new issue') + ->subtitle('Create new issue') + ->icon('issue') + ->arg('/'.$parts[0].'/issues/new?source=c') + ); + Workflow::addItem(Item::create() + ->title($parts[0].' new pull') + ->subtitle('Create new pull request') + ->icon('pull-request') + ->arg('/'.$parts[0].'/pull/new?source=c') + ); + if (empty($parts[1])) { + $subs = array( + '#' => array('Show a specific issue by number', 'issue'), + '@' => array('Show a specific branch', 'branch'), + '*' => array('Show a specific commit', 'commits'), + '/' => array('Show a blob', 'file'), + ); + foreach ($subs as $key => $sub) { + Workflow::addItem(Item::create() + ->title($parts[0].' '.$key) + ->subtitle($sub[0]) + ->icon($sub[1]) + ->arg($key.' '.$parts[0]) + ->valid(false) + ); + } + } + Workflow::addItem(Item::create() + ->title($parts[0].' clone') + ->subtitle('Clone this repo') + ->icon('clone') + ->arg('/'.$parts[0].'.git') + ); + } + } + + private static function addUserSubCommands($queryUser) + { + $subs = array( + 'overview' => array($queryUser, "View $queryUser's overview", 'user'), + 'repositories' => array($queryUser.'?tab=repositories', "View $queryUser's repositories", 'repo'), + 'stars' => array($queryUser.'?tab=stars', "View $queryUser's stars"), + ); + $prio = count($subs) + 2; + foreach ($subs as $key => $sub) { + Workflow::addItem(Item::create() + ->title('@'.$queryUser.' '.$key) + ->subtitle($sub[1]) + ->icon(isset($sub[2]) ? $sub[2] : $key) + ->arg('/'.$sub[0]) + ->prio($prio--) + ); + } + Workflow::addItem(Item::create() + ->title('@'.$queryUser.' gists') + ->subtitle("View $queryUser's' gists") + ->icon('gists') + ->arg(Workflow::getGistUrl().'/'.$queryUser) + ->prio(2) + ); + + Workflow::addItem(Item::create() + ->title($queryUser.'/') + ->comparator('@'.$queryUser.' ') + ->autocomplete($queryUser.'/') + ->subtitle("View $queryUser's' repositories (in alfred workflow)") + ->icon('repo') + ->prio(1) + ->valid(false) + ); + } + + private static function addMyCommands() + { + $parts = self::$parts; + if (isset($parts[2]) && in_array($parts[1], array('pulls', 'issues'))) { + $icon = $parts[1] === 'pulls' ? 'pull-request' : 'issue'; + $items = $icon.'s'; + $subs = array( + 'created' => array($parts[1], 'View your '.$items), + 'assigned' => array($parts[1].'/assigned', 'View your assigned '.$items), + 'mentioned' => array($parts[1].'/mentioned', 'View '.$items.' that mentioned you'), + ); + if ('pulls' === $parts[1]) { + $subs['review requested'] = array($parts[1].'/review-requested', 'View '.$items.' that require review'); + } + foreach ($subs as $key => $sub) { + Workflow::addItem(Item::create() + ->title('my '.$parts[1].' '.$key) + ->subtitle($sub[1]) + ->icon($icon) + ->arg('/'.$sub[0]) + ->prio(1) + ); + } + return; + } + + $myPages = array( + 'dashboard' => array('', 'View your dashboard'), + 'pulls ' => array('pulls', 'View your pull requests', 'pull-request'), + 'issues ' => array('issues', 'View your issues', 'issue'), + 'stars' => array('stars', 'View your starred repositories'), + 'profile' => array(self::$user->login, 'View your public user profile', 'user'), + 'settings' => array('settings', 'View or edit your account settings'), + 'notifications' => array('notifications', 'View all your notifications'), + ); + foreach ($myPages as $key => $my) { + Workflow::addItem(Item::create() + ->title('my '.$key) + ->subtitle($my[1]) + ->icon(isset($my[2]) ? $my[2] : rtrim($key)) + ->arg('/'.$my[0]) + ->prio(1) + ); + } + Workflow::addItem(Item::create() + ->title('my gists') + ->subtitle('View your gists') + ->icon('gists') + ->arg(Workflow::getGistUrl().'/'.self::$user->login) + ->prio(1) + ); + + Workflow::addItem(Item::create() + ->title('my repos') + ->subtitle('View your repos') + ->icon('repo') + ->arg('/'.self::$user->login.'?tab=repositories') + ->prio(1) + ); + } + + private static function addSystemCommands() + { + $cmds = array( + 'delete cache' => 'Delete GitHub Cache', + 'logout' => 'Log out this workflow', + 'delete database' => 'Delete database (contains login, config and cache)', + 'update' => 'Update this Alfred workflow', + ); + if (Workflow::getConfig('autoupdate', true)) { + $cmds['deactivate autoupdate'] = 'Deactivate auto updating this Alfred Workflow'; + } else { + $cmds['activate autoupdate'] = 'Activate auto updating this Alfred Workflow'; + } + if (self::$enterprise) { + $cmds['enterprise reset'] = 'Reset the GitHub Enterprise URL'; + } + foreach ($cmds as $cmd => $desc) { + Workflow::addItem(Item::create() + ->title('> '.$cmd) + ->subtitle($desc) + ->icon($cmd) + ->arg('> '.str_replace(' ', '-', $cmd)) + ); + } + + $cmds = array( + 'help' => 'readme', + 'changelog' => 'changelog', + ); + foreach ($cmds as $cmd => $file) { + Workflow::addItem(Item::create() + ->title('> '.$cmd) + ->subtitle('View the '.$file) + ->icon('file') + ->arg('https://github.com/gharlan/alfred-github-workflow/blob/master/'.strtoupper($file).'.md') + ); + } + } +} + +Search::run($argv[1], $argv[2], getenv('hotkey')); +echo Workflow::getItemsAsXml(); diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/server.php b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/server.php new file mode 100644 index 0000000..b269e6c --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/server.php @@ -0,0 +1,66 @@ + + + + + + alfred-github-workflow + + + +
+ +

+ alfred-github-workflow is ready.
+ Have fun. +

+
+ + diff --git a/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/workflow.php b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/workflow.php new file mode 100644 index 0000000..5b1ea9d --- /dev/null +++ b/alfred/Alfred.alfredpreferences/workflows/user.workflow.32EE707F-EC7F-4EBC-847E-01C2E786B242/workflow.php @@ -0,0 +1,440 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + if (!$exists) { + self::createTables(); + } + + if (self::$enterprise) { + self::$baseUrl = self::getConfig('enterprise_url'); + self::$apiUrl = self::$baseUrl ? self::$baseUrl.'/api/v3' : null; + self::$gistUrl = self::$baseUrl ? self::$baseUrl.'/gist' : null; + } + + self::$debug = getenv('alfred_debug') && defined('STDERR'); + + register_shutdown_function(array(__CLASS__, 'shutdown')); + } + + public static function shutdown() + { + if (self::$refreshUrls) { + $urls = implode(',', array_keys(self::$refreshUrls)); + exec('php action.php "> refresh-cache '.$urls.'" > /dev/null 2>&1 &'); + self::log('refreshing cache in background for %s', $urls); + } + } + + public static function setConfig($key, $value) + { + self::getStatement('REPLACE INTO config VALUES(?, ?)')->execute(array($key, $value)); + } + + public static function getConfig($key, $default = null) + { + $stmt = self::getStatement('SELECT value FROM config WHERE key = ?'); + $stmt->execute(array($key)); + $value = $stmt->fetchColumn(); + return false !== $value ? $value : $default; + } + + public static function removeConfig($key) + { + self::getStatement('DELETE FROM config WHERE key = ?')->execute(array($key)); + } + + public static function getBaseUrl() + { + return self::$baseUrl; + } + + public static function getApiUrl($path = null) + { + $url = self::$apiUrl; + + if ($path) { + $paramStart = false === strpos($path, '?') ? '?' : '&'; + $url .= $path.$paramStart.'per_page=100'; + } + + return $url; + } + + public static function getGistUrl() + { + return self::$gistUrl; + } + + public static function setAccessToken($token) + { + self::setConfig(self::$enterprise ? 'enterprise_access_token' : 'access_token', $token); + } + + public static function getAccessToken() + { + return self::getConfig(self::$enterprise ? 'enterprise_access_token' : 'access_token'); + } + + public static function removeAccessToken() + { + self::removeConfig(self::$enterprise ? 'enterprise_access_token' : 'access_token'); + } + + public static function request($url, Curl $curl = null, $callback = null, $withAuthorization = true) + { + self::log('loading content for %s', $url); + + $return = false; + $returnValue = null; + if (!$curl) { + $curl = new Curl(); + $return = true; + $callback = function ($content) use (&$returnValue) { + $returnValue = $content; + }; + } + + $token = $withAuthorization ? self::getAccessToken() : null; + $curl->add(new CurlRequest($url, null, $token, function (CurlResponse $response) use ($callback) { + if (is_callable($callback) && isset($response->content)) { + $callback($response->content); + } + })); + + if ($return) { + $curl->execute(); + } + return $returnValue; + } + + /** + * @param string $url + * @param Curl $curl + * @param callable $callback + * @param bool $firstPageOnly + * @param int $maxAge + * @param bool $refreshInBackground + * + * @return mixed + */ + public static function requestCache($url, Curl $curl = null, $callback = null, $firstPageOnly = false, $maxAge = self::DEFAULT_CACHE_MAX_AGE, $refreshInBackground = true) + { + $return = false; + $returnValue = null; + if (!$curl) { + $curl = new Curl(); + $return = true; + $callback = function ($content) use (&$returnValue) { + $returnValue = $content; + }; + } + + $stmt = self::getStatement('SELECT * FROM request_cache WHERE url = ?'); + $stmt->execute(array($url)); + $stmt->bindColumn('timestamp', $timestamp); + $stmt->bindColumn('etag', $etag); + $stmt->bindColumn('content', $content); + $stmt->bindColumn('refresh', $refresh); + $stmt->fetch(PDO::FETCH_BOUND); + + $shouldRefresh = $timestamp < time() - 60 * $maxAge; + $refreshInBackground = $refreshInBackground && !is_null($content); + + if ($shouldRefresh && $refreshInBackground && $refresh < time() - 3 * 60) { + self::getStatement('UPDATE request_cache SET refresh = ? WHERE url = ?')->execute(array(time(), $url)); + self::$refreshUrls[$url] = true; + } + + if (!$shouldRefresh || $refreshInBackground) { + self::log('using cached content for %s', $url); + $content = json_decode($content); + + if (!$firstPageOnly) { + $stmt = self::getStatement('SELECT url, content FROM request_cache WHERE parent = ? ORDER BY `timestamp` DESC'); + while ($stmt->execute(array($url)) && $data = $stmt->fetchObject()) { + $content = array_merge($content, json_decode($data->content)); + $url = $data->url; + } + } + + if (is_callable($callback)) { + $callback($content); + } + + return $returnValue; + } + + $responses = array(); + + $handleResponse = function (CurlResponse $response, $content, $parent = null) use (&$handleResponse, $curl, &$responses, $stmt, $callback, $firstPageOnly) { + $url = $response->request->url; + if ($response && in_array($response->status, array(200, 304))) { + $checkNext = false; + if (304 == $response->status) { + $response->content = $content; + $checkNext = true; + } elseif (false === stripos($response->contentType, 'json')) { + $response->content = json_encode($response->content); + } + $response->content = json_decode($response->content); + if (isset($response->content->items)) { + $response->content = $response->content->items; + } + $responses[] = $response->content; + Workflow::getStatement('REPLACE INTO request_cache VALUES(?, ?, ?, ?, 0, ?)') + ->execute(array($url, time(), $response->etag, json_encode($response->content), $parent)); + + if ($firstPageOnly) { + // do nothing + } elseif ($checkNext || $response->link && preg_match('/<([^<>]+)>; rel="next"/U', $response->link, $match)) { + $stmt = Workflow::getStatement('SELECT * FROM request_cache WHERE parent = ?'); + $stmt->execute(array($url)); + if ($checkNext) { + $stmt->bindColumn('url', $nextUrl); + } else { + $nextUrl = $match[1]; + } + $stmt->bindColumn('etag', $etag); + $stmt->bindColumn('content', $content); + $stmt->fetch(PDO::FETCH_BOUND); + if ($nextUrl) { + $curl->add(new CurlRequest($nextUrl, $etag, Workflow::getAccessToken(), function (CurlResponse $response) use ($handleResponse, $url, $content) { + $handleResponse($response, $content, $url); + })); + return; + } + } else { + Workflow::getStatement('DELETE FROM request_cache WHERE parent = ?')->execute(array($url)); + } + } else { + Workflow::getStatement('DELETE FROM request_cache WHERE url = ?')->execute(array($url)); + $url = null; + } + + if (is_callable($callback)) { + if (empty($responses)) { + $callback(array()); + return; + } + if (1 === count($responses)) { + $callback($responses[0]); + return; + } + $callback(array_reduce($responses, function ($content, $response) { + return array_merge($content, $response); + }, array())); + } + }; + + self::log('loading content for %s', $url); + $curl->add(new CurlRequest($url, $etag, self::getAccessToken(), function (CurlResponse $response) use (&$responses, $handleResponse, $callback, $content) { + $handleResponse($response, $content); + })); + + if ($return) { + $curl->execute(); + } + return $returnValue; + } + + public static function requestApi($url, Curl $curl = null, $callback = null, $firstPageOnly = false, $maxAge = self::DEFAULT_CACHE_MAX_AGE) + { + $url = self::getApiUrl($url); + return self::requestCache($url, $curl, $callback, $firstPageOnly, $maxAge); + } + + public static function cleanCache() + { + self::$db->exec('DELETE FROM request_cache WHERE timestamp < '.(time() - 100 * 24 * 60 * 60)); + } + + public static function deleteCache() + { + self::$db->exec('DELETE FROM request_cache'); + } + + public static function cacheWarmup() + { + $paths = array('/user', '/user/orgs', '/user/starred', '/user/subscriptions', '/user/repos', '/user/following'); + foreach ($paths as $path) { + self::$refreshUrls[self::getApiUrl($path)] = true; + } + } + + public static function startServer() + { + if (version_compare(PHP_VERSION, '5.4', '>=')) { + self::stopServer(); + shell_exec(sprintf( + 'alfred_workflow_data=%s php -d variables_order=EGPCS -S localhost:2233 server.php > /dev/null 2>&1 & echo $! >> %s', + escapeshellarg(getenv('alfred_workflow_data')), + escapeshellarg(self::$filePids) + )); + } + } + + public static function stopServer() + { + if (file_exists(self::$filePids)) { + $pids = file(self::$filePids); + foreach ($pids as $pid) { + shell_exec('kill -9 '.$pid); + } + unlink(self::$filePids); + } + } + + public static function checkUpdate() + { + if (self::getConfig('version') !== self::VERSION) { + self::setConfig('version', self::VERSION); + } + if (!self::getConfig('autoupdate', 1)) { + return false; + } + $release = self::requestCache('https://api.github.com/repos/gharlan/alfred-github-workflow/releases/latest', null, null, true, 1440); + if (!$release) { + return false; + } + $version = ltrim($release->tag_name, 'v'); + return version_compare($version, self::VERSION) > 0; + } + + private static function createTables() + { + self::$db->exec(' + CREATE TABLE config ( + key TEXT PRIMARY KEY NOT NULL, + value TEXT + ) WITHOUT ROWID + '); + + self::$db->exec(' + CREATE TABLE request_cache ( + url TEXT PRIMARY KEY NOT NULL, + timestamp INTEGER NOT NULL, + etag TEXT, + content TEXT, + refresh INTEGER, + parent TEXT + ) WITHOUT ROWID + '); + self::$db->exec('CREATE INDEX parent_url ON request_cache(parent) WHERE parent IS NOT NULL'); + } + + public static function deleteDatabase() + { + self::closeCursors(); + self::$db = null; + unlink(self::$fileDb); + } + + public static function addItem(Item $item, $check = true) + { + if (!$check || $item->match(self::$query)) { + self::$items[] = $item; + } + } + + public static function sortItems() + { + usort(self::$items, function (Item $a, Item $b) { + return $a->compare($b); + }); + } + + public static function getItemsAsXml() + { + return Item::toXml(self::$items, self::$enterprise, self::$hotkey, self::getBaseUrl()); + } + + public static function log($msg) + { + if (self::$debug) { + fwrite(STDERR, "\n".call_user_func_array('sprintf', func_get_args())); + } + } + + /** + * @param string $query + * + * @return PDOStatement + */ + public static function getStatement($query) + { + if (!isset(self::$statements[$query])) { + self::$statements[$query] = self::$db->prepare($query); + } + return self::$statements[$query]; + } + + protected static function closeCursors() + { + foreach (self::$statements as $statement) { + $statement->closeCursor(); + } + self::$statements = array(); + } +}