adamwiggins-rush 0.6.1

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 (50) hide show
  1. data/README.rdoc +87 -0
  2. data/Rakefile +61 -0
  3. data/VERSION +1 -0
  4. data/bin/rush +13 -0
  5. data/bin/rushd +7 -0
  6. data/lib/rush/access.rb +130 -0
  7. data/lib/rush/array_ext.rb +19 -0
  8. data/lib/rush/box.rb +112 -0
  9. data/lib/rush/commands.rb +55 -0
  10. data/lib/rush/config.rb +154 -0
  11. data/lib/rush/dir.rb +160 -0
  12. data/lib/rush/embeddable_shell.rb +26 -0
  13. data/lib/rush/entry.rb +185 -0
  14. data/lib/rush/exceptions.rb +31 -0
  15. data/lib/rush/file.rb +85 -0
  16. data/lib/rush/find_by.rb +39 -0
  17. data/lib/rush/fixnum_ext.rb +18 -0
  18. data/lib/rush/head_tail.rb +11 -0
  19. data/lib/rush/local.rb +402 -0
  20. data/lib/rush/process.rb +59 -0
  21. data/lib/rush/process_set.rb +62 -0
  22. data/lib/rush/remote.rb +156 -0
  23. data/lib/rush/search_results.rb +58 -0
  24. data/lib/rush/server.rb +117 -0
  25. data/lib/rush/shell.rb +187 -0
  26. data/lib/rush/ssh_tunnel.rb +122 -0
  27. data/lib/rush/string_ext.rb +3 -0
  28. data/lib/rush.rb +87 -0
  29. data/spec/access_spec.rb +134 -0
  30. data/spec/array_ext_spec.rb +15 -0
  31. data/spec/base.rb +24 -0
  32. data/spec/box_spec.rb +64 -0
  33. data/spec/commands_spec.rb +47 -0
  34. data/spec/config_spec.rb +108 -0
  35. data/spec/dir_spec.rb +164 -0
  36. data/spec/embeddable_shell_spec.rb +17 -0
  37. data/spec/entry_spec.rb +133 -0
  38. data/spec/file_spec.rb +83 -0
  39. data/spec/find_by_spec.rb +58 -0
  40. data/spec/fixnum_ext_spec.rb +19 -0
  41. data/spec/local_spec.rb +352 -0
  42. data/spec/process_set_spec.rb +50 -0
  43. data/spec/process_spec.rb +73 -0
  44. data/spec/remote_spec.rb +140 -0
  45. data/spec/rush_spec.rb +28 -0
  46. data/spec/search_results_spec.rb +44 -0
  47. data/spec/shell_spec.rb +23 -0
  48. data/spec/ssh_tunnel_spec.rb +122 -0
  49. data/spec/string_ext_spec.rb +23 -0
  50. metadata +141 -0
