r400 - in trunk: . scripts

DONOTREPLY at icculus.org DONOTREPLY at icculus.org
Sat Jan 12 05:48:34 EST 2008


Author: icculus
Date: 2008-01-12 05:48:34 -0500 (Sat, 12 Jan 2008)
New Revision: 400

Modified:
   trunk/docs.txt
   trunk/fileio.c
   trunk/gui.c
   trunk/gui_gtkplus2.c
   trunk/gui_ncurses.c
   trunk/gui_stdio.c
   trunk/gui_www.c
   trunk/lua_glue.c
   trunk/mojosetup.c
   trunk/platform_unix.c
   trunk/scripts/localization.lua
   trunk/universal.h
Log:
Reworked string formatting, so you can reorder formatting args by localization,
 dynamically allocate the formatted string, and not depend so much on the C
 runtime.


Modified: trunk/docs.txt
===================================================================
--- trunk/docs.txt	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/docs.txt	2008-01-12 10:48:34 UTC (rev 400)
@@ -740,7 +740,16 @@
  the basic config schema. Everything else is free game. Here are the globals
  that MojoSetup provides:
 
+  MojoSetup.format(fmt, ...)
 
+   Format a string, sort of (but not exactly!) like sprintf().
+    The only formatters accepted are %0 through %9 (and %%), which do not
+    have to appear in order in the string, but match the varargs in the
+    order they are passed to the function.
+
+   format('%1 %0 %1 %%', 'X', 'Y', 'Z') will return the string: 'Y X Y %'
+
+
   MojoSetup.fatal(errstr)
 
    Display (errstr) to the end user and stop the installation. The installer

Modified: trunk/fileio.c
===================================================================
--- trunk/fileio.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/fileio.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -570,7 +570,6 @@
 #if !SUPPORT_URL_HTTP && !SUPPORT_URL_FTP
 MojoInput *MojoInput_fromURL(const char *url)
 {
-    // !!! FIXME: localization.
     logError(_("No networking support in this build."));
     return NULL;
 } // MojoInput_fromURL

Modified: trunk/gui.c
===================================================================
--- trunk/gui.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/gui.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -73,7 +73,7 @@
         {
             if ( (i->priority == p) && (i->gui->init()) )
             {
-                logInfo("Selected '%s' UI.", i->gui->name());
+                logInfo("Selected '%0' UI.", i->gui->name());
                 return i;
             } // if
         } // for

Modified: trunk/gui_gtkplus2.c
===================================================================
--- trunk/gui_gtkplus2.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/gui_gtkplus2.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -781,14 +781,15 @@
     gint rc = 0;
     // !!! FIXME: Use stock GTK icon for "media"?
     // !!! FIXME: better text.
-    const char *title = entry->_("Media change");
+    const char *title = entry->xstrdup(entry->_("Media change"));
     // !!! FIXME: better text.
-    const char *fmt = entry->_("Please insert '%s'");
-    size_t len = strlen(fmt) + strlen(medianame) + 1;
-    char *text = (char *) entry->xmalloc(len);
-    snprintf(text, len, fmt, medianame);
+    const char *fmt = entry->xstrdup(entry->_("Please insert '%0'"));
+    const char *text = entry->format(fmt, medianame);
     rc = do_msgbox(title, text, GTK_MESSAGE_WARNING,
                    GTK_BUTTONS_OK_CANCEL, GTK_RESPONSE_OK, NULL);
+    free((void *) text);
+    free((void *) fmt);
+    free((void *) title);
     return (rc == GTK_RESPONSE_OK);
 } // MojoGui_gtkplus2_insertmedia
 

Modified: trunk/gui_ncurses.c
===================================================================
--- trunk/gui_ncurses.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/gui_ncurses.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -1285,25 +1285,22 @@
 
 static boolean MojoGui_ncurses_insertmedia(const char *medianame)
 {
-    char *fmt = entry->xstrdup(entry->_("Please insert '%s'"));
-    const size_t len = strlen(fmt) + strlen(medianame) + 1;
-    char *text = (char *) entry->xmalloc(len);
+    char *fmt = entry->xstrdup(entry->_("Please insert '%0'"));
+    char *text = entry->format(fmt, medianame);
     char *localized_ok = entry->xstrdup(entry->_("Ok"));
     char *localized_cancel = entry->xstrdup(entry->_("Cancel"));
     char *buttons[] = { localized_ok, localized_cancel };
     MojoBox *mojobox = NULL;
     int rc = 0;
 
-    snprintf(text, len, fmt, medianame);
-    free(fmt);
-
     mojobox = makeBox(entry->_("Media change"), text, buttons, 2, false, true);
     while ((rc = upkeepBox(&mojobox, wgetch(mojobox->mainwin))) == -1) {}
 
     freeBox(mojobox, true);
-    free(text);
     free(localized_cancel);
     free(localized_ok);
+    free(text);
+    free(fmt);
     return (rc == 0);
 } // MojoGui_ncurses_insertmedia
 

Modified: trunk/gui_stdio.c
===================================================================
--- trunk/gui_stdio.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/gui_stdio.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -46,16 +46,18 @@
     // !!! FIXME:  abort in read_stdin() if i/o fails?
 
     int retval = 0;
