├── .yardopts ├── .gitignore ├── Gemfile ├── etc ├── qed.rb └── test.rb ├── .travis.yml ├── test ├── case_brass.rb └── run.rb ├── MANIFEST ├── work └── sandbox │ └── testunit │ └── test_example.rb ├── Assembly ├── lib ├── brass │ ├── expect.rb │ └── adapters │ │ ├── minitest.rb │ │ ├── testunit.rb │ │ └── testunit1.rb └── brass.rb ├── Indexfile ├── VERSIONS.md ├── LICENSE.txt ├── .index ├── README.md └── .gemspec /.yardopts: -------------------------------------------------------------------------------- 1 | --title "BRASS" 2 | --readme README.md 3 | --protected 4 | --private 5 | lib 6 | - 7 | [A-Z]*.* 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | doc 4 | log 5 | pkg 6 | site 7 | tmp 8 | web 9 | wiki 10 | work/trash 11 | QED.rdoc 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :build do 4 | gem "detroit" 5 | end 6 | 7 | group :test do 8 | gem "lemon" 9 | gem "rubytest" 10 | end 11 | 12 | -------------------------------------------------------------------------------- /etc/qed.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Create coverage report. 4 | QED.configure 'cov' do 5 | require 'simplecov' 6 | SimpleCov.start do 7 | coverage_dir 'log/coverage' 8 | #add_group "RSpec", "lib/assay/rspec.rb" 9 | end 10 | end 11 | 12 | -------------------------------------------------------------------------------- /etc/test.rb: -------------------------------------------------------------------------------- 1 | require 'lemon' 2 | 3 | Test.run(:default) do |run| 4 | run.files << 'test/case_*.rb' 5 | end 6 | 7 | Test.run(:cov) do |run| 8 | run.files << 'test/case_*.rb' 9 | SimpleCov.start do |cov| 10 | cov.coverage_dir = 'log/coverage' 11 | end 12 | end 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec ruby test/run.rb" 4 | rvm: 5 | - 1.8.7 6 | - 1.9.3 7 | - 2.1.0 8 | - jruby 9 | - ree 10 | - rbx 11 | - rbx-2 12 | matrix: 13 | allow_failures: 14 | - rvm: rbx 15 | - rvm: rbx-2 16 | cache: bundler 17 | -------------------------------------------------------------------------------- /test/case_brass.rb: -------------------------------------------------------------------------------- 1 | covers 'brass' 2 | 3 | testcase Kernel do 4 | 5 | method :assert do 6 | 7 | test do 8 | assert(true) 9 | end 10 | 11 | end 12 | 13 | method :refute do 14 | 15 | test do 16 | refute(false) 17 | end 18 | 19 | end 20 | 21 | end 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .index .yardopts bin lib spec test *.txt *.md 2 | .index 3 | .yardopts 4 | lib/brass/adapters/minitest.rb 5 | lib/brass/adapters/testunit.rb 6 | lib/brass/adapters/testunit1.rb 7 | lib/brass/expect.rb 8 | lib/brass.rb 9 | test/case_brass.rb 10 | test/run.rb 11 | LICENSE.txt 12 | VERSIONS.md 13 | README.md 14 | -------------------------------------------------------------------------------- /work/sandbox/testunit/test_example.rb: -------------------------------------------------------------------------------- 1 | require 'test-unit' 2 | require 'brass' 3 | require 'brass/adapters/testunit' 4 | 5 | class ExampleTest < Test::Unit::TestCase 6 | 7 | def test_example 8 | e = StandardError.new('abstract brass assertion') 9 | e.set_assertion(true) 10 | raise e 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /test/run.rb: -------------------------------------------------------------------------------- 1 | require 'rubytest' 2 | require 'lemon' 3 | require 'rubytest/autorun' 4 | 5 | Test.run do |run| 6 | run.loadpath 'lib' 7 | run.files << 'test/case_*.rb' 8 | end 9 | 10 | Test.run(:cov) do |run| 11 | run.loadpath 'lib' 12 | run.files << 'test/case_*.rb' 13 | require 'simplecov' 14 | SimpleCov.start do |cov| 15 | cov.coverage_dir = 'log/coverage' 16 | end 17 | end 18 | 19 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | gh_pages: web 4 | 5 | gem: 6 | active: true 7 | 8 | dnote: 9 | title: Source Notes 10 | output: log/notes.html 11 | 12 | vclog: 13 | output: 14 | - log/history.html 15 | - log/changes.html 16 | 17 | email: 18 | mailto: 19 | - ruby-talk@ruby-lang.org 20 | - rubyworks-mailinglist@googlegroups.com 21 | 22 | qed: 23 | files: spec/*.rdoc 24 | active: false 25 | 26 | qedoc: 27 | files: spec/*.rdoc 28 | output: 29 | - QED.rdoc 30 | - web/demos.html 31 | active: false 32 | 33 | -------------------------------------------------------------------------------- /lib/brass/expect.rb: -------------------------------------------------------------------------------- 1 | module Kernel 2 | 3 | # TODO: Should `#expect` method be part of standard? 4 | 5 | # Executate a block asserting that a type of error will be raised. 6 | # 7 | # Presently this is not part of brass by default, as whether it should 8 | # be is under debate. So this file must be required separately: 9 | # 10 | # require 'brass/expect' 11 | # 12 | def expect(error_class) #:yield: 13 | begin 14 | yield 15 | assert(false, error_class, "#{error_class} expected but none thrown") 16 | rescue error_class 17 | assert(true) 18 | rescue Exception => err 19 | assert(false, error_class, "#{error_class} expected but #{err.class} was thrown") 20 | end 21 | end 22 | 23 | end 24 | 25 | -------------------------------------------------------------------------------- /Indexfile: -------------------------------------------------------------------------------- 1 | --- 2 | name: brass 3 | version: 1.2.1 4 | 5 | title: BRASS 6 | summary: Bare-metal Ruby Assertion System Standard 7 | 8 | description: 9 | BRASS stands for Bare-Metal Ruby Assertion System Standard. It is a very basic 10 | foundational assertions framework for other assertion and test frameworks 11 | to make use so they can all work together harmoniously. 12 | 13 | resources: 14 | home: http://rubyworks.github.com/brass 15 | docs: http://rubydoc.info/gems/brass 16 | code: http://github.com/rubyworks/brass 17 | mail: http://groups.google.com/groups/rubyworks-mailinglist 18 | 19 | repositories: 20 | upstream: git@github.com:rubyworks/brass.git 21 | 22 | authors: 23 | - Thomas Sawyer 24 | 25 | organization: Rubyworks 26 | 27 | created: 28 | 2012-01-24 29 | 30 | copyrights: 31 | - 2012 Rubyworks (BSD-2-Clause) 32 | 33 | -------------------------------------------------------------------------------- /lib/brass/adapters/minitest.rb: -------------------------------------------------------------------------------- 1 | module MiniTest #:nodoc: 2 | class Unit #:nodoc: 3 | # To teach MiniTest to recognize the expanded concept of assertions 4 | # we add in an extra capture clause to the it's #puke method. 5 | def puke c, m, x 6 | case x 7 | when MiniTest::Skip 8 | @skips = @skips + 1 9 | x = "Skipped:\n#{m}(#{c}) [#{location x}]:\n#{x.message}\n" 10 | when MiniTest::Assertion 11 | @failures = @failures + 1 12 | x = "Failure:\n#{m}(#{c}) [#{location x}]:\n#{x.message}\n" 13 | when x.respond_to?(:assertion?) && x.assertion? 14 | @failures = @failures + 1 15 | x = "Failure:\n#{m}(#{c}) [#{location x}]:\n#{x.message}\n" 16 | else 17 | @errors = @errors + 1 18 | b = MiniTest::filter_backtrace(x.backtrace).join("\n ") 19 | x = "Error:\n#{m}(#{c}):\n#{x.class}: #{x.message}\n #{b}\n" 20 | end 21 | @report << x 22 | x[0, 1] 23 | end 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /VERSIONS.md: -------------------------------------------------------------------------------- 1 | # HISTORY 2 | 3 | ## 1.2.1 | 2012-02-09 4 | 5 | This release adds framework adapters for MiniTest and TestUnit, which allows 6 | those frameworks to recognize BRASS assertions as "failures" rather than 7 | as "errors". 8 | 9 | Changes: 10 | 11 | * Add framework adapters for MiniTest and TestUnit. 12 | 13 | 14 | ## 1.2.0 | 2012-01-26 15 | 16 | The default error is `RuntimeError` rather than `StandardError` to 17 | match Ruby's default exception when no arguments are passed to `raise`. 18 | 19 | Changes: 20 | 21 | * Change default error to RuntimeError. 22 | 23 | 24 | ## 1.1.0 | 2012-01-25 25 | 26 | Quick fix for issue with parsing arguments of #assert and #refute methods. 27 | Also added #fail! method to dry-up code. 28 | 29 | Changes: 30 | 31 | * Fix arguments parsing for assert/refute. 32 | * Add #fail! method to dry-up code. 33 | 34 | 35 | ## 1.0.0 | 2012-01-24 36 | 37 | This is the initial release of BRASS. It is given a 1.0.0 version 38 | despite being such a new project because it is very simple and 39 | the code is derived from other system that have used it for some time. 40 | 41 | Changes: 42 | 43 | * Happy Birthday! 44 | 45 | -------------------------------------------------------------------------------- /lib/brass/adapters/testunit.rb: -------------------------------------------------------------------------------- 1 | module Test #:nodoc: 2 | module Unit #:nodoc: 3 | 4 | module BrassAssertionHandler 5 | class << self 6 | def included(base) 7 | base.exception_handler(:handle_brass_assertion_failed_error) 8 | end 9 | end 10 | 11 | private 12 | def handle_brass_assertion_failed_error(exception) 13 | return false unless exception.assertion? 14 | problem_occurred 15 | #parameters = exception.parameters # TODO: assertion parameters 16 | add_brass_failure exception.message, exception.backtrace, 17 | #:expected => exception.expected, 18 | #:actual => exception.actual, 19 | #:inspected_expected => exception.inspected_expected, 20 | #:inspected_actual => exception.inspected_actual, 21 | :user_message => exception.message #exception.user_message 22 | true 23 | end 24 | 25 | def add_brass_failure(message, backtrace, options={}) 26 | failure = Failure.new(name, filter_backtrace(backtrace), message, options) 27 | current_result.add_failure(failure) 28 | end 29 | end 30 | 31 | class TestCase #:nodoc: 32 | include BrassAssertionHandler 33 | end 34 | end 35 | end 36 | 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (BSD-2-Clause License) 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /lib/brass/adapters/testunit1.rb: -------------------------------------------------------------------------------- 1 | module Test #:nodoc: 2 | module Unit #:nodoc: 3 | class TestCase #:nodoc: 4 | # Runs the individual test method represented by this 5 | # instance of the fixture, collecting statistics, failures 6 | # and errors in result. 7 | def run(result) 8 | yield(STARTED, name) 9 | @_result = result 10 | begin 11 | setup 12 | __send__(@method_name) 13 | rescue AssertionFailedError => e 14 | add_failure(e.message, e.backtrace) 15 | rescue Exception => e 16 | if e.respond_to?(:assertion?) && e.assertion? 17 | add_failure(e.message, e.backtrace) 18 | else 19 | raise if PASSTHROUGH_EXCEPTIONS.include? $!.class 20 | add_error($!) 21 | end 22 | ensure 23 | begin 24 | teardown 25 | rescue AssertionFailedError => e 26 | add_failure(e.message, e.backtrace) 27 | rescue Exception => e 28 | if e.respond_to?(:assertion?) && e.assertion? 29 | add_failure(e.message, e.backtrace) 30 | else 31 | raise if PASSTHROUGH_EXCEPTIONS.include? $!.class 32 | add_error($!) 33 | end 34 | end 35 | end 36 | result.add_run 37 | yield(FINISHED, name) 38 | end 39 | end 40 | end 41 | end 42 | 43 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - Indexfile 6 | - Gemfile 7 | authors: 8 | - name: Thomas Sawyer 9 | email: transfire@gmail.com 10 | organizations: 11 | - name: Rubyworks 12 | requirements: 13 | - groups: 14 | - build 15 | version: '>= 0' 16 | name: detroit 17 | - groups: 18 | - test 19 | version: '>= 0' 20 | name: lemon 21 | - groups: 22 | - test 23 | version: '>= 0' 24 | name: rubytest 25 | conflicts: [] 26 | alternatives: [] 27 | resources: 28 | - type: home 29 | uri: http://rubyworks.github.com/brass 30 | label: Homepage 31 | - type: docs 32 | uri: http://rubydoc.info/gems/brass 33 | label: Documentation 34 | - type: code 35 | uri: http://github.com/rubyworks/brass 36 | label: Source Code 37 | - type: mail 38 | uri: http://groups.google.com/groups/rubyworks-mailinglist 39 | label: Mailing List 40 | repositories: 41 | - name: upstream 42 | scm: git 43 | uri: git@github.com:rubyworks/brass.git 44 | categories: [] 45 | copyrights: 46 | - holder: Rubyworks 47 | year: '2012' 48 | license: BSD-2-Clause 49 | customs: [] 50 | paths: 51 | lib: 52 | - lib 53 | name: brass 54 | title: BRASS 55 | version: 1.2.1 56 | summary: Bare-metal Ruby Assertion System Standard 57 | description: BRASS stands for Bare-Metal Ruby Assertion System Standard. It is a very 58 | basic foundational assertions framework for other assertion and test frameworks 59 | to make use so they can all work together harmoniously. 60 | created: '2012-01-24' 61 | date: '2014-07-18' 62 | -------------------------------------------------------------------------------- /lib/brass.rb: -------------------------------------------------------------------------------- 1 | class Exception 2 | 3 | # Is the exception an assertion? 4 | def assertion? 5 | @assertion 6 | end 7 | 8 | # Set the the exception's assertion flag. 9 | def set_assertion(boolean) 10 | @assertion = boolean # ? true : false 11 | end 12 | 13 | # Set message. 14 | # (not strictly needed here, but can be useful anyway). 15 | # 16 | # @todo Does the message have to be a string? 17 | def set_message(msg) 18 | @mesg = msg.to_str 19 | end 20 | 21 | # Set priority level. 22 | # 23 | # @param [Integer] level 24 | # The priority level of the exception. 25 | # 26 | def set_priority(level) 27 | @priority = level.to_i 28 | end 29 | 30 | # Exceptions can have priority levels. 31 | # 32 | # Zero is a nominal error, the higher the priority the more severe the 33 | # error is considered. Errors with priorities less than zero are taken 34 | # be warnings, the lower the number the more trivial. 35 | # 36 | def priority 37 | @priority ||= 0 38 | end 39 | 40 | # TODO: Consider assertion parameters for future vresion. If message 41 | # does not have to be string, it might serve this purpose instead. 42 | ## 43 | #def parameters 44 | # @parameters 45 | #end 46 | # 47 | ## Set exception parameters. These are used to store specific information 48 | ## relavent to a particular exception or assertion. Unlike the message, 49 | ## which is a String, this is a Hash. 50 | #def set_parameters(parameters) 51 | # @parameters = parameters.to_hash 52 | #end 53 | end 54 | 55 | module Kernel 56 | 57 | # Track assertions counts. 58 | $ASSERTION_COUNTS ||= Hash.new{ |h,k| h[k] = 0 } 59 | 60 | # 61 | # Universal assertion method. 62 | # 63 | def assert(truth, *raise_arguments) 64 | $ASSERTION_COUNTS[:total] += 1 65 | if truth 66 | $ASSERTION_COUNTS[:pass] += 1 67 | else 68 | $ASSERTION_COUNTS[:fail] += 1 69 | # if fail set assertion=true then just, 70 | # fail *raise_arguments 71 | # but alas ... 72 | fail! *raise_arguments 73 | end 74 | end 75 | 76 | module_function :assert 77 | 78 | # 79 | # Universal refutation method (opposite of `#assert`). 80 | # 81 | def refute(truth, *raise_arguments) 82 | $ASSERTION_COUNTS[:total] += 1 83 | if truth 84 | $ASSERTION_COUNTS[:fail] += 1 85 | # if fail set assertion=true then just, 86 | # fail *raise_arguments 87 | # but alas ... 88 | fail! *raise_arguments 89 | else 90 | $ASSERTION_COUNTS[:pass] += 1 91 | end 92 | end 93 | 94 | module_function :refute 95 | 96 | # 97 | # Alternate for #fail that also sets assertion flag to +true+. 98 | # 99 | def fail!(*raise_arguments) 100 | backtrace = case raise_arguments.last 101 | when Array 102 | raise_arguments.pop 103 | else 104 | nil 105 | end 106 | 107 | exception = case raise_arguments.first 108 | when Exception 109 | raise_arguments.shift 110 | when Class 111 | raise ArgumentError unless Exception > raise_arguments.first 112 | error_class = raise_arguments.shift 113 | error_class.new(*raise_arguments) 114 | else 115 | error_class = $! || RuntimeError 116 | error_class.new(*raise_arguments) 117 | end 118 | 119 | exception.set_backtrace(backtrace) if backtrace 120 | exception.set_assertion(true) 121 | 122 | fail exception 123 | end 124 | 125 | module_function :fail! 126 | 127 | private :assert 128 | private :refute 129 | private :fail! 130 | 131 | end 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BRASS 2 | 3 | [Website](http://rubyworks.github.com/brass) | 4 | [Report Issue](http://github.com/rubyworks/brass/issues) | 5 | [Development](http://github.com/rubyworks/brass) 6 | 7 | BRASS is a standard assertions framework, framework's framework, or even 8 | a framework's framework's framework, depending on where you are staking 9 | out your assertions keister. In other words, BRASS provides a foundational 10 | assertions framework which all other assertion and test frameworks 11 | can use, or at least comply with, and then everyone gets on swimingly. 12 | 13 | 14 | ## Status 15 | 16 | [![Gem Version](http://img.shields.io/gem/v/brass.svg?style=flat)](http://rubygems.org/gem/brass) 17 | [![Build Status](http://img.shields.io/travis/rubyworks/brass.svg?style=flat)](http://travis-ci.org/rubyworks/brass) 18 | [![Fork Me](http://img.shields.io/badge/scm-github-blue.svg?style=flat)](http://github.com/rubyworks/brass) 19 | [![Report Issue](http://img.shields.io/github/issues/rubyworks/brass.svg?style=flat)](http://github.com/rubyworks/brass/issues) 20 | [![Gittip](http://img.shields.io/badge/gittip-$1/wk-green.svg?style=flat)](https://www.gittip.com/on/github/rubyworks/) 21 | 22 | 23 | ## Overview 24 | 25 | BRASS is a very basic assertions framework. It is designed to provide only 26 | the barest essentials for making assertions such that other assertion and 27 | test frameworks can use it, or at least comply with it, making all said 28 | frameworks interoperable. 29 | 30 | Now, you may be thinking, "No thanks, I do it my way." But when you see 31 | how stupid simple BRASS is, you'll realize that, "Yea, that actually 32 | make sense." And then maybe feel a bit stupid yourself for doing exactly 33 | what this paragraph just said you would do. Yea, well, we've all been there. 34 | 35 | But enough with the fluff. 36 | 37 | BRASS defines two Kernel methods: `assert` and `refute`: 38 | 39 | assert(truthiness, *fail_arguments) 40 | refute(truthiness, *fail_arguments) 41 | 42 | Where `truthiness` is any object evaluated for it's truth value (`false` and `nil` 43 | are `false`, everything else is `true`), and `fail_arguments` are exactly the same 44 | as those we would pass to the `fail` or `raise` methods. 45 | 46 | The `assert` (and likewise `refute`) method does three things. First it tests the 47 | `truthiness`, then it ticks up the appropriate counts in the global assertions 48 | counter, and lastly, if truthiness came up short, it raises an error. This error 49 | is either `RuntimeError` or the one specified by the `fail_arguments`. 50 | 51 | The global assertions counter is `$ASSERTION_COUNTS`. It is simply a Hash formally 52 | defined as: 53 | 54 | $ASSERTIONS_COUNTS = Hash.new{|h,k| h[k] = 0} 55 | 56 | And though it is open to any key, the keys should be symbols. Three keys in 57 | particular and standard: `:pass`, `:fail` and `:total`. Whenever an assertion 58 | passes, the `:pass` and `:total` counts are incremented, and whenever an assertion 59 | fails the `:fail` and `:total` counts are incremented. You might wonder why 60 | there is a total entry when the sum of the other two would do just as well. 61 | Well, other frameworks might want to add other counts, such as `:skip`. So 62 | to ensure we still get the proper total despite this, we keep a separate tally. 63 | Moving on.... 64 | 65 | Okay, last thing. When `assert` or `refute` raises an error, it marks the error 66 | as an assertion via the `#set_assertion` method. This is a method extension 67 | to the Exception class along with the `#assertion?` method which any 68 | test framework can use to distinguish an assertion error from an ordinarily 69 | error. 70 | 71 | And that's all there is to it. If you *capice* then consider yourself top brass. 72 | 73 | Love, Peace and Brass Knuckles 74 | 75 | 76 | ## Contributing 77 | 78 | This project is so basic it is unlikely anything will even change. But you 79 | never can tell! so if your have a brilliant idea, jump right in. Source 80 | code management is handled by ye ol'Github at http://github.com/rubyworks/brass. 81 | 82 | 83 | ## Copyrights 84 | 85 | Copyright (c) 2010 Rubyworks. All rights reserved. 86 | 87 | Distribute in accordance with the **BSD-2-Clause** license. 88 | 89 | See LICENSE.txt file for details. 90 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec --------------------------------------------------------------------------------