<commit_before>class Hydra::Host
  SKIP_BEFORE_CMDS = [
    /^cd /
  ]


  attr_accessor :name, :user, :roles, :tasks, :hydra
  def initialize(name, opts = {})
    @name = name
    @user = opts[:user]
    @roles = opts[:roles] || []
    @tasks = opts[:tasks] || []
    @hydra = opts[:hydra]
    @before_cmds = []
  end

  def ==(other)
    self.name == other.name
  end

  # all calls to exec! within this block are prefixed by sudoing to the user.
  def as(user = nil)
    old_cmds = @before_cmds

    if user.nil?
      @before_cmds = ["sudo"]
    else
      @before_cmds = ["sudo -u #{escape(user)}"]
    end

    yield

    @before_cmds = old_cmds
  end

  # Changes the mode of a file. Mode is numeric.
  def chmod
    chmod mode.to_s(8), path
  end

  # Changes the mode of a file, recursively. Mode is numeric.
  def chmod_r(mode, path)
    chmod '-R', mode.to_s(8), path
  end

  # Returns true if a directory exists
  def dir?(path)
    ftype(path) == 'directory' rescue false
  end

  # Download a file to the local host. Local defaults to remote if not
  # specified.
  def download(remote, local=nil, opts = {})
    local ||= remote
    ssh.scp.download!(remote, local, opts)
  end

  # Quotes a string for inclusion in a bash command line
  def escape(string)
    '"' + string.to_s.gsub(/[\\\$`"]/) { |match| '\\' + match } + '"'
  end
  
  # Runs a remote command.
  def exec!(command)
    unless SKIP_BEFORE_CMDS === command.to_s
      command = (@before_cmds + [command]).join(' ')
    end
    
    if @shell
      # Run in shell
      status, output = @shell.exec! command
      raise RuntimeError, "#{command} returned non-zero exit status #{status}:\n#{output}" if status != 0
      output
    else
      response = ssh.exec! *args
    end
    response
  end

  # Returns true when a file exists, otherwise false
  def exists?(path)
    true if ftype(path) rescue false
  end

  # Returns true if a regular file exists.
  def file?(path)
    ftype(path) == 'file' rescue false
  end

  # Returns the filetype, as string. Raises exceptions on failed stat.
  def ftype(path)
    str = self.stat('-c', '%F', path).strip
    case str
    when /no such file or directory/i
      raise Errno::ENOENT, "#{self}:#{path} does not exist"
    when 'regular file'
      'file'
    when 'directory'
      'directory'
    when 'character special file'
      'characterSpecial'
    when 'block special file'
      'blockSpecial'
    when /link/
      'link'
    when /socket/
      'socket'
    when /fifo|pipe/
      'fifo'
    else
      raise RuntimeError, "stat #{self}:#{path} failed - #{stat}"
    end
  end

  # Returns the gateway for this host.
  def gw
    @hydra.gw
  end

  def inspect
    "#<#{@user}@#{@name} roles=#{@roles.inspect} tasks=#{@tasks.inspect}>"
  end

  # Missing methods are resolved as follows:
  # 1. From task_resolve
  # 2. Converted to a command string and exec!'ed
  def method_missing(meth, *args)
    if task = resolve_task(meth)
      task.run(self, *args)
    else
      str = ([meth] + args.map{|a| escape(a)}).join(' ')
      exec! str
    end
  end

  # Returns the file mode of a remote file.
  def mode(path)
    stat('-c', '%a', path).oct
  end

  # Finds a task for this host, by name.
  def resolve_task(name)
    @tasks.each do |task|
      return task if task.name == name
    end
    @roles.each do |role|
      role.tasks.each do |task|
        return task if task.name == name
      end
    end
    nil
  end

  # Removes a remote file
  def rm(path, rf=false)
  end

  # Assigns roles to a host from the Hydra. Roles are unique in hosts; repeat
  # assignments will not result in more than one copy of the role.
  def role(role)
    @roles = @roles | [@hydra.role(role)]
  end

  # Run a task, tasks, or all defined tasks.
  # Tasks are run in the order given.
  # Tasks are run with this host as context.
  def run(*task_names)
    ssh
    task_names.each do |name|
      task = resolve_task(name) or raise RuntimeError, "no such task #{name} on #{self}"
      task.run(self)
    end
  end

  # Opens a shell. Subsequent exec! commands are interpreted by the shell.
  def shell(&block)
    ssh.shell do |shell|
      @shell = shell
      instance_exec(&block)
    end
  end
<commit_msg>Every invocation of shell, even nested, sets aside and restores the appropriate shell.<commit_after>class Hydra::Host
  SKIP_BEFORE_CMDS = [
    /^cd /
  ]

  attr_accessor :name, :user, :roles, :tasks, :hydra
  def initialize(name, opts = {})
    @name = name
    @user = opts[:user]
    @roles = opts[:roles] || []
    @tasks = opts[:tasks] || []
    @hydra = opts[:hydra]
    @before_cmds = []
  end

  def ==(other)
    self.name == other.name
  end

  # all calls to exec! within this block are prefixed by sudoing to the user.
  def as(user = nil)
    old_cmds = @before_cmds

    if user.nil?
      @before_cmds = ["sudo"]
    else
      @before_cmds = ["sudo -u #{escape(user)}"]
    end

    yield

    @before_cmds = old_cmds
  end

  # Changes the mode of a file. Mode is numeric.
  def chmod
    chmod mode.to_s(8), path
  end

  # Changes the mode of a file, recursively. Mode is numeric.
  def chmod_r(mode, path)
    chmod '-R', mode.to_s(8), path
  end

  # Returns true if a directory exists
  def dir?(path)
    ftype(path) == 'directory' rescue false
  end

  # Download a file to the local host. Local defaults to remote if not
  # specified.
  def download(remote, local=nil, opts = {})
    local ||= remote
    ssh.scp.download!(remote, local, opts)
  end

  # Quotes a string for inclusion in a bash command line
  def escape(string)
    '"' + string.to_s.gsub(/[\\\$`"]/) { |match| '\\' + match } + '"'
  end
  
  # Runs a remote command.
  def exec!(command)
    unless SKIP_BEFORE_CMDS.any? { |cmd| cmd  === command.to_s }
      command = (@before_cmds + [command]).join(' ')
    end
    
    if @shell
      # Run in shell
      status, output = @shell.exec! command
      raise RuntimeError, "#{command} returned non-zero exit status #{status}:\n#{output}" if status != 0
      output
    else
      response = ssh.exec! *args
    end
    response
  end

  # Returns true when a file exists, otherwise false
  def exists?(path)
    true if ftype(path) rescue false
  end

  # Returns true if a regular file exists.
  def file?(path)
    ftype(path) == 'file' rescue false
  end

  # Returns the filetype, as string. Raises exceptions on failed stat.
  def ftype(path)
    str = self.stat('-c', '%F', path).strip
    case str
    when /no such file or directory/i
      raise Errno::ENOENT, "#{self}:#{path} does not exist"
    when 'regular file'
      'file'
    when 'directory'
      'directory'
    when 'character special file'
      'characterSpecial'
    when 'block special file'
      'blockSpecial'
    when /link/
      'link'
    when /socket/
      'socket'
    when /fifo|pipe/
      'fifo'
    else
      raise RuntimeError, "stat #{self}:#{path} failed - #{stat}"
    end
  end

  # Returns the gateway for this host.
  def gw
    @hydra.gw
  end

  def inspect
    "#<#{@user}@#{@name} roles=#{@roles.inspect} tasks=#{@tasks.inspect}>"
  end

  # Missing methods are resolved as follows:
  # 1. From task_resolve
  # 2. Converted to a command string and exec!'ed
  def method_missing(meth, *args)
    if task = resolve_task(meth)
      task.run(self, *args)
    else
      str = ([meth] + args.map{|a| escape(a)}).join(' ')
      exec! str
    end
  end

  # Returns the file mode of a remote file.
  def mode(path)
    stat('-c', '%a', path).oct
  end

  # Finds a task for this host, by name.
  def resolve_task(name)
    @tasks.each do |task|
      return task if task.name == name
    end
    @roles.each do |role|
      role.tasks.each do |task|
        return task if task.name == name
      end
    end
    nil
  end

  # Removes a remote file
  def rm(path, rf=false)
  end

  # Assigns roles to a host from the Hydra. Roles are unique in hosts; repeat
  # assignments will not result in more than one copy of the role.
  def role(role)
    @roles = @roles | [@hydra.role(role)]
  end

  # Run a task, tasks, or all defined tasks.
  # Tasks are run in the order given.
  # Tasks are run with this host as context.
  def run(*task_names)
    ssh
    task_names.each do |name|
      task = resolve_task(name) or raise RuntimeError, "no such task #{name} on #{self}"
      task.run(self)
    end
  end

  # Opens a shell. Subsequent exec! commands are interpreted by the shell.
  def shell(&block)
    ssh.shell do |shell|
      old_shell, @shell = @shell, shell
      instance_exec(&block)
      @shell = old_shell
    end
  end

{'fraction_non_alphanumeric': 0.11804267161410019, 'fraction_numerical': 0.0011595547309833025, 'mean_word_length': 2.5089503661513426, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1729438', 'n_tokens_mistral': 2983, 'n_tokens_neox': 2894, 'n_words': 992}