ktheory-juicer 1.0.0.ktheory1

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 (98) hide show
  1. data/History.txt +30 -0
  2. data/Manifest.txt +58 -0
  3. data/Rakefile +96 -0
  4. data/Readme.rdoc +312 -0
  5. data/VERSION +1 -0
  6. data/bin/juicer +8 -0
  7. data/lib/juicer.rb +70 -0
  8. data/lib/juicer/asset/path.rb +275 -0
  9. data/lib/juicer/asset/path_resolver.rb +79 -0
  10. data/lib/juicer/binary.rb +171 -0
  11. data/lib/juicer/cache_buster.rb +130 -0
  12. data/lib/juicer/chainable.rb +106 -0
  13. data/lib/juicer/cli.rb +56 -0
  14. data/lib/juicer/command/install.rb +61 -0
  15. data/lib/juicer/command/list.rb +57 -0
  16. data/lib/juicer/command/merge.rb +205 -0
  17. data/lib/juicer/command/util.rb +32 -0
  18. data/lib/juicer/command/verify.rb +60 -0
  19. data/lib/juicer/css_cache_buster.rb +80 -0
  20. data/lib/juicer/datafy/datafy.rb +20 -0
  21. data/lib/juicer/dependency_resolver/css_dependency_resolver.rb +29 -0
  22. data/lib/juicer/dependency_resolver/dependency_resolver.rb +101 -0
  23. data/lib/juicer/dependency_resolver/javascript_dependency_resolver.rb +23 -0
  24. data/lib/juicer/ext/logger.rb +5 -0
  25. data/lib/juicer/ext/string.rb +47 -0
  26. data/lib/juicer/ext/symbol.rb +15 -0
  27. data/lib/juicer/image_embed.rb +136 -0
  28. data/lib/juicer/install/base.rb +186 -0
  29. data/lib/juicer/install/closure_compiler_installer.rb +69 -0
  30. data/lib/juicer/install/jslint_installer.rb +51 -0
  31. data/lib/juicer/install/rhino_installer.rb +53 -0
  32. data/lib/juicer/install/yui_compressor_installer.rb +67 -0
  33. data/lib/juicer/jslint.rb +90 -0
  34. data/lib/juicer/merger/base.rb +74 -0
  35. data/lib/juicer/merger/javascript_merger.rb +29 -0
  36. data/lib/juicer/merger/stylesheet_merger.rb +110 -0
  37. data/lib/juicer/minifyer/closure_compiler.rb +90 -0
  38. data/lib/juicer/minifyer/java_base.rb +77 -0
  39. data/lib/juicer/minifyer/yui_compressor.rb +96 -0
  40. data/test/bin/jslint-1.0.js +523 -0
  41. data/test/bin/jslint.js +523 -0
  42. data/test/bin/rhino1_7R1.zip +0 -0
  43. data/test/bin/rhino1_7R2-RC1.jar +0 -0
  44. data/test/bin/rhino1_7R2-RC1.zip +0 -0
  45. data/test/bin/yuicompressor +0 -0
  46. data/test/bin/yuicompressor-2.3.5.zip +0 -0
  47. data/test/bin/yuicompressor-2.4.2.jar +0 -0
  48. data/test/bin/yuicompressor-2.4.2.zip +0 -0
  49. data/test/data/Changelog.txt +10 -0
  50. data/test/data/a.css +3 -0
  51. data/test/data/a.js +5 -0
  52. data/test/data/a1.css +5 -0
  53. data/test/data/b.css +1 -0
  54. data/test/data/b.js +5 -0
  55. data/test/data/b1.css +5 -0
  56. data/test/data/c1.css +3 -0
  57. data/test/data/css/2.gif +1 -0
  58. data/test/data/css/test.css +11 -0
  59. data/test/data/css/test2.css +1 -0
  60. data/test/data/d1.css +3 -0
  61. data/test/data/images/1.png +1 -0
  62. data/test/data/my_app.js +2 -0
  63. data/test/data/not-ok.js +2 -0
  64. data/test/data/ok.js +3 -0
  65. data/test/data/path_test.css +5 -0
  66. data/test/data/path_test2.css +14 -0
  67. data/test/data/pkg/module/moda.js +2 -0
  68. data/test/data/pkg/module/modb.js +3 -0
  69. data/test/data/pkg/pkg.js +1 -0
  70. data/test/test_helper.rb +169 -0
  71. data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
  72. data/test/unit/juicer/asset/path_test.rb +370 -0
  73. data/test/unit/juicer/cache_buster_test.rb +104 -0
  74. data/test/unit/juicer/chainable_test.rb +94 -0
  75. data/test/unit/juicer/command/install_test.rb +58 -0
  76. data/test/unit/juicer/command/list_test.rb +81 -0
  77. data/test/unit/juicer/command/merge_test.rb +162 -0
  78. data/test/unit/juicer/command/util_test.rb +58 -0
  79. data/test/unit/juicer/command/verify_test.rb +48 -0
  80. data/test/unit/juicer/css_cache_buster_test.rb +71 -0
  81. data/test/unit/juicer/datafy_test.rb +37 -0
  82. data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
  83. data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
  84. data/test/unit/juicer/ext/string_test.rb +59 -0
  85. data/test/unit/juicer/ext/symbol_test.rb +27 -0
  86. data/test/unit/juicer/image_embed_test.rb +271 -0
  87. data/test/unit/juicer/install/installer_base_test.rb +214 -0
  88. data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
  89. data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
  90. data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
  91. data/test/unit/juicer/jslint_test.rb +60 -0
  92. data/test/unit/juicer/merger/base_test.rb +122 -0
  93. data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
  94. data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
  95. data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
  96. data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
  97. data/test/unit/juicer_test.rb +1 -0
  98. metadata +265 -0
