Class: Thor::Interactive::Shell

Inherits:
Object
  • Object
show all
Includes:
CommandDispatch
Defined in:
lib/thor/interactive/shell.rb

Constant Summary collapse

DEFAULT_PROMPT =
"> "
DEFAULT_HISTORY_FILE =
"~/.thor_interactive_history"

Constants included from CommandDispatch

CommandDispatch::EXIT_COMMANDS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from CommandDispatch

#after_path_option?, #complete_command_options, #complete_commands, #complete_input, #complete_option_names, #complete_path, #complete_subcommand_args, #complete_subcommands, #format_path_completions, #handle_command, #handle_slash_command, #handle_unparseable_command, #invoke_thor_command, #is_help_request?, #parse_input, #parse_thor_options, #path_like?, #process_input, #safe_parse_input, #should_exit?, #show_help, #single_text_command?, #thor_command?

Constructor Details

#initialize(thor_class, options = {}) ⇒ Shell

Returns a new instance of Shell.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/thor/interactive/shell.rb', line 16

def initialize(thor_class, options = {})
  @thor_class = thor_class
  @thor_instance = thor_class.new

  # Merge class-level interactive options if available
  merged_options = {}
  if thor_class.respond_to?(:interactive_options)
    merged_options.merge!(thor_class.interactive_options)
  end
  merged_options.merge!(options)

  @merged_options = merged_options
  @default_handler = merged_options[:default_handler]
  @prompt = merged_options[:prompt] || DEFAULT_PROMPT
  @history_file = File.expand_path(merged_options[:history_file] || DEFAULT_HISTORY_FILE)

  # Ctrl-C handling configuration
  @ctrl_c_behavior = merged_options[:ctrl_c_behavior] || :clear_prompt
  @double_ctrl_c_timeout = merged_options.key?(:double_ctrl_c_timeout) ?
                          merged_options[:double_ctrl_c_timeout] : 0.5
  @last_interrupt_time = nil

  setup_completion
  load_history
end

Instance Attribute Details

#promptObject (readonly)

Returns the value of attribute prompt.



12
13
14
# File 'lib/thor/interactive/shell.rb', line 12

def prompt
  @prompt
end

#thor_classObject (readonly)

Returns the value of attribute thor_class.



12
13
14
# File 'lib/thor/interactive/shell.rb', line 12

def thor_class
  @thor_class
end

#thor_instanceObject (readonly)

Returns the value of attribute thor_instance.



12
13
14
# File 'lib/thor/interactive/shell.rb', line 12

def thor_instance
  @thor_instance
end

Instance Method Details

#startObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/thor/interactive/shell.rb', line 42

def start
  # Track that we're in an interactive session
  was_in_session = ENV["THOR_INTERACTIVE_SESSION"]
  nesting_level = ENV["THOR_INTERACTIVE_LEVEL"].to_i

  ENV["THOR_INTERACTIVE_SESSION"] = "true"
  ENV["THOR_INTERACTIVE_LEVEL"] = (nesting_level + 1).to_s

  puts "(Debug: Interactive session started, level #{nesting_level + 1})" if ENV["DEBUG"]

  # Adjust prompt for nested sessions if configured
  display_prompt = @prompt
  if nesting_level > 0 && @merged_options[:nested_prompt_format]
    display_prompt = @merged_options[:nested_prompt_format] % [nesting_level + 1, @prompt]
  elsif nesting_level > 0
    display_prompt = "(#{nesting_level + 1}) #{@prompt}"
  end

  show_welcome(nesting_level)

  puts "(Debug: Entering main loop)" if ENV["DEBUG"]

  loop do
    begin
      line = Reline.readline(display_prompt, true)
      puts "(Debug: Got input: #{line.inspect})" if ENV["DEBUG"]

      # Reset interrupt tracking on successful input
      @last_interrupt_time = nil if line

      if should_exit?(line)
        puts "(Debug: Exit condition met)" if ENV["DEBUG"]
        break
      end

      next if line.nil? || line.strip.empty?

      puts "(Debug: Processing input: #{line.strip})" if ENV["DEBUG"]
      process_input(line.strip)
      puts "(Debug: Input processed successfully)" if ENV["DEBUG"]
    rescue Interrupt
      # Handle Ctrl-C
      if handle_interrupt
        break # Exit on double Ctrl-C
      end
      next # Continue on single Ctrl-C
    rescue SystemExit => e
      puts "A command tried to exit with code #{e.status}. Staying in interactive mode."
      puts "(Debug: SystemExit caught in main loop)" if ENV["DEBUG"]
    rescue => e
      puts "Error: #{e.message}"
      puts e.backtrace.first(5) if ENV["DEBUG"]
      puts "(Debug: Error handled, continuing loop)" if ENV["DEBUG"]
      # Continue the loop - don't let errors break the session
    end
  end

  puts "(Debug: Exited main loop)" if ENV["DEBUG"]
  save_history
  puts nesting_level > 0 ? "Exiting nested session..." : "Goodbye!"
ensure
  # Restore previous session state
  if was_in_session
    ENV["THOR_INTERACTIVE_SESSION"] = "true"
    ENV["THOR_INTERACTIVE_LEVEL"] = nesting_level.to_s
  else
    ENV.delete("THOR_INTERACTIVE_SESSION")
    ENV.delete("THOR_INTERACTIVE_LEVEL")
  end
end