winton-captcha 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.markdown CHANGED
@@ -1,23 +1,23 @@
1
1
  captcha
2
2
  =======
3
3
 
4
- A simple captcha generator for Rails.
5
-
6
-
7
- How it works
8
- ------------
9
-
10
- If **public/images/captchas** does not exist, 100 captchas will be generated there when the application starts.
4
+ An Rmagick based, Google-style captcha generator for Rails.
11
5
 
12
6
 
13
7
  Install
14
8
  -------
15
9
 
16
- gem install winton-captcha
10
+ script/plugin install git://github.com/winton/captcha.git
11
+
12
+ ### Generate config/captcha.rb
13
+
14
+ rake captcha
17
15
 
18
- ### Add to environment.rb
16
+ Take a look at the config file.
19
17
 
20
- config.gem 'winton-captcha', :lib => 'captcha', :source => 'http://gems.github.com'
18
+ ### Generate captchas
19
+
20
+ rake captcha:generate
21
21
 
22
22
  ### Add to .gitignore
23
23
 
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+
3
+ task :default => 'captcha.gemspec'
4
+
5
+ file 'captcha.gemspec' => FileList['{lib,spec}/**','Rakefile'] do |f|
6
+ # read spec file and split out manifest section
7
+ spec = File.read(f.name)
8
+ parts = spec.split(" # = MANIFEST =\n")
9
+ fail 'bad spec' if parts.length != 3
10
+ # determine file list from git ls-files
11
+ files = `git ls-files`.
12
+ split("\n").
13
+ sort.
14
+ reject{ |file| file =~ /^\./ }.
15
+ reject { |file| file =~ /^doc/ }.
16
+ map{ |file| " #{file}" }.
17
+ join("\n")
18
+ # piece file back together and write...
19
+ parts[1] = " s.files = %w[\n#{files}\n ]\n"
20
+ spec = parts.join(" # = MANIFEST =\n")
21
+ File.open(f.name, 'w') { |io| io.write(spec) }
22
+ puts "Updated #{f.name}"
23
+ end
24
+
25
+ # sudo rake install
26
+ task :install do
27
+ `sudo gem uninstall captcha -x`
28
+ `gem build captcha.gemspec`
29
+ `sudo gem install captcha*.gem`
30
+ `rm captcha*.gem`
31
+ end
data/captcha.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'captcha'
3
+ s.version = '1.0.4'
4
+ s.date = '2009-03-23'
5
+
6
+ s.summary = "An Rmagick based, Google-style captcha generator"
7
+ s.description = "An Rmagick based, Google-style captcha generator"
8
+
9
+ s.author = 'Winton Welsh'
10
+ s.email = 'mail@wintoni.us'
11
+ s.homepage = 'http://github.com/winton/captcha'
12
+
13
+ s.has_rdoc = false
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ README.markdown
18
+ Rakefile
19
+ captcha.gemspec
20
+ init.rb
21
+ lib/captcha.rb
22
+ lib/captcha/action.rb
23
+ lib/captcha/actions.rb
24
+ lib/captcha/captcha.rb
25
+ lib/captcha/config.rb
26
+ lib/captcha/generator.rb
27
+ lib/captcha/image.rb
28
+ lib/captcha/routes.rb
29
+ resources/captcha.ttf
30
+ resources/captchas.rb
31
+ spec/lib/captcha_spec.rb
32
+ spec/spec.opts
33
+ spec/spec_helper.rb
34
+ tasks/captcha.rake
35
+ ]
36
+ # = MANIFEST =
37
+ end
data/init.rb CHANGED
@@ -1 +1,7 @@
1
- require 'captcha'
1
+ require 'captcha'
2
+ if File.exists?("#{RAILS_ROOT}/lib/captcha_config.rb")
3
+ require "#{RAILS_ROOT}/lib/captcha_config"
4
+ end
5
+
6
+ ActionController::Base.send :include, Captcha::Action
7
+ Captcha::Generator.new
data/lib/captcha.rb CHANGED
@@ -1,59 +1,4 @@
1
- require 'RMagick'
2
-
3
- Dir[File.expand_path('*/*.rb', File.dirname(__FILE__))].each do |f|
4
- require [ File.dirname(f), File.basename(f, '.rb') ].join('/')
5
- end
6
-
7
- class Captcha
8
-
9
- include Magick
10
- attr_reader :code, :code_image
11
-
12
- JIGGLE = 15
13
- WOBBLE = 25
14
-
15
- def initialize(len)
16
- chars = ('a'..'z').to_a - ['a','e','i','o','u','l','j']
17
- code_array = []
18
- 1.upto(len) { code_array << chars[rand(chars.length)] }
19
- granite = Magick::ImageList.new('granite:')
20
- canvas = Magick::ImageList.new
21
- canvas.new_image(32*len, 50, Magick::TextureFill.new(granite))
22
- text = Magick::Draw.new
23
- text.font = File.expand_path('vendor/plugins/captcha/resources/captcha.ttf')
24
- text.pointsize = 40
25
- cur = 10
26
-
27
- code_array.each { |c|
28
- rot = rand(10) > 5 ? rand(WOBBLE) : -rand(WOBBLE)
29
- weight = rand(10) > 5 ? NormalWeight : BoldWeight
30
- text.annotate(canvas,0,0,cur,30+rand(JIGGLE), c) {
31
- self.rotation = rot
32
- self.font_weight = weight
33
- self.fill = '#98999B'
34
- }
35
- cur += 30
36
- }
37
-
38
- @code = code_array.to_s
39
- @code_image = canvas.to_blob {
40
- self.format = "JPG"
41
- }
42
-
43
- GC.start
44
- end
45
-
46
- end
47
-
48
- to = "public/images/captchas"
49
- unless File.exists?(to)
50
- FileUtils.mkdir_p to
51
- (0..99).each do |x|
52
- c = Captcha.new(6)
53
- File.open("#{to}/#{c.code}.jpg", 'w') do |f|
54
- f << c.code_image
55
- end
56
- end
57
- end
58
-
59
- ActionController::Base.send :include, CaptchaActions
1
+ require File.dirname(__FILE__) + "/captcha/action.rb"
2
+ require File.dirname(__FILE__) + "/captcha/image.rb"
3
+ require File.dirname(__FILE__) + "/captcha/config.rb"
4
+ require File.dirname(__FILE__) + "/captcha/generator.rb"
@@ -0,0 +1,42 @@
1
+ module Captcha
2
+ module Action
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_captcha
10
+ include Captcha::Action::InstanceMethods
11
+ self.around_filter CaptchaFilter.new
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+ def new
17
+ files = Dir["#{RAILS_ROOT}/#{CAPTCHAS.options[:destination]}/*.jpg"]
18
+ @captcha = File.basename(files[rand(files.length)], '.jpg')
19
+ show
20
+ end
21
+
22
+ def show
23
+ new unless @captcha
24
+ send_file "#{RAILS_ROOT}/#{CAPTCHAS.options[:destination]}/#{@captcha}.jpg", :type => 'image/jpeg', :disposition => 'inline'
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ class CaptchaFilter
31
+ def before(controller)
32
+ @captcha = session[:captcha]
33
+ end
34
+ def after(controller)
35
+ if session[:captcha] != @captcha
36
+ session[:captcha] = @captcha
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,71 @@
1
+ module Captcha
2
+ class Config
3
+ def initialize(options={})
4
+ @@options = {
5
+ :colors => {
6
+ :background => '#1B1C20',
7
+ :font => '#7385C5'
8
+ },
9
+ # number of captcha images to generate
10
+ :count => 500,
11
+ :destination => File.expand_path(
12
+ (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/" : '') +
13
+ "public/images/captchas"
14
+ ),
15
+ :dimensions => {
16
+ # canvas height (px)
17
+ :height => 31,
18
+ # canvas width (px)
19
+ :width => 90
20
+ },
21
+ :generate_every => 24 * 60 * 60,
22
+ # http://www.imagemagick.org/RMagick/doc/image2.html#implode
23
+ :implode => 0.2,
24
+ :letters => {
25
+ # text baseline (px)
26
+ :baseline => 24,
27
+ # number of letters in captcha
28
+ :count => 6,
29
+ :ignore => ['a','e','i','o','u','l','j','q'],
30
+ # font size (pts)
31
+ :points => 34,
32
+ # width of a character (used to decrease or increase space between characters) (px)
33
+ :width => 14
34
+ },
35
+ :ttf => File.expand_path("#{File.dirname(__FILE__)}/../../resources/captcha.ttf"),
36
+ # http://www.imagemagick.org/RMagick/doc/image3.html#wave
37
+ :wave => {
38
+ # range is used for randomness (px)
39
+ :wavelength => (20..70),
40
+ # distance between peak and valley of sin wave (px)
41
+ :amplitude => 3
42
+ }
43
+ }.merge(options)
44
+ end
45
+
46
+ def self.captchas_ordered_by_modified
47
+ return unless @@options
48
+ files_modified = Dir["#{@@options[:destination]}/*.jpg"].collect do |file|
49
+ [ file, File.mtime(file) ]
50
+ end
51
+ # Youngest to oldest
52
+ files_modified.sort! { |a, b| b[1] <=> a[1] }
53
+ files_modified.collect { |f| f[0] }
54
+ end
55
+
56
+ def self.codes
57
+ self.captchas_ordered_by_modified.collect do |f|
58
+ File.basename f, '.jpg'
59
+ end
60
+ end
61
+
62
+ def self.options
63
+ @@options
64
+ end
65
+
66
+ def self.last_modified
67
+ youngest = self.captchas_ordered_by_modified.first
68
+ youngest ? File.mtime(youngest) : nil
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,29 @@
1
+ module Captcha
2
+ class Generator
3
+ def initialize
4
+ generate
5
+ end
6
+
7
+ def generate
8
+ return unless Config.options
9
+ return if Config.last_modified && Config.last_modified > Time.now - Config.options[:generate_every]
10
+ FileUtils.mkdir_p Config.options[:destination]
11
+ (1..Config.options[:count]).each do |x|
12
+ c = Image.new Config.options
13
+ File.open("#{Config.options[:destination]}/#{c.code}.jpg", 'w') do |f|
14
+ f << c.image
15
+ end
16
+ end
17
+ destroy_if_over_limit
18
+ GC.start
19
+ end
20
+
21
+ def destroy_if_over_limit
22
+ if Config.codes.length >= Config.options[:count] * 3
23
+ Config.captchas_ordered_by_modified[(Config.options[:count] * 2)..-1].each do |file|
24
+ FileUtils.rm_f(file)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ require 'RMagick'
2
+
3
+ module Captcha
4
+ class Image
5
+
6
+ include Magick
7
+ attr_reader :code, :image
8
+
9
+ def initialize(o)
10
+ @code = generate_code o
11
+
12
+ canvas = Magick::ImageList.new
13
+ canvas.new_image(o[:dimensions][:width], o[:dimensions][:height]) {
14
+ self.background_color = o[:colors][:background]
15
+ }
16
+
17
+ text = Magick::Draw.new
18
+ text.font = File.expand_path o[:ttf]
19
+ text.pointsize = o[:letters][:points]
20
+
21
+ cur = 0
22
+ @code.each { |c|
23
+ text.annotate(canvas, 0, 0, cur, o[:letters][:baseline], c) {
24
+ self.fill = o[:colors][:font]
25
+ }
26
+ cur += o[:letters][:width]
27
+ }
28
+
29
+ w = o[:wave][:wavelength]
30
+ canvas = canvas.wave(o[:wave][:amplitude], rand(w.last - w.first) + w.first)
31
+ canvas = canvas.implode(o[:implode])
32
+
33
+ @code = @code.to_s
34
+ @image = canvas.to_blob { self.format = "JPG" }
35
+ end
36
+
37
+ private
38
+
39
+ def generate_code(o)
40
+ chars = ('a'..'z').to_a - o[:letters][:ignore]
41
+ code_array = []
42
+ 1.upto(o[:letters][:count]) { code_array << chars[rand(chars.length)] }
43
+ code_array
44
+ end
45
+ end
46
+ end
Binary file
@@ -0,0 +1,24 @@
1
+ {
2
+ :count => 100, # number of captcha images to generate
3
+ :width => 90, # canvas width (px)
4
+ :height => 31, # canvas height (px)
5
+ :colors => {
6
+ :background => '#1B1C20',
7
+ :font => '#7385C5'
8
+ },
9
+ :letters => {
10
+ :baseline => 24,# text baseline (px)
11
+ :count => 6, # number of letters in captcha
12
+ :ignore => ['a','e','i','o','u','l','j','q'],
13
+ :points => 34, # font size (pts)
14
+ :width => 14, # width of a character (used to decrease or increase space between characters) (px)
15
+ },
16
+ :implode => 0.2, # http://www.imagemagick.org/RMagick/doc/image2.html#implode
17
+ :wave => { # http://www.imagemagick.org/RMagick/doc/image3.html#wave
18
+ :wavelength => (20..70), # range is used for randomness (px)
19
+ :amplitude => 3 # distance between peak and valley of sin wave (px)
20
+ },
21
+ :generate_on_startup => RAILS_ENV == 'production',
22
+ :destination => 'tmp/captchas',
23
+ :ttf => 'vendor/plugins/captcha/resources/captcha.ttf'
24
+ }
@@ -0,0 +1,63 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ describe :captcha do
4
+ before(:each) do
5
+ @delay = 2 # 2 seconds
6
+ Captcha::Config.new(
7
+ :count => 10,
8
+ :destination => File.expand_path(File.dirname(__FILE__) + "/../tmp"),
9
+ :generate_every => @delay
10
+ )
11
+ FileUtils.rm_rf Captcha::Config.options[:destination]
12
+ @generator = Captcha::Generator.new
13
+ end
14
+ after(:all) do
15
+ FileUtils.rm_rf Captcha::Config.options[:destination]
16
+ end
17
+ it "should generate captchas" do
18
+ Captcha::Config.codes.length.should == 10
19
+ end
20
+ it "should only generate more captchas if the youngest file is older than the generate_every option" do
21
+ @generator.generate
22
+ Captcha::Config.codes.length.should == 10
23
+ sleep @delay
24
+ @generator.generate
25
+ Captcha::Config.codes.length.should == 20
26
+ end
27
+ it "should not allow more than twice the captcha limit to exist" do
28
+ sleep @delay
29
+ @generator.generate
30
+ Captcha::Config.codes.length.should == 20
31
+ sleep @delay
32
+ @generator.generate
33
+ Captcha::Config.codes.length.should == 20
34
+ end
35
+ it "should only destroy the oldest captchas" do
36
+ codes = Captcha::Config.codes
37
+ sleep @delay
38
+ @generator.generate
39
+ codes2 = Captcha::Config.codes
40
+ Captcha::Config.codes.length.should == 20
41
+ Captcha::Config.codes[9..-1].should_include_all_from(codes)
42
+ sleep @delay
43
+ @generator.generate
44
+ Captcha::Config.codes.length.should == 20
45
+ Captcha::Config.codes[9..-1].should_include_all_from(codes2)
46
+ Captcha::Config.codes.should_not_include_any_from(codes)
47
+ @generator.generate
48
+ Captcha::Config.codes.length.should == 20
49
+ Captcha::Config.codes.should_not_include_any_from(codes2)
50
+ end
51
+ class Array
52
+ def should_include_all_from(array)
53
+ included_all = true
54
+ array.each { |a| included_all = false unless self.include?(a) }
55
+ included_all
56
+ end
57
+ def should_not_include_any_from(array)
58
+ included_any = false
59
+ array.each { |a| included_any = true if self.include?(a) }
60
+ included_any
61
+ end
62
+ end
63
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,5 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'captcha'
5
+ require 'spec'
@@ -0,0 +1,55 @@
1
+ require File.expand_path('../lib/captcha/captcha.rb', File.dirname(__FILE__))
2
+
3
+ desc 'Updates config/captcha.rb'
4
+ task :captcha => 'captcha:config'
5
+
6
+ namespace :captcha do
7
+
8
+ desc 'Updates config/captcha.rb'
9
+ task :config => 'captcha:config:to_app'
10
+
11
+ desc 'Generates captchas'
12
+ task :generate do
13
+ Captchas.new
14
+ end
15
+
16
+ namespace :config do
17
+ desc 'Copies plugin resources to app'
18
+ task :to_app do
19
+ captcha_resource 'captchas.rb', 'config/captchas.rb'
20
+ end
21
+
22
+ desc 'Copies app resources to plugin'
23
+ task :to_plugin do
24
+ captcha_resource 'captchas.rb', 'config/captchas.rb', true
25
+ end
26
+
27
+ desc 'Removes plugin resources from app'
28
+ task :remove do
29
+ rm_rf 'config/captchas.rb'
30
+ end
31
+ end
32
+
33
+ def captcha_resource(type, to, reverse=false, overwrite=true)
34
+ from = "#{File.dirname(__FILE__)}/../resources/#{type}"
35
+ from, to = to, from if reverse
36
+ puts "=> Removing old #{type}..."
37
+ puts to
38
+ return if File.exists?(to) && !overwrite
39
+ if File.directory?(from)
40
+ FileUtils.remove_dir(to, true) if File.exists?(to)
41
+ FileUtils.mkdir_p to
42
+ else
43
+ File.unlink(to) if File.exists?(to)
44
+ end
45
+ puts "=> Copying #{type}..."
46
+ (File.directory?(from) ? Dir["#{from}/*"] : [from]).each do |f|
47
+ if File.directory? f
48
+ FileUtils.mkdir_p "#{to}/#{File.basename(f)}"
49
+ FileUtils.cp_r f, to
50
+ else
51
+ FileUtils.cp f, to
52
+ end
53
+ end if File.exists?(from)
54
+ end
55
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winton-captcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Winton Welsh
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-16 00:00:00 -07:00
12
+ date: 2009-03-23 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A simple captcha generator for Rails
16
+ description: An Rmagick based, Google-style captcha generator
17
17
  email: mail@wintoni.us
18
18
  executables: []
19
19
 
@@ -22,12 +22,24 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
+ - README.markdown
26
+ - Rakefile
27
+ - captcha.gemspec
25
28
  - init.rb
26
29
  - lib/captcha.rb
27
- - lib/captcha
30
+ - lib/captcha/action.rb
28
31
  - lib/captcha/actions.rb
29
- - README.markdown
32
+ - lib/captcha/captcha.rb
33
+ - lib/captcha/config.rb
34
+ - lib/captcha/generator.rb
35
+ - lib/captcha/image.rb
36
+ - lib/captcha/routes.rb
30
37
  - resources/captcha.ttf
38
+ - resources/captchas.rb
39
+ - spec/lib/captcha_spec.rb
40
+ - spec/spec.opts
41
+ - spec/spec_helper.rb
42
+ - tasks/captcha.rake
31
43
  has_rdoc: false
32
44
  homepage: http://github.com/winton/captcha
33
45
  post_install_message:
@@ -53,6 +65,6 @@ rubyforge_project:
53
65
  rubygems_version: 1.2.0
54
66
  signing_key:
55
67
  specification_version: 2
56
- summary: A simple captcha generator for Rails
68
+ summary: An Rmagick based, Google-style captcha generator
57
69
  test_files: []
58
70
 
@@ -1,26 +0,0 @@
1
- module CaptchaActions
2
-
3
- def self.included(base)
4
- base.extend ClassMethods
5
- end
6
-
7
- module ClassMethods
8
- def acts_as_captcha
9
- include CaptchaActions::InstanceMethods
10
- end
11
- end
12
-
13
- module InstanceMethods
14
- def index
15
- new unless session[:captcha]
16
- send_file "#{RAILS_ROOT}/public/images/captchas/#{session[:captcha]}.jpg", :type => 'image/jpeg', :disposition => 'inline'
17
- end
18
-
19
- def new
20
- files = Dir["#{RAILS_ROOT}/public/images/captchas/*.jpg"]
21
- session[:captcha] = File.basename(files[rand(files.length)], '.jpg')
22
- index
23
- end
24
- end
25
-
26
- end