diff --git a/README.md b/README.md index 24b5291..522c10a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,48 @@ # hdoc -Emula um dos usos do mecanismo de "here doc" (didático) \ No newline at end of file +Emula um dos usos do mecanismo de "here doc" (didático). Equivale à linhas de comando como: + +``` + here document + ↓ +cat << END > arquivo +``` + +Mas... + +- Não utiliza uma palavra de término (termina com `Ctrl+D`); +- Por padrão, escreve apenas em novos arquivos (termina com erro se o arquivo existir); +- Com opções, a sobrescrita ou o *append* de arquivos existentes pode ser forçada. + +## Como utilizar + +``` +Here Document (hdoc) 0.1 + +Uso: hdoc [OPÇÕES] ARQUIVO + +Sem OPÇÕES, cria um novo ARQUIVO para receber linhas +digitadas no terminal. + +OPÇÕES: + -o Força abertura de ARQUIVO para sobrescrevê-lo. + -a Força abertura de ARQUIVO para append. + -h Exibe esta ajuda. +``` + +## Como compilar + +No diretório da sua escolha... + +``` +:~$ git clone https://bolha.dev/blau_araujo/hdoc.git +:~$ cd hdoc +:~/hdoc$ gcc -Wall -Wextra hdoc.c messages.c -o hdoc +``` + +O executável (`hdoc`) pode ser movido ou linkado para um diretório pessoal de executáveis ou para `/usr/local/bin`. + +## Importante! + +Este programa é apenas um exemplo didático para as pessoas que estão aprendendo C com o [Curso Básico da Linguagem C](https://www.youtube.com/playlist?list=PLXoSGejyuQGrDX08GVrQHAhh4j3KJ4iYN): use por sua conta e risco! + diff --git a/hdoc.c b/hdoc.c new file mode 100644 index 0000000..d274cf4 --- /dev/null +++ b/hdoc.c @@ -0,0 +1,120 @@ +/* + * Compilar com: gcc -Wall hdoc.c messages.c -o hdoc + */ +#include +#include +#include +#include + +#include "messages.h" + +typedef enum { + OPT_ERROR = -1, + OPT_NAME, + OPT_OVERWRITE, + OPT_APPEND, + OPT_HELP +} Option; + +int parse_arg(char *arg); // Trata argumentos na linha de comando + +int main(int argc, char **argv) { + + char *file = NULL; + char *mode = NULL; + + // Verificação do número de argumentos... + if (argc == 1 || argc > 3) { + // Só pode haver 2 ou 3 argumentos... + print_error(MSG_ARGC_ERROR); + return print_usage(EXIT_FAILURE, argv[0]); + } else if (argc == 2) { + // Invocação com 2 argumentos... + switch (parse_arg(argv[1])) { + case OPT_HELP: + // Imprime ajuda e termina... + return print_usage(EXIT_SUCCESS, argv[0]); + case OPT_NAME: + // Arquivo existe? + if (access(argv[1], F_OK) == 0) { + // Sem opções, só arquivos novos! + print_error(MSG_FILE_EXISTS); + return print_usage(EXIT_FAILURE, argv[0]); + } + // Nome do novo arquivo... + file = argv[1]; + mode = "w"; + break; + default: + // Nome do arquivo não pode começar com '-'! + print_error(MSG_INVALID_ARG); + return print_usage(EXIT_FAILURE, argv[0]); + } + } else { + // Invocação com 3 argumentos... + for (int i = 1; argv[i] != NULL; i++) { + switch (parse_arg(argv[i])) { + case OPT_NAME: + // Só pode haver uma definição de nome... + if (file == NULL) file = argv[i]; + break; + case OPT_OVERWRITE: + // Só pode haver uma definição de modo... + if (mode == NULL) mode = "w"; + break; + case OPT_APPEND: + // Só pode haver uma definição de modo... + if (mode == NULL) mode = "a"; + break; + case OPT_HELP: + // Exibe ajuda e termina com sucesso... + return print_usage(EXIT_SUCCESS, argv[0]); + case OPT_ERROR: + // Algum dos argumentos é inválido... + print_error_fmt(MSG_INVALID_FMT, argv[i]); + return print_usage(EXIT_FAILURE, argv[0]); + } + } + // Tem que haver um nome e um modo depois do loop... + if (file == NULL || mode == NULL) { + print_error(MSG_INCORRECT_ALL); + return print_usage(EXIT_FAILURE, argv[0]); + } + } + + FILE *fstr = fopen(file, mode); + if (!fstr) { + perror(MSG_FOPEN_ERROR); + return EXIT_FAILURE; + } + + char line[BUFSIZ]; + puts(MSG_EOF_HINT); + while (1) { + printf(MSG_PROMPT); + if (fgets(line, BUFSIZ, stdin) == NULL) break; + fprintf(fstr, "%s", line); + } + + fclose(fstr); + + return EXIT_SUCCESS; +} + +int parse_arg(char *arg) { + // Argumento iniciado com '-'... + if (arg[0] == '-') { + if (strcmp(arg, "-a") == 0) { + return OPT_APPEND; + } else if (strcmp(arg, "-o") == 0) { + return OPT_OVERWRITE; + } else if (strcmp(arg, "-h") == 0) { + return OPT_HELP; + } else { + // Argumento inválido! + return OPT_ERROR; + } + } + // Sem traço é nome de arquivo... + return OPT_NAME; +} diff --git a/messages.c b/messages.c new file mode 100644 index 0000000..92e9af7 --- /dev/null +++ b/messages.c @@ -0,0 +1,63 @@ +#include +#include +#include + +#include "messages.h" + +// Mensagens de uso e ajuda +const char *MSG_USAGE = + "Here Document (hdoc) 0.1\n\n" + "Uso: %s [OPÇÕES] ARQUIVO\n\n" + "Sem OPÇÕES, cria um novo ARQUIVO para receber linhas\n" + "digitadas no terminal.\n\n" + "OPÇÕES:\n" + " -o Força abertura de ARQUIVO para sobrescrevê-lo.\n" + " -a Força abertura de ARQUIVO para append.\n" + " -h Exibe esta ajuda.\n\n"; + +const char *MSG_EOF_HINT = "Tecle Ctrl+D para terminar..."; +const char *MSG_PROMPT = "> "; + +// Mensagens de erro +const char *MSG_ARGC_ERROR = "Número incorreto de argumentos!\n\n"; +const char *MSG_FILE_EXISTS = "Arquivo já existe!\n\n"; +const char *MSG_INVALID_ARG = "Argumento inválido!\n\n"; +const char *MSG_INVALID_FMT = "Argumento inválido (%s)!\n\n"; +const char *MSG_INCORRECT_ALL = "Argumentos incorretos!\n\n"; +const char *MSG_FOPEN_ERROR = "Erro na abertura do arquivo!\n\n"; + +// Estilo ANSI: negrito + amarelo +#define STYLE_ERROR "\033[1;33m" +#define STYLE_RESET "\033[0m" + +// Retorna sucesso se stderr estiver ligada ao terminal. +int is_tty_stderr() { + return isatty(fileno(stderr)); +} + +// Imprime mensagens de erro sem argumentos. +void print_error(const char *msg) { + if (is_tty_stderr()) + fprintf(stderr, STYLE_ERROR "%s" STYLE_RESET, msg); + else + fprintf(stderr, "%s", msg); +} + +// Imprime mensagens de erro com argumentos. +void print_error_fmt(const char *fmt, const char *arg) { + if (is_tty_stderr()) { + fprintf(stderr, "%s", STYLE_ERROR); + fprintf(stderr, fmt, arg); + fprintf(stderr, "%s", STYLE_RESET); + } else { + fprintf(stderr, fmt, arg); + } +} + +// Imprime versão e ajuda e retorna status. +int print_usage(int status, char *prog_name) { + FILE *stream = stderr; + if (status == EXIT_SUCCESS) stream = stdout; + fprintf(stream, MSG_USAGE, prog_name); + return status; +} diff --git a/messages.h b/messages.h new file mode 100644 index 0000000..c9e0dec --- /dev/null +++ b/messages.h @@ -0,0 +1,28 @@ +/* + * Strings definidas em messages.c + */ +#ifndef MESSAGES_H +#define MESSAGES_H + +// Mensagens de uso e ajuda +extern const char *MSG_USAGE; +extern const char *MSG_EOF_HINT; +extern const char *MSG_PROMPT; + +// Mensagens de erro +extern const char *MSG_ARGC_ERROR; +extern const char *MSG_FILE_EXISTS; +extern const char *MSG_INVALID_ARG; +extern const char *MSG_INVALID_FMT; +extern const char *MSG_INCORRECT_ALL; +extern const char *MSG_FOPEN_ERROR; + +// Impressão estilizada condicional +int is_tty_stderr(void); +void print_error(const char *msg); +void print_error_fmt(const char *fmt, const char *arg); + +// Impressão da ajuda +int print_usage(int status, char *prog_name); + +#endif // MESSAGES_H