wheneverd 0.2.1 → 0.4.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/FEATURE_SUMMARY.md +10 -1
  4. data/Gemfile.lock +2 -2
  5. data/README.md +70 -5
  6. data/lib/wheneverd/cli/diff.rb +98 -0
  7. data/lib/wheneverd/cli/init.rb +5 -1
  8. data/lib/wheneverd/cli/status.rb +37 -0
  9. data/lib/wheneverd/cli/validate.rb +50 -0
  10. data/lib/wheneverd/cli.rb +6 -0
  11. data/lib/wheneverd/dsl/context.rb +51 -8
  12. data/lib/wheneverd/dsl/period_parser.rb +8 -107
  13. data/lib/wheneverd/dsl/period_strategy/array_strategy.rb +29 -0
  14. data/lib/wheneverd/dsl/period_strategy/base.rb +65 -0
  15. data/lib/wheneverd/dsl/period_strategy/duration_strategy.rb +33 -0
  16. data/lib/wheneverd/dsl/period_strategy/string_strategy.rb +51 -0
  17. data/lib/wheneverd/dsl/period_strategy/symbol_strategy.rb +31 -0
  18. data/lib/wheneverd/dsl/period_strategy.rb +43 -0
  19. data/lib/wheneverd/duration.rb +1 -7
  20. data/lib/wheneverd/errors.rb +3 -0
  21. data/lib/wheneverd/interval.rb +22 -7
  22. data/lib/wheneverd/job/command.rb +88 -8
  23. data/lib/wheneverd/systemd/analyze.rb +56 -0
  24. data/lib/wheneverd/systemd/cron_parser/dow_parser.rb +208 -0
  25. data/lib/wheneverd/systemd/cron_parser/field_parser.rb +163 -0
  26. data/lib/wheneverd/systemd/cron_parser.rb +56 -303
  27. data/lib/wheneverd/systemd/errors.rb +3 -0
  28. data/lib/wheneverd/systemd/renderer.rb +6 -64
  29. data/lib/wheneverd/systemd/unit_content_builder.rb +76 -0
  30. data/lib/wheneverd/systemd/unit_deleter.rb +2 -28
  31. data/lib/wheneverd/systemd/unit_lister.rb +2 -28
  32. data/lib/wheneverd/systemd/unit_namer.rb +6 -14
  33. data/lib/wheneverd/systemd/unit_path_utils.rb +54 -0
  34. data/lib/wheneverd/systemd/unit_writer.rb +2 -28
  35. data/lib/wheneverd/trigger/base.rb +22 -0
  36. data/lib/wheneverd/trigger/boot.rb +8 -6
  37. data/lib/wheneverd/trigger/calendar.rb +7 -0
  38. data/lib/wheneverd/trigger/interval.rb +8 -6
  39. data/lib/wheneverd/validation.rb +89 -0
  40. data/lib/wheneverd/version.rb +1 -1
  41. data/lib/wheneverd.rb +5 -1
  42. data/test/cli_diff_test.rb +118 -0
  43. data/test/cli_init_test.rb +49 -0
  44. data/test/cli_status_test.rb +76 -0
  45. data/test/cli_validate_test.rb +81 -0
  46. data/test/domain_model_test.rb +106 -1
  47. data/test/dsl_context_shell_test.rb +23 -0
  48. data/test/dsl_loader_test.rb +23 -0
  49. data/test/job_command_test.rb +29 -0
  50. data/test/systemd_analyze_test.rb +55 -0
  51. data/test/systemd_cron_parser_test.rb +41 -25
  52. data/test/systemd_renderer_errors_test.rb +1 -1
  53. data/test/systemd_renderer_test.rb +6 -0
  54. metadata +25 -2
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+ require_relative "support/cli_test_helpers"
5
+
6
+ class CLIValidateTest < Minitest::Test
7
+ include CLITestHelpers
8
+
9
+ DUP_HOURLY_SCHEDULE = <<~RUBY
10
+ # frozen_string_literal: true
11
+
12
+ every :hour do
13
+ command "echo a"
14
+ end
15
+
16
+ every :hour do
17
+ command "echo b"
18
+ end
19
+ RUBY
20
+
21
+ HOURLY_SCHEDULE = <<~RUBY
22
+ # frozen_string_literal: true
23
+
24
+ every :hour do
25
+ command "echo a"
26
+ end
27
+ RUBY
28
+
29
+ def test_validate_runs_systemd_analyze_calendar_for_each_unique_on_calendar
30
+ with_project_dir do
31
+ write_schedule(DUP_HOURLY_SCHEDULE)
32
+ status, out, err, calls = run_validate("--verbose")
33
+ assert_cli_success(status, err)
34
+ assert_includes out, "OK OnCalendar=hourly"
35
+ assert_equal 1, calls.length
36
+ assert_equal %w[systemd-analyze calendar hourly], calls.fetch(0).fetch(0)
37
+ end
38
+ end
39
+
40
+ def test_validate_prints_message_when_no_on_calendar_and_verbose
41
+ with_project_dir do
42
+ write_empty_schedule
43
+ status, out, err, calls = run_validate("--verbose")
44
+ assert_cli_success(status, err)
45
+ assert_includes out, "No OnCalendar= values found"
46
+ assert_equal [], calls
47
+ end
48
+ end
49
+
50
+ def test_validate_verify_runs_systemd_analyze_verify
51
+ with_project_dir do
52
+ write_schedule(HOURLY_SCHEDULE)
53
+ status, out, err, calls = run_validate("--verify", "--verbose")
54
+ assert_cli_success(status, err)
55
+ assert_includes out, "OK systemd-analyze --user verify"
56
+ assert_equal %w[systemd-analyze --user verify], calls.fetch(1).fetch(0).take(3)
57
+ end
58
+ end
59
+
60
+ def test_validate_returns_nonzero_when_systemd_analyze_calendar_fails
61
+ with_project_dir do
62
+ write_schedule(HOURLY_SCHEDULE)
63
+ status, _out, err, calls = run_validate(exitstatus: 1, stderr: "boom\n")
64
+ assert_equal 1, status
65
+ assert_includes err, "systemd-analyze failed"
66
+ assert_includes err, "boom"
67
+ assert_equal %w[systemd-analyze calendar hourly], calls.fetch(0).fetch(0)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def run_validate(*args, **kwargs)
74
+ run_cli_with_capture3_stub(["validate", *args], **kwargs)
75
+ end
76
+
77
+ def write_schedule(contents)
78
+ FileUtils.mkdir_p("config")
79
+ File.write(File.join("config", "schedule.rb"), contents)
80
+ end
81
+ end
@@ -61,7 +61,7 @@ class DomainModelTest < Minitest::Test
61
61
  assert_raises(Wheneverd::InvalidCommandError) { Wheneverd::Job::Command.new(command: " ") }
