README: document nit_env.sh
[nit.git] / lib / ini.nit
index 232e7c5..84b200c 100644 (file)
@@ -19,66 +19,54 @@ module ini
 #
 # Write example:
 #
-#    var config = new ConfigTree("config.ini")
-#    config["goo"] = "goo"
-#    config["foo.bar"] = "foobar"
-#    config["foo.baz"] = "foobaz"
-#    config.save
-#    assert config.to_map.length == 3
+#     var config = new ConfigTree("config.ini")
+#     config["goo"] = "goo"
+#     config["foo.bar"] = "foobar"
+#     config["foo.baz"] = "foobaz"
+#     config.save
+#     assert config.to_map.length == 3
 #
 # Read example:
 #
-#    config = new ConfigTree("config.ini")
-#    assert config.has_key("foo.bar")
-#    assert config["foo.bar"] == "foobar"
+#     config = new ConfigTree("config.ini")
+#     assert config.has_key("foo.bar")
+#     assert config["foo.bar"] == "foobar"
 class ConfigTree
-       super Streamable
+       super Writable
 
        # The ini file used to read/store data
        var ini_file: String
 
-       init(file: String) do
-               self.ini_file = file
-               if file.file_exists then load
-       end
+       init do if ini_file.file_exists then load
 
        # Get the config value for `key`
        #
-       # REQUIRE: `has_key(key)`
-       #
-       #    var config = new ConfigTree("config.ini")
-       #    assert config["goo"] == "goo"
-       #    assert config["foo.bar"] == "foobar"
-       #    assert config["foo.baz"] == "foobaz"
-       fun [](key: String): String do
-               if not has_key(key) then
-                       print "error: config key `{key}` not found"
-                       abort
-               end
-               var node = get_node(key).as(not null)
-               if node.value == null then
-                       print "error: config key `{key}` has no value"
-                       abort
-               end
-               return node.value.as(not null)
+       #     var config = new ConfigTree("config.ini")
+       #     assert config["goo"] == "goo"
+       #     assert config["foo.bar"] == "foobar"
+       #     assert config["foo.baz"] == "foobaz"
+       #     assert config["fail.fail"] == null
+       fun [](key: String): nullable String do
+               var node = get_node(key)
+               if node == null then return null
+               return node.value
        end
 
        # Get the config values under `key`
        #
-       # REQUIRE: `has_key(key)`
+       #     var config = new ConfigTree("config.ini")
+       #     var values = config.at("foo")
+       #     assert values.has_key("bar")
+       #     assert values.has_key("baz")
+       #     assert not values.has_key("goo")
        #
-       #    var config = new ConfigTree("config.ini")
-       #    var values = config.at("foo")
-       #    assert values.has_key("bar")
-       #    assert values.has_key("baz")
-       #    assert not values.has_key("goo")
-       fun at(key: String): Map[String, String] do
-               if not has_key(key) then
-                       print "error: config key `{key}` not found"
-                       abort
-               end
+       # Return null if the key does not exists.
+       #
+       #     assert config.at("fail.fail") == null
+       fun at(key: String): nullable Map[String, String] do
+               var node = get_node(key)
+               if node == null then return null
                var map = new HashMap[String, String]
-               var node = get_node(key).as(not null)
                for k, child in node.children do
                        if child.value == null then continue
                        map[k] = child.value.to_s
@@ -88,22 +76,21 @@ class ConfigTree
 
        # Set `value` at `key`
        #
-       #    var config = new ConfigTree("config.ini")
-       #    assert config["foo.bar"] == "foobar"
-       #    config["foo.bar"] = "baz"
-       #    assert config["foo.bar"] == "baz"
+       #     var config = new ConfigTree("config.ini")
+       #     assert config["foo.bar"] == "foobar"
+       #     config["foo.bar"] = "baz"
+       #     assert config["foo.bar"] == "baz"
        fun []=(key: String, value: nullable String) do
                set_node(key, value)
        end
 
        # Is `key` in the config?
        #
-       #    var config = new ConfigTree("config.ini")
-       #    assert config.has_key("goo")
-       #    assert config.has_key("foo.bar")
-       #    assert not config.has_key("zoo")
+       #     var config = new ConfigTree("config.ini")
+       #     assert config.has_key("goo")
+       #     assert config.has_key("foo.bar")
+       #     assert not config.has_key("zoo")
        fun has_key(key: String): Bool do
