diff --git a/Gemfile b/Gemfile index 06c6c99..b976348 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,4 @@ source :rubygems gem 'fog' gem 'mime-types' gem 'rake' +gem 'nokogiri' diff --git a/Gemfile.lock b/Gemfile.lock index 6e2d577..73cac4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,4 +29,5 @@ PLATFORMS DEPENDENCIES fog mime-types + nokogiri rake diff --git a/Rakefile b/Rakefile index fcd2cfe..907101d 100644 --- a/Rakefile +++ b/Rakefile @@ -1,125 +1,11 @@ require 'rubygems' require 'bundler' Bundler.require - -require 'digest/sha1' +require_relative 'lib/simple_s3_deploy' +require_relative 'lib/emoji_optimizer' task :deploy do - SimpleS3Deploy.deploy('public') -end - - -module SimpleS3Deploy - - def self.deploy(site_path) - Site.new(site_path).deploy + EmojiOptimizer.new(:size => 22, :padding => 5).optimize! do + SimpleS3Deploy.deploy('public') end - - class Site - attr_accessor :path - attr_accessor :tmp_files - - def initialize(path) - @path = path - @tmp_files = [] - end - - def deploy - puts " ** Deploying #{path} to #{bucket.key}" - puts " ========================================================" - puts " ** Deleting existing remote files" - clear_bucket - puts " ** Uploading files" - files.each do |file| - if !File.directory?(file.path) - remote_file_name = file_base_path(file.path) - puts " Uploading #{remote_file_name} .." - bucket.files.create( - key: remote_file_name, - body: file.is_minifyable? ? open(minify(file.path)) : open(file.path), - public: true, - content_type: file.mime_type, - cache_control: 'max-age=604800, public' ) - end - end - if tmp_files.any? - puts " ** Cleaning up tmp files" - cleanup - end - puts "\n ** Done" - end - - private - - def clear_bucket - bucket.files.each do |f| - puts " Deleting #{f.key}" - f.destroy - end - end - - def cleanup - tmp_files.each do |file| - puts " Deleting #{file}" - File.unlink(file) - end - end - - def files - @files ||= Dir.glob("#{path}/**/*").map { |f| SiteFile.new(f) } - end - - def file_base_path(file) - file.gsub("#{path}/", "") - end - - def minify(file_path) - "#{file_path}-tmp".tap do |tmp_file_name| - `yuicompressor -o #{tmp_file_name} #{file_path}` - tmp_files << tmp_file_name - end - end - - def config - @config ||= YAML.load_file('config.yaml') - end - - def s3 - @s3 ||= Fog::Storage.new({ - provider: 'AWS', - aws_secret_access_key: config['s3']['secret_access_key'], - aws_access_key_id: config['s3']['access_key_id'] }) - end - - def bucket - @bucket ||= s3.directories.get(config['s3']['bucket']) - end - - end - - class SiteFile - attr_accessor :path - - def initialize(_path) - @path = _path - end - - def is_minifyable? - is_css_file? or is_js_file? - end - - def is_css_file? - path.end_with?('.css') - end - - def is_js_file? - path.end_with?('.js') - end - - def mime_type - MIME::Types.type_for(path)[0].to_s - end - - end - end diff --git a/lib/emoji_optimizer.rb b/lib/emoji_optimizer.rb new file mode 100644 index 0000000..e21ed93 --- /dev/null +++ b/lib/emoji_optimizer.rb @@ -0,0 +1,128 @@ +require 'digest/md5' +require 'tmpdir' + +class EmojiOptimizer + + def initialize(options = {}) + @size = options.delete(:size) { 22 } + @padding = options.delete(:padding) { 5 } + @source = Source.new('public/index.html') + end + + def optimize!(&block) + puts " ** Preparing for optimization" + prepare + + puts " ** Generating emoji sprite image" + if @source.create_sprite(@size, @padding, sprite_path) + puts " ** Generating css and updating markup" + generate_and_save + else + puts " ** Could not generate emoji sprite =(" + end + + yield + + ensure + puts " ** Cleaning up after optimization" + cleanup + end + + private + + def prepare + FileUtils.cp 'public/index.html', File.join(tmp_dir, 'index.html') + FileUtils.cp 'public/emoji.css', File.join(tmp_dir, 'emoji.css') + end + + def generate_and_save + update_source_markup + + File.open('public/emoji.css', 'a') { |f| f.puts css_rules.join("\n") } + File.open('public/index.html','w') { |f| f.write @source.to_html } + FileUtils.mv sprite_path, "public/graphics/#{digest_name}" + end + + def cleanup + FileUtils.mv File.join(tmp_dir, 'index.html'), 'public/index.html' + FileUtils.mv File.join(tmp_dir, 'emoji.css'), 'public/emoji.css' + FileUtils.rm_f "public/graphics/#{@digest_name}" + end + + def css_rules + [].tap do |rules| + rules << %Q{ + .emoji { + display:inline-block; + width:#{@size}px; + height:#{@size}px; + background:transparent url(/graphics/#{digest_name}) 0 0 no-repeat; + } + } + @source.emojis.size.times do |index| + rules << css_sprite_mapping(index) + end + end + end + + def update_source_markup + @source.emojis.each_with_index do |img, index| + img.replace @source.create_element('span', { + 'id' => "e_#{index+1}", + 'class' => 'emoji', + 'data-src' => img['src'] + }) + end + end + + def css_sprite_mapping(index) + offset = ((@size + @padding * 2) * index) + @padding + "#e_#{index+1} { background-position:-#{offset}px 0; }" + end + + def sprite_path + @sprite_path ||= File.join(tmp_dir, 'sprite.png') + end + + def digest_name + @digest_name ||= "sprite_#{Digest::MD5.hexdigest(File.read(sprite_path))}.png" + end + + def tmp_dir + @tmp_dir ||= Dir.mktmpdir 'emoji-optimization' + end + + class Source + + def initialize(file) + @file = file + end + + def emojis + @emojis ||= doc.css('#content img').find_all { |img| img['src'] =~ /emojis/ } + end + + def emoji_paths + @emoji_paths ||= emojis.map { |img| File.join('public', img['src']) } + end + + def create_sprite(size, padding, path) + system "montage #{emoji_paths.join(' ')} -tile x1 -geometry #{size}x#{size}+#{padding} -background transparent #{path}" + end + + def create_element(*args) + doc.create_element *args + end + + def to_html + doc.to_html + end + + private + + def doc + @doc ||= Nokogiri::HTML(File.open(@file)) + end + + end +end diff --git a/lib/simple_s3_deploy.rb b/lib/simple_s3_deploy.rb new file mode 100644 index 0000000..b636675 --- /dev/null +++ b/lib/simple_s3_deploy.rb @@ -0,0 +1,114 @@ +module SimpleS3Deploy + + def self.deploy(site_path) + Site.new(site_path).deploy + end + + class Site + attr_accessor :path + attr_accessor :tmp_files + + def initialize(path) + @path = path + @tmp_files = [] + end + + def deploy + puts " ** Deploying #{path} to #{bucket.key}" + puts " ========================================================" + puts " ** Deleting existing remote files" + clear_bucket + puts " ** Uploading files" + files.each do |file| + if !File.directory?(file.path) + remote_file_name = file_base_path(file.path) + puts " Uploading #{remote_file_name} .." + bucket.files.create( + key: remote_file_name, + body: file.is_minifyable? ? open(minify(file.path)) : open(file.path), + public: true, + content_type: file.mime_type, + cache_control: 'max-age=604800, public' ) + end + end + if tmp_files.any? + puts " ** Cleaning up tmp files" + cleanup + end + puts "\n ** Done" + end + + private + + def clear_bucket + bucket.files.each do |f| + puts " Deleting #{f.key}" + f.destroy + end + end + + def cleanup + tmp_files.each do |file| + puts " Deleting #{file}" + File.unlink(file) + end + end + + def files + @files ||= Dir.glob("#{path}/**/*").map { |f| SiteFile.new(f) } + end + + def file_base_path(file) + file.gsub("#{path}/", "") + end + + def minify(file_path) + "#{file_path}-tmp".tap do |tmp_file_name| + `yuicompressor -o #{tmp_file_name} #{file_path}` + tmp_files << tmp_file_name + end + end + + def config + @config ||= YAML.load_file('config.yaml') + end + + def s3 + @s3 ||= Fog::Storage.new({ + provider: 'AWS', + aws_secret_access_key: config['s3']['secret_access_key'], + aws_access_key_id: config['s3']['access_key_id'] }) + end + + def bucket + @bucket ||= s3.directories.get(config['s3']['bucket']) + end + + end + + class SiteFile + attr_accessor :path + + def initialize(_path) + @path = _path + end + + def is_minifyable? + is_css_file? or is_js_file? + end + + def is_css_file? + path.end_with?('.css') + end + + def is_js_file? + path.end_with?('.js') + end + + def mime_type + MIME::Types.type_for(path)[0].to_s + end + + end + +end