]> andersk Git - splint.git/blobdiff - src/llmain.c
Fixed the readme file based on the information on the web page.
[splint.git] / src / llmain.c
index ddfddc44cfcb3b8b49277d0b99c08d1e2b15b5f7..cfeabc3ff56c8d1f67969c686403532a2d7a5bff 100644 (file)
@@ -1,6 +1,6 @@
 /*
 ** Splint - annotation-assisted static program checker
-** Copyright (C) 1994-2001 University of Virginia,
+** Copyright (C) 1994-2002 University of Virginia,
 **         Massachusetts Institute of Technology
 **
 ** This program is free software; you can redistribute it and/or modify it
@@ -17,8 +17,8 @@
 ** the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 ** MA 02111-1307, USA.
 **
-** For information on lclint: lclint-request@cs.virginia.edu
-** To report a bug: lclint-bug@cs.virginia.edu
+** For information on splint: splint@cs.virginia.edu
+** To report a bug: splint-bug@cs.virginia.edu
 ** For more information: http://www.splint.org
 */
 /*
@@ -48,7 +48,7 @@
 # include <process.h>
 # endif
 
-# include "lclintMacros.nf"
+# include "splintMacros.nf"
 # include "llbasic.h"
 # include "osd.h"
 
@@ -91,8 +91,14 @@ static void cleanupFiles (void);
 static void showHelp (void);
 static void interrupt (int p_i);
 
+static bool readOptionsFile (cstring p_fname,
+                            cstringSList *p_passThroughArgs,
+                            bool p_report) 
+   /*@modifies fileSystem, internalState, *p_passThroughArgs@*/ ;
+   
 static void loadrc (FILE *p_rcfile, cstringSList *p_passThroughArgs)
-     /*@ensures closed p_rcfile@*/ ;
+   /*@modifies *p_passThroughArgs, p_rcfile@*/
+   /*@ensures closed p_rcfile@*/ ;
 
 static void describeVars (void);
 static bool specialFlagsHelp (char *p_next);
@@ -521,7 +527,7 @@ void showHerald (void)
 
   else
     {
-      fprintf (g_msgstream, "%s\n\n", LCL_VERSION);
+      fprintf (g_msgstream, "%s\n\n", SPLINT_VERSION);
       hasShownHerald = TRUE;
       llflush ();
     }
@@ -610,7 +616,7 @@ static void addXHFile (fileIdList files, /*@temp@*/ cstring s)
 }
 
 /*
-** Disable MSVC++ warning about return value.  Methinks humbly lclint control
+** Disable MSVC++ warning about return value.  Methinks humbly splint control
 ** comments are a mite more legible.
 */
 
@@ -766,8 +772,7 @@ int main (int argc, char *argv[])
 
   {
     cstring home = osd_getHomeDir ();
-    char *fname  = NULL;
-    FILE *rcfile;
+    cstring fname  = cstring_undefined;
     bool defaultf = TRUE;
     bool nof = FALSE;
 
@@ -778,35 +783,36 @@ int main (int argc, char *argv[])
        
        if (*thisarg == '-' || *thisarg == '+')
          {
+           bool set = (*thisarg == '+');
+           flagcode opt;
+
            thisarg++;
 
-           if (mstring_equal (thisarg, "nof"))
+           /*
+           ** Don't report warnings this time
+           */
+
+           opt = flags_identifyFlagQuiet (cstring_fromChars (thisarg));
+
+           if (opt == FLG_NOF)
              {
                nof = TRUE;
              }
-           else if (mstring_equal (thisarg, "f"))
+           else if (opt == FLG_SHOWSCAN || opt == FLG_WARNRC)
+             {
+               /*
+               ** Need to set it immediately, so rc file scan is displayed
+               */
+
+               context_userSetFlag (opt, set);
+             }
+           else if (opt == FLG_OPTF)
              {
                if (++i < argc)
                  {
                    defaultf = FALSE;
-                   fname = argv[i];
-                   rcfile = fileTable_openFile (context_fileTable (), cstring_fromChars (fname), "r");
-
-                   if (rcfile != NULL)
-                     {
-                       fileloc oloc = g_currentloc;
-                       
-                       g_currentloc = fileloc_createRc (cstring_fromChars (fname));
-                       loadrc (rcfile, &passThroughArgs);
-                       fileloc_reallyFree (g_currentloc); 
-                       g_currentloc = oloc;
-                     }
-                   else 
-                     {
-                       showHerald ();
-                       lldiagmsg (message ("Options file not found: %s", 
-                                           cstring_fromChars (fname)));
-                     }
+                   fname = cstring_fromChars (argv[i]);
+                   (void) readOptionsFile (fname, &passThroughArgs, TRUE);
                  }
                else
                  llfatalerror
@@ -819,55 +825,75 @@ int main (int argc, char *argv[])
              }
          }
       }
