tap 0.19.0 → 1.3.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.
- data/History +100 -45
- data/MIT-LICENSE +1 -1
- data/README +95 -51
- data/bin/tap +11 -57
- data/bin/tapexe +84 -0
- data/doc/API +91 -139
- data/doc/Configuration +93 -0
- data/doc/Examples/Command Line +10 -42
- data/doc/Examples/Tapfile +124 -0
- data/doc/Ruby to Ruby +87 -0
- data/doc/Workflow Syntax +185 -0
- data/lib/tap.rb +74 -5
- data/lib/tap/app.rb +217 -310
- data/lib/tap/app/api.rb +44 -23
- data/lib/tap/app/queue.rb +11 -12
- data/lib/tap/app/stack.rb +4 -4
- data/lib/tap/declarations.rb +200 -0
- data/lib/tap/declarations/context.rb +31 -0
- data/lib/tap/declarations/description.rb +33 -0
- data/lib/tap/env.rb +133 -779
- data/lib/tap/env/cache.rb +87 -0
- data/lib/tap/env/constant.rb +94 -39
- data/lib/tap/env/path.rb +71 -0
- data/lib/tap/join.rb +42 -78
- data/lib/tap/joins/gate.rb +85 -0
- data/lib/tap/joins/switch.rb +4 -2
- data/lib/tap/joins/sync.rb +3 -3
- data/lib/tap/middleware.rb +5 -5
- data/lib/tap/middlewares/debugger.rb +18 -58
- data/lib/tap/parser.rb +115 -183
- data/lib/tap/root.rb +162 -239
- data/lib/tap/signal.rb +72 -0
- data/lib/tap/signals.rb +20 -2
- data/lib/tap/signals/class_methods.rb +38 -43
- data/lib/tap/signals/configure.rb +19 -0
- data/lib/tap/signals/help.rb +5 -7
- data/lib/tap/signals/load.rb +49 -0
- data/lib/tap/signals/module_methods.rb +1 -0
- data/lib/tap/task.rb +46 -275
- data/lib/tap/tasks/dump.rb +21 -16
- data/lib/tap/tasks/list.rb +184 -0
- data/lib/tap/tasks/load.rb +4 -4
- data/lib/tap/tasks/prompt.rb +128 -0
- data/lib/tap/tasks/signal.rb +42 -0
- data/lib/tap/tasks/singleton.rb +35 -0
- data/lib/tap/tasks/stream.rb +64 -0
- data/lib/tap/utils.rb +83 -0
- data/lib/tap/version.rb +2 -2
- data/lib/tap/workflow.rb +124 -0
- data/tap.yml +0 -0
- metadata +59 -24
- data/cmd/console.rb +0 -43
- data/cmd/manifest.rb +0 -118
- data/cmd/run.rb +0 -145
- data/doc/Examples/Workflow +0 -40
- data/lib/tap/app/node.rb +0 -29
- data/lib/tap/env/context.rb +0 -61
- data/lib/tap/env/gems.rb +0 -63
- data/lib/tap/env/manifest.rb +0 -179
- data/lib/tap/env/minimap.rb +0 -308
- data/lib/tap/intern.rb +0 -50
- data/lib/tap/joins.rb +0 -9
- data/lib/tap/prompt.rb +0 -36
- data/lib/tap/root/utils.rb +0 -220
- data/lib/tap/root/versions.rb +0 -138
- data/lib/tap/signals/signal.rb +0 -68
data/lib/tap/tasks/dump.rb
CHANGED
@@ -2,33 +2,34 @@ require 'tap/task'
|
|
2
2
|
|
3
3
|
module Tap
|
4
4
|
module Tasks
|
5
|
-
# :startdoc::task
|
5
|
+
# :startdoc::task dump data
|
6
6
|
#
|
7
7
|
# Dumps data to $stdout or a file output.
|
8
8
|
#
|
9
|
-
# % tap
|
9
|
+
# % tap dump content --output FILEPATH
|
10
10
|
#
|
11
11
|
# Dump faciliates normal redirection:
|
12
12
|
#
|
13
|
-
# % tap
|
14
|
-
#
|
13
|
+
# % tap load 'goodnight moon' -: dump | more
|
14
|
+
# goodnight moon
|
15
15
|
#
|
16
|
-
# % tap
|
16
|
+
# % tap load 'goodnight moon' -: dump 1> results.txt
|
17
17
|
# % more results.txt
|
18
|
-
#
|
18
|
+
# goodnight moon
|
19
19
|
#
|
20
|
-
#
|
21
|
-
# at a time, so joins that produce an array (like sync) need to iterate
|
22
|
-
# outputs to dump:
|
20
|
+
# Dump converts objects to strings using to_s:
|
23
21
|
#
|
24
|
-
# % tap
|
25
|
-
#
|
26
|
-
#
|
22
|
+
# % tap load goodnight -- load moon - dump - sync 0,1 2
|
23
|
+
# ["goodnight", "moon"]
|
24
|
+
#
|
25
|
+
# % tap load goodnight -- load moon - dump - sync 0,1 2 -i
|
26
|
+
# goodnight
|
27
|
+
# moon
|
27
28
|
#
|
28
29
|
# :startdoc::task-
|
29
30
|
#
|
30
|
-
# Dump serves as a baseclass for more complicated dumps. A YAML dump
|
31
|
-
#
|
31
|
+
# Dump serves as a baseclass for more complicated dumps. A YAML dump (see
|
32
|
+
# {tap-tasks}[http://tap.rubyforge.org/tap-tasks]) looks like this:
|
32
33
|
#
|
33
34
|
# class Yaml < Tap::Tasks::Dump
|
34
35
|
# def dump(obj, io)
|
@@ -39,7 +40,11 @@ module Tap
|
|
39
40
|
class Dump < Tap::Task
|
40
41
|
config :output, $stdout, &c.io(:<<, :puts, :print) # The dump target file
|
41
42
|
config :overwrite, false, &c.flag # Overwrite the existing target
|
42
|
-
|
43
|
+
|
44
|
+
def call(input)
|
45
|
+
process(input)
|
46
|
+
end
|
47
|
+
|
43
48
|
# The default process prints dump headers as specified in the config,
|
44
49
|
# then append the audit value to io.
|
45
50
|
def process(input)
|
@@ -48,7 +53,7 @@ module Tap
|
|
48
53
|
end
|
49
54
|
input
|
50
55
|
end
|
51
|
-
|
56
|
+
|
52
57
|
# Dumps the object to io, by default dump puts (not prints) obj.to_s.
|
53
58
|
def dump(input, io)
|
54
59
|
io.puts input.to_s
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'tap/tasks/dump'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Tasks
|
5
|
+
# :startdoc::task list resources
|
6
|
+
#
|
7
|
+
# Prints a list of resources registered with the application env. Any of
|
8
|
+
# the resources may be used in a workflow. A list of filters may be used
|
9
|
+
# to limit the output; each is converted to a regexp and can match any
|
10
|
+
# part of the resource (path, class, desc).
|
11
|
+
#
|
12
|
+
# % tap list join gate
|
13
|
+
# join:
|
14
|
+
# gate # collects results before the join
|
15
|
+
#
|
16
|
+
# The configurations can be used to switch the resource description. By
|
17
|
+
# default env only lists resources registered as a task, join, or
|
18
|
+
# middleware.
|
19
|
+
#
|
20
|
+
# % tap list join gate --class --full
|
21
|
+
# join:
|
22
|
+
# /tap/joins/gate # Tap::Joins::Gate
|
23
|
+
#
|
24
|
+
class List < Dump
|
25
|
+
|
26
|
+
config :all, false, :short => :a, &c.flag # Shows all types
|
27
|
+
config :types, ['task', 'join', 'middleware'],
|
28
|
+
:long => :type,
|
29
|
+
:short => :t,
|
30
|
+
:reader => false,
|
31
|
+
&c.list(&c.string) # List types to show
|
32
|
+
|
33
|
+
config :full, false, :short => :f, &c.flag # Show full paths
|
34
|
+
config :path, false, :short => :p, &c.flag # Show require path
|
35
|
+
config :clas, false, :long => :class,
|
36
|
+
:short => :c, &c.flag # Show class
|
37
|
+
|
38
|
+
def call(input)
|
39
|
+
process manifest(*input).join("\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
def basis
|
43
|
+
app.env.constants
|
44
|
+
end
|
45
|
+
|
46
|
+
def types
|
47
|
+
return @types unless all
|
48
|
+
|
49
|
+
types = []
|
50
|
+
app.env.constants.each do |constant|
|
51
|
+
types.concat constant.types.keys
|
52
|
+
end
|
53
|
+
types.uniq!
|
54
|
+
types.sort!
|
55
|
+
types
|
56
|
+
end
|
57
|
+
|
58
|
+
def manifest(*filters)
|
59
|
+
constants = filter(basis, filters)
|
60
|
+
|
61
|
+
paths = full ? fullmap(constants) : minimap(constants)
|
62
|
+
constants = constants.sort_by {|constant| paths[constant] }
|
63
|
+
|
64
|
+
descriptions = {}
|
65
|
+
selected_paths = []
|
66
|
+
selected_types = types
|
67
|
+
|
68
|
+
selected_types.each do |type|
|
69
|
+
lines = []
|
70
|
+
constants.each do |constant|
|
71
|
+
next unless constant.types.include?(type)
|
72
|
+
|
73
|
+
path = paths[constant]
|
74
|
+
selected_paths << path
|
75
|
+
lines << [path, describe(constant, type)]
|
76
|
+
end
|
77
|
+
|
78
|
+
descriptions[type] = lines unless lines.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
format = " %-#{max_width(selected_paths)}s # %s"
|
82
|
+
|
83
|
+
lines = []
|
84
|
+
selected_types.each do |type|
|
85
|
+
next unless descriptions.has_key?(type)
|
86
|
+
|
87
|
+
lines << "#{type}:"
|
88
|
+
descriptions[type].each do |description|
|
89
|
+
lines << (format % description)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
if lines.empty?
|
94
|
+
lines << "(no constants match criteria)"
|
95
|
+
end
|
96
|
+
|
97
|
+
lines
|
98
|
+
end
|
99
|
+
|
100
|
+
def filter(constants, filters)
|
101
|
+
return constants if filters.empty?
|
102
|
+
|
103
|
+
filters.collect! {|filter| Regexp.new(filter) }
|
104
|
+
constants = constants.select do |constant|
|
105
|
+
filters.all? do |filter|
|
106
|
+
constant.path =~ filter
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def fullmap(constants)
|
112
|
+
paths = {}
|
113
|
+
constants.each {|constant| paths[constant] = constant.path }
|
114
|
+
paths
|
115
|
+
end
|
116
|
+
|
117
|
+
def minimap(constants)
|
118
|
+
paths = {}
|
119
|
+
constants.each do |constant|
|
120
|
+
paths[constant] = split(constant.path)
|
121
|
+
end
|
122
|
+
|
123
|
+
minimap = {}
|
124
|
+
queue = constants.dup
|
125
|
+
while !queue.empty?
|
126
|
+
next_queue = []
|
127
|
+
queue.each do |constant|
|
128
|
+
path = paths[constant].shift
|
129
|
+
|
130
|
+
if current = minimap[path]
|
131
|
+
next_queue << current unless current == :skip
|
132
|
+
next_queue << constant
|
133
|
+
minimap[path] = :skip
|
134
|
+
else
|
135
|
+
minimap[path] = constant
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
queue = next_queue
|
140
|
+
end
|
141
|
+
|
142
|
+
minimap.delete_if {|path, constant| constant == :skip }.invert
|
143
|
+
end
|
144
|
+
|
145
|
+
def split(path)
|
146
|
+
splits = []
|
147
|
+
current = nil
|
148
|
+
path.split('/').reverse_each do |split|
|
149
|
+
current = current ? File.join(split, current) : split
|
150
|
+
splits << current
|
151
|
+
end
|
152
|
+
splits
|
153
|
+
end
|
154
|
+
|
155
|
+
def describe(constant, type)
|
156
|
+
case
|
157
|
+
when clas
|
158
|
+
constant.const_name
|
159
|
+
|
160
|
+
when path
|
161
|
+
require_paths = constant.require_paths
|
162
|
+
require_paths = require_paths.collect do |path|
|
163
|
+
File.join(load_path(path), path)
|
164
|
+
end if full
|
165
|
+
require_paths.join(',')
|
166
|
+
|
167
|
+
else
|
168
|
+
constant.types[type]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def load_path(path)
|
173
|
+
$:.find do |load_path|
|
174
|
+
File.exists?(File.join(load_path, path))
|
175
|
+
end || '?'
|
176
|
+
end
|
177
|
+
|
178
|
+
def max_width(paths)
|
179
|
+
max = paths.collect {|path| path.length }.max
|
180
|
+
max.nil? || max < 20 ? 20 : max
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/tap/tasks/load.rb
CHANGED
@@ -3,20 +3,20 @@ require 'stringio'
|
|
3
3
|
|
4
4
|
module Tap
|
5
5
|
module Tasks
|
6
|
-
# :startdoc::task
|
6
|
+
# :startdoc::task load data
|
7
7
|
#
|
8
8
|
# Loads data from $stdin. String data may be passed directly. Load
|
9
9
|
# is typically used as a gateway to other tasks.
|
10
10
|
#
|
11
|
-
# % tap
|
11
|
+
# % tap load string -: dump
|
12
12
|
# string
|
13
13
|
#
|
14
14
|
# Load facilitates normal redirection:
|
15
15
|
#
|
16
|
-
# % echo goodnight moon | tap
|
16
|
+
# % echo goodnight moon | tap load -: dump
|
17
17
|
# goodnight moon
|
18
18
|
#
|
19
|
-
# % tap
|
19
|
+
# % tap load -: dump < somefile.txt
|
20
20
|
# contents of somefile
|
21
21
|
#
|
22
22
|
# :startdoc::task-
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'tap/tasks/stream'
|
2
|
+
require 'readline'
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
module Tasks
|
6
|
+
# :startdoc::task open a prompt
|
7
|
+
#
|
8
|
+
# Prompt reads signals from the input until a signal that returns the app
|
9
|
+
# is reached (ex run/stop) or the source io is closed.
|
10
|
+
#
|
11
|
+
# % tap prompt
|
12
|
+
# /set 0 load
|
13
|
+
# /set 1 dump
|
14
|
+
# /build join 0 1
|
15
|
+
# /enq 0 'goodnight moon'
|
16
|
+
# /run
|
17
|
+
# goodnight moon
|
18
|
+
#
|
19
|
+
# Prompts can be registered to a control signal (ex INT) so that that a
|
20
|
+
# running app may be interrupted, interrogated, or modified. This infinite
|
21
|
+
# loop can be stopped using ctl-C and a prompt.
|
22
|
+
#
|
23
|
+
# % tap dump '.' - join 0 0 -q - prompt --on INT
|
24
|
+
# .
|
25
|
+
# .
|
26
|
+
# .
|
27
|
+
# (ctl-C)
|
28
|
+
# /stop
|
29
|
+
#
|
30
|
+
class Prompt < Stream
|
31
|
+
include Tap::Utils
|
32
|
+
|
33
|
+
config :prompt, '/', &c.string_or_nil # The prompt sequence
|
34
|
+
config :terminal, $stdout, &c.io_or_nil # The terminal IO
|
35
|
+
config :variable, '', &c.string_or_nil # Assign to variable in app
|
36
|
+
config :on, nil, &c.string_or_nil # Register to a SIG
|
37
|
+
|
38
|
+
def initialize(*args)
|
39
|
+
super
|
40
|
+
trap(on) if on
|
41
|
+
end
|
42
|
+
|
43
|
+
# Traps interrupt the normal flow of the program and so I assume thread
|
44
|
+
# safety is an issue (ex if the INT occurs during an enque and a signal
|
45
|
+
# specifies another enque). A safer way to go is to enque the prompt...
|
46
|
+
# when the prompt is executed the app won't be be doing anything else so
|
47
|
+
# thread safety shouldn't be an issue.
|
48
|
+
def trap(sig)
|
49
|
+
::Signal.trap(sig) do
|
50
|
+
puts
|
51
|
+
puts "Interrupt! Signals from an interruption are not thread-safe."
|
52
|
+
|
53
|
+
call_prompt = true
|
54
|
+
3.times do
|
55
|
+
print "Wait for thread-safe break? (y/n): "
|
56
|
+
|
57
|
+
case gets.strip
|
58
|
+
when /^y(es)?$/i
|
59
|
+
puts "waiting for break..."
|
60
|
+
app.pq(self, [])
|
61
|
+
call_prompt = false
|
62
|
+
break
|
63
|
+
|
64
|
+
when /^no?$/i
|
65
|
+
break
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if call_prompt
|
70
|
+
call([])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def signal(sig)
|
76
|
+
lambda do |spec|
|
77
|
+
app.build('class' => sig, 'spec' => spec) do |obj, args|
|
78
|
+
obj.call(args)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def process(io=$stdin)
|
84
|
+
app.set(variable, self) if variable
|
85
|
+
|
86
|
+
result = super(io)
|
87
|
+
unless file || result.nil? || result == app
|
88
|
+
open_io(terminal) do |terminal|
|
89
|
+
terminal.puts result
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
app.set(variable, nil) if variable
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
def load(io)
|
98
|
+
line = readline(io)
|
99
|
+
return nil if line.empty?
|
100
|
+
|
101
|
+
begin
|
102
|
+
sig, *args = shellsplit(line)
|
103
|
+
app.call('sig' => sig, 'args' => args)
|
104
|
+
rescue
|
105
|
+
$!
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def readline(io)
|
110
|
+
if io == $stdin && terminal == $stdout
|
111
|
+
return Readline.readline(prompt, true)
|
112
|
+
end
|
113
|
+
|
114
|
+
if prompt && !file
|
115
|
+
open_io(terminal) do |terminal|
|
116
|
+
terminal.print prompt
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
io.eof? ? '' : io.readline.strip!
|
121
|
+
end
|
122
|
+
|
123
|
+
def complete?(io, result)
|
124
|
+
result == app
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'tap/task'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Tasks
|
5
|
+
# ::task signal via a task
|
6
|
+
class Signal < Tap::Task
|
7
|
+
class << self
|
8
|
+
def build(spec={}, app=Tap::App.current)
|
9
|
+
new(spec['sig'], spec['config'], app)
|
10
|
+
end
|
11
|
+
|
12
|
+
def convert_to_spec(parser, args)
|
13
|
+
if args.empty?
|
14
|
+
raise "no signal specified"
|
15
|
+
end
|
16
|
+
|
17
|
+
{
|
18
|
+
'config' => parser.nested_config,
|
19
|
+
'sig' => args.shift
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :sig
|
25
|
+
|
26
|
+
def initialize(sig, config={}, app=Tap::App.current)
|
27
|
+
super(config, app)
|
28
|
+
@sig = sig
|
29
|
+
end
|
30
|
+
|
31
|
+
def process(*args)
|
32
|
+
app.signal(sig).call(args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_spec
|
36
|
+
spec = super
|
37
|
+
spec['sig'] = sig
|
38
|
+
spec
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|