rush3 3.0.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 +15 -0
  3. data/Gemfile.lock +93 -0
  4. data/README.rdoc +124 -0
  5. data/Rakefile +58 -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 +16 -0
  12. data/lib/rush/box.rb +130 -0
  13. data/lib/rush/commands.rb +94 -0
  14. data/lib/rush/config.rb +147 -0
  15. data/lib/rush/dir.rb +159 -0
  16. data/lib/rush/embeddable_shell.rb +26 -0
  17. data/lib/rush/entry.rb +238 -0
  18. data/lib/rush/exceptions.rb +32 -0
  19. data/lib/rush/file.rb +100 -0
  20. data/lib/rush/find_by.rb +39 -0
  21. data/lib/rush/head_tail.rb +11 -0
  22. data/lib/rush/integer_ext.rb +18 -0
  23. data/lib/rush/local.rb +404 -0
  24. data/lib/rush/path.rb +9 -0
  25. data/lib/rush/process.rb +59 -0
  26. data/lib/rush/process_set.rb +62 -0
  27. data/lib/rush/search_results.rb +71 -0
  28. data/lib/rush/shell.rb +118 -0
  29. data/lib/rush/shell/completion.rb +108 -0
  30. data/lib/rush/string_ext.rb +33 -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 +162 -0
  40. data/spec/file_spec.rb +95 -0
  41. data/spec/find_by_spec.rb +58 -0
  42. data/spec/integer_ext_spec.rb +19 -0
  43. data/spec/local_spec.rb +363 -0
  44. data/spec/path_spec.rb +13 -0
  45. data/spec/process_set_spec.rb +50 -0
  46. data/spec/process_spec.rb +89 -0
  47. data/spec/rush_spec.rb +28 -0
  48. data/spec/search_results_spec.rb +44 -0
  49. data/spec/shell_spec.rb +39 -0
  50. data/spec/ssh_tunnel_spec.rb +122 -0
  51. data/spec/string_ext_spec.rb +23 -0
  52. metadata +228 -0
