holidays 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/CONTRIBUTING.md +37 -0
  4. data/README.md +16 -25
  5. data/REFERENCES +4 -1
  6. data/Rakefile +38 -8
  7. data/benchmark.rb +8 -0
  8. data/definitions/README.md +187 -8
  9. data/definitions/ar.yaml +2 -1
  10. data/definitions/at.yaml +17 -13
  11. data/definitions/au.yaml +65 -60
  12. data/definitions/be_fr.yaml +14 -10
  13. data/definitions/be_nl.yaml +8 -4
  14. data/definitions/bg.yaml +12 -6
  15. data/definitions/br.yaml +6 -3
  16. data/definitions/ca.yaml +8 -6
  17. data/definitions/ch.yaml +21 -15
  18. data/definitions/cl.yaml +4 -2
  19. data/definitions/cr.yaml +5 -3
  20. data/definitions/cz.yaml +4 -2
  21. data/definitions/de.yaml +25 -14
  22. data/definitions/dk.yaml +26 -17
  23. data/definitions/ecb_target.yaml +4 -2
  24. data/definitions/el.yaml +23 -18
  25. data/definitions/es.yaml +31 -28
  26. data/definitions/federal_reserve.yaml +12 -12
  27. data/definitions/fedex.yaml +6 -6
  28. data/definitions/fi.yaml +26 -25
  29. data/definitions/fr.yaml +8 -4
  30. data/definitions/gb.yaml +9 -7
  31. data/definitions/hr.yaml +8 -6
  32. data/definitions/hu.yaml +8 -6
  33. data/definitions/ie.yaml +17 -16
  34. data/definitions/index.yaml +1 -1
  35. data/definitions/is.yaml +29 -19
  36. data/definitions/it.yaml +10 -9
  37. data/definitions/jp.yaml +92 -44
  38. data/definitions/li.yaml +25 -20
  39. data/definitions/lt.yaml +2 -1
  40. data/definitions/ma.yaml +7 -7
  41. data/definitions/mx.yaml +11 -11
  42. data/definitions/nerc.yaml +6 -6
  43. data/definitions/nl.yaml +22 -18
  44. data/definitions/no.yaml +19 -11
  45. data/definitions/north_america_informal.yaml +6 -6
  46. data/definitions/nyse.yaml +9 -8
  47. data/definitions/nz.yaml +33 -29
  48. data/definitions/ph.yaml +15 -8
  49. data/definitions/pl.yaml +27 -17
  50. data/definitions/pt.yaml +4 -2
  51. data/definitions/ro.yaml +21 -18
  52. data/definitions/se.yaml +24 -18
  53. data/definitions/sg.yaml +10 -9
  54. data/definitions/si.yaml +4 -2
  55. data/definitions/sk.yaml +4 -2
  56. data/definitions/united_nations.yaml +12 -12
  57. data/definitions/ups.yaml +6 -6
  58. data/definitions/us.yaml +12 -11
  59. data/definitions/ve.yaml +8 -4
  60. data/definitions/vi.yaml +6 -6
  61. data/definitions/za.yaml +26 -24
  62. data/holidays.gemspec +3 -1
  63. data/lib/generated_definitions/MANIFEST +1 -1
  64. data/lib/generated_definitions/ar.rb +8 -6
  65. data/lib/generated_definitions/at.rb +11 -9
  66. data/lib/generated_definitions/au.rb +75 -99
  67. data/lib/generated_definitions/be.rb +12 -10
  68. data/lib/generated_definitions/be_fr.rb +12 -10
  69. data/lib/generated_definitions/be_nl.rb +12 -10
  70. data/lib/generated_definitions/bg.rb +14 -13
  71. data/lib/generated_definitions/br.rb +11 -9
  72. data/lib/generated_definitions/ca.rb +20 -20
  73. data/lib/generated_definitions/ch.rb +41 -44
  74. data/lib/generated_definitions/cl.rb +9 -7
  75. data/lib/generated_definitions/cr.rb +9 -7
  76. data/lib/generated_definitions/cz.rb +9 -6
  77. data/lib/generated_definitions/de.rb +25 -25
  78. data/lib/generated_definitions/dk.rb +17 -15
  79. data/lib/generated_definitions/ecb_target.rb +9 -7
  80. data/lib/generated_definitions/el.rb +13 -11
  81. data/lib/generated_definitions/es.rb +35 -33
  82. data/lib/generated_definitions/europe.rb +234 -247
  83. data/lib/generated_definitions/federal_reserve.rb +11 -9
  84. data/lib/generated_definitions/fedex.rb +42 -0
  85. data/lib/generated_definitions/fi.rb +32 -36
  86. data/lib/generated_definitions/fr.rb +12 -10
  87. data/lib/generated_definitions/gb.rb +15 -13
  88. data/lib/generated_definitions/hr.rb +10 -8
  89. data/lib/generated_definitions/hu.rb +9 -7
  90. data/lib/generated_definitions/ie.rb +17 -17
  91. data/lib/generated_definitions/is.rb +26 -26
  92. data/lib/generated_definitions/it.rb +9 -7
  93. data/lib/generated_definitions/jp.rb +112 -89
  94. data/lib/generated_definitions/li.rb +14 -12
  95. data/lib/generated_definitions/lt.rb +9 -7
  96. data/lib/generated_definitions/ma.rb +7 -5
  97. data/lib/generated_definitions/mx.rb +7 -5
  98. data/lib/generated_definitions/nerc.rb +10 -8
  99. data/lib/generated_definitions/nl.rb +15 -13
  100. data/lib/generated_definitions/no.rb +16 -14
  101. data/lib/generated_definitions/north_america.rb +34 -37
  102. data/lib/generated_definitions/nyse.rb +10 -8
  103. data/lib/generated_definitions/nz.rb +40 -40
  104. data/lib/generated_definitions/ph.rb +17 -13
  105. data/lib/generated_definitions/pl.rb +25 -27
  106. data/lib/generated_definitions/pt.rb +10 -8
  107. data/lib/generated_definitions/ro.rb +11 -9
  108. data/lib/generated_definitions/scandinavia.rb +92 -102
  109. data/lib/generated_definitions/se.rb +25 -27
  110. data/lib/generated_definitions/sg.rb +11 -9
  111. data/lib/generated_definitions/si.rb +10 -8
  112. data/lib/generated_definitions/sk.rb +9 -7
  113. data/lib/generated_definitions/united_nations.rb +7 -5
  114. data/lib/generated_definitions/ups.rb +13 -12
  115. data/lib/generated_definitions/us.rb +20 -21
  116. data/lib/generated_definitions/ve.rb +11 -9
  117. data/lib/generated_definitions/vi.rb +7 -5
  118. data/lib/generated_definitions/za.rb +19 -17
  119. data/lib/holidays.rb +20 -83
  120. data/lib/holidays/date_calculator/weekend_modifier.rb +22 -5
  121. data/lib/holidays/definition/context/generator.rb +67 -29
  122. data/lib/holidays/definition/context/merger.rb +8 -8
  123. data/lib/holidays/definition/decorator/custom_method_proc.rb +28 -0
  124. data/lib/holidays/definition/decorator/custom_method_source.rb +30 -0
  125. data/lib/holidays/definition/entity/custom_method.rb +11 -0
  126. data/lib/holidays/definition/parser/custom_method.rb +69 -0
  127. data/lib/holidays/definition/repository/custom_methods.rb +27 -0
  128. data/lib/holidays/definition/repository/holidays_by_month.rb +1 -1
  129. data/lib/holidays/definition/repository/{proc_cache.rb → proc_result_cache.rb} +19 -4
  130. data/lib/holidays/definition/validator/custom_method.rb +31 -0
  131. data/lib/holidays/definition_factory.rb +42 -6
  132. data/lib/holidays/errors.rb +6 -0
  133. data/lib/holidays/load_all_definitions.rb +57 -0
  134. data/lib/holidays/option/context/parse_options.rb +26 -16
  135. data/lib/holidays/option_factory.rb +1 -0
  136. data/lib/holidays/use_case/context/between.rb +41 -14
  137. data/lib/holidays/use_case_factory.rb +2 -1
  138. data/lib/holidays/version.rb +1 -1
  139. data/test/data/test_single_custom_holiday_with_custom_procs.yaml +24 -0
  140. data/test/defs/test_defs_at.rb +1 -1
  141. data/test/defs/test_defs_au.rb +3 -2
  142. data/test/defs/test_defs_cr.rb +1 -0
  143. data/test/defs/test_defs_cz.rb +1 -0
  144. data/test/defs/test_defs_dk.rb +2 -2
  145. data/test/defs/test_defs_el.rb +7 -6
  146. data/test/defs/test_defs_europe.rb +40 -33
  147. data/test/defs/test_defs_fedex.rb +24 -0
  148. data/test/defs/test_defs_fi.rb +4 -3
  149. data/test/defs/test_defs_hr.rb +2 -2
  150. data/test/defs/test_defs_hu.rb +2 -2
  151. data/test/defs/test_defs_is.rb +2 -1
  152. data/test/defs/test_defs_it.rb +2 -1
  153. data/test/defs/test_defs_jp.rb +1 -1
  154. data/test/defs/test_defs_li.rb +1 -1
  155. data/test/defs/test_defs_ma.rb +2 -1
  156. data/test/defs/test_defs_mx.rb +4 -3
  157. data/test/defs/test_defs_nl.rb +7 -6
  158. data/test/defs/test_defs_no.rb +1 -0
  159. data/test/defs/test_defs_north_america.rb +4 -3
  160. data/test/defs/test_defs_nyse.rb +2 -1
  161. data/test/defs/test_defs_pl.rb +1 -0
  162. data/test/defs/test_defs_ro.rb +11 -11
  163. data/test/defs/test_defs_scandinavia.rb +12 -8
  164. data/test/defs/test_defs_se.rb +3 -2
  165. data/test/defs/test_defs_sg.rb +2 -1
  166. data/test/defs/test_defs_vi.rb +1 -1
  167. data/test/defs/test_defs_za.rb +3 -2
  168. data/test/holidays/date_calculator/test_weekend_modifier.rb +11 -0
  169. data/test/holidays/definition/context/test_generator.rb +64 -5
  170. data/test/holidays/definition/context/test_merger.rb +5 -2
  171. data/test/holidays/definition/decorator/test_custom_method_proc.rb +113 -0
  172. data/test/holidays/definition/decorator/test_custom_method_source.rb +96 -0
  173. data/test/holidays/definition/parser/test_custom_method.rb +79 -0
  174. data/test/holidays/definition/repository/test_custom_methods.rb +43 -0
  175. data/test/holidays/definition/repository/test_holidays_by_month.rb +0 -32
  176. data/test/holidays/definition/repository/test_proc_result_cache.rb +84 -0
  177. data/test/holidays/definition/validator/test_custom_method.rb +89 -0
  178. data/test/holidays/option/context/test_parse_options.rb +5 -0
  179. data/test/holidays/test_definition_factory.rb +17 -2
  180. data/test/holidays/use_case/context/test_between.rb +2 -0
  181. data/test/test_all_regions.rb +7 -49
  182. data/test/test_custom_holidays.rb +8 -2
  183. data/test/test_helper.rb +9 -2
  184. data/test/test_holidays.rb +9 -29
  185. metadata +46 -11
  186. data/lib/generated_definitions/fed_ex.rb +0 -41
  187. data/test/holidays/definition/repository/test_proc_cache.rb +0 -29
  188. data/test/test_parse_definitions.rb +0 -30
@@ -2,18 +2,19 @@ module Holidays
2
2
  module DateCalculator
3
3
  class WeekendModifier
4
4
  # Move date to Monday if it occurs on a Saturday on Sunday.
5
+ # Does not modify date if it is not a weekend.
5
6
  # Used as a callback function.
6
7
  def to_monday_if_weekend(date)
7
- date += 1 if date.wday == 0
8
- date += 2 if date.wday == 6
9
- date
8
+ return date unless date.wday == 6 || date.wday == 0
9
+ to_next_weekday(date)
10
10
  end
11
11
 
12
12
  # Move date to Monday if it occurs on a Sunday.
13
+ # Does not modify the date if it is not a Sunday.
13
14
  # Used as a callback function.
14
15
  def to_monday_if_sunday(date)
15
- date += 1 if date.wday == 0
16
- date
16
+ return date unless date.wday == 0
17
+ to_next_weekday(date)
17
18
  end
18
19
 
19
20
  # if Christmas falls on a Sunday, move it to the next Tuesday (Boxing Day will go on Monday)
@@ -65,6 +66,22 @@ module Holidays
65
66
  date -= 1 if date.wday == 6
66
67
  date
67
68
  end
69
+
70
+ # Finds the next weekday. For example, if a 'Friday' date is received
71
+ # it will return the following Monday. If Sunday then return Monday,
72
+ # if Saturday return Monday, if Tuesday return Wednesday, etc.
73
+ def to_next_weekday(date)
74
+ case date.wday
75
+ when 6
76
+ date += 2
77
+ when 5
78
+ date += 3
79
+ else
80
+ date += 1
81
+ end
82
+
83
+ date
84
+ end
68
85
  end
69
86
  end
70
87
  end
@@ -4,6 +4,11 @@ module Holidays
4
4
  module Definition
5
5
  module Context
6
6
  class Generator
7
+ def initialize(custom_method_parser, custom_method_source_decorator, custom_methods_repository)
8
+ @custom_method_parser = custom_method_parser
9
+ @custom_method_source_decorator = custom_method_source_decorator
10
+ @custom_methods_repository = custom_methods_repository
11
+ end
7
12
 
8
13
  def parse_definition_files(files)
9
14
  raise ArgumentError, "Must have at least one file to parse" if files.nil? || files.empty?
@@ -18,7 +23,9 @@ module Holidays
18
23
  files.each do |file|
19
24
  definition_file = YAML.load_file(file)
20
25
 
21
- regions, rules_by_month = parse_month_definitions(definition_file['months'])
26
+ custom_methods = custom_method_parser.call(definition_file['methods'])
27
+
28
+ regions, rules_by_month = parse_month_definitions(definition_file['months'], custom_methods)
22
29
 
23
30
  all_regions << regions.flatten
24
31
 
@@ -27,7 +34,10 @@ module Holidays
27
34
  existing.flatten!
28
35
  }
29
36
 
30
- custom_methods = parse_method_definitions(definition_file['methods'])
37
+ #FIXME This is a problem. We will have a 'global' list of methods. That's always bad. What effects will this have?
38
+ # This is an existing problem (just so we are clear). An issue would be extremely rare because we are generally parsing
39
+ # single files/custom files. But it IS possible that we would parse a bunch of things at the same time and step
40
+ # on each other so we need a solution.
31
41
  all_custom_methods.merge!(custom_methods)
32
42
 
33
43
  all_tests << parse_test_definitions(definition_file['tests'])
@@ -39,12 +49,12 @@ module Holidays
39
49
  end
40
50
 
41
51
  def generate_definition_source(module_name, files, regions, rules_by_month, custom_methods, tests)
42
- month_strings = generate_month_definition_strings(rules_by_month)
52
+ month_strings = generate_month_definition_strings(rules_by_month, custom_methods)
43
53
 
44
54
  # Build the custom methods string
45
55
  custom_method_string = ''
46
56
  custom_methods.each do |key, code|
47
- custom_method_string << code + "\n\n"
57
+ custom_method_string << custom_method_source_decorator.call(code) + ",\n\n"
48
58
  end
49
59
 
50
60
  module_src = generate_module_src(module_name, files, regions, month_strings, custom_method_string)
@@ -55,7 +65,10 @@ module Holidays
55
65
 
56
66
  private
57
67
 
58
- def parse_month_definitions(month_definitions)
68
+ attr_reader :custom_method_parser, :custom_method_source_decorator, :custom_methods_repository
69
+
70
+ #FIXME This should be a 'month_definitions_parser' like the above parser
71
+ def parse_month_definitions(month_definitions, parsed_custom_methods)
59
72
  regions = []
60
73
  rules_by_month = {}
61
74
 
@@ -82,6 +95,12 @@ module Holidays
82
95
  end
83
96
 
84
97
  unless exists
98
+ # This will add in the custom method arguments so they are immediately
99
+ # available for 'on the fly' def loading.
100
+ if rule[:function]
101
+ rule[:function_arguments] = get_function_arguments(rule[:function], parsed_custom_methods)
102
+ end
103
+
85
104
  rules_by_month[month] << rule
86
105
  end
87
106
  end
@@ -91,18 +110,6 @@ module Holidays
91
110
  [regions, rules_by_month]
92
111
  end
93
112
 
94
- def parse_method_definitions(methods)
95
- custom_methods = {}
96
-
97
- if methods
98
- methods.each do |name, code|
99
- custom_methods[name] = code
100
- end
101
- end
102
-
103
- custom_methods
104
- end
105
-
106
113
  def parse_test_definitions(tests)
107
114
  test_strings = []
108
115
 
@@ -113,7 +120,8 @@ module Holidays
113
120
  test_strings
114
121
  end
115
122
 
116
- def generate_month_definition_strings(rules_by_month)
123
+ #FIXME This should really be split out and tested with its own unit tests.
124
+ def generate_month_definition_strings(rules_by_month, parsed_custom_methods)
117
125
  month_strings = []
118
126
 
119
127
  rules_by_month.each do |month, rules|
@@ -123,13 +131,28 @@ module Holidays
123
131
  string = '{'
124
132
  if rule[:mday]
125
133
  string << ":mday => #{rule[:mday]}, "
126
- elsif rule[:function]
127
- string << ":function => lambda { |year| Holidays.#{rule[:function]} }, "
128
- string << ":function_id => \"#{rule[:function].to_s}\", "
129
- else
134
+ end
135
+
136
+ if rule[:function]
137
+ string << ":function => \"#{rule[:function].to_s}\", "
138
+
139
+ # We need to add in the arguments so we can know what to send in when calling the custom proc during holiday lookups.
140
+ # NOTE: the allowed arguments are enforced in the custom methods parser.
141
+ string << ":function_arguments => #{get_function_arguments(rule[:function], parsed_custom_methods)}, "
142
+
143
+ if rule[:function_modifier]
144
+ string << ":function_modifier => #{rule[:function_modifier].to_s}, "
145
+ end
146
+ end
147
+
148
+ # This is the 'else'. It is possible for mday AND function
149
+ # to be set but this is the fallback. This whole area
150
+ # needs to be reworked!
151
+ if string == '{'
130
152
  string << ":wday => #{rule[:wday]}, :week => #{rule[:week]}, "
131
153
  end
132
154
 
155
+ #FIXME I think this should be split out into its own file.
133
156
  if rule[:year_ranges] && rule[:year_ranges].kind_of?(Array)
134
157
  year_string = " :year_ranges => ["
135
158
  len = rule[:year_ranges].length
@@ -145,8 +168,8 @@ module Holidays
145
168
  end
146
169
 
147
170
  if rule[:observed]
148
- string << ":observed => lambda { |date| Holidays.#{rule[:observed]}(date) }, "
149
- string << ":observed_id => \"#{rule[:observed].to_s}\", "
171
+ string << ":observed => \"#{rule[:observed].to_s}\", "
172
+ string << ":observed_arguments => #{get_function_arguments(rule[:observed], parsed_custom_methods)}, "
150
173
  end
151
174
 
152
175
  if rule[:type]
@@ -164,6 +187,19 @@ module Holidays
164
187
  return month_strings
165
188
  end
166
189
 
190
+ # This method sucks. The issue here is that the custom methods repo has the 'general' methods (like easter)
191
+ # but the 'parsed_custom_methods' have the recently parsed stuff. We don't load those until they are needed later.
192
+ # This entire file is a refactor target so I am adding some tech debt to get me over the hump.
193
+ # What we should do is ensure that all custom methods are loaded into the repo as soon as they are parsed
194
+ # so we only have one place to look.
195
+ def get_function_arguments(function_id, parsed_custom_methods)
196
+ if method = custom_methods_repository.find(function_id)
197
+ method.parameters.collect { |arg| arg[1] }
198
+ elsif method = parsed_custom_methods[function_id]
199
+ method.arguments.collect { |arg| arg.to_sym }
200
+ end
201
+ end
202
+
167
203
  def generate_module_src(module_name, files, regions, month_strings, custom_methods)
168
204
  module_src = ""
169
205
 
@@ -180,7 +216,7 @@ module Holidays
180
216
  # require 'holidays'
181
217
  # require '#{DEFINITIONS_PATH}/#{module_name.to_s.downcase}'
182
218
  #
183
- # All the definitions are available at https://github.com/alexdunae/holidays
219
+ # All the definitions are available at https://github.com/holidays/holidays
184
220
  module #{module_name.to_s.upcase} # :nodoc:
185
221
  def self.defined_regions
186
222
  [:#{regions.join(', :')}]
@@ -191,12 +227,14 @@ module Holidays
191
227
  #{month_strings.join(",\n")}
192
228
  }
193
229
  end
194
- end
195
230
 
196
- #{custom_methods}
231
+ def self.custom_methods
232
+ {
233
+ #{custom_methods}
234
+ }
235
+ end
236
+ end
197
237
  end
