Skip to content

Commit 52328a3

Browse files
committed
Add composer-list feature
1 parent 791a710 commit 52328a3

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

Diff for: composer-list.el

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
;;; composer-list.el --- Interface for viewing and manipulating composer packages -*- lexical-binding: t -*-
2+
3+
;; Copyright (C) 2024 Friends of Emacs-PHP development
4+
5+
;; Author: USAMI Kenta <[email protected]>
6+
;; Created: 15 June 2024
7+
;; Version: 0.2.0
8+
;; Keywords: tools php dependency manager
9+
;; Homepage: https://github.com/zonuexe/composer.el
10+
;; Package-Requires: ((emacs "25.1") (seq "1.9") (php-runtime "0.1.0"))
11+
;; License: GPL-3.0-or-later
12+
13+
;; This file is NOT part of GNU Emacs.
14+
15+
;; This program is free software; you can redistribute it and/or modify
16+
;; it under the terms of the GNU General Public License as published by
17+
;; the Free Software Foundation, either version 3 of the License, or
18+
;; (at your option) any later version.
19+
20+
;; This program is distributed in the hope that it will be useful,
21+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+
;; GNU General Public License for more details.
24+
25+
;; You should have received a copy of the GNU General Public License
26+
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
27+
28+
;;; Commentary:
29+
30+
;; Interface for viewing and manipulating composer packages.
31+
32+
;;; Code:
33+
(require 'tabulated-list)
34+
(require 'composer)
35+
(require 'php-project nil t)
36+
(require 'package)
37+
38+
(defgroup composer-list nil
39+
"Interface to PHP Composer."
40+
:group 'composer-list
41+
:tag "Composer List"
42+
:prefix "composer-list-")
43+
44+
(defcustom composer-list-name-column-width 30
45+
"Column width for the Package name in the composer list."
46+
:type 'natnum
47+
:group 'composer-list)
48+
49+
(defcustom composer-list-version-column-width 14
50+
"Column width for the Package version in the composer list."
51+
:type 'natnum
52+
:group 'composer-list)
53+
54+
(defcustom composer-list-status-column-width 12
55+
"Column width for the Package status in the composer list."
56+
:type 'natnum
57+
:group 'composer-list)
58+
59+
(define-derived-mode composer-list-mode tabulated-list-mode "Composer packages"
60+
"."
61+
:interactive nil
62+
(setq-local buffer-stale-function
63+
(lambda (&optional _noconfirm) 'fast))
64+
(setq tabulated-list-format
65+
`[("Package" ,composer-list-name-column-width t)
66+
("Version" ,composer-list-version-column-width t)
67+
("Latest" ,composer-list-version-column-width t)
68+
("Status" ,composer-list-status-column-width t)
69+
("Description" 0 package-menu--description-predicate)])
70+
(setq tabulated-list-padding 2)
71+
(add-hook 'tabulated-list-revert-hook #'composer-list--refresh-packages nil t)
72+
(tabulated-list-init-header)
73+
(composer-list--refresh-packages)
74+
(tabulated-list-print))
75+
76+
(defun composer-list--load-packages ()
77+
"List `composer' sub commands."
78+
(let ((output (composer--command-execute "show" "--latest" "--all" "--format=json")))
79+
(cdr-safe (assq 'locked (composer--parse-json-string output)))))
80+
81+
(defun composer-list--load-outdated ()
82+
"List `composer' sub commands."
83+
(let ((output (composer--command-execute "outdated" "--format=json")))
84+
(cdr-safe (assq 'installed (composer--parse-json-string output)))))
85+
86+
(defun composer-list--ensure-bool (value)
87+
"Ensure that the provided VALUE is a boolean.
88+
If VALUE is :false or nil, return nil.
89+
If VALUE is :true, return t.
90+
Otherwise, if VALUE is already a boolean, return it as is."
91+
(cond
92+
((or (eq :false value) (null value)) nil)
93+
((eq :true value) t)
94+
((booleanp value) value)))
95+
96+
(defun composer-list--ensure-string (value)
97+
"Ensure that the provided VALUE is a string.
98+
If VALUE is :null or NIL, return an empty string \"\".
99+
If VALUE is already a string, return it as is.
100+
If VALUE is a number, convert it to a string using `number-to-string'."
101+
(cond
102+
((or (eq :null value) (null value)) "")
103+
((stringp value) value)
104+
((numberp value) (number-to-string value))))
105+
106+
(defun composer-list--print-info (pkg)
107+
"Return a PKG package entry suitable for `tabulated-list-entries'."
108+
(let* ((latest-status (alist-get 'latest-status pkg))
109+
(face (pcase latest-status
110+
("update-possible" 'error)
111+
("semver-safe-update" 'warning)
112+
("up-to-date" 'success)))
113+
(name (alist-get 'name pkg))
114+
(version (alist-get 'version pkg))
115+
(direct (composer-list--ensure-bool (alist-get 'direct-dependency pkg)))
116+
(latest (alist-get 'latest pkg))
117+
(warning (composer-list--ensure-bool (alist-get 'warning pkg)))
118+
(abandoned (alist-get 'warning pkg))
119+
(status (cond (abandoned "abandoned")
120+
((not direct) "dependency")
121+
("installed")))
122+
(desc (composer-list--ensure-string (alist-get 'description pkg)))
123+
(entry `[(,name
124+
face link
125+
font-lock-face link
126+
follow-link t
127+
package-desc ,name
128+
action composer-list-describe-package)
129+
,version
130+
,(if face (propertize (or latest "") 'font-lock-face face) latest)
131+
,(if warning (propertize status 'font-lock-face 'error) status)
132+
,desc]))
133+
(list name entry)))
134+
135+
(defun composer-list--refresh-packages ()
136+
"Setup for `tabulated-list-format'."
137+
(let ((packages (composer-list--load-packages)))
138+
(tabulated-list-init-header)
139+
(setq tabulated-list-entries (seq-map #'composer-list--print-info packages))))
140+
141+
(define-derived-mode composer-list-describe-mode text-mode "Composer-pkg"
142+
"Major mode for viewing PsySH Doc."
143+
(setq show-trailing-whitespace nil)
144+
(goto-address-mode +1)
145+
(read-only-mode +1))
146+
147+
(defun composer-list-describe-package (package)
148+
"Display the full information of PACKAGE."
149+
(interactive (list (or (tabulated-list-get-id)
150+
(completing-read
151+
"Composer package: "
152+
(let ((composer-use-ansi-color nil))
153+
(split-string (composer--command-execute "show" "--name-only")))))))
154+
(let* ((buf (get-buffer-create "*Composer-pkg*"))
155+
(composer-use-ansi-color t)
156+
(command (composer--make-command-string "show" (list package "--ansi"))))
157+
(with-current-buffer buf
158+
(composer-list-describe-mode)
159+
(let ((default-directory (composer--find-composer-root default-directory))
160+
(buffer-read-only nil)
161+
(composer--quote-shell-argument t)
162+
pos)
163+
(erase-buffer)
164+
(insert command "\n\n")
165+
(setq pos (point))
166+
(shell-command command (current-buffer))
167+
(ansi-color-apply-on-region pos (point-max))))
168+
(pop-to-buffer-same-window buf)))
169+
170+
;;;###autoload
171+
(defun composer-list-packages (directory)
172+
"Display a list of packages in DIRECTORY."
173+
(interactive
174+
(list (read-directory-name "Composer Directory: " (composer--find-composer-root default-directory))))
175+
(let* ((default-directory (composer--find-composer-root directory))
176+
(buf (get-buffer-create (format "*Composer: %s*" default-directory))))
177+
(with-current-buffer buf
178+
(composer-list-mode))
179+
(pop-to-buffer-same-window buf)))
180+
181+
(provide 'composer-list)
182+
;;; composer-list.el ends here

0 commit comments

Comments
 (0)