Amélioration de l'interface de la ligne de commande
This commit is contained in:
parent
69f64a3fc5
commit
f5522c4036
|
@ -2,13 +2,8 @@
|
||||||
#define __IN303_COMPRESS_H__
|
#define __IN303_COMPRESS_H__
|
||||||
|
|
||||||
#include "huftree.h"
|
#include "huftree.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#define COMPRESS_OK 0
|
void compress(FILE* source, FILE* dest);
|
||||||
#define COMPRESS_READ_ERROR 1
|
|
||||||
#define COMPRESS_WRITE_ERROR 2
|
|
||||||
#define COMPRESS_OPEN_INPUT_ERROR 3
|
|
||||||
#define COMPRESS_OPEN_OUTPUT_ERROR 4
|
|
||||||
|
|
||||||
int compress(const char*, const char*);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define __IN303_HUFTREE_H__
|
#define __IN303_HUFTREE_H__
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "buffer.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,10 +48,10 @@ struct HufTree {
|
||||||
HufTree createTree(double* frequencies);
|
HufTree createTree(double* frequencies);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Écrit une représentation binaire de l'arbre dans le fichier
|
* Écrit une représentation binaire de l'arbre dans le
|
||||||
* passé en paramètre
|
* tampon passé en paramètre
|
||||||
*/
|
*/
|
||||||
void writeTree(HufTree, FILE*);
|
void writeTree(HufTree, Buffer*);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconstruit un arbre de Huffman à partir du fichier passé
|
* Reconstruit un arbre de Huffman à partir du fichier passé
|
||||||
|
|
|
@ -22,7 +22,7 @@ static double* _createFrequencies(FILE*);
|
||||||
* en suivant les étiquettes passées. Les étiquettes
|
* en suivant les étiquettes passées. Les étiquettes
|
||||||
* peuvent être calculées avec labelTree()
|
* peuvent être calculées avec labelTree()
|
||||||
*/
|
*/
|
||||||
void _encodeFromTable(char**, FILE*, FILE*);
|
void _encodeFromTable(char**, FILE*, Buffer*);
|
||||||
|
|
||||||
double* _createFrequencies(FILE* file) {
|
double* _createFrequencies(FILE* file) {
|
||||||
double* frequencies = malloc(NUM_CHARS * sizeof(*frequencies));
|
double* frequencies = malloc(NUM_CHARS * sizeof(*frequencies));
|
||||||
|
@ -49,12 +49,11 @@ double* _createFrequencies(FILE* file) {
|
||||||
return frequencies;
|
return frequencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _encodeFromTable(char** labels, FILE* input, FILE* output) {
|
void _encodeFromTable(char** labels, FILE* source, Buffer* output) {
|
||||||
Buffer buffer = createBuffer(output);
|
|
||||||
int current;
|
int current;
|
||||||
|
|
||||||
// Lecture du fichier d'entrée caractère par caractère
|
// Lecture du fichier d'entrée caractère par caractère
|
||||||
while ((current = fgetc(input)) != EOF) {
|
while ((current = fgetc(source)) != EOF) {
|
||||||
assert(current >= 0 && current < NUM_CHARS);
|
assert(current >= 0 && current < NUM_CHARS);
|
||||||
char* label = labels[current];
|
char* label = labels[current];
|
||||||
assert(label != NULL);
|
assert(label != NULL);
|
||||||
|
@ -62,40 +61,20 @@ void _encodeFromTable(char** labels, FILE* input, FILE* output) {
|
||||||
// Ajout du label dans le buffer, caractère par caractère
|
// Ajout du label dans le buffer, caractère par caractère
|
||||||
// vidant progressivement le buffer
|
// vidant progressivement le buffer
|
||||||
while (*label != '\0') {
|
while (*label != '\0') {
|
||||||
pushToBuffer(*label == '1', &buffer);
|
pushToBuffer(*label == '1', output);
|
||||||
label++;
|
label++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Écriture du dernier octet dans le fichier
|
|
||||||
// (pas toujours plein)
|
|
||||||
flushBuffer(&buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int compress(const char* inputpath, const char* outputpath) {
|
void compress(FILE* source, FILE* dest) {
|
||||||
// FIXME: gérer le fichier vide
|
// FIXME: gérer le fichier vide
|
||||||
// FIXME: gérer les fichiers avec un seul type de caractère
|
// FIXME: gérer les fichiers ne contenant qu'un seul type de caractère
|
||||||
|
// FIXME: gérer l'entrée depuis stdin (pas de double lecture)
|
||||||
|
|
||||||
printVerbose(
|
// Calcul de la clef de codage à partir de la source
|
||||||
"Compression du fichier '%s' en '%s'...\n",
|
|
||||||
inputpath, outputpath
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ouverture de l'entrée en lecture et la sortie en écriture
|
|
||||||
FILE* input;
|
|
||||||
FILE* output;
|
|
||||||
|
|
||||||
if ((input = fopen(inputpath, "r")) == NULL) {
|
|
||||||
return COMPRESS_OPEN_INPUT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((output = fopen(outputpath, "w")) == NULL) {
|
|
||||||
return COMPRESS_OPEN_OUTPUT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comptage des caractères et calcul des fréquences d'apparition
|
|
||||||
printVerbose("Calcul des fréquences d'apparition des caractères.\n");
|
printVerbose("Calcul des fréquences d'apparition des caractères.\n");
|
||||||
double* frequencies = _createFrequencies(input);
|
double* frequencies = _createFrequencies(source);
|
||||||
|
|
||||||
if (isVerbose()) {
|
if (isVerbose()) {
|
||||||
printFrequenciesTable(frequencies, NUM_CHARS);
|
printFrequenciesTable(frequencies, NUM_CHARS);
|
||||||
|
@ -118,19 +97,19 @@ int compress(const char* inputpath, const char* outputpath) {
|
||||||
printLabelsTable(labels, NUM_CHARS);
|
printLabelsTable(labels, NUM_CHARS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Écriture des données compressées vers la sortie
|
||||||
|
Buffer output = createBuffer(dest);
|
||||||
|
|
||||||
printVerbose("\nÉcriture de l'arbre dans la sortie.\n");
|
printVerbose("\nÉcriture de l'arbre dans la sortie.\n");
|
||||||
writeTree(tree, output);
|
writeTree(tree, &output);
|
||||||
|
|
||||||
printVerbose("Écriture des données compressées du fichier.\n");
|
printVerbose("Écriture des données compressées du fichier.\n");
|
||||||
rewind(input);
|
rewind(source);
|
||||||
_encodeFromTable(labels, input, output);
|
_encodeFromTable(labels, source, &output);
|
||||||
|
|
||||||
|
// Vidage du dernier octet du tampon et nettoyage
|
||||||
|
flushBuffer(&output);
|
||||||
freeTree(tree);
|
freeTree(tree);
|
||||||
freeTreeLabels(labels);
|
freeTreeLabels(labels);
|
||||||
labels = NULL;
|
labels = NULL;
|
||||||
|
|
||||||
fclose(input);
|
|
||||||
fclose(output);
|
|
||||||
|
|
||||||
return COMPRESS_OK;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,10 +146,8 @@ void _findMinimalVertices(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeTree(HufTree tree, FILE* output) {
|
void writeTree(HufTree tree, Buffer* buffer) {
|
||||||
Buffer buffer = createBuffer(output);
|
_writeVertex(*tree.root, buffer);
|
||||||
_writeVertex(*tree.root, &buffer);
|
|
||||||
flushBuffer(&buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _writeVertex(HufVertex vertex, Buffer* buffer) {
|
void _writeVertex(HufVertex vertex, Buffer* buffer) {
|
||||||
|
|
182
src/main.c
182
src/main.c
|
@ -1,31 +1,19 @@
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compress.h"
|
#include "compress.h"
|
||||||
#include <argp.h>
|
#include <argp.h>
|
||||||
|
#include <libintl.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/**
|
|
||||||
* Définition de la configuration de Argp, qui affichera
|
|
||||||
* ces informations dans la page d'aide entre autres
|
|
||||||
*/
|
|
||||||
static char doc[] = "Compresse ou décompresse des fichiers en "
|
|
||||||
"utilisant l'algorithme de Huffman";
|
|
||||||
static char args_doc[] = "FICHIER...";
|
|
||||||
static struct argp_option options[] = {
|
|
||||||
{
|
|
||||||
"verbose", 'V', 0, 0,
|
|
||||||
"Afficher des informations de débogage sur la compression"
|
|
||||||
},
|
|
||||||
{0}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contient les valeurs des arguments (options et opérandes)
|
* Contient les valeurs des arguments (options et opérandes)
|
||||||
* passées au programme et interprétées via Argp
|
* passées au programme et interprétées via Argp
|
||||||
*/
|
*/
|
||||||
typedef struct Args {
|
typedef struct Args {
|
||||||
int verbose;
|
int verbose;
|
||||||
char** files;
|
int compress;
|
||||||
|
FILE* source;
|
||||||
|
FILE* dest;
|
||||||
} Args;
|
} Args;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,100 +24,118 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) {
|
||||||
Args* args = state->input;
|
Args* args = state->input;
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'V':
|
case 'v':
|
||||||
args->verbose = TRUE;
|
args->verbose = TRUE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARGP_KEY_ARG:
|
case 'd':
|
||||||
// Consommation de tous les fichiers en argument
|
args->compress = FALSE;
|
||||||
// et arrêt du parsage
|
|
||||||
args->files = &state->argv[state->next - 1];
|
|
||||||
state->next = state->argc;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ARGP_KEY_NO_ARGS:
|
case ARGP_KEY_ARG:
|
||||||
argp_usage(state);
|
switch (state->arg_num) {
|
||||||
break;
|
case 0:
|
||||||
|
// Premier argument : fichier d'entrée. Si omis,
|
||||||
|
// utilisation de l'entrée standard
|
||||||
|
args->source = fopen(arg, "r");
|
||||||
|
|
||||||
|
if (args->source == NULL) {
|
||||||
|
argp_error(
|
||||||
|
state, "%s: %s",
|
||||||
|
arg, strerror(errno)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// Second argument : fichier de sortie. Si omis,
|
||||||
|
// utilisation de la sortie standard
|
||||||
|
args->dest = fopen(arg, "w");
|
||||||
|
|
||||||
|
if (args->dest == NULL) {
|
||||||
|
argp_error(
|
||||||
|
state, "%s: %s",
|
||||||
|
arg, strerror(errno)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
argp_error(
|
||||||
|
state, "Trop d'arguments - L'argument %s est superflu",
|
||||||
|
arg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
return ARGP_ERR_UNKNOWN;
|
return ARGP_ERR_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct argp argp = {options, parse_opt, args_doc, doc};
|
/**
|
||||||
|
* Définition de la configuration de Argp, qui affichera
|
||||||
|
* ces informations dans la page d'aide entre autres
|
||||||
|
*/
|
||||||
|
static struct argp_option options[] = {
|
||||||
|
{
|
||||||
|
.name = "decompress",
|
||||||
|
.key = 'd',
|
||||||
|
.doc = "Décompresse SOURCE vers DEST"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "verbose",
|
||||||
|
.key = 'v',
|
||||||
|
.doc = "Affiche des informations de débogage"
|
||||||
|
},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct argp argp = {
|
||||||
|
.options = options,
|
||||||
|
.parser = parse_opt,
|
||||||
|
.args_doc = "[SOURCE [DEST]]",
|
||||||
|
.doc = "Compresse ou décompresse SOURCE vers DEST en utilisant "
|
||||||
|
"l'algorithme de Huffman (par défaut, compresse SOURCE vers DEST)."
|
||||||
|
};
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
// Pour pouvoir afficher des caractères larges dans le terminal
|
// Paramètres de langue. Utilisation de la locale de l'utilisateur,
|
||||||
setlocale(LC_CTYPE, "");
|
// et des messages traduits dans libc (pour pallier à un bug de argp)
|
||||||
|
setlocale(LC_ALL, "");
|
||||||
|
textdomain("libc");
|
||||||
|
|
||||||
// Interprétation des arguments passés au programme par Argv,
|
// Valeurs par défaut des arguments optionnels
|
||||||
// suivant la configuration définie au dessus
|
Args args = {
|
||||||
Args args;
|
.verbose = FALSE,
|
||||||
|
.compress = TRUE,
|
||||||
|
.source = stdin,
|
||||||
|
.dest = stdout
|
||||||
|
};
|
||||||
|
|
||||||
args.verbose = FALSE;
|
// Interprétation des arguments passés au programme, résultat
|
||||||
args.files = NULL;
|
// dans `args`. Arrêt en cas d'erreur
|
||||||
|
if (argp_parse(&argp, argc, argv, 0, 0, &args)) {
|
||||||
argp_parse(&argp, argc, argv, 0, 0, &args);
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
// Passage en mode verbeux si demandé
|
// Passage en mode verbeux si demandé
|
||||||
setVerbose(args.verbose);
|
setVerbose(args.verbose);
|
||||||
|
|
||||||
// Compression des fichiers en argument
|
// Compression ou décompression du flux d'entrée vers le flux de sortie
|
||||||
char** files = args.files;
|
if (args.compress) {
|
||||||
|
compress(args.source, args.dest);
|
||||||
while (*files != NULL) {
|
} else {
|
||||||
char* input = *files;
|
fprintf(stderr, "Décompression non-implémentée.\n");
|
||||||
|
return EXIT_FAILURE;
|
||||||
// Compression du fichier xx en xx.huf
|
|
||||||
// TODO: rendre le nom du fichier de sortie configurable
|
|
||||||
char* output = malloc((strlen(input) + 5) * sizeof(*output));
|
|
||||||
strcpy(output, input);
|
|
||||||
strcat(output, ".huf");
|
|
||||||
|
|
||||||
switch (compress(input, output)) {
|
|
||||||
case COMPRESS_READ_ERROR:
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"Erreur : impossible de lire le fichier '%s' - %s\n",
|
|
||||||
input, strerror(errno)
|
|
||||||
);
|
|
||||||
return COMPRESS_READ_ERROR;
|
|
||||||
|
|
||||||
case COMPRESS_WRITE_ERROR:
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"Erreur : impossible d'écrire dans le fichier '%s' - %s\n",
|
|
||||||
output, strerror(errno)
|
|
||||||
);
|
|
||||||
return COMPRESS_WRITE_ERROR;
|
|
||||||
|
|
||||||
case COMPRESS_OPEN_INPUT_ERROR:
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"Erreur : impossible d'ouvrir le fichier '%s' - %s\n",
|
|
||||||
input, strerror(errno)
|
|
||||||
);
|
|
||||||
return COMPRESS_OPEN_INPUT_ERROR;
|
|
||||||
|
|
||||||
case COMPRESS_OPEN_OUTPUT_ERROR:
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"Erreur : impossible d'ouvrir le fichier '%s' - %s\n",
|
|
||||||
output, strerror(errno)
|
|
||||||
);
|
|
||||||
return COMPRESS_OPEN_OUTPUT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(output);
|
|
||||||
files++;
|
|
||||||
|
|
||||||
// Ligne de séparation entre les différents fichiers
|
|
||||||
if (*files != NULL) {
|
|
||||||
printVerbose("\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fermeture des flux et sortie
|
||||||
|
fclose(args.source);
|
||||||
|
fclose(args.dest);
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue