pik 0.1.1 → 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.
Files changed (68) hide show
  1. data/History.txt +13 -0
  2. data/Manifest.txt +35 -1
  3. data/README.rdoc +99 -39
  4. data/Rakefile +49 -8
  5. data/bin/pik_install +23 -0
  6. data/features/add_command.feature +28 -0
  7. data/features/checkup_command.feature +0 -0
  8. data/features/config_command.feature +12 -0
  9. data/features/default_command.feature +12 -0
  10. data/features/env.rb +52 -0
  11. data/features/gemsync_command.feature +0 -0
  12. data/features/help_command.feature +13 -0
  13. data/features/implode_command.feature +12 -0
  14. data/features/install_command.feature +13 -0
  15. data/features/list_command.feature +18 -0
  16. data/features/remove_command.feature +18 -0
  17. data/features/run_command.feature +22 -0
  18. data/features/step_definitions/pik_commands.rb +140 -0
  19. data/features/switch_command.feature +35 -0
  20. data/features/tag_command.feature +18 -0
  21. data/features/version.feature +9 -0
  22. data/lib/pik.rb +17 -3
  23. data/lib/pik/commands/add_command.rb +6 -6
  24. data/lib/pik/commands/batch_file_editor.rb +22 -8
  25. data/lib/pik/commands/checkup_command.rb +5 -2
  26. data/lib/pik/commands/command.rb +30 -26
  27. data/lib/pik/commands/config_command.rb +54 -2
  28. data/lib/pik/commands/default_command.rb +19 -5
  29. data/lib/pik/commands/help_command.rb +1 -1
  30. data/lib/pik/commands/implode_command.rb +12 -1
  31. data/lib/pik/commands/install_command.rb +182 -0
  32. data/lib/pik/commands/list_command.rb +3 -2
  33. data/lib/pik/commands/remove_command.rb +6 -6
  34. data/lib/pik/commands/run_command.rb +70 -10
  35. data/lib/pik/commands/switch_command.rb +10 -10
  36. data/lib/pik/commands/tag_command.rb +56 -0
  37. data/lib/pik/config_file.rb +26 -2
  38. data/lib/pik/contrib/progressbar.rb +237 -0
  39. data/lib/pik/contrib/unzip.rb +14 -0
  40. data/lib/pik/contrib/uri_ext.rb +296 -0
  41. data/lib/pik/contrib/zip/ioextras.rb +155 -0
  42. data/lib/pik/contrib/zip/stdrubyext.rb +111 -0
  43. data/lib/pik/contrib/zip/tempfile_bugfixed.rb +195 -0
  44. data/lib/pik/contrib/zip/zip.rb +1846 -0
  45. data/lib/pik/contrib/zip/zipfilesystem.rb +609 -0
  46. data/lib/pik/contrib/zip/ziprequire.rb +90 -0
  47. data/lib/pik/core_ext/pathname.rb +20 -7
  48. data/lib/pik/search_path.rb +21 -13
  49. data/lib/pik/which.rb +52 -0
  50. data/lib/pik/windows_env.rb +64 -25
  51. data/spec/add_command_spec.rb +0 -2
  52. data/spec/batch_file_spec.rb +3 -3
  53. data/spec/command_spec.rb +0 -7
  54. data/spec/gemsync_command_spec.rb +1 -1
  55. data/spec/help_command_spec.rb +1 -1
  56. data/spec/list_command_spec.rb +1 -1
  57. data/spec/pathname_spec.rb +30 -0
  58. data/spec/remove_command_spec.rb +6 -6
  59. data/spec/run_command_spec.rb +2 -30
  60. data/spec/search_path_spec.rb +9 -0
  61. data/spec/switch_command_spec.rb +14 -2
  62. data/spec/which_spec.rb +7 -0
  63. data/tools/pik.bat +2 -0
  64. data/tools/pik/pik +45 -0
  65. data/tools/pik/pik.exe +0 -0
  66. data/tools/pik/pik.exy +198 -0
  67. metadata +50 -21
  68. data/bin/pik +0 -33
@@ -0,0 +1,14 @@
1
+ $: << File.dirname(__FILE__)
2
+ require 'zip/zip'
3
+
4
+ module Zip
5
+ def self.fake_unzip(zipfilename, regexp, target_dir)
6
+ Zip::ZipFile.open(zipfilename) do |zipfile|
7
+ Zip::ZipFile.foreach(zipfilename) do |entry|
8
+ if regexp =~ entry.name
9
+ zipfile.extract(entry, File.join(target_dir, File.basename(entry.name))) { true }
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,296 @@
1
+ #
2
+ # I've striped down dependencies on Net::SSH and Facets to
3
+ # stay as simple as possible.
4
+ #
5
+ # Original code from Assaf Arkin, released under Apache License
6
+ # (http://buildr.rubyforge.org/license.html)
7
+ #
8
+ require 'cgi'
9
+ require 'uri'
10
+ require 'net/http'
11
+ require 'net/https'
12
+ require 'tempfile'
13
+ require 'fileutils'
14
+
15
+ # show progress of download
16
+ # require File.join(File.dirname(__FILE__), 'progressbar')
17
+
18
+ # Not quite open-uri, but similar. Provides read and write methods for the resource represented by the URI.
19
+ # Currently supports reads for URI::HTTP and writes for URI::SFTP. Also provides convenience methods for
20
+ # downloads and uploads.
21
+ module URI
22
+ # Raised when trying to read/download a resource that doesn't exist.
23
+ class NotFoundError < RuntimeError; end
24
+
25
+ class << self
26
+
27
+ # :call-seq:
28
+ # read(uri, options?) => content
29
+ # read(uri, options?) { |chunk| ... }
30
+ #
31
+ # Reads from the resource behind this URI. The first form returns the content of the resource,
32
+ # the second form yields to the block with each chunk of content (usually more than one).
33
+ #
34
+ # For example:
35
+ # File.open "image.jpg", "w" do |file|
36
+ # URI.read("http://example.com/image.jpg") { |chunk| file.write chunk }
37
+ # end
38
+ # Shorter version:
39
+ # File.open("image.jpg", "w") { |file| file.write URI.read("http://example.com/image.jpg") }
40
+ #
41
+ # Supported options:
42
+ # * :modified -- Only download if file modified since this timestamp. Returns nil if not modified.
43
+ # * :progress -- Show the progress bar while reading.
44
+ def read(uri, options = nil, &block)
45
+ uri = URI.parse(uri.to_s) unless URI === uri
46
+ uri.read(options, &block)
47
+ end
48
+
49
+ # :call-seq:
50
+ # download(uri, target, options?)
51
+ #
52
+ # Downloads the resource to the target.
53
+ #
54
+ # The target may be a file name (string or task), in which case the file is created from the resource.
55
+ # The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe.
56
+ #
57
+ # Use the progress bar when running in verbose mode.
58
+ def download(uri, target, options = nil)
59
+ uri = URI.parse(uri.to_s) unless URI === uri
60
+ uri.download(target, options)
61
+ end
62
+
63
+ # :call-seq:
64
+ # write(uri, content, options?)
65
+ # write(uri, options?) { |bytes| .. }
66
+ #
67
+ # Writes to the resource behind the URI. The first form writes the content from a string or an object
68
+ # that responds to +read+ and optionally +size+. The second form writes the content by yielding to the
69
+ # block. Each yield should return up to the specified number of bytes, the last yield returns nil.
70
+ #
71
+ # For example:
72
+ # File.open "killer-app.jar", "rb" do |file|
73
+ # write("sftp://localhost/jars/killer-app.jar") { |chunk| file.read(chunk) }
74
+ # end
75
+ # Or:
76
+ # write "sftp://localhost/jars/killer-app.jar", File.read("killer-app.jar")
77
+ #
78
+ # Supported options:
79
+ # * :progress -- Show the progress bar while reading.
80
+ def write(uri, *args, &block)
81
+ uri = URI.parse(uri.to_s) unless URI === uri
82
+ uri.write(*args, &block)
83
+ end
84
+ end
85
+
86
+ class Generic
87
+
88
+ # :call-seq:
89
+ # read(options?) => content
90
+ # read(options?) { |chunk| ... }
91
+ #
92
+ # Reads from the resource behind this URI. The first form returns the content of the resource,
93
+ # the second form yields to the block with each chunk of content (usually more than one).
94
+ #
95
+ # For options, see URI::read.
96
+ def read(options = nil, &block)
97
+ fail "This protocol doesn't support reading (yet, how about helping by implementing it?)"
98
+ end
99
+
100
+ # :call-seq:
101
+ # download(target, options?)
102
+ #
103
+ # Downloads the resource to the target.
104
+ #
105
+ # The target may be a file name (string or task), in which case the file is created from the resource.
106
+ # The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe.
107
+ #
108
+ # Use the progress bar when running in verbose mode.
109
+ def download(target, options = {})
110
+ case target
111
+ when String
112
+ # If download breaks we end up with a partial file which is
113
+ # worse than not having a file at all, so download to temporary
114
+ # file and then move over.
115
+ modified = File.stat(target).mtime if File.exist?(target)
116
+ temp = nil
117
+ result = nil
118
+ Tempfile.open(File.basename(target)) do |tf|
119
+ tf.binmode
120
+ result = read(options.merge(:modified => modified)) { |chunk| tf.write chunk }
121
+ temp = tf
122
+ end
123
+ FileUtils.mkpath(File.dirname(target))
124
+ FileUtils.move(temp.path, target) if result
125
+ when File
126
+ read(options.merge(:modified => target.mtime)) { |chunk| target.write chunk }
127
+ target.flush
128
+ else
129
+ raise ArgumentError, "Expecting a target that is either a file name (string, task) or object that responds to write (file, pipe)." unless target.respond_to?(:write)
130
+ read(options) { |chunk| target.write chunk }
131
+ target.flush
132
+ end
133
+ end
134
+
135
+ # :call-seq:
136
+ # write(content, options?)
137
+ # write(options?) { |bytes| .. }
138
+ #
139
+ # Writes to the resource behind the URI. The first form writes the content from a string or an object
140
+ # that responds to +read+ and optionally +size+. The second form writes the content by yielding to the
141
+ # block. Each yield should return up to the specified number of bytes, the last yield returns nil.
142
+ #
143
+ # For options, see URI::write.
144
+ def write(*args, &block)
145
+ options = args.pop if Hash === args.last
146
+ options ||= {}
147
+ if String === args.first
148
+ ios = StringIO.new(args.first, "r")
149
+ write(options.merge(:size => args.first.size)) { |bytes| ios.read(bytes) }
150
+ elsif args.first.respond_to?(:read)
151
+ size = args.first.size rescue nil
152
+ write({ :size => size }.merge(options)) { |bytes| args.first.read(bytes) }
153
+ elsif args.empty? && block
154
+ write_internal(options, &block)
155
+ else
156
+ raise ArgumentError, "Either give me the content, or pass me a block, otherwise what would I upload?"
157
+ end
158
+ end
159
+
160
+ protected
161
+
162
+ # :call-seq:
163
+ # with_progress_bar(enable, file_name, size) { |progress| ... }
164
+ #
165
+ # Displays a progress bar while executing the block. The first argument must be true for the
166
+ # progress bar to show (TTY output also required), as a convenient for selectively using the
167
+ # progress bar from a single block.
168
+ #
169
+ # The second argument provides a filename to display, the third its size in bytes.
170
+ #
171
+ # The block is yielded with a progress object that implements a single method.
172
+ # Call << for each block of bytes down/uploaded.
173
+ def with_progress_bar(enable, file_name, size) #:nodoc:
174
+ if enable && $stdout.isatty
175
+ progress_bar = Console::ProgressBar.new(file_name, size)
176
+ # Extend the progress bar so we can display count/total.
177
+ class << progress_bar
178
+ def total()
179
+ convert_bytes(@total)
180
+ end
181
+ end
182
+ # Squeeze the filename into 30 characters.
183
+ if file_name.size > 30
184
+ base, ext = File.basename(file_name), File.extname(file_name)
185
+ truncated = "#{base[0..26-ext.to_s.size]}..#{ext}"
186
+ else
187
+ truncated = file_name
188
+ end
189
+ progress_bar.format = "#{CGI.unescape(truncated)}: %3d%% %s %s/%s %s"
190
+ progress_bar.format_arguments = [:percentage, :bar, :bytes, :total, :stat]
191
+ progress_bar.bar_mark = "o"
192
+
193
+ begin
194
+ class << progress_bar
195
+ def <<(bytes)
196
+ inc bytes.respond_to?(:size) ? bytes.size : bytes
197
+ end
198
+ end
199
+ yield progress_bar
200
+ ensure
201
+ progress_bar.finish
202
+ end
203
+ else
204
+ progress_bar = Object.new
205
+ class << progress_bar
206
+ def <<(bytes)
207
+ end
208
+ end
209
+ yield progress_bar
210
+ end
211
+ end
212
+
213
+ # :call-seq:
214
+ # proxy_uri() => URI?
215
+ #
216
+ # Returns the proxy server to use. Obtains the proxy from the relevant environment variable (e.g. HTTP_PROXY).
217
+ # Supports exclusions based on host name and port number from environment variable NO_PROXY.
218
+ def proxy_uri()
219
+ proxy = ENV["#{scheme.upcase}_PROXY"]
220
+ proxy = URI.parse(proxy) if String === proxy
221
+ excludes = (ENV["NO_PROXY"] || "").split(/\s*,\s*/).compact
222
+ excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
223
+ return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
224
+ end
225
+
226
+ def write_internal(options, &block) #:nodoc:
227
+ fail "This protocol doesn't support writing (yet, how about helping by implementing it?)"
228
+ end
229
+ end
230
+
231
+ class HTTP #:nodoc:
232
+
233
+ def verbose
234
+ false
235
+ end
236
+
237
+ # See URI::Generic#read
238
+ def read(options = nil, &block)
239
+ options ||= {}
240
+ connect do |http|
241
+ puts "Requesting #{self}" if verbose
242
+ headers = { 'If-Modified-Since' => CGI.rfc1123_date(options[:modified].utc) } if options[:modified]
243
+ request = Net::HTTP::Get.new(request_uri.empty? ? '/' : request_uri, headers)
244
+ request.basic_auth self.user, self.password if self.user
245
+ http.request request do |response|
246
+ case response
247
+ when Net::HTTPNotModified
248
+ # No modification, nothing to do.
249
+ puts 'Not modified since last download' if verbose
250
+ return nil
251
+ when Net::HTTPRedirection
252
+ # Try to download from the new URI, handle relative redirects.
253
+ puts "Redirected to #{response['Location']}" if verbose
254
+ return (self + URI.parse(response['location'])).read(options, &block)
255
+ when Net::HTTPOK
256
+ puts "Downloading #{self}" if verbose
257
+ result = nil
258
+ with_progress_bar options[:progress], path.split('/').last, response.content_length do |progress|
259
+ if block
260
+ response.read_body do |chunk|
261
+ block.call chunk
262
+ progress << chunk
263
+ end
264
+ return true
265
+ else
266
+ result = ''
267
+ response.read_body do |chunk|
268
+ result << chunk
269
+ progress << chunk
270
+ end
271
+ return result
272
+ end
273
+ end
274
+ when Net::HTTPNotFound
275
+ raise NotFoundError, "Looking for #{self} and all I got was a 404!"
276
+ else
277
+ raise RuntimeError, "Failed to download #{self}: #{response.message}"
278
+ end
279
+ end
280
+ end
281
+ end
282
+
283
+ private
284
+
285
+ def connect
286
+ if proxy = proxy_uri
287
+ proxy = URI.parse(proxy) if String === proxy
288
+ http = Net::HTTP.new(host, port, proxy.host, proxy.port, proxy.user, proxy.password)
289
+ else
290
+ http = Net::HTTP.new(host, port)
291
+ end
292
+ http.use_ssl = true if self.instance_of? URI::HTTPS
293
+ yield http
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,155 @@
1
+ module IOExtras #:nodoc:
2
+
3
+ CHUNK_SIZE = 32768
4
+
5
+ RANGE_ALL = 0..-1
6
+
7
+ def self.copy_stream(ostream, istream)
8
+ s = ''
9
+ ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof?
10
+ end
11
+
12
+
13
+ # Implements kind_of? in order to pretend to be an IO object
14
+ module FakeIO
15
+ def kind_of?(object)
16
+ object == IO || super
17
+ end
18
+ end
19
+
20
+ # Implements many of the convenience methods of IO
21
+ # such as gets, getc, readline and readlines
22
+ # depends on: input_finished?, produce_input and read
23
+ module AbstractInputStream
24
+ include Enumerable
25
+ include FakeIO
26
+
27
+ def initialize
28
+ super
29
+ @lineno = 0
30
+ @outputBuffer = ""
31
+ end
32
+
33
+ attr_accessor :lineno
34
+
35
+ def read(numberOfBytes = nil, buf = nil)
36
+ tbuf = nil
37
+
38
+ if @outputBuffer.length > 0
39
+ if numberOfBytes <= @outputBuffer.length
40
+ tbuf = @outputBuffer.slice!(0, numberOfBytes)
41
+ else
42
+ numberOfBytes -= @outputBuffer.length if (numberOfBytes)
43
+ rbuf = sysread(numberOfBytes, buf)
44
+ tbuf = @outputBuffer
45
+ tbuf << rbuf if (rbuf)
46
+ @outputBuffer = ""
47
+ end
48
+ else
49
+ tbuf = sysread(numberOfBytes, buf)
50
+ end
51
+
52
+ return nil unless (tbuf)
53
+
54
+ if buf
55
+ buf.replace(tbuf)
56
+ else
57
+ buf = tbuf
58
+ end
59
+
60
+ buf
61
+ end
62
+
63
+ def readlines(aSepString = $/)
64
+ retVal = []
65
+ each_line(aSepString) { |line| retVal << line }
66
+ return retVal
67
+ end
68
+
69
+ def gets(aSepString=$/)
70
+ @lineno = @lineno.next
71
+ return read if aSepString == nil
72
+ aSepString="#{$/}#{$/}" if aSepString == ""
73
+
74
+ bufferIndex=0
75
+ while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil)
76
+ bufferIndex=@outputBuffer.length
77
+ if input_finished?
78
+ return @outputBuffer.empty? ? nil : flush
79
+ end
80
+ @outputBuffer << produce_input
81
+ end
82
+ sepIndex=matchIndex + aSepString.length
83
+ return @outputBuffer.slice!(0...sepIndex)
84
+ end
85
+
86
+ def flush
87
+ retVal=@outputBuffer
88
+ @outputBuffer=""
89
+ return retVal
90
+ end
91
+
92
+ def readline(aSepString = $/)
93
+ retVal = gets(aSepString)
94
+ raise EOFError if retVal == nil
95
+ return retVal
96
+ end
97
+
98
+ def each_line(aSepString = $/)
99
+ while true
100
+ yield readline(aSepString)
101
+ end
102
+ rescue EOFError
103
+ end
104
+
105
+ alias_method :each, :each_line
106
+ end
107
+
108
+
109
+ # Implements many of the output convenience methods of IO.
110
+ # relies on <<
111
+ module AbstractOutputStream
112
+ include FakeIO
113
+
114
+ def write(data)
115
+ self << data
116
+ data.to_s.length
117
+ end
118
+
119
+
120
+ def print(*params)
121
+ self << params.to_s << $\.to_s
122
+ end
123
+
124
+ def printf(aFormatString, *params)
125
+ self << sprintf(aFormatString, *params)
126
+ end
127
+
128
+ def putc(anObject)
129
+ self << case anObject
130
+ when Fixnum then anObject.chr
131
+ when String then anObject
132
+ else raise TypeError, "putc: Only Fixnum and String supported"
133
+ end
134
+ anObject
135
+ end
136
+
137
+ def puts(*params)
138
+ params << "\n" if params.empty?
139
+ params.flatten.each {
140
+ |element|
141
+ val = element.to_s
142
+ self << val
143
+ self << "\n" unless val[-1,1] == "\n"
144
+ }
145
+ end
146
+
147
+ end
148
+
149
+ end # IOExtras namespace module
150
+
151
+
152
+
153
+ # Copyright (C) 2002-2004 Thomas Sondergaard
154
+ # rubyzip is free software; you can redistribute it and/or
155
+ # modify it under the terms of the ruby license.