home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / boot / i386 / root / usr / share / YaST2 / modules / CommandLine.ycp < prev    next >
Text File  |  2006-11-29  |  39KB  |  1,373 lines

  1. /**
  2.  * File:    modules/CommandLine.ycp
  3.  * Package:    yast2
  4.  * Summary:    Command line interface for YaST2 modules
  5.  * Author:    Stanislav Visnovsky <visnov@suse.cz>
  6.  *
  7.  * $Id: CommandLine.ycp 26744 2006-01-03 14:23:45Z visnov $
  8.  */
  9.  
  10. {
  11.     module "CommandLine";
  12.  
  13.     import "Directory";
  14.     import "Mode";
  15.     import "Stage";
  16.     import "Report";
  17.     import "String";
  18.     import "TypeRepository";
  19.     import "XML";
  20.  
  21.     textdomain "base";
  22.  
  23.     typedef map<string, map <string, any > > commands_t;
  24.  
  25.     string cmdlineprompt = "YaST2 > ";
  26.  
  27.     /**
  28.      * Map of commands for every module. ATM the list of commands this module handles internally.
  29.      */
  30.     commands_t systemcommands = (commands_t)$[
  31.     "actions"    : $[
  32.                 "help"    : $[
  33.                 // translators: help for 'help' option on command line
  34.                 "help":_("Print the help for this module")
  35.                 ],
  36.                 "longhelp": $[
  37.                 // translators: help for 'longhelp' option on command line
  38.                 "help":_("Print a long version of help for this module")
  39.                 ],
  40.                 "xmlhelp": $[
  41.                 // translators: help for 'xmlhelp' option on command line
  42.                 "help":_("Print a long version of help for this module in XML format")
  43.                 ],
  44.                 "interactive"    : $[
  45.                 // translators: help for 'interactive' option on command line
  46.                 "help": _("Start interactive shell to control the module")
  47.                 ],
  48.                 "exit"    : $[
  49.                 // translators: help for 'exit' command line interactive mode
  50.                 "help": _("Exit interactive mode and save the changes")
  51.                 ],
  52.                 "abort"    : $[
  53.                 // translators: help for 'abort' command line interactive mode
  54.                 "help": _("Abort interactive mode without saving the changes")
  55.                 ]
  56.             ],
  57.     "options"    : $[
  58.                 "help"    : $[
  59.                 // translators:  command line "help" option 
  60.                 "help"    : _("Print the help for this command"),
  61.                 ],
  62.                 "verbose"    : $[
  63.                 // translators: command line "verbose" option
  64.                 "help"    : _("Show progress information"),
  65.                 ],
  66.                 "xmlfile"    : $[
  67.                 // translators: command line "xmlfile" option
  68.                 "help"    : _("Where to store the XML output"),
  69.                 "type"    : "string"
  70.                 ],
  71.             ],
  72.     "mappings"    : $[
  73.                 "help"    : ["help", "verbose"],
  74.                 "xmlhelp"    : ["help", "verbose", "xmlfile"],
  75.                 "interactive":["help", "verbose"],
  76.                 "exit"    : ["help"],
  77.                 "abort"    : ["help"],
  78.             ]
  79.     ];
  80.  
  81.     /**
  82.      * Map of commands defined by the YaST2 module.
  83.      */
  84.     map modulecommands = $[];
  85.  
  86.     /**
  87.      * Merged map of commands - both defined by the YaST2 module and system commands. Used for lookup
  88.      */
  89.     map allcommands = systemcommands;
  90.  
  91.     /**
  92.      * User asked for interactive session
  93.      */
  94.     boolean interactive = false;
  95.  
  96.     /**
  97.      * All commands have been processed
  98.      */
  99.     boolean done = false;
  100.  
  101.     /**
  102.      * User asked for quitting of interactive session, or there was an error
  103.      */
  104.     boolean aborted = false;
  105.  
  106.     /** a cache for already parsed but not processed command */
  107.     map<string, any> commandcache = $[];
  108.  
  109.     /**
  110.      * Verbose mode flag
  111.      */
  112.     boolean verbose = false;
  113.  
  114.     /**
  115.      * Remember the command line specification for later use
  116.      */
  117.     map cmdlinespec = $[];
  118.  
  119.  
  120.     // string: command line interface is not supported
  121.     string nosupport = _("This YaST2 module does not support the command line interface.");
  122.  
  123.     /**
  124.      *  @short Print a String
  125.      *  @descr Print a string to /dev/tty in interactive mode, to stderr in non-interactive
  126.      *  Suppress printing if there are no commands to be handled (starting GUI)
  127.      *
  128.      *  @param s    the string to be printed
  129.      */
  130.     define void PrintInternal(string s, boolean newline) ``{
  131.     if( ! Mode::commandline () ) return;
  132.  
  133.     // avoid using of uninitialized value in .dev.tty perl agent
  134.     if( s == nil )
  135.     {
  136.         y2warning("CommandLine::Print: invalid argument (nil)");
  137.         return;
  138.     }
  139.  
  140.     if( interactive ) {
  141.         if (newline)
  142.         SCR::Write(.dev.tty, s);
  143.         else
  144.         SCR::Write(.dev.tty.nocr, s);
  145.     }
  146.     else {
  147.         if (newline)
  148.         SCR::Write(.dev.tty.stderr, s);
  149.         else
  150.         SCR::Write(.dev.tty.stderr_nocr, s);
  151.     }
  152.     }
  153.  
  154.     /**
  155.      *  @short Print a String
  156.      *  @descr Print a string to /dev/tty in interactive mode, to stderr in non-interactive
  157.      *  Suppress printing if there are no commands to be handled (starting GUI)
  158.      *
  159.      *  @param s    the string to be printed
  160.      */
  161.     global define void Print(string s) ``{
  162.     return PrintInternal(s, true);
  163.     }
  164.  
  165.     /**
  166.      *  @short Print a String, don't add a trailing newline character
  167.      *  @descr Print a string to /dev/tty in interactive mode, to stderr in non-interactive
  168.      *  Suppress printing if there are no commands to be handled (starting GUI)
  169.      *
  170.      *  @param s    the string to be printed
  171.      */
  172.     global define void PrintNoCR(string s) ``{
  173.     return PrintInternal(s, false);
  174.     }
  175.  
  176.     /**
  177.      * Same as Print(), but the string is printed only when verbose command
  178.      * line mode was activated
  179.      * @param s string to print
  180.      */
  181.     global define void PrintVerbose(string s) {
  182.     if (verbose)
  183.     {
  184.         Print(s);
  185.     }
  186.     }
  187.  
  188.     /**
  189.      * Same as PrintNoCR(), but the string is printed only when verbose command
  190.      * line mode was activated
  191.      * @param s string to print
  192.      */
  193.     global define void PrintVerboseNoCR(string s) {
  194.     if (verbose)
  195.     {
  196.         PrintNoCR(s);
  197.     }
  198.     }
  199.  
  200.  
  201.     /**
  202.      * @short Print an Error Message
  203.      * @descr Print an error message and add the description how to get the help.
  204.      * @param message    error message to be printed. Use nil for no message
  205.      */
  206.     global define void Error( string message ) ``{
  207.         if( message != nil ) {
  208.         Print( message );
  209.     }
  210.  
  211.         if( interactive ) {
  212.         // translators: default error message for command line
  213.         Print(_("Use 'help' for a complete list of available commands."));
  214.     } else {
  215.         // translators: default error message for command line
  216.         Print(sformat(_("Use 'yast2 %1 help' for a complete list of available commands."), modulecommands["id"]:""));
  217.         }
  218.     }
  219.  
  220.     /**
  221.      *  @short Parse a list of arguments. 
  222.      *  @descr It checks the validity of the arguments, the type correctness
  223.      *  and returns the command and its options in a map.
  224.      *  @param arguments    the list of arguments to be parsed
  225.      *  @return map<string, any>    containing the command and it's option. In case of
  226.      *                error it is an empty map.
  227.      */
  228.     global define map<string, any> Parse (list<any> arguments) ``{
  229.     list<any> args = arguments;
  230.     if(size(args) < 1) return $[];
  231.  
  232.     /* Parse command */
  233.     string command = args[0]:"";
  234.     y2debug("command=%1", command);
  235.     args = remove(args, 0);
  236.     y2debug("args=%1", args);
  237.  
  238.     if(command == "") {
  239.         y2error( "CommandLine::Parse called with first parameter being empty. Arguments passed: %1", arguments);
  240.         return $[];
  241.     }
  242.  
  243.     /* Check command */
  244.     if(!haskey(allcommands["actions"]:$[], command) ) {
  245.         // translators: error message in command line interface
  246.         Error(sformat(_("Unknown Command: %1"), command));
  247.  
  248.         return $[ "command" : command ];
  249.     }
  250.  
  251.     // build the list of options for the command
  252.     list opts = allcommands["mappings", command]:[];
  253.     map allopts = allcommands["options"]:$[];
  254.     map cmdoptions = $[];
  255.     maplist( any k, opts, {
  256.         if (is(k, string)) cmdoptions = add( cmdoptions, k, allopts[k]:$[] );
  257.     } );
  258.  
  259.     boolean ret = true;
  260.  
  261.     /* Parse options */
  262.     map<string, any> givenoptions = $[];
  263.     maplist(any aos, args, ``{
  264.         y2debug("os=%1", aos);
  265.         if (!is(aos, string)) continue;
  266.         string os = (string)aos;
  267.         list<string> o = regexptokenize(os, "([^=]+)=(.+)");
  268.         y2debug("o=%1", o);
  269.         if( size(o) == 2 ) givenoptions = add(givenoptions, o[0]:"", o[1]:"");
  270.         else if( size(o) == 0 ) {
  271.         // check, if the last character is "="
  272.         // FIXME: consider whitespace
  273.         if( substring( os, size(os)-1 ) == "=" ) {
  274.             // translators: error message - user did not provide a value for option %1 on the command line
  275.             Print(sformat(_("Option '%1' is missing value."), substring( os, 0, size(os)-1 )) );
  276.             if( ! interactive ) aborted = true;
  277.             ret = false;
  278.             return $[];
  279.         } else {
  280.             givenoptions = add(givenoptions, os, "");
  281.         }
  282.         }
  283.     });
  284.  
  285.     if( ret != true ) return $[];
  286.  
  287.     y2debug("options=%1", givenoptions);
  288.  
  289.     /* Check options */
  290.     
  291.     // find out, if the action has a "non-strict" option set
  292.     boolean non_strict = contains ( allcommands["actions", command, "options"]: [], "non_strict");
  293.     if (non_strict)
  294.     {
  295.         y2debug ( "Using non-strict check for %1", command );
  296.     }
  297.  
  298.  
  299.     // check (and convert data types)
  300.     maplist(string o, any val, givenoptions, ``{
  301.         string v = (string)val;
  302.         if(ret != true) return;
  303.         if( cmdoptions[o]:nil == nil ) {
  304.         if( ! non_strict )
  305.         {
  306.             // translators: error message, %1 is a command, %2 is the wrong option given by the user
  307.             Print(sformat(_("Unknown option for command '%1': %2"), command, o));
  308.             if( ! interactive ) 
  309.             {
  310.             aborted = true;
  311.             }
  312.             ret = false;
  313.         }
  314.         } else {
  315.  
  316.         // this option is valid, let's check the type
  317.  
  318.         string opttype = cmdoptions[o, "type"]:"";
  319.  
  320.         if( opttype != "" ) {
  321.             // need to check the type
  322.             if( opttype == "regex" )  {
  323.             string opttypespec = cmdoptions[o, "typespec"]:"";
  324.             ret = TypeRepository::regex_validator( opttypespec, v );
  325.             if( ret != true ) {
  326.                 // translators: error message, %2 is the value given
  327.                 Print( sformat(_("Invalid value for option '%1': %2"), o, v ) );
  328.                 if( ! interactive ) aborted = true;
  329.             }
  330.             } else if( opttype == "enum" ) {
  331.             ret = TypeRepository::enum_validator ( cmdoptions[o, "typespec"]: [], v );
  332.             if( ret != true ) {
  333.                 // translators: error message, %2 is the value given
  334.                 Print( sformat(_("Invalid value for option '%1': %2"), o, v ) );
  335.                 if( ! interactive ) aborted = true;
  336.             }
  337.             } else if( opttype == "integer" ) {
  338.             integer i = tointeger(v);
  339.             ret = (i != nil);
  340.             if( ret != true ) {
  341.                 // translators: error message, %2 is the value given
  342.                 Print( sformat(_("Invalid value for option '%1': %2"), o, v ) );
  343.                 if( ! interactive ) aborted = true;
  344.             }
  345.             else
  346.             {
  347.                 // update value of the option to integer
  348.                 givenoptions[o] = i;
  349.             }
  350.             } else {
  351.             if( v == "" ) ret = false;
  352.             else ret = TypeRepository::is_a( v, opttype );
  353.  
  354.             if( ret != true ) {
  355.                 // translators: error message, %2 is expected type, %3 is the value given
  356.                 Print( sformat(_("Invalid value for option '%1' -- expected '%2', received %3"), o, opttype, v ) );
  357.                 if( ! interactive ) aborted = true;
  358.             }
  359.             }
  360.         }
  361.         else
  362.         {
  363.             // type is missing
  364.             if( v != "" )
  365.             {
  366.             y2error("Type specification for option '%1' is missing, cannot assign a value to the option", o);
  367.             // translators: error message if option has a value, but cannot have one
  368.             Print( sformat( _("Option '%1' cannot have a value. Given value: %2"), o, v ) );
  369.             if( ! interactive ) 
  370.             {
  371.                 aborted = true;
  372.             }
  373.             ret = false;
  374.             }
  375.         }
  376.         }
  377.     });
  378.  
  379.     // wrong, let's print the help message
  380.     if( ret != true ) {
  381.         if( interactive ) {
  382.         // translators: error message, how to get command line help for interactive mode
  383.         // %1 is the module name, %2 is the action name
  384.         Print(sformat(_("Use '%1 %2 help' for a complete list of available options."), modulecommands["id"]:"", command));
  385.         } else {
  386.         // translators: error message, how to get command line help for non-interactive mode
  387.         // %1 is the module name, %2 is the action name
  388.         Print(sformat(_("Use 'yast2 %1 %2 help' for a complete list of available options."), modulecommands["id"]:"", command));
  389.         }
  390.         return $[];
  391.     }
  392.  
  393.     return $[
  394.         "command"    : command,
  395.         "options"    : givenoptions
  396.     ];
  397.     }
  398.  
  399.     /**
  400.      * Print a nice heading for this module
  401.      */
  402.     define void PrintHead() ``{
  403.     // translators: command line interface header, %1 is identification of the module
  404.     string head = sformat(_("YaST Configuration Module %1\n"), modulecommands["id"]:"YaST");
  405.     integer headlen = size(head);
  406.     integer i=0; while (i<headlen) { head = head + "-"; i=i+1; }
  407.     head = "\n" + head + "\n";
  408.  
  409.     Print( head );
  410.     }
  411.  
  412.     /**
  413.      * Print a help text for a given action.
  414.      *
  415.      * @param action the action for which the help should be printed
  416.      */
  417.     define void PrintActionHelp (string action) ``{
  418.     // lookup action in actions
  419.     map command = allcommands["actions", action]:$[];
  420.     // translators: the command does not provide any help
  421.     any commandhelp = command["help"]:nil;
  422.     if (commandhelp == nil) {
  423.         commandhelp = _("No help available");
  424.     }
  425.  
  426.     /* Process <command> "help" */
  427.     // translators: %1 is the command name
  428.     Print(sformat(_("Command '%1'"), action));
  429.  
  430.     // print help
  431.     if (is(commandhelp, string))
  432.     {
  433.         Print(sformat("    %1", commandhelp));
  434.     }
  435.     else if (is(commandhelp, list<string>))
  436.     {
  437.         foreach(string e, (list<string>)commandhelp, ``{
  438.             Print(sformat("    %1", e));
  439.         }
  440.         );
  441.     }
  442.  
  443.     list opts = allcommands["mappings", action]:[];
  444.  
  445.     // no options, skip the rest
  446.     if( size(opts) == 0 ) {
  447.         Print("");
  448.         return;
  449.     }
  450.  
  451.     // translators: command line options
  452.     Print(_("\n    Options:"));
  453.  
  454.     map allopts = allcommands["options"]:$[];
  455.  
  456.     integer longestopt = 0;
  457.     integer longestarg = 0;
  458.  
  459.     foreach( any opt, opts, ``{
  460.         map op = allopts[opt]:$[];
  461.         string t = op["type"]:"";
  462.  
  463.         if ( t != "regex" && t != "enum" && t != "" ) t = "["+t+"]";
  464.         else if( t == "enum" ) {
  465.         t = "\[ ";
  466.         foreach( string s, op["typespec"]:[], ``{
  467.             t = t+ s+" ";
  468.         });
  469.         t = t + "\]";
  470.         }
  471.  
  472.         if( size(t) > longestarg ) longestarg = size(t);
  473.  
  474.         if( is (opt, string)
  475.         && size((string)opt) > longestopt )
  476.         {
  477.         longestopt = size((string)opt);
  478.         }
  479.     } );
  480.  
  481.  
  482.     foreach( any opt, opts, ``{
  483.         map op = allopts[opt]:$[];
  484.         string t = op["type"]:"";
  485.  
  486.         if ( t != "regex" && t != "enum" && t != "" ) t = "["+t+"]";
  487.         else if( t == "enum" ) {
  488.         t = "\[ ";
  489.         foreach( string s, op["typespec"]:[], ``{
  490.             t = t+ s+" ";
  491.         });
  492.         t = t + "\]";
  493.         }
  494.         else t = "    ";
  495.  
  496.         if (is (opt, string))
  497.         {
  498.         string helptext = "";
  499.         any opthelp = op["help"]:nil;
  500.  
  501.         if (is(opthelp, string))
  502.         {
  503.             helptext = (string)opthelp;
  504.         }
  505.         else if (is(opthelp, map<string,string>))
  506.         {
  507.             helptext = ((map<string,string>)opthelp)[action]:"";
  508.         }
  509.         else
  510.         {
  511.             y2error("Invalid data type of help text, only 'string' or 'map<string,string>' types are allowed.");
  512.         }
  513.  
  514.         Print(sformat("        %1  %2  %3", String::Pad((string)opt,longestopt), String::Pad(t, longestarg), helptext));
  515.         }
  516.     } );
  517.  
  518.     if( haskey( command, "example" ) ) {
  519.         // translators: example title for command line
  520.         Print( _("\n    Example:"));
  521.  
  522.         any example = command["example"]:nil;
  523.  
  524.         if (is(example, string))
  525.         {
  526.         Print(sformat("        %1", example));
  527.         }
  528.         else if (is(example, list<string>))
  529.         {
  530.         foreach(string e, (list<string>)example, ``{
  531.             Print(sformat("        %1", e));
  532.             }
  533.         );
  534.         }
  535.         else
  536.         {
  537.         y2error("Unsupported data type - value: %1", example);
  538.         }
  539.  
  540.     }
  541.     Print("");
  542.     }
  543.  
  544.     /**
  545.      * Print a general help - list of available command.
  546.      */
  547.     define void PrintGeneralHelp() ``{
  548.     // display custom defined help instead of generic one
  549.     if (haskey(modulecommands, "customhelp"))
  550.     {
  551.         Print(modulecommands["customhelp"]:"");
  552.         return;
  553.     }
  554.  
  555.     // translators: default module description if none is provided by the module itself
  556.     Print( modulecommands["help"]: _("This is a YaST2 module.")+"\n" );
  557.     // translators: short help title for command line
  558.     Print(_("Basic Syntax:"));
  559.  
  560.     if( ! interactive ) {
  561.         // translators: module command line help, %1 is the module name
  562.         Print(sformat(("    yast2 %1 interactive"), modulecommands["id"]:""));
  563.  
  564.         // translators: module command line help, %1 is the module name
  565.         // translate <command> and [options] only!
  566.         Print(sformat(_("    yast2 %1 <command> [verbose] [options]"), modulecommands["id"]:""));
  567.         // translators: module command line help, %1 is the module name
  568.         Print(sformat(("    yast2 %1 help"), modulecommands["id"]:""));
  569.         Print(sformat(("    yast2 %1 longhelp"), modulecommands["id"]:""));
  570.         Print(sformat(("    yast2 %1 xmlhelp"), modulecommands["id"]:""));
  571.         // translators: module command line help, %1 is the module name
  572.         // translate <command> only!
  573.         Print(sformat(_("    yast2 %1 <command> help"), modulecommands["id"]:""));
  574.     } else {
  575.         // translators: module command line help
  576.         // translate <command> and [options] only!
  577.         Print(_("    <command> [options]"));
  578.         // translators: module command line help
  579.         // translate <command> only!
  580.         Print(_("    <command> help"));
  581.         // translators: module command line help
  582.         Print("    help");
  583.         Print("    longhelp");
  584.         Print("    xmlhelp");
  585.         Print("");
  586.         Print("    exit");
  587.         Print("    abort");
  588.     }
  589.  
  590.     Print("");
  591.     // translators: command line title: list of available commands
  592.     Print(_("Commands:"));
  593.  
  594.     integer longest = 0;
  595.     foreach( string action, map desc, modulecommands["actions"]:$[], ``{
  596.         if( size(action) > longest ) longest = size(action);
  597.     });
  598.  
  599.     maplist(string cmd, map desc, modulecommands["actions"]:$[], ``{
  600.         // translators: error message: module does not provide any help messages
  601.         Print(sformat("    %1  %2", String::Pad(cmd, longest), desc["help"]:_("No help available.")));
  602.     });
  603.     Print("");
  604.     if( ! interactive ) {
  605.         // translators: module command line help, %1 is the module name
  606.         Print(sformat(_("Run 'yast2 %1 <command> help' for a list of available options."), modulecommands["id"]:""));
  607.         Print("");
  608.     }
  609.     }
  610.  
  611.     /**
  612.      * Handle the system-wide commands, like help etc.
  613.      *
  614.      * @param command    a map of the current command
  615.      * @return        true, if the command was handled
  616.      */
  617.     define boolean ProcessSystemCommands (map command) ``{
  618.  
  619.     // handle help for specific command
  620.     // this needs to be before general help, so "help help" works
  621.     if( command["options", "help"]: nil != nil ) {
  622.         PrintHead();
  623.         PrintActionHelp( command["command"]:"" );
  624.         return true;
  625.     }
  626.  
  627.     /* Process command "interactive" */
  628.     if( command["command"]:"" == "interactive" ) {
  629.         interactive = true;
  630.         return true;
  631.     }
  632.  
  633.     /* Process command "exit" */
  634.     if( command["command"]:"" == "exit" ) {
  635.         done = true;
  636.         aborted = false;
  637.         return true;
  638.     }
  639.  
  640.     /* Process command "abort" */
  641.     if( command["command"]:"" == "abort" ) {
  642.         done = true;
  643.         aborted = true;
  644.         return true;
  645.     }
  646.  
  647.     if( command["command"]:"" == "help" ) {
  648.         // don't print header when custom help is defined
  649.         if (!haskey(modulecommands, "customhelp"))
  650.         {
  651.         PrintHead();
  652.         }
  653.         PrintGeneralHelp();
  654.         return true;
  655.     }
  656.  
  657.     if( command["command"]:"" == "longhelp" ) {
  658.         PrintHead();
  659.         PrintGeneralHelp();
  660.         foreach( string action, map def, allcommands["actions"]:$[], ``{
  661.         PrintActionHelp( action );
  662.         });
  663.         return true;
  664.     }
  665.  
  666.     if( command["command"]:"" == "xmlhelp" ) {
  667.         if (haskey(command["options"]:$[], "xmlfile") == false)
  668.         {
  669.         // error message - command line option xmlfile is missing
  670.         Print(_("Target file name ('xmlfile' option) is missing. Use xmlfile=<target_XML_file> command line option."));
  671.         return false;
  672.         }
  673.  
  674.         string xmlfilename = command["options", "xmlfile"]:"";
  675.  
  676.         if (xmlfilename == nil || xmlfilename == "")
  677.         {
  678.         // error message - command line option xmlfile is missing
  679.         Print(_("Target file name ('xmlfile' option) is empty. Use xmlfile=<target_XML_file> command line option."));
  680.         return false;
  681.         }
  682.  
  683.         map doc = $[];
  684.  
  685. //        TODO: DTD specification
  686.         doc["listEntries"] =
  687.         $[
  688.             "commands": "command",
  689.             "options": "option",
  690.             "examples": "example",
  691.         ];
  692. //        doc["cdataSections"] = [];
  693.         doc["systemID"] = Directory::schemadir + "/commandline.dtd";
  694. //        doc["nameSpace"] = "http://www.suse.com/1.0/yast2ns";
  695.         doc["typeNamespace"] = "http://www.suse.com/1.0/configns";
  696.  
  697.         doc["rootElement"] = "commandline";
  698.         XML::xmlCreateDoc(`xmlhelp, doc);
  699.  
  700.         map exportmap = $[];
  701.         list<map> commands = [];
  702.  
  703.         map<string,map> actions = cmdlinespec["actions"]:$[];
  704.         map<string,list<string> > mappings = cmdlinespec["mappings"]:$[];
  705.         map<string,map> options = cmdlinespec["options"]:$[];
  706.  
  707.         y2debug("cmdlinespec: %1", cmdlinespec);
  708.  
  709.         foreach(string action, map description, actions, {
  710.             string help = description["help"]:"";
  711.  
  712.             list<map> opts = [];
  713.  
  714.             foreach(string option, mappings[action]:[], {
  715.                 //
  716.                 map optn = $[
  717.                 "name" : option,
  718.                 "help" : options[option,"help"]:"",
  719.                 ];
  720.  
  721.  
  722.                 // add type specification if it's present
  723.                 if (options[option,"type"]:"" != "")
  724.                 {
  725.                 optn = add(optn, "type", options[option,"type"]:"");
  726.                 }
  727.  
  728.                 opts = add(opts, optn);
  729.             }
  730.             );
  731.  
  732.             map actiondescr = $["help" : help, "name":action, "options" : opts];
  733.             // add example if it's present
  734.             if (haskey(actions[action]:$[], "example"))
  735.             {
  736.             any example = actions[action,"example"]:nil;
  737.             list examples = is (example, list)? (list)example: [example];
  738.             actiondescr = add(actiondescr, "examples", examples);
  739.             }
  740.  
  741.             commands = add(commands, actiondescr);
  742.         }
  743.         );
  744.  
  745.         exportmap["commands"] = commands;
  746.         exportmap["module"] = cmdlinespec["id"]:"";
  747.  
  748.         XML::YCPToXMLFile(`xmlhelp, exportmap, xmlfilename);
  749.         y2milestone("exported XML map: %1", exportmap);
  750.         return true;
  751.     }
  752.  
  753.     return false;
  754.     }
  755.  
  756.     /**
  757.      *  @short Initialize Module
  758.      *  @descr Initialize the module, setup the command line syntax and arguments passed on the command line.
  759.      *
  760.      *  @param cmdlineinfo        the map describing the module command line
  761.      *  @param args            arguments given by the user on the command line
  762.      *  @return boolean        true, if there are some commands to be processed
  763.      *  @see Command
  764.      */
  765.     global define boolean Init (map cmdlineinfo, list<any> args) ``{
  766.  
  767.     // remember the command line specification
  768.     // required later by xmlhelp command
  769.     cmdlinespec = cmdlineinfo;
  770.  
  771.     boolean cmdline_supported = true;
  772.  
  773.     // check whether the command line mode is really supported by the module
  774.     if (!haskey(cmdlineinfo, "actions") || size(cmdlineinfo["actions"]:$[]) == 0)
  775.     {
  776.         cmdline_supported = false;
  777.     }
  778.  
  779.     // initialize verbose flag
  780.     verbose = contains( WFM::Args(), "verbose" );
  781.  
  782.     any id_string = cmdlineinfo["id"]:"";
  783.     // sanity checks on cmdlineinfo
  784.     // check for id string , it must exist, and non-empty
  785.     if( cmdline_supported && (id_string == "" || ! is( id_string, string )) ) {
  786.         y2error( "Command line specification does not define module id" );
  787.  
  788.         // use 'unknown' as id
  789.         if( haskey( cmdlineinfo, "id" ) ) {
  790.             cmdlineinfo = remove( cmdlineinfo, "id" );
  791.         }
  792.  
  793.         // translators: fallback name for a module at command line
  794.         cmdlineinfo = add( cmdlineinfo, "id", _("unknown"));
  795.  
  796.         // it's better to abort now
  797.         done = true;
  798.         aborted = true;
  799.     }
  800.  
  801.     // check for helps, they are required everywhere
  802.     // global help text
  803.     if(cmdline_supported && ! haskey( cmdlineinfo, "help" )) {
  804.         y2error( "Command line specification does not define global help for the module" );
  805.  
  806.         // it's better to abort now
  807.         done = true;
  808.         aborted = true;
  809.     }
  810.  
  811.     // help texts for actions
  812.     if( haskey( cmdlineinfo, "actions" ) ) {
  813.         foreach( string action, map def, cmdlineinfo["actions"]:$[],  ``{
  814.         if( ! haskey( def, "help" ) ) {
  815.             y2error( "Command line specification does not define help for action '%1'", action );
  816.  
  817.             // it's better to abort now
  818.             done = true;
  819.             aborted = true;
  820.         }
  821.         });
  822.     }
  823.  
  824.     // help for options
  825.     if( haskey( cmdlineinfo, "options" ) ) {
  826.         foreach( string option, map def, cmdlineinfo["options"]:$[],  ``{
  827.         if( ! haskey( def, "help" ) ) {
  828.             y2error( "Command line specification does not define help for option '%1'", option );
  829.  
  830.             // it's better to abort now
  831.             done = true;
  832.             aborted = true;
  833.         }
  834.         // check that regex and enum have defined typespec
  835.         if( ( def["type"]:"" == "regex" || def["type"]:"" == "enum" ) && ! haskey( def, "typespec" ) ) {
  836.             y2error( "Command line specification does not define typespec for option '%1'", option );
  837.  
  838.             // it's better to abort now
  839.             done = true;
  840.             aborted = true;
  841.         }
  842.         });
  843.     }
  844.  
  845.     // mappings - check for existing actions and options
  846.     if( haskey( cmdlineinfo, "mappings" ) ) {
  847.         foreach( string mapaction, list def, cmdlineinfo["mappings"]:$[],  ``{
  848.         // is this action defined?
  849.         if( ! haskey( cmdlineinfo["actions"]:$[], mapaction ) ) {
  850.             y2error( "Command line specification maps undefined action '%1'", mapaction );
  851.  
  852.             // it's better to abort now
  853.             done = true;
  854.             aborted = true;
  855.         }
  856.  
  857.         foreach( any mapopt, def, ``{
  858.             if (!is(mapopt, string)) continue;
  859.             // is this option defined?
  860.             if( ! haskey( cmdlineinfo["options"]:$[], (string)mapopt ) ) {
  861.             y2error( "Command line specification maps undefined option '%1' for action '%2'", mapopt, mapaction );
  862.  
  863.             // it's better to abort now
  864.             done = true;
  865.             aborted = true;
  866.             }
  867.         });
  868.         });
  869.     }
  870.  
  871.     if( done ) return false;
  872.  
  873.     modulecommands = cmdlineinfo;
  874.  
  875.     // build allcommands - help and verbose options are added specially
  876.     allcommands = $[
  877.         "actions": union( modulecommands["actions"]:$[], systemcommands["actions"]:$[] ),
  878.         "options": union( modulecommands["options"]:$[], systemcommands["options"]:$[] ),
  879.         "mappings": union(
  880.         mapmap( string act, list opts, modulecommands["mappings"]:$[], ``(
  881.             $[act : union( opts, ["help", "verbose"] )] ) ),
  882.         systemcommands["mappings"]:$[] )
  883.     ];
  884.  
  885.     if( size(args) < 1 || Stage::stage () != "normal" || Stage::firstboot () ) {
  886.         Mode::SetUI ("dialog");
  887.         // start GUI, module has some work to do :-)
  888.         return true;
  889.     } else {
  890.         Mode::SetUI ("commandline");
  891.     }
  892.  
  893.     if (!cmdline_supported)
  894.     {
  895.         // command line is not supported
  896.         CommandLine::Print(String::UnderlinedHeader("YaST2 " + cmdlineinfo["id"]:"", 0));
  897.         CommandLine::Print("");
  898.  
  899.         string help = cmdlineinfo["help"]:"";
  900.         if (help != nil && help != "")
  901.         {
  902.         CommandLine::Print(cmdlineinfo["help"]:"");
  903.         CommandLine::Print("");
  904.         }
  905.  
  906.         CommandLine::Print(nosupport);
  907.         CommandLine::Print("");
  908.         return false;
  909.     }
  910.  
  911.     // setup prompt
  912.     cmdlineprompt = "YaST2 " + cmdlineinfo["id"]:"" + "> ";
  913.     SCR::Write( .dev.tty.prompt, cmdlineprompt);
  914.  
  915.     // parse args
  916.     commandcache = Parse( args );
  917.  
  918.     // return true, if there is some work to do:
  919.     // first, try to interpret system commands
  920.     if( ProcessSystemCommands(commandcache) ) {
  921.         // it was system command, there is work only in interactive mode
  922.         commandcache = $[];
  923.         done = ! interactive;
  924.         aborted = false;
  925.         return interactive;
  926.     } else {
  927.         // we cannot handle this on our own, return true if there is some command to be processed
  928.         // i.e, there is no parsing error
  929.         done = size( commandcache ) == 0 ;
  930.         aborted = done;
  931.         return ( ! done );
  932.     }
  933.     }
  934.  
  935.     /**
  936.      * @short Scan a command line from stdin, return it split into a list
  937.      * @return list<string> the list of command line parts, nil for end of file
  938.      */
  939.     global define list<string> Scan() ``{
  940.     string res = (string)SCR::Read( .dev.tty );
  941.     if( res == nil ) return nil;
  942.     return String::ParseOptions(res,$[ "separator":" " ]);
  943.     }
  944.  
  945.     /**
  946.      * Set prompt and read input from command line
  947.      * @param prompt Set prompt
  948.      * @param type Type
  949.      * @return string Entered string
  950.      */
  951.     define string GetInput(string prompt, symbol type) ``{
  952.     // set the required prompt
  953.     SCR::Write(.dev.tty.prompt, prompt);
  954.  
  955.     string res = nil;
  956.  
  957.     if (type == `nohistory)
  958.     {
  959.         res = (string)SCR::Read(.dev.tty.nohistory);
  960.     }
  961.     else if (type == `noecho)
  962.     {
  963.         res = (string)SCR::Read(.dev.tty.noecho);
  964.     }
  965.     else
  966.     {
  967.         res = (string)SCR::Read(.dev.tty);
  968.     }
  969.  
  970.     // set the default prompt
  971.     SCR::Write(.dev.tty.prompt, cmdlineprompt);
  972.  
  973.     return res;
  974.     }
  975.  
  976.     /**
  977.      * Read input from command line
  978.      * @param prompt Set prompt to this value
  979.      * @return string Entered string
  980.      */
  981.     global define string UserInput(string prompt) ``{
  982.     return GetInput(prompt, `nohistory);
  983.     }
  984.  
  985.     /**
  986.      * @short Read input from command line
  987.      * @descr Read input from command line, input is not displayed and not stored in
  988.      * the command line history. This function should be used for reading a password.
  989.      * @param prompt Set prompt to this value
  990.      * @return string Entered string
  991.      */
  992.     global define string PasswordInput(string prompt) ``{
  993.     return GetInput(prompt, `noecho);
  994.     }
  995.  
  996.     /**
  997.      *  @short Get next user-given command
  998.      *  @descr Get next user-given command. If there is a command available, returns it, otherwise ask
  999.      *  the user for a command (in interactive mode). Also processes system commands.
  1000.      *
  1001.      *  @return map of the new command. If there are no more commands, it returns exit or abort depending
  1002.      *  on the result user asked for.
  1003.      *
  1004.      *  @see Parse
  1005.      */
  1006.     global define map Command () ``{
  1007.     // if we are done already, return the result
  1008.     if( done ) {
  1009.         if( aborted ) return $[ "command" : "abort" ];
  1010.         else return $[ "command" : "exit" ];
  1011.     }
  1012.  
  1013.     // there is a command in the cache
  1014.     if( size(commandcache) != 0 )
  1015.     {
  1016.         map result = commandcache;
  1017.         commandcache = $[];
  1018.         done = !interactive;
  1019.         return result;
  1020.     } else {
  1021.         // if in interactive mode, ask user for input
  1022.         if( interactive ) {
  1023.         // handle all system commands as long as possible
  1024.         do {
  1025.             list<string> newcommand = [];
  1026.             do {
  1027.             newcommand = Scan();
  1028.             } while( size(newcommand) == 0 );
  1029.  
  1030.             // EOF reached
  1031.             if( newcommand == nil ) {
  1032.             done = true;
  1033.             return $[ "command" : "exit" ];
  1034.             }
  1035.  
  1036.             commandcache = Parse( newcommand );
  1037.  
  1038.         } while( ProcessSystemCommands( commandcache ) && ! done );
  1039.  
  1040.         if( done ) {
  1041.             if( aborted ) return $[ "command" : "abort" ];
  1042.             else return $[ "command" : "exit" ];
  1043.         }
  1044.  
  1045.         // we are not done, return the command asked back to module
  1046.         map result = commandcache;
  1047.         commandcache = $[];
  1048.  
  1049.         return result;
  1050.         } else {
  1051.         // there is no further commands left
  1052.         done = true;
  1053.         return $[ "command" : "exit" ];
  1054.         }
  1055.     }
  1056.     }
  1057.  
  1058.     /**
  1059.      *  Should module start UI?
  1060.      *
  1061.      *  @return boolean true, if the user asked for standard UI (no parameter was passed by command line)
  1062.      */
  1063.     global define boolean StartGUI() ``{
  1064.     return ! Mode::commandline ();
  1065.     }
  1066.  
  1067.     /**
  1068.      *  Is module started in interactive command-line mode?
  1069.      *
  1070.      *  @return boolean true, if the user asked for interactive command-line mode
  1071.      */
  1072.     global define boolean Interactive() ``{
  1073.     return interactive;
  1074.     }
  1075.  
  1076.     /**
  1077.      *  User asked for abort (forgetting the changes)
  1078.      *
  1079.      *  @return boolean true, if the user asked abort
  1080.      */
  1081.     global define boolean Aborted() ``{
  1082.     return aborted;
  1083.     }
  1084.  
  1085.     /**
  1086.      * Abort the command line handling
  1087.      */
  1088.     global define void Abort() ``{
  1089.     aborted = true;
  1090.     done = true;
  1091.     }
  1092.  
  1093.     /**
  1094.      *  Are there some commands to be processed?
  1095.      *
  1096.      *  @return boolean true, if there is no more commands to be processed, either because the user
  1097.      *  used command line, or the interactive mode was finished
  1098.      */
  1099.     global define boolean Done() ``{
  1100.     return done;
  1101.     }
  1102.  
  1103.     /**
  1104.      * @short Check uniqueness of an option
  1105.      * @descr Check uniqueness of an option. Simply pass the list of user-specified
  1106.      * options and a list of mutually exclusive options. In case of
  1107.      * error, Report::Error is used.
  1108.      *
  1109.      * @param options  options specified by the user on the command line to be checked
  1110.      * @param unique_options    list of mutually exclusive options to check against
  1111.      * @return    nil if there is a problem, otherwise the unique option found
  1112.      */
  1113.     global define string UniqueOption( map<string, string> options, list unique_options ) ``{
  1114.     // sanity check
  1115.     if( size( unique_options ) == 0 ) {
  1116.         y2error( "Unique test of options required, but the list of the possible options is empty");
  1117.         return nil;
  1118.     }
  1119.  
  1120.     // first do a filtering, then convert to a list of keys
  1121.     list cmds = maplist( string key, string value,
  1122.         filter( string opt, string value, options, ``( contains( unique_options, opt ) ) ),
  1123.         ``( key )
  1124.     );
  1125.  
  1126.     // if it is OK, quickly return
  1127.     if( size( cmds ) == 1 ) return (string)(cmds[0]:nil);
  1128.  
  1129.     // something is wrong, prepare the error report
  1130.     integer i = 0;
  1131.     string opt_list = "";
  1132.     while( i < size( unique_options )-1 ) {
  1133.         opt_list = opt_list + sformat( "'%1', ", unique_options[i]:nil );
  1134.         i = i + 1;
  1135.     }
  1136.  
  1137.     // translators: the last command %1 in a list of unique commands
  1138.     opt_list = opt_list + sformat( _("or '%1'"), unique_options[i]:nil );
  1139.  
  1140.     if( size( cmds ) == 0 ) {
  1141.         if( size( unique_options ) == 1 ) {
  1142.         // translators: error message - missing unique command for command line execution
  1143.         Report::Error( sformat( _("Specify the command '%1'."), unique_options[0]:nil ) );
  1144.         } else {
  1145.         // translators: error message - missing unique command for command line execution
  1146.         Report::Error( sformat( _("Specify one of the commands: %1."), opt_list ) );
  1147.         }
  1148.         return nil;
  1149.     }
  1150.  
  1151.     if( size( cmds ) != 1 ) {
  1152.         // size( unique_options ) == 1 here does not make sense
  1153.  
  1154.         Report::Error( sformat( _("Specify only one of the commands: %1."), opt_list ) );
  1155.         return nil;
  1156.     }
  1157.  
  1158.     return (string)(cmds[0]:nil);
  1159.     }
  1160.  
  1161. boolean fake_false() ``{
  1162.     return false;
  1163. }
  1164.  
  1165. boolean RunFunction( boolean() funct ) {
  1166.     Report::ClearAll();
  1167.     boolean() my_funct = funct;
  1168.     boolean ret = my_funct();
  1169.     string report = Report::GetMessages(
  1170.     Report::NumWarnings()>0,Report::NumErrors()>0,Report::NumMessages()>0, Report::NumYesNoMessages()>0);
  1171.     if( size(report) > 0 )
  1172.     {
  1173.     import "RichText";
  1174.     CommandLine::Print( RichText::Rich2Plain( report )  );
  1175.     }
  1176.     return ret;
  1177. }
  1178.  
  1179. boolean RunMapFunction( boolean(map<string,string>) funct,
  1180.     map<string,string> arg )
  1181. {
  1182.     Report::ClearAll();
  1183.     boolean(map<string,string>) my_funct = funct;
  1184.     boolean ret = my_funct(arg);
  1185.     string report = Report::GetMessages(
  1186.     Report::NumWarnings()>0,Report::NumErrors()>0,Report::NumMessages()>0, Report::NumYesNoMessages()>0);
  1187.     if( size(report) > 0 )
  1188.     {
  1189.     import "RichText";
  1190.     CommandLine::Print( RichText::Rich2Plain( report )  );
  1191.     }
  1192.     return ret;
  1193. }
  1194.  
  1195. /**
  1196.  * @short Parse the Command Line
  1197.  * @descr Function to parse the command line, start a GUI or handle interactive and
  1198.  * command line actions as supported by the @ref CommandLine module.
  1199.  *
  1200.  * @param commandline    a map used in the CommandLine module with information
  1201.  *                      about the handlers for GUI and commands.
  1202.  * @return any        false if there was an error or no changes to be written (for example "help").
  1203.  *            true if the changes should be written, or a value returned by the
  1204.  *            handler
  1205.  */
  1206. global any Run( map commandline ) {
  1207.     /* The main () */
  1208.     y2milestone("----------------------------------------");
  1209.     y2milestone("Command line interface started");
  1210.  
  1211.     /* Initialize the arguments */
  1212.     if(!CommandLine::Init(commandline, WFM::Args())) {
  1213.     return ! CommandLine::Aborted();
  1214.     }
  1215.  
  1216.     boolean ret = true;
  1217.  
  1218.     boolean initialized = false;
  1219.     if(commandline["initialize"]:nil == nil) {
  1220.     // no initialization routine
  1221.     // set initialized state to true => call finish handler at the end in command line mode
  1222.     initialized = true;
  1223.     }
  1224.  
  1225.     /* Start GUI */
  1226.     if(CommandLine::StartGUI()) {
  1227.     if( !haskey (commandline, "guihandler") ) {
  1228.         y2error( "Missing GUI handler for %1", commandline["id"]:"<unknown>" );
  1229.         // translators: error message - the module does not provide command line interface
  1230.         CommandLine::Error( _("There is no user interface available for this module.") );
  1231.         return false;
  1232.     }
  1233.  
  1234.     if ( is(commandline[ "guihandler" ]:nil, symbol() ) )
  1235.     {
  1236.         symbol() exec = (symbol())commandline[ "guihandler" ]: nil;
  1237.         symbol symbol_ret = exec();
  1238.         y2debug("GUI handler ret=%1", symbol_ret);
  1239.         return symbol_ret;
  1240.     }
  1241.     else
  1242.     {
  1243.         boolean() exec = commandline[ "guihandler" ]: fake_false;
  1244.         ret = exec();
  1245.         y2debug("GUI handler ret=%1", ret);
  1246.         return ret;
  1247.     }
  1248.     } else {
  1249.  
  1250.     // disable Reports, we handle them on our own
  1251.     Report::Import( $[
  1252.         "messages"    :$[ "show":false ],
  1253.         "warnings"    :$[ "show":false ],
  1254.         "errors"    :$[ "show":false ]
  1255.     ]);
  1256.  
  1257.     // translators: progress message - command line interface ready
  1258.     CommandLine::PrintVerbose( _("Ready") );
  1259.  
  1260.     ret = true;
  1261.  
  1262.     /* Init variables */
  1263.     string command = "";
  1264.     list flags = [];
  1265.     map<string,string> options = $[];
  1266.         string exit = "";
  1267.         list l = [];
  1268.  
  1269.  
  1270.     while(!CommandLine::Done()) {
  1271.         map m = CommandLine::Command();
  1272.             command = m["command"]:"exit";
  1273.         options = m["options"]:$[];
  1274.  
  1275.         // start initialization code if it wasn't already used
  1276.         if (!initialized)
  1277.         {
  1278.         // check whether command is defined in the map (i.e. it is not predefined command or invalid command)
  1279.         // and start initialization if it's defined
  1280.         if( haskey(commandline["actions"]:$[], command) && commandline["initialize"]:nil != nil ) {
  1281.             /* non-GUI handling */
  1282.             CommandLine::PrintVerbose( _("Initializing") );
  1283.             boolean ret = RunFunction( commandline["initialize"]:fake_false );
  1284.  
  1285.             if( !ret ) {
  1286.             y2milestone( "Module initialization failed" );
  1287.             return false;
  1288.             }
  1289.             else
  1290.             {
  1291.             initialized = true;
  1292.             }
  1293.         }
  1294.         }
  1295.  
  1296.         boolean(map<string,string>) exec = (boolean(map<string,string>))
  1297.         commandline["actions", command, "handler"]:nil;
  1298.  
  1299.         // there is a handler, execute the action
  1300.         if( exec != nil ) {
  1301.         boolean res = RunMapFunction( exec, options );
  1302.  
  1303.         // if it is not interactive, abort on errors
  1304.         if( !CommandLine::Interactive() && res == false )
  1305.             CommandLine::Abort();
  1306.         }
  1307.         else
  1308.         {
  1309.         if( !CommandLine::Done() ) {
  1310.             y2error("Unknown command '%1' from CommandLine", command );
  1311.             continue;
  1312.         }
  1313.         }
  1314.     }
  1315.  
  1316.     ret = ! CommandLine::Aborted();
  1317.     }
  1318.  
  1319.     if( ret && commandline["finish"]:nil != nil && initialized) {
  1320.     // translators: Progress message - the command line interface is about to finish
  1321.     CommandLine::PrintVerbose( _("Finishing") );
  1322.     ret = RunFunction( commandline["finish"]:fake_false );
  1323.     if( !ret ) {
  1324.         y2milestone( "Module finishing failed" );
  1325.         return false;
  1326.     }
  1327.     // translators: The command line interface is finished
  1328.     CommandLine::PrintVerbose( _("Done") );
  1329.     } else
  1330.     // translators: The command line interface is finished without writing the changes
  1331.     CommandLine::PrintVerbose( _("Quitting (without changes)") );
  1332.  
  1333.     y2milestone("Commandline interface finished");
  1334.     y2milestone("----------------------------------------");
  1335.  
  1336.     return ret;
  1337. }
  1338.  
  1339. /**
  1340.  * Ask user, commandline equivalent of Popup::YesNo()
  1341.  * @return boolean true if user entered "yes"
  1342.  */
  1343. global define boolean YesNo() {
  1344.     // prompt message displayed in the commandline mode
  1345.     // when user is asked to replay "yes" or "no" (localized)
  1346.     string prompt = _("yes or no?");
  1347.  
  1348.     string ui = CommandLine::UserInput(prompt);
  1349.  
  1350.     // yes - used in the command line mode as input text for yes/no confirmation
  1351.     string yes = _("yes");
  1352.  
  1353.     // no - used in the command line mode as input text for yes/no confirmation
  1354.     string no = _("no");
  1355.  
  1356.     while (ui != yes && ui != no) {
  1357.     ui = CommandLine::UserInput(prompt);
  1358.     }
  1359.  
  1360.     return (ui == yes);
  1361. }
  1362.  
  1363. /**
  1364.  * Return verbose flag
  1365.  * boolean verbose flag
  1366.  */
  1367. global define boolean Verbose() {
  1368.     return verbose;
  1369. }
  1370.  
  1371. /* EOF */
  1372. }
  1373.