yap-shell 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.travis.lock +104 -0
  3. data/bin/yap +6 -0
  4. data/bin/yap-dev +37 -0
  5. data/lib/yap.rb +29 -39
  6. data/lib/yap/addon.rb +24 -0
  7. data/lib/yap/addon/base.rb +52 -0
  8. data/lib/yap/addon/export_as.rb +12 -0
  9. data/lib/yap/addon/loader.rb +84 -0
  10. data/lib/yap/addon/path.rb +56 -0
  11. data/lib/yap/addon/rc_file.rb +21 -0
  12. data/lib/yap/addon/reference.rb +22 -0
  13. data/lib/yap/cli.rb +4 -0
  14. data/lib/yap/cli/commands.rb +6 -0
  15. data/lib/yap/cli/commands/addon.rb +14 -0
  16. data/lib/yap/cli/commands/addon/disable.rb +35 -0
  17. data/lib/yap/cli/commands/addon/enable.rb +35 -0
  18. data/lib/yap/cli/commands/addon/list.rb +37 -0
  19. data/lib/yap/cli/commands/addon/search.rb +99 -0
  20. data/lib/yap/cli/commands/generate.rb +13 -0
  21. data/lib/yap/cli/commands/generate/addon.rb +258 -0
  22. data/lib/yap/cli/commands/generate/addonrb.template +22 -0
  23. data/lib/yap/cli/commands/generate/gemspec.template +25 -0
  24. data/lib/yap/cli/commands/generate/license.template +21 -0
  25. data/lib/yap/cli/commands/generate/rakefile.template +6 -0
  26. data/lib/yap/cli/commands/generate/readme.template +40 -0
  27. data/lib/yap/cli/options.rb +162 -0
  28. data/lib/yap/cli/options/addon.rb +64 -0
  29. data/lib/yap/cli/options/addon/disable.rb +62 -0
  30. data/lib/yap/cli/options/addon/enable.rb +63 -0
  31. data/lib/yap/cli/options/addon/list.rb +65 -0
  32. data/lib/yap/cli/options/addon/search.rb +76 -0
  33. data/lib/yap/cli/options/generate.rb +59 -0
  34. data/lib/yap/cli/options/generate/addon.rb +63 -0
  35. data/lib/yap/configuration.rb +10 -3
  36. data/lib/yap/gem_helper.rb +195 -0
  37. data/lib/yap/gem_tasks.rb +6 -0
  38. data/lib/yap/shell.rb +1 -1
  39. data/lib/yap/shell/repl.rb +1 -1
  40. data/lib/yap/shell/version.rb +1 -1
  41. data/lib/yap/world.rb +45 -7
  42. data/rcfiles/yaprc +90 -10
  43. data/spec/features/addons/generating_an_addon_spec.rb +55 -0
  44. data/spec/features/addons/using_an_addon_spec.rb +182 -0
  45. data/spec/features/aliases_spec.rb +6 -6
  46. data/spec/features/grouping_spec.rb +18 -18
  47. data/spec/features/line_editing_spec.rb +9 -1
  48. data/spec/features/redirection_spec.rb +12 -3
  49. data/spec/spec_helper.rb +21 -11
  50. data/spec/support/matchers/have_printed.rb +38 -0
  51. data/spec/support/yap_spec_dsl.rb +24 -6
  52. data/yap-shell.gemspec +6 -11
  53. metadata +51 -45
  54. data/addons/history/README.md +0 -16
  55. data/addons/history/history.rb +0 -58
  56. data/addons/history_search/history_search.rb +0 -197
  57. data/addons/keyboard_macros/keyboard_macros.rb +0 -425
  58. data/addons/keyboard_macros/lib/keyboard_macros/cycle.rb +0 -38
  59. data/addons/prompt/Gemfile +0 -1
  60. data/addons/prompt/right_prompt.rb +0 -17
  61. data/addons/prompt_updates/prompt_updates.rb +0 -28
  62. data/addons/tab_completion/Gemfile +0 -0
  63. data/addons/tab_completion/lib/tab_completion/basic_completion.rb +0 -151
  64. data/addons/tab_completion/lib/tab_completion/completer.rb +0 -62
  65. data/addons/tab_completion/lib/tab_completion/custom_completion.rb +0 -33
  66. data/addons/tab_completion/lib/tab_completion/dsl_methods.rb +0 -7
  67. data/addons/tab_completion/tab_completion.rb +0 -174
  68. data/lib/tasks/addons.rake +0 -97
  69. data/lib/yap/world/addons.rb +0 -181
