balmora 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9f7d56a33f3e8d7e5e35beeb227e3881023ad7ca
4
+ data.tar.gz: 0a4e21d622e6a0c78c0ad5956d0b07b15221525e
5
+ SHA512:
6
+ metadata.gz: 766373a8b25c96fc88762099c517f6bbd8c66d1f404c95c4ed8dbdfe2286da69664c1b694d513fd55de58536d090d810a7ae4cd5e2996085af7c366866a8a8d8
7
+ data.tar.gz: ec88c1d20741eda666acf3083a63293d2b1730915cf0f3674b80bd624d80cb30e90ad3809cdb13287186d7ffb5e56cf1f0771b3df9c377cf82143cd562cdfbac
data/bin/balmora ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'balmora/cli'
4
+
5
+ result = Balmora::Cli.new().run(ARGV.clone())
6
+ exit(result)
data/lib/balmora.rb ADDED
@@ -0,0 +1,77 @@
1
+ class Balmora
2
+
3
+ class Error < StandardError; end
4
+ class Restart < StandardError; end
5
+ class Stop < StandardError
6
+
7
+ attr_reader :status
8
+
9
+ def initialize(message, status)
10
+ super(message)
11
+ @status = status
12
+ end
13
+
14
+ end
15
+
16
+ def self.run(task, arguments = {}, options = {})
17
+ state = Balmora::State.create(options, arguments)
18
+ state.balmora.run(task, state)
19
+ end
20
+
21
+ # def self.create(options = {}, arguments = {})
22
+ # state = Balmora::State.create(options, arguments)
23
+ # return state.balmora
24
+ # end
25
+
26
+ def self.factory(state)
27
+ return self.new(state.logger, state.extension, state)
28
+ end
29
+
30
+ def initialize(logger, extension, state)
31
+ @logger = logger
32
+ @extension = extension
33
+ @state = state
34
+ end
35
+
36
+ def run(task, state)
37
+ restarts = state.config.get([:max_restarts], default: 3)
38
+
39
+ restarts.
40
+ times() { |index|
41
+ begin
42
+ dir = state.config.get(:chdir, default: Dir.pwd)
43
+ dir = state.variables.inject(dir)
44
+ Dir.chdir(dir) {
45
+ commands = state.config.get([:tasks, task.to_sym(), :commands])
46
+ run_commands(state, commands)
47
+ }
48
+ return 0
49
+ rescue Restart
50
+ @logger.debug("Restarting task (#{restarts - index} attempts left)")
51
+ state = Balmora::State.create(@state.options, @state.arguments)
52
+ end
53
+ }
54
+
55
+
56
+ raise Error.new("Maximal restart attempts count (#{restarts}) reached")
57
+ rescue Stop => stop
58
+ @logger.debug("Stop with status #{stop.status} catched")
59
+ return stop.status
60
+ end
61
+
62
+ def run_commands(state, commands)
63
+ commands.
64
+ each() { |command|
65
+ run_command(state, command)
66
+ }
67
+ end
68
+
69
+ def run_command(state, command)
70
+ @extension.
71
+ create_command(state, command).
72
+ execute()
73
+ end
74
+
75
+ end
76
+
77
+ require 'balmora/require.rb'
@@ -0,0 +1,175 @@
1
+ class Balmora::Arguments
2
+
3
+ class Error < StandardError; end
4
+
5
+ def self.parse(options, argv)
6
+ shortcuts = _shortcuts(options)
7
+
8
+ result = {}
9
+ index = 0
10
+ while index < argv.length
11
+ arg = argv[index]
12
+
13
+ if arg.start_with?('--')
14
+ new_index = _parse_key(result, options, argv, index, arg[2..-1])
15
+ elsif arg.start_with?('-')
16
+ new_index = _parse_flags(result, options, argv, index, shortcuts, arg)
17
+ else
18
+ new_index = nil
19
+ end
20
+
21
+ if new_index.nil?()
22
+ return result, argv[index..-1]
23
+ end
24
+
25
+ index = new_index + 1
26
+ end
27
+
28
+ return result, nil
29
+ end
30
+
31
+ protected
32
+
33
+ def self._shortcuts(options)
34
+ shortcuts = Hash[
35
+ options.
36
+ collect() { |key, option|
37
+ if !option.has_key?(:shortcut)
38
+ next nil
39
+ end
40
+
41
+ [option[:shortcut].to_sym(), key]
42
+ }.
43
+ compact()
44
+ ]
45
+
46
+ return shortcuts
47
+ end
48
+
49
+ def self._parse_flags(result, options, argv, index, shortcuts, arg)
50
+ new_index = nil
51
+
52
+ if arg.length > 2
53
+ arg[1..-1].split('').each() { |flag|
54
+ new_index = _parse_key(result, options, argv, index,
55
+ shortcuts[flag.to_sym()], true)
56
+
57
+ if new_index.nil?()
58
+ return nil
59
+ end
60
+ }
61
+ else
62
+ new_index = _parse_key(result, options, argv, index,
63
+ shortcuts[arg[1..-1].to_sym()])
64
+ end
65
+
66
+ return new_index
67
+ end
68
+
69
+ def self._parse_key(result, options, argv, index, key, flag_required = false)
70
+ if key.nil?()
71
+ return nil
72
+ end
73
+
74
+ key = key.to_sym()
75
+
76
+ if !options.has_key?(key)
77
+ return nil
78
+ end
79
+
80
+ option = options[key]
81
+ if option[:flag] == true
82
+ result[key] = true
83
+ else
84
+ if flag_required
85
+ return nil
86
+ end
87
+
88
+ value = argv[index + 1]
89
+ result[key] = value
90
+ index += 1
91
+ end
92
+
93
+ return index
94
+ end
95
+
96
+ public
97
+
98
+ def self.help(options)
99
+ lines = []
100
+ options.each() { |long, option|
101
+ if option.instance_of?(::String)
102
+ lines.push(option)
103
+ next
104
+ end
105
+
106
+ arg =
107
+ if option[:shortcut].nil?()
108
+ (' ' * 7) + '--' + long.to_s()
109
+ else
110
+ (' ' * 4) + '-' + option[:shortcut] + ', ' + '--' + long.to_s()
111
+ end
112
+
113
+ lines.push([arg, option[:description]])
114
+ }
115
+
116
+ return format(lines)
117
+ end
118
+
119
+ def self.format(lines)
120
+ indent =
121
+ lines.
122
+ inject(0) { |current, line|
123
+ if line.instance_of?(::String)
124
+ next current
125
+ end
126
+
127
+ if current < line[0].length
128
+ current = line[0].length
129
+ end
130
+
131
+ current
132
+ } +
133
+ 4
134
+
135
+ result = []
136
+ lines.each() { |line|
137
+ if line.instance_of?(::String)
138
+ result.push(line)
139
+ next
140
+ end
141
+
142
+ summary = _wrap(line[1] || '', 60)
143
+ result.push(line[0] + (' ' * (indent - line[0].length)) +
144
+ (summary[0] || ''))
145
+
146
+ (summary[1..-1] || []).each() { |ln| result.push(' ' * indent + ln) }
147
+
148
+ result.push('')
149
+ }
150
+
151
+ return result.join("\n").rstrip()
152
+ end
153
+
154
+ def self._wrap(text, length)
155
+ line = ''
156
+ result = []
157
+
158
+ text.split("\n").each() { |text_line|
159
+ text_line.split(' ').each() { |word|
160
+ if (line + word + ' ').length > length
161
+ result.push(line.rstrip())
162
+ line = ''
163
+ end
164
+
165
+ line += word + ' '
166
+ }
167
+
168
+ result.push(line.rstrip())
169
+ line = ''
170
+ }
171
+
172
+ return result
173
+ end
174
+
175
+ end
@@ -0,0 +1,150 @@
1
+ require 'balmora'
2
+ require 'balmora/arguments'
3
+ require 'optparse'
4
+
5
+ class Balmora::Cli
6
+
7
+ class Error < StandardError; end
8
+
9
+ attr_accessor :out, :err, :rescue
10
+
11
+ def initialize()
12
+ _create_options()
13
+
14
+ @out = STDOUT
15
+ @err = STDERR
16
+ @rescue = true
17
+
18
+ @arguments = Balmora::Arguments
19
+ end
20
+
21
+ def _create_options()
22
+ @options = {
23
+ config: {
24
+ shortcut: 'c',
25
+ description: 'Configuration file; if not specified, balmora will ' +
26
+ 'look configuration file at ~/.config/balmora/balmora.conf and ' +
27
+ '/etc/balmora/balmora.conf in order',
28
+ },
29
+
30
+ quite: {
31
+ shortcut: 'q',
32
+ flag: true,
33
+ description: 'Quite mode; reports only errors',
34
+ },
35
+
36
+ verbose: {
37
+ shortcut: 'v',
38
+ flag: true,
39
+ description: 'Verbose mode; reports all executed commands',
40
+ },
41
+
42
+ help: {
43
+ shortcut: 'h',
44
+ flag: true,
45
+ description: 'Show help; if --config specified in arguments, shows ' +
46
+ 'config help; if TASK specified, shows task help',
47
+ },
48
+ }
49
+ end
50
+
51
+ def run(argv)
52
+ args, tail = @arguments.parse(@options, argv)
53
+ state = Balmora::State.create(args, {})
54
+ if tail.nil?()
55
+ if args[:help] == true
56
+ @out.puts(help(state))
57
+ return 1
58
+ end
59
+
60
+ @err.puts('No task specified; use --help to see help')
61
+ return 1
62
+ end
63
+
64
+ if tail[0].start_with?('-')
65
+ raise Error.new("Unknown option #{tail.first()}; use --help to see " +
66
+ "options")
67
+ end
68
+
69
+
70
+ task_name, *task_argv = tail
71
+ task = state.config.get([:tasks, task_name.to_sym()], default: nil,
72
+ variables: false)
73
+
74
+ if task.nil?()
75
+ raise Error.new("Unknown task #{task_name.to_s().inspect()}; use " +
76
+ "--help to see task list")
77
+ end
78
+
79
+ if args[:help] == true
80
+ @out.puts(task_help(state, task_name, task))
81
+ return 1
82
+ end
83
+
84
+ if task.has_key?(:arguments)
85
+ task_args, task_args_tail = @arguments.parse(task[:arguments], task_argv)
86
+ if !task_args_tail.nil?()
87
+ raise Error.new("Unknown task option #{task_args_tail.first().
88
+ inspect()}; use --help #{task_name} to see task options")
89
+ end
90
+ else
91
+ task_args = {}
92
+ end
93
+
94
+ state.arguments.merge!(task_args)
95
+
96
+ STDIN.reopen("/dev/tty") # make sure that input to tty will
97
+
98
+ return state.balmora.run(task_name, state)
99
+ rescue => error
100
+ if !@rescue || true
101
+ raise error
102
+ end
103
+
104
+ @err.puts('Error: ' + error.to_s())
105
+ if args[:debug]
106
+ @err.puts(error.backtrace)
107
+ end
108
+
109
+ return 1
110
+ end
111
+
112
+ def help(state)
113
+ tasks =
114
+ state.
115
+ config.
116
+ get([:tasks], variables: false, default: []).
117
+ collect() { |key, task|
118
+ [' ' * 4 + key.to_s(), task[:description] || '']
119
+ }
120
+
121
+ help =
122
+ "Usage: balmora [common-options] task [task-options]\n\n" +
123
+ "Options\n\n" +
124
+ @arguments.help(@options) + "\n\n" +
125
+ "Tasks:\n\n" +
126
+ @arguments.format(tasks)
127
+
128
+ return help
129
+ end
130
+
131
+ def task_help(statetask_name, task)
132
+ help =
133
+ "Usage: balmora [common-optinos] #{task_name} [task-options]\n\n" +
134
+ "Description:\n\n" +
135
+ (
136
+ task[:description] &&
137
+ @arguments.format([[' ', task[:description]]]) ||
138
+ ((' ' * 4) + 'No description provided')
139
+ ) + "\n\n" +
140
+ "Arguments:\n\n" +
141
+ (
142
+ task[:arguments] &&
143
+ @arguments.help(statetask[:arguments]) ||
144
+ ((' ' * 4) + 'Task has no arguments')
145
+ )
146
+
147
+ return help
148
+ end
149
+
150
+ end
@@ -0,0 +1,106 @@
1
+ class Balmora::Command
2
+
3
+ class Error < StandardError; end
4
+
5
+ def initialize(state, command)
6
+ @state = state
7
+
8
+ @logger = state.logger
9
+ @shell = state.shell
10
+ @config = state.config
11
+ @contexts = state.contexts
12
+ @variables = state.variables
13
+ @balmora = state.balmora
14
+
15
+ @command = command
16
+ end
17
+
18
+ def init()
19
+ if (@command.keys() - [:command] - options()).length != 0
20
+ raise Error.new("Unknown options #{(@command.keys() - [:command] -
21
+ options()).inspect()}")
22
+ end
23
+
24
+ options().each() { |key|
25
+ if self.instance_variable_defined?(:"@#{key}")
26
+ raise Error.new("Can not use #{key} as option")
27
+ end
28
+
29
+ option = @command.fetch(key, nil)
30
+ self.instance_variable_set(:"@#{key}", option)
31
+ }
32
+
33
+ verify()
34
+ end
35
+
36
+ def options()
37
+ return [:ignore_error, :fallback, :extensions, :sudo, :context, :chdir,
38
+ :require]
39
+ end
40
+
41
+ def execute()
42
+ if @command.has_key?(:chdir)
43
+ dir = @command[:chdir]
44
+ dir = @variables.inject(dir)
45
+ dir = @shell.expand(dir)
46
+ Dir.chdir(dir) {
47
+ _execute()
48
+ }
49
+ else
50
+ _execute()
51
+ end
52
+ end
53
+
54
+ def _execute()
55
+ if @command.instance_of?(::Hash) && !@contexts.check(@command[:context])
56
+ @logger.debug("Skip: #{@command.inspect()}")
57
+ return
58
+ end
59
+
60
+ init()
61
+
62
+ if !@require.nil?()
63
+ option(:require).each() { |file|
64
+ require file
65
+ }
66
+ end
67
+
68
+ @logger.debug("Run: #{@command.inspect()}")
69
+
70
+ execute = self.method(:run)
71
+
72
+ if !@sudo.nil?()
73
+ execute_sudo = execute
74
+ execute = Proc.new() {
75
+ @shell.sudo!(option(:sudo)) {
76
+ execute_sudo.call()
77
+ }
78
+ }
79
+ end
80
+
81
+ execute.call()
82
+ rescue => error
83
+ @logger.error("#{error.inspect()}; failed to run " +
84
+ "command: #{@command.inspect()}")
85
+
86
+ if @fallback
87
+ @logger.debug("Executing fallback for command #{@command.inspect()}")
88
+ @balmora.execute(@fallback)
89
+ end
90
+
91
+ if option(:ignore_error) == true
92
+ return
93
+ end
94
+
95
+ raise error
96
+ end
97
+
98
+ def run()
99
+ raise Error.new("Run should be implemented in subclass")
100
+ end
101
+
102
+ def option(option)
103
+ return @variables.inject(self.instance_variable_get(:"@#{option}"))
104
+ end
105
+
106
+ end