├── VERSION ├── var ├── name ├── created ├── title ├── organizations ├── summary ├── authors ├── copyrights ├── repositories ├── requirements ├── description └── resources ├── lib ├── bang.yml ├── bang │ ├── minitest.rb │ └── testunit.rb └── bang.rb ├── Gemfile ├── .ruby ├── demo ├── applique │ └── brass.rb └── 01_bang.md ├── .gitignore ├── .yardopts ├── .travis.yml ├── Manifest.txt ├── HISTORY.md ├── Assembly ├── LICENSE.txt ├── .index ├── work └── ideas │ └── assay-based │ ├── bang.rb │ └── 01_bang.rdoc ├── README.md └── bang.gemspec /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | -------------------------------------------------------------------------------- /var/name: -------------------------------------------------------------------------------- 1 | bang 2 | -------------------------------------------------------------------------------- /lib/bang.yml: -------------------------------------------------------------------------------- 1 | ../.index -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2012-01-26 2 | -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | Bang! Bang! 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /var/organizations: -------------------------------------------------------------------------------- 1 | --- 2 | - Rubyworks 3 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Bang methods for assertions! 2 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Trans 3 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - 2012 Rubyworks (BSD-2-Clause) 3 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | ruby 1.9.3p327 (2012-11-10 revision 37606) [x86_64-linux] 2 | -------------------------------------------------------------------------------- /demo/applique/brass.rb: -------------------------------------------------------------------------------- 1 | require 'brass' 2 | require 'brass/expect' 3 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git@github.com:rubyworks/bang.git 3 | -------------------------------------------------------------------------------- /lib/bang/minitest.rb: -------------------------------------------------------------------------------- 1 | require 'brass/adapters/minitest' 2 | require 'bang' 3 | -------------------------------------------------------------------------------- /lib/bang/testunit.rb: -------------------------------------------------------------------------------- 1 | require 'brass/adapters/testunit' 2 | require 'bang' 3 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - brass 3 | - detroit (build) 4 | - qed (test) 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | doc 4 | log 5 | pkg 6 | tmp 7 | web 8 | work/sandbox 9 | *.lock 10 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "Bang! Bang!" 2 | --readme README.md 3 | --protected 4 | --private 5 | lib 6 | - 7 | [A-Z]*.* 8 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | Bang! Bang! provides a dynamic assertions framework utlizing bang 2 | methods and built to BRASS standards. 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec qed" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | - rbx-19mode 8 | - jruby-19mode 9 | 10 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/bang 3 | code: http://github.com/rubyworks/bang 4 | bugs: http://github.com/rubyworks/bang/issues 5 | mail: http://groups.google.com/groups/rubyworks-mailinglist 6 | 7 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | #!mast -x *.lock .index .yardopts bin data demo lib man spec test *.md *.rdoc *.txt 2 | .index 3 | .yardopts 4 | demo/01_bang.md 5 | demo/applique/brass.rb 6 | lib/bang/minitest.rb 7 | lib/bang/testunit.rb 8 | lib/bang.rb 9 | lib/bang.yml 10 | HISTORY.md 11 | README.md 12 | LICENSE.txt 13 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.2.0 / 2012-12-21 4 | 5 | This release modifies Bang! Bang! to rely on BRASS #assert and #refute 6 | methods directly. This simplifies the code a bit and takes care of 7 | tracking assertion counts automatically. 8 | 9 | Changes: 10 | 11 | * Use Brass #assert and #refute methods. 12 | * Access project metadata dynamically via const_missing. 13 | 14 | 15 | ## 0.1.0 / 2012-02-09 16 | 17 | This is the initial release of Bang. 18 | 19 | Changes: 20 | 21 | * Happy Birthday! 22 | 23 | -------------------------------------------------------------------------------- /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 | yard: 13 | yardopts: true 14 | priority: -1 15 | 16 | #qedoc: 17 | # files : demo/ 18 | # output: DEMO.md 19 | # title : Bang Demonstrations 20 | 21 | email: 22 | file : ~ 23 | subject : ~ 24 | mailto : 25 | - ruby-talk@ruby-lang.org 26 | - rubyworks-mailinglist@googlegroups.com 27 | 28 | vclog: 29 | output: 30 | - log/history.html 31 | - log/changes.html 32 | 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | type: ruby 3 | revision: 2013 4 | sources: 5 | - var 6 | - VERSION 7 | authors: 8 | - name: Trans 9 | email: transfire@gmail.com 10 | organizations: 11 | - name: Rubyworks 12 | requirements: 13 | - name: brass 14 | - groups: 15 | - build 16 | development: true 17 | name: detroit 18 | - groups: 19 | - test 20 | development: true 21 | name: qed 22 | conflicts: [] 23 | alternatives: [] 24 | resources: 25 | - type: home 26 | uri: http://rubyworks.github.com/bang 27 | label: Homepage 28 | - type: code 29 | uri: http://github.com/rubyworks/bang 30 | label: Source Code 31 | - type: bugs 32 | uri: http://github.com/rubyworks/bang/issues 33 | label: Issue Tracker 34 | - type: mail 35 | uri: http://groups.google.com/groups/rubyworks-mailinglist 36 | label: Mailing List 37 | repositories: 38 | - name: upstream 39 | scm: git 40 | uri: git@github.com:rubyworks/bang.git 41 | categories: [] 42 | paths: 43 | load: 44 | - lib 45 | copyrights: 46 | - holder: Rubyworks 47 | year: '2012' 48 | license: BSD-2-Clause 49 | created: '2012-01-26' 50 | summary: Bang methods for assertions! 51 | title: Bang! Bang! 52 | name: bang 53 | description: ! 'Bang! Bang! provides a dynamic assertions framework utlizing bang 54 | 55 | methods and built to BRASS standards.' 56 | version: 0.2.0 57 | date: '2012-12-21' 58 | -------------------------------------------------------------------------------- /work/ideas/assay-based/bang.rb: -------------------------------------------------------------------------------- 1 | require 'assay' 2 | 3 | # This module provides assertion mettods as object extensions in the 4 | # form of bang methods. 5 | # 6 | # Examples 7 | # 8 | # "string".instance_of!(String) 9 | # 10 | # "string".equal!("string") 11 | # 12 | # "string".not_equal!("another") 13 | # 14 | module Bang 15 | 16 | BANG_NAMES = { 17 | :kind => :kind_of, 18 | :instance => :instance_of, 19 | :executes => :satisfy # mabybe change this in Assay 20 | } 21 | 22 | BANG_ALIAS = { 23 | #:equal => [:identical] 24 | } 25 | 26 | # 27 | # Meta-programming routine for creating all the subjective methods. 28 | # 29 | def self.bootstrap 30 | Assertion.subclasses.each do |const| 31 | name = const.assertive_name.to_sym 32 | name = BANG_NAMES[name] || name 33 | 34 | aliases = BANG_ALIAS[name] 35 | 36 | [name, *aliases].each do |name| 37 | define_method("#{name}!") do |*args, &blk| 38 | const.assert!(self, *args, :backtrace=>caller, &blk) 39 | end 40 | 41 | define_method("not_#{name}!") do |*args, &blk| 42 | const.refute!(self, *args, :backtrace=>caller, &blk) 43 | end 44 | end 45 | end 46 | end 47 | 48 | # 49 | # Do it! 50 | # 51 | bootstrap 52 | 53 | # 54 | def thrown!(&block) 55 | ThrowAssay.assert!(self, :backtrace=>caller, &block) 56 | end 57 | 58 | # 59 | def not_thrown!(&block) 60 | ThrowAssay.refute!(self, :backtrace=>caller, &block) 61 | end 62 | 63 | # 64 | # 65 | def throws!(object) 66 | ThrowAssay.assert!(object, :backtrace=>caller, &self) 67 | end 68 | 69 | # 70 | module ForProc 71 | # 72 | # 73 | def raises!(exception) 74 | RaiseAssay.assert!(exception, :backtrace=>caller, &self) 75 | end 76 | 77 | # 78 | # @todo Better name? 79 | def rescues!(exception) 80 | RescueAssay.assert!(exception, :backtrace=>caller, &self) 81 | end 82 | end 83 | 84 | end 85 | 86 | class Object 87 | include Bang 88 | end 89 | 90 | class Proc 91 | include Bang::ForProc 92 | end 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bang! Bang! 2 | 3 | [Website](http://rubyworks.github.com/bang) / 4 | [Report Issue](http://github.com/rubyworks/bang/issues) / 5 | [Source Code](http://github.com/rubyworks/bang) 6 | ( [![Build Status](https://secure.travis-ci.org/rubyworks/bang.png)](http://travis-ci.org/rubyworks/bang) ) 7 | 8 |
9 | 10 | Bang! Bang! is a [BRASS](http://rubyworks.github.com/brass) compliant assertions 11 | framework with a very clever design that translates any bang call, e.g. `#foo!` 12 | into an assertion based on the corresponding query call if it exists, e.g. `#foo?`. 13 | In practice the framework is similar to MiniTest's spec methods, 14 | e.g. `#must_equal`, but the dynamic nature of Bang! Bang! makes it much more 15 | flexible, as it is not limited to a finite set of assertion methods. 16 | 17 | It's also pretty interesting idea that bang methods would be assertion methods. 18 | In general practice bang methods are usually used for methods that change the 19 | state of an object *in-place*. But this isn't particularly necessary and 20 | is just as well handled by non-bang methods, e.g. `#update` vs `#merge!`. 21 | 22 | 23 | ## Instructions 24 | 25 | Usage is pretty straight forward. 26 | 27 | ```ruby 28 | require 'bang' 29 | 30 | "This string".equals!("That string") #=> raises Bang::Assertion 31 | ``` 32 | 33 | To use Bang! Bang! most effectively with common test frameworks, you may need 34 | to load an adapter to ensure the framework recognizes the assertions as 35 | such rather than as ordinary errors. 36 | 37 | For MiniTest use: 38 | 39 | ```ruby 40 | require 'bang/minitest'` 41 | ``` 42 | 43 | For TestUnit use: 44 | 45 | ```ruby 46 | require 'bang/testunit' 47 | ``` 48 | 49 | An RSpec adapter is in the works. 50 | 51 | Cucumber does not require an adapter as it does not differentiate errors 52 | from assertions. 53 | 54 | Note, these adapters simply require the `brass/adapters/minitest` and 55 | `brass/adapters/testunit` respectively along with `bang`. So that's another 56 | way to do it too. 57 | 58 | 59 | ## Considerations 60 | 61 | Bang! Bang! works via a set of core extensions. There may be some concern 62 | about this approach for a test framework. I can assure you that the fear 63 | of the inaptly named "monkey patch" is very much overwrought. 64 | 65 | Even though Bang! Bang! adds a `#method_missing` call to the Object class, it is 66 | almost always okay to use because it does not get called if an object already has 67 | a bang method defined for it's own use. And when it does get called it only applies 68 | if a corresponding query (e.g. `foo?`) method exists. 69 | 70 | The other core extensions it adds are simply convenience methods that make testing 71 | easier. Because these are only additions and not overrides, it is perfectly safe to 72 | use in all but the most esoteric cases (such a heavy meta-programming). In fact, if 73 | a program doesn't work because of these core extensions, that's usually a good 74 | indication that something isn't being done right in the program itself. 75 | 76 | 77 | ## Copyrights 78 | 79 | Bang Bang is copyrighted open source software. 80 | 81 | Copyright (c) 2012 Rubyworks 82 | 83 | You can redistribute it in accordance to the [BSD-2-Clause](http://spdx.org/licenses/BSD-2-Clause) license. 84 | 85 | See the included LICENSE.txt file for details. 86 | -------------------------------------------------------------------------------- /demo/01_bang.md: -------------------------------------------------------------------------------- 1 | # Bangers 2 | 3 | require 'bang' 4 | 5 | ## true! 6 | 7 | true.true! 8 | 9 | expect Bang::Assertion do 10 | false.true! 11 | end 12 | 13 | false.not_true! 14 | 15 | ## false! 16 | 17 | false.false! 18 | 19 | expect Bang::Assertion do 20 | true.false! 21 | end 22 | 23 | true.not_false! 24 | 25 | ## nil! 26 | 27 | nil.nil! 28 | 29 | expect Bang::Assertion do 30 | true.nil! 31 | end 32 | 33 | true.not_nil! 34 | 35 | ## equal! 36 | 37 | 1.equal!(1) 38 | 39 | expect Bang::Assertion do 40 | 1.equal!(2) 41 | end 42 | 43 | 1.not_equal!(2) 44 | 45 | ## eql! 46 | 47 | 1.eql!(1) 48 | 49 | expect Bang::Assertion do 50 | 1.eql!(1.0) 51 | end 52 | 53 | 1.not_eql!(1.0) 54 | 55 | ## identical! 56 | 57 | Ruby's built-in query method for this is `#equal?`, but obviously that conflicts 58 | with our use of `#equal!` to test quality using `###`. So instead we use `#identical!` 59 | which makes more sense really. 60 | 61 | :a.identical!(:a) 62 | 63 | expect Bang::Assertion do 64 | "a".identical!("a") 65 | end 66 | 67 | :a.not_identical!('a') 68 | 69 | ## instance_of! 70 | 71 | 1.instance_of!(Fixnum) 72 | 73 | expect Bang::Assertion do 74 | 1.instance_of!(String) 75 | end 76 | 77 | 1.not_instance_of!(String) 78 | 79 | ## kind_of! 80 | 81 | 1.kind_of!(Integer) 82 | 83 | expect Bang::Assertion do 84 | 1.kind_of!(String) 85 | end 86 | 87 | 1.not_kind_of!(String) 88 | 89 | ## within! 90 | 91 | 1.within!(2, 1.5) 92 | 93 | expect Bang::Assertion do 94 | 1.within!(2, 0.5) 95 | end 96 | 97 | 1.not_within!(2, 0.5) 98 | 99 | ## close! 100 | 101 | 1.close!(2, 1.5) 102 | 103 | expect Bang::Assertion do 104 | 1.close!(1.2, 0.5) 105 | end 106 | 107 | 1.not_close!(5, 0.5) 108 | 109 | ## match! 110 | 111 | "abc".match!(/a/) 112 | 113 | expect Bang::Assertion do 114 | "abc".match!(/x/) 115 | end 116 | 117 | "abc".not_match!(/g/) 118 | 119 | ## respond_to! 120 | 121 | "string".respond_to!(:upcase) 122 | 123 | expect Bang::Assertion do 124 | "string".respond_to!(:not_a_method) 125 | end 126 | 127 | "string".not_respond_to!(:not_a_method) 128 | 129 | ## satisfy! 130 | 131 | 5.satisfy!{ |x| x > 3 } 132 | 133 | expect Bang::Assertion do 134 | 5.satisfy!{ |x| x < 3 } 135 | end 136 | 137 | 5.not_satisfy!{ |x| x < 3 } 138 | 139 | ## raises! 140 | 141 | ::ArgumentError.raised!{ raise ::ArgumentError } 142 | 143 | An extension to Proc class can also be used. 144 | 145 | procedure = lambda{ raise ::ArgumentError } 146 | 147 | procedure.raises!(::ArgumentError) 148 | 149 | expect Bang::Assertion do 150 | procedure.raises!(::TypeError) 151 | end 152 | 153 | ## rescues! 154 | 155 | ::TypeError.rescued!{ raise ::TypeError } 156 | 157 | An extension to Proc class can also be used. 158 | 159 | procedure = lambda{ raise ::TypeError } 160 | 161 | procedure.rescues!(::StandardError) 162 | 163 | expect Bang::Assertion do 164 | procedure.rescues!(::IOError) 165 | end 166 | 167 | ## throws! 168 | 169 | :foo.thrown!{ throw :foo } 170 | 171 | An extension to Proc class can also be used. 172 | 173 | procedure = lambda{ throw :foo } 174 | 175 | procedure.throws!(:foo) 176 | 177 | expect Bang::Assertion do 178 | procedure.throws!(:bar) 179 | end 180 | 181 | -------------------------------------------------------------------------------- /work/ideas/assay-based/01_bang.rdoc: -------------------------------------------------------------------------------- 1 | = Bangers 2 | 3 | require 'bang' 4 | 5 | == true! 6 | 7 | true.true! 8 | 9 | expect ::TrueAssay do 10 | false.true! 11 | end 12 | 13 | false.not_true! 14 | 15 | == false! 16 | 17 | false.false! 18 | 19 | expect ::FalseAssay do 20 | true.false! 21 | end 22 | 23 | true.not_false! 24 | 25 | == nil! 26 | 27 | nil.nil! 28 | 29 | expect ::NilAssay do 30 | true.nil! 31 | end 32 | 33 | true.not_nil! 34 | 35 | == equal! 36 | 37 | 1.equal!(1) 38 | 39 | expect ::EqualAssay do 40 | 1.equal!(2) 41 | end 42 | 43 | 1.not_equal!(2) 44 | 45 | == eql! 46 | 47 | 1.eql!(1) 48 | 49 | expect ::EqualityAssay do 50 | 1.eql!(1.0) 51 | end 52 | 53 | 1.not_eql!(1.0) 54 | 55 | == identical! 56 | 57 | Ruby's built-in query method for this is `#equal?`, but obviously that conflicts 58 | with our use of `#equal!` to test quality using `#==`. So instead we use `#identical!` 59 | which makes more sense really. 60 | 61 | :a.identical!(:a) 62 | 63 | expect ::IdentityAssay do 64 | "a".identical!("a") 65 | end 66 | 67 | :a.not_identical!('a') 68 | 69 | == instance_of! 70 | 71 | 1.instance_of!(Fixnum) 72 | 73 | expect ::InstanceAssay do 74 | 1.instance_of!(String) 75 | end 76 | 77 | 1.not_instance_of!(String) 78 | 79 | == kind_of! 80 | 81 | 1.kind_of!(Integer) 82 | 83 | expect ::KindAssay do 84 | 1.kind_of!(String) 85 | end 86 | 87 | 1.not_kind_of!(String) 88 | 89 | == within! 90 | 91 | 1.within!(2, 1.5) 92 | 93 | expect ::WithinAssay do 94 | 1.within!(2, 0.5) 95 | end 96 | 97 | 1.not_within!(2, 0.5) 98 | 99 | == close! 100 | 101 | 1.close!(2, 1.5) 102 | 103 | expect ::CloseAssay do 104 | 1.close!(1.2, 0.5) 105 | end 106 | 107 | 1.not_close!(5, 0.5) 108 | 109 | == match! 110 | 111 | "abc".match!(/a/) 112 | 113 | expect ::MatchAssay do 114 | "abc".match!(/x/) 115 | end 116 | 117 | "abc".not_match!(/g/) 118 | 119 | == respond_to! 120 | 121 | "string".respond_to!(:upcase) 122 | 123 | expect ::RespondAssay do 124 | "string".respond_to!(:not_a_method) 125 | end 126 | 127 | "string".not_respond_to!(:not_a_method) 128 | 129 | == satisfy! 130 | 131 | 5.satisfy!{ |x| x > 3 } 132 | 133 | expect ::ExecutionAssay do 134 | 5.satisfy!{ |x| x < 3 } 135 | end 136 | 137 | 5.not_satisfy!{ |x| x < 3 } 138 | 139 | == raises! 140 | 141 | ::ArgumentError.raised!{ raise ::ArgumentError } 142 | 143 | An extension to Proc class can also be used. 144 | 145 | procedure = lambda{ raise ::ArgumentError } 146 | 147 | procedure.raises!(::ArgumentError) 148 | 149 | expect ::RaiseAssay do 150 | procedure.raises!(::TypeError) 151 | end 152 | 153 | == rescues! 154 | 155 | ::ArgumentError.rescued!{ raise ::ArgumentError } 156 | 157 | An extension to Proc class can also be used. 158 | 159 | procedure = lambda{ raise ::ArgumentError } 160 | 161 | procedure.rescues!(::StandardError) 162 | 163 | expect ::RescueAssay do 164 | procedure.rescues!(::TypeError) 165 | end 166 | 167 | == throws! 168 | 169 | :foo.thrown!{ throw :foo } 170 | 171 | An extension to Proc class can also be used. 172 | 173 | procedure = lambda{ throw :foo } 174 | 175 | procedure.throws!(:foo) 176 | 177 | expect ::ThrowAssay do 178 | procedure.throws!(:bar) 179 | end 180 | 181 | -------------------------------------------------------------------------------- /bang.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 -------------------------------------------------------------------------------- /lib/bang.rb: -------------------------------------------------------------------------------- 1 | module Bang 2 | 3 | # Bang has brass balls. 4 | require 'brass' 5 | 6 | # 7 | # If constant is missing check for value in project metadata. 8 | # e.g. `Bang::VERSION`. 9 | # 10 | def self.const_missing(const_name) 11 | index[const_name.to_s.downcase] || super(const_name) 12 | end 13 | 14 | # 15 | # Access project metadata. 16 | # 17 | def self.index 18 | @index ||= ( 19 | require 'yaml' 20 | YAML.load_file(File.dirname(__FILE__) + '/bang.yml') 21 | ) 22 | end 23 | 24 | # Bang's assertion class. Follows standard set by BRASS project, 25 | # defining `#assertion?` method which return `true`. 26 | # 27 | class Assertion < ::Exception 28 | 29 | # 30 | # Piece together an Assetion error give the message used to 31 | # cause the assertion failure. 32 | # 33 | # @return [Assertion] Assertion instance. 34 | # 35 | def self.piece(s, a, b, t) 36 | e = new(message(s, *a, &b)) 37 | e.set_backtrace(t) 38 | e 39 | end 40 | 41 | # 42 | # Put together an error message representive of the assertion made. 43 | # 44 | # @todo Imporve this to better handle operators. 45 | # 46 | # @return [String] Failed assertion message. 47 | # 48 | def self.message(s, *a, &b) 49 | "#{s}(%s)" % a.map{ |e| e.inspect }.join(',') 50 | end 51 | 52 | # 53 | # Bang::Assertion is alwasy an assertion. 54 | # 55 | # @return [true] Always true. 56 | # 57 | def assertion? 58 | true 59 | end 60 | end 61 | 62 | # Mixin of Object class, will take any undefined bang method, e.g. `foo!` 63 | # and if there is a corresponding query method, e.g. `foo?`, then it will 64 | # utilize the query method to make an assertion. 65 | # 66 | module MethodMissing 67 | 68 | # 69 | # If missing method is a bang method, see if there is a corresponding query 70 | # method and use that to make an assertion. Will also recognize the same 71 | # prefixed by `not_`, e.g. `not_equal_to?`. 72 | # 73 | def method_missing(s, *a, &b) 74 | return super(s, *a, &b) unless s.to_s.end_with?('!') 75 | 76 | neg = false 77 | name = s.to_s.chomp('!') 78 | 79 | if name.start_with?('not_') 80 | neg = true 81 | name = name[4..-1] 82 | end 83 | 84 | meth = method("#{name}?") rescue nil 85 | 86 | return super(s, *a, &b) unless meth 87 | 88 | result = meth.call(*a, &b) 89 | 90 | if neg 91 | refute(result, Bang::Assertion.piece(s, a, b, caller)) 92 | else 93 | assert(result, Bang::Assertion.piece(s, a, b, caller)) 94 | end 95 | end 96 | 97 | end 98 | 99 | # Mixin for Object class that adds some very useful query methods. 100 | # 101 | module ObjectMixin 102 | 103 | # 104 | # Is `self` identical with `other`? In other words, do two variables 105 | # reference the one and the same object. 106 | # 107 | # @return [true,false] Whether `self` is identical to `other`. 108 | # 109 | def identical?(other) 110 | other.object_id == object_id 111 | end 112 | 113 | # 114 | # Query method for `#==`. We have to use the `_to` suffix becuase Ruby 115 | # already defines the prepositionless term as a synonym for `#identical?`. 116 | # (Hopefully that will change one day.) 117 | # 118 | # @return [true,false] Whether `self` is equal to `other`. 119 | # 120 | def equal_to?(other) 121 | other == self 122 | end 123 | 124 | # 125 | # Until we can use `#equal?`, lets make the best of it and offer 126 | # the plural form as well. 127 | # 128 | alias :equals? :equal_to? 129 | 130 | # 131 | # Test whether `self` is like `other`. Like is broad equality 132 | # measure testing `identical?`, `eql?`, `==` and `===`. 133 | # 134 | # @todo Should `like?` this include `=~` also? 135 | # 136 | # @return [true,false] Whether `self` is like `other`. 137 | # 138 | def like?(other) 139 | other.identical?(self) || 140 | other.eql?(self) || 141 | other.==(self) || 142 | other.===(self) 143 | end 144 | 145 | # 146 | # Test whether `other` is a case of `self` via `#===` method. 147 | # 148 | # @return [true,false] Whether `other` is a case of `self`. 149 | # 150 | def case?(other) 151 | other === self 152 | end 153 | 154 | # 155 | # Test whether `self` matches `other` via `#=~` method. 156 | # 157 | # @return [true,false] Whether `self` matches `other`. 158 | # 159 | def match?(other) 160 | other =~ self 161 | end 162 | 163 | # 164 | # Test whether `self` is the `true` instance. 165 | # 166 | # @return [true,false] Whether `self` is `true`. 167 | # 168 | def true? 169 | TrueClass === self 170 | end 171 | 172 | # 173 | # Test whether `self` is the `false` instance. 174 | # 175 | # @return [true,false] Whether `self` is `false`. 176 | # 177 | def false? 178 | FalseClass === self 179 | end 180 | 181 | # 182 | # Yield the given block and return `true` if the `self` is throw, 183 | # otherwise `false`. 184 | # 185 | # @return [true,false] Whether `self` was thrown. 186 | # 187 | def thrown?(&block) 188 | pass = true 189 | catch(self) do 190 | begin 191 | yield 192 | rescue ArgumentError => err # 1.9 exception 193 | #msg += ", not #{err.message.split(/ /).last}" 194 | rescue NameError => err # 1.8 exception 195 | #msg += ", not #{err.name.inspect}" 196 | end 197 | pass = false 198 | end 199 | pass 200 | end 201 | 202 | # 203 | # Yield block and return true if it runs without exception and does not 204 | # return `nil` or `false`. 205 | # 206 | # @return [true,false] True if block succeeds, otherwise false. 207 | # 208 | def satisfy?(&block) 209 | begin 210 | block.call(self) 211 | rescue 212 | false 213 | end 214 | end 215 | 216 | end 217 | 218 | # Mixin for Numeric class that adds `#within?` and `#close?`. 219 | # 220 | module NumericMixin 221 | 222 | # 223 | # Is this value within a given absolute `delta` of another? 224 | # 225 | # @return [true,false] True if within absolute delta, otherwise false. 226 | # 227 | def within?(other, delta) 228 | a, b, d = self.to_f, other.to_f, delta.to_f 229 | 230 | (b - d) <= a && (b + d) >= a 231 | end 232 | 233 | # 234 | # Is this value within a given relative `epsilon` of another? 235 | # 236 | # @return [true,false] True if within relative epsilon, otherwise false. 237 | # 238 | def close?(other, epsilon) 239 | a, b, e = self.to_f, other.to_f, epsilon.to_f 240 | 241 | d = b * e 242 | 243 | (b - d) <= a && (b + d) >= a 244 | end 245 | 246 | end 247 | 248 | # Mixin for Proc class that adds `#raises?`, `#rescues?` and `#throws?`. 249 | # 250 | module ProcMixin 251 | 252 | # 253 | # Execute this procedure and return `true` if the specific given `exception` 254 | # is raised, otherwise `false`. 255 | # 256 | # @return [true,false] Whether exception was raised. 257 | # 258 | def raises?(exception) 259 | begin 260 | call 261 | false 262 | rescue exception => err 263 | exception == err.class 264 | rescue Exception => err 265 | false 266 | end 267 | end 268 | 269 | # 270 | # Execute this procedure and return `true` if the given `exception`, 271 | # or subclass there-of is raised, otherwise `false`. 272 | # 273 | # @return [true,false] Whether exception was rescued. 274 | # 275 | def rescues?(exception) 276 | begin 277 | call 278 | false 279 | rescue exception => err 280 | true 281 | rescue Exception => err 282 | false 283 | end 284 | end 285 | 286 | # 287 | # Execute this procedure and return `true` if the given `object`, 288 | # is thrown, otherwise `false`. 289 | # 290 | # @return [true,false] Whether object was thrown. 291 | # 292 | def throws?(object) 293 | pass = true 294 | catch(object) do 295 | begin 296 | call 297 | rescue ArgumentError => err # 1.9 exception 298 | #msg += ", not #{err.message.split(/ /).last}" 299 | rescue NameError => err # 1.8 exception 300 | #msg += ", not #{err.name.inspect}" 301 | end 302 | pass = false 303 | end 304 | pass 305 | end 306 | 307 | end 308 | 309 | # Class-level extension for Exception class that adds `#raised?` and `#rescued?`. 310 | # 311 | module ExceptionMixin 312 | 313 | # 314 | # Yield a given block and return `true` if this exception specifically 315 | # is raised, otherwise `false`. 316 | # 317 | # @return [true,false] Whether exception is raised. 318 | # 319 | def raised? #:yield: 320 | begin 321 | yield 322 | false 323 | rescue self => err 324 | self == err.class 325 | rescue Exception 326 | false 327 | end 328 | end 329 | 330 | # 331 | # Yield a given block and return `true` if this exception, or a sub-class 332 | # there-of is raised, otherwise `false`. 333 | # 334 | # @return [true,false] Whether exception is rescued. 335 | # 336 | def rescued? #:yield: 337 | begin 338 | yield 339 | false 340 | rescue self 341 | true 342 | rescue Exception 343 | false 344 | end 345 | end 346 | 347 | end 348 | 349 | end 350 | 351 | class Object 352 | include Bang::MethodMissing 353 | include Bang::ObjectMixin 354 | end 355 | 356 | class Proc 357 | include Bang::ProcMixin 358 | end 359 | 360 | class Numeric 361 | include Bang::NumericMixin 362 | end 363 | 364 | class Exception 365 | extend Bang::ExceptionMixin 366 | end 367 | 368 | --------------------------------------------------------------------------------