shell_shock 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -62
- data/example/adventure.rb +79 -0
- data/example/cat.rb +66 -0
- data/example/file_system.rb +95 -0
- data/lib/shell_shock/context.rb +6 -6
- data/lib/shell_shock/logger.rb +3 -2
- metadata +11 -14
data/README.rdoc
CHANGED
@@ -14,70 +14,10 @@ A library for creating command line shell application.
|
|
14
14
|
|
15
15
|
== Usage
|
16
16
|
|
17
|
-
The basic idea is that you create classes
|
17
|
+
The basic idea is that you create classes and include the ShellShock::Context mixin. Start a shell by instantiating the class and executing the push method. Any number of nested shells may be pushed and exit falls back to the previous shell.
|
18
18
|
|
19
19
|
These classes represent a shell context with a number of commands registered. These commands are matched with tab completion. In addition to registered commands, 'exit', 'quit', 'help' and '?' are always available.
|
20
20
|
|
21
21
|
Each command must have an execute method that accepts a string (the remaining content after the command name). Commands may also implement usage and help methods. They may also implement a completion method so may have arbitrarily complex completion rules per parameter.
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
require 'shell_shock/context'
|
26
|
-
|
27
|
-
class CatFileCommand
|
28
|
-
def initialize path
|
29
|
-
@path = path
|
30
|
-
end
|
31
|
-
|
32
|
-
def usage
|
33
|
-
'<file name>'
|
34
|
-
end
|
35
|
-
|
36
|
-
def help
|
37
|
-
'displays the content of a file'
|
38
|
-
end
|
39
|
-
|
40
|
-
def completion text
|
41
|
-
Dir.glob("#{@path}/#{text}*").select {|f| File.file?(f) }
|
42
|
-
end
|
43
|
-
|
44
|
-
def execute path
|
45
|
-
File.open(path) {|f| f.each_with_index {|l,i| puts "#{i+1}: #{l}" } }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
class ChangeDirectoryCommand
|
50
|
-
def initialize path
|
51
|
-
@path = path
|
52
|
-
end
|
53
|
-
|
54
|
-
def usage
|
55
|
-
'<directory name>'
|
56
|
-
end
|
57
|
-
|
58
|
-
def help
|
59
|
-
'switches to a new shell context in the specified directory'
|
60
|
-
end
|
61
|
-
|
62
|
-
def completion text
|
63
|
-
Dir.glob("#{@path}/#{text}*").select {|f| File.directory?(f) }
|
64
|
-
end
|
65
|
-
|
66
|
-
def execute text
|
67
|
-
DirectoryContext.new(text).push
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
class DirectoryContext
|
72
|
-
include ShellShock::Context
|
73
|
-
|
74
|
-
def initialize path
|
75
|
-
@prompt_text = "#{path} > "
|
76
|
-
@commands = {
|
77
|
-
'cd' => ChangeDirectoryCommand.new(path),
|
78
|
-
'cat' => CatFileCommand.new(path)
|
79
|
-
}
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
DirectoryContext.new('.').push
|
23
|
+
Refer to the examples directory for some sample shells.
|
@@ -0,0 +1,79 @@
|
|
1
|
+
$: << File.dirname(__FILE__)+'/../lib'
|
2
|
+
|
3
|
+
require 'shell_shock/context'
|
4
|
+
|
5
|
+
class MoveCommand
|
6
|
+
attr_reader :usage, :help
|
7
|
+
|
8
|
+
def initialize room
|
9
|
+
@room = room
|
10
|
+
@usage = '<direction>'
|
11
|
+
@help = 'moves to the adjoining room in the specified direction'
|
12
|
+
end
|
13
|
+
|
14
|
+
def completion text
|
15
|
+
@room.connections.keys.grep(/^#{text}/).sort
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute direction
|
19
|
+
room = @room.connections[direction]
|
20
|
+
if room
|
21
|
+
AdventureContext.new(room).push
|
22
|
+
else
|
23
|
+
puts "there is no adjoining room to the #{direction}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class AdventureContext
|
29
|
+
include ShellShock::Context
|
30
|
+
|
31
|
+
def initialize room
|
32
|
+
puts room.description
|
33
|
+
@prompt = "#{room.name} > "
|
34
|
+
add_command MoveCommand.new(room), 'go'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Room
|
39
|
+
attr_reader :name, :description, :connections
|
40
|
+
def initialize name, description
|
41
|
+
@name, @description = name, description
|
42
|
+
@connections = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def add direction, room
|
46
|
+
@connections[direction] = room
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
START = Room.new 'clearing', <<EOF
|
51
|
+
You have entered a clearing.
|
52
|
+
|
53
|
+
A dead goat lies on the ground in front of you
|
54
|
+
EOF
|
55
|
+
|
56
|
+
CAVE_ENTRANCE = Room.new 'cave entrance', <<EOF
|
57
|
+
You have arrived at the entrance to a cave.
|
58
|
+
|
59
|
+
A foul smell is emitting from the cave. Some smoke
|
60
|
+
can be seen off off to the east.
|
61
|
+
EOF
|
62
|
+
|
63
|
+
CAMP_SITE = Room.new 'camp site', <<EOF
|
64
|
+
You have arrived in a camp site.
|
65
|
+
|
66
|
+
There is a fire that has been recently put out.
|
67
|
+
EOF
|
68
|
+
|
69
|
+
CAVE = Room.new 'cave', <<EOF
|
70
|
+
You have entered a dark cave.
|
71
|
+
|
72
|
+
A faint growling sound can be heard.
|
73
|
+
EOF
|
74
|
+
|
75
|
+
START.add 'north', CAVE_ENTRANCE
|
76
|
+
CAVE_ENTRANCE.add 'east', CAMP_SITE
|
77
|
+
CAVE_ENTRANCE.add 'north', CAVE
|
78
|
+
|
79
|
+
AdventureContext.new(START).push
|
data/example/cat.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'shell_shock/context'
|
2
|
+
|
3
|
+
class Prompt
|
4
|
+
def say text=nil
|
5
|
+
puts text ? text : ''
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class CatFileCommand
|
10
|
+
def initialize path
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def usage
|
15
|
+
'<file name>'
|
16
|
+
end
|
17
|
+
|
18
|
+
def help
|
19
|
+
'displays the content of a file'
|
20
|
+
end
|
21
|
+
|
22
|
+
def completion text
|
23
|
+
Dir.glob("#{@path}/#{text}*").select {|f| File.file?(f) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute path
|
27
|
+
File.open(path) {|f| f.each_with_index {|l,i| puts "#{i+1}: #{l}" } }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ChangeDirectoryCommand
|
32
|
+
def initialize path
|
33
|
+
@path = path
|
34
|
+
end
|
35
|
+
|
36
|
+
def usage
|
37
|
+
'<directory name>'
|
38
|
+
end
|
39
|
+
|
40
|
+
def help
|
41
|
+
'switches to a new shell context in the specified directory'
|
42
|
+
end
|
43
|
+
|
44
|
+
def completion text
|
45
|
+
Dir.glob("#{@path}/#{text}*").select {|f| File.directory?(f) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def execute text
|
49
|
+
DirectoryContext.new(text).push
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class DirectoryContext
|
54
|
+
include ShellShock::Context
|
55
|
+
|
56
|
+
def initialize path
|
57
|
+
@io = Prompt.new
|
58
|
+
@prompt_text = "#{path} > "
|
59
|
+
@commands = {
|
60
|
+
'cd' => ChangeDirectoryCommand.new(path),
|
61
|
+
'cat' => CatFileCommand.new(path)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
DirectoryContext.new('.').push
|
@@ -0,0 +1,95 @@
|
|
1
|
+
$: << File.dirname(__FILE__)+'/../lib'
|
2
|
+
|
3
|
+
require 'shell_shock/context'
|
4
|
+
require 'find'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
class Finder
|
8
|
+
attr_reader :files
|
9
|
+
|
10
|
+
def initialize path
|
11
|
+
@path = path
|
12
|
+
end
|
13
|
+
|
14
|
+
def refresh pattern
|
15
|
+
files = []
|
16
|
+
here = Pathname.new(File.expand_path(@path))
|
17
|
+
Find.find(File.expand_path(@path)) do |p|
|
18
|
+
if File.basename(p).start_with?('.')
|
19
|
+
Find.prune
|
20
|
+
else
|
21
|
+
path = Pathname.new(p).relative_path_from(here).to_s
|
22
|
+
files << path if path.start_with?(pattern) and yield(p)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
files
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class CatFileCommand
|
30
|
+
include ShellShock::Logger
|
31
|
+
|
32
|
+
def initialize path
|
33
|
+
@finder = Finder.new path
|
34
|
+
end
|
35
|
+
|
36
|
+
def usage
|
37
|
+
'<file name>'
|
38
|
+
end
|
39
|
+
|
40
|
+
def help
|
41
|
+
'displays the content of a file'
|
42
|
+
end
|
43
|
+
|
44
|
+
def completion text
|
45
|
+
log { "cat command completing \"#{text}\"" }
|
46
|
+
@finder.refresh(text) {|path| File.file? path }
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute path=nil
|
50
|
+
return unless path
|
51
|
+
File.open(path) {|f| f.each_with_index {|l,i| puts "#{i+1}: #{l}" } }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class ChangeDirectoryCommand
|
56
|
+
include ShellShock::Logger
|
57
|
+
|
58
|
+
def initialize path
|
59
|
+
@path = path
|
60
|
+
@finder = Finder.new path
|
61
|
+
end
|
62
|
+
|
63
|
+
def usage
|
64
|
+
'<directory name>'
|
65
|
+
end
|
66
|
+
|
67
|
+
def help
|
68
|
+
'switches to a new shell context in the specified directory'
|
69
|
+
end
|
70
|
+
|
71
|
+
def completion text
|
72
|
+
log { "cd command completing \"#{text}\"" }
|
73
|
+
@finder.refresh(text) {|path| path != '.' and File.directory? path }
|
74
|
+
end
|
75
|
+
|
76
|
+
def execute text=nil
|
77
|
+
return unless text
|
78
|
+
log { "pushing new shell in \"#{text}\"" }
|
79
|
+
DirectoryContext.new(@path+'/'+text).push
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class DirectoryContext
|
84
|
+
include ShellShock::Context
|
85
|
+
|
86
|
+
def initialize path
|
87
|
+
@prompt_text = "#{path} > "
|
88
|
+
@commands = {
|
89
|
+
'cd' => ChangeDirectoryCommand.new(path),
|
90
|
+
'cat' => CatFileCommand.new(path)
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
DirectoryContext.new('.').push
|
data/lib/shell_shock/context.rb
CHANGED
@@ -20,24 +20,24 @@ module ShellShock
|
|
20
20
|
refresh_commands if respond_to?(:refresh_commands)
|
21
21
|
Readline.completer_word_break_characters = ''
|
22
22
|
Readline.completion_proc = lambda do |string|
|
23
|
-
log "trying completion for \"#{string}\""
|
23
|
+
log { "trying completion for \"#{string}\"" }
|
24
24
|
first, rest = head_tail(string)
|
25
|
-
log "split \"#{first}\" from \"#{rest}\""
|
25
|
+
log { "split \"#{first}\" from \"#{rest}\"" }
|
26
26
|
if first
|
27
27
|
command = @commands[first]
|
28
28
|
if command
|
29
|
-
log "matched #{first} command"
|
29
|
+
log { "matched #{first} command" }
|
30
30
|
if command.respond_to?(:completion)
|
31
31
|
completions = command.completion(rest).map {|c| "#{first} #{c}" }
|
32
32
|
else
|
33
|
-
log "#{first} has no completion proc"
|
33
|
+
log { "#{first} has no completion proc" }
|
34
34
|
completions = []
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
completions ||= @commands.keys.grep( /^#{Regexp.escape(first)}/ ).sort
|
40
|
-
log "returning #{completions.inspect} completions"
|
40
|
+
log { "returning #{completions.inspect} completions" }
|
41
41
|
completions
|
42
42
|
end
|
43
43
|
end
|
@@ -65,7 +65,7 @@ module ShellShock
|
|
65
65
|
line = Readline.readline(@prompt, true)
|
66
66
|
if line
|
67
67
|
first, rest = head_tail(line)
|
68
|
-
log "looking for command \"#{first}\" with parameter \"#{rest}\""
|
68
|
+
log { "looking for command \"#{first}\" with parameter \"#{rest}\"" }
|
69
69
|
if @commands[first]
|
70
70
|
@commands[first].execute rest
|
71
71
|
else
|
data/lib/shell_shock/logger.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shell_shock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 23
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
7
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
8
|
+
- 5
|
9
|
+
version: 0.0.5
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Mark Ryall
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date:
|
17
|
+
date: 2011-01-10 00:00:00 +10:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
@@ -26,7 +25,6 @@ dependencies:
|
|
26
25
|
requirements:
|
27
26
|
- - ~>
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 49
|
30
28
|
segments:
|
31
29
|
- 0
|
32
30
|
- 8
|
@@ -42,7 +40,6 @@ dependencies:
|
|
42
40
|
requirements:
|
43
41
|
- - ~>
|
44
42
|
- !ruby/object:Gem::Version
|
45
|
-
hash: 23
|
46
43
|
segments:
|
47
44
|
- 0
|
48
45
|
- 0
|
@@ -58,12 +55,11 @@ dependencies:
|
|
58
55
|
requirements:
|
59
56
|
- - ~>
|
60
57
|
- !ruby/object:Gem::Version
|
61
|
-
hash: 13
|
62
58
|
segments:
|
63
59
|
- 2
|
60
|
+
- 4
|
64
61
|
- 0
|
65
|
-
|
66
|
-
version: 2.0.1
|
62
|
+
version: 2.4.0
|
67
63
|
type: :development
|
68
64
|
version_requirements: *id003
|
69
65
|
description: |
|
@@ -79,11 +75,14 @@ extensions: []
|
|
79
75
|
extra_rdoc_files: []
|
80
76
|
|
81
77
|
files:
|
82
|
-
-
|
78
|
+
- example/adventure.rb
|
79
|
+
- example/cat.rb
|
80
|
+
- example/file_system.rb
|
81
|
+
- lib/shell_shock/command_spec.rb
|
82
|
+
- lib/shell_shock/context.rb
|
83
83
|
- lib/shell_shock/exit_command.rb
|
84
84
|
- lib/shell_shock/help_command.rb
|
85
|
-
- lib/shell_shock/
|
86
|
-
- lib/shell_shock/command_spec.rb
|
85
|
+
- lib/shell_shock/logger.rb
|
87
86
|
- README.rdoc
|
88
87
|
- MIT-LICENSE
|
89
88
|
has_rdoc: true
|
@@ -100,7 +99,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
100
99
|
requirements:
|
101
100
|
- - ">="
|
102
101
|
- !ruby/object:Gem::Version
|
103
|
-
hash: 3
|
104
102
|
segments:
|
105
103
|
- 0
|
106
104
|
version: "0"
|
@@ -109,7 +107,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
107
|
requirements:
|
110
108
|
- - ">="
|
111
109
|
- !ruby/object:Gem::Version
|
112
|
-
hash: 3
|
113
110
|
segments:
|
114
111
|
- 0
|
115
112
|
version: "0"
|