module Boson
  # This library is based on a file of the same name under the commands directory of a repository.
  # Since there can be multiple repositories, a library's file is looked for in the order given by
  # Boson.repos.
  #
  # To create this library, simply create a file with a module and some methods. Non-private methods
  # are automatically loaded as a library's commands.
  #
  # Take for example a library brain.rb:
  #   module Brain
  #     def take_over(destination)
  #       puts "Pinky, it's time to take over the #{destination}!"
  #     end
  #   end
  #
  # Once loaded, this library can be run from the commandline or irb:
  #  bash> boson take_over world
  #  irb>> take_over 'world'
  #
  # If the library is namespaced, the command would be run as brain.take_over.
  #
  # Let's give Brain an option in his conquest:
  #   module Brain
  #     options :execute=>:string
  #     def take_over(destination, options={})
  #       puts "Pinky, it's time to take over the #{destination}!"
  #       system(options[:execute]) if options[:execute]
  #     end
  #   end
  #
  # From the commandline and irb this runs as:
  #   bash> boson take_over world -e initiate_brainiac
  #   irb>> take_over 'world -e initiate_brainiac'
  #
  # To learn more about the depth of option types available to a command, see OptionParser.
  #
  # Since boson aims to make your libraries just standard ruby, we can achieve the above
  # by placing options in comments above a method:
  #   module Brain
  #     # @options :execute=>:string
  #     # Help Brain live the dream
  #     def take_over(destination, options={})
  #       puts "Pinky, it's time to take over the #{destination}!"
  #       system(options[:execute]) if options[:execute]
  #     end
  #   end
  #
  # Some points about the above:
  # * A '@' must prefix options and other method calls that become comments.
  # * Note the comment above the method. One-line comments right before a method set a command's description.
  # * See MethodInspector for other command attributes, like options, that can be placed above a method.
  # * See CommentInspector for the rules about commenting command attributes.
  #
  # Once a command has a defined option, a command can also recognize a slew of global options:
  #   irb>> take_over '-h'
  #   take_over [destination] [--execute=STRING]
  #
  #   # prints much more verbose help
  #   irb>> take_over '-hv'
  #
  # For more about these global options see Scientist.
  class FileLibrary < Library
    #:stopdoc:
    def self.library_file(library, dir)
      File.join(Repo.commands_dir(dir), library + ".rb")
    end

    def self.matched_repo; @repo; end

    def self.read_library_file(file, reload=false)
      @file_cache ||= {}
      @file_cache[file] = File.read(file) if (!@file_cache.has_key?(file) || reload)
      @file_cache[file]
    end

    def self.reset_file_cache(name=nil)
      if name && @file_cache
        #td: tia other repos
        @file_cache.delete(library_file(name, Boson.repo.dir))
      else
        @file_cache = nil
      end
    end

    handles {|source|
      @repo = Boson.repos.find {|e|
        File.exists? library_file(source.to_s, e.dir)
      }
      !!@repo
    }

    def library_file
      self.class.library_file(@name, @repo_dir)
    end

    def set_repo
      self.class.matched_repo
    end

    def load_source(reload=false)
      library_string = self.class.read_library_file(library_file, reload)
      Inspector.enable
      Commands.module_eval(library_string, library_file)
      Inspector.disable
    end

    def load_source_and_set_module
      detected = detect_additions(:modules=>true) { load_source }
      @module = determine_lib_module(detected[:modules]) unless @module
    end

    def reload_source_and_set_module
      detected = detect_additions(:modules=>true) { load_source(true) }
      if (@new_module = !detected[:modules].empty?)
        @commands = []
        @module = determine_lib_module(detected[:modules])
      end
    end

    def determine_lib_module(detected_modules)
      case detected_modules.size
      when 1 then lib_module = detected_modules[0]
      when 0 then raise LoaderError, "Can't detect module. Make sure at least one module is defined in the library."
      else
        unless ((lib_module = Util.constantize("boson/commands/#{@name}")) && lib_module.to_s[/^Boson::Commands/])
          raise LoaderError, "Can't detect module. Specify a module in this library's config."
        end
      end
      lib_module
    end
    #:startdoc:
  end
end