Modifier des fichiers en masse
La problèmatique du jour est la suivante : j'utilise actuellement selenium pour les besoins du boulot. Je vous passe les détails, mais j'ai moults fichiers html qui me permettent de tester le contenu d'un site. Sauf que ce site évolue parfois, et du coup, il me faut modifier relativement fréquemment ces fichiers.
Heureusement qu'il y a findus emacs.
1er cas : changer un lien
Dans certains tests (qui sont nommés trucVariable_toto.html
), j'ai à tester la présence d'un lien <a href="toto">trucConstant</a>
. Parfois, trucConstant
change et devient autreChose
. Du coup, pleins de fichiers dans lesquels je dois modifier moi aussi trucConstant
par autreChose
.
C'est un cas assez fréquent (correction d'une typo…), et assez simple à résoudre :
- On ouvre le dossier où sont contenus les fichiers .html (avec
C-x C-f
) et on se trouve dansdired
. - On marque les fichiers concerné %m TexteDansLeNomDesFichiers.
- On appuie sur
Q
(pourQuery replace regexp in marked files
). trucConstant
(mais une regexp est possible).autreChose
.Y
(Yes
, toutes les condordances pour tous les fichiers marqués (y,y,y au début pour voir que ça va bien si jamais on a un doute ;]).- Nos fichiers sont modifiés mais pas sauvés, et ouverts dans des buffers. Du coup
M-x ibuffer
(Parce queC-x C-b
n'est parfois pas suffisant (h
dansibuffer
pour l'aide)). * u
(pour marquer tous les non-sauvés).S
(pour sauver les buffers marqués).D
(pour supprimer les buffer marqués).q
(quitteribuffer
).- Profiter du temps gagné.
Idem mais un peu plus à changer
Parfois, c'est tout un tableau qui est à changer. Et ce tableau est identique pour tous les fichiers. Ça reste dans ce cas relativement simple. Pour ce cas, je copie le tableau, je le mets dans un buffer temporaire, je le prépare pour être « regexp ready », le stocke dans un registre, fais ma modification, stocke dans un second registre, et fais comme au-dessus, sauf qu'au lieu de taper trucConstant
et autreChose
, je restitue mes registres.
- Ouvrir un des fichiers et aller au début de la partie à modifier.
C-SPC
pour poser la marque.- Déplacer le curseur à la fin de la partie à modifier.
M-w
pour copier.- Ouvrir un buffer temporaire
C-x b auie
(ou*scratch*
). - Coller
C-y
la région précédemment sauvegardée. - Se placer au début du buffer
M-<
, ou sélectionner tout le bufferC-x h
. - Changer les
[
et]
par\[
et\]
pour queQuery replace regexp in marked files
ne les prennent pas pour des caractères spéciaux :C-M-%
changer\(\[\|\]\)
par\\\1
. !
pour tout substituer.- Notre
trucConstant
un peu plus long est prêt. On sélectionne toutC-x h
, et on stocke dans un registreC-x r s a
par exemple. On peut faire notre deuxième registre. - On supprime avec
C-w
(comme tout le buffer est sélectionné). - On colle la région sauvegardée du début. Pour cela, on remonte dans l'historique du
kill-ring
avecC-y
puisM-y
. - On fait les modifications nécessaires.
- On resélectionne tout
C-x h
, et on stocke notre deuxième registre avecC-x r s b
par exemple. - On supprime notre buffer temporaire
C-x k
. - On se place dans notre dossier où on marque les fichiers contenant le texte
qqChoseDansLeTableau
avec% g
puisqqChoseDansLeTableau
. Q
puis on restitue notre premier registre avecC-x r i a
.- La substitution est assurée par le registre b :
C-x r i b
. - Et comme au dessus, on appelle
M-x ibuffer
pour sauver/détruire les buffers modifiés.
Pas bien plus compliqué donc. Il faut juste faire attention au fait que Query replace regexp in marked files
prend en première partie une regexp, ce qui peut poser quelques problèmes, mais aussi être utile puisqu'on peut se servir de bouts de la regexp pour les restituer avec \1
, \2
… D'ailleurs, c'est ce que l'on pourrait faire si le tableau n'est pas vraiment identique d'un fichier à l'autre. Mais comme ça risque d'être long et fastidieux, je préfère passer par des macros !
Supprimer une ligne dans un tableau, utilisation de macros
Pour ceux qui ne suivent que moyennement, je replace le contexte : plein de fichiers .html à changer, avec plus ou moins la même chose à l'intérieur, mais pas vraiment, et du coup, ça pourrait être chiant. Mais au final, pas tant que ça.
Afin de mieux expliquer, voici ce que contient une des pages du site que je dois tester :
. . . . . . | | | +-----------------------+--------------------------+ | texte a : | valeur | +-----------------------+--------------------------+ | texte b : | valeur | +-----------------------+--------------------------+ | texte c : | | +-----------------------+--------------------------+ | sstexte i | valeur | +-----------------------+--------------------------+ | sstexte ii | valeur | +-----------------------+--------------------------+ | | | . . . . . .
Un tableau, avec des valeurs qui changent selon ce que l'on a fait avant, comment on a navigué, etc.
Mais maintenant, le site a changé, et la ligne avec texte b
a disparu :
. . . . . . | | | +-----------------------+--------------------------+ | texte a : | valeur | +-----------------------+--------------------------+ | texte c : | | +-----------------------+--------------------------+ | sstexte i | valeur | +-----------------------+--------------------------+ | sstexte ii | valeur | +-----------------------+--------------------------+ | | | . . . . . .
Dans mes tests selenium, j'utilise xpath pour retrouver les éléments. Les éléments du tableau ont pour chemin quelque chose comme :
. . . . . . | | | +-----------------------+--------------------------+ | //path/to/elem[x] | //path/to/avec[x] | +-----------------------+--------------------------+ | //path/to/elem[x+2] | | +-----------------------+--------------------------+ | //path/to/elem[x+4] | //path/to/avec[x+4] | +-----------------------+--------------------------+ | //path/to/elem[x+5] | //path/to/avec[x+5] | +-----------------------+--------------------------+ | | | . . . . . .
Et là, on voit bien le problème : le texte c
n'est plus à l'indice x+2
à présent, mais x+1
. À partir de x
, il va falloir décrémenter tous les chiffres.
Ça ferait un bon exercice, mais ne parlant pas encore le (e)lisp couramment, https://duckduckgo.com/?q=!+emacs+increment+number+at+point me donne la réponse plus rapidement…
(defun decrement-number-at-point () (interactive) (skip-chars-backward "0123456789") (or (looking-at "[0123456789]+") (error "No number at point")) (replace-match (number-to-string (1- (string-to-number (match-string 0))))))
On va procéder maintenant en deux étapes : enregistrer une macro pour faire le changement dans un fichier, et appeler cette macro pour faire le changement dans tous les fichiers.
Enregistrement de la première macro
On a de la chance, dans le fichier .html (le test selenium) c'est tout bien aligné :
... <tr> <td>verifyText</td> <td>//div[@id='unId']/table/tbody/tr[3]/th/span</td> <td>Texte a :</td> </tr> <tr> <td>verifyText</td> <td>//div[@id='unId']/table/tbody/tr[3]/td/span</td> <td>valeur</td> </tr> <tr> <td>verifyText</td> <td>//div[@id='unId']/table/tbody/tr[4]/th/span</td> <td>Texte b :</td> </tr> <tr> <td>verifyText</td> <td>//div[@id='unId']/table/tbody/tr[4]/td/span</td> <td>valeur</td> </tr> <tr> <td>verifyText</td> <td>//div[@id='unId']/table/tbody/tr[5]/th</td> <td>Texte c :</td> </tr> <tr> <td>verifyText</td> <td>//div[@id='unId']/table/tbody/tr[6]/th/span</td> <td>regexp:- sstexte i \* :</td> </tr> ...
- On se place sur le premier chiffre à changer.
F3
pour commencer l'enregistrement de la macro.M-x decrement-number-at-point
.M-5 C-n
(on descend de 5 lignes pour se placer sur le prochain chiffre).F4
on termine la macro.
Si on appuie maintenant à nouveau sur F4
, on va incrémenter le nombre sous le curseur et se déplacer vers le second chiffre.
Si on fait M-0 F4
, on va appliquer la macro tant que c'est possible, et du coup, changer tous les chiffres qui vont bien.
Notre première macro est finie, on peut la garder pour la suite. Pour cela on la nomme pour pouvoir l'appeler comme une fonction : C-x C-k n auie
.
Appliquer la première macro dans tous les fichiers
Enfin, tous les fichiers qui contiennent texte a
- dans dired :
%g texte a
- puis voilà comment je fais en principe :
F3 | Début de la macro |
M-} | Se positionner sur le fichier marqué suivant |
Enter | Ouvrir le fichier |
M-< | Aller au début du buffer (on pourrait l'avoir déjà d'ouvert) |
C-M-S verifyText.*C-qC-j.*unId.*tr.4.*C-qC-j.*Texte b | Texte b apparaissant à un autre endroit, je prends pas de risque |
C-u C-p | 4 lignes vers le haut |
M-16 M-z > | Supprimer jusqu'au 16e « > » |
C-s 5 | Chercher 5 |
Enter | Arrêter la recherche |
C-b | Positionner le curseur sur le 5 |
M-0 M-x auie | Exécuter tant que possible l'incrément/déplacement vers le bas |
C-x C-s | Sauver |
C-x C-k | Tuer le buffer |
F4 | Arrêt de la macro |
Je sauve et tue le buffer, mais on pourrait faire comme précédemment, utiliser ibuffer
. En plus, si jamais nos macros sont mal définies, on peut faire des M-x revert-buffer
si ça c'est mal passé. Avec ma méthode, ce n'est pas le cas. (Mais bon, mes tests sont versionnés, je prends pas de risque non plus.)
Voilà les trois façons que j'utilise pour modifier tout un tas de fichiers. Ce qui est pratique, c'est que même si mes fichiers sont dans des dossiers rep/ssrep1
et rep/ssrep2
, j'ouvre rep
dans un buffer, puis i
sur ssrep1
et ssrep2
me permet de voir tous les fichiers d'un coup :]
Si vous connaissez plus simple, n'hésitez pas à me dire comment !