gitout

A static git page generator
git clone https://git.bracken.jp/gitout.git
Log | Files | Refs | Submodules | README | LICENSE

format.c (7148B)


      1 #include "format.h"
      2 
      3 #include <err.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <time.h>
      7 
      8 void print_time(FILE* out, time_t time, int timezone_offset) {
      9   time_t local_time = time + (timezone_offset * 60);
     10   struct tm* time_in = gmtime(&local_time);
     11   if (!time_in) {
     12     return;
     13   }
     14 
     15   char formatted_time[32];
     16   if (!strftime(formatted_time, sizeof(formatted_time), "%a, %e %b %Y %H:%M:%S",
     17                 time_in)) {
     18     err(1, "strftime");
     19   }
     20 
     21   char timezone_sign = timezone_offset < 0 ? '-' : '+';
     22   int timezone_hours = (timezone_offset < 0 ? -1 : 1) * timezone_offset / 60;
     23   int timezone_mins = (timezone_offset < 0 ? -1 : 1) * timezone_offset % 60;
     24   char out_str[64];
     25   if (snprintf(out_str, sizeof(out_str), "%s %c%02d%02d", formatted_time,
     26                timezone_sign, timezone_hours, timezone_mins) < 0) {
     27     err(1, "snprintf");
     28   }
     29   fprintf(out, "%s", out_str);
     30 }
     31 
     32 void print_time_z(FILE* out, time_t time) {
     33   struct tm* time_in = gmtime(&time);
     34   if (!time_in) {
     35     return;
     36   }
     37 
     38   char formatted_time[32];
     39   if (!strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%dT%H:%M:%SZ",
     40                 time_in)) {
     41     err(1, "strftime");
     42   }
     43   fprintf(out, "%s", formatted_time);
     44 }
     45 
     46 void print_time_short(FILE* out, time_t time) {
     47   struct tm* time_in = gmtime(&time);
     48   if (!time_in) {
     49     return;
     50   }
     51 
     52   char formatted_time[32];
     53   if (!strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %H:%M",
     54                 time_in)) {
     55     err(1, "strftime");
     56   }
     57   fprintf(out, "%s", formatted_time);
     58 }
     59 
     60 void print_percent_encoded(FILE* out, const char* str) {
     61   static const char* hex_chars = "0123456789ABCDEF";
     62 
     63   size_t str_len = strlen(str);
     64   for (size_t i = 0; i < str_len; i++) {
     65     unsigned char uc = str[i];
     66     // NOTE: do not encode '/' for paths or ",-."
     67     if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') || uc == '[' ||
     68         uc == ']') {
     69       fprintf(out, "%%%c%c", hex_chars[(uc >> 4) & 0x0f], hex_chars[uc & 0x0f]);
     70     } else {
     71       fprintf(out, "%c", uc);
     72     }
     73   }
     74 }
     75 
     76 void print_xml_encoded(FILE* out, const char* str) {
     77   print_xml_encoded_len(out, str, -1, true);
     78 }
     79 
     80 void print_xml_encoded_len(FILE* out,
     81                            const char* str,
     82                            ssize_t str_len,
     83                            bool output_crlf) {
     84   size_t len = (str_len >= 0) ? (size_t)str_len : strlen(str);
     85   for (size_t i = 0; i < len && str[i] != '\0'; i++) {
     86     char c = str[i];
     87     switch (c) {
     88       case '<':
     89         fprintf(out, "&lt;");
     90         break;
     91       case '>':
     92         fprintf(out, "&gt;");
     93         break;
     94       case '\'':
     95         fprintf(out, "&#39;");
     96         break;
     97       case '&':
     98         fprintf(out, "&amp;");
     99         break;
    100       case '"':
    101         fprintf(out, "&quot;");
    102         break;
    103       case '\r':
    104       case '\n':
    105         if (output_crlf) {
    106           fprintf(out, "%c", c);
    107         }
    108         break;
    109       default:
    110         fprintf(out, "%c", c);
    111     }
    112   }
    113 }
    114 
    115 void print_gopher_text(FILE* out, const char* str, bool output_lf) {
    116   print_gopher_text_len(out, str, -1, output_lf);
    117 }
    118 
    119 void print_gopher_text_len(FILE* out,
    120                            const char* str,
    121                            ssize_t str_len,
    122                            bool output_lf) {
    123   size_t len = (str_len >= 0) ? (size_t)str_len : strlen(str);
    124   bool start_of_line = true;
    125   for (size_t i = 0; i < len && str[i] != '\0'; i++) {
    126     char c = str[i];
    127     if (output_lf && start_of_line && c == '[') {
    128       fprintf(out, "[|");
    129     } else if (c == '\t') {
    130       fprintf(out, "        ");
    131     } else if (c == '\r' || (!output_lf && c == '\n')) {
    132       // Ignore.
    133     } else {
    134       fprintf(out, "%c", c);
    135     }
    136     start_of_line = (c == '\n');
    137   }
    138 }
    139 
    140 void print_gopher_link(FILE* out, const char* str) {
    141   for (size_t i = 0; str[i] != '\0'; i++) {
    142     char c = str[i];
    143     if (c == '|') {
    144       fprintf(out, "\\|");
    145     } else if (c == '\t') {
    146       fprintf(out, "        ");
    147     } else if (c == '\r' || c == '\n') {
    148       // Ignore.
    149     } else {
    150       fprintf(out, "%c", c);
    151     }
    152   }
    153 }
    154 
    155 static size_t utf8_char_length(const char* str) {
    156   unsigned char c = (unsigned char)*str;
    157   if (c < 0x80) {
    158     return 1;
    159   }
    160   if ((c & 0xe0) == 0xc0) {
    161     return 2;
    162   }
    163   if ((c & 0xf0) == 0xe0) {
    164     return 3;
    165   }
    166   if ((c & 0xf8) == 0xf0) {
    167     return 4;
    168   }
    169   // Invalid UTF-8 sequence. Treat as single byte.
    170   return 1;
    171 }
    172 
    173 static bool is_zwj(const char* str) {
    174   return str[0] == '\xe2' && str[1] == '\x80' && str[2] == '\x8d';
    175 }
    176 
    177 static bool is_variation_selector(const char* str) {
    178   return str[0] == '\xef' && str[1] == '\xb8' && str[2] == '\x8f';
    179 }
    180 
    181 static size_t get_char_width(wchar_t c) {
    182   // ASCII characters in the range [0-31] and [127-255] are control characters
    183   // or non-printable characters
    184   if ((c >= 0 && c <= 31) || (c >= 127 && c <= 255)) {
    185     return 1;
    186   }
    187 
    188   // Emoji range.
    189   if (c >= 0x1F300 && c <= 0x1F6FF) {
    190     return 2;
    191   }
    192 
    193   // Extended pictographic characters.
    194   if (c >= 0x1F900 && c <= 0x1F9FF) {
    195     return 2;
    196   }
    197 
    198   // Hiragana and Katakana ranges.
    199   if ((c >= 0x3040 && c <= 0x309F) || (c >= 0x30A0 && c <= 0x30FF)) {
    200     return 2;
    201   }
    202 
    203   // Kanji range.
    204   if (c >= 0x4E00 && c <= 0x9FFF) {
    205     return 2;
    206   }
    207   return 1;
    208 }
    209 
    210 // TODO(cbracken): There has to be a better way.
    211 void print_gopher_link_padded(FILE* out,
    212                               const char* str,
    213                               size_t width,
    214                               char pad_char) {
    215   size_t str_len = strlen(str);
    216   size_t char_count = 0;
    217   size_t last_char_width = 0;
    218   size_t byte_count = 0;
    219   while (byte_count < str_len) {
    220     size_t pos = byte_count;
    221     size_t char_length = utf8_char_length(&str[byte_count]);
    222     byte_count += char_length;
    223     if (char_count == width - 1 && byte_count < str_len) {
    224       fprintf(out, "\u2026");
    225       last_char_width = 1;
    226       char_count += last_char_width;
    227     } else if (char_count < width) {
    228       if (str[pos] == '|') {
    229         fprintf(out, "\\|");
    230         char_count++;
    231         last_char_width = 1;
    232       } else if (str[pos] == '\t') {
    233         for (size_t i = 0; i < 8 && char_count < width; i++) {
    234           fprintf(out, " ");
    235           char_count++;
    236         }
    237         last_char_width = 1;
    238       } else if (str[pos] == '\r' || str[pos] == '\n') {
    239         // Ignore.
    240         last_char_width = 0;
    241       } else if (char_length == 3 && is_zwj(&str[pos])) {
    242         fprintf(out, "\xe2\x80\x8d");
    243         char_count -= last_char_width;
    244         last_char_width = 0;
    245       } else if (char_length == 3 && is_variation_selector(&str[pos])) {
    246         fprintf(out, "\xef\xb8\x8f");
    247         char_count -= last_char_width;
    248         last_char_width = 0;
    249       } else {
    250         for (size_t i = pos; i < byte_count; i++) {
    251           fprintf(out, "%c", str[i]);
    252         }
    253         wchar_t wide_char;
    254         if (mbtowc(&wide_char, &str[pos], MB_CUR_MAX) >= 0) {
    255           last_char_width = get_char_width(wide_char);
    256         } else {
    257           last_char_width = 1;
    258         }
    259         char_count += last_char_width;
    260       }
    261     }
    262   }
    263   if (pad_char != '\0') {
    264     while (char_count < width) {
    265       fprintf(out, "%c", pad_char);
    266       char_count++;
    267     }
    268   }
    269 }