tasking 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +94 -0
- data/bin/tasking +11 -0
- data/lib/tasking/namespace.rb +76 -0
- data/lib/tasking/task.rb +49 -0
- data/lib/tasking.rb +163 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f3d84bc5f691b364727b241743fb800bf6d571ac3885461124081e959fc6ac78
|
4
|
+
data.tar.gz: cd7411408417cd0942ba17e66ce38c3f707b4d76ca4f7171027b07bc670ce2cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d4efa63e1c58b9dac0997f1ff46ba067bcb724c052bbba2f221fde224f12e4f83983e3b56fab1e1a4c91759cfd76d5fc64f431fb0d45c671878f01ef5b3d7404
|
7
|
+
data.tar.gz: 7f25bef3273737f3655809b5af02268c88dc57bbbf1f70e67deca30cf21669584ed6161e02634839f6f0b8b23cbd59975da455fca0126341cd29e144b21ce143
|
data/README.rdoc
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
=== Running tasks from the command line
|
2
|
+
tasking <task_name>
|
3
|
+
|
4
|
+
=== The DSL
|
5
|
+
The DSL has the following commands:
|
6
|
+
* namespace
|
7
|
+
* task
|
8
|
+
* options
|
9
|
+
* before
|
10
|
+
* after
|
11
|
+
* execute
|
12
|
+
|
13
|
+
==== Namespaces
|
14
|
+
Namespaces are used to organize tasks into logical units and isolate options
|
15
|
+
from each other. Namespaces can be nested. To reference a nested namespace you
|
16
|
+
can use the format "outer_name::inner_name". To reference a namespace with it's
|
17
|
+
absolute name, prefix the name with a "::", e.g. "::outer::inner".
|
18
|
+
|
19
|
+
Namespaces can be re-opened. In that case their content will be merged with the
|
20
|
+
original content.
|
21
|
+
|
22
|
+
==== Tasks
|
23
|
+
Tasks contain commands to be executed. The execution of a task can be modified
|
24
|
+
by supplying before- and after-task chains (see the before/after sections
|
25
|
+
below).
|
26
|
+
|
27
|
+
Note that each task _must_ be contained in a namespace. Top-Level tasks will
|
28
|
+
raise an error.
|
29
|
+
|
30
|
+
Opening a task with a name that already exists within the same namespace will
|
31
|
+
overwrite the original task.
|
32
|
+
|
33
|
+
==== Before/After
|
34
|
+
The before and after commands modify the execution chain of a task, by giving a
|
35
|
+
list of other tasks that should be executed before and after the main task is
|
36
|
+
executed. Note that these filters will be run each time the main task is run.
|
37
|
+
|
38
|
+
Also, while the main task name can be given relative to the current namespace,
|
39
|
+
the tasks in the chain are always interpreted in an absolute fashion.
|
40
|
+
|
41
|
+
Referencing a task that does not exist will raise an error.
|
42
|
+
|
43
|
+
Also note that the before and after commands can only be given within the scope
|
44
|
+
of a namespace, not within a task.
|
45
|
+
|
46
|
+
==== Execute
|
47
|
+
aka run, invoke. Used to execute another task from within a task. The only
|
48
|
+
command that may be supplied within a task (and only from within a task).
|
49
|
+
|
50
|
+
The supplied task name will first be looked up in a fashion relative to the
|
51
|
+
current tasks parent namespace. If no task is found there, an absolute lookup
|
52
|
+
is performed. Will raise an error if a non-existant task is referenced.
|
53
|
+
|
54
|
+
==== Options
|
55
|
+
Supplies a hash of options that can accessed from within a task. Supplying
|
56
|
+
options within nested namespaces will result in the inner namespace merging its
|
57
|
+
set of options with the outer namespaces options.
|
58
|
+
|
59
|
+
Note that the namespace, task and execute commands can also supply an explicit
|
60
|
+
hash of options.
|
61
|
+
|
62
|
+
At the time being, explicitly invoking a task from another task does not
|
63
|
+
automatically supply the invoking tasks options to the invoked task.
|
64
|
+
|
65
|
+
Options that have a value that responds to #call (procs, lambdas, methods) will
|
66
|
+
be resolved to the return value of that #call invocation right before a task
|
67
|
+
is executed. During the resolving of the options, the original set of
|
68
|
+
(unresolved) options is passed as an argument.
|
69
|
+
|
70
|
+
Option resolution happens every time a task is executed, and the results are
|
71
|
+
not persisted or memoized. The resolution happens independently for before
|
72
|
+
and after hook tasks from the main task being executed.
|
73
|
+
|
74
|
+
For example:
|
75
|
+
|
76
|
+
namespace "outer", :foo => :bar do
|
77
|
+
options :bar => :baz
|
78
|
+
namespace "inner", :baz => :quux do
|
79
|
+
options :quux => :narf
|
80
|
+
task "some task" do
|
81
|
+
execute "my task", :bla => :blubb
|
82
|
+
end
|
83
|
+
|
84
|
+
task "my task", :narf => :bla do |options|
|
85
|
+
# options now has the contents: { :foo => :bar,
|
86
|
+
:bar => :baz,
|
87
|
+
:baz => :quux,
|
88
|
+
:quux => :narf,
|
89
|
+
:narf => :bla,
|
90
|
+
:bla => :blubb }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/bin/tasking
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Tasking
|
2
|
+
class Namespace
|
3
|
+
attr_reader :name, :options
|
4
|
+
|
5
|
+
def self.namespaces
|
6
|
+
@namespaces ||= {}
|
7
|
+
end
|
8
|
+
private_class_method :namespaces
|
9
|
+
|
10
|
+
def self.all
|
11
|
+
namespaces.values
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.add_namespace( ns )
|
15
|
+
namespaces[ns.name] = ns
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_namespace( name )
|
19
|
+
namespaces[name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find_task_in_namespace( ns_name, task_name )
|
23
|
+
ns = find_namespace( ns_name )
|
24
|
+
ns&.find_task( task_name )
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.find_task( full_name )
|
28
|
+
namespace_name, _, task_name = full_name.rpartition( '::' )
|
29
|
+
|
30
|
+
self.find_task_in_namespace( namespace_name, task_name )
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.find_or_create( name, options = {} )
|
34
|
+
find_namespace( name ) || new( name, options )
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize( name, options = {} )
|
38
|
+
@tasks = {}
|
39
|
+
@name = name
|
40
|
+
@options = options
|
41
|
+
|
42
|
+
self.class.add_namespace( self )
|
43
|
+
end
|
44
|
+
|
45
|
+
def tasks
|
46
|
+
@tasks.values
|
47
|
+
end
|
48
|
+
|
49
|
+
def parent_namespace
|
50
|
+
parent_name, _, _ = @name.rpartition( '::' )
|
51
|
+
|
52
|
+
parent_name.empty? ? nil : self.class.find_namespace( parent_name )
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute( options = {}, &block )
|
56
|
+
@options.merge!( options )
|
57
|
+
block.call if block
|
58
|
+
end
|
59
|
+
|
60
|
+
def merge_options( options )
|
61
|
+
@options.merge!( options )
|
62
|
+
end
|
63
|
+
|
64
|
+
def register_task( task )
|
65
|
+
@tasks[task.name] = task
|
66
|
+
end
|
67
|
+
|
68
|
+
def unregister_task( task )
|
69
|
+
@tasks.delete( task.name )
|
70
|
+
end
|
71
|
+
|
72
|
+
def find_task( name )
|
73
|
+
@tasks[name]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/tasking/task.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Tasking
|
2
|
+
class Task
|
3
|
+
attr_reader :name, :options, :block, :before_filters, :after_filters,
|
4
|
+
:parent_namespace
|
5
|
+
|
6
|
+
def initialize( name, parent_namespace, options = {}, &block )
|
7
|
+
@name = name
|
8
|
+
@parent_namespace = parent_namespace
|
9
|
+
@options = options
|
10
|
+
@block = block
|
11
|
+
@before_filters = []
|
12
|
+
@after_filters = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_before_filters( *filters )
|
16
|
+
@before_filters.concat( filters.flatten )
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_after_filters( *filters )
|
20
|
+
@after_filters.concat( filters.flatten )
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute( options = {} )
|
24
|
+
total_options = @options.merge( options )
|
25
|
+
execute_task_chain( before_filters, total_options, "Unknown before task '%s' for task '#{@name}'" )
|
26
|
+
@block.call( resolve_options( total_options ) ) if @block
|
27
|
+
execute_task_chain( after_filters, total_options, "Unknown after task '%s' for task '#{@name}'" )
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def resolve_options(options)
|
33
|
+
options.transform_values { |v| v.respond_to?(:call) ? v.call(options) : v }
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute_task_chain( tasks, options, fail_message )
|
37
|
+
tasks.each do |t|
|
38
|
+
task = task_lookup( t )
|
39
|
+
abort( fail_message % t ) unless task
|
40
|
+
task.execute(options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def task_lookup( name )
|
45
|
+
name.slice!( 0, 2 ) if name.start_with?( '::' )
|
46
|
+
Tasking::Namespace.find_task( name )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/tasking.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module Tasking
|
4
|
+
def task( name, options = {}, &block )
|
5
|
+
abort( "Tasks with empty names are not allowed" ) if name.to_s.empty?
|
6
|
+
|
7
|
+
full_name = fully_qualified_name( name )
|
8
|
+
namespace_name, task_name = split_task_from_namespace( full_name )
|
9
|
+
|
10
|
+
abort( "Task '#{name}' is not in a namespace" ) if namespace_name.empty?
|
11
|
+
|
12
|
+
build_namespace_hierarchy( namespace_name )
|
13
|
+
|
14
|
+
parent_namespace = Tasking::Namespace.find_namespace( namespace_name )
|
15
|
+
task = Tasking::Task.new( task_name, parent_namespace, options, &block )
|
16
|
+
parent_namespace.register_task( task )
|
17
|
+
end
|
18
|
+
|
19
|
+
def namespace( name, options = {}, &block )
|
20
|
+
abort( "Namespaces with empty names are not allowed" ) if name.to_s.empty?
|
21
|
+
@__parent_namespace ||= []
|
22
|
+
|
23
|
+
full_name = fully_qualified_name( name )
|
24
|
+
parent_namespace_names, _ = split_task_from_namespace( full_name )
|
25
|
+
build_namespace_hierarchy( parent_namespace_names )
|
26
|
+
|
27
|
+
next_namespace = Tasking::Namespace.find_or_create( full_name, options )
|
28
|
+
@__parent_namespace.push( next_namespace )
|
29
|
+
next_namespace.execute( options, &block )
|
30
|
+
@__parent_namespace.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
def options( options )
|
34
|
+
@__parent_namespace.last.merge_options( options )
|
35
|
+
end
|
36
|
+
|
37
|
+
def late_before( task_name, parent_namespace_name, *before_task_names )
|
38
|
+
task = Tasking::Namespace.find_task_in_namespace( parent_namespace_name, task_name ) ||
|
39
|
+
Tasking::Namespace.find_task( task_name )
|
40
|
+
abort( "Unknown task '#{task_name}' in before filter" ) unless task
|
41
|
+
|
42
|
+
task.add_before_filters( *before_task_names )
|
43
|
+
end
|
44
|
+
|
45
|
+
def late_after( task_name, parent_namespace_name, *after_task_names )
|
46
|
+
task = Tasking::Namespace.find_task_in_namespace( parent_namespace_name, task_name ) ||
|
47
|
+
Tasking::Namespace.find_task( task_name )
|
48
|
+
abort( "Unknown task '#{task_name}' in after filter" ) unless task
|
49
|
+
|
50
|
+
task.add_after_filters( *after_task_names )
|
51
|
+
end
|
52
|
+
|
53
|
+
def before( task_name, *before_task_names )
|
54
|
+
@__late_evaluations ||= {}
|
55
|
+
@__late_evaluations[:before] ||= []
|
56
|
+
parent_namespace_name = @__parent_namespace.last&.name.to_s
|
57
|
+
@__late_evaluations[:before] << [ task_name, parent_namespace_name, before_task_names.flatten ]
|
58
|
+
end
|
59
|
+
|
60
|
+
def after( task_name, *after_task_names )
|
61
|
+
@__late_evaluations ||= {}
|
62
|
+
@__late_evaluations[:after] ||= []
|
63
|
+
parent_namespace_name = @__parent_namespace.last&.name.to_s
|
64
|
+
@__late_evaluations[:after] << [ task_name, parent_namespace_name, after_task_names.flatten ]
|
65
|
+
end
|
66
|
+
|
67
|
+
def late_evaluations
|
68
|
+
return unless @__late_evaluations
|
69
|
+
@__late_evaluations.each_pair do |type, task_parameters|
|
70
|
+
task_parameters.each do |( task_name, parent_namespace_name, args )|
|
71
|
+
self.send( :"late_#{type}", task_name, parent_namespace_name, *args )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def execute( name, options = {} )
|
77
|
+
if !@__subsequent_executions
|
78
|
+
@__subsequent_executions = true
|
79
|
+
late_evaluations
|
80
|
+
end
|
81
|
+
task = task_lookup( name )
|
82
|
+
|
83
|
+
if !task
|
84
|
+
msg = "Unknown task '#{name}'"
|
85
|
+
msg << " or #{fully_qualified_name( name )}" if @__parent_namespace.size > 0
|
86
|
+
abort( msg )
|
87
|
+
end
|
88
|
+
|
89
|
+
namespace_hierarchy_options = gather_options_for( name, task )
|
90
|
+
namespace_hierarchy_options.merge!( options )
|
91
|
+
@__parent_namespace.push( task.parent_namespace )
|
92
|
+
task.execute( namespace_hierarchy_options )
|
93
|
+
@__parent_namespace.pop
|
94
|
+
end
|
95
|
+
alias_method :invoke, :execute
|
96
|
+
alias_method :run, :execute
|
97
|
+
|
98
|
+
private
|
99
|
+
def task_lookup( name )
|
100
|
+
@__parent_namespace ||= []
|
101
|
+
task = nil
|
102
|
+
|
103
|
+
if name.start_with?( '::' )
|
104
|
+
name.slice!( 0, 2 )
|
105
|
+
return Tasking::Namespace.find_task( name )
|
106
|
+
end
|
107
|
+
|
108
|
+
if @__parent_namespace.last
|
109
|
+
full_name = "#{@__parent_namespace.last.name}::#{name}"
|
110
|
+
task = Tasking::Namespace.find_task( full_name )
|
111
|
+
end
|
112
|
+
|
113
|
+
task || Tasking::Namespace.find_task( name )
|
114
|
+
end
|
115
|
+
|
116
|
+
def walk_namespace_tree_to( namespace_name, type = :namespace, &block )
|
117
|
+
ns_segments = namespace_name.split( '::' )
|
118
|
+
ns_segments.pop if type != :namespace
|
119
|
+
|
120
|
+
current_ns_hierarchy_level = nil
|
121
|
+
ns_segments.each do |segment|
|
122
|
+
if current_ns_hierarchy_level == nil
|
123
|
+
current_ns_hierarchy_level = segment
|
124
|
+
else
|
125
|
+
current_ns_hierarchy_level += "::#{segment}"
|
126
|
+
end
|
127
|
+
|
128
|
+
block.call( current_ns_hierarchy_level )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def gather_options_for( full_task_name, task )
|
133
|
+
final_options = {}
|
134
|
+
|
135
|
+
walk_namespace_tree_to( full_task_name, :task ) do |ns_name|
|
136
|
+
namespace = Tasking::Namespace.find_namespace( ns_name )
|
137
|
+
final_options.merge!( namespace.options )
|
138
|
+
end
|
139
|
+
|
140
|
+
final_options.merge!( task.options )
|
141
|
+
end
|
142
|
+
|
143
|
+
def fully_qualified_name( name )
|
144
|
+
@__parent_namespace&.last ?
|
145
|
+
"#{@__parent_namespace.last.name}::#{name}" :
|
146
|
+
name
|
147
|
+
end
|
148
|
+
|
149
|
+
def build_namespace_hierarchy( full_name )
|
150
|
+
walk_namespace_tree_to( full_name ) do |ns_name|
|
151
|
+
Tasking::Namespace.find_or_create( ns_name )
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def split_task_from_namespace( full_name )
|
156
|
+
namespace_name, _, task_name = full_name.rpartition( '::' )
|
157
|
+
|
158
|
+
[ namespace_name, task_name ]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
require_relative 'tasking/namespace'
|
163
|
+
require_relative 'tasking/task'
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tasking
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sven Riedel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-its
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A lightweight DSL for task definition and execution
|
56
|
+
email: sr@gimp.org
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- README.rdoc
|
62
|
+
- bin/tasking
|
63
|
+
- lib/tasking.rb
|
64
|
+
- lib/tasking/namespace.rb
|
65
|
+
- lib/tasking/task.rb
|
66
|
+
homepage: https://github.com/sriedel/tasking
|
67
|
+
licenses:
|
68
|
+
- GPL-2.0
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubygems_version: 3.0.3
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: A lightweight task runner DSL
|
89
|
+
test_files: []
|