@@ -1,38 +0,0 @@
1
- class KeyboardMacros
2
- class Cycle
3
- def initialize(cycle_proc:, on_cycle_proc: nil)
4
- @cycle_proc = cycle_proc
5
- @on_cycle_proc = on_cycle_proc
6
- @previous_result = nil
7
- reset
8
- end
9
-
10
- def next
11
- @index = -1 if @index >= cycle_values.length - 1
12
- on_cycle cycle_values[@index += 1]
13
- end
14
-
15
- def previous
16
- @index = cycle_values.length if @index < 0
17
- on_cycle cycle_values[@index -= 1]
18
- end
19
-
20
- def reset
21
- @index = -1
22
- @previous_result = nil
23
- @cycle_values = nil
24
- end
25
-
26
- private
27
-
28
- def cycle_values
29
- @cycle_values ||= @cycle_proc.call
30
- end
31
-
32
- def on_cycle(new_value)
33
- @on_cycle_proc.call(@previous_result, new_value) if @on_cycle_proc
34
- @previous_result = new_value
35
- new_value
36
- end
37
- end
38
- end
@@ -1 +0,0 @@
1
- gem "chronic", "~> 0.10.2"
@@ -1,17 +0,0 @@
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
@@ -1,28 +0,0 @@
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 2>/dev/null`.scan(/\*\s*(.*)$/).flatten.first
26
- end
27
-
28
- end
File without changes
@@ -1,151 +0,0 @@
1
- class TabCompletion
2
- class BasicCompletion
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, words, word_index)
18
- completions_by_name = {}
19
- if looking_for_command?(word, words, word_index)
20
- # Lowest Priority
21
- completions_by_name.merge! command_completion_matches_for(word, words)
22
-
23
- # Low Priority
24
- completions_by_name.merge! builtin_completion_matches_for(word, words)
25
-
26
- # Medium Priority
27
- completions_by_name.merge! executable_filename_completion_matches_for(word, words)
28
-
29
- # High Priority
30
- completions_by_name.merge! shell_command_completion_matches_for(word, words)
31
-
32
- # Highest Priority
33
- completions_by_name.merge! alias_completion_matches_for(word, words)
34
- else
35
- completions_by_name.merge! filename_completion_matches_for(word, words)
36
- end
37
- completions_by_name.merge! environment_variable_completions_for(word, words)
38
- completions_by_name.values
39
- end
40
-
41
- private
42
-
43
- def looking_for_command?(word, words, word_index)
44
- return false unless word_index
45
- return true if word_index == 0
46
- return true if words[word_index - 1] =~ /[;&]/
47
- false
48
- end
49
-
50
- def alias_completion_matches_for(word, words)
51
- @world.aliases.names.each_with_object({}) do |name, result|
52
- if name =~ /^#{Regexp.escape(word)}/
53
- result[name] ||= CompletionResult.new(
54
- type: :alias,
55
- text: name
56
- )
57
- end
58
- end
59
- end
60
-
61
- def builtin_completion_matches_for(word, words)
62
- @world.builtins.each_with_object({}) do |builtin, result|
63
- if builtin =~ /^#{Regexp.escape(word)}/
64
- result[builtin] ||= CompletionResult.new(
65
- type: :builtin,
66
- text: builtin
67
- )
68
- end
69
- end
70
- end
71
-
72
- def command_completion_matches_for(word, words)
73
- @paths.each_with_object({}) do |path, matches|
74
- glob = File.join(path, "#{word}*")
75
- arr = Dir[glob].select { |path| File.executable?(path) && File.file?(path) }
76
- arr.map { |path| File.basename(path) }.uniq.each do |command|
77
- matches[command] = CompletionResult.new(type: :command, text: command)
78
- end
79
- end
80
- end
81
-
82
- def environment_variable_completions_for(word, words)
83
- return {} unless word =~ /^\$/
84
- prefix, word_sans_prefix = word[0], word[1..-1]
85
- @world.env.keys.each_with_object({}) do |env_var, result|
86
- if env_var =~ /^#{Regexp.escape(word_sans_prefix)}/
87
- result[env_var] ||= CompletionResult.new(
88
- type: :env_var,
89
- text: prefix + env_var
90
- )
91
- end
92
- end
93
- end
94
-
95
- def executable_filename_completion_matches_for(word, words)
96
- glob = "#{word}*"
97
- glob.gsub!("~", world.env["HOME"])
98
- Dir.glob(glob, File::FNM_CASEFOLD).each_with_object({}) do |path, result|
99
- text = path.gsub(filtered_work_break_characters_rgx, '\\\\\1')
100
- descriptive_text = File.basename(text)
101
- if !File.directory?(path) && File.executable?(path)
102
- result[path] = CompletionResult.new(
103
- type: :command,
104
- text: text,
105
- descriptive_text: descriptive_text
106
- )
107
- end
108
- end
109
- end
110
-
111
- def shell_command_completion_matches_for(word, words)
112
- @world.shell_commands.each_with_object({}) do |shell_command, result|
113
- if shell_command =~ /^#{Regexp.escape(word)}/
114
- result[shell_command] ||= CompletionResult.new(
115
- type: :shell_command,
116
- text: shell_command
117
- )
118
- end
119
- end
120
- end
121
-
122
- def filename_completion_matches_for(word, line)
123
- glob = "#{word}*"
124
- glob.gsub!("~", world.env["HOME"])
125
- Dir.glob(glob, File::FNM_CASEFOLD).each_with_object({}) do |path, result|
126
- text = path.gsub(filtered_work_break_characters_rgx, '\\\\\1')
127
- descriptive_text = File.basename(text)
128
- result[path] = if File.directory?(path)
129
- CompletionResult.new(type: :directory, text: text, descriptive_text: descriptive_text)
130
- elsif File.symlink?(path)
131
- CompletionResult.new(type: :symlink, text: text, descriptive_text: descriptive_text)
132
- elsif File.file?(path) && File.executable?(path)
133
- CompletionResult.new(type: :command, text: text, descriptive_text: descriptive_text)
134
- else
135
- CompletionResult.new(type: :file, text: text, descriptive_text: descriptive_text)
136
- end
137
- end
138
- end
139
-
140
- # Remove file separator and the back-slash from word break characters when determining
141
- # the pre-word-context
142
- def filtered_word_break_characters
143
- @word_break_characters.sub(File::Separator, "").sub('\\', '')
144
- end
145
-
146
- def filtered_work_break_characters_rgx
147
- /([#{Regexp.escape(filtered_word_break_characters)}])/
148
- end
149
-
150
- end
151
- end
@@ -1,62 +0,0 @@
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
@@ -1,33 +0,0 @@
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
@@ -1,7 +0,0 @@
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
@@ -1,174 +0,0 @@
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/basic_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.to_s
28
- end
29
- alias_method :to_str, :to_s
30
- alias_method :inspect, :to_s
31
- end
32
-
33
- COMPLETIONS = [ BasicCompletion ]
34
-
35
- Color = Term::ANSIColor
36
-
37
- DISPLAY_PROCS = Hash.new{ |h,k| h[k] = ->(text){ text } }.merge(
38
- directory: -> (text){ text + "/" }
39
- )
40
-
41
- STYLE_PROCS = Hash.new{ |h,k| h[k] = ->(text){ text } }.merge(
42
- alias: -> (text){ Color.bold(Color.color("#ff00d7"){ text } ) },
43
- builtin: -> (text){ Color.bold(Color.color("#d7af00"){ text } ) },
44
- directory: -> (text){ Color.bold(Color.red(text)) },
45
- command: -> (text){ Color.bold(Color.green(text)) },
46
- shell_command: -> (text){ Color.bold(Color.color("#ffafff"){ text } ) },
47
- symlink: -> (text){ Color.bold(Color.cyan(text)) },
48
- selected: -> (text){ Color.negative(text) }
49
- )
50
-
51
- DECORATION_PROCS = Hash.new{ |h,k| h[k] = ->(text){ text } }.merge(
52
- directory: -> (text){ text + "/" },
53
- command: -> (text){ text + "@" },
54
- shell_command: -> (text) { text + "🐚" }
55
- )
56
-
57
- attr_reader :editor, :world
58
-
59
- def initialize_world(world)
60
- @world = world
61
- @world.extend TabCompletion::DslMethods
62
- @editor = @world.editor
63
- @editor.completion_proc = -> (word, line, word_index){
64
- complete(word, line, word_index)
65
- }
66
- @editor.bind(:tab){ @editor.complete }
67
- @completions = COMPLETIONS.dup
68
-
69
- @style_procs = STYLE_PROCS.dup
70
- @decoration_procs = DECORATION_PROCS.dup
71
- @display_procs = DISPLAY_PROCS.dup
72
-
73
- editor.on_word_complete do |event|
74
- debug_log "on_word_complete event: #{event}"
75
-
76
- sub_word = event[:payload][:sub_word]
77
- word = event[:payload][:word]
78
- actual_completion = event[:payload][:completion]
79
- possible_completions = event[:payload][:possible_completions]
80
-
81
- semi_formatted_possibilities = possible_completions.map.with_index do |completion, i|
82
- if completion == actual_completion
83
- style_text_for_selected_match(completion) + "\e[0m"
84
- else
85
- style_text_for_nonselected_match(completion) + "\e[0m"
86
- end
87
- end
88
-
89
- max_width = @editor.terminal_width
90
- max_item_width = semi_formatted_possibilities.map(&:length).max + 2
91
- most_per_line = max_width / max_item_width
92
- padding_at_the_end = max_width % max_item_width
93
-
94
- formatted_possibilities = semi_formatted_possibilities.map.with_index do |completion, i|
95
- spaces_to_pad = max_item_width - completion.length
96
- completion + (" " * spaces_to_pad)
97
- end
98
-
99
- editor.content_box.children = formatted_possibilities.map do |str|
100
- TerminalLayout::Box.new(content: str, style: { display: :float, float: :left, height: 1, width: max_item_width })
101
- end
102
- end
103
-
104
- editor.on_word_complete_no_match do |event|
105
- debug_log "on_word_complete_no_match event: #{event}"
106
-
107
- sub_word = event[:payload][:sub_word]
108
- word = event[:payload][:word]
109
- editor.content_box.children = []
110
- # editor.content_box.content = "Failed to find a match to complete #{sub_word} portion of #{word}"
111
- end
112
-
113
- editor.on_word_complete_done do |event|
114
- debug_log "on_word_complete_done event: #{event}"
115
-
116
- # TODO: add a better way to clear content
117
- editor.content_box.children = []
118
- end
119
- end
120
-
121
- def add_completion(name, pattern, &blk)
122
- raise ArgumentError, "Must supply block!" unless block_given?
123
- debug_log "NO-OP add_completion for name=#{name.inspect} pattern=#{pattern.inspect} block?=#{block_given?}"
124
- # @completions.push CustomCompletion.new(name:name, pattern:pattern, world:world, &blk)
125
- end
126
-
127
- def set_decoration(type, &blk)
128
- raise ArgumentError, "Must supply block!" unless block_given?
129
- debug_log "set_decoration for type=#{name.inspect}"
130
- @style_procs[type] = blk
131
- end
132
-
133
- def complete(word, words, word_index)
134
- debug_log "complete word=#{word.inspect} words=#{words.inspect} word_index=#{word_index.inspect}"
135
-
136
- matches = @completions.sort_by(&:priority).reverse.map do |completion|
137
- if completion.respond_to?(:call)
138
- completion.call
139
- else
140
- completions = completion.new(
141
- world: @world,
142
- word_break_characters: editor.word_break_characters
143
- ).completions_for(word, words, word_index)
144
- completions.each do |completion|
145
- completion.text = display_text_for_match(completion)
146
- end
147
- end
148
- end.flatten
149
-
150
- debug_log "complete possible matches are #{matches.inspect}"
151
- matches
152
- end
153
-
154
- private
155
-
156
- def display_text_for_match(match)
157
- ANSIString.new @display_procs[match.type].call(match.text.dup)
158
- end
159
-
160
- def style_text_for_selected_match(match)
161
- styled_text = @style_procs[match.type].call(match.descriptive_text.dup).to_s
162
- styled_text = @decoration_procs[match.type].call(styled_text).to_s
163
- uncolored_text = Color.uncolored(styled_text)
164
- ANSIString.new @style_procs[:selected].call(uncolored_text)
165
- end
166
-
167
- def style_text_for_nonselected_match(match)
168
- str = @decoration_procs[match.type].call(
169
- @style_procs[match.type].call(match.descriptive_text.dup)
170
- )
171
- ANSIString.new str
172
- end
173
-
174
- end