#+TITLE: Migrating to use no-littering

* Introduction

This document describes migration towards using no-littering for path
theming.

* Steps
** Prepare code

You must not install no-littering with a package manager because it
might automatically get loaded.  Make sure installing it won't load
it.  If you are not sure, clone the repository somewhere but do not
load or require the package.  We need this to preserve the existing
values set to themed variables.

Before you start, tangle this file by running ~M-x org-babel-tangle~ and
load =no-littering-migration.el=, for example by opening dired and
hitting ~L~ on the file.

** Save current values of your settings

First, we need to save the current values of the themed variables to
be able to move the files from their existing locations.  Execute the
code below:

#+begin_src elisp
  (setq no-littering-current-values
        (no-littering-get-current-variables))
#+end_src

** Load =no-littering= package

Now you can install the package with your package manager of choice.
After installation, run:

#+begin_src
  (require 'no-littering)
#+end_src

** Save new values

Now we save the new themed values set by no-littering.

#+begin_src elisp
  (setq no-littering-new-values (no-littering-get-current-variables))
#+end_src

** Generate migrations

We now have the old list and a new list and we can compare the
existing values to new values.  The code below generates a migration
script which you have to review and execute manually.
In order to generate the script, place the cursor inside the code block
and press =C-c C-c=.

#+begin_src elisp :wrap src elisp :results value pp
  (no-littering-generate-migration no-littering-current-values
				   no-littering-new-values)
#+end_src

The result is a list of =progn= forms which can be evaluated by placing
the point at the end of the =progn= form and calling =M-x eval-last-sexp=
(usually bound to ~C-x C-e~).

The form looks like this:

#+begin_src elisp
  (progn
    "abbrev-file-name" ; name of the variable
    (make-directory
     ;; create a parent dir for target
     "/home/matus/.config/emacs/etc/" t)
    (rename-file
     ;; old (current) file
     "/home/matus/.config/emacs/abbrev_defs"
     ;; new file
     "/home/matus/.config/emacs/etc/abbrev.el"
     1))
#+end_src

If you are satisfied, execute it and move to the next one.

Some of the forms might error out when the source file does not exist.
In that case there's nothing to do and you can move to the next one.

Occasionally, the generated code is not valid and you need to do the
migration manually.

** Remove customized values

If you were using =customize-variable= before, you need to remove
customized settings or they will keep overwriting the values managed
by =no-littering=.  Simply run the following to remove all
customizations.

*Make sure to backup your custom file before running this so you can
compare the results.*

#+begin_src elisp
  (no-littering-custom-reset)
  (custom-save-all)
#+end_src

Likewise, remove any other customization of the paths managed by
=no-littering=.

* Code
:PROPERTIES:
:ID:       5097feb0-d1e3-49b7-8129-276a40f8ec8a
:END:

#+begin_src elisp :tangle no-littering-migration.el
  (defun no-littering-get-themed-variables ()
    "Return all variables recognized by no-littering."
    (with-temp-buffer
      (insert-file-contents "no-littering.el")
      (goto-char (point-min))
      (re-search-forward "^(cl-letf")
      (beginning-of-line)
      (down-list)
      (forward-sexp 4)
      (let* ((data (cdr (read (current-buffer))))
	     (vars (cl-remove-if-not
		    (lambda (form) (eq (car form) 'setq)) data))
	     (current-values
	      (mapcar
	       (lambda (form)
		 (let* ((var (cadr form))
			(value (and (boundp var)
				    (symbol-value var))))
		   (when (stringp value)
		     (setq value (file-truename value)))
		   (cons var value)))
	       vars)))
	current-values)))

  (defun no-littering-get-current-variables ()
    "Return all variables recognized by no-littering currently used."
    (cl-remove-if
     (lambda (var) (not (cdr var)))
     (no-littering-get-themed-variables)))

  (defun no-littering-generate-migration (old-values new-values)
    (let* ((migrations
	    (mapcar (lambda (var)
		      (list (car var)
			    (cdr var)
			    (cdr (assq (car var) new-values))))
		    old-values))
	   (commands
	    (mapcar (lambda (var)
		      (if (stringp (nth 2 var))
			  (when (not (equal (nth 1 var) (nth 2 var)))
			    `(progn
			       ,(format "%s" (car var))
			       (make-directory
				,(file-name-directory (nth 2 var))
				t)
			       (rename-file ,(nth 1 var) ,(nth 2 var) 1)))
			`(progn ,(car var) "Value is not a directory")))
		    migrations)))
      commands))

  (defun no-littering--custom-reset-symbol (symbol)
    (put symbol 'variable-comment nil)
    (put symbol 'standard-value nil)
    (put symbol 'customized-value nil)
    (put symbol 'customized-variable-comment nil)
    (when (or (get symbol 'saved-value)
	      (get symbol 'saved-variable-comment))
      (put symbol 'saved-value nil)
      (put symbol 'saved-variable-comment nil)))

  (defun no-littering-custom-reset ()
    (dolist (var (no-littering-get-themed-variables))
      (no-littering--custom-reset-symbol (car var))))
#+end_src

#+export_file_name: docs/migrate
