Cthulhu  0.2.10
Cthulhu compiler collection
config.c
Go to the documentation of this file.
1 // SPDX-License-Identifier: LGPL-3.0-only
2 
3 #include "format/config.h"
4 #include "common.h"
5 
6 #include "config/config.h"
7 
8 #include "io/io.h"
9 
10 #include "std/str.h"
11 #include "std/typed/vector.h"
12 #include "std/vector.h"
13 
14 #include "base/panic.h"
15 #include "base/util.h"
16 
17 #include "core/macros.h"
18 
19 #include <limits.h>
20 #include <stdint.h>
21 
22 #define COLOUR_ARG eColourWhite
23 
24 typedef struct format_config_t
25 {
27  io_t *io;
30 
31 typedef struct alignment_info_t
32 {
33  size_t arg_alignment;
36 
37 // get the longest single line in a string
38 static size_t longest_line(const char *str)
39 {
40  size_t longest = 0;
41  size_t current = 0;
42 
43  for (size_t i = 0; str[i]; i++)
44  {
45  current += 1;
46 
47  if (str[i] == '\n')
48  {
49  longest = CT_MAX(longest, current);
50  current = 0;
51  }
52  }
53 
54  return CT_MAX(longest, current);
55 }
56 
57 static const char *get_arg_prefix(bool win_style, arg_style_t style)
58 {
59  if (win_style)
60  return "/";
61 
62  return cfg_arg_prefix(style);
63 }
64 
65 static bool should_skip_arg(bool win_style, arg_style_t style)
66 {
67  return (!win_style && style == eArgDOS);
68 }
69 
70 static size_t get_arg_length(const cfg_info_t *info, bool win_style)
71 {
72  size_t len = 0;
73  cfg_arg_array_t args = info->args;
74  for (size_t i = 0; i < args.count; i++)
75  {
76  cfg_arg_t arg = args.args[i];
77  if (should_skip_arg(false, arg.style))
78  continue;
79 
80  len += ctu_strlen(args.args[i].arg) + 1 + ctu_strlen(get_arg_prefix(win_style, arg.style));
81  }
82 
83  return len;
84 }
85 
86 static alignment_info_t get_group_alignment(const cfg_group_t *config, bool win_style)
87 {
88  size_t longest_brief = 0;
89 
90  size_t largest = 0;
91  vector_t *fields = cfg_get_fields(config);
92  size_t field_count = vector_len(fields);
93  for (size_t i = 0; i < field_count; i++)
94  {
95  const cfg_field_t *field = vector_get(fields, i);
96  const cfg_info_t *info = cfg_get_info(field);
97  size_t len = get_arg_length(info, win_style);
98  largest = CT_MAX(largest, len);
99 
100  size_t brief_len = longest_line(info->brief);
101 
102  longest_brief = CT_MAX(longest_brief, brief_len);
103  }
104 
105  alignment_info_t alignment = {
106  .arg_alignment = largest,
107  .brief_alignment = longest_brief + 1
108  };
109 
110  return alignment;
111 }
112 
113 // print the args for a single field
114 // returns the number of characters printed
115 static size_t print_field_args(format_config_t config, const cfg_info_t *info, bool win_style)
116 {
117  size_t len = 0;
118 
119  cfg_arg_array_t args = info->args;
120  for (size_t i = 0; i < args.count; i++)
121  {
122  cfg_arg_t arg = args.args[i];
123  if (should_skip_arg(win_style, arg.style))
124  continue;
125 
126  const char *prefix = get_arg_prefix(win_style, arg.style);
127  char *coloured = colour_format(config.context, COLOUR_ARG, "%s%s", prefix, arg.arg);
128  io_printf(config.io, "%s ", coloured);
129  len += ctu_strlen(arg.arg) + 1 + ctu_strlen(prefix);
130  }
131 
132  return len;
133 }
134 
135 static void print_range(io_t *io, int min, int max)
136 {
137  if (min != INT_MIN)
138  {
139  io_printf(io, ", min: %d", min);
140 
141  if (max != INT_MAX)
142  {
143  io_printf(io, ",");
144  }
145  }
146 
147  if (max != INT_MAX)
148  {
149  if (min == INT_MIN)
150  {
151  io_printf(io, ",");
152  }
153 
154  io_printf(io, " max: %d", max);
155  }
156 }
157 
158 static const char *get_enum_option(const cfg_choice_t *choices, size_t len, size_t choice)
159 {
160  for (size_t i = 0; i < len; i++)
161  {
162  if (choices[i].value == choice)
163  {
164  return choices[i].text;
165  }
166  }
167 
168  return "<unknown>";
169 }
170 
171 static void print_enum_default(format_config_t options, const cfg_field_t *field)
172 {
173  const cfg_enum_t *info = cfg_enum_info(field);
174  const char *option = get_enum_option(info->options, info->count, info->initial);
175  io_printf(options.io, "(default: %s)\n", option);
176 }
177 
178 static void print_enum(format_config_t options, alignment_info_t alignment, const cfg_field_t *field)
179 {
180  const cfg_enum_t *info = cfg_enum_info(field);
181 
182  char *padding = str_repeat(" ", alignment.arg_alignment, options.arena);
183  io_printf(options.io, "%soptions: ", padding);
184 
185  for (size_t i = 0; i < info->count; i++)
186  {
187  const cfg_choice_t *choice = info->options + i;
188  io_printf(options.io, "%s", choice->text);
189 
190  if (i != info->count - 1)
191  {
192  io_printf(options.io, ", ");
193  }
194  }
195 
196  io_printf(options.io, "\n");
197 }
198 
199 static void print_flags_default(format_config_t options, const cfg_field_t *field)
200 {
201  const cfg_enum_t *info = cfg_flags_info(field);
202  io_printf(options.io, "flags (default: ");
203 
204  bool first = true;
205  for (size_t i = 0; i < info->count; i++)
206  {
207  const cfg_choice_t choice = info->options[i];
208  if (info->initial & choice.value)
209  {
210  if (!first)
211  {
212  io_printf(options.io, "|");
213  }
214 
215  io_printf(options.io, "%s", choice.text);
216  first = false;
217  }
218  }
219 
220  io_printf(options.io, ")\n");
221 }
222 
223 static void print_flags(format_config_t options, alignment_info_t alignment, const cfg_field_t *field)
224 {
225  const cfg_enum_t *info = cfg_flags_info(field);
226 
227  char *padding = str_repeat(" ", alignment.arg_alignment, options.arena);
228  io_printf(options.io, "%soptions: ", padding);
229 
230  for (size_t i = 0; i < info->count; i++)
231  {
232  const cfg_choice_t *choice = info->options + i;
233  io_printf(options.io, "%s", choice->text);
234 
235  if (i != info->count - 1)
236  {
237  io_printf(options.io, " | ");
238  }
239  }
240 
241  io_printf(options.io, "\n");
242 }
243 
244 static void print_vector(io_t *io, const vector_t *vec)
245 {
246  if (vec == NULL)
247  {
248  io_printf(io, "(default: empty)\n");
249  return;
250  }
251 
252  size_t len = vector_len(vec);
253  if (len == 0)
254  {
255  io_printf(io, "(default: empty)\n");
256  return;
257  }
258 
259  io_printf(io, "(default: ");
260 
261  for (size_t i = 0; i < len; i++)
262  {
263  const char *str = vector_get(vec, i);
264  io_printf(io, "%s", str);
265 
266  if (i != len - 1)
267  {
268  io_printf(io, ", ");
269  }
270  }
271 
272  io_printf(io, ")");
273 }
274 
275 static void print_field_default(format_config_t options, const cfg_field_t *field)
276 {
277  switch (cfg_get_type(field))
278  {
279  case eConfigBool: {
280  io_printf(options.io, "(default: %s)\n", cfg_bool_info(field) ? "true" : "false");
281  break;
282  }
283 
284  case eConfigInt: {
285  const cfg_int_t *info = cfg_int_info(field);
286  io_printf(options.io, "(default: %d)", info->initial);
287  print_range(options.io, info->min, info->max);
288  io_printf(options.io, "\n");
289  break;
290  }
291 
292  case eConfigVector: {
293  const vector_t *info = cfg_vector_info(field);
294  print_vector(options.io, info);
295  io_printf(options.io, "\n");
296  break;
297  }
298 
299  case eConfigString: {
300  const char *info = cfg_string_info(field);
301  if (info != NULL)
302  {
303  io_printf(options.io, "(default: `%s`)", info);
304  }
305  io_printf(options.io, "\n");
306  break;
307  }
308 
309  case eConfigEnum: {
310  print_enum_default(options, field);
311  break;
312  }
313 
314  case eConfigFlags: {
315  print_flags_default(options, field);
316  break;
317  }
318 
319  default: {
320  const cfg_info_t *info = cfg_get_info(field);
321  const char *ty = cfg_type_string(cfg_get_type(field));
322  CT_NEVER("unknown field type %s (%s)", ty, info->name);
323  }
324  }
325 }
326 
327 static bool print_field_details(format_config_t options, alignment_info_t alignment, const cfg_field_t *field)
328 {
329  switch (cfg_get_type(field))
330  {
331  case eConfigEnum: {
332  print_enum(options, alignment, field);
333  return true;
334  }
335 
336  case eConfigFlags: {
337  print_flags(options, alignment, field);
338  return true;
339  }
340 
341  default:
342  break;
343  }
344  return false;
345 }
346 
347 // return true if the field needs a second line
348 static bool print_field_info(format_config_t options, alignment_info_t alignment, bool win_style, const cfg_field_t *field)
349 {
350  const cfg_info_t *info = cfg_get_info(field);
351 
352  size_t offset = print_field_args(options, info, win_style);
353  size_t padding = alignment.arg_alignment - offset;
354 
355  char *pad = str_repeat(" ", padding, options.arena);
356 
357  bool needs_second_line = false;
358 
359  // print the first line
360  size_t first_newline = str_find(info->brief, "\n");
361  if (first_newline == SIZE_MAX)
362  {
363  io_printf(options.io, "%s%s", pad, info->brief);
364  size_t after_brief = alignment.brief_alignment - ctu_strlen(info->brief);
365  char *pad2 = str_repeat(" ", after_brief, options.arena);
366 
367  io_printf(options.io, "%s", pad2);
368 
369  print_field_default(options, field);
370  }
371  else
372  {
373  // print the first line
374  io_printf(options.io, "%s%.*s", pad, (int)first_newline, info->brief);
375 
376  size_t after_brief = alignment.brief_alignment - first_newline;
377  char *pad2 = str_repeat(" ", after_brief, options.arena);
378 
379  io_printf(options.io, "%s", pad2);
380 
381  print_field_default(options, field);
382 
383  // print remaining lines, putting padding in front of each
384  char *pad_remaining = str_repeat(" ", alignment.arg_alignment, options.arena);
385  size_t start = first_newline + 1;
386  size_t len = 0;
387  for (size_t i = start; info->brief[i]; i++)
388  {
389  len += 1;
390  if (info->brief[i] == '\n')
391  {
392  io_printf(options.io, "%s%.*s\n", pad_remaining, (int)len, info->brief + start);
393  start = i + 1;
394  len = 0;
395  }
396  }
397 
398  if (len != 0)
399  {
400  io_printf(options.io, "%s%.*s\n", pad_remaining, (int)len, info->brief + start);
401  }
402 
403  needs_second_line = true;
404  }
405 
406  return print_field_details(options, alignment, field) || needs_second_line;
407 }
408 
409 static void print_config_group(format_config_t options, bool win_style, const cfg_group_t *config)
410 {
411  // we right align the args based on the longest
412  alignment_info_t alignment = get_group_alignment(config, win_style);
413 
414  vector_t *fields = cfg_get_fields(config);
415  size_t field_count = vector_len(fields);
416 
417  const cfg_info_t *group_info = cfg_group_info(config);
418 
419  io_printf(options.io, "%s: %s\n", group_info->name, group_info->brief);
420 
421  for (size_t i = 0; i < field_count; i++)
422  {
423  const cfg_field_t *field = vector_get(fields, i);
424  bool needs_second_line = print_field_info(options, alignment, win_style, field);
425  if (needs_second_line && i != field_count - 1)
426  {
427  io_printf(options.io, "\n");
428  }
429  }
430 
431  io_printf(options.io, "\n");
432  typevec_t *groups = cfg_get_groups(config);
433  size_t group_count = typevec_len(groups);
434 
435  for (size_t i = 0; i < group_count; i++)
436  {
437  const cfg_group_t *group = typevec_offset(groups, i);
438  print_config_group(options, win_style, group);
439  }
440 }
441 
442 static void print_usage(format_config_t options, const char *name)
443 {
444  CTASSERT(name != NULL);
445 
446  io_printf(options.io, "usage: %s [options] files...\n\n", name);
447  io_printf(options.io,
448  " +--- About --------------------------------------------------------------------------+\n"
449  " | The command line supports both posix and windows flag syntax |\n"
450  " | meaning that any flag that can be prefixed with a single dash |\n"
451  " | can also be prefixed with a forward slash. For example: -h and /h are equivalent. |\n"
452  " | String arguments can be quoted to allow spaces, For example: -o \"output file\" |\n"
453  " | `:`, `=`, or a space can be used to separate the flag from the value |\n"
454  " | -o:output.txt, -o=output.txt, -o output.txt |\n"
455  " | A single flag argument may be specified in multiple parts. |\n"
456  " | For example: /cpp:c++20 /cpp:modules and /cpp:\"c++20,modules\" are all equivilent |\n"
457  " | A leading `-` may be used to disable a flag. For example: /cpp:-modules |\n"
458  " +------------------------------------------------------------------------------------+\n\n"
459  );
460 }
461 
462 STA_DECL
463 void print_config(print_config_t print, const cfg_group_t *config)
464 {
465  print_options_t options = print.options;
466  format_config_t format_config = {
467  .arena = options.arena,
468  .io = options.io,
469  .context = format_context_make(options)
470  };
471 
472  if (print.print_usage)
473  {
474  print_usage(format_config, print.name);
475  }
476 
477  print_config_group(format_config, print.win_style, config);
478 }
STA_DECL char * colour_format(format_context_t context, colour_t idx, const char *fmt,...)
Definition: colour.c:65
#define STA_DECL
sal2 annotation on function implementations to copy annotations from the declaration
CT_NODISCARD CT_PUREFN CT_BASE_API size_t ctu_strlen(const char *str)
get the length of a string not including the null terminator equivalent to strlen but with safety che...
Definition: util.c:87
CT_PUREFN CT_CONFIG_API const vector_t * cfg_vector_info(const cfg_field_t *field)
get the information about a vector field
Definition: reflect.c:70
CT_CONSTFN CT_CONFIG_API const char * cfg_arg_prefix(arg_style_t style)
get the prefix for an argument style
Definition: reflect.c:125
CT_PUREFN CT_CONFIG_API bool cfg_bool_info(const cfg_field_t *field)
get the information about a yes/no field
Definition: reflect.c:54
CT_PUREFN CT_CONFIG_API const cfg_info_t * cfg_group_info(const cfg_group_t *config)
get the information about a configuration group
Definition: reflect.c:22
CT_CONSTFN CT_CONFIG_API const char * cfg_type_string(cfg_type_t type)
get the name of a configuration type
Definition: reflect.c:99
CT_PUREFN CT_CONFIG_API typevec_t * cfg_get_groups(const cfg_group_t *config)
get all subgroups in a configuration group
Definition: reflect.c:30
CT_PUREFN CT_CONFIG_API const cfg_int_t * cfg_int_info(const cfg_field_t *field)
get the information about an integer field
Definition: reflect.c:46
CT_PUREFN CT_CONFIG_API const cfg_enum_t * cfg_enum_info(const cfg_field_t *field)
get the information about a choice field
Definition: reflect.c:78
CT_PUREFN CT_CONFIG_API const cfg_enum_t * cfg_flags_info(const cfg_field_t *field)
get the information about a flags field
Definition: reflect.c:86
CT_PUREFN CT_CONFIG_API const char * cfg_string_info(const cfg_field_t *field)
get the information about a string field
Definition: reflect.c:62
CT_PUREFN CT_CONFIG_API vector_t * cfg_get_fields(const cfg_group_t *config)
get all fields in a configuration group
Definition: reflect.c:38
CT_PUREFN CT_CONFIG_API cfg_type_t cfg_get_type(const cfg_field_t *field)
get the type of a configuration field
Definition: reflect.c:6
CT_PUREFN CT_CONFIG_API const cfg_info_t * cfg_get_info(const cfg_field_t *field)
get the information about a configuration field
Definition: reflect.c:14
arg_style_t
Definition: config.h:38
CT_IO_API size_t io_printf(io_t *io, STA_FORMAT_STRING const char *fmt,...)
printf to an io object
#define CT_MAX(L, R)
Definition: macros.h:34
#define CT_NEVER(...)
assert that a code path is never reached
Definition: panic.h:136
#define CTASSERT(expr)
assert a condition, prints the condition as a message
Definition: panic.h:130
CT_PUREFN CT_STD_API size_t str_find(const char *str, const char *sub)
find the first instance of a substring in a string
Definition: str.c:896
CT_NODISCARD CT_STD_API char * str_repeat(const char *str, size_t times, arena_t *arena)
repeat a string
Definition: str.c:336
CT_NODISCARD CT_PUREFN CT_STD_API size_t typevec_len(const typevec_t *vec)
get the length of a vector
Definition: vector.c:120
CT_NODISCARD CT_PUREFN CT_STD_API void * typevec_offset(const typevec_t *vec, size_t index)
get a pointer to the value at the given index
Definition: vector.c:191
CT_NODISCARD CT_PUREFN CT_STD_API void * vector_get(const vector_t *vector, size_t index)
get a value from a vector
Definition: vector.c:134
CT_NODISCARD CT_PUREFN CT_STD_API size_t vector_len(const vector_t *vector)
get the length of a vector
Definition: vector.c:152
size_t brief_alignment
Definition: config.c:34
size_t arg_alignment
Definition: config.c:33
an allocator object
Definition: arena.h:86
const cfg_arg_t * args
Definition: config.h:61
size_t count
Definition: config.h:62
arg_style_t style
Definition: config.h:46
const char * arg
Definition: config.h:47
a choice in a set of options
Definition: config.h:97
STA_FIELD_STRING const char * text
the name of this choice
Definition: config.h:99
size_t value
the value of this choice
Definition: config.h:102
a choice from a set of options
Definition: config.h:107
size_t initial
the initial choice this must match the value of one of the choices
Definition: config.h:116
size_t count
the number of choices in this set
Definition: config.h:112
information about a configuration field
Definition: config.h:69
STA_FIELD_STRING const char * brief
a brief description of this field
Definition: config.h:74
cfg_arg_array_t args
the spellings to use for this field
Definition: config.h:77
STA_FIELD_STRING const char * name
the name of this field
Definition: config.h:71
an integer field
Definition: config.h:82
generic print options
Definition: format.h:35
arena_t * arena
temporary arena
Definition: format.h:37
io_t * io
io buffer
Definition: format.h:40
arena_t * arena
Definition: config.c:26
format_context_t context
Definition: config.c:28
io_t * io
Definition: config.c:27
a formatting context when using colours
Definition: colour.h:46
io object implementation
Definition: impl.h:122
config format options
Definition: config.h:19
const char * name
command line name of this program
Definition: config.h:33
print_options_t options
generic print options
Definition: config.h:21
bool print_usage
should the command line usage header be printed
Definition: config.h:24
bool win_style
which platform to format for if true, all flags will be formatted with a leading slash if false,...
Definition: config.h:29
A vector with a fixed type size.
Definition: vector.h:24
a generic vector of pointers
Definition: vector.c:16
format_context_t format_context_make(print_options_t options)
Definition: common.c:114
#define COLOUR_ARG
Definition: config.c:22
STA_DECL void print_config(print_config_t print, const cfg_group_t *config)
print a configuration object
Definition: config.c:463