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 ("
") append_info_to ("Configuration file", l_loc.name, r, s) end diff --git a/library/src/modules/node/handler/node_content_handler.e b/library/src/modules/node/handler/node_content_handler.e index 11ebd9d..8d20d3d 100644 --- a/library/src/modules/node/handler/node_content_handler.e +++ b/library/src/modules/node/handler/node_content_handler.e @@ -1,7 +1,7 @@ note description: "Summary description for {NEW_CONTENT_HANDLER}." - date: "$Date$" - revision: "$Revision$" + date: "$Date: 2014-12-19 10:17:32 -0300 (vi., 19 dic. 2014) $" + revision: "$Revision: 96402 $" class NODE_CONTENT_HANDLER @@ -78,7 +78,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) @@ -96,14 +96,14 @@ feature -- HTTP Methods if l_method.is_case_insensitive_equal ("PUT") then do_put (req, res) else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end end else do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) @@ -127,7 +127,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) diff --git a/library/src/modules/node/handler/node_handler.e b/library/src/modules/node/handler/node_handler.e index 2dcb47f..b96cba7 100644 --- a/library/src/modules/node/handler/node_handler.e +++ b/library/src/modules/node/handler/node_handler.e @@ -1,7 +1,7 @@ note description: "Summary description for {NODE_HANDLER}." - date: "$Date$" - revision: "$Revision$" + date: "$Date: 2014-12-19 10:17:32 -0300 (vi., 19 dic. 2014) $" + revision: "$Revision: 96402 $" class NODE_HANDLER @@ -96,7 +96,7 @@ feature -- HTTP Methods elseif l_method.is_case_insensitive_equal ("PUT") then do_put (req, res) else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end end else @@ -131,7 +131,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) @@ -151,7 +151,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) diff --git a/library/src/modules/node/handler/node_summary_handler.e b/library/src/modules/node/handler/node_summary_handler.e index 01553b2..a94552f 100644 --- a/library/src/modules/node/handler/node_summary_handler.e +++ b/library/src/modules/node/handler/node_summary_handler.e @@ -1,7 +1,7 @@ note description: "Summary description for {NODE_SUMMARY_HANDLER}." - date: "$Date$" - revision: "$Revision$" + date: "$Date: 2014-12-19 10:17:32 -0300 (vi., 19 dic. 2014) $" + revision: "$Revision: 96402 $" class NODE_SUMMARY_HANDLER @@ -77,7 +77,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) @@ -95,14 +95,14 @@ feature -- HTTP Methods if l_method.is_case_insensitive_equal ("PUT") then do_put (req, res) else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end end else do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) @@ -125,7 +125,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) diff --git a/library/src/modules/node/handler/node_title_handler.e b/library/src/modules/node/handler/node_title_handler.e index 0cfe30d..3e5d062 100644 --- a/library/src/modules/node/handler/node_title_handler.e +++ b/library/src/modules/node/handler/node_title_handler.e @@ -1,7 +1,7 @@ note description: "Summary description for {NODE_TITLE_HANDLER}." - date: "$Date$" - revision: "$Revision$" + date: "$Date: 2014-12-19 10:17:32 -0300 (vi., 19 dic. 2014) $" + revision: "$Revision: 96402 $" class NODE_TITLE_HANDLER @@ -77,7 +77,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) @@ -94,14 +94,14 @@ feature -- HTTP Methods if l_method.is_case_insensitive_equal ("PUT") then do_put (req, res) else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end end else do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) @@ -125,7 +125,7 @@ feature -- HTTP Methods do_error (req, res, l_id) end else - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute end else (create {CMS_GENERIC_RESPONSE}).new_response_unauthorized (req, res) diff --git a/library/src/service/cms_service.e b/library/src/service/cms_service.e index 4a339fe..dca91e0 100644 --- a/library/src/service/cms_service.e +++ b/library/src/service/cms_service.e @@ -45,7 +45,6 @@ feature {NONE} -- Initialization -- Build a CMS service with `a_api' do api := a_api - configuration := a_api.setup.configuration initialize ensure api_set: api = a_api @@ -230,10 +229,6 @@ feature -- Access Result := api.setup end - configuration: CMS_CONFIGURATION - -- CMS configuration. - --| Maybe we can compute it (using `setup') instead of using memory. - modules: CMS_MODULE_COLLECTION -- Configurator of possible modules. diff --git a/library/src/service/filter/cms_error_filter.e b/library/src/service/filter/cms_error_filter.e index 337e87e..07ad85c 100644 --- a/library/src/service/filter/cms_error_filter.e +++ b/library/src/service/filter/cms_error_filter.e @@ -1,7 +1,7 @@ note description: "Summary description for {CMS_ERROR_FILTER}." - date: "$Date$" - revision: "$Revision$" + date: "$Date: 2014-12-19 10:17:32 -0300 (vi., 19 dic. 2014) $" + revision: "$Revision: 96402 $" class CMS_ERROR_FILTER @@ -32,7 +32,7 @@ feature -- Basic operations execute_next (req, res) else log.write_critical (generator + ".execute" + api.as_string_representation ) - (create {ERROR_500_CMS_RESPONSE}.make (req, res, api)).execute + (create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, api)).execute api.reset end end diff --git a/library/src/service/response/bad_request_error_cms_response.e b/library/src/service/response/bad_request_error_cms_response.e new file mode 100644 index 0000000..98af2b5 --- /dev/null +++ b/library/src/service/response/bad_request_error_cms_response.e @@ -0,0 +1,38 @@ +note + description: "Summary description for {BAD_REQUEST_ERROR_CMS_RESPONSE}." + date: "$Date: 2014-12-19 10:17:32 -0300 (vi., 19 dic. 2014) $" + revision: "$Revision: 96402 $" + +class + BAD_REQUEST_ERROR_CMS_RESPONSE + +inherit + + CMS_RESPONSE + redefine + custom_prepare + end + +create + make + +feature -- Generation + + custom_prepare (page: CMS_HTML_PAGE) + do + page.register_variable (request.absolute_script_url (request.path_info), "request") + page.set_status_code ({HTTP_STATUS_CODE}.bad_request) + page.register_variable (page.status_code.out, "code") + end + +feature -- Execution + + process + -- Computed response message. + do + set_title ("Bad request") + set_page_title ("Bad request") + set_main_content ("Bad request.") + end + +end diff --git a/library/src/service/response/error_500_cms_response.e b/library/src/service/response/internal_server_error_cms_response.e similarity index 65% rename from library/src/service/response/error_500_cms_response.e rename to library/src/service/response/internal_server_error_cms_response.e index 7bfea99..06abb43 100644 --- a/library/src/service/response/error_500_cms_response.e +++ b/library/src/service/response/internal_server_error_cms_response.e @@ -1,10 +1,10 @@ note - description: "Summary description for {ERROR_500_CMS_RESPONSE}." - date: "$Date$" - revision: "$Revision$" + description: "Summary description for {INTERNAL_SERVER_ERROR_CMS_RESPONSE}." + date: "$Date: 2014-12-19 10:17:32 -0300 (vi., 19 dic. 2014) $" + revision: "$Revision: 96402 $" class - ERROR_500_CMS_RESPONSE + INTERNAL_SERVER_ERROR_CMS_RESPONSE inherit @@ -32,6 +32,7 @@ feature -- Execution do set_title ("Internal Server Error") set_page_title (Void) + set_main_content ("Internal Server Error") end end