62
62
 
63
63
  error = assert_raises(Wheneverd::InvalidCommandError) { Wheneverd::Job::Command.new(command: 123) }
64
- assert_includes error.message, "String"
64
+ assert_includes error.message, "String or an Array"
65
65
  end
66
66
 
67
67
  def test_triggers_render_timer_lines
@@ -105,4 +105,109 @@ class DomainModelTest < Minitest::Test
105
105
  def test_entry_requires_a_trigger
106
106
  assert_raises(ArgumentError) { Wheneverd::Entry.new(trigger: nil) }
107
107
  end
108
+
109
+ def test_trigger_base_module_raises_not_implemented
110
+ # Create a class that includes Base but doesn't implement the methods
111
+ test_trigger_class = Class.new do
112
+ include Wheneverd::Trigger::Base
113
+ end
114
+ trigger = test_trigger_class.new
115
+
116
+ error = assert_raises(NotImplementedError) { trigger.systemd_timer_lines }
117
+ assert_includes error.message, "must implement #systemd_timer_lines"
118
+
119
+ error = assert_raises(NotImplementedError) { trigger.signature }
120
+ assert_includes error.message, "must implement #signature"
121
+ end
122
+
123
+ def test_trigger_signatures
124
+ interval = Wheneverd::Trigger::Interval.new(seconds: 60)
125
+ assert_equal "interval:60", interval.signature
126
+
127
+ boot = Wheneverd::Trigger::Boot.new(seconds: 5)
128
+ assert_equal "boot:5", boot.signature
129
+
130
+ calendar = Wheneverd::Trigger::Calendar.new(on_calendar: %w[daily hourly])
131
+ assert_equal "calendar:daily|hourly", calendar.signature
132
+ end
133
+
134
+ def test_period_strategy_base_raises_not_implemented
135
+ # Create a class that inherits from Base but doesn't implement the methods
136
+ test_strategy_class = Class.new(Wheneverd::DSL::PeriodStrategy::Base)
137
+ strategy = test_strategy_class.new(path: "test")
138
+
139
+ error = assert_raises(NotImplementedError) { strategy.handles?(:anything) }
140
+ assert_includes error.message, "must implement #handles?"
141
+
142
+ error = assert_raises(NotImplementedError) { strategy.parse(:anything, at_times: []) }
143
+ assert_includes error.message, "must implement #parse"
144
+ end
145
+
146
+ def test_validation_type
147
+ # Valid type
148
+ assert_equal 42, Wheneverd::Validation.type(42, Integer, name: "value")
149
+
150
+ # Invalid type
151
+ error = assert_raises(ArgumentError) do
152
+ Wheneverd::Validation.type("string", Integer, name: "value")
153
+ end
154
+ assert_includes error.message, "value must be a Integer"
155
+ end
156
+
157
+ def test_validation_positive_integer
158
+ # Valid positive integer
159
+ assert_equal 5, Wheneverd::Validation.positive_integer(5, name: "count")
160
+
161
+ # Non-positive
162
+ error = assert_raises(ArgumentError) do
163
+ Wheneverd::Validation.positive_integer(0, name: "count")
164
+ end
165
+ assert_includes error.message, "must be positive"
166
+
167
+ # Non-integer
168
+ error = assert_raises(ArgumentError) do
169
+ Wheneverd::Validation.positive_integer("5", name: "count")
170
+ end
171
+ assert_includes error.message, "must be a Integer"
172
+ end
173
+
174
+ def test_validation_non_empty_string
175
+ # Valid non-empty string
176
+ assert_equal "hello", Wheneverd::Validation.non_empty_string(" hello ", name: "text")
177
+
178
+ # Empty string
179
+ error = assert_raises(ArgumentError) do
180
+ Wheneverd::Validation.non_empty_string(" ", name: "text")
181
+ end
182
+ assert_includes error.message, "must not be empty"
183
+ end
184
+
185
+ def test_validation_non_empty_array
186
+ # Valid non-empty array
187
+ arr = [1, 2, 3]
188
+ assert_equal arr, Wheneverd::Validation.non_empty_array(arr, name: "items")
189
+
190
+ # Empty array
191
+ error = assert_raises(ArgumentError) do
192
+ Wheneverd::Validation.non_empty_array([], name: "items")
193
+ end
194
+ assert_includes error.message, "must not be empty"
195
+
196
+ # Non-array
197
+ error = assert_raises(ArgumentError) do
198
+ Wheneverd::Validation.non_empty_array("not an array", name: "items")
199
+ end
200
+ assert_includes error.message, "must be a Array"
201
+ end
202
+
203
+ def test_validation_in_range
204
+ # Valid in range
205
+ assert_equal 5, Wheneverd::Validation.in_range(5, 1..10, name: "value")
206
+
207
+ # Out of range
208
+ error = assert_raises(ArgumentError) do
209
+ Wheneverd::Validation.in_range(15, 1..10, name: "value")
210
+ end
211
+ assert_includes error.message, "must be in 1..10"
212
+ end
108
213
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class DSLContextShellTest < Minitest::Test
6
+ def test_shell_requires_every_block
7
+ ctx = Wheneverd::DSL::Context.new(path: "/tmp/config/schedule.rb")
8
+ error = assert_raises(Wheneverd::DSL::LoadError) { ctx.shell("echo hi") }
9
+ assert_includes error.message, "shell() must be called inside every() block"
10
+ end
11
+
12
+ def test_shell_requires_string_script
13
+ ctx = Wheneverd::DSL::Context.new(path: "/tmp/config/schedule.rb")
14
+ error = assert_raises(Wheneverd::DSL::LoadError) { ctx.every("1m") { shell(123) } }
15
+ assert_includes error.message, "shell() script must be a String"
16
+ end
17
+
18
+ def test_shell_rejects_empty_script
19
+ ctx = Wheneverd::DSL::Context.new(path: "/tmp/config/schedule.rb")
20
+ error = assert_raises(Wheneverd::DSL::LoadError) { ctx.every("1m") { shell(" ") } }
21
+ assert_includes error.message, "shell() script must not be empty"
22
+ end
23
+ end
@@ -45,6 +45,29 @@ class DSLLoaderIntervalAndDurationTest < Minitest::Test
45
45
  assert_equal ["echo hello"], entry.jobs.map(&:command)
