require 'rim/rim_info'
require 'rim/rev_status'
require 'rim/dirty_check'

module RIM

class StatusBuilder

  # status object tree for revision rev
  # returns the root status object which points to any parent status objects
  # note that merge commits mean that the status tree branches
  # at the point were the merged branch branched off, the status tree joins
  # i.e. the parent status objects are the same at this point
  #
  # stops traversing a specific branch when a commit is found which is an ancestor
  # of :stop_rev or any remote branch if :stop_rev is not provided;
  #
  # with the :gerrit option, stops traversing on any ancestor of any known commit;
  # this is necessary since on gerrit there are no "remote" commits;
  # at the same time, gerrit doesn't "know" commits pushed in the ref-update hook yet
  # so the status will be built for the new commits pushed in the ref-update hook
  #
  # the leafs of the tree are the stop commits or commits which have no parents
  #
  # with the :fast option set to true, the leafs in the tree will not be checked
  # but instead all modules present in those commits will assumed to be clean;
  # be aware that this assumption might be wrong!
  # if the leaves of the tree are remote commits, the fast check basically tells
  # if any of the local commits is dirty or not
  #
  def rev_history_status(git_session, rev, options={})
    stop_rev = options[:stop_rev]
    relevant_revs = {}
    if stop_rev
      git_session.execute("git rev-list #{rev} \"^#{stop_rev}\"").split("\n").each do |r|
        relevant_revs[r] = true
      end
    elsif options[:gerrit]
      # in gerrit mode, stop on all known commits
      git_session.execute("git rev-list #{rev} --not --all --").split("\n").each do |r|
        relevant_revs[r] = true
      end
    else
      # remote revs are where we stop traversal
      git_session.all_reachable_non_remote_revs(rev).each do |r|
        relevant_revs[r] = true
      end
    end
    # make sure we deal only with sha1s
    rev = git_session.rev_sha1(rev)
    build_rev_history_status(git_session, rev, relevant_revs, {}, :fast => options[:fast])
  end

  # status object for single revision +rev+ without status of ancestors
  def rev_status(git_session, rev)
    mod_dirs = module_dirs(git_session, rev)
    mod_stats = []
    # export all relevant modules at once
    # this makes status calculation significantly faster compared
    # to exporting each module separately
    # (e.g. 1.0s instead of 1.5s on linux for a commit with 20 modules)
    git_session.within_exported_rev(rev, mod_dirs) do |d|
      mod_dirs.each do |rel_path|
        mod_stats << build_module_status(d, d+"/"+rel_path)
      end
    end
    stat = RevStatus.new(mod_stats)
    stat.git_rev = git_session.rev_sha1(rev)
    stat
  end

  # status object for a single module at +local_path+ in revision +rev+
  # returns nil if there is no such module in this revision
  def rev_module_status(git_session, rev, local_path)
    mod_stat = nil
    if git_session.execute("git ls-tree -r --name-only #{rev}").split("\n").include?(File.join(local_path, ".riminfo"))
      git_session.within_exported_rev(rev, [local_path]) do |d|
        mod_stat = build_module_status(d, File.join(d, local_path))
      end
    end
    mod_stat
  end

  # status object for the current file system content of dir
  # this can by any directory even outside of any git working copy
  def fs_status(dir)
    RevStatus.new(
      fs_rim_dirs(dir).collect { |d|
        build_module_status(dir, d)
      })
  end

  private

  def build_module_status(root_dir, dir)
    RevStatus::ModuleStatus.new(
      Pathname.new(dir).relative_path_from(Pathname.new(root_dir)).to_s,
      RimInfo.from_dir(dir),
      DirtyCheck.dirty?(dir)
    )
  end

  def fs_rim_dirs(dir)
    Dir.glob(dir+"/**/#{RimInfo::InfoFileName}").collect { |f|
      File.dirname(f)
    }
  end

  # building of the status of an ancestor chain works by checking
  # the dirty state of modules only when any files affecting some module
  # were changed; otherwise the status of the module in the ancestor is assumed
  #
  # for this to work, the chain must be walked from older commit to newer ones
  #
  # at the end of the chain, the status must be calculated in the regular "non-fast" way
  #
  def build_rev_history_status(gs, rev, relevant_revs, status_cache={}, options={})
    return status_cache[rev] if status_cache[rev]
    stat = nil
    if relevant_revs[rev]
      parent_revs = gs.parent_revs(rev)
      if parent_revs.size > 0
        # build status for all parent nodes
        parent_stats = parent_revs.collect do |p|
          build_rev_history_status(gs, p, relevant_revs, status_cache, options)
        end

        # if this is a merge commit with multiple parents
        # we decide to use the first commit (git primary parent)
        # note that it's not really important, which one we choose
        # just make sure to use the same commit when checking for changed files
        base_stat = parent_stats.first

        changed_files = gs.changed_files(rev, parent_revs.first)

        # build list of modules in this commit
        module_dirs = base_stat.modules.collect{|m| m.dir}
        changed_files.each do |f|
          if File.basename(f.path) == RimInfo::InfoFileName
            if f.kind == :added
              module_dirs << File.dirname(f.path)
            elsif f.kind == :deleted
              module_dirs.delete(File.dirname(f.path))
            end
          end
        end

        # a module needs to be checked if any of the files within were touched
        check_dirs = module_dirs.select{|d| changed_files.any?{|f| f.path.start_with?(d)} }

        module_stats = []
        # check out all modules to be checked at once
        if check_dirs.size > 0
          gs.within_exported_rev(rev, check_dirs) do |ws|
            check_dirs.each do |d|
              module_stats << build_module_status(ws, File.join(ws, d))
            end
          end
        end
        (module_dirs - check_dirs).each do |d|
          base_mod = base_stat.modules.find{|m| m.dir == d}
          module_stats << RevStatus::ModuleStatus.new(d, base_mod.rim_info, base_mod.dirty?)
        end

        stat = RevStatus.new(module_stats)
        stat.git_rev = gs.rev_sha1(rev)
        stat.parents.concat(parent_stats)
      else
        # no parents, need to do a full check
        if options[:fast]
          stat = rev_status_fast(gs, rev)
        else
          stat = rev_status(gs, rev)
        end
      end
    else
      # first "non-relevant", do the full check
      if options[:fast]
        stat = rev_status_fast(gs, rev)
      else
        stat = rev_status(gs, rev)
      end
    end
    status_cache[rev] = stat
  end

  def module_dirs(gs, rev)
    mod_dirs = []
    out = gs.execute("git ls-tree -r --name-only #{rev}")
    out.split("\n").each do |l|
      if File.basename(l) == RimInfo::InfoFileName
        mod_dirs << File.dirname(l)
      end
    end
    mod_dirs
  end

  # creates a RevStatus object for +rev+ with all modules assumend to be clean
  def rev_status_fast(git_session, rev)
    mod_dirs = module_dirs(git_session, rev)
    mod_stats = []
    git_session.within_exported_rev(rev, mod_dirs.collect{|d| "#{d}/#{RimInfo::InfoFileName}"}) do |temp_dir|
      mod_dirs.each do |rel_path|
        mod_stats << RevStatus::ModuleStatus.new(
          rel_path,
          RimInfo.from_dir("#{temp_dir}/#{rel_path}"),
          # never dirty
          false
        )
      end
    end
    stat = RevStatus.new(mod_stats)
    stat.git_rev = git_session.rev_sha1(rev)
    stat
  end

end

end


require 'rim/module_helper'
require 'rim/rim_info'
require 'rim/file_helper'
require 'rim/dirty_check'
require 'tempfile'
require 'pathname'

module RIM
  class SyncModuleHelper < ModuleHelper
    def initialize(dest_root, workspace_root, module_info, logger)
      super(workspace_root, module_info, logger)
      @dest_root = dest_root
    end

    # do the local sync without committing
    def sync(message = nil)
      fetch_module
      export_module(message)
    end

    private

    # export +revision+ of +mod+ into working copy
    # BEWARE: any changes to the working copy target dir will be lost!
    def export_module(message)
      changes = false
      RIM::git_session(@dest_root) do |d|
        start_sha1 = d.rev_sha1("HEAD")
        git_path = module_git_path(@remote_path)
        RIM::git_session(git_path) do |s|
          if !s.rev_sha1(@module_info.target_revision)
            raise RimException.new("Unknown target revision '#{@module_info.target_revision}' for module '#{@module_info.local_path}'.")
          end
          local_path = File.join(@dest_root, @module_info.local_path)
          prepare_empty_folder(local_path, @module_info.ignores)
          temp_commit(d, "clear directory") if d.uncommited_changes?
          strip = ""
          if @module_info.subdir
              depth = Pathname(@module_info.subdir).each_filename.count()
              strip = "--strip-components=#{depth}"
          end
          s.execute("git archive --format tar #{@module_info.target_revision} #{@module_info.subdir} | tar #{strip} -C #{local_path} -xf -")
          sha1 = s.execute("git rev-parse #{@module_info.target_revision}").strip
          @rim_info = RimInfo.new
          @rim_info.remote_url = @module_info.remote_url
          @rim_info.target_revision = @module_info.target_revision
          @rim_info.revision_sha1 = sha1
          @rim_info.ignores = @module_info.ignores.join(",")
          @rim_info.subdir = @module_info.subdir
          @rim_info.infos = s.rev_infos(@module_info.target_revision, RimInfo.git_infos)
          @rim_info.to_dir(local_path)
          DirtyCheck.mark_clean(local_path)
        end
        temp_commit(d, "commit changes") if needs_commit?(d)
        d.execute("git reset --soft #{start_sha1}")
        changes = d.uncommited_changes?
        commit(d, message || "rim sync: module #{@module_info.local_path}") if changes
      end
      changes
    end

    def needs_commit?(session)
      # do we need to commit something?
      stat = session.status(@module_info.local_path)
      # no files should be ignored due to --force option. Anyway we will check for ignored files
      ignored = []
      session.execute("git add --all --force #{@module_info.local_path}") do |out, e|
        ignored = parse_ignored_files(session, out, e)
      end
      if ignored.empty?
        stat = session.status(@module_info.local_path)
        ignored = stat.lines.select{ |l| l.ignored? }
      end
      if !ignored.empty?
        messages = ["Sync failed due to files/dirs of #{@module_info.local_path} which are ignored by workspace's .gitignore:"]
        ignored.each do |l|
          messages.push(l.file)
        end
        raise RimException.new(messages)
      end
      stat.lines.any?
    end

    def temp_commit(session, message)
      session.execute("git add --all")
      session.execute("git commit -m \"#{message}\" --")
    end

    def parse_ignored_files(session, out, e)
      first_line = true
      ignored = []
      out.gsub(/warning:.*will be replaced.*\r?\n.*\r?\n/, '').split(/\r?\n/).each do |l|
        raise e || RimException.new("Cannot parse ignored files after git add:\n#{out}") if first_line && !l.include?(".gitignore")
        if File.exist?(File.expand_path(l, session.execute_dir))
          ignored_line = GitSession::Status::Line.new
          ignored_line.file = l
          ignored_line.istat = "!"
          ignored.push(ignored_line)
        end
        first_line = false
      end
      ignored
    end

  end

end


require 'rim/command_helper'
require 'rim/sync_module_helper'
require 'rim/status_builder'
require 'tempfile'
require 'fileutils'

module RIM

