yap-shell 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c9ea62ec24c22dea32da508a7616ddcb03eedbe
4
- data.tar.gz: 19cb2c35c5806d94653782b5d5e06655bc734676
3
+ metadata.gz: e4b50596222f441b63d4b4db6a74b34cdcdee1e7
4
+ data.tar.gz: 2d0095e8845196cb5b6fa0a6c44b5783f8b2849e
5
5
  SHA512:
6
- metadata.gz: 496c9bd377d0e6fa56cd785d7efc751882af5c606690bfaaa1b08590c6bcd8f41ac7bdabaf1a51b21cb945d1c3ad709374a59c2c882b369e6baf23b1f6556f7d
7
- data.tar.gz: 31c37db8f24ef5be951a7bd4ed6c36675ded870f9af6328d62e5a6b7af34f6d7265a99be063b44d50327f4a506fba31108d68794aa72372941ff2f1bf196f538
6
+ metadata.gz: 64733efa5a216775d9c26ab3fe99b48270bf1edb7a80ebe5a2f898906085cd8f127a6c1ca141004890be5fcd8a895119f0bad8ed0856f4d9a4dceb43708a663b
7
+ data.tar.gz: f280c566e9e15cc692d77094a2659983b6dd9cf6433cdd26fe0cf0fe7b4d2d7107845885b080c4548411b82ffe195e4e462f691be165fa8e5271ac2e5a68ff39
data/addons/history.rb ADDED
@@ -0,0 +1,171 @@
1
+ require 'forwardable'
2
+ module Yap
3
+ module WorldAddons
4
+ class History
5
+ def self.load
6
+ instance
7
+ end
8
+
9
+ def self.instance
10
+ @history ||= History.new
11
+ end
12
+
13
+ def initialize
14
+ @history = []
15
+ end
16
+
17
+ def initialize_world(world)
18
+ load_history
19
+
20
+ world.func(:howmuch) do |args:, stdin:, stdout:, stderr:|
21
+ case args.first
22
+ when "time"
23
+ if history_item=self.last_executed_item
24
+ stdout.puts history_item.total_time_s
25
+ else
26
+ stdout.puts "Can't report on something you haven't done."
27
+ end
28
+ else
29
+ stdout.puts "How much what?"
30
+ end
31
+ end
32
+ end
33
+
34
+ def executing(command:, started_at:)
35
+ raise "Cannot acknowledge execution beginning of a command when no group has been started!" unless @history.last
36
+ @history.last.executing command:command, started_at:started_at
37
+ end
38
+
39
+ def executed(command:, stopped_at:)
40
+ raise "Cannot complete execution of a command when no group has been started!" unless @history.last
41
+ @history.last.executed command:command, stopped_at:stopped_at
42
+ end
43
+
44
+ def last_executed_item
45
+ @history.reverse.each do |group|
46
+ last_run = group.last_executed_item
47
+ break last_run if last_run
48
+ end
49
+ end
50
+
51
+ def start_group(started_at)
52
+ @history.push Group.new(started_at:started_at)
53
+ end
54
+
55
+ def stop_group(stopped_at)
56
+ @history.last.stopped_at(stopped_at)
57
+ end
58
+
59
+ private
60
+
61
+ def history_file
62
+ @history_file ||= File.expand_path('~') + '/.yap-history'
63
+ end
64
+
65
+ def load_history
66
+ return unless File.exists?(history_file) && File.readable?(history_file)
67
+ (YAML.load_file(history_file) || []).each do |item|
68
+ ::Readline::HISTORY.push item
69
+ end
70
+
71
+ at_exit do
72
+ File.write history_file, ::Readline::HISTORY.to_a.to_yaml
73
+ end
74
+ end
75
+
76
+ class Group
77
+ extend Forwardable
78
+
79
+ def initialize(started_at:Time.now)
80
+ @started_at = started_at
81
+ @stopped_at = nil
82
+ @items = []
83
+ end
84
+
85
+ def_delegators :@items, :push, :<<, :pop, :first, :last
86
+
87
+ def duration
88
+ return nil unless @stopped_at
89
+ @stopped_at - @started_at
90
+ end
91
+
92
+ def executing(command:, started_at:)
93
+ @items.push Item.new(command:command, started_at:started_at)
94
+ end
95
+
96
+ def executed(command:, stopped_at:)
97
+ raise "2:Cannot complete execution of a command when no group has been started!" unless @items.last
98
+ item = @items.reverse.detect do |item|
99
+ command == item.command && !item.finished?
100
+ end
101
+ item.finished!(stopped_at)
102
+ end
103
+
104
+ def last_executed_item
105
+ @items.reverse.detect{ |item| item.finished? }
106
+ end
107
+
108
+ def stopped_at(time)
109
+ @stopped_at ||= time
110
+ end
111
+ end
112
+
113
+ class Item
114
+ attr_reader :command
115
+
116
+ def initialize(command:command, started_at:Time.now)
117
+ @command = command
118
+ @started_at = started_at
119
+ @ended_at = nil
120
+ end
121
+
122
+ def finished!(at)
123
+ @ended_at = at
124
+ end
125
+
126
+ def finished?
127
+ !!@ended_at
128
+ end
129
+
130
+ def total_time_s
131
+ humanize(@ended_at - @started_at) if @ended_at && @started_at
132
+ end
133
+
134
+ private
135
+
136
+ def humanize secs
137
+ [[60, :seconds], [60, :minutes], [24, :hours], [1000, :days]].inject([]){ |s, (count, name)|
138
+ if secs > 0
139
+ secs, n = secs.divmod(count)
140
+ s.unshift "#{n} #{name}"
141
+ end
142
+ s
143
+ }.join(' ')
144
+ end
145
+ end
146
+
147
+ Yap::Shell::Execution::Context.on(:before_statements_execute) do |context|
148
+ puts "Before group: #{context.to_s}" if ENV["DEBUG"]
149
+ History.instance.start_group(Time.now)
150
+ end
151
+
152
+ Yap::Shell::Execution::Context.on(:after_statements_execute) do |context|
153
+ History.instance.stop_group(Time.now)
154
+ puts "After group: #{context.to_s}" if ENV["DEBUG"]
155
+ end
156
+
157
+ Yap::Shell::Execution::Context.on(:after_process_finished) do |context, *args|
158
+ # puts "After process: #{context.to_s}, args: #{args.inspect}"
159
+ end
160
+
161
+ Yap::Shell::Execution::Context.on(:before_execute) do |context, command:|
162
+ History.instance.executing command:command.str, started_at:Time.now
163
+ end
164
+
165
+ Yap::Shell::Execution::Context.on(:after_execute) do |context, command:, result:|
166
+ History.instance.executed command:command.str, stopped_at:Time.now
167
+ end
168
+
169
+ end
170
+ end
171
+ end
data/bin/yap CHANGED
@@ -1,24 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  file = __FILE__
3
4
  if File.symlink?(file)