46
46
  end
47
47
 
48
+ def test_loads_argv_command
49
+ schedule = load_schedule(<<~RUBY)
50
+ every "5m" do
51
+ command ["echo", "hello world"]
52
+ end
53
+ RUBY
54
+
55
+ job = schedule.entries.fetch(0).jobs.fetch(0)
56
+ assert_equal ["echo", "hello world"], job.argv
57
+ assert_equal "echo \"hello world\"", job.command
58
+ end
59
+
60
+ def test_loads_shell_helper
61
+ schedule = load_schedule(<<~RUBY)
62
+ every "5m" do
63
+ shell "echo hello | sed -e s/hello/hi/"
64
+ end
65
+ RUBY
66
+
67
+ job = schedule.entries.fetch(0).jobs.fetch(0)
68
+ assert_equal ["/bin/bash", "-lc", "echo hello | sed -e s/hello/hi/"], job.argv
69
+ end
70
+
48
71
  def test_loads_duration_with_at_as_calendar
49
72
  schedule = load_schedule(<<~RUBY)
50
73
  every 1.day, at: "4:30 am" do
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class JobCommandTest < Minitest::Test
6
+ def test_accepts_argv_and_formats_execstart
7
+ command = Wheneverd::Job::Command.new(command: ["echo", "hello world"])
8
+ assert_equal ["echo", "hello world"], command.argv
9
+ assert_equal "echo \"hello world\"", command.command
10
+ end
11
+
12
+ def test_accepts_empty_argv_argument
13
+ command = Wheneverd::Job::Command.new(command: ["printf", "%s", ""])
14
+ assert_equal "printf %s \"\"", command.command
15
+ end
16
+
17
+ def test_rejects_invalid_argv
18
+ assert_raises(Wheneverd::InvalidCommandError) { Wheneverd::Job::Command.new(command: []) }
19
+ assert_raises(Wheneverd::InvalidCommandError) { Wheneverd::Job::Command.new(command: [" "]) }
20
+ assert_raises(Wheneverd::InvalidCommandError) { Wheneverd::Job::Command.new(command: ["echo", 1]) }
21
+ end
22
+
23
+ def test_rejects_argv_with_newlines
24
+ error = assert_raises(Wheneverd::InvalidCommandError) do
25
+ Wheneverd::Job::Command.new(command: %W[echo hi\nthere])
26
+ end
27
+ assert_includes error.message, "must not include NUL or newlines"
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class SystemdAnalyzeTest < Minitest::Test
6
+ def with_capture3_stub(exitstatus:, stdout: "", stderr: "")
7
+ calls = []
8
+ Thread.current[:open3_capture3_stub] = {
9
+ calls: calls,
10
+ stdout: stdout,
11
+ stderr: stderr,
12
+ exitstatus: exitstatus
13
+ }
14
+ yield calls
15
+ ensure
16
+ Thread.current[:open3_capture3_stub] = nil
17
+ end
18
+
19
+ def test_calendar_builds_command_and_returns_output
20
+ with_capture3_stub(exitstatus: 0, stdout: "ok\n", stderr: "") do |calls|
21
+ out, err = Wheneverd::Systemd::Analyze.calendar("hourly")
22
+ assert_equal "ok\n", out
23
+ assert_equal "", err
24
+ assert_equal [%w[systemd-analyze calendar hourly], {}], calls.fetch(0)
25
+ end
26
+ end
27
+
28
+ def test_verify_builds_user_verify_command
29
+ with_capture3_stub(exitstatus: 0) do |calls|
30
+ Wheneverd::Systemd::Analyze.verify(["/tmp/a.timer", "/tmp/a.service"], user: true)
31
+ assert_equal(
32
+ [["systemd-analyze", "--user", "verify", "/tmp/a.timer", "/tmp/a.service"], {}],
33
+ calls.fetch(0)
34
+ )
35
+ end
36
+ end
37
+
38
+ def test_calendar_raises_systemd_analyze_error_on_failure_with_details
39
+ with_capture3_stub(exitstatus: 1, stdout: "oops\n", stderr: "nope\n") do
40
+ error = assert_raises(Wheneverd::Systemd::SystemdAnalyzeError) do
41
+ Wheneverd::Systemd::Analyze.calendar("hourly")
42
+ end
43
+ assert_includes error.message, "status: 1"
44
+ assert_includes error.message, "stdout: oops"
45
+ assert_includes error.message, "stderr: nope"
46
+ end
47
+ end
48
+
49
+ def test_calendar_raises_systemd_analyze_error_when_missing
50
+ error = assert_raises(Wheneverd::Systemd::SystemdAnalyzeError) do
51
+ Wheneverd::Systemd::Analyze.calendar("hourly", systemd_analyze: "/no/such/systemd-analyze")
52
+ end
53
+ assert_includes error.message, "systemd-analyze not found"
54
+ end
55
+ end
@@ -38,28 +38,51 @@ class SystemdCronParserTest < Minitest::Test
38
38
  end
