duty 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.
- checksums.yaml +7 -0
- data/.duty.yml.sample +1 -0
- data/.gitignore +3 -0
- data/.gitmodules +0 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/Rakefile +9 -0
- data/bin/duty +17 -0
- data/bin/duty.completion +18 -0
- data/duty.gemspec +26 -0
- data/lib/duty/cli.rb +215 -0
- data/lib/duty/meta/completion.rb +41 -0
- data/lib/duty/meta/help.rb +44 -0
- data/lib/duty/meta/humanizer.rb +19 -0
- data/lib/duty/meta.rb +3 -0
- data/lib/duty/plugins.rb +50 -0
- data/lib/duty/registry.rb +16 -0
- data/lib/duty/tasks.rb +205 -0
- data/lib/duty/version.rb +3 -0
- data/lib/duty.rb +6 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b04eb166b84df79f038e4cd1f03181ff7294049
|
4
|
+
data.tar.gz: 8cec611a0d0fcbf8ae02de1ea3ed798633fed7e7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1fd3ce618dcc3c2e631715efcb138558345e7b7b6481e9655443f14c668b4de115f66983239e3cb769cd2ff73c9cec63f543c68e0c2c326a10505af9bf6ecff4
|
7
|
+
data.tar.gz: 4b7e91969a5d696d976c3d7ea0739fdbec15e16fef9c89a74f62f7e215a4a468e466419c7f58943406ccd9ba06a280f9fe7139420afc1cb1d392851af52be0d5
|
data/.duty.yml.sample
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
tasks: /path/to/my/project/specific/tasks
|
data/.gitignore
ADDED
data/.gitmodules
ADDED
File without changes
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
duty
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Jan Owiesniak
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
[](https://travis-ci.org/JanOwiesniak/duty) [](http://inch-ci.org/github/JanOwiesniak/duty)
|
2
|
+
|
3
|
+
# Duty
|
4
|
+
|
5
|
+
Craft.
|
6
|
+
Don't battle.
|
7
|
+
Do your duty, let me handle the rest.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
```
|
12
|
+
$ gem install duty
|
13
|
+
```
|
14
|
+
|
15
|
+
## Add executable to your $PATH
|
16
|
+
|
17
|
+
```
|
18
|
+
$ export PATH="$PATH:$HOME/path/to/duty/bin"
|
19
|
+
```
|
20
|
+
```
|
21
|
+
|
22
|
+
## Add shell completion
|
23
|
+
|
24
|
+
This gem supports a simple shell completion for
|
25
|
+
[Bash](https://www.gnu.org/software/bash/) and [ZSH](http://www.zsh.org).
|
26
|
+
To enable this feature load the completion functions:
|
27
|
+
|
28
|
+
```
|
29
|
+
source duty.completion
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
```
|
35
|
+
$ duty <task> [<args>]
|
36
|
+
```
|
37
|
+
|
38
|
+
## Naming conventions
|
39
|
+
|
40
|
+
Task names should be a combination of one verb joined with one or more nouns.
|
41
|
+
|
42
|
+
Examples:
|
43
|
+
|
44
|
+
* `start-feature`
|
45
|
+
* `continue-feature`
|
46
|
+
|
47
|
+
## List of official duty plugins
|
48
|
+
|
49
|
+
* [duty-git](https://github.com/JanOwiesniak/duty-git)
|
50
|
+
|
51
|
+
## Extend duty with your own tasks
|
52
|
+
|
53
|
+
* Create a new `tasks` dir
|
54
|
+
* Create one or more duty task files in there
|
55
|
+
* Create a .duty file e.g. in your home dir
|
56
|
+
|
57
|
+
### How does a basic duty task looks like?
|
58
|
+
|
59
|
+
path/to/your/new/tasks/my_new_task.rb
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'duty/tasks/base'
|
63
|
+
|
64
|
+
module Duty
|
65
|
+
module Tasks
|
66
|
+
class MyNewTask < Duty::Tasks::Base
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
### How does a .duty file looks like?
|
73
|
+
|
74
|
+
.duty
|
75
|
+
|
76
|
+
```
|
77
|
+
tasks:
|
78
|
+
git: /path/to/my/git/specific/tasks
|
79
|
+
projectA: /path/to/my/projectA/specific/tasks
|
80
|
+
projectB: /path/to/my/projectB/specific/tasks
|
81
|
+
```
|
82
|
+
|
83
|
+
### How to use my own task?
|
84
|
+
|
85
|
+
Your new task will be immediately available from the CLI.
|
86
|
+
|
87
|
+
```
|
88
|
+
duty
|
89
|
+
```
|
90
|
+
|
91
|
+
Fire up the CLI and execute your new task.
|
92
|
+
Duty will tell you what you have to do next.
|
93
|
+
|
94
|
+
```
|
95
|
+
duty <your-task>
|
96
|
+
```
|
97
|
+
|
98
|
+
## Contributing
|
99
|
+
|
100
|
+
1. [Fork](http://github.com/JanOwiesniak/duty/fork)
|
101
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
102
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
103
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
104
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/duty
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Finds the path to the 'lib' directory of Duty. Follows symlinks, which
|
4
|
+
# comes in handy.
|
5
|
+
def find_lib_path
|
6
|
+
path = __FILE__
|
7
|
+
while File.symlink?(path)
|
8
|
+
path = File.expand_path(File.readlink(path), File.dirname(path))
|
9
|
+
end
|
10
|
+
File.join(File.dirname(File.expand_path(path)), '..', 'lib')
|
11
|
+
end
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(find_lib_path)
|
14
|
+
|
15
|
+
require 'duty'
|
16
|
+
|
17
|
+
Duty::CLI.new(ARGV).exec
|
data/bin/duty.completion
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
if [ -n "$BASH_VERSION" ]; then
|
2
|
+
_duty_complete() {
|
3
|
+
COMPREPLY=()
|
4
|
+
local word=${COMP_WORDS[COMP_CWORD]}
|
5
|
+
local completions=$(duty --cmplt "${word}")
|
6
|
+
COMPREPLY=( $completions )
|
7
|
+
}
|
8
|
+
complete -F _duty_complete duty
|
9
|
+
elif [ -n "$ZSH_VERSION" ]; then
|
10
|
+
_duty_complete() {
|
11
|
+
local word completions
|
12
|
+
word="$1"
|
13
|
+
completions="$(duty --cmplt "${word}")"
|
14
|
+
reply=( "${(ps:\n:)completions}" )
|
15
|
+
}
|
16
|
+
|
17
|
+
compctl -K _duty_complete duty
|
18
|
+
fi
|
data/duty.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "duty/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "duty"
|
7
|
+
spec.version = Duty::VERSION
|
8
|
+
spec.authors = ["Jan Owiesniak"]
|
9
|
+
spec.email = ["owiesniak@mailbox.org"]
|
10
|
+
|
11
|
+
spec.summary = "Extendable Task Manager"
|
12
|
+
spec.description = "Duty provides a CLI for high-level tasks"
|
13
|
+
spec.homepage = "https://github.com/JanOwiesniak/duty"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
|
20
|
+
spec.executables = spec.files.grep(%r{^bin\/}) { |f| File.basename(f) }
|
21
|
+
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.8"
|
26
|
+
end
|
data/lib/duty/cli.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'duty/registry'
|
2
|
+
require 'duty/plugins'
|
3
|
+
require 'duty/meta'
|
4
|
+
|
5
|
+
module Duty
|
6
|
+
class CLI
|
7
|
+
DUTY_CONFIG_FILENAME = '.duty.yml'
|
8
|
+
attr_reader :registry
|
9
|
+
|
10
|
+
def initialize(args)
|
11
|
+
@input = Input.new(args)
|
12
|
+
@output = Output.new
|
13
|
+
@registry = Duty::Registry.load(plugins)
|
14
|
+
end
|
15
|
+
|
16
|
+
def exec
|
17
|
+
stdout usage if help?
|
18
|
+
stdout completion if completion?
|
19
|
+
run_task
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :input, :output
|
25
|
+
|
26
|
+
def plugins
|
27
|
+
Duty::Plugins.load(DUTY_CONFIG_FILENAME)
|
28
|
+
end
|
29
|
+
|
30
|
+
def stdout(string)
|
31
|
+
$stdout.puts string
|
32
|
+
exit 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def help?
|
36
|
+
input.help?
|
37
|
+
end
|
38
|
+
|
39
|
+
def usage
|
40
|
+
Duty::Meta::Help.new(self).to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def completion?
|
44
|
+
input.completion?
|
45
|
+
end
|
46
|
+
|
47
|
+
def completion
|
48
|
+
Duty::Meta::Completion.new(self, input.drop(1)).to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def run_task
|
52
|
+
begin
|
53
|
+
task.run
|
54
|
+
rescue NameError => e
|
55
|
+
stdout invalid_task(e.message)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def task
|
60
|
+
input.task_class.new(input.task_input, view)
|
61
|
+
end
|
62
|
+
|
63
|
+
def invalid_task(error_message)
|
64
|
+
"duty: `#{input.join(' ')}` is not a duty task. Failed with: #{error_message}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def view
|
68
|
+
if verbose?
|
69
|
+
VerboseView.new(output)
|
70
|
+
else
|
71
|
+
View.new(output)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def verbose?
|
76
|
+
input.verbose?
|
77
|
+
end
|
78
|
+
|
79
|
+
class Input
|
80
|
+
def initialize(args)
|
81
|
+
@args = [args].flatten
|
82
|
+
end
|
83
|
+
|
84
|
+
def[](index)
|
85
|
+
@args[index]
|
86
|
+
end
|
87
|
+
|
88
|
+
def drop(index)
|
89
|
+
@args.drop(1)
|
90
|
+
end
|
91
|
+
|
92
|
+
def task_name
|
93
|
+
task, *rest = @args
|
94
|
+
task
|
95
|
+
end
|
96
|
+
|
97
|
+
def task_class
|
98
|
+
name = task_name.split('-').collect(&:capitalize).join
|
99
|
+
Object.const_get("Duty::Tasks::#{name}")
|
100
|
+
end
|
101
|
+
|
102
|
+
def task_input
|
103
|
+
task, *rest = @args
|
104
|
+
rest
|
105
|
+
end
|
106
|
+
|
107
|
+
def join(seperator='')
|
108
|
+
@args.join(seperator)
|
109
|
+
end
|
110
|
+
|
111
|
+
def verbose?
|
112
|
+
@args.include?('-v') || @args.include?('--verbose')
|
113
|
+
end
|
114
|
+
|
115
|
+
def completion?
|
116
|
+
@args.first == '--cmplt'
|
117
|
+
end
|
118
|
+
|
119
|
+
def help?
|
120
|
+
@args.empty? || @args == %w(-h) || @args == %w(--help)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Output
|
125
|
+
def print(*args)
|
126
|
+
$stdout.puts(*args)
|
127
|
+
end
|
128
|
+
|
129
|
+
def error(*args)
|
130
|
+
$stderr.puts(*args)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class View
|
135
|
+
def initialize(output)
|
136
|
+
@output = output
|
137
|
+
end
|
138
|
+
|
139
|
+
def task_explain(task)
|
140
|
+
task_class = task.class
|
141
|
+
description = task_class.description
|
142
|
+
usage = task_class.usage
|
143
|
+
|
144
|
+
@output.print(description)
|
145
|
+
@output.print(usage)
|
146
|
+
end
|
147
|
+
|
148
|
+
def task_success(task)
|
149
|
+
task_name = task.class.name
|
150
|
+
success("#{task_name} task executed")
|
151
|
+
end
|
152
|
+
|
153
|
+
def task_failure(task)
|
154
|
+
task_name = task.class.name
|
155
|
+
failure("#{task_name} task aborted")
|
156
|
+
end
|
157
|
+
|
158
|
+
def command_success(command)
|
159
|
+
description = command.description
|
160
|
+
success(description)
|
161
|
+
end
|
162
|
+
|
163
|
+
def command_failure(command)
|
164
|
+
description = command.description
|
165
|
+
failure(description)
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def success(msg)
|
171
|
+
@output.print([check_mark, msg].join(' '))
|
172
|
+
end
|
173
|
+
|
174
|
+
def failure(msg)
|
175
|
+
@output.error([cross_mark, msg].join(' '))
|
176
|
+
end
|
177
|
+
|
178
|
+
def cross_mark
|
179
|
+
unicode("2715")
|
180
|
+
end
|
181
|
+
|
182
|
+
def check_mark
|
183
|
+
unicode("2713")
|
184
|
+
end
|
185
|
+
|
186
|
+
def unicode(code)
|
187
|
+
["0x#{code}".hex].pack('U')
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class VerboseView < View
|
192
|
+
def command_success(command)
|
193
|
+
success(command_msg(command))
|
194
|
+
end
|
195
|
+
|
196
|
+
def command_failure(command)
|
197
|
+
failure(command_msg(command))
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def command_msg(command)
|
203
|
+
[command.description, command_logs(command)].join(' ')
|
204
|
+
end
|
205
|
+
|
206
|
+
def command_logs(command)
|
207
|
+
elements = command.logger.flatten
|
208
|
+
|
209
|
+
if elements.any?
|
210
|
+
["|>", elements.join(' | ')].join(' ')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Duty
|
2
|
+
module Meta
|
3
|
+
class Completion
|
4
|
+
def initialize(cli, args)
|
5
|
+
@registry = cli.registry
|
6
|
+
@humanizer = Humanizer.new
|
7
|
+
@input = args.join(" ").downcase
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
possible_completions
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :registry, :humanizer
|
17
|
+
|
18
|
+
def possible_completions
|
19
|
+
matching_tasks.join("\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
def matching_tasks
|
23
|
+
humanized_tasks.select do |cmd|
|
24
|
+
cmd.start_with?(@input)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def humanized_tasks
|
29
|
+
registry.plugins.map do |plugin|
|
30
|
+
tasks_for(plugin)
|
31
|
+
end.flatten
|
32
|
+
end
|
33
|
+
|
34
|
+
def tasks_for(plugin)
|
35
|
+
plugin.tasks.map do |task_class|
|
36
|
+
humanizer.task(task_class)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Duty
|
2
|
+
module Meta
|
3
|
+
class Help
|
4
|
+
def initialize(cli)
|
5
|
+
@registry = cli.registry
|
6
|
+
@humanizer = Humanizer.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
usage
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :registry, :humanizer
|
16
|
+
|
17
|
+
def usage
|
18
|
+
msg = <<-EOF
|
19
|
+
Usage: duty <task> [<args>]
|
20
|
+
|
21
|
+
Tasks:
|
22
|
+
|
23
|
+
#{plugins}
|
24
|
+
EOF
|
25
|
+
end
|
26
|
+
|
27
|
+
def plugins
|
28
|
+
registry.plugins.map do |plugin|
|
29
|
+
[namespace_for(plugin), tasks_for(plugin)].join("\n")
|
30
|
+
end.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
def namespace_for(plugin)
|
34
|
+
"[" + plugin.namespace + "]"
|
35
|
+
end
|
36
|
+
|
37
|
+
def tasks_for(plugin)
|
38
|
+
plugin.tasks.map do |task_class|
|
39
|
+
[" • ", humanizer.task(task_class).ljust(20), task_class.description].join
|
40
|
+
end.join("\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'duty/registry'
|
2
|
+
|
3
|
+
module Duty
|
4
|
+
module Meta
|
5
|
+
class Humanizer
|
6
|
+
def task(klass)
|
7
|
+
klass.to_s.
|
8
|
+
gsub(/([A-Z])/, '-\1').
|
9
|
+
split('-').
|
10
|
+
reject(&:empty?).
|
11
|
+
map(&:downcase).
|
12
|
+
join('-').
|
13
|
+
split('::').
|
14
|
+
last.
|
15
|
+
gsub(/^-/,'')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/duty/meta.rb
ADDED
data/lib/duty/plugins.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'duty/tasks'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Duty
|
5
|
+
module Plugins
|
6
|
+
def self.load(filename)
|
7
|
+
if File.exists?(filename)
|
8
|
+
duty_config = YAML.load(File.read(filename))
|
9
|
+
tasks = duty_config["tasks"]
|
10
|
+
tasks.map do |namespace, task_dir|
|
11
|
+
Plugin.new(namespace, task_dir) if Dir.exists?(task_dir)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Plugin
|
17
|
+
TASK_NAMESPACE = Duty::Tasks
|
18
|
+
def initialize(namespace, task_dir)
|
19
|
+
@namespace = namespace
|
20
|
+
@task_dir = task_dir
|
21
|
+
end
|
22
|
+
|
23
|
+
def namespace
|
24
|
+
@namespace
|
25
|
+
end
|
26
|
+
|
27
|
+
def require_tasks
|
28
|
+
task_files = File.expand_path(File.join(@task_dir, "*.rb"))
|
29
|
+
Dir[task_files].each do |path|
|
30
|
+
require path.gsub(/(\.rb)$/, '')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def tasks
|
35
|
+
task_names = TASK_NAMESPACE.constants - [:Base]
|
36
|
+
task_names.reduce([]) do |task_classes, task_name|
|
37
|
+
task_class = TASK_NAMESPACE.const_get(task_name)
|
38
|
+
task_classes << task_class if valid?(task_class)
|
39
|
+
task_classes
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def valid?(task_class)
|
46
|
+
task_class.superclass == TASK_NAMESPACE::Base
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Duty
|
2
|
+
class Registry
|
3
|
+
def self.load(plugins = [])
|
4
|
+
new(plugins).tap {|registry| registry.require_tasks }
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :plugins
|
8
|
+
def initialize(plugins)
|
9
|
+
@plugins = plugins
|
10
|
+
end
|
11
|
+
|
12
|
+
def require_tasks
|
13
|
+
plugins.each {|plugin| plugin.require_tasks }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/duty/tasks.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
module Duty
|
2
|
+
module Tasks
|
3
|
+
class Base
|
4
|
+
ExecutionError = Class.new(RuntimeError)
|
5
|
+
|
6
|
+
def initialize(arguments, view)
|
7
|
+
@arguments = arguments
|
8
|
+
@view = view
|
9
|
+
@parallel = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
if !valid?
|
14
|
+
task_explain
|
15
|
+
else
|
16
|
+
begin
|
17
|
+
execute_in_thread
|
18
|
+
task_success
|
19
|
+
rescue ExecutionError
|
20
|
+
task_failure
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.name
|
26
|
+
self.to_s.split('::').last
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.description
|
30
|
+
"TODO: Describe your task by overwriting `self.description`"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.usage
|
34
|
+
"TODO: Explain your task by overwriting `self.usage`"
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute
|
38
|
+
ruby(Proc.new{},'Describe your task')
|
39
|
+
ruby(Proc.new{},'Explain your task')
|
40
|
+
ruby(Proc.new{ raise ExecutionError.new },'TODO: Implement your task by overwriting `execute`')
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def execute_in_thread
|
46
|
+
Thread.new do
|
47
|
+
execute
|
48
|
+
@parallel.each {|thread| thread.join }
|
49
|
+
end.join
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid?
|
53
|
+
!todo?(self.class.description) && !todo?(self.class.usage)
|
54
|
+
end
|
55
|
+
|
56
|
+
def todo?(string)
|
57
|
+
string.match(/TODO/)
|
58
|
+
end
|
59
|
+
|
60
|
+
def task_explain
|
61
|
+
@view.task_explain(self)
|
62
|
+
end
|
63
|
+
|
64
|
+
def task_success
|
65
|
+
@view.task_success(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
def task_failure
|
69
|
+
@view.task_failure(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
def command_success(command)
|
73
|
+
@view.command_success(command)
|
74
|
+
end
|
75
|
+
|
76
|
+
def command_failure(command)
|
77
|
+
@view.command_failure(command)
|
78
|
+
end
|
79
|
+
|
80
|
+
def parallel(&blk)
|
81
|
+
@parallel << Thread.new(&blk)
|
82
|
+
end
|
83
|
+
|
84
|
+
def ruby(desc = 'Unknown ruby command', &blk)
|
85
|
+
handle_errors(Ruby.run(desc, blk))
|
86
|
+
end
|
87
|
+
|
88
|
+
def sh(desc = 'Unknown shell command', &blk)
|
89
|
+
handle_errors(Shell.run(desc, blk))
|
90
|
+
end
|
91
|
+
|
92
|
+
def handle_errors(command)
|
93
|
+
if command.success?
|
94
|
+
command_success(command)
|
95
|
+
else
|
96
|
+
command_failure(command)
|
97
|
+
raise ExecutionError.new
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Command
|
102
|
+
def initialize(description, callable)
|
103
|
+
@description = description
|
104
|
+
@callable = callable
|
105
|
+
@success = false
|
106
|
+
@error = nil
|
107
|
+
@logger = []
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.run(description, callable)
|
111
|
+
new(description, callable).tap {|command| command.execute}
|
112
|
+
end
|
113
|
+
|
114
|
+
def success?
|
115
|
+
@success
|
116
|
+
end
|
117
|
+
|
118
|
+
def description
|
119
|
+
@description
|
120
|
+
end
|
121
|
+
|
122
|
+
def logger
|
123
|
+
@logger
|
124
|
+
end
|
125
|
+
|
126
|
+
def error
|
127
|
+
@error
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Ruby < Command
|
132
|
+
def execute
|
133
|
+
begin
|
134
|
+
@callable.call
|
135
|
+
@success = true
|
136
|
+
rescue Exception => e
|
137
|
+
@error = "ERROR: #{e.inspect}"
|
138
|
+
ensure
|
139
|
+
@logger << summary
|
140
|
+
@logger << error
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def summary
|
147
|
+
"[RUBY] #{@callable.inspect}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Shell < Command
|
152
|
+
require 'open3'
|
153
|
+
def execute
|
154
|
+
if !cmd
|
155
|
+
@success = true
|
156
|
+
return
|
157
|
+
end
|
158
|
+
|
159
|
+
begin
|
160
|
+
@stdout, @stderr, @status = Open3.capture3(cmd)
|
161
|
+
@success = true if status.success?
|
162
|
+
rescue Exception => e
|
163
|
+
@error = "ERROR: #{e.inspect}"
|
164
|
+
ensure
|
165
|
+
@logger << summary
|
166
|
+
@logger << error
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def summary
|
173
|
+
[
|
174
|
+
"[SHELL]",
|
175
|
+
"COMMAND: #{cmd}",
|
176
|
+
"DIR: #{dir}",
|
177
|
+
"STDOUT: #{stdout}",
|
178
|
+
"STDERR: #{stderr}",
|
179
|
+
"STATUS: #{status}"
|
180
|
+
]
|
181
|
+
end
|
182
|
+
|
183
|
+
def stdout
|
184
|
+
@stdout
|
185
|
+
end
|
186
|
+
|
187
|
+
def stderr
|
188
|
+
@stderr
|
189
|
+
end
|
190
|
+
|
191
|
+
def status
|
192
|
+
@status
|
193
|
+
end
|
194
|
+
|
195
|
+
def cmd
|
196
|
+
@cmd ||= @callable.call
|
197
|
+
end
|
198
|
+
|
199
|
+
def dir
|
200
|
+
Dir.pwd
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
data/lib/duty/version.rb
ADDED
data/lib/duty.rb
ADDED
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: duty
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jan Owiesniak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.8'
|
41
|
+
description: Duty provides a CLI for high-level tasks
|
42
|
+
email:
|
43
|
+
- owiesniak@mailbox.org
|
44
|
+
executables:
|
45
|
+
- duty
|
46
|
+
- duty.completion
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- ".duty.yml.sample"
|
51
|
+
- ".gitignore"
|
52
|
+
- ".gitmodules"
|
53
|
+
- ".ruby-gemset"
|
54
|
+
- ".ruby-version"
|
55
|
+
- ".travis.yml"
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- bin/duty
|
61
|
+
- bin/duty.completion
|
62
|
+
- duty.gemspec
|
63
|
+
- lib/duty.rb
|
64
|
+
- lib/duty/cli.rb
|
65
|
+
- lib/duty/meta.rb
|
66
|
+
- lib/duty/meta/completion.rb
|
67
|
+
- lib/duty/meta/help.rb
|
68
|
+
- lib/duty/meta/humanizer.rb
|
69
|
+
- lib/duty/plugins.rb
|
70
|
+
- lib/duty/registry.rb
|
71
|
+
- lib/duty/tasks.rb
|
72
|
+
- lib/duty/version.rb
|
73
|
+
homepage: https://github.com/JanOwiesniak/duty
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.4.8
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Extendable Task Manager
|
97
|
+
test_files: []
|