@@ -0,0 +1,62 @@
1
+ # A container for processes that behaves like an array, and adds process-specific operations
2
+ # on the entire set, like kill.
3
+ #
4
+ # Example:
5
+ #
6
+ # processes.filter(:cmdline => /mongrel_rails/).kill
7
+ #
8
+ class Rush::ProcessSet
9
+ attr_reader :processes
10
+
11
+ def initialize(processes)
12
+ @processes = processes
13
+ end
14
+
15
+ # Filter by any field that the process responds to. Specify an exact value,
16
+ # or a regular expression. All conditions are put together as a boolean
17
+ # AND, so these two statements are equivalent:
18
+ #
19
+ # processes.filter(:uid => 501).filter(:cmdline => /ruby/)
20
+ # processes.filter(:uid => 501, :cmdline => /ruby/)
21
+ #
22
+ def filter(conditions)
23
+ Rush::ProcessSet.new(
24
+ processes.select do |p|
25
+ conditions.all? do |key, value|
26
+ value.class == Regexp ?
27
+ value.match(p.send(key)) :
28
+ p.send(key) == value
29
+ end
30
+ end
31
+ )
32
+ end
33
+
34
+ # Kill all processes in the set.
35
+ def kill(options={})
36
+ processes.each { |p| p.kill(options) }
37
+ end
38
+
39
+ # Check status of all processes in the set, returns an array of booleans.
40
+ def alive?
41
+ processes.map { |p| p.alive? }
42
+ end
43
+
44
+ include Enumerable
45
+
46
+ def each
47
+ processes.each { |p| yield p }
48
+ end
49
+
50
+ def ==(other)
51
+ if other.class == self.class
52
+ other.processes == processes
53
+ else
54
+ to_a == other
55
+ end
56
+ end
57
+
58
+ # All other messages (like size or first) are passed through to the array.
59
+ def method_missing(meth, *args)
60
+ processes.send(meth, *args)
61
+ end
62
+ 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,118 @@
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
+ @box = Rush::Box.new
23
+ @pure_binding = @box.instance_eval 'binding'
24
+ $last_res = nil
25
+ load_custom_commands
26
+ set_readline
27
+ @multiline_cmd = '' # Multiline commands should be stored somewhere
28
+ $last_backtrace = '' # Backtrace should too.
29
+ end
30
+
31
+ def set_readline
32
+ @history = Coolline::History.new config.history_file.full_path
33
+ Coolline::Settings[:word_boundaries] = [' ', "\t"]
34
+ Coolline::Settings[:completion_word_boundaries] = [' ', "\t"]
35
+ @readline = Coolline.new do |c|
36
+ c.transform_proc = proc { syntax_highlight c.line }
37
+ c.completion_proc = proc { complete c.completed_word }
38
+ end
39
+ end
40
+
41
+ def load_custom_commands
42
+ eval config.load_env, @pure_binding
43
+ commands = config.load_commands
44
+ [Rush::Dir, Rush::File, Array].each { |x| x.class_eval commands }
45
+ end
46
+
47
+ # Run the interactive shell using coolline.
48
+ def run
49
+ loop do
50
+ prompt = self.class.prompt || "#{`whoami`.chomp} $ "
51
+ cmd = @readline.readline prompt
52
+ finish if cmd.nil? || cmd == 'exit'
53
+ next if cmd.empty?
54
+ @history << cmd
55
+ execute cmd
56
+ end
57
+ end
58
+
59
+ # Run a single command.
60
+ def execute(cmd)
61
+ res = eval(@multiline_cmd << "\n" << cmd, @pure_binding)
62
+ $last_res = res
63
+ eval('_ = $last_res', @pure_binding)
64
+ @multiline_cmd = ''
65
+ print_result res
66
+ rescue SyntaxError => e
67
+ unless e.message.include? 'unexpected end-of-input'
68
+ @multiline_cmd = ''
69
+ puts "Exception #{e.class} -> #{e.message}"
70
+ end
71
+ # Else it should be multiline command.
72
+ rescue Rush::Exception => e
73
+ puts "Exception #{e.class} -> #{e.message}"
74
+ @multiline_cmd = ''
75
+ rescue ::Exception => e
76
+ puts "Exception #{e.class} -> #{e.message}"
77
+ $last_backtrace = e.backtrace
78
+ .map { |t| "\t#{::File.expand_path(t)}" }
79
+ .join("\n")
80
+ @multiline_cmd = ''
81
+ end
82
+
83
+ # Tune the prompt with
84
+ # Rush::Shell.prompt = 'hey there! > '
85
+ class << self
86
+ attr_accessor :prompt
87
+ end
88
+
89
+ # Save history to ~/.rush/history when the shell exists.
90
+ def finish
91
+ puts
92
+ exit
93
+ end
94
+
95
+ # Nice printing of different return types, particularly Rush::SearchResults.
96
+ #
97
+ def print_result(res)
98
+ return if suppress_output
99
+ if res.is_a? String
100
+ output = res
101
+ elsif res.is_a? Rush::SearchResults
102
+ output = res.to_s <<
103
+ "#{res.entries.size} matching files with #{res.lines.size} lines"
104
+ elsif res.respond_to? :each
105
+ output = res.pretty_inspect
106
+ else
107
+ output = " = #{res.inspect}"
108
+ end
109
+ output.lines.count > 5 ? output.less : puts(output)
110
+ end
111
+
112
+ # Syntax highlighting with coderay.
113
+ #
114
+ def syntax_highlight(input)
115
+ CodeRay.encode input, :ruby, :term
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,108 @@
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 }.sort
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
+ Rush::Path.executables
93
+ end
94
+
95
+ def complete_path(input)
96
+ delimiters = %w([' [" /' /")
97
+ delimiter = delimiters.find { |x| input.include? x }
98
+ object, _, path = *input.rpartition(delimiter)
99
+ path_done, _, path_part = path.rpartition('/')
100
+ return [] unless eval(object, pure_binding).class == Rush::Dir
101
+ box = eval(object + "/'" + path_done + "'", pure_binding)
102
+ box.entries
103
+ .map(&:name)
104
+ .select { |x| x.start_with? path_part }
105
+ .map { |x| object + delimiter + path_done + '/' + x }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,33 @@
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
+
17
+ def open_with(meth, *args, &block)
18
+ if executables.include? meth.to_s
19
+ system [meth.to_s, *args, self].join(' ')
20
+ else
21
+ raise 'No such executable. Maybe something wrong with PATH?'
22
+ end
23
+ end
24
+ alias_method :e, :open_with
25
+
26
+ def |(meth, *args, &block)
27
+ Open3.capture2(meth, stdin_data: self).first
28
+ end
29
+
30
+ def executables
31
+ Rush::Path.executables
32
+ end
33
+ 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
+ expect(@access.parts_from(:user)).to eq(%w(user))
18
+ end
19
+
20
+ it "gets parts from a two-part symbol like :read_write" do
21
+ expect(@access.parts_from(:read_write)).to eq(%w(read write))
22
+ end
23
+
24
+ it "allows use of 'and' in multipart symbols, like :user_and_group" do
25
+ expect(@access.parts_from(:user_and_group)).to eq( %w(user group))
26
+ end
27
+
28
+ it "extract_list verifies that all the parts among the valid choices" do
29
+ expect(@access).to receive(:parts_from).with(:red_green).and_return(%w(red green))
30
+ expect(@access.extract_list('type', :red_green, %w(red blue green))).to eq(%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
+ expect do
35
+ @access.extract_list('role', :user_bork, %w(user group))
36
+ end.to 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
+ expect(@access.user_can_read).to eq(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
+ expect(@access.user_can_read).to eq(true)
47
+ expect(@access.group_can_read).to eq(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
+ expect(@access.user_can_read).to eq(true)
53
+ expect(@access.group_can_read).to eq(true)
54
+ expect(@access.user_can_write).to eq(true)
55
+ expect(@access.group_can_write).to eq(true)
56
+ end
57
+
58
+ it "parse options hash" do
59
+ @access.parse(:user_can => :read)
60
+ expect(@access.user_can_read).to eq(true)
61
+ end
62
+
63
+ it "generates octal permissions from its member vars" do
64
+ @access.user_can_read = true
65
+ expect(@access.octal_permissions).to eq(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
+ expect(@access.octal_permissions).to eq(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
+ expect(`ls -l #{file}`).to 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
+ expect(@access.to_hash).to eq({
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
+ expect(@access.user_can_read).to eq(true)
101
+ end
102
+
103
+ it "initializes from a serialized hash" do
104
+ expect(@access.class).to receive(:new).and_return(@access)
105
+ expect(@access.class.from_hash(:user_can_read => '1')).to eq(@access)
106
+ expect(@access.user_can_read).to eq(true)
107
+ end
108
+
109
+ it "initializes from a parsed options hash" do
110
+ expect(@access.class).to receive(:new).and_return(@access)
111
+ expect(@access.class.parse(:user_and_group_can => :read)).to eq(@access)
112
+ expect(@access.user_can_read).to eq(true)
113
+ end
114
+
115
+ it "converts and octal integer into an array of integers" do
116
+ expect(@access.octal_integer_array(0740)).to eq([ 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
+ expect(@access.octal_integer_array(0100644)).to eq([ 6, 4, 4 ])
121
+ end
122
+
123
+ it "taskes permissions from an octal representation" do
124
+ @access.from_octal(0644)
125
+ expect(@access.user_can_read).to eq(true)
126
+ expect(@access.user_can_write).to eq(true)
127
+ expect(@access.user_can_execute).to eq(false)
128
+ end
129
+
130
+ it "computes a display hash by dropping false keys and converting the 1s to trues" do
131
+ expect(@access).to receive(:to_hash).and_return(:red => 1, :green => 0, :blue => 1)
132
+ expect(@access.display_hash).to eq({ :red => true, :blue => true })
133
+ end
134
+ end