39
39
  end
40
40
 
41
- def test_private_helpers_reject_empty_numeric_field
42
- assert_private_unsupported(
43
- :parse_mapped_numeric_expression,
44
- "",
45
- 0..59,
46
- field: "minute",
47
- input: "x",
48
- names: {}
49
- )
41
+ def test_field_parser_rejects_empty_numeric_field
42
+ assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
43
+ Wheneverd::Systemd::CronParser::FieldParser.parse_mapped(
44
+ "",
45
+ 0..59,
46
+ field: "minute",
47
+ input: "x",
48
+ names: {}
49
+ )
50
+ end
50
51
  end
51
52
 
52
- def test_private_helpers_reject_invalid_numeric_tokens
53
- common = { field: "minute", input: "x", names: {} }
54
- assert_private_unsupported(:parse_mapped_value, " ", 0..59, **common)
55
- assert_private_unsupported(:parse_positive_int, "x", field: "minute", input: "x", label: "step")
53
+ def test_field_parser_rejects_invalid_numeric_tokens
54
+ # Empty token (just whitespace)
55
+ assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
56
+ Wheneverd::Systemd::CronParser::FieldParser.parse_mapped(
57
+ " ",
58
+ 0..59,
59
+ field: "minute",
60
+ input: "x",
61
+ names: {}
62
+ )
63
+ end
56
64
  end
