<commit_before>require 'pty'
require 'expectr'
require 'expectr/interface'

class Expectr
  # Public: The Expectr::Child Module defines the interface for interacting
  # with child processes.
  module Child
    include Expectr::Interface

    # Public: Initialize the Expectr interface, spawning a sub-process
    # attaching to STDIN and STDOUT of the new process.
    #
    # args - A Hash containing arguments to Expectr.  Only :cmd is presently
    #        used for this function.
    #        :cmd - A String or File referencing the application to launch.
    #
    # Returns nothing.
    # Raises TypeError if args[:cmd] is anything other than String or File.
    def init_interface(args)
      cmd = args[:cmd]
      cmd = cmd.path if cmd.kind_of?(File)

      unless cmd.kind_of?(String)
        raise(TypeError, Errstr::STRING_FILE_EXPECTED)
      end

      @stdout,@stdin,@pid = PTY.spawn(cmd)
      @stdout.winsize = $stdout.winsize if $stdout.tty?
    end

    # Public: Send a signal to the running child process.
    #
    # signal - Symbol, String or FixNum indicating the symbol to be sent to the
    #          running process. (default: :TERM)
    #
    # Returns a boolean indicating whether the process was successfully sent
    # the signal.
    # Raises ProcessError if the process is not running (@pid = 0).
    def kill!(signal = :TERM)
      unless @pid > 0
        raise(ProcessError, Errstr::PROCESS_NOT_RUNNING)
      end
      Process::kill(signal.to_sym, @pid) == 1
    end

    # Public: Send input to the active child process.
    #
    # str - String to be sent.
    #
    # Returns nothing.
    # Raises Expectr::ProcessError if the process is not running (@pid = 0).
    def send(str)
      begin
        @stdin.syswrite str
      rescue Errno::EIO, EOFError # Application is not running
        @pid = 0
      end
      unless @pid > 0
        raise(Expectr::ProcessError, Errstr::PROCESS_GONE)
      end
    end

    # Public: Read the output of the child process, force UTF-8 encoding, then
    # append to the internal buffer and print to $stdout if appropriate.
    #
    # Returns nothing.
    def output_loop
      while @pid > 0
        unless select([@stdout], nil, nil, @timeout).nil?
          buf = ''

          begin
            @stdout.sysread(@buffer_size, buf)
          rescue Errno::EIO #Application is not running
            @pid = 0
            @thread.wakeup if @thread
            return
          end
          process_output(buf)
        end
      end
    end

    # Public: Return the PTY's window size.
    #
    # Returns a two-element Array (same as IO#winsize)
    def winsize
      @stdout.winsize
    end

    private

    # Internal: Set up the execution environment to prepare for entering
    # interact mode.
    # Presently assumes a Linux system.
    #
    # Returns nothing.<commit_msg>fixes #4 - catches EOFError from sysread and wakes up main thread<commit_after>require 'pty'
require 'expectr'
require 'expectr/interface'

class Expectr
  # Public: The Expectr::Child Module defines the interface for interacting
  # with child processes.
  module Child
    include Expectr::Interface

    # Public: Initialize the Expectr interface, spawning a sub-process
    # attaching to STDIN and STDOUT of the new process.
    #
    # args - A Hash containing arguments to Expectr.  Only :cmd is presently
    #        used for this function.
    #        :cmd - A String or File referencing the application to launch.
    #
    # Returns nothing.
    # Raises TypeError if args[:cmd] is anything other than String or File.
    def init_interface(args)
      cmd = args[:cmd]
      cmd = cmd.path if cmd.kind_of?(File)

      unless cmd.kind_of?(String)
        raise(TypeError, Errstr::STRING_FILE_EXPECTED)
      end

      @stdout,@stdin,@pid = PTY.spawn(cmd)
      @stdout.winsize = $stdout.winsize if $stdout.tty?
    end

    # Public: Send a signal to the running child process.
    #
    # signal - Symbol, String or FixNum indicating the symbol to be sent to the
    #          running process. (default: :TERM)
    #
    # Returns a boolean indicating whether the process was successfully sent
    # the signal.
    # Raises ProcessError if the process is not running (@pid = 0).
    def kill!(signal = :TERM)
      unless @pid > 0
        raise(ProcessError, Errstr::PROCESS_NOT_RUNNING)
      end
      Process::kill(signal.to_sym, @pid) == 1
    end

    # Public: Send input to the active child process.
    #
    # str - String to be sent.
    #
    # Returns nothing.
    # Raises Expectr::ProcessError if the process is not running (@pid = 0).
    def send(str)
      begin
        @stdin.syswrite str
      rescue Errno::EIO, EOFError # Application is not running
        @pid = 0
      end
      unless @pid > 0
        raise(Expectr::ProcessError, Errstr::PROCESS_GONE)
      end
    end

    # Public: Read the output of the child process, force UTF-8 encoding, then
    # append to the internal buffer and print to $stdout if appropriate.
    #
    # Returns nothing.
    def output_loop
      while @pid > 0
        unless select([@stdout], nil, nil, @timeout).nil?
          buf = ''

          begin
            @stdout.sysread(@buffer_size, buf)
          rescue Errno::EIO, EOFError #Application is not running
            @pid = 0
            @thread.wakeup if @thread
            return
          end
          process_output(buf)
        end
      end
    end

    # Public: Return the PTY's window size.
    #
    # Returns a two-element Array (same as IO#winsize)
    def winsize
      @stdout.winsize
    end

    private

    # Internal: Set up the execution environment to prepare for entering
    # interact mode.
    # Presently assumes a Linux system.
    #
    # Returns nothing.
{'fraction_non_alphanumeric': 0.0786109191430546, 'fraction_numerical': 0.0032826537664132687, 'mean_word_length': 2.252247191011236, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3907751', 'n_tokens_mistral': 1684, 'n_tokens_neox': 1650, 'n_words': 610}