4
5
  file = File.readlink(file)
5
6
  end
6
7
 
7
- class SuspendSignalError < StandardError
8
- end
8
+ trap "SIGTSTP", "IGNORE"
9
+ trap "SIGINT", "IGNORE"
10
+ trap "SIGTTIN", "IGNORE"
11
+ trap "SIGTTOU", "IGNORE"
9
12
 
10
- trap("SIGTSTP") do
11
- raise SuspendSignalError
12
- end
13
+ ENV["PATH"] = "/Applications/Postgres.app/Contents/MacOS/bin:/usr/local/share/npm/bin/:/usr/local/heroku/bin:/Users/zdennis/.bin:/Users/zdennis/.rvm/gems/ruby-2.1.5/bin:/Users/zdennis/.rvm/gems/ruby-2.1.5@global/bin:/Users/zdennis/.rvm/rubies/ruby-2.1.5/bin:/usr/local/bin:/usr/local/sbin:/Users/zdennis/bin:/bin:/opt/local/bin:/opt/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/CrossPack-AVR/bin:/private/tmp/.tidbits/bin:/Users/zdennis/source/playground/AdobeAir/AdobeAIRSDK/bin:/Users/zdennis/.rvm/bin:/Users/zdennis/Downloads/adt-bundle-mac-x86_64-20130219/sdk/tools/:/Users/zdennis/.rvm/bin"
14
+ ENV["GEM_HOME"] = "/Users/zdennis/.rvm/gems/ruby-2.1.5:/Users/zdennis/.rvm/gems/ruby-2.1.5@global"
15
+ ENV["GEM_PATH"] = "/Users/zdennis/.rvm/gems/ruby-2.1.5"
13
16
 
14
17
  $LOAD_PATH << File.dirname(file) + '/../lib'
15
18
 
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
19
+ require "yap"
20
+ Yap.run_shell
data/lib/tasks/gem.rake CHANGED
@@ -15,11 +15,13 @@ namespace :bump do
15
15
  _major = major.call($1) if major
16
16
  _minor = minor.call($2) if minor
17
17
  _patch = patch.call($3) if patch
18
- version = %|VERSION = "#{_major}.#{_minor}.#{_patch}"|
18
+ version = "#{_major}.#{_minor}.#{_patch}"
19
+ results = %|VERSION = "#{version}"|
19
20
  end
20
21
  File.write(@file, contents)
21
22
  system "bundle"
22
- system "git add #{ProjectVersion::FILE}\ && git commit -m 'Bumping version to #{version}'"
23
+ system "git add #{ProjectVersion::FILE} && git commit -m 'Bumping version to #{version}'"
24
+ system "git tag v#{version}"
23
25
  end
24
26
 
25
27
  private
data/lib/yap.rb CHANGED
@@ -14,17 +14,50 @@ module Yap
14
14
  (puts "Cannot load world addon: #{file} is not readable" and next) unless File.exist?(file)
15
15
  (puts "Cannot load world addon: #{file} is a directory file" and next) if File.directory?(file)
16
16
 