57
65
 
58
- def test_private_helpers_reject_empty_dow_and_invalid_tokens
59
- assert_private_unsupported(:parse_dow_set, "", input: "x")
60
- assert_private_unsupported(:apply_dow_part, Array.new(7, false), "", input: "x")
61
- assert_private_unsupported(:parse_dow_value, "", input: "x")
62
- assert_private_unsupported(:parse_dow_value, "8", input: "x")
66
+ def test_dow_parser_rejects_empty_and_invalid_tokens
67
+ # Empty day-of-week field
68
+ assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
69
+ Wheneverd::Systemd::CronParser::DowParser.parse("", input: "x")
70
+ end
71
+
72
+ # Out of range day-of-week value (8)
73
+ assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
74
+ Wheneverd::Systemd::CronParser::DowParser.parse("8", input: "x")
75
+ end
76
+
77
+ # Invalid step (non-numeric)
78
+ assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
79
+ Wheneverd::Systemd::CronParser::DowParser.parse("*/x", input: "x")
80
+ end
81
+
82
+ # Invalid step (zero)
83
+ assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
84
+ Wheneverd::Systemd::CronParser::DowParser.parse("*/0", input: "x")
85
+ end
63
86
  end
64
87
 
65
88
  def test_rejects_unsupported_cron_patterns