-    const char *backstr = NULL;
+    char *backstr = (back) ? entry->xstrdup(entry->_("back")) : NULL;
 
     if (prompt != NULL)
         printf("%s\n", prompt);
 
     if (back)
     {
-        backstr = entry->xstrdup(entry->_("back"));
-        printf(entry->_("Type '%s' to go back."), backstr);
-        printf("\n");
+        char *fmt = entry->xstrdup(entry->_("Type '%0' to go back."));
+        char *msg = entry->format(fmt, backstr);
+        printf("%s\n", msg);
+        free(msg);
+        free(fmt);
     } // if
 
     if (fwd)
@@ -73,7 +75,7 @@
             retval = -1;
     } // if
 
-    free((void *) backstr);
+    free(backstr);
     return retval;
 } // readstr
 
@@ -111,7 +113,11 @@
 static void MojoGui_stdio_msgbox(const char *title, const char *text)
 {
     char buf[128];
-    printf(entry->_("NOTICE: %s\n[hit enter]"), text);
+    char *fmt = entry->xstrdup(entry->_("NOTICE: %0\n[hit enter]"));
+    char *msg = entry->format(fmt, text);
+    printf("%s\n", msg);
+    free(msg);
+    free(fmt);
     fflush(stdout);
     read_stdin(buf, sizeof (buf));
 } // MojoGui_stdio_msgbox
@@ -123,10 +129,11 @@
     boolean retval = false;
     if (!feof(stdin))
     {
-        const char *fmt = ((defval) ? "%s\n[Y/n]: " : "%s\n[y/N]: ");
-        const char *localized_fmt = entry->xstrdup(entry->_(fmt));
-        const char *localized_no = entry->xstrdup(entry->_("N"));
-        const char *localized_yes = entry->xstrdup(entry->_("Y"));
+        const char *_fmt = ((defval) ? "%1\n[Y/n]: " : "%1\n[y/N]: ");
+        char *fmt = entry->xstrdup(entry->_(_fmt));
+        char *msg = entry->format(fmt, text);
+        char *localized_no = entry->xstrdup(entry->_("N"));
+        char *localized_yes = entry->xstrdup(entry->_("Y"));
         boolean getout = false;
         char buf[128];
 
@@ -135,7 +142,7 @@
             int rc = 0;
 
             getout = true;  // we may reset this later.
-            printf(localized_fmt, text);
+            printf("%s\n", msg);
             fflush(stdout);
             rc = read_stdin(buf, sizeof (buf));
 
@@ -151,9 +158,10 @@
                 getout = false;  // try again.
         } // while
 
-        free((void *) localized_yes);
-        free((void *) localized_no);
-        free((void *) localized_fmt);
+        free(localized_yes);
+        free(localized_no);
+        free(msg);
+        free(fmt);
     } // if
 
     return retval;
@@ -166,11 +174,12 @@
     MojoGuiYNAN retval = MOJOGUI_NO;
     if (!feof(stdin))
     {
-        const char *localized_fmt = entry->_("%s\n[y/n/Always/Never]: ");
-        const char *localized_no = entry->xstrdup(entry->_("N"));
-        const char *localized_yes = entry->xstrdup(entry->_("Y"));
-        const char *localized_always = entry->xstrdup(entry->_("Always"));
-        const char *localized_never = entry->xstrdup(entry->_("Never"));
+        char *fmt = entry->xstrdup(_("%0\n[y/n/Always/Never]: "));
+        char *msg = entry->format(fmt, txt);
+        char *localized_no = entry->xstrdup(entry->_("N"));
+        char *localized_yes = entry->xstrdup(entry->_("Y"));
+        char *localized_always = entry->xstrdup(entry->_("Always"));
+        char *localized_never = entry->xstrdup(entry->_("Never"));
         boolean getout = false;
         char buf[128];
 
@@ -179,7 +188,7 @@
             int rc = 0;
 
             getout = true;  // we may reset this later.
-            printf(localized_fmt, txt);
+            printf("%s\n", msg);
             fflush(stdout);
             rc = read_stdin(buf, sizeof (buf));
 
@@ -199,11 +208,12 @@
                 getout = false;  // try again.
         } // while
 
-        free((void *) localized_never);
-        free((void *) localized_always);
-        free((void *) localized_yes);
-        free((void *) localized_no);
-        free((void *) localized_fmt);
+        free(localized_never);
+        free(localized_always);
+        free(localized_yes);
+        free(localized_no);
+        free(msg);
+        free(fmt);
     } // if
 
     return retval;
@@ -303,9 +313,7 @@
 static void dumb_pager(const char *name, const char *data, size_t datalen)
 {
     const int MAX_PAGE_LINES = 21;
-    char buf[256];
-    const char *_fmt = entry->_("(%d-%d of %d lines, see more?)"); // !!! FIXME: localization
-    char *fmt = entry->xstrdup(_fmt);
+    char *fmt = entry->xstrdup(entry->_("(%0-%1 of %2 lines, see more?)"));
     int i = 0;
     int w = 0;
     int linecount = 0;
@@ -330,10 +338,13 @@
                 getout = true;
             else
             {
+                char *msg = NULL;
                 printf("\n");
-                snprintf(buf, sizeof (buf), fmt,
-                         (printed-i)+1, printed, linecount);
-                getout = !MojoGui_stdio_promptyn("", buf, true);
+                msg = entry->format(fmt, entry->numstr((printed-i)+1),
+                                    entry->numstr(printed),
+                                    entry->numstr(linecount));
+                getout = !MojoGui_stdio_promptyn("", msg, true);
+                free(msg);
                 printf("\n");
             } // else
         } while (!getout);
