vop 0.3.1 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Gemfile +2 -1
  4. data/Gemfile.lock +34 -33
  5. data/README.md +184 -2
  6. data/bin/console +17 -0
  7. data/bin/setup +8 -0
  8. data/bin/vop.sh +6 -2
  9. data/exe/vop +3 -25
  10. data/lib/boot.rb +3 -0
  11. data/lib/core/meta/commands/new_command.rb +1 -0
  12. data/lib/core/meta/commands/new_plugin.rb +47 -0
  13. data/lib/core/meta/commands/search_gems_for_plugins.rb +38 -0
  14. data/lib/core/meta/commands/search_path.rb +6 -0
  15. data/lib/core/meta/commands/set.rb +12 -0
  16. data/lib/core/meta/commands/show.rb +6 -0
  17. data/lib/core/meta/commands/show_config.rb +9 -0
  18. data/lib/core/meta/commands/who_provides.rb +6 -0
  19. data/lib/core/meta/meta.plugin +1 -0
  20. data/lib/core/shell/commands/change_loglevel.rb +6 -0
  21. data/lib/{vop/plugins/core → core/shell}/commands/clear_context.rb +0 -0
  22. data/lib/core/shell/commands/edit.rb +12 -0
  23. data/lib/core/shell/commands/help.rb +49 -0
  24. data/lib/core/shell/commands/reset.rb +4 -0
  25. data/lib/{vop/plugins/core → core/shell}/commands/show_context.rb +0 -0
  26. data/lib/core/shell/commands/source.rb +21 -0
  27. data/lib/{vop/plugins/core/helpers/plugin_loader/plugin_syntax.rb → core/shell/shell.plugin} +0 -0
  28. data/lib/core/structure/commands/collect_contributions.rb +46 -0
  29. data/lib/core/structure/commands/disable_contributor.rb +16 -0
  30. data/lib/core/structure/commands/generate_entity_commands.rb +52 -0
  31. data/lib/core/structure/commands/generate_invalidation_commands.rb +26 -0
  32. data/lib/core/structure/commands/list_commands.rb +11 -0
  33. data/lib/core/structure/commands/list_contributors.rb +10 -0
  34. data/lib/core/structure/commands/list_entities.rb +3 -0
  35. data/lib/core/structure/commands/list_plugins.rb +3 -0
  36. data/lib/core/structure/commands/register_contributor.rb +14 -0
  37. data/lib/core/structure/structure.plugin +4 -0
  38. data/lib/vop/objects/chain.rb +25 -0
  39. data/lib/vop/objects/command.rb +86 -0
  40. data/lib/vop/objects/command_param.rb +39 -0
  41. data/lib/vop/objects/entities.rb +21 -0
  42. data/lib/vop/objects/entity.rb +75 -0
  43. data/lib/vop/objects/entity_definition.rb +33 -0
  44. data/lib/vop/objects/filter.rb +48 -0
  45. data/lib/vop/objects/plugin.rb +208 -0
  46. data/lib/vop/objects/request.rb +73 -0
  47. data/lib/vop/objects/response.rb +17 -0
  48. data/lib/vop/objects/thing_with_params.rb +17 -0
  49. data/lib/vop/{command_loader.rb → parts/command_loader.rb} +8 -12
  50. data/lib/vop/parts/dependency_resolver.rb +56 -0
  51. data/lib/vop/parts/entity_loader.rb +46 -0
  52. data/lib/vop/parts/executor.rb +155 -0
  53. data/lib/vop/parts/filter_loader.rb +41 -0
  54. data/lib/vop/parts/plugin_finder.rb +46 -0
  55. data/lib/vop/parts/plugin_loader.rb +72 -0
  56. data/lib/vop/shell/shell.rb +221 -0
  57. data/lib/vop/shell/shell_formatter.rb +110 -0
  58. data/lib/vop/shell/shell_input.rb +14 -0
  59. data/lib/vop/shell/shell_input_readline.rb +20 -0
  60. data/lib/vop/shell/shell_input_testable.rb +27 -0
  61. data/lib/vop/syntax/command_syntax.rb +90 -0
  62. data/lib/vop/syntax/entity_syntax.rb +35 -0
  63. data/lib/vop/syntax/filter_syntax.rb +11 -0
  64. data/lib/vop/syntax/plugin_syntax.rb +55 -0
  65. data/lib/vop/util/errors.rb +45 -0
  66. data/lib/vop/util/pluralizer.rb +26 -0
  67. data/lib/vop/util/worker.rb +24 -0
  68. data/lib/vop/version.rb +1 -1
  69. data/lib/vop/vop.rb +216 -0
  70. data/lib/vop.rb +16 -229
  71. data/vop.gemspec +18 -15
  72. metadata +95 -63
  73. data/bin/vop.rb +0 -28
  74. data/lib/vop/command.rb +0 -168
  75. data/lib/vop/entity.rb +0 -61
  76. data/lib/vop/loader.rb +0 -35
  77. data/lib/vop/plugin.rb +0 -141
  78. data/lib/vop/plugin_loader.rb +0 -88
  79. data/lib/vop/plugins/core/commands/collect_contributions.rb +0 -31
  80. data/lib/vop/plugins/core/commands/edit.rb +0 -12
  81. data/lib/vop/plugins/core/commands/help.rb +0 -38
  82. data/lib/vop/plugins/core/commands/identity.rb +0 -4
  83. data/lib/vop/plugins/core/commands/list_contributors.rb +0 -8
  84. data/lib/vop/plugins/core/commands/list_entities.rb +0 -3
  85. data/lib/vop/plugins/core/commands/pry.rb +0 -9
  86. data/lib/vop/plugins/core/commands/reset.rb +0 -5
  87. data/lib/vop/plugins/core/commands/source.rb +0 -5
  88. data/lib/vop/plugins/core/commands/system_call.rb +0 -5
  89. data/lib/vop/plugins/core/core.plugin +0 -4
  90. data/lib/vop/plugins/core/helpers/command_loader/command_syntax.rb +0 -45
  91. data/lib/vop/plugins/core/helpers/command_loader/contributions.rb +0 -28
  92. data/lib/vop/plugins/core/helpers/command_loader/entities.rb +0 -57
  93. data/lib/vop/plugins/core/helpers/helper.rb +0 -3
  94. data/lib/vop/plugins/meta/commands/add_search_path.rb +0 -6
  95. data/lib/vop/plugins/meta/commands/delete_plugin.rb +0 -13
  96. data/lib/vop/plugins/meta/commands/list_commands.rb +0 -17
  97. data/lib/vop/plugins/meta/commands/list_plugins.rb +0 -8
  98. data/lib/vop/plugins/meta/commands/new_command.rb +0 -14
  99. data/lib/vop/plugins/meta/commands/new_plugin.rb +0 -25
  100. data/lib/vop/plugins/meta/commands/show_search_path.rb +0 -3
  101. data/lib/vop/plugins/meta/commands/who_provides.rb +0 -5
  102. data/lib/vop/plugins/meta/meta.plugin +0 -1
  103. data/lib/vop/plugins/ssh/commands/scp.rb +0 -11
  104. data/lib/vop/plugins/ssh/commands/ssh.rb +0 -19
  105. data/lib/vop/plugins/ssh/ssh.plugin +0 -1
  106. data/lib/vop/shell/backend.rb +0 -28
  107. data/lib/vop/shell/base_shell.rb +0 -112
  108. data/lib/vop/shell/formatter.rb +0 -46
  109. data/lib/vop/shell/vop_shell_backend.rb +0 -257
  110. data/lib/vop/shell.rb +0 -52
