diff --git a/Gemfile.lock b/Gemfile.lock index a968d18..f0ce4fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ PATH remote: . specs: configman (1.0.0) + deep_dup inifile GEM @@ -9,6 +10,7 @@ GEM specs: ast (2.4.2) base64 (0.1.1) + deep_dup (0.0.3) diff-lcs (1.5.0) inifile (3.0.0) json (2.6.3) diff --git a/configman.gemspec b/configman.gemspec index 6eca2d7..c7ffabc 100644 --- a/configman.gemspec +++ b/configman.gemspec @@ -28,6 +28,7 @@ Gem::Specification.new do |spec| # Uncomment to register a new dependency of your gem # spec.add_dependency "example-gem", "~> 1.0" + spec.add_dependency 'deep_dup' spec.add_dependency 'inifile' # For more information and examples about making a new gem, check out our diff --git a/lib/configman.rb b/lib/configman.rb index 2f68eaf..ed2ea8a 100644 --- a/lib/configman.rb +++ b/lib/configman.rb @@ -14,6 +14,20 @@ module ConfigMan @loaded_parser = nil @custom_modules = [] + EXPECTED_KEYS = { + 'API' => %w[api_endpoint api_key rate_limit], + 'Cache' => %w[cache_type host port], + 'Database' => %w[db_host db_port db_user db_password db_name], + 'Email' => %w[smtp_server smtp_port smtp_user smtp_password smtp_protocol], + 'FileStorage' => %w[storage_type cloud_provider local_path], + 'Localization' => %w[language time_zone encoding], + 'Logging' => %w[log_level log_file log_rotation] + }.freeze + + def self.expected_keys + EXPECTED_KEYS + end + # register any custom modules the user provides def self.register_module(file_path) raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path) @@ -30,13 +44,23 @@ module ConfigMan @custom_modules << mod_class end + def self.used_modules + @used_modules + end + # Setup ConfigMan with presets def self.setup(default_modules, custom_options = {}) + # Check if a parser is loaded + raise 'No parser module loaded. Please load a parser module before calling setup.' if @loaded_parser.nil? + final_config = {} + # Remove parser names from default_modules + default_modules = default_modules.reject { |mod| %w[json ini xml yaml].include?(mod.downcase) } + # Populate defaults from built-in modules default_modules.each do |mod| - mod_class = Object.const_get("ConfigMan::Modules::#{mod.capitalize}") + mod_class = Object.const_get("ConfigMan::Modules::#{mod}") final_config.merge!(mod_class.populate_defaults) end @@ -85,6 +109,7 @@ module ConfigMan if %w[json ini xml yaml].include?(module_name.downcase) # It's a parser require_relative "configman/parsers/#{module_name.downcase}" + @loaded_parser = module_name.downcase else # It's a regular module require_relative "configman/modules/#{module_name.downcase}" diff --git a/lib/configman/modules/api.rb b/lib/configman/modules/api.rb index 76f6b07..78ed421 100644 --- a/lib/configman/modules/api.rb +++ b/lib/configman/modules/api.rb @@ -2,7 +2,7 @@ module ConfigMan module Modules - module Api + module API def self.populate_defaults { 'api_endpoint' => 'https://api.example.com', diff --git a/lib/configman/modules/localization.rb b/lib/configman/modules/localization.rb index f074984..cd5e2d6 100644 --- a/lib/configman/modules/localization.rb +++ b/lib/configman/modules/localization.rb @@ -6,7 +6,8 @@ module ConfigMan def self.populate_defaults { 'language' => 'en', - 'time_zone' => 'UTC' + 'time_zone' => 'UTC', + 'encoding' => 'UTF-8' } end end diff --git a/lib/configman/modules/utils.rb b/lib/configman/modules/utils.rb new file mode 100644 index 0000000..c3a7795 --- /dev/null +++ b/lib/configman/modules/utils.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# config_util.rb + +module ConfigMan + module Utils + def self.sort_into_sections(config_hash, expected_keys, loaded_modules) + sorted_config = Hash.new { |hash, key| hash[key] = {} } + + config_hash.each do |key, value| + section_found = false + + expected_keys.each do |section, keys| + next unless loaded_modules.include?(section) + + next unless keys.include?(key) + + sorted_config[section][key] = value + section_found = true + break + end + + sorted_config['General'][key] = value unless section_found + end + + sorted_config + end + end +end diff --git a/lib/configman/parsers/ini.rb b/lib/configman/parsers/ini.rb index 6722c7e..1960cda 100644 --- a/lib/configman/parsers/ini.rb +++ b/lib/configman/parsers/ini.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'inifile' +require_relative '../modules/utils' module ConfigMan module Parsers @@ -30,7 +31,19 @@ module ConfigMan end def self.write(config_hash) - ini = IniFile.new(content: config_hash) + # Access the loaded modules and expected keys from the main class + loaded_modules = ConfigMan.used_modules + expected_keys = ConfigMan.expected_keys + + # Sort the keys into their respective sections + sorted_config = Utils.sort_into_sections(config_hash, expected_keys, loaded_modules) + + # Debug statement to output the sorted_config + puts 'Debug: About to write the following sorted_config to INI file:' + p sorted_config + + # Write the sorted config to the INI file + ini = IniFile.new(content: sorted_config) ini.write(filename: CONFIG_FILE_PATH) end end diff --git a/lib/configman/parsers/json.rb b/lib/configman/parsers/json.rb index 4762d46..9ae1594 100644 --- a/lib/configman/parsers/json.rb +++ b/lib/configman/parsers/json.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'json' +require_relative '../modules/utils' module ConfigMan module Parsers @@ -11,8 +12,6 @@ module ConfigMan def self.parse(file_path) raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path) - @file_path = file_path - file_content = File.read(file_path) parsed_config = ::JSON.parse(file_content) @@ -22,21 +21,24 @@ module ConfigMan end def self.update(key, new_value) - # Read existing config - existing_config = parse - - # Update the value + existing_config = parse(CONFIG_FILE_PATH) existing_config[key] = new_value - # Write the updated config back to the file File.open(CONFIG_FILE_PATH, 'w') do |file| file.write(::JSON.pretty_generate(existing_config)) end end def self.write(config_hash) + # Access the loaded modules and expected keys from the main class + loaded_modules = ConfigMan.used_modules + expected_keys = ConfigMan.expected_keys + + # Use the utility method to sort the keys into their respective sections + sorted_config = Utils.sort_into_sections(config_hash, expected_keys, loaded_modules) + File.open(CONFIG_FILE_PATH, 'w') do |file| - file.write(::JSON.pretty_generate(config_hash)) + file.write(::JSON.pretty_generate(sorted_config)) end end end diff --git a/lib/configman/parsers/xml.rb b/lib/configman/parsers/xml.rb index 0c2acab..5637892 100644 --- a/lib/configman/parsers/xml.rb +++ b/lib/configman/parsers/xml.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true require 'rexml/document' +require 'rexml/formatters/pretty' +require_relative '../modules/utils' module ConfigMan module Parsers @@ -32,15 +34,32 @@ module ConfigMan doc.add_element('config') existing_config.each { |k, v| doc.root.add_element(k).text = v } - File.open(CONFIG_FILE_PATH, 'w') { |file| doc.write(file) } + formatter = REXML::Formatters::Pretty.new + File.open(CONFIG_FILE_PATH, 'w') do |file| + formatter.write(doc, file) + end end def self.write(config_hash) + # Access the loaded modules and expected keys from the main class + loaded_modules = ConfigMan.used_modules + expected_keys = ConfigMan.expected_keys + + # Sort the keys into their respective sections + sorted_config = Utils.sort_into_sections(config_hash, expected_keys, loaded_modules) + doc = REXML::Document.new doc.add_element('config') - config_hash.each { |k, v| doc.root.add_element(k).text = v } - File.open(CONFIG_FILE_PATH, 'w') { |file| doc.write(file) } + sorted_config.each do |section, section_data| + section_element = doc.root.add_element(section) + section_data.each { |k, v| section_element.add_element(k).text = v } + end + + formatter = REXML::Formatters::Pretty.new + File.open(CONFIG_FILE_PATH, 'w') do |file| + formatter.write(doc, file) + end end end end diff --git a/lib/configman/parsers/yaml.rb b/lib/configman/parsers/yaml.rb index ac3f85a..ec41f45 100644 --- a/lib/configman/parsers/yaml.rb +++ b/lib/configman/parsers/yaml.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'yaml' +require_relative '../modules/utils' module ConfigMan module Parsers @@ -28,8 +29,15 @@ module ConfigMan end def self.write(config_hash) + # Access the loaded modules and expected keys from the main class + loaded_modules = ConfigMan.used_modules + expected_keys = ConfigMan.expected_keys + + # Use the utility method to sort the keys into their respective sections + sorted_config = Utils.sort_into_sections(config_hash, expected_keys, loaded_modules) + File.open(CONFIG_FILE_PATH, 'w') do |file| - file.write(config_hash.to_yaml) + file.write(sorted_config.to_yaml) end end end