class Holidays::Definition::Context::Generator
Public Class Methods
new(custom_method_parser, custom_method_source_decorator, custom_methods_repository, test_parser, test_source_generator, module_source_generator)
click to toggle source
# File lib/holidays/definition/context/generator.rb, line 14 def initialize(custom_method_parser, custom_method_source_decorator, custom_methods_repository, test_parser, test_source_generator, module_source_generator) @custom_method_parser = custom_method_parser @custom_method_source_decorator = custom_method_source_decorator @custom_methods_repository = custom_methods_repository @test_parser = test_parser @test_source_generator = test_source_generator @module_source_generator = module_source_generator end
Public Instance Methods
generate_definition_source(module_name, files, regions, rules_by_month, custom_methods, tests)
click to toggle source
# File lib/holidays/definition/context/generator.rb, line 61 def generate_definition_source(module_name, files, regions, rules_by_month, custom_methods, tests) month_strings = generate_month_definition_strings(rules_by_month, custom_methods) # Build the custom methods string custom_method_string = '' custom_methods.each do |key, code| custom_method_string << @custom_method_source_decorator.call(code) + ",\n\n" end module_src = @module_source_generator.call(module_name, files, regions, month_strings, custom_method_string) test_src = @test_source_generator.call(module_name, files, tests) return module_src, test_src || '' end
parse_definition_files(files)
click to toggle source
# File lib/holidays/definition/context/generator.rb, line 23 def parse_definition_files(files) raise ArgumentError, "Must have at least one file to parse" if files.nil? || files.empty? all_regions = [] all_rules_by_month = {} all_custom_methods = {} all_tests = [] files.flatten! files.each do |file| definition_file = YAML.load_file(file) custom_methods = @custom_method_parser.call(definition_file['methods']) regions, rules_by_month = parse_month_definitions(definition_file['months'], custom_methods) all_regions << regions.flatten all_rules_by_month.merge!(rules_by_month) { |month, existing, new| existing << new existing.flatten! } #FIXME This is a problem. We will have a 'global' list of methods. That's always bad. What effects will this have? # This is an existing problem (just so we are clear). An issue would be extremely rare because we are generally parsing # single files/custom files. But it IS possible that we would parse a bunch of things at the same time and step # on each other so we need a solution. all_custom_methods.merge!(custom_methods) all_tests += @test_parser.call(definition_file['tests']) end all_regions.flatten!.uniq! [all_regions, all_rules_by_month, all_custom_methods, all_tests] end
Private Instance Methods
generate_month_definition_strings(rules_by_month, parsed_custom_methods)
click to toggle source
FIXME This should really be split out and tested with its own unit tests.
# File lib/holidays/definition/context/generator.rb, line 136 def generate_month_definition_strings(rules_by_month, parsed_custom_methods) month_strings = [] rules_by_month.each do |month, rules| month_string = " #{month.to_s} => [" rule_strings = [] rules.each do |rule| string = '{' if rule[:mday] string << ":mday => #{rule[:mday]}, " end if rule[:function] string << ":function => \"#{rule[:function].to_s}\", " # We need to add in the arguments so we can know what to send in when calling the custom proc during holiday lookups. # NOTE: the allowed arguments are enforced in the custom methods parser. string << ":function_arguments => #{get_function_arguments(rule[:function], parsed_custom_methods)}, " if rule[:function_modifier] string << ":function_modifier => #{rule[:function_modifier].to_s}, " end end # This is the 'else'. It is possible for mday AND function # to be set but this is the fallback. This whole area # needs to be reworked! if string == '{' string << ":wday => #{rule[:wday]}, :week => #{rule[:week]}, " end if rule[:year_ranges] && rule[:year_ranges].is_a?(Hash) selector = rule[:year_ranges].keys.first value = rule[:year_ranges][selector] string << ":year_ranges => { :#{selector} => #{value} }," end if rule[:observed] string << ":observed => \"#{rule[:observed].to_s}\", " string << ":observed_arguments => #{get_function_arguments(rule[:observed], parsed_custom_methods)}, " end if rule[:type] string << ":type => :#{rule[:type]}, " end # shouldn't allow the same region twice string << ":name => \"#{rule[:name]}\", :regions => [:" + rule[:regions].uniq.join(', :') + "]}" rule_strings << string end month_string << rule_strings.join(",\n ") + "]" month_strings << month_string end return month_strings end
get_function_arguments(function_id, parsed_custom_methods)
click to toggle source
This method sucks. The issue here is that the custom methods repo has the ‘general’ methods (like easter) but the ‘parsed_custom_methods’ have the recently parsed stuff. We don’t load those until they are needed later. This entire file is a refactor target so I am adding some tech debt to get me over the hump. What we should do is ensure that all custom methods are loaded into the repo as soon as they are parsed so we only have one place to look.
# File lib/holidays/definition/context/generator.rb, line 199 def get_function_arguments(function_id, parsed_custom_methods) if method = @custom_methods_repository.find(function_id) method.parameters.collect { |arg| arg[1] } elsif method = parsed_custom_methods[function_id] method.arguments.collect { |arg| arg.to_sym } end end
parse_month_definitions(month_definitions, parsed_custom_methods)
click to toggle source
FIXME This should be a ‘month_definitions_parser’ like the above parser
# File lib/holidays/definition/context/generator.rb, line 79 def parse_month_definitions(month_definitions, parsed_custom_methods) regions = [] rules_by_month = {} if month_definitions month_definitions.each do |month, definitions| rules_by_month[month] = [] unless rules_by_month[month] definitions.each do |definition| rule = {} definition.each do |key, val| # Ruby 2.4 doesn't have the `transform_keys` method. Once we drop 2.4 support we can # use `val.transform_keys!(&:to_sym) if val.is_a?(Hash)` instead of this `if` statement. if val.is_a?(Hash) val = val.keys.each_with_object({}) do |k, result| result[k.to_sym] = val[k] end end rule[key.to_sym] = val end if rule[:year_ranges] && rule[:year_ranges].key?(:between) start_year = rule[:year_ranges][:between]["start"].to_i end_year = rule[:year_ranges][:between]["end"].to_i rule[:year_ranges][:between] = Range.new(start_year, end_year) end rule[:regions] = rule[:regions].collect { |r| r.to_sym } regions << rule[:regions] exists = false rules_by_month[month].each do |ex| if ex[:name] == rule[:name] and ex[:wday] == rule[:wday] and ex[:mday] == rule[:mday] and ex[:week] == rule[:week] and ex[:type] == rule[:type] and ex[:function] == rule[:function] and ex[:observed] == rule[:observed] and ex[:year_ranges] == rule[:year_ranges] ex[:regions] << rule[:regions].flatten exists = true end end unless exists # This will add in the custom method arguments so they are immediately # available for 'on the fly' def loading. if rule[:function] rule[:function_arguments] = get_function_arguments(rule[:function], parsed_custom_methods) end rules_by_month[month] << rule end end end end [regions, rules_by_month] end