class SyncHelper < CommandHelper

  def initialize(workspace_root, logger, module_infos = nil)
    @module_infos = []
    super(workspace_root, logger, module_infos)
  end

  # called to add a module info
  def add_module_info(module_info)
    @module_infos.push(module_info)
  end

  # sync all module changes into rim branch
  def sync(message = nil, rebase = nil, split = true)
    # get the name of the current workspace branch
    RIM::git_session(@ws_root) do |s|
      branch = s.current_branch || ''
      rim_branch = "rim/" + branch
      branch_sha1 = nil
      changed_modules = nil
      if branch.empty?
        raise RimException.new("Not on a git branch.")
      elsif branch.start_with?("rim/")
        raise RimException.new("The current git branch '#{branch}' is a rim integration branch. Please switch to a non rim branch to proceed.")
      else
        branch = "refs/heads/#{branch}"
        branch_sha1 = s.rev_sha1(rim_branch)
        remote_rev = get_latest_remote_revision(s, branch)
        rev = get_latest_clean_path_revision(s, branch, remote_rev)
        if !s.has_branch?(rim_branch) || has_ancestor?(s, branch, s.rev_sha1(rim_branch)) || !has_ancestor?(s, rim_branch, remote_rev)
          s.execute("git branch -f #{rim_branch} #{rev}")
          branch_sha1 = s.rev_sha1(rim_branch)
        end
        remote_url = "file://" + @ws_root
        @logger.debug("Folder for temporary git repositories: #{@rim_path}")
        tmpdir = clone_or_fetch_repository(remote_url, module_tmp_git_path(".ws"), "Cloning workspace git...")
        RIM::git_session(tmpdir) do |tmp_session|
          tmp_session.execute("git reset --hard")
          tmp_session.execute("git clean -xdf")
          # use -f here to prevent git checkout from checking for untracked files which might be overwritten.
          # this is safe since we removed any untracked files before.
          # this is a workaround for a name case problem on windows:
          # if a file's name changes case between the current head and the checkout target,
          # git checkout will report the file with the new name as untracked and will fail
          tmp_session.execute("git checkout -B #{rim_branch} -f remotes/origin/#{rim_branch}")
          changed_modules = sync_modules(tmp_session, message)
          if !split
            tmp_session.execute("git reset --soft #{branch_sha1}")
            commit(tmp_session, message ? message : get_commit_message(changed_modules)) if tmp_session.uncommited_changes?
          end
          tmp_session.execute("git push #{remote_url} #{rim_branch}:#{rim_branch}")
        end
      end
      if !changed_modules.empty?
        if rebase
          s.execute("git rebase #{rim_branch}")
          @logger.info("Changes have been commited to branch #{rim_branch} and workspace has been rebased successfully.")
        else
          @logger.info("Changes have been commited to branch #{rim_branch}. Rebase to apply changes to workspace.")
        end
      else
        @logger.info("No changes.")
      end
    end
  end

private
  # sync all modules
  def sync_modules(session, message)
    module_helpers = []
    @module_infos.each do |module_info|
      module_helpers.push(SyncModuleHelper.new(session.execute_dir, @ws_root, module_info, @logger))
    end
    changed_modules = []
    module_helpers.each do |m|
      @logger.info("Synchronizing #{m.module_info.local_path}...")
      if m.sync(message)
        changed_modules << m.module_info
      end
    end
    changed_modules
  end

  # get latest revision from which all parent revisions are clean
  def get_latest_clean_path_revision(session, rev, remote_rev)
    # make sure we deal only with sha1s
    rev = session.rev_sha1(rev)
    # get history status (up to last remote revision)
    status = StatusBuilder.new().rev_history_status(session, rev, :fast => true)
    clean_rev = rev;
    while status
      dirty = status.dirty?
      status = !status.parents.empty? ? status.parents[0] : nil
      clean_rev = status ? status.git_rev : remote_rev if dirty
    end
    clean_rev
  end

  # get latest remote revision
  def get_latest_remote_revision(session, rev)
    # remote revs are where we stop traversal
    non_remote_revs = {}
    session.all_reachable_non_remote_revs(rev).each do |r|
      non_remote_revs[r] = true
    end
    # make sure we deal only with sha1s
    rev = session.rev_sha1(rev)
    start_rev = rev;
    while rev && non_remote_revs[rev]
      rev = get_parent(session, rev)
    end
    rev
  end

  # check whether revision has a given ancestor
  def has_ancestor?(session, rev, ancestor)
    # make sure we deal only with sha1s
    rev = session.rev_sha1(rev)
    return rev == ancestor || session.is_ancestor?(ancestor, rev)
  end

  # get first parent node
  def get_parent(session, rev)
    parents = session.parent_revs(rev)
    !parents.empty? ? parents.first : nil
  end

  #create default commit message from array of changed modules
  def get_commit_message(changed_modules)
    StringIO.open do |s|
      s.puts "rim sync."
      s.puts
      changed_modules.each do |m|
        s.puts m.local_path
      end
      s.string
    end
  end

end

end


require_relative '../psutil_error'
require_relative '../libc'
require_relative 'helper'
require_relative 'system'
require_relative 'libposixpsutil'

module PosixPsutil

# taken from /fs/proc/array.c
PROC_STATUSES = {
    "R" => COMMON::STATUS_RUNNING,
    "S" => COMMON::STATUS_SLEEPING,
    "D" => COMMON::STATUS_DISK_SLEEP,
    "T" => COMMON::STATUS_STOPPED,
    "t" => COMMON::STATUS_TRACING_STOP,
    "Z" => COMMON::STATUS_ZOMBIE,
    "X" => COMMON::STATUS_DEAD,
    "x" => COMMON::STATUS_DEAD,
    "K" => COMMON::STATUS_WAKE_KILL,
    "W" => COMMON::STATUS_WAKING
}

# ioprio_* constants http://linux.die.net/man/2/ioprio_get
IOPRIO_CLASS_NONE = 0
IOPRIO_CLASS_RT = 1
IOPRIO_CLASS_BE = 2
IOPRIO_CLASS_IDLE = 3

IOPRIO_CLASS = {
  :none => IOPRIO_CLASS_NONE,
  :rt => IOPRIO_CLASS_RT,
  :be => IOPRIO_CLASS_BE,
  :idle => IOPRIO_CLASS_IDLE
}

# RLIMIT_ constants bits/resource.h
RLIMIT_CPU = 0
RLIMIT_FSIZE = 1
RLIMIT_DATA = 2
RLIMIT_STACK = 3
RLIMIT_CORE = 4
RLIMIT_RSS = 5
RLIMIT_NPROC = 6
RLIMIT_NOFILE = 7
RLIMIT_MEMLOCK = 8
RLIMIT_AS = 9
RLIMIT_LOCKS = 10
RLIMIT_SIGPENDING = 11
RLIMIT_MSGQUEUE = 12
RLIMIT_NICE = 13
RLIMIT_RTPRIO = 14
RLIMIT_RTTIME = 15
RLIMIT_NLIMITS = 16

RLIMIT = {
  :cpu => RLIMIT_CPU,
  :fsize => RLIMIT_FSIZE,
  :data => RLIMIT_DATA,
  :stack => RLIMIT_STACK,
  :core => RLIMIT_CORE,
  :rss => RLIMIT_RSS,
  :nproc => RLIMIT_NPROC,
  :nofile => RLIMIT_NOFILE,
  :memlock => RLIMIT_MEMLOCK,
  :as => RLIMIT_AS,
  :locks => RLIMIT_LOCKS,
  :sigpending => RLIMIT_SIGPENDING,
  :msgqueue => RLIMIT_MSGQUEUE,
  :nice => RLIMIT_NICE,
  :rtprio => RLIMIT_RTPRIO,
  :rttime => RLIMIT_RTTIME,
  :nlimits => RLIMIT_NLIMITS
}

PAGE_SIZE = LibPosixPsutil::PAGE_SIZE
CLOCK_TICKS = LibPosixPsutil::CLOCK_TICKS

