asset_id 0.2.0 → 0.2.1

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.
@@ -0,0 +1,154 @@
1
+ require 'digest/md5'
2
+ require 'mime/types'
3
+ require 'time'
4
+ require 'zlib'
5
+
6
+ module AssetID
7
+ class Asset
8
+
9
+ DEFAULT_ASSET_PATHS = ['favicon.ico', 'images', 'javascripts', 'stylesheets']
10
+ @@asset_paths = DEFAULT_ASSET_PATHS
11
+
12
+ DEFAULT_GZIP_TYPES = ['text/css', 'application/javascript']
13
+ @@gzip_types = DEFAULT_GZIP_TYPES
14
+
15
+ @@debug = false
16
+ @@nocache = false
17
+
18
+ def self.init(options)
19
+ @@debug = options[:debug] if options[:debug]
20
+ @@nocache = options[:nocache] if options[:nocache]
21
+ end
22
+
23
+ def self.asset_paths
24
+ @@asset_paths
25
+ end
26
+
27
+ def self.gzip_types
28
+ @@gzip_types
29
+ end
30
+
31
+ def self.asset_paths=(paths)
32
+ @@asset_paths = paths
33
+ end
34
+
35
+ def self.gzip_types=(types)
36
+ @@gzip_types = types
37
+ end
38
+
39
+ def self.find(paths=Asset.asset_paths)
40
+ paths.inject([]) {|assets, path|
41
+ path = File.join Rails.root, 'public', path
42
+ a = Asset.new(path)
43
+ assets << a if a.is_file? and !a.cache_hit?
44
+ assets += Dir.glob(path+'/**/*').inject([]) {|m, file|
45
+ a = Asset.new(file); m << a if a.is_file? and !a.cache_hit?; m
46
+ }
47
+ }
48
+ end
49
+
50
+ def self.fingerprint(path)
51
+ Asset.new(path).fingerprint
52
+ end
53
+
54
+ attr_reader :path
55
+
56
+ def initialize(path)
57
+ @path = path
58
+ @path = absolute_path
59
+ end
60
+
61
+ def path_prefix
62
+ File.join Rails.root, 'public'
63
+ end
64
+
65
+ def absolute_path
66
+ path =~ /#{path_prefix}/ ? path : File.join(path_prefix, path)
67
+ end
68
+
69
+ def relative_path
70
+ path.gsub(path_prefix, '')
71
+ end
72
+
73
+ def gzip_type?
74
+ Asset.gzip_types.include? mime_type
75
+ end
76
+
77
+ def data
78
+ @data ||= File.read(path)
79
+ end
80
+
81
+ def md5
82
+ @digest ||= Digest::MD5.hexdigest(data)
83
+ end
84
+
85
+ def fingerprint
86
+ p = relative_path
87
+ File.join File.dirname(p), "#{File.basename(p, File.extname(p))}-id-#{md5}#{File.extname(p)}"
88
+ end
89
+
90
+ def mime_type
91
+ MIME::Types.of(path).first.to_s
92
+ end
93
+
94
+ def css?
95
+ mime_type == 'text/css'
96
+ end
97
+
98
+ def replace_css_images!(options={})
99
+ options[:prefix] ||= ''
100
+ # adapted from https://github.com/blakink/asset_id
101
+ data.gsub! /url\((?:"([^"]*)"|'([^']*)'|([^)]*))\)/mi do |match|
102
+ begin
103
+ # $1 is the double quoted string, $2 is single quoted, $3 is no quotes
104
+ uri = ($1 || $2 || $3).to_s.strip
105
+ uri.gsub!(/^\.\.\//, '/')
106
+
107
+ # if the uri appears to begin with a protocol then the asset isn't on the local filesystem
108
+ if uri =~ /[a-z]+:\/\//i
109
+ "url(#{uri})"
110
+ else
111
+ asset = Asset.new(uri)
112
+ puts " - Changing CSS URI #{uri} to #{options[:prefix]}#{asset.fingerprint}" if @@debug
113
+ "url(#{options[:prefix]}#{asset.fingerprint})"
114
+ end
115
+ rescue Errno::ENOENT => e
116
+ puts " - Warning: #{uri} not found" if @@debug
117
+ "url(#{uri})"
118
+ end
119
+ end
120
+ end
121
+
122
+ def gzip!
123
+ # adapted from https://github.com/blakink/asset_id
124
+ @data = returning StringIO.open('', 'w') do |gz_data|
125
+ gz = Zlib::GzipWriter.new(gz_data, nil, nil)
126
+ gz.write(data)
127
+ gz.close
128
+ end.string
129
+ end
130
+
131
+ def expiry_date
132
+ @expiry_date ||= (Time.now + (60*60*24*365)).httpdate
133
+ end
134
+
135
+ def cache_headers
136
+ {'Expires' => expiry_date, 'Cache-Control' => 'public'} # 1 year expiry
137
+ end
138
+
139
+ def gzip_headers
140
+ {'Content-Encoding' => 'gzip', 'Vary' => 'Accept-Encoding'}
141
+ end
142
+
143
+ def is_file?
144
+ File.exists? absolute_path and !File.directory? absolute_path
145
+ end
146
+
147
+ def cache_hit?
148
+ return false if @@nocache or Cache.miss? self
149
+ puts "AssetID: #{relative_path} - Cache Hit"
150
+ return true
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,73 @@
1
+ require 'aws/s3'
2
+
3
+ module AssetID
4
+ class S3
5
+
6
+ def self.s3_config
7
+ @@config ||= YAML.load_file(File.join(Rails.root, "config/asset_id.yml"))[Rails.env] rescue nil || {}
8
+ end
9
+
10
+ def self.connect_to_s3
11
+ AWS::S3::Base.establish_connection!(
12
+ :access_key_id => s3_config['access_key_id'],
13
+ :secret_access_key => s3_config['secret_access_key']
14
+ )
15
+ end
16
+
17
+ def self.s3_permissions
18
+ :public_read
19
+ end
20
+
21
+ def self.s3_bucket
22
+ s3_config['bucket']
23
+ end
24
+
25
+ def self.s3_prefix
26
+ s3_config['prefix']
27
+ end
28
+
29
+ def self.upload(options={})
30
+ Asset.init(:debug => options[:debug])
31
+
32
+ assets = Asset.find
33
+ return if assets.empty?
34
+
35
+ connect_to_s3
36
+
37
+ assets.each do |asset|
38
+
39
+ puts "AssetID: #{asset.relative_path}" if options[:debug]
40
+
41
+ headers = {
42
+ :content_type => asset.mime_type,
43
+ :access => s3_permissions,
44
+ }.merge(asset.cache_headers)
45
+
46
+ asset.replace_css_images!(:prefix => s3_prefix) if asset.css?
47
+
48
+ if asset.gzip_type?
49
+ headers.merge!(asset.gzip_headers)
50
+ asset.gzip!
51
+ end
52
+
53
+ if options[:debug]
54
+ puts " - Uploading: #{asset.fingerprint} [#{asset.data.size} bytes]"
55
+ puts " - Headers: #{headers.inspect}"
56
+ end
57
+
58
+ unless options[:dry_run]
59
+ res = AWS::S3::S3Object.store(
60
+ asset.fingerprint,
61
+ asset.data,
62
+ s3_bucket,
63
+ headers
64
+ )
65
+ puts " - Response: #{res.inspect}" if options[:debug]
66
+ end
67
+ end
68
+
69
+ Cache.save! unless options[:dry_run]
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,33 @@
1
+ require 'yaml'
2
+
3
+ module AssetID
4
+ class Cache
5
+
6
+ def self.empty
7
+ @cache = {}
8
+ end
9
+
10
+ def self.cache
11
+ @cache ||= YAML.load_file(cache_path) rescue {}
12
+ end
13
+
14
+ def self.cache_path
15
+ File.join(Rails.root, 'log', 'asset_id_cache.yml')
16
+ end
17
+
18
+ def self.hit?(asset)
19
+ return true if cache[asset.relative_path] and cache[asset.relative_path][:fingerprint] == asset.fingerprint
20
+ cache[asset.relative_path] = {:expires => asset.expiry_date.to_s, :fingerprint => asset.fingerprint}
21
+ false
22
+ end
23
+
24
+ def self.miss?(asset)
25
+ !hit?(asset)
26
+ end
27
+
28
+ def self.save!
29
+ File.open(cache_path, 'w') {|f| f.write(YAML.dump(cache))}
30
+ end
31
+
32
+ end
33
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: asset_id
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.2.0
5
+ version: 0.2.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Richard Taylor
@@ -47,6 +47,9 @@ files:
47
47
  - LICENSE
48
48
  - README.textile
49
49
  - lib/asset_id.rb
50
+ - lib/asset_id/asset.rb
51
+ - lib/asset_id/cache.rb
52
+ - lib/asset_id/backend/s3.rb
50
53
  has_rdoc: true
51
54
  homepage: http://github.com/moomerman/asset_id
52
55
  licenses: []