~jakob/dired-rmjunk

ref: 6a9fa6a35498e53e8c57282e3b08dedc896d880d dired-rmjunk/dired-rmjunk.el -rw-r--r-- 7.4 KiB
6a9fa6a3 — Jakob L. Kreuze Bump version 3 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
;;; dired-rmjunk.el --- A home directory cleanup utility for Dired. -*- lexical-binding: t; -*-

;; Copyright (C) 2019 Jakob L. Kreuze

;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.lonestar.org>
;; Version: 1.2
;; Package-Requires (cl-lib dired)
;; Keywords: files matching
;; URL: https://git.sr.ht/~jakob/dired-rmjunk

;; 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 this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; dired-rmjunk is a port of Jakub Klinkovský's home directory cleanup tool to
;; Dired. The interactive function, `dired-rmjunk' will mark all files in the
;; current Dired buffer that match one of the patterns specified in
;; `dired-rmjunk-patterns'. The tool is intended as a simple means for
;; keeping one's home directory tidy -- removing "junk" dotfiles.

;; The script that this is based on can be found at:
;; <https://github.com/lahwaacz/Scripts/blob/master/rmshit.py>

;;; Code:

(defgroup dired-rmjunk ()
  "Remove junk files with dired."
  :group 'dired)

(defcustom dired-rmjunk-patterns
  '(".adobe" ".macromedia" ".recently-used"
    ".local/share/recently-used.xbel" "Desktop" ".thumbnails" ".gconfd"
    ".gconf" ".local/share/gegl-0.2" ".FRD/log/app.log" ".FRD/links.txt"
    ".objectdb" ".gstreamer-0.10" ".pulse" ".esd_auth" ".config/enchant"
    ".spicec" ".dropbox-dist" ".parallel" ".dbus" "ca2" "ca2~"
    ".distlib" ".bazaar" ".bzr.log" ".nv" ".viminfo" ".npm" ".java"
    ".oracle_jre_usage" ".jssc" ".tox" ".pylint.d" ".qute_test"
    ".QtWebEngineProcess" ".qutebrowser" ".asy" ".cmake" ".gnome"
    "unison.log" ".texlive" ".w3m" ".subversion" "nvvp_workspace")
  "Default list of files to remove. Current as of f707d92."
  :type '(list string))

(defvar dired-rmjunk--responsible-for-last-mark nil
  "Whether or not `dired-rmjunk' was responsible for any of the
current dired marks.")

(defvar dired-rmjunk--visit-queue nil
  "Queue of directories to visit following a removal.")

;;;###autoload
(defun dired-rmjunk ()
  "Mark all junk files in the current dired buffer.
'Junk' is defined to be any file with a name matching one of the
patterns in `dired-rmjunk-patterns'. A pattern is said to match
under the following conditions:

  1. If the pattern lacks a directory component, matching means
  that the regexp specified by the pattern matches the file-name.
  2. If the pattern lacks a directory component, matching means
  that that the regexp specified by the file-name component of
  the pattern matches the file-name, AND the regexp specified by
  the directory component of the pattern matches the current
  directory."
  (interactive)
  (when (eq major-mode 'dired-mode)
    (save-excursion
      (let ((files-marked-count 0))
        (dolist (file (directory-files dired-directory))
          (dolist (pattern dired-rmjunk-patterns)
            (when (or (and (not (dired-rmjunk--dir-name pattern))
                           (string-match pattern file))
                      (and (dired-rmjunk--dir-name pattern)
                           (string-match (dired-rmjunk--dir-name pattern)
                                         (dired-current-directory))
                           (string-match (dired-rmjunk--file-name pattern) file)))
              (setq files-marked-count (1+ files-marked-count))
              (dired-goto-file (concat (expand-file-name dired-directory) file))
              (dired-flag-file-deletion 1))))
        (if (zerop files-marked-count)
            (message "No junk files found :)")
          (progn
            (message "Junk files marked.")
            (setq dired-rmjunk--responsible-for-last-mark t)))
        files-marked-count))))

(advice-add #'dired-do-flagged-delete :after #'dired-rmjunk--after-delete)

(defun dired-rmjunk--after-delete ()
  ;; Prompt the user for whether or not they want to visit the subdirectories
  ;; named in dired-rmjunk-patterns.
  (when (and (not dired-rmjunk--visit-queue)
             dired-rmjunk--responsible-for-last-mark)
    (let* ((to-visit (map 'list
                          #'(lambda (subdir) (concat (dired-current-directory) "/" subdir))
                          (dired-rmjunk--directories-in-patterns)))
           (to-visit (cl-remove-if-not #'file-exists-p to-visit)))
      (when (and to-visit
                 (y-or-n-p "Visit subdirectories?"))
        (setq dired-rmjunk--visit-queue to-visit))))

  ;; Visit the next subdirectory in the queue.
  (when dired-rmjunk--visit-queue
    (while (and dired-rmjunk--visit-queue
                (set-buffer (dired (first dired-rmjunk--visit-queue)))
                (message "Visiting %s..." (first dired-rmjunk--visit-queue))
                (zerop (dired-rmjunk)))
      (setq dired-rmjunk--visit-queue
            (rest dired-rmjunk--visit-queue))))

  (setq dired-rmjunk--responsible-for-last-mark nil))

(defun dired-rmjunk--dir-name (path)
  "Return the directory portion of PATH, or `nil' if the path
does not contain a directory component."
  (let ((split-offset (cl-position ?\/ path :from-end t)))
    (if split-offset
        (cl-subseq path 0 (1+ split-offset)))))

(ert-deftest dired-rmjunk-test-dir-name ()
  (should (equal (dired-rmjunk--dir-name ".FRD/links.txt")
                 ".FRD/"))
  (should (equal (dired-rmjunk--dir-name ".local/share/recently-used.xbel")
                 ".local/share/"))
  (should (equal (dired-rmjunk--dir-name ".asy")
                 nil)))

(defun dired-rmjunk--file-name (path)
  "Return the file-name portion of PATH."
  (let ((split-offset (cl-position ?\/ path :from-end t)))
    (if split-offset
        (cl-subseq path (1+ split-offset))
      ;; If there's no directory component, `path' IS the file-name!
      path)))

(ert-deftest dired-rmjunk-test-file-name ()
  (should (equal (dired-rmjunk--file-name ".FRD/links.txt")
                 "links.txt"))
  (should (equal (dired-rmjunk--file-name ".local/share/recently-used.xbel")
                 "recently-used.xbel"))
  (should (equal (dired-rmjunk--file-name ".asy")
                 ".asy")))

(defun dired-rmjunk--directories-in-patterns (&optional patterns)
  (let ((patterns (or patterns dired-rmjunk-patterns)))
    (cl-remove-duplicates
     (cl-remove-if #'null
                   (map 'list #'dired-rmjunk--dir-name patterns))
     :test #'string=)))

(ert-deftest dired-rmjunk-test-directories-in-patterns ()
  (should (equal (dired-rmjunk--directories-in-patterns
                  '(".local/share/recently-used.xbel"
                    "Desktop"
                    ".thumbnails"))
                 '(".local/share/")))
  (should (equal (dired-rmjunk--directories-in-patterns
                  '(".distlib"
                    ".bazaar"
                    ".bzr.log"))
                 nil))
  (should (equal (dired-rmjunk--directories-in-patterns
                  '(".local/share/gegl-0.2"
                    ".FRD/app.log"
                    ".FRD/links.txt"))
                 '(".local/share/" ".FRD/"))))

(provide 'dired-rmjunk)
;;; dired-rmjunk.el ends here