cron_config_parser 0.1.1 → 0.1.2

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.
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