17
- # Module.new.tap { |m| m.module_eval IO.read(file) }
18
- IO.read(file)
17
+ addon = file.end_with?("rc") ? load_rcfile(file) : load_addon_file(file)
18
+ addon.load
19
+ end
20
+ end
21
+
22
+ class RcFile
23
+ def initialize(contents)
24
+ @contents = contents
25
+ end
26
+
27
+ def load
28
+ self
29
+ end
30
+
31
+ def initialize_world(world)
32
+ world.instance_eval @contents
19
33
  end
20
34
  end
21
- end
22
35
 
36
+ def self.load_rcfile(file)
37
+ RcFile.new IO.read(file)
38
+ end
39
+
40
+ def self.load_addon_file(file)
41
+ name = File.basename(file).sub(/\.[^\.]+$/, "").split(/[_]/).map(&:capitalize).join
42
+ klass_name = "Yap::WorldAddons::#{name}"
43
+
44
+ load file
45
+
46
+ if Yap::WorldAddons.const_defined?(name)
47
+ Yap::WorldAddons.const_get(name)
48
+ else
49
+ raise("Did not find #{klass_name} in #{file}")
50
+ end
51
+ end
52
+ end
23
53
 
24
54
  def self.run_shell
25
- addons = WorldAddons.load_from_files(files: [
26
- "#{ENV['HOME']}/.yaprc"
27
- ])
55
+ addon_files = Dir[
56
+ "#{ENV['HOME']}/.yaprc",
57
+ "#{ENV['HOME']}/.yap-addons/*.rb"
58
+ ]
59
+
60
+ addons = WorldAddons.load_from_files(files:addon_files)
28
61
  Shell::Impl.new(addons: addons).repl
29
62
  end
30
63
  end
data/lib/yap/shell.rb CHANGED
@@ -51,22 +51,6 @@ module Yap
51
51
  end
52
52
  end
53
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
54
  end
71
55
 
72
56
  end
@@ -6,9 +6,9 @@ module Yap::Shell
6
6
  Yap::Shell::BuiltinCommand.add(name, &blk)
7
7
  end
8
8
 
9
- def self.execute_builtin(name, *args)
10
- builtin = Yap::Shell::BuiltinCommand.builtins.fetch(name){ raise("Builtin #{name} not found") }
11
- builtin.call *args
9
+ def self.execute_builtin(name, args:, stdin:, stdout:, stderr:)
10
+ command = Yap::Shell::BuiltinCommand.new(str:name, args: args)
11
+ command.execute(stdin:stdin, stdout:stdout, stderr:stderr)
12
12
  end
13
13
 
14
14
  Dir[File.dirname(__FILE__) + "/builtins/**/*.rb"].each do |f|
@@ -3,35 +3,57 @@ module Yap::Shell
3
3
  DIRECTORY_HISTORY = []
4
4
  DIRECTORY_FUTURE = []
5
5
 
6
- builtin :cd do |path=ENV['HOME'], *_|
7
- DIRECTORY_HISTORY << Dir.pwd
8
- Dir.chdir(path)
9
- ENV["PWD"] = Dir.pwd
10
- output=""
6
+ builtin :cd do |args:, stderr:, **|
7
+ path = args.first || ENV['HOME']
8
+ cwd = Dir.pwd
9
+ if Dir.exist?(path)
10
+ DIRECTORY_HISTORY << cwd
11
+ Dir.chdir(path)
12
+ ENV["PWD"] = cwd
13
+ exit_status = 0
14
+ else
15
+ stderr.puts "cd: #{path}: No such file or directory"
16
+ exit_status = 1
17
+ end
11
18
  end
12
19
 
13
- builtin :popd do
20
+ builtin :popd do |args:, stderr:, **keyword_args|
14
21
  output = []
22
+ cwd = Dir.pwd
15
23
  if DIRECTORY_HISTORY.any?
16
- DIRECTORY_FUTURE << Dir.pwd
17
24
  path = DIRECTORY_HISTORY.pop
18
- execute_builtin :cd, path
25
+ if Dir.exist?(path)
26
+ DIRECTORY_FUTURE << cwd
27
+ Dir.chdir(path)
28
+ ENV["PWD"] =cwd
29
+ exit_status = 0
30
+ else
31
+ stderr.puts "popd: #{path}: No such file or directory"
32
+ exit_status = 1
33
+ end
19
34
  else
20
- output << "popd: directory stack empty\n"
35
+ stderr.puts "popd: directory stack empty"
36
+ exit_status = 1
21
37
  end
22
- output.join("\n")
23
38
  end
24
39
 
25
- builtin :pushd do
40
+ builtin :pushd do |args:, stderr:, **keyword_args|
26
41
  output = []
27
42
  if DIRECTORY_FUTURE.any?
28
- DIRECTORY_HISTORY << Dir.pwd
29
43
  path = DIRECTORY_FUTURE.pop
30
- execute_builtin :cd, path
44
+ if Dir.exist?(path)
45
+ DIRECTORY_HISTORY << Dir.pwd
46
+ Dir.chdir(path)
47
+ ENV["PWD"] = path
48
+ exit_status = 0
49
+ else
50
+ stderr.puts "pushd: #{path}: No such file or directory"
51
+ exit_status = 1
52
+ end
31
53
  else
