shell_shock 0.0.4 → 0.0.6
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/.gitignore +2 -0
- data/.rubocop.yml +48 -0
- data/.tool-versions +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +117 -0
- data/README.rdoc +2 -62
- data/Rakefile +12 -0
- data/example/adventure.rb +83 -0
- data/example/file_system.rb +99 -0
- data/lib/shell_shock/command_spec.rb +10 -8
- data/lib/shell_shock/context.rb +24 -22
- data/lib/shell_shock/exit_command.rb +8 -6
- data/lib/shell_shock/help_command.rb +11 -8
- data/lib/shell_shock/logger.rb +10 -6
- data/shell_shock.gemspec +27 -0
- metadata +40 -102
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a473df32bae252fade36508e5e0f9d3c33d0ddf54ee9c29470458fbb6c04d0e2
|
|
4
|
+
data.tar.gz: 15050e03f392c29fd8a68c5e06e7f2c55a8338cf71471442a28c3c806df4a088
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a417ea53a0345722e9d095b39632f2e12a5a96553a82e35bbf82aeb9a331d232ff27f16dc866aaa7dcf83e28b15c452bac1722a298c75aa5436122ff32b9c5fb
|
|
7
|
+
data.tar.gz: 2a827992f1aa325bb18b62f6cc95fc454072e10603b2dd6e53eb0b9662f2f92966f91185811f104a360b41847cd18cefe10f772c28f2360197c1a6e0fe46ecec
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require:
|
|
2
|
+
- rubocop-rspec
|
|
3
|
+
- rubocop-rake
|
|
4
|
+
|
|
5
|
+
AllCops:
|
|
6
|
+
TargetRubyVersion: 3.0
|
|
7
|
+
NewCops: enable
|
|
8
|
+
|
|
9
|
+
Style/StringLiterals:
|
|
10
|
+
Enabled: true
|
|
11
|
+
EnforcedStyle: double_quotes
|
|
12
|
+
|
|
13
|
+
Style/StringLiteralsInInterpolation:
|
|
14
|
+
Enabled: true
|
|
15
|
+
EnforcedStyle: double_quotes
|
|
16
|
+
|
|
17
|
+
Layout/LineLength:
|
|
18
|
+
Max: 120
|
|
19
|
+
|
|
20
|
+
Style/Documentation:
|
|
21
|
+
Enabled: false
|
|
22
|
+
|
|
23
|
+
Metrics/MethodLength:
|
|
24
|
+
Enabled: false
|
|
25
|
+
|
|
26
|
+
Metrics/BlockLength:
|
|
27
|
+
Enabled: false
|
|
28
|
+
|
|
29
|
+
Metrics/AbcSize:
|
|
30
|
+
Enabled: false
|
|
31
|
+
|
|
32
|
+
Metrics/PerceivedComplexity:
|
|
33
|
+
Enabled: false
|
|
34
|
+
|
|
35
|
+
Metrics/CyclomaticComplexity:
|
|
36
|
+
Enabled: false
|
|
37
|
+
|
|
38
|
+
RSpec/InstanceVariable:
|
|
39
|
+
Enabled: false
|
|
40
|
+
|
|
41
|
+
RSpec/ExampleLength:
|
|
42
|
+
Enabled: false
|
|
43
|
+
|
|
44
|
+
RSpec/VerifiedDoubles:
|
|
45
|
+
Enabled: false
|
|
46
|
+
|
|
47
|
+
RSpec/MultipleExpectations:
|
|
48
|
+
Enabled: false
|
data/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby 3.3.4
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
shell_shock (0.0.6)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
ast (2.4.2)
|
|
10
|
+
backport (1.2.0)
|
|
11
|
+
benchmark (0.3.0)
|
|
12
|
+
diff-lcs (1.5.1)
|
|
13
|
+
e2mmap (0.1.0)
|
|
14
|
+
jaro_winkler (1.6.0)
|
|
15
|
+
json (2.7.2)
|
|
16
|
+
kramdown (2.4.0)
|
|
17
|
+
rexml
|
|
18
|
+
kramdown-parser-gfm (1.1.0)
|
|
19
|
+
kramdown (~> 2.0)
|
|
20
|
+
language_server-protocol (3.17.0.3)
|
|
21
|
+
nokogiri (1.16.6-aarch64-linux)
|
|
22
|
+
racc (~> 1.4)
|
|
23
|
+
nokogiri (1.16.6-arm-linux)
|
|
24
|
+
racc (~> 1.4)
|
|
25
|
+
nokogiri (1.16.6-arm64-darwin)
|
|
26
|
+
racc (~> 1.4)
|
|
27
|
+
nokogiri (1.16.6-x86-linux)
|
|
28
|
+
racc (~> 1.4)
|
|
29
|
+
nokogiri (1.16.6-x86_64-darwin)
|
|
30
|
+
racc (~> 1.4)
|
|
31
|
+
nokogiri (1.16.6-x86_64-linux)
|
|
32
|
+
racc (~> 1.4)
|
|
33
|
+
parallel (1.25.1)
|
|
34
|
+
parser (3.3.4.0)
|
|
35
|
+
ast (~> 2.4.1)
|
|
36
|
+
racc
|
|
37
|
+
racc (1.8.0)
|
|
38
|
+
rainbow (3.1.1)
|
|
39
|
+
rake (13.2.1)
|
|
40
|
+
rbs (2.8.4)
|
|
41
|
+
regexp_parser (2.9.2)
|
|
42
|
+
reverse_markdown (2.1.1)
|
|
43
|
+
nokogiri
|
|
44
|
+
rexml (3.3.2)
|
|
45
|
+
strscan
|
|
46
|
+
rspec (3.13.0)
|
|
47
|
+
rspec-core (~> 3.13.0)
|
|
48
|
+
rspec-expectations (~> 3.13.0)
|
|
49
|
+
rspec-mocks (~> 3.13.0)
|
|
50
|
+
rspec-core (3.13.0)
|
|
51
|
+
rspec-support (~> 3.13.0)
|
|
52
|
+
rspec-expectations (3.13.1)
|
|
53
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
54
|
+
rspec-support (~> 3.13.0)
|
|
55
|
+
rspec-mocks (3.13.1)
|
|
56
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
57
|
+
rspec-support (~> 3.13.0)
|
|
58
|
+
rspec-support (3.13.1)
|
|
59
|
+
rubocop (1.65.0)
|
|
60
|
+
json (~> 2.3)
|
|
61
|
+
language_server-protocol (>= 3.17.0)
|
|
62
|
+
parallel (~> 1.10)
|
|
63
|
+
parser (>= 3.3.0.2)
|
|
64
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
65
|
+
regexp_parser (>= 2.4, < 3.0)
|
|
66
|
+
rexml (>= 3.2.5, < 4.0)
|
|
67
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
|
68
|
+
ruby-progressbar (~> 1.7)
|
|
69
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
70
|
+
rubocop-ast (1.31.3)
|
|
71
|
+
parser (>= 3.3.1.0)
|
|
72
|
+
rubocop-rake (0.6.0)
|
|
73
|
+
rubocop (~> 1.0)
|
|
74
|
+
rubocop-rspec (3.0.3)
|
|
75
|
+
rubocop (~> 1.61)
|
|
76
|
+
ruby-progressbar (1.13.0)
|
|
77
|
+
solargraph (0.50.0)
|
|
78
|
+
backport (~> 1.2)
|
|
79
|
+
benchmark
|
|
80
|
+
bundler (~> 2.0)
|
|
81
|
+
diff-lcs (~> 1.4)
|
|
82
|
+
e2mmap
|
|
83
|
+
jaro_winkler (~> 1.5)
|
|
84
|
+
kramdown (~> 2.3)
|
|
85
|
+
kramdown-parser-gfm (~> 1.1)
|
|
86
|
+
parser (~> 3.0)
|
|
87
|
+
rbs (~> 2.0)
|
|
88
|
+
reverse_markdown (~> 2.0)
|
|
89
|
+
rubocop (~> 1.38)
|
|
90
|
+
thor (~> 1.0)
|
|
91
|
+
tilt (~> 2.0)
|
|
92
|
+
yard (~> 0.9, >= 0.9.24)
|
|
93
|
+
strscan (3.1.0)
|
|
94
|
+
thor (1.3.1)
|
|
95
|
+
tilt (2.4.0)
|
|
96
|
+
unicode-display_width (2.5.0)
|
|
97
|
+
yard (0.9.36)
|
|
98
|
+
|
|
99
|
+
PLATFORMS
|
|
100
|
+
aarch64-linux
|
|
101
|
+
arm-linux
|
|
102
|
+
arm64-darwin
|
|
103
|
+
x86-linux
|
|
104
|
+
x86_64-darwin
|
|
105
|
+
x86_64-linux
|
|
106
|
+
|
|
107
|
+
DEPENDENCIES
|
|
108
|
+
rake
|
|
109
|
+
rspec
|
|
110
|
+
rubocop
|
|
111
|
+
rubocop-rake
|
|
112
|
+
rubocop-rspec
|
|
113
|
+
shell_shock!
|
|
114
|
+
solargraph
|
|
115
|
+
|
|
116
|
+
BUNDLED WITH
|
|
117
|
+
2.5.11
|
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.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
|
|
4
|
+
|
|
5
|
+
require "shell_shock/context"
|
|
6
|
+
|
|
7
|
+
class MoveCommand
|
|
8
|
+
attr_reader :usage, :help
|
|
9
|
+
|
|
10
|
+
def initialize(room)
|
|
11
|
+
@room = room
|
|
12
|
+
@usage = "<direction>"
|
|
13
|
+
@help = "moves to the adjoining room in the specified direction"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def completion(text)
|
|
17
|
+
@room.connections.keys.grep(/^#{text}/).sort
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def execute(direction)
|
|
21
|
+
room = @room.connections[direction]
|
|
22
|
+
if room
|
|
23
|
+
AdventureContext.new(room).push
|
|
24
|
+
else
|
|
25
|
+
puts "there is no adjoining room to the #{direction}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class AdventureContext
|
|
31
|
+
include ShellShock::Context
|
|
32
|
+
|
|
33
|
+
def initialize(room)
|
|
34
|
+
puts room.description
|
|
35
|
+
@prompt = "#{room.name} > "
|
|
36
|
+
add_command MoveCommand.new(room), "go"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Room
|
|
41
|
+
attr_reader :name, :description, :connections
|
|
42
|
+
|
|
43
|
+
def initialize(name, description)
|
|
44
|
+
@name = name
|
|
45
|
+
@description = description
|
|
46
|
+
@connections = {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def add(direction, room)
|
|
50
|
+
@connections[direction] = room
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
START = Room.new "clearing", <<~DESCRIPTION
|
|
55
|
+
You have entered a clearing.
|
|
56
|
+
|
|
57
|
+
A dead goat lies on the ground in front of you
|
|
58
|
+
DESCRIPTION
|
|
59
|
+
|
|
60
|
+
CAVE_ENTRANCE = Room.new "cave entrance", <<~DESCRIPTION
|
|
61
|
+
You have arrived at the entrance to a cave.
|
|
62
|
+
|
|
63
|
+
A foul smell is emitting from the cave. Some smoke
|
|
64
|
+
can be seen off off to the east.
|
|
65
|
+
DESCRIPTION
|
|
66
|
+
|
|
67
|
+
CAMP_SITE = Room.new "camp site", <<~DESCRIPTION
|
|
68
|
+
You have arrived in a camp site.
|
|
69
|
+
|
|
70
|
+
There is a fire that has been recently put out.
|
|
71
|
+
DESCRIPTION
|
|
72
|
+
|
|
73
|
+
CAVE = Room.new "cave", <<~DESCRIPTION
|
|
74
|
+
You have entered a dark cave.
|
|
75
|
+
|
|
76
|
+
A faint growling sound can be heard.
|
|
77
|
+
DESCRIPTION
|
|
78
|
+
|
|
79
|
+
START.add "north", CAVE_ENTRANCE
|
|
80
|
+
CAVE_ENTRANCE.add "east", CAMP_SITE
|
|
81
|
+
CAVE_ENTRANCE.add "north", CAVE
|
|
82
|
+
|
|
83
|
+
AdventureContext.new(START).push
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
|
|
4
|
+
|
|
5
|
+
require "shell_shock/context"
|
|
6
|
+
require "find"
|
|
7
|
+
require "pathname"
|
|
8
|
+
|
|
9
|
+
class Finder
|
|
10
|
+
attr_reader :files
|
|
11
|
+
|
|
12
|
+
def initialize(path)
|
|
13
|
+
@path = path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def refresh(pattern)
|
|
17
|
+
files = []
|
|
18
|
+
here = Pathname.new(File.expand_path(@path))
|
|
19
|
+
Find.find(File.expand_path(@path)) do |p|
|
|
20
|
+
if File.basename(p).start_with?(".")
|
|
21
|
+
Find.prune
|
|
22
|
+
else
|
|
23
|
+
path = Pathname.new(p).relative_path_from(here).to_s
|
|
24
|
+
files << path if path.start_with?(pattern) && yield(p)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
files
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class CatFileCommand
|
|
32
|
+
include ShellShock::Logger
|
|
33
|
+
|
|
34
|
+
def initialize(path)
|
|
35
|
+
@finder = Finder.new path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def usage
|
|
39
|
+
"<file name>"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def help
|
|
43
|
+
"displays the content of a file"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def completion(text)
|
|
47
|
+
log { "cat command completing \"#{text}\"" }
|
|
48
|
+
@finder.refresh(text) { |path| File.file? path }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def execute(path = nil)
|
|
52
|
+
return unless path
|
|
53
|
+
|
|
54
|
+
File.open(path) { |f| f.each_with_index { |l, i| puts "#{i + 1}: #{l}" } }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class ChangeDirectoryCommand
|
|
59
|
+
include ShellShock::Logger
|
|
60
|
+
|
|
61
|
+
def initialize(path)
|
|
62
|
+
@path = path
|
|
63
|
+
@finder = Finder.new path
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def usage
|
|
67
|
+
"<directory name>"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def help
|
|
71
|
+
"switches to a new shell context in the specified directory"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def completion(text)
|
|
75
|
+
log { "cd command completing \"#{text}\"" }
|
|
76
|
+
@finder.refresh(text) { |path| path != "." and File.directory? path }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def execute(text = nil)
|
|
80
|
+
return unless text
|
|
81
|
+
|
|
82
|
+
log { "pushing new shell in \"#{text}\"" }
|
|
83
|
+
DirectoryContext.new("#{@path}/#{text}").push
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class DirectoryContext
|
|
88
|
+
include ShellShock::Context
|
|
89
|
+
|
|
90
|
+
def initialize(path)
|
|
91
|
+
@prompt_text = "#{path} > "
|
|
92
|
+
@commands = {
|
|
93
|
+
"cd" => ChangeDirectoryCommand.new(path),
|
|
94
|
+
"cat" => CatFileCommand.new(path)
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
DirectoryContext.new(".").push
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ShellShock
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
module CommandSpec
|
|
5
|
+
def with_usage(text)
|
|
6
|
+
it("displays usage") { expect(@command.usage).to eq(text) }
|
|
7
|
+
end
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
end
|
|
9
|
+
def with_help(text)
|
|
10
|
+
it("displays help") { expect(@command.help).to eq(text) }
|
|
10
11
|
end
|
|
11
|
-
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/shell_shock/context.rb
CHANGED
|
@@ -1,43 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems"
|
|
4
|
+
require "readline"
|
|
5
|
+
require "shell_shock/exit_command"
|
|
6
|
+
require "shell_shock/help_command"
|
|
7
|
+
require "shell_shock/logger"
|
|
6
8
|
|
|
7
9
|
module ShellShock
|
|
8
10
|
module Context
|
|
9
11
|
include Logger
|
|
10
12
|
|
|
11
|
-
def head_tail
|
|
13
|
+
def head_tail(string)
|
|
12
14
|
if string
|
|
13
15
|
m = /[^ ]+/.match(string.strip)
|
|
14
16
|
return m[0], m.post_match.strip if m
|
|
15
17
|
end
|
|
16
|
-
|
|
18
|
+
["", ""]
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def refresh
|
|
20
22
|
refresh_commands if respond_to?(:refresh_commands)
|
|
21
|
-
Readline.completer_word_break_characters =
|
|
23
|
+
Readline.completer_word_break_characters = ""
|
|
22
24
|
Readline.completion_proc = lambda do |string|
|
|
23
|
-
log "trying completion for \"#{string}\""
|
|
25
|
+
log { "trying completion for \"#{string}\"" }
|
|
24
26
|
first, rest = head_tail(string)
|
|
25
|
-
log "split \"#{first}\" from \"#{rest}\""
|
|
27
|
+
log { "split \"#{first}\" from \"#{rest}\"" }
|
|
26
28
|
if first
|
|
27
29
|
command = @commands[first]
|
|
28
30
|
if command
|
|
29
|
-
log "matched #{first} command"
|
|
31
|
+
log { "matched #{first} command" }
|
|
30
32
|
if command.respond_to?(:completion)
|
|
31
|
-
completions = command.completion(rest).map {|c| "#{first} #{c}" }
|
|
33
|
+
completions = command.completion(rest).map { |c| "#{first} #{c}" }
|
|
32
34
|
else
|
|
33
|
-
log "#{first} has no completion proc"
|
|
35
|
+
log { "#{first} has no completion proc" }
|
|
34
36
|
completions = []
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
completions ||= @commands.keys.grep(
|
|
40
|
-
log "returning #{completions.inspect} completions"
|
|
41
|
+
completions ||= @commands.keys.grep(/^#{Regexp.escape(first)}/).sort.map { |c| "#{c} " }
|
|
42
|
+
log { "returning #{completions.inspect} completions" }
|
|
41
43
|
completions
|
|
42
44
|
end
|
|
43
45
|
end
|
|
@@ -52,20 +54,20 @@ module ShellShock
|
|
|
52
54
|
|
|
53
55
|
def add_command command, *aliases
|
|
54
56
|
@commands ||= {}
|
|
55
|
-
aliases.each {|a| @commands[a] = command}
|
|
57
|
+
aliases.each { |a| @commands[a] = command }
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def push
|
|
59
|
-
@prompt ||=
|
|
60
|
-
add_command HelpCommand.new(@commands),
|
|
61
|
-
add_command ExitCommand.new(self),
|
|
61
|
+
@prompt ||= " > "
|
|
62
|
+
add_command HelpCommand.new(@commands), "help"
|
|
63
|
+
add_command ExitCommand.new(self), "exit"
|
|
62
64
|
begin
|
|
63
65
|
until abort?
|
|
64
66
|
refresh
|
|
65
67
|
line = Readline.readline(@prompt, true)
|
|
66
68
|
if line
|
|
67
69
|
first, rest = head_tail(line)
|
|
68
|
-
log "looking for command \"#{first}\" with parameter \"#{rest}\""
|
|
70
|
+
log { "looking for command \"#{first}\" with parameter \"#{rest}\"" }
|
|
69
71
|
if @commands[first]
|
|
70
72
|
@commands[first].execute rest
|
|
71
73
|
else
|
|
@@ -76,9 +78,9 @@ module ShellShock
|
|
|
76
78
|
end
|
|
77
79
|
puts
|
|
78
80
|
end
|
|
79
|
-
rescue Interrupt
|
|
81
|
+
rescue Interrupt
|
|
80
82
|
puts
|
|
81
83
|
end
|
|
82
84
|
end
|
|
83
85
|
end
|
|
84
|
-
end
|
|
86
|
+
end
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ShellShock
|
|
2
4
|
class ExitCommand
|
|
3
5
|
attr_reader :help, :usage
|
|
4
6
|
|
|
5
|
-
def initialize
|
|
7
|
+
def initialize(context)
|
|
6
8
|
@context = context
|
|
7
|
-
@usage =
|
|
8
|
-
@help =
|
|
9
|
+
@usage = ""
|
|
10
|
+
@help = "exits the current context"
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def execute
|
|
13
|
+
def execute(_ignore)
|
|
12
14
|
@context.abort!
|
|
13
15
|
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ShellShock
|
|
2
4
|
class HelpCommand
|
|
3
5
|
attr_reader :help, :usage
|
|
4
6
|
|
|
5
|
-
def initialize
|
|
7
|
+
def initialize(commands)
|
|
6
8
|
@commands = commands
|
|
7
|
-
@usage =
|
|
8
|
-
@help =
|
|
9
|
+
@usage = "<command name>"
|
|
10
|
+
@help = "displays the help information for a command"
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def completion
|
|
13
|
+
def completion(text)
|
|
12
14
|
@commands.keys.grep(/^#{Regexp.escape(text)}/).sort
|
|
13
15
|
end
|
|
14
16
|
|
|
15
|
-
def execute
|
|
17
|
+
def execute(command)
|
|
16
18
|
command.empty? ? display_help_for_commands : display_help_for_command(command)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def display_help_for_commands
|
|
20
22
|
return if @commands.keys.empty?
|
|
21
|
-
|
|
23
|
+
|
|
24
|
+
puts "Available commands:"
|
|
22
25
|
@commands.keys.sort.each { |command| puts command }
|
|
23
26
|
end
|
|
24
27
|
|
|
25
|
-
def display_help_for_command
|
|
28
|
+
def display_help_for_command(command_name)
|
|
26
29
|
command = @commands[command_name]
|
|
27
30
|
if command
|
|
28
31
|
puts "Command \"#{command_name}\""
|
|
@@ -33,4 +36,4 @@ module ShellShock
|
|
|
33
36
|
end
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
|
-
end
|
|
39
|
+
end
|
data/lib/shell_shock/logger.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ShellShock
|
|
2
|
-
|
|
3
|
-
def log
|
|
4
|
-
return unless ENV[
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
module Logger
|
|
5
|
+
def log(message = nil)
|
|
6
|
+
return unless ENV["LOG_PATH"]
|
|
7
|
+
|
|
8
|
+
File.open(ENV.fetch("LOG_PATH", nil), "a") do |file|
|
|
9
|
+
file.puts message if message
|
|
10
|
+
file.puts yield if block_given?
|
|
7
11
|
end
|
|
8
12
|
end
|
|
9
13
|
end
|
|
10
|
-
end
|
|
14
|
+
end
|
data/shell_shock.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "shell_shock"
|
|
5
|
+
spec.version = "0.0.6"
|
|
6
|
+
spec.summary = "library for creating simple shell applications using readline"
|
|
7
|
+
spec.description = <<~DESCRIPTION
|
|
8
|
+
This is just some code extracted from a few command line gems i've created (shh and cardigan).
|
|
9
|
+
|
|
10
|
+
I wanted to move the shared functionality (related to creating a shell with readline) to a seperate gem.'
|
|
11
|
+
DESCRIPTION
|
|
12
|
+
|
|
13
|
+
spec.authors << "Mark Ryall"
|
|
14
|
+
spec.email = "mark@ryall.name"
|
|
15
|
+
spec.homepage = "http://github.com/markryall/shell_shock"
|
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0")
|
|
17
|
+
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
|
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
22
|
+
end
|
|
23
|
+
spec.bindir = "exe"
|
|
24
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
25
|
+
spec.require_paths = ["lib"]
|
|
26
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
27
|
+
end
|
metadata
CHANGED
|
@@ -1,124 +1,62 @@
|
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shell_shock
|
|
3
|
-
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
5
|
-
prerelease: false
|
|
6
|
-
segments:
|
|
7
|
-
- 0
|
|
8
|
-
- 0
|
|
9
|
-
- 4
|
|
10
|
-
version: 0.0.4
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.6
|
|
11
5
|
platform: ruby
|
|
12
|
-
authors:
|
|
6
|
+
authors:
|
|
13
7
|
- Mark Ryall
|
|
14
|
-
autorequire:
|
|
15
|
-
bindir:
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
16
10
|
cert_chain: []
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
default_executable:
|
|
20
|
-
dependencies:
|
|
21
|
-
- !ruby/object:Gem::Dependency
|
|
22
|
-
name: rake
|
|
23
|
-
prerelease: false
|
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
|
25
|
-
none: false
|
|
26
|
-
requirements:
|
|
27
|
-
- - ~>
|
|
28
|
-
- !ruby/object:Gem::Version
|
|
29
|
-
hash: 49
|
|
30
|
-
segments:
|
|
31
|
-
- 0
|
|
32
|
-
- 8
|
|
33
|
-
- 7
|
|
34
|
-
version: 0.8.7
|
|
35
|
-
type: :development
|
|
36
|
-
version_requirements: *id001
|
|
37
|
-
- !ruby/object:Gem::Dependency
|
|
38
|
-
name: gemesis
|
|
39
|
-
prerelease: false
|
|
40
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
|
41
|
-
none: false
|
|
42
|
-
requirements:
|
|
43
|
-
- - ~>
|
|
44
|
-
- !ruby/object:Gem::Version
|
|
45
|
-
hash: 23
|
|
46
|
-
segments:
|
|
47
|
-
- 0
|
|
48
|
-
- 0
|
|
49
|
-
- 4
|
|
50
|
-
version: 0.0.4
|
|
51
|
-
type: :development
|
|
52
|
-
version_requirements: *id002
|
|
53
|
-
- !ruby/object:Gem::Dependency
|
|
54
|
-
name: rspec
|
|
55
|
-
prerelease: false
|
|
56
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
|
57
|
-
none: false
|
|
58
|
-
requirements:
|
|
59
|
-
- - ~>
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
hash: 13
|
|
62
|
-
segments:
|
|
63
|
-
- 2
|
|
64
|
-
- 0
|
|
65
|
-
- 1
|
|
66
|
-
version: 2.0.1
|
|
67
|
-
type: :development
|
|
68
|
-
version_requirements: *id003
|
|
11
|
+
date: 2024-07-20 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
69
13
|
description: |
|
|
70
14
|
This is just some code extracted from a few command line gems i've created (shh and cardigan).
|
|
71
|
-
|
|
72
|
-
I wanted to move the shared functionality (related to creating a shell with readline) to a seperate gem.'
|
|
73
15
|
|
|
16
|
+
I wanted to move the shared functionality (related to creating a shell with readline) to a seperate gem.'
|
|
74
17
|
email: mark@ryall.name
|
|
75
18
|
executables: []
|
|
76
|
-
|
|
77
19
|
extensions: []
|
|
78
|
-
|
|
79
20
|
extra_rdoc_files: []
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
21
|
+
files:
|
|
22
|
+
- ".gitignore"
|
|
23
|
+
- ".rubocop.yml"
|
|
24
|
+
- ".tool-versions"
|
|
25
|
+
- Gemfile
|
|
26
|
+
- Gemfile.lock
|
|
27
|
+
- MIT-LICENSE
|
|
28
|
+
- README.rdoc
|
|
29
|
+
- Rakefile
|
|
30
|
+
- example/adventure.rb
|
|
31
|
+
- example/file_system.rb
|
|
32
|
+
- lib/shell_shock/command_spec.rb
|
|
33
|
+
- lib/shell_shock/context.rb
|
|
83
34
|
- lib/shell_shock/exit_command.rb
|
|
84
35
|
- lib/shell_shock/help_command.rb
|
|
85
|
-
- lib/shell_shock/
|
|
86
|
-
-
|
|
87
|
-
- README.rdoc
|
|
88
|
-
- MIT-LICENSE
|
|
89
|
-
has_rdoc: true
|
|
36
|
+
- lib/shell_shock/logger.rb
|
|
37
|
+
- shell_shock.gemspec
|
|
90
38
|
homepage: http://github.com/markryall/shell_shock
|
|
91
39
|
licenses: []
|
|
92
|
-
|
|
93
|
-
|
|
40
|
+
metadata:
|
|
41
|
+
homepage_uri: http://github.com/markryall/shell_shock
|
|
42
|
+
rubygems_mfa_required: 'true'
|
|
43
|
+
post_install_message:
|
|
94
44
|
rdoc_options: []
|
|
95
|
-
|
|
96
|
-
require_paths:
|
|
45
|
+
require_paths:
|
|
97
46
|
- lib
|
|
98
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
|
-
|
|
100
|
-
requirements:
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
101
49
|
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
version: "0"
|
|
107
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
|
-
none: false
|
|
109
|
-
requirements:
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3.0'
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
110
54
|
- - ">="
|
|
111
|
-
- !ruby/object:Gem::Version
|
|
112
|
-
|
|
113
|
-
segments:
|
|
114
|
-
- 0
|
|
115
|
-
version: "0"
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
116
57
|
requirements: []
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
signing_key:
|
|
121
|
-
specification_version: 3
|
|
58
|
+
rubygems_version: 3.5.11
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 4
|
|
122
61
|
summary: library for creating simple shell applications using readline
|
|
123
62
|
test_files: []
|
|
124
|
-
|