Files
ROC/library/configuration/src/ini_config.e

535 lines
13 KiB
Plaintext

note
description: "[
Object parsing a .ini file.
Lines starting by '#', or ';', or "--" are comments
Section are declared using brackets "[section_name]"
Values are declared as "key = value"
List values are declared as multiple lines "key[] = value"
example:
--------------------
[first_section]
one = abc
[second_section]
two = def
lst[] = a
lst[] = b
lst[] = c
[third_section]
three = ghi
table[a] = foo
table[b] = bar
--------------------
Values are accessible from `items' or by section from `sections'
`item' has smart support for '.'
item ("one") -> abc
item ("two") -> def
item ("second_section.two") -> def
item ("table[b]") -> foo
item ("table.b") -> foo
item ("third_section.table[b]") -> foo
item ("third_section.table.b") -> foo
notes:
it is considered that the .ini file is utf-8 encoded
keys are unicode string
values are stored UTF-8 string, but one can use unicode_string_item to convert to STRING_32
Additional features:
- Include other files:
@include=file-to-include
]"
date: "$Date: 2015-03-09 19:25:49 +0100 (lun., 09 mars 2015) $"
revision: "$Revision: 96797 $"
class
INI_CONFIG
inherit
CONFIG_READER
TABLE_ITERABLE [ANY, READABLE_STRING_GENERAL]
SHARED_EXECUTION_ENVIRONMENT
create
make_from_file,
make_from_string,
make_from_items
feature {NONE} -- Initialization
make_from_file (p: PATH)
do
initialize
parse_file (p)
end
make_from_string (a_content: STRING_8)
do
initialize
parse_content (a_content)
end
make_from_items (a_items: like items)
do
initialize
across
a_items as ic
loop
items.force (ic.item, ic.key)
end
end
initialize
-- Initialize data.
do
associated_path := Void
create utf
create items.make (0)
create sections.make (0)
end
reset
-- Reset internal data.
do
has_error := False
items.wipe_out
sections.wipe_out
end
feature -- Status report
has_item (k: READABLE_STRING_GENERAL): BOOLEAN
-- Has item associated with key `k'?
do
Result := item (k) /= Void
end
has_error: BOOLEAN
-- Has error?
--| Syntax error, source not found, ...
feature -- Access: Config Reader
text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
-- String item associated with key `k'.
do
Result := value_to_string_32 (item (k))
end
text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
-- List of String item associated with key `k'.
do
if attached {LIST [READABLE_STRING_8]} item (k) as l_list then
create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (l_list.count)
Result.compare_objects
across
l_list as ic
until
Result = Void
loop
if attached value_to_string_32 (ic.item) as s32 then
Result.force (s32)
else
Result := Void
end
end
end
end
text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
-- Table of String item associated with key `k'.
do
if attached {STRING_TABLE [READABLE_STRING_8]} item (k) as l_list then
create {STRING_TABLE [READABLE_STRING_32]} Result.make (l_list.count)
Result.compare_objects
across
l_list as ic
until
Result = Void
loop
if attached value_to_string_32 (ic.item) as s32 then
Result.force (s32, ic.key)
else
Result := Void
end
end
end
end
integer_item (k: READABLE_STRING_GENERAL): INTEGER
-- Integer item associated with key `k'.
do
if attached {READABLE_STRING_GENERAL} item (k) as s then
Result := s.to_integer
end
end
associated_path: detachable PATH
-- If current was built from a filename, return this path.
feature -- Duplication
sub_config (k: READABLE_STRING_GENERAL): detachable CONFIG_READER
-- Configuration representing a subset of Current for key `k'.
do
if attached sections.item (k) as l_items then
create {INI_CONFIG} Result.make_from_items (l_items)
end
end
feature -- Access
item (k: READABLE_STRING_GENERAL): detachable ANY
-- Value associated with key `k'.
local
i: INTEGER
s,sk: detachable READABLE_STRING_GENERAL
do
-- Try first directly in values
Result := items.item (k)
if Result = Void then
--| If there is a dot
--| this could be
--| section.variable
--| variable.name or variable[name]
i := k.index_of ('.', 1)
if i > 0 then
s := k.head (i - 1)
-- then search first in section
if attached sections.item (s) as l_section then
sk := k.substring (i + 1, k.count)
Result := l_section.item (sk)
if Result = Void then
Result := item_from_values (l_section, sk)
end
end
if Result = Void then
i := k.index_of ('.', i + 1)
if i > 0 then
-- There is another dot .. could be due to include
across
sections as ic
until
Result /= Void
loop
s := ic.key
-- If has other dot, this may be in sub sections ...
if s.has ('.') and then k.starts_with (s) and then k[s.count + 1] = '.' then
if attached sections.item (s) as l_section then
sk := k.substring (s.count + 2, k.count)
Result := l_section.item (sk)
if Result = Void then
Result := item_from_values (l_section, sk)
end
end
end
end
end
if Result = Void then
-- otherwise in values object.
Result := item_from_values (items, k)
end
end
else
--| Could be
--| variable[name]
Result := item_from_values (items, k)
end
end
end
items: STRING_TABLE [ANY]
sections: STRING_TABLE [like items]
feature -- Access
new_cursor: TABLE_ITERATION_CURSOR [ANY, READABLE_STRING_GENERAL]
-- Fresh cursor associated with current structure
do
Result := items.new_cursor
end
feature {NONE} -- Implementation
value_to_string_32 (obj: detachable ANY): detachable STRING_32
do
if attached {READABLE_STRING_32} obj as s32 then
Result := s32
elseif attached {READABLE_STRING_8} obj as s then
Result := utf.utf_8_string_8_to_escaped_string_32 (s)
end
end
item_from_values (a_values: STRING_TABLE [ANY]; k: READABLE_STRING_GENERAL): detachable ANY
local
i,j: INTEGER
s: READABLE_STRING_GENERAL
do
Result := a_values.item (k)
if Result = Void then
i := k.index_of ('.', 1)
if i > 0 then
s := k.head (i - 1)
if attached {STRING_TABLE [ANY]} a_values.item (s) as l_table then
Result := l_table.item (k.substring (i + 1, k.count))
end
else
i := k.index_of ('[', 1)
if i > 0 then
j := k.index_of (']', i + 1)
if j = k.count then
s := k.head (i - 1)
if attached {STRING_TABLE [ANY]} a_values.item (s) as l_table then
Result := l_table.item (k.substring (i + 1, j - 1))
end
end
end
end
end
end
record_in_section (obj: ANY; k: READABLE_STRING_GENERAL; a_section: READABLE_STRING_GENERAL)
local
v: detachable like items
do
v := sections.item (a_section)
if v = Void then
create v.make (1)
sections.force (v, a_section)
end
v.force (obj, k)
end
parse_content (a_content: STRING_8)
local
i,j,n: INTEGER
s: READABLE_STRING_8
do
last_section_name := Void
from
i := 1
n := a_content.count
until
i > n
loop
j := a_content.index_of ('%N', i)
if j > 0 then
s := a_content.substring (i, j - 1)
i := j + 1
if i <= n and then a_content[i] = '%R' then
i := i + 1
end
else
j := n
s := a_content.substring (i, j)
i := j + 1
end
analyze_line (s, Void)
variant
i
end
last_section_name := Void
rescue
last_section_name := Void
has_error := True
end
parse_file (p: PATH)
do
associated_path := p
last_section_name := Void
import_path (p, Void)
last_section_name := Void
end
import_path (p: PATH; a_section_prefix: detachable READABLE_STRING_32)
-- Import config from path `p'.
local
f: PLAIN_TEXT_FILE
l_last_section_name: like last_section_name
retried: BOOLEAN
l_old_associated_path: like associated_path
do
l_old_associated_path := associated_path
if retried then
has_error := True
else
associated_path := p
l_last_section_name := last_section_name
last_section_name := Void
create f.make_with_path (p)
if f.exists and then f.is_access_readable then
f.open_read
from
until
f.exhausted or f.end_of_file
loop
f.read_line_thread_aware
analyze_line (f.last_string, a_section_prefix)
end
f.close
else
-- File not readable
has_error := True
end
end
last_section_name := l_last_section_name
associated_path := l_old_associated_path
rescue
retried := True
retry
end
analyze_line (a_line: STRING_8; a_section_prefix: detachable READABLE_STRING_32)
-- Analyze line `a_line'.
local
k,sk: STRING_32
v: STRING_8
obj: detachable ANY
lst: LIST [STRING_8]
tb: STRING_TABLE [STRING_8]
i,j: INTEGER
p: PATH
l_section_name: like last_section_name
do
obj := Void
a_line.left_adjust
if
a_line.is_empty
or a_line.is_whitespace
or a_line.starts_with_general ("#")
or a_line.starts_with_general (";")
or a_line.starts_with_general ("--")
then
-- Ignore
elseif a_line.starts_with_general ("[") then
i := a_line.index_of (']', 1)
l_section_name := utf.utf_8_string_8_to_string_32 (a_line.substring (2, i - 1))
l_section_name.left_adjust
l_section_name.right_adjust
if a_section_prefix /= Void then
l_section_name.prepend_character ('.')
l_section_name.prepend (a_section_prefix)
end
last_section_name := l_section_name
else
i := a_line.index_of ('=', 1)
if i > 1 then
k := utf.utf_8_string_8_to_string_32 (a_line.head (i - 1))
k.right_adjust
v := a_line.substring (i + 1, a_line.count)
v.left_adjust
v.right_adjust
if k.is_case_insensitive_equal_general ("@include") then
create p.make_from_string (v)
if not p.is_absolute and attached associated_path as l_path then
p := l_path.parent.extended_path (p)
end
import_path (p, last_section_name)
else
i := k.index_of ('[', 1)
if i > 0 then
j := k.index_of (']', i + 1)
if j = i + 1 then -- ends_with "[]"
k.keep_head (i - 1)
if
a_section_prefix /= Void and then
attached {LIST [STRING_8]} items.item (a_section_prefix + {STRING_32} "." + k) as l_list
then
lst := l_list
elseif
attached last_section_name as l_section_prefix and then
attached {LIST [STRING_8]} items.item (l_section_prefix + {STRING_32} "." + k) as l_list
then
lst := l_list
elseif attached {LIST [STRING_8]} items.item (k) as l_list then
lst := l_list
else
create {ARRAYED_LIST [STRING_8]} lst.make (1)
record_item (lst, k, a_section_prefix)
end
lst.force (v)
obj := lst
elseif j > i then
-- table
sk := k.substring (i + 1, j - 1)
sk.left_adjust
sk.right_adjust
k.keep_head (i - 1)
if
a_section_prefix /= Void and then
attached {STRING_TABLE [STRING_8]} items.item (a_section_prefix + {STRING_32} "." + k) as l_table
then
tb := l_table
elseif
attached last_section_name as l_section_prefix and then
attached {STRING_TABLE [STRING_8]} items.item (l_section_prefix + {STRING_32} "." + k) as l_table
then
tb := l_table
elseif attached {STRING_TABLE [STRING_8]} items.item (k) as l_table then
tb := l_table
else
create tb.make (1)
record_item (tb, k, a_section_prefix)
end
tb.force (v, sk)
obj := tb
else
-- Error missing closing ']'
has_error := True
end
else
record_item (v, k, a_section_prefix)
obj := v
end
if attached last_section_name as l_session and then obj /= Void then
record_in_section (obj, k, l_session)
end
end
else
-- Error
has_error := True
end
end
end
record_item (v: ANY; k: READABLE_STRING_32; a_section_prefix: detachable READABLE_STRING_32)
do
if a_section_prefix /= Void then
items.force (v, a_section_prefix + {STRING_32} "." + k)
else
items.force (v, k)
end
end
feature {NONE} -- Implementation
last_section_name: detachable STRING_32
utf: UTF_CONVERTER
invariant
note
copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end