@@ -0,0 +1,32 @@
1
+ require "pathname"
2
+
3
+ module Juicer
4
+ module Command
5
+ # Utilities for Juicer command objects
6
+ #
7
+ module Util
8
+ # Returns an array of files from a variety of input. Input may be a single
9
+ # file, a single glob pattern or multiple files and/or patterns. It may
10
+ # even be an array of mixed input.
11
+ #
12
+ def files(*args)
13
+ args.flatten.collect { |file| Dir.glob(file) }.flatten
14
+ end
15
+
16
+ #
17
+ # Uses Pathname to calculate the shortest relative path from +path+ to
18
+ # +reference_path+ (default is +Dir.cwd+)
19
+ #
20
+ def relative(paths, reference_path = Dir.pwd)
21
+ paths = [paths].flatten.collect do |path|
22
+ path = Pathname.new(File.expand_path(path))
23
+ reference_path = Pathname.new(File.expand_path(reference_path))
24
+ path.relative_path_from(reference_path).to_s
25
+ end
26
+
27
+ paths.length == 1 ? paths.first : paths
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,60 @@
1
+ require "juicer/command/util"
2
+ require "rubygems"
3
+ require "cmdparse"
4
+ require "pathname"
5
+
6
+ module Juicer
7
+ module Command
8
+ # Verifies problem-free-ness of source code (JavaScript and CSS)
9
+ #
10
+ class Verify < CmdParse::Command
11
+ include Juicer::Command::Util
12
+
13
+ # Initializes command
14
+ #
15
+ def initialize(log = nil)
16
+ super('verify', false, true)
17
+ @log = log || Logger.new($STDIO)
18
+ self.short_desc = "Verifies that the given JavaScript/CSS file is problem free"
19
+ self.description = <<-EOF
20
+ Uses JsLint (http://www.jslint.com) to check that code adheres to good coding
21
+ practices to avoid potential bugs, and protect against introducing bugs by
22
+ minifying.
23
+ EOF
24
+ end
25
+
26
+ # Execute command
27
+ #
28
+ def execute(args)
29
+ # Need atleast one file
30
+ raise ArgumentError.new('Please provide atleast one input file/pattern') if args.length == 0
31
+ Juicer::Command::Verify.check_all(files(args), @log)
32
+ end
33
+
34
+ def self.check_all(files, log = nil)
35
+ log ||= Logger.new($stdio)
36
+ jslint = Juicer::JsLint.new(:bin_path => Juicer.home)
37
+ problems = false
38
+
39
+ # Check that JsLint is installed
40
+ raise FileNotFoundError.new("Missing 3rd party library JsLint, install with\njuicer install jslint") if jslint.locate_lib.nil?
41
+
42
+ # Verify all files
43
+ files.each do |file|
44
+ log.info "Verifying #{file} with JsLint"
45
+ report = jslint.check(file)
46
+
47
+ if report.ok?
48
+ log.info " OK!"
49
+ else
50
+ problems = true
51
+ log.warn " Problems detected"
52
+ log.warn " #{report.errors.join("\n").gsub(/\n/, "\n ")}\n"
53
+ end
54
+ end
55
+
56
+ !problems
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,80 @@
1
+ require "juicer/chainable"
2
+ require "juicer/cache_buster"
3
+ require "juicer/asset/path_resolver"
4
+
5
+ module Juicer
6
+ #
7
+ # The CssCacheBuster is a tool that can parse a CSS file and substitute all
8
+ # referenced URLs by a URL appended with a timestamp denoting it's last change.
9
+ # This causes the URLs to be unique every time they've been modified, thus
10
+ # facilitating using a far future expires header on your web server.
11
+ #
12
+ # See Juicer::CacheBuster for more information on how the cache buster URLs
13
+ # work.
14
+ #
15
+ # When dealing with CSS files that reference absolute URLs like /images/1.png
16
+ # you must specify the :web_root option that these URLs should be resolved
17
+ # against.
18
+ #
19
+ # When dealing with full URLs (ie including hosts) you can optionally specify
20
+ # an array of hosts to recognize as "local", meaning they serve assets from
21
+ # the :web_root directory. This way even asset host cycling can benefit from
22
+ # cache busters.
23
+ #
24
+ class CssCacheBuster
25
+ include Juicer::Chainable
26
+
27
+ def initialize(options = {})
28
+ @web_root = options[:web_root]
29
+ @web_root.sub!(%r{/?$}, "") if @web_root
30
+ @type = options[:type] || :soft
31
+ @hosts = (options[:hosts] || []).collect { |h| h.sub!(%r{/?$}, "") }
32
+ @contents = nil
33
+ @path_resolver = Juicer::Asset::PathResolver.new(:document_root => options[:web_root],
34
+ :hosts => options[:hosts])
35
+ end
36
+
37
+ #
38
+ # Update file. If no +output+ is provided, the input file is overwritten
39
+ #
40
+ def save(file, output = nil)
41
+ @contents = File.read(file)
42
+ @path_resolver = Juicer::Asset::PathResolver.new(:document_root => @web_root,
43
+ :hosts => @hosts,
44
+ :base => File.dirname(file))
45
+ used = []
46
+
47
+ urls(file).each do |asset|
48
+ begin
49
+ next if used.include?(asset.path)
50
+ @contents.gsub!(asset.path, asset.path(:cache_buster_type => @type))
51
+ rescue Errno::ENOENT
52
+ puts "Unable to locate file #{asset.path}, skipping cache buster"
53
+ rescue ArgumentError => e
54
+ if e.message =~ /No document root/
55
+ raise FileNotFoundError.new("Unable to resolve path #{asset.path} without :web_root option")
56
+ else
57
+ raise e
58
+ end
59
+ end
60
+ end
61
+
62
+ File.open(output || file, "w") { |f| f.puts @contents }
63
+ @contents = nil
64
+ end
65
+
66
+ chain_method :save
67
+
68
+ #
69
+ # Returns all referenced URLs in +file+. Returned paths are absolute (ie,
70
+ # they're resolved relative to the +file+ path.
71
+ #
72
+ def urls(file)
73
+ @contents = File.read(file) unless @contents
74
+
75
+ @contents.scan(/url\([\s"']*([^\)"'\s]*)[\s"']*\)/m).collect do |match|
76
+ @path_resolver.resolve(match.first)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ # Datafy code lifted from http://segment7.net/projects/ruby/datafy/
4
+
5
+ require 'base64'
6
+ require 'cgi'
7
+
8
+ module Datafy
9
+ def Datafy::make_data_uri(content, content_type)
10
+ outuri = 'data:' + content_type
11
+ unless content_type =~ /^text/i # base64 encode if not text
12
+ outuri += ';base64'
13
+ content = Base64.encode64(content).gsub("\n", '')
14
+ else
15
+ content = CGI::escape(content)
16
+ end
17
+ outuri += ",#{content}"
18
+ end
19
+
20
+ end
@@ -0,0 +1,29 @@
1
+ require "juicer/dependency_resolver/dependency_resolver"
2
+
3
+ module Juicer
4
+
5
+ # Resolves @import statements in CSS files and builds a list of all
6
+ # files, in order.
7
+ #
8
+ class CssDependencyResolver < DependencyResolver
9
+ # Regexp borrowed from similar project:
10
+ # http://github.com/cgriego/front-end-blender/tree/master/lib/front_end_architect/blender.rb
11
+ @@import_pattern = /^\s*@import(?: url\(| )(['"]?)([^\?'"\)\s]+)(\?(?:[^'"\)]+)?)?\1\)?(?:[^?;]+)?;?/im
12
+
13
+ private
14
+ def parse(line, imported_file = nil)
15
+ return $2 if line =~ @@import_pattern
16
+
17
+ # At first sight of actual CSS rules we abort (TODO: This does not take
18
+ # into account the fact that rules may be commented out and that more
19
+ # imports may follow)
20
+ throw :done if imported_file && line =~ %r{/*}
21
+ throw :done if line =~ /^[\.\#a-zA-Z\:]/
22
+ end
23
+
24
+ def extension
25
+ ".css"
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,101 @@
1
+ module Juicer
2
+ class DependencyResolver
3
+ include Enumerable
4
+ attr_reader :files
5
+
6
+ # Constructor
7
+ def initialize(options = {})
8
+ @files = []
9
+ @options = options
10
+ end
11
+
12
+ #
13
+ # Resolve dependencies.
14
+ # This method accepts an optional block. The block will receive each
15
+ # file in succession. The file is included in the returned collection
16
+ # if the block is true for the given file. Without a block every found
17
+ # file is returned.
18
+ #
19
+ def resolve(file, &block)
20
+ @files = []
21
+ _resolve(file, &block)
22
+ end
23
+
24
+ #
25
+ # Yield files recursively. Resolve dependencies first, then call each, or
26
+ # any other enumerable methods.
27
+ #
28
+ def each(&block)
29
+ @files.each(&block)
30
+ end
31
+
32
+ #
33
+ # Resolves a path relative to another. If the path is absolute (ie it
34
+ # starts with a protocol or /) the <tt>:web_root</tt> options has to be
35
+ # set as well.
36
+ #
37
+ def resolve_path(path, reference)
38
+ # Absolute URL
39
+ if path =~ %r{^(/|[a-z]+:)}
40
+ if @options[:web_root].nil?
41
+ msg = "Cannot resolve absolute path '#{path}' without web root option"
42
+ raise ArgumentError.new(msg)
43
+ end
44
+
45
+ path.sub!(%r{^[a-z]+://[^/]+/}, '')
46
+ return File.expand_path(File.join(@options[:web_root], path))
47
+ end
48
+
49
+ File.expand_path(File.join(File.dirname(reference), path))
50
+ end
51
+
52
+ private
53
+ def parse(line)
54
+ raise NotImplementedError.new
55
+ end
56
+
57
+ def extension
58
+ raise NotImplementedError.new
59
+ end
60
+
61
+ #
62
+ # Carries out the actual work of resolve. resolve resets the internal
63
+ # file list and yields control to _resolve for rebuilding the file list.
64
+ #
65
+ def _resolve(file)
66
+ imported_path = nil
67
+
68
+ IO.foreach(file) do |line|
69
+ # Implementing subclasses may throw :done from the parse method when
70
+ # the file is exhausted for dependency declaration possibilities.
71
+ catch(:done) do
72
+ imported_path = parse(line, imported_path)
73
+
74
+ # If a dependency declaration was found
75
+ if imported_path
76
+ # Resolves a path relative to the file that imported it
77
+ imported_path = resolve_path(imported_path, file)
78
+
79
+ if File.directory?(imported_path)
80
+ imported_files = Dir.glob(File.join(imported_path, "**", "*#{extension}"))
81
+ else
82
+ imported_files = [imported_path]
83
+ end
84
+
85
+ imported_files.each do |imported_file|
86
+ # Only keep processing file if it's not already included.
87
+ # Yield to block to allow caller to ignore file
88
+ if !@files.include?(imported_file) && (!block_given? || yield(imported_file))
89
+ # Check this file for imports before adding it to get order right
90
+ _resolve(imported_file) { |f| f != File.expand_path(file) }
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ file = File.expand_path(file)
98
+ @files << file if !@files.include?(file) && (!block_given? || yield(file))
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,23 @@
1
+ require "juicer/dependency_resolver/dependency_resolver"
2
+
3
+ module Juicer
4
+ # Resolves @depends and @depend statements in comments in JavaScript files.
5
+ # Only the first comment in a JavaScript file is parsed
6
+ #
7
+ class JavaScriptDependencyResolver < DependencyResolver
8
+ @@depends_pattern = /\@depends?\s+([^\s\'\"\;]+)/
9
+
10
+ private
11
+ def parse(line, imported_file = nil)
12
+ return $1 if line =~ @@depends_pattern
13
+
14
+ # If we have already skimmed through some @depend/@depends or a
15
+ # closing comment we're done.
16
+ throw :done unless imported_file.nil? || !(line =~ /\*\//)
17
+ end
18
+
19
+ def extension
20
+ ".js"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ class Logger
2
+ def format_message(severity, datetime, progname, msg)
3
+ "#{msg}\n"
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ #
2
+ # Additions to core Ruby objects
3
+ #
4
+
5
+ class String
6
+
7
+ unless String.method_defined?(:camel_case)
8
+ #
9
+ # Turn an underscored string into camel case, ie this_becomes -> ThisBecomes
10
+ #
11
+ def camel_case
12
+ self.split("_").inject("") { |str, piece| str + piece.capitalize }
13
+ end
14
+ end
15
+
16
+ unless String.method_defined?(:to_class)
17
+ #
18
+ # Treat a string as a class name and return the class. Optionally provide a
19
+ # module to look up the class in.
20
+ #
21
+ def to_class(mod = nil)
22
+ res = "#{mod}::#{self}".sub(/^::/, "").split("::").inject(Object) do |mod, obj|
23
+ raise "No such class/module" unless mod.const_defined?(obj)
24
+ mod = mod.const_get(obj)
25
+ end
26
+ end
27
+ end
28
+
29
+ unless String.method_defined?(:classify)
30
+ #
31
+ # Turn a string in either underscore or camel case form into a class directly
32
+ #
33
+ def classify(mod = nil)
34
+ self.camel_case.to_class(mod)
35
+ end
36
+ end
37
+
38
+ unless String.method_defined?(:underscore)
39
+ #
40
+ # Turn a camelcase string into underscore string
41
+ #
42
+ def underscore
43
+ self.split(/([A-Z][^A-Z]*)/).find_all { |str| str != "" }.join("_").downcase
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,15 @@
1
+ class Symbol
2
+ #
3
+ # Converts symbol to string and calls String#camel_case
4
+ #
5
+ def camel_case
6
+ self.to_s.camel_case
7
+ end
8
+
9
+ #
10
+ # Converts symbol to string and calls String#classify
11
+ #
12
+ def classify(mod = nil)
13
+ self.to_s.classify(mod)
14
+ end
15
+ end
@@ -0,0 +1,136 @@
1
+ require "juicer/chainable"
2
+ require "juicer/cache_buster"
3
+ require "juicer/asset/path_resolver"
4
+
5
+ module Juicer
6
+ #
7
+ # The ImageEmbed is a tool that can parse a CSS file and substitute all
8
+ # referenced URLs by a data uri
9
+ #
10
+ # - data uri (http://en.wikipedia.org/wiki/Data_URI_scheme)
11
+ #
12
+ # Only local resources will be processed this way, external resources referenced
13
+ # by absolute urls will be left alone
14
+ #
15
+ class ImageEmbed
16
+ include Juicer::Chainable
17
+
18
+ # The maximum supported limit for modern browsers, See the Readme.rdoc for details
19
+ SIZE_LIMIT = 32768
20
+
21
+ #
22
+ # Returns the size limit
23
+ #
24
+ def size_limit
25
+ SIZE_LIMIT
26
+ end
27
+
28
+ def initialize(options = {})
29
+ @web_root = options[:web_root]
30
+ @web_root.sub!(%r{/?$}, "") if @web_root # Remove trailing slash
31
+ @type = options[:type] || :none
32
+ @contents = nil
33
+ @hosts = options[:hosts]
34
+ @path_resolver = Juicer::Asset::PathResolver.new(:document_root => options[:web_root],
35
+ :hosts => options[:hosts])
36
+ end
37
+
38
+ #
39
+ # Update file. If no +output+ is provided, the input file is overwritten
40
+ #
41
+ def save(file, output = nil)
42
+ return unless @type == :data_uri
43
+
44
+ output_file = output || file
45
+ @contents = File.read(file)
46
+ used = []
47
+
48
+ @path_resolver = Juicer::Asset::PathResolver.new(:document_root => @web_root,
49
+ :hosts => @hosts,
50
+ :base => File.dirname(file))
51
+
52
+ assets = urls(file)
53
+
54
+ # TODO: Remove "?embed=true" from duplicate urls
55
+ duplicates = duplicate_urls(assets)
56
+
57
+ if duplicates.length > 0
58
+ Juicer::LOGGER.warn("Duplicate image urls detected, these images will not be embedded: #{duplicates.collect { |v| v.gsub('?embed=true', '') }.inspect}")
59
+ end
60
+
61
+ assets.each do |asset|
62
+ begin
63
+ next if used.include?(asset) || duplicates.include?(asset.path)
64
+ used << asset
65
+
66
+ # make sure we do not exceed SIZE_LIMIT
67
+ new_path = embed_data_uri(asset.filename)
68
+
69
+ if new_path.length < SIZE_LIMIT
70
+ # replace the url in the css file with the data uri
71
+ @contents.gsub!(asset.path, embed_data_uri(asset.path))
72
+ else
73
+ Juicer::LOGGER.warn("The final data uri for the image located at #{asset.path.gsub('?embed=true', '')} exceeds #{SIZE_LIMIT} and will not be embedded to maintain compatability.")
74
+ end
75
+ rescue Errno::ENOENT
76
+ puts "Unable to locate file #{asset.path}, skipping image embedding"
77
+ end
78
+ end
79
+
80
+ File.open(output || file, "w") { |f| f.puts @contents }
81
+ @contents = nil
82
+ end
83
+
84
+ chain_method :save
85
+
86
+ def embed_data_uri( path )
87
+ new_path = path
88
+
89
+ if path.match( /\?embed=true$/ )
90
+ supported_file_matches = path.match( /(?:\.)(png|gif|jpg|jpeg)(?:\?embed=true)$/i )
91
+ filetype = supported_file_matches[1] if supported_file_matches
92
+
93
+ if ( filetype )
94
+ filename = path.gsub('?embed=true','')
95
+
96
+ # check if file exists, throw an error if it doesn't exist
97
+ if File.exist?( filename )
98
+
99
+ # read contents of file into memory
100
+ content = File.read( filename )
101
+ content_type = "image/#{filetype}"
102
+
103
+ # encode the url
104
+ new_path = Datafy::make_data_uri( content, content_type )
105
+ else
106
+ puts "Unable to locate file #{filename} on local file system, skipping image embedding"
107
+ end
108
+ end
109
+ end
110
+ return new_path
111
+ end
112
+
113
+ #
114
+ # Returns all referenced URLs in +file+.
115
+ #
116
+ def urls(file)
117
+ @contents = File.read(file) unless @contents
118
+
119
+ @contents.scan(/url\([\s"']*([^\)"'\s]*)[\s"']*\)/m).collect do |match|
120
+ @path_resolver.resolve(match.first)
121
+ end
122
+ end
123
+
124
+ private
125
+ def duplicate_urls(urls)
126
+ urls.inject({}) { |h,v| h[v.path] = h[v.path].to_i+1; h }.reject{ |k,v| v == 1 }.keys
127
+ end
128
+ end
129
+ end
130
+
131
+ # http://snippets.dzone.com/posts/show/3838
132
+ #module Enumerable
133
+ # def duplicates
134
+ # inject({}) {|h,v| h[v]=h[v].to_i+1; h}.reject{|k,v| v==1}.keys
135
+ # end
136
+ #end