Ultima attività 4 days ago

Revisione 60016a0b71681fc1ca8abd8db8e5d108b4d6f52b

ssg.c Raw
1/* ssg.c, v1.05 Wed Jun 26 00:13:54 EDT 2024 jordanreger */
2
3#include <dirent.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <errno.h>
8#include <time.h>
9#include <sys/stat.h>
10#include <sys/types.h>
11
12#define MAX_PATH_LENGTH 512
13#define BUFFER_SIZE 1024
14
15/*
16 * Returns nonzero if `file` IS a valid html file,
17 * zero otherwise. Used as the filter to scandir.
18 */
19int isHtmlFile(const struct dirent *file);
20
21/*
22 * Returns nonzero if `file` IS a valid file,
23 * zero otherwise. Used as the filter to scandir.
24 */
25int isFile(const struct dirent *file);
26
27/*
28 * Returns nonzero if `directory` IS a valid directory,
29 * nonzero otherwise. Used as the filter to scandir.
30 */
31int isDirectory(const struct dirent *directory);
32
33/*
34 * Returns last modified time
35 */
36char *lastModified(char *path);
37
38/*
39 * Returns file size
40 */
41off_t fileSize(char *path);
42
43/*
44 * Recursively walk all directories starting from `dirname`
45 * iteratively templating all html files within.
46 */
47int walkDirectory(char *path);
48
49/*
50 * For each valid html file in current directory...
51 * X Open the HTML file
52 * X Find templates (if any)
53 * X Create a write buffer (FILE_NAME.html.tmpl) (use mode `aw+`)
54 * X Construct new file with templates (tmpl/TEMPLATE_NAME.html)
55 */
56void processHtmlFile(const char *file_path, const char *tmpl_dir);
57
58
59int main(int argc, char **argv)
60{
61 char path[MAX_PATH_LENGTH] = {'.'}; // start from current dir
62
63 /* manually set to start from current directory */
64 0[path] = '.';
65 1[path] = 0;
66
67 walkDirectory(path);
68}
69
70int isHtmlFile(const struct dirent *file)
71{
72 const char *extension_ptr = NULL;
73
74 extension_ptr = strrchr(file->d_name, '.');
75 if (NULL == extension_ptr)
76 return 0;
77
78 if (0 != strcmp(extension_ptr + 1, "html"))
79 return 0;
80
81 /* Valid html extension */
82 return 1;
83}
84
85int isFile(const struct dirent *file)
86{
87 return file->d_type == DT_REG
88 && strcmp(file->d_name, "ssg")
89 && strcmp(file->d_name, ".gitignore")
90 && strcmp(file->d_name, ".build.yml");
91}
92
93int isDirectory(const struct dirent *directory)
94{
95 return directory->d_type == DT_DIR
96 && strcmp(directory->d_name, ".")
97 && strcmp(directory->d_name, "..")
98 && strcmp(directory->d_name, ".git")
99 && strcmp(directory->d_name, ".tmpl");
100
101 // TODO: Add a loop and use a custom .getignore-esque file perhaps
102}
103
104char *lastModified(char *path)
105{
106 struct stat attr;
107 stat(path, &attr);
108 return ctime(&attr.st_mtime);
109}
110
111off_t fileSize(char *path)
112{
113 struct stat attr;
114 stat(path, &attr);
115 return attr.st_size;
116}
117
118int walkDirectory(char *path)
119{
120 struct dirent **directories = NULL,
121 **files = NULL;
122 int directory_count = -1,
123 file_count = -1,
124 index_found = 1;
125
126 FILE *index = NULL;
127 char index_path[MAX_PATH_LENGTH];
128 char index_header[BUFFER_SIZE];
129 char index_footer[BUFFER_SIZE];
130
131 size_t initial_path_length = strlen(path);
132
133 directory_count = scandir(path, &directories, isDirectory, alphasort);
134 if (directory_count == -1) {
135 perror("scandir");
136 exit(EXIT_FAILURE);
137 }
138
139 snprintf(index_path, sizeof(index_path), "%s/index.html", path);
140 index = fopen(index_path, "r");
141 if (NULL == index) {
142 index_found = 0;
143 }
144
145 if (0 == index_found) {
146 index = fopen(index_path, "w");
147 printf("%s", path);
148 snprintf(index_header, sizeof(index_header), "%s%s%s", "<!DOCTYPE html>\n<html>\n{ head }\n<body>\n<h1>Index of /", path + 2, "</h1>\n<hr>\n<pre><a href='../'>../</a>\n");
149 fprintf(index, "%s", index_header);
150 }
151
152 /* Base case: no directories are found */
153 for (int i = 0; i < directory_count; i++) {
154 char *directory_name = NULL;
155 char directory_path[MAX_PATH_LENGTH];
156 size_t directory_name_size = 0;
157 char *dir_last_modified = NULL;
158
159
160 directory_name = directories[i]->d_name;
161 snprintf(directory_path, sizeof(directory_path), "%s/%s", path, directory_name);
162 directory_name_size = strlen(directory_name);
163 dir_last_modified = lastModified(directory_path);
164
165 if (0 == index_found) {
166 fprintf(index, "<a href='%s/'>%s/</a>\n", directory_name, directory_name);
167 }
168
169 /*
170 * Bounds checking for the `path` buffer. Upon overflow
171 * manually set the `File name too long` errno and exit
172 */
173 if (strlen(path) + directory_name_size + 2 >= MAX_PATH_LENGTH) {
174 errno = ENAMETOOLONG;
175 perror("Recursion");
176 exit(EXIT_FAILURE);
177 }
178
179 path = strncat(path, "/", 2); // Add the `/` between entries
180 path = strncat(path, directory_name, directory_name_size);
181
182 walkDirectory(path);
183
184 /*
185 * Manually set the path back to where it was before
186 * the recursive call to stop infinite recursion.
187 * No one likes infinite recursion :(
188 */
189 path[initial_path_length] = 0;
190
191 free(directories[i]);
192 }
193
194 free(directories);
195
196 /* Get all files in current directory */
197 file_count = scandir(path, &files, isFile, alphasort);
198 if (-1 == file_count) {
199 perror("scandir");
200 exit(EXIT_FAILURE);
201 }
202
203 /* escape codes print directory name in blue */
204 printf("\033[0;34m%s\033[0m\n", path);
205
206 for (int i = 0; i < file_count; i++) {
207 char file_path[MAX_PATH_LENGTH];
208 char *file_last_modified = NULL;
209 size_t file_size = 0;
210
211 printf(" %s\n", files[i]->d_name);
212 snprintf(file_path, sizeof(file_path), "%s/%s", path, files[i]->d_name);
213 file_last_modified = lastModified(file_path);
214 file_size = fileSize(file_path);
215
216 if (0 == index_found && strcmp(files[i]->d_name, "index.html")) {
217 fprintf(index, "<a href='%s'>%s</a>\n", files[i]->d_name, files[i]->d_name);
218 }
219
220 char full_path[MAX_PATH_LENGTH];
221 snprintf(full_path, sizeof(full_path), "%s/%s", path, files[i]->d_name);
222
223 if (isHtmlFile(files[i])) {
224 processHtmlFile(full_path, "./.tmpl");
225 }
226
227 free(files[i]);
228 }
229
230 if (0 == index_found) {
231 snprintf(index_footer, sizeof(index_footer), "%s", "</pre>\n<hr>\n</body>\n</html>\n");
232 fprintf(index, "%s", index_footer);
233 fclose(index);
234 processHtmlFile(index_path, "./.tmpl");
235 }
236
237 free(files);
238
239 return EXIT_SUCCESS;
240}
241
242void processHtmlFile(const char *file_path, const char *tmpl_dir)
243{
244 FILE *file = NULL,
245 *output_file = NULL;
246
247 char output_path[MAX_PATH_LENGTH];
248 char line_buffer[BUFFER_SIZE];
249
250 file = fopen(file_path, "r");
251 if (NULL == file) {
252 perror("fopen");
253 return;
254 }
255
256 snprintf(output_path, sizeof(output_path), "%s.tmpl", file_path);
257
258 output_file = fopen(output_path, "w");
259 if (NULL == output_file) {
260 perror("fopen");
261 fclose(file);
262 return;
263 }
264
265
266 while (NULL != fgets(line_buffer, sizeof(line_buffer), file)) {
267 char *start = line_buffer,
268 *open_brace = NULL;
269
270
271 while (NULL != (open_brace = strchr(start, '{'))) {
272 char * close_brace = NULL;
273 FILE *template_file = NULL;
274 char template_name[BUFFER_SIZE];
275 char tmpl_buffer[BUFFER_SIZE];
276 size_t n;
277
278 close_brace = strchr(open_brace, '}');
279 if (close_brace == NULL) {
280 break; // Error handling?
281 }
282
283 /* Segment file into strings around curly braces */
284 *open_brace = '\0';
285 *close_brace = '\0';
286
287 /* Write upto first null byte into new file */
288 fprintf(output_file, "%s", start);
289
290 /* Get rid of template name leading space */
291 while(open_brace++, ' ' == *open_brace)
292 continue;
293
294 /* Get rid of template name trailing space */
295 while(close_brace--, ' ' == *(close_brace - 1))
296 continue;
297
298 /* Creates a substring around the template name */
299 *close_brace = '\0';
300
301 snprintf(template_name, sizeof(template_name), "%s/%s.html", tmpl_dir, open_brace);
302
303 template_file = fopen(template_name, "r");
304 if (NULL == template_file) {
305 fprintf(stderr, "Template file %s not found\n", template_name);
306 exit(EXIT_FAILURE);
307 }
308
309 // maybe getline would preserve tabs better?
310 while (0 < (n = fread(tmpl_buffer, 1, sizeof(tmpl_buffer), template_file))) {
311 fwrite(tmpl_buffer, 1, n, output_file);
312 }
313 fclose(template_file);
314
315 // Continue parsing after current closing_brace
316 start = close_brace + 1;
317 }
318
319 fprintf(output_file, "%s", start);
320 }
321
322 fclose(file);
323 fclose(output_file);
324}