class Holidays::Definition::Context::Generator

Attributes

custom_method_parser[R]
custom_method_source_decorator[R]
custom_methods_repository[R]

Public Class Methods

new(custom_method_parser, custom_method_source_decorator, custom_methods_repository) click to toggle source
# File lib/holidays/definition/context/generator.rb, line 7
def initialize(custom_method_parser, custom_method_source_decorator, custom_methods_repository)
  @custom_method_parser = custom_method_parser
  @custom_method_source_decorator = custom_method_source_decorator
  @custom_methods_repository = custom_methods_repository
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 51
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 = generate_module_src(module_name, files, regions, month_strings, custom_method_string)
  test_src = generate_test_src(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 13
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 << parse_test_definitions(definition_file['tests'])
  end

  all_regions.flatten!.uniq!

  [all_regions, all_rules_by_month, all_custom_methods, all_tests]
end

Private Instance Methods

generate_module_src(module_name, files, regions, month_strings, custom_methods) click to toggle source
# File lib/holidays/definition/context/generator.rb, line 203
        def generate_module_src(module_name, files, regions, month_strings, custom_methods)
          module_src = ""

          module_src =<<-EOM
# encoding: utf-8
module Holidays
  # This file is generated by the Ruby Holidays gem.
  #
  # Definitions loaded: #{files.join(', ')}
  #
  # To use the definitions in this file, load it right after you load the
  # Holiday gem:
  #
  #   require 'holidays'
  #   require '#{DEFINITIONS_PATH}/#{module_name.to_s.downcase}'
  #
  # All the definitions are available at https://github.com/holidays/holidays
  module #{module_name.to_s.upcase} # :nodoc:
    def self.defined_regions
      [:#{regions.join(', :')}]
    end

    def self.holidays_by_month
      {
        #{month_strings.join(",\n")}
      }
    end

    def self.custom_methods
      {
        #{custom_methods}
      }
    end
  end
end
        EOM

          return module_src
        end
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 124
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

      #FIXME I think this should be split out into its own file.
      if rule[:year_ranges] && rule[:year_ranges].kind_of?(Array)
        year_string = " :year_ranges => ["
        len = rule[:year_ranges].length
        rule[:year_ranges].each_with_index do |year,index|
          year_string << "{:#{year.keys.first} => #{year.values.first}}"
          if len == index + 1
            year_string << "],"
          else
            year_string << ","
          end
        end
        string << year_string
      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
generate_test_src(module_name, files, tests) click to toggle source
# File lib/holidays/definition/context/generator.rb, line 243
        def generate_test_src(module_name, files, tests)
          unless tests.empty?
            test_src = ""

            test_src =<<-EndOfTests
# encoding: utf-8
require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'

# This file is generated by the Ruby Holiday gem.
#
# Definitions loaded: #{files.join(', ')}
class #{module_name.to_s.capitalize}DefinitionTests < Test::Unit::TestCase  # :nodoc:

  def test_#{module_name.to_s.downcase}
#{tests.join("\n\n")}
  end
end
  EndOfTests
          end

          return test_src
        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 195
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 71
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|
          rule[key.to_sym] = val
        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
parse_test_definitions(tests) click to toggle source
# File lib/holidays/definition/context/generator.rb, line 113
def parse_test_definitions(tests)
  test_strings = []

  if tests
    test_strings << tests
  end

  test_strings
end