Module zip
Le module zip permet de manipuler des archives Zip depuis un script YScript : ouvrir une archive
existante ou en créer une nouvelle, lister son contenu, ajouter/remplacer/supprimer des entrées, et
compresser/décompresser un dossier entier.
require("io")
local zip = require("zip")
local ar = zip.open("package.zip", "a")
for _, e in ipairs(ar:entries()) do
print(e.name, e.size, e.isdirectory)
end
ar:add("readme.txt", "hello")
ar:close()
Prérequis : la bibliothèque io
zip n'a aucune fonctionnalité qui ne touche pas au système de fichiers : une archive est un
fichier, ajouter une entrée depuis un dossier lit des fichiers, compresser un dossier les parcourt.
La bibliothèque io est donc une dépendance obligatoire de tout le module (contrairement à des
modules comme http, où io n'est nécessaire qu'à certaines fonctions).
io doit être chargée avant zip :
require("io") -- ou l'hôte a déjà appelé OpenIOLib()
local zip = require("zip")
Si io n'est pas chargée, require("zip") échoue (l'erreur remonte comme n'importe quel loader qui
échoue — utilisez pcall(require, "zip") si vous voulez récupérer nil, message plutôt que de
laisser l'erreur se propager) :
local ok, err = pcall(require, "zip")
if not ok then
print("zip indisponible : " .. tostring(err))
end
Vue d'ensemble
| Fonction / méthode | Description |
|---|---|
zip.open(path [, mode]) |
Ouvre ou crée une archive, retourne l'objet archive |
zip.type(obj) |
"archive" / "closed archive" / nil |
zip.extractall(zippath, destdir [, overwrite]) |
Décompresse une archive entière vers un dossier |
zip.compressdir(srcdir, destzippath [, options]) |
Compresse un dossier entier vers une nouvelle archive |
archive:entries() |
Liste le contenu de l'archive |
archive:exists(name) |
Teste la présence d'une entrée |
archive:read(name) |
Lit le contenu d'une entrée en mémoire |
archive:add(name, content) |
Ajoute une nouvelle entrée depuis une chaîne |
archive:addfile(name, filepath) |
Ajoute une nouvelle entrée depuis un fichier du disque |
archive:replace(name, content) |
Remplace le contenu d'une entrée existante |
archive:remove(name) |
Supprime une entrée |
archive:extract(name, destpath [, overwrite]) |
Extrait une entrée vers un fichier du disque |
archive:extractall(destdir [, overwrite]) |
Extrait toutes les entrées vers un dossier |
archive:close() |
Referme l'archive |
L'objet archive
zip.open retourne un objet archive (un userdata avec ses propres méthodes, dans le même esprit
qu'un FILE* retourné par io.open). Il doit être refermé explicitement par archive:close(),
implicitement via l'attribut <close>, ou en dernier recours par le ramasse-miettes :
local ar <close> = zip.open("package.zip", "r")
-- ar:close() est appelé automatiquement en sortie de bloc
Utiliser une méthode sur une archive déjà fermée lève une erreur.
zip.open(path [, mode])
Ouvre une archive existante ou en crée une nouvelle. mode (chaîne, défaut "r") :
| Mode | Comportement |
|---|---|
"r" |
Lecture seule. Erreur (nil, message) si le fichier n'existe pas ou n'est pas un zip valide. |
"w" |
Nouvelle archive vide. Écrase silencieusement un fichier existant au même chemin. |
"a" |
Ouvre l'archive existante en modification, ou en crée une vide si elle n'existe pas encore. Permet ajout/remplacement/suppression. |
En cas d'échec (fichier absent en "r", contenu invalide, erreur disque...), zip.open retourne
nil suivi d'un message d'erreur — comme io.open — plutôt que de lever une erreur script :
local ar, err = zip.open("introuvable.zip", "r")
if not ar then
print("échec : " .. err)
return
end
Un mode autre que "r"/"w"/"a" est en revanche une erreur de programmation et lève une erreur
script immédiate (mauvais argument), pas un nil, message.
zip.type(obj)
Miroir de io.type : retourne "archive" si obj est une archive ouverte, "closed archive" si
elle est fermée, ou nil si obj n'est pas une archive de ce module.
local ar = zip.open("a.zip", "w")
print(zip.type(ar)) --> archive
ar:close()
print(zip.type(ar)) --> closed archive
print(zip.type("x")) --> nil
archive:entries()
Retourne un tableau (indices 1..n) décrivant chaque entrée de l'archive :
| Champ | Type | Description |
|---|---|---|
name |
string | Chemin de l'entrée dans l'archive, séparateur / |
size |
integer | Taille décompressée en octets |
compressedsize |
integer | Taille compressée en octets |
lastmodified |
integer | Horodatage (epoch, secondes) — même unité que os.time() |
isdirectory |
boolean | true si l'entrée représente un dossier (nom finissant par /) |
local ar = zip.open("package.zip", "r")
for _, e in ipairs(ar:entries()) do
print(string.format("%-30s %8d -> %8d octets", e.name, e.size, e.compressedsize))
end
ar:close()
archive:exists(name)
Retourne true/false selon la présence d'une entrée name (fichier ou dossier) dans l'archive.
Utile avant :add/:replace/:remove, qui sont volontairement stricts (voir plus bas) :
if ar:exists("config.json") then
ar:replace("config.json", newContent)
else
ar:add("config.json", newContent)
end
archive:read(name)
Retourne le contenu entier de l'entrée name sous forme de chaîne binaire (un caractère = un octet,
même convention que le mode binaire de io) — pratique pour les petits fichiers texte/JSON, mais
charge tout le contenu en mémoire de script. Pour un gros fichier, préférez :extract qui passe par
un flux sans tout charger.
Erreurs : entrée absente, ou entrée désignant un dossier.
local ar = zip.open("package.zip", "r")
local manifest = ar:read("package.json")
ar:close()
archive:add(name, content) / archive:addfile(name, filepath)
Créent une nouvelle entrée name. Une entrée déjà présente sous ce nom est une erreur (pas
d'écrasement implicite — utilisez :replace, ou testez :exists au préalable si vous voulez un
comportement « upsert »).
:add(name, content)— le contenu vient d'une chaîne de script (binaire, comme:read).:addfile(name, filepath)— le contenu vient d'un fichier du disque, copié en flux vers l'entrée sans être chargé intégralement en mémoire de script. Préférable pour les gros fichiers. Erreur sifilepathn'existe pas sur le disque.
local ar = zip.open("package.zip", "a")
ar:add("VERSION", "1.0.0\n")
ar:addfile("dist/app.dll", "bin/Release/app.dll")
ar:close()
archive:replace(name, content)
Remplace le contenu d'une entrée déjà présente. Le format Zip ne permettant pas de modifier une
entrée en place, ceci équivaut en interne à une suppression suivie d'un ajout — transparent pour le
script, mais à garder en tête si vous inspectez le flux de l'archive par un autre moyen pendant
l'opération. Erreur si name est absente (symétrique de :add).
ar:replace("VERSION", "1.0.1\n")
archive:remove(name)
Supprime l'entrée name. Erreur si elle est absente.
ar:remove("old-file.txt")
archive:extract(name, destpath [, overwrite])
Extrait l'entrée name directement vers le fichier destpath du disque, en flux (sans charger le
contenu en mémoire de script — pendant symétrique de :addfile).
overwrite (booléen, défaut false) : si destpath existe déjà et overwrite n'est pas true,
c'est une erreur — pas d'écrasement silencieux d'un fichier existant.
Erreur si l'entrée est absente, ou si elle désigne un dossier (utilisez :extractall ou io.mkdir
pour un dossier).
local ar = zip.open("package.zip", "r")
ar:extract("bin/app.dll", "install/app.dll", true) -- true = écrase si déjà présent
ar:close()
archive:extractall(destdir [, overwrite])
Extrait toutes les entrées vers destdir, en recréant la structure de dossiers automatiquement
(sous-dossiers créés au besoin). overwrite a la même sémantique que pour :extract, appliquée à
chaque fichier extrait.
local ar = zip.open("package.zip", "r")
ar:extractall("installed/mypackage", true)
ar:close()
archive:close()
Referme l'archive : persiste les modifications en attente (pour une archive ouverte en "w"/"a")
et referme le fichier sous-jacent. Idempotent — un second :close() ne fait rien.
zip.extractall(zippath, destdir [, overwrite])
Raccourci équivalent à zip.open(zippath, "r"):extractall(destdir, overwrite) suivi d'un :close()
automatique. Pratique pour un cas d'usage en un seul appel, par exemple installer un paquet
téléchargé :
zip.extractall("mypackage-1.0.0.zip", "installed/mypackage", true)
zip.compressdir(srcdir, destzippath [, options])
Crée une nouvelle archive à destzippath et y ajoute récursivement tout le contenu de srcdir, avec
des chemins d'entrée relatifs à srcdir (séparateur /, y compris depuis un hôte Windows). Une
erreur est levée si srcdir n'existe pas.
options (table optionnelle) :
| Option | Défaut | Description |
|---|---|---|
overwrite |
false |
Si destzippath existe déjà et overwrite n'est pas true, c'est une erreur (contrairement à zip.open(path, "w"), qui écrase toujours — ici l'écrasement en masse doit être explicite). |
includebase |
false |
Si true, les entrées sont préfixées par le nom du dossier srcdir lui-même (srcdir/fichier.txt) plutôt que par son seul contenu (fichier.txt). |
-- Sans includebase : les entrées sont "fichier.txt", "sub/nested.txt", ...
zip.compressdir("dist/mypackage", "mypackage-1.0.0.zip", { overwrite = true })
-- Avec includebase : les entrées sont "mypackage/fichier.txt", "mypackage/sub/nested.txt", ...
zip.compressdir("dist/mypackage", "mypackage-1.0.0.zip", { includebase = true })
Erreurs
Les erreurs de programmation (mauvais type/valeur d'argument, mode invalide, méthode appelée sur une
archive fermée, opération incohérente comme ajouter une entrée déjà présente) sont levées comme des
erreurs script classiques — à capturer avec pcall si nécessaire. Seul zip.open fait exception
pour les échecs liés au système de fichiers (archive absente en lecture, contenu invalide, erreur
disque) : il retourne nil, message, comme io.open.
| Situation | Comportement |
|---|---|
require("zip") sans io chargée |
require échoue (erreur du loader) |
zip.open(path, "r") sur un fichier absent |
nil, message |
zip.open(path, "r") sur un fichier qui n'est pas un zip valide |
nil, message |
zip.open(path, mode) avec un mode autre que "r"/"w"/"a" |
erreur script (mauvais argument) |
| Méthode appelée sur une archive déjà fermée | erreur script |
:add/:addfile sur un nom déjà présent |
erreur script |
:read/:replace/:remove/:extract sur un nom absent |
erreur script |
:read/:extract visant une entrée qui est un dossier |
erreur script |
:addfile avec un fichier disque absent |
erreur script |
:extract/:extractall vers un chemin existant sans overwrite |
erreur script |
zip.compressdir avec un srcdir absent |
erreur script |
zip.compressdir sans overwrite sur une destination déjà existante |
erreur script |
Exemple complet
require("io")
local zip = require("zip")
-- Empaqueter un dossier de sources en une archive
zip.compressdir("dist/mypackage", "mypackage-1.0.0.zip", { overwrite = true })
-- Inspecter le contenu d'une archive
local ar = zip.open("mypackage-1.0.0.zip", "r")
for _, e in ipairs(ar:entries()) do
print(e.name, e.size, e.isdirectory)
end
local manifest = ar:read("package.json")
ar:close()
-- Construire/mettre à jour une archive entrée par entrée
local pkg = zip.open("mypackage-1.0.0.zip", "a")
pkg:replace("package.json", manifest .. "\n-- updated")
pkg:addfile("CHANGELOG.md", "CHANGELOG.md")
pkg:remove("old-file.txt")
pkg:close()
-- "Installer" un paquet : tout extraire dans un dossier
zip.extractall("mypackage-1.0.0.zip", "installed/mypackage", true)
Limites connues (v1)
- Pas de support des mots de passe / chiffrement d'archive.
:replacen'est pas une opération atomique au niveau du fichier zip (suppression + ajout en interne).- Pas de contrôle fin du niveau de compression par entrée.
- Pas de préservation de métadonnées spécifiques à une plateforme (permissions Unix, attributs Windows, liens symboliques).