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
data/bin/necktie CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- $LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__)), File.expand_path("../rush/lib", File.dirname(__FILE__))
3
3
  require "necktie"
4
4
  necktie ARGV
@@ -0,0 +1,11 @@
1
+ require "rush"
2
+ include Rush
3
+
4
+ class Object
5
+ include Rush
6
+ Rush.methods(false).each do |method|
7
+ define_method method do |*args|
8
+ Rush.__send__ method, *args
9
+ end
10
+ end
11
+ end
@@ -1,13 +1,33 @@
1
- def start_service(name)
2
- puts " ** Starting service #{name}"
3
- system "update-rc.d #{name} defaults" and
4
- system "service #{name} start" or
5
- fail "failed to start #{name}"
6
- end
1
+ module Services
2
+ def self.start(name)
3
+ puts " ** Starting service #{name}"
4
+ system "update-rc.d #{name} defaults" and
5
+ system "service #{name} start" or
6
+ fail "failed to start #{name}"
7
+ end
8
+
9
+ def self.enable(name)
10
+ system "update-rc.d #{name} defaults" or "cannot enable #{name}"
11
+ end
12
+
13
+ def self.stop(name)
14
+ puts " ** Stopping service #{name}"
15
+ system "service #{name} stop" and
16
+ system "update-rc.d -f #{name} remove" or
17
+ fail "failed to stop #{name}"
18
+ end
19
+
20
+ def self.disable(name)
21
+ system "update-rc.d -f #{name} remove" or fail "cannot disable #{name}"
22
+ end
23
+
24
+ def self.restart(name)
25
+ puts " ** Stopping service #{name}"
26
+ system "service #{name} restart" or fail "failed to restart #{name}"
27
+ end
7
28
 