-    
-    if (fname == NULL)
-      {
-       if (!cstring_isEmpty (home)) {
-         fname = cstring_toCharsSafe (message ("%s%h%s", home, CONNECTCHAR,
-                                               cstring_fromChars (RCFILE)));
-         mstring_markFree (fname);
-       }
-      }
-
+        
     setCodePoint ();
 
     if (!nof && defaultf)
       {
-       if (!mstring_isEmpty (fname)) {
-         rcfile = fileTable_openFile (context_fileTable (), cstring_fromChars (fname), "r");
-         
-         if (rcfile != NULL)
-           {
-             fileloc oloc = g_currentloc;
-             
-             g_currentloc = fileloc_createRc (cstring_fromChars (fname));
-             loadrc (rcfile, &passThroughArgs);
-             fileloc_reallyFree (g_currentloc);
-             g_currentloc = oloc;
-           }
-       }
-
-# if defined(MSDOS) || defined(OS2)
-       fname = cstring_toCharsSafe (message ("%s",
-                                             cstring_fromChars (RCFILE)));
-# else
-       fname = cstring_toCharsSafe (message ("./%s", 
-                                             cstring_fromChars (RCFILE)));
-# endif
-
-       rcfile = fileTable_openFile (context_fileTable (), cstring_fromChars (fname), "r");
+       /*
+       ** No explicit rc file, first try reading ~/.splintrc
+       */
 
-       if (rcfile != NULL)
+       if (cstring_isUndefined (fname))
          {
-           fileloc oloc = g_currentloc;
+           if (!cstring_isEmpty (home)) 
+             {
+               bool readhomerc, readaltrc;
+               cstring homename, altname;
+
+               homename = message ("%s%h%s", home, CONNECTCHAR,
+                                cstring_fromChars (RCFILE));
+               readhomerc = readOptionsFile (homename, &passThroughArgs, FALSE);
+               
+               /*
+               ** Try ~/.splintrc also for historical accuracy
+               */
+               
+               altname = message ("%s%h%s", home, CONNECTCHAR,
+                                cstring_fromChars (ALTRCFILE));
+               readaltrc = readOptionsFile (altname, &passThroughArgs, FALSE);
+
+               if (readhomerc && readaltrc)
+                 {
 
-           g_currentloc = fileloc_createRc (cstring_fromChars (fname));
-           loadrc (rcfile, &passThroughArgs);
-           fileloc_reallyFree (g_currentloc);
-           g_currentloc = oloc;
+                   voptgenerror 
+                     (FLG_WARNRC,
+                      message ("Found both %s and %s files. Using both files, "
+                               "but recommend using only %s to avoid confusion.",
+                               homename, altname, homename),
+                      g_currentloc);
+                 }
+
+               cstring_free (homename);
+               cstring_free (altname);
+             }
          }
+       
+       /*
+       ** Next, read .splintrc in the current working directory
+       */
+       
+       {
+         cstring rcname = message ("%s%s",osd_getCurrentDirectory (), cstring_fromChars (RCFILE));
+         cstring altname = message ("%s%s",osd_getCurrentDirectory (), cstring_fromChars (ALTRCFILE));
+         bool readrc, readaltrc;
+         
+         readrc = readOptionsFile (rcname, &passThroughArgs, FALSE);
+         readaltrc = readOptionsFile (altname, &passThroughArgs, FALSE);
+         
+         if (readrc && readaltrc)
+           {
+             voptgenerror (FLG_WARNRC,
+                           message ("Found both %s and %s files. Using both files, "
+                                    "but recommend using only %s to avoid confusion.",
+                                    rcname, altname, rcname),
+                           g_currentloc);
+             
+           }
 
-       sfree (fname); 
+         cstring_free (rcname);
+         cstring_free (altname);
+       }
       }
   }
   