class PlatformSpecificProcess < PsutilHelper::Processes
  include PsutilHelper
  include NetworkConstance

  # for class scope variable which should be memorized
  @@terminal_map = {}
  @@boot_time = nil

  def initialize(pid)
    raise ArgumentError.new("pid is illegal!") if pid.nil? || pid <= 0
    @pid = pid
    @name = nil
  end

  # Decorator which translates Errno::ENOENT, Errno::ESRCH into AccessDenied;
  # Errno::EPERM, Errno::EACCES into NoSuchProcess.
  def self.wrap_exceptions(method)
    old_method = instance_method(method)
    define_method method do |*args, &block|
      begin
        old_method.bind(self).call(*args, &block)
      rescue Errno::ENOENT, Errno::ESRCH
        raise NoSuchProcess.new(pid:@pid, name:@name)
      rescue Errno::EPERM, Errno::EACCES
        raise AccessDenied.new(pid: @pid, name:@name)
      end
    end
  end

  def cmdline
    IO.read("/proc/#{@pid}/cmdline").split("\x00").delete_if {|x| !x}
  end

  def cpu_times
    st = IO.read("/proc/#{@pid}/stat").strip
    # ignore the first two values ("pid (exe)")
    st = st[/\) (.*$)/, 1]
    values = st.split(' ')
    utime = values[11].to_f / CLOCK_TICKS
    stime = values[12].to_f / CLOCK_TICKS
    OpenStruct.new(user:utime, system:stime)
  end

  def create_time
    if @create_time.nil?
      st = IO.read("/proc/#{@pid}/stat").strip
      st = st[/\) (.*$)/, 1]
      values = st.split(' ')
      @@boot_time = PsutilHelper::boot_time() if @@boot_time.nil?
      @create_time =  @@boot_time + values[19].to_f / CLOCK_TICKS
    end
    @create_time
  end

  def time_used
    (Time.now.to_f - create_time).round 2
  end

  def connections(interface = :inet)
    connection = Connection.new
    unless connection.tmap.key?(interface)
      raise ArgumentError.new("Unknown connection kind #{interface}")
    end
    inodes = Processes.get_proc_inodes(@pid)
    return [] if inodes.empty?

    ret = []
    connection.tmap[interface].each do |kind|
      f, family, type = kind
      if [AF_INET, AF_INET6].include?(family)
        ret.concat(connection.process_inet("/proc/net/#{f}",
                                           family, type, inodes, @pid))
      else
        ret.concat(connection.process_unix("/proc/net/#{f}",
                                           family, inodes, @pid))
      end
    end
    ret.each { |conn| conn.delete_field(:pid) }
  end

  def cwd
    File.readlink("/proc/#{@pid}/cwd").sub("\x00", "")
  end

  def cpu_affinity
    FFI::MemoryPointer.new(:pointer, 1) do |p|
      # p is an double pointer,
      # we will malloc enough space for the pointer it holds in `get_cpu_affinity`
      cpu_count = FFI::MemoryPointer.new(:int, 1)
      status = LibPosixPsutil.get_cpu_affinity(@pid, p, cpu_count)
      case status
      when 0
        affinity = p.get_pointer(0).get_array_of_long(0, cpu_count.read_int)
        LibC.free p.get_pointer(0)
        return affinity
      when -1 # got nothing
        return []
      else # error occured
        raise SystemCallError.new('in get_cpu_affinity', status)
      end
    end
  end

  def cpu_affinity=(cpus)
    total_cpus = CPU.cpu_count
    seq_len = cpus.size > total_cpus ? total_cpus : cpus.size
    begin
      FFI::MemoryPointer.new(:long, seq_len) do |affinity|
        affinity.write_array_of_long(cpus[0...seq_len])
        status = LibPosixPsutil.set_cpu_affinity(@pid, affinity, seq_len)
        raise SystemCallError.new('in set_cpu_affinity', status) if status != 0
      end
    # The  affinity bit mask mask contains no processors that are currently
    # physically on the system and permitted to the thread according to any
    # restrictions that may be imposed by the "cpuset" mechanism
    # described in cpuset(7)
    rescue Errno::EINVAL
      cpus.each do |cpu|
        if cpu < 0 || cpu >= total_cpus
          raise ArgumentError.new
            "invalid CPU #{cpu} (choose between 0 to #{total_cpus})"
        end
      end
      raise
    end
  end

  def exe
    begin
      # readlink() might return paths containing null bytes ('\x00').
      # Certain names have ' (deleted)' appended. Usually this is
      # bogus as the file actually exists. Either way that's not
      # important as we don't want to discriminate executables which
      # have been deleted.
      exe = File.readlink("/proc/#{@pid}/exe").split("\x00")[0]
      if exe.end_with?(' (deleted)') && !File.exists(exe)
        exe = exe[0...-10]
      end
      exe
    rescue Errno::ENOENT, Errno::ESRCH
      # no such file error; might be raised also if the
      # path actually exists for system processes with
      # low pids (about 0-20)
      if File.exists? "/proc/#{@pid}"
        return ""
      else
        raise NoSuchProcess.new(pid:@pid)
      end
      raise
    rescue Errno::EPERM, Errno::EACCES
      raise AccessDenied.new(pid:@pid, name:name())
    end
  end

  def gids
    IO.readlines("/proc/#{@pid}/status").each do |line|
      if line.start_with?("Gid:")
        _, real, effective, saved, _ = line.split
        return OpenStruct.new(real: real.to_i, effective: effective.to_i,
                             saved: saved.to_i)
      end
    end
    # impossible to reach here
    raise NotImplementedError.new('line not found')
  end

  def io_counters
    rcount = wcount = rbytes = wbytes = nil
    IO.readlines("/proc/#{@pid}/io").each do |line|
      if !rcount && line.start_with?("syscr")
        rcount = line.split[1]
      elsif !wcount && line.start_with?("syscw")
        wcount = line.split[1]
      elsif !rbytes && line.start_with?("read_bytes")
        rbytes = line.split[1]
      elsif !wbytes && line.start_with?("write_bytes")
        wbytes = line.split[1]
      end
    end
    [rcount, wcount, rbytes, wbytes].each do |item|
      raise NotImplementedError.new(
        "couldn't read all necessary info from /proc/#{@pid}/io") unless item
    end
    OpenStruct.new(rcount: rcount, wcount: wcount,
                   rbytes: rbytes, wbytes: wbytes)
  end

  def ionice
    ioclass = FFI::MemoryPointer.new(:int, 1)
    value = FFI::MemoryPointer.new(:int, 1)
    status = LibPosixPsutil.get_ionice(@pid, ioclass, value)
    raise SystemCallError.new('in get_ionice', status) if status != 0
    OpenStruct.new(ioclass: ioclass.read_int, value: value.read_int)
  end

  def set_ionice(ioclass, value)
    ioclass ||= IOPRIO_CLASS_NONE

    if ioclass.is_a?(Symbol)
      unless IOPRIO_CLASS.key? ioclass
        symbols = ":" + IOPRIO_CLASS.keys.join(', :')
        msg = "Unsupported symbol :#{ioclass}, only support #{symbols}"
        raise ArgumentError.new(msg)
      end
      ioclass = IOPRIO_CLASS[ioclass]
    end

    case ioclass
    when IOPRIO_CLASS_NONE
      raise ArgumentError.new("can't specify value with #{ioclass}") if value
      value = 0
    when IOPRIO_CLASS_RT, IOPRIO_CLASS_BE
      value = 4 if value.nil?
    when IOPRIO_CLASS_IDLE
      raise ArgumentError.new("can't specify value with #{ioclass}") if value
      value = 0
    else
      msg = "ioclass argument expected is an integer between 0 and 3, got #{ioclass}"
      raise ArgumentError.new(msg)
    end
    if value < 0 || value > 7 || value.to_i != value
      msg = "value argument expected is an integer between 0 and 7, got #{value}"
      raise ArgumentError.new(msg)
    end
    status = LibPosixPsutil.set_ionice(@pid, ioclass, value)
    raise SystemCallError.new('in set_ionice', status) if status != 0
  end

  def memory_info
    vms, rss = File.new("/proc/#{@pid}/statm").readline.split[0...2]
    OpenStruct.new(vms: vms.to_i * PAGE_SIZE,
                   rss: rss.to_i * PAGE_SIZE)
  end

  #  ============================================================
  # | FIELD  | DESCRIPTION                         | AKA  | TOP  |
  #  ============================================================
  # | rss    | resident set size                   |      | RES  |
  # | vms    | total program size                  | size | VIRT |
  # | shared | shared pages (from shared mappings) |      | SHR  |
  # | text   | text ('code')                       | trs  | CODE |
  # | lib    | library (unused in Linux 2.6)       | lrs  |      |
  # | data   | data + stack                        | drs  | DATA |
  # | dirty  | dirty pages (unused in Linux 2.6)   | dt   |      |
  #  ============================================================
  def memory_info_ex
    info = File.new("/proc/#{@pid}/statm").readline.split[0...7]
    vms, rss, shared, text, lib, data, dirty = info.map {|i| i.to_i * PAGE_SIZE}
    OpenStruct.new(vms: vms, rss: rss, shared: shared, text: text, lib: lib,
                  data: data, dirty: dirty)
  end

  if File.exists?("/proc/#{Process.pid}/smaps")

    # Return process's mapped memory regions as a list of nameduples.
    # Fields are explained in 'man proc'; here is an updated (Apr 2012)
    # version: http://goo.gl/fmebo
    def memory_maps
      lines = IO.readlines("/proc/#{@pid}/smaps")
      return [] if lines == [] # smaps file can be empty

      blocks = []
      # the header of each section
      first_line = lines[0]

      region_data = {}
      lines.each do |line|
        fields = line.split(/\s+/, 6)
        if fields[0].end_with?(':')
          value = fields[1].to_i
          # if fields[1] can be convert to number
          if value == 0 && fields[1] != '0'
            next if fields[0].start_with?('VmFlags:')
            raise ValueError.new("don't know how to interpret line #{line}")
          end
          region_data[fields[0]] = value * 1024
        else
          # new block section
          blocks.push([first_line, Marshal.load(Marshal.dump(region_data))])
          region_data.clear
          first_line = line
        end
      end
      # deal with final section
      blocks.push([first_line, region_data])

      maps = []
      blocks.each do |header, data|
        hfields = header.split(/\s+/, 6)
        addr, perms, _, _, _, path = hfields
        # hfields may only have 5 parts
        (path.nil? || path == '')? path = '[anon]' : path.strip!
        maps.push([
          addr,
          perms,
          path,
          data.fetch('Rss:', 0),
          data.fetch('Size:', 0),
          data.fetch('Pss:', 0),
          data.fetch('Shared_Clean:', 0),
          data.fetch('Shared_Dirty:', 0),
          data.fetch('Private_Clean:', 0),
          data.fetch('Private_Dirty:', 0),
          data.fetch('Referenced:', 0),
          data.fetch('Anonymous:', 0),
          data.fetch('Swap:', 0)
        ])
      end
      maps
    end

  else
    def memory_maps
      msg = <<-EOF.gsub(/(?:^\s+\||\n)/, '')
        |couldn't find /proc/#{@pid}/smaps; kernel < 2.6.14 or
        | CONFIG_MMU kernel configuration option is not enabled
      EOF
      raise NotImplementedError.new(msg)
    end
  end

  def name
    @name = File.new("/proc/#{@pid}/stat").readline.
      split(' ')[1][/\((.+?)\)/, 1] unless @name
    @name
  end

  def nice
    # A value in the range 19 (low priority) to -20 (high priority).
    # Use `man proc` to see the difference between priority and nice.
    IO.read("/proc/#{@pid}/stat").split[18].to_i
  end

  def nice=(value)
    if value.to_i != value || value < -20 || value > 19
      raise ArgumentError.new("nice expected is an integer between -20 and 19, got #{value}")
    end
    status = LibPosixPsutil.set_priority(@pid, value)
    raise SystemCallError.new("in set_priority", status) if status != 0
  end

  def num_ctx_switches
    vol = nonvol = nil
    IO.readlines("/proc/#{@pid}/status").each do |line|
      if line.start_with?("voluntary_ctxt_switches")
        vol = line.split[1].to_i
      elsif line.start_with?("nonvoluntary_ctxt_switches")
        nonvol = line.split[1].to_i
      end

      if vol && nonvol
        return OpenStruct.new(voluntary: vol, involuntary: nonvol)
      end
    end
    msg = <<-EOF.gsub(/(?:^\s+\||\n)/, '')
      |'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'
      | fields were not found in /proc/#{@pid}/status; the kernel is
      |probably older than 2.6.23
    EOF
    raise NotImplementedError.new(msg)
  end

  def num_fds
    Dir.entries("/proc/#{@pid}/fd").size - 2 # ignore '.' and '..'
  end

  def num_threads
    IO.readlines("/proc/#{@pid}/status").each do |line|
      return line.split[1].to_i if line.start_with?("Threads:")
    end
    raise NotImplementedError.new("line not found")
  end

  def open_files
    retlist = []
    Dir.entries("/proc/#{@pid}/fd").each do |fd|
      next if fd == '.' || fd == '..'
      file = "/proc/#{@pid}/fd/#{fd}"
      if File.symlink?(file)
        begin
          file = File.readlink(file)
        rescue Errno::ENOENT, Errno::ESRCH
          # raise NSP if the process disappeared on us
          next if File.exist?("/proc/#{@pid}")
        end
        # If file is not an absolute path there's no way
        # to tell whether it's a regular file or not,
        # so we skip it. A regular file is always supposed
        # to be absolutized though.
        if file.start_with?('/') && File.file?(file) # regular file only
          retlist.push(OpenStruct.new(path: file, fd: fd.to_i))
        end
      end
    end

    retlist
  end

  # data in pmmap_ext is an Array
  def pmmap_ext(data)
    pmmap_ext = ['addr', 'perms', 'path', 'rss', 'size', 'pss',
                 'shared_clean', 'shared_dirty', 'private_clean',
                 'private_dirty', 'referenced', 'anonymous', 'swap']
    os_list = []
    data.each do |datum|
      os = OpenStruct.new
      pmmap_ext.each_index {|i| os[pmmap_ext[i]] = datum[i]}
      os_list.push(os)
    end
    os_list
  end

  # data in pmmap_grouped is a Hash
  def pmmap_grouped(data)
    pmmap_grouped = ['rss', 'size', 'pss', 'shared_clean',
                     'shared_dirty', 'private_clean', 'private_dirty',
                     'referenced', 'anonymous', 'swap']
    os_list = []
    data.each do |k, v|
      os = OpenStruct.new
      os.path = k
      pmmap_grouped.each_index {|i| os[pmmap_grouped[i]] = v[i]}
      os_list.push(os)
    end
    os_list
  end

  def ppid
    IO.readlines("/proc/#{@pid}/status").each do |line|
      return line.split[1].to_i if line.start_with?("PPid:")
    end
    raise NotImplementedError.new("line not found")
  end

  def rlimit(resource, limits=nil)
    if resource.is_a?(Symbol)
      unless RLIMIT.key? resource
        symbols = ":" + RLIMIT.keys.join(', :')
        msg = "Unsupported symbol :#{resource}, only support #{symbols}"
        raise ArgumentError.new(msg)
      end
      resource = RLIMIT[resource]
    end

    # if pid is 0 prlimit() applies to the calling process and
    # we don't want that
    if @pid == 0
      raise ArgumentError.new("can't use prlimit() against PID 0 process")
    end
    if limits.nil?
      # get
      # On 64-bit system, long long is the same as long, can we replace it with long?
      soft = FFI::MemoryPointer.new(:long_long, 1)
      hard = FFI::MemoryPointer.new(:long_long, 1)
      status = LibPosixPsutil.get_rlimit(@pid, resource, soft, hard)
      raise SystemCallError.new("in get_rlimit", status) if status != 0
      {:soft => soft.read_long_long, :hard => hard.read_long_long}
    else
      # set
      if limits.is_a?(Hash) && limits.key?(:soft) && limits.key?(:hard)
        status = LibPosixPsutil.set_rlimit(@pid, resource,
                                           limits[:soft], limits[:hard])
        raise SystemCallError.new("in set_rlimit", status) if status != 0
        limits
      else
        msg = "second argument must be a {:soft, :hard} Hash"
        raise ArgumentError.new(msg)
      end
    end

  end

  def status
    PROC_STATUSES.default = '?'
    IO.readlines("/proc/#{@pid}/status").each do |line|
      # PROC_STATUSES will return '?' if given key is not existed
      return PROC_STATUSES[line.split[1]] if line.start_with?('State:')
    end
  end

  def terminal
    tmap = get_terminal_map
    tty_nr = IO.read("/proc/#{@pid}/stat").split(' ')[6].to_i
    tmap[tty_nr] # if tty_nr is not a key of tmap, retun nil
  end

  def threads
    thread_ids = Dir.entries("/proc/#{@pid}/task").sort - ['.', '..']
    retlist = []
    thread_ids.each do |id|
      begin
        st = IO.read("/proc/#{@pid}/task/#{id}/stat").strip
        st = st[/\) (.*$)/, 1]
        values = st.split(' ')
        utime = values[11].to_f / CLOCK_TICKS
        stime = values[12].to_f / CLOCK_TICKS
        retlist.push(OpenStruct.new(thread_id: id,
                                    user_time: utime, system_time: stime))
      rescue Errno::ENOENT
        # check if process disappeared on us
        # may raise NoSuchProcess
        raise NoSuchProcess.new(pid: @pid) unless File.exists?("/proc/#{@pid}")
        next
      end
    end
    retlist
  end

  def uids
    IO.readlines("/proc/#{@pid}/status").each do |line|
      if line.start_with?("Uid:")
        _, real, effective, saved, _ = line.split
        return OpenStruct.new(real: real.to_i, effective: effective.to_i,
                             saved: saved.to_i)
      end
    end
    # impossible to reach here
    raise NotImplementedError.new('line not found')
  end

  # assert the process is existed when specific method called
  def self.assert_process_exists(method)
    old_method = instance_method(method)
    define_method method do |*args, &block|
      file = "/proc/#{@pid}"
      # raise NSP if the process disappeared on us
      raise NoSuchProcess.new(pid: @pid) unless File.exists?(file)
      old_method.bind(self).call(*args, &block)
    end
  end
  assert_process_exists :connections

  def self.wrap_action_except_for(wrapper, methods)
    methods = self.instance_methods(false) - methods
    wrapper = method(wrapper)
    methods.each do |method|
      wrapper.call(method)
    end
  end

  wrap_action_except_for :wrap_exceptions, [:exe]

  private

  def get_terminal_map
    if @@terminal_map.empty?
      ret = {}
      list = Dir.glob('/dev/tty*') + Dir.glob('/dev/pts/*')
      list.each do |name|
        begin
          ret[File.stat(name).rdev] = name unless ret.key?(name)
        rescue Errno::ENOENT
          next
        end
      end
      @@terminal_map = ret
    end
    @@terminal_map
  end

