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 / String.ycp < prev    next >
Text File  |  2006-11-29  |  28KB  |  1,117 lines

  1. /**
  2.  * File:    modules/String.ycp
  3.  * Package:    yast2
  4.  * Summary:    String manipulation routines
  5.  * Authors:    Michal Svec <msvec@suse.cz>
  6.  *
  7.  * $Id: String.ycp 28838 2006-03-10 16:11:58Z mvidner $
  8.  */
  9.  
  10. {
  11.  
  12. module "String";
  13. textdomain "base";
  14.  
  15. /**
  16.  * Quote a string with 's
  17.  * @param var unquoted string
  18.  * @return quoted string
  19.  * @example quote("a'b") -> "a'\''b"
  20.  */
  21. global define string Quote(string var) ``{
  22.     if(var == nil || var == "") return "";
  23.     return mergestring(splitstring(var, "'"), "'\\''");
  24. }
  25.  
  26. /**
  27.  * Unquote a string with 's (quoted with quote)
  28.  * @param var quoted string
  29.  * @return unquoted string
  30.  * @see quote
  31.  */
  32. global define string UnQuote(string var) ``{
  33.     if(var == nil || var == "") return "";
  34.     y2debug("var=%1", var);
  35.     while(regexpmatch(var, "'\\\\''")) {
  36.     var = regexpsub(var, "(.*)'\\\\''(.*)", "\\1'\\2");
  37.     y2debug("var=%1", var);
  38.     }
  39.     return var;
  40. }
  41.  
  42. /**
  43.  * Optional formatted text
  44.  * @return sformat (f, s) if s is neither empty or nil, else ""
  45.  */
  46. global string OptFormat (string f, string s) {
  47.     return (s == "" || s == nil)? "": sformat (f, s);
  48. }
  49.  
  50. /**
  51.  * Optional parenthesized text
  52.  * @return " (Foo)" if Foo is neither empty or nil, else ""
  53.  */
  54. global string OptParens (string s) {
  55.     return OptFormat (" (%1)", s);
  56. }
  57.  
  58. /**
  59.  * @param l a list of strings
  60.  * @return only non-"" items
  61.  */
  62. global list<string> NonEmpty (list<string> l) {
  63.     return filter (string i, l, ``( i != "" ));
  64. }
  65.  
  66. /**
  67.  * @param s \n-terminated items
  68.  * @return the items as a list, with empty lines removed
  69.  */
  70. global list<string> NewlineItems (string s) {
  71.     return NonEmpty (splitstring (s, "\n"));
  72. }
  73.  
  74. /**
  75.  * Return a pretty description of a byte count
  76.  * 
  77.  * Return a pretty description of a byte count with required precision
  78.  * and using KB, MB or GB as unit as appropriate.
  79.  *
  80.  * @param bytes    size (e.g. free diskspace, memory size) in Bytes
  81.  * @param precision number of fraction digits in output
  82.  * @param omit_zeroes if true then do not add zeroes
  83.  *    (usefull for memory size - 128 MB RAM looks better than 128.00 MB RAM)
  84.  * @return formatted string
  85.  *
  86.  * @example FormatSizeWithPrecision(4096, 2, true) -> "4 KB"
  87.  * @example FormatSizeWithPrecision(4096, 2, false) -> "4.00 KB"
  88.  */
  89. global define string FormatSizeWithPrecision(integer bytes, integer precision, boolean omit_zeroes) ``{
  90.  
  91.     if(bytes == nil) return "";
  92.  
  93.     string unit = "";
  94.     list units = [
  95.     /* KiloByte abbreviated */
  96.     _("KB"),
  97.     /* MegaByte abbreviated */
  98.     _("MB"),
  99.     /* GigaByte abbreviated */
  100.     _("GB"),
  101.     /* TeraByte abbreviated */
  102.     _("TB"),
  103.     ];
  104.     integer index = 0;
  105.     float whole = tofloat(bytes);
  106.  
  107.     while((whole > 1024.0 || whole < -1024.0) && index < size(units)) {
  108.     whole = whole / 1024.0;
  109.     unit = units[index]:"";
  110.     index = index + 1;
  111.     }
  112.  
  113.     if (precision == nil || precision < 0 || (omit_zeroes == true && (whole - tointeger(whole) == 0.0)))
  114.     {
  115.     precision = 0;
  116.     }
  117.  
  118.     return tostring(whole, precision) + " " + unit;
  119. }
  120.  
  121. /**
  122.  * Return a pretty description of a byte count
  123.  *
  124.  * Return a pretty description of a byte count, with two fraction digits
  125.  * and using KB, MB or GB as unit as appropriate.
  126.  *
  127.  * @param bytes    size (e.g. free diskspace) in Bytes
  128.  * @return formatted string
  129.  *
  130.  * @example FormatSize(23456767890) -> "223.70 MB"
  131.  */
  132. global define string FormatSize(integer bytes) ``{
  133.     return FormatSizeWithPrecision(bytes, 2, false);
  134. }
  135.  
  136. /**
  137.  * Remove blanks at begin and end of input string.
  138.  * @param input string to be stripped
  139.  * @return stripped string
  140.  * @example CutBlanks("  any  input     ") -> "any  input"
  141.  */
  142. global define string CutBlanks(string input) ``{
  143.  
  144.     if(input == nil || size(input) < 1) return "";
  145.  
  146.     integer pos1 = findfirstnotof(input, " \t");
  147.     if(pos1 == nil) return "";
  148.  
  149.     integer pos2 = findlastnotof(input, " \t");
  150.  
  151.     return substring(input, pos1, pos2 - pos1 + 1);
  152. }
  153.  
  154. /**
  155.  * Remove any leading zeros
  156.  *
  157.  * Remove any leading zeros that make tointeger inadvertently
  158.  * assume an octal number (e.g. "09" -> "9", "0001" -> "1",
  159.  * but "0" -> "0")
  160.  *
  161.  * @param input number that might contain leadig zero
  162.  * @return string that has leading zeros removed
  163.  */
  164. global define string CutZeros(string input) ``{
  165.     if(input == nil || size(input) < 1) return "";
  166.     if(!regexpmatch(input, "^0.*")) return input;
  167.     string output = regexpsub(input, "^0+(.*)$", "\\1");
  168.     if(size(output) < 1) return "0";
  169.     return output;
  170. }
  171.  
  172. /**
  173.  * Add spaces after the text to make it long enough
  174.  *
  175.  * Add spaces after the text to make it long enough. If the text is longer
  176.  * than requested, no changes are made.
  177.  *
  178.  * @param text text to be padded
  179.  * @param length requested length
  180.  * @return padded text
  181.  */
  182. global define string Pad(string text, integer length) {
  183.     if(text == nil) text = "";
  184.  
  185.     integer rest = length - size(text);
  186.     string pad = "";
  187.     while(rest > 0) {
  188.     pad = pad + " ";
  189.     rest = rest - 1;
  190.     }
  191.  
  192.     return text + pad;
  193. }
  194.  
  195. /**
  196.  * Add zeros before the text to make it long enough. 
  197.  *
  198.  * Add zeros before the text to make it long enough. If the text is longer
  199.  * than requested, no changes are made.
  200.  *
  201.  * @param text text to be padded
  202.  * @param length requested length
  203.  * @return padded text
  204.  */
  205. global define string PadZeros(string text, integer length) {
  206.     if(text == nil) text = "";
  207.  
  208.     integer rest = length - size(text);
  209.     string pad = "";
  210.     while(rest > 0) {
  211.     pad = pad + "0";
  212.     rest = rest - 1;
  213.     }
  214.  
  215.     return pad + text;
  216. }
  217.  
  218. /**
  219.  * Parse string of values
  220.  *
  221.  * Parse string of values - split string to values, quoting and backslash sequences are supported
  222.  * @param options Input string
  223.  * @param parameters Parmeter used at parsing - map with keys:
  224.  *"separator":<string> - value separator (default: " \t"),
  225.  *"unique":<boolean> - result will not contain any duplicates, first occurance of the string is stored into output (default: false),
  226.  *"interpret_backslash":<boolean> - convert backslash sequence into one character (e.g. "\\n" => "\n") (default: true)
  227.  *"remove_whitespace":<boolean> - remove white spaces around values (default: true),
  228.  * @return list<string> List of strings
  229.  */
  230. global define list<string> ParseOptions(string options, map parameters) ``{
  231.     list<string> ret = [];
  232.  
  233.         // parsing options
  234.     string separator = parameters["separator"]:" \t";
  235.         boolean unique = parameters["unique"]:false;
  236.     boolean interpret_backslash = parameters["interpret_backslash"]:true;
  237.         boolean remove_whitespace = parameters["remove_whitespace"]:true;
  238.  
  239.     y2debug("Input: string: '%1', parameters: %2", options, parameters);
  240.         y2debug("Used values: separator: '%1', unique: %2, remove_whitespace: %3",
  241.         separator, unique, remove_whitespace);
  242.  
  243.         if (options == nil)
  244.     {
  245.         return [];
  246.         }
  247.  
  248.         // two algorithms are used:
  249.     // first is much faster, but only usable if string
  250.         // doesn't contain any double qoute characters
  251.     // and backslash sequences are not interpreted
  252.         // second is more general, but of course slower
  253.  
  254.         if (findfirstof(options, "\"") == nil && interpret_backslash == false)
  255.     {
  256.         // easy case - no qouting, don't interpres backslash sequences => use splitstring
  257.         list<string> values = splitstring(options, separator);
  258.  
  259.         foreach (string v, values, ``{
  260.         if (remove_whitespace == true)
  261.         {
  262.             v = CutBlanks(v);
  263.         }
  264.  
  265.         if (unique == true)
  266.         {
  267.             if (!contains(ret, v)) ret = add(ret, v);
  268.         }
  269.         else
  270.         {
  271.             ret = add(ret, v);
  272.         }
  273.         });
  274.     }
  275.     else
  276.     {
  277.         // quoting is used or backslash interpretation is enabled
  278.         // so it' not possible to split input
  279.         // parsing each character is needed - use finite automaton
  280.  
  281.         // state
  282.         symbol state = `out_of_string;
  283.         // position in the input string
  284.         integer index = 0;
  285.         // parsed value - buffer
  286.         string str = "";
  287.  
  288.         while(index < size(options))
  289.         {
  290.         string character = substring(options, index, 1);
  291.  
  292.         y2debug("character: %1 state: %2 index: %3", character, state, index);
  293.  
  294.         // interpret backslash sequence
  295.         if (character == "\\" && interpret_backslash == true)
  296.         {
  297.             if (index + 1 < size(options))
  298.             {
  299.             string nextcharacter = substring(options, index + 1, 1);
  300.             index = index + 1;
  301.  
  302.             // backslah sequences
  303.             map backslash_seq = $[
  304.                 "a"    : "\a",    // alert
  305.                 "b"    : "\b", // backspace
  306.                 "e"    : "\e", // escape
  307.                 "f"    : "\f", // FF
  308.                 "n"    : "\n", // NL
  309.                 "r"    : "\r", // CR
  310.                 "t"    : "\t", // tab
  311.                 "v"    : "\v", // vertical tab
  312.                 "\\": "\\", // backslash
  313.             ];
  314.  
  315.             if (haskey(backslash_seq, nextcharacter) == true)
  316.             {
  317.                 character = backslash_seq[nextcharacter]:"DUMMY";
  318.             }
  319.             else
  320.             {
  321.                 if (nextcharacter != "\"")
  322.                 {
  323.                 // ignore backslash in invalid backslash sequence
  324.                 character = nextcharacter;
  325.                 }
  326.                 else
  327.                 {
  328.                 // backslash will be removed later,
  329.                 // double quote and escaped double quote have to different yet
  330.                 character = "\\\"";
  331.                 }
  332.             }
  333.  
  334.             y2debug("backslash sequence: '%1'", character);
  335.             }
  336.             else
  337.             {
  338.             y2warning("Missing character after backslash (\\) at the end of string");
  339.             }
  340.         }
  341.  
  342.         if (state == `out_of_string)
  343.         {
  344.             // ignore separator or white space at the beginning of the string
  345.             if (issubstring(separator, character) == true || (remove_whitespace == true && (character == " " || character == "\t")) )
  346.             {
  347.             index = index + 1;
  348.             continue;
  349.             }
  350.             // start of a quoted string
  351.             else if (character == "\"")
  352.             {
  353.             state = `in_quoted_string;
  354.             }
  355.             else
  356.             {
  357.             // start of a string
  358.             state = `in_string;
  359.  
  360.             if (character == "\\\"")
  361.             {
  362.                 str = "\"";
  363.             }
  364.             else
  365.             {
  366.                 str = character;
  367.             }
  368.             }
  369.         }
  370.  
  371.         // after double quoted string - handle non-separator chars after double quote
  372.         else if (state == `in_quoted_string_after_dblqt)
  373.         {
  374.             if (issubstring(separator, character) == true)
  375.             {
  376.             if (unique == true)
  377.             {
  378.                 if (!contains(ret, str)) ret = add(ret, str);
  379.             }
  380.             else
  381.             {
  382.                 ret = add(ret, str);
  383.             }
  384.  
  385.             str = "";
  386.             state = `out_of_string;
  387.             }
  388.             else if (character == "\\\"")
  389.             {
  390.             str = str + "\"";
  391.             }
  392.             else
  393.             {
  394.             str = str + character;
  395.             }
  396.         }
  397.         else if (state == `in_quoted_string)
  398.         {
  399.             if (character == "\"")
  400.             {
  401.             // end of quoted string
  402.             state = `in_quoted_string_after_dblqt;
  403.             }
  404.             else if (character == "\\\"")
  405.             {
  406.             str = str + "\"";
  407.             }
  408.             else
  409.             {
  410.             str = str + character;
  411.             }
  412.         }
  413.         else if (state == `in_string)
  414.         {
  415.             if (issubstring(separator, character) == true)
  416.             {
  417.             state = `out_of_string;
  418.  
  419.             if (remove_whitespace == true)
  420.             {
  421.                 str = CutBlanks(str);
  422.             }
  423.  
  424.             if (unique == true)
  425.             {
  426.                 if (!contains(ret, str)) ret = add(ret, str);
  427.             }
  428.             else
  429.             {
  430.                 ret = add(ret, str);
  431.             }
  432.  
  433.             str = "";
  434.             }
  435.             else if (character == "\\\"")
  436.             {
  437.             str = str + "\"";
  438.             }
  439.             else
  440.             {
  441.             str = str + character;
  442.             }
  443.         }
  444.  
  445.         index = index + 1;
  446.         }
  447.  
  448.         // error - still in quoted string
  449.         if (state == `in_quoted_string || state == `in_quoted_string_after_dblqt)
  450.         {
  451.         if (state == `in_quoted_string)
  452.         {
  453.             y2warning("Missing trainling double quote character(\") in input: '%1'", options);
  454.         }
  455.  
  456.         if (unique == true)
  457.         {
  458.             if (!contains(ret, str)) ret = add(ret, str);
  459.         }
  460.         else
  461.         {
  462.             ret = add(ret, str);
  463.         }
  464.         }
  465.  
  466.         // process last string in the buffer
  467.         if (state == `in_string)
  468.         {
  469.         if (remove_whitespace)
  470.         {
  471.             str = CutBlanks(str);
  472.         }
  473.  
  474.         if (unique == true)
  475.         {
  476.             if (!contains(ret, str)) ret = add(ret, str);
  477.         }
  478.         else
  479.         {
  480.             ret = add(ret, str);
  481.         }
  482.         }
  483.     }
  484.  
  485.  
  486.     y2debug("Parsed values: %1", ret);
  487.  
  488.     return ret;
  489. }
  490.  
  491. /**
  492.  * Remove first or every match of given regular expression from a string
  493.  *
  494.  * (e.g. CutRegexMatch( "abcdef12ef34gh000", "[0-9]+", true ) -> "abcdefefgh",
  495.  * CutRegexMatch( "abcdef12ef34gh000", "[0-9]+", false ) -> "abcdefef34gh000")
  496.  *
  497.  * @param input string that might occur regex
  498.  * @param regex regular expression to search for, must not contain brackets
  499.  * @param glob flag if only first or every occuring match should be removed
  500.  * @return string that has matches removed
  501.  */
  502. global define string CutRegexMatch(string input, string regex, boolean glob) ``{
  503.     if(input == nil || size(input) < 1) return "";
  504.     string output = input;
  505.     if( regexpmatch( output, regex ) )
  506.     {
  507.     list p = regexppos( output, regex );
  508.     do
  509.         {
  510.         output = substring( output, 0, p[0]:0 ) +
  511.                  substring( output, p[0]:0+p[1]:0 );
  512.         p = regexppos( output, regex );
  513.         }
  514.     while( glob && size(p)>0 );
  515.     }
  516.     return output;
  517. }
  518.  
  519. /**
  520.  * Function for escaping (replacing) (HTML|XML...) tags with their
  521.  * (HTML|XML...) meaning.
  522.  *
  523.  * Usable to present text "as is" in RichText.
  524.  *
  525.  * @param string    text to escape
  526.  * @return string    escaped text
  527.  */
  528. global define string EscapeTags (string text) ``{
  529.     text = mergestring(splitstring(text, "&"), "&");
  530.     text = mergestring(splitstring(text, "<"), "<");
  531.     text = mergestring(splitstring(text, ">"), ">");
  532.  
  533.     return text;
  534. }
  535.  
  536. /**
  537.  * Shorthand for select (splitstring (s, separators), 0, "")
  538.  * Useful now that the above produces a deprecation warning.
  539.  * @param s string to be split
  540.  * @param separators characters which delimit components
  541.  * @return first component or ""
  542.  */
  543. global string FirstChunk (string s, string separators) {
  544.     list <string> l = splitstring (s, separators);
  545.     return l[0]:"";
  546. }
  547.  
  548. // character sets, suitable for ValidChars
  549.  
  550. string cupper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  551. string clower = "abcdefghijklmnopqrstuvwxyz";
  552. string calpha = cupper + clower;
  553. string cdigit = "0123456789";
  554. string cxdigit = cdigit + "ABCDEFabcdef";
  555. string calnum = calpha + cdigit;
  556. string cpunct = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
  557. string cgraph = calnum + cpunct;
  558. string cspace = " \f\r\n\t\013";
  559. string cprint = cspace + cgraph;
  560.  
  561. /**
  562.  * @return the 26 uppercase ASCII letters
  563.  */
  564. global string CUpper () {
  565.     return cupper;
  566. }
  567.  
  568. /**
  569.  * @return the 26 lowercase ASCII letters
  570.  */
  571. global string CLower () {
  572.     return clower;
  573. }
  574.  
  575. /**
  576.  * @return the 52 upper and lowercase ASCII letters
  577.  */
  578. global string CAlpha () {
  579.     return calpha;
  580. }
  581.  
  582. /**
  583.  * @return 0123456789
  584.  */
  585. global string CDigit () {
  586.     return cdigit;
  587. }
  588.  
  589. /**
  590.  * @return hexadecimal digits: 0123456789ABCDEFabcdef
  591.  */
  592. global string CXdigit () {
  593.     return cxdigit;
  594. }
  595.  
  596. /**
  597.  * @return the 52 upper and lowercase ASCII letters and digits
  598.  */
  599. global string CAlnum () {
  600.     return calnum;
  601. }
  602.  
  603. /**
  604.  * @return the ASCII printable non-blank non-alphanumeric characters
  605.  */
  606. global string CPunct () {
  607.     return cpunct;
  608. }
  609.  
  610. /**
  611.  * @return printable ASCII charcters except whitespace
  612.  */
  613. global string CGraph () {
  614.     return cgraph;
  615. }
  616.  
  617. /**
  618.  * @return ASCII whitespace
  619.  */
  620. global string CSpace () {
  621.     return cspace;
  622. }
  623.  
  624. /**
  625.  * @return printable ASCII characters including whitespace
  626.  */
  627. global string CPrint () {
  628.     return cprint;
  629. }
  630.  
  631. /**
  632.  * Characters valid in a filename (not pathname).
  633.  * Naturally "/" is disallowed. Otherwise, the graphical ASCII
  634.  * characters are allowed.
  635.  * @return string for ValidChars
  636.  */
  637. global string ValidCharsFilename () {
  638.     return deletechars (CGraph (), "/");
  639. }
  640.  
  641.  
  642.     // 64 characters is the base undeline length
  643.     string base_underline = "----------------------------------------------------------------";
  644.  
  645.     /* - hidden for documentation -
  646.      *
  647.      * Local function for finding longest records in the table.
  648.      *
  649.      * @param    list <list <string> > table items
  650.      * @return    list <integer> longest records by columns
  651.      */
  652.     list <integer> FindLongestRecords (list <list <string> > items) {
  653.     list <integer> longest = [];
  654.  
  655.     // searching all rows
  656.     foreach (list <string> row, items, {
  657.         // starting with column 0
  658.         integer col_counter = 0;
  659.         // testing all columns on the row
  660.         foreach (string col, row, {
  661.         integer col_size = size(col);
  662.         // found longer record for this column
  663.         if (col_size>longest[col_counter]:-1) longest[col_counter] = col_size;
  664.         // next column
  665.         col_counter = col_counter + 1;
  666.         });
  667.     });
  668.  
  669.     return longest;
  670.     }
  671.  
  672.     /* - hidden for documentation -
  673.      *
  674.      * Local function creates table row.
  675.      *
  676.      * @param    list <string> row items
  677.      * @param    list <integer> columns lengths
  678.      * @param    integer record horizontal padding
  679.      * @return    string padded table row
  680.      */
  681.     string CreateTableRow (list <string> row_items, list <integer> cols_lenghts, integer horizontal_padding) {
  682.     string row = "";
  683.  
  684.     integer col_counter = 0;
  685.     integer records_count = size(row_items) - 1;
  686.  
  687.     foreach (string record, row_items, {
  688.         integer padding = cols_lenghts[col_counter]:0;
  689.         if (col_counter<records_count) padding = padding + horizontal_padding;
  690.  
  691.         row = row + String::Pad(record, padding);
  692.         col_counter = col_counter + 1;
  693.     });
  694.  
  695.     return row;
  696.     }
  697.  
  698.     /* - hidden for documentation -
  699.      *
  700.      * Local function returns underline string /length/ long.
  701.      *
  702.      * @param    integer length of underline
  703.      * @return    string /length/ long underline
  704.      */
  705.     string CreateUnderline (integer length) {
  706.     string underline = base_underline;
  707.     while (size(underline)<length) {
  708.         underline = underline + base_underline;
  709.     }
  710.     underline = substring(underline, 0, length);
  711.      
  712.     return underline;
  713.     }
  714.  
  715.     /* - hidden for documentation -
  716.      *
  717.      * Local function for creating header underline for table.
  718.      * It uses maximal lengths of records defined in cols_lenghts.
  719.      *
  720.      * @param    list <integer> maximal lengths of records in columns
  721.      * @param    integer horizontal padding of records
  722.      * @return    string table header underline
  723.      */
  724.     string CreateTableHeaderUnderline (list <integer> cols_lenghts, integer horizontal_padding) {
  725.     integer col_counter = 0;
  726.     // count of added paddings
  727.     integer records_count = size(cols_lenghts) - 1;
  728.     // total length of underline
  729.     integer total_size = 0;
  730.  
  731.     foreach (integer col_size, cols_lenghts, {
  732.         total_size = total_size + col_size;
  733.         // adding padding where necessary
  734.         if (col_counter<records_count) {
  735.         total_size = total_size + horizontal_padding;
  736.         }
  737.         col_counter = col_counter + 1;
  738.     });
  739.  
  740.     return CreateUnderline(total_size);
  741.     }
  742.  
  743.     /**
  744.      * Function creates text table without using HTML tags.
  745.      * (Useful for commandline)
  746.      * Undefined option uses the default one.
  747.      *
  748.      * @param    list <string> header
  749.      * @param    list <list <string> > items
  750.      * @param    map <string, string> options
  751.      * @return    string table
  752.      *
  753.      * Header: [ "Id", "Configuration", "Device" ]
  754.      * Items: [ [ "1", "aaa", "Samsung Calex" ], [ "2", "bbb", "Trivial Trinitron" ] ]
  755.      * Possible Options: horizontal_padding (for columns), table_left_padding (for table)
  756.      */
  757.     global define string TextTable (list<string> header, list <list <string> > items, map <string, any> options) {
  758.  
  759.     integer current_horizontal_padding    = (integer) options["horizontal_padding"]:2;
  760.     integer current_table_left_padding    = (integer) options["table_left_padding"]:4;
  761.  
  762.     list <integer> cols_lenghts = FindLongestRecords(add(items, header));
  763.  
  764.     // whole table is left-padded
  765.     string table_left_padding = String::Pad("", current_table_left_padding);
  766.     // the last row has no newline
  767.     integer rows_count = size(items);
  768.     string table = "";
  769.  
  770.     table = table + table_left_padding
  771.         + CreateTableRow(header, cols_lenghts, current_horizontal_padding) + "\n";
  772.     table = table + table_left_padding
  773.         + CreateTableHeaderUnderline(cols_lenghts, current_horizontal_padding) + "\n";
  774.     integer rows_counter = 1;
  775.     foreach (list <string> row, items, {
  776.         table = table + table_left_padding
  777.         + CreateTableRow(row, cols_lenghts, current_horizontal_padding) +
  778.         (rows_counter<rows_count ? "\n":"");
  779.         rows_counter = rows_counter + 1;
  780.     });
  781.     return table;
  782.     }
  783.  
  784.     /**
  785.      * Function returns underlined text header without using HTML tags.
  786.      * (Useful for commandline)
  787.      *
  788.      * @param    string header line
  789.      * @param    integer left padding
  790.      * @return    string underlined header line
  791.      */
  792.     global define string UnderlinedHeader (string header_line, integer left_padding) {
  793.     
  794.     return
  795.         Pad("", left_padding) + header_line + "\n" +
  796.         Pad("", left_padding) + CreateUnderline(size(header_line));
  797.     }
  798.  
  799.  
  800.  
  801. //////////////////////////////////////////
  802. // sysconfig metadata related functions //
  803. //////////////////////////////////////////
  804.  
  805. /**
  806.  * Get metadata lines from input string
  807.  * @param input Input string - complete comment of a sysconfig variable
  808.  * @return list<string> Metadata lines in list
  809.  */
  810. global define list<string> GetMetaDataLines(string input) ``{
  811.     if (input == nil || input == "")
  812.     {
  813.     return [];
  814.     }
  815.  
  816.     list<string> lines = splitstring(input, "\n");
  817.     return (filter(string line, lines, ``(regexpmatch(line, "^##.*"))));
  818. }
  819.  
  820. /**
  821.  * Get comment without metadata
  822.  * @param input Input string - complete comment of a sysconfig variable
  823.  * @return string Comment used as variable description
  824.  */
  825. global define string GetCommentLines(string input) ``{
  826.     if (input == nil || input == "")
  827.     {
  828.     return "";
  829.     }
  830.  
  831.     list<string> lines = splitstring(input, "\n");
  832.  
  833.     string ret = "";
  834.  
  835.     foreach(string line, lines, ``{
  836.         string com_line = regexpsub(line, "^#([^#].*)", "\\1");
  837.  
  838.         if (com_line == nil)
  839.         {
  840.         // add empty lines
  841.         if (regexpmatch(line, "^#[ \t]*$") == true)
  842.         {
  843.             ret = ret + "\n";
  844.         }
  845.         }
  846.         else
  847.         {
  848.         ret = ret + com_line + "\n";
  849.         }
  850.     }
  851.     );
  852.  
  853.     return ret;
  854. }
  855.  
  856. /**
  857.  * Parse metadata from a sysconfig comment
  858.  * @param comment comment of a sysconfig variable (single line or multiline string)
  859.  * @return map parsed metadata
  860.  */
  861. global define map<string, string> ParseSysconfigComment(string comment) ``{
  862.     map<string, string> ret = $[];
  863.  
  864.     // get metadata part of comment
  865.     list<string> metalines = GetMetaDataLines(comment);
  866.     list<string> joined_multilines = [];
  867.     string multiline = "";
  868.  
  869.     y2debug("metadata: %1", metalines);
  870.  
  871.     // join multi line metadata lines
  872.     foreach(string metaline, metalines, ``{
  873.         if (substring(metaline, size(metaline) - 1, 1) != "\\")
  874.         {
  875.         if (multiline != "")
  876.         {
  877.             // this not first multiline so remove comment mark
  878.             string without_comment = regexpsub(metaline, "^##(.*)", "\\1");
  879.  
  880.             if (without_comment != nil)
  881.             {
  882.             metaline = without_comment;
  883.             }
  884.         }
  885.         joined_multilines = add(joined_multilines, multiline + metaline);
  886.         multiline = "";
  887.         }
  888.         else
  889.         {
  890.         string part = substring(metaline, 0, size(metaline) - 1);
  891.  
  892.         if (multiline != "")
  893.         {
  894.             // this not first multiline so remove comment mark
  895.             string without_comment = regexpsub(part, "^##(.*)", "\\1");
  896.  
  897.             if (without_comment != nil)
  898.             {
  899.             part = without_comment;
  900.             }
  901.         }
  902.  
  903.         // add line to the previous lines
  904.         multiline = multiline + part;
  905.         }
  906.     }
  907.     );
  908.  
  909.     y2debug("metadata after multiline joining: %1", joined_multilines);
  910.  
  911.     // parse each metadata line
  912.     foreach(string metaline, joined_multilines, ``{
  913.  
  914.         /* Ignore lines with ### -- general comments */
  915.         if (regexpmatch(metaline, "^###"))
  916.         {
  917.         return;
  918.         }
  919.  
  920.         string meta = regexpsub(metaline, "^##[ \t]*(.*)", "\\1");
  921.  
  922.         // split sting to the tag and value part
  923.         integer colon_pos = findfirstof(meta, ":");
  924.         string tag = "";
  925.         string val = "";
  926.  
  927.         if (colon_pos == nil)
  928.         {
  929.         // colon is missing
  930.         tag = meta;
  931.         }
  932.         else
  933.         {
  934.         tag = substring(meta, 0, colon_pos);
  935.  
  936.         if (size(meta) > colon_pos + 1)
  937.         {
  938.             val = substring(meta, colon_pos + 1);
  939.         }
  940.         }
  941.  
  942.         // remove whitespaces from parts
  943.         tag = CutBlanks(tag);
  944.         val = CutBlanks(val);
  945.  
  946.         y2debug("tag: %1 val: '%2'", tag, val);
  947.  
  948.         // add tag and value to map if they are present in comment
  949.         if (tag != "")
  950.         {
  951.         ret = (map<string, string>) add(ret, tag, val);
  952.         }
  953.         else
  954.         {
  955.         // ignore separator lines
  956.         if (!regexpmatch(metaline, "^#*$"))
  957.         {
  958.             y2warning("Unknown metadata line: %1", metaline);
  959.         }
  960.         }
  961.     }
  962.     );
  963.  
  964.     y2debug("parsed sysconfig comment: %1", ret);
  965.  
  966.     return ret;
  967. }
  968.  
  969. /**
  970.  * Replace substring in a string. All substrings source are replaced by string target.
  971.  * @param s input string
  972.  * @param source the string which will be replaced
  973.  * @param target the new string which is used instead of source
  974.  * @return string result
  975.  */
  976. global string Replace(string s, string source, string target) {
  977.     if (s == nil)
  978.     {
  979.     return nil;
  980.     }
  981.  
  982.     if (source == nil || source == "")
  983.     {
  984.     y2warning("invalid parameter source: %1", source);
  985.     return s;
  986.     }
  987.  
  988.     if (target == nil)
  989.     {
  990.     y2warning("invalid parameter target: %1", target);
  991.     return s;
  992.     }
  993.  
  994.     integer pos = find(s, source);
  995.     while (pos >= 0) {
  996.     string tmp = substring(s, 0, pos) + target;
  997.     if (size(s) > pos + size(source))
  998.     {
  999.         tmp = tmp + substring(s, pos + size(source));
  1000.     }
  1001.  
  1002.     s = tmp;
  1003.  
  1004.     pos = find(s, source);
  1005.     }
  1006.  
  1007.     return s;
  1008. }
  1009.  
  1010. /**
  1011.  * Returns text wrapped at defined margin. Very useful for translated strings
  1012.  * used for pop-up windows or dialogs where you can't know the width. It
  1013.  * controls the maximum width of the string so the text should allways fit into
  1014.  * the minimal ncurses window. If you expect some long words, such us URLs or
  1015.  * words with a hyphen inside, you can also set the additional split-characters
  1016.  * to "/-". Then the function can wrap the word also after these characters.
  1017.  * This function description was wrapped using the function String::WrapAt().
  1018.  *
  1019.  * @example String::WrapAt("Some very long text",30,"/-");
  1020.  *
  1021.  * @param string text to be wrapped
  1022.  * @param integer maximum width of the wrapped text
  1023.  * @param string additional split-characters such as "-" or "/"
  1024.  * @return string wrapped string
  1025.  */
  1026. global string WrapAt(string text, integer width, string split_string) {
  1027.     string new_string = "";
  1028.     integer avail = width;  // characters available in this line
  1029.     string lsep = "";  // set to "\n" when at the beginning of a new line
  1030.     string wsep = "";  // set to " " after words, unless at the beginning
  1031.  
  1032.     foreach (string word, splitstring(text, " \n"), {
  1033.     while (size(word) > 0) {
  1034.     // decide where to split the current word
  1035.     integer split_at = 0;
  1036.     if (size(word) <= width) {
  1037.         split_at = size(word);
  1038.     } else {
  1039.         split_at = findlastof(substring(word, 0, avail - size(wsep)),
  1040.                       " \n" + split_string);
  1041.         if (split_at != nil) {
  1042.         split_at = split_at + 1;
  1043.         } else {
  1044.         split_at = findlastof(substring(word, 0, width),
  1045.                           " \n" + split_string);
  1046.         if (split_at != nil) {
  1047.             split_at = split_at + 1;
  1048.         } else {
  1049.             split_at = avail - size(wsep);
  1050.         }
  1051.         }
  1052.     }
  1053.  
  1054.     // decide whether it fits into the same line or must go on
  1055.     // a separate line
  1056.     if (size(wsep) + split_at > avail) {
  1057.         if (size(new_string) > 0)
  1058.         new_string = new_string + "\n";
  1059.         avail = width;
  1060.         wsep = "";
  1061.         lsep = "";
  1062.     }
  1063.  
  1064.     // add the next word or partial word
  1065.     new_string = new_string + lsep + wsep +
  1066.              substring(word, 0, split_at);
  1067.     avail = avail - size(wsep) - split_at;
  1068.     wsep = ""; lsep = "";
  1069.     if (avail == 0) {
  1070.         avail = width;
  1071.         lsep = "\n";
  1072.     } else if (split_at == size(word)) {
  1073.         wsep = " ";
  1074.     }
  1075.     word = substring(word, split_at);
  1076.     }
  1077.     });
  1078.  
  1079.     return new_string;
  1080. }
  1081.  
  1082. /**
  1083.  * Make a random base-36 number.
  1084.  * srandom should be called beforehand.
  1085.  * @param len string length
  1086.  * @return random string of 0-9 and a-z
  1087.  */
  1088. global string Random (integer len) {
  1089.     if (len <= 0)
  1090.     {
  1091.     return "";
  1092.     }
  1093.     string digits = cdigit + clower; // uses the character classes from above
  1094.     integer base = size (digits);
  1095.     integer max = 1;
  1096.     integer i = len;
  1097.     while (i > 0)
  1098.     {
  1099.     max = max * base;
  1100.     i = i - 1;
  1101.     }
  1102.     integer rnum = random (max);
  1103.     string ret = "";
  1104.     i = len;
  1105.     while (i > 0)
  1106.     {
  1107.     integer digit = rnum % base;
  1108.     rnum = rnum / base;
  1109.     ret = ret + substring (digits, digit, 1);
  1110.     i = i -1;
  1111.     }
  1112.     return ret;
  1113. }
  1114.  
  1115. /* EOF */
  1116. }
  1117.