# class Camping::Loader::Reloader

## The `Camping` `Reloader`¶ ↑

`Camping` apps are generally small and predictable. Many `Camping` apps are contained within a single file. Larger apps are split into a handful of other Ruby libraries within the same directory.

Since `Camping` apps (and their dependencies) are loaded with Ruby’s require method, there is a record of them in $LOADED_FEATURES. Which leaves a perfect space for this class to manage auto-reloading an app if any of its immediate dependencies changes.

## Wrapping Your Apps¶ ↑

Since bin/camping and the `Camping::Server` class already use the `Reloader`, you probably don’t need to hack it on your own. But, if you’re rolling your own situation, here’s how.

Rather than this:

    require 'yourapp'

Use this:

    require 'camping/reloader'
    reloader = Camping::Reloader.new('/path/to/yourapp.rb')
    blog = reloader.apps[:Blog]
    wiki = reloader.apps[:Wiki]

The `blog` and `wiki` objects will behave exactly like your Blog and Wiki, but they will update themselves if yourapp.rb changes.

You can also give `Reloader` more than one script.

### Constants

Loaders
Reloader

  ## The `Camping` `Reloader`¶ ↑

  `Camping` apps are generally small and predictable. Many `Camping` apps are contained within a single file. Larger apps are split into a handful of other Ruby libraries within the same directory.

  Since `Camping` apps (and their dependencies) are loaded with Ruby’s require method, there is a record of them in $LOADED_FEATURES. Which leaves a perfect space for this class to manage auto-reloading an app if any of its immediate dependencies changes.

  ## Wrapping Your Apps¶ ↑

  Since bin/camping and the `Camping::Server` class already use the `Reloader`, you probably don’t need to hack it on your own. But, if you’re rolling your own situation, here’s how.

  Rather than this:

      require 'yourapp'

  Use this:

      require 'camping/reloader'
      reloader = Camping::Reloader.new('/path/to/yourapp.rb')
      blog = reloader.apps[:Blog]
      wiki = reloader.apps[:Wiki]

  The `blog` and `wiki` objects will behave exactly like your Blog and Wiki, but they will update themselves if yourapp.rb changes.

  You can also give `Reloader` more than one script.

### Attributes

file[R]

### Public Class Methods

new(file=nil, &blk) click to toggle source

    # File lib/camping/loader.rb, line 41
    def initialize(file=nil, &blk)
      @file = file
      @mtime = Time.at(0)
      @requires = []
      @apps = {}
      @callback = blk
      @root = Dir.pwd
      @file = @root + '/camp.rb' if @file == nil
      @zeit = Zeitwerk::Loader.new
      Loaders << @zeit

      # setup Zeit for this reloader
      setup_zeit(@zeit)

      dirs = [@root]
      dirs << "#{@root}/apps" if Dir.exist? "#{@root}/apps"
      dirs << "#{@root}/lib" if Dir.exist? "#{@root}/lib"

      # setup recursive listener on the apps and lib directories from the source script.
      @listener = Listen.to(*dirs) do |modified, added, removed|
        @mtime = Time.now
        reload!
      end
      start
    end

### Public Instance Methods

==(other) click to toggle source

Checks if both scripts watches the same file.

    # File lib/camping/loader.rb, line 174
    def ==(other)
      @file == other.file
    end

apps() click to toggle source

    # File lib/camping/loader.rb, line 178
    def apps
      if @app
        { name => @app }
      else
        @apps
      end
    end

can_add_directory(directory) click to toggle source

verifies that we can add a directory to the loader. used for testing to prevent multiple loaders from watching the same directory.

    # File lib/camping/loader.rb, line 199
    def can_add_directory(directory)
      if Dir.exist?(directory)
        Loaders.each do |loader|
          return false if loader.dirs.include? directory
        end
        true
      else
        false
      end
    end

folders_and_files_in(directory) click to toggle source

