|
1
|
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2
|
/* vim: set ts=2 et sw=2 tw=80: */ |
|
3
|
/* |
|
4
|
** Copyright (c) 2021 Stephan Beal (https://wanderinghorse.net/home/stephan/) |
|
5
|
** |
|
6
|
** This program is free software; you can redistribute it and/or |
|
7
|
** modify it under the terms of the Simplified BSD License (also |
|
8
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
9
|
** |
|
10
|
** This program is distributed in the hope that it will be useful, |
|
11
|
** but without any warranty; without even the implied warranty of |
|
12
|
** merchantability or fitness for a particular purpose. |
|
13
|
** |
|
14
|
******************************************************************************* |
|
15
|
** |
|
16
|
** This application reads in Fossil SCM skin configuration files and emits |
|
17
|
** them in a form suitable for importing directly into a fossil database |
|
18
|
** using the (fossil config import) command. |
|
19
|
** |
|
20
|
** As input it requires one or more skin configuration files (css.txt, |
|
21
|
** header.txt, footer.txt, details.txt, js.txt) and all output goes to |
|
22
|
** stdout unless redirected using the -o FILENAME flag. |
|
23
|
** |
|
24
|
** Run it with no arguments or one of (help, --help, -?) for help text. |
|
25
|
*/ |
|
26
|
#include <stdio.h> |
|
27
|
#include <stdlib.h> |
|
28
|
#include <string.h> |
|
29
|
#include <errno.h> |
|
30
|
#include <time.h> |
|
31
|
#include <stdarg.h> |
|
32
|
|
|
33
|
static struct App_ { |
|
34
|
const char * argv0; |
|
35
|
time_t now; |
|
36
|
FILE * ostr; |
|
37
|
} App = { |
|
38
|
0, 0, 0 |
|
39
|
}; |
|
40
|
|
|
41
|
static void err(const char *zFmt, ...){ |
|
42
|
va_list vargs; |
|
43
|
va_start(vargs, zFmt); |
|
44
|
fputs("ERROR: ",stderr); |
|
45
|
vfprintf(stderr, zFmt, vargs); |
|
46
|
fputc('\n', stderr); |
|
47
|
va_end(vargs); |
|
48
|
} |
|
49
|
|
|
50
|
static void app_usage(int isErr){ |
|
51
|
FILE * const ios = isErr ? stderr : stdout; |
|
52
|
fprintf(ios, "Usage: %s ?OPTIONS? input-filename...\n\n", |
|
53
|
App.argv0); |
|
54
|
fprintf(ios, "Each filename must be one file which is conventionally " |
|
55
|
"part of a Fossil SCM skin set:\n" |
|
56
|
" css.txt, header.txt, footer.txt, details.txt, js.txt\n"); |
|
57
|
fprintf(ios, "\nOptions:\n"); |
|
58
|
fprintf(ios, "\n\t-o FILENAME = send output to the given file. " |
|
59
|
"'-' means stdout (the default).\n"); |
|
60
|
fputc('\n', ios); |
|
61
|
} |
|
62
|
|
|
63
|
/* |
|
64
|
** Reads file zFilename, stores its contents in *zContent, and sets the |
|
65
|
** length of its contents to *nContent. |
|
66
|
** |
|
67
|
** Returns 0 on success. On error, *zContent and *nContent are not |
|
68
|
** modified and it may emit a message describing the problem. |
|
69
|
*/ |
|
70
|
int read_file(char const *zFilename, unsigned char ** zContent, |
|
71
|
int * nContent){ |
|
72
|
long fpos; |
|
73
|
int rc = 0; |
|
74
|
unsigned char * zMem = 0; |
|
75
|
FILE * f = fopen(zFilename, "rb"); |
|
76
|
if(!f){ |
|
77
|
err("Cannot open file %s. Errno=%d", zFilename, errno); |
|
78
|
return errno; |
|
79
|
} |
|
80
|
fseek(f, 0L, SEEK_END); |
|
81
|
rc = errno; |
|
82
|
if(rc){ |
|
83
|
err("Cannot seek() file %s. Errno=%d", zFilename, rc); |
|
84
|
goto end; |
|
85
|
} |
|
86
|
fpos = ftell(f); |
|
87
|
fseek(f, 0L, SEEK_SET); |
|
88
|
zMem = (unsigned char *)malloc((size_t)fpos + 1); |
|
89
|
if(!zMem){ |
|
90
|
err("Malloc failed."); |
|
91
|
rc = ENOMEM; |
|
92
|
goto end; |
|
93
|
} |
|
94
|
zMem[fpos] = 0; |
|
95
|
if(fpos && (size_t)1 != fread(zMem, (size_t)fpos, 1, f)){ |
|
96
|
rc = EIO; |
|
97
|
err("Error #%d reading file %s", rc, zFilename); |
|
98
|
goto end; |
|
99
|
} |
|
100
|
end: |
|
101
|
fclose(f); |
|
102
|
if(rc){ |
|
103
|
free(zMem); |
|
104
|
}else{ |
|
105
|
*zContent = zMem; |
|
106
|
*nContent = fpos; |
|
107
|
} |
|
108
|
return rc; |
|
109
|
} |
|
110
|
|
|
111
|
/* |
|
112
|
** Expects zFilename to be one of the conventional skin filename |
|
113
|
** parts. This routine converts it to config format and emits it to |
|
114
|
** App.ostr. |
|
115
|
*/ |
|
116
|
int dispatch_file(char const *zFilename){ |
|
117
|
const char * zKey = 0; |
|
118
|
int nContent = 0, nContent2 = 0, nOut = 0, nTime = 0, rc = 0; |
|
119
|
time_t theTime = App.now; |
|
120
|
unsigned char * zContent = 0; |
|
121
|
unsigned char * z = 0; |
|
122
|
if(strstr(zFilename, "css.txt")){ |
|
123
|
zKey = "css"; |
|
124
|
}else if(strstr(zFilename, "header.txt")){ |
|
125
|
zKey = "header"; |
|
126
|
}else if(strstr(zFilename, "footer.txt")){ |
|
127
|
zKey = "footer"; |
|
128
|
}else if(strstr(zFilename, "details.txt")){ |
|
129
|
zKey = "details"; |
|
130
|
}else if(strstr(zFilename, "js.txt")){ |
|
131
|
zKey = "js"; |
|
132
|
}else { |
|
133
|
err("Cannot determine skin part from filename: %s", zFilename); |
|
134
|
return 1; |
|
135
|
} |
|
136
|
rc = read_file(zFilename, &zContent, &nContent); |
|
137
|
if(rc) return rc; |
|
138
|
for( z = zContent; z < zContent + nContent; ++z ){ |
|
139
|
/* Count file content length with ' characters doubled */ |
|
140
|
nContent2 += ('\'' == *z) ? 2 : 1; |
|
141
|
} |
|
142
|
while(theTime > 0){/* # of digits in time */ |
|
143
|
++nTime; |
|
144
|
theTime /= 10; |
|
145
|
} |
|
146
|
fprintf(App.ostr, "config /config %d\n", |
|
147
|
(int)(nTime + 12/*"value"+spaces+quotes*/ |
|
148
|
+ (int)strlen(zKey) + nContent2)); |
|
149
|
fprintf(App.ostr, "%d '%s' value '", (int)App.now, zKey); |
|
150
|
for( z = zContent; z < zContent + nContent; ++z ){ |
|
151
|
/* Emit file content with ' characters doubled */ |
|
152
|
if('\'' == (char)*z){ |
|
153
|
fputc('\'', App.ostr); |
|
154
|
} |
|
155
|
fputc((char)*z, App.ostr); |
|
156
|
} |
|
157
|
free(zContent); |
|
158
|
fprintf(App.ostr, "'\n"); |
|
159
|
return 0; |
|
160
|
} |
|
161
|
|
|
162
|
int main(int argc, char const * const * argv){ |
|
163
|
int rc = 0, i ; |
|
164
|
App.argv0 = argv[0]; |
|
165
|
App.ostr = stdout; |
|
166
|
if(argc<2){ |
|
167
|
app_usage(1); |
|
168
|
rc = 1; |
|
169
|
goto end; |
|
170
|
} |
|
171
|
App.now = time(0); |
|
172
|
for( i = 1; i < argc; ++i ){ |
|
173
|
const char * zArg = argv[i]; |
|
174
|
if(0==strcmp(zArg,"help") || |
|
175
|
0==strcmp(zArg,"--help") || |
|
176
|
0==strcmp(zArg,"-?")){ |
|
177
|
app_usage(0); |
|
178
|
rc = 0; |
|
179
|
break; |
|
180
|
}else if(0==strcmp(zArg,"-o")){ |
|
181
|
/* -o OUTFILE (- == stdout) */ |
|
182
|
++i; |
|
183
|
if(i==argc){ |
|
184
|
err("Missing filename for -o flag"); |
|
185
|
rc = 1; |
|
186
|
break; |
|
187
|
}else{ |
|
188
|
const char *zOut = argv[i]; |
|
189
|
if(App.ostr != stdout){ |
|
190
|
err("Cannot specify -o more than once."); |
|
191
|
rc = 1; |
|
192
|
break; |
|
193
|
} |
|
194
|
if(0!=strcmp("-",zOut)){ |
|
195
|
FILE * o = fopen(zOut, "wb"); |
|
196
|
if(!o){ |
|
197
|
err("Could not open file %s for writing. Errno=%d", |
|
198
|
zOut, errno); |
|
199
|
rc = errno; |
|
200
|
break; |
|
201
|
} |
|
202
|
App.ostr = o; |
|
203
|
} |
|
204
|
} |
|
205
|
}else if('-' == zArg[0]){ |
|
206
|
err("Unhandled argument: %s", zArg); |
|
207
|
rc = 1; |
|
208
|
break; |
|
209
|
}else{ |
|
210
|
rc = dispatch_file(zArg); |
|
211
|
if(rc) break; |
|
212
|
} |
|
213
|
} |
|
214
|
end: |
|
215
|
if(App.ostr != stdout){ |
|
216
|
fclose(App.ostr); |
|
217
|
} |
|
218
|
return rc ? EXIT_FAILURE : EXIT_SUCCESS; |
|
219
|
} |
|
220
|
|