@@ -979,11 +1005,12 @@ int main (int argc, char *argv[])
              flagname = cstring_fromChars (thisarg);
 
              DPRINTF (("Flag: %s", flagname));
-             opt = identifyFlag (flagname);
+             opt = flags_identifyFlag (flagname);
              DPRINTF (("Flag: %s", flagcode_unparse (opt)));
 
-             if (flagcode_isSkip (opt))
+             if (flagcode_isSkip (opt) || opt == FLG_SHOWSCAN || opt == FLG_WARNRC)
                {
+                 /* showscan already processed */
                  DPRINTF (("Skipping!"));
                }
              else if (flagcode_isInvalid (opt))
@@ -1018,7 +1045,7 @@ int main (int argc, char *argv[])
                          passThroughArgs = cstringSList_add 
                            (passThroughArgs, cstring_fromChars (thisarg));
                        }
-                     else if (flagcode_hasValue (opt))
+                     else if (flagcode_hasNumber (opt))
                        {
                          if (++i < argc)
                            {
@@ -1032,6 +1059,20 @@ int main (int argc, char *argv[])
                                  flagcode_unparse (opt)));
                            }
                        } 
+                     else if (flagcode_hasChar (opt))
+                       {
+                         if (++i < argc)
+                           {
+                             setValueFlag (opt, cstring_fromChars (argv[i]));
+                           }
+                         else
+                           {
+                             llfatalerror 
+                               (message
+                                ("Flag %s must be followed by a character",
+                                 flagcode_unparse (opt)));
+                           }
+                       } 
                      else if (opt == FLG_INCLUDEPATH || opt == FLG_SPECPATH)
                        {
                          cstring dir = cstring_suffix (cstring_fromChars (thisarg), 1); /* skip over I */
@@ -1120,7 +1161,8 @@ int main (int argc, char *argv[])
     }
 
   setCodePoint ();  
-
+  showHerald (); 
+  
   /*
   ** create lists of C and LCL files
   */
@@ -1177,8 +1219,6 @@ int main (int argc, char *argv[])
        }
     } end_cstringSList_elements;
   
-    showHerald (); /*@i723 move earlier? */
-  
   if (showhelp)
     {
       if (allhelp)
@@ -1511,7 +1551,7 @@ int main (int argc, char *argv[])
              }
            else
              {
-               specErrors = message ("%d spec warning%& found\n       ",
+               specErrors = message ("%d spec warning%&\n       ",
                                      nspecErrors);
                expsuccess = FALSE;
              }
@@ -1558,7 +1598,7 @@ int main (int argc, char *argv[])
                  if (!isQuiet)
                    {
                      llmsg (message ("Finished checking --- "
-                                     "%s%d code warning%& found", 
+                                     "%s%d code warning%&", 
                                      specErrors, context_numErrors ()));
                    }
 
@@ -1592,11 +1632,13 @@ int main (int argc, char *argv[])
                        } 
                    }
                  else
+                   {
                      if (!isQuiet) 
                        {
                          llmsg (message ("Finished checking --- %sno code warnings",
                                          specErrors));
                        }
+                   }
                }
              else
                {
@@ -1739,6 +1781,16 @@ specialFlagsHelp (char *next)
          printAllFlags (FALSE, TRUE);
          return TRUE;
        }
+      else if (mstring_equal (next, "manual"))
+       {
+         printFlagManual (FALSE);
+         return TRUE;
+       }
+      else if (mstring_equal (next, "webmanual"))
+       {
+         printFlagManual (TRUE);
+         return TRUE;
+       }
       else
        {
          return FALSE;
@@ -1756,7 +1808,7 @@ printParseErrors (void)
   llmsglit ("Parse Errors");
   llmsglit ("------------");
   llmsglit ("");
-  llmsglit ("LCLint will sometimes encounter a parse error for code that "
+  llmsglit ("Splint will sometimes encounter a parse error for code that "
            "can be parsed with a local compiler. There are a few likely "
            "causes for this and a number of techniques that can be used "
            "to work around the problem.");
@@ -1771,7 +1823,7 @@ printParseErrors (void)
            "other compiler extensions by using a pre-processor define. "
            "Alternately, you can surround the unparseable code with");
   llmsglit ("");
-  llmsglit ("   # ifndef __LCLINT__");
+  llmsglit ("   # ifndef S_SPLINT_S");
   llmsglit ("   ...");
   llmsglit ("   # endif");
   llmsglit ("");
@@ -1792,12 +1844,12 @@ printParseErrors (void)
            "header files.");
   llmsglit ("");
   llmsglit ("Otherwise, you may need to either manually define the problematic "
-           "type (e.g., add -Dmlink_t=int to your .lclintrc file) or force "
-           "lclint to process the header file that defines it. This is done "
-           "by setting -skipansiheaders or -skipposixheaders before "
+           "type (e.g., add -Dmlink_t=int to your .splintrc file) or force "
+           "splint to process the header file that defines it. This is done "
+           "by setting -skipisoheaders or -skipposixheaders before "
            "the file that defines the type is #include'd.");
