<commit_before># vim: set sw=2 ts=2 :

module Rye
  
  # = Rye::Set
  #
  #
  class Set
    attr_reader :name
    attr_reader :boxes
    attr_reader :opts
    
      # Run commands in parallel? A Boolean value. Default: false.
    attr_accessor :parallel
    
    
    # * +name+ The name of the set of machines
    # * +opts+ a hash of optional arguments 
    #
    # The +opts+ hash is used as defaults for all for all Rye::Box objects. 
    # All args supported by Rye::Box are available here with the addition of:
    #
    # * :parallel => run the commands in parallel? true or false (default).
    #
    def initialize(name='default', opts={})
      @name = name
      @boxes = []
      
      # These opts are use by Rye::Box and also passed to Net::SSH
      @opts = {
        :parallel => false,
        :user => Rye.sysinfo.user, 
        :safe => true,
        :port => 22,
        :keys => [],
        :password => <PASSWORD>,
        :proxy => nil,
        :debug => nil,
        :error => STDERR,
      }.merge(opts)
      
      @parallel = @opts.delete(:parallel) # Rye::Box doesn't have :parallel
      
      @safe = @opts.delete(:safe)
      @debug = @opts.delete(:debug)
      @error = @opts.delete(:error)
      
      @opts[:keys] = [@opts[:keys]].flatten.compact
      
      add_keys(@opts[:keys])
    end
    
    def opts; @opts; end
    def user; (@opts || {})[:user]; end
    def root?; user.to_s == "root" end
    
    # * +boxes+ one or more boxes. Rye::Box objects will be added directly 
    # to the set. Hostnames will be used to create new instances of Rye::Box 
    # and those will be added to the list. 
    def add_box(*boxes)
      boxes = boxes.flatten.compact 
      @boxes += boxes.collect do |box|
        box = Rye::Box.new(box, @opts) if box.is_a?(String) 
        box.add_keys(@keys)
        box
      end
      self
    end
    alias :add_boxes :add_box
    
    # Add one or more private keys to each box. Also stores key paths
    # in the set so when new boxes are added they will get the same keys,
    # * +additional_keys+ is a list of file paths to private keys
    # Returns the instance of Rye::Set
    def add_keys(*additional_keys)
      additional_keys = additional_keys.flatten.compact
      @opts[:keys] ||= []
      @opts[:keys] += additional_keys
      @opts[:keys].uniq!
      @boxes.each do |box|
        box.add_keys *additional_keys
      end
      self
    end
    alias :add_key :add_keys
    
    def remove_keys(*keys)
      @opts[:keys] ||= []
      @opts[:keys] -= keys.flatten.compact
      @boxes.each do |box|
        box.remove_keys keys.flatten.compact
      end
      self
    end
    alias :remove_key :remove_keys
    
    # Add an environment variable. +n+ and +v+ are the name and value.
    # Returns the instance of Rye::Set
    def setenv(n, v)
      run_command(:setenv, n, v)
      self
    end
    alias :setenvironment_variable :setenv
    
    # See Rye.keys
    def keys
      Rye.keys
    end
    
    def to_s
      "%s:%s" % [self.class.to_s, @name]
    end
    
    def inspect
      a = [self.class.to_s, @name, @parallel, @opts.inspect, @boxes.inspect]
      %q{#<%s:%s parallel=%s opts=%s boxes=%s>} % a
    end
    
    # See Rye::Box.[]
    def [](key=nil)
      run_command(:cd, key)
      self
    end
#    alias :cd :'[]'  # fix for jruby
    def cd(key=nil)
      run_command(:cd, key)
      self
    end
    
    # Are there any boxes in this set?
    def empty?
      @boxes.nil? || @boxes.empty?
    end
    
    # Catches calls to Rye::Box commands. If +meth+ is the name of an 
    # instance method defined in Rye::Cmd then we call it against all 
    # the boxes in +@boxes+. Otherwise this method raises a
    # Rye::CommandNotFound exception. It will also raise a Rye::NoBoxes
    # exception if this set has no boxes defined. 
    #
    # Returns a Rye::Rap object containing the responses from each Rye::Box. 
    def method_missing(meth, *args, &block) 
      # Ruby 1.8 populates Module.instance_methods with Strings. 1.9 uses Symbols.
      meth = (Rye.sysinfo.ruby[1] == 8) ? meth.to_s : meth.to_sym
      raise Rye::NoBoxes if @boxes.empty?
      raise Rye::CommandNotFound, meth.to_s unless Rye::Box.instance_methods.member?(meth)
      run_command(meth, *args, &block)
    end
    
  private
    
    # Determines whether to call the serial or parallel method, then calls it. 
    def run_command(meth, *args, &block)
      runner = @parallel ? :run_command_parallel : :run_command_serial
      self.send(runner, meth, *args, &block)
    end
    
    
    # Run the command on all boxes in parallel
    def run_command_parallel(meth, *args, &block)
      debug "P: #{meth} on #{@boxes.size} boxes (#{@boxes.collect {|b| b.host }.join(', ')})"
      threads = []
      
      raps = Rye::Rap.new(self)
      (@boxes || []).each do |box|
        threads << Thread.new do
          Thread.current[:rap] = box.send(meth, *args, &block) # Store the result in the thread
        end
      end
      
      # Should it bubble up the exception for a single box? socket errors?
      threads.each do |t| <commit_msg>Allow sets to run unsafe commands<commit_after># vim: set sw=2 ts=2 :

module Rye
  
  # = Rye::Set
  #
  #
  class Set
    attr_reader :name
    attr_reader :boxes
    attr_reader :opts
    
      # Run commands in parallel? A Boolean value. Default: false.
    attr_accessor :parallel
    
    
    # * +name+ The name of the set of machines
    # * +opts+ a hash of optional arguments 
    #
    # The +opts+ hash is used as defaults for all for all Rye::Box objects. 
    # All args supported by Rye::Box are available here with the addition of:
    #
    # * :parallel => run the commands in parallel? true or false (default).
    #
    def initialize(name='default', opts={})
      @name = name
      @boxes = []
      
      # These opts are use by Rye::Box and also passed to Net::SSH
      @opts = {
        :parallel => false,
        :user => Rye.sysinfo.user, 
        :safe => true,
        :port => 22,
        :keys => [],
        :password => <PASSWORD>,
        :proxy => nil,
        :debug => nil,
        :error => STDERR,
      }.merge(opts)
      
      @parallel = @opts.delete(:parallel) # Rye::Box doesn't have :parallel
      
      @safe = @opts.delete(:safe)
      @debug = @opts.delete(:debug)
      @error = @opts.delete(:error)
      
      @opts[:keys] = [@opts[:keys]].flatten.compact
      
      add_keys(@opts[:keys])
    end
    
    def opts; @opts; end
    def user; (@opts || {})[:user]; end
    def root?; user.to_s == "root" end
    
    # * +boxes+ one or more boxes. Rye::Box objects will be added directly 
    # to the set. Hostnames will be used to create new instances of Rye::Box 
    # and those will be added to the list. 
    def add_box(*boxes)
      boxes = boxes.flatten.compact 
      @boxes += boxes.collect do |box|
        box = Rye::Box.new(box, @opts) if box.is_a?(String) 
        box.add_keys(@keys)
        box
      end
      self
    end
    alias :add_boxes :add_box
    
    # Add one or more private keys to each box. Also stores key paths
    # in the set so when new boxes are added they will get the same keys,
    # * +additional_keys+ is a list of file paths to private keys
    # Returns the instance of Rye::Set
    def add_keys(*additional_keys)
      additional_keys = additional_keys.flatten.compact
      @opts[:keys] ||= []
      @opts[:keys] += additional_keys
      @opts[:keys].uniq!
      @boxes.each do |box|
        box.add_keys *additional_keys
      end
      self
    end
    alias :add_key :add_keys
    
    def remove_keys(*keys)
      @opts[:keys] ||= []
      @opts[:keys] -= keys.flatten.compact
      @boxes.each do |box|
        box.remove_keys keys.flatten.compact
      end
      self
    end
    alias :remove_key :remove_keys
    
    # Add an environment variable. +n+ and +v+ are the name and value.
    # Returns the instance of Rye::Set
    def setenv(n, v)
      run_command(:setenv, n, v)
      self
    end
    alias :setenvironment_variable :setenv
    
    # See Rye.keys
    def keys
      Rye.keys
    end
    
    def to_s
      "%s:%s" % [self.class.to_s, @name]
    end
    
    def inspect
      a = [self.class.to_s, @name, @parallel, @opts.inspect, @boxes.inspect]
      %q{#<%s:%s parallel=%s opts=%s boxes=%s>} % a
    end
    
    # See Rye::Box.[]
    def [](key=nil)
      run_command(:cd, key)
      self
    end
#    alias :cd :'[]'  # fix for jruby
    def cd(key=nil)
      run_command(:cd, key)
      self
    end
    
    # Are there any boxes in this set?
    def empty?
      @boxes.nil? || @boxes.empty?
    end
    
    # Catches calls to Rye::Box commands. If +meth+ is the name of an 
    # instance method defined in Rye::Cmd then we call it against all 
    # the boxes in +@boxes+. Otherwise this method raises a
    # Rye::CommandNotFound exception. It will also raise a Rye::NoBoxes
    # exception if this set has no boxes defined. 
    #
    # Returns a Rye::Rap object containing the responses from each Rye::Box. 
    def method_missing(meth, *args, &block) 
      # Ruby 1.8 populates Module.instance_methods with Strings. 1.9 uses Symbols.
      meth = (Rye.sysinfo.ruby[1] == 8) ? meth.to_s : meth.to_sym
      raise Rye::NoBoxes if @boxes.empty?
      if @safe
        raise Rye::CommandNotFound, meth.to_s unless Rye::Box.instance_methods.member?(meth)
      end
      run_command(meth, *args, &block)
    end
    
  private
    
    # Determines whether to call the serial or parallel method, then calls it. 
    def run_command(meth, *args, &block)
      runner = @parallel ? :run_command_parallel : :run_command_serial
      self.send(runner, meth, *args, &block)
    end
    
    
    # Run the command on all boxes in parallel
    def run_command_parallel(meth, *args, &block)
      debug "P: #{meth} on #{@boxes.size} boxes (#{@boxes.collect {|b| b.host }.join(', ')})"
      threads = []
      
      raps = Rye::Rap.new(self)
      (@boxes || []).each do |box|
        threads << Thread.new do
          Thread.current[:rap] = box.send(meth, *args, &block) # Store the result in the thread
        end
      end
      
      # Should it bubble up the exception for a single box? socket errors?
      threads.each do |t| 
{'fraction_non_alphanumeric': 0.11591812978950432, 'fraction_numerical': 0.0019400523814142982, 'mean_word_length': 2.188988555521188, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '711733', 'n_tokens_mistral': 3389, 'n_tokens_neox': 3312, 'n_words': 1072}