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__ | ||||
| 
 | ||||
| #include "huftree.h" | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| #define COMPRESS_OK 0 | ||||
| #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*); | ||||
| void compress(FILE* source, FILE* dest); | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| #define __IN303_HUFTREE_H__ | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include "buffer.h" | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -47,10 +48,10 @@ struct HufTree { | |||
| HufTree createTree(double* frequencies); | ||||
| 
 | ||||
| /**
 | ||||
|  * Écrit une représentation binaire de l'arbre dans le fichier | ||||
|  * passé en paramètre | ||||
|  * Écrit une représentation binaire de l'arbre dans le | ||||
|  * tampon passé en paramètre | ||||
|  */ | ||||
| void writeTree(HufTree, FILE*); | ||||
| void writeTree(HufTree, Buffer*); | ||||
| 
 | ||||
| /**
 | ||||
|  * 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 | ||||
|  * peuvent être calculées avec labelTree() | ||||
|  */ | ||||
| void _encodeFromTable(char**, FILE*, FILE*); | ||||
| void _encodeFromTable(char**, FILE*, Buffer*); | ||||
| 
 | ||||
| double* _createFrequencies(FILE* file) { | ||||
|     double* frequencies = malloc(NUM_CHARS * sizeof(*frequencies)); | ||||
|  | @ -49,12 +49,11 @@ double* _createFrequencies(FILE* file) { | |||
|     return frequencies; | ||||
| } | ||||
| 
 | ||||
| void _encodeFromTable(char** labels, FILE* input, FILE* output) { | ||||
|     Buffer buffer = createBuffer(output); | ||||
| void _encodeFromTable(char** labels, FILE* source, Buffer* output) { | ||||
|     int current; | ||||
| 
 | ||||
|     // 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); | ||||
|         char* label = labels[current]; | ||||
|         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
 | ||||
|         // vidant progressivement le buffer
 | ||||
|         while (*label != '\0') { | ||||
|             pushToBuffer(*label == '1', &buffer); | ||||
|             pushToBuffer(*label == '1', output); | ||||
|             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 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( | ||||
|         "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
 | ||||
|     // Calcul de la clef de codage à partir de la source
 | ||||
|     printVerbose("Calcul des fréquences d'apparition des caractères.\n"); | ||||
|     double* frequencies = _createFrequencies(input); | ||||
|     double* frequencies = _createFrequencies(source); | ||||
| 
 | ||||
|     if (isVerbose()) { | ||||
|         printFrequenciesTable(frequencies, NUM_CHARS); | ||||
|  | @ -118,19 +97,19 @@ int compress(const char* inputpath, const char* outputpath) { | |||
|         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"); | ||||
|     writeTree(tree, output); | ||||
|     writeTree(tree, &output); | ||||
| 
 | ||||
|     printVerbose("Écriture des données compressées du fichier.\n"); | ||||
|     rewind(input); | ||||
|     _encodeFromTable(labels, input, output); | ||||
|     rewind(source); | ||||
|     _encodeFromTable(labels, source, &output); | ||||
| 
 | ||||
|     // Vidage du dernier octet du tampon et nettoyage
 | ||||
|     flushBuffer(&output); | ||||
|     freeTree(tree); | ||||
|     freeTreeLabels(labels); | ||||
|     labels = NULL; | ||||
| 
 | ||||
|     fclose(input); | ||||
|     fclose(output); | ||||
| 
 | ||||
|     return COMPRESS_OK; | ||||
| } | ||||
|  |  | |||
|  | @ -146,10 +146,8 @@ void _findMinimalVertices( | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void writeTree(HufTree tree, FILE* output) { | ||||
|     Buffer buffer = createBuffer(output); | ||||
|     _writeVertex(*tree.root, &buffer); | ||||
|     flushBuffer(&buffer); | ||||
| void writeTree(HufTree tree, Buffer* buffer) { | ||||
|     _writeVertex(*tree.root, buffer); | ||||
| } | ||||
| 
 | ||||
| void _writeVertex(HufVertex vertex, Buffer* buffer) { | ||||
|  |  | |||
							
								
								
									
										182
									
								
								src/main.c
								
								
								
								
							
							
						
						
									
										182
									
								
								src/main.c
								
								
								
								
							|  | @ -1,31 +1,19 @@ | |||
| #include "common.h" | ||||
| #include "compress.h" | ||||
| #include <argp.h> | ||||
| #include <libintl.h> | ||||
| #include <locale.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) | ||||
|  * passées au programme et interprétées via Argp | ||||
|  */ | ||||
| typedef struct Args { | ||||
|     int verbose; | ||||
|     char** files; | ||||
|     int compress; | ||||
|     FILE* source; | ||||
|     FILE* dest; | ||||
| } Args; | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -36,100 +24,118 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { | |||
|     Args* args = state->input; | ||||
| 
 | ||||
|     switch (key) { | ||||
|         case 'V': | ||||
|     case 'v': | ||||
|         args->verbose = TRUE; | ||||
|         break; | ||||
| 
 | ||||
|         case ARGP_KEY_ARG: | ||||
|         // Consommation de tous les fichiers en argument
 | ||||
|         // et arrêt du parsage
 | ||||
|         args->files = &state->argv[state->next - 1]; | ||||
|         state->next = state->argc; | ||||
|     case 'd': | ||||
|         args->compress = FALSE; | ||||
|         break; | ||||
| 
 | ||||
|         case ARGP_KEY_NO_ARGS: | ||||
|         argp_usage(state); | ||||
|         break; | ||||
|     case ARGP_KEY_ARG: | ||||
|         switch (state->arg_num) { | ||||
|         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: | ||||
|             argp_error( | ||||
|                 state, "Trop d'arguments - L'argument %s est superflu", | ||||
|                 arg | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         return ARGP_ERR_UNKNOWN; | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|     // Pour pouvoir afficher des caractères larges dans le terminal
 | ||||
|     setlocale(LC_CTYPE, ""); | ||||
|     // Paramètres de langue. Utilisation de la locale de l'utilisateur,
 | ||||
|     // 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,
 | ||||
|     // suivant la configuration définie au dessus
 | ||||
|     Args args; | ||||
|     // Valeurs par défaut des arguments optionnels
 | ||||
|     Args args = { | ||||
|         .verbose = FALSE, | ||||
|         .compress = TRUE, | ||||
|         .source = stdin, | ||||
|         .dest = stdout | ||||
|     }; | ||||
| 
 | ||||
|     args.verbose = FALSE; | ||||
|     args.files = NULL; | ||||
| 
 | ||||
|     argp_parse(&argp, argc, argv, 0, 0, &args); | ||||
|     // Interprétation des arguments passés au programme, résultat
 | ||||
|     // dans `args`. Arrêt en cas d'erreur
 | ||||
|     if (argp_parse(&argp, argc, argv, 0, 0, &args)) { | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     // Passage en mode verbeux si demandé
 | ||||
|     setVerbose(args.verbose); | ||||
| 
 | ||||
|     // Compression des fichiers en argument
 | ||||
|     char** files = args.files; | ||||
| 
 | ||||
|     while (*files != NULL) { | ||||
|         char* input = *files; | ||||
| 
 | ||||
|         // 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"); | ||||
|         } | ||||
|     // Compression ou décompression du flux d'entrée vers le flux de sortie
 | ||||
|     if (args.compress) { | ||||
|         compress(args.source, args.dest); | ||||
|     } else { | ||||
|         fprintf(stderr, "Décompression non-implémentée.\n"); | ||||
|         return EXIT_FAILURE; | ||||
|     } | ||||
| 
 | ||||
|     // Fermeture des flux et sortie
 | ||||
|     fclose(args.source); | ||||
|     fclose(args.dest); | ||||
| 
 | ||||
|     return EXIT_SUCCESS; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue