cron_config_parser 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf8cde4d225478b58bd0ca262720c1800bbf481badd4fd4297827440ae01fc80
4
- data.tar.gz: 81c27cb9f9bafcd0f6c140f5d709acf2a22ad1659d4df914b4e0233561e41d85
3
+ metadata.gz: 895da1f251f953d6815a2bb37ade3974189abbef9e6215c80de705b7164e6874
4
+ data.tar.gz: 1d1bcd7003feaf1453b86d87d96ff89126c2214d8c2218cb492e383e3b87d5cd
5
5
  SHA512:
6
- metadata.gz: 91a272c9eedd125bf5160e0c9c79e0a13c9fd1daada02cdf2ce19b1c410f0cd59c6a647004f3792209e4e7e7b07d1487c4bc82cb71512a112d23358aaddd633e
7
- data.tar.gz: 324653a9f72dca12c7964c4d826ebd4b94fadecc50d51d8c8eeeda89d052d51b2444dc136b23ff5f3558b9f28b2795352f2b9180469abba0e247197ec0f9ced7
6
+ metadata.gz: cbd0de9b030fe9134482ea5edc26fe82a87204572bf6fcdd54d9071476c1b8ee1388512236ed20c8ab4372dddcfeedf0102865b90a7357c2d5346b31822e2726
7
+ data.tar.gz: 9fafca4d6b2d9d03f716adf58e729fff7113009f0d1fc7ddd302d9ee3ad5dd040ddaa667e53c544a894821c4c452c867f3340a22bf7d91476df650699ae2c09d
data/Gemfile.lock CHANGED
@@ -1,12 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cron_config_parser (0.1.1)
4
+ cron_config_parser (0.1.2)
5
+ activesupport (~> 5.2)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
10
+ activesupport (5.2.3)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (>= 0.7, < 2)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
15
+ concurrent-ruby (1.1.5)
9
16
  diff-lcs (1.3)
17
+ i18n (1.6.0)
18
+ concurrent-ruby (~> 1.0)
19
+ minitest (5.11.3)
10
20
  rake (10.5.0)
11
21
  rspec (3.8.0)
12
22
  rspec-core (~> 3.8.0)
@@ -21,6 +31,9 @@ GEM
21
31
  diff-lcs (>= 1.2.0, < 2.0)
22
32
  rspec-support (~> 3.8.0)
23
33
  rspec-support (3.8.0)
34
+ thread_safe (0.3.6)
35
+ tzinfo (1.2.5)
36
+ thread_safe (~> 0.1)
24
37
 
25
38
  PLATFORMS
26
39
  ruby
data/README.md CHANGED
@@ -37,12 +37,20 @@ enable check configured properties.
37
37
  ``` ruby
38
38
  # return false if configured nil or '*'
39
39
  config = CronConfigParser::Parser.call('00 5 * * * Asia/Tokyo')
40
- config.minutes_difined?
40
+ config.minutes_configured?
41
41
  => true
42
- config.days_difined?
42
+ config.days_configured?
43
43
  => false
44
44
  ```
45
45
 
46
+ enable check next execute time.
47
+
48
+ ``` ruby
49
+ config = CronConfigParser::Parser.call('00 5 * * * Asia/Tokyo')
50
+ config.next_execute_at
51
+ => 2019-05-23 05:00:00 +0900
52
+ ```
53
+
46
54
  This gem check simple validation when CronConfigParser::CronConfig object initialize.
47
55
  If the config is invalid, Config::SyntaxError or Config::RequiredError is raised.
48
56
 
@@ -68,7 +76,7 @@ CronConfigParser::Parser.call('00 5,a * * * Asia/Tokyo', validation: false)
68
76
 
69
77
  ## Contributing
70
78
 
71
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/cron_config_parser. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
79
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Madogiwa0124/cron_config_parser. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
72
80
 
73
81
  ## License
74
82
 
@@ -76,4 +84,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
76
84
 
77
85
  ## Code of Conduct
78
86
 
79
- Everyone interacting in the CronConfigParser project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/cron_config_parser/blob/master/CODE_OF_CONDUCT.md).
87
+ Everyone interacting in the CronConfigParser project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Madogiwa0124/cron_config_parser/blob/master/CODE_OF_CONDUCT.md).
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
+ spec.add_dependency "activesupport", "~> 5.2"
25
26
  spec.add_development_dependency "bundler", "~> 1.17"
26
27
  spec.add_development_dependency "rake", "~> 10.0"
27
28
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -1,9 +1,7 @@
1
1
  require "cron_config_parser/version"
2
+ require 'active_support/all'
2
3
 
3
4
  module CronConfigParser
4
- class ConfigSyntaxError < StandardError; end
5
- class ConfigRequiredError < StandardError; end
6
-
7
5
  class Parser
8
6
  def self.call(cron_config, validation: true)
9
7
  new(cron_config, validation: validation).call
@@ -24,50 +22,193 @@ module CronConfigParser
24
22
  class CronConfig
25
23
  NOT_CONFIGURED_MARK = '*'.freeze
26
24
 
27
- def initialize(cron_config, validation: true)
25
+ def initialize(cron_config, validator: CronConfigParser::Varidator, validation: true)
28
26
  cron_config = cron_config.split(' ').map { |item| item.split(',') }
29
27
  @minutes, @hours, @days, @months, @wdays, @timezone = cron_config
30
28
  # timezone is not multiple values.
31
29
  @timezone = @timezone.to_a.first
32
- validate! if validation
30
+ validator.call!(self) if validation
33
31
  end
34
32
 
35
- attr_reader :minutes, :hours, :days, :months, :wdays, :timezone
33
+ attr_accessor :minutes, :hours, :days, :months, :wdays, :timezone
34
+
35
+ def next_execute_at(basis_datetime: Time.current)
36
+ ExecuteAtCalculator.new(cron_config: self, basis_datetime: basis_datetime).call
37
+ end
36
38
 
37
- # difine properties configured check methods
39
+ # define properties configured check methods
38
40
  [:minutes, :hours, :days, :months, :wdays, :timezone].each do |attr|
39
41
  define_method("#{attr}_configured?") do
40
42
  value = send(attr).class == Array ? send(attr)[0] : send(attr)
41
43
  ![NOT_CONFIGURED_MARK, nil].include?(value)
42
44
  end
43
45
  end
46
+ end
47
+
48
+ class Varidator
49
+ class ConfigSyntaxError < StandardError; end
50
+ class ConfigRequiredError < StandardError; end
51
+
52
+ def self.call!(cron_config)
53
+ new(cron_config).call!
54
+ end
55
+
56
+ def initialize(cron_config)
57
+ @cron_config = cron_config
58
+ end
59
+
60
+ def call!
61
+ raise ConfigRequiredError unless configured_required_properties?
62
+ raise ConfigSyntaxError unless configured_valid_syntax_properties?
63
+ end
44
64
 
45
65
  private
46
66
 
47
- def validate!
48
- check_required_properties!
49
- check_properties_syntax!
67
+ attr_reader :cron_config
68
+
69
+ private
70
+
71
+ def configured_required_properties?
72
+ ![cron_config.minutes,
73
+ cron_config.hours,
74
+ cron_config.days,
75
+ cron_config.months,
76
+ cron_config.wdays].flatten.include?(nil)
50
77
  end
51
78
 
52
- def check_required_properties!
53
- if [minutes, hours, days, months, wdays].flatten.include?(nil)
54
- raise ConfigRequiredError
55
- end
79
+ def configured_valid_syntax_properties?
80
+ return false if cron_config.minutes_configured? && !values_in_range?(:minutes) && !validate_format?(:minutes)
81
+ return false if cron_config.hours_configured? && !values_in_range?(:hours) && !validate_format?(:hours)
82
+ return false if cron_config.days_configured? && !values_in_range?(:days) && !validate_format?(:days)
83
+ return false if cron_config.wdays_configured? && !values_in_range?(:wdays) && !validate_format?(:wdays)
84
+ return false if cron_config.months_configured? && !values_in_range?(:months) && !validate_format?(:months)
85
+ true
56
86
  end
57
87
 
58
- def check_properties_syntax!
59
- raise ConfigSyntaxError if minutes_configured? && !values_in_range?(range: 0..60, values: minutes)
60
- raise ConfigSyntaxError if hours_configured? && !values_in_range?(range: 0..24, values: hours)
61
- raise ConfigSyntaxError if days_configured? && !values_in_range?(range: 1..31, values: days)
62
- raise ConfigSyntaxError if wdays_configured? && !values_in_range?(range: 0..6, values: wdays)
88
+ def validate_format?(property_sym)
89
+ valid_count = cron_config.send(property_sym).count do |value|
90
+ value.match?(/(\d{1,2}|\*)(\/|-)\d{1,2}/) || value.match?(/\d{1,2}/)
91
+ end
92
+ cron_config.send(property_sym).count == valid_count
63
93
  end
64
94
 
65
- def values_in_range?(range: , values:)
66
- values.count == values.count { |value| range.include?(to_i(value)) }
95
+ def values_in_range?(property_sym)
96
+ values = cron_config.send(property_sym)
97
+ hash = { minutes: 0..60, hours: 0..24, days: 1..31, months: 1..12, wdays: 0..6 }
98
+ values.count == values.count { |value| hash[property_sym].include?(to_i(value)) }
67
99
  end
68
100
 
69
101
  def to_i(string)
70
- Integer(string) rescue nil
102
+ Integer(string) rescue string
103
+ end
104
+ end
105
+
106
+ class ExecuteAtCalculator
107
+ WDAYS = %i[sunday monday tuesday wednesday thursday friday saturday].freeze
108
+
109
+ def initialize(cron_config:, basis_datetime:)
110
+ @cron_config = cron_config
111
+ @basis_datetime = basis_datetime
112
+ @execute_at = basis_datetime
113
+ prepare_cron_config
114
+ end
115
+
116
+ attr_accessor :cron_config, :basis_datetime, :execute_at
117
+
118
+ def call
119
+ next_minute
120
+ next_hour
121
+ next_day
122
+ next_wday
123
+ next_month
124
+ execute_at
125
+ end
126
+
127
+ private
128
+
129
+ def prepare_cron_config
130
+ @cron_config.minutes = parse_config_property(:minutes)
131
+ @cron_config.hours = parse_config_property(:hours)
132
+ @cron_config.days = parse_config_property(:days)
133
+ @cron_config.months = parse_config_property(:months)
134
+ @cron_config.wdays = parse_config_property(:wdays)
135
+ end
136
+
137
+ def to_i(value)
138
+ Integer(value) rescue value
139
+ end
140
+
141
+ def parse_config_property(property_sym)
142
+ cron_config.send(property_sym).map do |property|
143
+ next to_i(property) unless property.match?(/\/|-/)
144
+ next parse_for_devided_config(property, property_sym) if property.include?('/')
145
+ next parse_for_range_config(property) if property.include?('-')
146
+ end.flatten.uniq.sort
147
+ end
148
+
149
+ def parse_for_devided_config(property, property_sym)
150
+ hash = { minutes: 0..60, hours: 0..24, days: 1..31, months: 1..12, wdays: 0..6 }
151
+ hash[property_sym].select { |val| (val % (property).split('/')[1].to_i) == 0 }
152
+ end
153
+
154
+ def parse_for_range_config(property)
155
+ range_start, range_end = property.split('-').map(&:to_i)
156
+ (range_start..range_end).to_a
157
+ end
158
+
159
+ def next_minute
160
+ return @execute_at = execute_at.since(1.minute) unless cron_config.minutes_configured?
161
+ next_minute = cron_config.minutes.select { |config_min| config_min > execute_at.min }.first
162
+ @execute_at = change_to_property_and_move_up(property: next_minute, property_sym: :minutes)
163
+ end
164
+
165
+ def next_hour
166
+ return unless cron_config.hours_configured?
167
+ next_hour = cron_config.hours.select { |config_hour| config_hour > execute_at.hour }.first
168
+ @execute_at = change_to_property_and_move_up(property: next_hour, property_sym: :hours)
169
+ # reset minute when execute in freture and not configured minute
170
+ @execute_at = @execute_at.change(min: 0) unless cron_config.minutes_configured?
171
+ end
172
+
173
+ def next_day
174
+ return unless cron_config.days_configured?
175
+ next_day = cron_config.days.select { |config_day| config_day > execute_at.day }.first
176
+ @execute_at = change_to_property_and_move_up(property: next_day, property_sym: :days)
177
+ # reset minute when execute in freture and not configured minute
178
+ @execute_at = @execute_at.change(min: 0) unless cron_config.minutes_configured?
179
+ end
180
+
181
+ def next_wday
182
+ return unless cron_config.wdays_configured?
183
+ next_wday = cron_config.wdays.select { |config_wday| config_wday > execute_at.wday }.first
184
+ next_wday_sym = next_wday ? WDAYS[next_wday] : WDAYS[cron_config.wdays.first]
185
+ @execute_at = execute_at.next_occurring(next_wday_sym)
186
+ # reset minute when execute in freture and not configured minute
187
+ @execute_at = @execute_at.change(min: 0) unless cron_config.minutes_configured?
188
+ end
189
+
190
+ def next_month
191
+ return unless cron_config.months_configured?
192
+ next_month = cron_config.months.select { |config_month| config_month > execute_at.month }.first
193
+ @execute_at = change_to_property_and_move_up(property: next_month, property_sym: :months)
194
+ # reset minute when execute in freture and not configured minute
195
+ @execute_at = @execute_at.change(min: 0) unless cron_config.minutes_configured?
196
+ end
197
+
198
+ def change_to_property_and_move_up(property:, property_sym:)
199
+ change_property = convert_hash(property_sym)[:change_property]
200
+ move_up_property = convert_hash(property_sym)[:move_up_property]
201
+ return execute_at.change(change_property => property) if property
202
+ execute_at.since(1.send(move_up_property)).change(
203
+ change_property => cron_config.send(property_sym).first
204
+ )
205
+ end
206
+
207
+ def convert_hash(property_sym)
208
+ { minutes: { change_property: :min, move_up_property: :hour },
209
+ hours: { change_property: :hour, move_up_property: :day },
210
+ days: { change_property: :day, move_up_property: :month },
211
+ months: { change_property: :month, move_up_property: :year }, }[property_sym]
71
212
  end
72
213
  end
73
214
  end
@@ -1,3 +1,3 @@
1
1
  module CronConfigParser
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cron_config_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Madogiwa
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-18 00:00:00.000000000 Z
11
+ date: 2019-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement