diff --git a/cms-safe.ecf b/cms-safe.ecf
index 83915ff..0f3a885 100644
--- a/cms-safe.ecf
+++ b/cms-safe.ecf
@@ -9,6 +9,7 @@
+
diff --git a/cms.ecf b/cms.ecf
index 2038d39..ab8b278 100644
--- a/cms.ecf
+++ b/cms.ecf
@@ -12,6 +12,7 @@
+
diff --git a/library/configuration/config-safe.ecf b/library/configuration/config-safe.ecf
new file mode 100644
index 0000000..b29475b
--- /dev/null
+++ b/library/configuration/config-safe.ecf
@@ -0,0 +1,16 @@
+
+
+
+
+
+ /.git$
+ /EIFGENs$
+ /.svn$
+
+
+
+
+
+
+
diff --git a/library/configuration/config.ecf b/library/configuration/config.ecf
new file mode 100644
index 0000000..9be28f0
--- /dev/null
+++ b/library/configuration/config.ecf
@@ -0,0 +1,16 @@
+
+
+
+
+
+ /.git$
+ /EIFGENs$
+ /.svn$
+
+
+
+
+
+
+
diff --git a/library/configuration/license.lic b/library/configuration/license.lic
new file mode 100644
index 0000000..8ad712a
--- /dev/null
+++ b/library/configuration/license.lic
@@ -0,0 +1,10 @@
+${NOTE_KEYWORD}
+ copyright: "2011-${YEAR}, 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
+ ]"
diff --git a/library/configuration/src/config_reader.e b/library/configuration/src/config_reader.e
new file mode 100644
index 0000000..7e36ffc
--- /dev/null
+++ b/library/configuration/src/config_reader.e
@@ -0,0 +1,121 @@
+note
+ description: "Summary description for {CONFIG_READER}."
+ author: ""
+ date: "$Date: 2014-12-18 12:37:11 -0300 (ju. 18 de dic. de 2014) $"
+ revision: "$Revision: 96383 $"
+
+deferred class
+ CONFIG_READER
+
+inherit
+ SHARED_EXECUTION_ENVIRONMENT
+
+feature -- Status report
+
+ has_item (k: READABLE_STRING_GENERAL): BOOLEAN
+ -- Has item associated with key `k'?
+ deferred
+ end
+
+ has_error: BOOLEAN
+ -- Has error?
+ --| Syntax error, source not found, ...
+ deferred
+ end
+
+feature -- Query
+
+ resolved_text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
+ -- String item associated with key `k' and expanded to resolved variables ${varname}.
+ do
+ if attached text_item (k) as s then
+ Result := resolved_expression (s)
+ end
+ end
+
+ text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
+ -- String item associated with key `k'.
+ deferred
+ end
+
+ integer_item (k: READABLE_STRING_GENERAL): INTEGER
+ -- Integer item associated with key `k'.
+ deferred
+ ensure
+ not has_item (k) implies Result = 0
+ end
+
+feature {NONE} -- Implementation
+
+ resolved_items: detachable STRING_TABLE [READABLE_STRING_32]
+ -- Resolved items indexed by expression.
+
+ resolved_expression (exp: READABLE_STRING_GENERAL): STRING_32
+ -- Resolved `exp' using `Current' or else environment variables.
+ local
+ i,n,b,e: INTEGER
+ k: detachable READABLE_STRING_GENERAL
+ c: CHARACTER_32
+ l_resolved_items: like resolved_items
+ l_text: detachable READABLE_STRING_GENERAL
+ do
+ from
+ i := 1
+ n := exp.count
+ create Result.make (n)
+ until
+ i > n
+ loop
+ c := exp[i]
+ if i + 1 < n and then c = '$' and then exp[i+1] = '{' then
+ b := i + 2
+ e := exp.index_of ('}', b) - 1
+ if e > 0 then
+ k := exp.substring (b, e)
+ l_text := Void
+ l_resolved_items := resolved_items
+ if
+ l_resolved_items /= Void and then
+ attached l_resolved_items.item (k) as s
+ then
+ -- Already resolved, reuse value.
+ l_text := s
+ elseif attached text_item (k) as s then
+ -- has such item
+ l_text := s
+ elseif attached execution_environment.item (k) as v then
+ -- Or from environment
+ l_text := v
+ else
+ l_text := exp.substring (i, e + 1)
+ end
+ i := e + 1
+ Result.append_string_general (l_text)
+ else
+ Result.extend (c)
+ end
+ else
+ Result.extend (c)
+ end
+ i := i + 1
+ end
+ end
+
+feature -- Duplication
+
+ sub_config (k: READABLE_STRING_GENERAL): detachable CONFIG_READER
+ -- Configuration representing a subset of Current for key `k'.
+ deferred
+ end
+
+note
+ copyright: "2011-2014, 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
diff --git a/library/configuration/src/ini_config.e b/library/configuration/src/ini_config.e
new file mode 100644
index 0000000..f94845b
--- /dev/null
+++ b/library/configuration/src/ini_config.e
@@ -0,0 +1,463 @@
+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: 2014-12-18 12:37:11 -0300 (ju. 18 de dic. de 2014) $"
+ revision: "$Revision: 96383 $"
+
+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'.
+ local
+ obj: like item
+ do
+ obj := item (k)
+ 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
+
+ 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
+
+ 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
+ do
+ if retried then
+ has_error := True
+ else
+ 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
+ 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
+ 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
+ import_path (create {PATH}.make_from_string (v), 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 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 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-2014, 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
diff --git a/library/configuration/src/json_config.e b/library/configuration/src/json_config.e
new file mode 100644
index 0000000..e6f026d
--- /dev/null
+++ b/library/configuration/src/json_config.e
@@ -0,0 +1,175 @@
+note
+ description: "Object parsing a JSON configuration file."
+ date: "$Date: 2014-12-18 12:37:11 -0300 (ju. 18 de dic. de 2014) $"
+ revision: "$Revision: 96383 $"
+
+class
+ JSON_CONFIG
+
+inherit
+ CONFIG_READER
+
+create
+ make_from_file,
+ make_from_string,
+ make_from_json_object
+
+feature {NONE} -- Initialization
+
+ make_from_file (p: PATH)
+ -- Create object from a file `p'
+ do
+ parse (p)
+ end
+
+ make_from_string (a_json: READABLE_STRING_8)
+ -- Create current config from string `a_json'.
+ local
+ l_parser: JSON_PARSER
+ do
+ create l_parser.make_with_string (a_json)
+ l_parser.parse_content
+ if
+ l_parser.is_valid and then
+ attached l_parser.parsed_json_object as j_o
+ then
+ make_from_json_object (j_o)
+ else
+ has_error := True
+ end
+ end
+
+ make_from_json_object (a_object: JSON_OBJECT)
+ -- Create current config from JSON `a_object'.
+ do
+ associated_path := Void
+ json_value := a_object
+ 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 query `k'.
+ do
+ if attached {JSON_STRING} item (k) as l_string then
+ Result := l_string.unescaped_string_32
+ elseif attached {JSON_NUMBER} item (k) as l_number then
+ Result := l_number.item
+ end
+ end
+
+ integer_item (k: READABLE_STRING_GENERAL): INTEGER
+ -- Integer item associated with key `k'.
+ do
+ if attached {JSON_NUMBER} item (k) as l_number then
+ Result := l_number.item.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 {JSON_OBJECT} item (k) as j_o then
+ create {JSON_CONFIG} Result.make_from_json_object (j_o)
+ end
+ end
+
+feature -- Access
+
+ item (k: READABLE_STRING_GENERAL): detachable JSON_VALUE
+ -- Item associated with query `k' if any.
+ -- `k' can be a single name such as "foo",
+ -- or a qualified name such as "foo.bar" (assuming that "foo" is associated with a JSON object).
+ do
+ if attached json_value as obj then
+ Result := object_json_value (obj, k.to_string_32)
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ object_json_value (a_object: JSON_OBJECT; a_query: READABLE_STRING_32): detachable JSON_VALUE
+ -- Item associated with query `a_query' from object `a_object' if any.
+ local
+ i: INTEGER
+ h: READABLE_STRING_32
+ do
+ i := a_query.index_of ('.', 1)
+ if i > 1 then
+ h := a_query.head (i - 1)
+ if attached {JSON_OBJECT} a_object.item (h) as l_obj then
+ Result := object_json_value (l_obj, a_query.substring (i + 1, a_query.count))
+ else
+ -- This is not an object...
+ Result := Void
+ end
+ else
+ Result := a_object.item (a_query)
+ end
+ end
+
+ parse (a_path: PATH)
+ local
+ l_parser: JSON_PARSER
+ do
+ associated_path := a_path
+ has_error := False
+ if attached json_file_from (a_path) as json_file then
+ l_parser := new_json_parser (json_file)
+ l_parser.parse_content
+ if
+ l_parser.is_valid and then
+ attached l_parser.parsed_json_object as jv
+ then
+ json_value := jv
+ else
+ has_error := True
+ end
+ else
+ has_error := True
+ end
+ end
+
+feature {NONE} -- JSON
+
+ json_value: detachable JSON_OBJECT
+ -- Possible json object representation.
+
+ json_file_from (a_fn: PATH): detachable STRING
+ do
+ Result := (create {JSON_FILE_READER}).read_json_from (a_fn.name.out)
+ end
+
+ new_json_parser (a_string: STRING): JSON_PARSER
+ do
+ create Result.make_with_string (a_string)
+ end
+
+note
+ copyright: "2011-2014, 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
diff --git a/library/configuration/tests/config_tests-safe.ecf b/library/configuration/tests/config_tests-safe.ecf
new file mode 100644
index 0000000..0986a65
--- /dev/null
+++ b/library/configuration/tests/config_tests-safe.ecf
@@ -0,0 +1,18 @@
+
+
+
+
+
+ /.git$
+ /EIFGENs$
+ /.svn$
+
+
+
+
+
+
+
+
+
diff --git a/library/configuration/tests/config_tests.ecf b/library/configuration/tests/config_tests.ecf
new file mode 100644
index 0000000..3f93907
--- /dev/null
+++ b/library/configuration/tests/config_tests.ecf
@@ -0,0 +1,18 @@
+
+
+
+
+
+ /.git$
+ /EIFGENs$
+ /.svn$
+
+
+
+
+
+
+
+
+
diff --git a/library/configuration/tests/test_config_reader_set.e b/library/configuration/tests/test_config_reader_set.e
new file mode 100644
index 0000000..bfec54c
--- /dev/null
+++ b/library/configuration/tests/test_config_reader_set.e
@@ -0,0 +1,177 @@
+note
+ description: "[
+ Testing suite for CONFIG_READER .
+ ]"
+ author: "$Author: jfiat $"
+ date: "$Date: 2014-12-18 12:37:11 -0300 (ju. 18 de dic. de 2014) $"
+ revision: "$Revision: 96383 $"
+
+class
+ TEST_CONFIG_READER_SET
+
+inherit
+ EQA_TEST_SET
+
+feature -- Test
+
+ test_ini
+ local
+ cfg: CONFIG_READER
+ do
+ create {INI_CONFIG} cfg.make_from_string ("[
+ foo = bar
+
+ [first]
+ abc = 1
+ def = and so on
+ ghi = "path"
+
+ [ second ]
+ this = 1
+ is = 2
+ the = 3
+ end = 4
+
+ ]")
+
+ assert ("is valid", not cfg.has_error)
+ assert ("has_item (foo)", cfg.has_item ("foo"))
+ assert ("has_item (abc)", cfg.has_item ("abc"))
+ assert ("has_item (def)", cfg.has_item ("def"))
+ assert ("has_item (ghi)", cfg.has_item ("ghi"))
+ assert ("has_item (this)", cfg.has_item ("this"))
+ assert ("has_item (is)", cfg.has_item ("is"))
+ assert ("has_item (the)", cfg.has_item ("the"))
+ assert ("has_item (end)", cfg.has_item ("end"))
+
+ assert ("item (foo)", attached cfg.text_item ("foo") as v and then v.same_string_general ("bar"))
+ assert ("item (abc)", attached cfg.text_item ("abc") as v and then v.same_string_general ("1"))
+ assert ("item (def)", attached cfg.text_item ("def") as v and then v.same_string_general ("and so on"))
+ assert ("item (ghi)", attached cfg.text_item ("ghi") as v and then v.same_string_general ("%"path%""))
+ assert ("item (this)", attached cfg.text_item ("this") as v and then v.same_string_general ("1"))
+ assert ("item (is)", attached cfg.text_item ("is") as v and then v.same_string_general ("2"))
+ assert ("item (the)", attached cfg.text_item ("the") as v and then v.same_string_general ("3"))
+ assert ("item (end)", attached cfg.text_item ("end") as v and then v.same_string_general ("4"))
+
+ assert ("has_item (first.abc)", cfg.has_item ("first.abc"))
+ assert ("item (first.abc)", attached cfg.text_item ("first.abc") as v and then v.same_string_general ("1"))
+ assert ("has_item (second.is)", cfg.has_item ("second.is"))
+ assert ("item (second.is)", attached cfg.text_item ("second.is") as v and then v.same_string_general ("2"))
+
+ if attached cfg.sub_config ("second") as cfg_second then
+ assert ("has_item (is)", cfg_second.has_item ("is"))
+ assert ("item (is)", attached cfg_second.text_item ("is") as v and then v.same_string_general ("2"))
+
+ else
+ assert ("has second", False)
+ end
+
+ end
+
+ test_resolver_ini
+ local
+ cfg: CONFIG_READER
+ do
+ create {INI_CONFIG} cfg.make_from_string ("[
+ foo = bar
+
+ [extra]
+ a.b.c = abc
+
+ [expression]
+ text = ${foo}/${a.b.c}
+ ]")
+
+ assert ("is valid", not cfg.has_error)
+ assert ("has_item (extra.a.b.c)", cfg.has_item ("extra.a.b.c"))
+ assert ("has_item (extra.a.b.c)", cfg.has_item ("extra.a.b.c"))
+ assert ("has_item (expression.text)", cfg.has_item ("expression.text"))
+ assert ("item (expression.text)", attached cfg.resolved_text_item ("expression.text") as s and then s.same_string_general ("bar/abc"))
+ end
+
+ test_deep_ini
+ local
+ cfg: CONFIG_READER
+ f: RAW_FILE
+ do
+
+ create f.make_with_name ("test_deep.ini")
+ f.create_read_write
+ f.put_string ("[
+ test = extra
+ [new]
+ enabled = true
+ ]"
+ )
+ f.close
+ create {INI_CONFIG} cfg.make_from_string ("[
+ foo = bar
+
+ [extra]
+ a.b.c = abc
+
+ [outside]
+ before = include
+ @include=test_deep.ini
+
+ ]")
+ f.delete
+
+ assert ("is valid", not cfg.has_error)
+ assert ("has_item (extra.a.b.c)", cfg.has_item ("extra.a.b.c"))
+ assert ("has_item (extra.a.b.c)", cfg.has_item ("extra.a.b.c"))
+ assert ("has_item (outside.new.enabled)", cfg.has_item ("outside.new.enabled"))
+ end
+
+
+ test_json
+ local
+ cfg: CONFIG_READER
+ do
+ create {JSON_CONFIG} cfg.make_from_string ("[
+ {
+ "foo": "bar",
+ "first": {
+ "abc": 1,
+ "def": "and so on",
+ "ghi": "\"path\""
+ },
+ "second": {
+ "this": 1,
+ "is": 2,
+ "the": 3,
+ "end": 4
+ }
+ }
+ ]")
+
+ assert ("is valid", not cfg.has_error)
+ assert ("has_item (foo)", cfg.has_item ("foo"))
+ assert ("has_item (first.abc)", cfg.has_item ("first.abc"))
+ assert ("has_item (first.def)", cfg.has_item ("first.def"))
+ assert ("has_item (first.ghi)", cfg.has_item ("first.ghi"))
+ assert ("has_item (second.this)", cfg.has_item ("second.this"))
+ assert ("has_item (second.is)", cfg.has_item ("second.is"))
+ assert ("has_item (second.the)", cfg.has_item ("second.the"))
+ assert ("has_item (second.end)", cfg.has_item ("second.end"))
+
+ assert ("item (foo)", attached cfg.text_item ("foo") as v and then v.same_string_general ("bar"))
+ assert ("item (first.abc)", attached cfg.text_item ("first.abc") as v and then v.same_string_general ("1"))
+ assert ("item (first.def)", attached cfg.text_item ("first.def") as v and then v.same_string_general ("and so on"))
+ assert ("item (first.ghi)", attached cfg.text_item ("first.ghi") as v and then v.same_string_general ("%"path%""))
+ assert ("item (second.this)", attached cfg.text_item ("second.this") as v and then v.same_string_general ("1"))
+ assert ("item (second.is)", attached cfg.text_item ("second.is") as v and then v.same_string_general ("2"))
+ assert ("item (second.the)", attached cfg.text_item ("second.the") as v and then v.same_string_general ("3"))
+ assert ("item (second.end)", attached cfg.text_item ("second.end") as v and then v.same_string_general ("4"))
+
+ if attached cfg.sub_config ("second") as cfg_second then
+ assert ("has_item (is)", cfg_second.has_item ("is"))
+ assert ("item (is)", attached cfg_second.text_item ("is") as v and then v.same_string_general ("2"))
+
+ else
+ assert ("has second", False)
+ end
+ end
+
+
+end
diff --git a/library/src/configuration/cms_configuration.e b/library/src/configuration/cms_configuration.e
deleted file mode 100644
index 5e13c4e..0000000
--- a/library/src/configuration/cms_configuration.e
+++ /dev/null
@@ -1,317 +0,0 @@
-note
- description: "[
- Configure the basic settings for a CMS application,
- i.e: where to look for themes, name of the application, etc...
-
- The settings can be configured by default:
- - using the current working directory,
- - using the commands provided by the class
- - or by an external configuration file.
- ]"
-
-class
- CMS_CONFIGURATION
-
-inherit
- ANY
-
- SHARED_EXECUTION_ENVIRONMENT
- export
- {NONE} all
- end
-
-create
- make
-
-feature {NONE} -- Initialization
-
- make (a_layout: CMS_LAYOUT)
- -- Initialize `Current' with layout `a_layout'.
- do
- layout := a_layout
- create options.make_equal (10)
- configuration_location := layout.cms_config_ini_path
- import_from_path (layout.cms_config_ini_path)
- analyze
- end
-
- analyze
- do
- get_root_location
- get_var_location
- get_themes_location
- get_files_location
- get_smtp
- end
-
-feature -- Access
-
-
- configuration_location: detachable PATH
- -- Path to configuration location.
-
- option (a_name: READABLE_STRING_GENERAL): detachable ANY
- do
- Result := options.item (a_name)
- end
-
- options: STRING_TABLE [STRING_32]
-
-feature -- Conversion
-
- append_to_string (s: STRING)
- local
- utf: UTF_CONVERTER
- do
- s.append ("Options:%N")
- across
- options as c
- loop
- s.append (c.key.to_string_8)
- s.append_character ('=')
- utf.string_32_into_utf_8_string_8 (c.item, s)
- s.append_character ('%N')
- end
-
- s.append ("Specific:%N")
- s.append ("root_location=" + root_location.utf_8_name + "%N")
- s.append ("var_location=" + var_location.utf_8_name + "%N")
- s.append ("files_location=" + files_location.utf_8_name + "%N")
- s.append ("themes_location=" + themes_location.utf_8_name + "%N")
- end
-
-feature -- Element change
-
- set_option (a_name: READABLE_STRING_GENERAL; a_value: STRING_32)
- do
- options.force (a_value, a_name.as_string_8)
- end
-
-feature -- Access
-
- var_location: PATH
-
- root_location: PATH
-
- files_location: PATH
-
- themes_location: PATH
-
- theme_name (dft: detachable like theme_name): READABLE_STRING_8
- do
- if attached options.item ("theme") as s then
- Result := s
- elseif dft /= Void then
- Result := dft
- else
- Result := "default"
- end
- end
-
- site_id: READABLE_STRING_8
- do
- if attached options.item ("site.id") as s then
- Result := s
- else
- Result := "_EWF_CMS_NO_ID_"
- end
- end
-
- site_name (dft: like site_name): READABLE_STRING_8
- do
- if attached options.item ("site.name") as s then
- Result := s
- else
- Result := dft
- end
- end
-
- site_url (dft: like site_url): detachable READABLE_STRING_8
- do
- if attached options.item ("site.url") as s then
- Result := s
- else
- Result := dft
- end
- if Result /= Void then
- if Result.is_empty then
- -- ok
- elseif not Result.ends_with ("/") then
- Result := Result + "/"
- end
- end
- end
-
- site_script_url (dft: like site_script_url): detachable READABLE_STRING_8
- do
- if attached options.item ("site.script_url") as s then
- Result := s
- else
- Result := dft
- end
- if Result /= Void then
- if Result.is_empty then
- elseif not Result.ends_with ("/") then
- Result := Result + "/"
- end
- end
- end
-
- site_email (dft: like site_email): READABLE_STRING_8
- do
- if attached options.item ("site.email") as s then
- Result := s
- else
- Result := dft
- end
- end
-
- smtp: detachable READABLE_STRING_8
-
-
-feature -- Change
-
- get_var_location
- local
- utf: UTF_CONVERTER
- do
- if attached options.item ("var-dir") as s then
- create var_location.make_from_string (utf.utf_8_string_8_to_escaped_string_32 (s))
- else
- var_location := execution_environment.current_working_path
- end
- end
-
- get_root_location
- do
- root_location := layout.www_path
- end
-
- get_files_location
- local
- utf: UTF_CONVERTER
- do
- if attached options.item ("files-dir") as s then
- create files_location.make_from_string (utf.utf_8_string_8_to_escaped_string_32 (s))
- else
- create files_location.make_from_string ("files")
- end
- end
-
- get_themes_location
- local
- utf: UTF_CONVERTER
- do
- if attached options.item ("themes-dir") as s then
- create themes_location.make_from_string (utf.utf_8_string_8_to_escaped_string_32 (s))
- else
- themes_location := root_location.extended ("themes")
- end
- end
-
- get_smtp
- do
- if attached options.item ("smtp") as s then
- smtp := s
- end
- end
-
-feature {NONE} -- Implementation
-
- import_from_file (fn: READABLE_STRING_GENERAL)
- do
- import_from_path (create {PATH}.make_from_string (fn))
- end
-
- import_from_path (a_filename: PATH)
- -- Import ini file content
- local
- f: PLAIN_TEXT_FILE
- l,v: STRING_8
- p: INTEGER
- do
- create f.make_with_path (a_filename)
- if f.exists and f.is_readable then
- f.open_read
- from
- f.read_line
- until
- f.exhausted
- loop
- l := f.last_string
- l.left_adjust
- if not l.is_empty then
- if l[1] = '#' then
- -- commented line
- else
- p := l.index_of ('=', 1)
- if p > 1 then
- v := l.substring (p + 1, l.count)
- l.keep_head (p - 1)
- v.left_adjust
- v.right_adjust
- l.right_adjust
-
- if l.is_case_insensitive_equal ("@include") then
- import_from_file (resolved_string (v))
- else
- set_option (l.as_lower, resolved_string (v))
- end
- end
- end
- end
- f.read_line
- end
- f.close
- end
- end
-
-feature {NONE} -- Environment
-
- resolved_string (s: READABLE_STRING_8): STRING_32
- -- Resolved `s' using `options' or else environment variables.
- local
- i,n,b,e: INTEGER
- k: detachable READABLE_STRING_8
- do
- from
- i := 1
- n := s.count
- create Result.make (s.count)
- until
- i > n
- loop
- if i + 1 < n and then s[i] = '$' and then s[i+1] = '{' then
- b := i + 2
- e := s.index_of ('}', b) - 1
- if e > 0 then
- k := s.substring (b, e)
- if attached option (k) as v then
- if attached {READABLE_STRING_32} v as s32 then
- Result.append (s32)
- else
- Result.append (v.out)
- end
- i := e + 1
- elseif attached execution_environment.item (k) as v then
- Result.append (v)
- i := e + 1
- else
- Result.extend (s[i])
- end
- else
- Result.extend (s[i])
- end
- else
- Result.extend (s[i])
- end
- i := i + 1
- end
- end
-
-
-feature -- Implementation
-
- layout: CMS_LAYOUT
- -- Cms layout
-end
diff --git a/library/src/configuration/cms_setup.e b/library/src/configuration/cms_setup.e
index c2223f8..a9ad317 100644
--- a/library/src/configuration/cms_setup.e
+++ b/library/src/configuration/cms_setup.e
@@ -1,16 +1,13 @@
note
description: "Class that enable to set basic configuration, application layout, core modules and themes."
- date: "$Date$"
- revision: "$Revision$"
+ date: "$Date: 2014-12-18 12:47:20 -0300 (ju. 18 de dic. de 2014) $"
+ revision: "$Revision: 96384 $"
deferred class
CMS_SETUP
feature -- Access
- configuration: CMS_CONFIGURATION
- -- cms configuration.
-
layout: CMS_LAYOUT
-- CMS layout.
@@ -59,7 +56,8 @@ feature -- Access: Site
-- Name of the site.
site_email: READABLE_STRING_8
- -- Email for the site.
+ -- Admin email address for the site.
+ -- Mainly used for internal notification.
site_url: detachable READABLE_STRING_8
-- Optional base url of the site.
@@ -69,8 +67,31 @@ feature -- Access: Site
-- By default "" or "/".
smtp: detachable READABLE_STRING_8
- -- Smtp server
-
+ -- Smtp server
+
+feature -- Query
+
+ text_item (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
+ -- Configuration value associated with `a_name', if any.
+ deferred
+ end
+
+ text_item_or_default (a_name: READABLE_STRING_GENERAL; a_default_value: READABLE_STRING_GENERAL): READABLE_STRING_32
+ -- `text_item' associated with `a_name' or if none, `a_default_value'.
+ do
+ if attached text_item (a_name) as v then
+ Result := v
+ else
+ Result := a_default_value.as_string_32
+ end
+ end
+
+ string_8_item (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_8
+ -- Configuration value associated with `a_name', if any.
+ deferred
+ end
+
+
feature -- Access: Theme
themes_location: PATH
diff --git a/library/src/modules/cms_debug_module.e b/library/src/modules/cms_debug_module.e
index da1c39f..2468f48 100644
--- a/library/src/modules/cms_debug_module.e
+++ b/library/src/modules/cms_debug_module.e
@@ -1,7 +1,7 @@
note
description: "Summary description for {CMS_DEBUG_MODULE}."
- date: "$Date: 2014-08-28 13:21:49 +0200 (jeu., 28 août 2014) $"
- revision: "$Revision: 95708 $"
+ date: "$Date: 2014-12-18 12:47:20 -0300 (ju. 18 de dic. de 2014) $"
+ revision: "$Revision: 96384 $"
class
CMS_DEBUG_MODULE
@@ -82,7 +82,7 @@ feature -- Handler
append_info_to ("Name", api.setup.site_name, r, s)
append_info_to ("Url", api.setup.site_url, r, s)
- if attached api.setup.configuration.configuration_location as l_loc then
+ if attached api.setup.layout.cms_config_ini_path as l_loc then
s.append ("