@@ -575,8 +586,11 @@
 static boolean MojoGui_stdio_insertmedia(const char *medianame)
 {
     char buf[32];
-    printf(entry->_("Please insert '%s'"), medianame);
-    printf("\n");
+    char *fmt = entry->xstrdup(entry->_("Please insert '%0'"));
+    char *msg = entry->format(fmt, medianame);
+    printf("%s\n", msg);
+    free(msg);
+    free(fmt);
     return (readstr(NULL, buf, sizeof (buf), false, true) >= 0);
 } // MojoGui_stdio_insertmedia
 
@@ -601,12 +615,20 @@
     // limit update spam... will only write every one second, tops.
     if (percentTicks <= now)
     {
+        char *fmt = NULL;
+        char *msg = NULL;
         percentTicks = now + 1000;
         // !!! FIXME: localization.
         if (percent < 0)
-            printf(entry->_("%s\n"), item);
+            printf("%s\n", item);
         else
-            printf(entry->_("%s (total progress: %d%%)\n"), item, percent);
+        {
+            fmt = entry->xstrdup(entry->_("%0 (total progress: %1%%)\n"));
+            msg = entry->format(fmt, item, entry->numstr(percent));
+            printf("%s\n", msg);
+            free(msg);
+            free(fmt);
+        } // else
     } // if
 
     return true;

Modified: trunk/gui_www.c
===================================================================
--- trunk/gui_www.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/gui_www.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -52,18 +52,19 @@
         int rc = WSAStartup(MAKEWORD(1, 1), &data);
         if (rc != 0)
         {
-            entry->logError("www: WSAStartup() failed: %s", sockStrErrVal(rc));
+            entry->logError("www: WSAStartup() failed: %0", sockStrErrVal(rc));
             return false;
         } // if
 
-        entry->logInfo("www: WinSock initialized (want %d.%d, got %d.%d).",
-                         (int) (LOBYTE(data.wVersion)),
-                         (int) (HIBYTE(data.wVersion)),
-                         (int) (LOBYTE(data.wHighVersion)),
-                         (int) (HIBYTE(data.wHighVersion)));
-        entry->logInfo("www: WinSock description: %s", data.szDescription);
-        entry->logInfo("www: WinSock system status: %s", data.szSystemStatus);
-        entry->logInfo("www: WinSock max sockets: %s", (int) data.iMaxSockets);
+        entry->logInfo("www: WinSock initialized (want %0.%1, got %2.%3).",
+                        entry->numstr((int) (LOBYTE(data.wVersion))),
+                        entry->numstr((int) (HIBYTE(data.wVersion))),
+                        entry->numstr((int) (LOBYTE(data.wHighVersion))),
+                        entry->numstr((int) (HIBYTE(data.wHighVersion))));
+        entry->logInfo("www: WinSock description: %0", data.szDescription);
+        entry->logInfo("www: WinSock system status: %0", data.szSystemStatus);
+        entry->logInfo("www: WinSock max sockets: %0",
+                        entry->numstr((int) data.iMaxSockets));
 
         return true;
     } // initSocketSupport
@@ -158,7 +159,7 @@
         req->value = entry->xstrdup(val);
         req->next = webRequest;
         webRequest = req;
-        entry->logDebug("www: request element '%s' = '%s'", key, val);
+        entry->logDebug("www: request element '%0' = '%1'", key, val);
     } // if
 } // addWebRequest
 
@@ -308,7 +309,7 @@
             const int err = sockErrno();
             if (!intrError(err))
             {
-                entry->logError("www: send() failed: %s", sockStrErrVal(err));
+                entry->logError("www: send() failed: %0", sockStrErrVal(err));
                 break;
             } // if
         } // else
@@ -456,7 +457,7 @@
             assert(!blocking);
         else
         {
-            entry->logError("www: accept() failed: %s", sockStrErrVal(err));
+            entry->logError("www: accept() failed: %0", sockStrErrVal(err));
             closesocket(listenSocket);  // make all future i/o fail too.
             listenSocket = INVALID_SOCKET;
         } // else
@@ -474,7 +475,7 @@
             const int err = sockErrno();
             if (!intrError(err))  // just try again on interrupt.
             {
-                entry->logError("www: recv() failed: %s", sockStrErrVal(err));
+                entry->logError("www: recv() failed: %0", sockStrErrVal(err));
                 FREE_AND_NULL(reqstr);
                 closesocket(s);
                 s = INVALID_SOCKET;
@@ -519,7 +520,7 @@
             memmove(reqstr, ptr, len+1);
         } // else
 
-        entry->logDebug("www: request '%s'", get);
+        entry->logDebug("www: request '%0'", get);
 
         // okay, now (get) and (reqptr) are separate strings.
         // These parse*() functions update (webRequest).
