1 //
2 // aegis - project change supervisor
3 // Copyright (C) 1991-2008, 2011, 2012 Peter Miller
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
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. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see
17 // <http://www.gnu.org/licenses/>.
18 //
19
20 #include <common/ac/assert.h>
21 #include <common/ac/errno.h>
22 #include <common/ac/stdio.h>
23 #include <common/ac/stddef.h>
24 #include <common/ac/stdlib.h>
25 #include <common/ac/libintl.h>
26 #include <common/ac/limits.h>
27 #include <common/ac/string.h>
28 #include <common/ac/wchar.h>
29 #include <common/ac/wctype.h>
30 #include <common/ac/grp.h>
31 #include <common/ac/pwd.h>
32
33 #include <common/arglex.h>
34 #include <common/error.h>
35 #include <common/language.h>
36 #include <common/mem.h>
37 #include <common/page.h>
38 #include <common/progname.h>
39 #include <common/quit.h>
40 #include <common/str_list.h>
41 #include <common/trace.h>
42 #include <common/wstring/list.h>
43 #include <libaegis/change.h>
44 #include <libaegis/change/branch.h>
45 #include <libaegis/change/file.h>
46 #include <libaegis/collect.h>
47 #include <libaegis/file.h>
48 #include <libaegis/gonzo.h>
49 #include <libaegis/option.h>
50 #include <libaegis/os.h>
51 #include <libaegis/project.h>
52 #include <libaegis/project/file.h>
53 #include <libaegis/project/history.h>
54 #include <libaegis/sub.h>
55 #include <libaegis/sub/functor/glue.h>
56 #include <libaegis/sub/functor/hostname.h>
57 #include <libaegis/sub/functor/variable.h>
58 #include <libaegis/sub/addpathsuffi.h>
59 #include <libaegis/sub/architecture.h>
60 #include <libaegis/sub/base_relativ.h>
61 #include <libaegis/sub/basename.h>
62 #include <libaegis/sub/binary_direc.h>
63 #include <libaegis/sub/capitalize.h>
64 #include <libaegis/sub/change/active_direc.h>
65 #include <libaegis/sub/change/attribute.h>
66 #include <libaegis/sub/change/delta.h>
67 #include <libaegis/sub/change/develo_direc.h>
68 #include <libaegis/sub/change/develop_list.h>
69 #include <libaegis/sub/change/developer.h>
70 #include <libaegis/sub/change/files.h>
71 #include <libaegis/sub/change/integr_direc.h>
72 #include <libaegis/sub/change/integrator.h>
73 #include <libaegis/sub/change/number.h>
74 #include <libaegis/sub/change/reviewer.h>
75 #include <libaegis/sub/change/reviewr_list.h>
76 #include <libaegis/sub/change/state.h>
77 #include <libaegis/sub/change/version.h>
78 #include <libaegis/sub/comment.h>
79 #include <libaegis/sub/common_direc.h>
80 #include <libaegis/sub/copyri_owner.h>
81 #include <libaegis/sub/copyri_years.h>
82 #include <libaegis/sub/data_directo.h>
83 #include <libaegis/sub/date.h>
84 #include <libaegis/sub/diff.h>
85 #include <libaegis/sub/dirname.h>
86 #include <libaegis/sub/dirname_rel.h>
87 #include <libaegis/sub/dollar.h>
88 #include <libaegis/sub/downcase.h>
89 #include <libaegis/sub/email_addres.h>
90 #include <libaegis/sub/expr.h>
91 #include <libaegis/sub/getenv.h>
92 #include <libaegis/sub/histo_direc.h>
93 #include <libaegis/sub/history_path.h>
94 #include <libaegis/sub/identifier.h>
95 #include <libaegis/sub/left.h>
96 #include <libaegis/sub/length.h>
97 #include <libaegis/sub/librar_direc.h>
98 #include <libaegis/sub/namemax.h>
99 #include <libaegis/sub/path_reduce.h>
100 #include <libaegis/sub/perl.h>
101 #include <libaegis/sub/plural.h>
102 #include <libaegis/sub/plural_forms.h>
103 #include <libaegis/sub/project.h>
104 #include <libaegis/sub/project/adminis_list.h>
105 #include <libaegis/sub/project/baseline.h>
106 #include <libaegis/sub/project/develop_list.h>
107 #include <libaegis/sub/project/integra_list.h>
108 #include <libaegis/sub/project/reviewe_list.h>
109 #include <libaegis/sub/project/specific.h>
110 #include <libaegis/sub/quote.h>
111 #include <libaegis/sub/read_file.h>
112 #include <libaegis/sub/right.h>
113 #include <libaegis/sub/search_path.h>
114 #include <libaegis/sub/shell.h>
115 #include <libaegis/sub/source.h>
116 #include <libaegis/sub/split.h>
117 #include <libaegis/sub/substitute.h>
118 #include <libaegis/sub/substr.h>
119 #include <libaegis/sub/switch.h>
120 #include <libaegis/sub/trim_directo.h>
121 #include <libaegis/sub/trim_extensi.h>
122 #include <libaegis/sub/unsplit.h>
123 #include <libaegis/sub/upcase.h>
124 #include <libaegis/sub/user.h>
125 #include <libaegis/sub/zero_pad.h>
126
127
128 enum getc_type
129 {
130 getc_type_control,
131 getc_type_data
132 };
133
134
sub_context_ty(const char * arg1,int arg2)135 sub_context_ty::sub_context_ty(const char *arg1, int arg2) :
136 cp(0),
137 pp(0),
138 suberr(0),
139 errno_sequester(errno),
140 file_name(arg1 ? arg1 : __FILE__),
141 line_number(arg1 ? (arg2 > 0 ? arg2 : 1) : __LINE__)
142 {
143 }
144
145
~sub_context_ty()146 sub_context_ty::~sub_context_ty()
147 {
148 clear();
149 }
150
151
152 void
error_set(const char * s)153 sub_context_ty::error_set(const char *s)
154 {
155 suberr = s;
156 }
157
158
159 project *
project_get()160 sub_context_ty::project_get()
161 {
162 return pp;
163 }
164
165
166 change::pointer
change_get()167 sub_context_ty::change_get()
168 {
169 return cp;
170 }
171
172
173 //
174 // NAME
175 // sub_errno - the errno substitution
176 //
177 // SYNOPSIS
178 // wstring sub_errno(const wstring_list &arg);
179 //
180 // DESCRIPTION
181 // The sub_errno function implements the errno substitution. The
182 // errno substitution is replaced by the value if th errno variable
183 // provided by the system, as mapped through the strerror function.
184 //
185 // Requires exactly zero arguments.
186 //
187 // The sub_errno_setx() function may be used to remember errno,
188 // and thus isolate the error from subsequest system calls.
189 //
190 // ARGUMENTS
191 // arg - list of arguments, including the function name as [0]
192 //
193 // RETURNS
194 // a pointer to a string in dynamic memory;
195 // or NULL on error, setting suberr appropriately.
196 //
197
198 static wstring
sub_errno(sub_context_ty * scp,const wstring_list & arg)199 sub_errno(sub_context_ty *scp, const wstring_list &arg)
200 {
201 trace(("sub_errno()\n{\n"));
202 if (arg.size() != 1)
203 {
204 scp->error_set(i18n("requires zero arguments"));
205 trace(("return NULL;\n"));
206 trace(("}\n"));
207 return wstring();
208 }
209
210 int err = scp->errno_sequester_get();
211 if (err == EPERM || err == EACCES)
212 {
213 int uid;
214 int gid;
215 os_become_query(&uid, &gid, (int *)0);
216 struct passwd *pw = getpwuid(uid);
217 char uidn[20];
218 if (pw)
219 snprintf(uidn, sizeof(uidn), "user \"%.8s\"", pw->pw_name);
220 else
221 snprintf(uidn, sizeof(uidn), "uid %d", uid);
222
223 struct group *gr = getgrgid(gid);
224 char gidn[20];
225 if (gr)
226 snprintf(gidn, sizeof(gidn), "group \"%.8s\"", gr->gr_name);
227 else
228 snprintf(gidn, sizeof(gidn), "gid %d", gid);
229
230 nstring s = nstring::format("%s [%s, %s]", strerror(err), uidn, gidn);
231 wstring result(s);
232 trace(("return %p;\n", result.get_ref()));
233 trace(("}\n"));
234 return result;
235 }
236
237 wstring result(strerror(err));
238 trace(("return %p;\n", result.get_ref()));
239 trace(("}\n"));
240 return result;
241 }
242
243
244 static sub_functor_list globals;
245
246
247 static void
init_globals()248 init_globals()
249 {
250 if (!globals.empty())
251 return;
252 globals.push_back(sub_functor_glue::create("$", sub_dollar));
253 globals.push_back(sub_functor_glue::create("#", sub_comment));
254 globals.push_back
255 (
256 sub_functor_glue::create
257 (
258 "Active_Directory",
259 sub_change_active_directory
260 )
261 );
262 globals.push_back
263 (
264 sub_functor_glue::create("Add_Path_Suffix", sub_add_path_suffix)
265 );
266 globals.push_back
267 (
268 sub_functor_glue::create("Administrator_List", sub_administrator_list)
269 );
270 globals.push_back
271 (
272 sub_functor_glue::create("ARCHitecture", sub_architecture)
273 );
274 globals.push_back(sub_functor_glue::create("BaseLine", sub_baseline));
275 globals.push_back(sub_functor_glue::create("Basename", sub_basename));
276 globals.push_back
277 (
278 sub_functor_glue::create("BAse_RElative", sub_base_relative)
279 );
280 globals.push_back
281 (
282 sub_functor_glue::create("BINary_DIRectory", sub_binary_directory)
283 );
284 globals.push_back(sub_functor_glue::create("CAPitalize", sub_capitalize));
285 globals.push_back(sub_functor_glue::create("Change", sub_change_number));
286 globals.push_back
287 (
288 sub_functor_glue::create("Change_Attribute", sub_change_attribute)
289 );
290 globals.push_back
291 (
292 sub_functor_glue::create
293 (
294 "Change_Developer_List",
295 sub_change_developer_list
296 )
297 );
298 globals.push_back
299 (
300 sub_functor_glue::create("Change_Files", sub_change_files)
301 );
302 globals.push_back
303 (
304 sub_functor_glue::create
305 (
306 "Change_Reviewer_List",
307 sub_change_reviewer_list
308 )
309 );
310 globals.push_back
311 (
312 sub_functor_glue::create("Copyright_Owner", sub_copyright_owner)
313 );
314 globals.push_back
315 (
316 sub_functor_glue::create("Copyright_Years", sub_copyright_years)
317 );
318 globals.push_back(sub_functor_glue::create("COMment", sub_comment));
319 globals.push_back
320 (
321 sub_functor_glue::create("COMmon_DIRectory", sub_common_directory)
322 );
323 globals.push_back
324 (
325 sub_functor_glue::create("DATa_DIRectory", sub_data_directory)
326 );
327 globals.push_back(sub_functor_glue::create("DAte", sub_date));
328 globals.push_back(sub_functor_glue::create("DELta", sub_delta));
329 globals.push_back(sub_functor_glue::create("DEVeloper", sub_developer));
330 globals.push_back
331 (
332 sub_functor_glue::create("DEVeloper_List", sub_developer_list)
333 );
334 // Default_Development_Directory
335 globals.push_back
336 (
337 sub_functor_glue::create
338 (
339 "Development_Directory",
340 sub_development_directory
341 )
342 );
343 globals.push_back(sub_functor_glue::create("DIFference", sub_diff));
344 globals.push_back(sub_functor_glue::create("Dirname", sub_dirname));
345 globals.push_back
346 (
347 sub_functor_glue::create("Dirname_RELative", sub_dirname_relative)
348 );
349 globals.push_back(sub_functor_glue::create("DownCase", sub_downcase));
350 globals.push_back(sub_functor_glue::create("DOLlar", sub_dollar));
351 // Edit
352 globals.push_back
353 (
354 sub_functor_glue::create("EMail_Address", sub_email_address)
355 );
356 globals.push_back(sub_functor_glue::create("ENVironment", sub_getenv));
357 globals.push_back(sub_functor_glue::create("ERrno", sub_errno));
358 globals.push_back(sub_functor_glue::create("EXpression", sub_expression));
359 // FieLD_List
360 // File_List
361 // File_Name
362 globals.push_back(sub_functor_glue::create("Get_Environment", sub_getenv));
363 // Guess
364 // History
365 globals.push_back
366 (
367 sub_functor_glue::create("History_Directory", sub_history_directory)
368 );
369 globals.push_back
370 (
371 sub_functor_glue::create("History_Path", sub_history_path)
372 );
373 globals.push_back(sub_functor_hostname::create("HostName"));
374 // Input
375 globals.push_back(sub_functor_glue::create("IDentifier", sub_identifier));
376 globals.push_back
377 (
378 sub_functor_glue::create
379 (
380 "INTegration_Directory",
381 sub_integration_directory
382 )
383 );
384 globals.push_back(sub_functor_glue::create("INTegrator", sub_integrator));
385 globals.push_back
386 (
387 sub_functor_glue::create("INTegrator_List", sub_integrator_list)
388 );
389 globals.push_back(sub_functor_glue::create("LEFt", sub_left));
390 globals.push_back(sub_functor_glue::create("LENgth", sub_length));
391 globals.push_back(sub_functor_glue::create("LIBrary", sub_data_directory));
392 globals.push_back
393 (
394 sub_functor_glue::create("LIBrary_DIRectory", sub_library_directory)
395 );
396 // MAgic
397 // MeSsaGe
398 // Most_Recent
399 globals.push_back(sub_functor_glue::create("Name_Maximum", sub_namemax));
400 // Number
401 // Output
402 // ORiginal
403 globals.push_back(sub_functor_glue::create("PAth_REduce", sub_path_reduce));
404 globals.push_back(sub_functor_glue::create("PERL", sub_perl));
405 globals.push_back(sub_functor_glue::create("PLural", sub_plural));
406 globals.push_back
407 (
408 sub_functor_glue::create("PLural_Forms", sub_plural_forms)
409 );
410 globals.push_back(sub_functor_glue::create("Project", sub_project));
411 globals.push_back
412 (
413 sub_functor_glue::create("Project_Specific", sub_project_specific)
414 );
415 globals.push_back(sub_functor_glue::create("QUote", sub_quote));
416 globals.push_back
417 (
418 sub_functor_glue::create("Read_File", sub_read_file, true)
419 );
420 globals.push_back
421 (
422 sub_functor_glue::create("Read_File_Simple", sub_read_file)
423 );
424 globals.push_back(sub_functor_glue::create("Reviewer", sub_reviewer));
425 globals.push_back
426 (
427 sub_functor_glue::create("Reviewer_List", sub_reviewer_list)
428 );
429 globals.push_back(sub_functor_glue::create("RIght", sub_right));
430 globals.push_back(sub_functor_glue::create("Search_Path", sub_search_path));
431 globals.push_back
432 (
433 sub_functor_glue::create("Search_Path_Executable", sub_search_path)
434 );
435 globals.push_back(sub_functor_glue::create("SHell", sub_shell));
436 globals.push_back(sub_functor_glue::create("Source", sub_source));
437 globals.push_back(sub_functor_glue::create("SPLit", sub_split));
438 globals.push_back(sub_functor_glue::create("STate", sub_state));
439 globals.push_back(sub_functor_glue::create("SUBSTitute", sub_substitute));
440 globals.push_back(sub_functor_glue::create("SUBSTRing", sub_substr));
441 globals.push_back(sub_functor_glue::create("SWitch", sub_switch));
442 globals.push_back
443 (
444 sub_functor_glue::create("Trim_DIRectory", sub_trim_directory)
445 );
446 globals.push_back
447 (
448 sub_functor_glue::create("Trim_EXTension", sub_trim_extension)
449 );
450
451 // uname is undocumented
452 globals.push_back(sub_functor_glue::create("UName", sub_architecture));
453
454 globals.push_back(sub_functor_glue::create("UNSplit", sub_unsplit));
455 globals.push_back(sub_functor_glue::create("UpCase", sub_upcase));
456 globals.push_back(sub_functor_glue::create("USer", sub_user));
457 globals.push_back(sub_functor_glue::create("Version", sub_version));
458 globals.push_back(sub_functor_glue::create("Zero_Pad", sub_zero_pad));
459 }
460
461
462 void
execute(const wstring_list & arg)463 sub_context_ty::execute(const wstring_list &arg)
464 {
465 trace(("execute()\n{\n"));
466 init_globals();
467 if (arg.empty())
468 {
469 sub_context_ty inner;
470 inner.var_set_charstar("File_Name", file_name);
471 inner.var_set_long("Line_Number", line_number);
472 inner.fatal_intl
473 (
474 i18n("$filename: $linenumber: empty $${} substitution")
475 );
476 // NOTREACHED
477 }
478
479 //
480 // scan the variables and functions
481 //
482 nstring cmd = arg[0].to_nstring();
483 sub_functor_list hits;
484 globals.match(cmd, hits);
485 var_list.match(cmd, hits);
486
487 //
488 // figure what to do
489 //
490 switch (hits.size())
491 {
492 case 0:
493 {
494 nstring s3 = arg.unsplit(" ").to_nstring();
495 const char *the_error = i18n("unknown substitution name");
496 sub_context_ty inner(__FILE__, __LINE__);
497 inner.var_set_charstar("File_Name", file_name);
498 inner.var_set_long("Line_Number", line_number);
499 inner.var_set_string("Name", s3);
500 inner.var_set_charstar("MeSsaGe", gettext(the_error));
501 inner.fatal_intl
502 (
503 i18n
504 (
505 "$filename: $linenumber: substitution $${$name} "
506 "failed: $message"
507 )
508 );
509 // NOTREACHED
510 }
511
512 case 1:
513 break;
514
515 default:
516 {
517 nstring s3 = arg.unsplit(" ").to_nstring();
518 const char *the_error = i18n("ambiguous substitution name");
519 sub_context_ty inner(__FILE__, __LINE__);
520 inner.var_set_charstar("File_Name", file_name);
521 inner.var_set_long("Line_Number", line_number);
522 inner.var_set_string("Name", s3);
523 inner.var_set_charstar("MeSsaGe", gettext(the_error));
524 inner.fatal_intl
525 (
526 i18n
527 (
528 "$filename: $linenumber: substitution $${$name} "
529 "failed: $message"
530 )
531 );
532 // NOTREACHED
533 }
534 }
535
536 //
537 // Do it.
538 //
539 sub_functor::pointer tp = hits[0];
540 wstring_list tmp;
541 tmp.push_back(wstring(tp->name_get()));
542 for (size_t j = 1; j < arg.size(); ++j)
543 tmp.push_back(arg[j]);
544 wstring s = tp->evaluate(this, tmp);
545
546 //
547 // report errors as they happen
548 //
549 if (suberr)
550 {
551 sub_context_ty inner(__FILE__, __LINE__);
552 inner.var_set_charstar("File_Name", file_name);
553 inner.var_set_long("Line_Number", line_number);
554 inner.var_set_string("Name", tp->name_get());
555 inner.var_set_charstar("MeSsaGe", suberr);
556 inner.fatal_intl
557 (
558 i18n("$filename: $linenumber: substitution $${$name} failed: "
559 "$message")
560 );
561 // NOTREACHED
562 }
563
564 //
565 // deal with the result
566 //
567 if (!s.empty())
568 diversion_stack.push_back(s, tp->resubstitute());
569 trace(("}\n"));
570 }
571
572
573 wchar_t
getc_meta(getc_type & tr)574 sub_context_ty::getc_meta(getc_type &tr)
575 {
576 trace(("sub_getc_meta()\n{\n"));
577 if (diversion_stack.resub_both())
578 tr = getc_type_control;
579 else
580 tr = getc_type_data;
581 wchar_t result = diversion_stack.getch();
582 #ifdef DEBUG
583 if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
584 {
585 trace(("return '%c' %s;\n", (char)result,
586 (tr == getc_type_control ? "control" : "data")));
587 }
588 else
589 {
590 trace(("return %4.4lX %s;\n", (long)result,
591 (tr == getc_type_control ? "control" : "data")));
592 }
593 #endif
594 trace(("}\n"));
595 return result;
596 }
597
598
599 void
getc_meta_undo(wchar_t c)600 sub_context_ty::getc_meta_undo(wchar_t c)
601 {
602 trace(("sub_getc_meta_undo(%ld)\n{\n", (long)c));
603 #ifdef DEBUG
604 if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
605 trace(("c = '%c'\n", (char)c));
606 #endif
607 diversion_stack.ungetch(c);
608 trace(("}\n"));
609 }
610
611
612 wchar_t
dollar()613 sub_context_ty::dollar()
614 {
615 trace(("dollar()\n{\n"));
616 collect tmp;
617 wstring_list arg;
618 int result = 0;
619 getc_type ct;
620 wchar_t c = getc_meta(ct);
621 if (ct != getc_type_control)
622 goto normal;
623 switch (c)
624 {
625 case '0':
626 case '1':
627 case '2':
628 case '3':
629 case '4':
630 case '5':
631 case '6':
632 case '7':
633 case '8':
634 case '9':
635 {
636 for (;;)
637 {
638 tmp.append(c);
639 c = getc_meta(ct);
640 switch (c)
641 {
642 case '0':
643 case '1':
644 case '2':
645 case '3':
646 case '4':
647 case '5':
648 case '6':
649 case '7':
650 case '8':
651 case '9':
652 continue;
653
654 default:
655 getc_meta_undo(c);
656 break;
657 }
658 break;
659 }
660 wstring s = tmp.end();
661 trace(("push arg\n"));
662 arg.push_back(s);
663 execute(arg);
664 }
665 break;
666
667 case 'a':
668 case 'b':
669 case 'c':
670 case 'd':
671 case 'e':
672 case 'f':
673 case 'g':
674 case 'h':
675 case 'i':
676 case 'j':
677 case 'k':
678 case 'l':
679 case 'm':
680 case 'n':
681 case 'o':
682 case 'p':
683 case 'q':
684 case 'r':
685 case 's':
686 case 't':
687 case 'u':
688 case 'v':
689 case 'w':
690 case 'x':
691 case 'y':
692 case 'z':
693 case 'A':
694 case 'B':
695 case 'C':
696 case 'D':
697 case 'E':
698 case 'F':
699 case 'G':
700 case 'H':
701 case 'I':
702 case 'J':
703 case 'K':
704 case 'L':
705 case 'M':
706 case 'N':
707 case 'O':
708 case 'P':
709 case 'Q':
710 case 'R':
711 case 'S':
712 case 'T':
713 case 'U':
714 case 'V':
715 case 'W':
716 case 'X':
717 case 'Y':
718 case 'Z':
719 {
720 for (;;)
721 {
722 tmp.append(c);
723 c = getc_meta(ct);
724 switch (c)
725 {
726 case 'a':
727 case 'b':
728 case 'c':
729 case 'd':
730 case 'e':
731 case 'f':
732 case 'g':
733 case 'h':
734 case 'i':
735 case 'j':
736 case 'k':
737 case 'l':
738 case 'm':
739 case 'n':
740 case 'o':
741 case 'p':
742 case 'q':
743 case 'r':
744 case 's':
745 case 't':
746 case 'u':
747 case 'v':
748 case 'w':
749 case 'x':
750 case 'y':
751 case 'z':
752 case 'A':
753 case 'B':
754 case 'C':
755 case 'D':
756 case 'E':
757 case 'F':
758 case 'G':
759 case 'H':
760 case 'I':
761 case 'J':
762 case 'K':
763 case 'L':
764 case 'M':
765 case 'N':
766 case 'O':
767 case 'P':
768 case 'Q':
769 case 'R':
770 case 'S':
771 case 'T':
772 case 'U':
773 case 'V':
774 case 'W':
775 case 'X':
776 case 'Y':
777 case 'Z':
778 case '0':
779 case '1':
780 case '2':
781 case '3':
782 case '4':
783 case '5':
784 case '6':
785 case '7':
786 case '8':
787 case '9':
788 case '_':
789 case '-':
790 continue;
791
792 default:
793 getc_meta_undo(c);
794 break;
795 }
796 break;
797 }
798 wstring s = tmp.end();
799 trace(("push arg\n"));
800 arg.push_back(s);
801 execute(arg);
802 }
803 break;
804
805 case '{':
806 c = getch(ct);
807 for (;;)
808 {
809 //
810 // look for terminator
811 //
812 if (c == '}' && ct == getc_type_control)
813 break;
814
815 //
816 // watch out for unterminated substitutions
817 //
818 if (!c)
819 {
820 sub_context_ty inner(__FILE__, __LINE__);
821 assert(file_name);
822 inner.var_set_charstar("File_Name", file_name);
823 assert(line_number);
824 inner.var_set_long("Line_Number", line_number);
825 inner.fatal_intl
826 (
827 i18n("$filename: $linenumber: unterminated $${} substitution")
828 );
829 // NOTREACHED
830 break;
831 }
832
833 //
834 // skip white space separating the arguments
835 //
836 if (iswspace(c))
837 {
838 c = getch(ct);
839 continue;
840 }
841
842 //
843 // collect the argument
844 // any run of non-white-space characters
845 //
846 wchar_t quoted = 0;
847 for (;;)
848 {
849 if (!c)
850 {
851 if (quoted)
852 {
853 sub_context_ty inner(__FILE__, __LINE__);
854 assert(file_name);
855 inner.var_set_charstar("File_Name", file_name);
856 assert(line_number);
857 inner.var_set_long("Line_Number", line_number);
858 inner.fatal_intl
859 (
860 i18n("$filename: $linenumber: unterminated $${} quotes")
861 );
862 // NOTREACHED
863 }
864 break;
865 }
866 if
867 (
868 !quoted
869 &&
870 (
871 iswspace(c)
872 ||
873 (ct == getc_type_control && c == '}')
874 )
875 )
876 break;
877 if (c == quoted)
878 {
879 assert(quoted);
880 quoted = 0;
881 }
882 else if (c == '\'' && ct == getc_type_control)
883 {
884 assert(!quoted);
885 quoted = c;
886 }
887 else if (c == '\\' && ct == getc_type_control)
888 {
889 c = getch(ct);
890 if (!c)
891 {
892 sub_context_ty inner(__FILE__, __LINE__);
893 assert(file_name);
894 inner.var_set_charstar("File_Name", file_name);
895 assert(line_number);
896 inner.var_set_long("Line_Number", line_number);
897 inner.fatal_intl
898 (
899 i18n("$filename: $linenumber: unterminated $${} \\ sequence")
900 );
901 // NOTREACHED
902 }
903 tmp.append(c);
904 }
905 else
906 tmp.append(c);
907 c = getch(ct);
908 }
909 wstring s = tmp.end();
910 trace(("push arg\n"));
911 arg.push_back(s);
912 }
913 execute(arg);
914 break;
915
916 case '$':
917 result = '$';
918 break;
919
920 case '#':
921 for (;;)
922 {
923 c = getc_meta(ct);
924 if (!c || (c == '\n' && ct == getc_type_control))
925 break;
926 }
927 result = 0;
928 break;
929
930 default:
931 normal:
932 getc_meta_undo(c);
933 result = '$';
934 break;
935 }
936 #ifdef DEBUG
937 if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
938 trace(("return '%c';\n", (char)result));
939 else
940 trace(("return %4.4lX;\n", (long)result));
941 #endif
942 trace(("}\n"));
943 return result;
944 }
945
946
947 wchar_t
getch(getc_type & tr)948 sub_context_ty::getch(getc_type &tr)
949 {
950 trace(("sub_getc()\n{\n"));
951 wchar_t c = 0;
952 for (;;)
953 {
954 c = getc_meta(tr);
955 if (c && tr != getc_type_control)
956 break;
957 switch (c)
958 {
959 default:
960 break;
961
962 case 0:
963 if (diversion_stack.empty())
964 break;
965 diversion_stack.pop_back();
966 continue;
967
968 case '$':
969 if (!diversion_stack.resub_both())
970 break;
971 c = dollar();
972 if (!c)
973 continue;
974 tr = getc_type_data;
975 break;
976 }
977 break;
978 }
979 #ifdef DEBUG
980 if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
981 {
982 trace(("return '%c' %s;\n", (char)c,
983 (tr == getc_type_control ? "control" : "data")));
984 }
985 else
986 {
987 trace(("return 0x%lX; /* %s */\n", (long)c,
988 (tr == getc_type_control ? "control" : "data")));
989 }
990 #endif
991 trace(("}\n"));
992 return c;
993 }
994
995
996 void
subst_intl_project(project * a)997 sub_context_ty::subst_intl_project(project *a)
998 {
999 if (a != pp)
1000 {
1001 assert(!pp);
1002 assert(!cp);
1003 pp = a;
1004 cp = 0;
1005 }
1006 }
1007
1008
1009 void
subst_intl_change(change::pointer a)1010 sub_context_ty::subst_intl_change(change::pointer a)
1011 {
1012 assert(!pp);
1013 assert(!cp);
1014 pp = a->pp;
1015 cp = a;
1016 }
1017
1018
1019 wstring
subst(const wstring & s)1020 sub_context_ty::subst(const wstring &s)
1021 {
1022 trace(("subst(s = %p)\n{\n", s.get_ref()));
1023 collect buf;
1024 diversion_stack.push_back(s, true);
1025 for (;;)
1026 {
1027 //
1028 // get the next character
1029 //
1030 getc_type ct;
1031 wchar_t c = getch(ct);
1032 if (!c)
1033 break;
1034
1035 //
1036 // save the character
1037 //
1038 buf.append(c);
1039 }
1040
1041 //
1042 // find any unused variables marked "append if unused"
1043 //
1044 for (size_t j = 0; j < var_list.size(); ++j)
1045 {
1046 sub_functor::pointer tp = var_list[j];
1047 if (!tp->append_if_unused())
1048 continue;
1049 if (!tp->must_be_used())
1050 continue;
1051
1052 //
1053 // append to the buffer, separated by a space
1054 //
1055 buf.append(L' ');
1056 wstring_list args;
1057 buf.push_back(tp->evaluate(this, args));
1058 }
1059
1060 //
1061 // find any unused variables
1062 // and complain about them
1063 //
1064 int error_count = 0;
1065 for (size_t j = 0; j < var_list.size(); ++j)
1066 {
1067 sub_functor::pointer tp = var_list[j];
1068 if (!tp->must_be_used())
1069 continue;
1070
1071 //
1072 // Make sure the variables of this message are optional,
1073 // to avoid infinite loops if there is a mistake in the
1074 // translation string.
1075 //
1076 sub_context_ty inner(__FILE__, __LINE__);
1077 assert(file_name);
1078 inner.var_set_charstar("File_Name", file_name);
1079 assert(line_number);
1080 inner.var_set_long("Line_Number", line_number);
1081 inner.var_set_string("MeSsaGe", s.to_nstring());
1082 inner.var_optional("MeSsaGe");
1083 inner.var_set_string("Name", "$" + tp->name_get());
1084 inner.var_optional("Name");
1085 inner.error_intl
1086 (
1087 i18n
1088 (
1089 "$filename: $linenumber: in substitution \"$message\" "
1090 "variable \"$name\" unused"
1091 )
1092 );
1093 ++error_count;
1094 }
1095 if (error_count > 0)
1096 {
1097 //
1098 // Make sure the variables of this message are optional,
1099 // to avoid infinite loops if there is a mistake in the
1100 // translation string.
1101 //
1102 sub_context_ty inner(__FILE__, __LINE__);
1103 assert(file_name);
1104 inner.var_set_charstar("File_Name", file_name);
1105 assert(line_number);
1106 inner.var_set_long("Line_Number", line_number);
1107 inner.var_set_string("MeSsaGe", s.to_nstring());
1108 inner.var_optional("MeSsaGe");
1109 inner.var_set_long("Number", error_count);
1110 inner.var_optional("Number");
1111 inner.fatal_intl
1112 (
1113 i18n
1114 (
1115 "$filename: $linenumber: in substitution \"$message\" "
1116 "found unused variables"
1117 )
1118 );
1119 // NOTREACHED
1120 }
1121
1122 //
1123 // clear the slate, ready for the next run
1124 //
1125 clear();
1126 wstring result = buf.end();
1127 trace(("return %p;\n", result.get_ref()));
1128 trace(("}\n"));
1129 return result;
1130 }
1131
1132
1133 wstring
subst_intl_wide(const char * msg)1134 sub_context_ty::subst_intl_wide(const char *msg)
1135 {
1136 trace(("subst_intl_wide(msg = \"%s\")\n{\n", msg));
1137 language_human();
1138 const char *tmp = gettext(msg);
1139 language_C();
1140 #if 0
1141 #ifdef HAVE_GETTEXT
1142 if (tmp == msg)
1143 {
1144 error_raw
1145 (
1146 "%s: %d: warning: message \"%s\" has no translation",
1147 file_name,
1148 line_number,
1149 msg
1150 );
1151 }
1152 #endif // HAVE_GETTEXT
1153 #endif // DEBUG
1154 wstring s(tmp);
1155 wstring result = subst(s);
1156 trace(("return %p;\n", result.get_ref()));
1157 trace(("}\n"));
1158 return result;
1159 }
1160
1161
1162 string_ty *
subst_intl(const char * s)1163 sub_context_ty::subst_intl(const char *s)
1164 {
1165 trace(("subst_intl(s = \"%s\")\n{\n", s));
1166 wstring result_wide = subst_intl_wide(s);
1167 string_ty *result = wstr_to_str(result_wide.get_ref());
1168 trace(("return \"%s\";\n", result->str_text));
1169 trace(("}\n"));
1170 return result;
1171 }
1172
1173
1174 string_ty *
substitute(change::pointer acp,string_ty * s)1175 sub_context_ty::substitute(change::pointer acp, string_ty *s)
1176 {
1177 trace(("substitute(acp = %p, s = \"%s\")\n{\n", acp, s->str_text));
1178 assert(acp);
1179 subst_intl_change(acp);
1180 wstring wis(s->str_text);
1181 wstring result_wide = subst(wis);
1182 string_ty *result = wstr_to_str(result_wide.get_ref());
1183 trace(("return \"%s\";\n", result->str_text));
1184 trace(("}\n"));
1185 return result;
1186 }
1187
1188
1189 string_ty *
substitute_p(project * app,string_ty * s)1190 sub_context_ty::substitute_p(project *app, string_ty *s)
1191 {
1192 trace(("substitute(pp = %p, s = \"%s\")\n{\n", app, s->str_text));
1193 assert(app);
1194 subst_intl_project(app);
1195 wstring wis(s->str_text, s->str_length);
1196 wstring result_wide = subst(wis);
1197 string_ty *result = wstr_to_str(result_wide.get_ref());
1198 trace(("return \"%s\";\n", result->str_text));
1199 trace(("}\n"));
1200 return result;
1201 }
1202
1203
1204 void
clear()1205 sub_context_ty::clear()
1206 {
1207 var_list.clear();
1208 cp = 0;
1209 pp = 0;
1210 errno_sequester = 0;
1211 }
1212
1213
1214 void
var_set_vformat(const char * name,const char * fmt,va_list ap)1215 sub_context_ty::var_set_vformat(const char *name, const char *fmt, va_list ap)
1216 {
1217 string_ty *s = str_vformat(fmt, ap);
1218 var_set_string(name, s);
1219 str_free(s);
1220 }
1221
1222
1223 void
var_set_format(const char * name,const char * fmt,...)1224 sub_context_ty::var_set_format(const char *name, const char *fmt, ...)
1225 {
1226 va_list ap;
1227 va_start(ap, fmt);
1228 var_set_vformat(name, fmt, ap);
1229 va_end(ap);
1230 }
1231
1232
1233 void
var_set_string(const char * name,const nstring & value)1234 sub_context_ty::var_set_string(const char *name, const nstring &value)
1235 {
1236 var_list.push_back(sub_functor_variable::create(name, value));
1237 }
1238
1239
1240 void
var_set_string(const char * name,string_ty * value)1241 sub_context_ty::var_set_string(const char *name, string_ty *value)
1242 {
1243 var_set_string(name, nstring(value));
1244 }
1245
1246
1247 void
var_resubstitute(const char * name)1248 sub_context_ty::var_resubstitute(const char *name)
1249 {
1250 sub_functor::pointer tp = var_list.find(name);
1251 if (tp)
1252 tp->resubstitute_set();
1253 else
1254 this_is_a_bug();
1255 }
1256
1257
1258 void
var_override(const char * name)1259 sub_context_ty::var_override(const char *name)
1260 {
1261 sub_functor::pointer tp = var_list.find(name);
1262 if (tp)
1263 tp->override_set();
1264 else
1265 this_is_a_bug();
1266 }
1267
1268
1269 void
var_optional(const char * name)1270 sub_context_ty::var_optional(const char *name)
1271 {
1272 sub_functor::pointer tp = var_list.find(name);
1273 if (tp)
1274 tp->optional_set();
1275 else
1276 this_is_a_bug();
1277 }
1278
1279
1280 void
var_append_if_unused(const char * name)1281 sub_context_ty::var_append_if_unused(const char *name)
1282 {
1283 sub_functor::pointer tp = var_list.find(name);
1284 if (tp)
1285 tp->append_if_unused_set();
1286 else
1287 this_is_a_bug();
1288 }
1289
1290
1291 //
1292 // NAME
1293 // wrap - wrap s string over lines
1294 //
1295 // SYNOPSIS
1296 // void wrap(const wstring &);
1297 //
1298 // DESCRIPTION
1299 // The wrap function is used to print error messages onto stderr
1300 // wrapping ling lines. Be very careful of multi-byte characters
1301 // in international character sets.
1302 //
1303 // CAVEATS
1304 // Line length is assumed to be 80 characters.
1305 //
1306
1307 static void
wrap(const wchar_t * s)1308 wrap(const wchar_t *s)
1309 {
1310 const char *progname;
1311 int page_width;
1312 int first_line;
1313 int nbytes;
1314 static int progname_width;
1315 int midway;
1316
1317 //
1318 // flush any pending output,
1319 // so the error message appears in a sensible place.
1320 //
1321 if (fflush(stdout) || ferror(stdout))
1322 nfatal("(stdout)");
1323
1324 //
1325 // Ask the system how wide the terminal is.
1326 // Don't use last column, many terminals are dumb.
1327 //
1328 page_width = page_width_get(-1) - 1;
1329 midway = (page_width + 8) / 2;
1330
1331 //
1332 // Because it must be a legal UNIX file name, it is unlikely to
1333 // be stupid - unprintable characters are hard to type, and most
1334 // file systems don't allow high-bit-on characters in file
1335 // names. Thus, assume progname is all legal characters.
1336 //
1337 progname = progname_get();
1338 if (!progname_width)
1339 {
1340 wstring wis = wstr_from_c(progname);
1341 progname_width = wis.column_width();
1342 }
1343
1344 //
1345 // the message is for a human, so
1346 // use the human's locale
1347 //
1348 language_human();
1349
1350 //
1351 // Emit the message a line at a time, wrapping as we go. The
1352 // first line starts with the program name, subsequent lines are
1353 // indented by a tab.
1354 //
1355 first_line = 1;
1356 while (*s)
1357 {
1358 const wchar_t *ep;
1359 int ocol;
1360 const wchar_t *break_space;
1361 int break_space_col;
1362 const wchar_t *break_punct;
1363 int break_punct_col;
1364
1365 //
1366 // Work out how many characters fit on the line.
1367 //
1368 if (first_line)
1369 ocol = progname_width + 2;
1370 else
1371 ocol = 8;
1372
1373 wctomb(NULL, 0);
1374 ep = s;
1375 break_space = 0;
1376 break_space_col = 0;
1377 break_punct = 0;
1378 break_punct_col = 0;
1379 while (*ep)
1380 {
1381 char dummy[MB_LEN_MAX];
1382 int cw;
1383 wchar_t c;
1384
1385 //
1386 // Keep printing characters. Use a dummy
1387 // character for unprintable sequences (which
1388 // should not happen).
1389 //
1390 c = *ep;
1391 if (!iswprint(c))
1392 c = '?';
1393 nbytes = wctomb(dummy, c);
1394
1395 cw = wcwidth(c);
1396 if (nbytes <= 0)
1397 {
1398 //
1399 // This should not happen! All
1400 // unprintable characters should have
1401 // been turned into C escapes inside the
1402 // common/wstr.c file when converting from C
1403 // string to wide strings.
1404 //
1405 // Replace invalid wide characters with
1406 // a C escape.
1407 //
1408 cw = 4;
1409 nbytes = 4;
1410
1411 //
1412 // The wctomb state will be "error",
1413 // so reset it and brave the worst. No
1414 // need to reset the wctomb state, it is
1415 // not broken.
1416 //
1417 wctomb(NULL, 0);
1418 }
1419
1420 //
1421 // Keep track of good places to break the line,
1422 // but try to avoid runs of white space. There
1423 // is a pathological case where the line is
1424 // entirely composed of white space, but it does
1425 // not happen often.
1426 //
1427 if (c == ' ')
1428 {
1429 break_space = ep;
1430 break_space_col = ocol;
1431 while (break_space > s && break_space[-1] == ' ')
1432 {
1433 --break_space;
1434 --break_space_col;
1435 }
1436 }
1437 if (iswpunct(c) && ocol + cw <= page_width)
1438 {
1439 break_punct = ep + 1;
1440 break_punct_col = ocol + cw;
1441 }
1442
1443 //
1444 // if we have run out of room, break here
1445 //
1446 if (ocol + cw > page_width)
1447 break;
1448 ocol += cw;
1449 ++ep;
1450 }
1451
1452 //
1453 // see if there is a better place to break the line
1454 //
1455 // Break the line at space characters, otherwise break
1456 // at punctuator characters. If it is possible to break
1457 // on either a space or a punctuator, choose the space.
1458 //
1459 // However, if the space is in the left half of the
1460 // line, things look very unbalanced, so break on a
1461 // punctuator in that case.
1462 //
1463 if (*ep && *ep != ' ')
1464 {
1465 if (break_space == s)
1466 break_space = 0;
1467 if
1468 (
1469 break_space
1470 &&
1471 break_punct
1472 &&
1473 break_space_col < midway
1474 &&
1475 break_punct_col >= midway
1476 )
1477 ep = break_punct;
1478 else if (break_space)
1479 ep = break_space;
1480 else if (break_punct)
1481 ep = break_punct;
1482 }
1483
1484 //
1485 // print the line
1486 //
1487 char tmp[(MAX_PAGE_WIDTH + 2) * MB_LEN_MAX];
1488 char *tp = tmp;
1489 if (first_line)
1490 {
1491 tp = strendcpy(tp, progname, tmp + sizeof(tmp));
1492 tp = strendcpy(tp, ": ", tmp + sizeof(tmp));
1493 }
1494 else
1495 tp = strendcpy(tp, "\t", tmp + sizeof(tmp));
1496
1497 //
1498 // Turn the input into a multi bytes chacacters.
1499 //
1500 wctomb(NULL, 0);
1501 while (s < ep)
1502 {
1503 wchar_t c;
1504
1505 //
1506 // Keep printing characters. Use a dummy
1507 // character for unprintable sequences (which
1508 // should not happen).
1509 //
1510 c = *s++;
1511 if (!iswprint(c))
1512 c = '?';
1513 nbytes = wctomb(tp, c);
1514
1515 if (nbytes <= 0)
1516 {
1517 //
1518 // This should not happen! All
1519 // unprintable characters should have
1520 // been turned into C escapes inside the
1521 // wstring.c file when converting from C
1522 // string to wide strings.
1523 //
1524 // Replace invalid wide characters with
1525 // a C escape.
1526 //
1527 nbytes = 4;
1528 tp[0] = '\\';
1529 tp[1] = '0' + ((c >> 6) & 7);
1530 tp[2] = '0' + ((c >> 3) & 7);
1531 tp[3] = '0' + (c & 7);
1532
1533 //
1534 // The wctomb state will be "error",
1535 // so reset it and brave the worst. No
1536 // need to reset the wctomb state, it is
1537 // not broken.
1538 //
1539 wctomb(NULL, 0);
1540 }
1541 tp += nbytes;
1542 }
1543
1544 //
1545 // Add a newline and end any outstanding shift state and
1546 // add a NUL character.
1547 //
1548 nbytes = wctomb(tp, (wchar_t)'\n');
1549 if (nbytes > 0)
1550 tp += nbytes;
1551 nbytes = wctomb(tp, (wchar_t)0);
1552 if (nbytes > 0)
1553 tp += nbytes;
1554
1555 //
1556 // Emit the line to stderr. It is important to do this
1557 // a whole line at a time, otherwise performance is
1558 // terrible - stderr by default is character buffered.
1559 //
1560 fputs(tmp, stderr);
1561 if (ferror(stderr))
1562 break;
1563
1564 //
1565 // skip leading spaces for subsequent lines
1566 //
1567 while (*s == ' ')
1568 ++s;
1569 first_line = 0;
1570 }
1571
1572 //
1573 // done with humans
1574 //
1575 language_C();
1576
1577 //
1578 // make sure nothing went wrong
1579 //
1580 if (fflush(stderr) || ferror(stderr))
1581 nfatal("(stderr)");
1582 }
1583
1584
1585 void
error_intl(const char * s)1586 sub_context_ty::error_intl(const char *s)
1587 {
1588 if (!os_testing_mode())
1589 language_check_translations();
1590 wstring message = subst_intl_wide(s);
1591 wrap(message.c_str());
1592 }
1593
1594
1595 void
fatal_intl(const char * s)1596 sub_context_ty::fatal_intl(const char *s)
1597 {
1598 if (!os_testing_mode())
1599 language_check_translations();
1600
1601 //
1602 // Make sure that there isn't an infinite loop,
1603 // if there is a problem with a substitution
1604 // in an error message.
1605 //
1606 static const char *double_jeopardy;
1607 if (double_jeopardy)
1608 {
1609 //
1610 // this error message can't be internationalized
1611 //
1612 fatal_raw
1613 (
1614 "a fatal_intl error (\"%s\") happened while attempting "
1615 "to report an earlier fatal_intl error (\"%s\"). "
1616 "This is probably a bug.",
1617 s,
1618 double_jeopardy
1619 );
1620 }
1621 double_jeopardy = s;
1622
1623 wstring message = subst_intl_wide(s);
1624 wrap(message.c_str());
1625 double_jeopardy = 0;
1626 quit(1);
1627 // NOTREACHED
1628 }
1629
1630
1631 void
verbose_intl(const char * s)1632 sub_context_ty::verbose_intl(const char *s)
1633 {
1634 if (option_verbose_get())
1635 {
1636 if (!os_testing_mode())
1637 language_check_translations();
1638 wstring message = subst_intl_wide(s);
1639 wrap(message.c_str());
1640 }
1641 clear();
1642 }
1643
1644
1645 void
errno_setx(int x)1646 sub_context_ty::errno_setx(int x)
1647 {
1648 errno_sequester = x;
1649 }
1650
1651
1652 int
errno_sequester_get() const1653 sub_context_ty::errno_sequester_get()
1654 const
1655 {
1656 return errno_sequester;
1657 }
1658
1659
1660 // vim: set ts=8 sw=4 et :
1661