@@ -97,13 +120,6 @@ class SystemdCronParserTest < Minitest::Test
97
120
  assert_equal expected, Wheneverd::Systemd::CronParser.to_on_calendar_values(cron)
98
121
  end
99
122
 
100
- def assert_private_unsupported(method, *args, **kwargs)
101
- parser = Wheneverd::Systemd::CronParser
102
- assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
103
- parser.send(method, *args, **kwargs)
104
- end
105
- end
106
-
107
123
  def assert_unsupported(*crons)
108
124
  crons.each do |cron|
109
125
  assert_raises(Wheneverd::Systemd::UnsupportedCronError) do
@@ -44,7 +44,7 @@ class SystemdRendererErrorsTest < Minitest::Test
44
44
 
45
45
  def test_timer_lines_for_rejects_unknown_trigger
46
46
  assert_raises(ArgumentError) do
47
- Wheneverd::Systemd::Renderer.send(:timer_lines_for, Object.new)
47
+ Wheneverd::Systemd::UnitContentBuilder.timer_lines_for(Object.new)
48
48
  end
49
49
  end
50
50
 
@@ -62,6 +62,12 @@ class SystemdRendererIntervalTest < Minitest::Test
62
62
  assert_includes service.contents, "Type=oneshot"
63
63
  assert_includes service.contents, "ExecStart=echo hello"
64
64
  end
65
+
66
+ def test_interval_service_contains_execstart_for_argv
67
+ entry = interval_entry(seconds: 60, command: ["echo", "hello world"])
68
+ service = service_for(entry)
69
+ assert_includes service.contents, "ExecStart=echo \"hello world\""
70
+ end
65
71
  end
66
72
 
67
73
  class SystemdRendererCalendarTest < Minitest::Test
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wheneverd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bigcurl
@@ -50,11 +50,14 @@ files:
50
50
  - lib/wheneverd/cli/current.rb
51
51
  - lib/wheneverd/cli/deactivate.rb
52
52
  - lib/wheneverd/cli/delete.rb
53
+ - lib/wheneverd/cli/diff.rb
53
54
  - lib/wheneverd/cli/help.rb
54
55
  - lib/wheneverd/cli/init.rb
55
56
  - lib/wheneverd/cli/linger.rb
56
57
  - lib/wheneverd/cli/reload.rb
57
58
  - lib/wheneverd/cli/show.rb
59
+ - lib/wheneverd/cli/status.rb
60
+ - lib/wheneverd/cli/validate.rb
58
61
  - lib/wheneverd/cli/write.rb
59
62
  - lib/wheneverd/core_ext/numeric_duration.rb
60
63
  - lib/wheneverd/dsl/at_normalizer.rb
@@ -63,41 +66,61 @@ files:
63
66
  - lib/wheneverd/dsl/errors.rb
64
67
  - lib/wheneverd/dsl/loader.rb
