<file_sep>#!/usr/bin/env ruby

require 'ak47'
Ak47::CLI.run(*$*)
<file_sep>module Ak47
  class Runner
    attr_reader :watch_dirs, :maximum, :interval, :error_time, :command, :interrupt

    def initialize(opts = nil, &blk)
      @watch_dirs = Array(opts && opts[:watch_dirs] || Dir.pwd)
      @maximum    = opts && opts[:maximum]
      @interval   = opts && opts[:interval] || 0.01
      @error_time = opts && opts[:error_time] || 5
      @command    = opts && opts[:command]
      @interrupt  = opts && opts.key?(:interrupt) ? opts[:interrupt] : true
      @blk        = blk
    end

    def start
      listeners = watch_dirs.map {|wd| Guard::Listener.select_and_init(:watchdir => wd, :watch_all_modifications => true) }
      change_detected = false
      running = false
      listeners.each do |l|
        l.on_change { |f|
          if @interrupt
            Thread.main.raise Reload, "File system changed"
          else
            change_detected = true
            Thread.main.raise Reload, "File system changed" unless running
          end
        }
        Thread.new { l.start }
      end

      at_exit { kill_pid }

      puts "[Starting ak47 #{VERSION} in #{watch_dirs.join(', ')}]".green
      loop do
        begin
          puts "[Running... #{Time.new.to_s}]".yellow
          puts "# #{command}" if command
          if maximum 
            @thread = Thread.new { sleep maximum; Thread.main.raise Reload, "Cancelled due to maximum time" }
          end
          running = true
          change_detected = false
          begin
            @pid = fork(&@blk)
            Process.detach(@pid)
            _, status = Process.waitpid2(@pid)
          rescue Errno::ECHILD
            @pid = nil
          ensure
            running = false
          end
          @thread.kill if @thread
          if @pid.nil? || status.success?
            if change_detected
              puts "[Change detected while previously running]".green
              change_detected = false
            else
              puts "[Terminated, waiting for file system change]".green
              maximum ? sleep(interval) : sleep
            end
          else
            puts "[Terminated abnormally (#{status.inspect}), retrying in 5s]".red
            sleep error_time
          end
        rescue Reload => e
          kill_pid
          sleep interval
          puts "[Reloading (#{e.message}) #{Time.new.to_s}]".yellow
        rescue Interrupt
          puts "[Interrupted, exiting]".yellow
          exit
        end
      end
    end

    def kill_pid
      if @pid
        begin
          unless wait_pid('INT')
            unless wait_pid('KILL')
              raise "[Unable to kill #{@pid}]"
            end
          end
        rescue Errno::ESRCH
        end
      end
    end

    def wait_pid(sig)
      count = 0
      while count < 5
        begin
          Process.kill(sig, @pid)
          count += 1
          sleep 1
        rescue Errno::ESRCH
          @pid = nil
          return true
        end
      end
      false
    end
  end
end<file_sep>require 'guard'
require 'shell_tools'
require "smart_colored/extend"
require 'optparse'

require "ak47/version"
require "ak47/runner"
require 'ak47/cli'

module Ak47
  Reload = Class.new(RuntimeError)
end

def Ak47(opts = nil, &blk)
  Ak47::Runner.new(opts, &blk).start
end<file_sep># Ak47

Ak47 is a general purpose reloader. It allows you to reload any command line argument based on either time or
file system change events. File system change events are only supported on Windows, Mac, and Linux.

## Installation

    gem install ak47

## Usage

To start an application using ak47, just prepend the entire command with ak47. For example:

    ak47 bundle exec thin -R config.ru start

This will run your webserver, but will reload it if there are any changes in your working directory.

There are a few command line options as well:

  * -m / --maximum Maximum time to wait before restarting, if unspecified wait forever.
  * -i / --interval Interval of time to wait before restarting in the event of a restart. Defaults to 0.01 seconds.
  * -e / --error-time Amount of time to wait between restarts if there was an error. Defaults to 5 seconds.
  
Any remaining arguments passed to the path will be interpretted as directories to watch. To stop parsing command line arguments
and enter your command, use `--` to seperate options from your command. For example:

    ak47 -i2 test -- rake test

This will watch your `test` directory and wait two seconds between restarts.

### Programmatic Usage

You can use Ak47 within a Ruby program in the following way.

    require 'ak47'
    Ak47 {
      puts "Reloading!"
    }
<file_sep>module Ak47
  module CLI
    def self.run(*argv)
      argv_opts, commands = if argv == ['--help'] or argv == ['-help']
        [argv, []]
      elsif divider_index = argv.index('--')
        [argv[0...divider_index], argv[divider_index.succ...argv.size]]
      else
        [[], argv]
      end

      interval, maximum, error_time, interrupt = nil, nil, 5, true
      optparse = OptionParser.new do |opts|
        opts.banner = "Usage: ak47 [cmd] / ak47 [options] -- [cmd]"
        opts.on( '-i', '--interval [FLOAT]', 'Interval before restarting' ) do |i|
          interval = Float(i) rescue raise("Interval must be a valid floating point number (e.g. -i0.5)")
          raise("Interval must be a positive number") unless interval >= 0
        end
        opts.on( '-m', '--maximum [FLOAT]', 'Maximum time to wait before restarting' ) do |m|
          maximum = Float(m) rescue raise("Maximum must be a valid floating point number (e.g. -m60)")
          raise("Maximum must be a positive number") unless maximum >= 0
        end
        opts.on( '-e', '--error-time [FLOAT]', 'Maximum time to wait before restarting if there was an abnormal status code' ) do |e|
          error_time = Float(e) rescue raise("Error time must be a valid floating point number (e.g. -e10)")
          raise("Maximum must be a positive number") unless error_time >= 0
        end
        opts.on( '-d', '--dont-interrupt', 'Don\'t interrupt a running process, wait for it to finish before reloading' ) do
          interrupt = false
        end
        opts.on( '-h', '--help', 'Display this screen' ) do
          puts opts
          exit
        end
      end
      optparse.parse!(argv_opts)
      watch_dirs = argv_opts
      watch_dirs << Dir.pwd if watch_dirs.empty?
      watch_dirs.map! { |wd| File.expand_path(wd, Dir.pwd) }

      command = ShellTools.escape(commands).strip
      if command.empty?
        puts optparse
        puts
        raise "No command supplied"
      end
      Runner.new(:watch_dirs => watch_dirs, :maximum => maximum, :interval => interval, :error_time => error_time, :command => command, :interrupt => interrupt) {
        exec(command)
      }.start
    rescue
      puts $!.backtrace.join("\n  ") if ENV['DEBUG']
      puts $!.message.red
      exit 1
    end
  end
end