end
end


require 'digest'
require 'pathname'
require 'rim/rim_info'
require 'rim/file_helper'

module RIM

# Module dirty state checker.
#
# Provides means to mark modules as "clean" and check if they become "dirty" later on.
#
# Once a module has been marked as being clean, it will become dirty if
# any of the following is true:
#
# * Number of contained files has changed
# * File names or location have changed
# * File contents have changed
# * One of the RIM info attributes listed in ChecksumAttributes has changed
# * The RIM info file is missing or became invalid
#
# Ignored files are not considered by this check.
#
class DirtyCheck

  # raised when there is not enough info for checksum calculation
  class MissingInfoException < Exception
  end

  # attributes to be included into checksum calculation
  # checksum calculation fails if those attributes are not present
  ChecksumAttributes = [
    :remote_url,
    :revision_sha1
  ]

  # rim info must exist in dir and must be valid
  # it also must contain attributes listed in ChecksumAttributes
  # otherwise a MissingInfoException is raised
  def self.mark_clean(dir)
    mi = RimInfo.from_dir(dir)
    cs = self.new.calc_checksum(mi, dir)
    raise MissingInfoException unless cs
    mi.checksum = cs
    mi.to_dir(dir)
  end

  def self.dirty?(dir)
    mi = RimInfo.from_dir(dir)
    # always fails if there is no checksum
    mi.dirty? || !mi.checksum || mi.checksum != self.new.calc_checksum(mi, dir)
  end

  # returns nil if checksum can't be calculated due to missing info
  def calc_checksum(mi, dir)
    if check_required_attributes(mi)
      sha1 = Digest::SHA1.new
      # all files and directories within dir
      files = FileHelper.find_matching_files(dir, false, "/**/*", File::FNM_DOTMATCH)
      # Dir.glob with FNM_DOTMATCH might return . and ..
      files.delete(".")
      files.delete("..")
      # ignore the info file itself
      files.delete(RimInfo::InfoFileName)
      # ignores defined by user
      files -= FileHelper.find_matching_files(dir, false, mi.ignores)
      # order of files makes a difference
      # sort to eliminate platform specific glob behavior
      files.sort!
      files.each do |fn|
        update_file(sha1, dir, fn)
      end
      ChecksumAttributes.each do |a|
        sha1.update(mi.send(a))
      end
      sha1.hexdigest
    else
      # can't calc checksum
      nil
    end
  end

  private

  def ignored_files(mi, dir)
    find_matching_files(dir, mi.ignores)
  end

  def update_file(sha1, dir, filename)
    fn = dir+"/"+filename
    if File.directory?(fn)
      if Dir.entries(fn).size > 2 # 2 for . and ..
        # add directory names but only for non-empty directories
        sha1.update(filename)
      end
    else
      # file name
      sha1.update(filename)
      # file contents
      File.open(fn, "rb") do |f|
        sha1.update(f.read.gsub("\r\n", "\n"))
      end
    end
  end

  def check_required_attributes(mi)
    ChecksumAttributes.all?{|a| mi.send(a)}
  end

end

end


require 'tmpdir'
require 'logger'

module RIM

# raised when there is an error emitted by git call
class GitException < Exception
  attr_reader :cmd, :exitstatus, :out
  def initialize(cmd, exitstatus, out)
    super("git \"#{cmd}\" => #{exitstatus}\n#{out}")
    @cmd = cmd
    @exitstatus = exitstatus
    @out = out
  end
end

class GitSession

  attr_reader :execute_dir

  def initialize(logger, execute_dir, arg = {})
    @execute_dir = execute_dir
    if arg.is_a?(Hash)
      @work_dir = arg.has_key?(:work_dir) ? arg[:work_dir] : ""
      @git_dir = arg.has_key?(:git_dir) ? arg[:git_dir] : ""
    end
    @logger = logger
  end

  def self.logger=(logger)
    @logger = logger
  end

  def self.open(execute_dir, options = {})
    log = @logger || Logger.new($stdout)
    self.new(log, execute_dir, options)
  end

  def self.next_invocation_id
    @invocation_id ||= 0
    @invocation_id += 1
  end

  class Status
    attr_accessor :lines

    # X          Y     Meaning
    # -------------------------------------------------
    #           [MD]   not updated
    # M        [ MD]   updated in index
    # A        [ MD]   added to index
    # D         [ M]   deleted from index
    # R        [ MD]   renamed in index
    # C        [ MD]   copied in index
    # [MARC]           index and work tree matches
    # [ MARC]     M    work tree changed since index
    # [ MARC]     D    deleted in work tree
    # -------------------------------------------------
    # D           D    unmerged, both deleted
    # A           U    unmerged, added by us
    # U           D    unmerged, deleted by them
    # U           A    unmerged, added by them
    # D           U    unmerged, deleted by us
    # A           A    unmerged, both added
    # U           U    unmerged, both modified
    # -------------------------------------------------
    # ?           ?    untracked
    # !           !    ignored
    # -------------------------------------------------
    class Line
      attr_accessor :istat, :wstat, :file, :rename

      def untracked?
        istat == "?" && wstat == "?"
      end

      def ignored?
        istat == "!" && wstat == "!"
      end

      def unmerged?
        istat == "D" && wstat == "D" ||
        istat == "A" && wstat == "A" ||
        istat == "U" ||
        wstat == "U"
      end

    end
  end

  # returns the current branch
  def current_branch
    out = execute "git branch"
    out.split("\n").each do |l|
      if !l.include?('(') && (l =~ /^\*\s+(\S+)/)
        return $1
      end
    end
    nil
  end

  # check whether branch exists
  def has_branch?(branch)
    execute("git show-ref refs/heads/#{branch}") do |b, e|
      return !e
    end
  end

  # check whether remote branch exists
  def has_remote_branch?(branch)
    out = execute("git ls-remote --heads")
    out.split("\n").each do |l|
      return true if l.split(/\s+/)[1] == "refs/heads/#{branch}"
    end
    false
  end

  # check whether remote repository is valid
  def has_valid_remote_repository?()
    execute("git ls-remote") do |b, e|
      return !e
    end
  end

  # checks whether the first (ancestor) revision is is ancestor of the second (child) revision
  def is_ancestor?(ancestor, child)
    execute("git merge-base --is-ancestor #{ancestor} #{child}") do |b, e|
      return !e
    end
  end

  # returns the parent commits of rev as SHA-1s
  # returns an empty array if there are no parents (e.g. orphan or initial)
  def parent_revs(rev)
    out = execute "git rev-list -n 1 --parents #{rev} --"
    out.strip.split[1..-1]
  end

  # returns the SHA-1 representation of rev
  def rev_sha1(rev)
    sha1 = nil
    execute "git rev-list -n 1 #{rev} --" do |out, e|
      sha1 = out.strip if !e
    end
    sha1
  end

  # returns some informations about a revision
  def rev_infos(rev, desired)
    info = {}
    desired.each_pair do |key, value|
      execute "git log -1 --format=#{value} #{rev} --" do |out, e|
        info[key] = out.strip if !e
      end
    end
    info
  end

  # returns the SHA-1 representations of the heads of all remote branches
  def remote_branch_revs
    out = execute "git show-ref"
    out.split("\n").collect { |l|
      if l =~ /refs\/remotes\//
        l.split[0]
      else
        nil
      end
    }.compact
  end

  # all commits reachable from rev which are not ancestors of remote branches
  def all_reachable_non_remote_revs(rev)
    out = execute "git rev-list #{rev} --not --remotes --"
    out.split("\n")
  end

  # export file contents of rev to dir
  # if +paths+ is given and non-empty, checks out only those parts of the filesystem tree
  # does not remove any files from dir which existed before
  def export_rev(rev, dir, paths=[])
    paths = paths.dup
    loop do
      path_args = ""
      # max command line length on Windows XP and higher is 8191
      # consider the following extra characters which will be added:
      # up to 3 paths in execute, 1 path for tar, max path length 260 = 1040
      # plus some "glue" characters, plus the last path item with 260 max;
      # use 6000 to be on the safe side
      while !paths.empty? && path_args.size < 6000
        path_args << " "
        path_args << paths.shift
      end
      execute "git archive --format tar #{rev} #{path_args} | tar -C #{dir} -xf -"
      break if paths.empty?
    end
  end

  # checks out rev to a temporary directory and yields this directory to the given block
  # if +paths+ is given and non-empty, checks out only those parts of the filesystem tree
  # returns the value returned by the block
  def within_exported_rev(rev, paths=[])
    Dir.mktmpdir("rim") do |d|
      d = Dir.glob(d)[0]
      c = File.join(d, "content")
      FileUtils.mkdir(c)
      export_rev(rev, c, paths)
      # return contents of yielded block
      # mktmpdir returns value return by our block
      yield c
      FileUtils.rm_rf(c)
      # retry to delete if it hasn't been deleted yet
      # this could be due to Windows keeping the files locked for some time
      # this is especially a problem if the machine is at its limits
      retries = 600
      while File.exist?(c) && retries > 0
        sleep(0.1)
        FileUtils.rm_rf(c)
        retries -= 1
      end
      if File.exist?(c)
        @logger.warn "could not delete temp dir: #{c}"
      end
    end
  end

  def uncommited_changes?
    # either no status lines are all of them due to ignored items
    !status.lines.all?{|l| l.ignored?}
  end

  def current_branch_name
    out = execute "git rev-parse --abbrev-ref HEAD"
    out.strip
  end

  ChangedFile = Struct.new(:path, :kind)

  # returns a list of all files which changed in commit +rev+
  # together with the kind of the change (:modified, :deleted, :added)
  #
  # if +from_rev+ is given, lists changes between +from_rev and +rev+
  # with one argument only, no changes will be returned for merge commits
  # use the two argument variant for merge commits and decide for one parent
  def changed_files(rev, rev_from=nil)
    out = execute "git diff-tree -r --no-commit-id #{rev_from} #{rev}"
    out.split("\n").collect do |l|
      cols = l.split
      path = cols[5]
      kind = case cols[4]
        when "M"
          :modified
        when "A"
          :added
        when "D"
          :deleted
        else
          nil
        end
      ChangedFile.new(path, kind)
    end
  end

  # 3 most significant numbers of git version of nil if it can't be determined
  def git_version
    out = execute("git --version")
    if out =~ /^git version (\d+\.\d+\.\d+)/
      $1
    else
      nil
    end
  end

  def status(dir = nil)
    # -s            short format
    # --ignored     show ignored
    out = execute "git status -s --ignored #{dir}"
    parse_status(out)
  end

  def execute(cmd)
    raise "git command has to start with 'git'" unless cmd.start_with? "git "
    cmd.slice!("git ")
    # remove any newlines as they will cause the command line to end prematurely
    cmd.gsub!("\n", "")
    options = ((!@execute_dir || @execute_dir == ".") ? "" : " -C #{@execute_dir}") \
        + (@work_dir.empty? ? "" : " --work-tree=#{File.expand_path(@work_dir)}") \
        + (@git_dir.empty? ? "" : " --git-dir=#{File.expand_path(@git_dir)}")
    cmd = "git#{options} #{cmd} 2>&1"

    out = `#{cmd}`
    # make sure we don't run into any encoding misinterpretation issues
    out.force_encoding("binary")

    exitstatus = $?.exitstatus

    invid = self.class.next_invocation_id.to_s.ljust(4)
    @logger.debug "git##{invid} \"#{cmd}\" => #{exitstatus}"

    out.split(/\r?\n/).each do |ol|
      @logger.debug "git##{invid} out : #{ol}"
    end

    exception = exitstatus != 0 ? GitException.new(cmd, exitstatus, out) : nil

    if block_given?
      yield out, exception
    elsif exception
      raise exception
    end

    out
  end

  private

  def parse_status(out)
    status = Status.new
    status.lines = []
    out.split(/\r?\n/).each do |l|
      sl = Status::Line.new
      sl.istat, sl.wstat = l[0], l[1]
      f1, f2 = l[3..-1].split(" -> ")
      f1 = unquote(f1)
      f2 = unquote(f2) if f2
      sl.file = f1
      sl.rename = f2
      status.lines << sl
    end
    status
  end

  def unquote(s)
    if s[0] == "\"" && s[-1] == "\""
      s = s[1..-2]
      s.gsub!("\\\\", "\\")
      s.gsub!("\\\"", "\"")
      s.gsub!("\\t", "\t")
      s.gsub!("\\r", "\r")
      s.gsub!("\\n", "\n")
    end
    s
  end

