Skip to content

Commit

Permalink
Version 1.0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
winton committed Mar 23, 2009
1 parent 1a8bad4 commit 69ea841
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 100 deletions.
31 changes: 31 additions & 0 deletions Rakefile
@@ -0,0 +1,31 @@
require 'rake'

task :default => 'captcha.gemspec'

file 'captcha.gemspec' => FileList['{lib,spec}/**','Rakefile'] do |f|
# read spec file and split out manifest section
spec = File.read(f.name)
parts = spec.split(" # = MANIFEST =\n")
fail 'bad spec' if parts.length != 3
# determine file list from git ls-files
files = `git ls-files`.
split("\n").
sort.
reject{ |file| file =~ /^\./ }.
reject { |file| file =~ /^doc/ }.
map{ |file| " #{file}" }.
join("\n")
# piece file back together and write...
parts[1] = " s.files = %w[\n#{files}\n ]\n"
spec = parts.join(" # = MANIFEST =\n")
File.open(f.name, 'w') { |io| io.write(spec) }
puts "Updated #{f.name}"
end

# sudo rake install
task :install do
`sudo gem uninstall captcha -x`
`gem build captcha.gemspec`
`sudo gem install captcha*.gem`
`rm captcha*.gem`
end
35 changes: 25 additions & 10 deletions captcha.gemspec
@@ -1,22 +1,37 @@
Gem::Specification.new do |s|
s.name = 'captcha'
s.version = '1.0.3'
s.date = '2008-08-16'
s.version = '1.0.4'
s.date = '2009-03-23'

s.summary = "A simple captcha generator for Rails"
s.description = "A simple captcha generator for Rails"
s.summary = "An Rmagick based, Google-style captcha generator"
s.description = "An Rmagick based, Google-style captcha generator"

s.author = 'Winton Welsh'
s.email = 'mail@wintoni.us'
s.homepage = 'http://github.com/winton/captcha'

s.has_rdoc = false