Splits the descendent files and folders found in a given directory for eager loading and recursion. If the given directory doesn’t exist or is empty, then nothing is returned.

    # File lib/camping/loader.rb, line 212
    def folders_and_files_in(directory)
      directory = directory + "/*" # unless directory
      [Dir.glob(directory).select {|f| !File.directory? f },
      Dir.glob(directory).select {|f| File.directory? f }]
    end

full_path(req) click to toggle source

Figures out the full path of a required file.

    # File lib/camping/loader.rb, line 239
    def full_path(req)
      return req if File.exist?(req)
      dir = $LOAD_PATH.detect { |l| File.exist?(File.join(l, req)) }
      if dir
        File.expand_path(req, File.expand_path(dir))
      else
        req
      end
    end

load_everything() click to toggle source

`remove_constants` called inside this.

    # File lib/camping/loader.rb, line 84
    def load_everything()
      all_requires = $LOADED_FEATURES.dup
      all_apps = Camping::Apps.dup

      # Zeitwerk will Autoload stuff, which is great. But we don't want Zeitwerk
      # autoloading when we evaluate the camp.rb file because it will try to
      # autoload any controllers and helpers that we've defined in there from
      # the descendant /apps and /lib directory, which then make it break.
      @zeit.unload
      load_file
      @zeit.setup
      reload_directory("#{@root}/apps")
      reload_directory("#{@root}/lib")
      Camping.make_camp
    ensure
      @requires = []
      new_apps = Camping::Apps - all_apps

      @apps = new_apps.inject({}) do |hash, app|
        if file = app.options[:__FILE__]
          full = File.expand_path(file)
          @requires << [file, full]
        end

        key = app.name.to_sym
        hash[key] = app

        if !@apps.include?(key)
          @callback.call(app) if @callback
          app.create if app.respond_to?(:create)
          app.kindling if app.respond_to?(:kindling)
        end

        hash
      end

      ($LOADED_FEATURES - all_requires).each do |req|
        full = full_path(req)
        @requires << [req, full] # if dirs.any? { |x| full.index(x) == 0 }
      end

      @mtime = mtime

      self

    end

load_file() click to toggle source

`load_file`

Rack::Builder is mainly used to parse a config.ru file and to build a rack app with middleware from that.

    # File lib/camping/loader.rb, line 135
    def load_file
      if @file =~ /\.ru$/
        @app = Rack::Builder.parse_file(@file)
      else
        load(@file)
      end
      @requires << [@file, File.expand_path(@file)]
    end

mtime() click to toggle source

    # File lib/camping/loader.rb, line 232
    def mtime
      @requires.map do |(path, full)|
        File.mtime(full)
      end.reject {|t| t > Time.now }.max || Time.now
    end

name() click to toggle source

    # File lib/camping/loader.rb, line 74
    def name
      @name ||= begin
        base = @file.dup
        base = File.dirname(base) if base =~ /\bconfig\.ru$/
        base.sub!(/\.[^.]+/, '')
        File.basename(base).to_sym
      end
    end

pause(= @listener.pause) click to toggle source

    # File lib/camping/loader.rb, line 71
        def pause = @listener.pause
        def start = @listener.start

        def name
          @name ||= begin
            base = @file.dup
            base = File.dirname(base) if base =~ /\bconfig\.ru$/
            base.sub!(/\.[^.]+/, '')
            File.basename(base).to_sym
          end
        end

        # remove_constants called inside this.
        def load_everything()
          all_requires = $LOADED_FEATURES.dup
          all_apps = Camping::Apps.dup

          # Zeitwerk will Autoload stuff, which is great. But we don't want Zeitwerk
          # autoloading when we evaluate the camp.rb file because it will try to
          # autoload any controllers and helpers that we've defined in there from
          # the descendant /apps and /lib directory, which then make it break.
          @zeit.unload
          load_file
          @zeit.setup
          reload_directory("#{@root}/apps")
          reload_directory("#{@root}/lib")
          Camping.make_camp
        ensure
          @requires = []
          new_apps = Camping::Apps - all_apps

          @apps = new_apps.inject({}) do |hash, app|
            if file = app.options[:__FILE__]
              full = File.expand_path(file)
              @requires << [file, full]
            end

            key = app.name.to_sym
            hash[key] = app

            if !@apps.include?(key)
              @callback.call(app) if @callback
              app.create if app.respond_to?(:create)
              app.kindling if app.respond_to?(:kindling)
            end

            hash
          end

          ($LOADED_FEATURES - all_requires).each do |req|
            full = full_path(req)
            @requires << [req, full] # if dirs.any? { |x| full.index(x) == 0 }
          end

          @mtime = mtime

          self

        end

        # load_file
        #
        # Rack::Builder is mainly used to parse a config.ru file and to
        # build a rack app with middleware from that.
        def load_file
          if @file =~ /\.ru$/
            @app = Rack::Builder.parse_file(@file)
          else
            load(@file)
          end
          @requires << [@file, File.expand_path(@file)]
        end

        # removes all constants recursively included using this script as a root.
        # so everything in /apps, and /lib in relation from this script.
        def remove_constants
            @requires.each do |(path, full)|
                    $LOADED_FEATURES.delete(path) unless path.match? "concurrent-ruby"
            end

          @apps.each do |name, app|
            Camping::Apps.delete(app)
            Object.send :remove_const, name
          end.dup
        ensure
          @apps.clear
          @requires.clear
        end

        # Reloads the file if needed.  No harm is done by calling this multiple
        # times, so feel free call just to be sure.
        def reload
          return if @mtime >= mtime rescue nil
          reload!
        end

        # Force a reload.
        def reload!
          remove_constants
          load_everything
        end

        # Checks if both scripts watches the same file.
        def ==(other)
          @file == other.file
        end

        def apps
          if @app
            { name => @app }
          else
            @apps
          end
        end

        private

        # sets up Zeit autoloading for the script locations.
        def setup_zeit(loader)
          loader.push_dir("#{@root}/apps") if can_add_directory "#{@root}/apps"
          loader.push_dir("#{@root}/lib") if can_add_directory "#{@root}/lib"
          loader.ignore("#{@root}/lib/camping-unabridged.rb")
                loader.enable_reloading if ENV['environment'] == 'development'
                loader.setup
        end

        # verifies that we can add a directory to the loader.
        # used for testing to prevent multiple loaders from watching the same directory.
        def can_add_directory(directory)
          if Dir.exist?(directory)
            Loaders.each do |loader|
              return false if loader.dirs.include? directory
            end
            true
          else
            false
          end
        end

        # Splits the descendent files and folders found in a given directory for eager loading and recursion.
        # If the given directory doesn't exist or is empty, then nothing is returned.
        def folders_and_files_in(directory)
          directory = directory + "/*" # unless directory
          [Dir.glob(directory).select {|f| !File.directory? f },
          Dir.glob(directory).select {|f| File.directory? f }]
        end

        # Reloads a directory recursively. loading more shallow files before deeper files.
        # If the given directory doesn't exist or is empty, then nothing happens.
        def reload_directory(directory)
          files, folders = folders_and_files_in(directory)
          files.each {|file|
            next if file.include? "unabridged"
            @requires << [file, File.expand_path(file)]
            load file
          }
          folders.each {|folder|
            reload_directory folder
          }
        end

        def mtime
          @requires.map do |(path, full)|
            File.mtime(full)
          end.reject {|t| t > Time.now }.max || Time.now
        end

        # Figures out the full path of a required file.
        def full_path(req)
          return req if File.exist?(req)
          dir = $LOAD_PATH.detect { |l| File.exist?(File.join(l, req)) }
          if dir
            File.expand_path(req, File.expand_path(dir))
          else
            req
          end
        end
      end

      Reloader = Loader

    end

processing_events?(= @listener.processing?) click to toggle source

pass through methods to the Listener. for testing purposes.

    # File lib/camping/loader.rb, line 69
      def processing_events? = @listener.processing?
      def stop = @listener.stop
      def pause = @listener.pause
      def start = @listener.start

      def name
        @name ||= begin
          base = @file.dup
          base = File.dirname(base) if base =~ /\bconfig\.ru$/
          base.sub!(/\.[^.]+/, '')
          File.basename(base).to_sym
        end
      end

      # remove_constants called inside this.
      def load_everything()
        all_requires = $LOADED_FEATURES.dup
        all_apps = Camping::Apps.dup

        # Zeitwerk will Autoload stuff, which is great. But we don't want Zeitwerk
        # autoloading when we evaluate the camp.rb file because it will try to
        # autoload any controllers and helpers that we've defined in there from
        # the descendant /apps and /lib directory, which then make it break.
        @zeit.unload
        load_file
        @zeit.setup
        reload_directory("#{@root}/apps")
        reload_directory("#{@root}/lib")
        Camping.make_camp
      ensure
        @requires = []
        new_apps = Camping::Apps - all_apps

        @apps = new_apps.inject({}) do |hash, app|
          if file = app.options[:__FILE__]
            full = File.expand_path(file)
            @requires << [file, full]
          end

          key = app.name.to_sym
          hash[key] = app

          if !@apps.include?(key)
            @callback.call(app) if @callback
            app.create if app.respond_to?(:create)
            app.kindling if app.respond_to?(:kindling)
          end

          hash
        end

        ($LOADED_FEATURES - all_requires).each do |req|
          full = full_path(req)
          @requires << [req, full] # if dirs.any? { |x| full.index(x) == 0 }
        end

        @mtime = mtime

        self

      end

      # load_file
      #
      # Rack::Builder is mainly used to parse a config.ru file and to
      # build a rack app with middleware from that.
      def load_file
        if @file =~ /\.ru$/
          @app = Rack::Builder.parse_file(@file)
        else
          load(@file)
        end
        @requires << [@file, File.expand_path(@file)]
      end

      # removes all constants recursively included using this script as a root.
      # so everything in /apps, and /lib in relation from this script.
      def remove_constants
          @requires.each do |(path, full)|
                  $LOADED_FEATURES.delete(path) unless path.match? "concurrent-ruby"
          end

        @apps.each do |name, app|
          Camping::Apps.delete(app)
          Object.send :remove_const, name
        end.dup
      ensure
        @apps.clear
        @requires.clear
      end

      # Reloads the file if needed.  No harm is done by calling this multiple
      # times, so feel free call just to be sure.
      def reload
        return if @mtime >= mtime rescue nil
        reload!
      end

      # Force a reload.
      def reload!
        remove_constants
        load_everything
      end

      # Checks if both scripts watches the same file.
      def ==(other)
        @file == other.file
      end

      def apps
        if @app
          { name => @app }
        else
          @apps
        end
      end

      private

      # sets up Zeit autoloading for the script locations.
      def setup_zeit(loader)
        loader.push_dir("#{@root}/apps") if can_add_directory "#{@root}/apps"
        loader.push_dir("#{@root}/lib") if can_add_directory "#{@root}/lib"
        loader.ignore("#{@root}/lib/camping-unabridged.rb")
              loader.enable_reloading if ENV['environment'] == 'development'
              loader.setup
      end

      # verifies that we can add a directory to the loader.
      # used for testing to prevent multiple loaders from watching the same directory.
      def can_add_directory(directory)
        if Dir.exist?(directory)
          Loaders.each do |loader|
            return false if loader.dirs.include? directory
          end
          true
        else
          false
        end
      end

      # Splits the descendent files and folders found in a given directory for eager loading and recursion.
      # If the given directory doesn't exist or is empty, then nothing is returned.
      def folders_and_files_in(directory)
        directory = directory + "/*" # unless directory
        [Dir.glob(directory).select {|f| !File.directory? f },
        Dir.glob(directory).select {|f| File.directory? f }]
      end

      # Reloads a directory recursively. loading more shallow files before deeper files.
      # If the given directory doesn't exist or is empty, then nothing happens.
      def reload_directory(directory)
        files, folders = folders_and_files_in(directory)
        files.each {|file|
          next if file.include? "unabridged"
          @requires << [file, File.expand_path(file)]
          load file
        }
        folders.each {|folder|
          reload_directory folder
        }
      end

      def mtime
        @requires.map do |(path, full)|
          File.mtime(full)
        end.reject {|t| t > Time.now }.max || Time.now
      end

      # Figures out the full path of a required file.
      def full_path(req)
        return req if File.exist?(req)
        dir = $LOAD_PATH.detect { |l| File.exist?(File.join(l, req)) }
        if dir
          File.expand_path(req, File.expand_path(dir))
        else
          req
        end
      end
    end

    Reloader =