end

def RIM.git_session(execute_dir, options = {})
  s = GitSession.open(execute_dir, options)
  yield s
end

end


require 'rim/command_helper'
require 'rim/upload_module_helper'

module RIM

class UploadHelper < CommandHelper

  def initialize(workspace_root, review, logger, module_infos = nil)
    @module_helpers = []
    @review = review
    super(workspace_root, logger, module_infos)
  end

  # upload all module changes into corresponding remote repositories
  def upload
    # get the name of the current workspace branch
    RIM::git_session(@ws_root) do |s|
      branch = s.current_branch
      if branch.nil?
        raise RimException.new("Not on a git branch.")
      elsif !branch.start_with?("rim/")
        begin
          sha1 = s.rev_sha1(branch)
          @logger.info("Uploading modules...")
          upload_modules(get_upload_revisions(s, sha1))
        ensure
          s.execute("git checkout -B #{branch}")
        end
      else
        raise RimException.new("The current git branch '#{branch}' is a rim integration branch. Please switch to a non rim branch to proceed.")
      end
    end
  end

  # called to add a module info
  def add_module_info(module_info)
    @module_helpers.push(UploadModuleHelper.new(@ws_root, module_info, @review, @logger))
  end

private
  # upload all modules
  def upload_modules(info)
    each_module_parallel("uploading", @module_helpers) do |m|
      m.upload(info.parent, info.sha1s)
    end
  end

  # get revisions to upload i.e. the revisions up to the last remote revision
  # the function returns the revisions in order of appearal i.e. the oldest first
  def get_upload_revisions(session, rev)
    # remote revs are where we stop traversal
    non_remote_revs = {}
    session.all_reachable_non_remote_revs(rev).each do |r|
      non_remote_revs[r] = true
    end
    revisions = []
    # make sure we deal only with sha1s
    rev = session.rev_sha1(rev)
    while rev && non_remote_revs[rev]
      revisions.push(rev)
      parents = session.parent_revs(rev)
      rev = parents.size > 0 ? parents.first : nil
    end
    Struct.new(:parent, :sha1s).new(rev, revisions.reverse!)
  end

end

end


require 'rim/processor'
require 'rim/rim_exception'
require 'rim/rim_info'
require 'rim/file_helper'
require 'rim/dirty_check'

module RIM

class ModuleHelper < Processor

  attr_reader :module_info

  def initialize(workspace_root, module_info, logger)
    super(workspace_root, logger)
    @module_info = module_info
    @remote_url = get_absolute_remote_url(@module_info.remote_url) if @module_info.remote_url
    @remote_path = remote_path(@module_info.remote_url) if @module_info.remote_url
    @logger = logger
  end

protected

  # fetch module +mod+ into the .rim folder
  # works both for initial fetch and updates
  def fetch_module
    FileUtils.mkdir_p git_path
    RIM::git_session(git_path) do |s|
      if !File.exist?(git_path + "/config")
        s.execute("git clone --mirror #{@remote_url} #{git_path}") do |out, e|
          raise RimException.new("Remote repository '#{@remote_url}' of module '#{@module_info.local_path}' not found.") if e
        end
      else
        s.execute("git remote update")
      end
    end
    git_path
  end

  # prepare empty folder: remove all files not on the ignore list and empty folders
  def prepare_empty_folder(local_path, ignores)
    ignores = FileHelper.find_matching_files(local_path, true, ignores)
    FileHelper.find_matching_files(local_path, true, "/**/*", File::FNM_DOTMATCH).each do |f|
      if File.file?(f) && !ignores.include?(f)
        FileUtils.rm(f)
      end
    end
    FileHelper.remove_empty_dirs(local_path)
    FileUtils.mkdir_p(local_path)
  end

  def git_path
    module_git_path(@remote_path)
  end

end

end


module VatsimTools

  class CallsignParser

    %w{tmpdir csv}.each { |lib| require lib }
    require_relative "data_downloader"
    require_relative "station"

    attributes = %w{role callsign gcmap_width gcmap_height}
    attributes.each {|attribute| attr_accessor attribute.to_sym }

    LOCAL_DATA = "#{Dir.tmpdir}/vatsim_data.txt"

    def initialize(callsign, args = nil)
      VatsimTools::DataDownloader.new
      # args.class == Hash ? @role = determine_role(args) : @role = "all"
      @callsign = callsign.upcase.split(',').each {|s| s.strip!}
      # @excluded = args[:exclude].upcase if args && args[:exclude]
      @gcmap_width = args[:gcmap_width] if args && args[:gcmap_width]
      @gcmap_height = args[:gcmap_height] if args && args[:gcmap_height]
    end

    # def determine_role(args)
    #   args[:atc] == false ? role = "pilot" : role = "all"
    #   args[:pilots] == false ? role = "atc" : role = role
    #   role = "all" if args[:pilots] == false && args[:atc] == false
    #   role
    # end

    def stations
      stations = []
      CSV.foreach(LOCAL_DATA, :col_sep =>':') do |row|
        callsign, origin, destination, client = row[0].to_s, row[11].to_s, row[13].to_s, row[3].to_s
        for cs in @callsign
          stations << row if callsign[0...cs.length] == cs # && client == "ATC") unless @role == "pilot"
          # stations << row if (origin[0...icao.length] == icao || destination[0...icao.length] == icao) unless @role == "atc"
        end
      end
      stations
    end

    def station_objects
      station_objects= []
      args = {}
      args[:gcmap_width] = @gcmap_width if @gcmap_width
      args[:gcmap_height] = @gcmap_height if @gcmap_height
      stations.each {|station| station_objects << VatsimTools::Station.new(station, args) }
      station_objects
    end

  end

end


module Cranky
  class Job

    attr_writer :defaults
    attr_reader :overrides, :return_attributes

    def initialize(target, overrides={})
      @defaults = {}
      @target = target
      @overrides = overrides
      @return_attributes = overrides.delete(:_return_attributes)
    end

    def attributes
      @attributes ||= @defaults.merge(@overrides)
    end

    def defaults=(defs)
      @attributes = nil   # Needs to be re-calculated
      @defaults = defs
    end

    # Returns the created item
    def item
      return @item if @item
      if @return_attributes
        @item = Hash.new
      else
        @item = get_constant(attributes[:class] ? attributes.delete(:class) : @target).new
      end
    end

    # Assign the value to the given attribute of the item
    def assign(attribute, value)
      unless value == :skip || attribute == :class
        if item.respond_to?("#{attribute}=")
          item.send("#{attribute}=", value)
        elsif item.is_a?(Hash)
          item[attribute] = value
        end
      end
    end

    def execute
      values = attributes.reject { |attribute, value| value.respond_to?("call") }
      blocks = attributes.select { |attribute, value| value.respond_to?("call") }

      values.each { |attribute, value| assign(attribute, value) }
      blocks.each { |attribute, value| assign(attribute, value.call(*(value.arity > 0 ? [item] : []))) }

      item
    end

    private

      # Nicked from here: http://gist.github.com/301173
      def get_constant(name_sym)
        return name_sym if name_sym.is_a? Class

        name = name_sym.to_s.split('_').collect {|s| s.capitalize }.join('')
        Object.const_defined?(name) ? Object.const_get(name) : Object.const_missing(name)
      end

  end
end


