/* ssg.c, v1.05 Wed Jun 26 00:13:54 EDT 2024 jordanreger */ #include #include #include #include #include #include #include #include #define MAX_PATH_LENGTH 512 #define BUFFER_SIZE 1024 /* * Returns nonzero if `file` IS a valid html file, * zero otherwise. Used as the filter to scandir. */ int isHtmlFile(const struct dirent *file); /* * Returns nonzero if `file` IS a valid file, * zero otherwise. Used as the filter to scandir. */ int isFile(const struct dirent *file); /* * Returns nonzero if `directory` IS a valid directory, * nonzero otherwise. Used as the filter to scandir. */ int isDirectory(const struct dirent *directory); /* * Returns last modified time */ char *lastModified(char *path); /* * Returns file size */ off_t fileSize(char *path); /* * Recursively walk all directories starting from `dirname` * iteratively templating all html files within. */ int walkDirectory(char *path); /* * For each valid html file in current directory... * X Open the HTML file * X Find templates (if any) * X Create a write buffer (FILE_NAME.html.tmpl) (use mode `aw+`) * X Construct new file with templates (tmpl/TEMPLATE_NAME.html) */ void processHtmlFile(const char *file_path, const char *tmpl_dir); int main(int argc, char **argv) { char path[MAX_PATH_LENGTH] = {'.'}; // start from current dir /* manually set to start from current directory */ 0[path] = '.'; 1[path] = 0; walkDirectory(path); } int isHtmlFile(const struct dirent *file) { const char *extension_ptr = NULL; extension_ptr = strrchr(file->d_name, '.'); if (NULL == extension_ptr) return 0; if (0 != strcmp(extension_ptr + 1, "html")) return 0; /* Valid html extension */ return 1; } int isFile(const struct dirent *file) { return file->d_type == DT_REG && strcmp(file->d_name, "ssg") && strcmp(file->d_name, ".gitignore") && strcmp(file->d_name, ".build.yml"); } int isDirectory(const struct dirent *directory) { return directory->d_type == DT_DIR && strcmp(directory->d_name, ".") && strcmp(directory->d_name, "..") && strcmp(directory->d_name, ".git") && strcmp(directory->d_name, ".tmpl"); // TODO: Add a loop and use a custom .getignore-esque file perhaps } char *lastModified(char *path) { struct stat attr; stat(path, &attr); return ctime(&attr.st_mtime); } off_t fileSize(char *path) { struct stat attr; stat(path, &attr); return attr.st_size; } int walkDirectory(char *path) { struct dirent **directories = NULL, **files = NULL; int directory_count = -1, file_count = -1, index_found = 1; FILE *index = NULL; char index_path[MAX_PATH_LENGTH]; char index_header[BUFFER_SIZE]; char index_footer[BUFFER_SIZE]; size_t initial_path_length = strlen(path); directory_count = scandir(path, &directories, isDirectory, alphasort); if (directory_count == -1) { perror("scandir"); exit(EXIT_FAILURE); } snprintf(index_path, sizeof(index_path), "%s/index.html", path); index = fopen(index_path, "r"); if (NULL == index) { index_found = 0; } if (0 == index_found) { index = fopen(index_path, "w"); printf("%s", path); snprintf(index_header, sizeof(index_header), "%s%s%s", "\n\n{ head }\n\n

Index of /", path + 2, "

