yap-shell 0.0.2 → 0.1.0

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