-  llmsglit ("(See lclint -help "
-           "skipansiheaders and lclint -help skipposixheaders for a list of "
+  llmsglit ("(See splint -help "
+           "skipisoheaders and splint -help skipposixheaders for a list of "
            "standard headers.)  For example, if <sys/local.h> uses a type "
            "defined by posix header <sys/types.h> but not defined by the "
            "posix library, we might do: ");
@@ -1900,19 +1952,19 @@ printAnnotations (void)
   llmsglit ("");
   llmsglit ("Null State:");
   llmsglit ("   /*@null@*/ - possibly null pointer");
-  llmsglit ("   /*@notnull@*/ - non-null pointer");
+  llmsglit ("   /*@notnull@*/ - definitely non-null pointer");
   llmsglit ("   /*@relnull@*/ - relax null checking");
   llmsglit ("");
   llmsglit ("Null Predicates:");
-  llmsglit ("   /*@truenull@*/ - if result is TRUE, first parameter is NULL");
-  llmsglit ("   /*@falsenull@*/ - if result is TRUE, first parameter is not NULL");
+  llmsglit ("   /*@nullwhentrue@*/ - if result is TRUE, first parameter is NULL");
+  llmsglit ("   /*@falsewhennull@*/ - if result is TRUE, first parameter is not NULL");
   llmsglit ("");
   llmsglit ("Execution:");
-  llmsglit ("   /*@exits@*/ - function never returns");
-  llmsglit ("   /*@mayexit@*/ - function may or may not return");
-  llmsglit ("   /*@trueexit@*/ - function does not return if first parameter is TRUE");
-  llmsglit ("   /*@falseexit@*/ - function does not return if first parameter if FALSE");
-  llmsglit ("   /*@neverexit@*/ - function always returns");
+  llmsglit ("   /*@noreturn@*/ - function never returns");
+  llmsglit ("   /*@maynotreturn@*/ - function may or may not return");
+  llmsglit ("   /*@noreturnwhentrue@*/ - function does not return if first parameter is TRUE");
+  llmsglit ("   /*@noreturnwhenfalse@*/ - function does not return if first parameter if FALSE");
+  llmsglit ("   /*@alwaysreturns@*/ - function always returns");
   llmsglit ("");
   llmsglit ("Side-Effects:");
   llmsglit ("   /*@sef@*/ - corresponding actual parameter has no side effects");
@@ -1997,15 +2049,15 @@ printFlags (void)
   llmsglit ("Flag Categories");
   llmsglit ("---------------");
   listAllCategories ();
-  llmsglit ("\nTo see the flags in a flag category, do\n   lclint -help flags <category>");
-  llmsglit ("To see a list of all flags in alphabetical order, do\n   lclint -help flags alpha");
-  llmsglit ("To see a full description of all flags, do\n   lclint -help flags full");
+  llmsglit ("\nTo see the flags in a flag category, do\n   splint -help flags <category>");
+  llmsglit ("To see a list of all flags in alphabetical order, do\n   splint -help flags alpha");
+  llmsglit ("To see a full description of all flags, do\n   splint -help flags full");
 }
 
 void
 printMaintainer (void)
 {
-  llmsg (message ("Maintainer: %s", cstring_makeLiteralTemp (LCLINT_MAINTAINER)));
+  llmsg (message ("Maintainer: %s", cstring_makeLiteralTemp (SPLINT_MAINTAINER)));
   llmsglit (LCL_COMPILE);
 }
 
