Intelligent variational extension based on format string

I have a daemon that reads a configuration file to find out where to write something. The following line exists in the configuration file:

output = /tmp/foo/%d/%s/output 

Or it might look like this:

 output = /tmp/foo/%s/output/%d 

... or just like that:

 output = /tmp/foo/%s/output 

... or finally:

 output = /tmp/output 

I have this line as cfg-> pathfmt in my program. What I'm trying to do now is come up with some kind of smart way to use it.

A little more explanation, a path can contain up to two components for formatting. % d will be expanded as job identifier (int),% s as job name (string). The user may want to use one or both in the configuration file. I need to know what they want and in what order before I finally pass it snprintf (). I can narrow it down a bit, but I still want to talk to strtok (), and that seems ugly.

I want to give users this kind of flexibility, but I get lost looking for a reasonable, portable way to implement it. I will also completely and completely lose how to start looking for this.

I would be very happy if:

  • Someone can help me narrow my search phrase to find good examples.
  • Someone may post a link to some OSS project that implements this
  • Someone might post some psuedo code

I don’t need the code written for me, I'm just fixated on the fact that (I think) should be something very simple and need some help taking the first bite. It really seems to me that I changed my mind and did not notice the obvious.

The end result should be a logical function such as this:

 bool output_sugar(const char *fmt, int jobid, const char *jobname, struct job *j); 

Then it will call snprintf () (reasonably) on j-> outpath, returning false if any garbage (i.e.% followed by something other than s, d or%) is in the configuration line (or its zero). Sanity checks are easy, I just get a bit of time for the number (and order) of arguments to format correctly.

Thanks in advance. Also, feel free to edit this headline if you have a reputation to do this, as I said, I'm not quite sure how to ask a question on one line. I think I need a parser , but it feels awkward using a full lexer / parser to process one simple string.

+4
source share
3 answers

Yes, you need some kind of parser. However, this should not be complicated:

 void format_filename(const char *fmt, int jobid, const char *jobname, char *buffer, size_t buflen) { char *end = buffer + buflen - 1; const char *src = fmt; char *dst = buffer; char c; assert(buffer != 0 && fmt != 0 && buflen != 0 && jobname != 0); while ((c = *src++) != '\0') { if (dst >= end) err_exit("buffer overflow in %s(): format = %s\n", __func__, fmt); else if (c != '%') *dst++ = c; else if ((c = *src++) == '\0' || c == '%') { *dst++ = '%'; if (c == '\0') break; } else if (c == 's') { size_t len = strlen(jobname); if (len > end - dst) err_exit("buffer overflow on jobname in %s(): format = %s\n", __func__, fmt); else { strcpy(dst, jobname); dst += len; } } else if (c == 'd') { int nchars = snprintf(dst, end - dst, "%d", jobid); if (nchars < 0 || nchars >= end - dst) err_exit("format error on jobid in %s(); format = %s\n", __func__, fmt); dst += nchars; } else err_exit("invalid format character %d in %s(): format = %s\n", c, __func__, fmt); } *dst = '\0'; } 

Now tested code. Note that it supports the notation "%%" to allow the user to embed one "%" in the output. In addition, it treats one β€œ%” at the end of the line as valid and equivalent to β€œ%%”. It calls err_exit () on error; You can choose alternative error strategies appropriate to your system. I just assume that you included <assert.h> , <stdio.h> and <string.h> and a header for the err_exit() (variadic) function.


Test code ...

 #include <stdio.h> #include <string.h> #include <stdarg.h> #include <assert.h> static void err_exit(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); exit(1); } 

... then format_filename() as above, then ...

 #define DIM(x) (sizeof(x)/sizeof(*(x))) static const char *format[] = { "/tmp/%d/name/%s", "/tmp/%s/number/%d", "/tmp/%s.%d%%", "/tmp/%", }; int main(void) { char buffer[64]; size_t i; for (i = 0; i < DIM(format); i++) { format_filename(format[i], 1234, "job-name", buffer, sizeof(buffer)); printf("fmt = %-20s; name = %s\n", format[i], buffer); } return(0); } 
+8
source

Using strtok is error prone. You can treat variables as a mini-language using (fl) lex and yacc. There is a simple tutorial here.

 %{ #include <stdio.h> %} %% %d printf("%04d",jobid); %s printf("%s",stripspaces(dirname)); %% 

I created an ODBC wrapper that would allow you to do things like dbprintf ("insert into blah values% s% D% T% Y", stuff here ...); But that was many years ago, and I bit it and parsed the format string using strtok.

+5
source

If the number of options is small and you no longer need / need additional flexibility and complexity of the analyzer, you can simply find each potential replacement substring using strstr ().

If you have only two options, you can easily create a structure with four branched if / else (only A, only B, from A to B, and from B to A), in which you can call sprintf () with correctly ordered arguments. Otherwise, make several calls to sprintf (), each of which replaces only the first replacement marker in the format string. (This means creating a list of necessary replacements and sorting in order of appearance ...)

+1
source

All Articles