rush2 0.7.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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +86 -0
  4. data/README.rdoc +125 -0
  5. data/Rakefile +57 -0
  6. data/VERSION +1 -0
  7. data/bin/rush +13 -0
  8. data/bin/rushd +7 -0
  9. data/lib/rush.rb +91 -0
  10. data/lib/rush/access.rb +121 -0
  11. data/lib/rush/array_ext.rb +19 -0
  12. data/lib/rush/box.rb +124 -0
  13. data/lib/rush/commands.rb +76 -0
  14. data/lib/rush/config.rb +147 -0
  15. data/lib/rush/dir.rb +166 -0
  16. data/lib/rush/embeddable_shell.rb +26 -0
  17. data/lib/rush/entry.rb +229 -0
  18. data/lib/rush/exceptions.rb +32 -0
  19. data/lib/rush/file.rb +94 -0
  20. data/lib/rush/find_by.rb +39 -0
  21. data/lib/rush/fixnum_ext.rb +18 -0
  22. data/lib/rush/head_tail.rb +11 -0
  23. data/lib/rush/local.rb +377 -0
  24. data/lib/rush/process.rb +59 -0
  25. data/lib/rush/process_set.rb +62 -0
  26. data/lib/rush/remote.rb +33 -0
  27. data/lib/rush/search_results.rb +71 -0
  28. data/lib/rush/shell.rb +111 -0
  29. data/lib/rush/shell/completion.rb +110 -0
  30. data/lib/rush/string_ext.rb +16 -0
  31. data/spec/access_spec.rb +134 -0
  32. data/spec/array_ext_spec.rb +15 -0
  33. data/spec/base.rb +22 -0
  34. data/spec/box_spec.rb +76 -0
  35. data/spec/commands_spec.rb +47 -0
  36. data/spec/config_spec.rb +108 -0
  37. data/spec/dir_spec.rb +163 -0
  38. data/spec/embeddable_shell_spec.rb +17 -0
  39. data/spec/entry_spec.rb +133 -0
  40. data/spec/file_spec.rb +83 -0
  41. data/spec/find_by_spec.rb +58 -0
  42. data/spec/fixnum_ext_spec.rb +19 -0
  43. data/spec/local_spec.rb +365 -0
  44. data/spec/process_set_spec.rb +50 -0
  45. data/spec/process_spec.rb +73 -0
  46. data/spec/remote_spec.rb +140 -0
  47. data/spec/rush_spec.rb +28 -0
  48. data/spec/search_results_spec.rb +44 -0
  49. data/spec/shell_spec.rb +35 -0
  50. data/spec/ssh_tunnel_spec.rb +122 -0
  51. data/spec/string_ext_spec.rb +23 -0
  52. metadata +209 -0
