├── lib ├── crypt3.yml └── crypt3.rb ├── var ├── name ├── title ├── version ├── created ├── organizations ├── summary ├── copyrights ├── repositories ├── authors ├── requirements ├── description └── resources ├── work └── released ├── Gemfile ├── Rakefile ├── .gitignore ├── .yardopts ├── .travis.yml ├── MANIFEST ├── task └── test.rake ├── Assembly ├── test └── test_crypt3.rb ├── HISTORY.md ├── .index ├── LICENSE.txt ├── README.md └── .gemspec /lib/crypt3.yml: -------------------------------------------------------------------------------- 1 | ../.index -------------------------------------------------------------------------------- /var/name: -------------------------------------------------------------------------------- 1 | crypt3 2 | -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | Crypt3 2 | -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 1.1.6 2 | -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2002-06-01 2 | -------------------------------------------------------------------------------- /work/released: -------------------------------------------------------------------------------- 1 | 2009-10-23 2 | -------------------------------------------------------------------------------- /var/organizations: -------------------------------------------------------------------------------- 1 | --- 2 | - Rubyworks 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Crypt3 is a ruby version of crypt(3) 2 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | Dir['task/*.rake'].each { |f| import f } 3 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | --- 2 | - 2002 (c) Poul-Henning Kamp (BSD-2-Clause) 3 | 4 | -------------------------------------------------------------------------------- /var/repositories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/crypt3.git 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | log 4 | doc 5 | pkg 6 | tmp 7 | *.gem 8 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - Thomas Sawyer 3 | - Poul-Henning Kamp 4 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - rake (build) 3 | - detroit (build) 4 | - minitest (test) 5 | 6 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | Crypt3 is a ruby version of crypt(3), 2 | a salted one-way hashing of a password. 3 | 4 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title Crypt3 2 | --readme README.md 3 | --protected 4 | --private 5 | --plugin tomdoc 6 | lib 7 | - 8 | *.txt 9 | *.md 10 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/crypt3 3 | code: http://github.com/rubyworks/crypt3 4 | bugs: http://github.com/rubyworks/crypt3/issues 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec rake test" 4 | rvm: 5 | - 1.8.7 6 | - 1.9.3 7 | - rbx 8 | - rbx-19mode 9 | - jruby 10 | - jruby-19mode 11 | 12 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .index .yardopts bin lib spec test *.txt *.md 2 | .index 3 | .yardopts 4 | lib/crypt3.rb 5 | lib/crypt3.yml 6 | test/test_crypt3.rb 7 | LICENSE.txt 8 | README.md 9 | HISTORY.md 10 | -------------------------------------------------------------------------------- /task/test.rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rake/testtask' 4 | 5 | Rake::TestTask.new do |t| 6 | t.libs << "test" 7 | t.test_files = FileList['test/test*.rb'] 8 | t.verbose = true 9 | end 10 | 11 | task :default => :test 12 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | active: true 4 | 5 | gem: 6 | active: true 7 | 8 | dnote: 9 | output: log/notes.html 10 | 11 | yard: 12 | priority: 2 13 | 14 | minitest: 15 | tests: 'test/*' 16 | 17 | email: 18 | mailto: 19 | - "ruby-talk@ruby-lang.org" 20 | - "rubyworks-mailinglist@googlegroups.com" 21 | 22 | vclog: 23 | output: 24 | - 'log/changes.html' 25 | - 'log/history.html' 26 | 27 | -------------------------------------------------------------------------------- /test/test_crypt3.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__) + '/../lib') 2 | 3 | require "crypt3.rb" 4 | require "minitest/autorun" 5 | 6 | class Crypt3Test < MiniTest::Unit::TestCase 7 | 8 | def array_test(arr, algo) 9 | arr.each do |password, hash| 10 | assert(Crypt3.check(password, hash, algo)) 11 | end 12 | end 13 | 14 | def test_md5 15 | a = [ 16 | [' ', '$1$yiiZbNIH$YiCsHZjcTkYd31wkgW8JF.'], 17 | ['pass', '$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90'], 18 | ['____fifteen____', '$1$s9lUWACI$Kk1jtIVVdmT01p0z3b/hw1'], 19 | ['____sixteen_____', '$1$dL3xbVZI$kkgqhCanLdxODGq14g/tW1'], 20 | ['____seventeen____', '$1$NaH5na7J$j7y8Iss0hcRbu3kzoJs5V.'], 21 | ['__________thirty-three___________', '$1$HO7Q6vzJ$yGwp2wbL5D7eOVzOmxpsy.'], 22 | ['apache', '$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1'] 23 | ] 24 | array_test(a, :md5) 25 | end 26 | 27 | def test_bad_algorithm 28 | assert_raises(ArgumentError) do 29 | Crypt3.crypt("qsdf", :qsdf) 30 | end 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 1.1.5 / 2013-01-30 4 | 5 | This release adds TomDoc based documentation. 6 | 7 | Changes: 8 | 9 | * Add TomDoc documentation. 10 | 11 | 12 | ## 1.1.4 / 2011-10-23 13 | 14 | This release fixes VERSION constant. That's it. 15 | 16 | Changes: 17 | 18 | * Fix VERSION constant. 19 | 20 | 21 | ## 1.1.3 / 2011-10-22 22 | 23 | This release is simply an administrative release to modernize the 24 | build tools and build configuration used for the project. 25 | 26 | Changes: 27 | 28 | * Modernize build. 29 | 30 | 31 | ## 1.1.2 / 2010-09-03 32 | 33 | Well, it looks as if the String#^ (xor) method from Facets wasn't ever 34 | actually needed. 35 | 36 | Changes: 37 | 38 | * Removed String#^ extension. 39 | 40 | 41 | ## 1.1.1 / 2010-09-02 42 | 43 | This release simply removes the dependency on Facets and updates 44 | project metadata. 45 | 46 | Changes: 47 | 48 | * Remove dependency on Ruby Facets. 49 | 50 | 51 | ## 1.1.0 / 2009-07-27 52 | 53 | This release renames the Crypt module to Crypt3 --which is really 54 | what it should have been from the start. 55 | 56 | Changes: 57 | 58 | * Rename Crypt module to Crypt3. 59 | 60 | 61 | ## 1.0.0 / 2009-07-20 62 | 63 | This is the initial release of Crypt3. 64 | 65 | Changes: 66 | 67 | * Happy Birthday! 68 | 69 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - var 6 | authors: 7 | - name: Thomas Sawyer 8 | email: transfire@gmail.com 9 | - name: Poul-Henning Kamp 10 | organizations: 11 | - name: Rubyworks 12 | requirements: 13 | - groups: 14 | - build 15 | development: true 16 | name: rake 17 | - groups: 18 | - build 19 | development: true 20 | name: detroit 21 | - groups: 22 | - test 23 | development: true 24 | name: minitest 25 | conflicts: [] 26 | alternatives: [] 27 | resources: 28 | - type: home 29 | uri: http://rubyworks.github.com/crypt3 30 | label: Homepage 31 | - type: code 32 | uri: http://github.com/rubyworks/crypt3 33 | label: Source Code 34 | - type: bugs 35 | uri: http://github.com/rubyworks/crypt3/issues 36 | label: Issue Tracker 37 | repositories: 38 | - name: upstream 39 | scm: git 40 | uri: git://github.com/rubyworks/crypt3.git 41 | categories: [] 42 | copyrights: 43 | - holder: '' 44 | year: '2002' 45 | license: c) Poul-Henning Kamp (BSD-2-Clause 46 | customs: [] 47 | paths: 48 | lib: 49 | - lib 50 | name: crypt3 51 | title: Crypt3 52 | summary: Crypt3 is a ruby version of crypt(3) 53 | created: '2002-06-01' 54 | description: |- 55 | Crypt3 is a ruby version of crypt(3), 56 | a salted one-way hashing of a password. 57 | version: 1.1.6 58 | date: '2013-08-09' 59 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD-2-Clause License 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are 4 | permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of 7 | conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 10 | of conditions and the following disclaimer in the documentation and/or other materials 11 | provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED 14 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 16 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 17 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 18 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 21 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crypt3 2 | 3 | [Website](http://rubyworks.github.com/crypt3) · 4 | [Report Issue](http://github.com/rubyworks/crypt3/issues) · 5 | [Source Code](http://github.com/rubyworks/crypt3)       6 | [![Gem Version](https://badge.fury.io/rb/crypt3.png)](http://badge.fury.io/rb/crypt3) 7 | [![Build Status](https://secure.travis-ci.org/rubyworks/crypt3.png)](http://travis-ci.org/rubyworks/crypt3) 8 | 9 | 10 | ## [About](#about) 11 | 12 | Crypt3 is a pure Ruby version of crypt(3) --a salted one-way 13 | hashing of a password. 14 | 15 | Supported hashing algorithms are: md5, sha1, sha256, sha384, 16 | sha512, rmd160. Only the md5 hashing algorithm is standard 17 | and compatible with crypt(3); the others are non-standard. 18 | 19 | 20 | ## [Features](#features) 21 | 22 | * Standard compliant crypt(3) implementation 23 | * Supports alternate hashing algorithms 24 | * Written in Ruby (but depends on Ruby's standard digest libraries) 25 | 26 | 27 | ## [Installing](#installing) 28 | 29 | To install with RubyGems simply open a console and type: 30 | 31 | gem install crypt3 32 | 33 | Site installation requires Setup.rb (gem install setup), 34 | then download the tarball package and type: 35 | 36 | tar -xvzf crypt3-1.0.0.tar.gz 37 | cd crypt3-1.0.0 38 | sudo setup.rb all 39 | 40 | Windows users use 'ruby setup.rb all'. 41 | 42 | 43 | ## [Basic Usage](#usage) 44 | 45 | Crypt3 provides a module method call `crypt`. 46 | 47 | Crypt3.crypt('pass') 48 | 49 | It will return an encypted string, something like: 50 | 51 | '$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90' 52 | 53 | The validitly of which can ensured it using `check`: 54 | 55 | Crypt3.check('pass', '$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90') 56 | 57 | See the [API Documentation](http://rubydoc.info/gems/crypt3/frames) for further 58 | details and options. 59 | 60 | 61 | ## [Copyrights](#copyright) 62 | 63 | Copyright © 2009 Poul-Henning Kamp 64 | 65 | This program is ditributed under the terms of the [BSD-2-Clause](http://opensource.org/licenses/BSD-2-Clause) 66 | license. 67 | 68 | See LICENSE.txt for full text. 69 | -------------------------------------------------------------------------------- /lib/crypt3.rb: -------------------------------------------------------------------------------- 1 | # Crypt3 is a pure ruby version of crypt(3), a salted one-way hashing of a password. 2 | # 3 | # The Ruby version was written by Poul-Henning Kamp. 4 | # 5 | # Adapted by guillaume__dot__pierronnet_at__laposte__dot_net based on 6 | # * http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/325204/index_txt 7 | # which is based on FreeBSD src/lib/libcrypt/crypt.c 1.2 8 | # * http://www.freebsd.org/cgi/cvsweb.cgi/~checkout~/src/lib/libcrypt/crypt.c?rev=1.2&content-type=text/plain 9 | # 10 | # _Original License_ 11 | # 12 | # "THE BEER-WARE LICENSE" (Revision 42): 13 | # wrote this file. As long as you retain this notice you 14 | # can do whatever you want with this stuff. If we meet some day, and you think 15 | # this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 16 | # 17 | # Copyright (c) 2002 Poul-Henning Kamp 18 | 19 | module Crypt3 20 | 21 | # Current version of the library. 22 | VERSION = '1.1.5' #:erb: VERSION = '<%= version %>' 23 | 24 | # Base 64 character set. 25 | ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 26 | 27 | # Extends strings in #crypt. 28 | module ImpOrd2String 29 | def [](*args) 30 | self.slice(*args).ord 31 | end 32 | end 33 | 34 | # A pure ruby version of crypt(3), a salted one-way hashing of a password. 35 | # 36 | # Supported hashing algorithms are: md5, sha1, sha256, sha384, sha512, rmd160. 37 | # 38 | # Only the md5 hashing algorithm is standard and compatible with crypt(3), 39 | # the others are not standard. 40 | # 41 | # Automatically generates an 8-byte salt if none given. 42 | # 43 | # Output a length hashed and salted string with size of `magic.size + salt.size + 23`. 44 | # 45 | # password - The pharse that was encrypted. [String] 46 | # algo - The algorithm used. [Symbol] 47 | # salt - Cryptographic salt, random if `nil`. 48 | # 49 | # Retuns the cryptogrphic hash. [String] 50 | def self.crypt(password, algo=:md5, salt=nil, magic='$1$') 51 | 52 | salt ||= generate_salt(8) 53 | 54 | case algo 55 | when :md5 56 | require "digest/md5" 57 | when :sha1 58 | require "digest/sha1" 59 | when :rmd160 60 | require "digest/rmd160" 61 | when :sha256, :sha384, :sha512 62 | require "digest/sha2" 63 | else 64 | raise(ArgumentError, "unknown algorithm") 65 | end 66 | digest_class = Digest.const_get(algo.to_s.upcase) 67 | 68 | # The password first, since that is what is most unknown. Then our magic string. Then the raw salt. 69 | m = digest_class.new 70 | m.update(password + magic + salt) 71 | 72 | # Then just as many characters of the MD5(pw,salt,pw) 73 | mixin = digest_class.new.update(password + salt + password).digest 74 | password.length.times do |i| 75 | m.update(mixin[i % 16].chr) 76 | end 77 | 78 | # Then something really weird... 79 | # Also really broken, as far as I can tell. -m 80 | i = password.length 81 | while i != 0 82 | if (i & 1) != 0 83 | m.update("\x00") 84 | else 85 | m.update(password[0].chr) 86 | end 87 | i >>= 1 88 | end 89 | 90 | final = m.digest 91 | 92 | # and now, just to make sure things don't run too fast 93 | 1000.times do |i| 94 | m2 = digest_class.new 95 | 96 | if (i & 1) != 0 97 | m2.update(password) 98 | else 99 | m2.update(final) 100 | end 101 | 102 | if (i % 3) != 0 103 | m2.update(salt) 104 | end 105 | if (i % 7) != 0 106 | m2.update(password) 107 | end 108 | 109 | if (i & 1) != 0 110 | m2.update(final) 111 | else 112 | m2.update(password) 113 | end 114 | 115 | final = m2.digest 116 | end 117 | 118 | # This is the bit that uses to64() in the original code. 119 | 120 | rearranged = "" 121 | 122 | if defined?("has_ord?".ord) 123 | final.extend ImpOrd2String 124 | end 125 | [ [0, 6, 12], [1, 7, 13], [2, 8, 14], [3, 9, 15], [4, 10, 5] ].each do |a, b, c| 126 | 127 | v = final[a] << 16 | final[b] << 8 | final[c] 128 | 129 | 4.times do 130 | rearranged += ITOA64[v & 0x3f].chr 131 | v >>= 6 132 | end 133 | end 134 | 135 | v = final[11] 136 | 137 | 2.times do 138 | rearranged += ITOA64[v & 0x3f].chr 139 | v >>= 6 140 | end 141 | 142 | magic + salt + '$' + rearranged 143 | end 144 | 145 | # Check the validity of a password against an hashed string. 146 | # 147 | # password - The pharse that was encrypted. [String] 148 | # hash - The cryptogrphic hash. [String] 149 | # algo - The algorithm used. [Symbol] 150 | # 151 | # Returns true if it checks out. [Boolean] 152 | def self.check(password, hash, algo = :md5) 153 | magic, salt = hash.split('$')[1,2] 154 | magic = '$' + magic + '$' 155 | self.crypt(password, algo, salt, magic) == hash 156 | end 157 | 158 | # Generate a random salt of the given `size`. 159 | # 160 | # size - The size of the salt. [Integer] 161 | # 162 | # Returns random salt. [String] 163 | def self.generate_salt(size) 164 | (1..size).collect { ITOA64[rand(ITOA64.size)].chr }.join("") 165 | end 166 | 167 | # # Binary XOR of two strings. 168 | # # 169 | # # a = xor("\000\000\001\001", "\000\001\000\001") 170 | # # b = xor("\003\003\003", "\000\001\002") 171 | # # 172 | # # a #=> "\000\001\001\000" 173 | # # b #=> "\003\002\001" 174 | # # 175 | # def xor(string1, string2) 176 | # a = string1.unpack('C'*(self.length)) 177 | # b = string2.unpack('C'*(aString.length)) 178 | # if (b.length < a.length) 179 | # (a.length - b.length).times { b << 0 } 180 | # end 181 | # xor = "" 182 | # 0.upto(a.length-1) { |pos| 183 | # x = a[pos] ^ b[pos] 184 | # xor << x.chr() 185 | # } 186 | # return(xor) 187 | # end 188 | end 189 | 190 | -------------------------------------------------------------------------------- /.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 --------------------------------------------------------------------------------