tap 0.19.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/History +100 -45
  2. data/MIT-LICENSE +1 -1
  3. data/README +95 -51
  4. data/bin/tap +11 -57
  5. data/bin/tapexe +84 -0
  6. data/doc/API +91 -139
  7. data/doc/Configuration +93 -0
  8. data/doc/Examples/Command Line +10 -42
  9. data/doc/Examples/Tapfile +124 -0
  10. data/doc/Ruby to Ruby +87 -0
  11. data/doc/Workflow Syntax +185 -0
  12. data/lib/tap.rb +74 -5
  13. data/lib/tap/app.rb +217 -310
  14. data/lib/tap/app/api.rb +44 -23
  15. data/lib/tap/app/queue.rb +11 -12
  16. data/lib/tap/app/stack.rb +4 -4
  17. data/lib/tap/declarations.rb +200 -0
  18. data/lib/tap/declarations/context.rb +31 -0
  19. data/lib/tap/declarations/description.rb +33 -0
  20. data/lib/tap/env.rb +133 -779
  21. data/lib/tap/env/cache.rb +87 -0
  22. data/lib/tap/env/constant.rb +94 -39
  23. data/lib/tap/env/path.rb +71 -0
  24. data/lib/tap/join.rb +42 -78
  25. data/lib/tap/joins/gate.rb +85 -0
  26. data/lib/tap/joins/switch.rb +4 -2
  27. data/lib/tap/joins/sync.rb +3 -3
  28. data/lib/tap/middleware.rb +5 -5
  29. data/lib/tap/middlewares/debugger.rb +18 -58
  30. data/lib/tap/parser.rb +115 -183
  31. data/lib/tap/root.rb +162 -239
  32. data/lib/tap/signal.rb +72 -0
  33. data/lib/tap/signals.rb +20 -2
  34. data/lib/tap/signals/class_methods.rb +38 -43
  35. data/lib/tap/signals/configure.rb +19 -0
  36. data/lib/tap/signals/help.rb +5 -7
  37. data/lib/tap/signals/load.rb +49 -0
  38. data/lib/tap/signals/module_methods.rb +1 -0
  39. data/lib/tap/task.rb +46 -275
  40. data/lib/tap/tasks/dump.rb +21 -16
  41. data/lib/tap/tasks/list.rb +184 -0
  42. data/lib/tap/tasks/load.rb +4 -4
  43. data/lib/tap/tasks/prompt.rb +128 -0
  44. data/lib/tap/tasks/signal.rb +42 -0
  45. data/lib/tap/tasks/singleton.rb +35 -0
  46. data/lib/tap/tasks/stream.rb +64 -0
  47. data/lib/tap/utils.rb +83 -0
  48. data/lib/tap/version.rb +2 -2
  49. data/lib/tap/workflow.rb +124 -0
  50. data/tap.yml +0 -0
  51. metadata +59 -24
  52. data/cmd/console.rb +0 -43
  53. data/cmd/manifest.rb +0 -118
  54. data/cmd/run.rb +0 -145
  55. data/doc/Examples/Workflow +0 -40
  56. data/lib/tap/app/node.rb +0 -29
  57. data/lib/tap/env/context.rb +0 -61
  58. data/lib/tap/env/gems.rb +0 -63
  59. data/lib/tap/env/manifest.rb +0 -179
  60. data/lib/tap/env/minimap.rb +0 -308
  61. data/lib/tap/intern.rb +0 -50
  62. data/lib/tap/joins.rb +0 -9
  63. data/lib/tap/prompt.rb +0 -36
  64. data/lib/tap/root/utils.rb +0 -220
  65. data/lib/tap/root/versions.rb +0 -138
  66. data/lib/tap/signals/signal.rb +0 -68
@@ -2,33 +2,34 @@ require 'tap/task'
2
2
 
3
3
  module Tap
4
4
  module Tasks
5
- # :startdoc::task the default dump task
5
+ # :startdoc::task dump data
6
6
  #
7
7
  # Dumps data to $stdout or a file output.
8
8
  #
9
- # % tap run -- dump content --output FILEPATH
9
+ # % tap dump content --output FILEPATH
10
10
  #
11
11
  # Dump faciliates normal redirection:
12
12
  #
13
- # % tap run -- load hello --: dump | more
14
- # hello
13
+ # % tap load 'goodnight moon' -: dump | more
14
+ # goodnight moon
15
15
  #
16
- # % tap run -- load hello --: dump 1> results.txt
16
+ # % tap load 'goodnight moon' -: dump 1> results.txt
17
17
  # % more results.txt
18
- # hello
18
+ # goodnight moon
19
19
  #
20
- # Note that dumps are appended to the file. Dump only accepts one object
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 run -- load hello -- load world -- dump --[0,1][2]i.sync
25
- # hello
26
- # world
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
- # (see {tap-tasks}[http://tap.rubyforge.org/tap-tasks]) looks like this:
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
@@ -3,20 +3,20 @@ require 'stringio'
3
3
 
4
4
  module Tap
5
5
  module Tasks
6
- # :startdoc::task the default load 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 run -- load string --: dump
11
+ # % tap load string -: dump
12
12
  # string
13
13
  #
14
14
  # Load facilitates normal redirection:
15
15
  #
16
- # % echo goodnight moon | tap run -- load --: dump
16
+ # % echo goodnight moon | tap load -: dump
17
17
  # goodnight moon
18
18
  #
19
- # % tap run -- load --: dump < somefile.txt
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