shelldon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'byebug'
4
+ gem 'terminal-table'
5
+ gem 'rb-readline'
6
+ gem 'getopt'
7
+
8
+ gemspec
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,3 @@
1
+ rm shelldon*.gem
2
+ gem build shelldon.gemspec
3
+ gem install shelldon-*.gem
@@ -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