| 
						
						
							
								
							
						
						
					 | 
				
				 | 
				
					@ -4,24 +4,57 @@ require 'dotenv' | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					module HotConstants | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  HOTENV = {} # rubocop:disable Style/MutableConstant | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  LISTENERS = {} # rubocop:disable Style/MutableConstant | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  LOGGER = SemanticLogger[to_s] | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  LOGGER = SemanticLogger[self] | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  class << self | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def initialize | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      env_sample = Dotenv.parse('.env.sample') # TODO: able to detect add/remove, then either call define_method or undef_method! | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      env_sample.each do |key, value| | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        dkey = key.downcase | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        method = method_from_value(value) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        singleton_class.define_method(dkey) { method.call(key, value) } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        LOGGER.info("create method <#{dkey}> performing <#{method.name}> with default value <#{value}> ") | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      @old_definitions = {} | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      @hot_definitions = {} | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      set_definitions | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      load_values | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def set_definitions | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      LOGGER.info('--set_definitions') | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      new_definitions = Dotenv.parse('.env.sample') | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      definitions_to_actualize = {} | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      @old_definitions.merge(new_definitions) do |key, old_value, new_value| | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        definitions_to_actualize[key] = new_value if new_value != old_value | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      definitions_to_actualize.each { |key, value| define_method(:actualize, key, value) } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      @old_definitions = new_definitions | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      definitions_to_remove = @hot_definitions.except(*new_definitions.keys) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      definitions_to_remove.each_pair do |key, dkey| | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        LOGGER.info("remove method <#{dkey}>") | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        singleton_class.undef_method(dkey) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        @hot_definitions.delete(key) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      definitions_to_add = new_definitions.except(*@hot_definitions.keys) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      definitions_to_add.each { |key, value| define_method(:add, key, value) } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def reload! | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      HOTENV.replace Dotenv.parse # TODO: detect true changes before processing block below (or yield) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      LISTENERS.each_pair do |key, block| | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        perform_change(key, block) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def load_values | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      LOGGER.info('--load_values') | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      new_env = Dotenv.parse | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      constants_to_delete = HOTENV.except(*new_env.keys) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      constants_to_delete.each do |constant| | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        LOGGER.info("constant <#{constant}> reverts to default value <#{}>") | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      constants_to_add = new_env.except(*HOTENV.keys) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      constants_to_add.each do |constant| | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					        LOGGER.info("constant to add <#{constant}>") | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      HOTENV.replace new_env | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      # LISTENERS.each_pair { |k, b| perform_change(k, b) } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def on_change(key, &block) | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -31,6 +64,17 @@ module HotConstants | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    private | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def define_method(mode, key, value) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      dkey = key.downcase | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      method = infer_method_from_value(value) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      inferred_type = method.name.to_s.split('_')[1] | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      LOGGER.info("#{mode} method <#{dkey}> of type <#{inferred_type}> with default value <#{value}> ") | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      singleton_class.define_method(dkey) { method.call(key, value) } | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      @hot_definitions.store(key, dkey) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def perform_change(key, block) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      old_value = nil # TODO: remember last previous value | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      new_value = method(key).call | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -41,7 +85,7 @@ module HotConstants | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def load_integer(key, default) = HOTENV.fetch(key, default).to_i | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def load_string(key, default) = HOTENV.fetch(key, default) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def method_from_value(value) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    def infer_method_from_value(value) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      case value.downcase | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      when /^(true|false)$/ then method(:load_boolean) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					      when /^\d+$/ then method(:load_integer) | 
				
			
			
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
				 | 
				
					@ -50,6 +94,5 @@ module HotConstants | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					    end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  end | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					
 | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  # initialize on first require | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  initialize | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					  initialize # done once on first require, cause this is just a module (not a class!) | 
				
			
			
		
	
		
			
				
					 | 
					 | 
				
				 | 
				
					end |