airbrush 0.0.2

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.
Files changed (56) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +55 -0
  3. data/README.txt +39 -0
  4. data/Rakefile +4 -0
  5. data/bin/airbrush +61 -0
  6. data/bin/airbrush-example-client +60 -0
  7. data/config/hoe.rb +71 -0
  8. data/config/requirements.rb +17 -0
  9. data/lib/airbrush/client.rb +53 -0
  10. data/lib/airbrush/core_ext/get_args.rb +94 -0
  11. data/lib/airbrush/core_ext/timestamped_logger.rb +10 -0
  12. data/lib/airbrush/handler.rb +18 -0
  13. data/lib/airbrush/listeners/listener.rb +45 -0
  14. data/lib/airbrush/listeners/memcache.rb +53 -0
  15. data/lib/airbrush/listeners/socket.rb +0 -0
  16. data/lib/airbrush/listeners/webservice.rb +0 -0
  17. data/lib/airbrush/processors/image/image_processor.rb +31 -0
  18. data/lib/airbrush/processors/image/profiles/cmyk-profile.icc +0 -0
  19. data/lib/airbrush/processors/image/profiles/srgb-profile.icc +0 -0
  20. data/lib/airbrush/processors/image/rmagick.rb +116 -0
  21. data/lib/airbrush/processors/processor.rb +43 -0
  22. data/lib/airbrush/publishers/http.rb +13 -0
  23. data/lib/airbrush/publishers/memcache.rb +24 -0
  24. data/lib/airbrush/publishers/publisher.rb +16 -0
  25. data/lib/airbrush/server.rb +20 -0
  26. data/lib/airbrush/version.rb +9 -0
  27. data/lib/airbrush.rb +30 -0
  28. data/log/debug.log +0 -0
  29. data/script/destroy +14 -0
  30. data/script/generate +14 -0
  31. data/script/txt2html +74 -0
  32. data/setup.rb +1585 -0
  33. data/spec/airbrush/client_spec.rb +87 -0
  34. data/spec/airbrush/core_ext/get_args_spec.rb +0 -0
  35. data/spec/airbrush/handler_spec.rb +44 -0
  36. data/spec/airbrush/listeners/listener_spec.rb +18 -0
  37. data/spec/airbrush/listeners/memcache_spec.rb +131 -0
  38. data/spec/airbrush/processors/image/image_processor_spec.rb +56 -0
  39. data/spec/airbrush/processors/image/rmagick_spec.rb +179 -0
  40. data/spec/airbrush/processors/processor_spec.rb +110 -0
  41. data/spec/airbrush/publishers/memcache_spec.rb +46 -0
  42. data/spec/airbrush/publishers/publisher_spec.rb +17 -0
  43. data/spec/airbrush/server_spec.rb +57 -0
  44. data/spec/airbrush_spec.rb +9 -0
  45. data/spec/spec.opts +1 -0
  46. data/spec/spec_helper.rb +10 -0
  47. data/tasks/deployment.rake +34 -0
  48. data/tasks/environment.rake +7 -0
  49. data/tasks/rspec.rake +36 -0
  50. data/tasks/website.rake +17 -0
  51. data/website/index.html +11 -0
  52. data/website/index.txt +39 -0
  53. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  54. data/website/stylesheets/screen.css +138 -0
  55. data/website/template.rhtml +48 -0
  56. metadata +161 -0
