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.
- checksums.yaml +7 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +86 -0
- data/README.rdoc +125 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/bin/rush +13 -0
- data/bin/rushd +7 -0
- data/lib/rush.rb +91 -0
- data/lib/rush/access.rb +121 -0
- data/lib/rush/array_ext.rb +19 -0
- data/lib/rush/box.rb +124 -0
- data/lib/rush/commands.rb +76 -0
- data/lib/rush/config.rb +147 -0
- data/lib/rush/dir.rb +166 -0
- data/lib/rush/embeddable_shell.rb +26 -0
- data/lib/rush/entry.rb +229 -0
- data/lib/rush/exceptions.rb +32 -0
- data/lib/rush/file.rb +94 -0
- data/lib/rush/find_by.rb +39 -0
- data/lib/rush/fixnum_ext.rb +18 -0
- data/lib/rush/head_tail.rb +11 -0
- data/lib/rush/local.rb +377 -0
- data/lib/rush/process.rb +59 -0
- data/lib/rush/process_set.rb +62 -0
- data/lib/rush/remote.rb +33 -0
- data/lib/rush/search_results.rb +71 -0
- data/lib/rush/shell.rb +111 -0
- data/lib/rush/shell/completion.rb +110 -0
- data/lib/rush/string_ext.rb +16 -0
- data/spec/access_spec.rb +134 -0
- data/spec/array_ext_spec.rb +15 -0
- data/spec/base.rb +22 -0
- data/spec/box_spec.rb +76 -0
- data/spec/commands_spec.rb +47 -0
- data/spec/config_spec.rb +108 -0
- data/spec/dir_spec.rb +163 -0
- data/spec/embeddable_shell_spec.rb +17 -0
- data/spec/entry_spec.rb +133 -0
- data/spec/file_spec.rb +83 -0
- data/spec/find_by_spec.rb +58 -0
- data/spec/fixnum_ext_spec.rb +19 -0
- data/spec/local_spec.rb +365 -0
- data/spec/process_set_spec.rb +50 -0
- data/spec/process_spec.rb +73 -0
- data/spec/remote_spec.rb +140 -0
- data/spec/rush_spec.rb +28 -0
- data/spec/search_results_spec.rb +44 -0
- data/spec/shell_spec.rb +35 -0
- data/spec/ssh_tunnel_spec.rb +122 -0
- data/spec/string_ext_spec.rb +23 -0
- metadata +209 -0
data/lib/rush/remote.rb
ADDED
@@ -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
|
data/lib/rush/shell.rb
ADDED
@@ -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
|
data/spec/access_spec.rb
ADDED
@@ -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
|