yap-shell 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/Gemfile +5 -0
  4. data/WISHLIST.md +14 -0
  5. data/addons/history/Gemfile +2 -0
  6. data/addons/history/history.rb +101 -0
  7. data/addons/history/lib/history/buffer.rb +204 -0
  8. data/addons/history/lib/history/events.rb +13 -0
  9. data/addons/keyboard_macros/keyboard_macros.rb +295 -0
  10. data/addons/prompt/Gemfile +1 -0
  11. data/addons/prompt/right_prompt.rb +17 -0
  12. data/addons/prompt_updates/prompt_updates.rb +28 -0
  13. data/addons/tab_completion/Gemfile +0 -0
  14. data/addons/tab_completion/lib/tab_completion/completer.rb +62 -0
  15. data/addons/tab_completion/lib/tab_completion/custom_completion.rb +33 -0
  16. data/addons/tab_completion/lib/tab_completion/dsl_methods.rb +7 -0
  17. data/addons/tab_completion/lib/tab_completion/file_completion.rb +75 -0
  18. data/addons/tab_completion/tab_completion.rb +157 -0
  19. data/bin/yap +13 -4
  20. data/lib/tasks/addons.rake +51 -0
  21. data/lib/yap.rb +4 -55
  22. data/lib/yap/shell.rb +51 -10
  23. data/lib/yap/shell/builtins.rb +2 -2
  24. data/lib/yap/shell/builtins/alias.rb +2 -2
  25. data/lib/yap/shell/builtins/cd.rb +9 -11
  26. data/lib/yap/shell/builtins/env.rb +11 -0
  27. data/lib/yap/shell/commands.rb +29 -18
  28. data/lib/yap/shell/evaluation.rb +185 -68
  29. data/lib/yap/shell/evaluation/shell_expansions.rb +85 -0
  30. data/lib/yap/shell/event_emitter.rb +18 -0
  31. data/lib/yap/shell/execution/builtin_command_execution.rb +1 -1
  32. data/lib/yap/shell/execution/command_execution.rb +3 -3
  33. data/lib/yap/shell/execution/context.rb +32 -9
  34. data/lib/yap/shell/execution/file_system_command_execution.rb +12 -7
  35. data/lib/yap/shell/execution/ruby_command_execution.rb +6 -6
  36. data/lib/yap/shell/execution/shell_command_execution.rb +17 -2
  37. data/lib/yap/shell/prompt.rb +21 -0
  38. data/lib/yap/shell/repl.rb +179 -18
  39. data/lib/yap/shell/version.rb +1 -1
  40. data/lib/yap/world.rb +149 -15
  41. data/lib/yap/world/addons.rb +135 -0
  42. data/rcfiles/.yaprc +240 -10
  43. data/test.rb +206 -0
  44. data/update-rawline.sh +6 -0
  45. data/yap-shell.gemspec +11 -3
  46. metadata +101 -10
  47. data/addons/history.rb +0 -171
