tasking 0.2.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/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: []
|