-               var children = roots
                var parts = key.split(".").reversed
                var node = get_root(parts.pop)
                if node == null then return false
@@ -116,12 +103,12 @@ class ConfigTree
 
        # Get `self` as a Map of `key`, `value`
        #
-       #    var config = new ConfigTree("config.ini")
-       #    var map = config.to_map
-       #    assert map.has_key("goo")
-       #    assert map.has_key("foo.bar")
-       #    assert map.has_key("foo.baz")
-       #    assert map.length == 3
+       #     var config = new ConfigTree("config.ini")
+       #     var map = config.to_map
+       #     assert map.has_key("goo")
+       #     assert map.has_key("foo.bar")
+       #     assert map.has_key("foo.baz")
+       #     assert map.length == 3
        fun to_map: Map[String, String] do
                var map = new HashMap[String, String]
                for node in leaves do
@@ -188,28 +175,50 @@ class ConfigTree
        #     assert config["foo.bar.baz"] == "foobarbaz"
        #     assert config["goo.boo.bar"] == "gooboobar"
        #     assert config["goo.boo.baz.bar"] == "gooboobazbar"
+       #
+       # Using the array notation
+       #
+       #     str = """
+       #     foo[]=a
+       #     foo[]=b
+       #     foo[]=c"""
+       #     str.write_to_file("config4.ini")
+       #     # load file
+       #     config = new ConfigTree("config4.ini")
+       #     print config.to_map.join(":", ",")
+       #     assert config["foo.0"] == "a"
+       #     assert config["foo.1"] == "b"
+       #     assert config["foo.2"] == "c"
+       #     assert config.at("foo").values.join(",") == "a,b,c"
        fun load do
                roots.clear
-               var stream = new IFStream.open(ini_file)
+               var stream = new FileReader.open(ini_file)
                var path: nullable String = null
+               var line_number = 0
                while not stream.eof do
                        var line = stream.read_line
+                       line_number += 1
                        if line.is_empty then
                                continue
                        else if line.has_prefix(";") then
                                continue
                        else if line.has_prefix("[") then
-                               var key = line.trim.substring(1, line.length - 2)
+                               line = line.trim
+                               var key = line.substring(1, line.length - 2)
                                path = key
                                set_node(path, null)
                        else
                                var parts = line.split("=")
+                               assert parts.length > 1 else
+                                       print "Error: malformed ini at line {line_number}"
+                               end
                                var key = parts[0].trim
                                var val = parts[1].trim
-                               if path == null then
-                                       set_node(key, val)
+                               if path != null then key = "{path}.{key}"
+                               if key.has_suffix("[]") then
+                                       set_array(key, val)
                                else
-                                       set_node("{path}.{key}", val)
+                                       set_node(key,val)
                                end
                        end
                end
@@ -221,8 +230,17 @@ class ConfigTree
 
        private var roots = new Array[ConfigNode]
 
+       # Append `value` to array at `key`
+       private fun set_array(key: String, value: nullable String) do
+               key = key.substring(0, key.length - 2)
+               var len = 0
+               if has_key(key) then
+                       len = get_node(key).children.length
+               end
+               set_node("{key}.{len.to_s}", value)
+       end
+
        private fun set_node(key: String, value: nullable String) do
-               var children = roots
                var parts = key.split(".").reversed
                var k = parts.pop
                var root = get_root(k)
@@ -249,10 +267,9 @@ class ConfigTree
        end
 
        private fun get_node(key: String): nullable ConfigNode do
-               var children = roots
                var parts = key.split(".").reversed
                var node = get_root(parts.pop)
-               while not parts.is_empty do
+               while node != null and not parts.is_empty do
                        node = node.get_child(parts.pop)
                end
                return node
@@ -282,14 +299,11 @@ class ConfigTree
 end
 
 private class ConfigNode
-       var parent: nullable ConfigNode
-       var children = new HashMap[String, ConfigNode]
-       var name: String writable
-       var value: nullable String
 
-       init(name: String) do
-               self.name = name
-       end
+       var parent: nullable ConfigNode = null
+       var children = new HashMap[String, ConfigNode]
+       var name: String is writable
+       var value: nullable String = null
 
        fun key: String do
                if parent == null then
@@ -305,4 +319,3 @@ private class ConfigNode
                return null
        end
 end
-