@@ -0,0 +1,116 @@
1
+ require 'RMagick'
2
+
3
+ module Airbrush
4
+ module Processors
5
+ module Image
6
+ class Rmagick < ImageProcessor
7
+ filter_params :image # ignore any argument called 'image' in any logging
8
+
9
+ def resize(image, width, height = nil)
10
+ width, height = calculate_dimensions(image, width) unless height
11
+
12
+ process image do
13
+ change_geometry("#{width}x#{height}") { |cols, rows, image| image.resize!(cols, rows) }
14
+ end
15
+ end
16
+
17
+ def crop(image, tl_x, tl_y, br_x, br_y)
18
+ process image do
19
+ crop!(tl_x, tl_y, br_x, br_y)
20
+ end
21
+ end
22
+
23
+ def crop_resize(image, width, height = nil)
24
+ width, height = calculate_dimensions(image, width) unless height
25
+
26
+ process image do
27
+ crop_resized!(width, height)
28
+ end
29
+ end
30
+
31
+ def previews(image, sizes) # sizes => { :small => [200,100], :medium => [400,200], :large => [600,300] }
32
+ images = sizes.inject(Hash.new) { |m, (k, v)| m[k] = crop_resize(image, *v); m }
33
+ images[:original] = dimensions(image)
34
+ images
35
+ end
36
+
37
+ protected
38
+
39
+ def process(image, &block)
40
+ img = Magick::Image.from_blob(image).first
41
+ img.instance_eval &block
42
+ img.ensure_rgb!
43
+ img.format = 'JPEG' # ensure that resized output is a JPEG
44
+ {
45
+ :image => img.to_blob, :width => img.columns, :height => img.rows
46
+ }
47
+ end
48
+
49
+ def dimensions(image_data)
50
+ image = Magick::Image.from_blob(image_data).first
51
+ return [image.columns, image.rows]
52
+ end
53
+
54
+ def calculate_dimensions(image_data, size)
55
+ image = Magick::Image.from_blob(image_data).first
56
+ return image.columns, image.rows if clipping_required?(image, size)
57
+
58
+ ratio = image.columns.to_f / image.rows.to_f
59
+
60
+ portrait image do
61
+ return [ size, size.to_f / ratio ]
62
+ end
63
+
64
+ landscape image do
65
+ return [ ratio * size, size ]
66
+ end
67
+
68
+ # Must be a square image.
69
+ return [ size, size ]
70
+ end
71
+
72
+ def clipping_required?(image, size)
73
+ size > image.columns or size > image.rows
74
+ end
75
+
76
+ def portrait(image, &block)
77
+ block.call if image.columns > image.rows
78
+ end
79
+
80
+ def landscape(image, &block)
81
+ block.call if image.columns < image.rows
82
+ end
83
+
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ module Magick
90
+ class Image
91
+ SCT_CMYK_ICC = File.dirname(__FILE__) + '/profiles/cmyk-profile.icc'
92
+ SCT_SRGB_ICC = File.dirname(__FILE__) + '/profiles/srgb-profile.icc'
93
+
94
+ def ensure_rgb!
95
+ return if rgb?
96
+
97
+ if cmyk?
98
+ add_profile SCT_CMYK_ICC if color_profile == nil
99
+ add_profile SCT_SRGB_ICC
100
+ log.debug "Added sRGB profile to image"
101
+ else
102
+ log.warn "Non CMYK/RGB color profile encountered, please install a profile for #{colorspace} and update ensure_rgb! implementation"
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def rgb?
109
+ colorspace == Magick::RGBColorspace
110
+ end
111
+
112
+ def cmyk?
113
+ colorspace == Magick::CMYKColorspace
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,43 @@
1
+ module Airbrush
2
+ module Processors
3
+ class Processor
4
+ class_inheritable_accessor :filtered_params
5
+
6
+ def dispatch(command, args)
7
+ raise "Unknown processor operation #{command} (#{filter(args).inspect unless args.blank?})" unless self.respond_to? command
8
+ returning self.send(command, *assign(command, args)) do
9
+ log.debug "Processed #{command} (#{filter(args).inspect unless args.blank?})"
10
+ end
11
+ rescue Exception => e
12
+ buffer = "ERROR: Received error during processor dispatch for command '#{command}' (#{filter(args).inspect unless args.blank?})"
13
+ log.error buffer; log.error e
14
+ { :exception => buffer, :message => e.message }
15
+ end
16
+
17
+ def self.filter_params(*symbols)
18
+ self.filtered_params = symbols
19
+ end
20
+
21
+ protected
22
+
23
+ def assign(command, args)
24
+ params = ParseTreeArray.translate(self.class, command).get_args
25
+ params.collect do |param|
26
+ name, default = *param
27
+ args[name] ? args[name] : (raise "No value (default or otherwise) provided for #{name} in #{command}" unless default; default)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def filter(args)
34
+ return args if self.filtered_params.blank?
35
+
36
+ args.dup.inject({}) do |m, (k, v)|
37
+ m[k] = self.filtered_params.include?(k) ? '[FILTERED]' : v; m
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ module Airbrush
2
+ module Publishers
3
+ class Http < Publisher
4
+ def initialize(target)
5
+ @target = target
6
+ end
7
+
8
+ def publish(result)
9
+ # http post/put to target
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require 'memcache'
2
+
3
+ module Airbrush
4
+ module Publishers
5
+ class Memcache < Publisher
6
+ attr_reader :host
7
+
8
+ def initialize(host)
9
+ @host = host
10
+ end
11
+
12
+ def publish(id, results)
13
+ # need to calculate an outboue queue name somehow, client will also need this to get the results
14
+ name = unique_name(id)
15
+ queue = MemCache.new(@host)
16
+ queue.set(name, results)
17
+
18
+ log.debug "Published results from #{name}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # resize the dnssd or command line gear in listeners?
@@ -0,0 +1,16 @@
1
+ module Airbrush
2
+ module Publishers
3
+ class Publisher
4
+
5
+ def publish
6
+ raise 'implementations provide concrete publisher functionality'
7
+ end
8
+
9
+ protected
10
+
11
+ def unique_name(id)
12
+ id.to_s
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module Airbrush
2
+ class Server
3
+ attr_reader :listener
4
+
5
+ def initialize(context = {})
6
+ memcache_host = context[:memcache]
7
+ memcache_poll = context[:frequency]
8
+ Object.send :__create_logger__, context[:log_target] if context.include? :log_target
9
+ log.level = context[:verbose] ? ActiveSupport::BufferedLogger::Severity::DEBUG : ActiveSupport::BufferedLogger::Severity::INFO
10
+
11
+ @listener = Airbrush::Listeners::Memcache.new(memcache_host, memcache_poll)
12
+ @listener.handler = Handler.new(Processors::Image::Rmagick.new, Publishers::Memcache.new(memcache_host))
13
+ end
14
+
15
+ def start
16
+ @listener.start
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Airbrush #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/airbrush.rb ADDED
@@ -0,0 +1,30 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ # required gems
4
+ require 'rubygems'
5
+ require 'active_support'
6
+
7
+ ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__)
8
+
9
+ # for the moment lets log dependency loading
10
+ #Dependencies::RAILS_DEFAULT_LOGGER = Logger.new($stdout)
11
+ #Dependencies.log_activity = true
12
+
13
+ # Load up extensions to existing classes
14
+ Dir[File.dirname(__FILE__) + '/airbrush/core_ext/*.rb'].each { |e| require e }
15
+
16
+ module Airbrush
17
+ end
18
+
19
+ class Object
20
+ def log
21
+ @@__log__ ||= __create_logger__($stdout)
22
+ end
23
+
24
+ private
25
+
26
+ def __create_logger__(target)
27
+ @@__log__ = ActiveSupport::BufferedLogger.new(target, ActiveSupport::BufferedLogger::Severity::INFO)
28
+ end
29
+
30
+ end
data/log/debug.log ADDED
File without changes
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/script/txt2html ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'newgem'
6
+ rescue LoadError
7
+ puts "\n\nGenerating the website requires the newgem RubyGem"
8
+ puts "Install: gem install newgem\n\n"
9
+ exit(1)
10
+ end
11
+ require 'redcloth'
12
+ require 'syntax/convertors/html'
13
+ require 'erb'
14
+ require File.dirname(__FILE__) + '/../lib/airbrush/version.rb'
15
+
16
+ version = Airbrush::VERSION::STRING
17
+ download = 'http://rubyforge.org/projects/airbrush'
18
+
19
+ class Fixnum
20
+ def ordinal
21
+ # teens
22
+ return 'th' if (10..19).include?(self % 100)
23
+ # others
24
+ case self % 10
25
+ when 1: return 'st'
26
+ when 2: return 'nd'
27
+ when 3: return 'rd'
28
+ else return 'th'
29
+ end
30
+ end
31
+ end
32
+
33
+ class Time
34
+ def pretty
35
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
36
+ end
37
+ end
38
+
39
+ def convert_syntax(syntax, source)
40
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
41
+ end
42
+
43
+ if ARGV.length >= 1
44
+ src, template = ARGV
45
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
46
+
47
+ else
48
+ puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
49
+ exit!
50
+ end
51
+
52
+ template = ERB.new(File.open(template).read)
53
+
54
+ title = nil
55
+ body = nil
56
+ File.open(src) do |fsrc|
57
+ title_text = fsrc.readline
58
+ body_text = fsrc.read
59
+ syntax_items = []
60
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
61
+ ident = syntax_items.length
62
+ element, syntax, source = $1, $2, $3
63
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
64
+ "syntax-temp-#{ident}"
65
+ }
66
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
67
+ body = RedCloth.new(body_text).to_html
68
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
69
+ end
70
+ stat = File.stat(src)
71
+ created = stat.ctime
72
+ modified = stat.mtime
73
+
74
+ $stdout << template.result(binding)