yap-shell 0.0.2

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: 8c9ea62ec24c22dea32da508a7616ddcb03eedbe
4
+ data.tar.gz: 19cb2c35c5806d94653782b5d5e06655bc734676
5
+ SHA512:
6
+ metadata.gz: 496c9bd377d0e6fa56cd785d7efc751882af5c606690bfaaa1b08590c6bcd8f41ac7bdabaf1a51b21cb945d1c3ad709374a59c2c882b369e6baf23b1f6556f7d
7
+ data.tar.gz: 31c37db8f24ef5be951a7bd4ed6c36675ded870f9af6328d62e5a6b7af34f6d7265a99be063b44d50327f4a506fba31108d68794aa72372941ff2f1bf196f538
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/DESIGN.md ADDED
@@ -0,0 +1,87 @@
1
+ ### Shell
2
+
3
+ The shell takes input from the user, turns them into commands, and then runs them by evaluating or executing them. It is responsible for job control, and for providing useful information to the user such as a prompt and other line decorations.
4
+
5
+ The shell collects input from the user by running a REPL. It then takes the input collected and turns in into a parse tree. The parse tree is then walked, evaluating nodes along the way, which is what evaluates/executes the command. Before each command is evaluated it is processed in order to expand aliases, environment variables, etc.
6
+
7
+ The parse tree is generated by the yap/shell/line-parser. All nodes are defined by that library.
8
+
9
+ ### Nodes
10
+
11
+ The nodes that the parse tree knows about are the following: AssignmentNode, ConditionalNode, CommandNode, EnvNode, EnvWrapperNode, InternalEvalNode, PipelineNode, RedirectionNode, and StatementsNode.
12
+
13
+ These are evaluated by the Shell in the following manner.
14
+
15
+ #### AssignmentNode, EnvNode, EnvWrapperNode
16
+
17
+ An env node represents a type of assignment such as an expression "A=B". This node is evaluated to set environment variables for the current execution context.
18
+
19
+ Currently an AssignmentNode is not used, but it may be used to replace an EnvNode as necessary. An EnvWrapperNode is a meta-EnvNode, all it does is wrap EnvNode(s). It is on the chopping block to go away with parser optimizations that would merge all of these types of concepts together.
20
+
21
+ #### ConditionalNode
22
+
23
+ A conditional node represents a conditional expression such as "&&" or "||". It is evaluated by processing its left child node and then processing its right child node based on its result.
24
+
25
+ #### CommandNode
26
+
27
+ A command node represents any command and arguments provided by the user such as the "ls" in "ls -al" or the "echo" in "echo foo bar baz".
28
+
29
+ The command associated with a command node may be a shell function, an alias, a builtin, or an executable found on the file-system. This lookup and determination of what the command refers to is determined when processing a command node.
30
+
31
+ Every command executed generates an exit status or a signal status.
32
+
33
+ #### InternalEvalNode
34
+
35
+ An internal eval node represents an expression that should be evaluated internally (currently only supports the ruby interpreter running the shell).
36
+
37
+ It needs to generate an exit status or a signal status.
38
+
39
+ #### PipelineNode
40
+
41
+ A pipeline node represents a series of commands or statements where the output of one should be the input of another. For example, "ls | grep foo" is a pipeline.
42
+
43
+ #### RedirectionNode
44
+
45
+ A redirection node represents the intent to redirect STDOUT, STDERR, or both to file. For example, "echo foo > bar.txt".
46
+
47
+ #### StatementsNode
48
+
49
+ A statements node represents more than one statement and contains two nodes: a head and a tail. Either node can refer to any other node.
50
+
51
+
52
+ ### Commands
53
+
54
+ In order to process a CommandNode it must first determine what type of command it represents. Since a CommandNode could refer to a shell function, alias, builtin, or executable on the file-system, we need to determine which one.
55
+
56
+ The CommandFactory is used to do just this.
57
+
58
+
59
+ ### Execution
60
+
61
+ Once a CommandNode is fully expanded, the resulting command will be executed in a corresponding ExecutionContext. There is an execution context for every type command that can be run.
62
+
63
+ It is the job of the execution context to handle wiring up stdin, stdout, stderr before running a command as well as provide the exit status or signal status after a command has finished executing.
64
+
65
+
66
+ #### Handling SIGINT
67
+
68
+ The shell responds to a SIGINT signal (typically Ctrl-C) which will interrupt execution of the shell or any running process.
69
+
70
+ When a process is not running and a SIGINT is received the REPL will print a "^C" and then put a new line and prompt. The shell will not abort itself.
71
+
72
+ When a process is running and the SIGINT signal is received it will forwarded on to the currently running child process. In most cases, the child process will abort, but in some cases it will not. An example of this is program like `irb` that is itself an interactive REPL with the user.
73
+
74
+ #### Handling SIGSTOP / SIGCONT
75
+
76
+ The shell responds to a SIGSTOP signal (typically Ctrl-Z) by forwarding it to the currently running process.
77
+
78
+ When no process is running it prints "^Z" in the REPL and prints a new line and prompt. The shell itself will not suspend.
79
+
80
+ When a process is running and the SIGSTOP signal is received it will be forwarded on to the currently running child process. In most cases, the child process will suspend running.
81
+
82
+ When the child process is suspended control will be returned to the REPL, a user will be able to run new commands. The suspended process can be restarted by sending it the SIGCONT signal. The `fg` builtin can be used to send the suspended process the SIGCONT signal.
83
+
84
+
85
+ ### Job Control
86
+
87
+ .
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yap.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Zach Dennis
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Yap
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'yap-shell'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install yap-shell
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/zdennis/yap-shell/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ Dir[File.join(File.dirname(__FILE__), 'lib/tasks/**/*.rake')].each {|f| load f }
data/WISHLIST.md ADDED
@@ -0,0 +1,40 @@
1
+
2
+ * need to add custom tab completion for aliases, builtins, shell commands, and file system commands
3
+ * support user-specified gems to install (so they're available for rc files)
4
+ * truncate the prompt so you can type long commands
5
+ * add right-prompt (similar to ZSH)
6
+ * provide realtime updating prompt items
7
+ * don't overwrite timestamp? (or have it disappear when you write over it altogether, similar to ZSH)
8
+ * have configurable prompt components
9
+ * interact with the "World" rather than just operating on string contents
10
+ * communicate exit codes to user (w/color)
11
+ * allow for a multiline smart prompt or at least some kind of status notifications
12
+ * get readline's history to work between start and restops
13
+ * fix reload! so it works again
14
+ * handle stderr
15
+ * intelligently handle pipes that show up in ruby blocks, e.g. map{ |a,b| a }
16
+ * Fix shell builtins like pushd/popd. :(
17
+ * Support user-requested background processes, e.g. "ls &"
18
+ * You cannto load yap if you are within yap. Handle the error or let it happen.
19
+
20
+ Others requests.
21
+
22
+ * Sam: Better text movement/manipulation tools
23
+ ** e.g. delete-forward-word
24
+ ** I spend a lot of time using my arrow keys (or equivalents) in my terminal and I hate that.
25
+
26
+ * Jonah: The one thing that seems passive agressive to me is the lack of confirmation when something has gone well. You get barked at if things don’t work, but when they do you get nothing. I think that’s a BS way to behave. Everything. The long running processes give you feedback. Something short and sweet returns nothing. Consistent behavior would be nice.
27
+
28
+ * EJ reply: my shell’s prompt color changes depending on the success/failure of the last command
29
+
30
+ * Sam: a way to say "make the last 7 items in my history into a script/macro”
31
+
32
+ * Sam: I want my history to be dependent on the project, so when I go into an old project I can see the commands I had been running there months ago.
33
+
34
+ * Sam: integration with ruby/python/etc interpreters and my editor, so I can interact with stack traces -- even just to open file and jump to line number.
35
+
36
+ * Sam: sublime style fuzzy autocomplete* Sam: (ooh, but what about an 'oh shit please pipe this through less/paginate this' that you could run after a command has started. 6/3)
37
+
38
+ * @dylanized: themeable
39
+ * @dylanized: browser-based
40
+ * @dylanized: bookmarks
data/bin/yap ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ file = __FILE__
3
+ if File.symlink?(file)
4
+ file = File.readlink(file)
5
+ end
6
+
7
+ class SuspendSignalError < StandardError
8
+ end
9
+
10
+ trap("SIGTSTP") do
11
+ raise SuspendSignalError
12
+ end
13
+
14
+ $LOAD_PATH << File.dirname(file) + '/../lib'
15
+
16
+ # loop do
17
+ load "yap.rb"
18
+ begin
19
+ Yap.run_shell
20
+ rescue StandardError => ex
21
+ puts ex.message
22
+ puts ex.backtrace
23
+ end
24
+ # end
@@ -0,0 +1,60 @@
1
+ namespace :bump do
2
+ namespace :version do
3
+ class ProjectVersion
4
+ FILE = File.dirname(__FILE__) + '/../yap/shell/version.rb'
5
+ PATTERN = /VERSION\s*=\s*"(\d+)\.(\d+)\.(\d+)"/m
6
+
7
+ def initialize(file=FILE, pattern=PATTERN)
8
+ @file = file
9
+ @pattern = pattern
10
+ end
11
+
12
+ def bump(major:nil, minor:nil, patch:nil)
13
+ version = nil
14
+ contents.sub!(@pattern) do
15
+ _major = major.call($1) if major
16
+ _minor = minor.call($2) if minor
17
+ _patch = patch.call($3) if patch
18
+ version = %|VERSION = "#{_major}.#{_minor}.#{_patch}"|
19
+ end
20
+ File.write(@file, contents)
21
+ system "bundle"
22
+ system "git add #{ProjectVersion::FILE}\ && git commit -m 'Bumping version to #{version}'"
23
+ end
24
+
25
+ private
26
+
27
+ def contents
28
+ @contents ||= File.read(@file)
29
+ end
30
+ end
31
+
32
+ desc "Increments the patch number by 1 for the project"
33
+ task :patch do
34
+ ProjectVersion.new.bump(
35
+ major: ->(major){ major },
36
+ minor: ->(minor){ minor },
37
+ patch: ->(patch){ patch.succ }
38
+ )
39
+ end
40
+
41
+ desc "Increments the minor number by 1 for the project"
42
+ task :minor do
43
+ ProjectVersion.new.bump(
44
+ major: ->(major){ major },
45
+ minor: ->(minor){ minor.succ },
46
+ patch: ->(patch){ 0 }
47
+ )
48
+ end
49
+
50
+ desc "Increments the major number by 1 for the project"
51
+ task :major do
52
+ ProjectVersion.new.bump(
53
+ major: ->(major){ major.succ },
54
+ minor: ->(minor){ 0 },
55
+ patch: ->(patch){ 0 }
56
+ )
57
+ end
58
+
59
+ end
60
+ end
data/lib/yap.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'yap/shell'
2
+ require 'yap/world'
3
+
4
+ module Yap
5
+ module WorldAddons
6
+ def self.syntax_ok?(file)
7
+ `ruby -c #{file}`
8
+ $?.exitstatus == 0
9
+ end
10
+
11
+ def self.load_from_files(files:[])
12
+ files.map do |file|
13
+ (puts "Cannot load world addon: #{file} does not exist" and next) unless File.exist?(file)
14
+ (puts "Cannot load world addon: #{file} is not readable" and next) unless File.exist?(file)
15
+ (puts "Cannot load world addon: #{file} is a directory file" and next) if File.directory?(file)
16
+
17
+ # Module.new.tap { |m| m.module_eval IO.read(file) }
18
+ IO.read(file)
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ def self.run_shell
25
+ addons = WorldAddons.load_from_files(files: [
26
+ "#{ENV['HOME']}/.yaprc"
27
+ ])
28
+ Shell::Impl.new(addons: addons).repl
29
+ end
30
+ end
data/lib/yap/shell.rb ADDED
@@ -0,0 +1,73 @@
1
+ require 'readline'
2
+ require 'yaml'
3
+ require 'yap/shell/version'
4
+ require 'yap/shell/builtins'
5
+
6
+ module Yap
7
+ module Shell
8
+ autoload :Aliases, "yap/shell/aliases"
9
+
10
+ autoload :CommandFactory, "yap/shell/commands"
11
+ autoload :CommandError, "yap/shell/commands"
12
+ autoload :CommandUnknownError, "yap/shell/commands"
13
+ autoload :BuiltinCommand, "yap/shell/commands"
14
+ autoload :FileSystemCommand, "yap/shell/commands"
15
+ autoload :RubyCommand, "yap/shell/commands"
16
+ autoload :ShellCommand, "yap/shell/commands"
17
+
18
+ autoload :Execution, "yap/shell/execution"
19
+
20
+ autoload :Evaluation, "yap/shell/evaluation"
21
+ autoload :Repl, "yap/shell/repl"
22
+
23
+ class Impl
24
+ def initialize(addons:)
25
+ @stdin = $stdin
26
+ @stdout = $stdout
27
+ @stderr = $stderr
28
+
29
+ @stdout.sync = true
30
+ @stderr.sync = true
31
+
32
+ @addons = addons
33
+ end
34
+
35
+ def repl
36
+ @world = Yap::World.new(addons:@addons)
37
+ context = Yap::Shell::Execution::Context.new(
38
+ stdin: @stdin,
39
+ stdout: @stdout,
40
+ stderr: @stderr
41
+ )
42
+
43
+ @repl = Yap::Shell::Repl.new(world:@world)
44
+ @repl.loop_on_input do |input|
45
+ evaluation = Yap::Shell::Evaluation.new(stdin:@stdin, stdout:@stdout, stderr:@stderr)
46
+ evaluation.evaluate(input) do |command, stdin, stdout, stderr|
47
+ context.clear_commands
48
+ context.add_command_to_run command, stdin:stdin, stdout:stdout, stderr:stderr
49
+ context.execute(world:@world)
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def history_file
57
+ File.expand_path('~') + '/.yap-history'
58
+ end
59
+
60
+ def load_history
61
+ return unless File.exists?(history_file) && File.readable?(history_file)
62
+ (YAML.load_file(history_file) || []).each do |item|
63
+ ::Readline::HISTORY.push item
64
+ end
65
+
66
+ at_exit do
67
+ File.write history_file, ::Readline::HISTORY.to_a.to_yaml
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,40 @@
1
+ require 'singleton'
2
+
3
+ module Yap::Shell
4
+ class Aliases
5
+ include Singleton
6
+
7
+ def initialize
8
+ @file = ENV["HOME"] + "/.yapaliases.yml"
9
+ @aliases = begin
10
+ YAML.load_file(@file)
11
+ rescue
12
+ {}
13
+ end
14
+ end
15
+
16
+ def fetch_alias(name)
17
+ @aliases[name]
18
+ end
19
+
20
+ def set_alias(name, command)
21
+ @aliases[name] = command
22
+ File.write @file, @aliases.to_yaml
23
+ end
24
+
25
+ def unset_alias(name)
26
+ @aliases.delete(name)
27
+ end
28
+
29
+ def has_key?(key)
30
+ @aliases.has_key?(key)
31
+ end
32
+
33
+ def to_h
34
+ @aliases.keys.sort.inject(Hash.new) do |h,k|
35
+ h[k] = @aliases[k]
36
+ h
37
+ end
38
+ end
39
+ end
40
+ end