lib/string: add `Text::escape_to_mk` to escape to Makefiles
[nit.git] / lib / standard / string.nit
index 0529b60..0ffb303 100644 (file)
@@ -181,8 +181,25 @@ abstract class Text
        #
        #     assert "abcd".has_substring("bc",1)            ==  true
        #     assert "abcd".has_substring("bc",2)            ==  false
+       #
+       # Returns true iff all characters of `str` are presents
+       # at the expected index in `self.`
+       # The first character of `str` being at `pos`, the second
+       # character being at `pos+1` and so on...
+       #
+       # This means that all characters of `str` need to be inside `self`.
+       #
+       #     assert "abcd".has_substring("xab", -1)         == false
+       #     assert "abcd".has_substring("cdx", 2)          == false
+       #
+       # And that the empty string is always a valid substring.
+       #
+       #     assert "abcd".has_substring("", 2)             == true
+       #     assert "abcd".has_substring("", 200)           == true
        fun has_substring(str: String, pos: Int): Bool
        do
+               if str.is_empty then return true
+               if pos < 0 or pos + str.length > length then return false
                var myiter = self.chars.iterator_from(pos)
                var itsiter = str.chars.iterator
                while myiter.is_ok and itsiter.is_ok do
@@ -369,6 +386,33 @@ abstract class Text
        #     assert "\na\nb\tc\t".trim          == "a\nb\tc"
        fun trim: SELFTYPE do return (self.l_trim).r_trim
 
+       # Justify a self in a space of `length`
+       #
+       # `left` is the space ratio on the left side.
+       # * 0.0 for left-justified (no space at the left)
+       # * 1.0 for right-justified (all spaces at the left)
+       # * 0.5 for centered (half the spaces at the left)
+       #
+       #     assert "hello".justify(10, 0.0)  == "hello     "
+       #     assert "hello".justify(10, 1.0)  == "     hello"
+       #     assert "hello".justify(10, 0.5)  == "  hello   "
+       #
+       # If `length` is not enough, `self` is returned as is.
+       #
+       #     assert "hello".justify(2, 0.0)   == "hello"
+       #
+       # REQUIRE: left >= 0.0 and left <= 1.0
+       # ENSURE: `self.length <= length implies result.length == length`
+       # ENSURE: `self.length >= length implies result == self
+       fun justify(length: Int, left: Float): SELFTYPE
+       do
+               var diff = length - self.length
+               if diff <= 0 then return self
+               assert left >= 0.0 and left <= 1.0
+               var before = (diff.to_f * left).to_i
+               return " " * before + self + " " * (diff-before)
+       end
+
        # Mangle a string to be a unique string only made of alphanumeric characters
        fun to_cmangle: String
        do
@@ -450,6 +494,50 @@ abstract class Text
        #     assert "\n\"'\\\{\}".escape_to_nit      == "\\n\\\"\\'\\\\\\\{\\\}"
        fun escape_to_nit: String do return escape_more_to_c("\{\}")
 