32
- output << "pushd: there are no directories in your future\n"
54
+ stderr.puts "pushd: there are no directories in your future"
55
+ exit_status = 1
33
56
  end
34
- output.join("\n")
35
57
  end
36
58
  end
37
59
  end
@@ -1,5 +1,6 @@
1
1
  require 'shellwords'
2
2
  require 'yap/shell/aliases'
3
+ require 'yap/shell/execution/result'
3
4
 
4
5
  module Yap::Shell
5
6
  class CommandError < StandardError ; end
@@ -41,9 +42,9 @@ module Yap::Shell
41
42
 
42
43
  def self.builtins
43
44
  @builtins ||= {
44
- builtins: lambda { puts @builtins.keys.sort },
45
- exit: lambda { |code = 0| exit(code.to_i) },
46
- fg: lambda{ :resume },
45
+ builtins: lambda { |stdout:, **| stdout.puts @builtins.keys.sort },
46
+ exit: lambda { |code = 0, **| exit(code.to_i) },
47
+ fg: lambda{ |**| :resume },
47
48
  }
48
49
  end
49
50
 
@@ -51,9 +52,9 @@ module Yap::Shell
51
52
  builtins.merge!(command.to_sym => action)
52
53
  end
53
54
 
54
- def execute
55
+ def execute(stdin:, stdout:, stderr:)
55
56
  action = self.class.builtins.fetch(str.to_sym){ raise("Missing proc for builtin: '#{builtin}' in #{str.inspect}") }
56
- action.call *args
57
+ action.call args:args, stdin:stdin, stdout:stdout, stderr:stderr
57
58
  end
58
59
 
59
60
  def type
@@ -11,12 +11,33 @@ module Yap::Shell
11
11
 
12
12
  def evaluate(input, &blk)
13
13
  @blk = blk
14
+ parser = Yap::Shell::Parser.new
15
+ input = recursively_find_and_replace_command_substitutions(parser, input)
14
16
  ast = Yap::Shell::Parser.new.parse(input)
15
17
  ast.accept(self)
16
18
  end
17
19
 
18
20
  private
19
21
 
22
+ # +recursively_find_and_replace_command_substitutions+ is responsible for recursively
23
+ # finding and expanding command substitutions, in a depth first manner.
24
+ def recursively_find_and_replace_command_substitutions(parser, input)
25
+ input = input.dup
26
+ parser.each_command_substitution_for(input) do |substitution_result, start_position, end_position|
27
+ result = recursively_find_and_replace_command_substitutions(parser, substitution_result.str)
28
+ position = substitution_result.position
29
+ ast = parser.parse(result)
30
+ with_standard_streams do |stdin, stdout, stderr|
31
+ r,w = IO.pipe
32
+ @stdout = w
33
+ ast.accept(self)
34
+ input[position.min...position.max] = r.read.chomp
35
+ end
36
+ end
37
+ input
38
+ end
39
+
40
+
20
41
  ######################################################################
21
42
  # #
22
43
  # VISITOR METHODS FOR AST TREE WALKING #
@@ -26,9 +47,10 @@ module Yap::Shell
26
47
  def visit_CommandNode(node)
27
48
  @aliases_expanded ||= []
28
49
  with_standard_streams do |stdin, stdout, stderr|
50
+ args = node.args.map(&:lvalue)
29
51
  if !node.literal? && !@aliases_expanded.include?(node.command) && _alias=Aliases.instance.fetch_alias(node.command)
30
52
  @suppress_events = true
31
- ast = Yap::Shell::Parser.new.parse([_alias].concat(node.args).join(" "))
53
+ ast = Yap::Shell::Parser.new.parse([_alias].concat(args).join(" "))
32
54
  @aliases_expanded.push(node.command)
33
55
  ast.accept(self)
34
56
  @aliases_expanded.pop
@@ -36,7 +58,7 @@ module Yap::Shell
36
58
  else
37
59
  command = CommandFactory.build_command_for(
38
60
  command: node.command,
39
- args: shell_expand(node.args),
61
+ args: shell_expand(args),
40
62
  heredoc: node.heredoc,
41
63
  internally_evaluate: node.internally_evaluate?)
42
64
  @stdin, @stdout, @stderr = stream_redirections_for(node)
@@ -94,7 +116,6 @@ module Yap::Shell
94
116
  with_standard_streams do |stdin, stdout, stderr|
95
117
  # Modify @stdout and @stderr for the first command
96
118
  stdin, @stdout = IO.pipe
97
- @stderr = @stdout
98
119
 
99
120
  # Don't modify @stdin for the first command in the pipeline.
100
121
  node.head.accept(self)