@@ -554,7 +555,7 @@
 
     s = socket(PF_INET, SOCK_STREAM, protocol);
     if (s == INVALID_SOCKET)
-        entry->logInfo("www: socket() failed ('%s')", sockStrError());
+        entry->logInfo("www: socket() failed ('%0')", sockStrError());
     else
     {
         boolean success = false;
@@ -572,12 +573,13 @@
         #endif
 
         if (bind(s, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR)
-            entry->logError("www: bind() failed ('%s')", sockStrError());
+            entry->logError("www: bind() failed ('%0')", sockStrError());
         else if (listen(s, 5) == SOCKET_ERROR)
-            entry->logError("www: listen() failed ('%s')", sockStrError());
+            entry->logError("www: listen() failed ('%0')", sockStrError());
         else
         {
-            entry->logInfo("www: socket created on port %d", (int) portnum);
+            entry->logInfo("www: socket created on port %0",
+                           entry->numstr(portnum));
             success = true;
         } // else
 
@@ -987,10 +989,12 @@
             htmlescape(entry->_("OK")),
     };
 
-    // !!! FIXME: better text.
     char *title = entry->xstrdup(entry->_("Media change"));
-    // !!! FIXME: better text.
-    strAdd(&text, &len, &alloc, entry->_("Please insert '%s'"), medianame);
+    char *fmt = entry->xstrdup(entry->_("Please insert '%0'"));
+    char *msg = entry->format(fmt, medianame);
+    strAdd(&text, &len, &alloc, msg);
+    free(msg);
+    free(fmt);
 
     htmltext = htmlescape(text);
     free(text);

Modified: trunk/lua_glue.c
===================================================================
--- trunk/lua_glue.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/lua_glue.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -215,7 +215,7 @@
     int i = 0;
 
     if (errstr != NULL)
-        logDebug("%s\n", errstr);
+        logDebug("%0", errstr);
 
     logDebug("Lua stack backtrace:");
 
@@ -233,7 +233,7 @@
         if (!lua_getinfo(L, "nSl", &ldbg))
         {
             snprintfcat(&ptr, &len, "???\n");
-            logDebug((const char *) scratchbuf_128k);
+            logDebug("%0", (const char *) scratchbuf_128k);
             continue;
         } // if
 
@@ -252,7 +252,7 @@
                 snprintfcat(&ptr, &len, "unidentifiable function");
         } // if
 
-        logDebug((const char *) scratchbuf_128k);
+        logDebug("%0", (const char *) scratchbuf_128k);
         ptr = (char *) scratchbuf_128k;
         len = sizeof (scratchbuf_128k);
 
@@ -271,7 +271,7 @@
             if (ldbg.currentline != -1)
                 snprintfcat(&ptr, &len, ":%d", ldbg.currentline);
         } // else
-        logDebug((const char *) scratchbuf_128k);
+        logDebug("%0", (const char *) scratchbuf_128k);
     } // for
 
     return retvalString(L, errstr ? errstr : "");
@@ -446,12 +446,13 @@
     int post = 0;
 
     pre = (lua_gc(L, LUA_GCCOUNT, 0) * 1024) + lua_gc(L, LUA_GCCOUNTB, 0);
-    logDebug("Collecting garbage (currently using %d bytes).", pre);
+    logDebug("Collecting garbage (currently using %0 bytes).", numstr(pre));
     ticks = MojoPlatform_ticks();
     lua_gc (L, LUA_GCCOLLECT, 0);
     profile("Garbage collection", ticks);
     post = (lua_gc(L, LUA_GCCOUNT, 0) * 1024) + lua_gc(L, LUA_GCCOUNTB, 0);
-    logDebug("Now using %d bytes (%d bytes savings).\n", post, pre - post);
+    logDebug("Now using %0 bytes (%1 bytes savings).",
+             numstr(post), numstr(pre - post));
 } // MojoLua_collectGarbage
 
 
@@ -499,6 +500,37 @@
 } // translate
 
 
+// Lua interface to format().
+static int luahook_format(lua_State *L)
+{
+    const int argc = lua_gettop(L);
+    const char *fmt = luaL_checkstring(L, 1);
+    char *formatted = NULL;
+    char *s[10];
+    int i;
+
+    assert(argc <= 11);  // fmt, plus %0 through %9.
+
+    for (i = 0; i < STATICARRAYLEN(s); i++)
+    {
+        const char *str = NULL;
+        if ((i+2) <= argc)
+            str = lua_tostring(L, i+2);
+        s[i] = (str == NULL) ? NULL : xstrdup(str);
+    } // for
+
+    // I think this is legal (but probably not moral) C code.
+    formatted = format(fmt,s[0],s[1],s[2],s[3],s[4],s[5],s[6],s[7],s[8],s[9]);
+
+    for (i = 0; i < STATICARRAYLEN(s); i++)
+        free(s[i]);
+
+    lua_pushstring(L, formatted);
+    free(formatted);
+    return 1;
+} // luahook_format
+
+
 // Use this instead of Lua's error() function if you don't have a
 //  programatic error, so you don't get stack callback stuff:
 // MojoSetup.fatal("You need the base game to install this expansion pack.")
