yap-shell 0.1.1 → 0.3.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.
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