\n
\n
../\n");
    fprintf(index, "%s", index_header);
  }

  /* Base case: no directories are found */
  for (int i = 0; i < directory_count; i++) {
    char *directory_name = NULL;
    char directory_path[MAX_PATH_LENGTH];
    size_t directory_name_size = 0;
    char *dir_last_modified = NULL;


    directory_name = directories[i]->d_name;
    snprintf(directory_path, sizeof(directory_path), "%s/%s", path, directory_name);
    directory_name_size = strlen(directory_name);
    dir_last_modified = lastModified(directory_path); 

    if (0 == index_found) {
      fprintf(index, "%s/\n", directory_name, directory_name);
    }

    /*
     * Bounds checking for the `path` buffer. Upon overflow
     * manually set the `File name too long` errno and exit
     */
    if (strlen(path) + directory_name_size + 2 >= MAX_PATH_LENGTH) {
      errno = ENAMETOOLONG;
      perror("Recursion");
      exit(EXIT_FAILURE);
    }

    path = strncat(path, "/", 2); // Add the `/` between entries
    path = strncat(path, directory_name, directory_name_size);

    walkDirectory(path);

    /* 
     * Manually set the path back to where it was before
     * the recursive call to stop infinite recursion.
     * No one likes infinite recursion  :(
     */
    path[initial_path_length] = 0;

    free(directories[i]);
  }

  free(directories);

  /* Get all files in current directory */
  file_count = scandir(path, &files, isFile, alphasort);
  if (-1 == file_count) {
    perror("scandir");
    exit(EXIT_FAILURE);
  }

  /* escape codes print directory name in blue */
  printf("\033[0;34m%s\033[0m\n", path);

  for (int i = 0; i < file_count; i++) {
    char file_path[MAX_PATH_LENGTH];
    char *file_last_modified = NULL;
    size_t file_size = 0;

    printf("  %s\n", files[i]->d_name);
    snprintf(file_path, sizeof(file_path), "%s/%s", path, files[i]->d_name);
    file_last_modified = lastModified(file_path);
    file_size = fileSize(file_path);

    if (0 == index_found && strcmp(files[i]->d_name, "index.html")) {
      fprintf(index, "%s\n", files[i]->d_name, files[i]->d_name);
    }

    char full_path[MAX_PATH_LENGTH];
    snprintf(full_path, sizeof(full_path), "%s/%s", path, files[i]->d_name);

    if (isHtmlFile(files[i])) {
      processHtmlFile(full_path, "./.tmpl");
    }

    free(files[i]);
  }

  if (0 == index_found) {
    snprintf(index_footer, sizeof(index_footer), "%s", "
\n
\n\n\n"); fprintf(index, "%s", index_footer); fclose(index); processHtmlFile(index_path, "./.tmpl"); } free(files); return EXIT_SUCCESS; } void processHtmlFile(const char *file_path, const char *tmpl_dir) { FILE *file = NULL, *output_file = NULL; char output_path[MAX_PATH_LENGTH]; char line_buffer[BUFFER_SIZE]; file = fopen(file_path, "r"); if (NULL == file) { perror("fopen"); return; } snprintf(output_path, sizeof(output_path), "%s.tmpl", file_path); output_file = fopen(output_path, "w"); if (NULL == output_file) { perror("fopen"); fclose(file); return; } while (NULL != fgets(line_buffer, sizeof(line_buffer), file)) { char *start = line_buffer, *open_brace = NULL; while (NULL != (open_brace = strchr(start, '{'))) { char * close_brace = NULL; FILE *template_file = NULL; char template_name[BUFFER_SIZE]; char tmpl_buffer[BUFFER_SIZE]; size_t n; close_brace = strchr(open_brace, '}'); if (close_brace == NULL) { break; // Error handling? } /* Segment file into strings around curly braces */ *open_brace = '\0'; *close_brace = '\0'; /* Write upto first null byte into new file */ fprintf(output_file, "%s", start); /* Get rid of template name leading space */ while(open_brace++, ' ' == *open_brace) continue; /* Get rid of template name trailing space */ while(close_brace--, ' ' == *(close_brace - 1)) continue; /* Creates a substring around the template name */ *close_brace = '\0'; snprintf(template_name, sizeof(template_name), "%s/%s.html", tmpl_dir, open_brace); template_file = fopen(template_name, "r"); if (NULL == template_file) { fprintf(stderr, "Template file %s not found\n", template_name); exit(EXIT_FAILURE); } // maybe getline would preserve tabs better? while (0 < (n = fread(tmpl_buffer, 1, sizeof(tmpl_buffer), template_file))) { fwrite(tmpl_buffer, 1, n, output_file); } fclose(template_file); // Continue parsing after current closing_brace start = close_brace + 1; } fprintf(output_file, "%s", start); } fclose(file); fclose(output_file); }