module Cranky
  class FactoryBase

    TRAIT_METHOD_REGEXP = /apply_trait_(\w+)_to_(\w+)/.freeze

    def initialize
      # Factory jobs can be nested, i.e. a factory method can itself invoke another factory method to
      # build a dependent object. In this case jobs the jobs are pushed into a pipeline and executed
      # in a last in first out order.
      @pipeline = []
      @n = 0
      @errors = []
    end

    def build(what, overrides={})
      item = crank_it(what, overrides)
      call_after_build(what, item)

      item
    end

    def create(what, overrides={})
      item = build(what, overrides)
      Array(item).each do |i|
        call_before_create(what, i)
        i.save && call_after_create(what, i)
      end
      item
    end

    def create!(what, overrides={})
      item = build(what, overrides)
      Array(item).each do |i|
        call_before_create(what, i)
        i.save!
        call_after_create(what, i)
      end
      item
    end

    # Reset the factory instance, clear all instance variables
    def reset
      self.instance_variables.each do |var|
        instance_variable_set(var, nil)
      end
      initialize
    end

    def attributes_for(what, attrs={})
      build(what, attrs.merge(:_return_attributes => true))
    end

    # Can be left in your tests as an alternative to build and to warn if your factory method
    # ever starts producing invalid instances
    def debug(*args)
      item = build(*args)
      invalid_item = Array(item).find(&:invalid?)
      if invalid_item
        if invalid_item.errors.respond_to?(:messages)
          errors = invalid_item.errors.messages
        else
          errors = invalid_item.errors
        end
        raise "Oops, the #{invalid_item.class} created by the Factory has the following errors: #{errors}"
      end
      item
    end

    # Same thing for create
    def debug!(*args)
      item = debug(*args)
      item.save
      item
    end

    # Look for errors in factories and (optionally) their traits.
    # Parameters:
    # factory_names - which factories to lint; omit for all factories
    # options:
    #   traits : true - to lint traits as well as factories
    def lint!(factory_names: nil, traits: false)
      factories_to_lint = Array(factory_names || self.factory_names)
      strategy = traits ? :factory_and_traits : :factory
      Linter.new(self, factories_to_lint, strategy).lint!
    end

    def factory_names
      public_methods(false).reject {|m| TRAIT_METHOD_REGEXP === m  }.sort
    end

    def traits_for(factory_name)
      regexp = /^apply_trait_(\w+)_to_#{factory_name}$/.freeze
      available_methods = private_methods(false) + public_methods(false)
      trait_methods = available_methods.select {|m| regexp === m  }
      trait_methods.map {|m| regexp.match(m)[1] }
    end

    def fetch(*args)
      if block_given?
        options.fetch(*args, &Proc.new)
      else
        options.fetch(*args)
      end
    end

    private

      def call_after_build(what, item)
        method_name = "after_build_#{what}"
        respond_to?(method_name, true) && send(method_name, item)
      end

      def call_before_create(what, item)
        method_name = "before_create_#{what}"
        respond_to?(method_name, true) && send(method_name, item)
      end

      def call_after_create(what, item)
        method_name = "after_create_#{what}"
        respond_to?(method_name, true) && send(method_name, item)
      end

      def apply_traits(what, item)
        Array(options[:traits]).each do |t|
          trait_method_name = "apply_trait_#{t}_to_#{what}"
          respond_to?(trait_method_name, true) || fail("Invalid trait '#{t}'! No method '#{trait_method_name}' is defined.")
          send(trait_method_name, item)
        end

        item
      end

      def n
        @n += 1
      end

      # Execute the requested factory method, crank out the target object!
      def crank_it(what, overrides)
        if what.to_s =~ /(.*)_attrs$/
          what = $1
          overrides = overrides.merge(:_return_attributes => true)
        end
        item = "TBD"
        new_job(what, overrides) do
          item = self.send(what)        # Invoke the factory method
          item = apply_traits(what, item)
        end
        item
      end

      # This method actually makes the required object instance, it gets called by the users factory
      # method, where the name 'define' makes more sense than it does here!
      def define(defaults={})
        current_job.defaults = defaults
        current_job.execute
      end

      def inherit(what, overrides={})
        overrides = overrides.merge(options)
        overrides = overrides.merge(:_return_attributes => true) if current_job.return_attributes
        build(what, overrides)
      end

      def current_job
        @pipeline.last
      end

      # Returns a hash containing any top-level overrides passed in when the current factory was invoked
      def options
        current_job.overrides
      end

      # Adds a new job to the pipeline then yields to the caller to execute it
      def new_job(what, overrides)
        @pipeline << Job.new(what, overrides)
        yield
        @pipeline.pop
      end

  end

  class Factory < FactoryBase
  end

end


# frozen_string_literal: true

module Pres
  module ViewDelegation
    def view_context
      @view_context
    end

    # Send missing methods to view_context first
    def method_missing(method, *args, &block)
      if view_context.respond_to?(method, true)
        view_context.send(method, *args, &block)
      else
        super
      end
    end

    def respond_to_missing?(method, _ = false)
      view_context.respond_to?(method, true) || super
    end
  end
end


# frozen_string_literal: true

module Pres
  module Presents
    private

    # Wrap an object or collection of objects with a presenter class.
    #
    # object    - A ruby object. May be nil.
    # presenter - A Presenter class (optional)
    # args      - optional hash / kwargs passed to the presenter
    #
    # An instance of a presenter class is created. The class is
    # one of the following:
    # - the `presenter` argument
    # - `Pres::Presenter` if object is nil
    # - object.presenter_class (if that method is defined)
    # - the default presenter class for the object
    #   (for example: User -> UserPresenter)
    #
    # Examples
    #
    # user = User.new
    # present(user, cool: true)
    # => #<UserPresenter object: #<User> ...>
    #
    # user = User.new
    # present(user) do |up|
    #   up.something
    # end
    # up => #<UserPresenter object: #<User> ...>
    #
    # user = User.new
    # present(user, presenter: NiceUserPresenter, cool: true)
    # => #<NiceUserPresenter object: #<User> ...>
    #
    # class User
    #   def presenter_class
    #     MyPresenter
    #   end
    # end
    # user = User.new
    # present(user)
    # => #<MyPresenter object: #<User> ...>
    #
    # present([user])
    # => [#<UserPresenter object: #<User> ...>]
    #
    # present(nil)
    # => [#<Presenter object: nil ...>]
    #
    # Returns a new Presenter object or array of new Presenter objects
    # Yields a new Presenter object if a block is given
    def present(object, presenter: nil, **args)
      if object.respond_to?(:to_ary)
        object.map { |item| present(item, presenter: presenter, **args) }
      else
        presenter ||= presenter_klass(object)
        wrapper = presenter.new(object, view_context, **args)
        block_given? ? yield(wrapper) : wrapper
      end
    end

    def presenter_klass(object)
      if object.nil?
        Presenter
      elsif object.respond_to?(:presenter_class)
        object.presenter_class
      else
        Object.const_get("#{object.class.name}Presenter")
      end
    end
  end
end


module HasAccounts
  module Model
    extend ActiveSupport::Concern

    included do
      class_attribute :direct_account

      has_many :bookings, as: :reference, dependent: :nullify, inverse_of: :reference do
        # TODO: duplicated in Booking (without parameter)
        def direct_balance(value_date = nil, direct_account = nil)
          return BigDecimal.new('0') unless proxy_association.owner.direct_account

          direct_account ||= proxy_association.owner.direct_account
          balance = BigDecimal.new('0')

          # Scope by value_date
          if value_date.is_a?(Range) || value_date.is_a?(Array)
            direct_bookings = where('date(value_date) BETWEEN :from AND :to', from: value_date.first, to: value_date.last)
          elsif value_date
            direct_bookings = where('date(value_date) <= ?', value_date) if value_date
          else
            direct_bookings = scoped
          end

          # Accumulate
          for booking in direct_bookings.all
            balance += booking.accounted_amount(direct_account)
          end

          balance
        end
      end
    end

    # Delegate to class
    def direct_account
      self.class.direct_account
    end

    # Build booking
    def build_booking(params = {}, template_code = nil)
      template_code ||= self.class.to_s.underscore + ':invoice'
      booking_template = BookingTemplate.find_by_code(template_code)

      # Prepare booking parameters
      booking_params = { reference: self }
      booking_params.merge!(params)

      # Build and assign booking
      booking = booking_template.build_booking(booking_params)
      bookings << booking

      booking
    end

    def balance(value_date = nil, direct_account = nil)
      bookings.direct_balance(value_date, direct_account)
    end
  end
end


require "gem_publisher/git_remote"
require "gem_publisher/builder"
require "gem_publisher/pusher"
require "rubygems/specification"