8
- def stop_service(name)
9
- puts " ** Stopping service #{name}"
10
- system "service #{name} stop" and
11
- system "update-rc.d -f #{name} remove" or
12
- fail "failed to shutdown #{name}"
29
+ def self.status(name)
30
+ status = File.read("|sudo service --status-all 2>&1")[/^ \[ (.) \] #{Regexp.escape name}$/,1]
31
+ stauts == "+" ? true : status == "-" ? false : nil
32
+ end
13
33
  end
data/lib/necktie.rb CHANGED
@@ -2,7 +2,10 @@ require "fileutils"
2
2
  extend FileUtils
3
3
  require "necktie/gems"
4
4
  require "necktie/services"
5
- require "necktie/files"
5
+ require "necktie/rush"
6
+
7
+ puts launch_dir
8
+ exit!
6
9
 
7
10
  def necktie(args)
8
11
  etc = "/etc/necktie"
@@ -35,8 +38,9 @@ def necktie(args)
35
38
  Dir.chdir repo do
36
39
  executed = File.exist?(etc) ? File.read(etc).split("\n") : []
37
40
  roles.each do |role|
38
- tasks = Dir["tasks/#{role}/*.rb"].sort.map { |name| File.basename(name).gsub(/(.*)\.rb$/, "#{role}/\\1") }
39
- todo = tasks - executed
41
+ skip = executed.map { |t| t[/^#{Regexp.escape role}\/(.*)$/, 1] }.reject(&:nil?)
42
+ tasks = Dir["tasks/#{role}/*.rb"].sort.map { |name| File.basename(name).gsub(/\.rb$/, "") }
43
+ todo = tasks - skip
40
44
  if tasks.empty?
41
45
  puts " * No tasks for role #{role}"
42
46
  elsif todo.empty?
@@ -45,9 +49,9 @@ def necktie(args)
45
49
  puts " * Now in role: #{role}"
46
50
  File.open etc, "a" do |f|
47
51
  todo.each do |task|
48
- puts " ** Executing #{task.split("/").last}"
49
- load "tasks/#{task}.rb"
50
- f.puts task
52
+ puts " ** Executing #{task}"
53
+ load "tasks/#{role}/#{task}.rb"
54
+ f.puts task if task[/^\d+_/]
51
55
  end
52
56
  end
53
57
  end
data/necktie.gemspec CHANGED
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "necktie"
3
- spec.version = "0.1.1"
3
+ spec.version = "0.2.0"
4
4
  spec.author = "Assaf Arkin"
5
5
  spec.email = "assaf@labnotes.org"
6
6
  spec.homepage = ""
7
7
  spec.summary = "Dress to impress"
8
8
  spec.description = ""
9
9
 
10
- spec.files = Dir["{bin,lib}/**/*", "*.{gemspec,rdoc}"]
10
+ spec.files = Dir["{bin,lib,rush}/**/*", "*.{gemspec,rdoc}"]
11
11
  spec.executable = "necktie"
12
12
  end
data/rush/README.rdoc ADDED
@@ -0,0 +1,87 @@
1
+ = rush -- manage your unix systems with pure Ruby
2
+
3
+ rush is a unix integration library and an interactive shell which uses pure Ruby syntax. Walk directory trees; create, copy, search, and destroy files; find and kill processes - everything you'd normally do with shell commands, now in the strict and elegant world of Ruby.
4
+
5
+ == Usage
6
+
7
+ Count the number of classes in your project using bash:
8
+
9
+ find myproj -name \*.rb | xargs grep '^\s*class' | wc -l
10
+
11
+ In rush, this is:
12
+
13
+ myproj['**/*.rb'].search(/^\s*class/).lines.size
14
+
15
+ Pesky stray mongrels? In bash:
16
+
17
+ kill `ps aux | grep mongrel_rails | grep -v grep | cut -c 10-20`
18
+
19
+ In rush:
20
+
21
+ processes.filter(:cmdline => /mongrel_rails/).kill
22
+
23
+ == As a library
24
+
25
+ require 'rubygems'
26
+ require 'rush'
27
+
28
+ file = Rush['/tmp/myfile']
29
+ file.write "hello"
30
+ puts file.contents
31
+ file.destroy
32
+
33
+ puts Rush.my_process.pid
34
+ puts Rush.processes.size
35
+ puts Rush.bash("echo SHELL COMMAND | tr A-Z a-z")
36
+ puts Rush.launch_dir['*.rb'].search(/Rush/).entries.inspect
37
+
38
+ == Invoking the shell
39
+
40
+ Run the "rush" binary to enter the interactive shell.
41
+
42
+ == Remote access and clustering
43
+
44
+ rush can control any number of remote machines from a single location. Copy files or directories between servers as seamlessly as if it was all local.
45
+
46
+ Example of remote access:
47
+
48
+ local = Rush::Box.new('localhost')
49
+ remote = Rush::Box.new('my.remote.server.com')
50
+ local_dir = local['/Users/adam/myproj/']
51
+ remote_dir = remote['/home/myproj/app/']
52
+
53
+ local_dir.copy_to remote_dir
54
+ remote_dir['**/.svn/'].each { |d| d.destroy }
55
+
56
+ Clustering:
57
+
58
+ local_dir = Rush::Box.new('localhost')['/Users/adam/server_logs/'].create
59
+ servers = %w(www1 www2 www3).map { |n| Rush::Box.new(n) }
60
+ servers.each { |s| s['/var/log/nginx/access.log'].copy_to local_dir["#{s.host}_access.log"] }
61
+
62
+ == Reference
63
+
64
+ For more details on syntax and commands, see:
65
+
66
+ * Rush
67
+ * Rush::Entry
68
+ * Rush::File
69
+ * Rush::Dir
70
+ * Rush::Commands
71
+ * Rush::Box
72
+ * Rush::Process
73
+
74
+ == Meta
75
+
76
+ Created by Adam Wiggins
77
+
78
+ Patches contributed by Chihiro Ito, Gabriel Ware, Michael Schutte, Ricardo Chimal Jr., and Nicholas Schlueter, Pedro Belo, and Martin Kuehl
79
+
80
+ Logo by James Lindenbaum
81
+
82
+ Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
83
+
84
+ http://rush.heroku.com
85
+
86
+ http://groups.google.com/group/ruby-shell
87
+
data/rush/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rake'
2
+
3
+ require 'jeweler'
4
+
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "rush"
7
+ s.summary = "A Ruby replacement for bash+ssh."
8
+ s.description = "A Ruby replacement for bash+ssh, providing both an interactive shell and a library. Manage both local and remote unix systems from a single client."
9
+ s.author = "Adam Wiggins"
10
+ s.email = "adam@heroku.com"
11
+ s.homepage = "http://rush.heroku.com/"
12
+ s.executables = [ "rush", "rushd" ]
13
+ s.rubyforge_project = "ruby-shell"
14
+ s.has_rdoc = true
15
+
16
+ s.add_dependency 'session'
17
+
18
+ s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
19
+ end
20
+
21
+ Jeweler::RubyforgeTasks.new
22
+
23
+ ######################################################
24
+
25
+ require 'spec/rake/spectask'
26
+
27
+ desc "Run all specs"
28
+ Spec::Rake::SpecTask.new('spec') do |t|
29
+ t.spec_files = FileList['spec/*_spec.rb']
30
+ end
31
+
32
+ desc "Print specdocs"
33
+ Spec::Rake::SpecTask.new(:doc) do |t|
34
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
35
+ t.spec_files = FileList['spec/*_spec.rb']
36
+ end
37
+
38
+ desc "Run all examples with RCov"
39
+ Spec::Rake::SpecTask.new('rcov') do |t|
40
+ t.spec_files = FileList['spec/*_spec.rb']
41
+ t.rcov = true
42
+ t.rcov_opts = ['--exclude', 'examples']
43
+ end
44
+
45
+ task :default => :spec
46
+
47
+ ######################################################
48
+
49
+ require 'rake/rdoctask'
50
+
51
+ Rake::RDocTask.new do |t|
52
+ t.rdoc_dir = 'rdoc'
53
+ t.title = "rush, a Ruby replacement for bash+ssh"
54
+ t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
55
+ t.options << '--charset' << 'utf-8'
56
+ t.rdoc_files.include('README.rdoc')
57
+ t.rdoc_files.include('lib/rush.rb')
58
+ t.rdoc_files.include('lib/rush/*.rb')
59
+ end
60
+
data/rush/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.2
data/rush/bin/rush ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/rush'
4
+ require File.dirname(__FILE__) + '/../lib/rush/shell'
5
+
6
+ shell = Rush::Shell.new
7
+
8
+ if ARGV.size > 0
9
+ shell.execute ARGV.join(' ')
10
+ else
11
+ shell.run
12
+ end
13
+
data/rush/bin/rushd ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/rush'
4
+ require File.dirname(__FILE__) + '/../lib/rush/server'
5
+
6
+ RushServer.new.run
7
+
@@ -0,0 +1,130 @@
1
+ # A class to hold permissions (read, write, execute) for files and dirs.
2
+ # See Rush::Entry#access= for information on the public-facing interface.
3
+ class Rush::Access
4
+ attr_accessor :user_can_read, :user_can_write, :user_can_execute
5
+ attr_accessor :group_can_read, :group_can_write, :group_can_execute
6
+ attr_accessor :other_can_read, :other_can_write, :other_can_execute
7
+
8
+ def self.roles
9
+ %w(user group other)
10
+ end
11
+
12
+ def self.permissions
13
+ %w(read write execute)
14
+ end
15
+
16
+ def parse(options)
17
+ options.each do |key, value|
18
+ next unless m = key.to_s.match(/(.*)_can$/)
19
+ key = m[1].to_sym
20
+ roles = extract_list('role', key, self.class.roles)
21
+ perms = extract_list('permission', value, self.class.permissions)
22
+ set_matrix(perms, roles)
23
+ end
24
+ self
25
+ end
26
+
27
+ def self.parse(options)
28
+ new.parse(options)
29
+ end
30
+
31
+ def apply(full_path)
32
+ FileUtils.chmod(octal_permissions, full_path)
33
+ rescue Errno::ENOENT
34
+ raise Rush::DoesNotExist, full_path
35
+ end
36
+
37
+ def to_hash
38
+ hash = {}
39
+ self.class.roles.each do |role|
40
+ self.class.permissions.each do |perm|
41
+ key = "#{role}_can_#{perm}".to_sym
42
+ hash[key] = send(key) ? 1 : 0
43
+ end
44
+ end
45
+ hash
46
+ end
47
+
48
+ def display_hash
49
+ hash = {}
50
+ to_hash.each do |key, value|
51
+ hash[key] = true if value == 1
52
+ end
53
+ hash
54
+ end
55
+
56
+ def from_hash(hash)
57
+ self.class.roles.each do |role|
58
+ self.class.permissions.each do |perm|
59
+ key = "#{role}_can_#{perm}"
60
+ send("#{key}=".to_sym, hash[key.to_sym].to_i == 1 ? true : false)
61
+ end
62
+ end
63
+ self
64
+ end
65
+
66
+ def self.from_hash(hash)
67
+ new.from_hash(hash)
68
+ end
69
+
70
+ def octal_permissions
71
+ perms = [ 0, 0, 0 ]
72
+ perms[0] += 4 if user_can_read
73
+ perms[0] += 2 if user_can_write
74
+ perms[0] += 1 if user_can_execute
75
+
76
+ perms[1] += 4 if group_can_read
77
+ perms[1] += 2 if group_can_write
78
+ perms[1] += 1 if group_can_execute
79
+
80
+ perms[2] += 4 if other_can_read
81
+ perms[2] += 2 if other_can_write
82
+ perms[2] += 1 if other_can_execute
83
+
84
+ eval("0" + perms.join)
85
+ end
86
+
87
+ def from_octal(mode)
88
+ perms = octal_integer_array(mode)
89
+
90
+ self.user_can_read = (perms[0] & 4) > 0 ? true : false
91
+ self.user_can_write = (perms[0] & 2) > 0 ? true : false
92
+ self.user_can_execute = (perms[0] & 1) > 0 ? true : false
93
+
94
+ self.group_can_read = (perms[1] & 4) > 0 ? true : false
95
+ self.group_can_write = (perms[1] & 2) > 0 ? true : false
96
+ self.group_can_execute = (perms[1] & 1) > 0 ? true : false
97
+
98
+ self.other_can_read = (perms[2] & 4) > 0 ? true : false
99
+ self.other_can_write = (perms[2] & 2) > 0 ? true : false
100
+ self.other_can_execute = (perms[2] & 1) > 0 ? true : false
101
+
102
+ self
103
+ end
104
+
105
+ def octal_integer_array(mode)
106
+ mode %= 01000 # filter out everything but the bottom three digits
107
+ mode = sprintf("%o", mode) # convert to string
108
+ mode.split("").map { |p| p.to_i } # and finally, array of integers
109
+ end
110
+
111
+ def set_matrix(perms, roles)
112
+ perms.each do |perm|
113
+ roles.each do |role|
114
+ meth = "#{role}_can_#{perm}=".to_sym
115
+ send(meth, true)
116
+ end
117
+ end
118
+ end
119
+
120
+ def extract_list(type, value, choices)
121
+ list = parts_from(value)
122
+ list.each do |value|
123
+ raise(Rush::BadAccessSpecifier, "Unrecognized #{type}: #{value}") unless choices.include? value
124
+ end
125
+ end
126
+
127
+ def parts_from(value)
128
+ value.to_s.split('_').reject { |r| r == 'and' }
129
+ end
130
+ end
@@ -0,0 +1,19 @@
1
+ # Rush mixes in Rush::Commands in order to allow operations on groups of
2
+ # Rush::Entry items. For example, dir['**/*.rb'] returns an array of files, so
3
+ # dir['**/*.rb'].destroy would destroy all the files specified.
4
+ #
5
+ # One cool tidbit: the array can contain entries anywhere, so you can create
6
+ # collections of entries from different servers and then operate across them:
7
+ #
8
+ # [ box1['/var/log/access.log'] + box2['/var/log/access.log'] ].search /#{url}/
9
+ class Array
10
+ include Rush::Commands
11
+
12
+ def entries
13
+ self
14
+ end
15
+
16
+ include Rush::FindBy
17
+
18
+ include Rush::HeadTail
19
+ end
@@ -0,0 +1,112 @@
1
+ # A rush box is a single unix machine - a server, workstation, or VPS instance.
2
+ #
3
+ # Specify a box by hostname (default = 'localhost'). If the box is remote, the
4
+ # first action performed will attempt to open an ssh tunnel. Use square
5
+ # brackets to access the filesystem, or processes to access the process list.
6
+ #
7
+ # Example:
8
+ #
9
+ # local = Rush::Box.new
10
+ # local['/etc/hosts'].contents
11
+ # local.processes
12
+ #
13
+ class Rush::Box
14
+ attr_reader :host
15
+
16
+ # Instantiate a box. No action is taken to make a connection until you try
17
+ # to perform an action. If the box is remote, an ssh tunnel will be opened.
18
+ # Specify a username with the host if the remote ssh user is different from
19
+ # the local one (e.g. Rush::Box.new('user@host')).
20
+ def initialize(host='localhost')
21
+ @host = host
22
+ end
23
+
24
+ def to_s # :nodoc:
25
+ host
26
+ end
27
+
28
+ def inspect # :nodoc:
29
+ host
30
+ end
31
+
32
+ # Access / on the box.
33
+ def filesystem
34
+ Rush::Entry.factory('/', self)
35
+ end
36
+
37
+ # Look up an entry on the filesystem, e.g. box['/path/to/some/file'].
38
+ # Returns a subclass of Rush::Entry - either Rush::Dir if you specifiy
39
+ # trailing slash, or Rush::File otherwise.
40
+ def [](key)
41
+ filesystem[key]
42
+ end
43
+
44
+ # Get the list of processes running on the box, not unlike "ps aux" in bash.
45
+ # Returns a Rush::ProcessSet.
46
+ def processes
47
+ Rush::ProcessSet.new(
48
+ connection.processes.map do |ps|
49
+ Rush::Process.new(ps, self)
50
+ end
51
+ )
52
+ end
53
+
54
+ # Execute a command in the standard unix shell. Returns the contents of
55
+ # stdout if successful, or raises Rush::BashFailed with the output of stderr
56
+ # if the shell returned a non-zero value. Options:
57
+ #
58
+ # :user => unix username to become via sudo
59
+ # :env => hash of environment variables
60
+ # :background => run in the background (returns Rush::Process instead of stdout)
61
+ #
62
+ # Examples:
63
+ #
64
+ # box.bash '/etc/init.d/mysql restart', :user => 'root'
65
+ # box.bash 'rake db:migrate', :user => 'www', :env => { :RAILS_ENV => 'production' }
66
+ # box.bash 'mongrel_rails start', :background => true
67
+ #
68
+ def bash(command, options={})
69
+ cmd_with_env = command_with_environment(command, options[:env])
70
+
71
+ if options[:background]
72
+ pid = connection.bash(cmd_with_env, options[:user], true)
73
+ processes.find_by_pid(pid)
74
+ else
75
+ connection.bash(cmd_with_env, options[:user], false)
76
+ end
77
+ end
78
+
79
+ def command_with_environment(command, env) # :nodoc:
80
+ return command unless env
81
+
82
+ vars = env.map do |key, value|
83
+ "export #{key}='#{value}'"
84
+ end
85
+ vars.push(command).join("\n")
86
+ end
87
+
88
+ # Returns true if the box is responding to commands.
89
+ def alive?
90
+ connection.alive?
91
+ end
92
+
93
+ # This is called automatically the first time an action is invoked, but you
94
+ # may wish to call it manually ahead of time in order to have the tunnel
95
+ # already set up and running. You can also use this to pass a timeout option,
96
+ # either :timeout => (seconds) or :timeout => :infinite.
97
+ def establish_connection(options={})
98
+ connection.ensure_tunnel(options)
99
+ end
100
+
101
+ def connection # :nodoc:
102
+ @connection ||= make_connection
103
+ end
104
+
105
+ def make_connection # :nodoc:
106
+ host == 'localhost' ? Rush::Connection::Local.new : Rush::Connection::Remote.new(host)
107
+ end
108
+
109
+ def ==(other) # :nodoc:
110
+ host == other.host
111
+ end
112
+ end
@@ -0,0 +1,55 @@
1
+ # The commands module contains operations against Rush::File entries, and is
2
+ # mixed in to Rush::Entry and Array. This means you can run these commands against a single
3
+ # file, a dir full of files, or an arbitrary list of files.
4
+ #
5
+ # Examples:
6
+ #
7
+ # box['/etc/hosts'].search /localhost/ # single file
8
+ # box['/etc/'].search /localhost/ # entire directory
9
+ # box['/etc/**/*.conf'].search /localhost/ # arbitrary list
10
+ module Rush::Commands
11
+ # The entries command must return an array of Rush::Entry items. This
12
+ # varies by class that it is mixed in to.
13
+ def entries
14
+ raise "must define me in class mixed in to for command use"
15
+ end
16
+
17
+ # Search file contents for a regular expression. A Rush::SearchResults
18
+ # object is returned.
19
+ def search(pattern)
20
+ results = Rush::SearchResults.new(pattern)
21
+ entries.each do |entry|
22
+ if !entry.dir? and matches = entry.search(pattern)
23
+ results.add(entry, matches)
24
+ end
25
+ end
26
+ results
27
+ end
28
+
29
+ # Search and replace file contents.
30
+ def replace_contents!(pattern, with_text)
31
+ entries.each do |entry|
32
+ entry.replace_contents!(pattern, with_text) unless entry.dir?
33
+ end
34
+ end
35
+
36
+ # Count the number of lines in the contained files.
37
+ def line_count
38
+ entries.inject(0) do |count, entry|
39
+ count += entry.lines.size if !entry.dir?
40
+ count
41
+ end
42
+ end
43
+
44
+ # Invoke vi on one or more files - only works locally.
45
+ def vi(*args)
46
+ names = entries.map { |f| f.quoted_path }.join(' ')
47
+ system "vim #{names} #{args.join(' ')}"
48
+ end
49
+
50
+ # Invoke TextMate on one or more files - only works locally.
51
+ def mate(*args)
52
+ names = entries.map { |f| f.quoted_path }.join(' ')
53
+ system "mate #{names} #{args.join(' ')}"
54
+ end
55
+ end