diff --git a/lib/shanty/artifact.rb b/lib/shanty/artifact.rb new file mode 100644 index 0000000..b5f052c --- /dev/null +++ b/lib/shanty/artifact.rb @@ -0,0 +1,56 @@ +require 'uri' +require 'pathname' + +module Shanty + # Public: Contain information on an artifact published by a project + class Artifact + attr_reader :file_extension, :plugin, :uri + + # Public: Initialise an artifact instance. + # + # file_extension - The extension of the artifact. + # plugin - The plugin publishing the artifact. + # uri - The URI to the object. + # + # Fails if the URI is not absolute or a scheme is missing. + def initialize(file_extension, plugin, uri) + @file_extension = file_extension + @plugin = plugin + @uri = uri + + validate_uri + end + + # Public: Whether the underlying URI of the artifact points to a local + # file path. + # + # Returns a Boolean representing whether the artifact is local. + def local? + @uri.scheme == 'file' + end + + # Public: If the underlying artifact is stored locally, returns the path + # at which the artifact can be found. + # + # Returns a String representing the path at which the artifact can be found. + # Raises RuntimeError if the artifact is not local. + def to_local_path + return @uri.path if local? + fail 'URI is not a local resource' + end + + # Public: A simple string representation of this artifact. + # + # Returns a String representing the URI at which this artifact can be found. + def to_s + @uri.to_s + end + + private + + def validate_uri + fail 'Scheme not present on URI' unless @uri.absolute? + fail 'URI is not absolute' if @uri.path.nil? + end + end +end diff --git a/lib/shanty/plugin.rb b/lib/shanty/plugin.rb index dfcabbc..702d117 100644 --- a/lib/shanty/plugin.rb +++ b/lib/shanty/plugin.rb @@ -41,6 +41,10 @@ def projects end end + def artifacts(_) + [] + end + def with_graph(graph) callbacks = self.class.instance_variable_get(:@with_graph_callbacks) return [] if callbacks.nil? || callbacks.empty? diff --git a/lib/shanty/plugins/bundler_plugin.rb b/lib/shanty/plugins/bundler_plugin.rb index 6499941..141d6e0 100644 --- a/lib/shanty/plugins/bundler_plugin.rb +++ b/lib/shanty/plugins/bundler_plugin.rb @@ -8,7 +8,7 @@ class BundlerPlugin < Plugin projects '**/Gemfile' subscribe :build, :bundle_install - def bundle_install + def bundle_install(_) Bundler.with_clean_env do # FIXME: Add support for the --jobs argument to parallelise the bundler run. system 'bundle install --quiet' diff --git a/lib/shanty/plugins/cucumber_plugin.rb b/lib/shanty/plugins/cucumber_plugin.rb index 72a2d65..0747eb7 100644 --- a/lib/shanty/plugins/cucumber_plugin.rb +++ b/lib/shanty/plugins/cucumber_plugin.rb @@ -17,7 +17,7 @@ def cucumber_projects end end - def cucumber + def cucumber(_) system 'cucumber' end end diff --git a/lib/shanty/plugins/rspec_plugin.rb b/lib/shanty/plugins/rspec_plugin.rb index 6fd6366..feed6fc 100644 --- a/lib/shanty/plugins/rspec_plugin.rb +++ b/lib/shanty/plugins/rspec_plugin.rb @@ -17,7 +17,7 @@ def rspec_projects end end - def rspec + def rspec(_) system 'rspec' end end diff --git a/lib/shanty/plugins/rubocop_plugin.rb b/lib/shanty/plugins/rubocop_plugin.rb index 1c7241c..8ea2dba 100644 --- a/lib/shanty/plugins/rubocop_plugin.rb +++ b/lib/shanty/plugins/rubocop_plugin.rb @@ -7,7 +7,7 @@ class RubocopPlugin < Plugin projects '**/.rubocop.yml' subscribe :test, :rubocop - def rubocop + def rubocop(_) system 'rubocop' end end diff --git a/lib/shanty/plugins/rubygem_plugin.rb b/lib/shanty/plugins/rubygem_plugin.rb index 44519b9..ffd75df 100644 --- a/lib/shanty/plugins/rubygem_plugin.rb +++ b/lib/shanty/plugins/rubygem_plugin.rb @@ -1,14 +1,36 @@ require 'shanty/plugin' +require 'shanty/artifact' module Shanty # Public: Rubygem plugin for buildin gems. class RubygemPlugin < Plugin + ARTIFACT_EXTENSION = 'gem' + tags :rubygem projects '**/*.gemspec' subscribe :build, :build_gem - def build_gem - system 'gem build *.gemspec' + def build_gem(project) + gemspec_files(project).each do |file| + system "gem build #{file}" + end + end + + def artifacts(project) + gemspec_files(project).flat_map do |file| + gemspec = Gem::Specification.load(file) + Artifact.new( + ARTIFACT_EXTENSION, + 'rubygem', + URI("file://#{project.path}/#{gemspec.name}-#{gemspec.version}.#{ARTIFACT_EXTENSION}") + ) + end + end + + private + + def gemspec_files(project) + @gemspec_files ||= project_tree.glob(File.join(project.path, '*.gemspec')) end end end diff --git a/lib/shanty/project.rb b/lib/shanty/project.rb index a6ea843..dd8aeda 100644 --- a/lib/shanty/project.rb +++ b/lib/shanty/project.rb @@ -10,7 +10,7 @@ class Project include ActsAsGraphVertex include Env - attr_accessor :name, :path, :tags, :options + attr_accessor :artifacts, :name, :path, :tags, :options # Multiton or Flyweight pattern - only allow once instance per unique path. # @@ -40,6 +40,7 @@ def initialize(path) @path = path @name = File.basename(path) + @artifacts = [] @plugins = [] @tags = [] @options = {} @@ -61,22 +62,22 @@ def all_tags (@tags + @plugins.flat_map { |plugin| plugin.class.tags }).map(&:to_sym).uniq end + # Public: The artifacts published by this project and any artifacts published + # by any plugins operating on this project. + # + # Returns the Array of Artifacts available for this project. + def all_artifacts + (@artifacts + @plugins.flat_map { |plugin| plugin.artifacts(project) }).uniq + end + def publish(name, *args) @plugins.each do |plugin| next unless plugin.subscribed?(name) logger.info("Executing #{name} on the #{plugin.class} plugin...") - return false if plugin.publish(name, *args) == false + return false if plugin.publish(name, self, *args) == false end end - # Public: The absolute paths to the artifacts that would be created by this - # project when built, if any. This is expected to be overriden in subclasses. - # - # Returns an Array of Strings representing the absolute paths to the artifacts. - def artifact_paths - [] - end - # Public: Overriden String conversion method to return a simplified # representation of this instance that doesn't include the cyclic # parent/children attributes as defined by the ActsAsGraphVertex mixin. diff --git a/spec/lib/shanty/artifact_spec.rb b/spec/lib/shanty/artifact_spec.rb new file mode 100644 index 0000000..a9085af --- /dev/null +++ b/spec/lib/shanty/artifact_spec.rb @@ -0,0 +1,44 @@ +require 'shanty/artifact' + +# All classes referenced belong to the shanty project +module Shanty + RSpec.describe(Artifact) do + subject { described_class.new('html', 'test', uri) } + let(:relative_path_artifact) { described_class.new('html', 'test', URI('file:kim/cage.html')) } + let(:http_artifact) { described_class.new('html', 'test', URI('http://www.nic.com/kim/cage.html')) } + let(:schemless_artifact) { described_class.new('html', 'test', URI('kim/cage.html')) } + + let(:uri) { URI('file:///nic/kim/cage.html') } + + describe('.new') do + it('fails if the URI is not an absolute path') do + expect { relative_path_artifact }.to raise_error('URI is not absolute') + end + + it('fails if there is no scheme present in the URI') do + expect { schemless_artifact }.to raise_error('Scheme not present on URI') + end + end + + describe('#local?') do + it('returns false if the file is not a local file') do + expect(http_artifact.local?).to be(false) + end + + it('returns true if the file is a local file') do + expect(subject.local?).to be(true) + end + end + + describe('#to_local_path') do + it('throws an exception if the resource is not local') do + allow(subject).to receive(:local?).and_return(false) + expect { subject.to_local_path }.to raise_error('URI is not a local resource') + end + + it('returns the path if the resource is local') do + expect(subject.to_local_path).to eql('/nic/kim/cage.html') + end + end + end +end diff --git a/spec/lib/shanty/plugins/bundler_plugin_spec.rb b/spec/lib/shanty/plugins/bundler_plugin_spec.rb index 32c14b2..9667915 100644 --- a/spec/lib/shanty/plugins/bundler_plugin_spec.rb +++ b/spec/lib/shanty/plugins/bundler_plugin_spec.rb @@ -4,7 +4,7 @@ # All classes referenced belong to the shanty project module Shanty RSpec.describe(BundlerPlugin) do - include_context('basics') + include_context('graph') it('adds the bundler tag') do expect(described_class).to add_tags(:bundler) @@ -22,7 +22,7 @@ module Shanty it('calls bundler install') do expect(subject).to receive(:system).with('bundle install --quiet') - subject.bundle_install + subject.bundle_install(project) end end end diff --git a/spec/lib/shanty/plugins/cucumber_plugin_spec.rb b/spec/lib/shanty/plugins/cucumber_plugin_spec.rb index 8f72584..b22407c 100644 --- a/spec/lib/shanty/plugins/cucumber_plugin_spec.rb +++ b/spec/lib/shanty/plugins/cucumber_plugin_spec.rb @@ -42,7 +42,7 @@ module Shanty it('calls cucumber') do expect(subject).to receive(:system).with('cucumber') - subject.cucumber + subject.cucumber(project) end end end diff --git a/spec/lib/shanty/plugins/rspec_plugin_spec.rb b/spec/lib/shanty/plugins/rspec_plugin_spec.rb index 9e44846..27e3be3 100644 --- a/spec/lib/shanty/plugins/rspec_plugin_spec.rb +++ b/spec/lib/shanty/plugins/rspec_plugin_spec.rb @@ -43,7 +43,7 @@ module Shanty it('calls rspec') do expect(subject).to receive(:system).with('rspec') - subject.rspec + subject.rspec(project) end end end diff --git a/spec/lib/shanty/plugins/rubocop_plugin_spec.rb b/spec/lib/shanty/plugins/rubocop_plugin_spec.rb index e7f1507..601d995 100644 --- a/spec/lib/shanty/plugins/rubocop_plugin_spec.rb +++ b/spec/lib/shanty/plugins/rubocop_plugin_spec.rb @@ -4,7 +4,7 @@ # All classes referenced belong to the shanty project module Shanty RSpec.describe(RubocopPlugin) do - include_context('basics') + include_context('graph') it('adds the rubocop tag') do expect(described_class).to add_tags(:rubocop) @@ -22,7 +22,7 @@ module Shanty it('calls rubocop') do expect(subject).to receive(:system).with('rubocop') - subject.rubocop + subject.rubocop(project) end end end diff --git a/spec/lib/shanty/plugins/rubygem_plugin_spec.rb b/spec/lib/shanty/plugins/rubygem_plugin_spec.rb index 453e72e..53aba26 100644 --- a/spec/lib/shanty/plugins/rubygem_plugin_spec.rb +++ b/spec/lib/shanty/plugins/rubygem_plugin_spec.rb @@ -1,10 +1,21 @@ require 'spec_helper' +require 'fileutils' require 'shanty/plugins/rubygem_plugin' # All classes referenced belong to the shanty project module Shanty RSpec.describe(RubygemPlugin) do - include_context('basics') + include_context('graph') + let(:gemspec_path) { File.join(project_path, 'foo.gemspec') } + + before do + File.write(gemspec_path, <<-eof) + Gem::Specification.new do |gem| + gem.name = 'foo' + gem.version = '1.2.3' + end + eof + end it('adds the rubygem tag') do expect(described_class).to add_tags(:rubygem) @@ -20,9 +31,23 @@ module Shanty describe('#build_gem') do it('calls gem build') do - expect(subject).to receive(:system).with('gem build *.gemspec') + expect(subject).to receive(:system).with("gem build #{gemspec_path}") + + subject.build_gem(project) + end + end + + describe('#artifacts') do + let(:gem_path) { File.join(project_path, 'foo-1.2.3.gem') } + + it('lists the project artifacts') do + result = subject.artifacts(project) - subject.build_gem + expect(result.length).to eql(1) + expect(result.first.to_local_path).to eql(gem_path) + expect(result.first.file_extension).to eql('gem') + expect(result.first.local?).to be true + expect(result.first.uri.scheme).to eql('file') end end end diff --git a/spec/lib/shanty/project_spec.rb b/spec/lib/shanty/project_spec.rb index d6d3e43..503889f 100644 --- a/spec/lib/shanty/project_spec.rb +++ b/spec/lib/shanty/project_spec.rb @@ -68,7 +68,7 @@ module Shanty it('publishes the event on any listening plugins') do allow(plugin).to receive(:subscribed?).and_return(true) - expect(plugin).to receive(:publish).with(:foo, :bar, :lux) + expect(plugin).to receive(:publish).with(:foo, subject, :bar, :lux) subject.publish(:foo, :bar, :lux) end @@ -84,9 +84,9 @@ module Shanty end end - describe('#artifact_paths') do - it('defaults the paths to an empty array') do - expect(subject.artifact_paths).to eql([]) + describe('#all_artifacts') do + it('defaults the artifacts to an empty array') do + expect(subject.all_artifacts).to eql([]) end end