@@ -2025,7 +2077,7 @@ printMail (void)
   llmsglit ("");
   llmsglit ("   lclint-interest@virginia.edu");
   llmsglit ("");
-  llmsglit ("      Informal discussions on the use and development of lclint.");
+  llmsglit ("      Informal discussions on the use and development of Splint.");
   llmsglit ("      To subscribe, send a message to majordomo@virginia.edu with body: ");
   llmsglit ("           subscribe lclint-interest");
 }
@@ -2137,7 +2189,7 @@ interrupt (int i)
                 cstring_toCharsSafe (loc));
        cstring_free (loc);
        printCodePoint ();
-       fprintf (stderr, "*** Please report bug to %s\n", LCLINT_MAINTAINER);
+       fprintf (stderr, "*** Please report bug to %s\n", SPLINT_MAINTAINER);
        exit (LLGIVEUP);
       }
     default:
@@ -2147,7 +2199,7 @@ interrupt (int i)
               cstring_toCharsSafe (fileloc_unparse (g_currentloc)));
       /*@=mustfree@*/
       printCodePoint ();
-      fprintf (stderr, "*** Please report bug to %s ***\n", LCLINT_MAINTAINER);
+      fprintf (stderr, "*** Please report bug to %s ***\n", SPLINT_MAINTAINER);
       exit (LLGIVEUP);
     }
 }
@@ -2195,7 +2247,7 @@ cleanupFiles (void)
 ** cleans up temp files (if necessary) and exits
 */
 
