bblib 0.3.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +11 -10
- data/.rspec +2 -2
- data/.travis.yml +4 -4
- data/CODE_OF_CONDUCT.md +13 -13
- data/Gemfile +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +247 -757
- data/Rakefile +6 -6
- data/bblib.gemspec +34 -34
- data/bin/console +14 -14
- data/bin/setup +7 -7
- data/lib/array/bbarray.rb +71 -29
- data/lib/bblib.rb +12 -12
- data/lib/bblib/version.rb +3 -3
- data/lib/class/effortless.rb +23 -0
- data/lib/error/abstract.rb +3 -0
- data/lib/file/bbfile.rb +93 -52
- data/lib/hash/bbhash.rb +130 -46
- data/lib/hash/hash_struct.rb +24 -0
- data/lib/hash/tree_hash.rb +364 -0
- data/lib/hash_path/hash_path.rb +210 -0
- data/lib/hash_path/part.rb +83 -0
- data/lib/hash_path/path_hash.rb +84 -0
- data/lib/hash_path/proc.rb +93 -0
- data/lib/hash_path/processors.rb +239 -0
- data/lib/html/bbhtml.rb +2 -0
- data/lib/html/builder.rb +34 -0
- data/lib/html/tag.rb +49 -0
- data/lib/logging/bblogging.rb +42 -0
- data/lib/mixins/attrs.rb +422 -0
- data/lib/mixins/bbmixins.rb +7 -0
- data/lib/mixins/bridge.rb +17 -0
- data/lib/mixins/family_tree.rb +41 -0
- data/lib/mixins/hooks.rb +139 -0
- data/lib/mixins/logger.rb +31 -0
- data/lib/mixins/serializer.rb +71 -0
- data/lib/mixins/simple_init.rb +160 -0
- data/lib/number/bbnumber.rb +15 -7
- data/lib/object/bbobject.rb +46 -19
- data/lib/opal/bbopal.rb +0 -4
- data/lib/os/bbos.rb +24 -16
- data/lib/os/bbsys.rb +60 -43
- data/lib/string/bbstring.rb +165 -66
- data/lib/string/cases.rb +37 -29
- data/lib/string/fuzzy_matcher.rb +48 -50
- data/lib/string/matching.rb +43 -30
- data/lib/string/pluralization.rb +156 -0
- data/lib/string/regexp.rb +45 -0
- data/lib/string/roman.rb +17 -30
- data/lib/system/bbsystem.rb +42 -0
- data/lib/time/bbtime.rb +79 -58
- data/lib/time/cron.rb +174 -132
- data/lib/time/task_timer.rb +86 -70
- metadata +27 -10
- data/lib/gem/bbgem.rb +0 -28
- data/lib/hash/hash_path.rb +0 -344
- data/lib/hash/hash_path_proc.rb +0 -256
- data/lib/hash/path_hash.rb +0 -81
- data/lib/object/attr.rb +0 -182
- data/lib/object/hooks.rb +0 -69
- data/lib/object/lazy_class.rb +0 -73
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Opal seems to be missing the extended flag, this MonkeyPatch prevents errors.
|
4
|
+
if BBLib.in_opal?
|
5
|
+
class Regexp
|
6
|
+
EXTENDED = 2
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module BBLib
|
11
|
+
REGEXP_MODE_HASH = {
|
12
|
+
i: Regexp::IGNORECASE,
|
13
|
+
m: Regexp::MULTILINE,
|
14
|
+
x: Regexp::EXTENDED
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
REGEXP_OPTIONS = {
|
18
|
+
i: [:ignore_case, :ignorecase, :i, :case_insensitive, Regexp::IGNORECASE],
|
19
|
+
m: [:multiline, :multi_line, :m, Regexp::MULTILINE],
|
20
|
+
x: [:extended, :x, Regexp::EXTENDED]
|
21
|
+
}.freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
class Regexp
|
25
|
+
def self.from_s(str, *options, ignore_invalid: false)
|
26
|
+
opt_map = options.map { |o| BBLib::REGEXP_OPTIONS.find { |k, v| o == k || o == k.to_s || v.include?(o) || v.include?(o.to_s.to_sym) }.first }.compact
|
27
|
+
return Regexp.new(str, opt_map.inject(0) { |s, x| s += BBLib::REGEXP_MODE_HASH[x] }) if str.encap_by?('(') || !str.start_with?('/')
|
28
|
+
str += opt_map.join
|
29
|
+
mode = 0
|
30
|
+
unless str.end_with?('/')
|
31
|
+
str.split('/').last.chars.uniq.each do |l|
|
32
|
+
raise ArgumentError, "Invalid Regexp mode: '#{l}'" unless ignore_invalid || BBLib::REGEXP_MODE_HASH[l.to_sym]
|
33
|
+
mode += (BBLib::REGEXP_MODE_HASH[l.to_sym] || 0)
|
34
|
+
end
|
35
|
+
str = str[0..(str.rindex('/') || -1)]
|
36
|
+
end
|
37
|
+
Regexp.new(str.uncapsulate('/', limit: 1), mode)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class String
|
42
|
+
def to_regex(*options, ignore_invalid: false)
|
43
|
+
Regexp.from_s(self, *options, ignore_invalid: ignore_invalid)
|
44
|
+
end
|
45
|
+
end
|
data/lib/string/roman.rb
CHANGED
@@ -1,53 +1,48 @@
|
|
1
1
|
|
2
|
+
# frozen_string_literal: true
|
2
3
|
module BBLib
|
3
|
-
|
4
|
+
ROMAN_NUMERALS = { 1000 => 'M', 900 => 'CM', 500 => 'D', 400 => 'CD', 100 => 'C', 90 => 'XC', 50 => 'L',
|
5
|
+
40 => 'XL', 10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 3 => 'III', 2 => 'II', 1 => 'I' }.freeze
|
4
6
|
# Converts any integer up to 1000 to a roman numeral
|
5
|
-
def self.to_roman
|
7
|
+
def self.to_roman(num)
|
6
8
|
return num.to_s if num > 1000
|
7
|
-
|
8
|
-
|
9
|
-
numeral = ""
|
10
|
-
roman.each do |n, r|
|
9
|
+
numeral = ''
|
10
|
+
ROMAN_NUMERALS.each do |n, r|
|
11
11
|
while num >= n
|
12
|
-
num-= n
|
13
|
-
numeral+= r
|
12
|
+
num -= n
|
13
|
+
numeral += r
|
14
14
|
end
|
15
15
|
end
|
16
16
|
numeral
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.string_to_roman
|
19
|
+
def self.string_to_roman(str)
|
20
20
|
sp = str.split ' '
|
21
21
|
sp.map do |s|
|
22
22
|
if s.drop_symbols.to_i.to_s == s.drop_symbols && !(s =~ /\d+\.\d+/)
|
23
|
-
s
|
23
|
+
s.sub(s.scan(/\d+/).first.to_s, BBLib.to_roman(s.to_i))
|
24
24
|
else
|
25
25
|
s
|
26
26
|
end
|
27
|
-
end.join
|
27
|
+
end.join(' ')
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
def self.from_roman str
|
30
|
+
def self.from_roman(str)
|
32
31
|
sp = str.split(' ')
|
33
32
|
(0..1000).each do |n|
|
34
33
|
num = BBLib.to_roman n
|
35
|
-
if
|
36
|
-
|
37
|
-
|
38
|
-
sp[i] = sp[i].sub(num ,n.to_s)
|
39
|
-
end
|
40
|
-
end
|
34
|
+
next if sp.select { |i| i[/#{num}/i] }.empty?
|
35
|
+
(0..(sp.length-1)).each do |i|
|
36
|
+
sp[i] = sp[i].sub(num, n.to_s) if sp[i].drop_symbols.upcase == num
|
41
37
|
end
|
42
38
|
end
|
43
39
|
sp.join ' '
|
44
40
|
end
|
45
|
-
|
46
41
|
end
|
47
42
|
|
48
|
-
class
|
43
|
+
class Integer
|
49
44
|
def to_roman
|
50
|
-
BBLib.to_roman
|
45
|
+
BBLib.to_roman to_i
|
51
46
|
end
|
52
47
|
end
|
53
48
|
|
@@ -56,15 +51,7 @@ class String
|
|
56
51
|
BBLib.from_roman self
|
57
52
|
end
|
58
53
|
|
59
|
-
def from_roman!
|
60
|
-
replace self.from_roman
|
61
|
-
end
|
62
|
-
|
63
54
|
def to_roman
|
64
55
|
BBLib.string_to_roman self
|
65
56
|
end
|
66
|
-
|
67
|
-
def to_roman!
|
68
|
-
replace self.to_roman
|
69
|
-
end
|
70
57
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module BBLib
|
2
|
+
# A string representation of the command line that evoked this ruby instance (platform agnostic)
|
3
|
+
def self.cmd_line(*args, include_args: true, include_ruby: true, prefix: nil, suffix: nil)
|
4
|
+
args = ARGV if args.empty?
|
5
|
+
include_ruby = false if special_program?
|
6
|
+
"#{prefix}#{include_ruby ? Command.quote(Gem.ruby) : nil} #{Command.quote($PROGRAM_NAME)}" \
|
7
|
+
" #{include_args ? args.map { |a| Command.quote(a) }.join(' ') : nil}#{suffix}"
|
8
|
+
.strip
|
9
|
+
end
|
10
|
+
|
11
|
+
# EXPERIMENTAL: Reloads the original file that was called
|
12
|
+
# Use at your own risk, this could cause some weird issues
|
13
|
+
def self.reload(include_args: true)
|
14
|
+
return false if special_program?
|
15
|
+
load cmd_line(*args, include_ruby: false, include_args: include_args)
|
16
|
+
end
|
17
|
+
|
18
|
+
# EXPERIMENTAL: Restart the ruby process that is currently running.
|
19
|
+
# Use at your own risk
|
20
|
+
def self.restart(*args, include_args: true, stay_alive: 1)
|
21
|
+
exit(0)
|
22
|
+
rescue SystemExit
|
23
|
+
opts = BBLib::OS.windows? ? { new_pgroup: true } : { pgroup: true }
|
24
|
+
pid = spawn(cmd_line(*args, include_args: include_args, prefix: (BBLib::OS.windows? ? 'start ' : nil)), **opts)
|
25
|
+
Process.detach(pid)
|
26
|
+
sleep(stay_alive)
|
27
|
+
exit(0) if special_program?
|
28
|
+
end
|
29
|
+
|
30
|
+
SPECIAL_PROGRAMS = ['pry', 'irb.cmd', 'irb'].freeze
|
31
|
+
|
32
|
+
def self.special_program?
|
33
|
+
SPECIAL_PROGRAMS.include?($PROGRAM_NAME)
|
34
|
+
end
|
35
|
+
|
36
|
+
module Command
|
37
|
+
def self.quote(arg)
|
38
|
+
arg =~ /\s+/ ? "\"#{arg}\"" : arg.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/time/bbtime.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative 'task_timer'
|
2
3
|
require_relative 'cron'
|
3
4
|
|
4
5
|
module BBLib
|
5
|
-
|
6
6
|
# Parses known time based patterns out of a string to construct a numeric duration.
|
7
|
-
def self.parse_duration
|
7
|
+
def self.parse_duration(str, output: :sec, min_interval: :sec)
|
8
8
|
msecs = 0.0
|
9
9
|
|
10
10
|
# Parse time expressions such as 04:05.
|
@@ -20,7 +20,7 @@ module BBLib
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Parse expressions such as '1m' or '1 min'
|
23
|
-
TIME_EXPS.each do |
|
23
|
+
TIME_EXPS.each do |_k, v|
|
24
24
|
v[:exp].each do |e|
|
25
25
|
numbers = str.downcase.scan(/(?=\w|\D|\A)\d*\.?\d+[[:space:]]*#{e}(?=\W|\d|\z)/i)
|
26
26
|
numbers.each do |n|
|
@@ -28,109 +28,130 @@ module BBLib
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
32
|
-
msecs / (TIME_EXPS[output][:mult] rescue 1)
|
31
|
+
msecs / TIME_EXPS[output][:mult]
|
33
32
|
end
|
34
33
|
|
35
34
|
# Turns a numeric input into a time string.
|
36
|
-
def self.to_duration
|
37
|
-
return nil unless Numeric
|
38
|
-
|
35
|
+
def self.to_duration(num, input: :sec, stop: :milli, style: :medium)
|
36
|
+
return nil unless num.is_a?(Numeric)
|
37
|
+
return '0' if num.zero?
|
38
|
+
style = :medium unless [:long, :medium, :short].include?(style)
|
39
39
|
expression = []
|
40
|
-
n
|
40
|
+
n = num * TIME_EXPS[input.to_sym][:mult]
|
41
|
+
done = false
|
41
42
|
TIME_EXPS.reverse.each do |k, v|
|
42
|
-
next
|
43
|
-
if k == stop
|
43
|
+
next if done
|
44
|
+
done = true if k == stop
|
44
45
|
div = n / v[:mult]
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
46
|
+
next unless div >= 1
|
47
|
+
val = (done ? div.round : div.floor)
|
48
|
+
expression << "#{val}#{v[:styles][style]}#{val > 1 && style != :short ? 's' : nil}"
|
49
|
+
n -= val.to_f * v[:mult]
|
50
50
|
end
|
51
|
-
expression.join
|
51
|
+
expression.join(' ')
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.to_nearest_duration(num, input: :sec, style: :medium)
|
55
|
+
n = num * TIME_EXPS[input.to_sym][:mult]
|
56
|
+
stop = nil
|
57
|
+
TIME_EXPS.each do |k, v|
|
58
|
+
stop = k if v[:mult] <= n
|
59
|
+
end
|
60
|
+
stop = :year unless stop
|
61
|
+
to_duration(num, input: input, style: style, stop: stop)
|
52
62
|
end
|
53
63
|
|
54
64
|
TIME_EXPS = {
|
55
65
|
yocto: {
|
56
66
|
mult: 0.000000000000000000001,
|
57
|
-
styles: {
|
58
|
-
exp:
|
67
|
+
styles: { long: ' yoctosecond', medium: ' yocto', short: 'ys' },
|
68
|
+
exp: %w(yoctosecond yocto yoctoseconds yoctos ys)
|
59
69
|
},
|
60
70
|
zepto: {
|
61
71
|
mult: 0.000000000000000001,
|
62
|
-
styles: {
|
63
|
-
exp:
|
72
|
+
styles: { long: ' zeptosecond', medium: ' zepto', short: 'zs' },
|
73
|
+
exp: %w(zeptosecond zepto zeptoseconds zeptos zs)
|
64
74
|
},
|
65
75
|
atto: {
|
66
76
|
mult: 0.000000000000001,
|
67
|
-
styles: {
|
68
|
-
exp:
|
77
|
+
styles: { long: ' attosecond', medium: ' atto', short: 'as' },
|
78
|
+
exp: %w(attoseconds atto attoseconds attos as)
|
69
79
|
},
|
70
80
|
femto: {
|
71
81
|
mult: 0.000000000001,
|
72
|
-
styles: {
|
73
|
-
exp:
|
82
|
+
styles: { long: ' femtosecond', medium: ' fempto', short: 'fs' },
|
83
|
+
exp: %w(femtosecond fempto femtoseconds femptos fs)
|
74
84
|
},
|
75
85
|
pico: {
|
76
86
|
mult: 0.000000001,
|
77
|
-
styles: {
|
78
|
-
exp:
|
87
|
+
styles: { long: ' picosecond', medium: ' pico', short: 'ps' },
|
88
|
+
exp: %w(picosecond pico picoseconds picos ps)
|
79
89
|
},
|
80
90
|
nano: {
|
81
91
|
mult: 0.000001,
|
82
|
-
styles: {
|
83
|
-
exp:
|
92
|
+
styles: { long: ' nanosecond', medium: ' nano', short: 'ns' },
|
93
|
+
exp: %w(nanosecond nano nanoseconds nanos ns)
|
84
94
|
},
|
85
95
|
micro: {
|
86
96
|
mult: 0.001,
|
87
|
-
styles: {
|
88
|
-
exp:
|
97
|
+
styles: { long: ' microsecond', medium: ' micro', short: 'μs' },
|
98
|
+
exp: %W(microsecond micro microseconds micros \u03BCs)
|
89
99
|
},
|
90
100
|
milli: {
|
91
101
|
mult: 1,
|
92
|
-
styles: {
|
93
|
-
exp:
|
102
|
+
styles: { long: ' millisecond', medium: ' mil', short: 'ms' },
|
103
|
+
exp: %w(ms mil mils milli millis millisecond milliseconds milsec milsecs msec msecs msecond mseconds)
|
104
|
+
},
|
94
105
|
sec: {
|
95
106
|
mult: 1000,
|
96
|
-
styles: {
|
97
|
-
exp:
|
107
|
+
styles: { long: ' second', medium: ' sec', short: 's' },
|
108
|
+
exp: %w(s sec secs second seconds)
|
109
|
+
},
|
98
110
|
min: {
|
99
|
-
mult:
|
100
|
-
styles: {
|
101
|
-
exp:
|
111
|
+
mult: 60_000,
|
112
|
+
styles: { long: ' minute', medium: ' min', short: 'm' },
|
113
|
+
exp: %w(m mn mns min mins minute minutes)
|
114
|
+
},
|
102
115
|
hour: {
|
103
|
-
mult:
|
104
|
-
styles: {
|
105
|
-
exp:
|
116
|
+
mult: 3_600_000,
|
117
|
+
styles: { long: ' hour', medium: ' hr', short: 'h' },
|
118
|
+
exp: %w(h hr hrs hour hours)
|
119
|
+
},
|
106
120
|
day: {
|
107
|
-
mult:
|
108
|
-
styles: {
|
109
|
-
exp:
|
121
|
+
mult: 86_400_000,
|
122
|
+
styles: { long: ' day', medium: ' day', short: 'd' },
|
123
|
+
exp: %w(d day days)
|
124
|
+
},
|
110
125
|
week: {
|
111
|
-
mult:
|
112
|
-
styles: {
|
113
|
-
exp:
|
126
|
+
mult: 604_800_000,
|
127
|
+
styles: { long: ' week', medium: ' wk', short: 'w' },
|
128
|
+
exp: %w(w wk wks week weeks)
|
129
|
+
},
|
114
130
|
month: {
|
115
|
-
mult:
|
116
|
-
styles: {
|
117
|
-
exp:
|
131
|
+
mult: 2_592_000_000,
|
132
|
+
styles: { long: ' month', medium: ' mo', short: 'mo' },
|
133
|
+
exp: %w(mo mon mons month months mnth mnths mth mths)
|
134
|
+
},
|
118
135
|
year: {
|
119
|
-
mult:
|
120
|
-
styles: {
|
121
|
-
exp:
|
122
|
-
|
123
|
-
|
136
|
+
mult: 31_536_000_000,
|
137
|
+
styles: { long: ' year', medium: ' yr', short: 'y' },
|
138
|
+
exp: %w(y yr yrs year years)
|
139
|
+
}
|
140
|
+
}.freeze
|
124
141
|
end
|
125
142
|
|
126
143
|
class String
|
127
|
-
def parse_duration
|
128
|
-
BBLib.parse_duration self, output:output, min_interval:min_interval
|
144
|
+
def parse_duration(output: :sec, min_interval: :sec)
|
145
|
+
BBLib.parse_duration self, output: output, min_interval: min_interval
|
129
146
|
end
|
130
147
|
end
|
131
148
|
|
132
149
|
class Numeric
|
133
|
-
def to_duration
|
150
|
+
def to_duration(input: :sec, stop: :milli, style: :medium)
|
134
151
|
BBLib.to_duration self, input: input, stop: stop, style: style
|
135
152
|
end
|
153
|
+
|
154
|
+
def to_nearest_duration(*args)
|
155
|
+
BBLib.to_nearest_duration(self, *args)
|
156
|
+
end
|
136
157
|
end
|
data/lib/time/cron.rb
CHANGED
@@ -1,171 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module BBLib
|
2
|
-
|
3
3
|
class Cron
|
4
|
-
|
4
|
+
include Effortless
|
5
|
+
attr_str :expression, default: '* * * * * *'
|
6
|
+
attr_reader :parts, serialize: false
|
5
7
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
8
|
+
def next(exp = expression, count: 1, time: Time.now)
|
9
|
+
self.expression = exp unless exp == expression
|
10
|
+
closest(count: count, time: time, direction: 1)
|
9
11
|
end
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
return results unless @exp
|
15
|
-
(1..count).each{ |i| results.push next_time(i == 1 ? time : results.last, direction) }
|
16
|
-
count <= 1 ? results.first : results.reject{ |r| r.nil? }
|
13
|
+
def prev(exp = expression, count: 1, time: Time.now)
|
14
|
+
self.expression = exp unless exp == expression
|
15
|
+
closest(count: count, time: time, direction: -1)
|
17
16
|
end
|
18
17
|
|
19
|
-
def
|
20
|
-
|
18
|
+
def expression=(e)
|
19
|
+
e = e.to_s.downcase
|
20
|
+
SPECIAL_EXP.each { |x, v| e = x if v.include?(e) }
|
21
|
+
@expression = e
|
22
|
+
parse
|
23
|
+
e
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
26
|
+
def self.next(exp, count: 1, time: Time.now)
|
27
|
+
BBLib::Cron.new(exp).next(count: count, time: time)
|
25
28
|
end
|
26
29
|
|
27
|
-
def exp
|
28
|
-
|
29
|
-
@exp = e
|
30
|
-
parse
|
30
|
+
def self.prev(exp, count: 1, time: Time.now)
|
31
|
+
BBLib::Cron.new(exp).prev(count: count, time: time)
|
31
32
|
end
|
32
33
|
|
33
|
-
def self.
|
34
|
-
|
34
|
+
def self.valid?(exp)
|
35
|
+
!(numeralize(exp) =~ /\A(.*?\s){4,5}.*?\S\z/).nil?
|
35
36
|
end
|
36
37
|
|
37
|
-
def
|
38
|
-
BBLib::Cron.
|
38
|
+
def valid?(exp)
|
39
|
+
BBLib::Cron.valid?(exp)
|
39
40
|
end
|
40
41
|
|
41
|
-
def self.
|
42
|
-
|
42
|
+
def self.numeralize(exp)
|
43
|
+
REPLACE.each do |k, v|
|
44
|
+
v.each do |r|
|
45
|
+
exp = exp.to_s.gsub(r.to_s, k.to_s)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
exp
|
43
49
|
end
|
44
50
|
|
45
|
-
def
|
46
|
-
|
51
|
+
def time_match?(time)
|
52
|
+
(@parts[:minute].empty? || @parts[:minute].include?(time.min)) &&
|
53
|
+
(@parts[:hour].empty? || @parts[:hour].include?(time.hour)) &&
|
54
|
+
(@parts[:day].empty? || @parts[:day].include?(time.day)) &&
|
55
|
+
(@parts[:weekday].empty? || @parts[:weekday].include?(time.wday)) &&
|
56
|
+
(@parts[:month].empty? || @parts[:month].include?(time.month)) &&
|
57
|
+
(@parts[:year].empty? || @parts[:year].include?(time.year))
|
47
58
|
end
|
48
59
|
|
49
60
|
private
|
50
61
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
62
|
+
def simple_init(*args)
|
63
|
+
@parts = {}
|
64
|
+
self.expression = args.first if args.first.is_a?(String)
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse
|
68
|
+
@parts = {}
|
69
|
+
PARTS.keys.zip(@expression.split(' ')).to_h.each do |part, piece|
|
70
|
+
info = PARTS[part]
|
71
|
+
@parts[part] = parse_cron_numbers(piece, info[:min], info[:max], Time.now.send(info[:send]))
|
58
72
|
end
|
73
|
+
end
|
59
74
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
75
|
+
def parse_cron_numbers(exp, min, max, qmark)
|
76
|
+
numbers = []
|
77
|
+
return numbers if exp == '*'
|
78
|
+
exp = Cron.numeralize(exp).gsub('?', qmark.to_s).gsub('*', "#{min}-#{max}")
|
79
|
+
exp.scan(/\*\/\d+|\d+\/\d+|\d+-\d+\/\d+/).each do |s|
|
80
|
+
range = s.split('/').first.split('-').map(&:to_i) + [max]
|
81
|
+
divisor = s.split('/').last.to_i
|
82
|
+
Range.new(*range[0..1]).each_with_index do |i, index|
|
83
|
+
numbers.push(i) if index.zero? || (index % divisor).zero?
|
66
84
|
end
|
67
|
-
exp
|
85
|
+
exp = exp.sub(s, '')
|
68
86
|
end
|
87
|
+
exp.scan(/\d+\-\d+/).each do |e|
|
88
|
+
nums = e.scan(/\d+/).map(&:to_i)
|
89
|
+
numbers.push(Range.new(*nums).to_a)
|
90
|
+
end
|
91
|
+
numbers.push(exp.scan(/\d+/).map(&:to_i))
|
92
|
+
numbers.flatten.uniq.sort.reject { |r| r < min || r > max }
|
93
|
+
end
|
69
94
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
exp.scan(/\*\/\d+|\d+\/\d+|\d+-\d+\/\d+/).each do |s|
|
75
|
-
range, divisor = s.split('/').first, s.split('/').last.to_i
|
76
|
-
if range == '*'
|
77
|
-
range = (min..max)
|
78
|
-
elsif range =~ /\d+\-\d+/
|
79
|
-
range = (range.split('-').first.to_i..range.split('-').last.to_i)
|
80
|
-
else
|
81
|
-
range = (range.to_i..max)
|
82
|
-
end
|
83
|
-
index = 0
|
84
|
-
range.each do |i|
|
85
|
-
if index == 0 || index % divisor.to_i == 0
|
86
|
-
numbers.push i
|
87
|
-
end
|
88
|
-
index+=1
|
89
|
-
end
|
90
|
-
exp = exp.sub(s, '')
|
91
|
-
end
|
92
|
-
numbers.push exp.scan(/\d+/).map{ |m| m.to_i }
|
93
|
-
exp.strip.scan(/\d+\-\d+/).each do |e|
|
94
|
-
nums = e.scan(/\d+/).map{ |n| n.to_i }
|
95
|
-
numbers.push (nums.min..nums.max).map{ |n| n }
|
96
|
-
end
|
97
|
-
numbers.flatten.sort.uniq.reject{ |r| r < min || r > max }
|
95
|
+
def closest(direction: 1, count: 1, time: Time.now)
|
96
|
+
return unless @expression
|
97
|
+
results = (1..count).flat_map do |_i|
|
98
|
+
time = next_time(time + 60 * direction, direction)
|
98
99
|
end
|
100
|
+
count <= 1 ? results.first : results.compact
|
101
|
+
end
|
99
102
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
else
|
108
|
-
time+= 24*60*60*direction
|
109
|
-
# time = Time.new(time.year, time.month, time.day, 0, 0)
|
110
|
-
end
|
111
|
-
safety+=1
|
103
|
+
def next_time(time, direction)
|
104
|
+
original = time.dup
|
105
|
+
safety = 0
|
106
|
+
methods = [:next_year, :next_month, :next_weekday, :next_day, :next_hour, :next_min]
|
107
|
+
until safety >= 1_000_000 || time_match?(time)
|
108
|
+
methods.each do |sym|
|
109
|
+
time = send(sym, time, direction)
|
112
110
|
end
|
113
|
-
|
114
|
-
time
|
111
|
+
safety += 1
|
115
112
|
end
|
113
|
+
time - (time.sec.zero? ? 0 : original.sec)
|
114
|
+
end
|
116
115
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
116
|
+
def next_min(time, direction)
|
117
|
+
return time if @parts[:minute].empty?
|
118
|
+
time += 60 * direction until @parts[:minute].include?(time.min)
|
119
|
+
time
|
120
|
+
end
|
121
|
+
|
122
|
+
def next_hour(time, direction)
|
123
|
+
return time if @parts[:hour].empty?
|
124
|
+
until @parts[:hour].include?(time.hour)
|
125
|
+
time -= time.min * 60 if direction.positive?
|
126
|
+
time += (59 - time.min) * 60 if direction.negative?
|
127
|
+
time += 60*60 * direction
|
128
|
+
end
|
129
|
+
time
|
130
|
+
end
|
131
|
+
|
132
|
+
def next_day(time, direction)
|
133
|
+
return time if @parts[:day].empty?
|
134
|
+
time += 24*60*60 * direction until @parts[:day].include?(time.day)
|
135
|
+
time
|
136
|
+
end
|
137
|
+
|
138
|
+
def next_weekday(time, direction)
|
139
|
+
return time if @parts[:weekday].empty?
|
140
|
+
time += 24*60*60 * direction until @parts[:weekday].include?(time.wday)
|
141
|
+
time
|
142
|
+
end
|
143
|
+
|
144
|
+
def next_month(time, direction)
|
145
|
+
return time if @parts[:month].empty?
|
146
|
+
until @parts[:month].include?(time.month)
|
147
|
+
original = time.month
|
148
|
+
min = direction.positive? ? 0 : 59
|
149
|
+
hour = direction.positive? ? 0 : 23
|
150
|
+
day = direction.positive? ? 1 : 31
|
151
|
+
month = BBLib.loop_between(time.month + direction, 1, 12)
|
152
|
+
year = if direction.positive? && month == 1
|
153
|
+
time.year + 1
|
154
|
+
elsif direction.negative? && month == 12
|
155
|
+
time.year - 1
|
156
|
+
else
|
157
|
+
time.year
|
158
|
+
end
|
159
|
+
time = Time.new(year, month, day, hour, min)
|
160
|
+
if direction.negative? && time.month == original
|
161
|
+
time -= 24 * 60 * 60 while time.month == original
|
131
162
|
end
|
132
|
-
current - current.sec
|
133
163
|
end
|
164
|
+
time
|
165
|
+
end
|
134
166
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
day
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
1 => [:monday, :mon, :january, :jan],
|
147
|
-
2 => [:tuesday, :tues, :february, :feb],
|
148
|
-
3 => [:wednesday, :wednes, :tue, :march, :mar],
|
149
|
-
4 => [:thursday, :thurs, :wed, :april, :apr],
|
150
|
-
5 => [:friday, :fri, :thu, :may],
|
151
|
-
6 => [:saturday, :sat, :june, :jun],
|
152
|
-
7 => [:july, :jul],
|
153
|
-
8 => [:august, :aug],
|
154
|
-
9 => [:september, :sept, :sep],
|
155
|
-
10 => [:october, :oct],
|
156
|
-
11 => [:november, :nov],
|
157
|
-
12 => [:december, :dec]
|
158
|
-
}
|
159
|
-
|
160
|
-
SPECIAL_EXP = {
|
161
|
-
'0 0 * * * *' => ['@daily', '@midnight', 'daily', 'midnight'],
|
162
|
-
'0 12 * * * *' => ['@noon', 'noon'],
|
163
|
-
'0 0 * * 0 *' => ['@weekly', 'weekly'],
|
164
|
-
'0 0 1 * * *' => ['@monthly', 'monthly'],
|
165
|
-
'0 0 1 1 * *' => ['@yearly', '@annually', 'yearly', 'annually'],
|
166
|
-
'? ? ? ? ? ?' => ['@reboot', '@restart', 'reboot', 'restart']
|
167
|
-
}
|
167
|
+
def next_year(time, direction)
|
168
|
+
return time if @parts[:year].empty?
|
169
|
+
until @parts[:year].include?(time.year)
|
170
|
+
day = direction.positive? ? 1 : 31
|
171
|
+
hour = direction.positive? ? 0 : 23
|
172
|
+
min = direction.positive? ? 0 : 59
|
173
|
+
month = direction.positive? ? 1 : 12
|
174
|
+
time = Time.new(time.year + direction, month, day, hour, min)
|
175
|
+
end
|
176
|
+
time
|
177
|
+
end
|
168
178
|
|
179
|
+
PARTS = {
|
180
|
+
minute: { send: :min, min: 0, max: 59, size: 60 },
|
181
|
+
hour: { send: :hour, min: 0, max: 23, size: 60*60 },
|
182
|
+
day: { send: :day, min: 1, max: 31, size: 60*60*24 },
|
183
|
+
month: { send: :month, min: 1, max: 12 },
|
184
|
+
weekday: { send: :wday, min: 0, max: 6 },
|
185
|
+
year: { send: :year, min: 0, max: 3_000 }
|
186
|
+
}.freeze
|
187
|
+
|
188
|
+
REPLACE = {
|
189
|
+
0 => [:sunday, :sun],
|
190
|
+
1 => [:monday, :mon, :january, :jan],
|
191
|
+
2 => [:tuesday, :tues, :february, :feb],
|
192
|
+
3 => [:wednesday, :wednes, :tue, :march, :mar],
|
193
|
+
4 => [:thursday, :thurs, :wed, :april, :apr],
|
194
|
+
5 => [:friday, :fri, :thu, :may],
|
195
|
+
6 => [:saturday, :sat, :june, :jun],
|
196
|
+
7 => [:july, :jul],
|
197
|
+
8 => [:august, :aug],
|
198
|
+
9 => [:september, :sept, :sep],
|
199
|
+
10 => [:october, :oct],
|
200
|
+
11 => [:november, :nov],
|
201
|
+
12 => [:december, :dec]
|
202
|
+
}.freeze
|
203
|
+
|
204
|
+
SPECIAL_EXP = {
|
205
|
+
'0 0 * * * *' => ['@daily', '@midnight', 'daily', 'midnight'],
|
206
|
+
'0 12 * * * *' => ['@noon', 'noon'],
|
207
|
+
'0 0 * * 0 *' => ['@weekly', 'weekly'],
|
208
|
+
'0 0 1 * * *' => ['@monthly', 'monthly'],
|
209
|
+
'0 0 1 1 * *' => ['@yearly', '@annually', 'yearly', 'annually'],
|
210
|
+
'? ? ? ? ? ?' => ['@reboot', '@restart', 'reboot', 'restart']
|
211
|
+
}.freeze
|
169
212
|
end
|
170
|
-
|
171
213
|
end
|