diff --git a/.gitignore b/.gitignore index 6b98b82..08767a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.gem *.gemspec +doc/ +.yardoc/ +.yardopts.html diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..e1fdc2e --- /dev/null +++ b/.yardopts @@ -0,0 +1 @@ +--no-private --protected --private --query '(!object.docstring.blank?&&object.docstring.line)||object.root?' --markup markdown --no-stats --plugin yard-heuristics-1.0 \ No newline at end of file diff --git a/README b/README index 4aa3785..2abbdc1 100644 --- a/README +++ b/README @@ -1,6 +1,211 @@ Inventory - Inventory allows you to create inventories of your Ruby projects. These - inventories can then be used to load the project, create gem specifications - and gems, run unit tests, and verify that the project’s content is what you - think it is. + Inventory keeps track of the contents of your Ruby projects. Such an + inventory can be used to load the project, create gem specifications and + gems, run unit tests, compile extensions, and verify that the project’s + content is what you think it is. + +§ Usage + + Let’s begin by discussing the project structure that Inventory expects you + to use. It’s pretty much exactly the same as the standard Ruby project + structure¹: + + ├── README + ├── Rakefile + ├── lib + │ ├── foo-1.0 + │ │ ├── bar.rb + │ │ └── version.rb + │ └── foo-1.0.rb + └── test + └── unit + ├── foo-1.0 + │ ├── bar.rb + │ └── version.rb + └── foo-1.0.rb + + Here you see a simplified version of a project called “Foo”’s project + structure. The only real difference from the standard is that the main + entry point into the library is named “foo-1.0.rb” instead of “foo.rb” and + that the root sub-directory of “lib” is similarly named “foo-1.0” instead + of “foo”. The difference is the inclusion of the API version. This must + be the major version of the project followed by a constant “.0”. The + reason for this is that it allows concurrent installations of different + major versions of the project and means that the wrong version will never + accidentally be loaded with require. + + There’s a bigger difference in the content of the files. + ‹Lib/foo-1.0/version.rb› will contain our inventory instead of a String: + + require 'inventory-1.0' + + class Foo + Version = Foo.new(1, 4, 0){ + def dependencies + super + Dependencies.new{ + development 'baz', 1, 3, 0 + runtime 'goo', 2, 0, 0 + optional 'roo-loo', 3, 0, 0, :feature => 'roo-loo' + } + end + + def package_libs + %w[bar.rb] + end + } + end + + We’re introducing quite a few concepts at once, and we’ll look into each in + greater detail, but we begin by setting the ‹Version› constant to a new + instance of an Inventory with major, minor, and patch version atoms 1, 4, + and 0. Then we add a couple of dependencies and list the library files + that are included in this project. + + The version numbers shouldn’t come as a surprise. These track the version + of the API that we’re shipping using {semantic versioning}². They also + allow the Inventory#to_s method to act as if you’d defined Version as + ‹'1.4.0'›. + + We then extend the definition of ‹dependencies› by adding another set of + dependencies to ‹super›. ‹Super› includes a dependency on the version of + the inventory project that’s being used with this project, so you’ll never + have to list that yourself. The other three dependencies are all of + different kinds: development, runtime, and optional. A development + dependency is one that’s required while developing the project, for + example, a unit-testing framework, a documentation generator, and so on. + Runtime dependencies are requirements of the project to be able to run, + both during development and when installed. Finally, optional dependencies + are runtime dependencies that may or may not be required during execution. + The difference between runtime and optional is that the inventory won’t try + to automatically load an optional dependency, instead leaving that up to + you to do when and if it becomes necessary. By that logic, runtime + dependencies will be automatically loaded, which is a good reason for + having dependency information available at runtime. + + The version numbers of dependencies also use semantic versioning, but note + that the patch atom is ignored unless the major atom is 0. You should + always only depend on the major and minor atoms. + + As mentioned, runtime dependencies will be automatically loaded and the + feature they try to load is based on the name of the dependency with a + “-X.0” tacked on the end, where ‘X’ is the major version of the dependency. + Sometimes, this isn’t correct, in which case the :feature option may be + given to specify the name of the feature. + + You may also override other parts of a dependency by passing in a block to + the dependency, much like we’re doing for inventories. + + The rest of an inventory will list the various files included in the + project. This project only consists of one additional file to those that + an inventory automatically include (Rakefile, README, the main entry point, + and the version.rb file that defines the inventory itself), namely the + library file ‹bar.rb›. Library files will be loaded automatically when the + main entry point file loads the inventory. Library files that shouldn’t be + loaded may be listed under a different heading, namely “additional_libs”. + Both these sets of files will be used to generate a list of unit test files + automatically, so each library file will have a corresponding unit test + file in the inventory. We’ll discuss the different headings of an + inventory in more detail later on. + + Now that we’ve written our inventory, let’s set it up so that it’s content + gets loaded when our main entry point gets loaded. We add the following + piece of code to ‹lib/foo-1.0.rb›: + + module Foo + load File.expand_path('../foo-1.0/version.rb', __FILE__) + Version.load + end + + That’s all there’s to it. + + The inventory can also be used to great effect from a Rakefile using a + separate project called Inventory-Rake³. Using it’ll give us tasks for + cleaning up our project, compiling extensions, installing dependencies, + installing and uninstalling the project itself, and creating and pushing + distribution files to distribution points. + + require 'inventory-rake-1.0' + + load File.expand_path('../lib/foo-1.0/version.rb', __FILE__) + + Inventory::Rake::Tasks.define Foo::Version, :gem => proc{ |_, s| + s.author = 'Your Name' + s.email = 'you@example.com' + s.homepage = 'https://example.com/' + } + + Inventory::Rake::Tasks.unless_installing_dependencies do + require 'lookout-rake-3.0' + Lookout::Rake::Tasks::Test.new + end + + It’s ‹Inventory::Rake::Tasks.define› that does the heavy lifting. It takes + our inventory and sets up the tasks mentioned above. We also do some + additional customization of the gem specification. + + As we want to be able to use our Rakefile to install our dependencies for + us, the rest of the Rakefile is inside the conditional + #unless_installing_dependencies, which, as the name certainly implies, + executes its block unless the task being run is the one that installs our + dependencies. This becomes relevant when we set up Travis⁴ integration + next. The only conditional set-up we do in our Rakefile is creating our + test task via Lookout-Rake⁵, which also uses our inventory to find the unit + tests to run when executed. + + Travis integration is straightforward. Simply put + + before_script: + - gem install inventory-rake -v '~> VERSION' --no-rdoc --no-ri + - rake gem:deps:install + + in the project’s ‹.travis.yml› file, replacing ‹VERSION› with the version + of Inventory-Rake that you require. This’ll make sure that Travis installs + all development, runtime, and optional dependencies that you’ve listed in + your inventory before running any tests. + + You might also need to put + + env: + - RUBYOPT=rubygems + + in your ‹.travis.yml› file, depending on how things are set up. + +¹ Ruby project structure: http://guides.rubygems.org/make-your-own-gem/ +² Semantic versioning: http://semver.org/ +³ Inventory-Rake: http://disu.se/software/inventory-rake/ +⁴ Travis: http://travis-ci.org/ +⁵ Lookout-Rake: http://disu.se/software/lookout-rake/ + +§ API + + If the guide above doesn’t provide you with all the answers you seek, you + may refer to the API¹ for more answers. + +¹ See http://disu.se/software/inventory/api/inventory/ + +§ Financing + + Currently, most of my time is spent at my day job and in my rather busy + private life. Please motivate me to spend time on this piece of software + by donating some of your money to this project. Yeah, I realize that + requesting money to develop software is a bit, well, capitalistic of me. + But please realize that I live in a capitalistic society and I need money + to have other people give me the things that I need to continue living + under the rules of said society. So, if you feel that this piece of + software has helped you out enough to warrant a reward, please PayPal a + donation to now@disu.se¹. Thanks! Your support won’t go unnoticed! + +¹ Send a donation: + https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=now%40disu%2ese&item_name=Nikolai%20Weibull%20Software%20Services + +§ Reporting Bugs + + Please report any bugs that you encounter to the {issue tracker}¹. + + ¹ See https://github.com/now/inventory/issues + +§ Authors + + Nikolai Weibull wrote the code, the tests, the manual pages, and this + README. diff --git a/Rakefile b/Rakefile index 1ea7cac..8115484 100644 --- a/Rakefile +++ b/Rakefile @@ -14,4 +14,10 @@ Inventory::Rake::Tasks.define Inventory::Version, :gem => proc{ |_, s| Inventory::Rake::Tasks.unless_installing_dependencies do require 'lookout/rake-3.0' Lookout::Rake::Tasks::Test.new + + require 'inventory-rake-tasks-yard-1.0' + Inventory::Rake::Tasks::YARD.new do |t| + t.options += %w'--plugin yard-heuristics-1.0' + t.globals[:source_code_url] = 'https://github.com/now/%s/blob/v%s/%%s#L%%d' % [t.inventory.package, t.inventory] + end end diff --git a/lib/inventory-1.0.rb b/lib/inventory-1.0.rb index 06937d4..cbfd578 100644 --- a/lib/inventory-1.0.rb +++ b/lib/inventory-1.0.rb @@ -1,31 +1,94 @@ # -*- coding: utf-8 -*- +# An Inventory keeps track of your Ruby project’s {Inventory#to_s version} and +# its {Inventory#to_a content}. It also allows you to {#load} your project’s +# source files in a simple manner and track its {#dependencies}. Add-ons, such +# as [Inventory-Rake](http://disu.se/software/inventory-rake/) and +# [Inventory-Rake-Tasks-YARD](http://disu.se/software/inventory-rake-tasks-yard), +# can also use this information to great effect. +# +# The basic usage pattern is to set your project’s Version constant to an +# instance of this class, where you set the version information and override +# the relevant methods with the information relevant to your project. For +# example, almost all project will want to override the {#package_libs} method +# with a method that returns an Array of the files relevant to it. +# +# Quite a few methods have definitions that you’ll want to extend, for example, +# {#dependencies}, in which case you simply call super and add your additions +# to its result. +# +# The naming convention of methods is that any method named “X_files” will +# return an Array of complete paths to files of kind X inside the project, +# while any method named “X” will return an Array of paths relative to the +# sub-directory that the method is named after. For example, “lib_files” +# returns an Array of complete paths to files under the “lib” directory +# ([lib/foo-1.0.rb, …]), while “libs” returns an Array of paths relative to the +# “lib” directory ([foo-1.0.rb]). +# +# See {Dependencies} for more information on managing dependencies. +# +# See {Extension} for more information on adding extensions. +# +# @example Creating an Inventory +# require 'inventory-1.0' +# +# class Foo +# Version = Inventory.new(1, 2, 0){ +# def dependencies +# super + Dependencies.new{ +# development 'inventory-rake', 1, 3, 0 +# runtime 'bar', 1, 6, 0 +# } +# end +# +# def package_libs +# %w'a.rb +# b.rb +# b/c.rb' +# end +# } +# end class Inventory + # Sets up an inventory for version MAJOR.MINOR.PATCH for a library whose + # `lib/PACKAGE/version.rb` is at PATH. Any block will be #instance_exec’d. + # + # @param major [String] + # @param minor [String] + # @param patch [String] + # @param path [String] + # @raise [ArgumentError] If PATH’s default value can’t be calculated + # @raise [ArgumentError] If PATH isn’t of the form `lib/PACKAGE/version.rb` def initialize(major, minor, patch, path = (m = /\A(.*):\d+(?::in .*)?\z/.match(caller.first) and m[1])) @major, @minor, @patch = major, minor, patch raise ArgumentError, 'default value of path argument could not be calculated' unless path @path = path @srcdir, _, @package_path = File.dirname(File.expand_path(path)).rpartition('/lib/') - @package_require = '%s-%d.0' % [package_path, major] + @package = @package_path.sub(/-#{Regexp.escape(major.to_s)}\.0\z/, '').gsub('/', '-') + @package_require = package == package_path.gsub('/', '-') ? '%s-%d.0' % [package_path, major] : package_path raise ArgumentError, 'path is not of the form PATH/lib/PACKAGE/version.rb: %s' % path if @srcdir.empty? instance_exec(&Proc.new) if block_given? end - def package - package_path.gsub('/', '-') - end + # @return [String] The name of the package, as derived from the + # {#package_path} + attr_reader :package def version_require + warn '%s#%s is deprecated and there’s no replacement' % [self.class, __method__] File.join(package_path, 'version') end def lib_directories - %w'lib' + %w[lib] end + # Requires all {#requires}, {Dependencies#require requires} all + # {#dependencies}, and loads all {#loads}. + # + # @return [self] def load requires.each do |requirement| require requirement @@ -37,60 +100,96 @@ def load self end + # @return [Dependencies] The dependencies of the package + # @note The default list of dependencies is an {Dependencies#optional + # optional} dependency on the inventory package itself. def dependencies Dependencies.new{ optional 'inventory', Version.major, Version.minor, Version.patch } end + # @return [Array] The files to require when {#load}ing, the default + # being empty def requires [] end + # @return [Array] The files to load when {#load}ing, the default + # being {#libs} def loads libs end + # @return [Array] The extensions included in the package, the + # default being empty def extensions [] end - def libs + # @return [Array] The library files belonging to the package that will + # be loaded when {#load}ing, the default being empty + def package_libs [] end + # @return [Array] The library files to load when {#load}ing, the + # default being {#package_libs} inside a directory with the same name as + # {#package_require} + def libs + package_libs.map{ |e| '%s/%s' % [package_path, e] } + end + + # @return [Array] Any additional library files, the default being + # `{#package_path}-{#major}.0.rb` and `{#package_path}/version.rb` def additional_libs - [package_require, version_require].map{ |e| '%s.rb' % e } + [package_require, File.join(package_path, 'version')].map{ |e| '%s.rb' % e } end + # @return [Array] All library files, that is, {#libs} + + # {#additional_libs} def all_libs libs + additional_libs end + # @return [Array] The complete paths of {#all_libs all library files} + # inside the package def lib_files all_libs.map{ |e| 'lib/%s' % e } end + # @return [Array] The unit tests included in the package, the default + # being {#all_libs}, meaning that there’s one unit test for each library + # file def unit_tests all_libs end + # @return [Array] Any additional unit tests, the default being empty def additional_unit_tests [] end + # @return [Array] All unit tests, that is {#unit_tests} + + # {#additional_unit_tests} def all_unit_tests unit_tests + additional_unit_tests end + # @return [Array] The complete paths of {#all_unit_tests all unit + # test} files inside the package def unit_test_files all_unit_tests.map{ |e| 'test/unit/%s' % e } end + # @return [Array] All test files included in the package, the default + # being {#unit_test_files} def test_files unit_test_files end + # @return [Array] Any additional files included in the package, the + # default being README and Rakefile def additional_files %w' README @@ -98,14 +197,19 @@ def additional_files ' end + # @return [Array] All files included in the package, that is + # {#lib_files} + {#test_files} + {#additional_files} + all files from + # {#extensions} def files lib_files + test_files + additional_files + extensions.map(&:files).flatten end + # @return [Array] Whatever {#files} returns def to_a files end + # @return [String] The version atoms formatted as {#major}.{#minor}.{#patch} def to_s '%d.%d.%d' % [major, minor, patch] end @@ -114,7 +218,27 @@ def inspect '#<%s: %s %s>' % [self.class, package, self] end - attr_reader :major, :minor, :patch, :path, :srcdir, :package_path, :package_require + # @return [Integer] The major version atom of the package + attr_reader :major + + # @return [Integer] The minor version atom of the package + attr_reader :minor + + # @return [Integer] The patch version atom of the package + attr_reader :patch + + # @return [String] The path to the file containing the inventory + attr_reader :path + + # @return [String] The top-level path of the package + attr_reader :srcdir + + # @return [String] The root sub-directory under the “lib” directory of the + # package of the package + attr_reader :package_path + + # @return [String] The feature to require for the package + attr_reader :package_require load File.expand_path('../inventory/version.rb', __FILE__) Version.loads.each do |load| diff --git a/lib/inventory/dependencies.rb b/lib/inventory/dependencies.rb index 923a50c..b302f69 100644 --- a/lib/inventory/dependencies.rb +++ b/lib/inventory/dependencies.rb @@ -1,17 +1,75 @@ # -*- coding: utf-8 -*- +# Contains zero or more {Dependency dependencies} of the project. Dependencies +# can be {#+ added}, {#require}d, {#each enumerated}, and +# {#add_to_gem_specification added to Gem specifications}. Dependencies are +# set up by passing a block to {#initialize} and calling {#development}, +# {#runtime}, and {#optional} inside it. +# +# @example Creating a List of Dependencies +# Dependencies.new{ +# development 'inventory-rake', 1, 3, 0 +# runtime 'bar', 1, 6, 0 +# } class Inventory::Dependencies include Enumerable + # Creates a new list of DEPENDENCIES and allows more to be added in the + # optionally #instance_exec’d block by calling {#development}, {#runtime}, + # and {#optional} inside it. + # + # @param [Array] dependencies + # @yield [?] def initialize(*dependencies) @dependencies = dependencies instance_exec(&Proc.new) if block_given? end + private + + # Add a {Development} dependency on project NAME, version MAJOR, MINOR, + # PATCH, along with any OPTIONS. + # + # @param (see Inventory::Dependency#initialize) + # @option (see Inventory::Dependency#initialize) + def development(name, major, minor, patch, options = {}) + dependencies << Development.new(name, major, minor, patch, options) + self + end + + # Add a {Runtime} dependency on project NAME, version MAJOR, MINOR, PATCH, + # along with any OPTIONS. + # + # @param (see Inventory::Dependency#initialize) + # @option (see Inventory::Dependency#initialize) + def runtime(name, major, minor, patch, options = {}) + dependencies << Runtime.new(name, major, minor, patch, options) + self + end + + # Add an {Optional} dependency on project NAME, version MAJOR, MINOR, PATCH, + # along with any OPTIONS. + # + # @param (see Inventory::Dependency#initialize) + # @option (see Inventory::Dependency#initialize) + def optional(name, major, minor, patch, options = {}) + dependencies << Optional.new(name, major, minor, patch, options) + self + end + + public + + # @return [Dependencies] The dependencies of the receiver and those of OTHER def +(other) self.class.new(*(dependencies + other.dependencies)) end + # @overload + # Enumerates the dependencies. + # + # @yieldparam [Dependency] dependency + # @overload + # @return [Enumerator] An Enumerator over the dependencies def each return enum_for(__method__) unless block_given? dependencies.each do |dependency| @@ -20,10 +78,17 @@ def each self end + # {Dependency#require Require} each dependency in turn. + # @return [self] def require map{ |dependency| dependency.require } + self end + # {Dependency#add_to_gem_specification Add} each dependency to a Gem + # specification. + # @param [Gem::Specification] specification + # @return [self] def add_to_gem_specification(specification) each do |dependency| dependency.add_to_gem_specification specification @@ -34,21 +99,4 @@ def add_to_gem_specification(specification) protected attr_reader :dependencies - - private - - def development(name, major, minor, patch, options = {}) - dependencies << Development.new(name, major, minor, patch, options) - self - end - - def runtime(name, major, minor, patch, options = {}) - dependencies << Runtime.new(name, major, minor, patch, options) - self - end - - def optional(name, major, minor, patch, options = {}) - dependencies << Optional.new(name, major, minor, patch, options) - self - end end diff --git a/lib/inventory/dependencies/development.rb b/lib/inventory/dependencies/development.rb index 7c82532..bf47dcd 100644 --- a/lib/inventory/dependencies/development.rb +++ b/lib/inventory/dependencies/development.rb @@ -1,12 +1,20 @@ # -*- coding: utf-8 -*- +# A development dependency is one that’s needed during development of a +# project. It won’t be required when the library is loaded, but it will be +# added to a Gem specification as a development dependency. class Inventory::Dependencies::Development include Inventory::Dependency + # Do nothing. + # @return [nil] def require nil end + # Add the receiver as a development dependency to SPECIFICATION. + # @param [Gem::Specification] specification + # @return [self] def add_to_gem_specification(specification) specification.add_development_dependency name, gem_requirement self diff --git a/lib/inventory/dependencies/optional.rb b/lib/inventory/dependencies/optional.rb index 3815670..0dd39ca 100644 --- a/lib/inventory/dependencies/optional.rb +++ b/lib/inventory/dependencies/optional.rb @@ -1,8 +1,13 @@ # -*- coding: utf-8 -*- +# An optional dependency is one that may or may not be needed during runtime. +# It won’t be required when the library is loaded, but it will be added to a +# Gem specification as a runtime dependency. class Inventory::Dependencies::Optional include Inventory::Dependency + # Do nothing. + # @return [nil] def require nil end diff --git a/lib/inventory/dependencies/runtime.rb b/lib/inventory/dependencies/runtime.rb index da24418..aa7e302 100644 --- a/lib/inventory/dependencies/runtime.rb +++ b/lib/inventory/dependencies/runtime.rb @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +# A runtime dependency is one that’s needed during runtime. It’ll be required +# when the library is loaded and added to a Gem specification as a runtime +# dependency. class Inventory::Dependencies::Runtime include Inventory::Dependency end diff --git a/lib/inventory/dependency.rb b/lib/inventory/dependency.rb index 0d577d1..17bc0f0 100644 --- a/lib/inventory/dependency.rb +++ b/lib/inventory/dependency.rb @@ -1,29 +1,84 @@ # -*- coding: utf-8 -*- +# Some form of dependency of this project. A dependency can be {#require}d, +# {#add_to_gem_specification added to a gem specification}, and has various +# information associated with it: {#name}, {#major}, {#minor}, {#patch}, and +# {#feature}. module Inventory::Dependency + # Sets up a dependency on project NAME, version MAJOR, MINOR, PATCH, along + # with any OPTIONS. Any methods may be overridden on the instance in the + # optionally #instance_exec’d block, if a dependency has any non-standard + # requirements. + # + # @param [String] name + # @param [Integer] major + # @param [Integer] minor + # @param [Integer] patch + # @param [Hash] options + # @option options [String] :feature ('%s-%s.0' % [name.gsub('-', '/'), + # major]) The name of the feature to load def initialize(name, major, minor, patch, options = {}) @name, @major, @minor, @patch = name, major, minor, patch - @feature = options.fetch(:feature, '%s-%d.0' % [name.gsub('-', '/'), major]) + @altfeature = nil + @feature = options.fetch(:feature){ + @altfeature = '%s-%d.0' % [name.gsub('-', '/'), major] + '%s-%d.0' % [name, major] + } instance_exec(&Proc.new) if block_given? end - def to_s - [major, minor, patch].join('.') - end - + # @return [Boolean] The result of requiring {#feature} def require super feature + rescue LoadError => e + if not e.respond_to? :path or e.path.end_with? feature + begin + super @altfeature + rescue LoadError + raise e + end + end end + # Add the receiver as a runtime dependency to SPECIFICATION. + # @param [Gem::Specification] specification + # @return [self] def add_to_gem_specification(specification) specification.add_runtime_dependency name, gem_requirement self end - attr_reader :name, :major, :minor, :patch, :feature + # @return [String] The version atoms on the form {#major}.{#minor}.{#patch} + def to_s + [major, minor, patch].join('.') + end + + # @return [String] The name of the project that this dependency pertains to + attr_reader :name + + # @return [Integer] The major version atom of the dependency + attr_reader :major + + # @return [Integer] The minor version atom of the dependency + attr_reader :minor + + # @return [Integer] The patch version atom of the dependency + attr_reader :patch + + # @return [String] The name of the feature to {#require} + attr_reader :feature private + # Returns the version range to use in {#add_to_gem_specification}. The + # default is to use `~>` {#major}.{#minor} if {#major} > 0 and `~>` + # {#major}.{#minor}.{#patch} otherwise. The reasoning here is that the + # version numbers are using [semantic versioning](http://semver.org) with the + # unfortunate use of minor and patch as a fake major/minor combination when + # major is 0, which is most often the case for Ruby projects. Start your + # major versions at 1 and you’ll avoid this misuse of version numbers. + # + # @return [String] def gem_requirement '~> %s' % (major > 0 ? '%d.%d' % [major, minor] : to_s) end diff --git a/lib/inventory/extension.rb b/lib/inventory/extension.rb index 26b8fa8..f843c0d 100644 --- a/lib/inventory/extension.rb +++ b/lib/inventory/extension.rb @@ -1,43 +1,74 @@ # -*- coding: utf-8 -*- +# An extension represents a sub-directory under the “ext” directory in the +# project. Such a directory contains source files, usually written in C, that +# interface with the underlying Ruby implementation, and possibly other +# low-level systems, that aren’t otherwise available to Ruby code. +# +# For an inventory, an extension is a set of files that should be included in +# the project and can contain multiple extensions. +# [Inventory-Rake](http://disu.se/software/inventory-rake/) uses information +# found in these extensions to compile them for use from Ruby code. class Inventory::Extension + # Creates an extension named NAME. A block may be given that’ll be + # \#instance_exec’d in the extension being created so that various methods may + # be overridden to suit the extension being created. + # + # @param [String] name + # @yield [?] def initialize(name) @name = name instance_exec(&Proc.new) if block_given? end + # @return [String] The sub-directory into the project that contains the + # extension, the default being `ext/{#name}` def directory 'ext/%s' % name end + # @return [String] The path into the project that contains the “extconf.rb” + # file for the extension, the default being `{#directory}/extconf.rb` def extconf '%s/extconf.rb' % directory end + # @return [String] The path into the project that contains the “depend” + # file for the extension, the default being `{#directory}/depend` + # @note This file is usually created by hand by invoking `gcc -MM *.c > + # depend` def depend '%s/depend' % directory end + # @return [Array] The source files that make up the extension, the + # default being empty def sources [] end + # @return [Array] The complete paths of {#sources} inside the package def source_files sources.map{ |e| '%s/%s' % [directory, e] } end + # @return [Array] The complete paths to any additional files def additional_files [] end + # @return [Array] All files included in the package, that is + # [{#extconf}, {#depend} + {#source_files} + {#additional_files} def files [extconf, depend] + source_files + additional_files end + # @return [Array] Whatever {#files} returns def to_a files end + # @return [String] The receiver’s {#name} def to_s name end @@ -46,5 +77,6 @@ def inspect '#<%s: %s %s>' % [self.class, name, directory] end + # @return [String] The receiver’s {#name} attr_reader :name end diff --git a/test/unit/inventory-1.0.rb b/test/unit/inventory-1.0.rb index eef255d..7edc4ec 100644 --- a/test/unit/inventory-1.0.rb +++ b/test/unit/inventory-1.0.rb @@ -1,6 +1,14 @@ # -*- coding: utf-8 -*- Expectations do + expect 'a/lib/a/version.rb' do + Inventory.new(1, 0, 0, 'a/lib/a/version.rb').path + end + + expect 'a' do + Inventory.new(1, 0, 0, 'a/lib/a/version.rb').package_path + end + expect 'a' do Inventory.new(1, 0, 0, 'a/lib/a/version.rb').package end