├── var ├── name ├── title ├── version ├── created ├── summary ├── organizations ├── authors ├── copyrights ├── repositories ├── description ├── requirements └── resources ├── lib ├── roman.yml └── roman.rb ├── Gemfile ├── .gitignore ├── .travis.yml ├── etc └── test.rb ├── test ├── fixnum_case.rb ├── string_case.rb └── roman_case.rb ├── HISTORY.md ├── LICENSE.txt ├── .index ├── README.md └── .gemspec /var/name: -------------------------------------------------------------------------------- 1 | roman 2 | -------------------------------------------------------------------------------- /lib/roman.yml: -------------------------------------------------------------------------------- 1 | ../.index -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | Roman 2 | -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 0.2.0 2 | -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2010-03-02 2 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Real Roman Numerals 2 | -------------------------------------------------------------------------------- /var/organizations: -------------------------------------------------------------------------------- 1 | --- 2 | - Rubyworks 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Trans 3 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - (c) 2007 Rubyworks (BSD-2-Clause) 3 | 4 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/roman.git 3 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | RomanNumeral class for working with roman numerals in a native form. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ergo/digest 2 | .yardoc 3 | doc/ 4 | log/ 5 | pkg/ 6 | tmp/ 7 | web/ 8 | /QED.md 9 | *.lock 10 | *.gem 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec rubytest" 4 | rvm: 5 | - 1.9.3 6 | - rbx-19mode 7 | - jruby-19mode 8 | 9 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - detroit (build) 3 | - ergo (build) 4 | - rubytest-cli (test) 5 | - lemon (test) 6 | - ae (test) 7 | 8 | -------------------------------------------------------------------------------- /etc/test.rb: -------------------------------------------------------------------------------- 1 | Test.run do |r| 2 | r.requires << 'lemon' 3 | r.requires << 'ae' 4 | r.loadpath << 'lib' 5 | r.files << 'test/*_case.rb' 6 | end 7 | 8 | -------------------------------------------------------------------------------- /test/fixnum_case.rb: -------------------------------------------------------------------------------- 1 | require 'roman' 2 | 3 | TestCase Fixnum do 4 | 5 | Method :to_roman do 6 | test do 7 | 1.to_roman.assert == RomanNumeral.new(1) 8 | 2.to_roman.assert == RomanNumeral.new(2) 9 | end 10 | end 11 | 12 | end 13 | 14 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/roman 3 | code: http://github.com/rubyworks/roman 4 | docs: http://rubydoc.info/gems/roman 5 | wiki: http://wiki.github.com/rubyworks/roman 6 | bugs: http://github.com/rubyworks/roman/issues 7 | mail: http://groups.google.com/group/rubyworks-mailinglist 8 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | 4 | ## 0.2.0 / 2013-03-10 5 | 6 | This release fixes the conversion of lower-case letters. 7 | 8 | Changes: 9 | 10 | * Fixed conversion of lower-case letters. 11 | 12 | 13 | ## 0.1.0 / 2010-10-10 14 | 15 | Initialize release. 16 | 17 | Changes: 18 | 19 | * All of them ;) 20 | -------------------------------------------------------------------------------- /test/string_case.rb: -------------------------------------------------------------------------------- 1 | require 'roman' 2 | 3 | TestCase String do 4 | 5 | Method :to_roman do 6 | test "single digit conversions" do 7 | "I".to_roman.assert == RomanNumeral.new(1) 8 | "V".to_roman.assert == RomanNumeral.new(5) 9 | "X".to_roman.assert == RomanNumeral.new(10) 10 | 11 | "i".to_roman.assert == RomanNumeral.new(1) 12 | "v".to_roman.assert == RomanNumeral.new(5) 13 | "x".to_roman.assert == RomanNumeral.new(10) 14 | end 15 | 16 | test "simple mulit-digit conversions" do 17 | "IV".to_roman.assert == RomanNumeral.new(4) 18 | "VI".to_roman.assert == RomanNumeral.new(6) 19 | "VII".to_roman.assert == RomanNumeral.new(7) 20 | "VIII".to_roman.assert == RomanNumeral.new(8) 21 | "IX".to_roman.assert == RomanNumeral.new(9) 22 | "XI".to_roman.assert == RomanNumeral.new(11) 23 | "XII".to_roman.assert == RomanNumeral.new(12) 24 | "XIII".to_roman.assert == RomanNumeral.new(13) 25 | end 26 | 27 | test "complex conversions" do 28 | "XXIV".to_roman.assert == RomanNumeral.new(24) 29 | end 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (BSD-2-Clause License) 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | 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 notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | 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 COPYRIGHT 16 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 17 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 18 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 19 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 20 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | -------------------------------------------------------------------------------- /test/roman_case.rb: -------------------------------------------------------------------------------- 1 | require 'roman' 2 | 3 | TestCase RomanNumeral do 4 | 5 | Method :to_s do 6 | test "one to three" do 7 | 1.to_roman.to_s.assert == 'I' 8 | 2.to_roman.to_s.assert == 'II' 9 | 3.to_roman.to_s.assert == 'III' 10 | end 11 | end 12 | 13 | Method :to_s do 14 | test "four" do 15 | 4.to_roman.to_s.assert == 'IV' 16 | end 17 | end 18 | 19 | Method :to_s do 20 | test "five to eight" do 21 | 5.to_roman.to_s.assert == 'V' 22 | 6.to_roman.to_s.assert == 'VI' 23 | 7.to_roman.to_s.assert == 'VII' 24 | 8.to_roman.to_s.assert == 'VIII' 25 | end 26 | end 27 | 28 | Method :to_s do 29 | test "nine" do 30 | 9.to_roman.to_s.assert == 'IX' 31 | end 32 | end 33 | 34 | Method :to_s do 35 | test "ten to thriteen" do 36 | 10.to_roman.to_s.assert == 'X' 37 | 11.to_roman.to_s.assert == 'XI' 38 | 12.to_roman.to_s.assert == 'XII' 39 | 13.to_roman.to_s.assert == 'XIII' 40 | end 41 | end 42 | 43 | Method :to_s do 44 | test "fourteen plus" do 45 | 14.to_roman.to_s.assert == 'XIV' 46 | 15.to_roman.to_s.assert == 'XV' 47 | 16.to_roman.to_s.assert == 'XVI' 48 | end 49 | end 50 | 51 | Method :+ do 52 | test do 53 | (5.to_roman + 6).assert == 11 54 | (2.to_roman + 3.to_roman).assert == 5 55 | end 56 | end 57 | 58 | end 59 | 60 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - var 6 | authors: 7 | - name: Trans 8 | email: transfire@gmail.com 9 | organizations: 10 | - name: Rubyworks 11 | requirements: 12 | - groups: 13 | - build 14 | development: true 15 | name: detroit 16 | - groups: 17 | - build 18 | development: true 19 | name: ergo 20 | - groups: 21 | - test 22 | development: true 23 | name: rubytest-cli 24 | - groups: 25 | - test 26 | development: true 27 | name: lemon 28 | - groups: 29 | - test 30 | development: true 31 | name: ae 32 | conflicts: [] 33 | alternatives: [] 34 | resources: 35 | - type: home 36 | uri: http://rubyworks.github.com/roman 37 | label: Homepage 38 | - type: code 39 | uri: http://github.com/rubyworks/roman 40 | label: Source Code 41 | - type: docs 42 | uri: http://rubydoc.info/gems/roman 43 | label: Documentation 44 | - type: wiki 45 | uri: http://wiki.github.com/rubyworks/roman 46 | label: User Guide 47 | - type: bugs 48 | uri: http://github.com/rubyworks/roman/issues 49 | label: Issue Tracker 50 | - type: mail 51 | uri: http://groups.google.com/group/rubyworks-mailinglist 52 | label: Mailing List 53 | repositories: 54 | - name: upstream 55 | scm: git 56 | uri: git://github.com/rubyworks/roman.git 57 | categories: [] 58 | copyrights: 59 | - holder: Rubyworks 60 | year: '2007' 61 | license: BSD-2-Clause 62 | customs: [] 63 | paths: 64 | lib: 65 | - lib 66 | created: '2010-03-02' 67 | summary: Real Roman Numerals 68 | title: Roman 69 | version: 0.2.0 70 | name: roman 71 | description: RomanNumeral class for working with roman numerals in a native form. 72 | date: '2013-03-09' 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Roman Numerals 2 | 3 | [Website](http://rubyworks.github.com/roman) / 4 | [Documentation](http://rubydoc.info/gems/roman/frames) / 5 | [Report Issue](http://github.com/rubyworks/roman/issues) / 6 | [Source Code](http://github.com/rubyworks/roman)     7 | [![Flattr Me](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/324911/Rubyworks-Ruby-Development-Fund) 8 | 9 | ## STATUS 10 | 11 | [![Gem Version](https://badge.fury.io/rb/roman.png)](http://badge.fury.io/rb/roman) 12 | [![Build Status](https://travis-ci.org/rubyworks/roman.png)](https://travis-ci.org/rubyworks/roman) 13 | 14 | 15 | ## ABOUT 16 | 17 | The Roman library provides a full-fledged Numeric subclass 18 | for working in Roman Numerals. 19 | 20 | In most cases this library is probably overkill. Instead, 21 | [Ruby Facets](http://rubyworks.github.com/facets) provides Roman numeral 22 | core extension methods for the `Integer` and `String` classes which are 23 | likely to do everything that one really needs. However, the RomanNumeral 24 | class has one advantage. It can be maniputated like any other Numeric 25 | object without having to switch back and forth between representations. 26 | Moreover, and probably most importantly, it makes an excellect example of 27 | how to create a Numeric subclass in Ruby. 28 | 29 | 30 | ## HOW TO USE 31 | 32 | We can create Roman numerals via the usual instantiation. 33 | 34 | RomanNumeral.new(1) #=> # 35 | 36 | But to make it easier the Integer class is extended with #to_roman. 37 | 38 | 1.to_roman #=> # 39 | 40 | Roman numerals can also be formed from their String represnetation. 41 | 42 | RomanNumeral.new('I') #=> # 43 | 44 | 'I'.to_roman #=> # 45 | 46 | Because +RomanNumeral+ is a full-fledged subclass of Numeric, 47 | we can work with them like we can an Integer. 48 | 49 | a = 2.to_roman #=> # 50 | b = 3.to_roman #=> # 51 | r = a + b #=> # 52 | 53 | When we want to see the actual Romanized figure, we simple 54 | call #to_s. 55 | 56 | r.to_s #=> "IV" 57 | 58 | 59 | ## HOW TO INSTALL 60 | 61 | Using RubyGems: 62 | 63 | gem install roman 64 | 65 | 66 | ## COPYRIGHT & LICENSE 67 | 68 | Copyright (c) 2007 Rubyworks 69 | 70 | Roman is destributed under the terms of the 71 | [BSD-2-Clause](http://spdx.org/license/BSD-2-Clause) license. 72 | 73 | Please see LICENSE.txt file for details. 74 | -------------------------------------------------------------------------------- /lib/roman.rb: -------------------------------------------------------------------------------- 1 | # Work with Roman numerals just like normal Integers. 2 | class RomanNumeral < Numeric 3 | 4 | include Comparable 5 | 6 | # The largest integer representable as a roman 7 | # numerable by this module. 8 | MAX = 3999 9 | 10 | # Taken from O'Reilly's Perl Cookbook 6.23. Regular Expression Grabbag. 11 | REGEXP = /^M*(D?C{0,3}|C[DM])(L?X{0,3}|X[LC])(V?I{0,3}|I[VX])$/i 12 | 13 | # 14 | ROMAN_VALUES_ASSOC = [ 15 | ["M", 1000], 16 | ["CM", 900], 17 | ["D", 500], 18 | ["CD", 400], 19 | ["C", 100], 20 | ["XC", 90], 21 | ["L", 50], 22 | ["XL", 40], 23 | ["X", 10], 24 | ["IX", 9], 25 | ["V", 5], 26 | ["IV", 4], 27 | ["I", 1] 28 | ] 29 | 30 | # 31 | ROMAN_VALUES = ROMAN_VALUES_ASSOC.inject({}){ |h,(r,a)| h[r] = a; h } 32 | 33 | # 34 | def self.from_integer(int) 35 | #return nil if integer > MAX 36 | return "-#{(-int).roman}" if int < 0 37 | return "" if int == 0 38 | ROMAN_VALUES_ASSOC.each do |(i, v)| 39 | return(i + from_integer(int-v)) if v <= int 40 | end 41 | end 42 | 43 | # 44 | def self.to_integer(roman) 45 | #return nil unless roman_string.is_roman_numeral? 46 | last = roman[-1,1].upcase 47 | roman.reverse.split('').inject(0) do |result, c| 48 | c = c.upcase 49 | if ROMAN_VALUES[c] < ROMAN_VALUES[last] 50 | result -= ROMAN_VALUES[c] 51 | else 52 | last = c 53 | result += ROMAN_VALUES[c] 54 | end 55 | end 56 | end 57 | 58 | # Returns true if +string+ is a roman numeral. 59 | def self.is_roman_numeral?(string) 60 | REGEXP =~ string 61 | end 62 | 63 | 64 | # Create a new instance from an Integer, String 65 | # or other RomanNumeral. 66 | def initialize(val) 67 | case val 68 | when String 69 | @i = self.class.to_integer(val) 70 | @s = val.frozen? ? val : val.dup.freeze 71 | when Numeric 72 | @i = val.to_i 73 | else 74 | raise ArgumentError, 'Cannot convert %p' % val 75 | end 76 | end 77 | 78 | # hash code calculation 79 | 80 | def hash 81 | @i.hash 82 | end 83 | 84 | # 85 | def to_s 86 | @s ||= self.class.from_integer(@i) 87 | end 88 | 89 | # 90 | def to_num ; @i ; end 91 | 92 | # 93 | def to_i ; @i ; end 94 | 95 | # 96 | def to_int ; @i ; end 97 | 98 | # 99 | def to_arabic ; @i ; end 100 | 101 | # 102 | def to_roman 103 | self 104 | end 105 | 106 | # 107 | def eql?(num) 108 | self.class.equal?(num.class) && @i == num.to_i 109 | end 110 | 111 | # 112 | def ==(num) 113 | @i == num.to_num 114 | end 115 | 116 | # 117 | def coerce(o) 118 | [self.class.new(o.to_int), self] 119 | end 120 | 121 | # 122 | def <=>(o) 123 | case o 124 | when Numeric 125 | @i <=> o.to_num 126 | else 127 | a, b = o.coerce(self) 128 | a <=> b 129 | end rescue nil 130 | end 131 | 132 | # various tests that Fixnum also has 133 | 134 | def zero? 135 | @i.zero? 136 | end 137 | 138 | def nonzero? 139 | @i.nonzero? 140 | end 141 | 142 | def odd? 143 | @i.odd? 144 | end 145 | 146 | def even? 147 | @i.even? 148 | end 149 | 150 | def between?(a, b) 151 | @i.between? a, b 152 | end 153 | 154 | def integer? 155 | true 156 | end 157 | 158 | def real? 159 | true 160 | end 161 | 162 | def +@ 163 | self 164 | end 165 | 166 | def -@ 167 | self.class.new(-@i) 168 | end 169 | 170 | # 171 | def +(o) 172 | if Numeric === o 173 | self.class.new(@i + o.to_num) 174 | else 175 | a, b = o.coerce(self) 176 | a + b 177 | end 178 | end 179 | 180 | # 181 | def -(o) 182 | if Numeric === o 183 | self.class.new(@i - o.to_num) 184 | else 185 | a, b = o.coerce(self) 186 | a - b 187 | end 188 | end 189 | 190 | # 191 | def *(o) 192 | if Numeric === o 193 | self.class.new(@i * o.to_num) 194 | else 195 | a, b = o.coerce(self) 196 | a * b 197 | end 198 | end 199 | 200 | # 201 | def /(o) 202 | if Numeric === o 203 | self.class.new(@i / o.to_num) 204 | else 205 | a, b = o.coerce(self) 206 | a / b 207 | end 208 | end 209 | 210 | def **(o) 211 | if Numeric === o 212 | self.class.new(@i / o.to_num) 213 | else 214 | a, b = o.coerce(self) 215 | a / b 216 | end 217 | end 218 | 219 | def <<(o) 220 | self.class.new(@i << o.to_int) 221 | end 222 | 223 | def >>(o) 224 | self.class.new(@i >> o.to_int) 225 | end 226 | 227 | # bit operators 228 | 229 | def &(o) 230 | self.class.new(@i & o.to_int) 231 | end 232 | 233 | def |(o) 234 | self.class.new(@i | o.to_int) 235 | end 236 | 237 | # Freeze 238 | def freeze 239 | to_s 240 | super 241 | end 242 | 243 | end 244 | 245 | 246 | class Integer 247 | 248 | # 249 | def self.from_roman(roman) 250 | RomanNumeral.to_integer(roman) 251 | end 252 | 253 | # Converts this integer to a roman numeral. 254 | def to_roman 255 | RomanNumeral.new(self) 256 | end 257 | 258 | end 259 | 260 | 261 | class String 262 | 263 | # Considers string a roman numeral numeral, 264 | # and converts it to the corresponding integer. 265 | def to_roman 266 | RomanNumeral.new(self) 267 | end 268 | 269 | # Returns true if a valid roman numeral. 270 | def is_roman_numeral? 271 | RomanNumeral.is_roman_numeral?(self) 272 | end 273 | 274 | end 275 | 276 | 277 | class Numeric 278 | 279 | # 280 | def to_num 281 | self 282 | end 283 | 284 | end 285 | 286 | -------------------------------------------------------------------------------- /.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 --------------------------------------------------------------------------------