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.
@@ -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 with 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.
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
- Here's a sample shell application using shell shock. It has 'cd' and 'cat' commands. 'cd' performs tab completion for directories and execution enters a nested context. 'cat' performs tab completion for files and execution dumps the file content.
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
@@ -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
@@ -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
@@ -1,9 +1,10 @@
1
1
  module ShellShock
2
2
  module Logger
3
- def log message
3
+ def log message=nil
4
4
  return unless ENV['LOG_PATH']
5
5
  File.open(ENV['LOG_PATH'], 'a') do |file|
6
- file.puts message
6
+ file.puts message if message
7
+ file.puts yield if block_given?
7
8
  end
8
9
  end
9
10
  end
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
- - 4
10
- version: 0.0.4
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: 2010-10-24 00:00:00 +10:00
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
- - 1
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
- - lib/shell_shock/logger.rb
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/context.rb
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"