65
68
  - lib/wheneverd/dsl/period_parser.rb
69
+ - lib/wheneverd/dsl/period_strategy.rb
70
+ - lib/wheneverd/dsl/period_strategy/array_strategy.rb
71
+ - lib/wheneverd/dsl/period_strategy/base.rb
72
+ - lib/wheneverd/dsl/period_strategy/duration_strategy.rb
73
+ - lib/wheneverd/dsl/period_strategy/string_strategy.rb
74
+ - lib/wheneverd/dsl/period_strategy/symbol_strategy.rb
66
75
  - lib/wheneverd/duration.rb
67
76
  - lib/wheneverd/entry.rb
68
77
  - lib/wheneverd/errors.rb
69
78
  - lib/wheneverd/interval.rb
70
79
  - lib/wheneverd/job/command.rb
71
80
  - lib/wheneverd/schedule.rb
81
+ - lib/wheneverd/systemd/analyze.rb
72
82
  - lib/wheneverd/systemd/calendar_spec.rb
73
83
  - lib/wheneverd/systemd/cron_parser.rb
84
+ - lib/wheneverd/systemd/cron_parser/dow_parser.rb
85
+ - lib/wheneverd/systemd/cron_parser/field_parser.rb
74
86
  - lib/wheneverd/systemd/errors.rb
75
87
  - lib/wheneverd/systemd/loginctl.rb
76
88
  - lib/wheneverd/systemd/renderer.rb
77
89
  - lib/wheneverd/systemd/systemctl.rb
78
90
  - lib/wheneverd/systemd/time_parser.rb
91
+ - lib/wheneverd/systemd/unit_content_builder.rb
79
92
  - lib/wheneverd/systemd/unit_deleter.rb
80
93
  - lib/wheneverd/systemd/unit_lister.rb
81
94
  - lib/wheneverd/systemd/unit_namer.rb
95
+ - lib/wheneverd/systemd/unit_path_utils.rb
82
96
  - lib/wheneverd/systemd/unit_writer.rb
97
+ - lib/wheneverd/trigger/base.rb
83
98
  - lib/wheneverd/trigger/boot.rb
84
99
  - lib/wheneverd/trigger/calendar.rb
85
100
  - lib/wheneverd/trigger/interval.rb
101
+ - lib/wheneverd/validation.rb
86
102
  - lib/wheneverd/version.rb
87
103
  - test/cli_activate_test.rb
88
104
  - test/cli_current_test.rb
89
105
  - test/cli_deactivate_test.rb
106
+ - test/cli_diff_test.rb
90
107
  - test/cli_end_to_end_test.rb
108
+ - test/cli_init_test.rb
91
109
  - test/cli_linger_test.rb
92
110
  - test/cli_reload_test.rb
111
+ - test/cli_status_test.rb
93
112
  - test/cli_systemctl_integration_test.rb
94
113
  - test/cli_systemd_analyze_test.rb
95
114
  - test/cli_test.rb
115
+ - test/cli_validate_test.rb
96
116
  - test/domain_model_test.rb
97
117
  - test/dsl_calendar_symbol_period_list_test.rb
118
+ - test/dsl_context_shell_test.rb
98
119
  - test/dsl_loader_test.rb
120
+ - test/job_command_test.rb
99
121
  - test/support/cli_subprocess_test_helpers.rb
100
122
  - test/support/cli_test_helpers.rb
123
+ - test/systemd_analyze_test.rb
101
124
  - test/systemd_calendar_spec_test.rb
102
125
  - test/systemd_cron_parser_test.rb
103
126
  - test/systemd_renderer_errors_test.rb
@@ -131,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
154
  - !ruby/object:Gem::Version
132
155
  version: '0'
133
156
  requirements: []
134
- rubygems_version: 4.0.3
157
+ rubygems_version: 4.0.4
135
158
  specification_version: 4
136
159
  summary: Wheneverd is to systemd timers what whenever is to cron.
137
160
  test_files: []