diff --git a/.github/workflows/doccheck.yml b/.github/workflows/doccheck.yml new file mode 100644 index 0000000..e0ce097 --- /dev/null +++ b/.github/workflows/doccheck.yml @@ -0,0 +1,16 @@ +name: Docstring Check +on: + pull_request: + push: + branches: [ main ] + +jobs: + doccheck: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build doc_check tool + run: gcc -std=c99 -Wall -Wextra -o tools/doc_check tools/doc_check.c + - name: Run docstring check + run: tools/doc_check src/*.c src/*.h src/*.cpp diff --git a/src/base64.h b/src/base64.h index 2ed8c69..835d537 100644 --- a/src/base64.h +++ b/src/base64.h @@ -5,7 +5,12 @@ #include #include -// Base64 decoding function +/** + * @brief Decode a base64-encoded string. + * + * @param base64_str Input base64 string. + * @return Decoded binary data as a string. + */ inline std::string base64_decode(const std::string& base64_str) { const char* base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -61,7 +66,12 @@ inline std::string base64_decode(const std::string& base64_str) { } -// Base64 encoding function +/** + * @brief Encode binary data as base64. + * + * @param input Raw input string. + * @return Base64 encoded representation. + */ inline std::string base64_encode(const std::string& input) { const char* base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/src/dirdoc.c b/src/dirdoc.c index b0be9fd..882ce31 100644 --- a/src/dirdoc.c +++ b/src/dirdoc.c @@ -156,10 +156,14 @@ int main(int argc, char *argv[]) { } #endif /* !defined(UNIT_TEST) */ -/* - * Implementation of get_default_output() - * Returns a default output filename based on the input directory. - * The returned string is dynamically allocated and must be freed by the caller. +/** + * @brief Returns a default output filename based on the input directory. + * + * Generates a markdown filename based on the provided directory name. The + * returned string is dynamically allocated and must be freed by the caller. + * + * @param input_dir The path of the directory being documented. + * @return char* Newly allocated output filename. */ char *get_default_output(const char *input_dir) { const char *default_name = "directory_documentation.md"; diff --git a/src/dirdoc.h b/src/dirdoc.h index 8274838..5a944d9 100644 --- a/src/dirdoc.h +++ b/src/dirdoc.h @@ -28,13 +28,26 @@ typedef struct { size_t total_tokens; } DocumentInfo; -/* Main documentation generation function. - * Returns 0 on success, non-zero on failure. +/** + * @brief Main documentation generation function. + * + * Scans a directory and writes a markdown description of its structure and + * optionally its file contents. + * + * @param input_dir The directory to document. + * @param output_file Output markdown path or NULL for default. + * @param flags Combination of option flags (e.g., STRUCTURE_ONLY). + * @return int 0 on success, non-zero on failure. */ int document_directory(const char *input_dir, const char *output_file, int flags); -/* Returns a default output filename based on the input directory. - * The returned string is dynamically allocated and must be freed by the caller. +/** + * @brief Builds a default output filename based on the input directory. + * + * The returned string must be freed by the caller. + * + * @param input_dir Directory being documented. + * @return char* Newly allocated output filename. */ char *get_default_output(const char *input_dir); diff --git a/src/gitignore.c b/src/gitignore.c index 0e8095f..29ffc00 100644 --- a/src/gitignore.c +++ b/src/gitignore.c @@ -8,7 +8,15 @@ #define MAX_LINE_LENGTH 1024 -// Helper: Escapes regex special characters in a string. +/** + * @brief Escapes regex special characters in a string. + * + * Allocates and returns a new string where characters that have special meaning + * in regular expressions are prefixed with a backslash. + * + * @param str Input string to escape. + * @return char* Newly allocated escaped string or NULL on failure. + */ static char *escape_regex(const char *str) { size_t len = strlen(str); char *escaped = (char*)malloc(2 * len + 1); // worst-case: every character escaped @@ -24,12 +32,16 @@ static char *escape_regex(const char *str) { return escaped; } -// Helper: Translates a gitignore pattern into a POSIX regular expression. -// This simplified translator replaces: -// - "**" with ".*" (matching across directories) -// - "*" with "[^/]*" (matching within a single directory) -// - "?" with "." (any single character) -// Change the function signature to accept dir_only flag +/** + * @brief Convert a gitignore pattern to a POSIX regular expression. + * + * This simplified translator handles `**`, `*` and `?` wildcards and optionally + * appends a directory-only suffix when `dir_only` is true. + * + * @param pattern The gitignore pattern string. + * @param dir_only Non-zero if the pattern applies only to directories. + * @return char* Newly allocated regex string or NULL on failure. + */ static char *translate_gitignore_pattern(const char *pattern, bool dir_only) { size_t len = strlen(pattern); // Allocate a buffer that is large enough for worst-case expansion. @@ -87,9 +99,14 @@ static char *translate_gitignore_pattern(const char *pattern, bool dir_only) { return regex_pattern; } -// Update the parse_gitignore_line function to pass dir_only to translate_gitignore_pattern -/* Parses a single gitignore pattern string and adds it to the provided GitignoreList. - * Returns 0 on success, -1 on error. +/** + * @brief Parse a single gitignore pattern string and add it to a list. + * + * This wrapper is shared by the line-based parser and the command line handler. + * + * @param pattern_str The gitignore pattern text. + * @param list Gitignore list to append the compiled rule to. + * @return int 0 on success, -1 on error. */ int parse_gitignore_pattern_string(const char *pattern_str, GitignoreList *list) { if (!pattern_str || !list) return -1; @@ -160,6 +177,13 @@ int parse_gitignore_pattern_string(const char *pattern_str, GitignoreList *list) return 0; } +/** + * @brief Parse a single line from a .gitignore file. + * + * @param line The raw line text. + * @param list Gitignore list to update. + * @return int 0 on success, -1 on error. + */ static int parse_gitignore_line(const char *line, GitignoreList *list) { // Skip leading whitespace. while (*line && isspace((unsigned char)*line)) line++; @@ -180,6 +204,12 @@ static int parse_gitignore_line(const char *line, GitignoreList *list) { return result; } +/** + * @brief Load rules from a .gitignore file located in a directory. + * + * @param dir_path Directory that may contain a .gitignore file. + * @param gitignore Gitignore list to populate. + */ void load_gitignore(const char *dir_path, GitignoreList *gitignore) { gitignore->rules = NULL; gitignore->count = 0; @@ -203,6 +233,13 @@ void load_gitignore(const char *dir_path, GitignoreList *gitignore) { fclose(f); } +/** + * @brief Determine if a path should be ignored by gitignore rules. + * + * @param path Relative path to test. + * @param gitignore Compiled gitignore rule list. + * @return true if the path is ignored, false otherwise. + */ bool match_gitignore(const char *path, const GitignoreList *gitignore) { if (!gitignore || gitignore->count == 0) return false; @@ -218,6 +255,9 @@ bool match_gitignore(const char *path, const GitignoreList *gitignore) { return ignored; } +/** + * @brief Free memory associated with a GitignoreList. + */ void free_gitignore(GitignoreList *gitignore) { if (!gitignore) return; for (size_t i = 0; i < gitignore->count; i++) { diff --git a/src/gitignore.h b/src/gitignore.h index 8d3f0d1..6afcda9 100644 --- a/src/gitignore.h +++ b/src/gitignore.h @@ -21,22 +21,34 @@ typedef struct { size_t capacity; } GitignoreList; -/* Loads the .gitignore file from the specified directory, - * parses each rule (supporting ** patterns, negation, etc.), and compiles a regex for each. +/** + * @brief Load gitignore rules from a directory. + * + * @param dir_path Directory containing a .gitignore file. + * @param gitignore Gitignore list to populate. */ void load_gitignore(const char *dir_path, GitignoreList *gitignore); -/* Parses a single gitignore pattern string and adds it to the provided GitignoreList. - * Returns 0 on success, -1 on error. +/** + * @brief Add a gitignore pattern string to a list. + * + * @param pattern Pattern text. + * @param list Gitignore list to modify. + * @return int 0 on success, -1 on error. */ int parse_gitignore_pattern_string(const char *pattern, GitignoreList *list); -/* Checks whether the given path should be ignored based on the compiled gitignore rules. - * Returns true if the path should be ignored. +/** + * @brief Check if a path matches gitignore rules. + * + * @param path Relative path to test. + * @param gitignore Compiled gitignore list. + * @return true if the path should be ignored. */ bool match_gitignore(const char *path, const GitignoreList *gitignore); -/* Frees all memory allocated for the GitignoreList and releases compiled regex resources. +/** + * @brief Free resources associated with a GitignoreList. */ void free_gitignore(GitignoreList *gitignore); diff --git a/src/reconstruct.c b/src/reconstruct.c index c209c0c..ab4c760 100644 --- a/src/reconstruct.c +++ b/src/reconstruct.c @@ -7,6 +7,12 @@ #include "reconstruct.h" #include "dirdoc.h" // for MAX_PATH_LEN and BUFFER_SIZE +/** + * @brief Recursively create directories for a given path. + * + * @param path Directory path to create. + * @return int 0 on success, non-zero on failure. + */ static int mkdirs(const char *path) { char tmp[MAX_PATH_LEN]; snprintf(tmp, sizeof(tmp), "%s", path); @@ -25,6 +31,13 @@ static int mkdirs(const char *path) { return mkdir(tmp, 0755); // final component } +/** + * @brief Check if a line begins a fenced code block. + * + * @param line Input line from the markdown file. + * @param len Output parameter storing the fence length. + * @return int Non-zero if the line begins a fence. + */ static int is_fence_start(const char *line, int *len) { int i = 0; while (line[i] == '`') i++; @@ -35,6 +48,13 @@ static int is_fence_start(const char *line, int *len) { return 0; } +/** + * @brief Check if a line ends a fenced code block of a given length. + * + * @param line Input line from the markdown file. + * @param len Number of backticks that opened the fence. + * @return int Non-zero if this line closes the fence. + */ static int is_fence_end(const char *line, int len) { for (int i = 0; i < len; i++) { if (line[i] != '`') return 0; @@ -43,6 +63,13 @@ static int is_fence_end(const char *line, int len) { return c == '\n' || c == '\0' || c == '\r'; } +/** + * @brief Reconstruct files from a dirdoc-generated markdown document. + * + * @param md_path Path to the documentation markdown. + * @param out_dir Output directory for reconstructed files. + * @return int 0 on success, non-zero on failure. + */ int reconstruct_from_markdown(const char *md_path, const char *out_dir) { FILE *in = fopen(md_path, "r"); if (!in) { diff --git a/src/reconstruct.h b/src/reconstruct.h index 0854ef1..035a8e2 100644 --- a/src/reconstruct.h +++ b/src/reconstruct.h @@ -5,10 +5,12 @@ extern "C" { #endif -/* Reconstructs a directory from a dirdoc generated markdown file. - * md_path: path to the documentation markdown - * out_dir: directory to create reconstructed files - * Returns 0 on success, non-zero on failure. +/** + * @brief Reconstruct a directory from a dirdoc markdown file. + * + * @param md_path Path to the documentation markdown. + * @param out_dir Output directory to write reconstructed files. + * @return int 0 on success, non-zero on failure. */ int reconstruct_from_markdown(const char *md_path, const char *out_dir); diff --git a/src/scanner.h b/src/scanner.h index 8a15655..e7cf872 100644 --- a/src/scanner.h +++ b/src/scanner.h @@ -11,33 +11,43 @@ typedef struct { size_t capacity; } FileList; -/* Initializes the given FileList structure. */ +/** + * @brief Initialize a FileList structure. + */ void init_file_list(FileList *list); -/* Adds a new file entry to the FileList. - * @param list: Pointer to the FileList. - * @param path: Relative path of the file or directory. - * @param is_dir: Boolean indicating if the entry is a directory. - * @param depth: Depth level in the directory hierarchy. +/** + * @brief Add a new file entry to a FileList. + * + * @param list List to update. + * @param path Relative path of the file or directory. + * @param is_dir Non-zero if the entry is a directory. + * @param depth Depth level within the tree. */ void add_file_entry(FileList *list, const char *path, bool is_dir, int depth); -/* Frees all memory associated with the FileList. */ +/** + * @brief Free memory used by a FileList. + */ void free_file_list(FileList *list); -/* Recursively scans the directory at dir_path and populates the FileList. - * @param dir_path: The absolute path of the directory to scan. - * @param rel_path: The relative path from the root directory (can be NULL). - * @param list: Pointer to the FileList to populate. - * @param depth: Current depth level. - * @param gitignore: Pointer to a GitignoreList structure (can be NULL). - * @param flags: Flags controlling scanning behavior (e.g., INCLUDE_GIT) - * @return: true if scanning was successful, false otherwise. +/** + * @brief Recursively scan a directory and populate a FileList. + * + * @param dir_path Absolute path to scan. + * @param rel_path Relative path from the root directory. + * @param list Output FileList. + * @param depth Current recursion depth. + * @param gitignore Optional gitignore rule list. + * @param flags Flags controlling scanning behavior. + * @return true if entries were found, otherwise false. */ bool scan_directory(const char *dir_path, const char *rel_path, FileList *list, int depth, const GitignoreList *gitignore, int flags); -/* Comparison function for qsort to order FileEntry items. - * @return: Negative if a < b, zero if equal, positive if a > b. +/** + * @brief Comparison function for ordering FileEntry items. + * + * @return Negative if a < b, zero if equal, positive if a > b. */ int compare_entries(const void *a, const void *b); diff --git a/src/tiktoken.c b/src/tiktoken.c index 930274f..e29f564 100644 --- a/src/tiktoken.c +++ b/src/tiktoken.c @@ -15,15 +15,34 @@ extern void tiktoken_cleanup(void); // External init function from C++ extern bool tiktoken_cpp_init(void); -// Global initialization function +/** + * @brief Initialize the underlying tiktoken library. + * + * @return true on success, false otherwise. + */ bool tiktoken_init(void) { return tiktoken_cpp_init(); } +/** + * @brief Obtain a tiktoken encoding by name. + * + * @param encoding_name Encoding identifier, e.g. "cl100k_base". + * @return tiktoken_t Encoding handle or NULL on error. + */ tiktoken_t tiktoken_get_encoding(const char* encoding_name) { return (tiktoken_t)tiktoken_cpp_get_encoding(encoding_name); } +/** + * @brief Encode a string into tokens. + * + * @param encoding Encoding handle. + * @param text Text to encode. + * @param text_len Length of text. + * @param tokens_out Output array of tokens (allocated with malloc()). + * @return int Number of tokens or -1 on error. + */ int tiktoken_encode(tiktoken_t encoding, const char* text, size_t text_len, tiktoken_token_t** tokens_out) { if (encoding == NULL || text == NULL || tokens_out == NULL) { return -1; @@ -32,6 +51,14 @@ int tiktoken_encode(tiktoken_t encoding, const char* text, size_t text_len, tikt return tiktoken_cpp_encode((TiktokenWrapper*)encoding, text, text_len, tokens_out); } +/** + * @brief Count tokens in a string without returning them. + * + * @param encoding Encoding handle. + * @param text Text to count. + * @param text_len Length of text. + * @return int Number of tokens or -1 on error. + */ int tiktoken_count(tiktoken_t encoding, const char* text, size_t text_len) { if (encoding == NULL || text == NULL) { return -1; @@ -40,13 +67,20 @@ int tiktoken_count(tiktoken_t encoding, const char* text, size_t text_len) { return tiktoken_cpp_count((TiktokenWrapper*)encoding, text, text_len); } +/** + * @brief Free an encoding instance. + * + * @param encoding Encoding handle to free. + */ void tiktoken_free(tiktoken_t encoding) { if (encoding != NULL) { tiktoken_cpp_free((TiktokenWrapper*)encoding); } } -// Register a cleanup function to be called at program exit +/** + * @brief Register cleanup of global resources at process exit. + */ static void __attribute__((constructor)) tiktoken_register_cleanup(void) { atexit(tiktoken_cleanup); } diff --git a/src/writer.c b/src/writer.c index cd977e3..e8faa52 100644 --- a/src/writer.c +++ b/src/writer.c @@ -653,6 +653,9 @@ int document_directory(const char *input_dir, const char *output_file, int flags return 0; } +/** + * @brief Frees memory allocated for extra ignore patterns. + */ void free_extra_ignore_patterns() { if (g_extra_ignore_patterns) { for (int i = 0; i < g_extra_ignore_count; i++) { diff --git a/src/writer.h b/src/writer.h index e39d880..7e49f6e 100644 --- a/src/writer.h +++ b/src/writer.h @@ -7,52 +7,61 @@ #include "stats.h" #include "gitignore.h" -/* Writes the tree structure representation to the given file stream. - * @param out: Output file stream. - * @param list: Pointer to the FileList containing scanned entries. - * @param info: Pointer to DocumentInfo for accumulating token and size statistics. +/** + * @brief Write the directory tree structure to a file stream. + * + * @param out Output file stream. + * @param list List of scanned file entries. + * @param info Document statistics accumulator. */ void write_tree_structure(FILE *out, FileList *list, DocumentInfo *info); -/* Writes the content of a file with fenced code blocks to the output stream. - * @param out: Output file stream. - * @param path: Path of the file to write. - * @param info: Pointer to DocumentInfo for accumulating token and size statistics. +/** + * @brief Write a file's contents to the output stream using fenced code blocks. + * + * @param out Output file stream. + * @param path Path of the file to write. + * @param info Document statistics accumulator. */ void write_file_content(FILE *out, const char *path, DocumentInfo *info); -/* Finalizes the output file by prepending token statistics. - * If split output is enabled and the file exceeds the specified limit, - * the output will be split into multiple files with increasing number suffixes. - * @param out_path: Path of the output file. - * @param info: Pointer to DocumentInfo containing the computed statistics. - * @return: 0 on success, non-zero on failure. +/** + * @brief Finalize the output file and optionally split it by size. + * + * @param out_path Path of the output file. + * @param info Document statistics. + * @return int 0 on success, non-zero on failure. */ int finalize_output(const char *out_path, DocumentInfo *info); -/* Sets the split output options. - * @param enabled: Non-zero to enable splitting. - * @param limit_mb: Maximum size in MB per output file. +/** + * @brief Configure splitting of the output file. + * + * @param enabled Non-zero to enable splitting. + * @param limit_mb Maximum size per file in MB. */ void set_split_options(int enabled, double limit_mb); -/* Sets extra ignore patterns to be applied during directory scanning. - * @param patterns: Array of pattern strings. - * @param count: Number of patterns. +/** + * @brief Set additional ignore patterns for directory scanning. + * + * @param patterns Array of pattern strings. + * @param count Number of patterns. */ void set_extra_ignore_patterns(char **patterns, int count); /** - * @brief Frees memory allocated for extra ignore patterns. - * - * This function releases all memory allocated for the global extra ignore patterns - * that were set using set_extra_ignore_patterns(). It should be called when - * these patterns are no longer needed to prevent memory leaks. + * @brief Free memory allocated for extra ignore patterns. */ - void free_extra_ignore_patterns(); +void free_extra_ignore_patterns(); -/* Main documentation generation function. - * Returns 0 on success, non-zero on failure. +/** + * @brief Generate documentation for a directory. + * + * @param input_dir Directory to document. + * @param output_file Output markdown path or NULL for default. + * @param flags Flags controlling the documentation process. + * @return int 0 on success, non-zero on failure. */ int document_directory(const char *input_dir, const char *output_file, int flags); diff --git a/tools/doc_check.c b/tools/doc_check.c new file mode 100644 index 0000000..f5dc7bc --- /dev/null +++ b/tools/doc_check.c @@ -0,0 +1,94 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include + +static int lstrip_index(const char *s) { + int i = 0; + while (s[i] == ' ' || s[i] == '\t') i++; + return i; +} + +static int has_doc_comment(char **lines, int idx) { + for (int j = idx - 1; j >= 0; j--) { + const char *line = lines[j]; + int i = lstrip_index(line); + if (line[i] == '\0') continue; + if (line[i] == '*' || + (line[i] == '/' && line[i+1] == '/') || + line[i] == '#') { + continue; + } + return strncmp(line + i, "/**", 3) == 0; + } + return 0; +} + +static int check_file(const char *path, regex_t *re) { + FILE *fp = fopen(path, "r"); + if (!fp) { + perror(path); + return 1; + } + char **lines = NULL; + size_t count = 0, capacity = 0; + char *line = NULL; + size_t len = 0; + ssize_t n; + while ((n = getline(&line, &len, fp)) != -1) { + if (count >= capacity) { + capacity = capacity ? capacity * 2 : 128; + lines = realloc(lines, capacity * sizeof(char *)); + if (!lines) { + perror("realloc"); + fclose(fp); + free(line); + return 1; + } + } + lines[count++] = strdup(line); + } + free(line); + fclose(fp); + + int missing = 0; + for (size_t i = 0; i < count; i++) { + if (regexec(re, lines[i], 0, NULL, 0) == 0) { + char *s = lines[i]; + int off = lstrip_index(s); + if (strncmp(s + off, "struct ", 7) == 0 || + strncmp(s + off, "enum ", 5) == 0 || + strncmp(s + off, "class ", 6) == 0) { + continue; + } + if (!has_doc_comment(lines, (int)i)) { + printf("%s:%zu: missing Doxygen comment\n", path, i + 1); + missing = 1; + } + } + } + + for (size_t i = 0; i < count; i++) free(lines[i]); + free(lines); + return missing; +} + +int main(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + regex_t re; + const char *pattern = "^[A-Za-z_][A-Za-z0-9_[:space:]*]*\\([^;]*\\)[[:space:]]*\\{"; + if (regcomp(&re, pattern, REG_EXTENDED)) { + fprintf(stderr, "Failed to compile regex\n"); + return 1; + } + int missing = 0; + for (int i = 1; i < argc; i++) { + missing |= check_file(argv[i], &re); + } + regfree(&re); + return missing ? 1 : 0; +}