reload() click to toggle source

Reloads the file if needed. No harm is done by calling this multiple times, so feel free call just to be sure.

    # File lib/camping/loader.rb, line 162
    def reload
      return if @mtime >= mtime rescue nil
      reload!
    end

reload!() click to toggle source

Force a reload.

    # File lib/camping/loader.rb, line 168
    def reload!
      remove_constants
      load_everything
    end

reload_directory(directory) click to toggle source

Reloads a directory recursively. loading more shallow files before deeper files. If the given directory doesn’t exist or is empty, then nothing happens.

    # File lib/camping/loader.rb, line 220
    def reload_directory(directory)
      files, folders = folders_and_files_in(directory)
      files.each {|file|
        next if file.include? "unabridged"
        @requires << [file, File.expand_path(file)]
        load file
      }
      folders.each {|folder|
        reload_directory folder
      }
    end

remove_constants() click to toggle source

removes all constants recursively included using this script as a root. so everything in /apps, and /lib in relation from this script.

    # File lib/camping/loader.rb, line 146
    def remove_constants
        @requires.each do |(path, full)|
                $LOADED_FEATURES.delete(path) unless path.match? "concurrent-ruby"
        end

      @apps.each do |name, app|
        Camping::Apps.delete(app)
        Object.send :remove_const, name
      end.dup
    ensure
      @apps.clear
      @requires.clear
    end

setup_zeit(loader) click to toggle source

sets up Zeit autoloading for the script locations.

    # File lib/camping/loader.rb, line 189
    def setup_zeit(loader)
      loader.push_dir("#{@root}/apps") if can_add_directory "#{@root}/apps"
      loader.push_dir("#{@root}/lib") if can_add_directory "#{@root}/lib"
      loader.ignore("#{@root}/lib/camping-unabridged.rb")
            loader.enable_reloading if ENV['environment'] == 'development'
            loader.setup
    end

