Merge: Factorize `capitalize` and add option to preserve uppercase letters
authorJean Privat <jean@pryen.org>
Fri, 3 Jun 2016 01:44:04 +0000 (21:44 -0400)
committerJean Privat <jean@pryen.org>
Fri, 3 Jun 2016 01:44:04 +0000 (21:44 -0400)
Merge the duplicated implementation of `String::capitalized` and `Buffer::capitalize` and extend it to accept the `keep_upper` optional parameter. This parameter triggers keeping uppercase letters as is.

It is applied in nitiwiki to preserve acronyms, like the many apparences of FFI at http://nitlanguage.org/FFI/.

Pull-Request: #2085
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

1  2 
contrib/nitiwiki/src/wiki_base.nit
lib/core/text/abstract_text.nit

@@@ -94,7 -94,7 +94,7 @@@ class Nitiwik
                                var entry = entries[path]
                                if not entry.is_dirty then continue
                                var name = entry.name
 -                              if entry.has_source then name = entry.src_path.to_s
 +                              if entry.has_source then name = entry.src_path.as(not null)
                                if entry.is_new then
                                        print " + {name}"
                                else
        fun need_render(src, target: String): Bool do
                if force_render then return true
                if not target.file_exists then return true
 -              return src.file_stat.mtime >= target.file_stat.mtime
 +              return src.file_stat.as(not null).mtime >= target.file_stat.as(not null).mtime
        end
  
        # Create a new `WikiSection`.
        # Used to translate ids in beautiful page names.
        fun pretty_name(name: String): String do
                name = name.replace("_", " ")
-               name = name.capitalized
+               name = name.capitalized(keep_upper=true)
                return name
        end
  end
@@@ -283,7 -283,7 +283,7 @@@ abstract class WikiEntr
        # Returns `-1` if not `has_source`.
        fun create_time: Int do
                if not has_source then return -1
 -              return src_full_path.file_stat.ctime
 +              return src_full_path.as(not null).file_stat.as(not null).ctime
        end
  
        # Entry last modification time.
        # Returns `-1` if not `has_source`.
        fun last_edit_time: Int do
                if not has_source then return -1
 -              return src_full_path.file_stat.mtime
 +              return src_full_path.as(not null).file_stat.as(not null).mtime
        end
  
        # Entry list rendering time.
        # Returns `-1` if `is_new`.
        fun last_render_time: Int do
                if is_new then return -1
 -              return out_full_path.file_stat.mtime
 +              return out_full_path.file_stat.as(not null).mtime
        end
  
        # Entries hierarchy
        # then returns the main wiki template file.
        fun template_file: String do
                if is_root then return wiki.config.template_file
 -              return parent.template_file
 +              return parent.as(not null).template_file
        end
  
        # Header template file for `self`.
        # Behave like `template_file`.
        fun header_file: String do
                if is_root then return wiki.config.header_file
 -              return parent.header_file
 +              return parent.as(not null).header_file
        end
  
        # Footer template file for `self`.
        # Behave like `template_file`.
        fun footer_file: String do
                if is_root then return wiki.config.footer_file
 -              return parent.footer_file
 +              return parent.as(not null).footer_file
        end
  
        # Menu template file for `self`.
        # Behave like `template_file`.
        fun menu_file: String do
                if is_root then return wiki.config.menu_file
 -              return parent.menu_file
 +              return parent.as(not null).menu_file
        end
  
        # Display the entry `name`.
@@@ -442,7 -442,7 +442,7 @@@ class WikiSectio
  
        redef fun title do
                if has_config then
 -                      var title = config.title
 +                      var title = config.as(not null).title
                        if title != null then return title
                end
                return super
        #
        # Hidden section are rendered but not linked in menus.
        fun is_hidden: Bool do
 -              if has_config then return config.is_hidden
 +              if has_config then return config.as(not null).is_hidden
                return false
        end
  
                if parent == null then
                        return wiki.config.source_dir
                else
 -                      return wiki.expand_path(parent.src_path, name)
 +                      return wiki.expand_path(parent.as(not null).src_path, name)
                end
        end
  
        # Also check custom config.
        redef fun template_file do
                if has_config then
 -                      var tpl = config.template_file
 +                      var tpl = config.as(not null).template_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.template_file
 -              return parent.template_file
 +              return parent.as(not null).template_file
        end
  
        # Also check custom config.
        redef fun header_file do
                if has_config then
 -                      var tpl = config.header_file
 +                      var tpl = config.as(not null).header_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.header_file
 -              return parent.header_file
 +              return parent.as(not null).header_file
        end
  
        # Also check custom config.
        redef fun footer_file do
                if has_config then
 -                      var tpl = config.footer_file
 +                      var tpl = config.as(not null).footer_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.footer_file
 -              return parent.footer_file
 +              return parent.as(not null).footer_file
        end
  
        # Also check custom config.
        redef fun menu_file do
                if has_config then
 -                      var tpl = config.menu_file
 +                      var tpl = config.as(not null).menu_file
                        if tpl != null then return tpl
                end
                if is_root then return wiki.config.menu_file
 -              return parent.menu_file
 +              return parent.as(not null).menu_file
        end
  end
  
