patriot-workflow-scheduler 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +15 -0
  2. data/bin/patriot +8 -0
  3. data/bin/patriot-init +35 -0
  4. data/lib/patriot.rb +11 -0
  5. data/lib/patriot/command.rb +71 -0
  6. data/lib/patriot/command/base.rb +199 -0
  7. data/lib/patriot/command/command_group.rb +43 -0
  8. data/lib/patriot/command/command_macro.rb +141 -0
  9. data/lib/patriot/command/composite.rb +49 -0
  10. data/lib/patriot/command/parser.rb +78 -0
  11. data/lib/patriot/command/sh_command.rb +42 -0
  12. data/lib/patriot/controller.rb +2 -0
  13. data/lib/patriot/controller/package_controller.rb +81 -0
  14. data/lib/patriot/controller/worker_admin_controller.rb +159 -0
  15. data/lib/patriot/job_store.rb +66 -0
  16. data/lib/patriot/job_store/base.rb +159 -0
  17. data/lib/patriot/job_store/factory.rb +19 -0
  18. data/lib/patriot/job_store/in_memory_store.rb +252 -0
  19. data/lib/patriot/job_store/job.rb +118 -0
  20. data/lib/patriot/job_store/job_ticket.rb +30 -0
  21. data/lib/patriot/job_store/rdb_job_store.rb +353 -0
  22. data/lib/patriot/tool.rb +2 -0
  23. data/lib/patriot/tool/batch_parser.rb +102 -0
  24. data/lib/patriot/tool/patriot_command.rb +48 -0
  25. data/lib/patriot/tool/patriot_commands/execute.rb +92 -0
  26. data/lib/patriot/tool/patriot_commands/job.rb +62 -0
  27. data/lib/patriot/tool/patriot_commands/plugin.rb +41 -0
  28. data/lib/patriot/tool/patriot_commands/register.rb +77 -0
  29. data/lib/patriot/tool/patriot_commands/upgrade.rb +24 -0
  30. data/lib/patriot/tool/patriot_commands/validate.rb +84 -0
  31. data/lib/patriot/tool/patriot_commands/worker.rb +35 -0
  32. data/lib/patriot/tool/patriot_commands/worker_admin.rb +60 -0
  33. data/lib/patriot/util.rb +14 -0
  34. data/lib/patriot/util/config.rb +58 -0
  35. data/lib/patriot/util/config/base.rb +22 -0
  36. data/lib/patriot/util/config/inifile_config.rb +63 -0
  37. data/lib/patriot/util/cron_format_parser.rb +104 -0
  38. data/lib/patriot/util/date_util.rb +200 -0
  39. data/lib/patriot/util/db_client.rb +65 -0
  40. data/lib/patriot/util/db_client/base.rb +142 -0
  41. data/lib/patriot/util/db_client/hash_record.rb +53 -0
  42. data/lib/patriot/util/db_client/record.rb +25 -0
  43. data/lib/patriot/util/logger.rb +24 -0
  44. data/lib/patriot/util/logger/facade.rb +33 -0
  45. data/lib/patriot/util/logger/factory.rb +59 -0
  46. data/lib/patriot/util/logger/log4r_factory.rb +111 -0
  47. data/lib/patriot/util/logger/webrick_log_factory.rb +47 -0
  48. data/lib/patriot/util/param.rb +73 -0
  49. data/lib/patriot/util/retry.rb +30 -0
  50. data/lib/patriot/util/script.rb +52 -0
  51. data/lib/patriot/util/system.rb +120 -0
  52. data/lib/patriot/worker.rb +35 -0
  53. data/lib/patriot/worker/base.rb +153 -0
  54. data/lib/patriot/worker/info_server.rb +90 -0
  55. data/lib/patriot/worker/job_store_server.rb +32 -0
  56. data/lib/patriot/worker/multi_node_worker.rb +157 -0
  57. data/lib/patriot/worker/servlet.rb +23 -0
  58. data/lib/patriot/worker/servlet/job_servlet.rb +128 -0
  59. data/lib/patriot/worker/servlet/worker_status_servlet.rb +44 -0
  60. data/skel/batch/sample/daily/test.pbc +4 -0
  61. data/skel/config/patriot.ini +21 -0
  62. data/skel/public/css/bootstrap.css +2495 -0
  63. data/skel/public/css/original.css +54 -0
  64. data/skel/public/js/bootstrap-alerts.js +124 -0
  65. data/skel/public/js/bootstrap-buttons.js +64 -0
  66. data/skel/public/js/bootstrap-dropdown.js +55 -0
  67. data/skel/public/js/bootstrap-modal.js +260 -0
  68. data/skel/public/js/bootstrap-popover.js +90 -0
  69. data/skel/public/js/bootstrap-scrollspy.js +107 -0
  70. data/skel/public/js/bootstrap-tabs.js +80 -0
  71. data/skel/public/js/bootstrap-twipsy.js +321 -0
  72. data/skel/public/js/jquery-1.6.4.min.js +4 -0
  73. data/skel/public/templates/_jobs.erb +97 -0
  74. data/skel/public/templates/job.erb +119 -0
  75. data/skel/public/templates/jobs.erb +21 -0
  76. data/skel/public/templates/jobs_deleted.erb +6 -0
  77. data/skel/public/templates/layout.erb +103 -0
  78. data/skel/public/templates/state_updated.erb +6 -0
  79. metadata +235 -0
