Amélioration de l'interface de la ligne de commande

This commit is contained in:
Mattéo Delabre 2016-10-28 16:27:25 +02:00
parent 69f64a3fc5
commit f5522c4036
5 changed files with 119 additions and 140 deletions

View File

@ -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

View File

@ -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é

View File

@ -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;
} }

View File

@ -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) {

View File

@ -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;
} }