require 'ore/exceptions/project_not_found' require 'ore/exceptions/invalid_metadata' require 'ore/naming' require 'ore/paths' require 'ore/checks' require 'ore/inferences' require 'ore/settings' require 'ore/rubygems' require 'ore/document_file' require 'pathname' require 'yaml' require 'find' require 'fileutils' module Ore # # Combines the metadata from the `gemspec.yml` file and the inferred # information from the project directory. # class Project include Naming include Paths include Checks include Inferences include Settings include RubyGems # The project metadata file @@metadata_file = 'gemspec.yml' # The root directory of the project attr_reader :root # The SCM which the project is currently under attr_reader :scm # The files of the project attr_reader :project_files # The fully-qualified namespace of the project attr_reader :namespace # The inferred namespace modules of the project attr_reader :namespace_modules # The directory contain the project code. attr_reader :namespace_dir # The name of the project attr_reader :name # The version of the project attr_reader :version # The project summary attr_reader :summary # The project description attr_reader :description # The licenses of the project attr_reader :licenses # The authors of the project attr_reader :authors # The homepage for the project attr_reader :homepage # The email contacts for the project attr_reader :emails # The build date for any project gems attr_reader :date # The parsed `.document` file attr_reader :document # The directories to search within the project when requiring files attr_reader :require_paths # The names of the executable scripts attr_reader :executables # The default executable attr_reader :default_executable # The documentation of the project attr_reader :documentation # Any extra files to include in the project documentation attr_reader :extra_doc_files # The files of the project attr_reader :files # The test files for the project attr_reader :test_files # Any external requirements needed by the project attr_reader :requirements # The version of Ruby required by the project attr_reader :required_ruby_version # The version of RubyGems required by the project attr_reader :required_rubygems_version # The dependencies of the project attr_reader :dependencies # The runtime-dependencies of the project attr_reader :runtime_dependencies # The development-dependencies of the project attr_reader :development_dependencies # The post-installation message attr_reader :post_install_message # # Creates a new {Project}. # # @param [String] root # The root directory of the project. # def initialize(root=Dir.pwd) @root = Pathname.new(root).expand_path unless @root.directory? raise(ProjectNotFound,"#{@root} is not a directory") end infer_scm! infer_project_files! metadata_file = @root.join(@@metadata_file) unless metadata_file.file? raise(ProjectNotFound,"#{@root} does not contain #{@@metadata_file}") end metadata = YAML.load_file(metadata_file) unless metadata.kind_of?(Hash) raise(InvalidMetadata,"#{metadata_file} did not contain valid metadata") end if metadata['name'] @name = metadata['name'].to_s else infer_name! end # infer the namespace from the project name infer_namespace! if metadata['version'] set_version! metadata['version'] else infer_version! end @summary = (metadata['summary'] || metadata['description']) @description = (metadata['description'] || metadata['summary']) @licenses = [] if metadata['license'] set_license!(metadata['license']) end @authors = [] if metadata['authors'] set_authors! metadata['authors'] end @homepage = metadata['homepage'] @emails = [] if metadata['email'] set_emails! metadata['email'] end if metadata['date'] set_date! metadata['date'] else infer_date! end @document = DocumentFile.find(self) @require_paths = [] if metadata['require_paths'] set_require_paths! metadata['require_paths'] else infer_require_paths! end @executables = [] if metadata['executables'] set_executables! metadata['executables'] else infer_executables! end @default_executable = nil if metadata['default_executable'] set_default_executable! metadata['default_executable'] else infer_default_executable! end if metadata['has_yard'] @documentation = :yard elsif metadata.has_key?('has_rdoc') @documentation = if metadata['has_rdoc'] :rdoc end else infer_documentation! end @extra_doc_files = [] if metadata['extra_doc_files'] set_extra_doc_files! metadata['extra_doc_files'] else infer_extra_doc_files! end @files = [] if metadata['files'] set_files! metadata['files'] else infer_files! end @test_files = [] if metadata['test_files'] set_test_files! metadata['test_files'] else infer_test_files! end if metadata['post_install_message'] @post_install_message = metadata['post_install_message'] end @requirements = [] if metadata['requirements'] set_requirements! metadata['requirements'] end if metadata['required_ruby_version'] @required_ruby_version = metadata['required_ruby_version'] end if metadata['required_rubygems_version'] @required_rubygems_version = metadata['required_rubygems_version'] else infer_required_rubygems_version! end @dependencies = [] if metadata['dependencies'] set_dependencies! metadata['dependencies'] end @runtime_dependencies = [] if metadata['runtime_dependencies'] set_runtime_dependencies! metadata['runtime_dependencies'] end @development_dependencies = [] if metadata['development_dependencies'] set_development_dependencies! metadata['development_dependencies'] end end # # Finds the project metadata file and creates a new {Project} object. # # @param [String] dir (Dir.pwd) # The directory to start searching upward from. # # @return [Project] # The found project. # # @raise [ProjectNotFound] # No project metadata file could be found. # def self.find(dir=Dir.pwd) Pathname.new(dir).ascend do |root| return self.new(root) if root.join(@@metadata_file).file? end raise(ProjectNotFound,"could not find #{@@metadata_file}") end # # Executes code within the project. # # @param [String] sub_dir # An optional sub-directory within the project to execute from. # # @yield [] # The given block will be called once the current working-directory # has been switched. Once the block finishes executing, the current # working-directory will be switched back. # # @see http://ruby-doc.org/core/classes/Dir.html#M002314 # def within(sub_dir=nil,&block) dir = if sub_dir @root.join(sub_dir) else @root end Dir.chdir(dir,&block) end # # The primary license of the project. # # @return [String, nil] # The primary license for the project. # def license @licenses.first end # # The primary email address of the project. # # @return [String, nil] # The primary email address for the project. # # @since 0.1.3 # def email @emails.first end # # Determines whether the project prefers using # [RVM](http://rvm.beginrescueend.com/). # # @return [Boolean] # Specifies whether the project prefers being developed under RVM. # # @since 0.1.2 # def rvm? file?('.rvmrc') end # # Determines whether the project uses Bundler. # # @return [Boolean] # Specifies whether the project uses Bundler. # def bundler? file?('Gemfile') end # # Determines whether the project has been bundled using Bundler. # # @return [Boolean] # Specifies whether the project has been bundled. # def bundled? file?('Gemfile.lock') end # # Determines if the project contains RDoc documentation. # # @return [Boolean] # Specifies whether the project has RDoc documentation. # def has_rdoc @documentation == :rdoc end alias has_rdoc? has_rdoc # # Determines if the project contains YARD documentation. # # @return [Boolean] # Specifies whether the project has YARD documentation. # def has_yard @documentation == :yard end alias has_yard? has_yard # # Builds a gem for the project. # # @param [Symbol] package # The type of package to build. # # @return [Pathname] # The path to the built gem file within the `pkg/` directory. # # @raise [ArgumentError] # The given package type is not supported. # def build!(package=:gem) if package == :gem to_gem else raise(ArgumentError,"unsupported package type #{package}") end end protected # # Prints multiple warning messages. # # @param [Array] messages # The messages to print. # def warn(*messages) messages.each { |mesg| STDERR.puts("WARNING: #{mesg}") } end # # Adds a require-path to the project. # # @param [String] path # A directory path relative to the project. # def add_require_path(path) check_directory(path) { |dir| @require_paths << dir } end # # Adds an executable to the project. # # @param [String] name # The name of the executable. # def add_executable(name) path = File.join(@@bin_dir,name) check_executable(path) { |exe| @executables << exe } end # # Adds an extra documentation file to the project. # # @param [String] path # The path to the file, relative to the project. # def add_extra_doc_file(path) check_file(path) { |file| @extra_doc_files << file } end # # Adds a file to the project. # # @param [String] path # The path to the file, relative to the project. # def add_file(path) check_file(path) { |file| @files << file } end # # Adds a testing-file to the project. # # @param [String] path # The path to the testing-file, relative to the project. # def add_test_file(path) check_file(path) { |file| @test_files << file } end end end