|
diff --git a/README.md b/README.md
|
| ... |
| 18 |
- **Slices (`slice`)**: Generic non-owning views into arrays. |
18 |
- **Slices (`slice`)**: Generic non-owning views into arrays. |
| 19 |
- **Memory Arena**: Simple block-based arena allocator for bulk memory management. |
19 |
- **Memory Arena**: Simple block-based arena allocator for bulk memory management. |
| 20 |
- **File I/O**: Helper functions to read and write entire files with a single call. |
20 |
- **File I/O**: Helper functions to read and write entire files with a single call. |
|
|
21 |
- **Logging**: Simple, leveled logging with ANSI colors and timestamps. |
| 21 |
|
22 |
|
| 22 |
## Installation |
23 |
## Installation |
| 23 |
|
24 |
|
| ... |
| 163 |
// ... use file_sb ... |
164 |
// ... use file_sb ... |
| 164 |
sb_free(&file_sb); |
165 |
sb_free(&file_sb); |
| 165 |
|
166 |
|
|
|
167 |
``` |
|
|
168 |
|
|
|
169 |
### 6. Logging |
|
|
170 |
|
|
|
171 |
Simple logging with levels (`ERROR`, `WARN`, `INFO`, `DEBUG`), timestamps, and colors. |
|
|
172 |
|
|
|
173 |
```c |
|
|
174 |
// Set log level (default is INFO) |
|
|
175 |
set_log_level(LOG_DEBUG); |
|
|
176 |
|
|
|
177 |
// Use macros for logging |
|
|
178 |
LOG_INFO_MSG("Starting application..."); |
|
|
179 |
LOG_DEBUG_MSG("Variable x = %d", 42); |
|
|
180 |
LOG_WARN_MSG("Low memory warning"); |
|
|
181 |
LOG_ERROR_MSG("Connection failed"); |
|
|
182 |
|
|
|
183 |
// Environment variable override supported: |
|
|
184 |
// LOG_LEVEL=0 (ERROR) ... 3 (DEBUG) |
| 166 |
``` |
185 |
``` |
| 167 |
|
186 |
|
| 168 |
## Testing |
187 |
## Testing |
| ... |
|
diff --git a/nonstd.h b/nonstd.h
|
|
|
1 |
#ifdef NONSTD_IMPLEMENTATION |
|
|
2 |
#ifndef _POSIX_C_SOURCE |
|
|
3 |
#define _POSIX_C_SOURCE 200809L |
|
|
4 |
#endif |
|
|
5 |
#endif |
|
|
6 |
|
| 1 |
#ifndef NONSTD_H |
7 |
#ifndef NONSTD_H |
| 2 |
#define NONSTD_H |
8 |
#define NONSTD_H |
| 3 |
|
9 |
|
| ... |
| 6 |
#include <stdio.h> |
12 |
#include <stdio.h> |
| 7 |
#include <stdlib.h> |
13 |
#include <stdlib.h> |
| 8 |
#include <string.h> |
14 |
#include <string.h> |
|
|
15 |
#include <stdarg.h> |
|
|
16 |
#include <time.h> |
|
|
17 |
#include <sys/time.h> |
|
|
18 |
#include <unistd.h> |
| 9 |
|
19 |
|
| 10 |
#ifndef NONSTD_DEF |
20 |
#ifndef NONSTD_DEF |
| 11 |
#ifdef NONSTD_STATIC |
21 |
#ifdef NONSTD_STATIC |
| ... |
| 88 |
NONSTD_DEF stringv sb_as_sv(const stringb *sb); |
98 |
NONSTD_DEF stringv sb_as_sv(const stringb *sb); |
| 89 |
|
99 |
|
| 90 |
// Slice - generic non-owning view into an array |
100 |
// Slice - generic non-owning view into an array |
| 91 |
// Usage: |
101 |
// Usage: SLICE_DEF(int); slice(int) view = ...; |
| 92 |
// SLICE_DEF(int); // Define slice_int type |
|
|
| 93 |
// slice(int) view = ...; // Use it |
|
|
| 94 |
#define SLICE_DEF(T) \ |
102 |
#define SLICE_DEF(T) \ |
| 95 |
typedef struct { \ |
103 |
typedef struct { \ |
| 96 |
T *data; \ |
104 |
T *data; \ |
| ... |
| 228 |
index < (arr).length && ((var) = (arr).data[index], 1); ++index) |
236 |
index < (arr).length && ((var) = (arr).data[index], 1); ++index) |
| 229 |
|
237 |
|
| 230 |
// Arena - block-based memory allocator |
238 |
// Arena - block-based memory allocator |
| 231 |
// Usage: Arena a = arena_make(); void* p = arena_alloc(&a, 100); |
|
|
| 232 |
// arena_free(&a); |
|
|
| 233 |
typedef struct { |
239 |
typedef struct { |
| 234 |
char *ptr; |
240 |
char *ptr; |
| 235 |
char *end; |
241 |
char *end; |
| ... |
| 246 |
// File I/O helpers |
252 |
// File I/O helpers |
| 247 |
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size); |
253 |
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size); |
| 248 |
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size); |
254 |
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size); |
| 249 |
// read_entire_file_sv removed for security (ownership confusion) |
|
|
| 250 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath); |
255 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath); |
| 251 |
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv); |
256 |
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv); |
| 252 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb); |
257 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb); |
| 253 |
|
258 |
|
|
|
259 |
// Logging |
|
|
260 |
typedef enum { |
|
|
261 |
LOG_ERROR, |
|
|
262 |
LOG_WARN, |
|
|
263 |
LOG_INFO, |
|
|
264 |
LOG_DEBUG, |
|
|
265 |
} LogLevel; |
|
|
266 |
|
|
|
267 |
NONSTD_DEF void set_log_level(LogLevel level); |
|
|
268 |
NONSTD_DEF LogLevel get_log_level_from_env(void); |
|
|
269 |
NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...); |
|
|
270 |
|
|
|
271 |
#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__) |
|
|
272 |
#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__) |
|
|
273 |
#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__) |
|
|
274 |
#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__) |
|
|
275 |
|
|
|
276 |
#define COLOR_RESET "\033[0m" |
|
|
277 |
#define COLOR_INFO "\033[32m" |
|
|
278 |
#define COLOR_DEBUG "\033[36m" |
|
|
279 |
#define COLOR_WARNING "\033[33m" |
|
|
280 |
#define COLOR_ERROR "\033[31m" |
|
|
281 |
|
| 254 |
#endif // NONSTD_H |
282 |
#endif // NONSTD_H |
| 255 |
|
283 |
|
| 256 |
#ifdef NONSTD_IMPLEMENTATION |
284 |
#ifdef NONSTD_IMPLEMENTATION |
| ... |
| 474 |
return written == size; |
502 |
return written == size; |
| 475 |
} |
503 |
} |
| 476 |
|
504 |
|
| 477 |
// read_entire_file_sv removed |
|
|
| 478 |
|
|
|
| 479 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) { |
505 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) { |
| 480 |
size_t size = 0; |
506 |
size_t size = 0; |
| 481 |
char *data = read_entire_file(filepath, &size); |
507 |
char *data = read_entire_file(filepath, &size); |
| ... |
| 494 |
|
520 |
|
| 495 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) { |
521 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) { |
| 496 |
return write_entire_file(filepath, sb->data, sb->length); |
522 |
return write_entire_file(filepath, sb->data, sb->length); |
|
|
523 |
} |
|
|
524 |
|
|
|
525 |
// Logging Implementation |
|
|
526 |
|
|
|
527 |
static LogLevel max_level = LOG_INFO; |
|
|
528 |
|
|
|
529 |
static const char *level_strings[] = { |
|
|
530 |
"ERROR", |
|
|
531 |
"WARN", |
|
|
532 |
"INFO", |
|
|
533 |
"DEBUG", |
|
|
534 |
}; |
|
|
535 |
|
|
|
536 |
static const char *level_colors[] = { |
|
|
537 |
COLOR_ERROR, |
|
|
538 |
COLOR_WARNING, |
|
|
539 |
COLOR_INFO, |
|
|
540 |
COLOR_DEBUG, |
|
|
541 |
}; |
|
|
542 |
|
|
|
543 |
NONSTD_DEF void set_log_level(LogLevel level) { |
|
|
544 |
max_level = level; |
|
|
545 |
} |
|
|
546 |
|
|
|
547 |
NONSTD_DEF LogLevel get_log_level_from_env(void) { |
|
|
548 |
const char *env = getenv("LOG_LEVEL"); |
|
|
549 |
if (env) { |
|
|
550 |
int level = atoi(env); |
|
|
551 |
if (level >= 0 && level <= 3) { |
|
|
552 |
return (LogLevel)level; |
|
|
553 |
} |
|
|
554 |
} |
|
|
555 |
|
|
|
556 |
return max_level; |
|
|
557 |
} |
|
|
558 |
|
|
|
559 |
NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) { |
|
|
560 |
if (max_level < level) |
|
|
561 |
return; |
|
|
562 |
|
|
|
563 |
struct timeval tv; |
|
|
564 |
gettimeofday(&tv, NULL); |
|
|
565 |
struct tm *tm_info = localtime(&tv.tv_sec); |
|
|
566 |
|
|
|
567 |
char time_str[24]; |
|
|
568 |
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); |
|
|
569 |
|
|
|
570 |
const char *color = isatty(fileno(stream)) ? level_colors[level] : ""; |
|
|
571 |
const char *reset = isatty(fileno(stream)) ? COLOR_RESET : ""; |
|
|
572 |
|
|
|
573 |
const char *log_format = "%s[%s.%03d] [%-5s] "; |
|
|
574 |
fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), |
|
|
575 |
level_strings[level]); |
|
|
576 |
|
|
|
577 |
va_list args; |
|
|
578 |
va_start(args, format); |
|
|
579 |
vfprintf(stream, format, args); |
|
|
580 |
va_end(args); |
|
|
581 |
|
|
|
582 |
fprintf(stream, "%s\n", reset); |
|
|
583 |
fflush(stream); |
| 497 |
} |
584 |
} |
| 498 |
|
585 |
|
| 499 |
#endif // NONSTD_IMPLEMENTATION |
586 |
#endif // NONSTD_IMPLEMENTATION |
|
diff --git a/tests.c b/tests.c
|
| ... |
| 857 |
mu_assert_int_eq(0, sb.length); |
857 |
mu_assert_int_eq(0, sb.length); |
| 858 |
} |
858 |
} |
| 859 |
|
859 |
|
|
|
860 |
// Logging tests |
|
|
861 |
MU_TEST(test_logging_level_filtering) { |
|
|
862 |
FILE *tmp = tmpfile(); |
|
|
863 |
mu_check(tmp != NULL); |
|
|
864 |
|
|
|
865 |
set_log_level(LOG_WARN); |
|
|
866 |
|
|
|
867 |
log_message(tmp, LOG_INFO, "Info message"); // Should not be logged |
|
|
868 |
log_message(tmp, LOG_ERROR, "Error message"); // Should be logged |
|
|
869 |
|
|
|
870 |
rewind(tmp); |
|
|
871 |
char buffer[1024]; |
|
|
872 |
size_t read = fread(buffer, 1, sizeof(buffer), tmp); |
|
|
873 |
buffer[read] = '\0'; |
|
|
874 |
|
|
|
875 |
mu_check(strstr(buffer, "Info message") == NULL); |
|
|
876 |
mu_check(strstr(buffer, "Error message") != NULL); |
|
|
877 |
mu_check(strstr(buffer, "[ERROR]") != NULL); |
|
|
878 |
|
|
|
879 |
fclose(tmp); |
|
|
880 |
} |
|
|
881 |
|
|
|
882 |
MU_TEST(test_logging_env_level) { |
|
|
883 |
// Enum: ERROR=0, WARN=1, INFO=2, DEBUG=3 |
|
|
884 |
setenv("LOG_LEVEL", "0", 1); // ERROR |
|
|
885 |
mu_assert_int_eq(LOG_ERROR, get_log_level_from_env()); |
|
|
886 |
|
|
|
887 |
setenv("LOG_LEVEL", "3", 1); // DEBUG |
|
|
888 |
mu_assert_int_eq(LOG_DEBUG, get_log_level_from_env()); |
|
|
889 |
|
|
|
890 |
// Invalid level |
|
|
891 |
set_log_level(LOG_INFO); // Reset |
|
|
892 |
setenv("LOG_LEVEL", "99", 1); |
|
|
893 |
mu_assert_int_eq(LOG_INFO, get_log_level_from_env()); // Should return current max_level (defaults check) |
|
|
894 |
|
|
|
895 |
unsetenv("LOG_LEVEL"); |
|
|
896 |
} |
|
|
897 |
|
|
|
898 |
MU_TEST(test_logging_format) { |
|
|
899 |
FILE *tmp = tmpfile(); |
|
|
900 |
mu_check(tmp != NULL); |
|
|
901 |
|
|
|
902 |
set_log_level(LOG_INFO); |
|
|
903 |
log_message(tmp, LOG_INFO, "Test %d %s", 123, "format"); |
|
|
904 |
|
|
|
905 |
rewind(tmp); |
|
|
906 |
char buffer[1024]; |
|
|
907 |
size_t read = fread(buffer, 1, sizeof(buffer), tmp); |
|
|
908 |
buffer[read] = '\0'; |
|
|
909 |
|
|
|
910 |
mu_check(strstr(buffer, "Test 123 format") != NULL); |
|
|
911 |
mu_check(strstr(buffer, "[INFO ]") != NULL); |
|
|
912 |
// Check timestamp format roughly (YYYY-MM-DD) |
|
|
913 |
mu_check(strstr(buffer, "20") != NULL); |
|
|
914 |
|
|
|
915 |
fclose(tmp); |
|
|
916 |
} |
|
|
917 |
|
| 860 |
// Test suites |
918 |
// Test suites |
| 861 |
MU_TEST_SUITE(test_suite_stringv) { |
919 |
MU_TEST_SUITE(test_suite_stringv) { |
| 862 |
printf("\n[String View Tests]\n"); |
920 |
printf("\n[String View Tests]\n"); |
| ... |
| 960 |
RUN_TEST_WITH_NAME(test_file_io_read_missing_sb); |
1018 |
RUN_TEST_WITH_NAME(test_file_io_read_missing_sb); |
| 961 |
} |
1019 |
} |
| 962 |
|
1020 |
|
|
|
1021 |
MU_TEST_SUITE(test_suite_logging) { |
|
|
1022 |
printf("\n[Logging Tests]\n"); |
|
|
1023 |
RUN_TEST_WITH_NAME(test_logging_level_filtering); |
|
|
1024 |
RUN_TEST_WITH_NAME(test_logging_env_level); |
|
|
1025 |
RUN_TEST_WITH_NAME(test_logging_format); |
|
|
1026 |
} |
|
|
1027 |
|
| 963 |
int main(int argc, char *argv[]) { |
1028 |
int main(int argc, char *argv[]) { |
| 964 |
(void)argc; |
1029 |
(void)argc; |
| 965 |
(void)argv; |
1030 |
(void)argv; |
| ... |
| 972 |
MU_RUN_SUITE(test_suite_types); |
1037 |
MU_RUN_SUITE(test_suite_types); |
| 973 |
MU_RUN_SUITE(test_suite_arena); |
1038 |
MU_RUN_SUITE(test_suite_arena); |
| 974 |
MU_RUN_SUITE(test_suite_files); |
1039 |
MU_RUN_SUITE(test_suite_files); |
|
|
1040 |
MU_RUN_SUITE(test_suite_logging); |
| 975 |
|
1041 |
|
| 976 |
MU_REPORT(); |
1042 |
MU_REPORT(); |
| 977 |
|
1043 |
|
| ... |