@@ -507,7 +539,9 @@
 static int luahook_fatal(lua_State *L)
 {
     const char *errstr = lua_tostring(L, 1);
-    return fatal(errstr);  // doesn't actually return.
+    if (errstr == NULL)
+        return fatal(NULL);  // doesn't actually return.
+    return fatal("%0", errstr);  // doesn't actually return.
 } // luahook_fatal
 
 
@@ -589,28 +623,28 @@
 
 static int luahook_logwarning(lua_State *L)
 {
-    logWarning(luaL_checkstring(L, 1));
+    logWarning("%0", luaL_checkstring(L, 1));
     return 0;
 } // luahook_logwarning
 
 
 static int luahook_logerror(lua_State *L)
 {
-    logError(luaL_checkstring(L, 1));
+    logError("%0", luaL_checkstring(L, 1));
     return 0;
 } // luahook_logerror
 
 
 static int luahook_loginfo(lua_State *L)
 {
-    logInfo(luaL_checkstring(L, 1));
+    logInfo("%0", luaL_checkstring(L, 1));
     return 0;
 } // luahook_loginfo
 
 
 static int luahook_logdebug(lua_State *L)
 {
-    logDebug(luaL_checkstring(L, 1));
+    logDebug("%0", luaL_checkstring(L, 1));
     return 0;
 } // luahook_logdebug
 
@@ -729,7 +763,7 @@
             const char *permstr = luaL_checkstring(L, 3);
             perms = MojoPlatform_makePermissions(permstr, &valid);
             if (!valid)
-                fatal(_("BUG: '%s' is not a valid permission string"), permstr);
+                fatal(_("BUG: '%0' is not a valid permission string"), permstr);
         } // if
         rc = MojoInput_toPhysicalFile(in, path, perms, &sums,
                                           writeCallback, L);
@@ -912,7 +946,7 @@
         const char *permstr = luaL_checkstring(L, 2);
         perms = MojoPlatform_makePermissions(permstr, &valid);
         if (!valid)
-            fatal(_("BUG: '%s' is not a valid permission string"), permstr);
+            fatal(_("BUG: '%0' is not a valid permission string"), permstr);
     } // if
     return retvalBoolean(L, MojoPlatform_mkdir(dir, perms));
 } // luahook_platform_mkdir
@@ -1041,7 +1075,7 @@
     const boolean can_go_fwd = canGoForward(thisstage, maxstage);
 
     if (data == NULL)
-        fatal(_("failed to load file '%s'"), fname);
+        fatal(_("failed to load file '%0'"), fname);
 
     lua_pushnumber(L, GGui->readme(name, data, len, can_go_back, can_go_fwd));
     free((void *) data);