@@ -0,0 +1 @@
1
+ gem "chronic", "~> 0.10.2"
@@ -0,0 +1,17 @@
1
+ class RightPrompt < Addon
2
+ def self.load_addon
3
+ @instance ||= new
4
+ end
5
+
6
+ def initialize_world(world)
7
+ @world = world
8
+
9
+ # @world.subscribe(:refresh_right_prompt) do |event|
10
+ # @world.right_prompt_text = Time.now.strftime("%H:%M:%S")
11
+ # end
12
+ #
13
+ # @world.events.recur(
14
+ # name: "refresh_right_prompt", source: self, interval_in_ms: 1_000
15
+ # )
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ class PromptUpdates < Addon
2
+ attr_reader :world
3
+
4
+ def initialize_world(world)
5
+ current_branch = determine_branch
6
+
7
+ @world = world
8
+ @world.events.recur(
9
+ name: 'prompt_updates',
10
+ source: self,
11
+ interval_in_ms: 100
12
+ ) do
13
+ new_branch = determine_branch
14
+ if current_branch != new_branch
15
+ current_branch = new_branch
16
+ @world.refresh_prompt
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ private
23
+
24
+ def determine_branch
25
+ `git branch`.scan(/\*\s*(.*)$/).flatten.first
26
+ end
27
+
28
+ end
File without changes
@@ -0,0 +1,62 @@
1
+ class TabCompletion
2
+
3
+ class Completer
4
+ def initialize(char:, line:, completion:, completion_found:, completion_not_found:, done:)
5
+ @completion_char = char
6
+ @line = line
7
+ @completion_proc = completion
8
+ @completion_found_proc = completion_found
9
+ @completion_not_found_proc = completion_not_found
10
+ @done_proc = done
11
+
12
+ @completion_matches = HistoryBuffer.new(0) do |h|
13
+ h.duplicates = false
14
+ h.cycle = true
15
+ end
16
+ @completion_matches.empty
17
+
18
+ @first_time = true
19
+ @word_start = @line.word[:start]
20
+ end
21
+
22
+ def read_bytes(bytes)
23
+ return unless bytes.any?
24
+
25
+ if bytes.map(&:ord) != @completion_char
26
+ @done_proc.call(bytes)
27
+ elsif @first_time
28
+ matches = @completion_proc.call(sub_word) unless !@completion_proc || @completion_proc == []
29
+ matches = matches.to_a.compact.sort.reverse
30
+
31
+ if matches.any?
32
+ @completion_matches.resize(matches.length)
33
+ matches.each { |w| @completion_matches << w }
34
+
35
+ # Get first match
36
+ @completion_matches.back
37
+ match = @completion_matches.get
38
+
39
+ # completion matches is a history implementation and its in reverse order from what
40
+ # a user would expect
41
+ @completion_found_proc.call(completion: match, possible_completions: @completion_matches.reverse)
42
+ else
43
+ @completion_not_found_proc.call
44
+ @done_proc.call
45
+ end
46
+ @first_time = false
47
+ else
48
+ @completion_matches.back
49
+ match = @completion_matches.get
50
+
51
+ @completion_found_proc.call(completion: match, possible_completions: @completion_matches.reverse)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def sub_word
58
+ @line.text[@line.word[:start]..@line.position-1] || ""
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,33 @@
1
+ class TabCompletion
2
+ class CustomCompletion
3
+ PRIORITY = 2
4
+
5
+ attr_reader :name, :pattern, :priority
6
+
7
+ def initialize(world:, name:nil, pattern:nil, priority:PRIORITY, &blk)
8
+ @world = world
9
+ @name = name
10
+ @pattern = pattern
11
+ @priority = priority
12
+ @blk = blk
13
+ end
14
+
15
+ def new(world:)
16
+ @world = world
17
+ self
18
+ end
19
+
20
+ def completions_for(word, line)
21
+ # TODO
22
+ return []
23
+ end
24
+
25
+ private
26
+
27
+ def match_rgx
28
+ return // if pattern.nil?
29
+ return pattern if pattern.is_a?(Regexp)
30
+ /^#{Regexp.escape(pattern.to_s)}\s/
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ class TabCompletion
2
+ module DslMethods
3
+ def tab_completion(name, pattern, &blk)
4
+ self[:tab_completion].add_completion(name, pattern, &blk)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,75 @@
1
+ class TabCompletion
2
+ class FileCompletion
3
+ class << self
4
+ attr_accessor :priority
5
+ end
6
+ self.priority = 1
7
+
8
+ attr_reader :world
9
+
10
+ def initialize(world:, word_break_characters:, path:nil)
11
+ @world = world
12
+ @word_break_characters = word_break_characters
13
+ path ||= @world.env["PATH"]
14
+ @paths = path.split(":")
15
+ end
16
+
17
+ def completions_for(word, line)
18
+ completions = []
19
+ if looking_for_command?
20
+ completions.concat command_completion_matches_for(word, line)
21
+ end
22
+ completions.concat filename_completion_matches_for(word, line)
23
+ completions
24
+ end
25
+
26
+ private
27
+
28
+ def looking_for_command?
29
+ # TODO
30
+ false
31
+ end
32
+
33
+ def command_completion_matches_for(word, line)
34
+ matches = @paths.inject([]) do |matches, path|
35
+ glob = "#{path}*"
36
+ arr = Dir[glob].select { |path| File.executable?(path) && File.file?(path) }
37
+ arr.each { |path| matches << path }
38
+ matches
39
+ end
40
+
41
+ matches.map { |path| File.basename(path) }.sort.uniq.map do |command|
42
+ CompletionResult.new(type: :command, text:command)
43
+ end
44
+ end
45
+
46
+ def filename_completion_matches_for(word, line)
47
+ glob = "#{word}*"
48
+ glob.gsub!("~", world.env["HOME"])
49
+ Dir.glob(glob, File::FNM_CASEFOLD).map do |path|
50
+ text = path.gsub(filtered_work_break_characters_rgx, '\\\\\1')
51
+ descriptive_text = File.basename(text)
52
+ if File.directory?(path)
53
+ CompletionResult.new(type: :directory, text: text, descriptive_text: descriptive_text)
54
+ elsif File.symlink?(path)
55
+ CompletionResult.new(type: :symlink, text: text, descriptive_text: descriptive_text)
56
+ elsif File.file?(path) && File.executable?(path)
57
+ CompletionResult.new(type: :command, text: text, descriptive_text: descriptive_text)
58
+ else
59
+ CompletionResult.new(type: :file, text: text, descriptive_text: descriptive_text)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Remove file separator and the back-slash from word break characters when determining
65
+ # the pre-word-context
66
+ def filtered_word_break_characters
67
+ @word_break_characters.sub(File::Separator, "").sub('\\', '')
68
+ end
69
+
70
+ def filtered_work_break_characters_rgx
71
+ /([#{Regexp.escape(filtered_word_break_characters)}])/
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,157 @@
1
+ require 'term/ansicolor'
2
+
3
+ class TabCompletion < Addon
4
+ require 'tab_completion/completer'
5
+ require 'tab_completion/dsl_methods'
6
+ require 'tab_completion/custom_completion'
7
+ require 'tab_completion/file_completion'
8
+
9
+ class CompletionResult
10
+ attr_accessor :text, :type, :descriptive_text
11
+
12
+ def initialize(text:, type:, descriptive_text: nil)
13
+ @descriptive_text = descriptive_text || text
14
+ @text = text
15
+ @type = type
16
+ end
17
+
18
+ def ==(other)
19
+ other.is_a?(self.class) && @text == other.text && @type == other.type
20
+ end
21
+
22
+ def <=>(other)
23
+ @text <=> other.text
24
+ end
25
+
26
+ def to_s
27
+ @text
28
+ end
29
+ end
30
+
31
+ COMPLETIONS = [ FileCompletion ]
32
+
33
+ Color = Term::ANSIColor
34
+
35
+ DISPLAY_PROCS = Hash.new{ |h,k| h[k] = ->(text){ text } }.merge(
36
+ directory: -> (text){ text + "/" }
37
+ )
38
+
39
+ STYLE_PROCS = Hash.new{ |h,k| h[k] = ->(text){ text } }.merge(
40
+ directory: -> (text){ Color.bold(Color.red(text)) },
41
+ command: -> (text){ Color.bold(Color.green(text)) },
42
+ symlink: -> (text){ Color.bold(Color.cyan(text)) },
43
+ selected: -> (text){ Color.negative(text) }
44
+ )
45
+
46
+ DECORATION_PROCS = Hash.new{ |h,k| h[k] = ->(text){ text } }.merge(
47
+ directory: -> (text){ text + "/" },
48
+ command: -> (text){ text + "@" }
49
+ )
50
+
51
+ attr_reader :editor, :world
52
+
53
+ def initialize_world(world)
54
+ @world = world
55
+ @world.extend TabCompletion::DslMethods
56
+ @editor = @world.editor
57
+ @editor.completion_proc = -> (word, line){
58
+ complete(word, line)
59
+ }
60
+ @editor.bind(:tab){ @editor.complete }
61
+ @completions = COMPLETIONS.dup
62
+
63
+ @style_procs = STYLE_PROCS.dup
64
+ @decoration_procs = DECORATION_PROCS.dup
65
+ @display_procs = DISPLAY_PROCS.dup
66
+
67
+ editor.on_word_complete do |event|
68
+ sub_word = event[:payload][:sub_word]
69
+ word = event[:payload][:word]
70
+ actual_completion = event[:payload][:completion]
71
+ possible_completions = event[:payload][:possible_completions]
72
+
73
+ semi_formatted_possibilities = possible_completions.map.with_index do |completion, i|
74
+ if completion == actual_completion
75
+ style_text_for_selected_match(completion) + "\e[0m"
76
+ else
77
+ style_text_for_nonselected_match(completion) + "\e[0m"
78
+ end
79
+ end
80
+
81
+ max_width = @editor.terminal_width
82
+ max_item_width = semi_formatted_possibilities.map(&:length).max + 2
83
+ most_per_line = max_width / max_item_width
84
+ padding_at_the_end = max_width % max_item_width
85
+
86
+ formatted_possibilities = semi_formatted_possibilities.map.with_index do |completion, i|
87
+ spaces_to_pad = max_item_width - completion.length
88
+ completion + (" " * spaces_to_pad)
89
+ end
90
+
91
+ editor.content_box.children = formatted_possibilities.map do |str|
92
+ TerminalLayout::Box.new(content: str, style: { display: :float, float: :left, height: 1, width: max_item_width })
93
+ end
94
+ end
95
+
96
+ editor.on_word_complete_no_match do |event|
97
+ sub_word = event[:payload][:sub_word]
98
+ word = event[:payload][:word]
99
+ editor.content_box.children = []
100
+ # editor.content_box.content = "Failed to find a match to complete #{sub_word} portion of #{word}"
101
+ end
102
+
103
+ editor.on_word_complete_done do |event|
104
+ # TODO: add a better way to clear content
105
+ editor.content_box.children = []
106
+ end
107
+ end
108
+
109
+ def add_completion(name, pattern, &blk)
110
+ raise ArgumentError, "Must supply block!" unless block_given?
111
+ # @completions.push CustomCompletion.new(name:name, pattern:pattern, world:world, &blk)
112
+ end
113
+
114
+ def set_decoration(type, &blk)
115
+ raise ArgumentError, "Must supply block!" unless block_given?
116
+ @style_procs[type] = blk
117
+ end
118
+
119
+ def complete(word, line)
120
+ matches = @completions.sort_by(&:priority).reverse.map do |completion|
121
+ if completion.respond_to?(:call)
122
+ completion.call
123
+ else
124
+ completions = completion.new(
125
+ world: @world,
126
+ word_break_characters: editor.word_break_characters
127
+ ).completions_for(word, line)
128
+ completions.each do |completion|
129
+ completion.text = display_text_for_match(completion)
130
+ end
131
+ end
132
+ end.flatten
133
+
134
+ matches
135
+ end
136
+
137
+ private
138
+
139
+ def display_text_for_match(match)
140
+ ANSIString.new @display_procs[match.type].call(match.text.dup)
141
+ end
142
+
143
+ def style_text_for_selected_match(match)
144
+ styled_text = @style_procs[match.type].call(match.descriptive_text.dup).to_s
145
+ styled_text = @decoration_procs[match.type].call(styled_text).to_s
146
+ uncolored_text = Color.uncolored(styled_text)
147
+ ANSIString.new @style_procs[:selected].call(uncolored_text)
148
+ end
149
+
150
+ def style_text_for_nonselected_match(match)
151
+ str = @decoration_procs[match.type].call(
152
+ @style_procs[match.type].call(match.descriptive_text.dup)
153
+ )
154
+ ANSIString.new str
155
+ end
156
+
157
+ end
data/bin/yap CHANGED
@@ -1,20 +1,29 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'bundler'
4
+ Bundler.setup
5
+ require 'pry'
6
+
3
7
  file = __FILE__
4
8
  if File.symlink?(file)
5
9
  file = File.readlink(file)
6
10
  end
7
11
 
12
+ $z = File.open("/tmp/z.log", "w+")
13
+ $z.sync = true
14
+
8
15
  trap "SIGTSTP", "IGNORE"
9
16
  trap "SIGINT", "IGNORE"
10
17
  trap "SIGTTIN", "IGNORE"
11
18
  trap "SIGTTOU", "IGNORE"
12
19
 
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"
20
+ #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"
16
21
 
17
- $LOAD_PATH << File.dirname(file) + '/../lib'
22
+ $LOAD_PATH.unshift File.dirname(file) + '/../lib'
23
+ # $LOAD_PATH.unshift File.dirname(file) + '/../../yap-shell-parser/lib'
18
24
 
19
25
  require "yap"
26
+
27
+ $stdout.sync = true
28
+
20
29
  Yap.run_shell
@@ -0,0 +1,51 @@
1
+ namespace :addons do
2
+ namespace :update do
3
+ desc "Update the gemspec based on add-on specific dependnecies"
4
+ task :gemspec do
5
+ require 'bundler'
6
+ runtime_deps = []
7
+ development_deps = []
8
+ root_dir = File.dirname(__FILE__) + "/../.."
9
+ gemfiles = Dir[root_dir + "/addons/**/**/Gemfile"]
10
+ gemfiles.each do |gemfile|
11
+ bd = Bundler::Definition.build(gemfile, nil, nil)
12
+ runtime_deps.push *bd.dependencies.select{ |dep| dep.type == :runtime }
13
+ development_deps.push *bd.dependencies.select{ |dep| dep.type == :development }
14
+ end
15
+
16
+ runtime_h = Hash.new{ |h,name| h[name] = Gem::Dependency.new(name) }
17
+ runtime_deps.each { |dep| runtime_h[dep.name] = runtime_h[dep.name].merge(dep) }
18
+
19
+ dev_h = Hash.new{ |h,name| h[name] = Gem::Dependency.new(name) }
20
+ development_deps.each { |dep| dev_h[dep.name] = dev_h[dep.name].merge(dep) }
21
+
22
+ deps_str = ""
23
+ deps_str << runtime_h.map do |name, dep|
24
+ if dep.requirement.none?
25
+ %| spec.add_dependency "#{dep.name}"|
26
+ else
27
+ %| spec.add_dependency "#{dep.name}", "#{dep.requirement.as_list.first}"|
28
+ end
29
+ end.join("\n")
30
+
31
+ deps_str << dev_h.map do |name, dep|
32
+ if dep.requirement.none?
33
+ %| spec.add_development_dependency "#{dep.name}"|
34
+ else
35
+ %| spec.add_development_dependency "#{dep.name}", "#{dep.requirement.as_list.first}"|
36
+ end
37
+ end.join("\n")
38
+
39
+ gemspec = Dir[root_dir + "/*.gemspec"].first || raise("No gemspec found in directory: #{root_dir}")
40
+ gemspec = File.expand_path(gemspec)
41
+ contents = File.read(gemspec)
42
+ new_contents = contents.sub(/(\#--BEGIN_ADDON_GEM_DEPENDENCIES--\#)\s*.*(^.*\#--END_ADDON_GEM_DEPENDENCIES--\#)/mx) do
43
+ "#{$1}\n#{deps_str}\n#{$2}"
44
+ end
45
+
46
+ File.write(gemspec, new_contents)
47
+ puts "Updated #{gemspec}"
48
+ puts new_contents
49
+ end
50
+ end
51
+ end