start(= @listener.start) click to toggle source

    # File lib/camping/loader.rb, line 72
      def start = @listener.start

      def name
        @name ||= begin
          base = @file.dup
          base = File.dirname(base) if base =~ /\bconfig\.ru$/
          base.sub!(/\.[^.]+/, '')
          File.basename(base).to_sym
        end
      end

      # remove_constants called inside this.
      def load_everything()
        all_requires = $LOADED_FEATURES.dup
        all_apps = Camping::Apps.dup

        # Zeitwerk will Autoload stuff, which is great. But we don't want Zeitwerk
        # autoloading when we evaluate the camp.rb file because it will try to
        # autoload any controllers and helpers that we've defined in there from
        # the descendant /apps and /lib directory, which then make it break.
        @zeit.unload
        load_file
        @zeit.setup
        reload_directory("#{@root}/apps")
        reload_directory("#{@root}/lib")
        Camping.make_camp
      ensure
        @requires = []
        new_apps = Camping::Apps - all_apps

        @apps = new_apps.inject({}) do |hash, app|
          if file = app.options[:__FILE__]
            full = File.expand_path(file)
            @requires << [file, full]
          end

          key = app.name.to_sym
          hash[key] = app

          if !@apps.include?(key)
            @callback.call(app) if @callback
            app.create if app.respond_to?(:create)
            app.kindling if app.respond_to?(:kindling)
          end

          hash
        end

        ($LOADED_FEATURES - all_requires).each do |req|
          full = full_path(req)
          @requires << [req, full] # if dirs.any? { |x| full.index(x) == 0 }
        end

        @mtime = mtime

        self

      end

      # load_file
      #
      # Rack::Builder is mainly used to parse a config.ru file and to
      # build a rack app with middleware from that.
      def load_file
        if @file =~ /\.ru$/
          @app = Rack::Builder.parse_file(@file)
        else
          load(@file)
        end
        @requires << [@file, File.expand_path(@file)]
      end

      # removes all constants recursively included using this script as a root.
      # so everything in /apps, and /lib in relation from this script.
      def remove_constants
          @requires.each do |(path, full)|
                  $LOADED_FEATURES.delete(path) unless path.match? "concurrent-ruby"
          end

        @apps.each do |name, app|
          Camping::Apps.delete(app)
          Object.send :remove_const, name
        end.dup
      ensure
        @apps.clear
        @requires.clear
      end

      # Reloads the file if needed.  No harm is done by calling this multiple
      # times, so feel free call just to be sure.
      def reload
        return if @mtime >= mtime rescue nil
        reload!
      end

      # Force a reload.
      def reload!
        remove_constants
        load_everything
      end

      # Checks if both scripts watches the same file.
      def ==(other)
        @file == other.file
      end

      def apps
        if @app
          { name => @app }
        else
          @apps
        end
      end

      private

      # sets up Zeit autoloading for the script locations.
      def setup_zeit(loader)
        loader.push_dir("#{@root}/apps") if can_add_directory "#{@root}/apps"
        loader.push_dir("#{@root}/lib") if can_add_directory "#{@root}/lib"
        loader.ignore("#{@root}/lib/camping-unabridged.rb")
              loader.enable_reloading if ENV['environment'] == 'development'
              loader.setup
      end

      # verifies that we can add a directory to the loader.
      # used for testing to prevent multiple loaders from watching the same directory.
      def can_add_directory(directory)
        if Dir.exist?(directory)
          Loaders.each do |loader|
            return false if loader.dirs.include? directory
          end
          true
        else
          false
        end
      end

      # Splits the descendent files and folders found in a given directory for eager loading and recursion.
      # If the given directory doesn't exist or is empty, then nothing is returned.
      def folders_and_files_in(directory)
        directory = directory + "/*" # unless directory
        [Dir.glob(directory).select {|f| !File.directory? f },
        Dir.glob(directory).select {|f| File.directory? f }]
      end

      # Reloads a directory recursively. loading more shallow files before deeper files.
      # If the given directory doesn't exist or is empty, then nothing happens.
      def reload_directory(directory)
        files, folders = folders_and_files_in(directory)
        files.each {|file|
          next if file.include? "unabridged"
          @requires << [file, File.expand_path(file)]
          load file
        }
        folders.each {|folder|
          reload_directory folder
        }
      end

      def mtime
        @requires.map do |(path, full)|
          File.mtime(full)
        end.reject {|t| t > Time.now }.max || Time.now
      end

      # Figures out the full path of a required file.
      def full_path(req)
        return req if File.exist?(req)
        dir = $LOAD_PATH.detect { |l| File.exist?(File.join(l, req)) }
        if dir
          File.expand_path(req, File.expand_path(dir))
        else
          req
        end
      end
    end

