branch: javaimp_devel commit 5e69ed13eec3fa055e86cf91f789ca167360b6ac Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
javaimp: Rewrite Maven output parsing code --- packages/javaimp/javaimp.el | 350 ++++++++++++++++++++++++------------------- 1 files changed, 193 insertions(+), 157 deletions(-) diff --git a/packages/javaimp/javaimp.el b/packages/javaimp/javaimp.el index df402ce..f05629a 100644 --- a/packages/javaimp/javaimp.el +++ b/packages/javaimp/javaimp.el @@ -1,6 +1,6 @@ ;;; javaimp.el --- Add and reorder Java import statements in Maven projects -*- lexical-binding: t; -*- -;; Copyright (C) 2014, 2015 Free Software Foundation, Inc. +;; Copyright (C) 2014, 2015, 2016 Free Software Foundation, Inc. ;; Author: Filipp Gunbin <fgun...@fastmail.fm> ;; Maintainer: Filipp Gunbin <fgun...@fastmail.fm> @@ -66,11 +66,12 @@ ;; which are not matched by any regexp in that variable are assigned a ;; default order defined by `javaimp-import-default-order' (50 by default). ;; -;; Sample setup (put this into your .emacs): +;; Sample .emacs initialization: ;; ;; (require 'javaimp) ;; -;; (add-to-list 'javaimp-import-group-alist '("\\`\\(ru\\.yota\\.\\|tv\\.okko\\.\\)" . 80)) +;; (add-to-list 'javaimp-import-group-alist +;; '("\\`\\(my\\.company\\.\\|my\\.company2\\.\\)" . 80)) ;; ;; (setq javaimp-jdk-home (getenv "JAVA_HOME")) ;; (setq javaimp-include-current-project-classes t) @@ -82,19 +83,28 @@ ;; (local-set-key "\C-co" 'javaimp-organize-imports))) ;; ;; -;; TODO: +;; TODO before version 1.0: ;; -;; Support adding static imports by giving a prefix argument to +;; - correct submodule tree for each top-level project (now top-level projects +;; hold linear submodule list and this prevents modification checking for parent +;; poms). If a project doesn't have any children, then it should be in the list +;; by itself. +;; +;; - cl-defstruct for data +;; +;; Other: +;; +;; - support adding static imports by giving a prefix argument to ;; `javaimp-add-import'. ;; -;; Use functions `cygwin-convert-file-name-from-windows' and +;; - use functions `cygwin-convert-file-name-from-windows' and ;; `cygwin-convert-file-name-to-windows' when they are available instead of -;; calling `cygpath'. See -;; https://cygwin.com/ml/cygwin/2013-03/msg00228.html. - +;; calling `cygpath'. See https://cygwin.com/ml/cygwin/2013-03/msg00228.html. ;;; Code: +(require 'seq) + ;;; User options @@ -163,7 +173,7 @@ Only top-level classes are included.") (defconst javaimp-debug-buf-name "*javaimp-debug*") -;;; Dealing with XML +;;; XML routines (defun javaimp-xml-child-list (xml-tree child-name) "Returns list of children of XML-TREE filtered by CHILD-NAME" @@ -182,8 +192,10 @@ Only top-level classes are included.") (car (cddr el))) -;; A module is represented as a list of the form `(ARTIFACT POM-FILE -;; SOURCE-DIR TEST-SOURCE-DIR BUILD-DIR POM-FILE-MOD-TS PARENT PARENT-TS)'. +;; FIXME: use cl-defstruct! + +;; A module is represented as a list: `(ARTIFACT POM-FILE SOURCE-DIR +;; TEST-SOURCE-DIR BUILD-DIR POM-FILE-MOD-TS PARENT PARENT-TS)'. (defsubst javaimp-make-mod (artifact pom-file source-dir test-source-dir build-dir @@ -197,6 +209,8 @@ Only top-level classes are included.") (defsubst javaimp-get-mod-pom-file (module) (nth 1 module)) +(defsubst javaimp-set-mod-pom-file (module value) + (setcar (nthcdr 1 module) value)) (defsubst javaimp-get-mod-source-dir (module) (nth 2 module)) @@ -230,18 +244,16 @@ Only top-level classes are included.") ;; An artifact is represented as a list: (GROUP-ID ARTIFACT-ID VERSION). -;; FIXME: use cl-defstruct! - -(defun javaimp-make-artifact (group-id artifact-id version) +(defsubst javaimp-make-artifact (group-id artifact-id version) (list group-id artifact-id version)) -(defun javaimp-artifact-group-id (artifact) +(defsubst javaimp-artifact-group-id (artifact) (car artifact)) -(defun javaimp-artifact-artifact-id (artifact) +(defsubst javaimp-artifact-artifact-id (artifact) (cadr artifact)) -(defun javaimp-artifact-version (artifact) +(defsubst javaimp-artifact-version (artifact) (nth 2 artifact)) (defun javaimp-artifact-to-string (artifact) @@ -255,7 +267,7 @@ Only top-level classes are included.") -;; A jar is represented as follows: `(JAR-PATH JAR-MOD-TS . CLASSES-LIST). +;; A jar is represented as a list: `(JAR-PATH JAR-MOD-TS . CLASSES-LIST). (defsubst javaimp-make-jar (jar-path jar-mod-ts classes-list) (cons jar-path (cons jar-mod-ts classes-list))) @@ -276,150 +288,168 @@ Only top-level classes are included.") (setcdr (cdr jar) value)) -;;; Loading maven projects tree +;;; Loading Maven projects tree ;;;###autoload (defun javaimp-maven-visit-root (path) "Loads all modules starting from root module identified by -PATH. PATH should point to a directory." +PATH. PATH should point to a directory containing pom.xml." (interactive "DVisit maven root project: ") - (let ((root-pom (expand-file-name - (concat (file-name-as-directory path) "pom.xml"))) - modules existing-module) - (unless (file-readable-p root-pom) - (error "Cannot read root pom: %s" root-pom)) - (setq modules (javaimp-maven-load-module-tree root-pom)) - ;; if a root module with such path is already loaded, replace its - ;; modules - (setq existing-module (assoc root-pom javaimp-maven-root-modules)) - (if existing-module - (setcdr existing-module modules) + (let* ((root-pom (expand-file-name + (concat (file-name-as-directory path) "pom.xml"))) + (modules (if (file-readable-p root-pom) + (javaimp-maven-load-module-tree root-pom) + (error "Cannot read root pom: %s" root-pom))) + (root-project (assoc root-pom javaimp-maven-root-modules))) + (if root-project + (setcdr root-project modules) (push (cons root-pom modules) javaimp-maven-root-modules)) (message "Loaded modules for %s" path))) -(defun javaimp-get-projects (xml-tree) - (cond ((assq 'projects xml-tree) - (javaimp-xml-child-list (assq 'projects xml-tree) 'project)) - ((assq 'project xml-tree) - (list (assq 'project xml-tree))) - (t - (error "Cannot find projects in mvn output")))) - -(defun javaimp-maven-load-module-tree (pom) - "Returns an alist of all Maven modules in a hierarchy starting -with POM" +(defun javaimp-parse-effective-pom (pom) + "Calls `mvn help:effective:pom and returns XML parse tree" (message "Loading root pom %s..." pom) (javaimp-call-mvn pom "help:effective-pom" (lambda () - (let (xml-start-pos xml-end-pos) - ;; find where we should start parsing XML - (goto-char (point-min)) - (re-search-forward "<\\?xml\\|<projects?") - (setq xml-start-pos (match-beginning 0)) - ;; determine the start tag - (goto-char (point-min)) - (re-search-forward "<\\(projects?\\)") - ;; find closing tag which is also the end of the region to parse - (search-forward (concat "</" (match-string 1) ">")) - (setq xml-end-pos (match-end 0)) - ;; parse - (let ((artifact-pomfile-alist - (javaimp-build-artifact-pomfile-alist (list pom))) - (children (javaimp-get-projects - (xml-parse-region xml-start-pos xml-end-pos)))) - (javaimp-maven-build-children children artifact-pomfile-alist)))))) - -(defun javaimp-make-artifact-from-xml (node) + (let ((xml-start-pos + (save-excursion + (progn + (goto-char (point-min)) + (re-search-forward "<\\?xml\\|<projects?") + (match-beginning 0)))) + (xml-end-pos + (save-excursion + (progn + (goto-char (point-min)) + (re-search-forward "<\\(projects?\\)") + ;; corresponding closing tag is the end of parse region + (search-forward (concat "</" (match-string 1) ">")) + (match-end 0))))) + (xml-parse-region xml-start-pos xml-end-pos))))) + +(defun javaimp-maven-load-module-tree (pom) + "Returns an alist of all Maven modules in a hierarchy starting +with POM" + (let* ((effective-pom (javaimp-parse-effective-pom pom)) + (project-elts + (cond ((assq 'projects effective-pom) ;project contains <module> tag(s) + (javaimp-xml-child-list (assq 'projects effective-pom) 'project)) + ((assq 'project effective-pom) ;single-module project + (list (assq 'project effective-pom))) + (t + (error "Cannot find projects in XML tree")))) + (modules-alist (javaimp-maven-process-projects project-elts))) + (javaimp-fill-pom-file-paths modules-alist pom) + modules-alist)) + +(defun javaimp-fill-pom-file-paths (modules pom) + "Subroutine of `javaimp-maven-load-module-tree'" + (let ((artifact-alist (javaimp-traverse-pom-tree (list pom)))) + (dolist (module modules-alist) + (let* ((artifact (javaimp-get-mod-artifact module)) + (path + (cdr (or (seq-find (lambda (el) + (equal artifact (car el))) + artifact-alist) + ;; Compare just id if comparison by all fields failed + (seq-find (lambda (el) + (equal (javaimp-artifact-artifact-id artifact) + (javaimp-artifact-artifact-id (car el)))) + artifact-alist) + (error "Cannot find path for artifact: %s" artifact))))) + (javaimp-set-mod-pom-file module path))))) + +(defun javaimp-extract-artifact-info (elt) (javaimp-make-artifact - (javaimp-xml-first-child (javaimp-xml-child 'groupId node)) - (javaimp-xml-first-child (javaimp-xml-child 'artifactId node)) - (javaimp-xml-first-child (javaimp-xml-child 'version node)))) - -(defun javaimp-get-pom-file-path-lax (artifact artifact-pomfile-alist) - (assoc-default - artifact artifact-pomfile-alist - (lambda (tested target) - (or (equal target tested) - (equal (javaimp-artifact-artifact-id target) - (javaimp-artifact-artifact-id tested)))))) - -(defun javaimp-maven-build-children (projects artifact-pomfile-alist) - (let (result) - (dolist (proj projects result) - (let* ((artifact (javaimp-make-artifact-from-xml proj)) - (pom-file-path (javaimp-get-pom-file-path-lax - artifact artifact-pomfile-alist)) - (build (javaimp-xml-child 'build proj)) - (source-dir (javaimp-xml-first-child - (javaimp-xml-child 'sourceDirectory build))) - (test-source-dir (javaimp-xml-first-child - (javaimp-xml-child 'testSourceDirectory - build))) - (build-dir (javaimp-xml-first-child - (javaimp-xml-child 'directory build))) - (parent (javaimp-make-artifact-from-xml - (javaimp-xml-child 'parent proj)))) - (push (javaimp-make-mod - artifact - pom-file-path - (file-name-as-directory - (if (eq system-type 'cygwin) - (car (process-lines javaimp-cygpath-program "-u" - source-dir)) - source-dir)) - (file-name-as-directory - (if (eq system-type 'cygwin) - (car (process-lines javaimp-cygpath-program "-u" - test-source-dir)) - test-source-dir)) - (file-name-as-directory - (if (eq system-type 'cygwin) - (car (process-lines javaimp-cygpath-program "-u" - build-dir)) - build-dir)) - nil nil parent nil) - result))))) - -(defun javaimp-build-artifact-pomfile-alist (pom-file-list) - "Recursively builds an alist where each element is of the -form (\"ARTIFACT\" . \"POM-FILE-PATH\"). This is needed because -there is no pom file path in the output of `mvn -help:effective-pom'. Each pom file path in POM-FILE-LIST should -be in platform's default format." - (when pom-file-list - (let ((pom-file (car pom-file-list)) - xml-tree project) - (message "Saving artifact id -> pom file mapping for %s" pom-file) - (with-temp-buffer - (insert-file-contents pom-file) - (setq xml-tree (xml-parse-region (point-min) (point-max)))) - (setq project (if (assq 'top xml-tree) - (assq 'project (cddr (assq 'top xml-tree))) - (assq 'project xml-tree))) - (cons - ;; this pom - (cons (javaimp-make-artifact-from-xml project) pom-file) - (append - ;; submodules - (javaimp-build-artifact-pomfile-alist - (mapcar (lambda (submodule) - (expand-file-name - (concat - ;; this pom's path - (file-name-directory pom-file) - ;; relative submodule directory - (file-name-as-directory - (let ((submodule-path (car (cddr submodule)))) - (if (eq system-type 'cygwin) - (car (process-lines javaimp-cygpath-program "-u" - submodule-path)) - submodule-path))) - ;; well-known file name - "pom.xml"))) - (javaimp-xml-child-list (assq 'modules (cddr project)) 'module))) - ;; rest items - (javaimp-build-artifact-pomfile-alist (cdr pom-file-list))))))) + (javaimp-xml-first-child (javaimp-xml-child 'groupId elt)) + (javaimp-xml-first-child (javaimp-xml-child 'artifactId elt)) + (javaimp-xml-first-child (javaimp-xml-child 'version elt)))) + +(defun javaimp-cygpath-convert-maybe (path) + (if (eq system-type 'cygwin) + (car (process-lines javaimp-cygpath-program "-u" source-dir)) + source-dir)) + +(defun javaimp-maven-process-projects (projects-elts) + (mapcar + (lambda (project-elt) + (let ((build-elt (javaimp-xml-child 'build project-elt))) + (javaimp-make-mod + (javaimp-extract-artifact-info project-elt) + nil ;pom-file will be set later + (file-name-as-directory + (javaimp-cygpath-convert-maybe + (javaimp-xml-first-child (javaimp-xml-child 'sourceDirectory build-elt)))) + (file-name-as-directory + (javaimp-cygpath-convert-maybe + (javaimp-xml-first-child (javaimp-xml-child 'testSourceDirectory build-elt)))) + (file-name-as-directory + (javaimp-cygpath-convert-maybe + (javaimp-xml-first-child (javaimp-xml-child 'directory build-elt)))) + nil ;pom-file-mod-ts will be set later + nil ;jar-list will be set later + (javaimp-extract-artifact-info + (javaimp-xml-child 'parent project-elt)) + nil ;parent-ts will be set later + ))) + project-elts)) + +(defun javaimp-extract-submodules-paths (project-elt) + (let* ((modules-elt (javaimp-xml-child 'modules project-elt)) + (module-elts (javaimp-xml-child-list modules-elt 'module))) + (mapcar #'javaimp-xml-first-child module-elts))) + +(defun javaimp-parse-pom-file (pom-file) + "Subroutine of `javaimp-traverse-pom-tree'. Parses POM-FILE. +Car of result is artifact info. Cdr of result is submodules +relative path list." + (message "Parsing pom file mapping for %s" pom-file) + (let* ((xml-tree (with-temp-buffer + (insert-file-contents pom-file) + (xml-parse-region (point-min) (point-max)))) + (project-elt (cond ((assq 'top xml-tree) + (javaimp-xml-child 'project (assq 'top xml-tree))) + ((assq 'project xml-tree) + (assq 'project xml-tree)) + (t + (error "Cannot find <project> element in pom %s!" pom-file))))) + (cons (javaimp-extract-artifact-info project-elt) + (javaimp-extract-submodules-paths project-elt)))) + +(defun javaimp-create-absolute-submodules-paths (base-pom paths) + "Subroutine of `javaimp-traverse-pom-tree'" + (mapcar + (lambda (rel-path) + (expand-file-name + (concat + ;; base path + (file-name-directory base-pom) + ;; submodule relative path + (file-name-as-directory + (javaimp-cygpath-convert-maybe rel-path)) + ;; well-known pom name + "pom.xml"))) + paths)) + +(defun javaimp-traverse-pom-tree (pom-file-list) + "Traverses pom tree and returns alist where each element is of +the form (\"ARTIFACT\" . \"POM-FILE-PATH\"). Result paths are in +platform default format." + (if pom-file-list + (append + ;; this item + (let* ((this-pom (car pom-file-list)) + (pom-file-data (javaimp-parse-pom-file this-pom))) + (append + ;; this pom itself + (list (cons (car pom-file-data) this-pom)) + ;; children of this pom + (javaimp-traverse-pom-tree + (javaimp-create-absolute-submodules-paths + this-pom (cdr pom-file-data))))) + ;; rest items + (javaimp-traverse-pom-tree (cdr pom-file-list))))) (defun javaimp-call-mvn (pom-file target handler) "Runs Maven target TARGET on POM-FILE, then calls HANDLER in @@ -439,13 +469,14 @@ the temporary buffer and returns its result" (with-current-buffer (get-buffer-create javaimp-debug-buf-name) (erase-buffer) (insert-buffer-substring output-buf)) - (unless (and (numberp status) (= status 0)) - (error "Maven target \"%s\" failed with status \"%s\"" - target status)) + (or (and (numberp status) (= status 0)) + (error "Maven target \"%s\" failed with status \"%s\"" + target status)) + (goto-char (point-min)) (funcall handler)))) -;;; Reading and caching dependencies +;;; Working with module dependency JARs (defun javaimp-maven-fetch-module-deps (module) "Returns list of dependency jars for MODULE" @@ -720,25 +751,30 @@ argument is a list of additional classes to import." (javaimp-insert-import-groups static-import-groups t)) (message "Nothing to organize"))))) -;;;###autoload (defun javaimp-invalidate-jar-classes-cache () "Resets jar classes cache (debugging only)" (interactive) (setq javaimp-jar-classes-cache nil)) -;;;###autoload (defun javaimp-forget-all-visited-modules () "Resets `javaimp-maven-root-modules' (debugging only)" (interactive) (setq javaimp-maven-root-modules nil)) -;;;###autoload (defun javaimp-reset () "Resets all data (debugging only)" (interactive) (javaimp-forget-all-visited-modules) (javaimp-invalidate-jar-classes-cache)) +;; Some functions which can be used in other modules + +(defun javaimp-get-source-directories () + (apply #'seq-concatenate 'list + (mapcar (lambda (root) + (mapcar #'javaimp-get-mod-source-dir (cdr root))) + javaimp-maven-root-modules))) + (provide 'javaimp) ;;; javaimp.el ends here