198
-
199
- Holidays.merge_defs(Holidays::#{module_name.to_s.upcase}.defined_regions, Holidays::#{module_name.to_s.upcase}.holidays_by_month)
200
238
  EOM
201
239
 
202
240
  return module_src
@@ -7,19 +7,19 @@ module Holidays
7
7
  # files. This is accomplished because the Generator class generates the
8
8
  # definition source with this class explicitly.
9
9
  class Merger
10
- def initialize(holidays_by_month_repo, regions_repo)
10
+ def initialize(holidays_by_month_repo, regions_repo, custom_methods_repo)
11
11
  @holidays_repo = holidays_by_month_repo
12
12
  @regions_repo = regions_repo
13
+ @custom_methods_repo = custom_methods_repo
13
14
  end
14
15
 
15
- def call(target_regions, target_holidays)
16
- regions_repo.add(target_regions)
17
- holidays_repo.add(target_holidays)
16
+ def call(target_regions, target_holidays, target_custom_methods)
17
+ #FIXME Does this need to come in this exact order? God I hope not.
18
+ # If not then we should swap the order so it matches the init.
19
+ @regions_repo.add(target_regions)
20
+ @holidays_repo.add(target_holidays)
21
+ @custom_methods_repo.add(target_custom_methods)
18
22
  end
19
-
20
- private
21
-
22
- attr_reader :holidays_repo, :regions_repo
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,28 @@
1
+ module Holidays
2
+ module Definition
3
+ module Decorator
4
+ class CustomMethodProc
5
+ def call(proc)
6
+ validate!(proc)
7
+
8
+ eval("Proc.new { |#{parse_arguments(proc.arguments)}|
9
+ #{proc.source}
10
+ }")
11
+ end
12
+
13
+ private
14
+
15
+ def validate!(proc)
16
+ raise ArgumentError if proc.name.nil? || proc.name.empty?
17
+ raise ArgumentError if proc.arguments.nil? || proc.arguments.empty?
18
+ raise ArgumentError if proc.source.nil? || proc.source.empty?
19
+ end
20
+
21
+ def parse_arguments(args)
22
+ a = args.join(", ")
23
+ a[0..-1]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module Holidays
2
+ module Definition
3
+ module Decorator
4
+ class CustomMethodSource
5
+ def call(proc)
6
+ validate!(proc)
7
+
8
+ method_name = proc.name
9
+ args = args_string(proc.arguments)
10
+ source = proc.source
11
+
12
+ "\"#{method_name.to_s}(#{args})\" => Proc.new { |#{args}|\n#{source}}"
13
+ end
14
+
15
+ private
16
+
17
+ def validate!(proc)
18
+ raise ArgumentError if proc.name.nil? || proc.name == ""
19
+ raise ArgumentError if proc.arguments.nil? || !proc.arguments.is_a?(Array) || proc.arguments.empty?
20
+ raise ArgumentError if proc.source.nil? || proc.source == ""
21
+ end
22
+
23
+ def args_string(args)
24
+ a = args.join(", ")
25
+ a[0..-1]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ module Holidays
2
+ module Definition
3
+ module Entity
4
+ CustomMethod = Struct.new(:name, :arguments, :source) do
5
+ def initialize(fields = {})
6
+ super(*fields.values_at(*members))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,69 @@
1
+ require 'holidays/definition/entity/custom_method'
2
+
3
+ module Holidays
4
+ module Definition
5
+ module Parser
6
+ class CustomMethod
7
+ def initialize(validator)
8
+ @validator = validator
9
+ end
10
+
11
+ def call(methods)
12
+ return {} if methods.nil? || methods.empty?
13
+
14
+ validate!(methods)
15
+
16
+ custom_methods = {}
17
+
18
+ methods.each do |name, pieces|
19
+ arguments = parse_arguments!(pieces["arguments"])
20
+
21
+ custom_methods[method_key(name, arguments)] = Entity::CustomMethod.new({
22
+ name: name,
23
+ arguments: arguments,
24
+ source: pieces["source"],
25
+ })
26
+ end
27
+
28
+ custom_methods
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :validator
34
+
35
+ def validate!(methods)
36
+ raise ArgumentError unless methods.all? do |name, pieces|
37
+ validator.valid?(
38
+ {
39
+ :name => name,
40
+ :arguments => pieces["arguments"],
41
+ :source => pieces["source"]
42
+ }
43
+ )
44
+ end
45
+ end
46
+
47
+ def parse_arguments!(arguments)
48
+ splitArgs = arguments.split(",")
49
+ parsedArgs = []
50
+
51
+ splitArgs.each do |arg|
52
+ parsedArgs << arg.strip
53
+ end
54
+
55
+ parsedArgs
56
+ end
57
+
58
+ def method_key(name, args)
59
+ "#{name.to_s}(#{args_string(args)})"
60
+ end
61
+
62
+ def args_string(args)
63
+ a = args.join(", ")
64
+ a[0..-1]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,27 @@
1
+ module Holidays
2
+ module Definition
3
+ module Repository
4
+ class CustomMethods
5
+ def initialize
6
+ @custom_methods = {}
7
+ end
8
+
9
+ # This performs a merge that overwrites any conflicts.
10
+ # While this is not ideal I'm leaving it as-is since I have no
11
+ # evidence of any current definitions that will cause an issue.
12
+ #
13
+ # FIXME: this should probably return an error if a method with the
14
+ # same ID already exists.
15
+ def add(new_custom_methods)
16
+ raise ArgumentError if new_custom_methods.nil?
17
+ @custom_methods.merge!(new_custom_methods)
18
+ end
19
+
20
+ def find(method_id)
21
+ raise ArgumentError if method_id.nil? || method_id.empty?
22
+ @custom_methods[method_id]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -41,7 +41,7 @@ module Holidays
41
41
  private
42
42
 
43
43
  def definition_exists?(existing_def, target_def)
44
- existing_def[:name] == target_def[:name] && existing_def[:wday] == target_def[:wday] && existing_def[:mday] == target_def[:mday] && existing_def[:week] == target_def[:week] && existing_def[:function_id] == target_def[:function_id] && existing_def[:type] == target_def[:type] && existing_def[:observed_id] == target_def[:observed_id] && existing_def[:year_ranges] == target_def[:year_ranges]
44
+ existing_def[:name] == target_def[:name] && existing_def[:wday] == target_def[:wday] && existing_def[:mday] == target_def[:mday] && existing_def[:week] == target_def[:week] && existing_def[:function] == target_def[:function] && existing_def[:type] == target_def[:type] && existing_def[:observed] == target_def[:observed] && existing_def[:year_ranges] == target_def[:year_ranges]
45
45
  end
46
46
  end
47
47
  end