necktie 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/bin/necktie +1 -1
  2. data/lib/necktie/rush.rb +11 -0
  3. data/lib/necktie/services.rb +31 -11
  4. data/lib/necktie.rb +10 -6
  5. data/necktie.gemspec +2 -2
  6. data/rush/README.rdoc +87 -0
  7. data/rush/Rakefile +60 -0
  8. data/rush/VERSION +1 -0
  9. data/rush/bin/rush +13 -0
  10. data/rush/bin/rushd +7 -0
  11. data/rush/lib/rush/access.rb +130 -0
  12. data/rush/lib/rush/array_ext.rb +19 -0
  13. data/rush/lib/rush/box.rb +112 -0
  14. data/rush/lib/rush/commands.rb +55 -0
  15. data/rush/lib/rush/config.rb +154 -0
  16. data/rush/lib/rush/dir.rb +160 -0
  17. data/rush/lib/rush/embeddable_shell.rb +26 -0
  18. data/rush/lib/rush/entry.rb +185 -0
  19. data/rush/lib/rush/exceptions.rb +31 -0
  20. data/rush/lib/rush/file.rb +85 -0
  21. data/rush/lib/rush/find_by.rb +39 -0
  22. data/rush/lib/rush/fixnum_ext.rb +18 -0
  23. data/rush/lib/rush/head_tail.rb +11 -0
  24. data/rush/lib/rush/local.rb +402 -0
  25. data/rush/lib/rush/process.rb +59 -0
  26. data/rush/lib/rush/process_set.rb +62 -0
  27. data/rush/lib/rush/remote.rb +156 -0
  28. data/rush/lib/rush/search_results.rb +58 -0
  29. data/rush/lib/rush/server.rb +117 -0
  30. data/rush/lib/rush/shell.rb +187 -0
  31. data/rush/lib/rush/ssh_tunnel.rb +122 -0
  32. data/rush/lib/rush/string_ext.rb +3 -0
  33. data/rush/lib/rush.rb +87 -0
  34. data/rush/rush.gemspec +113 -0
  35. data/rush/spec/access_spec.rb +134 -0
  36. data/rush/spec/array_ext_spec.rb +15 -0
  37. data/rush/spec/base.rb +24 -0
  38. data/rush/spec/box_spec.rb +64 -0
  39. data/rush/spec/commands_spec.rb +47 -0
  40. data/rush/spec/config_spec.rb +108 -0
  41. data/rush/spec/dir_spec.rb +164 -0
  42. data/rush/spec/embeddable_shell_spec.rb +17 -0
  43. data/rush/spec/entry_spec.rb +133 -0
  44. data/rush/spec/file_spec.rb +83 -0
  45. data/rush/spec/find_by_spec.rb +58 -0
  46. data/rush/spec/fixnum_ext_spec.rb +19 -0
  47. data/rush/spec/local_spec.rb +352 -0
  48. data/rush/spec/process_set_spec.rb +50 -0
  49. data/rush/spec/process_spec.rb +73 -0
  50. data/rush/spec/remote_spec.rb +140 -0
  51. data/rush/spec/rush_spec.rb +28 -0
  52. data/rush/spec/search_results_spec.rb +44 -0
  53. data/rush/spec/shell_spec.rb +23 -0
  54. data/rush/spec/ssh_tunnel_spec.rb +122 -0
  55. data/rush/spec/string_ext_spec.rb +23 -0
  56. metadata +53 -3
  57. data/lib/necktie/files.rb +0 -14