@@ -1,15 +1,13 @@
1
+ require "yap/shell/execution/context"
2
+ require "yap/shell/execution/command_execution"
3
+ require "yap/shell/execution/builtin_command_execution"
4
+ require "yap/shell/execution/file_system_command_execution"
5
+ require "yap/shell/execution/ruby_command_execution"
6
+ require "yap/shell/execution/shell_command_execution"
7
+ require "yap/shell/execution/result"
8
+
1
9
  module Yap::Shell
2
10
  module Execution
3
- autoload :Context, "yap/shell/execution/context"
4
-
5
- autoload :CommandExecution, "yap/shell/execution/command_execution"
6
- autoload :BuiltinCommandExecution, "yap/shell/execution/builtin_command_execution"
7
- autoload :FileSystemCommandExecution, "yap/shell/execution/file_system_command_execution"
8
- autoload :RubyCommandExecution, "yap/shell/execution/ruby_command_execution"
9
- autoload :ShellCommandExecution, "yap/shell/execution/shell_command_execution"
10
-
11
- autoload :Result, "yap/shell/execution/result"
12
-
13
11
  Context.register BuiltinCommandExecution, command_type: :BuiltinCommand
14
12
  Context.register FileSystemCommandExecution, command_type: :FileSystemCommand
15
13
  Context.register ShellCommandExecution, command_type: :ShellCommand
@@ -1,14 +1,15 @@
1
+ require 'yap/shell/execution/result'
2
+
1
3
  module Yap::Shell::Execution
2
4
  class BuiltinCommandExecution < CommandExecution
3
5
  on_execute do |command:, n:, of:|
4
- command_output = command.execute
5
- if command_output == :resume
6
+ status_code = command.execute(stdin:@stdin, stdout:@stdout, stderr:@stderr)
7
+ if status_code == :resume
6
8
  ResumeExecution.new(status_code:0, directory:Dir.pwd, n:n, of:of)
7
9
  else
8
- @stdout.write command_output
9
10
  @stdout.close if @stdout != $stdout && !@stdout.closed?
10
11
  @stderr.close if @stderr != $stderr && !@stderr.closed?
11
- Result.new(status_code:0, directory:Dir.pwd, n:n, of:of)
12
+ Result.new(status_code:status_code, directory:Dir.pwd, n:n, of:of)
12
13
  end
13
14
  end
14
15
  end
@@ -57,26 +57,13 @@ module Yap::Shell::Execution
57
57
  world: world
58
58
  )
59
59
 
60
+ @saved_tty_attrs = Termios.tcgetattr(STDIN)
60
61
  self.class.fire :before_execute, execution_context, command: command
61
62
  result = execution_context.execute(command:command, n:i, of:of)
62
63
  self.class.fire :after_execute, execution_context, command: command, result: result
63
64
 
64
- case result
65
- when SuspendExecution
66
- # Ensure echo is turned back on. Some suspended programs
67
- # may have turned it off.
68
- `stty echo`
69
- @suspended_execution_contexts.push execution_context
70
- when ResumeExecution
71
- execution_context = @suspended_execution_contexts.pop
72
- if execution_context
73
- execution_context.resume
74
- else
75
- stderr.puts "fg: No such job"
76
- end
77
- end
78
-
79
- results << result
65
+ results << process_execution_result(execution_context:execution_context, result: result)
66
+ Termios.tcsetattr(STDIN, Termios::TCSANOW, @saved_tty_attrs)
80
67
  end
81
68
  end
82
69
 
@@ -84,5 +71,26 @@ module Yap::Shell::Execution
84
71
 
85
72
  results.last
86
73
  end
74
+
75
+ private
76
+
77
+ def process_execution_result(execution_context:, result:)
78
+ case result
79
+ when SuspendExecution
80
+ @suspended_execution_contexts.push execution_context
81
+ return result
82
+
83
+ when ResumeExecution
84
+ execution_context = @suspended_execution_contexts.pop
85
+ if execution_context
86
+ nresult = execution_context.resume
87
+ return process_execution_result execution_context: execution_context, result: nresult
88
+ else
89
+ stderr.puts "fg: No such job"
90
+ end
91
+ else
92
+ return result
93
+ end
94
+ end
87
95
  end
88
96
  end
@@ -1,79 +1,93 @@
1
+ require 'yap/shell/execution/result'
2
+ require 'termios'
3
+
1
4
  module Yap::Shell::Execution
2
5
  class FileSystemCommandExecution < CommandExecution
3
6
  on_execute do |command:, n:, of:, resume_blk:nil|
4
7
  stdin, stdout, stderr, world = @stdin, @stdout, @stderr, @world
5
8
  result = nil
