yap-shell 0.6.0 → 0.7.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 (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