@@ -1,88 +0,0 @@
1
- require 'vop/plugin'
2
- require 'pp'
3
-
4
- module Vop
5
-
6
- class PluginLoader
7
-
8
- attr_reader :op
9
- attr_reader :dir
10
-
11
- def initialize(op, dir)
12
- @op = op
13
- @dir = dir
14
- end
15
-
16
- def new_plugin(plugin_name, plugin_path)
17
- @plugin = Plugin.new(@op, plugin_name, plugin_path)
18
-
19
- # TODO activate plugin_loader helpers?
20
- # (this will allow plugins to define helpers that can be used in plugins)
21
- #@plugin.inject_helpers(self)
22
- #@plugin.inject_helpers(self, 'plugin_loader')
23
-
24
- @op.plugins[plugin_name] = @plugin
25
- @plugin
26
- end
27
-
28
- def on(hook_sym, &block)
29
- @plugin.hook(hook_sym, &block)
30
-
31
- core_hooks = %i|before_execute after_execute|
32
- if core_hooks.include? hook_sym
33
- @op.hook(hook_sym, @plugin.name)
34
- end
35
- end
36
-
37
- def dependency(sym)
38
- @plugin.dependencies << sym.to_s
39
- end
40
- alias_method :depends, :dependency
41
-
42
- def read_plugin_template
43
- template_path = File.join(@dir, 'plugin.vop')
44
- plugin_template = nil
45
- if File.exists?(template_path)
46
- plugin_template = IO.read(template_path)
47
- end
48
- if plugin_template
49
- begin
50
- self.instance_eval plugin_template
51
- rescue => e
52
- $stderr.puts "could not load plugin #{template_path} : " + e.message
53
- $stderr.puts e.backtrace.join("\n")
54
- raise e
55
- end
56
- end
57
- end
58
-
59
- def self.read(op, dir)
60
- $logger.debug("scanning dir #{dir} for plugins...")
61
-
62
- loader = new(op, dir)
63
- loader.read_plugin_template
64
-
65
- Dir.new(dir).each do |plugin_name|
66
- next if /^\./.match(plugin_name)
67
-
68
- plugin_path = File.join(dir, plugin_name)
69
- file_name = File.join(plugin_path, "#{plugin_name}.plugin")
70
- next unless File.exists?(file_name)
71
- $logger.debug("reading plugin '#{plugin_name}' from '#{file_name}'")
72
-
73
- plugin = loader.new_plugin(plugin_name, plugin_path)
74
-
75
- code = File.read(file_name)
76
- begin
77
- loader.instance_eval(code, file_name)
78
- rescue => detail
79
- raise "problem loading plugin #{plugin_name} : #{detail.message}\n#{detail.backtrace.join("\n")}"
80
- end
81
-
82
- $logger.debug "loaded plugin #{plugin_name}"
83
- end
84
- end
85
-
86
- end
87
-
88
- end
@@ -1,31 +0,0 @@
1
- param! "name"
2
- param "except", :multi => true
3
- param! "raw_params"
4
-
5
- run do |name, raw_params, params|
6
- result = []
7
-
8
- registry = @op.plugins["core"].state[:contributions]
9
- if registry.has_key? name
10
- contributors = registry[name]
11
- contributors.each do |contributor|
12
- except = params["except"] || []
13
- if except.include? contributor
14
- puts "skipping contributor #{contributor} because of except"
15
- next
16
- end
17
-
18
- $logger.debug "calling #{contributor} for contribution to #{name}"
19
- #puts caller[0..10]
20
- if caller.grep(/eval.*block in entity/).size > 2
21
- $logger.debug "skipping contribution from #{contributor} because of loop"
22
- else
23
- $logger.debug "raw params: " + raw_params.inspect
24
- short_name = contributor.to_s.split(".").last
25
- result += @op.send(short_name.to_sym, raw_params)
26
- end
27
- end
28
- end
29
-
30
- result
31
- end
@@ -1,12 +0,0 @@
1
- param! "name", :lookup => lambda { @op.list_commands.map { |x| x[:name] } }
2
-
3
- run do |params, name|
4
- editor = ENV["EDITOR"]
5
- editor = "vim" # TODO if "config.default_editor"
6
- raise "please set the EDITOR environment variable" unless editor
7
-
8
- command_file = @op.command(name).source[:file_name]
9
- system("#{editor} #{command_file}")
10
-
11
- @op.reset
12
- end
@@ -1,38 +0,0 @@
1
- param! "name", :lookup => lambda { @op.list_commands.map { |x| x[:name] } }
2
-
3
- run do |params|
4
- puts
5
- puts params["name"]
6
- command = @op.commands[params["name"]]
7
- if command.description
8
- puts " #{command.description}"
9
- end
10
-
11
- puts
12
- puts "syntax:"
13
-
14
- minimal_syntax = command.short_name
15
- maximal_syntax = command.short_name
16
- command.params.each do |p|
17
- example = if p == command.default_param
18
- "<#{p[:name]}>"
19
- else
20
- "#{p[:name]}=<#{p[:name]}>"
21
- end
22
- minimal_syntax << " #{example}" if p[:mandatory]
23
- maximal_syntax << " #{example}"
24
- end
25
- puts " #{minimal_syntax}"
26
- puts " #{maximal_syntax}" unless minimal_syntax == maximal_syntax
27
-
28
- if command.params.size > 0
29
- puts
30
- puts "parameters:"
31
- max_param_length = command.params.map { |p| p[:name].length }.max
32
- command.params.each do |p|
33
- puts " %-#{max_param_length}s\t%s" % [p[:name], p[:description]]
34
- end
35
- end
36
-
37
- nil
38
- end
@@ -1,4 +0,0 @@
1
- run do
2
- foo()
3
- config(:identity) || 'localhost'
4
- end
@@ -1,8 +0,0 @@
1
- param! "name"
2
-
3
- run do |name|
4
- registry = @op.plugins["core"].state[:contributions]
5
- if registry.has_key? name
6
- registry[name]
7
- end
8
- end
@@ -1,3 +0,0 @@
1
- run do
2
- @plugin.state[:entities].map { |x| x[:name] }
3
- end
@@ -1,9 +0,0 @@
1
- require "pry"
2
-
3
- run do |params|
4
- @op._pry
5
- # pry messes up the terminal completion setup by the vop shell backend,
6
- # so currently this is a one-call command:
7
- @op.system_call("bin/vop.sh")
8
- Kernel.exit(42)
9
- end
@@ -1,5 +0,0 @@
1
- description "reloads plugins and commands"
2
-
3
- run do
4
- @op._reset
5
- end
@@ -1,5 +0,0 @@
1
- param! "name", :lookup => lambda { |_| @op.list_commands.map { |x| x[:name] } }
2
-
3
- run do |params, name|
4
- @op.command(name).source[:code]
5
- end
@@ -1,5 +0,0 @@
1
- param! "command"
2
-
3
- run do |command|
4
- `#{command}`
5
- end
@@ -1,4 +0,0 @@
1
- on :init do |plugin|
2
- plugin.state[:entities] ||= []
3
- plugin.state[:contributions] ||= Hash.new { |h,k| h[k] = [] }
4
- end
@@ -1,45 +0,0 @@
1
- def config(sym)
2
- @plugin.config[sym]
3
- end
4
-
5
- def description(s)
6
- @command.description = s
7
- end
8
-
9
- def param(name, options = {})
10
- p = {
11
- :name => name.to_s,
12
- :multi => false,
13
- :mandatory => false,
14
- :default_param => false
15
- }
16
-
17
- if name.is_a? Symbol
18
- # parameters whose names are symbols are resolved into entities
19
- op = @plugin.op
20
-
21
- entity_names = op.core.state[:entities].map { |entity| entity[:name] }
22
- if entity_names.include? name.to_s
23
- list_command_name = "list_#{name.to_s.pluralize(42)}"
24
- p[:lookup] = lambda do |params|
25
- # TODO :name is probably specific to the entity (key?)
26
- op.send(list_command_name.to_sym).map { |x| x[:name] }
27
- end
28
- end
29
- end
30
-
31
- @command.params << p.merge(options)
32
- end
33
-
34
- def param!(name, options = {})
35
- options.merge! :mandatory => true
36
- param(name, options)
37
- end
38
-
39
- def show(options = {})
40
- column_options = options.delete(:columns)
41
-
42
- raise "unknown keyword #{options.keys.first}" if options.keys.length > 0
43
-
44
- @command.show_options[:columns] = column_options
45
- end
@@ -1,28 +0,0 @@
1
- # TODO remove?
2
- def accept_contributions
3
- #puts "#{@command.name} accepts contributions"
4
- # TODO this init should happen earlier
5
- #@op.plugins['core'].state[:contributions] ||= Hash.new { |h,k| h[k] = [] }
6
- end
7
-
8
- def contribute(options = {}, &block)
9
- target = options[:to] || @command.short_name
10
- #puts "#{@command.name} contributes to #{target}"
11
- # TODO check that the command exists
12
- @op.plugins['core'].state[:contributions][target] << @command.name
13
- @command.block = block
14
- end
15
-
16
- def with_contributions(options = {}, &block)
17
- raise "untested"
18
- collected = @op.collect_contributions('name' => command_name, 'raw_params' => params)
19
- params_with_contributions = params.merge(:contributions => collected)
20
- list = block.call(params_with_contributions)
21
-
22
- found = list.select { |x| x[key.to_sym] == params[key] }
23
- if found && found.size > 0
24
- Entity.new(@op, command_name, key, found.first)
25
- else
26
- raise "no such entity : #{params[key]} [#{command_name}]"
27
- end
28
- end
@@ -1,57 +0,0 @@
1
- require 'vop/entity'
2
- require 'pp'
3
-
4
- # if an entity doesn't define a block, by default
5
- # it just passes through all contributions
6
- def default_entity_block(params)
7
- params['contributions']
8
- end
9
-
10
- def define_entity(name, key, options)
11
- @op.plugins['core'].state[:entities] << {
12
- name: name,
13
- key: key,
14
- options: options
15
- }
16
- end
17
-
18
- def entity(key, options = {}, &block)
19
- command_name = @command.short_name
20
-
21
- # the entity command gets a mandatory param automatically, using the
22
- # block defined by the entity as lookup
23
- # TODO make the default block work
24
- #list_block = block || default_entity_block
25
- list_block = block
26
-
27
- if options[:on]
28
- param! options[:on]
29
- end
30
-
31
- param! key, :lookup => lambda { |params|
32
- collected = @op.collect_contributions('name' => command_name, 'raw_params' => params)
33
- params_with_contributions = params.merge(:contributions => collected)
34
- the_list = list_block.call(params_with_contributions)
35
- the_list
36
- }
37
-
38
- define_entity(command_name, key, options)
39
-
40
- # entities generally accept contributions...
41
- accept_contributions
42
-
43
- # ...and they have a special run block that filters the correct row from
44
- # the lookup list and returns an entity populated from that row
45
- @command.block = lambda do |params|
46
- collected = @op.collect_contributions('name' => command_name, 'raw_params' => params)
47
- params_with_contributions = params.merge(:contributions => collected)
48
- list = list_block.call(params_with_contributions)
49
-
50
- found = list.select { |x| x[key.to_sym] == params[key] }
51
- if found && found.size > 0
52
- Entity.new(@op, command_name, key, found.first)
53
- else
54
- raise "no such entity : #{params[key]} [#{command_name}]"
55
- end
56
- end
57
- end
@@ -1,3 +0,0 @@
1
- def foo
2
- puts "call for mr. beeblebrox!"
3
- end
@@ -1,6 +0,0 @@
1
- param! 'path'
2
-
3
- run do |params|
4
- @op.config[:search_path] << params['path']
5
- @op.reset
6
- end
@@ -1,13 +0,0 @@
1
- require 'fileutils'
2
-
3
- param! 'name'
4
-
5
- run do |params|
6
- name = params['name']
7
-
8
- plugin = @op.plugins[name]
9
- raise "no such plugin : #{name}" unless plugin
10
-
11
- FileUtils.rm_r plugin.path
12
- @op.reset
13
- end
@@ -1,17 +0,0 @@
1
- description "returns a list of commands with descriptions"
2
-
3
- param 'plugin', :description => 'list of plugins to filter by',
4
- :multi => true,
5
- :lookup => lambda { |params| @op.list_plugins.map { |x| x[:name] } },
6
- :default_param => true
7
-
8
- run do |params|
9
- @op.commands.select do |name, command|
10
- not params.has_key?('plugin') or params['plugin'].include?(command.plugin.name)
11
- end.map do |name, command|
12
- {
13
- :name => name,
14
- :description => command.description || ''
15
- }
16
- end
17
- end
@@ -1,8 +0,0 @@
1
- run do
2
- @op.plugins.map do |name, plugin|
3
- {
4
- :name => name,
5
- :path => plugin.path
6
- }
7
- end
8
- end
@@ -1,14 +0,0 @@
1
- param! 'name'
2
- param! 'plugin', :lookup => lambda { @op.list_plugins.map { |x| x[:name] } }
3
-
4
- run do |params|
5
- pp params
6
- plugin = @op.list_plugins.select { |x| x[:name] == params['plugin'] }.first
7
-
8
- command_file = File.join(plugin[:path], 'commands', "#{params['name']}.rb")
9
- File.write(command_file, "run { 42 }")
10
-
11
- @op.reset
12
-
13
- nil
14
- end
@@ -1,25 +0,0 @@
1
- description "initializes a new plugin (folder)"
2
-
3
- param! 'name'
4
- param! 'path', :description => 'the path in which to create the new plugin.'
5
-
6
- require 'fileutils'
7
-
8
- run do |params|
9
- raise "no such path: #{params['path']}" unless File.exists? params['path']
10
-
11
- # a plugin is a directory
12
- plugin_path = File.join(params['path'], params['name'])
13
- Dir.mkdir(plugin_path)
14
-
15
- # with subfolders for commands and helpers
16
- %w|commands helpers|.each do |thing|
17
- Dir.mkdir(File.join(plugin_path, thing))
18
- end
19
-
20
- # and a metadata file called '<name>.plugin'
21
- plugin_file = params['name'] + '.plugin'
22
- FileUtils.touch(File.join(plugin_path, plugin_file))
23
-
24
- @op.reset
25
- end
@@ -1,3 +0,0 @@
1
- run do
2
- @op._search_path
3
- end
@@ -1,5 +0,0 @@
1
- param! 'name', :lookup => lambda { |params| @op.list_commands.map { |x| x[:name] } }
2
-
3
- run do |name|
4
- @op.command(name).plugin.name
5
- end
@@ -1 +0,0 @@
1
- dependency :core
@@ -1,11 +0,0 @@
1
- require 'net/scp'
2
-
3
- param! 'machine'
4
- param 'user'
5
- param! 'local_path'
6
- param! 'remote_path'
7
-
8
- run do |machine, local_path, remote_path, params|
9
- user = params.has_key?('user') ? params['user'] : ENV['USER']
10
- Net::SCP.upload!(params['machine'], user, local_path, remote_path)
11
- end
@@ -1,19 +0,0 @@
1
- require 'net/ssh'
2
-
3
- param! 'machine'
4
- param! 'command', :default_param => true
5
- param 'user' #, :default => ENV['USER']
6
- param 'key_file', :multi => true
7
-
8
- run do |params|
9
- machine_name = params['machine']
10
- user = params.has_key?('user') ? params['user'] : ENV['USER']
11
-
12
- options = {}
13
- if params.has_key? 'key_file'
14
- options[:keys] = params['key_file']
15
- end
16
- Net::SSH.start(machine_name, user, options) do |ssh|
17
- ssh.exec! params['command']
18
- end
19
- end
@@ -1 +0,0 @@
1
- depends :core
@@ -1,28 +0,0 @@
1
- # responsible for what should happen when the user interacts with the shell
2
- # provides tab completion options and processes the user's input
3
- class Backend
4
-
5
- # is called whenever the user submits a command (hitting enter)
6
- def process_input(command_line)
7
- raise "not implemented in abstract Backend!"
8
- end
9
-
10
- # is called whenever the user requests tab completion
11
- # should return an array of completion proposals
12
- def complete(word)
13
- raise "not implemented in abstract Backend!"
14
- end
15
-
16
- def prompt
17
- ">"
18
- end
19
-
20
- def process_ctrl_c
21
-
22
- end
23
-
24
- def show_banner
25
-
26
- end
27
-
28
- end
@@ -1,112 +0,0 @@
1
- require 'readline'
2
-
3
- # This class is an abstract implementation of a command shell
4
- # It handles command completion and history functions.
5
- # For the actual business logic, you need to pass it an implementation of ShellBackend
6
- class BaseShell
7
- attr_reader :backend
8
-
9
- def initialize(backend)
10
- @logger = $logger
11
- @backend = backend
12
-
13
- at_exit { console.close }
14
-
15
- trap("INT") {
16
- Thread.kill(@thread)
17
- @backend.process_ctrl_c
18
- }
19
- end
20
-
21
- class SimpleConsole
22
- def initialize(input = $stdin)
23
- @input = input
24
- end
25
-
26
- def readline
27
- begin
28
- line = @input.readline
29
- line.chomp! if line
30
- line
31
- rescue EOFError
32
- nil
33
- end
34
- end
35
-
36
- def close
37
- end
38
- end
39
-
40
- class ReadlineConsole
41
- HISTORY_FILE = ".vop_history"
42
- MAX_HISTORY = 200
43
-
44
- def history_path
45
- File.join(ENV['HOME'] || ENV['USERPROFILE'], HISTORY_FILE)
46
- end
47
-
48
- def initialize(shell)
49
- @shell = shell
50
-
51
- if File.exist?(history_path)
52
- hist = File.readlines(history_path).map{|line| line.chomp}
53
- Readline::HISTORY.push(*hist)
54
- end
55
-
56
- #Readline.basic_word_break_characters = " \t\n\\`@><=;|&{([+*%"
57
-
58
- # see http://stackoverflow.com/questions/13876024/how-to-write-a-ruby-command-line-app-that-supports-tab-completion#13876556
59
- Readline.completer_word_break_characters = ""
60
-
61
- Readline.completion_append_character = nil
62
- Readline.completion_proc = @shell.backend.method(:complete).to_proc
63
- end
64
-
65
- def close
66
- open(history_path, "wb") do |f|
67
- history = Readline::HISTORY.to_a
68
- if history.size > MAX_HISTORY
69
- history = history[history.size - MAX_HISTORY, MAX_HISTORY]
70
- end
71
- history.each{|line| f.puts(line)}
72
- end
73
- end
74
-
75
- def readline
76
- line = Readline.readline(@shell.backend.prompt, true)
77
- Readline::HISTORY.pop if /^\s*$/ =~ line
78
- line
79
- end
80
- end
81
-
82
- def console
83
- @console ||= $stdin.tty? ? ReadlineConsole.new(self) : SimpleConsole.new
84
- end
85
-
86
- def run
87
- backend.show_banner
88
- loop do
89
- @thread = Thread.new {
90
- line = console.readline
91
-
92
- if line
93
- backend.process_input line
94
- end
95
- }
96
- begin
97
- @thread.join
98
- rescue
99
- error = $!
100
- if error == "exit"
101
- Kernel.exit
102
- else
103
- $stderr.puts "error: >>#{error}<<"
104
- $stderr.puts error.backtrace.join("\n")
105
- end
106
- end
107
-
108
- end
109
- $stderr.puts "Exiting shell..."
110
- end
111
-
112
- end