+       # Escape to POSIX Shell (sh).
+       #
+       # Abort if the text contains a null byte.
+       #
+       #     assert "\n\"'\\\{\}0".escape_to_sh == "'\n\"'\\''\\\{\}0'"
+       fun escape_to_sh: String do
+               var b = new FlatBuffer
+               b.chars.add '\''
+               for i in [0..length[ do
+                       var c = chars[i]
+                       if c == '\'' then
+                               b.append("'\\''")
+                       else
+                               assert without_null_byte: c != '\0'
+                               b.add(c)
+                       end
+               end
+               b.chars.add '\''
+               return b.to_s
+       end
+
+       # Escape to include in a Makefile
+       #
+       # Unfortunately, some characters are not escapable in Makefile.
+       # These characters are `;`, `|`, `\`, and the non-printable ones.
+       # They will be rendered as `"?{hex}"`.
+       fun escape_to_mk: String do
+               var b = new FlatBuffer
+               for i in [0..length[ do
+                       var c = chars[i]
+                       if c == '$' then
+                               b.append("$$")
+                       else if c == ':' or c == ' ' or c == '#' then
+                               b.add('\\')
+                               b.add(c)
+                       else if c.ascii < 32 or c == ';' or c == '|' or c == '\\' or c == '=' then
+                               b.append("?{c.ascii.to_base(16, false)}")
+                       else
+                               b.add(c)
+                       end
+               end
+               return b.to_s
+       end
+
        # Return a string where Nit escape sequences are transformed.
        #
        #     var s = "\\n"
@@ -693,11 +781,6 @@ private abstract class StringCharView
 
        private var target: SELFTYPE
 
-       private init(tgt: SELFTYPE)
-       do
-               target = tgt
-       end
-
        redef fun is_empty do return target.is_empty
 
        redef fun length do return target.length
@@ -739,6 +822,8 @@ abstract class String
 
        fun insert_at(s: String, pos: Int): SELFTYPE is abstract
 
+       redef fun substrings: Iterator[String] is abstract
+
        # Returns a reversed version of self
        #
        #     assert "hello".reversed  == "olleh"
@@ -869,8 +954,6 @@ private class FlatSubstringsIter
 
        var tgt: nullable FlatText
 
-       init(tgt: FlatText) do self.tgt = tgt
-
        redef fun item do
                assert is_ok
                return tgt.as(not null)
@@ -1775,12 +1858,12 @@ redef class Int
 end
 
 redef class Float
-       # Pretty print self, print needoed decimals up to a max of 3.
+       # Pretty representation of `self`, with decimals as needed from 1 to a maximum of 3
        #
-       #     assert 12.34.to_s        == "12.34"
-       #     assert (-0120.03450).to_s  == "-120.035"
+       #     assert 12.34.to_s       == "12.34"
+       #     assert (-0120.030).to_s == "-120.03"
        #
-       # see `to_precision` for a different precision.
+       # see `to_precision` for a custom precision.
        redef fun to_s do
                var str = to_precision( 3 )
                if is_inf != 0 or is_nan then return str
@@ -1799,13 +1882,15 @@ redef class Float
                return str
        end
 
-       # `self` representation with `nb` digits after the '.'.
+       # `String` representation of `self` with the given number of `decimals`
        #
-       #     assert 12.345.to_precision(1) == "12.3"
-       #     assert 12.345.to_precision(2) == "12.35"
-       #     assert 12.345.to_precision(3) == "12.345"
-       #     assert 12.345.to_precision(4) == "12.3450"
-       fun to_precision(nb: Int): String
+       #     assert 12.345.to_precision(0)    == "12"
+       #     assert 12.345.to_precision(3)    == "12.345"
+       #     assert (-12.345).to_precision(3) == "-12.345"
+       #     assert (-0.123).to_precision(3)  == "-0.123"
+       #     assert 0.999.to_precision(2)     == "1.00"
+       #     assert 0.999.to_precision(4)     == "0.9990"
+       fun to_precision(decimals: Int): String
        do
                if is_nan then return "nan"
 
@@ -1816,25 +1901,34 @@ redef class Float
                        return  "-inf"
                end
 
-               if nb == 0 then return self.to_i.to_s
+               if decimals == 0 then return self.to_i.to_s
                var f = self
-               for i in [0..nb[ do f = f * 10.0
+               for i in [0..decimals[ do f = f * 10.0
                if self > 0.0 then
                        f = f + 0.5
                else
                        f = f - 0.5
                end
                var i = f.to_i
-               if i == 0 then return "0.0"
-               var s = i.to_s
+               if i == 0 then return "0." + "0"*decimals
+
+               # Prepare both parts of the float, before and after the "."
+               var s = i.abs.to_s
                var sl = s.length
-               if sl > nb then
-                       var p1 = s.substring(0, s.length-nb)
-                       var p2 = s.substring(s.length-nb, nb)
-                       return p1 + "." + p2
+               var p1
+               var p2
+               if sl > decimals then
+                       # Has something before the "."
+                       p1 = s.substring(0, sl-decimals)
+                       p2 = s.substring(sl-decimals, decimals)
                else
-                       return "0." + ("0"*(nb-sl)) + s
+                       p1 = "0"
+                       p2 = "0"*(decimals-sl) + s
                end
+
+               if i < 0 then p1 = "-" + p1
+
+               return p1 + "." + p2
        end
 
        # `self` representation with `nb` digits after the '.'.
@@ -2050,7 +2144,6 @@ extern class NativeString `{ char* `}
        do
                assert length >= 0
                var str = new FlatString.with_infos(self, length, 0, length - 1)
-               str.real_items = self
                return str
        end
 
@@ -2060,7 +2153,8 @@ extern class NativeString `{ char* `}
                var new_self = calloc_string(length + 1)
                copy_to(new_self, length, 0, 0)
                var str = new FlatString.with_infos(new_self, length, 0, length - 1)
-               str.real_items = self
+               new_self[length] = '\0'
+               str.real_items = new_self
                return str
        end
 end