@@ -0,0 +1,33 @@
1
+ require 'net/ssh'
2
+
3
+ module Rush
4
+ # wrapper of command
5
+ # sshfs '-o idmap=user <user_name>@<server_address>:<path> <local_path>'
6
+ #
7
+ class Connection::Remote
8
+ attr_reader :local_path, :full_remote_path, :remote_path, :remote_server, :remote_user
9
+
10
+ def initialize(full_remote_path, local_path)
11
+ local_path = local_path.full_path if local_path.respond_to?(:full_path)
12
+ @full_remote_path = full_remote_path
13
+ @local_path = Rush::Dir.new(local_path)
14
+ @local_path.create unless @local_path.exists?
15
+ @remote_user, server_and_path = *full_remote_path.split('@', 2)
16
+ @remote_server, @remote_address = *server_and_path.split(':', 2)
17
+ end
18
+
19
+ def connect
20
+ system "sshfs -o idmap=user #{full_remote_path} #{local_path}"
21
+ end
22
+ alias_method :mount, :connect
23
+
24
+ def disconnect
25
+ system "fusermount -u #{local_path.full_path}"
26
+ end
27
+ alias_method :umount, :disconnect
28
+
29
+ def method_missing(meth, *args, &block)
30
+ @local_path.send(meth, *args, &block)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,71 @@
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
+ include Rush::Commands
15
+ include Enumerable
16
+
17
+ attr_reader :entries, :lines, :entries_with_lines, :pattern
18
+
19
+ # Make a blank container. Track the pattern so that we can colorize the
20
+ # output to show what was matched.
21
+ def initialize(pattern)
22
+ # Duplication of data, but this lets us return everything in the exact
23
+ # order it was received.
24
+ @pattern = pattern
25
+ @entries = []
26
+ @entries_with_lines = {}
27
+ @lines = []
28
+ end
29
+
30
+ # Add a Rush::Entry and the array of string matches.
31
+ def add(entry, lines)
32
+ # this assumes that entry is unique
33
+ @entries << entry
34
+ @entries_with_lines[entry] = lines
35
+ @lines += lines
36
+ self
37
+ end
38
+ alias_method :<<, :add
39
+
40
+ def to_s
41
+ widest = entries.map { |k| k.full_path.length }.max
42
+ entries_with_lines.inject('') do |result, (entry, lines)|
43
+ result << entry.full_path
44
+ result << ' ' * (widest - entry.full_path.length + 2)
45
+ result << "=> "
46
+ result << colorize(lines.first.strip.head(30))
47
+ lines.each { |line| result << "\t" << line << "\n" }
48
+ result << "\n"
49
+ end
50
+ end
51
+
52
+ def each(&block)
53
+ @entries.each(&block)
54
+ end
55
+
56
+ def colorize(line)
57
+ lowlight + line.gsub(/(#{pattern.source})/, "#{hilight}\\1#{lowlight}") + normal
58
+ end
59
+
60
+ def hilight
61
+ "\e[34;1m"
62
+ end
63
+
64
+ def lowlight
65
+ "\e[37;2m"
66
+ end
67
+
68
+ def normal
69
+ "\e[0m"
70
+ end
71
+ end
@@ -0,0 +1,111 @@
1
+ require_relative 'shell/completion'
2
+ require 'coolline'
3
+ require 'coderay'
4
+ require 'pp'
5
+
6
+ module Rush
7
+ # Rush::Shell is used to create an interactive shell.
8
+ # It is invoked by the rush binary.
9
+ #
10
+ class Shell
11
+ include Rush::Completion
12
+
13
+ attr_accessor :suppress_output, :config, :history, :pure_binding
14
+ # Set up the user's environment, including a pure binding into which
15
+ # env.rb and commands.rb are mixed.
16
+ def initialize
17
+ root = Rush::Dir.new('/')
18
+ home = Rush::Dir.new(ENV['HOME']) if ENV['HOME']
19
+ pwd = Rush::Dir.new(ENV['PWD']) if ENV['PWD']
20
+
21
+ @config = Rush::Config.new
22
+ @history = Coolline::History.new config.history_file.full_path
23
+ @readline = Coolline.new do |c|
24
+ c.transform_proc = proc { syntax_highlight c.line }
25
+ c.completion_proc = proc { complete c.completed_word }
26
+ end
27
+
28
+ @box = Rush::Box.new
29
+ @pure_binding = @box.instance_eval 'binding'
30
+ $last_res = nil
31
+
32
+ eval config.load_env, @pure_binding
33
+ commands = config.load_commands
34
+ Rush::Dir.class_eval commands
35
+ Rush::File.class_eval commands
36
+ Array.class_eval commands
37
+
38
+ # Multiline commands should be stored somewhere
39
+ @multiline_cmd = ''
40
+ end
41
+
42
+ # Run a single command.
43
+ def execute(cmd)
44
+ res = eval(@multiline_cmd << "\n" << cmd, @pure_binding)
45
+ $last_res = res
46
+ eval('_ = $last_res', @pure_binding)
47
+ @multiline_cmd = ''
48
+ print_result res
49
+ rescue SyntaxError => e
50
+ unless e.message.include? 'unexpected end-of-input'
51
+ @multiline_cmd = ''
52
+ puts "Exception #{e.class} -> #{e.message}"
53
+ end
54
+ # Else it should be multiline command.
55
+ rescue Rush::Exception => e
56
+ puts "Exception #{e.class} -> #{e.message}"
57
+ @multiline_cmd = ''
58
+ rescue ::Exception => e
59
+ puts "Exception #{e.class} -> #{e.message}"
60
+ e.backtrace.each { |t| puts "\t#{::File.expand_path(t)}" }
61
+ @multiline_cmd = ''
62
+ end
63
+
64
+ # Run the interactive shell using coolline.
65
+ def run
66
+ loop do
67
+ prompt = self.class.prompt || "#{`whoami`.chomp} $ "
68
+ cmd = @readline.readline prompt
69
+ finish if cmd.nil? || cmd == 'exit'
70
+ next if cmd.empty?
71
+ @history << cmd
72
+ execute cmd
73
+ end
74
+ end
75
+
76
+ # Tune the prompt with
77
+ # Rush::Shell.prompt = 'hey there! > '
78
+ class << self
79
+ attr_accessor :prompt
80
+ end
81
+
82
+ # Save history to ~/.rush/history when the shell exists.
83
+ def finish
84
+ puts
85
+ exit
86
+ end
87
+
88
+ # Nice printing of different return types, particularly Rush::SearchResults.
89
+ #
90
+ def print_result(res)
91
+ return if suppress_output
92
+ if res.is_a? String
93
+ output = res
94
+ elsif res.is_a? Rush::SearchResults
95
+ output = res.to_s <<
96
+ "#{res.entries.size} matching files with #{res.lines.size} lines"
97
+ elsif res.respond_to? :each
98
+ output = res.pretty_inspect
99
+ else
100
+ output = " = #{res.inspect}"
101
+ end
102
+ output.lines.count > 5 ? output.less : puts(output)
103
+ end
104
+
105
+ # Syntax highlighting with coderay.
106
+ #
107
+ def syntax_highlight(input)
108
+ CodeRay.encode input, :ruby, :term
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,110 @@
1
+ module Rush
2
+ # Paths, executables and methods copmletion module.
3
+ #
4
+ module Completion
5
+ # The complete method itself.
6
+ # @param input [String] part of line from last space/beginning of line.
7
+ # @returns [String] completed string.
8
+ #
9
+ # Types of completed lines:
10
+ # kernel constants, global variables, executables:
11
+ # CON<tab> -- constant;
12
+ # met<tab> -- method/variable name;
13
+ # execu<tab> -- executable file from PATH environment variable;
14
+ # given module/class constants:
15
+ # Object::CON<tab> -- constant of given module/class;
16
+ # methods:
17
+ # Object.met<tab> -- method of object;
18
+ # Object.method1.meth<tab>;
19
+ # variable.met<tab>;
20
+ # variable.method1.method2.met<tab> -- method number N in chain;
21
+ # paths:
22
+ # box['pa<tab. -- relative to box path;
23
+ # box['path/to/fi<tab> -- relative path with multiple lvls;
24
+ # box/'pa<tab> -- another syntax to relative path;
25
+ # box/'path/to/fi<tab> -- the same;
26
+ #
27
+ def complete(input)
28
+ TEMPLATES.values
29
+ .select { |x| x[:regexp].match input }
30
+ .map { |x| send x[:method], input }
31
+ .flatten
32
+ .compact
33
+ end
34
+
35
+ TEMPLATES = {
36
+ constant: {
37
+ regexp: /[A-Z](\w|_)+/,
38
+ method: :complete_constant
39
+ },
40
+ object_constant: {
41
+ regexp: /^([A-Z](\w|_)+::)+[A-Z](\w|_)+$/,
42
+ method: :complete_object_constant
43
+ },
44
+ global_method: {
45
+ regexp: /^[a-z](\w|_)+$/,
46
+ method: :complete_global_method
47
+ },
48
+ method: {
49
+ regexp: /^(((\w|_)+(\.|::))+)((\w|_)+)$/,
50
+ method: :complete_method
51
+ },
52
+ path: {
53
+ regexp: /^(\w|_|.|:)+[\[\/][\'\"].+$/,
54
+ method: :complete_path
55
+ }
56
+ }
57
+
58
+ def complete_constant(input)
59
+ Object.constants.map(&:to_s).select { |x| x.start_with? input }
60
+ end
61
+
62
+ def complete_object_constant(input)
63
+ receiver, delimiter, const_part = *input.rpartition('::')
64
+ eval(receiver, pure_binding).constants
65
+ .map(&:to_s)
66
+ .select { |x| x.start_with? const_part }
67
+ .map { |x| receiver + delimiter + x }
68
+ end
69
+
70
+ def complete_global_method(input)
71
+ complete_for(pure_binding, input)
72
+ end
73
+
74
+ def complete_method(input)
75
+ receiver, delimiter, method_part = *input.rpartition('.')
76
+ the_binding = eval(receiver, pure_binding).instance_eval('binding')
77
+ complete_for(the_binding, method_part)
78
+ .map { |x| receiver + delimiter + x }
79
+ end
80
+
81
+ def complete_for(the_binding, method_part)
82
+ lvars = eval('local_variables', the_binding)
83
+ gvars = eval('global_variables', the_binding)
84
+ ivars = eval('instance_variables', the_binding)
85
+ mets = eval('methods', the_binding)
86
+ (executables + lvars + gvars + ivars + mets)
87
+ .map(&:to_s)
88
+ .select { |e| e.start_with? method_part }
89
+ end
90
+
91
+ def executables
92
+ ENV['PATH'].split(':')
93
+ .map { |x| Rush::Dir.new(x).entries.map(&:name) }
94
+ .flatten
95
+ end
96
+
97
+ def complete_path(input)
98
+ delimiters = %w([' [" /' /")
99
+ delimiter = delimiters.find { |x| input.include? x }
100
+ object, _, path = *input.rpartition(delimiter)
101
+ path_done, _, path_part = path.rpartition('/')
102
+ return [] unless eval(object, pure_binding).class == Rush::Dir
103
+ box = eval(object + "/'" + path_done + "'", pure_binding)
104
+ box.entries
105
+ .map(&:name)
106
+ .select { |x| x.start_with? path_part }
107
+ .map { |x| object + delimiter + path_done + '/' + x }
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,16 @@
1
+ class String
2
+ include Rush::HeadTail
3
+
4
+ def less
5
+ IO.popen('less -R', 'w') { |f| f.puts self }
6
+ end
7
+ alias_method :pager, :less
8
+
9
+ def dir?
10
+ ::Dir.exists? self
11
+ end
12
+
13
+ def locate
14
+ Rush::Dir.new(ENV['HOME']).locate self
15
+ end
16
+ end
@@ -0,0 +1,134 @@
1
+ require_relative '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