rush2 0.7.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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +86 -0
  4. data/README.rdoc +125 -0
  5. data/Rakefile +57 -0
  6. data/VERSION +1 -0
  7. data/bin/rush +13 -0
  8. data/bin/rushd +7 -0
  9. data/lib/rush.rb +91 -0
  10. data/lib/rush/access.rb +121 -0
  11. data/lib/rush/array_ext.rb +19 -0
  12. data/lib/rush/box.rb +124 -0
  13. data/lib/rush/commands.rb +76 -0
  14. data/lib/rush/config.rb +147 -0
  15. data/lib/rush/dir.rb +166 -0
  16. data/lib/rush/embeddable_shell.rb +26 -0
  17. data/lib/rush/entry.rb +229 -0
  18. data/lib/rush/exceptions.rb +32 -0
  19. data/lib/rush/file.rb +94 -0
  20. data/lib/rush/find_by.rb +39 -0
  21. data/lib/rush/fixnum_ext.rb +18 -0
  22. data/lib/rush/head_tail.rb +11 -0
  23. data/lib/rush/local.rb +377 -0
  24. data/lib/rush/process.rb +59 -0
  25. data/lib/rush/process_set.rb +62 -0
  26. data/lib/rush/remote.rb +33 -0
  27. data/lib/rush/search_results.rb +71 -0
  28. data/lib/rush/shell.rb +111 -0
  29. data/lib/rush/shell/completion.rb +110 -0
  30. data/lib/rush/string_ext.rb +16 -0
  31. data/spec/access_spec.rb +134 -0
  32. data/spec/array_ext_spec.rb +15 -0
  33. data/spec/base.rb +22 -0
  34. data/spec/box_spec.rb +76 -0
  35. data/spec/commands_spec.rb +47 -0
  36. data/spec/config_spec.rb +108 -0
  37. data/spec/dir_spec.rb +163 -0
  38. data/spec/embeddable_shell_spec.rb +17 -0
  39. data/spec/entry_spec.rb +133 -0
  40. data/spec/file_spec.rb +83 -0
  41. data/spec/find_by_spec.rb +58 -0
  42. data/spec/fixnum_ext_spec.rb +19 -0
  43. data/spec/local_spec.rb +365 -0
  44. data/spec/process_set_spec.rb +50 -0
  45. data/spec/process_spec.rb +73 -0
  46. data/spec/remote_spec.rb +140 -0
  47. data/spec/rush_spec.rb +28 -0
  48. data/spec/search_results_spec.rb +44 -0
  49. data/spec/shell_spec.rb +35 -0
  50. data/spec/ssh_tunnel_spec.rb +122 -0
  51. data/spec/string_ext_spec.rb +23 -0
  52. metadata +209 -0
@@ -0,0 +1,26 @@
1
+ require_relative 'shell'
2
+
3
+ module Rush
4
+ # This is a class that can be embedded in other applications
5
+ # rake tasks, utility scripts, etc
6
+ #
7
+ # Delegates unknown method calls to a Rush::Shell instance
8
+ class EmbeddableShell
9
+ attr_accessor :shell
10
+ def initialize(suppress_output = true)
11
+ self.shell = Rush::Shell.new
12
+ shell.suppress_output = suppress_output
13
+ end
14
+
15
+ # evalutes and unkown method call agains the rush shell
16
+ def method_missing(sym, *args, &block)
17
+ shell.execute sym.to_s
18
+ $last_res
19
+ end
20
+
21
+ # take a whole block and execute it as if it were inside a shell
22
+ def execute_in_shell(&block)
23
+ self.instance_eval(&block)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,229 @@
1
+ # Rush::Entry is the base class for Rush::File and Rush::Dir. One or more of
2
+ # these is instantiated whenever you use square brackets to access the
3
+ # filesystem on a box, as well as any other operation that returns an entry or
4
+ # list of entries.
5
+ class Rush::Entry
6
+ attr_reader :box, :name, :path
7
+
8
+ # Initialize with full path to the file or dir, and the box it resides on.
9
+ def initialize(full_path, box=nil)
10
+ full_path = ::File.expand_path(full_path, '/')
11
+ @path = ::File.dirname(full_path)
12
+ @name = ::File.basename(full_path)
13
+ @box = box || Rush::Box.new('localhost')
14
+ end
15
+
16
+ def method_missing(meth, *args, &block)
17
+ if executables.include? meth.to_s
18
+ open_with meth, *args
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def executables
25
+ ENV['PATH'].split(':')
26
+ .map { |x| Rush::Dir.new(x).entries.map(&:name) }
27
+ .flatten
28
+ end
29
+
30
+ # The factory checks to see if the full path has a trailing slash for
31
+ # creating a Rush::Dir rather than the default Rush::File.
32
+ def self.factory(full_path, box=nil)
33
+ if full_path.tail(1) == '/'
34
+ Rush::Dir.new(full_path, box)
35
+ elsif File.directory?(full_path)
36
+ Rush::Dir.new(full_path, box)
37
+ else
38
+ Rush::File.new(full_path, box)
39
+ end
40
+ end
41
+
42
+ def to_s # :nodoc:
43
+ if box.host == 'localhost'
44
+ "#{full_path}"
45
+ else
46
+ inspect
47
+ end
48
+ end
49
+
50
+ def inspect # :nodoc:
51
+ "#{box}:#{full_path}"
52
+ end
53
+
54
+ def connection
55
+ box ? box.connection : Rush::Connection::Local.new
56
+ end
57
+
58
+ # The parent dir. For example, box['/etc/hosts'].parent == box['etc/']
59
+ def parent
60
+ @parent ||= Rush::Dir.new(@path)
61
+ end
62
+
63
+ def full_path
64
+ "#{@path}/#{@name}"
65
+ end
66
+
67
+ def quoted_path
68
+ Rush.quote(full_path)
69
+ end
70
+
71
+ # Return true if the entry currently exists on the filesystem of the box.
72
+ def exists?
73
+ stat
74
+ true
75
+ rescue Rush::DoesNotExist
76
+ false
77
+ end
78
+
79
+ # Timestamp of most recent change to the entry (permissions, contents, etc).
80
+ def changed_at
81
+ stat[:ctime]
82
+ end
83
+
84
+ # Timestamp of last modification of the contents.
85
+ def last_modified
86
+ stat[:mtime]
87
+ end
88
+
89
+ # Timestamp that entry was last accessed (read from or written to).
90
+ def last_accessed
91
+ stat[:atime]
92
+ end
93
+
94
+ # Rename an entry to another name within the same dir. The object's name
95
+ # will be updated to match the change on the filesystem.
96
+ def rename(new_name)
97
+ connection.rename(@path, @name, new_name)
98
+ @name = new_name
99
+ self
100
+ end
101
+ alias_method :mv, :rename
102
+
103
+ # Rename an entry to another name within the same dir. The existing object
104
+ # will not be affected, but a new object representing the newly-created
105
+ # entry will be returned.
106
+ def duplicate(new_name)
107
+ raise Rush::NameCannotContainSlash if new_name.match(/\//)
108
+ new_full_path = "#{@path}/#{new_name}"
109
+ connection.copy(full_path, new_full_path)
110
+ self.class.new(new_full_path, box)
111
+ end
112
+
113
+ # Copy the entry to another dir. Returns an object representing the new
114
+ # copy.
115
+ def copy_to(dir)
116
+ raise Rush::NotADir unless dir.class == Rush::Dir
117
+
118
+ if box == dir.box
119
+ connection.copy(full_path, dir.full_path)
120
+ else
121
+ archive = connection.read_archive(full_path)
122
+ dir.box.connection.write_archive(archive, dir.full_path)
123
+ end
124
+
125
+ new_full_path = "#{dir.full_path}#{name}"
126
+ self.class.new(new_full_path, dir.box)
127
+ end
128
+
129
+ # Move the entry to another dir. The object will be updated to show its new
130
+ # location.
131
+ def move_to(dir)
132
+ moved = copy_to(dir)
133
+ destroy
134
+ mimic(moved)
135
+ end
136
+
137
+ def mimic(from) # :nodoc:
138
+ @box = from.box
139
+ @path = from.path
140
+ @name = from.name
141
+ end
142
+
143
+ # Unix convention considers entries starting with a . to be hidden.
144
+ def hidden?
145
+ name.slice(0, 1) == '.'
146
+ end
147
+
148
+ # Set the access permissions for the entry.
149
+ #
150
+ # Permissions are set by role and permissions combinations which can be specified individually
151
+ # or grouped together. :user_can => :read, :user_can => :write is the same
152
+ # as :user_can => :read_write.
153
+ #
154
+ # You can also insert 'and' if you find it reads better, like :user_and_group_can => :read_and_write.
155
+ #
156
+ # Any permission excluded is set to deny access. The access call does not set partial
157
+ # permissions which combine with the existing state of the entry, like "chmod o+r" would.
158
+ #
159
+ # Examples:
160
+ #
161
+ # file.access = { :user_can => :read_write, :group_other_can => :read }
162
+ # dir.access = { :user => 'adam', :group => 'users', :read_write_execute => :user_group }
163
+ #
164
+ def access=(options)
165
+ connection.set_access(full_path, Rush::Access.parse(options))
166
+ end
167
+
168
+ # Returns a hash with up to nine values, combining user/group/other with read/write/execute.
169
+ # The key is omitted if the value is false.
170
+ #
171
+ # Examples:
172
+ #
173
+ # entry.access # -> { :user_can_read => true, :user_can_write => true, :group_can_read => true }
174
+ # entry.access[:other_can_read] # -> true or nil
175
+ #
176
+ def access
177
+ Rush::Access.new.from_octal(stat[:mode]).display_hash
178
+ end
179
+
180
+ # Change entry ownership
181
+ #
182
+ # Changes owner and group on the named files (in list) to the user user and the group group. user and group may be an ID (Integer/String) or a name (String). If user or group is nil, this method does not change the attribute.
183
+ #
184
+ # @param user [string/integer] The user to own the file
185
+ # @param group [string/integer] The group to own the file
186
+ # @param options [hash] the options to pass to FileUtils.chown (eg. 'noop', 'verbose' or 'recursive' )
187
+ #
188
+ def chown(user = nil, group = nil, options = {})
189
+ connection.chown(full_path, user, group, options)
190
+ self
191
+ end
192
+
193
+ # Shortcut to Entry::chown to pass the 'recursive' option by default
194
+ #
195
+ def chown_R(user = nil, group = nil, options = {})
196
+ options[:recursive] = true
197
+ chown(user, group, options)
198
+ end
199
+
200
+ # Chown in ruby way. Ruby way is creating accessors.
201
+ def owner
202
+ stat = ::File.stat(full_path)
203
+ { user: Etc.getpwuid(stat.uid).name, group: Etc.getgrgid(stat.gid).name }
204
+ end
205
+
206
+ def owner=(params)
207
+ case params
208
+ when Hash then chown(params.delete(:user), params.delete(:group), params)
209
+ when String then chown(params)
210
+ when Numeric then chown(Etc.getpwuid(params).name)
211
+ else raise 'Something wrong with params for chown'
212
+ end
213
+ end
214
+
215
+ # Destroy the entry. If it is a dir, everything inside it will also be destroyed.
216
+ def destroy
217
+ connection.destroy(full_path)
218
+ end
219
+
220
+ def ==(other) # :nodoc:
221
+ full_path == other.full_path and box == other.box
222
+ end
223
+
224
+ private
225
+
226
+ def stat
227
+ connection.stat(full_path)
228
+ end
229
+ end
@@ -0,0 +1,32 @@
1
+ module Rush
2
+ # Base class for all rush exceptions.
3
+ class Exception < ::RuntimeError; end
4
+
5
+ # Client was not authorized by remote server; check credentials.
6
+ class NotAuthorized < Exception; end
7
+
8
+ # rushd is not running on the remote box.
9
+ class RushdNotRunning < Exception; end
10
+
11
+ # An unrecognized status code was returned by rushd.
12
+ class FailedTransmit < Exception; end
13
+
14
+ # The entry (file or dir) referenced does not exist. Message is the entry's full path.
15
+ class DoesNotExist < Exception; end
16
+
17
+ # The bash command had a non-zero return value. Message is stderr.
18
+ class BashFailed < Exception; end
19
+
20
+ # Attempts to rename, copy, or otherwise place an entry into a dir that
21
+ # already contains an entry by that name will fail with this exception.
22
+ class NameAlreadyExists < Exception; end
23
+
24
+ # Do not use rename or duplicate with a slash; use copy_to or move_to instead.
25
+ class NameCannotContainSlash < Exception; end
26
+
27
+ # You cannot move or copy entries to a path that is not a dir (should end with trailing slash).
28
+ class NotADir < Exception; end
29
+
30
+ # A user or permission value specified to set access was not valid.
31
+ class BadAccessSpecifier < Exception; end
32
+ end
@@ -0,0 +1,94 @@
1
+ # Files are a subclass of Rush::Entry. Most of the file-specific operations
2
+ # relate to manipulating the file's contents, like search and replace.
3
+ class Rush::File < Rush::Entry
4
+ def dir?
5
+ false
6
+ end
7
+
8
+ def dirname
9
+ ::File.dirname full_path
10
+ end
11
+
12
+ # Create a blank file.
13
+ def create
14
+ write('')
15
+ self
16
+ end
17
+ alias_method :touch, :create
18
+
19
+ # Size in bytes on disk.
20
+ def size
21
+ stat[:size]
22
+ end
23
+
24
+ # Raw contents of the file. For non-text files, you probably want to avoid
25
+ # printing this on the screen.
26
+ def contents
27
+ connection.file_contents(full_path)
28
+ end
29
+
30
+ alias_method :read, :contents
31
+ alias_method :cat, :contents
32
+
33
+ # Write to the file, overwriting whatever was already in it.
34
+ #
35
+ # Example: file.write "hello, world\n"
36
+ def write(new_contents)
37
+ connection.write_file(full_path, new_contents)
38
+ end
39
+
40
+ # Append new contents to the end of the file, keeping what was in it.
41
+ def append(contents)
42
+ connection.append_to_file(full_path, contents)
43
+ end
44
+ alias_method :<<, :append
45
+
46
+ # Return an array of lines from the file, similar to stdlib's File#readlines.
47
+ def lines
48
+ contents.split("\n")
49
+ end
50
+
51
+ # Search the file's for a regular expression. Returns nil if no match, or
52
+ # each of the matching lines in its entirety.
53
+ #
54
+ # Example: box['/etc/hosts'].search(/localhost/) # -> [ "127.0.0.1 localhost\n", "::1 localhost\n" ]
55
+ def search(pattern)
56
+ matching_lines = lines.select { |line| line.match(pattern) }
57
+ matching_lines.size == 0 ? nil : matching_lines
58
+ end
59
+
60
+ # Search-and-replace file contents.
61
+ #
62
+ # Example: box['/etc/hosts'].replace_contents!(/localhost/, 'local.host')
63
+ def replace_contents!(pattern, replace_with)
64
+ write contents.gsub(pattern, replace_with)
65
+ end
66
+ alias_method :gsub, :replace_contents!
67
+ # Because I like vim.
68
+ alias_method :s, :replace_contents!
69
+
70
+ # Return the file's contents, or if it doesn't exist, a blank string.
71
+ def contents_or_blank
72
+ contents
73
+ rescue Rush::DoesNotExist
74
+ ""
75
+ end
76
+
77
+ # Count the number of lines in the file.
78
+ def line_count
79
+ lines.size
80
+ end
81
+
82
+ # Return an array of lines, or an empty array if the file does not exist.
83
+ def lines_or_empty
84
+ lines
85
+ rescue Rush::DoesNotExist
86
+ []
87
+ end
88
+
89
+ include Rush::Commands
90
+
91
+ def entries
92
+ [ self ]
93
+ end
94
+ end
@@ -0,0 +1,39 @@
1
+ # Generic find_by (returns first match) and find_all_by (returns all matches)
2
+ # against arrays.
3
+ #
4
+ # Examples:
5
+ #
6
+ # processes.find_by_pid(::Process.pid)
7
+ # processes.find_all_by_cmdline(/mongrel_rails/)
8
+ #
9
+ module Rush::FindBy
10
+ def method_missing(meth, *args)
11
+ if m = meth.to_s.match(/^find_by_([a-z_]+)$/)
12
+ find_by(m[1], args.first)
13
+ elsif m = meth.to_s.match(/^find_all_by_([a-z_]+)$/)
14
+ find_all_by(m[1], args.first)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def find_by(field, arg)
21
+ detect do |item|
22
+ item.respond_to?(field) and compare_or_match(item.send(field), arg)
23
+ end
24
+ end
25
+
26
+ def find_all_by(field, arg)
27
+ select do |item|
28
+ item.respond_to?(field) and compare_or_match(item.send(field), arg)
29
+ end
30
+ end
31
+
32
+ def compare_or_match(value, against)
33
+ if against.class == Regexp
34
+ value.match(against) ? true : false
35
+ else
36
+ value == against
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ # Integer extensions for file and dir sizes (returned in bytes).
2
+ #
3
+ # Example:
4
+ #
5
+ # box['/assets/'].files_flattened.select { |f| f.size > 10.mb }
6
+ class Fixnum
7
+ def kb
8
+ self * 1024
9
+ end
10
+
11
+ def mb
12
+ kb * 1024
13
+ end
14
+
15
+ def gb
16
+ mb * 1024
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # Mixin for array and string for methods I wish they had.
2
+ module Rush::HeadTail
3
+ def head(n)
4
+ slice(0, n)
5
+ end
6
+
7
+ def tail(n)
8
+ n = [ n, length ].min
9
+ slice(-n, n)
10
+ end
11
+ end