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