6
- begin
7
- if resume_blk
8
- pid = resume_blk.call
9
- else
10
- r,w = nil, nil
11
- if command.heredoc
12
- r,w = IO.pipe
13
- stdin = r
14
- end
15
-
16
- pid = fork do
17
- # Start a new process gruop as the session leader. Now we are
18
- # responsible for sending signals that would have otherwise
19
- # been propagated to the process, e.g. SIGINT, SIGSTOP, SIGCONT, etc.
20
- stdin = File.open(stdin, "rb") if stdin.is_a?(String)
21
- stdout = File.open(stdout, "wb") if stdout.is_a?(String)
22
- stderr = File.open(stderr, "wb") if stderr.is_a?(String)
23
-
24
- stdout = stderr if stdout == :stderr
25
- stderr = stdout if stderr == :stdout
26
-
27
- $stdin.reopen stdin
28
- $stdout.reopen stdout
29
- $stderr.reopen stderr
30
- Process.setsid
31
-
32
- Kernel.exec command.to_executable_str
33
- end
34
- if command.heredoc
35
- w.write command.heredoc
36
- w.close
37
- end
9
+ if resume_blk
10
+ pid = resume_blk.call
11
+ else
12
+ r,w = nil, nil
13
+ if command.heredoc
14
+ r,w = IO.pipe
15
+ stdin = r
38
16
  end
39
17
 
40
- # This prevents the shell from processing sigint and lets the child
41
- # process handle it. Necessary for interactive shells that do not
42
- # abort on Ctrl-C such as irb.
43
- Signal.trap("SIGINT") do
44
- Process.kill("SIGINT", pid)
45
- end
18
+ pid = fork do
19
+ # reset signals in case any were ignored
20
+ Signal.trap("SIGINT", "DEFAULT")
21
+ Signal.trap("SIGQUIT", "DEFAULT")
22
+ Signal.trap("SIGTSTP", "DEFAULT")
23
+ Signal.trap("SIGTTIN", "DEFAULT")
24
+ Signal.trap("SIGTTOU", "DEFAULT")
25
+
26
+ # Set the process group of the forked to child to that of the
27
+ Process.setpgrp
28
+
29
+ # Start a new process group as the session leader. Now we are
30
+ # responsible for sending signals that would have otherwise
31
+ # been propagated to the process, e.g. SIGINT, SIGSTOP, SIGCONT, etc.
32
+ stdin = File.open(stdin, "rb") if stdin.is_a?(String)
33
+ stdout = File.open(stdout, "wb") if stdout.is_a?(String)
34
+ stderr = File.open(stderr, "wb") if stderr.is_a?(String)
46
35
 
47
- Process.waitpid(pid) unless of > 1
48
- Signal.trap("SIGINT", "DEFAULT")
36
+ stdout = stderr if stdout == :stderr
37
+ stderr = stdout if stderr == :stdout
49
38
 
50
- # If we're not printing to the terminal than close in/out/err. This
51
- # is so the next command in the pipeline can complete and don't hang waiting for
52
- # stdin after the command that's writing to its stdin has completed.
53
- if stdout != $stdout && stdout.is_a?(IO) && !stdout.closed? then
54
- stdout.close
39
+ $stdin.reopen stdin
40
+ $stdout.reopen stdout
41
+ $stderr.reopen stderr
42
+
43
+ Kernel.exec command.to_executable_str
55
44
  end
56
- if stderr != $stderr && stderr.is_a?(IO) && !stderr.closed? then
57
- stderr.close
45
+
46
+ # Put the child process into a process group of its own
47
+ Process.setpgid pid, pid
48
+
49
+ if command.heredoc
50
+ w.write command.heredoc
51
+ w.close
58
52
  end
59
- # if stdin != $stdin && !stdin.closed? then stdin.close end
53
+ end
60
54
 
61
- rescue Interrupt
62
- Process.kill "SIGINT", pid
55
+ # Set terminal's process group to that of the child process
56
+ Termios.tcsetpgrp STDIN, pid
57
+ pid, status = Process.wait2(-1, Process::WUNTRACED) unless of > 1
58
+ puts "Process (#{pid}) stopped: #{status.inspect}" if ENV["DEBUG"]
63
59
 
64
- rescue SuspendSignalError => ex
65
- Process.kill "SIGSTOP", pid
60
+ # If we're not printing to the terminal than close in/out/err. This
61
+ # is so the next command in the pipeline can complete and don't hang waiting for
62
+ # stdin after the command that's writing to its stdin has completed.
63
+ if stdout != $stdout && stdout.is_a?(IO) && !stdout.closed? then
64
+ stdout.close
65
+ end
66
+ if stderr != $stderr && stderr.is_a?(IO) && !stderr.closed? then
67
+ stderr.close
68
+ end
69
+ # if stdin != $stdin && !stdin.closed? then stdin.close end
66
70
 
67
- # The Process started above with the PID +pid+ is a child process
68
- # so it has also received the suspend/SIGTSTP signal.
69
- suspended(command:command, n:n, of:of, pid: pid)
70
71
 
71
- result = SuspendExecution.new(status_code:nil, directory:Dir.pwd, n:n, of:of)
72
+ # if the pid that just stopped was the process group owner then
73
+ # give it back to the us so we can become the foreground process
74
+ # in the terminal
75
+ if pid == Termios.tcgetpgrp(STDIN)
76
+ Process.setpgid Process.pid, Process.pid
77
+ Termios.tcsetpgrp STDIN, Process.pid
72
78
  end
73
79
 