@@@ -534,8 -534,7 +534,8 @@@ class WikiArticl
        # Articles can only have `WikiSection` as parents.
        redef type PARENT: WikiSection
  
 -      redef fun title: String do
 +      redef fun title do
 +              var parent = self.parent
                if name == "index" and parent != null then return parent.title
                return super
        end
                content = md
        end
  
 -      redef var src_full_path: nullable String = null
 +      redef var src_full_path = null
  
        redef fun src_path do
                var src_full_path = self.src_full_path
        # REQUIRE: `has_source`.
        var md: nullable String is lazy do
                if not has_source then return null
 -              var file = new FileReader.open(src_full_path.to_s)
 +              var file = new FileReader.open(src_full_path.as(not null))
                var md = file.read_all
                file.close
                return md
        redef fun is_dirty do
                if super then return true
                if has_source then
 -                      return wiki.need_render(src_full_path.to_s, out_full_path)
 +                      return wiki.need_render(src_full_path.as(not null), out_full_path)
                end
                return false
        end
@@@ -780,7 -779,7 +780,7 @@@ class WikiConfi
        var sidebar_blocks: Array[String] is lazy do
                var res = new Array[String]
                if not has_key("wiki.sidebar.blocks") then return res
 -              for val in at("wiki.sidebar.blocks").values do
 +              for val in at("wiki.sidebar.blocks").as(not null).values do
                        res.add val
                end
                return res
@@@ -1360,30 -1360,19 +1360,19 @@@ abstract class Strin
        # Letters that follow a letter are lowercased
        # Letters that follow a non-letter are upcased.
        #
+       # If `keep_upper = true`, already uppercase letters are not lowercased.
+       #
        # 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"
-       fun capitalized: SELFTYPE do
+       #     assert "preserve my ACRONYMS".capitalized(keep_upper=true) == "Preserve My ACRONYMS"
+       fun capitalized(keep_upper: nullable Bool): SELFTYPE do
                if length == 0 then return self
  
                var buf = new Buffer.with_cap(length)
-               var curr = chars[0].to_upper
-               var prev = curr
-               buf[0] = curr
-               for i in [1 .. length[ do
-                       prev = curr
-                       curr = self[i]
-                       if prev.is_letter then
-                               buf[i] = curr.to_lower
-                       else
-                               buf[i] = curr.to_upper
-                       end
-               end
+               buf.capitalize(keep_upper=keep_upper, src=self)
                return buf.to_s
        end
  end
@@@ -1478,6 -1467,13 +1467,13 @@@ abstract class Buffe
        # Letters that follow a letter are lowercased
        # Letters that follow a non-letter are upcased.
        #
+       # If `keep_upper = true`, uppercase letters are not lowercased.
+       #
+       # When `src` is specified, this method reads from `src` instead of `self`
+       # but it still writes the result to the beginning of `self`.
+       # This requires `self` to have the capacity to receive all of the
+       # capitalized content of `src`.
+       #
        # SEE: `Char::is_letter` for the definition of a letter.
        #
        #     var b = new FlatBuffer.from("jAVAsCriPt")
        #     b = new FlatBuffer.from("ab_c -ab0c ab\nc")
        #     b.capitalize
        #     assert b == "Ab_C -Ab0C Ab\nC"
-       fun capitalize do
+       #
+       #     b = new FlatBuffer.from("12345")
+       #     b.capitalize(src="foo")
+       #     assert b == "Foo45"
+       #
+       #     b = new FlatBuffer.from("preserve my ACRONYMS")
+       #     b.capitalize(keep_upper=true)
+       #     assert b == "Preserve My ACRONYMS"
+       fun capitalize(keep_upper: nullable Bool, src: nullable Text) do
+               src = src or else self
+               var length = src.length
                if length == 0 then return
-               var c = self[0].to_upper
+               keep_upper = keep_upper or else false
+               var c = src[0].to_upper
                self[0] = c
                var prev = c
                for i in [1 .. length[ do
                        prev = c
-                       c = self[i]
+                       c = src[i]
                        if prev.is_letter then
-                               self[i] = c.to_lower
+                               if keep_upper then
+                                       self[i] = c
+                               else
+                                       self[i] = c.to_lower
+                               end
                        else
                                self[i] = c.to_upper
                        end
  # see `alpha_comparator`
  private class AlphaComparator
        super Comparator
 -      redef fun compare(a, b) do return a.to_s <=> b.to_s
 +      redef fun compare(a, b) do
 +              if a == b then return 0
 +              if a == null then return -1
 +              if b == null then return 1
 +              return a.to_s <=> b.to_s
 +      end
  end
  
  # Stateless comparator that naively use `to_s` to compare things.
@@@ -2165,11 -2172,6 +2177,11 @@@ redef class NativeStrin
        # SEE: `abstract_text::Text` for more info on the difference
        # between `Text::bytelen` and `Text::length`.
        fun to_s_full(bytelen, unilen: Int): String is abstract
 +
 +      # Copies the content of `src` to `self`
 +      #
 +      # NOTE: `self` must be large enough to withold `self.bytelen` bytes
 +      fun fill_from(src: Text) do src.copy_to_native(self, src.bytelen, 0, 0)
  end
  
  redef class NativeArray[E]