Bienvenue dans ma série YOLO, où je présenterai des outils et des projets simples que j'ai construits, parfois pour m'amuser, parfois pour résoudre des problèmes spécifiques, et d'autres fois par pure curiosité. Le but ici n'est pas seulement de présenter un outil ; Je vais également plonger dans quelque chose d'intéressant lié au processus, qu'il s'agisse d'un aperçu technique ou d'une leçon apprise lors de l'élaboration de ces petites expériences.
Personne ne l’a demandé, et personne ne le veut, mais le voici quand même. Découvrez rrm, un outil qui résout un problème que je semble avoir (mais bon, il pourrait s'agir d'un problème de couche 8 ou, plus probablement, d'un problème de compétence !).
rrm ajoute une couche de sécurité à votre expérience de ligne de commande en déplaçant les fichiers vers une corbeille au lieu de les supprimer définitivement. Avec un délai de grâce personnalisable, vous avez la possibilité de réaliser : "Oups, j'en avais vraiment besoin !" avant qu'il ne soit trop tard.
De plus, rrm ne s'appuie pas sur des fichiers de configuration externes ou des systèmes de suivi pour gérer les fichiers supprimés. Au lieu de cela, il exploite les attributs étendus de votre système de fichiers pour stocker les métadonnées essentielles, telles que le chemin du fichier d'origine et l'heure de suppression, directement dans l'élément supprimé.
Vous vous demandez peut-être : "Pourquoi est-ce que je crée cet outil alors qu'il existe des outils similaires, voire meilleurs ?" Eh bien, la réponse est simple :
Remarque amusante : En travaillant avec std::Path, j'ai trouvé un exemple dans la bibliothèque standard Rust qui utilise un dossier nommé laputa
. Je sais que c'est une référence à Castle in the Sky, mais pour les hispanophones, c'est aussi un gros mot, ce qui en a fait un moment amusant pour moi !<script> // Detect dark theme var iframe = document.getElementById('tweet-1844834987184410735-190'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1844834987184410735&theme=dark" } </script>Quand j'ai commencé à créer rrm, j'avais besoin d'un moyen de suivre le chemin d'origine des fichiers supprimés et l'heure à laquelle ils devaient être définitivement supprimés. Je ne voulais pas utiliser de fichier JSON ni implémenter un format de dénomination étrange incluant ces informations, surtout si je souhaitais stocker plus de données plus tard. Une base de données semblait excessive pour une si petite tâche.
C'est à ce moment-là que j'ai découvert les attributs étendus.
Maintenant, je ne sais pas pour vous, mais je n'avais pas réalisé qu'il existait un mécanisme intégré qui vous permet d'ajouter des métadonnées personnalisées aux fichiers, qui est pris en charge par la plupart des systèmes de fichiers Linux et des systèmes de type Unix tels que macOS . Cette fonctionnalité est appelée Attributs de fichier étendus. Différents systèmes ont leurs propres limites, comme la quantité de données pouvant être ajoutées ou les espaces de noms spécifiques utilisés, mais ils vous permettent de stocker des métadonnées définies par l'utilisateur.
Les attributs étendus sont essentiellement des paires nom:valeur associées en permanence aux fichiers et aux répertoires. Comme je l’ai mentionné plus tôt, les systèmes diffèrent dans la manière dont ils gèrent cela. Par exemple, sous Linux, le nom commence par un identifiant d'espace de noms. Il existe quatre espaces de noms : sécurité, système, approuvé et utilisateur. Sous Linux, le nom commence par l'un de ces éléments, suivi d'un point ("".") puis d'une chaîne terminée par un caractère nul. Sur macOS, les choses sont un peu différentes. macOS ne nécessite aucun espace de noms, grâce à son approche unifiée des métadonnées, qui traite les attributs étendus comme des métadonnées supplémentaires directement liées aux fichiers sans avoir besoin d'être catégorisées.
Dans cette petite CLI, j'utilise le crate xattr, qui prend en charge à la fois Linux et macOS. Concernant les espaces de noms que j'ai mentionnés plus tôt pour Linux, nous nous concentrerons sur l'espace de noms utilisateur puisque ces attributs sont destinés à être utilisés par l'utilisateur. Ainsi, dans le code, vous verrez quelque chose comme ceci :
/// Namespace for extended attributes (xattrs) on macOS and other operating systems. /// On macOS, this is an empty string, while on other operating systems, it is "user.". #[cfg(target_os = "macos")] const XATTR_NAMESPACE: &str = ""; #[cfg(not(target_os = "macos"))] const XATTR_NAMESPACE: &str = "user."; ... fn set_attr(&self, path: &Path, attr: &str, value: &str) -> Result<()> { let attr_name = format!("{}{}", XATTR_NAMESPACE, attr); ... }
L'attribut #[cfg(target_os = "macos")] dans Rust est utilisé pour compiler conditionnellement du code basé sur le système d'exploitation cible. Dans ce cas, cela garantit que le bloc de code n'est inclus que lors de la compilation pour macOS. Ceci est pertinent car, comme mentionné précédemment, macOS ne nécessite pas d'espace de noms pour les attributs étendus, donc XATTR_NAMESPACE est défini sur une chaîne vide. Pour les autres systèmes d'exploitation, l'espace de noms est défini sur "user". Cette compilation conditionnelle permet au code de s'adapter de manière transparente sur différentes plates-formes, rendant la CLI compatible avec Linux et macOS.
Une chose que j'ai trouvée plutôt intéressante à propos des attributs étendus, c'est qu'ils ne modifient pas le fichier lui-même. Les métadonnées résident dans un espace disque séparé, référencé par l'inode. Cela signifie que le contenu réel du fichier reste inchangé. Par exemple, si nous créons un fichier simple et utilisons shasum pour obtenir sa somme de contrôle :
L'inode (nœud d'index) est une structure de données dans un système de fichiers de style Unix qui décrit un objet du système de fichiers tel qu'un fichier ou un répertoire. Lien
/// Namespace for extended attributes (xattrs) on macOS and other operating systems. /// On macOS, this is an empty string, while on other operating systems, it is "user.". #[cfg(target_os = "macos")] const XATTR_NAMESPACE: &str = ""; #[cfg(not(target_os = "macos"))] const XATTR_NAMESPACE: &str = "user."; ... fn set_attr(&self, path: &Path, attr: &str, value: &str) -> Result<()> { let attr_name = format!("{}{}", XATTR_NAMESPACE, attr); ... }
Après avoir utilisé rrm pour supprimer le fichier, nous pouvons lister les fichiers supprimés et voir que le fichier a été déplacé vers la corbeille avec ses métadonnées intactes :
$ cat a.txt https://www.kungfudev.com/ $ shasum a.txt e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5 a.txt
Comme vous pouvez le voir, le nom du fichier est remplacé par un UUID. Ceci est fait pour éviter les collisions de noms lors de la suppression de fichiers portant le même nom. En attribuant un identifiant unique à chaque fichier, rrm garantit que chaque fichier supprimé, même s'il porte des noms identiques, peut être suivi et récupéré sans aucun problème.
Nous pouvons accéder au dossier corbeille et inspecter le fichier pour confirmer que son contenu reste inchangé :
$ rrm rm a.txt $ rrm list ╭──────────────────────────────────────────────────────┬──────────────────────────────────────┬──────┬─────────────────────╮ │ Original Path ┆ ID ┆ Kind ┆ Deletion Date │ ╞══════════════════════════════════════════════════════╪══════════════════════════════════════╪══════╪═════════════════════╡ │ /Users/douglasmakey/workdir/personal/kungfudev/a.txt ┆ 3f566788-75dc-4674-b069-0faeaa86aa55 ┆ File ┆ 2024-10-27 04:10:19 │ ╰──────────────────────────────────────────────────────┴──────────────────────────────────────┴──────┴─────────────────────╯
De plus, en utilisant xattr sur macOS, nous pouvons vérifier que le fichier a ses métadonnées, telles que la date de suppression et le chemin d'origine :
$ shasum 3f566788-75dc-4674-b069-0faeaa86aa55 e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5 3f566788-75dc-4674-b069-0faeaa86aa55
Vous pouvez imaginer l'éventail de cas d'utilisation potentiels pour des validations ou des actions simples utilisant ces métadonnées. Étant donné que les attributs étendus fonctionnent sans modifier le fichier lui-même, ils vous permettent de vérifier l'intégrité du fichier ou d'effectuer d'autres opérations sans affecter le contenu d'origine.
Ceci n'est qu'une petite introduction aux attributs étendus et à la façon dont ils sont utilisés dans ce projet. Il ne s’agit pas d’une explication approfondie, mais si vous souhaitez en savoir plus, il existe de nombreuses ressources détaillées. Voici quelques liens vers les ressources les plus utiles et les mieux décrites sur le sujet :
J'ai passé quelques années à travailler avec Go et je suis devenu friand de certains modèles, dont la moquerie. Dans Go, j'implémente généralement les choses moi-même si cela évite les importations inutiles ou me donne plus de flexibilité. Je suis tellement habitué à cette approche que lorsque j'ai commencé à écrire des tests dans Rust, je me suis retrouvé à préférer me moquer manuellement de certaines choses, comme créer des implémentations simulées de traits.
Par exemple, dans cette petite CLI, j'ai créé un trait pour dissocier le gestionnaire de corbeille de la façon dont il interagit avec les attributs étendus. Le trait, nommé ExtendedAttributes, était initialement destiné à des fins de test, mais aussi parce que je n'étais pas sûr d'utiliser xattr ou une autre implémentation. J'ai donc défini le trait suivant :
$ xattr -l 3f566788-75dc-4674-b069-0faeaa86aa55 deletion_date: 2024-10-27T04:10:19.875614+00:00 original_path: /Users/douglasmakey/workdir/personal/kungfudev/a.txt
Dans Go, je créerais quelque chose comme ce qui suit, qui fournit une implémentation simple de l'interface mentionnée précédemment. Le code ci-dessous est simple et généré sans trop de considération, juste à titre d'exemple :
/// Namespace for extended attributes (xattrs) on macOS and other operating systems. /// On macOS, this is an empty string, while on other operating systems, it is "user.". #[cfg(target_os = "macos")] const XATTR_NAMESPACE: &str = ""; #[cfg(not(target_os = "macos"))] const XATTR_NAMESPACE: &str = "user."; ... fn set_attr(&self, path: &Path, attr: &str, value: &str) -> Result<()> { let attr_name = format!("{}{}", XATTR_NAMESPACE, attr); ... }
Ensuite, j'utiliserais ma simulation et injecterais le comportement spécifique nécessaire pour chaque test. Encore une fois, il s'agit d'un code simple juste à titre d'exemple :
$ cat a.txt https://www.kungfudev.com/ $ shasum a.txt e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5 a.txt
Je me suis habitué à ce modèle dans Go et je prévois de continuer à l'utiliser. Mais j’ai aussi fait quelque chose de similaire dans Rust. Pour ce projet, j'ai décidé d'essayer la caisse mockall, et je l'ai trouvée vraiment utile.
D’abord, j’ai utilisé la maquette ! macro pour se moquer manuellement de ma structure. Je sais que mockall a une fonctionnalité de simulation automatique, mais je préfère définir la structure simulée directement dans mes tests où elle sera utilisée. Faites-moi savoir si c'est quelque chose de courant ou si la communauté a une norme différente à ce sujet.
$ rrm rm a.txt $ rrm list ╭──────────────────────────────────────────────────────┬──────────────────────────────────────┬──────┬─────────────────────╮ │ Original Path ┆ ID ┆ Kind ┆ Deletion Date │ ╞══════════════════════════════════════════════════════╪══════════════════════════════════════╪══════╪═════════════════════╡ │ /Users/douglasmakey/workdir/personal/kungfudev/a.txt ┆ 3f566788-75dc-4674-b069-0faeaa86aa55 ┆ File ┆ 2024-10-27 04:10:19 │ ╰──────────────────────────────────────────────────────┴──────────────────────────────────────┴──────┴─────────────────────╯
J'ai trouvé mockall vraiment utile, me permettant d'injecter des comportements spécifiques dans mes tests sans la verbosité de mon ancien modèle.
$ shasum 3f566788-75dc-4674-b069-0faeaa86aa55 e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5 3f566788-75dc-4674-b069-0faeaa86aa55
Comme nous pouvons le voir, mockall nous donne la possibilité de définir des comportements spécifiques pour nos tests en utilisant ses méthodes mock :
Certains d'entre vous pourraient trouver cela super basique ou pas très intéressant, mais comme je l'ai mentionné, dans cette série YOLO, je partage des choses que je trouve intéressantes ou dont je veux juste parler. Je n’étais pas un grand fan de l’utilisation de ce type de bibliothèque dans Go, en partie à cause des contraintes de Go, mais dans Rust, j’ai trouvé mockall vraiment utile. Cela m'a même rappelé mes vieux jours avec Python.
Encore une fois, cette section n’était pas destinée à expliquer la moquerie dans Rust ou mockall. Je suis sûr qu’il existe de nombreuses ressources intéressantes qui le couvrent en détail. Je voulais juste le mentionner brièvement.
Dans cet article, j'ai partagé une partie du raisonnement derrière la création de rrm et les outils que j'ai utilisés tout au long du processus. De l'utilisation d'attributs étendus pour simplifier la gestion des métadonnées à l'expérimentation de la caisse mockall pour les tests dans Rust, ce ne sont que des choses qui ont piqué mon intérêt.
L'objectif de cette série YOLO est de mettre en évidence le plaisir et l'apprentissage qui accompagnent la construction d'outils, même simples. J'espère que vous avez trouvé quelque chose d'utile ici et j'ai hâte de partager plus de projets et d'idées dans les prochains articles. Comme toujours, les commentaires sont les bienvenus !
Bon codage !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!