From cc1e41e54836ea7eecbb97289998d1f4ecd0c875 Mon Sep 17 00:00:00 2001 From: Damien Merenne Date: Fri, 19 Oct 2018 00:13:40 +0200 Subject: [PATCH 1/4] Refactor synchronous and asynchronous docset install functions. Use macros for docset installation helper so that they can be used in the `async-start' calls. Modify the asynchronous methods to use those helper. Modify the synchronous methods to call the asynchronous ones and wait for them to finish. --- helm-dash.el | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 55 deletions(-) diff --git a/helm-dash.el b/helm-dash.el index c67152c..ffdfc09 100644 --- a/helm-dash.el +++ b/helm-dash.el @@ -299,22 +299,41 @@ If doesn't exist, it asks to create it." (when (helm-dash--ensure-created-docsets-path (helm-dash-docsets-path)) (helm-dash--install-docset (car (assoc-default docset-name (helm-dash-unofficial-docsets))) docset-name))) +(defmacro helm-dash-extract-docset (docset-archive docsets-path) + "Extract DOCSET-ARCHIVE to DOCSETS-PATH." + `(let* ((tar-output (shell-command-to-string + (format "tar xvf %s -C %s" + (shell-quote-argument (expand-file-name ,docset-archive)) + (shell-quote-argument ,docsets-path)))) + (last-line (car (last (split-string tar-output "\n" t))))) + (replace-regexp-in-string "^x " "" (car (split-string last-line "\\." t))))) + +(defmacro helm-dash-get-docset-url (feed-path) + "Parse a xml feed with docset urls and return the first url. +The Argument FEED-PATH should be a string with the path of the xml file." + `(let* ((xml (xml-parse-file ,feed-path)) + (urls (car xml)) + (url (xml-get-children urls 'url))) + (caddr (car url)))) + +(defmacro helm-dash-docset-url (docset-name docsets-url) + "Fetch and parse docset feed returning the url for DOCSET-NAME located at DOCSETS-URL." + `(let* ((feed-url (format "%s/%s.xml" ,docsets-url ,docset-name)) + (feed-path (url-file-local-copy feed-url))) + (helm-dash-get-docset-url feed-path))) + +(defmacro helm-dash-download-install-docset (docset-name docsets-path docsets-url) + "Download and install DOCSET-NAME to DOCSETS-PATH, using feeds at DOCSETS-URL." + `(let ((docset-url (helm-dash-docset-url ,docset-name ,docsets-url))) + (helm-dash-extract-docset-into (url-file-local-copy docset-url) docsets-path docset-name))) ;;;###autoload (defun helm-dash-install-docset-from-file (docset-tmp-path) "Extract the content of DOCSET-TMP-PATH, move it to `helm-dash-docsets-path` and activate the docset." (interactive (list (car (find-file-read-args "Docset Tarball: " t)))) - (let ((docset-folder - (helm-dash-docset-folder-name - (shell-command-to-string - (format "tar xvf %s -C %s" - (shell-quote-argument (expand-file-name docset-tmp-path)) - (shell-quote-argument (helm-dash-docsets-path))))))) - (helm-dash-activate-docset docset-folder) - (message (format - "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." - docset-folder)))) + (let ((proc (helm-dash-async-install-docset-from-file docset-tmp-path))) + (when proc (async-wait proc)))) ;;;###autoload (defun helm-dash-install-docset (docset-name) @@ -322,54 +341,46 @@ If doesn't exist, it asks to create it." (interactive (list (helm-dash-read-docset "Install docset" (helm-dash-official-docsets)))) - - (when (helm-dash--ensure-created-docsets-path (helm-dash-docsets-path)) - (let ((feed-url (format "%s/%s.xml" helm-dash-docsets-url docset-name)) - (feed-tmp-path (format "%s%s-feed.xml" temporary-file-directory docset-name))) - (url-copy-file feed-url feed-tmp-path t) - (helm-dash--install-docset (helm-dash-get-docset-url feed-tmp-path) docset-name)))) + (let ((proc (helm-dash-async-install-docset docset-name))) + (when proc (async-wait proc)))) ;;;###autoload (defun helm-dash-async-install-docset (docset-name) "Asynchronously download docset with specified DOCSET-NAME and move its stuff to docsets-path." (interactive (list (helm-dash-read-docset "Install docset" (helm-dash-official-docsets)))) (when (helm-dash--ensure-created-docsets-path (helm-dash-docsets-path)) - (let ((feed-url (format "%s/%s.xml" helm-dash-docsets-url docset-name))) - - (message (concat "The docset \"" docset-name "\" will now be installed asynchronously.")) - + (message (concat "The docset \"" docset-name "\" will now be installed.")) + (let ((docsets-path helm-dash-docsets-path) + (docsets-url helm-dash-docsets-url)) (async-start ; First async call gets the docset meta data (lambda () ;; Beware! This lambda is run in it's own instance of emacs. - (url-file-local-copy feed-url)) - (lambda (filename) - (let ((docset-url (helm-dash-get-docset-url filename))) - (async-start ; Second async call gets the docset itself - (lambda () - ;; Beware! This lambda is run in it's own instance of emacs. - (url-file-local-copy docset-url)) - (lambda (docset-tmp-path) - (helm-dash-async-install-docset-from-file docset-tmp-path))))))))) + (ignore-errors + (require 'mm-decode) ;; needed by `url-file-local-copy' + (helm-dash-download-install-docset docset-name docsets-path docsets-url))) + (lambda (docset-folder) + (when (string= docset-folder "nil") + (user-error "Error installing docset \"%s\"" docset-name)) + (helm-dash-activate-docset docset-folder) + (message (format + "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." + docset-folder))))))) ;;;###autoload (defun helm-dash-async-install-docset-from-file (docset-tmp-path) "Asynchronously extract the content of DOCSET-TMP-PATH, move it to `helm-dash-docsets-path` and activate the docset." (interactive (list (car (find-file-read-args "Docset Tarball: " t)))) (let ((docset-tar-path (expand-file-name docset-tmp-path)) - (docset-out-path (helm-dash-docsets-path))) + (docsets-path helm-dash-docsets-path)) (async-start (lambda () ;; Beware! This lambda is run in it's own instance of emacs. - (shell-command-to-string - (format "tar xvf %s -C %s" - (shell-quote-argument docset-tar-path) - (shell-quote-argument docset-out-path)))) - (lambda (shell-output) - (let ((docset-folder (helm-dash-docset-folder-name shell-output))) - (helm-dash-activate-docset docset-folder) - (message (format - "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." - docset-folder))))))) + (helm-dash-extract-docset docset-tar-path docsets-path)) + (lambda (docset-folder) + (helm-dash-activate-docset docset-folder) + (message (format + "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." + docset-folder)))))) (defalias 'helm-dash-update-docset 'helm-dash-install-docset) @@ -383,21 +394,6 @@ If doesn't exist, it asks to create it." (unless (helm-dash-docset-installed-p docset) (helm-dash-install-docset docset))) -(defun helm-dash-docset-folder-name (tar-output) - "Return the name of the folder where the docset has been extracted. -The argument TAR-OUTPUT should be an string with the output of a tar command." - (let ((last-line - (car (last (split-string tar-output "\n" t))))) - (replace-regexp-in-string "^x " "" (car (split-string last-line "\\." t))))) - -(defun helm-dash-get-docset-url (feed-path) - "Parse a xml feed with docset urls and return the first url. -The Argument FEED-PATH should be a string with the path of the xml file." - (let* ((xml (xml-parse-file feed-path)) - (urls (car xml)) - (url (xml-get-children urls 'url))) - (cl-caddr (cl-first url)))) - (defvar helm-dash-sql-queries '((DASH . (lambda (pattern) (let ((like (helm-dash-sql-compose-like "t.name" pattern)) From 35e5ef7ea7320146f3d166b4a7931602b3ba3a91 Mon Sep 17 00:00:00 2001 From: Damien Merenne Date: Fri, 19 Oct 2018 01:01:37 +0200 Subject: [PATCH 2/4] Elisp `message' function already understand format strings. --- helm-dash.el | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/helm-dash.el b/helm-dash.el index ffdfc09..de1982f 100644 --- a/helm-dash.el +++ b/helm-dash.el @@ -349,7 +349,7 @@ The Argument FEED-PATH should be a string with the path of the xml file." "Asynchronously download docset with specified DOCSET-NAME and move its stuff to docsets-path." (interactive (list (helm-dash-read-docset "Install docset" (helm-dash-official-docsets)))) (when (helm-dash--ensure-created-docsets-path (helm-dash-docsets-path)) - (message (concat "The docset \"" docset-name "\" will now be installed.")) + (message "The docset \"%s\" will now be installed." docset-name) (let ((docsets-path helm-dash-docsets-path) (docsets-url helm-dash-docsets-url)) (async-start ; First async call gets the docset meta data @@ -362,9 +362,8 @@ The Argument FEED-PATH should be a string with the path of the xml file." (when (string= docset-folder "nil") (user-error "Error installing docset \"%s\"" docset-name)) (helm-dash-activate-docset docset-folder) - (message (format - "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." - docset-folder))))))) + (message "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." + docset-folder)))))) ;;;###autoload (defun helm-dash-async-install-docset-from-file (docset-tmp-path) @@ -378,9 +377,8 @@ The Argument FEED-PATH should be a string with the path of the xml file." (helm-dash-extract-docset docset-tar-path docsets-path)) (lambda (docset-folder) (helm-dash-activate-docset docset-folder) - (message (format - "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." - docset-folder)))))) + (message "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." + docset-folder))))) (defalias 'helm-dash-update-docset 'helm-dash-install-docset) From 7a9c69a5bf4191e6aa67b0f25b9a35a89f08021b Mon Sep 17 00:00:00 2001 From: Damien Merenne Date: Fri, 19 Oct 2018 01:31:31 +0200 Subject: [PATCH 3/4] Do not use the docset tarball top directory as docset name. In some newer docsets (Ruby_2 for example), the top directory of the docset archive (Ruby.docset) does not match the docset name. It breaks the docset detection has helm-dash is looking for Ruby_2.docset instead of Ruby.docest. It also prevents having docsets with different names but with the same top folders (Ruby and Ruby_2 for example) to coexists. Install docset in their matching directory by stripping the top level directory from the archive when extracting to the docset content directory. --- helm-dash.el | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/helm-dash.el b/helm-dash.el index de1982f..1fc6c44 100644 --- a/helm-dash.el +++ b/helm-dash.el @@ -299,6 +299,15 @@ If doesn't exist, it asks to create it." (when (helm-dash--ensure-created-docsets-path (helm-dash-docsets-path)) (helm-dash--install-docset (car (assoc-default docset-name (helm-dash-unofficial-docsets))) docset-name))) +(defmacro helm-dash-extract-docset-into (docset-archive docsets-path docset-name) + "Extract DOCSET-ARCHIVE into DOCSETS-PATH/DOCSET-NAME, removing top directory." + `(let ((docset-dir (concat (file-name-as-directory docsets-path) docset-name ".docset"))) + (mkdir docset-dir t) + (shell-command (format "tar xvf %s -C %s --strip-components=1" + (shell-quote-argument (expand-file-name ,docset-archive)) + (shell-quote-argument (expand-file-name docset-dir)))) + docset-name)) + (defmacro helm-dash-extract-docset (docset-archive docsets-path) "Extract DOCSET-ARCHIVE to DOCSETS-PATH." `(let* ((tar-output (shell-command-to-string @@ -361,9 +370,9 @@ The Argument FEED-PATH should be a string with the path of the xml file." (lambda (docset-folder) (when (string= docset-folder "nil") (user-error "Error installing docset \"%s\"" docset-name)) - (helm-dash-activate-docset docset-folder) + (helm-dash-activate-docset docset-name) (message "Docset installed. Add \"%s\" to helm-dash-common-docsets or helm-dash-docsets." - docset-folder)))))) + docset-name)))))) ;;;###autoload (defun helm-dash-async-install-docset-from-file (docset-tmp-path) @@ -384,7 +393,7 @@ The Argument FEED-PATH should be a string with the path of the xml file." (defun helm-dash-docset-installed-p (docset) "Return true if DOCSET is installed." - (member (replace-regexp-in-string "_" " " docset) (helm-dash-installed-docsets))) + (member docset (helm-dash-installed-docsets))) ;;;###autoload (defun helm-dash-ensure-docset-installed (docset) From 261aa103296d6dc25ddd0ae872464a68e32bf95a Mon Sep 17 00:00:00 2001 From: Damien Merenne Date: Fri, 19 Oct 2018 02:03:02 +0200 Subject: [PATCH 4/4] Use async install in `helm-dash-ensure-docset-installed`. Using async install avoids blocking the Emacs initialization in case the ensure function is called. --- helm-dash.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm-dash.el b/helm-dash.el index 1fc6c44..7f26305 100644 --- a/helm-dash.el +++ b/helm-dash.el @@ -399,7 +399,7 @@ The Argument FEED-PATH should be a string with the path of the xml file." (defun helm-dash-ensure-docset-installed (docset) "Install DOCSET if it is not currently installed." (unless (helm-dash-docset-installed-p docset) - (helm-dash-install-docset docset))) + (helm-dash-async-install-docset docset))) (defvar helm-dash-sql-queries '((DASH . (lambda (pattern)