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 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