@@ -1088,7 +1122,7 @@
         if (required)
         {
             lua_getfield(L, -2, "description");
-            logWarning("Option '%s' is both required and disabled!",
+            logWarning("Option '%0' is both required and disabled!",
                         lua_tostring(L, -1));
             lua_pop(L, 1);
         } // if
@@ -1166,7 +1200,7 @@
         // !!! FIXME: schema should check?
         if ((is_group) && (opts->is_group_parent))
         {
-            fatal("OptionGroup '%s' inside OptionGroup '%s'.",
+            fatal("OptionGroup '%0' inside OptionGroup '%1'.",
                   opts->description, parent->description);
         } // if
 
@@ -1174,7 +1208,7 @@
         {
             if (seen_enabled)
             {
-                logWarning("Options '%s' and '%s' are both enabled in group '%s'.",
+                logWarning("Options '%0' and '%1' are both enabled in group '%2'.",
                             seen_enabled->description, opts->description,
                             parent->description);
                 seen_enabled->value = false;
@@ -1191,7 +1225,7 @@
 
     if ((prev) && (is_group) && (!seen_enabled))
     {
-        logWarning("Option group '%s' has no enabled items, choosing first ('%s').",
+        logWarning("Option group '%0' has no enabled items, choosing first ('%1').",
                     parent->description, prev->description);
         prev->value = true;
     } // if
@@ -1460,6 +1494,7 @@
         set_cfunc(luaState, luahook_runfile, "runfile");
         set_cfunc(luaState, luahook_translate, "translate");
         set_cfunc(luaState, luahook_ticks, "ticks");
+        set_cfunc(luaState, luahook_format, "format");
         set_cfunc(luaState, luahook_fatal, "fatal");
         set_cfunc(luaState, luahook_msgbox, "msgbox");
         set_cfunc(luaState, luahook_promptyn, "promptyn");

Modified: trunk/mojosetup.c
===================================================================
--- trunk/mojosetup.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/mojosetup.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -33,6 +33,8 @@
     logError,
     logInfo,
     logDebug,
+    format,
+    numstr,
     MojoPlatform_ticks,
 };
 
@@ -74,7 +76,7 @@
         io->close(io);
         if (br == imglen)
         {
-            logInfo("Switching binary with '%s'...", ar->prevEnum.filename);
+            logInfo("Switching binary with '%0'...", ar->prevEnum.filename);
             MojoPlatform_switchBin(img, imglen);  // no return on success.
             logError("...Switch binary failed.");
         } // if
@@ -329,6 +331,104 @@
 } // wildcardMatch
 
 
+const char *numstr(int val)
+{
+    static int pos = 0;
+    char *ptr = ((char *) scratchbuf_128k) + (pos * 128);
+    snprintf(ptr, 128, "%d", val);
+    pos = (pos + 1) % 1000;
+    return ptr;
+} // numstr
+
+
+static char *format_internal(const char *fmt, va_list ap)
+{
+    // This is kinda nasty. String manipulation in C always is.
+    char *retval = NULL;
+    const char *strs[10];  // 0 through 9.
+    const char *ptr = NULL;
+    char *wptr = NULL;
+    size_t len = 0;
+    int maxfmtid = -2;
+    int i;
+
+    // figure out what this format string contains...
+    for (ptr = fmt; *ptr; ptr++)
+    {
+        if (*ptr == '%')
+        {
+            const char ch = *(++ptr);
+            if (ch == '%')  // a literal '%'
+                maxfmtid = (maxfmtid == -2) ? -1 : maxfmtid;
+            else if ((ch >= '0') && (ch <= '9'))
+                maxfmtid = ((maxfmtid > (ch - '0')) ? maxfmtid : (ch - '0'));
+            else
+                fatal(_("BUG: Invalid format() string"));
+        } // if
+    } // while
+
+    if (maxfmtid == -2)  // no formatters present at all.
+        return xstrdup(fmt);  // just copy it, we're done.
+
+    for (i = 0; i <= maxfmtid; i++)  // referenced varargs --> linear array.
+    {
+        strs[i] = va_arg(ap, const char *);
+        if (strs[i] == NULL)
+            strs[i] = "(null)";  // just to match sprintf() behaviour...
+    } // for
+
+    // allocate the string we'll need in one shot, so we don't have to resize.
+    for (ptr = fmt; *ptr; ptr++)
+    {
+        if (*ptr != '%')
+            len++;
+        else
+        {
+            const char ch = *(++ptr);
+            if (ch == '%')  // a literal '%'
+                len++;  // just want '%' char.
+            else //if ((ch >= '0') && (ch <= '9'))
+                len += strlen(strs[ch - '0']);
+        } // else
+    } // while
+
+    // Now write the formatted string...
+    wptr = retval = (char *) xmalloc(len+1);
+    for (ptr = fmt; *ptr; ptr++)
+    {
+        const char strch = *ptr;
+        if (strch != '%')
+            *(wptr++) = strch;
+        else
+        {
+            const char ch = *(++ptr);
+            if (ch == '%')  // a literal '%'
+                *(wptr++) = '%';
+            else //if ((ch >= '0') && (ch <= '9'))
+            {
+                const char *str = strs[ch - '0'];
+                strcpy(wptr, str);
+                wptr += strlen(str);
+            } // else
+        } // else
+    } // while
+    *wptr = '\0';
+
+    return retval;
+} // format_internal
+
+
+char *format(const char *fmt, ...)
+{
+    char *retval = NULL;
+    va_list ap;
+    va_start(ap, fmt);
+    retval = format_internal(fmt, ap);
+    va_end(ap);
+    return retval;
+} // format
+
+
 #if ((defined _NDEBUG) || (defined NDEBUG))
 #define DEFLOGLEV "info"
 #else
@@ -380,21 +480,22 @@
 {
     if (level <= MojoLog_logLevel)
     {
-        char buf[1024];
+        char *str = format_internal(fmt, ap);
         //int len = vsnprintf(buf + 2, sizeof (buf) - 2, fmt, ap) + 2;
         //buf[0] = levelchar;
         //buf[1] = ' ';
-        int len = vsnprintf(buf, sizeof (buf), fmt, ap);
-        while ( (--len >= 0) && ((buf[len] == '\n') || (buf[len] == '\r')) ) {}
-        buf[len+1] = '\0';  // delete trailing newline crap.
-        MojoPlatform_log(buf);
+        int len = strlen(str);
+        while ( (--len >= 0) && ((str[len] == '\n') || (str[len] == '\r')) ) {}
+        str[len+1] = '\0';  // delete trailing newline crap.
+        MojoPlatform_log(str);
         if (logFile != NULL)
         {
             const char *endl = MOJOPLATFORM_ENDLINE;
-            MojoPlatform_write(logFile, buf, strlen(buf));
+            MojoPlatform_write(logFile, str, strlen(str));
             MojoPlatform_write(logFile, endl, strlen(endl));
             MojoPlatform_flush(logFile);
         } // if
+        free(str);
     } // if
 } // addLog
 
@@ -439,9 +540,7 @@
 {
     uint32 retval = MojoPlatform_ticks() - start_time;
     if (what != NULL)
-    {
-        logDebug("%s took %lu ms.", what, (unsigned long) retval);
-    } // if
+        logDebug("%0 took %1 ms.", what, numstr((int) retval));
     return retval;
 } // profile_start
 
@@ -458,24 +557,13 @@
     // may not want to show a message, since we displayed one elsewhere, etc.
     if (fmt != NULL)
     {
+        char *buf = NULL;
         va_list ap;
-        int rc = 0;
-        int len = 128;
-        char *buf = xmalloc(len);
-
         va_start(ap, fmt);
-        rc = vsnprintf(buf, len, fmt, ap);
+        buf = format_internal(fmt, ap);
         va_end(ap);
-        if (rc >= len)
-        {
-            len = rc;
-            buf = xrealloc(buf, len);
-            va_start(ap, fmt);
-            vsnprintf(buf, len, fmt, ap);
-            va_end(ap);
-        } // if
 
-        logError("FATAL: %s", buf);
+        logError("FATAL: %0", buf);
 
         if (GGui != NULL)
             GGui->msgbox(_("Fatal error"), buf);
@@ -502,7 +590,7 @@
     panic_runs++;
     if (panic_runs == 1)
     {
-        logError("PANIC: %s", err);
+        logError("PANIC: %0", err);
         panic(err);
     } // if
 
@@ -583,7 +671,7 @@
 #if MOJOSETUP_INTERNAL_BZLIB && BZ_NO_STDIO
 void bz_internal_error(int errcode)
 {
-    fatal(_("bzlib triggered an internal error: %d"), errcode);
+    fatal(_("bzlib triggered an internal error: %0"), numstr(numbuf));
 } // bz_internal_error
 #endif
 

Modified: trunk/platform_unix.c
===================================================================
--- trunk/platform_unix.c	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/platform_unix.c	2008-01-12 10:48:34 UTC (rev 400)
@@ -1031,7 +1031,7 @@
     if (first_shot)
     {
         first_shot = false;
-        logError("Caught signal #%d", sig);
+        logError("Caught signal #%0", numstr(sig));
     } // if
 } // signal_catcher
 

Modified: trunk/scripts/localization.lua
===================================================================
--- trunk/scripts/localization.lua	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/scripts/localization.lua	2008-01-12 10:48:34 UTC (rev 400)
@@ -17,6 +17,17 @@
 -- You should leave the existing strings here. They aren't hurting anything,
 --  and most are used by MojoSetup itself. Add your own, though.
 --
+-- Whenever you see a %x sequence, that is replaced with a string at runtime.
+--  So if you see, ["Hello, %0, my name is %1."], then this might become
+--  "Hello, Alice, my name is Bob." at runtime. If your culture would find
+--  introducing yourself second to be rude, you might translate this to:
+--  "My name is %1, hello %0." If you need a literal '%' char, write "%%":
+--  "Operation is %0%% complete" might give "Operation is 3% complete."
+--  All strings, from your locale or otherwise, are checked for formatter
+--  correctness at startup. This is to prevent the installer working fine
+--  in all reasonable tests, then finding out that one guy in Ghana has a
+--  crashing installer because his localization forgot to add a %1 somewhere.
+--
 -- The table you create here goes away shortly after creation, as the relevant
 --  parts of it get moved somewhere else. You should call MojoSetup.translate()
 --  to get the proper translation for a given string.
@@ -42,19 +53,19 @@
     };
 
     -- stdio GUI plugin says this for msgboxes (printf format string).
-    ["NOTICE: %s\n[hit enter]"] = {
+    ["NOTICE: %1\n[hit enter]"] = {
     };
 
     -- stdio GUI plugin says this for yes/no prompts that default to yes (printf format string).
-    ["%s\n[Y/n]: "] = {
+    ["%1\n[Y/n]: "] = {
     };
 
     -- stdio GUI plugin says this for yes/no prompts that default to no (printf format string).
-    ["%s\n[y/N]: "] = {
+    ["%1\n[y/N]: "] = {
     };
 
     -- stdio GUI plugin says this for yes/no/always/never prompts (printf format string).
-    ["%s\n[y/n/Always/Never]: "] = {
+    ["%1\n[y/n/Always/Never]: "] = {
     };
 
     -- This is utf8casecmp()'d for "yes" answers in stdio GUI's promptyn().
@@ -66,7 +77,7 @@
     };
 
     -- This is shown when using stdio GUI's built-in README pager (printf format).
-    ["(Viewing %d-%d of %d lines, see more?)"] = {
+    ["(Viewing %1-%2 of %3 lines, see more?)"] = {
     };
 
     -- This is utf8casecmp()'d for "always" answers in stdio GUI's promptyn().
@@ -95,10 +106,10 @@
     ["Choose number to change."] = {
     };
 
-    ["Type '%s' to go back."] = {
+    ["Type '%1' to go back."] = {
     };
 
-    -- This is the string used for the '%s' in the above string.
+    -- This is the string used for the '%1' in the above string.
     ["back"] = {
     };
 
@@ -142,10 +153,10 @@
     ["You must accept the license before you may install"] = {
     };
 
-    ["failed to load file '%s'"] = {
+    ["failed to load file '%1'"] = {
     };
 
-    ["Please insert '%s'"] = {
+    ["Please insert '%1'"] = {
     };
 
     ["(I want to specify a path.)"] = {
@@ -161,6 +172,9 @@
 
     ["Setup program is shutting down. You can close this browser now."] = {
     };
+
+    ["No networking support in this build."] = {
+    };
 };
 
 -- end of localization.lua ...

Modified: trunk/universal.h
===================================================================
--- trunk/universal.h	2008-01-12 10:16:04 UTC (rev 399)
+++ trunk/universal.h	2008-01-12 10:48:34 UTC (rev 400)
@@ -96,6 +96,23 @@
 // Static, non-stack memory for scratch work...not thread safe!
 extern uint8 scratchbuf_128k[128 * 1024];
 
+// Format a string, sort of (but not exactly!) like sprintf().
+//  The only formatters accepted are %0 through %9 (and %%), which do not
+//  have to appear in order in the string, but match the varargs passed to the
+//  function. Only strings are accepted for varargs. This function allocates
+//  memory as necessary, so you need to free() the result, but don't need to
+//  preallocate a buffer and be concerned about overflowing it.
+// This does not use scratchbuf_128k.
+char *format(const char *fmt, ...);
+
+// Convert an int to a string. This uses incremental pieces of
+//  scratchbuf_128k for a buffer to store the results, and
+//  will overwrite itself after some number of calls when the memory
+//  is all used, but note that other things use scratchbuf_128k too,
+//  so this is only good for printf() things:
+// fmtfunc("mission took %0 seconds, %1 points", numstr(secs), numstr(pts));
+const char *numstr(int val);
+
 // Call this for fatal errors that require immediate app termination.
 //  Does not clean up, or translate the error string. Try to avoid this.
 //  These are for crucial lowlevel issues that preclude any meaningful
@@ -112,7 +129,9 @@
 // If there's no GUI or Lua isn't initialized, this calls panic(). That's bad.
 // Doesn't return, but if it did, you can assume it returns zero, so you can
 //  do:  'return fatal("missing config file");' or whatnot.
-int fatal(const char *fmt, ...) ISPRINTF(1,2);
+// THIS DOES NOT USE PRINTF-STYLE FORMAT CODES. Please see the comments for
+//  format() for details.
+int fatal(const char *fmt, ...);
 
 // The platform layer should set up signal/exception handlers before calling
 //  MojoSetup_main(), that will call these functions. "crashed" for bug
@@ -121,9 +140,6 @@
 void MojoSetup_crashed(void);
 void MojoSetup_terminated(void);
 
-// Call this to pop up a warning dialog box and block until user hits OK.
-void warn(const char *fmt, ...) ISPRINTF(1,2);
-
 // Malloc replacements that blow up on allocation failure.
 // Please note that xmalloc() will zero the newly-allocated memory buffer,
 //  like calloc() would, but xrealloc() makes no such promise!
@@ -253,12 +269,16 @@
 extern MojoSetupLogLevel MojoLog_logLevel;
 void MojoLog_initLogging(void);
 void MojoLog_deinitLogging(void);
-void logWarning(const char *fmt, ...) ISPRINTF(1,2);
-void logError(const char *fmt, ...) ISPRINTF(1,2);
-void logInfo(const char *fmt, ...) ISPRINTF(1,2);
-void logDebug(const char *fmt, ...) ISPRINTF(1,2);
 
+// Logging facilities.
+// THESE DO NOT USE PRINTF-STYLE FORMAT CODES. Please see the comments for
+//  format() for details.
+void logWarning(const char *fmt, ...);
+void logError(const char *fmt, ...);
+void logInfo(const char *fmt, ...);
+void logDebug(const char *fmt, ...);
 
+
 // Checksums.
 
 typedef uint32 MojoCrc32;
@@ -320,10 +340,12 @@
     char *(*xstrdup)(const char *str);
     char *(*xstrncpy)(char *dst, const char *src, size_t len);
     const char *(*translate)(const char *str);
-    void (*logWarning)(const char *fmt, ...) ISPRINTF(1,2);
-    void (*logError)(const char *fmt, ...) ISPRINTF(1,2);
-    void (*logInfo)(const char *fmt, ...) ISPRINTF(1,2);
-    void (*logDebug)(const char *fmt, ...) ISPRINTF(1,2);
+    void (*logWarning)(const char *fmt, ...);
+    void (*logError)(const char *fmt, ...);
+    void (*logInfo)(const char *fmt, ...);
+    void (*logDebug)(const char *fmt, ...);
+    char *(*format)(const char *fmt, ...);
+    const char *(*numstr)(int val);
     uint32 (*ticks)(void);
 } MojoSetupEntryPoints;
 extern MojoSetupEntryPoints GEntryPoints;
@@ -339,6 +361,9 @@
 #endif
 #endif  // DOXYGEN_SHOULD_IGNORE_THIS
 
+#define DEFINE_TO_STR2(x) #x
+#define DEFINE_TO_STR(x) DEFINE_TO_STR2(x)
+
 #ifndef DOXYGEN_SHOULD_IGNORE_THIS
 #define STUBBED(x) \
 do { \
@@ -346,17 +371,14 @@
     if (!seen_this) \
     { \
         seen_this = true; \
-        logDebug("STUBBED: %s at %s (%s:%d)\n", x, __FUNCTION__, \
-                __FILE__, __LINE__); \
+        logDebug("STUBBED: %0 at %1 (%2:%3)\n", x, __FUNCTION__, \
+                __FILE__, DEFINE_TO_STR(__LINE__)); \
     } \
 } while (false)
 #endif
 
 #define STATICARRAYLEN(x) ( (sizeof ((x))) / (sizeof ((x)[0])) )
 
-#define DEFINE_TO_STR2(x) #x
-#define DEFINE_TO_STR(x) DEFINE_TO_STR2(x)
-
 #ifdef __cplusplus
 }
 #endif




More information about the mojosetup-commits mailing list