From 736cda067ea380dd8347336dfbc71c2613b1efe8 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Fri, 14 Oct 2016 12:20:48 +0200 Subject: [PATCH] Install helm-github-stars --- .../helm-github-stars-autoloads.el | 22 + .../helm-github-stars-pkg.el | 2 + .../helm-github-stars.el | 424 ++++++++++++++++++ init.el | 1 + 4 files changed, 449 insertions(+) create mode 100644 elpa/helm-github-stars-20150625.1523/helm-github-stars-autoloads.el create mode 100644 elpa/helm-github-stars-20150625.1523/helm-github-stars-pkg.el create mode 100644 elpa/helm-github-stars-20150625.1523/helm-github-stars.el diff --git a/elpa/helm-github-stars-20150625.1523/helm-github-stars-autoloads.el b/elpa/helm-github-stars-20150625.1523/helm-github-stars-autoloads.el new file mode 100644 index 0000000..23cb942 --- /dev/null +++ b/elpa/helm-github-stars-20150625.1523/helm-github-stars-autoloads.el @@ -0,0 +1,22 @@ +;;; helm-github-stars-autoloads.el --- automatically extracted autoloads +;; +;;; Code: +(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path)))) + +;;;### (autoloads nil "helm-github-stars" "helm-github-stars.el" +;;;;;; (22528 45090 510206 835000)) +;;; Generated autoloads from helm-github-stars.el + +(autoload 'helm-github-stars "helm-github-stars" "\ +Show and Browse your github's stars. + +\(fn)" t nil) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; End: +;;; helm-github-stars-autoloads.el ends here diff --git a/elpa/helm-github-stars-20150625.1523/helm-github-stars-pkg.el b/elpa/helm-github-stars-20150625.1523/helm-github-stars-pkg.el new file mode 100644 index 0000000..3e0a8a0 --- /dev/null +++ b/elpa/helm-github-stars-20150625.1523/helm-github-stars-pkg.el @@ -0,0 +1,2 @@ +;;; -*- no-byte-compile: t -*- +(define-package "helm-github-stars" "20150625.1523" "Helm integration for your starred repositories on github" '((helm "1.6.8") (emacs "24.4")) :url "https://github.com/Sliim/helm-github-stars" :keywords '("helm" "github" "stars")) diff --git a/elpa/helm-github-stars-20150625.1523/helm-github-stars.el b/elpa/helm-github-stars-20150625.1523/helm-github-stars.el new file mode 100644 index 0000000..09a50e8 --- /dev/null +++ b/elpa/helm-github-stars-20150625.1523/helm-github-stars.el @@ -0,0 +1,424 @@ +;;; helm-github-stars.el --- Helm integration for your starred repositories on github -*- lexical-binding: t; -*- +;; +;; Author: Sliim +;; xuchunyang +;; URL: https://github.com/Sliim/helm-github-stars +;; Package-Version: 20150625.1523 +;; Version: 1.3.2 +;; Package-Requires: ((helm "1.6.8") (emacs "24.4")) +;; Keywords: helm github stars + +;; This file is not part of GNU Emacs. + +;;; License: + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License +;; as published by the Free Software Foundation; either version 3 +;; of the License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; helm-github-stars provides capabilities to fetch your starred +;; repositories from github and select one for browsing. +;; +;; Install: +;; +;; M-x package-install helm-github-stars +;; +;; Usage: +;; +;; Copy helm-github-stars.el in your load-path and put this in your ~/.emacs.d/init.el: +;; +;; (require 'helm-github-stars) +;; ;; Setup your github username: +;; (setq helm-github-stars-username "USERNAME") +;; +;; Type M-x helm-github-stars to show starred repositories. +;; +;; At the first execution of helm-github-stars, list of repositories is +;; fetched from github and saved into a cache file. +;; Default cache location: $HOME/.emacs.d/hgs-cache. +;; To refresh cache and open helm interface run helm-github-stars-fetch. +;; +;; You can customize cache file path: +;; +;; (setq helm-github-stars-cache-file "/cache/path") +;; +;; For a clean look, repositories's description is aligned by default, you can +;; customize this behavior via helm-github-stars-name-length, it's default +;; value is 30. +;; You can disable this by setting helm-github-stars-name-length to nil: +;; +;; (setq helm-github-stars-name-length nil) +;; +;; If you want to be able to show your private repositories, customize +;; helm-github-stars-token: +;; +;; (setq helm-github-stars-token "TOKEN") + +;;; Code: + +;; requires +(require 'helm) +(require 'helm-utils) +(require 'json) +(require 'subr-x) + +(defgroup helm-github-stars nil + "Helm integration for your starred repositories on github." + :group 'helm + :prefix "helm-github-stars-") + +(defcustom helm-github-stars-username "Sliim" + "Github's username to fetch starred repositories." + :type 'string) + +(defcustom helm-github-stars-token nil + "Access token to use for your repositories and your starred repositories. + +If you don't have or don't want to show your private repositories, you don't +need access token at all. + +To generate an access token: + 1. Visit the page https://github.com/settings/tokens/new and + login to github (if asked). + 2. Give the token any name you want (helm-github-stars, for instance). + 3. The permission we need is \"repo\" and \"delete_repo\", so unmark + all others. + 4. Click on \"Generate Token\", copy the generated token, and + save it to this variable by writing + (setq helm-github-stars-token TOKEN) + somewhere in your configuration and evaluating it (or just + restart emacs). + +DISCLAIMER +When you save this variable, DON'T WRITE IT ANYWHERE PUBLIC. This +token grants (very) limited access to your account. +END DISCLAIMER + +when disabled (nil) don't use Github token." + :type '(choice (string :tag "Token") + (const :tag "Disable" nil))) + +(defcustom helm-github-stars-cache-file (concat user-emacs-directory "hgs-cache") + "Cache file for starred repositories." + :type 'file) + +(defcustom helm-github-stars-name-length 30 + "Length of repo name before truncate. +When disabled (nil) don't align description." + :type '(choice (const :tag "Disabled" nil) + (integer :tag "Length before truncate"))) + +(defcustom helm-github-stars-refetch-time nil + "Days to refetch cache file. +When disabled (nil) don't refetch automatically. " + :type '(choice (const :tag "Disabled" nil) + (number :tag "Days to refetch cache file"))) + +(defcustom helm-github-stars-full-frame helm-full-frame + "Use full-screen window to show candidates in the `helm-github-stars' command. +Its default value is `helm-full-frame' (whose default value is nil)." + :type 'boolean) + +(defcustom helm-github-stars-default-sources '(hgs/helm-c-source-stars + hgs/helm-c-source-repos + hgs/helm-c-source-search) + "Default sources list used in the `helm-github-stars' command." + :type '(repeat (choice symbol))) + +(defvar hgs/github-url "https://github.com/" + "Github URL for browsing.") + +(defvar helm-github-stars-clone-done-hook nil + "Normal hook run by `hgs/clone', after clone is done. +Function in this hook takes one argument, path to just cloned repo. + +For example, to open just cloned repo in dired automatically: + (add-hook 'helm-github-stars-clone-done-hook #'dired)") + +(defvar hgs/helm-stars-common-actions + '(("Browse URL" . (lambda (_candidate) + (dolist (candidate (helm-marked-candidates)) + (browse-url + (concat hgs/github-url candidate))))) + ("Show URL" . (lambda (candidate) + (message + (concat hgs/github-url candidate)))) + ("Clone" . hgs/clone))) + +(defvar hgs/helm-stars-actions + (append hgs/helm-stars-common-actions + '(("Unstar" . (lambda (candidate) + "Unstar a starred repository." + (hgs/unstar-or-delete-repo + "https://api.github.com/user/starred/" + candidate)))))) + +(defvar hgs/helm-repos-actions + (append hgs/helm-stars-common-actions + '(("Delete" . (lambda (candidate) + "Delete a user repository." + (hgs/unstar-or-delete-repo + "https://api.github.com/repos/" + candidate)))))) + +(defvar hgs/helm-c-source-stars + (helm-build-in-buffer-source "Starred repositories" + :init (lambda () + (with-current-buffer (helm-candidate-buffer 'local) + (insert (mapconcat 'identity (hgs/get-github-stars) "\n")))) + :real-to-display (lambda (candidate) (hgs/align-description candidate)) + :coerce (lambda (candidate) (hgs/get-repo-name candidate)) + :action hgs/helm-stars-actions) + "Helm source definition.") + +(defvar hgs/helm-c-source-repos + (helm-build-in-buffer-source "Your repositories" + :init (lambda () + (with-current-buffer (helm-candidate-buffer 'local) + (insert (mapconcat 'identity (hgs/get-github-repos) "\n")))) + :real-to-display (lambda (candidate) (hgs/align-description candidate)) + :coerce (lambda (candidate) (hgs/get-repo-name candidate)) + :action hgs/helm-repos-actions) + "Helm source definition.") + +(defvar hgs/helm-c-source-search + (helm-build-dummy-source "Search on github" + :action (lambda (candidate) + (browse-url (concat "https://github.com/search?q=" candidate))))) + +(defun hgs/align-description (item) + "Truncate repo name in ITEM." + (let* ((index (string-match " - " item)) + (name (substring item 0 index)) + (description (substring item (+ 3 index)))) + (concat + ;; Name + (if (> (string-width name) helm-github-stars-name-length) + (helm-substring-by-width name helm-github-stars-name-length) + (concat name (make-string + (- (+ helm-github-stars-name-length 3) + (string-width name)) ? ))) + ;; Separator + " " + ;; Description + description))) + +(defun hgs/read-cache-file () + "Read cache file and return list of starred repositories." + (with-temp-buffer + (insert-file-contents helm-github-stars-cache-file) + (read (current-buffer)))) + +(defun hgs/write-cache-file (hash) + "Write HASH of repositories in cache file." + (with-temp-buffer + (let ((file helm-github-stars-cache-file) + (coding-system-for-write 'utf-8)) + (print hash (current-buffer)) + (when (file-writable-p file) + (write-region (point-min) (point-max) file))))) + +(defun hgs/cache-file-exists () + "Check that cache file exists." + (file-exists-p helm-github-stars-cache-file)) + +(defun hgs/clear-cache-file () + "Delete file cache if exists." + (when (hgs/cache-file-exists) + (delete-file helm-github-stars-cache-file))) + +(defun hgs/clear-cache-file-by-time () + "Delete cache file if it is too old." + (when (and (hgs/cache-file-exists) helm-github-stars-refetch-time) + (let ((time-since-last-fetch + (time-subtract (current-time) + (nth 5 (file-attributes helm-github-stars-cache-file))))) + (when (> (time-to-number-of-days time-since-last-fetch) + helm-github-stars-refetch-time) + (hgs/clear-cache-file))))) + +(defun hgs/generate-cache-file () + "Generate or regenerate cache file if already exists." + (let ((stars-list []) + (repos-list []) + (cache-hash-table (make-hash-table :test 'equal))) + ;; Fetch user's starred repositories + (let ((next-request t) + (current-page 1)) + (while next-request + (let ((response (hgs/parse-github-response + (funcall (if helm-github-stars-token + 'hgs/request-github-stars-by-token + 'hgs/request-github-stars) + current-page)))) + (if (= 0 (length response)) + (setq next-request nil) + (progn + (setq stars-list (vconcat stars-list response)) + (setq current-page (1+ current-page))))))) + ;; Fetch user's repositories + (let ((next-request t) + (current-page 1)) + (while next-request + (let ((response (hgs/parse-github-response + (funcall (if helm-github-stars-token + 'hgs/request-github-repos-by-token + 'hgs/request-github-repos) + current-page)))) + (if (= 0 (length response)) + (setq next-request nil) + (progn + (setq repos-list (vconcat repos-list response)) + (setq current-page (1+ current-page))))))) + + (puthash '"stars" stars-list cache-hash-table) + (puthash '"repos" repos-list cache-hash-table) + (hgs/write-cache-file cache-hash-table))) + +(defun hgs/request-github-stars (page) + "Request Github API user's stars with PAGE parameter and return response." + (hgs/request-github (concat "https://api.github.com/users/" + helm-github-stars-username + "/starred?per_page=100&page=" + (number-to-string page)))) + +(defun hgs/request-github-repos (page) + "Request Github API user's repositories with PAGE parameter and return response." + (hgs/request-github (concat "https://api.github.com/users/" + helm-github-stars-username + "/repos?per_page=100&page=" + (number-to-string page)))) + +(defun hgs/request-github-repos-by-token (page) + "Request Github API user's repositories and return response." + (let ((url-request-extra-headers `(("Authorization" . + ,(format "token %s" helm-github-stars-token))))) + (hgs/request-github (concat + "https://api.github.com/user/repos?per_page=100&page=" + (number-to-string page))))) + +(defun hgs/request-github-stars-by-token (page) + "Request Github API user's repositories and return response." + (let ((url-request-extra-headers `(("Authorization" . + ,(format "token %s" helm-github-stars-token))))) + (hgs/request-github (concat + "https://api.github.com/user/starred?per_page=100&page=" + (number-to-string page))))) + +(defun hgs/request-github (url) + "Request Github URL and return response." + (with-current-buffer + (url-retrieve-synchronously url) + (let ((start (save-excursion + (goto-char (point-min)) + (and (re-search-forward "\\[" (point-max) t) + (match-beginning 0))))) + (and start + (decode-coding-string + (buffer-substring-no-properties start (point-max)) + 'utf-8))))) + +(defun hgs/parse-github-response (response) + "Parse Github API RESPONSE to get repositories full name." + (let ((github-repos (json-read-from-string response)) + (repos []) + (i 0)) + (while (< i (length github-repos)) + (setq repos (vconcat repos (vector (concat + (cdr (assoc 'full_name (elt github-repos i))) + " - " + (cdr (assoc 'description (elt github-repos i))))))) + (setq i (1+ i))) + repos)) + +(defun hgs/get-github-stars () + "Get user's starred repositories." + (when (not (hgs/cache-file-exists)) + (hgs/generate-cache-file)) + (gethash "stars" (hgs/read-cache-file))) + +(defun hgs/get-github-repos () + "Get user's repositories." + (when (not (hgs/cache-file-exists)) + (hgs/generate-cache-file)) + (gethash "repos" (hgs/read-cache-file))) + +(defun hgs/unstar-or-delete-repo (api repo-name) + "Unstar a starred repository or delete a user repository." + (unless helm-github-stars-token + (error "`helm-github-stars-token' is nil.")) + + (let ((url-request-method "DELETE") + (url-request-extra-headers + `(("Authorization" . ,(format "token %s" helm-github-stars-token))))) + (with-current-buffer (url-retrieve-synchronously (concat api repo-name)) + (goto-char (point-min)) + (when (not (string-match "204 No Content" (buffer-string))) + (error "Problem unstar or delete repo.")) + (kill-buffer))) + + ;; FIXME: there is no way to modify the cache file + ;; (puthash "stars" (delete candidate (gethash "stars" (hgs/read-cache-file))) + ;; (hgs/read-cache-file)) + ;; (hgs/write-cache-file cache-hash-table) + (helm-github-stars-fetch)) + +(defun hgs/get-repo-name (candidate) + (substring candidate 0 (string-match " - " candidate))) + +(defun hgs/clone (candidate) + "Git clone in the background.. +When git cloen is not yet done, use `list-processes' to dispaly related process." + (unless (executable-find "git") (error "git not found")) + (let* ((repo-name candidate) + (repository (format "https://github.com/%s.git" repo-name)) + (default-clone-directory + (substring repo-name (1+ (string-match "/" repo-name)))) + (directory + (read-directory-name + (format "Clone %s to: " repo-name) nil nil nil default-clone-directory)) + (command (format "git clone %s %s" repository directory)) + (output-buffer "*git-clone-output*") + (proc (start-process-shell-command "git-clone" output-buffer command))) + (set-process-sentinel + proc + (lambda (_process event) + (unless (string-equal event "finished\n") + (error "Git clone failed, see %s buffer for details" output-buffer)) + (kill-buffer output-buffer) + (message "Git clone done.") + (run-hook-with-args 'helm-github-stars-clone-done-hook directory))))) + +(defun helm-github-stars-fetch () + "Remove cache file before calling helm-github-stars." + (interactive) + (hgs/clear-cache-file) + (helm-github-stars)) + +;;;###autoload +(defun helm-github-stars () + "Show and Browse your github's stars." + (interactive) + (hgs/clear-cache-file-by-time) + (helm :sources helm-github-stars-default-sources + :candidate-number-limit 9999 + :buffer "*helm github stars*" + :prompt "> " + :full-frame helm-github-stars-full-frame)) + +(provide 'helm-github-stars) + +;;; helm-github-stars.el ends here diff --git a/init.el b/init.el index 883e518..9829f23 100644 --- a/init.el +++ b/init.el @@ -34,6 +34,7 @@ '(eww-search-prefix "https://www.google.com/?q=") '(foreground-color "#5c5cff") '(global-hl-line-mode t) + '(helm-github-stars-username "gergelypolonkai") '(indent-tabs-mode nil) '(indicate-empty-lines t) '(inhibit-startup-screen t)