shelldon 0.0.1
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 +8 -0
- data/Gemfile.lock +43 -0
- data/LICENSE.txt +21 -0
- data/README.md +138 -0
- data/Rakefile +1 -0
- data/bin/shelldon +8 -0
- data/build_and_install.sh +3 -0
- data/lib/Exceptions/error_factory.rb +26 -0
- data/lib/cli.rb +19 -0
- data/lib/command/command.rb +134 -0
- data/lib/command/command_list.rb +72 -0
- data/lib/config/config.rb +85 -0
- data/lib/config/config_factory.rb +41 -0
- data/lib/config/param.rb +54 -0
- data/lib/config/param/boolean_param.rb +28 -0
- data/lib/config/param/number_param.rb +7 -0
- data/lib/config/param/string_param.rb +8 -0
- data/lib/config/param_factory.rb +75 -0
- data/lib/defaults/commands.rb +0 -0
- data/lib/dsl.rb +21 -0
- data/lib/file_management/config_file_manager.rb +34 -0
- data/lib/file_management/file_manager.rb +20 -0
- data/lib/file_management/history_file.rb +34 -0
- data/lib/file_management/yaml_manager.rb +16 -0
- data/lib/helpers/timer.rb +17 -0
- data/lib/opts/opt_factory.rb +30 -0
- data/lib/opts/opts.rb +13 -0
- data/lib/shell/shell.rb +167 -0
- data/lib/shell/shell_factory.rb +38 -0
- data/lib/shell/shell_index.rb +33 -0
- data/lib/shelldon.rb +26 -0
- data/lib/shelldon/version.rb +3 -0
- data/shelldon.gemspec +28 -0
- data/test_shell/Gemfile +3 -0
- data/test_shell/Gemfile.lock +20 -0
- data/test_shell/run.sh +3 -0
- data/test_shell/simple_shell.rb +106 -0
- data/test_shell/test_shell.rb +82 -0
- data/test_shell/useful_commands.rb +11 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1050dd8126c65561d242ce862a4cf2947ed61618
|
4
|
+
data.tar.gz: fd6ad927cc21f2087011bd1584e652d4b4ac415d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f9a2c2be6b2cba4b8bd61fa6b666fd1b5daf9d15bc9d8d04eb2c1bfb2c7811aa3836f18357d779d8ca86ef6f98b9fbe64cdcbb2c0c48f23d2ee0150bb1a767c4
|
7
|
+
data.tar.gz: 168ab3b8d30c478081e3d2dbac3f032f5b74f93d93736c618eb2909e235207eb5f0bcf9a409bdb75ae8bf440b05c270aee8148e81feb79fcdea04d457f77787d
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
shelldon (0.0.1)
|
5
|
+
rb-readline
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ast (2.1.0)
|
11
|
+
astrolabe (1.3.1)
|
12
|
+
parser (~> 2.2)
|
13
|
+
byebug (8.2.1)
|
14
|
+
getopt (1.4.2)
|
15
|
+
parser (2.2.2.6)
|
16
|
+
ast (>= 1.1, < 3.0)
|
17
|
+
powerpack (0.1.1)
|
18
|
+
rainbow (2.0.0)
|
19
|
+
rake (10.4.2)
|
20
|
+
rb-readline (0.5.3)
|
21
|
+
rubocop (0.33.0)
|
22
|
+
astrolabe (~> 1.3)
|
23
|
+
parser (>= 2.2.2.5, < 3.0)
|
24
|
+
powerpack (~> 0.1)
|
25
|
+
rainbow (>= 1.99.1, < 3.0)
|
26
|
+
ruby-progressbar (~> 1.4)
|
27
|
+
ruby-progressbar (1.7.5)
|
28
|
+
terminal-table (1.5.2)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
bundler (~> 1.10)
|
35
|
+
byebug
|
36
|
+
getopt
|
37
|
+
rake (~> 10.0)
|
38
|
+
rubocop
|
39
|
+
shelldon!
|
40
|
+
terminal-table
|
41
|
+
|
42
|
+
BUNDLED WITH
|
43
|
+
1.10.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Wesley Boynton
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# Shelldon
|
2
|
+
|
3
|
+
Shelldon is an expressive DSL for building interactive terminal applications, or REPLs (Read-Evaluate-Print-Loops).
|
4
|
+
|
5
|
+
There are some good gems out there for building command-line executables, but I couldn't find anything that built a REPL in the way that I wanted it -- and I build a lot of REPLs.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Gemfile
|
11
|
+
gem 'shelldon'
|
12
|
+
|
13
|
+
|
14
|
+
$ bundle install
|
15
|
+
```
|
16
|
+
Or just `gem install shelldon` -- You know the drill.
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Here's a simple Shelldon app, available in `test_shell/simple_shell.rb`
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'shelldon'
|
24
|
+
require 'pp'
|
25
|
+
|
26
|
+
Shelldon.shell do
|
27
|
+
opts do
|
28
|
+
opt '--myopt', '-m', :boolean
|
29
|
+
end
|
30
|
+
|
31
|
+
config do
|
32
|
+
# Set the config file, which can hold values one level higher than their default
|
33
|
+
# The order of precedence for params is: Set in-session > set by command-line flag > set by config file > default
|
34
|
+
config_file '.my-config-file'
|
35
|
+
|
36
|
+
param :myparam do # Create a config option (a 'param')
|
37
|
+
type :boolean # Make it a boolean
|
38
|
+
default false # Make its default value false
|
39
|
+
opt 'myopt' # Override it with the value of command-line opt '--myopt' if present
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Define a command that sets a config option
|
44
|
+
command :set do
|
45
|
+
# Set up some help information for the command
|
46
|
+
help 'Set a configuration option for the remainder of the session.'
|
47
|
+
examples ['set myparam']
|
48
|
+
usage 'set [config_option]'
|
49
|
+
|
50
|
+
# Define the command's action - this has access to some helpers, like 'config'
|
51
|
+
# You can also give the block access to the remaining (unusued) tokens of the command
|
52
|
+
# "Unused" meaning tokens that weren't used up to call the command in the first place
|
53
|
+
action do |args|
|
54
|
+
tokens = args.split(' ')
|
55
|
+
config[tokens[0].to_sym] = tokens[1]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Here's a simplification of grabbing args for use in an action
|
61
|
+
command :arg do
|
62
|
+
help 'Show your args off!'
|
63
|
+
action { |args| puts args }
|
64
|
+
end
|
65
|
+
|
66
|
+
# This command will show the active config if called witout args, or
|
67
|
+
# show the value of a specific option if called with an argument
|
68
|
+
command :config do
|
69
|
+
help 'Show the configuration of the current session.'
|
70
|
+
usage 'config'
|
71
|
+
action do |args|
|
72
|
+
if args.empty?
|
73
|
+
pp config.to_a
|
74
|
+
else
|
75
|
+
param = config.find(args.to_sym)
|
76
|
+
puts "#{param.name}: #{param.val}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# This is a subcommand - it will automatically take precedence
|
81
|
+
# if you call a command beginning with "config save"
|
82
|
+
subcommand :save do
|
83
|
+
help 'Save your current configuration'
|
84
|
+
usage 'config save'
|
85
|
+
action { config.save }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# This will show all that nice help information we've been defining.
|
91
|
+
# This produces a two-dimensional array, so you could make it into a table with some
|
92
|
+
# table-printing gem if you wanted.
|
93
|
+
command :help do
|
94
|
+
action { |args| pp command_list.help(args) }
|
95
|
+
help 'Show help. Optionally specify specific command for more information.'
|
96
|
+
usage 'help [cmd]'
|
97
|
+
examples ['help', 'help quit']
|
98
|
+
end
|
99
|
+
|
100
|
+
# Define a default command - This is what happens when a command doesn't match up
|
101
|
+
command_missing do
|
102
|
+
action { |cmd| puts "No such command \"#{cmd}\"" }
|
103
|
+
end
|
104
|
+
|
105
|
+
# LASTLY, define some basic shell properties. The shell will run at the end of this block.
|
106
|
+
shell do
|
107
|
+
# You can make your prompt a string or a block
|
108
|
+
prompt 'shelldon> ' # This is okay
|
109
|
+
prompt { "shelldon#{4+2}>" } # This is okay too
|
110
|
+
|
111
|
+
# This is the "home" directory of your shell, used for config files, history files, etc.
|
112
|
+
home '~/.shelldon-test'
|
113
|
+
|
114
|
+
# Enable in-session history (enabled by default)
|
115
|
+
history true
|
116
|
+
|
117
|
+
# Enable history logging and reloading between sessions
|
118
|
+
history_file '.shelldon-history'
|
119
|
+
|
120
|
+
# Error handling - You can 'accept' an error or 'reject' it.
|
121
|
+
# The only difference is an accepted error won't kill the shell.
|
122
|
+
# You can also pass a block to run when that specific command is caught.
|
123
|
+
errors do
|
124
|
+
reject StandardError
|
125
|
+
accept(Interrupt) { puts '^C' }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
## Contributing
|
131
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wwboynton/shelldon.
|
132
|
+
|
133
|
+
|
134
|
+
## License
|
135
|
+
|
136
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
137
|
+
|
138
|
+
Let me know if you find a cool use for Shelldon!
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/shelldon
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'pathname'
|
3
|
+
bin_file = Pathname.new(__FILE__).realpath
|
4
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', bin_file)
|
5
|
+
|
6
|
+
require_relative '../lib/cli'
|
7
|
+
Shelldon::CLI.source_root(File.expand_path('../../templates', bin_file))
|
8
|
+
Shelldon::CLI.start(ARGV)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shelldon
|
2
|
+
class ErrorFactory
|
3
|
+
def initialize(&block)
|
4
|
+
@accept_errors = {}
|
5
|
+
@reject_errors = {}
|
6
|
+
@default = proc { |e| on_error(e) }
|
7
|
+
instance_eval(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def default(&block)
|
11
|
+
@default = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def accept(e, &block)
|
15
|
+
@accept_errors[e] = (block_given? ? block : nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def reject(e, &block)
|
19
|
+
@reject_errors[e] = (block_given? ? block : nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get
|
23
|
+
[@accept_errors, @reject_errors]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/cli.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module Shelldon
|
5
|
+
class CLI < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
desc 'new PROJECT_NAME', 'Sets up ALL THE THINGS needed for your Shelldon project.'
|
9
|
+
|
10
|
+
def new(name)
|
11
|
+
name = Thor::Util.snake_case(name)
|
12
|
+
system("bundle gem #{name}")
|
13
|
+
directory(:project, "#{name}/lib/")
|
14
|
+
File.open(name, 'a') { |f| f.write("gem 'shelldon'") }
|
15
|
+
system("cd #{name}")
|
16
|
+
system('bundle install')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Shelldon
|
2
|
+
class Command
|
3
|
+
attr_reader :name, :aliases, :subcommands, :parent
|
4
|
+
|
5
|
+
def initialize(name, parent, &block)
|
6
|
+
@name = name
|
7
|
+
@aliases = []
|
8
|
+
@subcommands = {}
|
9
|
+
@show = true
|
10
|
+
@parent = parent
|
11
|
+
instance_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def christian_name
|
15
|
+
if @parent.is_a?(Shelldon::Command)
|
16
|
+
"#{@parent.christian_name} #{@name}"
|
17
|
+
else
|
18
|
+
@name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def command_list
|
23
|
+
@parent
|
24
|
+
end
|
25
|
+
|
26
|
+
def shell
|
27
|
+
# This will recurse through subcommands until
|
28
|
+
# eventually .shell gets called on the command list :)
|
29
|
+
@parent.shell
|
30
|
+
end
|
31
|
+
|
32
|
+
def config
|
33
|
+
shell.config
|
34
|
+
end
|
35
|
+
|
36
|
+
def run(tokens = [])
|
37
|
+
tokens = [tokens] unless tokens.is_a?(Array)
|
38
|
+
instance_exec(tokens.join(' '), &@action)
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid?(input)
|
42
|
+
return true unless @validator
|
43
|
+
if instance_exec(input, &@validator)
|
44
|
+
true
|
45
|
+
else
|
46
|
+
@error ? fail(@error) : false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def auto_complete(_input)
|
51
|
+
@autocomplete = @subcommands.values
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_subcommand?
|
55
|
+
!@subcommands.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def find(tokens)
|
59
|
+
tokens = tokens.split(' ') if tokens.is_a?(String)
|
60
|
+
return [self, tokens] if tokens.empty?
|
61
|
+
|
62
|
+
if @subcommands.key?(tokens.first.to_sym)
|
63
|
+
key = tokens.shift.to_sym
|
64
|
+
@subcommands[key].find(tokens)
|
65
|
+
else
|
66
|
+
[self, tokens]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def first_token(arr)
|
71
|
+
arr.first.to_sym
|
72
|
+
end
|
73
|
+
|
74
|
+
def register
|
75
|
+
command_list.register(self)
|
76
|
+
end
|
77
|
+
|
78
|
+
# DSL
|
79
|
+
|
80
|
+
def show(bool = nil)
|
81
|
+
bool.nil? ? @show : @show = (bool ? true : false)
|
82
|
+
end
|
83
|
+
|
84
|
+
def help(str = nil)
|
85
|
+
str ? @help = str : @help
|
86
|
+
end
|
87
|
+
|
88
|
+
def usage(str = nil)
|
89
|
+
str ? @usage = str : @usage
|
90
|
+
end
|
91
|
+
|
92
|
+
def examples(arr = nil)
|
93
|
+
if arr
|
94
|
+
arr = [arr] unless arr.is_a?(Array)
|
95
|
+
@examples = arr
|
96
|
+
else
|
97
|
+
@examples
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def timeout(i = nil)
|
102
|
+
i ? @timeout = i : @timeout
|
103
|
+
end
|
104
|
+
|
105
|
+
# DSL Only
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def validate(error, &block)
|
110
|
+
@error = error if error
|
111
|
+
@validator = block
|
112
|
+
end
|
113
|
+
|
114
|
+
def aliased(names)
|
115
|
+
[names].flatten.each { |n| @aliases << n }
|
116
|
+
end
|
117
|
+
|
118
|
+
def action(&block)
|
119
|
+
@action = block
|
120
|
+
end
|
121
|
+
|
122
|
+
def subcommand(name, &block)
|
123
|
+
@subcommands[name.to_sym] = Shelldon::Command.new(name, self, &block)
|
124
|
+
end
|
125
|
+
|
126
|
+
def completion(arr = [], &block)
|
127
|
+
@completion = (block_given? ? block : arr)
|
128
|
+
end
|
129
|
+
|
130
|
+
def placeholder
|
131
|
+
@action = proc { fail StandardError }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Shelldon
|
2
|
+
class CommandList
|
3
|
+
attr_reader :shell
|
4
|
+
alias_method :parent, :shell
|
5
|
+
|
6
|
+
def initialize(parent)
|
7
|
+
@shell = parent
|
8
|
+
@commands = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(command)
|
12
|
+
@commands[command.name] = command
|
13
|
+
command.aliases.each { |a| @commands[a.to_sym] = command }
|
14
|
+
end
|
15
|
+
|
16
|
+
def register_default(cmd)
|
17
|
+
@default_command = cmd
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(str)
|
21
|
+
cmd, tokens = find(str)
|
22
|
+
cmd.run(tokens)
|
23
|
+
end
|
24
|
+
|
25
|
+
def find(str)
|
26
|
+
tokens = str.split(' ')
|
27
|
+
key = tokens.first.to_sym
|
28
|
+
if @commands.key?(key)
|
29
|
+
@commands[key].find(tokens[1..-1])
|
30
|
+
else
|
31
|
+
[@default_command, tokens.first]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_default?(cmd)
|
36
|
+
puts cmd.inspect unless cmd.is_a?(Command)
|
37
|
+
cmd.name == @default_command.name
|
38
|
+
end
|
39
|
+
|
40
|
+
def compile_help(cmd)
|
41
|
+
res = [['Command', cmd.christian_name]]
|
42
|
+
res << ['Help', cmd.help] if cmd.help
|
43
|
+
res << ['Usage', "\"#{cmd.usage}\""] if cmd.usage
|
44
|
+
res << ['Examples', cmd.examples] if cmd.examples
|
45
|
+
res << ['Subcommands', cmd.subcommands.values.map(&:name)] unless cmd.subcommands.empty?
|
46
|
+
res
|
47
|
+
end
|
48
|
+
|
49
|
+
def help(str)
|
50
|
+
if str.empty?
|
51
|
+
to_a
|
52
|
+
else
|
53
|
+
cmd = find(str).first
|
54
|
+
if cmd.show && !is_default?(cmd)
|
55
|
+
compile_help(cmd)
|
56
|
+
else
|
57
|
+
fail StandardError
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def config
|
63
|
+
@shell.config
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_a
|
67
|
+
@commands.values.uniq
|
68
|
+
.map { |cmd| cmd.show ? [cmd.name, cmd.aliases, cmd.help] : nil }
|
69
|
+
.compact.sort_by { |(n, _, _)| n.to_s }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|