74
- # if a signal killed or stopped the process (such as SIGINT or SIGTSTP) $? is nil.
75
- exitstatus = $? ? $?.exitstatus : nil
76
- result || Result.new(status_code:exitstatus, directory:Dir.pwd, n:n, of:of)
80
+ # if the reason we stopped is from being suspended
81
+ if status.stopsig == Signal.list["TSTP"]
82
+ puts "Process (#{pid}) suspended: #{status.stopsig}" if ENV["DEBUG"]
83
+ suspended(command:command, n:n, of:of, pid: pid)
84
+ result = Yap::Shell::Execution::SuspendExecution.new(status_code:nil, directory:Dir.pwd, n:n, of:of)
85
+ else
86
+ puts "Process (#{pid}) not suspended? #{status.stopsig}" if ENV["DEBUG"]
87
+ # if a signal killed or stopped the process (such as SIGINT or SIGTSTP) $? is nil.
88
+ exitstatus = $? ? $?.exitstatus : nil
89
+ result = Yap::Shell::Execution::Result.new(status_code:exitstatus, directory:Dir.pwd, n:n, of:of)
90
+ end
77
91
  end
78
92
 
79
93
  def resume
@@ -27,12 +27,6 @@ module Yap::Shell
27
27
  rescue Interrupt
28
28
  puts "^C"
29
29
  next
30
- rescue SuspendSignalError
31
- # no-op since if we got here we're on the already at the top-level
32
- # repl and there's nothing to suspend but ourself and we're not
33
- # about to do that.
34
- puts "^Z"
35
- next
36
30
  end
37
31
  end
38
32
  end
@@ -1,5 +1,5 @@
1
1
  module Yap
2
2
  module Shell
3
- VERSION = "0.0.2"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
data/lib/yap/world.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'term/ansicolor'
2
2
  require 'forwardable'
3
+ require 'yap/shell/execution'
3
4
 
4
5
  module Yap
5
6
  class World
@@ -14,7 +15,7 @@ module Yap
14
15
  end
15
16
 
16
17
  addons.each do |addon|
17
- self.instance_eval addon
18
+ addon.initialize_world(self)
18
19
  end
19
20
  end
20
21
 
data/rcfiles/.yaprc CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
- require 'chronic'
4
- require 'term/ansicolor'
3
+ ENV["PATH"] = "/Applications/Postgres.app/Contents/MacOS/bin:/usr/local/share/npm/bin/:/usr/local/heroku/bin:/Users/zdennis/.bin:/Users/zdennis/.rvm/gems/ruby-2.1.5/bin:/Users/zdennis/.rvm/gems/ruby-2.1.5@global/bin:/Users/zdennis/.rvm/rubies/ruby-2.1.5/bin:/usr/local/bin:/usr/local/sbin:/Users/zdennis/bin:/bin:/opt/local/bin:/opt/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/CrossPack-AVR/bin:/private/tmp/.tidbits/bin:/Users/zdennis/source/playground/AdobeAir/AdobeAIRSDK/bin:/Users/zdennis/.rvm/bin:/Users/zdennis/Downloads/adt-bundle-mac-x86_64-20130219/sdk/tools/:/Users/zdennis/.rvm/bin"
4
+ ENV["GEM_HOME"] = "/Users/zdennis/.rvm/gems/ruby-2.1.5:/Users/zdennis/.rvm/gems/ruby-2.1.5@global"
5
+
6
+ # require 'chronic'
7
+ # require 'term/ansicolor'
5
8
 
6
9
  #
7
10
  # Configuring your prompt. This can be set to a static value or to a
@@ -33,151 +36,8 @@ self.prompt = -> do
33
36
  "#{dark(green('£'))} #{yellow(pwd)} #{git_branch}#{red(arrow)} "
34
37
  end
35
38
 
36
- func :howmuch do |args:, stdin:, stdout:, stderr:|
37
- case args.first
38
- when "time"
39
- if history_item=CommandHistory.last_run_command
40
- stdout.puts history_item.total_time_s
41
- else
42
- stdout.puts "Can't report on something you haven't done."
43
- end
44
- else
45
- stdout.puts "How much what?"
46
- end
47
- end
48
39
 
49
40
  func :upcase do |args:, stdin:, stdout:, stderr:|
50
41
  str = stdin.read
51
42
  stdout.puts str.upcase
52
43
  end
