//
// aegis - project change supervisor
// Copyright (C) 1991-2008, 2011, 2012, 2014 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
enum getc_type
{
getc_type_control,
getc_type_data
};
sub_context_ty::sub_context_ty(const char *a__file__, int a__line__) :
cp(),
pp(0),
suberr(0),
errno_sequester(errno),
file_name(a__file__ ? a__file__ : __FILE__),
line_number(a__file__ ? (a__line__ > 0 ? a__line__ : 1) : __LINE__)
{
}
sub_context_ty::~sub_context_ty()
{
clear();
}
void
sub_context_ty::error_set(const char *s)
{
suberr = s;
}
project *
sub_context_ty::project_get(void)
{
return pp;
}
change::pointer
sub_context_ty::change_get(void)
{
return cp;
}
//
// NAME
// sub_errno - the errno substitution
//
// SYNOPSIS
// wstring sub_errno(const wstring_list &arg);
//
// DESCRIPTION
// The sub_errno function implements the errno substitution. The
// errno substitution is replaced by the value if th errno variable
// provided by the system, as mapped through the strerror function.
//
// Requires exactly zero arguments.
//
// The sub_errno_setx() function may be used to remember errno,
// and thus isolate the error from subsequest system calls.
//
// ARGUMENTS
// arg - list of arguments, including the function name as [0]
//
// RETURNS
// a pointer to a string in dynamic memory;
// or NULL on error, setting suberr appropriately.
//
static wstring
sub_errno(sub_context_ty *scp, const wstring_list &arg)
{
trace(("sub_errno()\n{\n"));
if (arg.size() != 1)
{
scp->error_set(i18n("requires zero arguments"));
trace(("return NULL;\n"));
trace(("}\n"));
return wstring();
}
int err = scp->errno_sequester_get();
if (err == EPERM || err == EACCES)
{
int uid;
int gid;
os_become_query(&uid, &gid, (int *)0);
struct passwd *pw = getpwuid(uid);
char uidn[20];
if (pw)
snprintf(uidn, sizeof(uidn), "user \"%.8s\"", pw->pw_name);
else
snprintf(uidn, sizeof(uidn), "uid %d", uid);
struct group *gr = getgrgid(gid);
char gidn[20];
if (gr)
snprintf(gidn, sizeof(gidn), "group \"%.8s\"", gr->gr_name);
else
snprintf(gidn, sizeof(gidn), "gid %d", gid);
nstring s = nstring::format("%s [%s, %s]", strerror(err), uidn, gidn);
wstring result(s);
trace(("return %p;\n", result.get_ref()));
trace(("}\n"));
return result;
}
wstring result(strerror(err));
trace(("return %p;\n", result.get_ref()));
trace(("}\n"));
return result;
}
static sub_functor_list globals;
static void
init_globals(void)
{
if (!globals.empty())
return;
globals.push_back(sub_functor_glue::create("$", sub_dollar));
globals.push_back(sub_functor_glue::create("#", sub_comment));
globals.push_back
(
sub_functor_glue::create
(
"Active_Directory",
sub_change_active_directory
)
);
globals.push_back
(
sub_functor_glue::create("Add_Path_Suffix", sub_add_path_suffix)
);
globals.push_back
(
sub_functor_glue::create("Administrator_List", sub_administrator_list)
);
globals.push_back
(
sub_functor_glue::create("ARCHitecture", sub_architecture)
);
globals.push_back(sub_functor_glue::create("BaseLine", sub_baseline));
globals.push_back(sub_functor_glue::create("Basename", sub_basename));
globals.push_back
(
sub_functor_glue::create("BAse_RElative", sub_base_relative)
);
globals.push_back
(
sub_functor_glue::create("BINary_DIRectory", sub_binary_directory)
);
globals.push_back(sub_functor_glue::create("CAPitalize", sub_capitalize));
globals.push_back(sub_functor_glue::create("Change", sub_change_number));
globals.push_back
(
sub_functor_glue::create("Change_Attribute", sub_change_attribute)
);
globals.push_back
(
sub_functor_glue::create
(
"Change_Developer_List",
sub_change_developer_list
)
);
globals.push_back
(
sub_functor_glue::create("Change_Files", sub_change_files)
);
globals.push_back
(
sub_functor_glue::create
(
"Change_Reviewer_List",
sub_change_reviewer_list
)
);
globals.push_back
(
sub_functor_glue::create("Copyright_Owner", sub_copyright_owner)
);
globals.push_back
(
sub_functor_glue::create("Copyright_Years", sub_copyright_years)
);
globals.push_back(sub_functor_glue::create("COMment", sub_comment));
globals.push_back
(
sub_functor_glue::create("COMmon_DIRectory", sub_common_directory)
);
globals.push_back
(
sub_functor_glue::create("DATa_DIRectory", sub_data_directory)
);
globals.push_back(sub_functor_glue::create("DAte", sub_date));
globals.push_back(sub_functor_glue::create("DELta", sub_delta));
globals.push_back(sub_functor_glue::create("DEVeloper", sub_developer));
globals.push_back
(
sub_functor_glue::create("DEVeloper_List", sub_developer_list)
);
// Default_Development_Directory
globals.push_back
(
sub_functor_glue::create
(
"Development_Directory",
sub_development_directory
)
);
globals.push_back(sub_functor_glue::create("DIFference", sub_diff));
globals.push_back(sub_functor_glue::create("Dirname", sub_dirname));
globals.push_back
(
sub_functor_glue::create("Dirname_RELative", sub_dirname_relative)
);
globals.push_back(sub_functor_glue::create("DownCase", sub_downcase));
globals.push_back(sub_functor_glue::create("DOLlar", sub_dollar));
// Edit
globals.push_back
(
sub_functor_glue::create("EMail_Address", sub_email_address)
);
globals.push_back(sub_functor_glue::create("ENVironment", sub_getenv));
globals.push_back(sub_functor_glue::create("ERrno", sub_errno));
globals.push_back(sub_functor_glue::create("EXpression", sub_expression));
// FieLD_List
// File_List
// File_Name
globals.push_back(sub_functor_glue::create("Get_Environment", sub_getenv));
// Guess
// History
globals.push_back
(
sub_functor_glue::create("History_Directory", sub_history_directory)
);
globals.push_back
(
sub_functor_glue::create("History_Path", sub_history_path)
);
globals.push_back(sub_functor_hostname::create("HostName"));
// Input
globals.push_back(sub_functor_glue::create("IDentifier", sub_identifier));
globals.push_back
(
sub_functor_glue::create
(
"INTegration_Directory",
sub_integration_directory
)
);
globals.push_back(sub_functor_glue::create("INTegrator", sub_integrator));
globals.push_back
(
sub_functor_glue::create("INTegrator_List", sub_integrator_list)
);
globals.push_back(sub_functor_glue::create("LEFt", sub_left));
globals.push_back(sub_functor_glue::create("LENgth", sub_length));
globals.push_back(sub_functor_glue::create("LIBrary", sub_data_directory));
globals.push_back
(
sub_functor_glue::create("LIBrary_DIRectory", sub_library_directory)
);
// MAgic
// MeSsaGe
// Most_Recent
globals.push_back(sub_functor_glue::create("Name_Maximum", sub_namemax));
// Number
// Output
// ORiginal
globals.push_back(sub_functor_glue::create("PAth_REduce", sub_path_reduce));
globals.push_back(sub_functor_glue::create("PERL", sub_perl));
globals.push_back(sub_functor_glue::create("PLural", sub_plural));
globals.push_back
(
sub_functor_glue::create("PLural_Forms", sub_plural_forms)
);
globals.push_back(sub_functor_glue::create("Project", sub_project));
globals.push_back
(
sub_functor_glue::create("Project_Specific", sub_project_specific)
);
globals.push_back(sub_functor_glue::create("QUote", sub_quote));
globals.push_back
(
sub_functor_glue::create("Read_File", sub_read_file, true)
);
globals.push_back
(
sub_functor_glue::create("Read_File_Simple", sub_read_file)
);
globals.push_back(sub_functor_glue::create("Reviewer", sub_reviewer));
globals.push_back
(
sub_functor_glue::create("Reviewer_List", sub_reviewer_list)
);
globals.push_back(sub_functor_glue::create("RIght", sub_right));
globals.push_back(sub_functor_glue::create("Search_Path", sub_search_path));
globals.push_back
(
sub_functor_glue::create("Search_Path_Executable", sub_search_path)
);
globals.push_back(sub_functor_glue::create("SHell", sub_shell));
globals.push_back(sub_functor_glue::create("Source", sub_source));
globals.push_back(sub_functor_glue::create("SPLit", sub_split));
globals.push_back(sub_functor_glue::create("STate", sub_state));
globals.push_back(sub_functor_glue::create("SUBSTitute", sub_substitute));
globals.push_back(sub_functor_glue::create("SUBSTRing", sub_substr));
globals.push_back(sub_functor_glue::create("SWitch", sub_switch));
globals.push_back
(
sub_functor_glue::create("Trim_DIRectory", sub_trim_directory)
);
globals.push_back
(
sub_functor_glue::create("Trim_EXTension", sub_trim_extension)
);
// uname is undocumented
globals.push_back(sub_functor_glue::create("UName", sub_architecture));
globals.push_back(sub_functor_glue::create("UNSplit", sub_unsplit));
globals.push_back(sub_functor_glue::create("UpCase", sub_upcase));
globals.push_back(sub_functor_glue::create("USer", sub_user));
globals.push_back(sub_functor_glue::create("Version", sub_version));
globals.push_back(sub_functor_glue::create("Zero_Pad", sub_zero_pad));
}
void
sub_context_ty::execute(const wstring_list &arg)
{
trace(("execute()\n{\n"));
init_globals();
if (arg.empty())
{
sub_context_ty inner;
inner.var_set_charstar("File_Name", file_name);
inner.var_set_long("Line_Number", line_number);
inner.fatal_intl
(
i18n("$filename: $linenumber: empty $${} substitution")
);
// NOTREACHED
}
//
// scan the variables and functions
//
nstring cmd = arg[0].to_nstring();
sub_functor_list hits;
globals.match(cmd, hits);
var_list.match(cmd, hits);
//
// figure what to do
//
switch (hits.size())
{
case 0:
{
nstring s3 = arg.unsplit(" ").to_nstring();
const char *the_error = i18n("unknown substitution name");
sub_context_ty inner(__FILE__, __LINE__);
inner.var_set_charstar("File_Name", file_name);
inner.var_set_long("Line_Number", line_number);
inner.var_set_string("Name", s3);
inner.var_set_charstar("MeSsaGe", gettext(the_error));
inner.fatal_intl
(
i18n
(
"$filename: $linenumber: substitution $${$name} "
"failed: $message"
)
);
// NOTREACHED
}
case 1:
break;
default:
{
nstring s3 = arg.unsplit(" ").to_nstring();
const char *the_error = i18n("ambiguous substitution name");
sub_context_ty inner(__FILE__, __LINE__);
inner.var_set_charstar("File_Name", file_name);
inner.var_set_long("Line_Number", line_number);
inner.var_set_string("Name", s3);
inner.var_set_charstar("MeSsaGe", gettext(the_error));
inner.fatal_intl
(
i18n
(
"$filename: $linenumber: substitution $${$name} "
"failed: $message"
)
);
// NOTREACHED
}
}
//
// Do it.
//
sub_functor::pointer tp = hits[0];
wstring_list tmp;
tmp.push_back(wstring(tp->name_get()));
for (size_t j = 1; j < arg.size(); ++j)
tmp.push_back(arg[j]);
wstring s = tp->evaluate(this, tmp);
//
// report errors as they happen
//
if (suberr)
{
sub_context_ty inner(__FILE__, __LINE__);
inner.var_set_charstar("File_Name", file_name);
inner.var_set_long("Line_Number", line_number);
inner.var_set_string("Name", tp->name_get());
inner.var_set_charstar("MeSsaGe", suberr);
inner.fatal_intl
(
i18n("$filename: $linenumber: substitution $${$name} failed: "
"$message")
);
// NOTREACHED
}
//
// deal with the result
//
if (!s.empty())
diversion_stack.push_back(s, tp->resubstitute());
trace(("}\n"));
}
wchar_t
sub_context_ty::getc_meta(getc_type &tr)
{
trace(("sub_getc_meta()\n{\n"));
if (diversion_stack.resub_both())
tr = getc_type_control;
else
tr = getc_type_data;
wchar_t result = diversion_stack.getch();
#ifdef DEBUG
if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
{
trace(("return '%c' %s;\n", (char)result,
(tr == getc_type_control ? "control" : "data")));
}
else
{
trace(("return %4.4lX %s;\n", (long)result,
(tr == getc_type_control ? "control" : "data")));
}
#endif
trace(("}\n"));
return result;
}
void
sub_context_ty::getc_meta_undo(wchar_t c)
{
trace(("sub_getc_meta_undo(%ld)\n{\n", (long)c));
#ifdef DEBUG
if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
trace(("c = '%c'\n", (char)c));
#endif
diversion_stack.ungetch(c);
trace(("}\n"));
}
wchar_t
sub_context_ty::dollar(void)
{
trace(("dollar()\n{\n"));
collect tmp;
wstring_list arg;
int result = 0;
getc_type ct;
wchar_t c = getc_meta(ct);
if (ct != getc_type_control)
goto normal;
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
for (;;)
{
tmp.append(c);
c = getc_meta(ct);
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
continue;
default:
getc_meta_undo(c);
break;
}
break;
}
wstring s = tmp.end();
trace(("push arg\n"));
arg.push_back(s);
execute(arg);
}
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
{
for (;;)
{
tmp.append(c);
c = getc_meta(ct);
switch (c)
{
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '_':
case '-':
continue;
default:
getc_meta_undo(c);
break;
}
break;
}
wstring s = tmp.end();
trace(("push arg\n"));
arg.push_back(s);
execute(arg);
}
break;
case '{':
c = getch(ct);
for (;;)
{
//
// look for terminator
//
if (c == '}' && ct == getc_type_control)
break;
//
// watch out for unterminated substitutions
//
if (!c)
{
sub_context_ty inner(__FILE__, __LINE__);
assert(file_name);
inner.var_set_charstar("File_Name", file_name);
assert(line_number);
inner.var_set_long("Line_Number", line_number);
inner.fatal_intl
(
i18n("$filename: $linenumber: unterminated $${} substitution")
);
// NOTREACHED
break;
}
//
// skip white space separating the arguments
//
if (iswspace(c))
{
c = getch(ct);
continue;
}
//
// collect the argument
// any run of non-white-space characters
//
wchar_t quoted = 0;
for (;;)
{
if (!c)
{
if (quoted)
{
sub_context_ty inner(__FILE__, __LINE__);
assert(file_name);
inner.var_set_charstar("File_Name", file_name);
assert(line_number);
inner.var_set_long("Line_Number", line_number);
inner.fatal_intl
(
i18n("$filename: $linenumber: unterminated $${} quotes")
);
// NOTREACHED
}
break;
}
if
(
!quoted
&&
(
iswspace(c)
||
(ct == getc_type_control && c == '}')
)
)
break;
if (c == quoted)
{
assert(quoted);
quoted = 0;
}
else if (c == '\'' && ct == getc_type_control)
{
assert(!quoted);
quoted = c;
}
else if (c == '\\' && ct == getc_type_control)
{
c = getch(ct);
if (!c)
{
sub_context_ty inner(__FILE__, __LINE__);
assert(file_name);
inner.var_set_charstar("File_Name", file_name);
assert(line_number);
inner.var_set_long("Line_Number", line_number);
inner.fatal_intl
(
i18n("$filename: $linenumber: unterminated $${} \\ sequence")
);
// NOTREACHED
}
tmp.append(c);
}
else
tmp.append(c);
c = getch(ct);
}
wstring s = tmp.end();
trace(("push arg\n"));
arg.push_back(s);
}
execute(arg);
break;
case '$':
result = '$';
break;
case '#':
for (;;)
{
c = getc_meta(ct);
if (!c || (c == '\n' && ct == getc_type_control))
break;
}
result = 0;
break;
default:
normal:
getc_meta_undo(c);
result = '$';
break;
}
#ifdef DEBUG
if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
trace(("return '%c';\n", (char)result));
else
trace(("return %4.4lX;\n", (long)result));
#endif
trace(("}\n"));
return result;
}
wchar_t
sub_context_ty::getch(getc_type &tr)
{
trace(("sub_getc()\n{\n"));
wchar_t c = 0;
for (;;)
{
c = getc_meta(tr);
if (c && tr != getc_type_control)
break;
switch (c)
{
default:
break;
case 0:
if (diversion_stack.empty())
break;
diversion_stack.pop_back();
continue;
case '$':
if (!diversion_stack.resub_both())
break;
c = dollar();
if (!c)
continue;
tr = getc_type_data;
break;
}
break;
}
#ifdef DEBUG
if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
{
trace(("return '%c' %s;\n", (char)c,
(tr == getc_type_control ? "control" : "data")));
}
else
{
trace(("return 0x%lX; /* %s */\n", (long)c,
(tr == getc_type_control ? "control" : "data")));
}
#endif
trace(("}\n"));
return c;
}
void
sub_context_ty::subst_intl_project(project *a)
{
if (a != pp)
{
assert(!pp);
assert(!cp);
pp = a;
cp.reset();
}
}
void
sub_context_ty::subst_intl_change(change::pointer a)
{
assert(!pp);
assert(!cp);
pp = a->project_get();
cp = a;
}
wstring
sub_context_ty::subst(const wstring &s)
{
trace(("subst(s = %p)\n{\n", s.get_ref()));
collect buf;
diversion_stack.push_back(s, true);
for (;;)
{
//
// get the next character
//
getc_type ct;
wchar_t c = getch(ct);
if (!c)
break;
//
// save the character
//
buf.append(c);
}
//
// find any unused variables marked "append if unused"
//
for (size_t j = 0; j < var_list.size(); ++j)
{
sub_functor::pointer tp = var_list[j];
if (!tp->append_if_unused())
continue;
if (!tp->must_be_used())
continue;
//
// append to the buffer, separated by a space
//
buf.append(L' ');
wstring_list args;
buf.push_back(tp->evaluate(this, args));
}
//
// find any unused variables
// and complain about them
//
int error_count = 0;
for (size_t j = 0; j < var_list.size(); ++j)
{
sub_functor::pointer tp = var_list[j];
if (!tp->must_be_used())
continue;
//
// Make sure the variables of this message are optional,
// to avoid infinite loops if there is a mistake in the
// translation string.
//
sub_context_ty inner(__FILE__, __LINE__);
assert(file_name);
inner.var_set_charstar("File_Name", file_name);
assert(line_number);
inner.var_set_long("Line_Number", line_number);
inner.var_set_string("MeSsaGe", s.to_nstring());
inner.var_optional("MeSsaGe");
inner.var_set_string("Name", "$" + tp->name_get());
inner.var_optional("Name");
inner.error_intl
(
i18n
(
"$filename: $linenumber: in substitution \"$message\" "
"variable \"$name\" unused"
)
);
++error_count;
}
if (error_count > 0)
{
//
// Make sure the variables of this message are optional,
// to avoid infinite loops if there is a mistake in the
// translation string.
//
sub_context_ty inner(__FILE__, __LINE__);
assert(file_name);
inner.var_set_charstar("File_Name", file_name);
assert(line_number);
inner.var_set_long("Line_Number", line_number);
inner.var_set_string("MeSsaGe", s.to_nstring());
inner.var_optional("MeSsaGe");
inner.var_set_long("Number", error_count);
inner.var_optional("Number");
inner.fatal_intl
(
i18n
(
"$filename: $linenumber: in substitution \"$message\" "
"found unused variables"
)
);
// NOTREACHED
}
//
// clear the slate, ready for the next run
//
clear();
wstring result = buf.end();
trace(("return %p;\n", result.get_ref()));
trace(("}\n"));
return result;
}
wstring
sub_context_ty::subst_intl_wide(const char *msg)
{
trace(("subst_intl_wide(msg = \"%s\")\n{\n", msg));
language_human();
const char *tmp = gettext(msg);
language_C();
#if 0
#ifdef HAVE_GETTEXT
if (tmp == msg)
{
error_raw
(
"%s: %d: warning: message \"%s\" has no translation",
file_name,
line_number,
msg
);
}
#endif // HAVE_GETTEXT
#endif // DEBUG
wstring s(tmp);
wstring result = subst(s);
trace(("return %p;\n", result.get_ref()));
trace(("}\n"));
return result;
}
string_ty *
sub_context_ty::subst_intl(const char *s)
{
trace(("subst_intl(s = \"%s\")\n{\n", s));
wstring result_wide = subst_intl_wide(s);
string_ty *result = wstr_to_str(result_wide.get_ref());
trace(("return \"%s\";\n", result->str_text));
trace(("}\n"));
return result;
}
string_ty *
sub_context_ty::substitute(change::pointer acp, string_ty *s)
{
trace(("substitute(acp = %p, s = \"%s\")\n{\n", acp.get(), s->str_text));
assert(acp);
subst_intl_change(acp);
wstring wis(s->str_text);
wstring result_wide = subst(wis);
string_ty *result = wstr_to_str(result_wide.get_ref());
trace(("return \"%s\";\n", result->str_text));
trace(("}\n"));
return result;
}
nstring
sub_context_ty::substitute(change::pointer acp, const nstring &s)
{
trace(("%s\n{\n", __PRETTY_FUNCTION__));
trace(("acp = %p;\n", acp.get()));
trace_string(s);
assert(acp);
subst_intl_change(acp);
wstring wis(s);
wstring result_wide = subst(wis);
nstring result = result_wide.to_nstring();
trace(("return %s;\n", result.quote_c().c_str()));
trace(("}\n"));
return result;
}
string_ty *
sub_context_ty::substitute_p(project *app, string_ty *s)
{
trace(("substitute(pp = %p, s = \"%s\")\n{\n", app, s->str_text));
assert(app);
subst_intl_project(app);
wstring wis(s->str_text, s->str_length);
wstring result_wide = subst(wis);
string_ty *result = wstr_to_str(result_wide.get_ref());
trace(("return \"%s\";\n", result->str_text));
trace(("}\n"));
return result;
}
nstring
sub_context_ty::substitute_p(project *app, const nstring &s)
{
trace(("%s\n{\n", __PRETTY_FUNCTION__));
trace(("pp = %p;\n", app));
trace_string(s);
assert(app);
subst_intl_project(app);
wstring wis(s);
wstring result_wide = subst(wis);
nstring result = result_wide.to_nstring();
trace(("return %s;\n", result.quote_c().c_str()));
trace(("}\n"));
return result;
}
void
sub_context_ty::clear(void)
{
var_list.clear();
cp.reset();
pp = 0;
errno_sequester = 0;
}
void
sub_context_ty::var_set_vformat(const char *name, const char *fmt, va_list ap)
{
string_ty *s = str_vformat(fmt, ap);
var_set_string(name, s);
str_free(s);
}
void
sub_context_ty::var_set_format(const char *name, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
var_set_vformat(name, fmt, ap);
va_end(ap);
}
void
sub_context_ty::var_set_string(const char *name, const nstring &value)
{
var_list.push_back(sub_functor_variable::create(name, value));
}
void
sub_context_ty::var_set_string(const char *name, string_ty *value)
{
var_set_string(name, nstring(value));
}
void
sub_context_ty::var_resubstitute(const char *name)
{
sub_functor::pointer tp = var_list.find(name);
if (tp)
tp->resubstitute_set();
else
this_is_a_bug();
}
void
sub_context_ty::var_override(const char *name)
{
sub_functor::pointer tp = var_list.find(name);
if (tp)
tp->override_set();
else
this_is_a_bug();
}
void
sub_context_ty::var_optional(const char *name)
{
sub_functor::pointer tp = var_list.find(name);
if (tp)
tp->optional_set();
else
this_is_a_bug();
}
void
sub_context_ty::var_append_if_unused(const char *name)
{
sub_functor::pointer tp = var_list.find(name);
if (tp)
tp->append_if_unused_set();
else
this_is_a_bug();
}
//
// NAME
// wrap - wrap s string over lines
//
// SYNOPSIS
// void wrap(const wstring &);
//
// DESCRIPTION
// The wrap function is used to print error messages onto stderr
// wrapping ling lines. Be very careful of multi-byte characters
// in international character sets.
//
// CAVEATS
// Line length is assumed to be 80 characters.
//
static void
wrap(const wchar_t *s)
{
const char *progname;
int page_width;
int first_line;
int nbytes;
static int progname_width;
int midway;
//
// flush any pending output,
// so the error message appears in a sensible place.
//
if (fflush(stdout) || ferror(stdout))
nfatal("(stdout)");
//
// Ask the system how wide the terminal is.
// Don't use last column, many terminals are dumb.
//
page_width = page_width_get(-1) - 1;
midway = (page_width + 8) / 2;
//
// Because it must be a legal UNIX file name, it is unlikely to
// be stupid - unprintable characters are hard to type, and most
// file systems don't allow high-bit-on characters in file
// names. Thus, assume progname is all legal characters.
//
progname = progname_get();
if (!progname_width)
{
wstring wis = wstr_from_c(progname);
progname_width = wis.column_width();
}
//
// the message is for a human, so
// use the human's locale
//
language_human();
//
// Emit the message a line at a time, wrapping as we go. The
// first line starts with the program name, subsequent lines are
// indented by a tab.
//
first_line = 1;
while (*s)
{
const wchar_t *ep;
int ocol;
const wchar_t *break_space;
int break_space_col;
const wchar_t *break_punct;
int break_punct_col;
//
// Work out how many characters fit on the line.
//
if (first_line)
ocol = progname_width + 2;
else
ocol = 8;
wctomb(NULL, 0);
ep = s;
break_space = 0;
break_space_col = 0;
break_punct = 0;
break_punct_col = 0;
while (*ep)
{
char dummy[MB_LEN_MAX];
int cw;
wchar_t c;
//
// Keep printing characters. Use a dummy
// character for unprintable sequences (which
// should not happen).
//
c = *ep;
if (!iswprint(c))
c = '?';
nbytes = wctomb(dummy, c);
cw = wcwidth(c);
if (nbytes <= 0)
{
//
// This should not happen! All
// unprintable characters should have
// been turned into C escapes inside the
// common/wstr.c file when converting from C
// string to wide strings.
//
// Replace invalid wide characters with
// a C escape.
//
cw = 4;
nbytes = 4;
//
// The wctomb state will be "error",
// so reset it and brave the worst. No
// need to reset the wctomb state, it is
// not broken.
//
wctomb(NULL, 0);
}
//
// Keep track of good places to break the line,
// but try to avoid runs of white space. There
// is a pathological case where the line is
// entirely composed of white space, but it does
// not happen often.
//
if (c == ' ')
{
break_space = ep;
break_space_col = ocol;
while (break_space > s && break_space[-1] == ' ')
{
--break_space;
--break_space_col;
}
}
if (iswpunct(c) && ocol + cw <= page_width)
{
break_punct = ep + 1;
break_punct_col = ocol + cw;
}
//
// if we have run out of room, break here
//
if (ocol + cw > page_width)
break;
ocol += cw;
++ep;
}
//
// see if there is a better place to break the line
//
// Break the line at space characters, otherwise break
// at punctuator characters. If it is possible to break
// on either a space or a punctuator, choose the space.
//
// However, if the space is in the left half of the
// line, things look very unbalanced, so break on a
// punctuator in that case.
//
if (*ep && *ep != ' ')
{
if (break_space == s)
break_space = 0;
if
(
break_space
&&
break_punct
&&
break_space_col < midway
&&
break_punct_col >= midway
)
ep = break_punct;
else if (break_space)
ep = break_space;
else if (break_punct)
ep = break_punct;
}
//
// print the line
//
char tmp[(MAX_PAGE_WIDTH + 2) * MB_LEN_MAX];
char *tp = tmp;
if (first_line)
{
tp = strendcpy(tp, progname, tmp + sizeof(tmp));
tp = strendcpy(tp, ": ", tmp + sizeof(tmp));
}
else
tp = strendcpy(tp, "\t", tmp + sizeof(tmp));
//
// Turn the input into a multi bytes chacacters.
//
wctomb(NULL, 0);
while (s < ep)
{
wchar_t c;
//
// Keep printing characters. Use a dummy
// character for unprintable sequences (which
// should not happen).
//
c = *s++;
if (!iswprint(c))
c = '?';
nbytes = wctomb(tp, c);
if (nbytes <= 0)
{
//
// This should not happen! All
// unprintable characters should have
// been turned into C escapes inside the
// wstring.c file when converting from C
// string to wide strings.
//
// Replace invalid wide characters with
// a C escape.
//
nbytes = 4;
tp[0] = '\\';
tp[1] = '0' + ((c >> 6) & 7);
tp[2] = '0' + ((c >> 3) & 7);
tp[3] = '0' + (c & 7);
//
// The wctomb state will be "error",
// so reset it and brave the worst. No
// need to reset the wctomb state, it is
// not broken.
//
wctomb(NULL, 0);
}
tp += nbytes;
}
//
// Add a newline and end any outstanding shift state and
// add a NUL character.
//
nbytes = wctomb(tp, (wchar_t)'\n');
if (nbytes > 0)
tp += nbytes;
nbytes = wctomb(tp, (wchar_t)0);
if (nbytes > 0)
tp += nbytes;
//
// Emit the line to stderr. It is important to do this
// a whole line at a time, otherwise performance is
// terrible - stderr by default is character buffered.
//
fputs(tmp, stderr);
if (ferror(stderr))
break;
//
// skip leading spaces for subsequent lines
//
while (*s == ' ')
++s;
first_line = 0;
}
//
// done with humans
//
language_C();
//
// make sure nothing went wrong
//
if (fflush(stderr) || ferror(stderr))
nfatal("(stderr)");
}
void
sub_context_ty::error_intl(const char *s)
{
if (!os_testing_mode())
language_check_translations();
wstring message = subst_intl_wide(s);
wrap(message.c_str());
}
void
sub_context_ty::fatal_intl(const char *s)
{
if (!os_testing_mode())
language_check_translations();
//
// Make sure that there isn't an infinite loop,
// if there is a problem with a substitution
// in an error message.
//
static const char *double_jeopardy;
if (double_jeopardy)
{
//
// this error message can't be internationalized
//
fatal_raw
(
"a fatal_intl error (\"%s\") happened while attempting "
"to report an earlier fatal_intl error (\"%s\"). "
"This is probably a bug.",
s,
double_jeopardy
);
}
double_jeopardy = s;
wstring message = subst_intl_wide(s);
wrap(message.c_str());
double_jeopardy = 0;
quit(1);
// NOTREACHED
}
void
sub_context_ty::verbose_intl(const char *s)
{
if (option_verbose_get())
{
if (!os_testing_mode())
language_check_translations();
wstring message = subst_intl_wide(s);
wrap(message.c_str());
}
clear();
}
void
sub_context_ty::errno_setx(int x)
{
errno_sequester = x;
}
int
sub_context_ty::errno_sequester_get(void)
const
{
return errno_sequester;
}
// vim: set ts=8 sw=4 et :