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
@@ -1,50 +0,0 @@
1
- module Tap
2
- # Generates an Intern module to override the specified method_name. Intern
3
- # modules are useful to override a tiny bit of functionality without having
4
- # to generate a full subclass.
5
- #
6
- # An Intern module:
7
- #
8
- # - adds an accessor for <method_name>_block
9
- # - overrides <method_name> to call the block, prepending self to
10
- # the input arguments
11
- #
12
- # For example:
13
- #
14
- # array = [1,2,3].extend Intern(:last)
15
- #
16
- # array.last # => 3
17
- # array.last_block = lambda {|arr| arr.first }
18
- # array.last # => 3
19
- #
20
- def self.Intern(method_name)
21
- mod = INTERN_MODULES[method_name.to_sym]
22
- return mod unless mod == nil
23
-
24
- mod = INTERN_MODULES[method_name.to_sym] = Module.new
25
- mod.module_eval %Q{
26
- attr_accessor :#{method_name}_block
27
-
28
- def #{method_name}(*inputs)
29
- return super unless #{method_name}_block
30
- inputs.unshift(self)
31
-
32
- arity = #{method_name}_block.arity
33
- n = inputs.length
34
- unless n == arity || (arity < 0 && (-1-n) <= arity)
35
- raise ArgumentError.new("wrong number of arguments (\#{n} for \#{arity})")
36
- end
37
-
38
- #{method_name}_block.call(*inputs)
39
- end
40
- }
41
- mod
42
- end
43
-
44
- # An array of already-declared intern modules,
45
- # keyed by method_name.
46
- INTERN_MODULES = {}
47
-
48
- # An Intern module for :process.
49
- Intern = Tap::Intern(:process)
50
- end
@@ -1,9 +0,0 @@
1
- require 'tap/join'
2
- require 'tap/joins/switch'
3
- require 'tap/joins/sync'
4
-
5
- module Tap
6
- # A module of standard Join classes supported by Tap.
7
- module Joins
8
- end
9
- end
@@ -1,36 +0,0 @@
1
- require 'tap/app/api'
2
- require 'readline'
3
-
4
- module Tap
5
-
6
- # :startdoc::prompt
7
- #
8
- # A prompt to signal a running app. Any signals that return app (ie /run
9
- # /stop /terminate) will exit the prompt.
10
- class Prompt < App::Api
11
-
12
- def call
13
- puts "starting prompt (help for help):"
14
- loop do
15
- begin
16
- line = Readline.readline('--/', true).strip
17
- next if line.empty?
18
-
19
- args = Shellwords.shellwords(line)
20
- "/#{args.shift}" =~ Tap::Parser::SIGNAL
21
-
22
- result = app.call('obj' => $1, 'sig' => $2, 'args' => args)
23
- if result == app
24
- break
25
- else
26
- puts "=> #{result}"
27
- end
28
- rescue
29
- puts $!.message
30
- puts $!.backtrace if app.debug?
31
- end
32
- end
33
- end
34
- end
35
- end
36
-
@@ -1,220 +0,0 @@
1
- require 'tap/root/versions'
2
- autoload(:FileUtils, 'fileutils')
3
-
4
- module Tap
5
- class Root
6
-
7
- # A variety of utility methods for working with paths.
8
- module Utils
9
- include Versions
10
-
11
- # Regexp to match a windows-style root path.
12
- WIN_ROOT_PATTERN = /^[A-z]:\//
13
-
14
- module_function
15
-
16
- # Returns the path of 'path' relative to dir. Both dir and path will
17
- # be expanded to dir_string, if specified, before the relative path is
18
- # determined. Returns nil if the path is not relative to dir.
19
- #
20
- # relative_path('dir', "dir/path/to/file.txt") # => "path/to/file.txt"
21
- #
22
- def relative_path(dir, path, dir_string=Dir.pwd)
23
- if dir_string
24
- dir = File.expand_path(dir, dir_string)
25
- path = File.expand_path(path, dir_string)
26
- end
27
- return nil unless Utils.relative?(dir, path, false)
28
-
29
- # use dir.length + 1 to remove a leading '/'. If dir.length + 1 >= expanded.length
30
- # as in: relative_path('/path', '/path') then the first arg returns nil, and an
31
- # empty string is returned
32
- path[(dir.chomp("/").length + 1)..-1] || ""
33
- end
34
-
35
- # Generates a target path translated from the source_dir to the
36
- # target_dir. Raises an error if the path is not relative to the
37
- # source_dir.
38
- #
39
- # translate("/path/to/file.txt", "/path", "/another/path") # => '/another/path/to/file.txt'
40
- #
41
- def translate(path, source_dir, target_dir)
42
- unless relative_path = relative_path(source_dir, path)
43
- raise ArgumentError, "\n#{path}\nis not relative to:\n#{source_dir}"
44
- end
45
- File.join(target_dir, relative_path)
46
- end
47
-
48
- # Returns the path, exchanging the extension with extname. Extname may
49
- # optionally omit the leading period.
50
- #
51
- # exchange('path/to/file.txt', '.html') # => 'path/to/file.html'
52
- # exchange('path/to/file.txt', 'rb') # => 'path/to/file.rb'
53
- #
54
- def exchange(path, extname)
55
- "#{path.chomp(File.extname(path))}#{extname[0] == ?. ? '' : '.'}#{extname}"
56
- end
57
-
58
- # Lists all unique paths matching the input glob patterns.
59
- def glob(*patterns)
60
- Dir[*patterns].uniq
61
- end
62
-
63
- # Lists all unique versions of path matching the glob version patterns. If
64
- # no patterns are specified, then all versions of path will be returned.
65
- def version_glob(path, *vpatterns)
66
- paths = []
67
-
68
- vpatterns << "*" if vpatterns.empty?
69
- vpatterns.each do |vpattern|
70
- paths.concat Dir.glob(version(path, vpattern))
71
-
72
- # extra work to include the default version path for any version
73
- paths << path if vpattern == "*" && File.exists?(path)
74
- end
75
-
76
- paths.uniq
77
- end
78
-
79
- # Path suffix glob. Globs along the paths for the specified suffix
80
- # pattern.
81
- def suffix_glob(suffix_pattern, *paths)
82
- paths.collect! {|path| File.join(path, suffix_pattern) }
83
- Dir[*paths].uniq
84
- end
85
-
86
- # Like Dir.chdir but makes the directory, if necessary, when mkdir is
87
- # specified. chdir raises an error for non-existant directories, as well
88
- # as non-directory inputs.
89
- def chdir(dir, mkdir=false, &block)
90
- dir = File.expand_path(dir)
91
-
92
- unless File.directory?(dir)
93
- if !File.exists?(dir) && mkdir
94
- FileUtils.mkdir_p(dir)
95
- else
96
- raise ArgumentError, "not a directory: #{dir}"
97
- end
98
- end
99
-
100
- Dir.chdir(dir, &block)
101
- end
102
-
103
- # Prepares the input path by making the parent directory for path. If a
104
- # block is given, a file is created at path and passed to it; in this
105
- # way files with non-existant parent directories are readily made.
106
- #
107
- # Returns path.
108
- def prepare(path)
109
- dirname = File.dirname(path)
110
- FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
111
- File.open(path, "w") {|io| yield(io) } if block_given?
112
- path
113
- end
114
-
115
- # The path root type indicating windows, *nix, or some unknown style of
116
- # paths (:win, :nix, :unknown).
117
- def path_root_type
118
- @path_root_type ||= case
119
- when RUBY_PLATFORM =~ /mswin/ && File.expand_path(".") =~ WIN_ROOT_PATTERN then :win
120
- when File.expand_path(".")[0] == ?/ then :nix
121
- else :unknown
122
- end
123
- end
124
-
125
- # Returns true if the input path appears to be an expanded path, based on
126
- # path_root_type.
127
- #
128
- # If root_type == :win returns true if the path matches WIN_ROOT_PATTERN.
129
- #
130
- # expanded?('C:/path') # => true
131
- # expanded?('c:/path') # => true
132
- # expanded?('D:/path') # => true
133
- # expanded?('path') # => false
134
- #
135
- # If root_type == :nix, then expanded? returns true if the path begins
136
- # with '/'.
137
- #
138
- # expanded?('/path') # => true
139
- # expanded?('path') # => false
140
- #
141
- # Otherwise expanded? always returns nil.
142
- def expanded?(path, root_type=path_root_type)
143
- case root_type
144
- when :win
145
- path =~ WIN_ROOT_PATTERN ? true : false
146
- when :nix
147
- path[0] == ?/
148
- else
149
- nil
150
- end
151
- end
152
-
153
- # Returns true if path is relative to dir. Both path and dir will be
154
- # expanded relative to dir_string, if specified.
155
- def relative?(dir, path, dir_string=Dir.pwd)
156
- if dir_string
157
- dir = File.expand_path(dir, dir_string)
158
- path = File.expand_path(path, dir_string)
159
- end
160
-
161
- path.rindex(dir, 0) == 0
162
- end
163
-
164
- # Trivial indicates when a path does not have content to load. Returns
165
- # true if the file at path is empty, non-existant, a directory, or nil.
166
- def trivial?(path)
167
- path == nil || !File.file?(path) || File.size(path) == 0
168
- end
169
-
170
- # Empty returns true when dir is an existing directory that has no files.
171
- def empty?(dir)
172
- File.directory?(dir) && (Dir.entries(dir) - ['.', '..']).empty?
173
- end
174
-
175
- # Returns the path segments for the given path, splitting along the path
176
- # divider. Env paths are always represented by a string, if only an
177
- # empty string.
178
- #
179
- # os divider example
180
- # windows '\' split('C:\path\to\file') # => ["C:", "path", "to", "file"]
181
- # *nix '/' split('/path/to/file') # => ["", "path", "to", "file"]
182
- #
183
- # The path is always expanded relative to the expand_dir; so '.' and
184
- # '..' are resolved. However, unless expand_path == true, only the
185
- # segments relative to the expand_dir are returned.
186
- #
187
- # On windows (note that expanding paths allows the use of slashes or
188
- # backslashes):
189
- #
190
- # Dir.pwd # => 'C:/'
191
- # split('path\to\..\.\to\file') # => ["C:", "path", "to", "file"]
192
- # split('path/to/.././to/file', false) # => ["path", "to", "file"]
193
- #
194
- # On *nix (or more generally systems with '/' roots):
195
- #
196
- # Dir.pwd # => '/'
197
- # split('path/to/.././to/file') # => ["", "path", "to", "file"]
198
- # split('path/to/.././to/file', false) # => ["path", "to", "file"]
199
- #
200
- def split(path, expand_path=true, expand_dir=Dir.pwd)
201
- path = if expand_path
202
- File.expand_path(path, expand_dir)
203
- else
204
- # normalize the path by expanding it, then
205
- # work back to the relative path as needed
206
- expanded_dir = File.expand_path(expand_dir)
207
- expanded_path = File.expand_path(path, expand_dir)
208
- expanded_path.index(expanded_dir) != 0 ? expanded_path : relative_path(expanded_dir, expanded_path)
209
- end
210
-
211
- segments = path.scan(/[^\/]+/)
212
-
213
- # add back the root path as needed on *nix
214
- segments.unshift "" if path[0] == ?/
215
- segments
216
- end
217
-
218
- end
219
- end
220
- end
@@ -1,138 +0,0 @@
1
- module Tap
2
- class Root
3
- # Version provides methods for adding, removing, and incrementing versions
4
- # at the end of paths. Versions are all formatted like:
5
- # 'path-version.extension'.
6
- #
7
- module Versions
8
-
9
- # Adds a version to the path. Versioned paths follow the format:
10
- # 'path-version.extension'. If no version is specified, then the
11
- # path is returned.
12
- #
13
- # version("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
14
- #
15
- def version(path, version)
16
- version = version.to_s.strip
17
- if version.empty?
18
- path
19
- else
20
- extname = File.extname(path)
21
- path.chomp(extname) + '-' + version + extname
22
- end
23
- end
24
-
25
- # Increments the version of the path by the specified increment.
26
- #
27
- # increment("path/to/file-1.0.txt", "0.0.1") # => "path/to/file-1.0.1.txt"
28
- # increment("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
29
- #
30
- def increment(path, increment)
31
- path, version = deversion(path)
32
-
33
- # split the version and increment into integer arrays of equal length
34
- increment, version = [increment, version].collect do |vstr|
35
- begin
36
- vstr.to_s.split(/\./).collect {|v| v.to_i}
37
- rescue
38
- raise "Bad version or increment: #{vstr}"
39
- end
40
- end
41
-
42
- if increment.length > version.length
43
- version.concat Array.new(increment.length - version.length, 0)
44
- end
45
-
46
- # add the increment to version
47
- 0.upto(version.length-1) do |i|
48
- version[i] += (increment[i] || 0)
49
- end
50
-
51
- self.version(path, version.join("."))
52
- end
53
-
54
- # Splits the version from the input path, then returns the path and
55
- # version. If no version is specified, then the returned version will be
56
- # nil.
57
- #
58
- # deversion("path/to/file-1.0.txt") # => ["path/to/file.txt", "1.0"]
59
- # deversion("path/to/file.txt") # => ["path/to/file.txt", nil]
60
- #
61
- def deversion(path)
62
- extname = File.extname(path)
63
- extname = '' if extname =~ /^\.\d+$/
64
- path =~ /^(.*)-(\d(\.?\d)*)#{extname}$/ ? [$1 + extname, $2] : [path, nil]
65
- end
66
-
67
- # A <=> comparison for versions. compare_versions can take strings,
68
- # integers, or even arrays representing the parts of a version.
69
- #
70
- # compare_versions("1.0.0", "0.9.9") # => 1
71
- # compare_versions(1.1, 1.1) # => 0
72
- # compare_versions([0,9], [0,9,1]) # => -1
73
- def compare_versions(a,b)
74
- a, b = [a,b].collect {|item| to_integer_array(item) }
75
-
76
- # equalize the lengths of the integer arrays
77
- d = b.length - a.length
78
- case
79
- when d < 0 then b.concat Array.new(-d, 0)
80
- when d > 0 then a.concat Array.new(d, 0)
81
- end
82
-
83
- a <=> b
84
- end
85
-
86
- # Version unique. Select the latest or earliest versions of each file
87
- # in the array. For paths that have no version, vniq considers any
88
- # version to beat no version. The order of paths is preserved by
89
- # default, but the extra sort doing so may be turned off.
90
- #
91
- # paths = [
92
- # "/path/to/two-0.0.1.txt",
93
- # "/path/to/one-0.0.1.txt",
94
- # "/path/to/one.txt",
95
- # "/path/to/two-1.0.1.txt",
96
- # "/path/to/three.txt"]
97
- #
98
- # vniq(paths)
99
- # # => [
100
- # # "/path/to/one-0.0.1.txt",
101
- # # "/path/to/two-1.0.1.txt",
102
- # # "/path/to/three.txt"]
103
- #
104
- def vniq(array, earliest=false, preserve_order=true)
105
- unique = {}
106
- array.sort.each do |path|
107
- base, version = deversion(path)
108
- (unique[base] ||= []) << version
109
- end
110
-
111
- results = []
112
- unique.each_pair do |base, versions|
113
- versions = versions.sort {|a, b| compare_versions(a,b) }
114
- winner = earliest ? versions.shift : versions.pop
115
- results << version(base, winner)
116
- end
117
-
118
- results = results.sort_by do |path|
119
- array.index(path)
120
- end if preserve_order
121
-
122
- results
123
- end
124
-
125
- private
126
-
127
- # Converts an input argument (typically a string or an array) to an
128
- # array of integers. Splits version string on "."
129
- def to_integer_array(arg)
130
- arr = case arg
131
- when Array then arg
132
- else arg.to_s.split('.')
133
- end
134
- arr.collect {|i| i.to_i}
135
- end
136
- end
137
- end
138
- end