585 |
mateuszvis |
1 |
/*
|
|
|
2 |
* Locales configuration for DOS
|
|
|
3 |
* Copyright (C) Mateusz Viste 2015
|
|
|
4 |
*
|
|
|
5 |
* Redistribution and use in source and binary forms, with or without
|
|
|
6 |
* modification, are permitted provided that the following conditions are met:
|
|
|
7 |
*
|
|
|
8 |
* 1. Redistributions of source code must retain the above copyright notice,
|
|
|
9 |
* this list of conditions and the following disclaimer.
|
|
|
10 |
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
11 |
* notice, this list of conditions and the following disclaimer in the
|
|
|
12 |
* documentation and/or other materials provided with the distribution.
|
|
|
13 |
*/
|
|
|
14 |
|
|
|
15 |
#include <stdio.h>
|
|
|
16 |
#include <stdlib.h> /* atoi() */
|
|
|
17 |
#include <string.h> /* strchr */
|
|
|
18 |
|
|
|
19 |
#include "country.h"
|
|
|
20 |
|
|
|
21 |
#define PVER "0.90"
|
|
|
22 |
#define PDATE "2015"
|
|
|
23 |
|
|
|
24 |
|
|
|
25 |
static void about(void) {
|
|
|
26 |
puts("localcfg v" PVER " - locales configuration for DOS\n"
|
|
|
27 |
"Copyright (C) Mateusz Viste 2015 / http://localcfg.sourceforge.net\n"
|
|
|
28 |
"\n"
|
|
|
29 |
"localcfg creates a custom COUNTRY.SYS-like file matching your preferences.\n"
|
|
|
30 |
"\n"
|
|
|
31 |
"usage: localcfg myprefs.sys [options]\n"
|
|
|
32 |
"\n"
|
|
|
33 |
"options allow to configure country locales to your likening, as follows:\n"
|
|
|
34 |
" /country:XX indicates your country code is XX (1 for USA, 33 for France, etc)\n"
|
|
|
35 |
" /cp:XXX adapts country data for codepage XXX (example: '437')\n"
|
|
|
36 |
" /decim:X reconfigures the decimal symbol to be 'X'");
|
|
|
37 |
puts(" /thous:X reconfigures the thousands symbol to be 'X'\n"
|
|
|
38 |
" /datesep:X sets the date separator to 'X' (for example '/')\n"
|
|
|
39 |
" /datefmt:X sets the date format, can be: MDY, DMY or YMD\n"
|
|
|
40 |
" /timesep:X sets the time separator to 'X' (for example ':')\n"
|
|
|
41 |
" /timefmt:X sets the time format: 0=12h with AM/PM or 1=24h\n"
|
|
|
42 |
" /curr:XXX sets the currency to XXX (a string of 1 to 4 characters)\n"
|
|
|
43 |
" /currpos:X sets the currency symbol position to X, where X is either");
|
|
|
44 |
puts(" 0=currency precedes the value, 1=currency follows the value and\n"
|
|
|
45 |
" 2=currency replaces the decimal sign\n"
|
|
|
46 |
" /currspc:X space between the currency and the value (0=no, 1=yes)\n"
|
|
|
47 |
" /currprec:X currency's precision (number of decimal digits, 0..9)\n"
|
|
|
48 |
" /yesno:XY sets the 'Yes/No' letter to XY (default: YN)\n"
|
|
|
49 |
);
|
|
|
50 |
}
|
|
|
51 |
|
|
|
52 |
|
|
|
53 |
static char *datestring(struct country *c) {
|
|
|
54 |
static char result[16];
|
|
|
55 |
switch (c->datefmt) {
|
|
|
56 |
case COUNTRY_DATE_MDY:
|
|
|
57 |
sprintf(result, "12%c31%c1990", c->datesep, c->datesep);
|
|
|
58 |
break;
|
|
|
59 |
case COUNTRY_DATE_DMY:
|
|
|
60 |
sprintf(result, "31%c12%c1990", c->datesep, c->datesep);
|
|
|
61 |
break;
|
|
|
62 |
case COUNTRY_DATE_YMD:
|
|
|
63 |
default:
|
|
|
64 |
sprintf(result, "1990%c12%c31", c->datesep, c->datesep);
|
|
|
65 |
break;
|
|
|
66 |
}
|
|
|
67 |
return(result);
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
|
|
|
71 |
static char *timestring(struct country *c) {
|
|
|
72 |
static char result[16];
|
|
|
73 |
if (c->timefmt == COUNTRY_TIME12) {
|
|
|
74 |
sprintf(result, "11%c59%c59 PM", c->timesep, c->timesep);
|
|
|
75 |
} else {
|
|
|
76 |
sprintf(result, "23%c59%c59", c->timesep, c->timesep);
|
|
|
77 |
}
|
|
|
78 |
return(result);
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
|
|
|
82 |
static char *currencystring(struct country *c) {
|
|
|
83 |
static char result[16];
|
|
|
84 |
char decimalpart[16];
|
|
|
85 |
char space[2] = {0, 0};
|
|
|
86 |
char decsym[8];
|
|
|
87 |
char cursym[8];
|
|
|
88 |
decimalpart[0] = '1';
|
|
|
89 |
decimalpart[1] = '2';
|
|
|
90 |
decimalpart[2] = '3';
|
|
|
91 |
decimalpart[3] = '4';
|
|
|
92 |
decimalpart[4] = '5';
|
|
|
93 |
decimalpart[5] = '6';
|
|
|
94 |
decimalpart[6] = '7';
|
|
|
95 |
decimalpart[7] = '8';
|
|
|
96 |
decimalpart[8] = '9';
|
|
|
97 |
decimalpart[9] = 0;
|
|
|
98 |
/* prepare the decimal string first */
|
|
|
99 |
if (c->currencyprec < 9) {
|
|
|
100 |
decimalpart[c->currencyprec] = 0;
|
|
|
101 |
}
|
|
|
102 |
/* prepare the currency space string */
|
|
|
103 |
if (c->currencyspace != 0) {
|
|
|
104 |
space[0] = ' ';
|
|
|
105 |
}
|
|
|
106 |
/* prepare the currency and decimal symbols */
|
|
|
107 |
if (c->currencydecsym != 0) { /* currency replaces the decimal point */
|
|
|
108 |
sprintf(decsym, "%s", c->currency);
|
|
|
109 |
cursym[0] = 0;
|
|
|
110 |
} else {
|
|
|
111 |
sprintf(decsym, "%c", c->decimal);
|
|
|
112 |
sprintf(cursym, "%s", c->currency);
|
|
|
113 |
}
|
|
|
114 |
if (c->currencyprec == 0) decsym[0] = 0;
|
|
|
115 |
/* compute the final string */
|
|
|
116 |
if (c->currencypos == 0) { /* currency precedes value */
|
|
|
117 |
sprintf(result, "%s%s99%s%s", cursym, space, decsym, decimalpart);
|
|
|
118 |
} else { /* currency follows value or replaces decimal symbol */
|
|
|
119 |
sprintf(result, "99%s%s%s%s", decsym, decimalpart, space, cursym);
|
|
|
120 |
}
|
|
|
121 |
return(result);
|
|
|
122 |
}
|
|
|
123 |
|
|
|
124 |
|
|
|
125 |
/* checks if str starts with prefix. returns 0 if so, non-zero otherwise. */
|
|
|
126 |
static int stringstartswith(char *str, char *prefix) {
|
|
|
127 |
for (;;) {
|
|
|
128 |
/* end of prefix means success */
|
|
|
129 |
if (*prefix == 0) return(0);
|
|
|
130 |
/* otherwise there is no match */
|
|
|
131 |
if (*str != *prefix) return(-1);
|
|
|
132 |
/* if match good so far, look at next char */
|
|
|
133 |
str += 1;
|
|
|
134 |
prefix += 1;
|
|
|
135 |
}
|
|
|
136 |
}
|
|
|
137 |
|
|
|
138 |
|
|
|
139 |
/* processes an argument. returns 0 on success, non-zero otherwise. */
|
|
|
140 |
static int processarg(char *arg, struct country *c) {
|
|
|
141 |
char *value;
|
|
|
142 |
int intvalue;
|
|
|
143 |
/* an option must start with a '/' */
|
|
|
144 |
if (arg[0] != '/') return(-1);
|
|
|
145 |
arg += 1; /* skip the slash */
|
|
|
146 |
/* find where the value starts */
|
|
|
147 |
value = strchr(arg, ':');
|
|
|
148 |
/* if no value present, fail */
|
|
|
149 |
if (value == NULL) return(-2);
|
|
|
150 |
value += 1;
|
|
|
151 |
if (*value == 0) return(-3);
|
|
|
152 |
/* interpret the option now */
|
|
|
153 |
if (stringstartswith(arg, "country:") == 0) {
|
|
|
154 |
intvalue = atoi(value);
|
|
|
155 |
if ((intvalue > 0) && (intvalue < 1000)) {
|
|
|
156 |
c->id = intvalue;
|
|
|
157 |
return(0);
|
|
|
158 |
}
|
|
|
159 |
} else if (stringstartswith(arg, "cp:") == 0) {
|
|
|
160 |
intvalue = atoi(value);
|
|
|
161 |
if ((intvalue > 0) && (intvalue < 1000)) {
|
|
|
162 |
c->codepage = intvalue;
|
|
|
163 |
return(0);
|
|
|
164 |
}
|
|
|
165 |
} else if (stringstartswith(arg, "decim:") == 0) {
|
|
|
166 |
if (value[1] == 0) { /* value must be exactly one character */
|
|
|
167 |
c->decimal = *value;
|
|
|
168 |
return(0);
|
|
|
169 |
}
|
|
|
170 |
} else if (stringstartswith(arg, "thous:") == 0) {
|
|
|
171 |
if (value[1] == 0) { /* value must be exactly one character */
|
|
|
172 |
c->thousands = *value;
|
|
|
173 |
return(0);
|
|
|
174 |
}
|
|
|
175 |
} else if (stringstartswith(arg, "datesep:") == 0) {
|
|
|
176 |
if (value[1] == 0) { /* value must be exactly one character */
|
|
|
177 |
c->datesep = *value;
|
|
|
178 |
return(0);
|
|
|
179 |
}
|
|
|
180 |
} else if (stringstartswith(arg, "timesep:") == 0) {
|
|
|
181 |
if (value[1] == 0) { /* value must be exactly one character */
|
|
|
182 |
c->timesep = *value;
|
|
|
183 |
return(0);
|
|
|
184 |
}
|
|
|
185 |
} else if (stringstartswith(arg, "datefmt:") == 0) {
|
|
|
186 |
if (strcmp(value, "MDY") == 0) {
|
|
|
187 |
c->datefmt = COUNTRY_DATE_MDY;
|
|
|
188 |
return(0);
|
|
|
189 |
} else if (strcmp(value, "DMY") == 0) {
|
|
|
190 |
c->datefmt = COUNTRY_DATE_DMY;
|
|
|
191 |
return(0);
|
|
|
192 |
} else if (strcmp(value, "YMD") == 0) {
|
|
|
193 |
c->datefmt = COUNTRY_DATE_YMD;
|
|
|
194 |
return(0);
|
|
|
195 |
}
|
|
|
196 |
} else if (stringstartswith(arg, "timefmt:") == 0) {
|
|
|
197 |
if (value[1] == 0) {
|
|
|
198 |
if ((value[0] >= '0') && (value[0] <= '1')) {
|
|
|
199 |
c->timefmt = value[0] - '0';
|
|
|
200 |
return(0);
|
|
|
201 |
}
|
|
|
202 |
}
|
|
|
203 |
} else if (stringstartswith(arg, "curr:") == 0) {
|
|
|
204 |
if (strlen(value) <= 4) {
|
|
|
205 |
strcpy(c->currency, value);
|
|
|
206 |
return(0);
|
|
|
207 |
}
|
|
|
208 |
} else if (stringstartswith(arg, "currpos:") == 0) {
|
|
|
209 |
if (value[1] == 0) {
|
|
|
210 |
if (value[0] == '0') {
|
|
|
211 |
c->currencypos = 0;
|
|
|
212 |
return(0);
|
|
|
213 |
} else if (value[0] == '1') {
|
|
|
214 |
c->currencypos = 1;
|
|
|
215 |
return(0);
|
|
|
216 |
} else if (value[0] == '2') {
|
|
|
217 |
c->currencypos = 0;
|
|
|
218 |
c->currencydecsym = 1;
|
|
|
219 |
return(0);
|
|
|
220 |
}
|
|
|
221 |
}
|
|
|
222 |
} else if (stringstartswith(arg, "currspc:") == 0) {
|
|
|
223 |
if (value[1] == 0) {
|
|
|
224 |
if ((value[0] >= '0') && (value[0] <= '1')) {
|
|
|
225 |
c->currencyspace = value[0] - '0';
|
|
|
226 |
return(0);
|
|
|
227 |
}
|
|
|
228 |
}
|
|
|
229 |
} else if (stringstartswith(arg, "currprec:") == 0) {
|
|
|
230 |
if (value[1] == 0) {
|
|
|
231 |
if ((value[0] >= '0') && (value[0] <= '9')) {
|
|
|
232 |
c->currencyprec = value[0] - '0';
|
|
|
233 |
return(0);
|
|
|
234 |
}
|
|
|
235 |
}
|
|
|
236 |
} else if (stringstartswith(arg, "yesno:") == 0) {
|
|
|
237 |
/* string must be exactly 2 characters long */
|
|
|
238 |
if ((value[0] != 0) && (value[1] != 0) && (value[2] == 0)) {
|
|
|
239 |
c->yes = value[0];
|
|
|
240 |
c->no = value[1];
|
|
|
241 |
return(0);
|
|
|
242 |
}
|
|
|
243 |
}
|
|
|
244 |
/* if I'm here, something went wrong */
|
|
|
245 |
return(-4);
|
|
|
246 |
}
|
|
|
247 |
|
|
|
248 |
|
|
|
249 |
int main(int argc, char **argv) {
|
|
|
250 |
struct country cntdata;
|
|
|
251 |
int changedflag;
|
|
|
252 |
int x;
|
|
|
253 |
char *fname;
|
|
|
254 |
|
|
|
255 |
if ((argc < 2) || (argv[1][0] == '/')) {
|
|
|
256 |
about();
|
|
|
257 |
return(1);
|
|
|
258 |
}
|
|
|
259 |
|
|
|
260 |
fname = argv[1];
|
|
|
261 |
|
|
|
262 |
x = country_read(&cntdata, fname);
|
|
|
263 |
if (x != 0) {
|
|
|
264 |
printf("ERROR: failed to read the preference file [%d]\n", x);
|
|
|
265 |
return(2);
|
|
|
266 |
}
|
|
|
267 |
|
|
|
268 |
changedflag = argc - 2;
|
|
|
269 |
|
|
|
270 |
/* process command line arguments */
|
|
|
271 |
while (--argc > 1) {
|
|
|
272 |
if (processarg(argv[argc], &cntdata) != 0) {
|
|
|
273 |
about();
|
|
|
274 |
return(3);
|
|
|
275 |
}
|
|
|
276 |
}
|
|
|
277 |
|
|
|
278 |
printf("Country intl code.....: %03d\n", cntdata.id);
|
|
|
279 |
printf("Codepage..............: %d\n", cntdata.codepage);
|
|
|
280 |
printf("Decimal separator.....: %c\n", cntdata.decimal);
|
|
|
281 |
printf("Thousands separator...: %c\n", cntdata.thousands);
|
|
|
282 |
printf("Date format...........: %s\n", datestring(&cntdata));
|
|
|
283 |
printf("Time format...........: %s\n", timestring(&cntdata));
|
|
|
284 |
printf("Yes/No letters........: %c/%c\n", cntdata.yes, cntdata.no);
|
|
|
285 |
printf("Currency example......: %s\n", currencystring(&cntdata));
|
|
|
286 |
|
|
|
287 |
printf("\n"
|
|
|
288 |
"Note: Please make sure your CONFIG.SYS contains a COUNTRY directive pointing\n"
|
|
|
289 |
" to your custom preferences file. Example:\n"
|
|
|
290 |
" COUNTRY=%03d,,C:\\MYPREFS.SYS\n\n", cntdata.id);
|
|
|
291 |
|
|
|
292 |
/* if anything changed, write the new file */
|
|
|
293 |
if (changedflag != 0) country_write(fname, &cntdata);
|
|
|
294 |
|
|
|
295 |
return(0);
|
|
|
296 |
}
|