X-Git-Url: http://nitlanguage.org diff --git a/lib/standard/string.nit b/lib/standard/string.nit index 664ecbc..2c46179 100644 --- a/lib/standard/string.nit +++ b/lib/standard/string.nit @@ -385,17 +385,38 @@ abstract class Text # assert "\na\nb\tc\t".trim == "a\nb\tc" fun trim: SELFTYPE do return (self.l_trim).r_trim - # Returns `self` removed from its last `\n` (if any). + # Returns `self` removed from its last line terminator (if any). # # assert "Hello\n".chomp == "Hello" # assert "Hello".chomp == "Hello" - # assert "\n\n\n".chomp == "\n\n" # - # This method is mainly used to remove the LINE_FEED character from lines of text. + # assert "\n".chomp == "" + # assert "".chomp == "" + # + # Line terminators are `"\n"`, `"\r\n"` and `"\r"`. + # A single line terminator, the last one, is removed. + # + # assert "\r\n".chomp == "" + # assert "\r\n\n".chomp == "\r\n" + # assert "\r\n\r\n".chomp == "\r\n" + # assert "\r\n\r".chomp == "\r\n" + # + # Note: unlike with most IO methods like `IStream::read_line`, + # a single `\r` is considered here to be a line terminator and will be removed. fun chomp: SELFTYPE do - if self.chars.last != '\n' then return self - return substring(0, length-1) + var len = length + if len == 0 then return self + var l = self.chars.last + if l == '\r' then + return substring(0, len-1) + else if l != '\n' then + return self + else if len > 1 and self.chars[len-2] == '\r' then + return substring(0, len-2) + else + return substring(0, len-1) + end end # Justify a self in a space of `length` @@ -405,6 +426,8 @@ abstract class Text # * 1.0 for right-justified (all spaces at the left) # * 0.5 for centered (half the spaces at the left) # + # Examples + # # assert "hello".justify(10, 0.0) == "hello " # assert "hello".justify(10, 1.0) == " hello" # assert "hello".justify(10, 0.5) == " hello " @@ -413,9 +436,9 @@ abstract class Text # # assert "hello".justify(2, 0.0) == "hello" # - # REQUIRE: left >= 0.0 and left <= 1.0 + # REQUIRE: `left >= 0.0 and left <= 1.0` # ENSURE: `self.length <= length implies result.length == length` - # ENSURE: `self.length >= length implies result == self + # ENSURE: `self.length >= length implies result == self` fun justify(length: Int, left: Float): SELFTYPE do var diff = length - self.length @@ -425,13 +448,39 @@ abstract class Text return " " * before + self + " " * (diff-before) end - # Mangle a string to be a unique string only made of alphanumeric characters + # Mangle a string to be a unique string only made of alphanumeric characters and underscores. + # + # This method is injective (two different inputs never produce the same + # output) and the returned string always respect the following rules: + # + # * Contains only US-ASCII letters, digits and underscores. + # * Never starts with a digit. + # * Never ends with an underscore. + # * Never contains two contiguous underscores. + # + # assert "42_is/The answer!".to_cmangle == "_52d2_is_47dThe_32danswer_33d" + # assert "__".to_cmangle == "_95d_95d" + # assert "__d".to_cmangle == "_95d_d" + # assert "_d_".to_cmangle == "_d_95d" + # assert "_42".to_cmangle == "_95d42" + # assert "foo".to_cmangle == "foo" + # assert "".to_cmangle == "" fun to_cmangle: String do + if is_empty then return "" var res = new FlatBuffer var underscore = false - for i in [0..length[ do - var c = chars[i] + var start = 0 + var c = chars[0] + + if c >= '0' and c <= '9' then + res.add('_') + res.append(c.ascii.to_s) + res.add('d') + start = 1 + end + for i in [start..length[ do + c = chars[i] if (c >= 'a' and c <= 'z') or (c >='A' and c <= 'Z') then res.add(c) underscore = false @@ -454,6 +503,10 @@ abstract class Text underscore = false end end + if underscore then + res.append('_'.ascii.to_s) + res.add('d') + end return res.to_s end @@ -653,9 +706,11 @@ abstract class Text return buf.to_s end - # Escape the four characters `<`, `>`, `&`, and `"` with their html counterpart + # Escape the characters `<`, `>`, `&`, `"`, `'` and `/` as HTML/XML entity references. + # + # assert "a&b-<>\"x\"/'".html_escape == "a&b-<>"x"/'" # - # assert "a&b->\"x\"".html_escape == "a&b->"x"" + # SEE: fun html_escape: SELFTYPE do var buf = new FlatBuffer @@ -669,7 +724,11 @@ abstract class Text else if c == '>' then buf.append ">" else if c == '"' then - buf.append """ + buf.append """ + else if c == '\'' then + buf.append "'" + else if c == '/' then + buf.append "/" else buf.add c end @@ -941,9 +1000,9 @@ abstract class String # # SEE : `Char::is_letter` for the definition of letter. # - # assert "jAVASCRIPT".capitalized == "Javascript" - # assert "i am root".capitalized == "I Am Root" - # assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC" + # assert "jAVASCRIPT".capitalized == "Javascript" + # assert "i am root".capitalized == "I Am Root" + # assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC" fun capitalized: SELFTYPE do if length == 0 then return self @@ -1033,15 +1092,19 @@ class FlatString from = 0 end - var realFrom = index_from + from + var new_from = index_from + from - if (realFrom + count) > index_to then return new FlatString.with_infos(items, index_to - realFrom + 1, realFrom, index_to) + if (new_from + count) > index_to then + var new_len = index_to - new_from + 1 + if new_len <= 0 then return empty + return new FlatString.with_infos(items, new_len, new_from, index_to) + end - if count == 0 then return empty + if count <= 0 then return empty - var to = realFrom + count - 1 + var to = new_from + count - 1 - return new FlatString.with_infos(items, to - realFrom + 1, realFrom, to) + return new FlatString.with_infos(items, to - new_from + 1, new_from, to) end redef fun empty do return "".as(FlatString) @@ -1418,15 +1481,15 @@ abstract class Buffer # # SEE: `Char::is_letter` for the definition of a letter. # - # var b = new FlatBuffer.from("jAVAsCriPt") - # b.capitalize - # assert b == "Javascript" - # b = new FlatBuffer.from("i am root") - # b.capitalize - # assert b == "I Am Root" - # b = new FlatBuffer.from("ab_c -ab0c ab\nc") - # b.capitalize - # assert b == "Ab_C -Ab0C Ab\nC" + # var b = new FlatBuffer.from("jAVAsCriPt") + # b.capitalize + # assert b == "Javascript" + # b = new FlatBuffer.from("i am root") + # b.capitalize + # assert b == "I Am Root" + # b = new FlatBuffer.from("ab_c -ab0c ab\nc") + # b.capitalize + # assert b == "Ab_C -Ab0C Ab\nC" fun capitalize do if length == 0 then return var c = self[0].to_upper @@ -1783,7 +1846,7 @@ redef class Object # The class name of the object. # - # assert 5.class_name == "Int" + # assert 5.class_name == "Int" fun class_name: String do return native_class_name.to_s # Developer readable representation of `self`. @@ -1978,10 +2041,10 @@ redef class Char # Returns true if the char is a numerical digit # - # assert '0'.is_numeric - # assert '9'.is_numeric - # assert not 'a'.is_numeric - # assert not '?'.is_numeric + # assert '0'.is_numeric + # assert '9'.is_numeric + # assert not 'a'.is_numeric + # assert not '?'.is_numeric fun is_numeric: Bool do return self >= '0' and self <= '9' @@ -1989,10 +2052,10 @@ redef class Char # Returns true if the char is an alpha digit # - # assert 'a'.is_alpha - # assert 'Z'.is_alpha - # assert not '0'.is_alpha - # assert not '?'.is_alpha + # assert 'a'.is_alpha + # assert 'Z'.is_alpha + # assert not '0'.is_alpha + # assert not '?'.is_alpha fun is_alpha: Bool do return (self >= 'a' and self <= 'z') or (self >= 'A' and self <= 'Z') @@ -2000,11 +2063,11 @@ redef class Char # Returns true if the char is an alpha or a numeric digit # - # assert 'a'.is_alphanumeric - # assert 'Z'.is_alphanumeric - # assert '0'.is_alphanumeric - # assert '9'.is_alphanumeric - # assert not '?'.is_alphanumeric + # assert 'a'.is_alphanumeric + # assert 'Z'.is_alphanumeric + # assert '0'.is_alphanumeric + # assert '9'.is_alphanumeric + # assert not '?'.is_alphanumeric fun is_alphanumeric: Bool do return self.is_numeric or self.is_alpha @@ -2115,7 +2178,7 @@ redef class Map[K,V] var i = iterator var k = i.key var e = i.item - s.append("{k}{couple_sep}{e or else ""}") + s.append("{k or else ""}{couple_sep}{e or else ""}") # Concat other items i.next @@ -2123,7 +2186,7 @@ redef class Map[K,V] s.append(sep) k = i.key e = i.item - s.append("{k}{couple_sep}{e or else ""}") + s.append("{k or else ""}{couple_sep}{e or else ""}") i.next end return s.to_s