balmora 0.0.1
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 +7 -0
- data/bin/balmora +6 -0
- data/lib/balmora.rb +77 -0
- data/lib/balmora/arguments.rb +175 -0
- data/lib/balmora/cli.rb +150 -0
- data/lib/balmora/command.rb +106 -0
- data/lib/balmora/command/commands.rb +21 -0
- data/lib/balmora/command/exec.rb +24 -0
- data/lib/balmora/command/file.rb +168 -0
- data/lib/balmora/command/files.rb +122 -0
- data/lib/balmora/command/pacman.rb +71 -0
- data/lib/balmora/command/reload_config.rb +13 -0
- data/lib/balmora/command/restart.rb +13 -0
- data/lib/balmora/command/set_variable.rb +41 -0
- data/lib/balmora/command/stop.rb +17 -0
- data/lib/balmora/command/yaourt.rb +8 -0
- data/lib/balmora/config.rb +190 -0
- data/lib/balmora/context.rb +98 -0
- data/lib/balmora/context/config_changed.rb +11 -0
- data/lib/balmora/context/exec.rb +26 -0
- data/lib/balmora/context/exec_result.rb +7 -0
- data/lib/balmora/contexts.rb +76 -0
- data/lib/balmora/extension.rb +58 -0
- data/lib/balmora/extension/file_secret.rb +45 -0
- data/lib/balmora/logger.rb +25 -0
- data/lib/balmora/require.rb +25 -0
- data/lib/balmora/shell.rb +118 -0
- data/lib/balmora/state.rb +82 -0
- data/lib/balmora/variables.rb +110 -0
- data/lib/balmora/variables/config.rb +7 -0
- data/lib/balmora/variables/variables.rb +7 -0
- metadata +74 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
class Balmora::Command::Stop < Balmora::Command
|
2
|
+
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
def options()
|
6
|
+
return super().concat([:status])
|
7
|
+
end
|
8
|
+
|
9
|
+
def verify()
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
|
13
|
+
def run()
|
14
|
+
raise Balmora::Stop.new(nil, @status)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Balmora::Config
|
4
|
+
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
attr_accessor :file, :dir, :require, :variables
|
8
|
+
attr_reader :old, :config
|
9
|
+
|
10
|
+
def self.factory(state)
|
11
|
+
return self.create(state.options[:config], state.variables)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(config = nil, variables = nil)
|
15
|
+
if config.nil?()
|
16
|
+
config = File.join(Dir.home(), '.config/balmora/balmora.conf')
|
17
|
+
end
|
18
|
+
|
19
|
+
if !File.exist?(config)
|
20
|
+
config = File.join('/etc/balmora/balmora.conf')
|
21
|
+
end
|
22
|
+
|
23
|
+
if !File.exist?(config)
|
24
|
+
raise Error.new('Config not found in ~/.config/balmora/balmora.conf ' +
|
25
|
+
'and /etc/balmora/balmora.conf; you should create config to ' +
|
26
|
+
'continue or use --config switch')
|
27
|
+
end
|
28
|
+
|
29
|
+
return self.new(config || '~/.config/balmora/balmora.conf', variables)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(path, variables = nil)
|
33
|
+
@file = File
|
34
|
+
@dir = Dir
|
35
|
+
|
36
|
+
@variables = variables
|
37
|
+
@path = path
|
38
|
+
@require = Object.method(:require)
|
39
|
+
|
40
|
+
@config = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def get(key = [], options = {})
|
44
|
+
if !key.instance_of?(::Array)
|
45
|
+
key = [key]
|
46
|
+
end
|
47
|
+
|
48
|
+
result =
|
49
|
+
key.
|
50
|
+
inject(@config) { |current, part|
|
51
|
+
begin
|
52
|
+
current = current.fetch(part)
|
53
|
+
rescue KeyError
|
54
|
+
if options[:default]
|
55
|
+
return options[:default]
|
56
|
+
end
|
57
|
+
|
58
|
+
raise "Config value #{key.inspect()} should be defined in " +
|
59
|
+
"#{@path.inspect()}"
|
60
|
+
end
|
61
|
+
|
62
|
+
next current
|
63
|
+
}
|
64
|
+
|
65
|
+
result = _deep_clone(result)
|
66
|
+
if options[:variables] != false
|
67
|
+
result = @variables.inject(result, false)
|
68
|
+
end
|
69
|
+
|
70
|
+
return result
|
71
|
+
end
|
72
|
+
|
73
|
+
def load()
|
74
|
+
@old = _deep_clone(@config)
|
75
|
+
|
76
|
+
Dir.chdir(File.dirname(@path)) {
|
77
|
+
@config = _load(@path)
|
78
|
+
}
|
79
|
+
|
80
|
+
@config[:config_dir] = File.dirname(@path)
|
81
|
+
@config[:config_path] = @path
|
82
|
+
|
83
|
+
get(:require, default: [], variables: false).each() { |file|
|
84
|
+
@require.call(file)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def _load(path)
|
89
|
+
contents = @file.read(@file.expand_path(path))
|
90
|
+
value = JSON.parse(contents, {symbolize_names: true})
|
91
|
+
value = _process(value)
|
92
|
+
return value
|
93
|
+
rescue => error
|
94
|
+
raise Error.new("Failed to read config file #{path}: #{error.to_s()}")
|
95
|
+
end
|
96
|
+
|
97
|
+
def _process(value)
|
98
|
+
result =
|
99
|
+
if value.instance_of?(::Array)
|
100
|
+
_process_array(value)
|
101
|
+
elsif value.instance_of?(::Hash)
|
102
|
+
_process_hash(value)
|
103
|
+
else
|
104
|
+
value
|
105
|
+
end
|
106
|
+
|
107
|
+
return result
|
108
|
+
end
|
109
|
+
|
110
|
+
def _process_array(value)
|
111
|
+
result = []
|
112
|
+
value.each() { |item|
|
113
|
+
if item.instance_of?(::Hash) && item.keys() == [:'extend-file']
|
114
|
+
files = item[:'extend-file']
|
115
|
+
if !files.instance_of?(::Array)
|
116
|
+
files = [files]
|
117
|
+
end
|
118
|
+
|
119
|
+
files.each() { |path|
|
120
|
+
_glob(path).each() { |file|
|
121
|
+
result.concat(_load(file))
|
122
|
+
}
|
123
|
+
}
|
124
|
+
else
|
125
|
+
result.push(_process(item))
|
126
|
+
end
|
127
|
+
}
|
128
|
+
|
129
|
+
return result
|
130
|
+
end
|
131
|
+
|
132
|
+
def _process_hash(value)
|
133
|
+
result = {}
|
134
|
+
value.each() { |key, item|
|
135
|
+
if key == :'include-file'
|
136
|
+
files = item
|
137
|
+
if !files.instance_of?(::Array)
|
138
|
+
files = [files]
|
139
|
+
end
|
140
|
+
|
141
|
+
files.each() { |path|
|
142
|
+
_glob(path).each() { |file|
|
143
|
+
result.merge!(_load(file))
|
144
|
+
}
|
145
|
+
}
|
146
|
+
else
|
147
|
+
result[key] = _process(item)
|
148
|
+
end
|
149
|
+
}
|
150
|
+
|
151
|
+
return result
|
152
|
+
end
|
153
|
+
|
154
|
+
def _glob(file)
|
155
|
+
if !file.include?('*')
|
156
|
+
return [file]
|
157
|
+
end
|
158
|
+
|
159
|
+
return @dir.glob(file)
|
160
|
+
end
|
161
|
+
|
162
|
+
def _deep_clone(value)
|
163
|
+
if value.instance_of?(::Array)
|
164
|
+
value = value.collect() { |item|
|
165
|
+
_deep_clone(item)
|
166
|
+
}
|
167
|
+
elsif value.instance_of?(::Hash)
|
168
|
+
value = Hash[
|
169
|
+
value.collect() { |key, item|
|
170
|
+
[_deep_clone(key), _deep_clone(item)]
|
171
|
+
}
|
172
|
+
]
|
173
|
+
else
|
174
|
+
is_clonable = (
|
175
|
+
!value.is_a?(::Numeric) &&
|
176
|
+
!value.is_a?(::TrueClass) &&
|
177
|
+
!value.is_a?(::FalseClass) &&
|
178
|
+
!value.is_a?(::NilClass) &&
|
179
|
+
!value.is_a?(::Symbol)
|
180
|
+
)
|
181
|
+
|
182
|
+
if is_clonable
|
183
|
+
value = value.clone()
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
return value
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class Balmora::Context < Balmora::Command
|
2
|
+
|
3
|
+
def initialize(state, context)
|
4
|
+
@state = state
|
5
|
+
|
6
|
+
@logger = state.logger
|
7
|
+
@shell = state.shell
|
8
|
+
@config = state.config
|
9
|
+
@variables = state.variables
|
10
|
+
@balmora = state.balmora
|
11
|
+
|
12
|
+
@context = context
|
13
|
+
end
|
14
|
+
|
15
|
+
def init()
|
16
|
+
if (@context.keys() - [:context] - options()).length != 0
|
17
|
+
raise Error.new("Unknown options #{(@context.keys() - [:context] -
|
18
|
+
options()).inspect()}")
|
19
|
+
end
|
20
|
+
|
21
|
+
options().each() { |key|
|
22
|
+
if self.instance_variable_defined?(:"@#{key}")
|
23
|
+
raise Error.new("Can not use #{key} as option")
|
24
|
+
end
|
25
|
+
|
26
|
+
option = @context.fetch(key, nil)
|
27
|
+
self.instance_variable_set(:"@#{key}", option)
|
28
|
+
}
|
29
|
+
|
30
|
+
verify()
|
31
|
+
end
|
32
|
+
|
33
|
+
def options()
|
34
|
+
return [:operator, :value]
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify()
|
38
|
+
if @operator.nil?()
|
39
|
+
raise Error.new('"operator" should be defined')
|
40
|
+
end
|
41
|
+
|
42
|
+
if @value.nil?()
|
43
|
+
raise Error.new('"value" should be defined')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute()
|
48
|
+
result = run()
|
49
|
+
|
50
|
+
operator = @operator
|
51
|
+
|
52
|
+
is_not = false
|
53
|
+
if operator.start_with?('not-')
|
54
|
+
is_not = true
|
55
|
+
operator = operator[4..-1]
|
56
|
+
end
|
57
|
+
|
58
|
+
case operator
|
59
|
+
when 'match'
|
60
|
+
return _not(is_not, result.match(@value) != nil)
|
61
|
+
when 'equal'
|
62
|
+
return _not(is_not, result == @value)
|
63
|
+
when 'greater'
|
64
|
+
return _not(is_not, result > @value)
|
65
|
+
when 'greater-or-equal'
|
66
|
+
return _not(is_not, result >= @value)
|
67
|
+
when 'lesser'
|
68
|
+
return _not(is_not, result < @value)
|
69
|
+
when 'lesser-or-equal'
|
70
|
+
return _not(is_not, result <= @value)
|
71
|
+
end
|
72
|
+
|
73
|
+
raise Error.new("Unknown operator #{operator}")
|
74
|
+
rescue => error
|
75
|
+
@logger.error("#{error.inspect()}; failed to run " +
|
76
|
+
"context: #{@context.inspect()}")
|
77
|
+
|
78
|
+
raise error
|
79
|
+
end
|
80
|
+
|
81
|
+
def _not(is_not, result)
|
82
|
+
if is_not
|
83
|
+
return !result
|
84
|
+
end
|
85
|
+
|
86
|
+
return result
|
87
|
+
end
|
88
|
+
|
89
|
+
def run()
|
90
|
+
raise Error.new("run should be implemented in subclass")
|
91
|
+
end
|
92
|
+
|
93
|
+
def option(option)
|
94
|
+
return @variables.inject(self.instance_variable_get(:"@#{option}"))
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Balmora::Context::Exec < Balmora::Context
|
2
|
+
|
3
|
+
def options()
|
4
|
+
return super().concat([:exec])
|
5
|
+
end
|
6
|
+
|
7
|
+
def verify()
|
8
|
+
if !@exec
|
9
|
+
raise Error.new('"exec" should be defined')
|
10
|
+
end
|
11
|
+
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def run()
|
16
|
+
return @shell.run!(_exec(), verbose: false).rstrip()
|
17
|
+
end
|
18
|
+
|
19
|
+
def _exec()
|
20
|
+
exec = option(:exec)
|
21
|
+
if exec.instance_of?(::String)
|
22
|
+
exec = [@shell.expression(exec)]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class Balmora::Contexts
|
2
|
+
|
3
|
+
def self.factory(state)
|
4
|
+
return self.new(state.logger, state.extension, state.variables, state)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(logger, extension, variables, state)
|
8
|
+
@state = state
|
9
|
+
@logger = logger
|
10
|
+
@extension = extension
|
11
|
+
@variables = variables
|
12
|
+
end
|
13
|
+
|
14
|
+
def check(context)
|
15
|
+
if context.nil?()
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
|
19
|
+
if context.instance_of?(::Array)
|
20
|
+
return _check_array(context)
|
21
|
+
end
|
22
|
+
|
23
|
+
if context.instance_of?(::String)
|
24
|
+
context = {
|
25
|
+
context: 'exec-result',
|
26
|
+
exec: context,
|
27
|
+
operator: 'equal',
|
28
|
+
value: 0,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
context_class = @extension.get(Balmora::Context, context[:context])
|
34
|
+
context_instance = context_class.new(@state, context)
|
35
|
+
context_instance.init()
|
36
|
+
|
37
|
+
if !(context_class < Balmora::Context)
|
38
|
+
raise Error.new("Context #{context_class} should be subclass of context")
|
39
|
+
end
|
40
|
+
|
41
|
+
context_instance.verify()
|
42
|
+
result = context_instance.execute()
|
43
|
+
return result
|
44
|
+
end
|
45
|
+
|
46
|
+
def _check_array(contexts)
|
47
|
+
operator = :and
|
48
|
+
result = true
|
49
|
+
contexts.each() { |context|
|
50
|
+
if context == 'or'
|
51
|
+
operator = :or
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
if operator == :or
|
56
|
+
result = result || check(context)
|
57
|
+
operator = :and
|
58
|
+
else
|
59
|
+
if !result
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
result = result && check(context)
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
return result
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# [
|
73
|
+
# {"context": "test -e folder/file1; echo $?", "operator": "equal", "value": "0"},
|
74
|
+
# "or",
|
75
|
+
# {"context": "test -e folder/file2; echo $?", "operator": "equal", "value": "0"},
|
76
|
+
# ]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Balmora::Extension
|
2
|
+
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
attr_accessor :command_constant, :extension_constant
|
6
|
+
|
7
|
+
def self.factory(state)
|
8
|
+
return self.new(state)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(state)
|
12
|
+
@command_constant = ::Balmora::Command
|
13
|
+
@extension_constant = ::Balmora::Extension
|
14
|
+
@state = state
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(namespace, extension)
|
18
|
+
class_name =
|
19
|
+
extension.
|
20
|
+
to_s().
|
21
|
+
gsub(/(-|^)(\w)/) { |char | (char[1] || char[0]).upcase() }.
|
22
|
+
to_sym()
|
23
|
+
|
24
|
+
if !namespace.const_defined?(class_name, false)
|
25
|
+
raise Error.new("Extension #{class_name.inspect()} not found in " +
|
26
|
+
"namespace " + namespace.inspect())
|
27
|
+
end
|
28
|
+
|
29
|
+
return namespace.const_get(class_name, false)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_command(state, command)
|
33
|
+
if command.instance_of?(::String)
|
34
|
+
command = {command: 'exec', exec: command}
|
35
|
+
end
|
36
|
+
|
37
|
+
if command[:command].nil?()
|
38
|
+
raise Error.new("\"command\" should be defined in command " +
|
39
|
+
"#{command.inspect()}")
|
40
|
+
end
|
41
|
+
|
42
|
+
command_name = @state.variables.inject(command[:command])
|
43
|
+
command_class = get(@command_constant, command_name)
|
44
|
+
if !(command_class < @command_constant)
|
45
|
+
raise Error.new("Command should be instance of #{@command_constant}")
|
46
|
+
end
|
47
|
+
|
48
|
+
command_instance = command_class.new(state, command)
|
49
|
+
|
50
|
+
(command[:extensions] || []).each() { |extension|
|
51
|
+
extension_class = get(@extension_constant, extension)
|
52
|
+
command_instance.extend(extension_class)
|
53
|
+
}
|
54
|
+
|
55
|
+
return command_instance
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|