necktie 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 (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