-/*@exits@*/ void
+/*@noreturn@*/ void
 llexit (int status)
 {
   DPRINTF (("llexit: %d", status));
@@ -2226,17 +2278,67 @@ llexit (int status)
   exit ((status == LLSUCCESS) ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 
+bool readOptionsFile (cstring fname, cstringSList *passThroughArgs, bool report)
+{
+  bool res = FALSE;
+
+  if (fileTable_exists (context_fileTable (), fname))
+    {
+      if (report)
+       {
+         voptgenerror
+           (FLG_WARNRC, 
+            message ("Multiple attempts to read options file: %s", fname),
+            g_currentloc);
+       }
+    }
+  else
+    {
+      FILE *innerf = fileTable_openFile (context_fileTable (), fname, "r");
+      
+      if (innerf != NULL)
+       {
+         fileloc fc = g_currentloc;
+         g_currentloc = fileloc_createRc (fname);
+
+         if (context_getFlag (FLG_SHOWSCAN))
+           {
+             lldiagmsg (message ("< reading options from %q >", 
+                                 fileloc_outputFilename (g_currentloc)));
+           }
+         
+         loadrc (innerf, passThroughArgs);
+         fileloc_reallyFree (g_currentloc);
+         g_currentloc = fc;
+         res = TRUE;
+       }
+      else 
+       {
+         if (report)
+           {
+             voptgenerror
+               (FLG_WARNRC, 
+                message ("Cannot open options file: %s", fname),
+                g_currentloc);
+           }
+       }
+    }
+
+  return res;
+}
+
 /*
 ** This shouldn't be necessary, but Apple Darwin can't handle '"''s.
 */
 
 void
 loadrc (/*:open:*/ FILE *rcfile, cstringSList *passThroughArgs)
+   /*@modifies rcfile@*/
    /*@ensures closed rcfile@*/
 {
   char *s = mstring_create (MAX_LINE_LENGTH);
   char *os = s;
-
+  
   DPRINTF (("Pass through: %s", cstringSList_unparse (*passThroughArgs)));
 
   s = os;
@@ -2341,7 +2443,7 @@ loadrc (/*:open:*/ FILE *rcfile, cstringSList *passThroughArgs)
 
          DPRINTF (("Flag: %s", thisflag));
 
-         opt = identifyFlag (cstring_fromChars (thisflag));
+         opt = flags_identifyFlag (cstring_fromChars (thisflag));
          
          if (flagcode_isSkip (opt))
            {
@@ -2404,7 +2506,8 @@ loadrc (/*:open:*/ FILE *rcfile, cstringSList *passThroughArgs)
                        }
                    }
                  else if (flagcode_hasString (opt)
-                          || flagcode_hasValue (opt)
+                          || flagcode_hasNumber (opt)
+                          || flagcode_hasChar (opt)
                           || opt == FLG_INIT || opt == FLG_OPTF)
                    {
                      cstring extra = cstring_undefined;
@@ -2453,34 +2556,14 @@ loadrc (/*:open:*/ FILE *rcfile, cstringSList *passThroughArgs)
                          
                          DPRINTF (("Here we are: %s", extra));
 
-                         if (flagcode_hasValue (opt))
+                         if (flagcode_hasNumber (opt) || flagcode_hasChar (opt))
                            {
                              DPRINTF (("Set value flag: %s", extra));
                              setValueFlag (opt, extra);
-                             cstring_free (extra);
                            }
                          else if (opt == FLG_OPTF)
                            {
-                             FILE *innerf = fileTable_openFile (context_fileTable (), extra, "r");
-                             cstring_markOwned (extra);
-                             
-                             if (innerf != NULL)
-                               {
-                                 fileloc fc = g_currentloc;
-                                 g_currentloc = fileloc_createRc (extra);
-                                 loadrc (innerf, passThroughArgs);
-                                 fileloc_reallyFree (g_currentloc);
-                                 g_currentloc = fc;
-                               }
-                             else 
-                               {
-                                 showHerald ();
-                                 voptgenerror
-                                   (FLG_BADFLAG, 
-                                    message ("Options file not found: %s", 
-                                             extra),
-                                    g_currentloc);
-                               }
+                             (void) readOptionsFile (extra, passThroughArgs, TRUE);
                            }
                          else if (opt == FLG_INIT)
                            {
@@ -2488,25 +2571,30 @@ loadrc (/*:open:*/ FILE *rcfile, cstringSList *passThroughArgs)
                              llassert (inputStream_isUndefined (initFile));
                              
                              initFile = inputStream_create 
-                               (extra
+                               (cstring_copy (extra)
                                 cstring_makeLiteralTemp (LCLINIT_SUFFIX),
                                 FALSE);
-# else
-                             cstring_free (extra);
 # endif
                            }
                          else if (flagcode_hasString (opt))
                            {
+                             DPRINTF (("Here: %s", extra));
+
+                             /*
+                             ** If it has "'s, we need to remove them.
+                             */
+
                              if (cstring_firstChar (extra) == '\"')
                                {
                                  if (cstring_lastChar (extra) == '\"')
                                    {
-                                     char *extras = cstring_toCharsSafe (extra);
-                                     
-                                     llassert (extras[strlen(extras) - 1] == '\"');
-                                     extras[strlen(extras) - 1] = '\0';
-                                     extra = cstring_fromChars (extras + 1); 
-                                     DPRINTF (("Remove quotes: %s", extra));
+                                     cstring unquoted = cstring_copyLength 
+                                       (cstring_toCharsSafe (cstring_suffix (extra, 1)),
+                                        cstring_length (extra) - 2);
+
+                                     DPRINTF (("string flag: %s -> %s", extra, unquoted));
+                                     setStringFlag (opt, unquoted);
+                                     cstring_free (extra);
                                    }
                                  else
                                    {
@@ -2515,17 +2603,24 @@ loadrc (/*:open:*/ FILE *rcfile, cstringSList *passThroughArgs)
                                         message ("Unmatched \" in option string: %s", 
                                                  extra),
                                         g_currentloc);
+                                     setStringFlag (opt, extra);
                                    }
                                }
-                             
-                             setStringFlag (opt, extra);
+                             else
+                               {
+                                 DPRINTF (("No quotes: %s", extra));
+                                 setStringFlag (opt, extra);
+                               }
+
+                             extra = cstring_undefined;
                            }
                          else
                            {
-                             cstring_free (extra);
                              BADEXIT;
                            }
                        }
+
+                     cstring_free (extra); 
                    }
                  else
                    {
@@ -2568,7 +2663,7 @@ static fileIdList preprocessFiles (fileIdList fl, bool xhfiles)
 
       if (!(osd_fileIsReadable (ppfname)))
        {
-         lldiagmsg (message ("Cannot open file: %s", osd_outputPath (ppfname)));
+         lldiagmsg (message ("Cannot open file: %q", osd_outputPath (ppfname)));
          ppfname = cstring_undefined;
        }
 
This page took 0.196412 seconds and 4 git commands to generate.