diff --git a/Makefile b/Makefile index b7ee761..2d27cf7 100644 --- a/Makefile +++ b/Makefile @@ -2,21 +2,28 @@ NAME = ft CC = clang CFLAGS = -Wall -Werror -Wextra -g -INCLUDE = -I ./app/include - -SRC = main.c -SRC += init_and_free_cli.c -SRC += is_valid_option.c -SRC += is_valid_arg.c -SRC += is_valid_name.c -SRC += input_validation.c -SRC += building_project.c -SRC += cli_utils.c -SRC += write_in_files.c - -OBJS = $(SRC:.c=.o) -OBJ = $(addprefix ./app/obj/, $(OBJS)) -OBJ_DIR = ./app/obj +INCLUDE = -I app/include + +# Fontes organizados por subdiretórios +SRC = \ + app/src/main.c \ + app/src/cli/init_and_free_cli.c \ + app/src/cli/is_valid_option.c \ + app/src/cli/is_valid_arg.c \ + app/src/cli/is_valid_name.c \ + app/src/cli/input_validation.c \ + app/src/core/building_project.c \ + app/src/core/write_in_files.c \ + app/src/utils/cli_utils.c \ + app/src/commands/cmd_new.c \ + app/src/dispatcher/dispatcher.c \ + app/src/parser/parser.c \ + app/src/fs/fs_create_dir.c \ + app/src/fs/fs_create_file.c \ + app/src/fs/fs_read_file.c + +OBJ_DIR = app/obj +OBJS = $(SRC:app/src/%.c=$(OBJ_DIR)/%.o) RM = rm -rf @@ -24,16 +31,19 @@ VPATH = ./app/src LOCAL_INSTALL = /usr/local/bin + +# Garante que o subdiretório de destino existe antes de compilar $(OBJ_DIR)/%.o: %.c - $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ all: obj_dir $(NAME) -$(NAME): $(OBJ) - $(CC) $(CFLAGS) $(OBJ) $(LIBFT) -o $(NAME) +$(NAME): $(OBJS) + $(CC) $(CFLAGS) $(OBJS) -o $(NAME) clean: - $(RM) $(OBJ) + $(RM) $(OBJS) $(RM) $(OBJ_DIR) fclean: clean @@ -41,14 +51,11 @@ fclean: clean install: $(NAME) cp $(NAME) $(LOCAL_INSTALL) + cp ./ft_templates/ $(LOCAL_INSTALL) -r re: fclean all obj_dir: - @if [ ! -d "$(OBJ_DIR)" ]; then\ - mkdir $(OBJ_DIR);\ - else\ - echo "make: Nothing to be done for 'all'.";\ - fi + @mkdir -p $(OBJ_DIR) .PHONY= all clean fclean re install $(NAME) diff --git a/README.md b/README.md index 4f97fd4..e0b565b 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,137 @@ ft new project minishell ![giff exemplo do ft cli](./assets/ft_cli_ex.gif) + +# Prompts + +Como podemos aprimorar este projeto? Por exemplo, que arquitetura de projeto poderiamos usar? E, como otimizar o código para facilitar a inclusão de novas funcionalidades? + +Como podemos aprimorar um projeto em C que é uma CLI para criar arquivos para começar um projeto em C? Hoje o projeto executa apenas o comando `ft new project minishell` criando os arquivos: minishell/app/includes/minishell.h, minishell/app/src/main.c, minishell/.gitignore, minishell/README.md e minishell/Makefile. Por exemplo, que arquitetura de projeto poderiamos usar? E, como otimizar o código para facilitar a inclusão de novas funcionalidades? + +# Projeto refatoração + +Já tememos uma CLI que cria um template básico. Agora a evolução natural é transformar isso em uma ferramenta extensível, modular e sustentável. + +## Separação sujerida: + +| Módulo | Responsabilidade | +| --------------- | ---------------------------- | +| parser | Interpretar argv | +| dispatcher | Chamar comando correto | +| commands | Lógica específica do comando | +| file_generator | Criar diretórios e arquivos | +| template_engine | Substituir variáveis | +| fs | Abstração de filesystem | + +## Implementar `Command Pattern` em C + +Mesmo em C, podemos simular orientação a objetos usando struct + function pointers. + +**Exemplo**: + +```C +//Nova estrutura para comando +typedef struct s_command +{ + const char *name; + int (*execute)(int argc, char **argv); +} t_command; + +//Registro de comandos, uma array de comandos +t_command commands[] = { + {"new", cmd_new}, + {"init", cmd_init}, + {NULL, NULL} +}; + +//Dispatcher +int dispatch_command(const char *cmd, int argc, char **argv) +{ + for (int i = 0; commands[i].name != NULL; i++) + { + if (strcmp(commands[i].name, cmd) == 0) + return commands[i].execute(argc, argv); + } + printf("Unknown command\n"); + return 1; +} +``` + +Com isso para adicionar um novo comando fazemos: + +1- Criar arquivo em `commands/` + +2- Registrar no array + +Zero impacto no core. + +## Orientações de projeto: + +1- Uma função = uma responsabilidade + +2- Evitar funções com mais de 30 linhas + +3- Evitar dependência circular entre módulos + +4- Header minimalista + +5- Nunca incluir header desnecessário + +## Ordem para execução de tarefas: + +Etapa 1: +Refatorar para Command Pattern + separação por módulos + +getopt(), getopt_long() + +Etapa 2: +Criar sistema de templates externo + +Etapa 3: +Criar arquitetura de projeto mais robusta + +Etapa 4: +Adicionar novos comandos + +Etapa 5: +Testes + documentação + +## Sugestão de comportamento + +```markdown +sequenceDiagram + participant User + participant Main + participant Parser + participant Dispatcher + participant CmdNew + participant FileGenerator + participant TemplateEngine + participant ft_fs + + User->>Main: ft new project minishell + + Main->>Parser: parse(argc, argv) + Parser-->>Main: CLIContext { command="new", args } + + Main->>Dispatcher: dispatch(context) + + Dispatcher->>CmdNew: execute(context) + + CmdNew->>FileGenerator: create_project_structure(context) + + FileGenerator->>ft_fs: create_dir("minishell/") + FileGenerator->>ft_fs: create_dir("minishell/app/") + FileGenerator->>ft_fs: create_dir("minishell/app/src/") + FileGenerator->>ft_fs: create_dir("minishell/app/includes/") + + CmdNew->>TemplateEngine: render("main.c.tpl", context) + TemplateEngine-->>CmdNew: rendered_main.c + + CmdNew->>FileGenerator: create_file(path, rendered_main.c) + + FileGenerator->>ft_fs: write_file(path, content) + + CmdNew-->>Dispatcher: SUCCESS + Dispatcher-->>Main: EXIT_SUCCESS +``` \ No newline at end of file diff --git a/app/include/command.h b/app/include/command.h new file mode 100644 index 0000000..f6beee8 --- /dev/null +++ b/app/include/command.h @@ -0,0 +1,14 @@ +#ifndef COMMAND_H +# define COMMAND_H + +# include +# include "ft_cli.h" + +int cmd_new_execute(t_cli_context *ctx); + +t_command g_commands[] = { + {"new", cmd_new_execute}, + {NULL, NULL} +}; + +#endif \ No newline at end of file diff --git a/app/include/fs.h b/app/include/fs.h new file mode 100644 index 0000000..2417504 --- /dev/null +++ b/app/include/fs.h @@ -0,0 +1,20 @@ +#ifndef FS_H +# define FS_H + +# include +# include +# include +# include +# include + +# include +# include +# include +# include + +int fs_create_dir(const char *path); +int fs_create_file(const char *path); +char *fs_read_file(const char *path); +// int fs_exists(const char *path); + +#endif \ No newline at end of file diff --git a/app/include/ft_cli.h b/app/include/ft_cli.h index 9cbc1ba..7e854f6 100644 --- a/app/include/ft_cli.h +++ b/app/include/ft_cli.h @@ -24,6 +24,24 @@ typedef struct s_cli char *include; } t_cli; +typedef struct s_cli_context +{ + char *command; + char *type; + char *project_name; + int verbose; +} t_cli_context; + +typedef int (*t_command_fn)(t_cli_context *ctx); + +typedef struct s_command +{ + const char *name; + t_command_fn execute; +} t_command; + + + void init_cli(t_cli *data, int argc, char **argv); void free_cli(t_cli *data); int is_valid_option(t_cli *data); @@ -38,4 +56,8 @@ void write_in_files(t_cli *data); char *join(char *s1, char *s2); char *strmapi(char const *s, int (*f)(int)); + +int parse_args(int argc, char **argv, t_cli_context *ctx); +int dispatch_command(t_cli_context *ctx); + #endif diff --git a/app/src/init_and_free_cli.c b/app/src/cli/init_and_free_cli.c similarity index 100% rename from app/src/init_and_free_cli.c rename to app/src/cli/init_and_free_cli.c diff --git a/app/src/input_validation.c b/app/src/cli/input_validation.c similarity index 100% rename from app/src/input_validation.c rename to app/src/cli/input_validation.c diff --git a/app/src/is_valid_arg.c b/app/src/cli/is_valid_arg.c similarity index 100% rename from app/src/is_valid_arg.c rename to app/src/cli/is_valid_arg.c diff --git a/app/src/is_valid_name.c b/app/src/cli/is_valid_name.c similarity index 100% rename from app/src/is_valid_name.c rename to app/src/cli/is_valid_name.c diff --git a/app/src/is_valid_option.c b/app/src/cli/is_valid_option.c similarity index 100% rename from app/src/is_valid_option.c rename to app/src/cli/is_valid_option.c diff --git a/app/src/commands/cmd_new.c b/app/src/commands/cmd_new.c new file mode 100644 index 0000000..4f48243 --- /dev/null +++ b/app/src/commands/cmd_new.c @@ -0,0 +1,198 @@ +#include "../../include/ft_cli.h" +#include "../../include/fs.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// Função para converter string para maiúscula +static char *str_to_upper(const char *str) +{ + char *result = malloc(strlen(str) + 1); + if (!result) + return NULL; + for (int i = 0; str[i]; i++) + result[i] = toupper((unsigned char)str[i]); + result[strlen(str)] = '\0'; + return result; +} + +// Função para substituir placeholders em um string +static char *replace_placeholder(const char *content, const char *placeholder, const char *replacement) +{ + size_t content_len = strlen(content); + size_t placeholder_len = strlen(placeholder); + size_t replacement_len = strlen(replacement); + + // Contar ocorrências + int count = 0; + const char *temp = content; + while ((temp = strstr(temp, placeholder)) != NULL) { + count++; + temp += placeholder_len; + } + + if (count == 0) + return strdup(content); + + // Alocar espaço para o novo conteúdo + size_t new_size = content_len + count * (replacement_len - placeholder_len) + 1; + char *result = malloc(new_size); + if (!result) + return NULL; + + // Fazer as substituições + const char *src = content; + char *dst = result; + const char *found; + + while ((found = strstr(src, placeholder)) != NULL) { + // Copiar tudo até o placeholder + size_t len = found - src; + strncpy(dst, src, len); + dst += len; + + // Copiar o replacement + strcpy(dst, replacement); + dst += replacement_len; + + // Avançar src + src = found + placeholder_len; + } + + // Copiar o resto + strcpy(dst, src); + return result; +} + +// Função para criar um diretório dentro do projeto +static void create_project_dir(const char *project_name, const char *subdir) +{ + char path[512]; + snprintf(path, sizeof(path), "%s/%s", project_name, subdir); + fs_create_dir(path); +} + +// Função para processar e escrever um template +static int process_template(const char *template_path, const char *output_path, + const char *project_name, const char *include_guard) +{ + char *content = fs_read_file(template_path); + if (!content) { + fprintf(stderr, "Erro ao ler template: %s\n", template_path); + return 1; + } + + // Fazer substituições + char *temp = replace_placeholder(content, "{{PROJECT_NAME}}", project_name); + free(content); + + char *final = replace_placeholder(temp, "{{INCLUDE_NAME}}", include_guard); + free(temp); + + // Escrever arquivo + FILE *out = fopen(output_path, "w"); + if (!out) { + fprintf(stderr, "Erro ao criar arquivo: %s\n", output_path); + free(final); + return 1; + } + + fputs(final, out); + fclose(out); + free(final); + + printf("Criado arquivo: %s\n", output_path); + return 0; +} + +// Função para processar múltiplos templates +static int process_templates(const char *exe_dir, const char *project_name, + const char *include_guard) +{ + struct { + const char *tpl_file; + const char *output_format; + } templates[] = { + {"include.tpl", "%s/app/includes/%s.h"}, + {"main.tpl", "%s/app/src/main.c"}, + {"makefile.tpl", "%s/Makefile"}, + {"readme.tpl", "%s/README.md"}, + {NULL, NULL} + }; + + for (int i = 0; templates[i].tpl_file; i++) { + char tpl_path[512]; + char output_path[512]; + + snprintf(tpl_path, sizeof(tpl_path), "%s/ft_templates/default/%s", + exe_dir, templates[i].tpl_file); + + // Tratamento especial para o header + if (strstr(templates[i].output_format, ".h")) { + snprintf(output_path, sizeof(output_path), templates[i].output_format, + project_name, project_name); + } else { + snprintf(output_path, sizeof(output_path), templates[i].output_format, + project_name); + } + + if (process_template(tpl_path, output_path, project_name, include_guard) != 0) + return 1; + } + + return 0; +} + +int cmd_new_execute(t_cli_context *ctx) +{ + printf("Executando comando NEW para projeto %s\n", ctx->project_name); + + // Obter caminho do executável + char exe_path[512]; + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len == -1) { + fprintf(stderr, "Erro ao obter caminho do executável\n"); + return 1; + } + exe_path[len] = '\0'; + char *exe_dir = dirname(exe_path); + + // Preparar nomes + char *project_upper = str_to_upper(ctx->project_name); + if (!project_upper) + return 1; + + char include_guard[256]; + snprintf(include_guard, sizeof(include_guard), "%s_H", project_upper); + free(project_upper); + + // Criar diretórios principais + const char *dirs[] = { + "app", + "app/includes", + "app/src", + "app/obj", + "app/tests", + "app/bin", + "doc", + "doc/assets", + NULL + }; + + fs_create_dir(ctx->project_name); + for (int i = 0; dirs[i]; i++) { + create_project_dir(ctx->project_name, dirs[i]); + } + + // Processar templates + if (process_templates(exe_dir, ctx->project_name, include_guard) != 0) + return 1; + + printf("Estrutura criada com sucesso em %s!\n", ctx->project_name); + return 0; +} \ No newline at end of file diff --git a/app/src/building_project.c b/app/src/core/building_project.c similarity index 100% rename from app/src/building_project.c rename to app/src/core/building_project.c diff --git a/app/src/write_in_files.c b/app/src/core/write_in_files.c similarity index 100% rename from app/src/write_in_files.c rename to app/src/core/write_in_files.c diff --git a/app/src/dispatcher/dispatcher.c b/app/src/dispatcher/dispatcher.c new file mode 100644 index 0000000..9c256dd --- /dev/null +++ b/app/src/dispatcher/dispatcher.c @@ -0,0 +1,16 @@ +#include "ft_cli.h" +#include "command.h" +#include +#include + +int dispatch_command(t_cli_context *ctx) +{ + for (int i = 0; g_commands[i].name != NULL; i++) + { + if (strcmp(g_commands[i].name, ctx->command) == 0) + return g_commands[i].execute(ctx); + } + + printf("Comando desconhecido: %s\n", ctx->command); + return 1; +} \ No newline at end of file diff --git a/app/src/fs/fs_create_dir.c b/app/src/fs/fs_create_dir.c new file mode 100644 index 0000000..929e1f7 --- /dev/null +++ b/app/src/fs/fs_create_dir.c @@ -0,0 +1,11 @@ +#include + +int fs_create_dir(const char *path) +{ + if (mkdir(path, 0755) == -1) + { + perror("Error creating directory"); + return -1; + } + return 0; +} \ No newline at end of file diff --git a/app/src/fs/fs_create_file.c b/app/src/fs/fs_create_file.c new file mode 100644 index 0000000..b951a95 --- /dev/null +++ b/app/src/fs/fs_create_file.c @@ -0,0 +1,13 @@ +#include + +int fs_create_file(const char *path) +{ + FILE *file = fopen(path, "w"); + if (file == NULL) + { + perror("Error creating file"); + return -1; + } + fclose(file); + return 0; +} \ No newline at end of file diff --git a/app/src/fs/fs_read_file.c b/app/src/fs/fs_read_file.c new file mode 100644 index 0000000..756b474 --- /dev/null +++ b/app/src/fs/fs_read_file.c @@ -0,0 +1,24 @@ +#include + +// Função para ler um arquivo inteiro +char *fs_read_file(const char *path) +{ + FILE *file = fopen(path, "r"); + if (!file) + return NULL; + + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + + char *content = malloc(size + 1); + if (!content) { + fclose(file); + return NULL; + } + + fread(content, 1, size, file); + content[size] = '\0'; + fclose(file); + return content; +} \ No newline at end of file diff --git a/app/src/main.c b/app/src/main.c index 207d166..8ff8621 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -6,23 +6,34 @@ static void fallback_msg(void) dprintf(STDERR_FILENO, FALLBACK); } -int main(int argc, char **argv) +// int main(int argc, char **argv) +// { +// t_cli data; + +// if (argc == 2 && !strcmp(argv[1], "--help")) +// { +// dprintf(1, HELP); +// return (0); +// } +// if (argc != 4) +// return (fallback_msg() , 1); +// init_cli(&data, argc, argv); +// if (is_valid_input(&data)) +// { +// building_project(&data); +// write_in_files(&data); +// } +// free_cli(&data); +// return (0); +// } + + +int main(int argc, char **argv) { - t_cli data; - - if (argc == 2 && !strcmp(argv[1], "--help")) - { - dprintf(1, HELP); - return (0); - } - if (argc != 4) + t_cli_context ctx; + + if (parse_args(argc, argv, &ctx)) return (fallback_msg() , 1); - init_cli(&data, argc, argv); - if (is_valid_input(&data)) - { - building_project(&data); - write_in_files(&data); - } - free_cli(&data); - return (0); -} + + return dispatch_command(&ctx); +} \ No newline at end of file diff --git a/app/src/parser/parser.c b/app/src/parser/parser.c new file mode 100644 index 0000000..9a6c61e --- /dev/null +++ b/app/src/parser/parser.c @@ -0,0 +1,14 @@ +#include "ft_cli.h" + +int parse_args(int argc, char **argv, t_cli_context *ctx) +{ + if (argc != 4) + return 1; + + ctx->command = argv[1]; + ctx->type = argv[2]; + ctx->project_name = argv[3]; + ctx->verbose = 0; + + return 0; +} \ No newline at end of file diff --git a/app/src/cli_utils.c b/app/src/utils/cli_utils.c similarity index 100% rename from app/src/cli_utils.c rename to app/src/utils/cli_utils.c diff --git a/ft_templates/default/include.tpl b/ft_templates/default/include.tpl new file mode 100644 index 0000000..f960ae6 --- /dev/null +++ b/ft_templates/default/include.tpl @@ -0,0 +1,11 @@ +#ifndef {{INCLUDE_NAME}} +# define {{INCLUDE_NAME}} + +//Includes +# include + +//Estruturas + +//Funções + +#endif // {{INCLUDE_NAME}} \ No newline at end of file diff --git a/ft_templates/default/main.tpl b/ft_templates/default/main.tpl new file mode 100644 index 0000000..8aabc8b --- /dev/null +++ b/ft_templates/default/main.tpl @@ -0,0 +1,7 @@ +#include "{{PROJECT_NAME}}.h" + +int main(void) +{ + printf("Hello {{PROJECT_NAME}}!\n"); + return (0); +} \ No newline at end of file diff --git a/ft_templates/default/makefile.tpl b/ft_templates/default/makefile.tpl new file mode 100644 index 0000000..5a59f03 --- /dev/null +++ b/ft_templates/default/makefile.tpl @@ -0,0 +1,43 @@ +NAME = {{PROJECT_NAME}} +CC = clang +CFLAGS = -Wall -Werror -Wextra -g +INCLUDE = -I ./app/includes + +SRC = main.c + +OBJS = $(notdir $(SRC:.c=.o)) +OBJ = $(addprefix ./app/obj/, $(OBJS)) +OBJ_DIR = ./app/obj + +RM = rm -rf + +VPATH = ./app/src + +$(OBJ_DIR)/%.o: app/src/%.c + $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ + +all: obj_dir $(NAME) + +$(NAME): $(OBJ) + $(CC) $(CFLAGS) $(OBJ) $(LIBFT) -o app/bin/$(NAME) + cp app/bin/$(NAME) ./$(NAME) + +clean: + $(RM) $(OBJ) + +fclean: + $(RM) $(OBJ) + $(RM) $(OBJ_DIR) + $(RM) app/bin/$(NAME) + $(RM) $(NAME) + +re: fclean all + +obj_dir: + @if [ ! -d "$(OBJ_DIR)" ]; then\ + mkdir $(OBJ_DIR);\ + else\ + echo "make: Nothing to be done for 'all'.";\ + fi + +.PHONY= all clean fclean re $(NAME) \ No newline at end of file diff --git a/ft_templates/default/readme.tpl b/ft_templates/default/readme.tpl new file mode 100644 index 0000000..66927e1 --- /dev/null +++ b/ft_templates/default/readme.tpl @@ -0,0 +1 @@ +# Hello {{PROJECT_NAME}}