stop(= @listener.stop) click to toggle source

    # File lib/camping/loader.rb, line 70
      def stop = @listener.stop
      def pause = @listener.pause
      def start = @listener.start

      def name
        @name ||= begin
          base = @file.dup
          base = File.dirname(base) if base =~ /\bconfig\.ru$/
          base.sub!(/\.[^.]+/, '')
          File.basename(base).to_sym
        end
      end

      # remove_constants called inside this.
      def load_everything()
        all_requires = $LOADED_FEATURES.dup
        all_apps = Camping::Apps.dup

        # Zeitwerk will Autoload stuff, which is great. But we don't want Zeitwerk
        # autoloading when we evaluate the camp.rb file because it will try to
        # autoload any controllers and helpers that we've defined in there from
        # the descendant /apps and /lib directory, which then make it break.
        @zeit.unload
        load_file
        @zeit.setup
        reload_directory("#{@root}/apps")
        reload_directory("#{@root}/lib")
        Camping.make_camp
      ensure
        @requires = []
        new_apps = Camping::Apps - all_apps

        @apps = new_apps.inject({}) do |hash, app|
          if file = app.options[:__FILE__]
            full = File.expand_path(file)
            @requires << [file, full]
          end

          key = app.name.to_sym
          hash[key] = app

          if !@apps.include?(key)
            @callback.call(app) if @callback
            app.create if app.respond_to?(:create)
            app.kindling if app.respond_to?(:kindling)
          end

          hash
        end

        ($LOADED_FEATURES - all_requires).each do |req|
          full = full_path(req)
          @requires << [req, full] # if dirs.any? { |x| full.index(x) == 0 }
        end

        @mtime = mtime

        self

      end

      # load_file
      #
      # Rack::Builder is mainly used to parse a config.ru file and to
      # build a rack app with middleware from that.
      def load_file
        if @file =~ /\.ru$/
          @app = Rack::Builder.parse_file(@file)
        else
          load(@file)
        end
        @requires << [@file, File.expand_path(@file)]
      end

      # removes all constants recursively included using this script as a root.
      # so everything in /apps, and /lib in relation from this script.
      def remove_constants
          @requires.each do |(path, full)|
                  $LOADED_FEATURES.delete(path) unless path.match? "concurrent-ruby"
          end

        @apps.each do |name, app|
          Camping::Apps.delete(app)
          Object.send :remove_const, name
        end.dup
      ensure
        @apps.clear
        @requires.clear
      end

      # Reloads the file if needed.  No harm is done by calling this multiple
      # times, so feel free call just to be sure.
      def reload
        return if @mtime >= mtime rescue nil
        reload!
      end

      # Force a reload.
      def reload!
        remove_constants
        load_everything
      end

      # Checks if both scripts watches the same file.
      def ==(other)
        @file == other.file
      end

      def apps
        if @app
          { name => @app }
        else
          @apps
        end
      end

      private

      # sets up Zeit autoloading for the script locations.
      def setup_zeit(loader)
        loader.push_dir("#{@root}/apps") if can_add_directory "#{@root}/apps"
        loader.push_dir("#{@root}/lib") if can_add_directory "#{@root}/lib"
        loader.ignore("#{@root}/lib/camping-unabridged.rb")
              loader.enable_reloading if ENV['environment'] == 'development'
              loader.setup
      end

      # verifies that we can add a directory to the loader.
      # used for testing to prevent multiple loaders from watching the same directory.
      def can_add_directory(directory)
        if Dir.exist?(directory)
          Loaders.each do |loader|
            return false if loader.dirs.include? directory
          end
          true
        else
          false
        end
      end

      # Splits the descendent files and folders found in a given directory for eager loading and recursion.
      # If the given directory doesn't exist or is empty, then nothing is returned.
      def folders_and_files_in(directory)
        directory = directory + "/*" # unless directory
        [Dir.glob(directory).select {|f| !File.directory? f },
        Dir.glob(directory).select {|f| File.directory? f }]
      end

      # Reloads a directory recursively. loading more shallow files before deeper files.
      # If the given directory doesn't exist or is empty, then nothing happens.
      def reload_directory(directory)
        files, folders = folders_and_files_in(directory)
        files.each {|file|
          next if file.include? "unabridged"
          @requires << [file, File.expand_path(file)]
          load file
        }
        folders.each {|folder|
          reload_directory folder
        }
      end

      def mtime
        @requires.map do |(path, full)|
          File.mtime(full)
        end.reject {|t| t > Time.now }.max || Time.now
      end

      # Figures out the full path of a required file.
      def full_path(req)
        return req if File.exist?(req)
        dir = $LOAD_PATH.detect { |l| File.exist?(File.join(l, req)) }
        if dir
          File.expand_path(req, File.expand_path(dir))
        else
          req
        end
      end
    end

    Reloader = Loader