@@ -0,0 +1,58 @@
1
+ # An instance of this class is returned by Rush::Commands#search. It contains
2
+ # both the list of entries which matched the search, as well as the raw line
3
+ # matches. These methods get equivalent functionality to "grep -l" and "grep -h".
4
+ #
5
+ # SearchResults mixes in Rush::Commands so that you can chain multiple searches
6
+ # or do file operations on the resulting entries.
7
+ #
8
+ # Examples:
9
+ #
10
+ # myproj['**/*.rb'].search(/class/).entries.size
11
+ # myproj['**/*.rb'].search(/class/).lines.size
12
+ # myproj['**/*.rb'].search(/class/).copy_to other_dir
13
+ class Rush::SearchResults
14
+ attr_reader :entries, :lines, :entries_with_lines, :pattern
15
+
16
+ # Make a blank container. Track the pattern so that we can colorize the
17
+ # output to show what was matched.
18
+ def initialize(pattern)
19
+ # Duplication of data, but this lets us return everything in the exact
20
+ # order it was received.
21
+ @pattern = pattern
22
+ @entries = []
23
+ @entries_with_lines = {}
24
+ @lines = []
25
+ end
26
+
27
+ # Add a Rush::Entry and the array of string matches.
28
+ def add(entry, lines)
29
+ # this assumes that entry is unique
30
+ @entries << entry
31
+ @entries_with_lines[entry] = lines
32
+ @lines += lines
33
+ end
34
+
35
+ include Rush::Commands
36
+
37
+ def each(&block)
38
+ @entries.each(&block)
39
+ end
40
+
41
+ include Enumerable
42
+
43
+ def colorize(line)
44
+ lowlight + line.gsub(/(#{pattern.source})/, "#{hilight}\\1#{lowlight}") + normal
45
+ end
46
+
47
+ def hilight
48
+ "\e[34;1m"
49
+ end
50
+
51
+ def lowlight
52
+ "\e[37;2m"
53
+ end
54
+
55
+ def normal
56
+ "\e[0m"
57
+ end
58
+ end
@@ -0,0 +1,117 @@
1
+ require 'rubygems'
2
+ require 'mongrel'
3
+ require 'base64'
4
+
5
+ # Mongrel handler that translates the incoming HTTP request into a
6
+ # Rush::Connection::Local call. The results are sent back across the wire to
7
+ # be decoded by Rush::Connection::Remote on the other side.
8
+ class RushHandler < Mongrel::HttpHandler
9
+ def process(request, response)
10
+ params = {}
11
+ request.params['QUERY_STRING'].split("?").last.split("&").each do |tuple|
12
+ key, value = tuple.split("=")
13
+ params[key.to_sym] = value
14
+ end
15
+
16
+ unless authorize(request.params['HTTP_AUTHORIZATION'])
17
+ response.start(401) do |head, out|
18
+ end
19
+ else
20
+ payload = request.body.read
21
+
22
+ without_action = params
23
+ without_action.delete(params[:action])
24
+
25
+ msg = sprintf "%-20s", params[:action]
26
+ msg += without_action.inspect
27
+ msg += " + #{payload.size} bytes of payload" if payload.size > 0
28
+ log msg
29
+
30
+ params[:payload] = payload
31
+
32
+ begin
33
+ result = box.connection.receive(params)
34
+
35
+ response.start(200) do |head, out|
36
+ out.write result
37
+ end
38
+ rescue Rush::Exception => e
39
+ response.start(400) do |head, out|
40
+ out.write "#{e.class}\n#{e.message}\n"
41
+ end
42
+ end
43
+ end
44
+ rescue Exception => e
45
+ log e.full_display
46
+ end
47
+
48
+ def authorize(auth)
49
+ unless m = auth.match(/^Basic (.+)$/)
50
+ log "Request with no authorization data"
51
+ return false
52
+ end
53
+
54
+ decoded = Base64.decode64(m[1])
55
+ user, password = decoded.split(':', 2)
56
+
57
+ if user.nil? or user.length == 0 or password.nil? or password.length == 0
58
+ log "Malformed user or password"
59
+ return false
60
+ end
61
+
62
+ if password == config.passwords[user]
63
+ return true
64
+ else
65
+ log "Access denied to #{user}"
66
+ return false
67
+ end
68
+ end
69
+
70
+ def box
71
+ @box ||= Rush::Box.new('localhost')
72
+ end
73
+
74
+ def config
75
+ @config ||= Rush::Config.new
76
+ end
77
+
78
+ def log(msg)
79
+ File.open('rushd.log', 'a') do |f|
80
+ f.puts "#{Time.now.strftime('%Y-%m-%d %H:%I:%S')} :: #{msg}"
81
+ end
82
+ end
83
+ end
84
+
85
+ # A container class to run the Mongrel server for rushd.
86
+ class RushServer
87
+ def run
88
+ host = "127.0.0.1"
89
+ port = Rush::Config::DefaultPort
90
+
91
+ rushd = RushHandler.new
92
+ rushd.log "rushd listening on #{host}:#{port}"
93
+
94
+ h = Mongrel::HttpServer.new(host, port)
95
+ h.register("/", rushd)
96
+ h.run.join
97
+ end
98
+ end
99
+
100
+ class Exception
101
+ def full_display
102
+ out = []
103
+ out << "Exception #{self.class} => #{self}"
104
+ out << "Backtrace:"
105
+ out << self.filtered_backtrace.collect do |t|
106
+ " #{t}"
107
+ end
108
+ out << ""
109
+ out.join("\n")
110
+ end
111
+
112
+ def filtered_backtrace
113
+ backtrace.reject do |bt|
114
+ bt.match(/^\/usr\//)
115
+ end
116
+ end
117
+ end
data/lib/rush/shell.rb ADDED
@@ -0,0 +1,187 @@
1
+ require 'readline'
2
+
3
+ # Rush::Shell is used to create an interactive shell. It is invoked by the rush binary.
4
+ module Rush
5
+ class Shell
6
+ attr_accessor :suppress_output
7
+ # Set up the user's environment, including a pure binding into which
8
+ # env.rb and commands.rb are mixed.
9
+ def initialize
10
+ root = Rush::Dir.new('/')
11
+ home = Rush::Dir.new(ENV['HOME']) if ENV['HOME']
12
+ pwd = Rush::Dir.new(ENV['PWD']) if ENV['PWD']
13
+
14
+ @config = Rush::Config.new
15
+
16
+ @config.load_history.each do |item|
17
+ Readline::HISTORY.push(item)
18
+ end
19
+
20
+ Readline.basic_word_break_characters = ""
21
+ Readline.completion_append_character = nil
22
+ Readline.completion_proc = completion_proc
23
+
24
+ @box = Rush::Box.new
25
+ @pure_binding = @box.instance_eval "binding"
26
+ $last_res = nil
27
+
28
+ eval @config.load_env, @pure_binding
29
+
30
+ commands = @config.load_commands
31
+ Rush::Dir.class_eval commands
32
+ Array.class_eval commands
33
+ end
34
+
35
+ # Run a single command.
36
+ def execute(cmd)
37
+ res = eval(cmd, @pure_binding)
38
+ $last_res = res
39
+ eval("_ = $last_res", @pure_binding)
40
+ print_result res
41
+ rescue Rush::Exception => e
42
+ puts "Exception #{e.class} -> #{e.message}"
43
+ rescue ::Exception => e
44
+ puts "Exception #{e.class} -> #{e.message}"
45
+ e.backtrace.each do |t|
46
+ puts " #{::File.expand_path(t)}"
47
+ end
48
+ end
49
+
50
+ # Run the interactive shell using readline.
51
+ def run
52
+ loop do
53
+ cmd = Readline.readline('rush> ')
54
+
55
+ finish if cmd.nil? or cmd == 'exit'
56
+ next if cmd == ""
57
+ Readline::HISTORY.push(cmd)
58
+
59
+ execute(cmd)
60
+ end
61
+ end
62
+
63
+ # Save history to ~/.rush/history when the shell exists.
64
+ def finish
65
+ @config.save_history(Readline::HISTORY.to_a)
66
+ puts
67
+ exit
68
+ end
69
+
70
+ # Nice printing of different return types, particularly Rush::SearchResults.
71
+ def print_result(res)
72
+ return if self.suppress_output
73
+ if res.kind_of? String
74
+ puts res
75
+ elsif res.kind_of? Rush::SearchResults
76
+ widest = res.entries.map { |k| k.full_path.length }.max
77
+ res.entries_with_lines.each do |entry, lines|
78
+ print entry.full_path
79
+ print ' ' * (widest - entry.full_path.length + 2)
80
+ print "=> "
81
+ print res.colorize(lines.first.strip.head(30))
82
+ print "..." if lines.first.strip.length > 30
83
+ if lines.size > 1
84
+ print " (plus #{lines.size - 1} more matches)"
85
+ end
86
+ print "\n"
87
+ end
88
+ puts "#{res.entries.size} matching files with #{res.lines.size} matching lines"
89
+ elsif res.respond_to? :each
90
+ counts = {}
91
+ res.each do |item|
92
+ puts item
93
+ counts[item.class] ||= 0
94
+ counts[item.class] += 1
95
+ end
96
+ if counts == {}
97
+ puts "=> (empty set)"
98
+ else
99
+ count_s = counts.map do |klass, count|
100
+ "#{count} x #{klass}"
101
+ end.join(', ')
102
+ puts "=> #{count_s}"
103
+ end
104
+ else
105
+ puts "=> #{res.inspect}"
106
+ end
107
+ end
108
+
109
+ def path_parts(input) # :nodoc:
110
+ case input
111
+ when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)([\[\/])(['"])([^\3]*)$/
112
+ $~.to_a.slice(1, 4).push($~.pre_match)
113
+ when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)(\.)(\w*)$/
114
+ $~.to_a.slice(1, 3).push($~.pre_match)
115
+ when /((?:@{1,2}|\$|)\w+)$/
116
+ $~.to_a.slice(1, 1).push(nil).push($~.pre_match)
117
+ else
118
+ [ nil, nil, nil ]
119
+ end
120
+ end
121
+
122
+ def complete_method(receiver, dot, partial_name, pre)
123
+ path = eval("#{receiver}.full_path", @pure_binding) rescue nil
124
+ box = eval("#{receiver}.box", @pure_binding) rescue nil
125
+ if path and box
126
+ (box[path].methods - Object.methods).select do |e|
127
+ e.match(/^#{Regexp.escape(partial_name)}/)
128
+ end.map do |e|
129
+ (pre || '') + receiver + dot + e
130
+ end
131
+ end
132
+ end
133
+
134
+ def complete_path(possible_var, accessor, quote, partial_path, pre) # :nodoc:
135
+ original_var, fixed_path = possible_var, ''
136
+ if /^(.+\/)([^\/]*)$/ === partial_path
137
+ fixed_path, partial_path = $~.captures
138
+ possible_var += "['#{fixed_path}']"
139
+ end
140
+ full_path = eval("#{possible_var}.full_path", @pure_binding) rescue nil
141
+ box = eval("#{possible_var}.box", @pure_binding) rescue nil
142
+ if full_path and box
143
+ Rush::Dir.new(full_path, box).entries.select do |e|
144
+ e.name.match(/^#{Regexp.escape(partial_path)}/)
145
+ end.map do |e|
146
+ (pre || '') + original_var + accessor + quote + fixed_path + e.name + (e.dir? ? "/" : "")
147
+ end
148
+ end
149
+ end
150
+
151
+ def complete_variable(partial_name, pre)
152
+ lvars = eval('local_variables', @pure_binding)
153
+ gvars = eval('global_variables', @pure_binding)
154
+ ivars = eval('instance_variables', @pure_binding)
155
+ (lvars + gvars + ivars).select do |e|
156
+ e.match(/^#{Regexp.escape(partial_name)}/)
157
+ end.map do |e|
158
+ (pre || '') + e
159
+ end
160
+ end
161
+
162
+ # Try to do tab completion on dir square brackets and slash accessors.
163
+ #
164
+ # Example:
165
+ #
166
+ # dir['subd # presing tab here will produce dir['subdir/ if subdir exists
167
+ # dir/'subd # presing tab here will produce dir/'subdir/ if subdir exists
168
+ #
169
+ # This isn't that cool yet, because it can't do multiple levels of subdirs.
170
+ # It does work remotely, though, which is pretty sweet.
171
+ def completion_proc
172
+ proc do |input|
173
+ receiver, accessor, *rest = path_parts(input)
174
+ if receiver
175
+ case accessor
176
+ when /^[\[\/]$/
177
+ complete_path(receiver, accessor, *rest)
178
+ when /^\.$/
179
+ complete_method(receiver, accessor, *rest)
180
+ when nil
181
+ complete_variable(receiver, *rest)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,122 @@
1
+ # Internal class for managing an ssh tunnel, across which relatively insecure
2
+ # HTTP commands can be sent by Rush::Connection::Remote.
3
+ class Rush::SshTunnel
4
+ def initialize(real_host)
5
+ @real_host = real_host
6
+ end
7
+
8
+ def host
9
+ 'localhost'
10
+ end
11
+
12
+ def port
13
+ @port
14
+ end
15
+
16
+ def ensure_tunnel(options={})
17
+ return if @port and tunnel_alive?
18
+
19
+ @port = config.tunnels[@real_host]
20
+
21
+ if !@port or !tunnel_alive?
22
+ setup_everything(options)
23
+ end
24
+ end
25
+
26
+ def setup_everything(options={})
27
+ display "Connecting to #{@real_host}..."
28
+ push_credentials
29
+ launch_rushd
30
+ establish_tunnel(options)
31
+ end
32
+
33
+ def push_credentials
34
+ display "Pushing credentials"
35
+ config.ensure_credentials_exist
36
+ ssh_append_to_credentials(config.credentials_file.contents.strip)
37
+ end
38
+
39
+ def ssh_append_to_credentials(string)
40
+ # the following horror is exactly why rush is needed
41
+ passwords_file = "~/.rush/passwords"
42
+ string = "'#{string}'"
43
+ ssh "M=`grep #{string} #{passwords_file} 2>/dev/null | wc -l`; if [ $M = 0 ]; then mkdir -p .rush; chmod 700 .rush; echo #{string} >> #{passwords_file}; chmod 600 #{passwords_file}; fi"
44
+ end
45
+
46
+ def launch_rushd
47
+ display "Launching rushd"
48
+ ssh("if [ `ps aux | grep rushd | grep -v grep | wc -l` -ge 1 ]; then exit; fi; rushd > /dev/null 2>&1 &")
49
+ end
50
+
51
+ def establish_tunnel(options={})
52
+ display "Establishing ssh tunnel"
53
+ @port = next_available_port
54
+
55
+ make_ssh_tunnel(options)
56
+
57
+ tunnels = config.tunnels
58
+ tunnels[@real_host] = @port
59
+ config.save_tunnels tunnels
60
+
61
+ sleep 0.5
62
+ end
63
+
64
+ def tunnel_options
65
+ {
66
+ :local_port => @port,
67
+ :remote_port => Rush::Config::DefaultPort,
68
+ :ssh_host => @real_host,
69
+ }
70
+ end
71
+
72
+ def tunnel_alive?
73
+ `#{tunnel_count_command}`.to_i > 0
74
+ end
75
+
76
+ def tunnel_count_command
77
+ "ps x | grep '#{ssh_tunnel_command_without_stall}' | grep -v grep | wc -l"
78
+ end
79
+
80
+ class SshFailed < Exception; end
81
+ class NoPortSelectedYet < Exception; end
82
+
83
+ def ssh(command)
84
+ raise SshFailed unless system("ssh #{@real_host} '#{command}'")
85
+ end
86
+
87
+ def make_ssh_tunnel(options={})
88
+ raise SshFailed unless system(ssh_tunnel_command(options))
89
+ end
90
+
91
+ def ssh_tunnel_command_without_stall
92
+ options = tunnel_options
93
+ raise NoPortSelectedYet unless options[:local_port]
94
+ "ssh -f -L #{options[:local_port]}:127.0.0.1:#{options[:remote_port]} #{options[:ssh_host]}"
95
+ end
96
+
97
+ def ssh_stall_command(options={})
98
+ if options[:timeout] == :infinite
99
+ "while [ 1 ]; do sleep 1000; done"
100
+ elsif options[:timeout].to_i > 10
101
+ "sleep #{options[:timeout].to_i}"
102
+ else
103
+ "sleep 9000"
104
+ end
105
+ end
106
+
107
+ def ssh_tunnel_command(options={})
108
+ ssh_tunnel_command_without_stall + ' "' + ssh_stall_command(options) + '"'
109
+ end
110
+
111
+ def next_available_port
112
+ (config.tunnels.values.max || Rush::Config::DefaultPort) + 1
113
+ end
114
+
115
+ def config
116
+ @config ||= Rush::Config.new
117
+ end
118
+
119
+ def display(msg)
120
+ puts msg
121
+ end
122
+ end
@@ -0,0 +1,3 @@
1
+ class String
2
+ include Rush::HeadTail
3
+ end
data/lib/rush.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'rubygems'
2
+
3
+ # The top-level Rush module has some convenience methods for accessing the
4
+ # local box.
5
+ module Rush
6
+ # Access the root filesystem of the local box. Example:
7
+ #
8
+ # Rush['/etc/hosts'].contents
9
+ #
10
+ def self.[](key)
11
+ box[key]
12
+ end
13
+
14
+ # Create a dir object from the path of a provided file. Example:
15
+ #
16
+ # Rush.dir(__FILE__).files
17
+ #
18
+ def self.dir(filename)
19
+ box[::File.expand_path(::File.dirname(filename)) + '/']
20
+ end
21
+
22
+ # Create a dir object based on the shell's current working directory at the
23
+ # time the program was run. Example:
24
+ #
25
+ # Rush.launch_dir.files
26
+ #
27
+ def self.launch_dir
28
+ box[::Dir.pwd + '/']
29
+ end
30
+
31
+ # Run a bash command in the root of the local machine. Equivalent to
32
+ # Rush::Box.new.bash.
33
+ def self.bash(command, options={})
34
+ box.bash(command, options)
35
+ end
36
+
37
+ # Pull the process list for the local machine. Example:
38
+ #
39
+ # Rush.processes.filter(:cmdline => /ruby/)
40
+ #
41
+ def self.processes
42
+ box.processes
43
+ end
44
+
45
+ # Get the process object for this program's PID. Example:
46
+ #
47
+ # puts "I'm using #{Rush.my_process.mem} blocks of memory"
48
+ #
49
+ def self.my_process
50
+ box.processes.filter(:pid => ::Process.pid).first
51
+ end
52
+
53
+ # Create a box object for localhost.
54
+ def self.box
55
+ @@box = Rush::Box.new
56
+ end
57
+
58
+ # Quote a path for use in backticks, say.
59
+ def self.quote(path)
60
+ path.gsub(/(?=[^a-zA-Z0-9_.\/\-\x7F-\xFF\n])/n, '\\').gsub(/\n/, "'\n'").sub(/^$/, "''")
61
+ end
62
+ end
63
+
64
+ module Rush::Connection; end
65
+
66
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
67
+
68
+ require 'rush/exceptions'
69
+ require 'rush/config'
70
+ require 'rush/commands'
71
+ require 'rush/access'
72
+ require 'rush/entry'
73
+ require 'rush/file'
74
+ require 'rush/dir'
75
+ require 'rush/search_results'
76
+ require 'rush/head_tail'
77
+ require 'rush/find_by'
78
+ require 'rush/string_ext'
79
+ require 'rush/fixnum_ext'
80
+ require 'rush/array_ext'
81
+ require 'rush/process'
82
+ require 'rush/process_set'
83
+ require 'rush/local'
84
+ require 'rush/remote'
85
+ require 'rush/ssh_tunnel'
86
+ require 'rush/box'
87
+ require 'rush/embeddable_shell'
@@ -0,0 +1,134 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe Rush::Access do
4
+ before do
5
+ @access = Rush::Access.new
6
+ end
7
+
8
+ it "has roles: user, group, other" do
9
+ @access.class.roles == %w(user group other)
10
+ end
11
+
12
+ it "has permissions: read, write, execute" do
13
+ @access.class.permissions == %w(read write execute)
14
+ end
15
+
16
+ it "gets parts from a one-part symbol like :user" do
17
+ @access.parts_from(:user).should == %w(user)
18
+ end
19
+
20
+ it "gets parts from a two-part symbol like :read_write" do
21
+ @access.parts_from(:read_write).should == %w(read write)
22
+ end
23
+
24
+ it "allows use of 'and' in multipart symbols, like :user_and_group" do
25
+ @access.parts_from(:user_and_group).should == %w(user group)
26
+ end
27
+
28
+ it "extract_list verifies that all the parts among the valid choices" do
29
+ @access.should_receive(:parts_from).with(:red_green).and_return(%w(red green))
30
+ @access.extract_list('type', :red_green, %w(red blue green)).should == %w(red green)
31
+ end
32
+
33
+ it "extract_list raises a BadAccessSpecifier when there is part not in the list of choices" do
34
+ lambda do
35
+ @access.extract_list('role', :user_bork, %w(user group))
36
+ end.should raise_error(Rush::BadAccessSpecifier, "Unrecognized role: bork")
37
+ end
38
+
39
+ it "sets one value in the matrix of permissions and roles" do
40
+ @access.set_matrix(%w(read), %w(user))
41
+ @access.user_can_read.should == true
42
+ end
43
+
44
+ it "sets two values in the matrix of permissions and roles" do
45
+ @access.set_matrix(%w(read), %w(user group))
46
+ @access.user_can_read.should == true
47
+ @access.group_can_read.should == true
48
+ end
49
+
50
+ it "sets four values in the matrix of permissions and roles" do
51
+ @access.set_matrix(%w(read write), %w(user group))
52
+ @access.user_can_read.should == true
53
+ @access.group_can_read.should == true
54
+ @access.user_can_write.should == true
55
+ @access.group_can_write.should == true
56
+ end
57
+
58
+ it "parse options hash" do
59
+ @access.parse(:user_can => :read)
60
+ @access.user_can_read.should == true
61
+ end
62
+
63
+ it "generates octal permissions from its member vars" do
64
+ @access.user_can_read = true
65
+ @access.octal_permissions.should == 0400
66
+ end
67
+
68
+ it "generates octal permissions from its member vars" do
69
+ @access.user_can_read = true
70
+ @access.user_can_write = true
71
+ @access.user_can_execute = true
72
+ @access.group_can_read = true
73
+ @access.group_can_execute = true
74
+ @access.octal_permissions.should == 0750
75
+ end
76
+
77
+ it "applies its settings to a file" do
78
+ file = "/tmp/rush_spec_#{Process.pid}"
79
+ begin
80
+ system "rm -rf #{file}; touch #{file}; chmod 770 #{file}"
81
+ @access.user_can_read = true
82
+ @access.apply(file)
83
+ `ls -l #{file}`.should match(/^-r--------/)
84
+ ensure
85
+ system "rm -rf #{file}; touch #{file}"
86
+ end
87
+ end
88
+
89
+ it "serializes itself to a hash" do
90
+ @access.user_can_read = true
91
+ @access.to_hash.should == {
92
+ :user_can_read => 1, :user_can_write => 0, :user_can_execute => 0,
93
+ :group_can_read => 0, :group_can_write => 0, :group_can_execute => 0,
94
+ :other_can_read => 0, :other_can_write => 0, :other_can_execute => 0,
95
+ }
96
+ end
97
+
98
+ it "unserializes from a hash" do
99
+ @access.from_hash(:user_can_read => '1')
100
+ @access.user_can_read.should == true
101
+ end
102
+
103
+ it "initializes from a serialized hash" do
104
+ @access.class.should_receive(:new).and_return(@access)
105
+ @access.class.from_hash(:user_can_read => '1').should == @access
106
+ @access.user_can_read.should == true
107
+ end
108
+
109
+ it "initializes from a parsed options hash" do
110
+ @access.class.should_receive(:new).and_return(@access)
111
+ @access.class.parse(:user_and_group_can => :read).should == @access
112
+ @access.user_can_read.should == true
113
+ end
114
+
115
+ it "converts and octal integer into an array of integers" do
116
+ @access.octal_integer_array(0740).should == [ 7, 4, 0 ]
117
+ end
118
+
119
+ it "filters out anything above the top three digits (File.stat returns some extra data there)" do
120
+ @access.octal_integer_array(0100644).should == [ 6, 4, 4 ]
121
+ end
122
+
123
+ it "taskes permissions from an octal representation" do
124
+ @access.from_octal(0644)
125
+ @access.user_can_read.should == true
126
+ @access.user_can_write.should == true
127
+ @access.user_can_execute.should == false
128
+ end
129
+
130
+ it "computes a display hash by dropping false keys and converting the 1s to trues" do
131
+ @access.should_receive(:to_hash).and_return(:red => 1, :green => 0, :blue => 1)
132
+ @access.display_hash.should == { :red => true, :blue => true }
133
+ end
134
+ end