s.files = Dir[*%w(
init.rb
lib/*
lib/**/*
# = MANIFEST =
s.files = %w[
README.markdown
resources/*
)]
Rakefile
captcha.gemspec
init.rb
lib/captcha.rb
lib/captcha/action.rb
lib/captcha/actions.rb
lib/captcha/captcha.rb
lib/captcha/config.rb
lib/captcha/generator.rb
lib/captcha/image.rb
lib/captcha/routes.rb
resources/captcha.ttf
resources/captchas.rb
spec/lib/captcha_spec.rb
spec/spec.opts
spec/spec_helper.rb
tasks/captcha.rake
]
# = MANIFEST =
end
8 changes: 7 additions & 1 deletion init.rb
@@ -1 +1,7 @@
require 'captcha' # named capture.rb because of config/captcha.rb
require 'captcha'
if File.exists?("#{RAILS_ROOT}/lib/captcha_config.rb")
require "#{RAILS_ROOT}/lib/captcha_config"
end

ActionController::Base.send :include, Captcha::Action
Captcha::Generator.new
12 changes: 4 additions & 8 deletions lib/captcha.rb
@@ -1,8 +1,4 @@
# named capture.rb because of config/captcha.rb

Dir[File.expand_path('*/*.rb', File.dirname(__FILE__))].each do |f|
require [ File.dirname(f), File.basename(f, '.rb') ].join('/')
end

ActionController::Base.send :include, CaptchaActions
CAPTCHAS = Captchas.new true
require File.dirname(__FILE__) + "/captcha/action.rb"
require File.dirname(__FILE__) + "/captcha/image.rb"
require File.dirname(__FILE__) + "/captcha/config.rb"
require File.dirname(__FILE__) + "/captcha/generator.rb"
42 changes: 42 additions & 0 deletions lib/captcha/action.rb
@@ -0,0 +1,42 @@
module Captcha
module Action

def self.included(base)
base.extend ClassMethods
end

module ClassMethods
def acts_as_captcha
include Captcha::Action::InstanceMethods
self.around_filter CaptchaFilter.new
end
end

module InstanceMethods
def new
files = Dir["#{RAILS_ROOT}/#{CAPTCHAS.options[:destination]}/*.jpg"]
@captcha = File.basename(files[rand(files.length)], '.jpg')
show
end

def show
new unless @captcha
send_file "#{RAILS_ROOT}/#{CAPTCHAS.options[:destination]}/#{@captcha}.jpg", :type => 'image/jpeg', :disposition => 'inline'
end
end

private

class CaptchaFilter
def before(controller)
@captcha = session[:captcha]
end
def after(controller)
if session[:captcha] != @captcha
session[:captcha] = @captcha
end
end
end

end
end
26 changes: 0 additions & 26 deletions lib/captcha/actions.rb

This file was deleted.

71 changes: 71 additions & 0 deletions lib/captcha/config.rb
@@ -0,0 +1,71 @@
module Captcha
class Config
def initialize(options={})
@@options = {
:colors => {
:background => '#1B1C20',
:font => '#7385C5'
},
# number of captcha images to generate
:count => 500,
:destination => File.expand_path(
(defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/" : '') +
"public/images/captchas"
),
:dimensions => {
# canvas height (px)
:height => 31,
# canvas width (px)
:width => 90
},
:generate_every => 24 * 60 * 60,
# http://www.imagemagick.org/RMagick/doc/image2.html#implode
:implode => 0.2,
:letters => {
# text baseline (px)
:baseline => 24,
# number of letters in captcha
:count => 6,
:ignore => ['a','e','i','o','u','l','j','q'],
# font size (pts)
:points => 34,
# width of a character (used to decrease or increase space between characters) (px)
:width => 14
},
:ttf => File.expand_path("#{File.dirname(__FILE__)}/../../resources/captcha.ttf"),
# http://www.imagemagick.org/RMagick/doc/image3.html#wave
:wave => {
# range is used for randomness (px)
:wavelength => (20..70),
# distance between peak and valley of sin wave (px)
:amplitude => 3
}
}.merge(options)
end

def self.captchas_ordered_by_modified
return unless @@options
files_modified = Dir["#{@@options[:destination]}/*.jpg"].collect do |file|
[ file, File.mtime(file) ]
end
# Youngest to oldest
files_modified.sort! { |a, b| b[1] <=> a[1] }
files_modified.collect { |f| f[0] }
end

def self.codes
self.captchas_ordered_by_modified.collect do |f|
File.basename f, '.jpg'
end
end

def self.options
@@options
end

def self.last_modified
youngest = self.captchas_ordered_by_modified.first
youngest ? File.mtime(youngest) : nil
end
end
end
29 changes: 29 additions & 0 deletions lib/captcha/generator.rb
@@ -0,0 +1,29 @@
module Captcha
class Generator
def initialize
generate
end

def generate
return unless Config.options
return if Config.last_modified && Config.last_modified > Time.now - Config.options[:generate_every]
FileUtils.mkdir_p Config.options[:destination]
(1..Config.options[:count]).each do |x|
c = Image.new Config.options
File.open("#{Config.options[:destination]}/#{c.code}.jpg", 'w') do |f|
f << c.image
end
end
destroy_if_over_limit
GC.start
end

def destroy_if_over_limit
if Config.codes.length >= Config.options[:count] * 3
Config.captchas_ordered_by_modified[(Config.options[:count] * 2)..-1].each do |file|
FileUtils.rm_f(file)
end
end
end
end
end
61 changes: 11 additions & 50 deletions lib/captcha/captcha.rb → lib/captcha/image.rb
@@ -1,80 +1,41 @@
require 'RMagick'

class Captchas

attr_reader :codes

def initialize(startup=false)
@o = get_options
return unless @o
generate if !startup || @o[:generate_on_startup]
update_codes
end

def options
@o
end

def generate
FileUtils.rm_rf @o[:destination]
FileUtils.mkdir_p @o[:destination]

(1..@o[:count]).each do |x|
c = Captcha.new @o
File.open("#{@o[:destination]}/#{c.code}.jpg", 'w') do |f|
f << c.image
end
end
GC.start
end

def update_codes
@codes = Dir["#{@o[:destination]}/*.jpg"].collect do |f|
File.basename f, '.jpg'
end
end

private
module Captcha
class Image

def get_options
if File.exists?('config/captchas.rb')
eval File.read('config/captchas.rb')
end
end

class Captcha

include Magick
attr_reader :code, :image

def initialize(o)
@code = generate_code o

canvas = Magick::ImageList.new
canvas.new_image(o[:width], o[:height]) {
canvas.new_image(o[:dimensions][:width], o[:dimensions][:height]) {
self.background_color = o[:colors][:background]
}

text = Magick::Draw.new
text.font = File.expand_path o[:ttf]
text.pointsize = o[:letters][:points]

cur = 0
@code.each { |c|
text.annotate(canvas, 0, 0, cur, o[:letters][:baseline], c) {
self.fill = o[:colors][:font]
}
cur += o[:letters][:width]
}

w = o[:wave][:wavelength]
canvas = canvas.wave(o[:wave][:amplitude], rand(w.last - w.first) + w.first)
canvas = canvas.implode(o[:implode])

@code = @code.to_s
@image = canvas.to_blob { self.format = "JPG" }
end

private

def generate_code(o)
chars = ('a'..'z').to_a - o[:letters][:ignore]
code_array = []
Expand Down
5 changes: 0 additions & 5 deletions lib/captcha/routes.rb

This file was deleted.

0 comments on commit 69ea841

Please sign in to comment.