summaryrefslogtreecommitdiff
path: root/libs/cargs/src/cargs.c
blob: 2d7d94572f62ad31754c5d86ae0e0375ae63da41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
#include <assert.h>
#include <cargs.h>
#include <memory.h>
#include <stdio.h>
#include <string.h>

#define CAG_OPTION_PRINT_DISTANCE 4
#define CAG_OPTION_PRINT_MIN_INDENTION 20

static void cag_option_print_value(const cag_option *option,
  size_t *accessor_length, FILE *destination)
{
  if (option->value_name != NULL) {
    *accessor_length += fprintf(destination, "=%s", option->value_name);
  }
}

static void cag_option_print_letters(const cag_option *option, bool *first,
  size_t *accessor_length, FILE *destination)
{
  const char *access_letter;
  access_letter = option->access_letters;
  if (access_letter != NULL) {
    while (*access_letter) {
      if (*first) {
        *accessor_length += fprintf(destination, "-%c", *access_letter);
        *first = false;
      } else {
        *accessor_length += fprintf(destination, ", -%c", *access_letter);
      }
      ++access_letter;
    }
  }
}

static void cag_option_print_name(const cag_option *option, bool *first,
  size_t *accessor_length, FILE *destination)
{
  if (option->access_name != NULL) {
    if (*first) {
      *accessor_length += fprintf(destination, "--%s", option->access_name);
    } else {
      *accessor_length += fprintf(destination, ", --%s", option->access_name);
    }
  }
}

static size_t cag_option_get_print_indention(const cag_option *options,
  size_t option_count)
{
  size_t option_index, indention, result;
  const cag_option *option;

  result = CAG_OPTION_PRINT_MIN_INDENTION;

  for (option_index = 0; option_index < option_count; ++option_index) {
    indention = CAG_OPTION_PRINT_DISTANCE;
    option = &options[option_index];
    if (option->access_letters != NULL && *option->access_letters) {
      indention += strlen(option->access_letters) * 4 - 2;
      if (option->access_name != NULL) {
        indention += strlen(option->access_name) + 4;
      }
    } else if (option->access_name != NULL) {
      indention += strlen(option->access_name) + 2;
    }

    if (option->value_name != NULL) {
      indention += strlen(option->value_name) + 1;
    }

    if (indention > result) {
      result = indention;
    }
  }

  return result;
}

void cag_option_print(const cag_option *options, size_t option_count,
  FILE *destination)
{
  size_t option_index, indention, i, accessor_length;
  const cag_option *option;
  bool first;

  indention = cag_option_get_print_indention(options, option_count);

  for (option_index = 0; option_index < option_count; ++option_index) {
    option = &options[option_index];
    accessor_length = 0;
    first = true;

    fputs("  ", destination);

    cag_option_print_letters(option, &first, &accessor_length, destination);
    cag_option_print_name(option, &first, &accessor_length, destination);
    cag_option_print_value(option, &accessor_length, destination);

    for (i = accessor_length; i < indention; ++i) {
      fputs(" ", destination);
    }

    fputs(" ", destination);
    fputs(option->description, destination);

    fprintf(destination, "\n");
  }
}

void cag_option_prepare(cag_option_context *context, const cag_option *options,
  size_t option_count, int argc, char **argv)
{
  // This just initialized the values to the beginning of all the arguments.
  context->options = options;
  context->option_count = option_count;
  context->argc = argc;
  context->argv = argv;
  context->index = 1;
  context->inner_index = 0;
  context->forced_end = false;
}

static const cag_option *cag_option_find_by_name(cag_option_context *context,
  char *name, size_t name_size)
{
  const cag_option *option;
  size_t i;

  // We loop over all the available options and stop as soon as we have found
  // one. We don't use any hash map table, since there won't be that many
  // arguments anyway.
  for (i = 0; i < context->option_count; ++i) {
    option = &context->options[i];

    // The option might not have an item name, we can just skip those.
    if (option->access_name == NULL) {
      continue;
    }

    // Try to compare the name of the access name. We can use the name_size or
    // this comparison, since we are guaranteed to have null-terminated access
    // names.
    if (strncmp(option->access_name, name, name_size) == 0) {
      return option;
    }
  }

  return NULL;
}