@@ -0,0 +1,154 @@
1
+ # The config class accesses files in ~/.rush to load and save user preferences.
2
+ class Rush::Config
3
+ DefaultPort = 7770
4
+
5
+ attr_reader :dir
6
+
7
+ # By default, reads from the dir ~/.rush, but an optional parameter allows
8
+ # using another location.
9
+ def initialize(location=nil)
10
+ @dir = Rush::Dir.new(location || "#{ENV['HOME']}/.rush")
11
+ @dir.create
12
+ end
13
+
14
+ # History is a flat file of past commands in the interactive shell,
15
+ # equivalent to .bash_history.
16
+ def history_file
17
+ dir['history']
18
+ end
19
+
20
+ def save_history(array)
21
+ history_file.write(array.join("\n") + "\n")
22
+ end
23
+
24
+ def load_history
25
+ history_file.contents_or_blank.split("\n")
26
+ end
27
+
28
+ # The environment file is executed when the interactive shell starts up.
29
+ # Put aliases and your own functions here; it is the equivalent of .bashrc
30
+ # or .profile.
31
+ #
32
+ # Example ~/.rush/env.rb:
33
+ #
34
+ # server = Rush::Box.new('www@my.server')
35
+ # myproj = home['projects/myproj/']
36
+ def env_file
37
+ dir['env.rb']
38
+ end
39
+
40
+ def load_env
41
+ env_file.contents_or_blank
42
+ end
43
+
44
+ # Commands are mixed in to Array and Rush::Entry, alongside the default
45
+ # commands from Rush::Commands. Any methods here should reference "entries"
46
+ # to get the list of entries to operate on.
47
+ #
48
+ # Example ~/.rush/commands.rb:
49
+ #
50
+ # def destroy_svn(*args)
51
+ # entries.select { |e| e.name == '.svn' }.destroy
52
+ # end
53
+ def commands_file
54
+ dir['commands.rb']
55
+ end
56
+
57
+ def load_commands
58
+ commands_file.contents_or_blank
59
+ end
60
+
61
+ # Passwords contains a list of username:password combinations used for
62
+ # remote access via rushd. You can fill this in manually, or let the remote
63
+ # connection publish it automatically.
64
+ def passwords_file
65
+ dir['passwords']
66
+ end
67
+
68
+ def passwords
69
+ hash = {}
70
+ passwords_file.lines_or_empty.each do |line|
71
+ user, password = line.split(":", 2)
72
+ hash[user] = password
73
+ end
74
+ hash
75
+ end
76
+
77
+ # Credentials is the client-side equivalent of passwords. It contains only
78
+ # one username:password combination that is transmitted to the server when
79
+ # connecting. This is also autogenerated if it does not exist.
80
+ def credentials_file
81
+ dir['credentials']
82
+ end
83
+
84
+ def credentials
85
+ credentials_file.lines.first.split(":", 2)
86
+ end
87
+
88
+ def save_credentials(user, password)
89
+ credentials_file.write("#{user}:#{password}\n")
90
+ end
91
+
92
+ def credentials_user
93
+ credentials[0]
94
+ end
95
+
96
+ def credentials_password
97
+ credentials[1]
98
+ end
99
+
100
+ def ensure_credentials_exist
101
+ generate_credentials if credentials_file.contents_or_blank == ""
102
+ end
103
+
104
+ def generate_credentials
105
+ save_credentials(generate_user, generate_password)
106
+ end
107
+
108
+ def generate_user
109
+ generate_secret(4, 8)
110
+ end
111
+
112
+ def generate_password
113
+ generate_secret(8, 15)
114
+ end
115
+
116
+ def generate_secret(min, max)
117
+ chars = self.secret_characters
118
+ len = rand(max - min + 1) + min
119
+ password = ""
120
+ len.times do |index|
121
+ password += chars[rand(chars.length)]
122
+ end
123
+ password
124
+ end
125
+
126
+ def secret_characters
127
+ [ ('a'..'z'), ('1'..'9') ].inject([]) do |chars, range|
128
+ chars += range.to_a
129
+ end
130
+ end
131
+
132
+ # ~/.rush/tunnels contains a list of previously created ssh tunnels. The
133
+ # format is host:port, where port is the local port that the tunnel is
134
+ # listening on.
135
+ def tunnels_file
136
+ dir['tunnels']
137
+ end
138
+
139
+ def tunnels
140
+ tunnels_file.lines_or_empty.inject({}) do |hash, line|
141
+ host, port = line.split(':', 2)
142
+ hash[host] = port.to_i
143
+ hash
144
+ end
145
+ end
146
+
147
+ def save_tunnels(hash)
148
+ string = ""
149
+ hash.each do |host, port|
150
+ string += "#{host}:#{port}\n"
151
+ end
152
+ tunnels_file.write string
153
+ end
154
+ end
@@ -0,0 +1,160 @@
1
+ # A dir is a subclass of Rush::Entry that contains other entries. Also known
2
+ # as a directory or a folder.
3
+ #
4
+ # Dirs can be operated on with Rush::Commands the same as an array of files.
5
+ # They also offer a square bracket accessor which can use globbing to get a
6
+ # list of files.
7
+ #
8
+ # Example:
9
+ #
10
+ # dir = box['/home/adam/']
11
+ # dir['**/*.rb'].line_count
12
+ #
13
+ # In the interactive shell, dir.ls is a useful command.
14
+ class Rush::Dir < Rush::Entry
15
+ def dir?
16
+ true
17
+ end
18
+
19
+ def full_path
20
+ "#{super}/"
21
+ end
22
+
23
+ # Entries contained within this dir - not recursive.
24
+ def contents
25
+ find_by_glob('*')
26
+ end
27
+
28
+ # Files contained in this dir only.
29
+ def files
30
+ contents.select { |entry| !entry.dir? }
31
+ end
32
+
33
+ # Other dirs contained in this dir only.
34
+ def dirs
35
+ contents.select { |entry| entry.dir? }
36
+ end
37
+
38
+ # Access subentries with square brackets, e.g. dir['subdir/file']
39
+ def [](key)
40
+ key = key.to_s
41
+ if key == '**'
42
+ files_flattened
43
+ elsif key.match(/\*/)
44
+ find_by_glob(key)
45
+ else
46
+ find_by_name(key)
47
+ end
48
+ end
49
+ # Slashes work as well, e.g. dir/'subdir/file'
50
+ alias_method :/, :[]
51
+
52
+ def find_by_name(name) # :nodoc:
53
+ Rush::Entry.factory("#{full_path}/#{name}", box)
54
+ end
55
+
56
+ def find_by_glob(glob) # :nodoc:
57
+ connection.index(full_path, glob).map do |fname|
58
+ Rush::Entry.factory("#{full_path}/#{fname}", box)
59
+ end
60
+ end
61
+
62
+ # A list of all the recursively contained entries in flat form.
63
+ def entries_tree
64
+ find_by_glob('**/*')
65
+ end
66
+
67
+ # Recursively contained files.
68
+ def files_flattened
69
+ entries_tree.select { |e| !e.dir? }
70
+ end
71
+
72
+ # Recursively contained dirs.
73
+ def dirs_flattened
74
+ entries_tree.select { |e| e.dir? }
75
+ end
76
+
77
+ # Given a list of flat filenames, product a list of entries under this dir.
78
+ # Mostly for internal use.
79
+ def make_entries(filenames)
80
+ filenames.map do |fname|
81
+ Rush::Entry.factory("#{full_path}/#{fname}")
82
+ end
83
+ end
84
+
85
+ # Create a blank file within this dir.
86
+ def create_file(name)
87
+ file = self[name].create
88
+ file.write('')
89
+ file
90
+ end
91
+
92
+ # Create an empty subdir within this dir.
93
+ def create_dir(name)
94
+ name += '/' unless name.tail(1) == '/'
95
+ self[name].create
96
+ end
97
+
98
+ # Create an instantiated but not yet filesystem-created dir.
99
+ def create
100
+ connection.create_dir(full_path)
101
+ self
102
+ end
103
+
104
+ # Get the total disk usage of the dir and all its contents.
105
+ def size
106
+ connection.size(full_path)
107
+ end
108
+
109
+ # Contained dirs that are not hidden.
110
+ def nonhidden_dirs
111
+ dirs.select do |dir|
112
+ !dir.hidden?
113
+ end
114
+ end
115
+
116
+ # Contained files that are not hidden.
117
+ def nonhidden_files
118
+ files.select do |file|
119
+ !file.hidden?
120
+ end
121
+ end
122
+
123
+ # Run a bash command starting in this directory. Options are the same as Rush::Box#bash.
124
+ def bash(command, options={})
125
+ box.bash "cd #{quoted_path} && #{command}", options
126
+ end
127
+
128
+ # Destroy all of the contents of the directory, leaving it fresh and clean.
129
+ def purge
130
+ connection.purge full_path
131
+ end
132
+
133
+ # Text output of dir listing, equivalent to the regular unix shell's ls command.
134
+ def ls
135
+ out = [ "#{self}" ]
136
+ nonhidden_dirs.each do |dir|
137
+ out << " #{dir.name}/"
138
+ end
139
+ nonhidden_files.each do |file|
140
+ out << " #{file.name}"
141
+ end
142
+ out.join("\n")
143
+ end
144
+
145
+ # Run rake within this dir.
146
+ def rake(*args)
147
+ bash "rake #{args.join(' ')}"
148
+ end
149
+
150
+ # Run git within this dir.
151
+ def git(*args)
152
+ bash "git #{args.join(' ')}"
153
+ end
154
+
155
+ include Rush::Commands
156
+
157
+ def entries
158
+ contents
159
+ end
160
+ end
@@ -0,0 +1,26 @@
1
+ require 'rush/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,185 @@
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
+ # The factory checks to see if the full path has a trailing slash for
17
+ # creating a Rush::Dir rather than the default Rush::File.
18
+ def self.factory(full_path, box=nil)
19
+ if full_path.tail(1) == '/'
20
+ Rush::Dir.new(full_path, box)
21
+ elsif File.directory?(full_path)
22
+ Rush::Dir.new(full_path, box)
23
+ else
24
+ Rush::File.new(full_path, box)
25
+ end
26
+ end
27
+
28
+ def to_s # :nodoc:
29
+ if box.host == 'localhost'
30
+ "#{full_path}"
31
+ else
32
+ inspect
33
+ end
34
+ end
35
+
36
+ def inspect # :nodoc:
37
+ "#{box}:#{full_path}"
38
+ end
39
+
40
+ def connection
41
+ box ? box.connection : Rush::Connection::Local.new
42
+ end
43
+
44
+ # The parent dir. For example, box['/etc/hosts'].parent == box['etc/']
45
+ def parent
46
+ @parent ||= Rush::Dir.new(@path)
47
+ end
48
+
49
+ def full_path
50
+ "#{@path}/#{@name}"
51
+ end
52
+
53
+ def quoted_path
54
+ Rush.quote(full_path)
55
+ end
56
+
57
+ # Return true if the entry currently exists on the filesystem of the box.
58
+ def exists?
59
+ stat
60
+ true
61
+ rescue Rush::DoesNotExist
62
+ false
63
+ end
64
+
65
+ # Timestamp of most recent change to the entry (permissions, contents, etc).
66
+ def changed_at
67
+ stat[:ctime]
68
+ end
69
+
70
+ # Timestamp of last modification of the contents.
71
+ def last_modified
72
+ stat[:mtime]
73
+ end
74
+
75
+ # Timestamp that entry was last accessed (read from or written to).
76
+ def last_accessed
77
+ stat[:atime]
78
+ end
79
+
80
+ # Attempts to rename, copy, or otherwise place an entry into a dir that already contains an entry by that name will fail with this exception.
81
+ class NameAlreadyExists < Exception; end
82
+
83
+ # Do not use rename or duplicate with a slash; use copy_to or move_to instead.
84
+ class NameCannotContainSlash < Exception; end
85
+
86
+ # Rename an entry to another name within the same dir. The object's name
87
+ # will be updated to match the change on the filesystem.
88
+ def rename(new_name)
89
+ connection.rename(@path, @name, new_name)
90
+ @name = new_name
91
+ self
92
+ end
93
+
94
+ # Rename an entry to another name within the same dir. The existing object
95
+ # will not be affected, but a new object representing the newly-created
96
+ # entry will be returned.
97
+ def duplicate(new_name)
98
+ raise Rush::NameCannotContainSlash if new_name.match(/\//)
99
+ new_full_path = "#{@path}/#{new_name}"
100
+ connection.copy(full_path, new_full_path)
101
+ self.class.new(new_full_path, box)
102
+ end
103
+
104
+ # Copy the entry to another dir. Returns an object representing the new
105
+ # copy.
106
+ def copy_to(dir)
107
+ raise Rush::NotADir unless dir.class == Rush::Dir
108
+
109
+ if box == dir.box
110
+ connection.copy(full_path, dir.full_path)
111
+ else
112
+ archive = connection.read_archive(full_path)
113
+ dir.box.connection.write_archive(archive, dir.full_path)
114
+ end
115
+
116
+ new_full_path = "#{dir.full_path}#{name}"
117
+ self.class.new(new_full_path, dir.box)
118
+ end
119
+
120
+ # Move the entry to another dir. The object will be updated to show its new
121
+ # location.
122
+ def move_to(dir)
123
+ moved = copy_to(dir)
124
+ destroy
125
+ mimic(moved)
126
+ end
127
+
128
+ def mimic(from) # :nodoc:
129
+ @box = from.box
130
+ @path = from.path
131
+ @name = from.name
132
+ end
133
+
134
+ # Unix convention considers entries starting with a . to be hidden.
135
+ def hidden?
136
+ name.slice(0, 1) == '.'
137
+ end
138
+
139
+ # Set the access permissions for the entry.
140
+ #
141
+ # Permissions are set by role and permissions combinations which can be specified individually
142
+ # or grouped together. :user_can => :read, :user_can => :write is the same
143
+ # as :user_can => :read_write.
144
+ #
145
+ # You can also insert 'and' if you find it reads better, like :user_and_group_can => :read_and_write.
146
+ #
147
+ # Any permission excluded is set to deny access. The access call does not set partial
148
+ # permissions which combine with the existing state of the entry, like "chmod o+r" would.
149
+ #
150
+ # Examples:
151
+ #
152
+ # file.access = { :user_can => :read_write, :group_other_can => :read }
153
+ # dir.access = { :user => 'adam', :group => 'users', :read_write_execute => :user_group }
154
+ #
155
+ def access=(options)
156
+ connection.set_access(full_path, Rush::Access.parse(options))
157
+ end
158
+
159
+ # Returns a hash with up to nine values, combining user/group/other with read/write/execute.
160
+ # The key is omitted if the value is false.
161
+ #
162
+ # Examples:
163
+ #
164
+ # entry.access # -> { :user_can_read => true, :user_can_write => true, :group_can_read => true }
165
+ # entry.access[:other_can_read] # -> true or nil
166
+ #
167
+ def access
168
+ Rush::Access.new.from_octal(stat[:mode]).display_hash
169
+ end
170
+
171
+ # Destroy the entry. If it is a dir, everything inside it will also be destroyed.
172
+ def destroy
173
+ connection.destroy(full_path)
174
+ end
175
+
176
+ def ==(other) # :nodoc:
177
+ full_path == other.full_path and box == other.box
178
+ end
179
+
180
+ private
181
+
182
+ def stat
183
+ connection.stat(full_path)
184
+ end
185
+ end
@@ -0,0 +1,31 @@
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
+ # There's already an entry by the given name in the given dir.
21
+ class NameAlreadyExists < Exception; end
22
+
23
+ # The name cannot contain a slash; use two operations, rename and then move, instead.
24
+ class NameCannotContainSlash < Exception; end
25
+
26
+ # You cannot move or copy entries to a path that is not a dir (should end with trailing slash).
27
+ class NotADir < Exception; end
28
+
29
+ # A user or permission value specified to set access was not valid.
30
+ class BadAccessSpecifier < Exception; end
31
+ end
@@ -0,0 +1,85 @@
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
+ # Create a blank file.
9
+ def create
10
+ write('')
11
+ self
12
+ end
13
+
14
+ # Size in bytes on disk.
15
+ def size
16
+ stat[:size]
17
+ end
18
+
19
+ # Raw contents of the file. For non-text files, you probably want to avoid
20
+ # printing this on the screen.
21
+ def contents
22
+ connection.file_contents(full_path)
23
+ end
24
+
25
+ alias :read :contents
26
+
27
+ # Write to the file, overwriting whatever was already in it.
28
+ #
29
+ # Example: file.write "hello, world\n"
30
+ def write(new_contents)
31
+ connection.write_file(full_path, new_contents)
32
+ end
33
+
34
+ # Append new contents to the end of the file, keeping what was in it.
35
+ def append(contents)
36
+ connection.append_to_file(full_path, contents)
37
+ end
38
+ alias_method :<<, :append
39
+
40
+ # Return an array of lines from the file, similar to stdlib's File#readlines.
41
+ def lines
42
+ contents.split("\n")
43
+ end
44
+
45
+ # Search the file's for a regular expression. Returns nil if no match, or
46
+ # each of the matching lines in its entirety.
47
+ #
48
+ # Example: box['/etc/hosts'].search(/localhost/) # -> [ "127.0.0.1 localhost\n", "::1 localhost\n" ]
49
+ def search(pattern)
50
+ matching_lines = lines.select { |line| line.match(pattern) }
51
+ matching_lines.size == 0 ? nil : matching_lines
52
+ end
53
+
54
+ # Search-and-replace file contents.
55
+ #
56
+ # Example: box['/etc/hosts'].replace_contents!(/localhost/, 'local.host')
57
+ def replace_contents!(pattern, replace_with)
58
+ write contents.gsub(pattern, replace_with)
59
+ end
60
+
61
+ # Return the file's contents, or if it doesn't exist, a blank string.
62
+ def contents_or_blank
63
+ contents
64
+ rescue Rush::DoesNotExist
65
+ ""
66
+ end
67
+
68
+ # Count the number of lines in the file.
69
+ def line_count
70
+ lines.size
71
+ end
72
+
73
+ # Return an array of lines, or an empty array if the file does not exist.
74
+ def lines_or_empty
75
+ lines
76
+ rescue Rush::DoesNotExist
77
+ []
78
+ end
79
+
80
+ include Rush::Commands
81
+
82
+ def entries
83
+ [ self ]
84
+ end
85
+ 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