@@ -0,0 +1,60 @@
1
+ module Patriot
2
+ module Tool
3
+ module PatriotCommands
4
+ # remote worker administration tool
5
+ module WorkerAdmin
6
+
7
+ Patriot::Tool::PatriotCommand.class_eval do
8
+ desc 'worker_admin [options] [start|stop|restart|sleep|wake|status]',
9
+ 'controll remote workers'
10
+ method_option :all,
11
+ :aliases => '-a',
12
+ :type => :boolean,
13
+ :default => false,
14
+ :desc => 'target all worker hosts'
15
+ method_option :host,
16
+ :aliases => '-h',
17
+ :type => :string,
18
+ :desc => 'target host'
19
+ def worker_admin(sub_cmd)
20
+ sub_cmd = "#{sub_cmd}_worker" unless sub_cmd == "status"
21
+ begin
22
+ opts = symbolize_options(options)
23
+ conf = {:type => 'worker_admin'}
24
+ conf[:path] = opts[:config] if opts.has_key?(:config)
25
+ config = load_config(conf)
26
+ controller = Patriot::Controller::WorkerAdminController.new(config)
27
+ result = controller.send(sub_cmd.to_sym, opts)
28
+ print_mtd ="print_#{sub_cmd}".to_sym
29
+ self.send(print_mtd, result, opts) if self.respond_to?(print_mtd)
30
+ rescue => e
31
+ puts e
32
+ raise e
33
+ end
34
+ end
35
+
36
+ no_commands do
37
+ def print_status(result, opts)
38
+ statuses = {}
39
+ unless opts[:all]
40
+ raise "illegal response #{result} from #{opts[:host]}" unless result.size == 1
41
+ result[opts[:host]] = result.delete(result.keys.first)
42
+ end
43
+ result.each do |s,r|
44
+ if r.nil?
45
+ statuses[s] = 'HALT'
46
+ else
47
+ r = JSON.parse(r)
48
+ raise "illegal response #{r} from #{s}" unless r.size == 1
49
+ statuses[s] = r.values[0].nil? ? 'HALT' : r.values[0]
50
+ end
51
+ end
52
+ puts JSON.generate(statuses)
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ module Patriot
2
+ # namespace for utility functions
3
+ module Util
4
+ require 'patriot/util/config'
5
+ require 'patriot/util/logger'
6
+ require 'patriot/util/param'
7
+ require 'patriot/util/date_util'
8
+ require 'patriot/util/script'
9
+ require 'patriot/util/retry'
10
+ require 'patriot/util/db_client'
11
+ require 'patriot/util/system'
12
+ require 'patriot/util/cron_format_parser'
13
+ end
14
+ end
@@ -0,0 +1,58 @@
1
+ require 'patriot/util/config/base'
2
+ require 'patriot/util/config/inifile_config'
3
+
4
+ module Patriot
5
+ module Util
6
+ # namespace for configuration files
7
+ module Config
8
+ # the path to default configuration file
9
+ DEFAULT_CONFIG = File.join($home || Dir.pwd, 'config', 'patriot.ini')
10
+ # a configuration parameter key for plugins
11
+ PLUGIN_KEY = "plugins"
12
+ # a configuration parameter key for plugins directory
13
+ PLUGIN_DIR_KEY = "plugins.dir"
14
+ # default plugins directory
15
+ DEFAULT_PLUGIN_DIR = "plugins"
16
+ # plugin directory
17
+ PLUGIN_LIB_DIR = 'lib'
18
+ # plugin initiation script
19
+ PLUGIN_INIT_SCRIPT = 'init.rb'
20
+
21
+ # load configuration file
22
+ # @param option [Hash]
23
+ # @option option :path [String] path to configuration file
24
+ # @option option :type [String] load type (differe by tool)
25
+ # @option option :ignore_plugin [Boolean] set true not to load plugins
26
+ def load_config(option = {})
27
+ option = {:path => DEFAULT_CONFIG,
28
+ :type => nil,
29
+ :ignore_plugin => false }.merge(option)
30
+ conf = nil
31
+ case File.extname(option[:path])
32
+ when '.ini'
33
+ conf = Patriot::Util::Config::IniFileConfig.new(option[:path], option[:type])
34
+ else
35
+ raise "unsupported config file name: #{conf[:path]}"
36
+ end
37
+ load_plugins(conf) unless option[:ignore_plugin]
38
+ return conf
39
+ end
40
+
41
+ # load plugins
42
+ # @param conf [Patriot::Util::Config::Base] configuration to load plugins
43
+ def load_plugins(conf)
44
+ plugins = conf.get(PLUGIN_KEY)
45
+ return conf if plugins.nil?
46
+ plugins = [plugins] unless plugins.is_a?(Array)
47
+ plugin_dir = conf.get(PLUGIN_DIR_KEY, DEFAULT_PLUGIN_DIR)
48
+ plugins.each do |plugin|
49
+ path = File.join($home, plugin_dir, plugin)
50
+ init_script = File.join(path, PLUGIN_INIT_SCRIPT)
51
+ raise "Failed to load #{plugin}: #{init_script} does not exist" unless File.file?(init_script)
52
+ $: << File.join(path, PLUGIN_LIB_DIR)
53
+ require init_script
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ module Patriot
2
+ module Util
3
+ module Config
4
+ # base class of configuration classes
5
+ class Base
6
+ # get value for the specified key
7
+ # @param [String] key
8
+ # @param [Object] default default value
9
+ # @return [Object] the value for the ke
10
+ def get(key, default=nil)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ # get path where this configuration is loaded
15
+ # @return [String] path to the file
16
+ def path
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,63 @@
1
+ require 'inifile'
2
+ module Patriot
3
+ module Util
4
+ module Config
5
+ # a configuration implementation definied by the ini-file format
6
+ class IniFileConfig < Patriot::Util::Config::Base
7
+
8
+ # comman section name
9
+ COMMON_SECTION = 'common'
10
+
11
+ # @param path [String] path to a configuration file
12
+ # @param type [String] load type (section name to be loaded)
13
+ def initialize(path, type = nil)
14
+ raise "path in String is expected but #{path.class}" unless path.is_a?(String)
15
+ @path = path
16
+ config = IniFile.load(path)
17
+ raise "#{path} not found" if config.nil?
18
+ @config = {}
19
+ read_section(config, COMMON_SECTION)
20
+ read_section(config, type)
21
+ end
22
+
23
+ # @private
24
+ # read configuration from a section
25
+ # @param config [IniFile] ini file configuration
26
+ # @param section [String] section name
27
+ def read_section(config, section)
28
+ sect = config[section]
29
+ return if sect.nil?
30
+ sect.each{|k,v| @config[k.to_sym] = v }
31
+ end
32
+ private :read_section
33
+
34
+ # @see Patriot::Util::Config::Base
35
+ def path
36
+ return @path
37
+ end
38
+
39
+ # @see Patriot::Util::Config::Base
40
+ def get(name, default=nil)
41
+ v = @config[name.to_sym]
42
+ v = split_value(v)
43
+ return v.nil? ? default : v
44
+ end
45
+
46
+ # split configuration value by a delimiter
47
+ # @param value [String] the value to be splitted
48
+ # @param delimiter [String] delimiter for splitting
49
+ def split_value(value, delimiter = ',')
50
+ # don't allow spaces around value
51
+ regexp = Regexp.new("\\s*#{delimiter}\\s*")
52
+ if value.is_a?(String) && value =~ regexp
53
+ return value.split(regexp)
54
+ else
55
+ return value
56
+ end
57
+ end
58
+ private :split_value
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,104 @@
1
+ require 'date'
2
+ module Patriot
3
+ module Util
4
+ # module for handling cron format interval
5
+ module CronFormatParser
6
+
7
+ # start 00:00:00 everyday
8
+ DEFAULT_CRON_FIELD = "0 0 * * *"
9
+
10
+ # a key word to indicate interval in which jobs are executed on the end of every month
11
+ END_OF_EVERY_MONTH = 'end_of_every_month'
12
+
13
+ # the list of week days
14
+ WEEKS = 0.upto(6).to_a
15
+ # the list of months
16
+ MONTHS = 1.upto(12).to_a
17
+ # the list of days
18
+ DAYS = 1.upto(31).to_a
19
+ # the list of hours
20
+ HOURS = 0.upto(23).to_a
21
+ # the list of minutes
22
+ MINUTES = 0.upto(59).to_a
23
+
24
+ # expand a given date to the array of time which should be executed in case of a given cron field
25
+ # @param date [DateTime] target datetime
26
+ # @param cron_field [String] interval in cron format
27
+ # @return [Array<DateTime>] a list of datetime which match the cron format
28
+ def expand_on_date(date, cron_field = nil)
29
+ cron_field = DEFAULT_CRON_FIELD if cron_field.nil? || cron_field.empty?
30
+ if cron_field == END_OF_EVERY_MONTH
31
+ return date.next.day == 1 ? [date] : []
32
+ end
33
+ field_splits = cron_field.split
34
+ raise "illegal cron field format #{cron_field}" unless field_splits.size == 5
35
+ minute, hour, day, month, week = field_splits
36
+ return [] unless is_target_day?(date, day, month, week)
37
+ return target_hours(hour).map do |h|
38
+ target_minutes(minute).map do |m|
39
+ DateTime.new(date.year, date.month, date.day, h, m, 0)
40
+ end
41
+ end.flatten
42
+ end
43
+
44
+ # check a given date is a target or not
45
+ # @param date [DateTime] a datetime to be checked
46
+ # @param day [String] day field in cron format
47
+ # @param month [String] month field in cron format
48
+ # @param week [String] week field in cron format
49
+ def is_target_day?(date, day, month, week)
50
+ unless month == "*" || parse_field(month, MONTHS).to_a.include?(date.month)
51
+ return false
52
+ end
53
+ return true if day == "*" && week == "*"
54
+ target_weeks = parse_field(week, WEEKS)
55
+ return parse_field(day, DAYS).include?(date.day) if week == "*"
56
+ return parse_field(week, WEEKS).include?(date.wday)
57
+ end
58
+
59
+ # @param hour [String] hour field in cron format
60
+ # @return [Array<Integer>] a target hours for the given hour specification
61
+ def target_hours(hour)
62
+ return parse_field(hour, HOURS)
63
+ end
64
+
65
+ # @param minute [String] minute field in cron format
66
+ # @return [Array<Integer>] a target minutes for the given minute specification
67
+ def target_minutes(minute)
68
+ return parse_field(minute, MINUTES)
69
+ end
70
+
71
+ # select elements which match a given field from a given domain
72
+ # @param field [String] one of the cron field
73
+ # @param domain [Array<Integer>] the domain of the field
74
+ # @return [Array<Integer>] a list of the domain elements match with the field
75
+ def parse_field(field, domain)
76
+ field = field.split("/")
77
+ raise "illegal cron field format #{field.join("/")}" unless field.size <= 2
78
+ range = []
79
+ if field[0] == "*"
80
+ range = domain
81
+ else
82
+ range = field[0].split(",").map do |r|
83
+ subrange = r.split("-")
84
+ if subrange.size == 1
85
+ subrange[0].to_i
86
+ elsif subrange.size == 2
87
+ subrange[0].to_i.upto(subrange[1].to_i).to_a
88
+ else
89
+ raise "illegal cron field format #{field.join("/")}"
90
+ end
91
+ end.flatten
92
+ end
93
+ return range if field.size == 1
94
+ interval = field[1].to_i
95
+ filtered_range = []
96
+ range.each_with_index do |r,i|
97
+ filtered_range << r if (i % interval) == 0
98
+ end
99
+ return filtered_range
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,200 @@
1
+ require 'date'
2
+ module Patriot
3
+ module Util
4
+ # namesapce for date functions
5
+ module DateUtil
6
+
7
+ # format date
8
+ # @param dt [String] date in '%Y-%m-%d'
9
+ # @param time [String] time in '%H:%M:%S'
10
+ def date_format(dt, time, fmt, diff={})
11
+ diff = {:year => 0,
12
+ :month => 0,
13
+ :day => 0,
14
+ :hour => 0,
15
+ :min => 0,
16
+ :sec => 0}.merge(diff)
17
+
18
+ dt = eval "\"#{dt}\""
19
+ time = eval "\"#{time}\""
20
+
21
+ t = time.split(':')
22
+ d = dt.split('-')
23
+ sec = t[2].to_i + diff[:sec]
24
+ min = t[1].to_i + diff[:min]
25
+ hour = t[0].to_i + diff[:hour]
26
+ day = d[2].to_i
27
+ month = d[1].to_i
28
+ year = d[0].to_i + diff[:year]
29
+
30
+ min = min+ sec/60
31
+ hour = hour + min/60
32
+ diff[:day]= diff[:day] + hour/24
33
+
34
+ sec = sec%60
35
+ min = min%60
36
+ hour = hour%24
37
+
38
+ new_dt = DateTime.new(year, month, day, hour, min,sec)
39
+ new_dt = new_dt >> diff[:month]
40
+ new_dt = new_dt + diff[:day]
41
+ return new_dt.strftime(fmt)
42
+ end
43
+
44
+ # add interval to date
45
+ # @param date [String] date in '%Y-%m-%d'
46
+ # @param interval [Integer] interval in day
47
+ # @return [String] date in '%Y-%m-%d'
48
+ def date_add(date, interval)
49
+ d = to_date_obj(date)
50
+ d = d + interval
51
+ return d.strftime('%Y-%m-%d')
52
+ end
53
+
54
+ # subtract interval from date
55
+ # @param date [String] date in '%Y-%m-%d'
56
+ # @param interval [Integer] interval in day
57
+ # @return [String] date in '%Y-%m-%d'
58
+ def date_sub(date, interval)
59
+ d = to_date_obj(date)
60
+ d = d - interval
61
+ return d.strftime('%Y-%m-%d')
62
+ end
63
+
64
+ # @param date [String] date in '%Y-%m-%d'
65
+ # @param interval [Integer] interval in year
66
+ # @return [String] date of some years later in '%Y-%m-%d'
67
+ def date_add_year(date, interval)
68
+ s = date.split("-");
69
+ return Date.new(s[0].to_i + interval,s[1].to_i,s[2].to_i).strftime("%Y-%m-%d")
70
+ end
71
+
72
+ # @param date [String] date in '%Y-%m-%d'
73
+ # @param interval [Integer] interval in year
74
+ # @return [String] date of some years before in '%Y-%m-%d'
75
+ def date_sub_year(date, interval)
76
+ s = date.split("-");
77
+ return Date.new(s[0].to_i - interval,s[1].to_i,s[2].to_i).strftime("%Y-%m-%d")
78
+ end
79
+
80
+ # add interval to month
81
+ # @param month [String] month in '%Y-%m'
82
+ # @param interval [Integer] interval in month
83
+ # @return [String] month in '%Y-%m'
84
+ def month_add(month, interval)
85
+ d = to_date_obj("#{month}-01")
86
+ d = d >> interval
87
+ return d.strftime('%Y-%m')
88
+ end
89
+
90
+ # subtract interval from month
91
+ # @param month [String] month in '%Y-%m'
92
+ # @param interval [Integer] interval in month
93
+ # @return [String] month in '%Y-%m'
94
+ def month_sub(month, interval)
95
+ d = to_date_obj("#{month}-01")
96
+ d = d << interval
97
+ return d.strftime('%Y-%m')
98
+ end
99
+
100
+ # convert to month expression
101
+ # @param date [String] date in '%Y-%m-%d'
102
+ # @return [String] month in '%Y-%m'
103
+ def to_month(date)
104
+ d = to_date_obj(date)
105
+ return d.strftime('%Y-%m')
106
+ end
107
+
108
+ # get first date of the month
109
+ # @param date [String] date in '%Y-%m-%d'
110
+ # @return [String] date in '%Y-%m-%d'
111
+ def to_start_of_month(date)
112
+ s = date.split("-");
113
+ return Date.new(s[0].to_i,s[1].to_i,1).strftime("%Y-%m-%d")
114
+ end
115
+
116
+ # get the last date of the month
117
+ # @param date [String] date in '%Y-%m-%d'
118
+ # @return [String] date in '%Y-%m-%d'
119
+ def to_end_of_month(date)
120
+ s = date.split("-");
121
+ return ((Date.new(s[0].to_i,s[1].to_i,1)>>1)-1).strftime("%Y-%m-%d")
122
+ end
123
+
124
+ # get the last date of the last month
125
+ # @param date [String] date in '%Y-%m-%d'
126
+ # @return [String] date in '%Y-%m-%d'
127
+ def to_end_of_last_month(date)
128
+ d = to_date_obj(date)
129
+ d = d - d.day
130
+ return d.strftime('%Y-%m-%d')
131
+ end
132
+
133
+ # @param date [String] date in '%Y-%m-%d'
134
+ # @return [Integer] week of the date in (0-6)
135
+ def days_of_week(date)
136
+ d = to_date_obj(date)
137
+ return d.wday
138
+ end
139
+
140
+ # @param month [String] month in '%Y-%m'
141
+ # @return [Array<String>] the list of days in the month
142
+ def days_of_month(month)
143
+ s = month.split('-')
144
+ y = s[0].to_i
145
+ m = s[1].to_i
146
+ return 1.upto(31).map{|d| Date.new(y,m,d).strftime('%Y-%m-%d') if Date.valid_date?(y,m,d)}.compact
147
+ end
148
+
149
+ # @param dt [String] date in '%Y-%m-%d'
150
+ # @return [Array<String>] the list of days between the first date and the given date in the month
151
+ def days_of_month_until(dt)
152
+ s = dt.split('-')
153
+ y = s[0].to_i
154
+ m = s[1].to_i
155
+ d = s[2].to_i
156
+ return 1.upto(d).map{|d| Date.new(y,m,d).strftime('%Y-%m-%d') if Date.valid_date?(y,m,d)}.compact
157
+ end
158
+
159
+ # convert to a Date instance
160
+ # @param date [String] date in '%Y-%m-%d'
161
+ # @return [Date]
162
+ def to_date_obj(date)
163
+ d = date.split('-')
164
+ return Date.new(d[0].to_i, d[1].to_i, d[2].to_i)
165
+ end
166
+
167
+ # @deprecated
168
+ def date_to_month(date)
169
+ d = date.split('-')
170
+ return Date.new(d[0].to_i, d[1].to_i, d[2].to_i).strftime('%Y-%m')
171
+ end
172
+
173
+ # @return [Array<String>] a list of hours (00..23)
174
+ def hours
175
+ return 0.upto(23).map do |h| h_str = h.to_s.rjust(2, "0") end.flatten
176
+ end
177
+
178
+ # validate date or date range expression
179
+ def validate_and_parse_dates(date)
180
+ date_objs = date.split(",").map do |d|
181
+ unless d.to_s =~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/
182
+ raise ArgumentError, "ERROR: invalid date #{d} in #{date}"
183
+ end
184
+ Date.parse(d.to_s)
185
+ end
186
+ if date_objs.size == 1
187
+ return [date]
188
+ elsif date_objs.size == 2
189
+ dates = []
190
+ date_objs[0].upto(date_objs[1]){|d| dates << d.to_s}
191
+ return dates
192
+ end
193
+ raise ArgumentError, "ERROR: invalid date #{date}"
194
+ end
195
+ private :validate_and_parse_dates
196
+
197
+ end
198
+ end
199
+ end
200
+