static const cag_option *cag_option_find_by_letter(cag_option_context *context,
  char letter)
{
  const cag_option *option;
  size_t i;

  // We loop over all the available options and stop as soon as we have found
  // one. We don't use any look up table, since there won't be that many
  // arguments anyway.
  for (i = 0; i < context->option_count; ++i) {
    option = &context->options[i];

    // If this option doesn't have any access letters we will skip them.
    if (option->access_letters == NULL) {
      continue;
    }

    // Verify whether this option has the access letter in it's access letter
    // string. If it does, then this is our option.
    if (strchr(option->access_letters, letter) != NULL) {
      return option;
    }
  }

  return NULL;
}

static void cag_option_parse_value(cag_option_context *context,
  const cag_option *option, char **c)
{
  // And now let's check whether this option is supposed to have a value, which
  // is the case if there is a value name set. The value can be either submitted
  // with a '=' sign or a space, which means we would have to jump over to the
  // next argv index. This is somewhat ugly, but we do it to behave the same as
  // the other option parsers.
  if (option->value_name != NULL) {
    if (**c == '=') {
      context->value = ++(*c);
    } else {
      // If the next index is larger or equal to the argument count, then the
      // parameter for this option is missing. The user will know about this,
      // since the value pointer of the context will be NULL because we don't
      // set it here in that case.
      if (context->argc > context->index + 1) {
        // We consider this argv to be the value, no matter what the contents
        // are.
        ++context->index;
        *c = context->argv[context->index];
        context->value = *c;
      }
    }

    // Move c to the end of the value, to not confuse the caller about our
    // position.
    while (**c) {
      ++(*c);
    }
  }
}

static void cag_option_parse_access_name(cag_option_context *context, char **c)
{
  const cag_option *option;
  char *n;

  // Now we need to extract the access name, which is any symbol up to a '=' or
  // a '\0'.
  n = *c;
  while (**c && **c != '=') {
    ++*c;
  }

  // Now this will obviously always be true, but we are paranoid. Sometimes. It
  // doesn't hurt to check.
  assert(*c >= n);

  // Figure out which option this name belongs to. This might return NULL if the
  // name is not registered, which means the user supplied an unknown option. In
  // that case we return true to indicate that we finished with this option. We
  // have to skip the value parsing since we don't know whether the user thinks
  // this option has one or not. Since we don't set any identifier specifically,
  // it will remain '?' within the context.
  option = cag_option_find_by_name(context, n, (size_t)(*c - n));
  if (option == NULL) {
    // Since this option is invalid, we will move on to the next index. There is
    // nothing we can do about this.
    ++context->index;
    return;
  }

  // We found an option and now we can specify the identifier within the
  // context.
  context->identifier = option->identifier;

  // And now we try to parse the value. This function will also check whether
  // this option is actually supposed to have a value.
  cag_option_parse_value(context, option, c);

  // And finally we move on to the next index.
  ++context->index;
}

static void cag_option_parse_access_letter(cag_option_context *context,
  char **c)
{
  const cag_option *option;
  char *n = *c;
  char *v;

  // Figure out which option this letter belongs to. This might return NULL if
  // the letter is not registered, which means the user supplied an unknown
  // option. In that case we return true to indicate that we finished with this
  // option. We have to skip the value parsing since we don't know whether the
  // user thinks this option has one or not. Since we don't set any identifier
  // specifically, it will remain '?' within the context.
  option = cag_option_find_by_letter(context, n[context->inner_index]);
  if (option == NULL) {
    ++context->index;
    context->inner_index = 0;
    return;
  }

  // We found an option and now we can specify the identifier within the
  // context.
  context->identifier = option->identifier;

  // And now we try to parse the value. This function will also check whether
  // this option is actually supposed to have a value.
  v = &n[++context->inner_index];
  cag_option_parse_value(context, option, &v);

  // Check whether we reached the end of this option argument.
  if (*v == '\0') {
    ++context->index;
    context->inner_index = 0;
  }
}

