rush3 3.0.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 +15 -0
  3. data/Gemfile.lock +93 -0
  4. data/README.rdoc +124 -0
  5. data/Rakefile +58 -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 +16 -0
  12. data/lib/rush/box.rb +130 -0
  13. data/lib/rush/commands.rb +94 -0
  14. data/lib/rush/config.rb +147 -0
  15. data/lib/rush/dir.rb +159 -0
  16. data/lib/rush/embeddable_shell.rb +26 -0
  17. data/lib/rush/entry.rb +238 -0
  18. data/lib/rush/exceptions.rb +32 -0
  19. data/lib/rush/file.rb +100 -0
  20. data/lib/rush/find_by.rb +39 -0
  21. data/lib/rush/head_tail.rb +11 -0
  22. data/lib/rush/integer_ext.rb +18 -0
  23. data/lib/rush/local.rb +404 -0
  24. data/lib/rush/path.rb +9 -0
  25. data/lib/rush/process.rb +59 -0
  26. data/lib/rush/process_set.rb +62 -0
  27. data/lib/rush/search_results.rb +71 -0
  28. data/lib/rush/shell.rb +118 -0
  29. data/lib/rush/shell/completion.rb +108 -0
  30. data/lib/rush/string_ext.rb +33 -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 +162 -0
  40. data/spec/file_spec.rb +95 -0
  41. data/spec/find_by_spec.rb +58 -0
  42. data/spec/integer_ext_spec.rb +19 -0
  43. data/spec/local_spec.rb +363 -0
  44. data/spec/path_spec.rb +13 -0
  45. data/spec/process_set_spec.rb +50 -0
  46. data/spec/process_spec.rb +89 -0
  47. data/spec/rush_spec.rb +28 -0
  48. data/spec/search_results_spec.rb +44 -0
  49. data/spec/shell_spec.rb +39 -0
  50. data/spec/ssh_tunnel_spec.rb +122 -0
  51. data/spec/string_ext_spec.rb +23 -0
  52. metadata +228 -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,238 @@
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
+ Rush::Path.executables
26
+ end
27
+
28
+ # The factory checks to see if the full path has a trailing slash for
29
+ # creating a Rush::Dir rather than the default Rush::File.
30
+ def self.factory(full_path, box=nil)
31
+ if full_path.tail(1) == '/'
32
+ Rush::Dir.new(full_path, box)
33
+ elsif File.directory?(full_path)
34
+ Rush::Dir.new(full_path, box)
35
+ else
36
+ Rush::File.new(full_path, box)
37
+ end
38
+ end
39
+
40
+ def to_s # :nodoc:
41
+ if box.host == 'localhost'
42
+ full_path
43
+ else
44
+ inspect
45
+ end
46
+ end
47
+
48
+ def inspect # :nodoc:
49
+ "#{box}:#{full_path}"
50
+ end
51
+
52
+ def connection
53
+ box ? box.connection : Rush::Connection::Local.new
54
+ end
55
+
56
+ # The parent dir. For example, box['/etc/hosts'].parent == box['etc/']
57
+ def parent
58
+ @parent ||= Rush::Dir.new(@path)
59
+ end
60
+
61
+ def full_path
62
+ "#{path}/#{name}"
63
+ end
64
+
65
+ def quoted_path
66
+ Rush.quote(full_path)
67
+ end
68
+
69
+ # Return true if the entry currently exists on the filesystem of the box.
70
+ def exists?
71
+ stat
72
+ true
73
+ rescue Rush::DoesNotExist
74
+ false
75
+ end
76
+
77
+ # Timestamp of most recent change to the entry (permissions, contents, etc).
78
+ def changed_at
79
+ stat[:ctime]
80
+ end
81
+
82
+ # Timestamp of last modification of the contents.
83
+ def last_modified
84
+ stat[:mtime]
85
+ end
86
+
87
+ # Timestamp that entry was last accessed (read from or written to).
88
+ def last_accessed
89
+ stat[:atime]
90
+ end
91
+
92
+ # Rename an entry to another name within the same dir. The object's name
93
+ # will be updated to match the change on the filesystem.
94
+ def rename(new_name)
95
+ connection.rename(@path, @name, new_name)
96
+ @name = new_name
97
+ self
98
+ end
99
+ alias_method :mv, :rename
100
+
101
+ # Rename an entry to another name within the same dir. The existing object
102
+ # will not be affected, but a new object representing the newly-created
103
+ # entry will be returned.
104
+ def duplicate(new_name)
105
+ raise Rush::NameCannotContainSlash if new_name.match(/\//)
106
+ new_full_path = "#{@path}/#{new_name}"
107
+ connection.copy(full_path, new_full_path)
108
+ self.class.new(new_full_path, box)
109
+ end
110
+
111
+ # Copy the entry to another dir. Returns an object representing the new
112
+ # copy.
113
+ def copy_to(dir)
114
+ raise Rush::NotADir unless dir.class == Rush::Dir
115
+
116
+ if box == dir.box
117
+ connection.copy(full_path, dir.full_path)
118
+ else
119
+ archive = connection.read_archive(full_path)
120
+ dir.box.connection.write_archive(archive, dir.full_path)
121
+ end
122
+
123
+ new_full_path = "#{dir.full_path}#{name}"
124
+ self.class.new(new_full_path, dir.box)
125
+ end
126
+
127
+ # Move the entry to another dir. The object will be updated to show its new
128
+ # location.
129
+ def move_to(dir)
130
+ moved = copy_to(dir)
131
+ destroy
132
+ mimic(moved)
133
+ end
134
+
135
+ # Symlink the file (see File.ln for options)
136
+ def symlink(dst, options = {})
137
+ connection.ln_s(full_path, dst, options)
138
+ self.class.new(dst, box)
139
+ end
140
+
141
+ # Return true if the entry is a symlink.
142
+ def symlink?
143
+ connection.symlink? full_path
144
+ end
145
+
146
+ def mimic(from) # :nodoc:
147
+ @box = from.box
148
+ @path = from.path
149
+ @name = from.name
150
+ end
151
+
152
+ # Unix convention considers entries starting with a . to be hidden.
153
+ def hidden?
154
+ name.slice(0, 1) == '.'
155
+ end
156
+
157
+ # Set the access permissions for the entry.
158
+ #
159
+ # Permissions are set by role and permissions combinations which can be specified individually
160
+ # or grouped together. :user_can => :read, :user_can => :write is the same
161
+ # as :user_can => :read_write.
162
+ #
163
+ # You can also insert 'and' if you find it reads better, like :user_and_group_can => :read_and_write.
164
+ #
165
+ # Any permission excluded is set to deny access. The access call does not set partial
166
+ # permissions which combine with the existing state of the entry, like "chmod o+r" would.
167
+ #
168
+ # Examples:
169
+ #
170
+ # file.access = { :user_can => :read_write, :group_other_can => :read }
171
+ # dir.access = { :user => 'adam', :group => 'users', :read_write_execute => :user_group }
172
+ #
173
+ def access=(options)
174
+ connection.set_access(full_path, Rush::Access.parse(options))
175
+ end
176
+
177
+ # Returns a hash with up to nine values, combining user/group/other with read/write/execute.
178
+ # The key is omitted if the value is false.
179
+ #
180
+ # Examples:
181
+ #
182
+ # entry.access # -> { :user_can_read => true, :user_can_write => true, :group_can_read => true }
183
+ # entry.access[:other_can_read] # -> true or nil
184
+ #
185
+ def access
186
+ Rush::Access.new.from_octal(stat[:mode]).display_hash
187
+ end
188
+
189
+ # Change entry ownership
190
+ #
191
+ # 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.
192
+ #
193
+ # @param user [string/integer] The user to own the file
194
+ # @param group [string/integer] The group to own the file
195
+ # @param options [hash] the options to pass to FileUtils.chown (eg. 'noop', 'verbose' or 'recursive' )
196
+ #
197
+ def chown(user = nil, group = nil, options = {})
198
+ connection.chown(full_path, user, group, options)
199
+ self
200
+ end
201
+
202
+ # Shortcut to Entry::chown to pass the 'recursive' option by default
203
+ #
204
+ def chown_R(user = nil, group = nil, options = {})
205
+ options[:recursive] = true
206
+ chown(user, group, options)
207
+ end
208
+
209
+ # Chown in ruby way. Ruby way is creating accessors.
210
+ def owner
211
+ stat = ::File.stat(full_path)
212
+ { user: Etc.getpwuid(stat.uid).name, group: Etc.getgrgid(stat.gid).name }
213
+ end
214
+
215
+ def owner=(params)
216
+ case params
217
+ when Hash then chown(params.delete(:user), params.delete(:group), params)
218
+ when String then chown(params)
219
+ when Numeric then chown(Etc.getpwuid(params).name)
220
+ else raise 'Something wrong with params for chown'
221
+ end
222
+ end
223
+
224
+ # Destroy the entry. If it is a dir, everything inside it will also be destroyed.
225
+ def destroy
226
+ connection.destroy(full_path)
227
+ end
228
+
229
+ def ==(other) # :nodoc:
230
+ full_path == other.full_path and box == other.box
231
+ end
232
+
233
+ private
234
+
235
+ def stat
236
+ connection.stat(full_path)
237
+ end
238
+ 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,100 @@
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
+ # Hardlink the file (see File.ln for options)
20
+ def link(dst, options = {})
21
+ connection.ln(full_path, dst, options)
22
+ self.class.new(dst, box)
23
+ end
24
+
25
+ # Size in bytes on disk.
26
+ def size
27
+ stat[:size]
28
+ end
29
+
30
+ # Raw contents of the file. For non-text files, you probably want to avoid
31
+ # printing this on the screen.
32
+ def contents
33
+ connection.file_contents(full_path)
34
+ end
35
+
36
+ alias_method :read, :contents
37
+ alias_method :cat, :contents
38
+
39
+ # Write to the file, overwriting whatever was already in it.
40
+ #
41
+ # Example: file.write "hello, world\n"
42
+ def write(new_contents)
43
+ connection.write_file(full_path, new_contents)
44
+ end
45
+
46
+ # Append new contents to the end of the file, keeping what was in it.
47
+ def append(contents)
48
+ connection.append_to_file(full_path, contents)
49
+ end
50
+ alias_method :<<, :append
51
+
52
+ # Return an array of lines from the file, similar to stdlib's File#readlines.
53
+ def lines
54
+ contents.split("\n")
55
+ end
56
+
57
+ # Search the file's for a regular expression. Returns nil if no match, or
58
+ # each of the matching lines in its entirety.
59
+ #
60
+ # Example: box['/etc/hosts'].search(/localhost/) # -> [ "127.0.0.1 localhost\n", "::1 localhost\n" ]
61
+ def search(pattern)
62
+ matching_lines = lines.select { |line| line.match(pattern) }
63
+ matching_lines.size == 0 ? nil : matching_lines
64
+ end
65
+
66
+ # Search-and-replace file contents.
67
+ #
68
+ # Example: box['/etc/hosts'].replace_contents!(/localhost/, 'local.host')
69
+ def replace_contents!(pattern, replace_with)
70
+ write contents.gsub(pattern, replace_with)
71
+ end
72
+ alias_method :gsub, :replace_contents!
73
+ # Because I like vim.
74
+ alias_method :s, :replace_contents!
75
+
76
+ # Return the file's contents, or if it doesn't exist, a blank string.
77
+ def contents_or_blank
78
+ contents
79
+ rescue Rush::DoesNotExist
80
+ ""
81
+ end
82
+
83
+ # Count the number of lines in the file.
84
+ def line_count
85
+ lines.size
86
+ end
87
+
88
+ # Return an array of lines, or an empty array if the file does not exist.
89
+ def lines_or_empty
90
+ lines
91
+ rescue Rush::DoesNotExist
92
+ []
93
+ end
94
+
95
+ include Rush::Commands
96
+
97
+ def entries
98
+ [ self ]
99
+ end
100
+ 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,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