tap 0.19.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|