static void cag_option_shift(cag_option_context *context, int start, int option,
  int end)
{
  char *tmp;
  int a_index, shift_index, shift_count, left_index, right_index;

  shift_count = option - start;

  // There is no shift is required if the start and the option have the same
  // index.
  if (shift_count == 0) {
    return;
  }

  // Lets loop through the option strings first, which we will move towards the
  // beginning.
  for (a_index = option; a_index < end; ++a_index) {
    // First remember the current option value, because we will have to save
    // that later at the beginning.
    tmp = context->argv[a_index];

    // Let's loop over all option values and shift them one towards the end.
    // This will override the option value we just stored temporarily.
    for (shift_index = 0; shift_index < shift_count; ++shift_index) {
      left_index = a_index - shift_index;
      right_index = a_index - shift_index - 1;
      context->argv[left_index] = context->argv[right_index];
    }

    // Now restore the saved option value at the beginning.
    context->argv[a_index - shift_count] = tmp;
  }

  // The new index will be before all non-option values, in such a way that they
  // all will be moved again in the next fetch call.
  context->index = end - shift_count;
}

static bool cag_option_is_argument_string(const char *c)
{
  return *c == '-' && *(c + 1) != '\0';
}

static int cag_option_find_next(cag_option_context *context)
{
  int next_index, next_option_index;
  char *c;

  // Prepare to search the next option at the next index.
  next_index = context->index;
  next_option_index = next_index;

  // Grab a pointer to the string and verify that it is not the end. If it is
  // the end, we have to return false to indicate that we finished.
  c = context->argv[next_option_index];
  if (context->forced_end || c == NULL) {
    return -1;
  }

  // Check whether it is a '-'. We need to find the next option - and an option
  // always starts with a '-'. If there is a string "-\0", we don't consider it
  // as an option neither.
  while (!cag_option_is_argument_string(c)) {
    c = context->argv[++next_option_index];
    if (c == NULL) {
      // We reached the end and did not find any argument anymore. Let's tell
      // our caller that we reached the end.
      return -1;
    }
  }

  // Indicate that we found an option which can be processed. The index of the
  // next option will be returned.
  return next_option_index;
}

bool cag_option_fetch(cag_option_context *context)
{
  char *c;
  int old_index, new_index;

  // Reset our identifier to a question mark, which indicates an "unknown"
  // option. The value is set to NULL, to make sure we are not carrying the
  // parameter from the previous option to this one.
  context->identifier = '?';
  context->value = NULL;

  // Check whether there are any options left to parse and remember the old
  // index as well as the new index. In the end we will move the option junk to
  // the beginning, so that non option arguments can be read.
  old_index = context->index;
  new_index = cag_option_find_next(context);
  if (new_index >= 0) {
    context->index = new_index;
  } else {
    return false;
  }

  // Grab a pointer to the beginning of the option. At this point, the next
  // character must be a '-', since if it was not the prepare function would
  // have returned false. We will skip that symbol and proceed.
  c = context->argv[context->index];
  assert(*c == '-');
  ++c;

  // Check whether this is a long option, starting with a double "--".
  if (*c == '-') {
    ++c;

    // This might be a double "--" which indicates the end of options. If this
    // is the case, we will not move to the next index. That ensures that
    // another call to the fetch function will not skip the "--".
    if (*c == '\0') {
      context->forced_end = true;
    } else {
      // We parse now the access name. All information about it will be written
      // to the context.
      cag_option_parse_access_name(context, &c);
    }
  } else {
    // This is no long option, so we can just parse an access letter.
    cag_option_parse_access_letter(context, &c);
  }

  // Move the items so that the options come first followed by non-option
  // arguments.
  cag_option_shift(context, old_index, new_index, context->index);

  return context->forced_end == false;
}

char cag_option_get(const cag_option_context *context)
{
  // We just return the identifier here.
  return context->identifier;
}

const char *cag_option_get_value(const cag_option_context *context)
{
  // We just return the internal value pointer of the context.
  return context->value;
}

int cag_option_get_index(const cag_option_context *context)
{
  // Either we point to a value item,
  return context->index;
}