module GemPublisher
  class Publisher
    attr_accessor :git_remote, :builder, :pusher
    attr_reader :version

    # Supported options:
    #   :tag_prefix - use a custom prefix for Git tags (defaults to 'v')
    def initialize(gemspec, options = {})
      @gemspec = gemspec
      @tag_prefix = options[:tag_prefix] || 'v'

      @version = eval(File.read(gemspec), TOPLEVEL_BINDING).version.to_s

      @git_remote = GitRemote.new
      @builder    = Builder.new
      @pusher     = Pusher.new
    end

    # Publish the gem if its version has changed since the last release.
    #
    # Supported options:
    #   :as - specify a shared account to publish the gem (Gemfury only)
    def publish_if_updated(method, options = {})
      return if version_released?
      @builder.build(@gemspec).tap { |gem|
        @pusher.push gem, method, options
        @git_remote.add_tag "#{@tag_prefix}#{@version}"
      }
    end

    def version_released?
      releases = @git_remote.tags.
        select { |t| t =~ /^#{@tag_prefix}\d+(\.\d+)+/ }.
        map { |t| t.scan(/\d+/).map(&:to_i) }
      this_release = @version.split(/\./).map(&:to_i)
      releases.include?(this_release)
    end
  end
end


require "gem_publisher/cli_facade"

module GemPublisher
  class Pusher
    def initialize(cli_facade = CliFacade.new)
      @cli_facade = cli_facade
    end

    PUSH_METHODS = {
      "rubygems" => %w[gem push],
      "gemfury"  => %w[fury push]
    }

    # Publish the gem.
    #
    # Supported options:
    #   :as - specify a shared account to publish the gem (Gemfury only)
    def push(gem, method, options = {})
      push_command = PUSH_METHODS[method.to_s] or raise "Unknown Gem push method #{method.inspect}."
      push_command += [gem]
      push_command += ["--as", options[:as]] if options[:as]
      @cli_facade.execute(*push_command)
    end
  end
end


module Disc::Job
  attr_accessor :disque_id

  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def disque
      defined?(@disque) ? @disque : Disc.disque
    end

    def disque=(disque)
      @disque = disque
    end

    def disc(queue: nil, **options)
      @queue = queue
      @disc_options = options
    end

    def disc_options
      @disc_options ||= {}
    end

    def queue
      @queue || Disc.default_queue
    end

    def perform(arguments)
      self.new.perform(*arguments)
    end

    ## Disc's `#enqueue` is the main user-facing method of a Disc job, it
    #  enqueues a job with a given set of arguments in Disque, so it can be
    #  picked up by a Disc worker process.
    #
    ## Parameters:
    #
    ## `arguments`  - an optional array of arguments with which to execute
    #                 the job's #perform method.
    #
    ## `at`         - an optional named parameter specifying a moment in the
    #                 future in which to run the job, must respond to
    #                 `#to_time`.
    #
    ## `queue`      - an optional named parameter specifying the name of the
    #                 queue in which to store the job, defaults to the class
    #                 Disc queue or to 'default' if no Disc queue is specified
    #                 in the class.
    #
    ##  `**options` - an optional hash of options to forward internally to
    #                 [disque-rb](https://github.com/soveran/disque-rb)'s
    #                 `#push` method, valid options are:
    #
    ##  `replicate: <count>`  - specifies the number of nodes the job should
    #                           be replicated to.
    #
    ### `delay: <sec>`        - specifies a delay time in seconds for the job
    #                           to be delivered to a Disc worker, it is ignored
    #                           if using the `at` parameter.
    #
    ### `ttl: <sec>`          - specifies the job's time to live in seconds:
    #                           after this time, the job is deleted even if
    #                           it was not successfully delivered. If not
    #                           specified, the default TTL is one day.
    #
    ### `maxlen: <count>`     - specifies that if there are already <count>
    #                           messages queued for the specified queue name,
    #                           the message is refused.
    #
    ### `async: true`         - asks the server to let the command return ASAP
    #                           and replicate the job to other nodes in the background.
    #
    #
    ### CAVEATS
    #
    ## For convenience, any object can be passed as the `arguments` parameter,
    #  `Array()` will be used internally to preserve the array structure.
    #
    ## The `arguments` parameter is serialized for storage using `Disc.serialize`
    #  and Disc workers picking it up use `Disc.deserialize` on it, both methods
    #  use standard library json but can be overriden by the user
    #
    def enqueue(args = [], at: nil, queue: nil, **options)
      options = disc_options.merge(options).tap do |opt|
        opt[:delay] = at.to_time.to_i - DateTime.now.to_time.to_i unless at.nil?
      end

      disque.push(
        queue || self.queue,
        Disc.serialize({
          class: self.name,
          arguments: Array(args)
        }),
        Disc.disque_timeout,
        options
      )
    end
  end
end


#--
# = Interpolation
# Copyright (c) 2014, Sameer Deshmukh
# All rights reserved.
#
# Interpolation is a library for executing various interpolation
# functions in Ruby.
#
# == spec/lib/1d_interpolation_spec.rb
#
# Tests for 1D interpolation.
#++

$:.unshift File.dirname(__FILE__)

require 'base.rb'

module Interpolation

  # Implements one dimensional interpolation routines.
  #
  # ==== Usage
  #
  # x = (1..10).step(1).to_a
  # y = x.exp
  #
  # f = Interpolation::OneDimensional.new x, y, {kind: :linear, sorted: true}
  # i = f.interpolate 2.5
  #
  # puts "Interpolated value for 2.5 is #{i}"
  class OneDimensional < Interpolation::Base

    # !@attribute return_type
    #   The data type of the returned data. Set to :array, :matrix, nmatrix.
    #   :array by default (only supports Arrray as of now).
    #   @return [Array]   Returns data as a ruby Array if set to :array
    #   @return [Matrix]  Returns data as a ruby Matrix if set to :matrix
    #   @return [NMatrix] Returns data as an NMatrix if set to :nmatrix. Needs
    #   the NMatrix gem installed.
    attr_writer :return_type

    # Constructor for all One Dimensional interpolation operations.
    #
    # The function values to be supplied to this class are of the form y = f(x).
    #
    # Henceforth, y will be referred to as ordinate and x as absicca. If absicca
    # and ordinate arrays are not of the same length, then the effective size used
    # for interpolation will be MIN(x.size, y.size).
    #
    # ==== Arguments
    #
    # * +x+ -    The collection of absiccas. Must be a 1 D NMatrix or ruby Array.
    #
    # * +y+ -    The collection of ordinates corresponding to the absicca. 'y' can
    #            either be a 1D NMatrix or Array OR a 2D NMatrix. In case y contains
    #            multiple columns, the interpolation is carried out on each column,
    #            unless specified.
    #
    # * +opts+ - Various options for carrying out the interpolation.
    #
    # ==== Options
    #
    # * +:type+ - The kind of interpolation that the user wants to perform. Should be
    #             specified as a symbol. Defaults to linear. Only linear and cubic
    #             interpolation supported as of now. Cubic interpolation done with splines.
    #
    # * +:sorted+ - Set this option as *true* if the absicca collection is supplied in
    #             the arguments in a sorted manner. If not supplied, it will be assumed
    #             that absiccas are not sorted and they will sorted be sorted anyway.
    #
    # * +:axis+ - In case of a multidimensional ordinate matrix, specify the column over
    # which interpolation must be performed. *axis* starts indexing from 0 and should be
    # lower than the number of columns in the ordinate matrix.
    #
    # * +:precision+ - Specifies the precision of the interpolated values returned. Defaults
    # to 3.
    #
    # * +:yp1+ - First derivative of the 0th point (cubic spline).
    #
    # * +:ypn+ - First derivative of the last (n-1)th point (cubic spline).
    #
    # == Usage
    #
    #   x = (0..9).step(1).to_a
    #   y = x.map { |n| Math.exp(n) }
    #   f = Interpolation::OneDimensional.new x,y, type: :cubic
    #   f.interpolate 2.5
    #     #=> 12.287
    def initialize x, y, opts={}
      @return_type = :array
      super(x,y,opts)

      compute_second_derivatives if @opts[:type] == :cubic
    end

    # Performs the actual interpolation on the value passed as an argument. Kind of
    # interpolation performed is determined according to what is specified in the
    # constructor.
    #
    # ==== Arguments
    #
    # * +interpolant+ - The value for which the interpolation is to be performed. Can
    #                   either be a Numeric, Array of Numerics or NMatrix. If multidimensional
    #                   NMatrix is supplied then will flatten it and interpolate over
    #                   all its values. Will return answer in the form of an NMatrix if
    #                   *interpolant* is supplied as an NMatrix.
    def interpolate interpolant
      case @opts[:type]
      when :linear
        for_each (interpolant) { |x| linear_interpolation(x)  }
      when :cubic
        cubic_spline_interpolation interpolant
      else
        raise ArgumentError, "1 D interpolation of type #{@opts[:type]} not supported"
      end
    end

    alias_method :[], :interpolate

    # Return the data passed for interpolation alongwith the interpolated values.
    # @param [Numeric, Array, NMatrix] interpolant the value of the X co-ordinate
    # @return [Array, NMatrix] returns data based on the value of return_type
    # def interp interpolant

    # end
   private

    # Linear interpolation functions

    def for_each interpolant
      result = []

      if interpolant.kind_of? Numeric
        return yield interpolant
      else
        interpolant.each { |x| result << yield(x) }
      end

      result
    end

    def linear_interpolation interpolant
      # TODO : Make this more efficient by using hunt/locate from NR
      index  = locate(interpolant)
      same   = @x[index] == @x[index+1]
      result = []

      if (@y.respond_to?(:vector?) and @y.vector?) or
        @y.instance_of?(Array)

        return @y[index] if same
        return _lin_interpolator @y, index, interpolant
      elsif @opts[:axis]

        return @y.column(@opts[:axis])[index] if same
        return _lin_interpolator @y.column(@opts[:axis]), index, interpolant
      else
        @y.each_column do |c|
          result << (same ? c[index] : _lin_interpolator(c, index, interpolant))
        end
      end

      result
    end

    def _lin_interpolator y, index, interpolant
      (y[index] +
      ((interpolant - @x[index]) / (@x[index + 1] - @x[index])) *
       (y[index + 1] - y[index])).round(@opts[:precision])
    end

    # Cubic spline interpolation functions

    def compute_second_derivatives
      @opts.merge!({
        yp1: 1E99,
        ypn: 1E99
      })

      if @opts[:axis] == :all
        compute_multi_axis_second_derivatives
      else
        compute_single_axis_second_derivatives
      end
    end

    def compute_single_axis_second_derivatives
      @y_sd = compute_second_derivatives_for(axis_or_array_for(@y))
    end

    def compute_multi_axis_second_derivatives
      @y_sd = []
      @y.each_column do |column|
        @y_sd << compute_second_derivatives_for(column)
      end

      @y_sd = Matrix.columns @y_sd
    end

    def cubic_spline_interpolation interpolant
      if @opts[:axis] == :all
        multi_axis_evaluation_for interpolant
      else
        single_axis_evaluation_for interpolant
      end
    end

    def axis_or_array_for object
      object.is_a?(Array) ? object : object.column(@opts[:axis])
    end

    def interpolate_over_all_y_columns interpolant
      results = []
      0.upto(@y.column_count - 1) do |col_num|
        results << evaluate_cubic_spline_polynomial(interpolant, @y.column(col_num), @y_sd.column(col_num))
      end

      results
    end

    def multi_axis_evaluation_for interpolant
      if interpolant.is_a?(Array)
        interpolant.inject([]) do |acc, int|
          acc << interpolate_over_all_y_columns(int)
        end
      else
        interpolate_over_all_y_columns interpolant
      end
    end

    def single_axis_evaluation_for interpolant
      y    = axis_or_array_for @y
      y_sd = axis_or_array_for @y_sd

      if interpolant.is_a?(Array)
        interpolant.inject([]) do |results, int|
          results << evaluate_cubic_spline_polynomial(int, y, y_sd)
        end
      else
        evaluate_cubic_spline_polynomial interpolant, y, y_sd
      end
    end

    def evaluate_cubic_spline_polynomial interpolant, y, y_sd
      klo = locate interpolant
      khi = klo + 1

      h = @x[khi] - @x[klo]

      raise StandardError, "Wrong input at X index #{klo} and #{khi} for cubic spline" if h == 0

      a = (@x[khi] - interpolant) / h
      b = (interpolant - @x[klo]) / h
      (a * y[klo] + b * y[khi] + ((a * a * a - a) * y_sd[klo] +
        ( b * b * b - b) * y_sd[khi]) * ( h * h ) / 6.0).round(@opts[:precision])
    end

    # References: Numerical Recipes Edition 3. Chapter 3.3
    def compute_second_derivatives_for y
      y_sd = Array.new(@size)

      n      = y_sd.size
      u      = Array.new(n-1)
      yp1    = @opts[:yp1] # first derivative of the 0th point as specified by the user
      ypn    = @opts[:ypn] # first derivative of the nth point as specified by the user
      qn, un = nil, nil

      if yp1 > 0.99E30
        y_sd[0], u[0] = 0.0, 0.0
      else
        y_sd[0] = -0.5
        u[0] = (3.0 / (@x[1] - @x[0])) * ((y[1] - y[0]) / (@x[1] - @x[0]) - yp1)
      end

      1.upto(n-2) do |i| # decomposition loop for tridiagonal algorithm
        sig      = ( @x[i] - @x[i-1] ) / ( @x[i+1] - @x[i-1] )
        p        = sig * y_sd[i-1] + 2
        y_sd[i] = ( sig - 1) / p
        u[i]     = (( y[i+1] - y[i]) / (@x[i+1] - @x[i])) - ((y[i] - y[i-1]) / (@x[i] - @x[i-1]))
        u[i]     = ( 6 * u[i] / ( @x[i+1] - @x[i-1] ) - sig * u[i-1] ) / p;
      end

      if ypn > 0.99E30
        qn, un = 0.0, 0.0
      else
        qn = 0.5
        un = (3.0 / ( @x[n-1] - @x[n-2] )) * ( ypn - ( y[n-1] - y[n-2] ) / ( @x[n-1] - @x[n-2] ))
      end
      y_sd[n-1] = ( un - qn * u[n-2] ) / ( qn * y_sd[n-2] + 1.0 )

      (n-2).downto(0) do |k|
        y_sd[k] = y_sd[k] * y_sd[k+1] + u[k]
      end

      y_sd
    end
  end
end


# frozen_string_literal: true
require 'logger'
require 'open-uri'

require 'asciidoctor/include_ext/version'
require 'asciidoctor/include_ext/reader_ext'
require 'asciidoctor/include_ext/lineno_lines_selector'
require 'asciidoctor/include_ext/logging'
require 'asciidoctor/include_ext/tag_lines_selector'
require 'asciidoctor'
require 'asciidoctor/extensions'

module Asciidoctor::IncludeExt
  # Asciidoctor preprocessor for processing `include::<target>[]` directives
  # in the source document.
  #
  # @see http://asciidoctor.org/docs/user-manual/#include-directive
  class IncludeProcessor < ::Asciidoctor::Extensions::IncludeProcessor

    # @param selectors [Array<Class>] an array of selectors that can filter
    #   specified portions of the document to include
    #   (see <http://asciidoctor.org/docs/user-manual#include-partial>).
    # @param logger [Logger] the logger to use for logging warning and errors
    #   from this object and selectors.
    def initialize(selectors: [LinenoLinesSelector, TagLinesSelector],
                   logger: Logging.default_logger, **)
      super
      @selectors = selectors.dup.freeze
      @logger = logger
    end

    # @param reader [Asciidoctor::Reader]
    # @param target [String] name of the source file to include as specified
    #   in the target slot of the `include::[]` directive.
    # @param attributes [Hash<String, String>] parsed attributes of the
    #   `include::[]` directive.
    def process(_, reader, target, attributes)
      unless include_allowed? target, reader
        reader.unshift_line("link:#{target}[]")
        return
      end

      if (max_depth = reader.exceeded_max_depth?)
        logger.error "#{reader.line_info}: maximum include depth of #{max_depth} exceeded"
        return
      end

      unless (path = resolve_target_path(target, reader))
        if attributes.key? 'optional-option'
          reader.shift
        else
          logger.error "#{reader.line_info}: include target not found: #{target}"
          unresolved_include!(target, reader)
        end
        return
      end

      selector = lines_selector_for(target, attributes)
      begin
        lines = read_lines(path, selector)
      rescue => e  # rubocop:disable RescueWithoutErrorClass
        logger.error "#{reader.line_info}: failed to read include file: #{path}: #{e}"
        unresolved_include!(target, reader)
        return
      end

      if selector && selector.respond_to?(:first_included_lineno)
        incl_offset = selector.first_included_lineno
      end

      unless lines.empty?
        reader.push_include(lines, path, target, incl_offset || 1, attributes)
      end
    end

    protected

    attr_reader :logger

    # @param target (see #process)
    # @param reader (see #process)
    # @return [Boolean] `true` if it's allowed to include the *target*,
    #   `false` otherwise.
    def include_allowed?(target, reader)
      doc = reader.document

      return false if doc.safe >= ::Asciidoctor::SafeMode::SECURE
      return false if doc.attributes.fetch('max-include-depth', 64).to_i < 1
      return false if target_uri?(target) && !doc.attributes.key?('allow-uri-read')
      true
    end

    # @param target (see #process)
    # @param reader (see #process)
    # @return [String, nil] file path or URI of the *target*, or `nil` if not found.
    def resolve_target_path(target, reader)
      return target if target_uri? target

      # Include file is resolved relative to dir of the current include,
      # or base_dir if within original docfile.
      path = reader.document.normalize_system_path(target, reader.dir, nil,
                                                   target_name: 'include file')
      path if ::File.file?(path)
    end

    # Reads the specified file as individual lines, filters them using the
    # *selector* (if provided) and returns those lines in an array.
    #
    # @param filename [String] path of the file to be read.
    # @param selector [#to_proc, nil] predicate to filter lines that should be
    #   included in the output. It must accept two arguments: line and
    #   the line number. If `nil` is given, all lines are passed.
    # @return [Array<String>] an array of read lines.
    def read_lines(filename, selector)
      if selector
        IO.foreach(filename).select.with_index(1, &selector)
      else
        open(filename, &:read)
      end
    end

    # Finds and initializes a lines selector that can handle the specified include.
    #
    # @param target (see #process)
    # @param attributes (see #process)
    # @return [#to_proc, nil] an instance of lines selector, or `nil` if not found.
    def lines_selector_for(target, attributes)
      if (klass = @selectors.find { |s| s.handles? target, attributes })
        klass.new(target, attributes, logger: logger)
      end
    end

    # Replaces the include directive in ouput with a notice that it has not
    # been resolved.
    #
    # @param target (see #process)
    # @param reader (see #process)
    def unresolved_include!(target, reader)
      reader.unshift_line("Unresolved directive in #{reader.path} - include::#{target}[]")
    end

    private

    # @param target (see #process)
    # @return [Boolean] `true` if the *target* is an URI, `false` otherwise.
    def target_uri?(target)
      ::Asciidoctor::Helpers.uriish?(target)
    end
  end
end


# frozen_string_literal: true
require 'asciidoctor/include_ext/version'

module Asciidoctor::IncludeExt
  # Lines selector that selects lines of the content to be included based on
  # the specified ranges of line numbers.
  #
  # @note Instance of this class can be used only once, as a predicate to
  #   filter a single include directive.
  #
  # @example
  #   include::some-file.adoc[lines=1;3..4;6..-1]
  #
  # @example
  #   selector = LinenoLinesSelector.new("some-file.adoc", {"lines" => "1;3..4;6..-1"})
  #   IO.foreach(filename).select.with_index(1, &selector)
  #
  # @see http://asciidoctor.org/docs/user-manual#by-line-ranges
  class LinenoLinesSelector

    # @return [Integer, nil] 1-based line number of the first included line,
    #   or `nil` if none.
    attr_reader :first_included_lineno

    # @param attributes [Hash<String, String>] the attributes parsed from the
    #   `include::[]`s attributes slot.
    # @return [Boolean] `true` if the *attributes* hash contains a key `"lines"`.
    def self.handles?(_, attributes)
      attributes.key? 'lines'
    end

    # @param attributes [Hash<String, String>] the attributes parsed from the
    #   `include::[]`s attributes slot. It must contain a key `"lines"`.
    def initialize(_, attributes, **)
      @ranges = parse_attribute(attributes['lines'])
      @first_included_lineno = @ranges.last.first unless @ranges.empty?
    end

    # Returns `true` if the given line should be included, `false` otherwise.
    #
    # @note This method modifies state of this object. It's supposed to be
    #   called successively with each line of the content being included.
    #   See {LinenoLinesSelector example}.
    #
    # @param line_num [Integer] 1-based *line* number.
    # @return [Boolean] `true` to select the *line*, or `false` to reject.
    def include?(_, line_num)
      return false if @ranges.empty?

      ranges = @ranges
      ranges.pop while !ranges.empty? && ranges.last.last < line_num
      ranges.last.cover?(line_num) if !ranges.empty?
    end

    # @return [Proc] {#include?} method as a Proc.
    def to_proc
      method(:include?).to_proc
    end

    protected

    # @param lines_def [String] a comma or semicolon separated numbers and
    #   and ranges (e.g. `1..2`) specifying lines to be selected, or rejected
    #   if prefixed with "!".
    # @return [Array<Range>] an array of ranges sorted by the range begin in
    #   _descending_ order.
    def parse_attribute(lines_def)
      lines_def
        .split(/[,;]/)
        .map! { |line_def|
          from, to = line_def.split('..', 2).map(&:to_i)
          to ||= from
          to = ::Float::INFINITY if to == -1
          (from..to)
        }.sort! do |a, b|
          b.first <=> a.first
        end
    end
  end
end


# frozen_string_literal: true
require 'logger'
require 'set'

require 'asciidoctor'
require 'asciidoctor/include_ext/version'
require 'asciidoctor/include_ext/logging'

module Asciidoctor::IncludeExt
  # Lines selector that selects lines of the content based on the specified tags.
  #
  # @note Instance of this class can be used only once, as a predicate to
  #   filter a single include directive.
  #
  # @example
  #   include::some-file.adoc[tags=snippets;!snippet-b]
  #   include::some-file.adoc[tag=snippets]
  #
  # @example
  #   selector = TagLinesSelector.new("some-file.adoc", {"tag" => "snippets"})
  #   IO.foreach(filename).select.with_index(1, &selector)
  #
  # @see http://asciidoctor.org/docs/user-manual#by-tagged-regions
  class TagLinesSelector

    # @return [Integer, nil] 1-based line number of the first included line,
    #   or `nil` if none.
    attr_reader :first_included_lineno

    # @param attributes [Hash<String, String>] the attributes parsed from the
    #   `include::[]`s attributes slot.
    # @return [Boolean] `true` if the *attributes* hash contains a key `"tag"`
    #   or `"tags"`.
    def self.handles?(_, attributes)
      attributes.key?('tag') || attributes.key?('tags')
    end

    # @param target [String] name of the source file to include as specified
    #   in the target slot of the `include::[]` directive.
    # @param attributes [Hash<String, String>] the attributes parsed from the
    #   `include::[]`s attributes slot. It must contain a key `"tag"` or `"tags"`.
    # @param logger [Logger]
    def initialize(target, attributes, logger: Logging.default_logger, **)
      tag_flags =
        if attributes.key? 'tag'
          parse_attribute(attributes['tag'], true)
        else
          parse_attribute(attributes['tags'])
        end

      wildcard = tag_flags.delete('*')
      if tag_flags.key? '**'
        default_state = tag_flags.delete('**')
        wildcard = default_state if wildcard.nil?
      else
        default_state = !tag_flags.value?(true)
      end

      # "immutable"
      @target = target
      @logger = logger
      @tag_flags = tag_flags.freeze
      @wildcard = wildcard
      @tag_directive_rx = /\b(?:tag|(end))::(\S+)\[\](?=$| )/.freeze

      # mutable (state variables)
      @stack = [[nil, default_state]]
      @state = default_state
      @used_tags = ::Set.new
    end

    # Returns `true` if the given line should be included, `false` otherwise.
    #
    # @note This method modifies state of this object. It's supposed to be
    #   called successively with each line of the content being included.
    #   See {TagLinesSelector example}.
    #
    # @param line [String]
    # @param line_num [Integer] 1-based *line* number.
    # @return [Boolean] `true` to select the *line*, `false` to reject.
    def include?(line, line_num)
      tag_type, tag_name = parse_tag_directive(line)

      case tag_type
      when :start
        enter_region!(tag_name, line_num)
        false
      when :end
        exit_region!(tag_name, line_num)
        false
      when nil
        if @state && @first_included_lineno.nil?
          @first_included_lineno = line_num
        end
        @state
      end
    end

    # @return [Proc] {#include?} method as a Proc.
    def to_proc
      method(:include?).to_proc
    end

    protected

    attr_reader :logger, :target

    # @return [String, nil] a name of the active tag (region), or `nil` if none.
    def active_tag
      @stack.last.first
    end

    # @param tag_name [String]
    # @param _line_num [Integer]
    def enter_region!(tag_name, _line_num)
      if @tag_flags.key? tag_name
        @used_tags << tag_name
        @state = @tag_flags[tag_name]
        @stack << [tag_name, @state]
      elsif !@wildcard.nil?
        @state = active_tag && !@state ? false : @wildcard
        @stack << [tag_name, @state]
      end
    end

    # @param tag_name [String]
    # @param line_num [Integer]
    def exit_region!(tag_name, line_num)
      # valid end tag
      if tag_name == active_tag
        @stack.pop
        @state = @stack.last[1]

      # mismatched/unexpected end tag
      elsif @tag_flags.key? tag_name
        log_prefix = "#{target}: line #{line_num}"

        if (idx = @stack.rindex { |key, _| key == tag_name })
          @stack.delete_at(idx)
          logger.warn "#{log_prefix}: mismatched end tag include: expected #{active_tag}, found #{tag_name}"  # rubocop:disable LineLength
        else
          logger.warn "#{log_prefix}: unexpected end tag in include: #{tag_name}"
        end
      end
    end

    # Parses `tag::<name>[]` and `end::<name>[]` in the given *line*.
    #
    # @param line [String]
    # @return [Array, nil] a tuple `[Symbol, String]` where the first item is
    #   `:start` or `:end` and the second is a tag name. If no tag is matched,
    #   then `nil` is returned.
    def parse_tag_directive(line)
      @tag_directive_rx.match(line) do |m|
        [m[1].nil? ? :start : :end, m[2]]
      end
    end

    # @param tags_def [String] a comma or semicolon separated names of tags to
    #   be selected, or rejected if prefixed with "!".
    # @param single [Boolean] whether the *tags_def* should be parsed as
    #   a single tag name (i.e. without splitting on comma/semicolon).
    # @return [Hash<String, Boolean>] a Hash with tag names as keys and boolean
    #   flags as values.
    def parse_attribute(tags_def, single = false)
      atoms = single ? [tags_def] : tags_def.split(/[,;]/)

      atoms.each_with_object({}) do |atom, tags|
        if atom.start_with? '!'
          tags[atom[1..-1]] = false if atom != '!'
        elsif !atom.empty?
          tags[atom] = true
        end
      end
    end
  end
end


module Pagescript
  # A helper that creates elements with metadata attached as data attributes.
  module TagsHelper
    # Generates a `<body>` tag
    # with HTML5 data attributes for the controller & action name.
    # The keyword arguments are forwarded to tag and content_tag.
    # @see http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html ActionView::Helpers::TagHelper
    # @return [String]
    # @yield Yields flow to an optional block.
    #   Using a block adds a closing </body> tag.
    # @param [Boolean] params_as_metadata default: false.
    #   Includes params from the URL as data attributes.
    #   The data attributes are prefixed with `param-`.
    #   Underscores in the param name are converted to '-'.
    # @example when called without a block
    #   body_tag
    #   =>  <body data-controller="foo" data-action="bar">
    # @example when called with a block
    #   body_tag { 'hello world' }
    #   =>  <body data-controller="foo" data-action="bar">Hello World</body>
    # @example when called with keyword arguments
    #   body_tag(foo: 'bar', data: { x: 2 })
    #   =>  <body data-controller="foo" data-action="bar" foo="" data-x="2">
    # @example when params_as_metadata is true
    #   body_tag(params_as_metadata: true)
    #   =>  <body data-controller="foo" data-action="bar" data-params-user-id="2">
    # @since 0.1.0
    # @api
    def body_tag(params_as_metadata: false, **kwargs)
      options = kwargs.deep_merge( data: data_attrs(params_as_metadata) )
      if block_given?
        content_tag(:body, options) { yield }
      else
        tag(:body, options, true)
      end
    end

    private

    def data_attrs(params_as_metadata = false)
      {
        controller: controller_name,
        action: action_name
      }.tap do |hash|
        if params_as_metadata
          params_from_url.each do |k, v|
            hash["params-#{k}"] = v
          end
        end
      end
    end

    def params_from_url
      {}.tap do |h|
        h.merge! request.path_parameters
        h.merge! request.query_parameters
      end.except(:controller, :action)
    end
  end
end