ACC SHELL
/**
* File: modules/String.ycp
* Package: yast2
* Summary: String manipulation routines
* Authors: Michal Svec <msvec@suse.cz>
*
* $Id: String.ycp 57542 2009-06-11 16:48:30Z lslezak $
*/
{
module "String";
textdomain "base";
/**
* Quote a string with 's
*
* More precisely it protects single quotes inside the string but does not
* prepend or append single quotes.
*
* @param var unquoted string
* @return quoted string
* @example quote("a'b") -> "a'\''b"
*/
global define string Quote(string var) ``{
if(var == nil || var == "") return "";
return mergestring(splitstring(var, "'"), "'\\''");
}
/**
* Unquote a string with 's (quoted with quote)
* @param var quoted string
* @return unquoted string
* @see quote
*/
global define string UnQuote(string var) ``{
if(var == nil || var == "") return "";
y2debug("var=%1", var);
while(regexpmatch(var, "'\\\\''")) {
var = regexpsub(var, "(.*)'\\\\''(.*)", "\\1'\\2");
y2debug("var=%1", var);
}
return var;
}
/**
* Optional formatted text
* @return sformat (f, s) if s is neither empty or nil, else ""
*/
global string OptFormat (string f, string s) {
return (s == "" || s == nil)? "": sformat (f, s);
}
/**
* Optional parenthesized text
* @return " (Foo)" if Foo is neither empty or nil, else ""
*/
global string OptParens (string s) {
return OptFormat (" (%1)", s);
}
/**
* @param l a list of strings
* @return only non-"" items
*/
global list<string> NonEmpty (list<string> l) {
return filter (string i, l, ``( i != "" ));
}
/**
* @param s \n-terminated items
* @return the items as a list, with empty lines removed
*/
global list<string> NewlineItems (string s) {
return NonEmpty (splitstring (s, "\n"));
}
/**
* Return a pretty description of a byte count
*
* Return a pretty description of a byte count with required precision
* and using B, kB, MB, GB or TB as unit as appropriate.
*
* Uses the current locale defined decimal separator
* (i.e. the result is language dependant).
*
* @param bytes size (e.g. free diskspace, memory size) in Bytes
* @param precision number of fraction digits in output
* @param omit_zeroes if true then do not add zeroes
* (useful for memory size - 128 MB RAM looks better than 128.00 MB RAM)
* @return formatted string
*
* @example FormatSizeWithPrecision(128, 2, true) -> "128 B"
* @example FormatSizeWithPrecision(4096, 2, true) -> "4 kB"
* @example FormatSizeWithPrecision(4096, 2, false) -> "4.00 kB"
* @example FormatSizeWithPrecision(1024*1024, 2, true) -> "1 MB"
*/
global define string FormatSizeWithPrecision(integer bytes, integer precision, boolean omit_zeroes) ``{
if(bytes == nil) return "";
const list units = [
/* Byte abbreviated */
_("B"),
/* KiloByte abbreviated */
_("kB"),
/* MegaByte abbreviated */
_("MB"),
/* GigaByte abbreviated */
_("GB"),
/* TeraByte abbreviated */
_("TB"),
];
integer index = 0;
float whole = tofloat(bytes);
while((whole >= 1024.0 || whole <= -1024.0) && index + 1 < size(units)) {
whole = whole / 1024.0;
index = index + 1;
}
if (precision == nil || precision < 0 || (omit_zeroes == true && (whole - tointeger(whole) == 0.0)))
{
precision = 0;
}
return float::tolstring(whole, precision) + " " + units[index]:"";
}
/**
* Return a pretty description of a byte count
*
* Return a pretty description of a byte count, with two fraction digits
* and using B, kB, MB, GB or TB as unit as appropriate.
*
* Uses the current locale defined decimal separator
* (i.e. the result is language dependant).
*
* @param bytes size (e.g. free diskspace) in Bytes
* @return formatted string
*
* @example FormatSize(23456767890) -> "223.70 MB"
*/
global define string FormatSize(integer bytes) ``{
return FormatSizeWithPrecision(bytes, 2, false);
}
/**
* Return a pretty description of a download rate
*
* Return a pretty description of a download rate, with two fraction digits
* and using B/s, kB/s, MB/s, GB/s or TB/s as unit as appropriate.
*
* @param bytes_per_second download rate (in B/s)
* @return formatted string
*
* @example FormatRate(6780) -> ""
* @example FormatRate(0) -> ""
* @example FormatRate(895321) -> ""
*/
global string FormatRate(integer bytes_per_second)
{
// covert a number to download rate string
// %1 is string - size in bytes, B, kB, MB, GB or TB
return sformat(_("%1/s"), FormatSize(bytes_per_second));
}
/**
* Add a download rate status to a message.
*
* Add the current and the average download rate to the message.
*
* @param text the message with %1 placeholder for the download rate string
* @param avg_bps average download rate (in B/s)
* @param curr_bps current download rate (in B/s)
*
* @return string formatted message
*/
global string FormatRateMessage(string text, integer avg_bps, integer curr_bps)
{
string rate = "";
if (curr_bps > 0)
{
rate = FormatRate(curr_bps);
if (avg_bps > 0)
{
// format download rate message: %1 = the current download rate (e.g. "242.6kB/s")
// %2 is the average download rate (e.g. "228.3kB/s")
// to translators: keep translation of "on average" as short as possible
rate = sformat(_("%1 (on average %2)"), rate, String::FormatRate(avg_bps));
}
}
// add download rate to the downloading message
// %1 is URL, %2 is formatted download rate, e.g. "242.6kB/s (avg. 228.3kB/s)"
// in ncurses UI the strings are exchanged (%1 is the rate, %2 is URL)
// due to limited space on the screen
return sformat(text, rate);
}
/**
* Format an integer number as (at least) two digits; use leading zeroes if
* necessary.
* @param x input
* @return string number as two-digit string
**/
string FormatTwoDigits( integer x )
{
return x < 10 && x >= 0 ?
sformat( "0%1", x ) :
sformat( "%1", x );
}
/**
* Format an integer seconds value with min:sec or hours:min:sec
* @param seconds time (in seconds)
* @return string formatted string (empty for negative values)
**/
global string FormatTime( integer seconds )
{
if ( seconds < 0 )
return "";
if ( seconds < 3600 ) // Less than one hour
{
return sformat( "%1:%2", FormatTwoDigits( seconds / 60 ), FormatTwoDigits( seconds % 60 ) );
}
else // More than one hour - we don't hope this will ever happen, but who knows?
{
integer hours = seconds / 3600;
seconds = seconds % 3600;
return sformat( "%1:%2:%3", hours, FormatTwoDigits( seconds / 60 ), FormatTwoDigits( seconds % 60 ) );
}
}
/**
* Remove blanks at begin and end of input string.
* @param input string to be stripped
* @return stripped string
* @example CutBlanks(" any input ") -> "any input"
*/
global define string CutBlanks(string input) ``{
if(input == nil || size(input) < 1) return "";
integer pos1 = findfirstnotof(input, " \t");
if(pos1 == nil) return "";
integer pos2 = findlastnotof(input, " \t");
return substring(input, pos1, pos2 - pos1 + 1);
}
/**
* Remove any leading zeros
*
* Remove any leading zeros that make tointeger inadvertently
* assume an octal number (e.g. "09" -> "9", "0001" -> "1",
* but "0" -> "0")
*
* @param input number that might contain leadig zero
* @return string that has leading zeros removed
*/
global define string CutZeros(string input) ``{
if(input == nil || size(input) < 1) return "";
if(!regexpmatch(input, "^0.*")) return input;
string output = regexpsub(input, "^0+(.*)$", "\\1");
if(size(output) < 1) return "0";
return output;
}
/**
* Add spaces after the text to make it long enough
*
* Add spaces after the text to make it long enough. If the text is longer
* than requested, no changes are made.
*
* @param text text to be padded
* @param length requested length
* @return padded text
*/
global define string Pad(string text, integer length) {
if(text == nil) text = "";
integer rest = length - size(text);
string pad = "";
while(rest > 0) {
pad = pad + " ";
rest = rest - 1;
}
return text + pad;
}
/**
* Add zeros before the text to make it long enough.
*
* Add zeros before the text to make it long enough. If the text is longer
* than requested, no changes are made.
*
* @param text text to be padded
* @param length requested length
* @return padded text
*/
global define string PadZeros(string text, integer length) {
if(text == nil) text = "";
integer rest = length - size(text);
string pad = "";
while(rest > 0) {
pad = pad + "0";
rest = rest - 1;
}
return pad + text;
}
/**
* Parse string of values
*
* Parse string of values - split string to values, quoting and backslash sequences are supported
* @param options Input string
* @param parameters Parmeter used at parsing - map with keys:
*"separator":<string> - value separator (default: " \t"),
*"unique":<boolean> - result will not contain any duplicates, first occurance of the string is stored into output (default: false),
*"interpret_backslash":<boolean> - convert backslash sequence into one character (e.g. "\\n" => "\n") (default: true)
*"remove_whitespace":<boolean> - remove white spaces around values (default: true),
* @return list<string> List of strings
*/
global define list<string> ParseOptions(string options, map parameters) ``{
list<string> ret = [];
// parsing options
string separator = parameters["separator"]:" \t";
boolean unique = parameters["unique"]:false;
boolean interpret_backslash = parameters["interpret_backslash"]:true;
boolean remove_whitespace = parameters["remove_whitespace"]:true;
y2debug("Input: string: '%1', parameters: %2", options, parameters);
y2debug("Used values: separator: '%1', unique: %2, remove_whitespace: %3",
separator, unique, remove_whitespace);
if (options == nil)
{
return [];
}
// two algorithms are used:
// first is much faster, but only usable if string
// doesn't contain any double qoute characters
// and backslash sequences are not interpreted
// second is more general, but of course slower
if (findfirstof(options, "\"") == nil && interpret_backslash == false)
{
// easy case - no qouting, don't interpres backslash sequences => use splitstring
list<string> values = splitstring(options, separator);
foreach (string v, values, ``{
if (remove_whitespace == true)
{
v = CutBlanks(v);
}
if (unique == true)
{
if (!contains(ret, v)) ret = add(ret, v);
}
else
{
ret = add(ret, v);
}
});
}
else
{
// quoting is used or backslash interpretation is enabled
// so it' not possible to split input
// parsing each character is needed - use finite automaton
// state
symbol state = `out_of_string;
// position in the input string
integer index = 0;
// parsed value - buffer
string str = "";
while(index < size(options))
{
string character = substring(options, index, 1);
y2debug("character: %1 state: %2 index: %3", character, state, index);
// interpret backslash sequence
if (character == "\\" && interpret_backslash == true)
{
if (index + 1 < size(options))
{
string nextcharacter = substring(options, index + 1, 1);
index = index + 1;
// backslah sequences
map backslash_seq = $[
"a" : "\a", // alert
"b" : "\b", // backspace
"e" : "\e", // escape
"f" : "\f", // FF
"n" : "\n", // NL
"r" : "\r", // CR
"t" : "\t", // tab
"v" : "\v", // vertical tab
"\\": "\\", // backslash
];
if (haskey(backslash_seq, nextcharacter) == true)
{
character = backslash_seq[nextcharacter]:"DUMMY";
}
else
{
if (nextcharacter != "\"")
{
// ignore backslash in invalid backslash sequence
character = nextcharacter;
}
else
{
// backslash will be removed later,
// double quote and escaped double quote have to different yet
character = "\\\"";
}
}
y2debug("backslash sequence: '%1'", character);
}
else
{
y2warning("Missing character after backslash (\\) at the end of string");
}
}
if (state == `out_of_string)
{
// ignore separator or white space at the beginning of the string
if (issubstring(separator, character) == true || (remove_whitespace == true && (character == " " || character == "\t")) )
{
index = index + 1;
continue;
}
// start of a quoted string
else if (character == "\"")
{
state = `in_quoted_string;
}
else
{
// start of a string
state = `in_string;
if (character == "\\\"")
{
str = "\"";
}
else
{
str = character;
}
}
}
// after double quoted string - handle non-separator chars after double quote
else if (state == `in_quoted_string_after_dblqt)
{
if (issubstring(separator, character) == true)
{
if (unique == true)
{
if (!contains(ret, str)) ret = add(ret, str);
}
else
{
ret = add(ret, str);
}
str = "";
state = `out_of_string;
}
else if (character == "\\\"")
{
str = str + "\"";
}
else
{
str = str + character;
}
}
else if (state == `in_quoted_string)
{
if (character == "\"")
{
// end of quoted string
state = `in_quoted_string_after_dblqt;
}
else if (character == "\\\"")
{
str = str + "\"";
}
else
{
str = str + character;
}
}
else if (state == `in_string)
{
if (issubstring(separator, character) == true)
{
state = `out_of_string;
if (remove_whitespace == true)
{
str = CutBlanks(str);
}
if (unique == true)
{
if (!contains(ret, str)) ret = add(ret, str);
}
else
{
ret = add(ret, str);
}
str = "";
}
else if (character == "\\\"")
{
str = str + "\"";
}
else
{
str = str + character;
}
}
index = index + 1;
}
// error - still in quoted string
if (state == `in_quoted_string || state == `in_quoted_string_after_dblqt)
{
if (state == `in_quoted_string)
{
y2warning("Missing trainling double quote character(\") in input: '%1'", options);
}
if (unique == true)
{
if (!contains(ret, str)) ret = add(ret, str);
}
else
{
ret = add(ret, str);
}
}
// process last string in the buffer
if (state == `in_string)
{
if (remove_whitespace)
{
str = CutBlanks(str);
}
if (unique == true)
{
if (!contains(ret, str)) ret = add(ret, str);
}
else
{
ret = add(ret, str);
}
}
}
y2debug("Parsed values: %1", ret);
return ret;
}
/**
* Remove first or every match of given regular expression from a string
*
* (e.g. CutRegexMatch( "abcdef12ef34gh000", "[0-9]+", true ) -> "abcdefefgh",
* CutRegexMatch( "abcdef12ef34gh000", "[0-9]+", false ) -> "abcdefef34gh000")
*
* @param input string that might occur regex
* @param regex regular expression to search for, must not contain brackets
* @param glob flag if only first or every occuring match should be removed
* @return string that has matches removed
*/
global define string CutRegexMatch(string input, string regex, boolean glob) ``{
if(input == nil || size(input) < 1) return "";
string output = input;
if( regexpmatch( output, regex ) )
{
list p = regexppos( output, regex );
do
{
output = substring( output, 0, p[0]:0 ) +
substring( output, p[0]:0+p[1]:0 );
p = regexppos( output, regex );
}
while( glob && size(p)>0 );
}
return output;
}
/**
* Function for escaping (replacing) (HTML|XML...) tags with their
* (HTML|XML...) meaning.
*
* Usable to present text "as is" in RichText.
*
* @param string text to escape
* @return string escaped text
*/
global define string EscapeTags (string text) ``{
text = mergestring(splitstring(text, "&"), "&");
text = mergestring(splitstring(text, "<"), "<");
text = mergestring(splitstring(text, ">"), ">");
return text;
}
/**
* Shorthand for select (splitstring (s, separators), 0, "")
* Useful now that the above produces a deprecation warning.
* @param s string to be split
* @param separators characters which delimit components
* @return first component or ""
*/
global string FirstChunk (string s, string separators) {
list <string> l = splitstring (s, separators);
return l[0]:"";
}
// character sets, suitable for ValidChars
string cupper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string clower = "abcdefghijklmnopqrstuvwxyz";
string calpha = cupper + clower;
string cdigit = "0123456789";
string cxdigit = cdigit + "ABCDEFabcdef";
string calnum = calpha + cdigit;
string cpunct = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
string cgraph = calnum + cpunct;
string cspace = " \f\r\n\t\013";
string cprint = cspace + cgraph;
/**
* The 26 uppercase ASCII letters
*/
global string CUpper () {
return cupper;
}
/**
* The 26 lowercase ASCII letters
*/
global string CLower () {
return clower;
}
/**
* The 52 upper and lowercase ASCII letters
*/
global string CAlpha () {
return calpha;
}
/**
* Digits: 0123456789
*/
global string CDigit () {
return cdigit;
}
/**
* Hexadecimal digits: 0123456789ABCDEFabcdef
*/
global string CXdigit () {
return cxdigit;
}
/**
* The 62 upper and lowercase ASCII letters and digits
*/
global string CAlnum () {
return calnum;
}
/**
* The ASCII printable non-blank non-alphanumeric characters
*/
global string CPunct () {
return cpunct;
}
/**
* Printable ASCII charcters except whitespace, 33-126
*/
global string CGraph () {
return cgraph;
}
/**
* ASCII whitespace: SPACE CR LF HT VT FF
*/
global string CSpace () {
return cspace;
}
/**
* Printable ASCII characters including whitespace
*/
global string CPrint () {
return cprint;
}
/**
* Characters valid in a filename (not pathname).
* Naturally "/" is disallowed. Otherwise, the graphical ASCII
* characters are allowed.
* @return string for ValidChars
*/
global string ValidCharsFilename () {
return deletechars (CGraph (), "/");
}
// 64 characters is the base undeline length
string base_underline = "----------------------------------------------------------------";
/* - hidden for documentation -
*
* Local function for finding longest records in the table.
*
* @param list <list <string> > table items
* @return list <integer> longest records by columns
*/
list <integer> FindLongestRecords (list <list <string> > items) {
list <integer> longest = [];
// searching all rows
foreach (list <string> row, items, {
// starting with column 0
integer col_counter = 0;
// testing all columns on the row
foreach (string col, row, {
integer col_size = size(col);
// found longer record for this column
if (col_size>longest[col_counter]:-1) longest[col_counter] = col_size;
// next column
col_counter = col_counter + 1;
});
});
return longest;
}
/* - hidden for documentation -
*
* Local function creates table row.
*
* @param list <string> row items
* @param list <integer> columns lengths
* @param integer record horizontal padding
* @return string padded table row
*/
string CreateTableRow (list <string> row_items, list <integer> cols_lenghts, integer horizontal_padding) {
string row = "";
integer col_counter = 0;
integer records_count = size(row_items) - 1;
foreach (string record, row_items, {
integer padding = cols_lenghts[col_counter]:0;
if (col_counter<records_count) padding = padding + horizontal_padding;
row = row + String::Pad(record, padding);
col_counter = col_counter + 1;
});
return row;
}
/* - hidden for documentation -
*
* Local function returns underline string /length/ long.
*
* @param integer length of underline
* @return string /length/ long underline
*/
string CreateUnderline (integer length) {
string underline = base_underline;
while (size(underline)<length) {
underline = underline + base_underline;
}
underline = substring(underline, 0, length);
return underline;
}
/* - hidden for documentation -
*
* Local function for creating header underline for table.
* It uses maximal lengths of records defined in cols_lenghts.
*
* @param list <integer> maximal lengths of records in columns
* @param integer horizontal padding of records
* @return string table header underline
*/
string CreateTableHeaderUnderline (list <integer> cols_lenghts, integer horizontal_padding) {
integer col_counter = 0;
// count of added paddings
integer records_count = size(cols_lenghts) - 1;
// total length of underline
integer total_size = 0;
foreach (integer col_size, cols_lenghts, {
total_size = total_size + col_size;
// adding padding where necessary
if (col_counter<records_count) {
total_size = total_size + horizontal_padding;
}
col_counter = col_counter + 1;
});
return CreateUnderline(total_size);
}
/**
* Function creates text table without using HTML tags.
* (Useful for commandline)
* Undefined option uses the default one.
*
* @param list <string> header
* @param list <list <string> > items
* @param map <string, string> options
* @return string table
*
* Header: [ "Id", "Configuration", "Device" ]
* Items: [ [ "1", "aaa", "Samsung Calex" ], [ "2", "bbb", "Trivial Trinitron" ] ]
* Possible Options: horizontal_padding (for columns), table_left_padding (for table)
*/
global define string TextTable (list<string> header, list <list <string> > items, map <string, any> options) {
integer current_horizontal_padding = (integer) options["horizontal_padding"]:2;
integer current_table_left_padding = (integer) options["table_left_padding"]:4;
list <integer> cols_lenghts = FindLongestRecords(add(items, header));
// whole table is left-padded
string table_left_padding = String::Pad("", current_table_left_padding);
// the last row has no newline
integer rows_count = size(items);
string table = "";
table = table + table_left_padding
+ CreateTableRow(header, cols_lenghts, current_horizontal_padding) + "\n";
table = table + table_left_padding
+ CreateTableHeaderUnderline(cols_lenghts, current_horizontal_padding) + "\n";
integer rows_counter = 1;
foreach (list <string> row, items, {
table = table + table_left_padding
+ CreateTableRow(row, cols_lenghts, current_horizontal_padding) +
(rows_counter<rows_count ? "\n":"");
rows_counter = rows_counter + 1;
});
return table;
}
/**
* Function returns underlined text header without using HTML tags.
* (Useful for commandline)
*
* @param string header line
* @param integer left padding
* @return string underlined header line
*/
global define string UnderlinedHeader (string header_line, integer left_padding) {
return
Pad("", left_padding) + header_line + "\n" +
Pad("", left_padding) + CreateUnderline(size(header_line));
}
//////////////////////////////////////////
// sysconfig metadata related functions //
//////////////////////////////////////////
/**
* Get metadata lines from input string
* @param input Input string - complete comment of a sysconfig variable
* @return list<string> Metadata lines in list
*/
global define list<string> GetMetaDataLines(string input) ``{
if (input == nil || input == "")
{
return [];
}
list<string> lines = splitstring(input, "\n");
return (filter(string line, lines, ``(regexpmatch(line, "^##.*"))));
}
/**
* Get comment without metadata
* @param input Input string - complete comment of a sysconfig variable
* @return string Comment used as variable description
*/
global define string GetCommentLines(string input) ``{
if (input == nil || input == "")
{
return "";
}
list<string> lines = splitstring(input, "\n");
string ret = "";
foreach(string line, lines, ``{
string com_line = regexpsub(line, "^#([^#].*)", "\\1");
if (com_line == nil)
{
// add empty lines
if (regexpmatch(line, "^#[ \t]*$") == true)
{
ret = ret + "\n";
}
}
else
{
ret = ret + com_line + "\n";
}
}
);
return ret;
}
/**
* Parse metadata from a sysconfig comment
* @param comment comment of a sysconfig variable (single line or multiline string)
* @return map parsed metadata
*/
global define map<string, string> ParseSysconfigComment(string comment) ``{
map<string, string> ret = $[];
// get metadata part of comment
list<string> metalines = GetMetaDataLines(comment);
list<string> joined_multilines = [];
string multiline = "";
y2debug("metadata: %1", metalines);
// join multi line metadata lines
foreach(string metaline, metalines, ``{
if (substring(metaline, size(metaline) - 1, 1) != "\\")
{
if (multiline != "")
{
// this not first multiline so remove comment mark
string without_comment = regexpsub(metaline, "^##(.*)", "\\1");
if (without_comment != nil)
{
metaline = without_comment;
}
}
joined_multilines = add(joined_multilines, multiline + metaline);
multiline = "";
}
else
{
string part = substring(metaline, 0, size(metaline) - 1);
if (multiline != "")
{
// this not first multiline so remove comment mark
string without_comment = regexpsub(part, "^##(.*)", "\\1");
if (without_comment != nil)
{
part = without_comment;
}
}
// add line to the previous lines
multiline = multiline + part;
}
}
);
y2debug("metadata after multiline joining: %1", joined_multilines);
// parse each metadata line
foreach(string metaline, joined_multilines, ``{
/* Ignore lines with ### -- general comments */
if (regexpmatch(metaline, "^###"))
{
return;
}
string meta = regexpsub(metaline, "^##[ \t]*(.*)", "\\1");
// split sting to the tag and value part
integer colon_pos = findfirstof(meta, ":");
string tag = "";
string val = "";
if (colon_pos == nil)
{
// colon is missing
tag = meta;
}
else
{
tag = substring(meta, 0, colon_pos);
if (size(meta) > colon_pos + 1)
{
val = substring(meta, colon_pos + 1);
}
}
// remove whitespaces from parts
tag = CutBlanks(tag);
val = CutBlanks(val);
y2debug("tag: %1 val: '%2'", tag, val);
// add tag and value to map if they are present in comment
if (tag != "")
{
ret = (map<string, string>) add(ret, tag, val);
}
else
{
// ignore separator lines
if (!regexpmatch(metaline, "^#*$"))
{
y2warning("Unknown metadata line: %1", metaline);
}
}
}
);
y2debug("parsed sysconfig comment: %1", ret);
return ret;
}
/**
* Replace substring in a string. All substrings source are replaced by string target.
* @param s input string
* @param source the string which will be replaced
* @param target the new string which is used instead of source
* @return string result
*/
global string Replace(string s, string source, string target) {
if (s == nil)
{
return nil;
}
if (source == nil || source == "")
{
y2warning("invalid parameter source: %1", source);
return s;
}
if (target == nil)
{
y2warning("invalid parameter target: %1", target);
return s;
}
integer pos = find(s, source);
while (pos >= 0) {
string tmp = substring(s, 0, pos) + target;
if (size(s) > pos + size(source))
{
tmp = tmp + substring(s, pos + size(source));
}
s = tmp;
pos = find(s, source);
}
return s;
}
/**
* Returns text wrapped at defined margin. Very useful for translated strings
* used for pop-up windows or dialogs where you can't know the width. It
* controls the maximum width of the string so the text should allways fit into
* the minimal ncurses window. If you expect some long words, such us URLs or
* words with a hyphen inside, you can also set the additional split-characters
* to "/-". Then the function can wrap the word also after these characters.
* This function description was wrapped using the function String::WrapAt().
*
* @example String::WrapAt("Some very long text",30,"/-");
*
* @param string text to be wrapped
* @param integer maximum width of the wrapped text
* @param string additional split-characters such as "-" or "/"
* @return string wrapped string
*/
global string WrapAt(string text, integer width, string split_string) {
string new_string = "";
integer avail = width; // characters available in this line
string lsep = ""; // set to "\n" when at the beginning of a new line
string wsep = ""; // set to " " after words, unless at the beginning
foreach (string word, splitstring(text, " \n"), {
while (size(word) > 0) {
// decide where to split the current word
integer split_at = 0;
if (size(word) <= width) {
split_at = size(word);
} else {
split_at = findlastof(substring(word, 0, avail - size(wsep)),
" \n" + split_string);
if (split_at != nil) {
split_at = split_at + 1;
} else {
split_at = findlastof(substring(word, 0, width),
" \n" + split_string);
if (split_at != nil) {
split_at = split_at + 1;
} else {
split_at = avail - size(wsep);
}
}
}
// decide whether it fits into the same line or must go on
// a separate line
if (size(wsep) + split_at > avail) {
if (size(new_string) > 0)
new_string = new_string + "\n";
avail = width;
wsep = "";
lsep = "";
}
// add the next word or partial word
new_string = new_string + lsep + wsep +
substring(word, 0, split_at);
avail = avail - size(wsep) - split_at;
wsep = ""; lsep = "";
if (avail == 0) {
avail = width;
lsep = "\n";
} else if (split_at == size(word)) {
wsep = " ";
}
word = substring(word, split_at);
}
});
return new_string;
}
/**
* Make a random base-36 number.
* srandom should be called beforehand.
* @param len string length
* @return random string of 0-9 and a-z
*/
global string Random (integer len) {
if (len <= 0)
{
return "";
}
string digits = cdigit + clower; // uses the character classes from above
integer base = size (digits);
integer max = 1;
integer i = len;
while (i > 0)
{
max = max * base;
i = i - 1;
}
integer rnum = random (max);
string ret = "";
i = len;
while (i > 0)
{
integer digit = rnum % base;
rnum = rnum / base;
ret = ret + substring (digits, digit, 1);
i = i -1;
}
return ret;
}
/**
* Format file name - truncate the middle part of the directory to fit to the reqested lenght.
* Path elements in the middle of the string are replaced by ellipsis (...).
* The result migth be longer that requested size if size of the last element
* (with ellipsis) is longer than the requested size. If the requested size is greater than
* size of the input then the string is not modified. The last part (file name) is never removed.
*
* @example FormatFilename("/really/very/long/file/name", 15) -> "/.../file/name"
* @example FormatFilename("/really/very/long/file/name", 5) -> ".../name"
* @example FormatFilename("/really/very/long/file/name", 100) -> "/really/very/long/file/name"
*
* @param file_path file name
* @param len requested maximum lenght of the output
* @return string Truncated file name
*/
global string FormatFilename(string file_path, integer len)
{
if (size(file_path) <= len)
{
return file_path;
}
list<string> dir = splitstring(file_path, "/");
string file = dir[size(dir) - 1]:"";
dir = remove(dir, size(dir) - 1);
// there is a slash at the end, add the directory name
if (file == "")
{
file = dir[size(dir) - 1]:"" + "/";
dir = remove(dir, size(dir) - 1);
}
if (size(mergestring(dir, "/")) <= 3 || size(dir) == 0)
{
// the path is short, replacing by ... cannot help
return file_path;
}
string ret = "";
do
{
// put the ellipsis in the middle of the path
integer ellipsis = size(dir) / 2;
// ellipsis - used to replace part of text to make it shorter
// example: "/really/very/long/file/name", "/.../file/name")
dir[ellipsis] = _("...");
ret = mergestring(add(dir, file), "/");
if (size(ret) > len)
{
// still too long, remove the ellipsis and start a new iteration
dir = remove(dir, ellipsis);
}
else
{
// the size is OK
break;
}
}
while (size(dir) > 0);
return ret;
}
/**
* Remove a shortcut from a label, so that it can be inserted into help
* to avoid risk of different translation of the label
* @param label string a label possibly including a shortcut
* @return string the label without the shortcut mark
*/
global string RemoveShortcut (string label) {
string ret = label;
if (regexpmatch (label, "^(.*[^&])?(&&)*&[[:alnum:]].*$"))
ret = regexpsub (label, "^((.*[^&])?(&&)*)&([[:alnum:]].*)$", "\\1\\4");
return ret;
}
/**
* Checks whether string str starts with test.
*/
global boolean StartsWith(string str, string test)
{
return search(str, test) == 0;
}
/* EOF */
}
ACC SHELL 2018