do_run 0.1.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.
@@ -0,0 +1,124 @@
1
+ require_relative 'config'
2
+ require_relative 'scheduler'
3
+ require_relative 'task'
4
+ require_relative 'error'
5
+
6
+ module Do
7
+ # Stateless validation over a parsed {Config}. Returns human-readable error
8
+ # strings and never touches the system.
9
+ module Validator
10
+ module_function
11
+
12
+ # @param config [Config] parsed configuration
13
+ # @return [Array<String>] validation errors (empty when valid)
14
+ def validate(config)
15
+ errors = []
16
+ errors.concat(validate_commands(config))
17
+ errors.concat(validate_task_fields(config))
18
+ errors.concat(validate_duplicates(config))
19
+ errors.concat(validate_schedules(config))
20
+ errors.concat(validate_unit_names(config))
21
+ errors.concat(validate_directories(config))
22
+ errors.concat(validate_environment(config))
23
+ errors
24
+ end
25
+
26
+ def valid?(config)
27
+ validate(config).empty?
28
+ end
29
+
30
+ # @raise [ValidationError] when config is invalid
31
+ def validate!(config)
32
+ errors = validate(config)
33
+ raise ValidationError, errors unless errors.empty?
34
+
35
+ true
36
+ end
37
+
38
+ def validate_commands(config)
39
+ config.tasks.flat_map do |task|
40
+ if task.command.nil? || task.command.to_s.strip.empty?
41
+ ["task '#{task.name}' is missing required field 'command'"]
42
+ else
43
+ []
44
+ end
45
+ end
46
+ end
47
+
48
+ def validate_task_fields(config)
49
+ config.tasks.flat_map do |task|
50
+ unknown = task.raw.keys - Config::KNOWN_TASK_FIELDS
51
+ unknown.map { |field| "unsupported field '#{field}' for task '#{task.name}'" }
52
+ end
53
+ end
54
+
55
+ def validate_duplicates(config)
56
+ lowered = config.tasks.map { |t| t.name.downcase }
57
+ lowered
58
+ .select { |n| lowered.count(n) > 1 }.uniq
59
+ .map { |n| "duplicate task name '#{n}'" }
60
+ end
61
+
62
+ def validate_schedules(config)
63
+ config.tasks.flat_map { |task| schedule_errors(task) }
64
+ end
65
+
66
+ def validate_unit_names(config)
67
+ config.tasks.flat_map do |task|
68
+ name = task.name.to_s
69
+ if name.empty?
70
+ ['task has an empty name']
71
+ elsif name.start_with?('.') || name =~ %r{[\s/]|\.}
72
+ ["task name '#{name}' is not a valid systemd unit name " \
73
+ "(must not contain spaces, '/', dots, or start with a dot)"]
74
+ else
75
+ []
76
+ end
77
+ end
78
+ end
79
+
80
+ def validate_directories(config)
81
+ config.tasks.flat_map do |task|
82
+ wd = task.working_directory
83
+ next [] if wd.nil? || wd.empty?
84
+
85
+ if File.directory?(File.expand_path(wd))
86
+ []
87
+ else
88
+ ["working directory '#{wd}' for task '#{task.name}' does not exist"]
89
+ end
90
+ end
91
+ end
92
+
93
+ def validate_environment(config)
94
+ config.tasks.flat_map do |task|
95
+ (task.environment || {}).flat_map do |key, value|
96
+ if key.is_a?(String) && value.is_a?(String)
97
+ []
98
+ else
99
+ ["environment for task '#{task.name}' must map strings to strings"]
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ def schedule_errors(task)
106
+ sched = task.schedule
107
+ return [] if sched.nil? || sched.empty?
108
+
109
+ unless Task::SCHEDULES.include?(sched)
110
+ return ["invalid schedule for task '#{task.name}'. " \
111
+ 'Expected: once, hourly, daily, weekly, or monthly.']
112
+ end
113
+
114
+ # Driving the calendar generator lets us reuse the exact same parsing
115
+ # rules that will later be used to emit the systemd unit.
116
+ begin
117
+ Scheduler.on_calendar(task)
118
+ []
119
+ rescue Do::Error => e
120
+ ["invalid schedule for task '#{task.name}': #{e.message}"]
121
+ end
122
+ end
123
+ end
124
+ end
data/lib/do/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Do
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/lib/do.rb ADDED
@@ -0,0 +1,13 @@
1
+ require_relative 'do/version'
2
+ require_relative 'do/error'
3
+ require_relative 'do/config'
4
+ require_relative 'do/task'
5
+ require_relative 'do/scheduler'
6
+ require_relative 'do/systemd'
7
+ require_relative 'do/validator'
8
+ require_relative 'do/executor'
9
+ require_relative 'do/manager'
10
+ require_relative 'do/cli'
11
+
12
+ module Do
13
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: do_run
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - do contributors
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: toml-rb
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '4.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4.2'
40
+ description: do is a declarative execution layer for Linux. It translates a small
41
+ TOML configuration into systemd user services and timers, giving you a friendly
42
+ control plane without reinventing scheduling.
43
+ executables:
44
+ - do
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - LICENSE
50
+ - README.md
51
+ - bin/do
52
+ - lib/do.rb
53
+ - lib/do/cli.rb
54
+ - lib/do/config.rb
55
+ - lib/do/error.rb
56
+ - lib/do/executor.rb
57
+ - lib/do/manager.rb
58
+ - lib/do/scheduler.rb
59
+ - lib/do/systemd.rb
60
+ - lib/do/task.rb
61
+ - lib/do/validator.rb
62
+ - lib/do/version.rb
63
+ homepage: https://sahil.im./tools/do
64
+ licenses:
65
+ - GPL-3.0-or-later
66
+ metadata:
67
+ rubygems_mfa_required: 'true'
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '3.1'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 4.0.16
83
+ specification_version: 4
84
+ summary: Declarative Linux execution layer backed by systemd user services and timers.
85
+ test_files: []