53
-
54
- class CommandHistoryImplementation
55
- def initialize
56
- @history = []
57
- end
58
-
59
- def start_group(time)
60
- @group = Group.new(started_at:time)
61
- @history.push @group
62
- end
63
-
64
- def stop_group(time)
65
- @group.stopped_at(time)
66
- end
67
-
68
- def push(item)
69
- @group.add_item item
70
- end
71
-
72
- def last_group
73
- @history.last
74
- end
75
-
76
- def last_command
77
- return @history.last.last_item if @history.last
78
- nil
79
- end
80
-
81
- def last_run_command
82
- @history.reverse.each do |group|
83
- last_run = group.items.reverse.detect{ |item| item.finished? }
84
- break last_run if last_run
85
- end
86
- end
87
-
88
- class Group
89
- def initialize(started_at:Time.now)
90
- @started_at = started_at
91
- @items = []
92
- end
93
-
94
- def add_item(item)
95
- @items.push item
96
- end
97
-
98
- def items
99
- @items
100
- end
101
-
102
- def last_item
103
- @items.last
104
- end
105
-
106
- def stopped_at(time)
107
- @stopped_at ||= time
108
- end
109
-
110
- def duration
111
- return nil unless @stopped_at
112
- @stopped_at - @started_at
113
- end
114
- end
115
-
116
- class Item
117
- def initialize(command_str:command_str, started_at:Time.now)
118
- @command_str = command_str
119
- @started_at = started_at
120
- @ended_at = nil
121
- end
122
-
123
- def finished!
124
- @ended_at = Time.now
125
- end
126
-
127
- def finished?
128
- !!@ended_at
129
- end
130
-
131
- def total_time_s
132
- humanize(@ended_at - @started_at) if @ended_at && @started_at
133
- end
134
-
135
- private
136
-
137
- def humanize secs
138
- [[60, :seconds], [60, :minutes], [24, :hours], [1000, :days]].inject([]){ |s, (count, name)|
139
- if secs > 0
140
- secs, n = secs.divmod(count)
141
- s.unshift "#{n} #{name}"
142
- end
143
- s
144
- }.join(' ')
145
- end
146
- end
147
-
148
- end
149
-
150
- CommandHistory = CommandHistoryImplementation.new
151
-
152
- Yap::Shell::Execution::Context.on(:before_statements_execute) do |context|
153
- puts "Before group: #{context.to_s}" if ENV["DEBUG"]
154
- CommandHistory.start_group(Time.now)
155
- end
156
-
157
- Yap::Shell::Execution::Context.on(:after_statements_execute) do |context|
158
- CommandHistory.stop_group(Time.now)
159
- puts "After group: #{context.to_s}" if ENV["DEBUG"]
160
- end
161
-
162
- Yap::Shell::Execution::Context.on(:after_process_finished) do |context, *args|
163
- # puts "After process: #{context.to_s}, args: #{args.inspect}"
164
- end
165
-
166
- Yap::Shell::Execution::Context.on(:before_execute) do |context, command:|
167
- CommandHistory.push CommandHistoryImplementation::Item.new(command_str: command.str, started_at: Time.now)
168
- end
169
-
170
- Yap::Shell::Execution::Context.on(:after_execute) do |context, command:, result:|
171
- CommandHistory.last_command.finished!
172
- # if result.status_code == 0
173
- # # t = TermInfo.new("xterm-color", STDOUT)
174
- # # h, w = t.screen_size
175
- # # t.control "cub", w
176
- # # # msg =
177
- # # t.control "cuf"
178
- # # # t.control "home"
179
- # #
180
- # # # t.write "hi"
181
- # # # t.control "rc"
182
- # end
183
- end
data/scripts/4 ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ 1000.times do
4
+ ("A".."z").each do |letter|
5
+ puts letter
6
+ sleep 2
7
+ end
8
+ end
data/scripts/bg-vim ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+ sleep 5
4
+ vim < /dev/tty
data/scripts/fail ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ exit 1
data/scripts/letters ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ 1000.times do
4
+ ("A".."z").each do |letter|
5
+ puts letter
6
+ sleep 2
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ 1000.times do |i|
4
+ puts i
5
+ sleep 2
6
+ end
data/scripts/pass ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ exit 0
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ sleep 1000
4
+ echo "Done"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ STDERR.puts "this is an error"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ STDOUT.puts "This is stdout"
data/yap-shell.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "yap-shell-parser", "~> 0.0.2"
21
+ spec.add_dependency "yap-shell-parser", "~> 0.1.0"
22
22
  spec.add_dependency "term-ansicolor", "~> 1.3"
23
23
  spec.add_dependency "chronic", "~> 0.10"
24
24
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yap-shell
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Dennis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-02 00:00:00.000000000 Z
11
+ date: 2015-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yap-shell-parser
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.2
19
+ version: 0.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.0.2
26
+ version: 0.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: term-ansicolor
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -95,6 +95,7 @@ files:
95
95
  - README.md
96
96
  - Rakefile
97
97
  - WISHLIST.md
98
+ - addons/history.rb
98
99
  - bin/yap
99
100
  - lib/tasks/gem.rake
100
101
  - lib/yap.rb
@@ -117,6 +118,15 @@ files:
117
118
  - lib/yap/shell/version.rb
118
119
  - lib/yap/world.rb
119
120
  - rcfiles/.yaprc
121
+ - scripts/4
122
+ - scripts/bg-vim
123
+ - scripts/fail
124
+ - scripts/letters
125
+ - scripts/lots-of-output
126
+ - scripts/pass
127
+ - scripts/simulate-long-running
128
+ - scripts/write-to-stderr.rb
129
+ - scripts/write-to-stdout.rb
120
130
  - yap-shell.gemspec
121
131
  homepage: https://github.com/zdennis/yap-shell
122
132
  licenses: