In an INI file, properties (or keys) are associated to values thanks to the
equals symbol (=
).
Properties may be grouped into section marked between brackets ([
and ]
).
var ini_string = """
; Example INI
key=value1
[section1]
key=value2
[section2]
key=value3
"""
The main class, IniFile
, can be created from an INI string and allows easy
access to its content.
var ini = new IniFile.from_string(ini_string)
# Check keys presence
assert ini.has_key("key")
assert ini.has_key("section1.key")
assert not ini.has_key("not.found")
# Access values
assert ini["key"] == "value1"
assert ini["section2.key"] == "value3"
assert ini["not.found"] == null
# Access sections
assert ini.sections.length == 2
assert ini.section("section1")["key"] == "value2"
IniFile
can also be used to create new INI files from scratch, or edit
existing ones through its API.
ini = new IniFile
ini["key"] = "value1"
ini["section1.key"] = "value2"
ini["section2.key"] = "value3"
ini.write_to_file("my_config.ini")
# Load the INI file from disk
ini = new IniFile.from_file("my_config.ini")
assert ini["key"] == "value1"
assert ini["section1.key"] == "value2"
assert ini["section2.key"] == "value3"
"my_config.ini".to_path.delete
ini :: IniFile :: _stop_on_first_error
Stop parsing on the first errorini :: IniFile :: defaultinit
ini :: IniFile :: from_string
Create a IniFile from astring
content
ini :: IniFile :: stop_on_first_error=
Stop parsing on the first errorini :: IniFile :: update_value
core :: HashCollection :: _array
core :: HashCollection :: _capacity
core :: HashCollection :: _first_item
core :: HashCollection :: _last_accessed_key
The last key accessed (used for cache)core :: HashCollection :: _last_accessed_node
The last node accessed (used for cache)core :: HashCollection :: _last_item
ini :: IniFile :: _stop_on_first_error
Stop parsing on the first errorcore :: HashCollection :: _the_length
serialization :: Serializable :: accept_inspect_serializer_core
serialization :: Serializable :: accept_json_serializer
Refinable service to customize the serialization of this class to JSONserialization :: Serializable :: accept_msgpack_attribute_counter
Hook to customize the behavior of theAttributeCounter
serialization :: Serializable :: accept_msgpack_serializer
Hook to customize the serialization of this class to MessagePackserialization :: Serializable :: add_to_bundle
Called by[]=
to dynamically choose the appropriate method according
core :: HashCollection :: array
core :: HashCollection :: array=
core :: HashCollection :: capacity
core :: HashCollection :: capacity=
core :: Object :: class_factory
Implementation used byget_class
to create the specific class.
serialization :: Serializable :: core_serialize_to
Actual serialization ofself
to serializer
core :: Object :: defaultinit
core :: MapRead :: defaultinit
core :: Writable :: defaultinit
ini :: IniFile :: defaultinit
core :: Map :: defaultinit
core :: HashCollection :: defaultinit
core :: HashMap :: defaultinit
core :: MapRead :: filter_keys
Return all elements ofkeys
that have a value.
core :: HashCollection :: first_item
core :: HashCollection :: first_item=
serialization :: Serializable :: from_deserializer
Create an instance of this class from thedeserializer
ini :: IniFile :: from_string
Create a IniFile from astring
content
core :: MapRead :: get_or_default
Get the item atkey
or return default
if not in map
core :: MapRead :: get_or_null
Get the item atkey
or null if key
is not in the map.
core :: HashCollection :: gt_collide
Count and update length of collisions fornode_at_idx
core :: Object :: is_same_instance
Return true ifself
and other
are the same instance (i.e. same identity).
core :: Object :: is_same_serialized
Isself
the same as other
in a serialization context?
core :: Object :: is_same_type
Return true ifself
and other
have the same dynamic type.
core :: MapRead :: keys_sorted_by_values
Return an array of all keys sorted with their values usingcomparator
.
core :: HashCollection :: last_accessed_key
The last key accessed (used for cache)core :: HashCollection :: last_accessed_key=
The last key accessed (used for cache)core :: HashCollection :: last_accessed_node
The last node accessed (used for cache)core :: HashCollection :: last_accessed_node=
The last node accessed (used for cache)core :: HashCollection :: last_item
core :: HashCollection :: last_item=
core :: MapRead :: lookup_all_values
Search all the values inpe.greaters
.
core :: MapRead :: lookup_values
Combine the values inpe.greaters
from the most smaller elements that have a value.
serialization :: Serializable :: msgpack_extra_array_items
Hook to request a larger than usual metadata arraycore :: Object :: native_class_name
The class name of the object in CString format.core :: HashCollection :: node_at_idx
Return the node associated with the key (but with the index already known)core :: Object :: output_class_name
Display class name on stdout (debug only).core :: MapRead :: provide_default_value
Called by the underling implementation of[]
to provide a default value when a key
has no value
core :: HashCollection :: remove_node
Remove the node assosiated with the keyserialization :: Serializable :: serialize_msgpack
Serializeself
to MessagePack bytes
serialization :: Serializable :: serialize_to
Serializeself
to serializer
serialization :: Serializable :: serialize_to_json
Serializeself
to JSON
serialization :: Serializable :: serialize_to_or_delay
Accept references or force direct serialization (usingserialize_to
)
core :: HashCollection :: st_collide
Count and update length of collisions forstore
ini :: IniFile :: stop_on_first_error=
Stop parsing on the first errorcore :: HashCollection :: the_length
core :: HashCollection :: the_length=
core :: MapRead :: to_map_comparator
A comparator that compares things with their values in self.serialization :: Serializable :: to_pretty_json
Serializeself
to plain pretty JSON
ini :: IniFile :: update_value
core :: MapRead :: values_sorted_by_key
Return an array of all values sorted with their keys usingcomparator
.
core :: Writable :: write_to_bytes
Likewrite_to
but return a new Bytes (may be quite large)
core :: Writable :: write_to_file
Likewrite_to
but take care of creating the file
core :: Writable :: write_to_string
Likewrite_to
but return a new String (may be quite large).
Serializer::serialize
# Read and write INI configuration files
#
# In an INI file, properties (or keys) are associated to values thanks to the
# equals symbol (`=`).
# Properties may be grouped into section marked between brackets (`[` and `]`).
#
# ~~~
# var ini_string = """
# ; Example INI
# key=value1
# [section1]
# key=value2
# [section2]
# key=value3
# """
# ~~~
#
# The main class, `IniFile`, can be created from an INI string and allows easy
# access to its content.
#
# ~~~
# # Read INI from string
# var ini = new IniFile.from_string(ini_string)
#
# # Check keys presence
# assert ini.has_key("key")
# assert ini.has_key("section1.key")
# assert not ini.has_key("not.found")
#
# # Access values
# assert ini["key"] == "value1"
# assert ini["section2.key"] == "value3"
# assert ini["not.found"] == null
#
# # Access sections
# assert ini.sections.length == 2
# assert ini.section("section1")["key"] == "value2"
# ~~~
#
# `IniFile` can also be used to create new INI files from scratch, or edit
# existing ones through its API.
#
# ~~~
# # Create a new INI file and write it to disk
# ini = new IniFile
# ini["key"] = "value1"
# ini["section1.key"] = "value2"
# ini["section2.key"] = "value3"
# ini.write_to_file("my_config.ini")
#
# # Load the INI file from disk
# ini = new IniFile.from_file("my_config.ini")
# assert ini["key"] == "value1"
# assert ini["section1.key"] == "value2"
# assert ini["section2.key"] == "value3"
#
# "my_config.ini".to_path.delete
# ~~~
class IniFile
super Writable
super HashMap[String, nullable String]
# Create a IniFile from a `string` content
#
# ~~~
# var ini = new IniFile.from_string("""
# key1=value1
# [section1]
# key2=value2
# """)
# assert ini["key1"] == "value1"
# assert ini["section1.key2"] == "value2"
# ~~~
#
# See also `stop_on_first_error` and `errors`.
init from_string(string: String, stop_on_first_error: nullable Bool) do
init stop_on_first_error or else false
load_string(string)
end
# Create a IniFile from a `file` content
#
# ~~~
# """
# key1=value1
# [section1]
# key2=value2
# """.write_to_file("my_config.ini")
#
# var ini = new IniFile.from_file("my_config.ini")
# assert ini["key1"] == "value1"
# assert ini["section1.key2"] == "value2"
#
# "my_config.ini".to_path.delete
# ~~~
#
# See also `stop_on_first_error` and `errors`.
init from_file(file: String, stop_on_first_error: nullable Bool) do
init stop_on_first_error or else false
load_file(file)
end
# Sections composing this IniFile
#
# ~~~
# var ini = new IniFile.from_string("""
# [section1]
# key1=value1
# [ section 2 ]
# key2=value2
# """)
# assert ini.sections.length == 2
# assert ini.sections.first.name == "section1"
# assert ini.sections.last.name == "section 2"
# ~~~
var sections = new Array[IniSection]
# Get a section by its `name`
#
# Returns `null` if the section is not found.
#
# ~~~
# var ini = new IniFile.from_string("""
# [section1]
# key1=value1
# [section2]
# key2=value2
# """)
# assert ini.section("section1") isa IniSection
# assert ini.section("section2").name == "section2"
# assert ini.section("not.found") == null
# ~~~
fun section(name: String): nullable IniSection do
for section in sections do
if section.name == name then return section
end
return null
end
# Does this file contains no properties and no sections?
#
# ~~~
# var ini = new IniFile.from_string("")
# assert ini.is_empty
#
# ini = new IniFile.from_string("""
# key=value
# """)
# assert not ini.is_empty
#
# ini = new IniFile.from_string("""
# [section]
# """)
# assert not ini.is_empty
# ~~~
redef fun is_empty do return super and sections.is_empty
# Is there a property located at `key`?
#
# Returns `true` if the `key` is not found of if its associated value is `null`.
#
# ~~~
# var ini = new IniFile.from_string("""
# key=value1
# [section1]
# key=value2
# [section2]
# key=value3
# """)
# assert ini.has_key("key")
# assert ini.has_key("section1.key")
# assert ini.has_key("section2.key")
# assert not ini.has_key("section1")
# assert not ini.has_key("not.found")
# ~~~
redef fun has_key(key) do return self[key] != null
# Get the value associated with a property (`key`)
#
# Returns `null` if the key is not found.
# Section properties can be accessed with the `.` notation.
#
# ~~~
# var ini = new IniFile.from_string("""
# key=value1
# [section1]
# key=value2
# [section2]
# key=value3
# """)
# assert ini["key"] == "value1"
# assert ini["section1.key"] == "value2"
# assert ini["section2.key"] == "value3"
# assert ini["section1"] == null
# assert ini["not.found"] == null
# ~~~
redef fun [](key) do
if key == null then return null
key = key.to_s.trim
# Look in root
var node = node_at(key)
if node != null then return node.value
# Look in sections
for section in sections do
# Matched if the section name is a prefix of the key
if not key.has_prefix(section.name) then continue
var skey = key.substring(section.name.length + 1, key.length)
if section.has_key(skey) then return section[skey]
end
return null
end
# Set the `value` for the property locaated at `key`
#
# ~~~
# var ini = new IniFile
# ini["key"] = "value1"
# ini["section1.key"] = "value2"
# ini["section2.key"] = "value3"
#
# assert ini["key"] == "value1"
# assert ini["section1.key"] == "value2"
# assert ini["section2.key"] == "value3"
# assert ini.section("section1").name == "section1"
# assert ini.section("section2")["key"] == "value3"
# ~~~
redef fun []=(key, value) do
if value == null then return
var parts = key.split_once_on(".")
# No dot notation, store value in root
if parts.length == 1 then
super(key.trim, value.trim)
return
end
# First part matches a section, store value in it
var section = self.section(parts.first.trim)
if section != null then
section[parts.last.trim] = value.trim
return
end
# No section matched, create a new one and store value in it
section = new IniSection(parts.first.trim)
section[parts.last.trim] = value.trim
sections.add section
end
# Flatten `self` and its subsection in a `Map` of keys => values
#
# Properties from section are prefixed with their section names with the
# dot (`.`) notation.
#
# ~~~
# var ini = new IniFile.from_string("""
# key=value1
# [section]
# key=value2
# """)
# assert ini.flatten.join(", ", ": ") == "key: value1, section.key: value2"
# ~~~
fun flatten: Map[String, String] do
var map = new HashMap[String, String]
for key, value in self do
if value == null then continue
map[key] = value
end
for section in sections do
for key, value in section do
if value == null then continue
map["{section.name}.{key}"] = value
end
end
return map
end
# Write `self` to a `stream`
#
# Key with `null` values are ignored.
# The empty string can be used to represent an empty value.
#
# ~~~
# var ini = new IniFile
# ini["key"] = "value1"
# ini["key2"] = null
# ini["key3"] = ""
# ini["section1.key"] = "value2"
# ini["section1.key2"] = null
# ini["section2.key"] = "value3"
#
# var stream = new StringWriter
# ini.write_to(stream)
#
# assert stream.to_s == """
# key=value1
# key3=
# [section1]
# key=value2
# [section2]
# key=value3
# """
# ~~~
redef fun write_to(stream) do
for key, value in self do
if value == null then continue
stream.write "{key}={value}\n"
end
for section in sections do
stream.write "[{section.name}]\n"
for key, value in section do
if value == null then continue
stream.write "{key}={value}\n"
end
end
end
# Read INI content from `string`
#
# ~~~
# var ini = new IniFile
# ini.load_string("""
# section1.key1=value1
# section1.key2=value2
# [section2]
# key=value3
# """)
# assert ini["section1.key1"] == "value1"
# assert ini["section1.key2"] == "value2"
# assert ini["section2.key"] == "value3"
# ~~~
#
# Returns `true` if the parsing finished correctly.
#
# See also `stop_on_first_error` and `errors`.
fun load_string(string: String): Bool do
var stream = new StringReader(string)
var last_section = null
var was_error = false
var i = 0
while not stream.eof do
i += 1
var line = stream.read_line.trim
if line.is_empty then
continue
else if line.has_prefix(";") then
continue
else if line.has_prefix("#") then
continue
else if line.has_prefix("[") then
var section = new IniSection(line.substring(1, line.length - 2).trim)
sections.add section
last_section = section
continue
else
var parts = line.split_once_on("=")
if parts.length != 2 then
# FIXME silent skip?
# we definitely need exceptions...
was_error = true
errors.add new IniError("Unexpected string `{line}` at line {i}.")
if stop_on_first_error then return was_error
continue
end
var key = parts[0].trim
var value = parts[1].trim
if last_section != null then
last_section[key] = value
else
self[key] = value
end
end
end
stream.close
return was_error
end
# Load a `file` content as INI
#
# New properties will be appended to the `self`, existing properties will be
# overwrote by the values contained in `file`.
#
# ~~~
# var ini = new IniFile
# ini["key1"] = "value1"
# ini["key2"] = "value2"
#
# """
# key2=changed
# key3=added
# """.write_to_file("load_config.ini")
#
# ini.load_file("load_config.ini")
# assert ini["key1"] == "value1"
# assert ini["key2"] == "changed"
# assert ini["key3"] == "added"
#
# "load_config.ini".to_path.delete
# ~~~
#
# The process fails silently if the file does not exist.
#
# ~~~
# ini = new IniFile
# ini.load_file("ini_not_found.ini")
# assert ini.is_empty
# ~~~
#
# Returns `true` if the parsing finished correctly.
#
# See also `stop_on_first_error` and `errors`.
fun load_file(file: String): Bool do return load_string(file.to_path.read_all)
# Stop parsing on the first error
#
# By default, `load_string` will skip unparsable properties so the string can
# be loaded.
#
# ~~~
# var ini = new IniFile.from_string("""
# key1=value1
# key2
# key3=value3
# """)
#
# assert ini.length == 2
# assert ini["key1"] == "value1"
# assert ini["key2"] == null
# assert ini["key3"] == "value3"
# ~~~
#
# Set `stop_on_first_error` to `true` to force the parsing to stop.
#
# ~~~
# ini = new IniFile
# ini.stop_on_first_error = true
# ini.load_string("""
# key1=value1
# key2
# key3=value3
# """)
#
# assert ini.length == 1
# assert ini["key1"] == "value1"
# assert ini["key2"] == null
# assert ini["key3"] == null
# ~~~
#
# See also `errors`.
var stop_on_first_error = false is optional, writable
# Errors found during parsing
#
# Wathever the value of `stop_on_first_error`, errors from parsing a string
# or a file are logged into `errors`.
#
# ~~~
# var ini = new IniFile.from_string("""
# key1=value1
# key2
# key3=value3
# """)
#
# assert ini.errors.length == 1
# assert ini.errors.first.message == "Unexpected string `key2` at line 2."
# ~~~
#
# `errors` is not cleared between two parsing:
#
# ~~~
# ini.load_string("""
# key4
# key5=value5
# """)
#
# assert ini.errors.length == 2
# assert ini.errors.last.message == "Unexpected string `key4` at line 1."
# ~~~
#
# See also `stop_on_first_error`.
var errors = new Array[IniError]
end
lib/ini/ini.nit:21,1--505,3
redef class IniFile
private fun check_key(ini_file: String, toolcontext: ToolContext, mpackage: MPackage, key: String, value: nullable String) do
if not has_key(key) then
toolcontext.warning(mpackage.location, "missing-ini-key",
"Warning: missing `{key}` key in `{ini_file}`")
return
end
if self[key].as(not null).is_empty then
toolcontext.warning(mpackage.location, "missing-ini-value",
"Warning: empty `{key}` key in `{ini_file}`")
return
end
if value != null and self[key] != value then
toolcontext.warning(mpackage.location, "wrong-ini-value",
"Warning: wrong value for `{key}` in `{ini_file}`. " +
"Expected `{value}`, got `{self[key] or else ""}`")
end
end
private fun update_value(key: String, value: nullable String) do
if value == null then return
if not has_key(key) then
self[key] = value
else
var old_value = self[key]
if not value.is_empty and old_value != value then
self[key] = value
end
end
end
end
src/nitpackage.nit:531,1--561,3