esvg 3.2.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5df426e77287c9878d1d1e4015cb0e53b587bb5a
4
- data.tar.gz: 37c2c9e0f77f3898afdef009a59fecaae870fbec
3
+ metadata.gz: 103e4a996c91ea83c6ce587b3f6c7f9f21e1f3ab
4
+ data.tar.gz: e221e24b800402c241ade048d560fd578086d741
5
5
  SHA512:
6
- metadata.gz: dbcb61a40159c48fef3e224b3a83723ef243d224acb6eff08b2c42032c66471b214e3d16ef6399abeb80d3c13c4f86ed5e4b6766cefa21fc4ee5d0026c5f7b32
7
- data.tar.gz: 3305442d217788371c21e7c9f069522257c1d1f38dd85bef58ea996904a3197201ac5ab086b2c306a4db733ad092df56b107d22620bdbdfd72558a970bb77d06
6
+ metadata.gz: 48afc2004b3768882d80f17d338bf476116584005694c20e737ccdc0a411a67fcc1446aa50641ddcfbe214e6ed861f8e848a610b26c042129db653f23433a577
7
+ data.tar.gz: e2637534ae3808537b3b8619433d959584d9f69a2b0a078d6a8b77e964e5cbbb1647480629f55269b9639d3bc5a7c7c54ff4e3adb0f5dae988bcc1421df83503
data/.gitignore CHANGED
@@ -10,4 +10,5 @@
10
10
  /test/build
11
11
  /test/optimized
12
12
  .DS_Store
13
+ .esvg-cache
13
14
  node_modules
data/.travis.yml CHANGED
@@ -2,4 +2,4 @@ language: ruby
2
2
  rvm:
3
3
  - 2.1.2
4
4
  before_install: gem install bundler -v 1.10.3
5
- script: bundle exec clash
5
+ script: bundle exec clash 1-4
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Esvg
2
2
 
3
- Easily embed optimized SVGs in JS or HTML. Use as a standalone tool or with Rails.
3
+ Easly slip optimized, cached svgs into your workflow using standalone CLI or the simple Rails integration.
4
4
 
5
- 1. Converts a directory full of SVGs into a single optimized SVG using symbols.
6
- 2. Uses Javascript to inject SVGs into pages, so it's easily cacheable.
5
+ 1. Converts a directory full of SVGs into a an optimized SVG using symbols for each file.
6
+ 2. Build a Javascript file to inject SVGs into pages, so it's easily cacheable.
7
7
  3. Offers Rails application helpers for placing icons in your views.
8
8
 
9
9
  [![Gem Version](http://img.shields.io/gem/v/esvg.svg)](https://rubygems.org/gems/esvg)
@@ -28,63 +28,75 @@ Or install it yourself as:
28
28
 
29
29
  ## Usage: Rails
30
30
 
31
- First, add SVG files to your `app/assets/esvg/` directory.
31
+ Add `Esvg.precompile_assets` to your `config/initializers/assets.rb` to add build with `rake assets:precomile`.
32
32
 
33
- ### Inject SVG symbols
33
+ Add SVG files to your `app/assets/svgs/` directory.
34
34
 
35
- Then create an `esvg.js.erb` in your `app/assets/javascripts/` and add the following:
35
+ for example:
36
36
 
37
37
  ```
38
- <%= Esvg.embed %>
38
+ app/assets/svgs
39
+ - logo.svg
40
+ - share.svg
41
+ - thumbs-up.svg
39
42
  ```
40
43
 
41
- Finally add the following to your `application.js`
44
+ ### Inject SVG symbols
45
+
46
+ Add this to a page or layout where SVGs should be available
42
47
 
43
48
  ```
44
- //= require esvg
49
+ <%= embed_svgs %>
45
50
  ```
46
51
 
47
- This script will inject your SVG symbols at the top of your site's `<body>` as soon as the DOM is ready, and after any Turbolinks `page:change` event.
52
+ **During development:**
53
+
54
+ This will embed a `<script>` which will place svg symbols at the top of your site's `<body>` as soon as the DOM is ready, and after any Turbolinks page load events.
55
+
56
+ **In Production:**
57
+
58
+ The `embed_svgs` view helper will write a `javascript_include_tag` to include the script (rather than embeding it in the page).
59
+ When you run `rake assets:precompile` this script will be built to `public/assets/svgs-{fingerprint}.js` (and a gzipped version).
60
+
61
+ This allows browsers to cache the javascript files and reduce the weight of downloading svgs for each page.
48
62
 
49
- ### Place an icon
63
+ ### Placing an SVG
50
64
 
51
- To place an SVG icon, use the `svg_icon` helper. This helper will embed an SVG `use` tag which will reference the appropriate symbol. Here's how it works.
65
+ To place an SVG, use the `use_svg` vew helper. This helper will embed an SVG `use` tag which will reference the appropriate symbol. Here's how it works.
52
66
 
53
67
  ```
54
- # Syntax: svg_icon name, [options]
68
+ # Syntax: use_svg name, [options]
55
69
 
56
70
  # Standard example
57
- <%= svg_icon 'kitten' %>
71
+ <%= use_svg 'logo' %>
58
72
 
59
73
  # Output:
60
- # <svg class="icon kitten-icon"><use xlink:href="#kitten-icon"/></svg>
74
+ # <svg class="svg-symbol svg-logo"><use xlink:href="#svg-logo"/></svg>
61
75
 
62
76
  # Add custom classnames
63
- <%= svg_icon 'kitten', class: 'adorbs' %>
77
+ <%= use_svg 'share', class: 'disabled' %>
64
78
 
65
79
  # Output:
66
- # <svg class="icon kitten-icon adorbs"><use xlink:href="#kitten-icon"/></svg>
67
-
68
- # Provide fallback icon if an icon is missing
69
- <%= svg_icon 'missing', fallback: 'kitten' %>
70
-
71
- # Output:
72
- # <svg class="icon kitten-icon"><use xlink:href="#kitten-icon"/></svg>
80
+ # <svg class="svg-symbol svg-share disabled"><use xlink:href="#svg-share"/></svg>
73
81
 
74
82
  # Add custom styles
75
- <%= svg_icon 'kitten', style: 'color: #c0ffee' %>
83
+ <%= use_svg 'logo', style: 'fill: #c0ffee' %>
76
84
 
77
85
  # Output:
78
- # <svg class="icon kitten-icon" style="color: #coffee;"><use xlink:href="#kitten-icon"/></svg>
86
+ # <svg class="svg-symbol svg-logo" style="fill: #coffee;"><use xlink:href="#svg-logo"/></svg>
79
87
 
80
88
  # Add title and desc tags for SVG accessibility.
81
- <%= svg_icon 'kitten', title: "Mr. Snuggles", desc: "A graphic of a cat snuggling a ball of yarn" %>
89
+ <%= use_svg 'kitten', title: "Mr. Snuggles", desc: "A graphic of a cat snuggling a ball of yarn" %>
82
90
 
83
91
  # Output:
84
92
  # <svg class="icon kitten-icon"><use xlink:href="#kitten-icon"/>
85
93
  # <title>Mr. Snuggles</title>
86
94
  # <desc>A graphic of a cat snuggling a ball of yarn</desc>
87
95
  # </svg>
96
+
97
+ # Provide fallback icon if an icon is missing (great for when you are generating icon names from code)
98
+ <%= use_svg 'missing', fallback: 'default' %>
99
+
88
100
  ```
89
101
 
90
102
  ## Usage: stand-alone CLI
@@ -94,10 +106,10 @@ To place an SVG icon, use the `svg_icon` helper. This helper will embed an SVG `
94
106
  $ esvg PATH [options]
95
107
 
96
108
  # Examples:
97
- $ esvg # Read icons from current directory, write js to ./esvg.js
98
- $ esvg icons # Read icons from 'icons' directory, write js to ./esvg.js
99
- $ esvg --output embedded # Read icons from current directory, write js to embedded/esvg.js
100
- $ esvg -c --config foo.yml # Read confguration from foo.yml (otherwise, defaults to esvg.yml, or config/esvg.yml)
109
+ $ esvg # Read icons from current directory, write js to ./svgs.js
110
+ $ esvg icons # Read icons from 'icons' directory, write js to ./svgs.js
111
+ $ esvg --output build # Read icons from current directory, write js to build/svgs.js
112
+ $ esvg -c --config foo.yml # Read confguration from foo.yml (otherwise, defaults to esvg.yml, or config/esvg.yml)
101
113
  ```
102
114
 
103
115
  ## Configuration
@@ -105,13 +117,14 @@ $ esvg -c --config foo.yml # Read confguration from foo.yml (otherwise, def
105
117
  If you're using esvg from the command line, configuration files are read from `./esvg.yml` or you can pass a path with the `--config` option to read the config file from elsewhere.
106
118
 
107
119
  ```
108
- path: . # Where to find SVG icons (Rails defaults to app/assets/esvg)
109
- output_path: . # Where to write output files (CLI only)
110
- format: js # Format for output (js, html)
111
-
112
- base_class: svg-icon # Select all icons with this base classname
113
- namespace: icon # Namespace for symbol ids
114
- namespace_before: true # Add namespace before, e.g. icon-kitten
120
+ source: . # Where to find SVG icons (Rails defaults to app/assets/esvg)
121
+ build: . # Where to write build files
122
+ assets: . # Where to write asset files (builds for directories beginning in _)
123
+ tmp: . # Write temporary cache files (will write to #{dir}/.esvg-cache/
124
+
125
+ class: svg-symbol # All svgs with `use_svg` will have this base classname
126
+ namespace: svg # Namespace for symbol ids, e.g 'svg-logo'
127
+ namespace_before: true # Add namespace before, e.g. 'svg-logo', false would be 'logo-svg'
115
128
 
116
129
  alias: # Add aliases for icon names
117
130
  comment: chat # use "chat" to reference comment.svg
data/exe/esvg CHANGED
@@ -8,12 +8,8 @@ require 'esvg'
8
8
  options = {}
9
9
 
10
10
  OptionParser.new do |opts|
11
- opts.on("-f", "--format TYPE", String, "Options: js, html (defaults to js)") do |format|
12
- options[:format] = format
13
- end
14
-
15
11
  opts.on("-o", "--output PATH", String, "Where should JS/HTML files be written, (default: current directory)") do |path|
16
- options[:output_path] = path
12
+ options[:build] = path
17
13
  end
18
14
 
19
15
  opts.on("-c", "--config PATH", String, "Path to a config file (default: esvg.yml, config/esvg.yml)") do |path|
@@ -24,11 +20,11 @@ OptionParser.new do |opts|
24
20
  options[:rails] = true
25
21
  end
26
22
 
27
- opts.on("-O", "--optimize", "Optimize svgs with svgo") do |svgo|
28
- options[:optimize] = svgo
23
+ opts.on("-O", "--optimize", "Optimize svgs with svgo") do
24
+ options[:optimize] = true
29
25
  end
30
26
 
31
- opts.on("-v", "--version", "Print version") do |version|
27
+ opts.on("-v", "--version", "Print version") do
32
28
  options[:version] = true
33
29
  end
34
30
 
@@ -39,9 +35,9 @@ if options[:version]
39
35
  else
40
36
 
41
37
  if path = ARGV.shift
42
- options[:path] = path
38
+ options[:source] = path
43
39
  end
44
40
 
45
- options[:cli] = true
46
- esvg = Esvg::SVG.new(options).write
41
+ options[:print] = true
42
+ esvg = Esvg::SVG.new(options).build
47
43
  end
data/lib/esvg.rb CHANGED
@@ -19,12 +19,23 @@ module Esvg
19
19
  @svgs
20
20
  end
21
21
 
22
- def embed
23
- new.embed
22
+ def embed(key)
23
+ new.embed(key)
24
24
  end
25
25
 
26
26
  def rails?
27
27
  defined?(Rails)
28
28
  end
29
29
 
30
+ def build(options={})
31
+ new(options).build
32
+ end
33
+
34
+ def precompile_assets
35
+ if rails? && defined?(Rake)
36
+ ::Rake::Task['assets:precompile'].enhance do
37
+ build(compress: true, print: true)
38
+ end
39
+ end
40
+ end
30
41
  end
data/lib/esvg/helpers.rb CHANGED
@@ -1,17 +1,24 @@
1
1
  module Esvg::Helpers
2
- def svg_icons(options={})
3
- svgs = Esvg.svgs
4
2
 
5
- if svgs.nil? || !options.empty?
6
- svgs = Esvg.new(options)
3
+ def embed_svgs(*keys)
4
+ if Rails.env.production?
5
+ esvg_files.build_paths(keys).each do |path|
6
+ javascript_include_tag(path)
7
+ end.join("\n")
8
+ else
9
+ esvg_files.embed_script(keys).html_safe
7
10
  end
11
+ end
12
+
13
+ def use_svg(name, options={})
14
+ esvg_files.use(name, options).html_safe
15
+ end
16
+
17
+ def esvg_files
18
+ svgs = Esvg.svgs || Esvg.new()
8
19
 
9
20
  svgs.read_files if Rails.env.development?
10
21
 
11
22
  svgs
12
23
  end
13
-
14
- def svg_icon(name, options={})
15
- svg_icons.use(name, options).html_safe
16
- end
17
24
  end
data/lib/esvg/svg.rb CHANGED
@@ -1,34 +1,38 @@
1
1
  require 'yaml'
2
2
  require 'json'
3
+ require 'zlib'
3
4
 
4
5
  module Esvg
5
6
  class SVG
6
- attr_accessor :files, :svgs, :last_read, :svg_cache
7
+ attr_accessor :svgs, :last_read, :svg_symbols
7
8
 
8
9
  CONFIG = {
9
- base_class: 'svg-icon',
10
- namespace: 'icon',
11
- optimize: false,
10
+ filename: 'svgs',
11
+ class: 'svg-symbol',
12
+ namespace: 'svg',
13
+ core: true,
12
14
  namespace_before: true,
13
- font_size: '1em',
14
- verbose: false,
15
- format: 'js',
15
+ optimize: false,
16
+ compress: false,
16
17
  throttle_read: 4,
17
18
  flatten: [],
18
19
  alias: {}
19
20
  }
20
21
 
21
22
  CONFIG_RAILS = {
22
- path: "app/assets/esvg",
23
- js_path: "app/assets/javascripts/esvg.js",
23
+ source: "app/assets/svgs",
24
+ assets: "app/assets/javascripts",
25
+ build: "public/assets",
26
+ temp: "tmp"
24
27
  }
25
28
 
26
29
  def initialize(options={})
27
30
  config(options)
28
31
 
32
+ @modules = {}
29
33
  @last_read = nil
30
- @svg_cache = {}
31
34
 
35
+ read_cache
32
36
  read_files
33
37
  end
34
38
 
@@ -36,10 +40,10 @@ module Esvg
36
40
  @config ||= begin
37
41
  paths = [options[:config_file], 'config/esvg.yml', 'esvg.yml'].compact
38
42
 
39
- config = CONFIG
43
+ config = CONFIG.dup
40
44
 
41
45
  if Esvg.rails? || options[:rails]
42
- config.merge!(CONFIG_RAILS)
46
+ config.merge!(CONFIG_RAILS)
43
47
  end
44
48
 
45
49
  if path = paths.select{ |p| File.exist?(p)}.first
@@ -47,61 +51,21 @@ module Esvg
47
51
  end
48
52
 
49
53
  config.merge!(options)
50
-
51
- config[:path] ||= Dir.pwd
52
- config[:output_path] ||= Dir.pwd
53
54
 
54
- if config[:cli]
55
- config[:path] = File.expand_path(config[:path])
56
- config[:output_path] = File.expand_path(config[:output_path])
57
- end
55
+ config[:filename] = File.basename(config[:filename], '.*')
58
56
 
59
- config[:js_path] ||= File.join(config[:output_path], 'esvg.js')
60
- config[:js_core_path] ||= config[:js_path].sub(/[^\/]+?\./, '_esvg-core.')
61
- config[:html_path] ||= File.join(config[:output_path], 'esvg.html')
62
- config.delete(:output_path)
63
- config[:aliases] = load_aliases(config[:alias])
64
- config[:flatten] = config[:flatten].map { |dir| File.join(dir, '/') }.join('|')
57
+ config[:pwd] = File.expand_path Dir.pwd
58
+ config[:source] = File.expand_path config[:source] || config[:pwd]
59
+ config[:build] = File.expand_path config[:build] || config[:pwd]
60
+ config[:assets] = File.expand_path config[:assets] || config[:pwd]
65
61
 
66
- config
67
- end
68
- end
62
+ config[:temp] = config[:pwd] if config[:temp].nil?
63
+ config[:temp] = File.expand_path File.join(config[:temp], '.esvg-cache')
69
64
 
70
- # Load aliases from configuration.
71
- # returns a hash of aliasees mapped to a name.
72
- # Converts configuration YAML:
73
- # alias:
74
- # foo: bar
75
- # baz: zip, zop
76
- # To output:
77
- # { :bar => "foo", :zip => "baz", :zop => "baz" }
78
- #
79
- def load_aliases(aliases)
80
- a = {}
81
- aliases.each do |name,alternates|
82
- alternates.split(',').each do |val|
83
- a[dasherize(val.strip).to_sym] = dasherize(name.to_s)
84
- end
85
- end
86
- a
87
- end
88
-
89
- def get_alias(name)
90
- config[:aliases][dasherize(name).to_sym] || name
91
- end
92
-
93
- def embed
94
- return if files.empty?
95
- output = if config[:format] == "html"
96
- html
97
- elsif config[:format] == "js"
98
- js
99
- end
65
+ config[:aliases] = load_aliases(config[:alias])
66
+ config[:flatten] = [config[:flatten]].flatten.map { |dir| File.join(dir, '/') }.join('|')
100
67
 
101
- if Esvg.rails?
102
- output.html_safe
103
- else
104
- output
68
+ config
105
69
  end
106
70
  end
107
71
 
@@ -110,157 +74,219 @@ module Esvg
110
74
  return
111
75
  end
112
76
 
113
- @files = {}
114
-
115
77
  # Get a list of svg files and modification times
116
78
  #
117
- find_files.each do |f|
118
- files[f] = File.mtime(f)
119
- end
79
+ find_files
80
+ write_cache
120
81
 
121
82
  @last_read = Time.now.to_i
122
83
 
123
- puts "Read #{files.size} files from #{config[:path]}" if config[:cli]
84
+ puts "Read #{svgs.size} files from #{config[:source]}" if config[:print]
124
85
 
125
- process_files
126
-
127
- if files.empty? && config[:cli]
128
- puts "No svgs found at #{config[:path]}"
86
+ if svgs.empty? && config[:print]
87
+ puts "No svgs found at #{config[:source]}"
129
88
  end
130
89
  end
131
90
 
132
- # Add new svgs, update modified svgs, remove deleted svgs
133
- #
134
- def process_files
135
- @svgs = {}
91
+ def find_files
92
+ files = Dir[File.join(config[:source], '**/*.svg')].uniq.sort
93
+
94
+ # Remove deleted files from svg cache
95
+ (svgs.keys - file_keys(files)).each { |f| svgs.delete(f) }
96
+
97
+ dirs = {}
98
+
99
+ files.each do |path|
136
100
 
137
- files.each do |path, mtime|
138
- key = file_key( path )
101
+ mtime = File.mtime(path).to_i
102
+ key = file_key path
103
+ dkey = dir_key path
139
104
 
140
- if svg_cache[key].nil? || svg_cache[key][:last_modified] != mtime
141
- svg_cache[key] = process_file(path, mtime, key)
105
+ # Use cache if possible
106
+ if svgs[key].nil? || svgs[key][:last_modified] != mtime
107
+ svgs[key] = process_file(path, mtime, key)
142
108
  end
143
109
 
144
- svgs[File.dirname( flatten_path( path ) )] ||= {}
145
- svgs[File.dirname( flatten_path( path ) )][key] = svg_cache[key]
110
+ dirs[dkey] ||= {}
111
+ (dirs[dkey][:files] ||= []) << key
112
+
113
+ if dirs[dkey][:last_modified].nil? || dirs[dkey][:last_modified] < mtime
114
+ dirs[dkey][:last_modified] = mtime
115
+ end
146
116
  end
147
117
 
148
- # Remove deleted svgs
149
- #
150
- (svg_cache.keys - files.keys.map {|file| file_key(file) }).each do |f|
151
- svg_cache.delete(f)
118
+ dirs = sort(dirs)
119
+
120
+ # Remove deleted directories from svg_symbols cache
121
+ (svg_symbols.keys - dirs.keys).each {|dir| svg_symbols.delete(dir) }
122
+
123
+ dirs.each do |dir, data|
124
+
125
+ # overwrite cache if
126
+ if svg_symbols[dir].nil? || # No cache for this dir yet
127
+ svg_symbols[dir][:last_modified] != data[:last_modified] || # New or updated file
128
+ svg_symbols[dir][:optimized] != optimize? || # Cache is unoptimized
129
+ svg_symbols[dir][:files] != data[:files] # Changed files
130
+
131
+ symbols = data[:files].map { |f| svgs[f][:content] }.join
132
+ attributes = data[:files].map { |f| svgs[f][:attr] }
133
+
134
+ svg_symbols[dir] = data.merge({
135
+ name: dir,
136
+ symbols: optimize(symbols, attributes),
137
+ optimized: optimize?,
138
+ version: config[:version] || Digest::MD5.hexdigest(symbols),
139
+ asset: File.basename(dir).start_with?('_')
140
+ })
141
+
142
+ end
143
+
144
+ svg_symbols.keys.each do |dir|
145
+ svg_symbols[dir][:path] = write_path(dir)
146
+ end
152
147
  end
148
+
149
+ @svg_symbols = sort(@svg_symbols)
150
+ @svgs = sort(@svgs)
151
+ end
152
+
153
+ def read_cache
154
+ @svgs = YAML.load(read_tmp '.svgs') || {}
155
+ @svg_symbols = YAML.load(read_tmp '.svg_symbols') || {}
156
+ end
157
+
158
+ def write_cache
159
+ return if production?
160
+
161
+ write_tmp '.svgs', sort(@svgs).to_yaml
162
+ write_tmp '.svg_symbols', sort(@svg_symbols).to_yaml
163
+
153
164
  end
154
165
 
155
- def build_paths
156
- build_keys = svgs.keys.reject do |key|
157
- key == '.' ||
158
- key.match(/^_/) ||
159
- config[:flatten].include?(key)
166
+ def sort(hash)
167
+ sorted = {}
168
+ hash.sort.each do |h|
169
+ sorted[h.first] = h.last
160
170
  end
171
+ sorted
172
+ end
161
173
 
162
- build_keys.map { | key | File.expand_path(build_path(key)) }
174
+ def embed_script(key=nil)
175
+ if script = js(key)
176
+ "<script>#{script}</script>"
177
+ else
178
+ ''
179
+ end
163
180
  end
164
181
 
165
- def flatten_path(path)
166
- root_path = File.expand_path(config[:path])
182
+ def build_paths(keys=nil)
183
+ build_files(keys).map { |s| File.basename(s[:path]) }
184
+ end
167
185
 
168
- path.sub("#{root_path}/",'').sub('.svg', '')
169
- .sub(Regexp.new(config[:flatten]), '')
186
+ def build_files(keys=nil)
187
+ valid_keys(keys).reject do |k|
188
+ svg_symbols[k][:asset]
189
+ end.map { |k| svg_symbols[k] }
170
190
  end
171
191
 
172
- def file_key(path)
173
- dasherize flatten_path(path)
192
+ def asset_files(keys=nil)
193
+ valid_keys(keys).select do |k|
194
+ svg_symbols[k][:asset]
195
+ end.map { |k| svg_symbols[k] }
174
196
  end
175
197
 
176
198
  def process_file(file, mtime, name)
177
- content = File.read(file).gsub(/<?.+\?>/,'').gsub(/<!.+?>/,'')
178
- {
199
+ content = File.read(file)
200
+ classname = classname(name)
201
+ size_attr = dimensions(content)
202
+
203
+ svg = {
179
204
  name: name,
180
- content: content,
181
- use: use_svg(name, content),
182
- last_modified: mtime
205
+ use: %Q{<use xlink:href="##{classname}"/>},
206
+ last_modified: mtime,
207
+ attr: { class: classname }.merge(dimensions(content))
183
208
  }
184
- end
209
+ # Add attributes
210
+ svg[:content] = prep_svg(content, svg[:attr])
185
211
 
186
- def use_svg(file, content)
187
- name = classname get_alias(file)
188
- viewbox = content.scan(/<svg.+(viewBox=["'](.+?)["'])/).flatten.first
189
- %Q{<svg class="#{config[:base_class]} #{name}" #{viewbox}><use xlink:href="##{name}"/></svg>}
212
+ svg
190
213
  end
191
214
 
192
- def svg_icon(file, options={})
193
- name = dasherize file
194
-
195
- if !exist?(name)
196
- if fallback = options.delete(:fallback)
197
- svg_icon(fallback, options)
198
- else
199
- if Esvg.rails? && Rails.env.production?
200
- return ''
201
- else
202
- raise "no svg named '#{get_alias(file)}' exists at #{config[:path]}"
203
- end
204
- end
205
- else
206
-
207
- embed = use_icon(name)
208
- embed = embed.sub(/class="(.+?)"/, 'class="\1 '+options[:class]+'"') if options[:class]
215
+ def use(file, options={})
216
+ if name = exist?(file, options[:fallback])
217
+ svg = svgs[name]
209
218
 
210
219
  if options[:color]
211
220
  options[:style] ||= ''
212
- options[:style] += ";color:#{options[:color]};"
221
+ options[:style] += "color:#{options[:color]};#{options[:style]}"
213
222
  end
214
223
 
215
- embed = add_attribute(embed, 'style', options[:style], ';')
216
- embed = add_attribute(embed, 'fill', options[:fill])
217
- embed = add_attribute(embed, 'height', options[:height])
218
- embed = add_attribute(embed, 'width', options[:width])
224
+ attr = {
225
+ fill: options[:fill],
226
+ style: options[:style],
227
+ viewbox: svg[:attr][:viewbox],
228
+ classname: [config[:class], svg[:attr][:class], options[:class]].compact.join(' ')
229
+ }
219
230
 
220
- embed = embed.sub(/><\/svg/, ">#{title(options)}#{desc(options)}</svg")
231
+ # If user doesn't pass a size or set scale: true
232
+ if !(options[:width] || options[:height] || options[:scale])
221
233
 
222
- if Esvg.rails?
223
- embed.html_safe
234
+ # default to svg dimensions
235
+ attr[:width] = svg[:attr][:width]
236
+ attr[:height] = svg[:attr][:height]
224
237
  else
225
- embed
238
+
239
+ # Add sizes (nil options will be stripped)
240
+ attr[:width] = options[:width]
241
+ attr[:height] = options[:height]
226
242
  end
227
- end
228
- end
229
243
 
230
- alias :use :svg_icon
244
+ use = %Q{<svg #{attributes(attr)}>#{svg[:use]}#{title(options)}#{desc(options)}</svg>}
231
245
 
232
- def add_attribute(tag, attr, content=nil, append=false)
233
- return tag if content.nil?
234
- content = content.to_s
235
- if tag.match(/#{attr}/)
236
- if append
237
- tag.sub(/#{attr}="(.+?)"/, attr+'="\1'+append+content+'"')
246
+ if Esvg.rails?
247
+ use.html_safe
238
248
  else
239
- tag.sub(/#{attr}=".+?"/, attr+'="'+content+'"')
249
+ use
240
250
  end
241
251
  else
242
- tag.sub(/><use/, %Q{ #{attr}="#{content}"><use})
252
+ if production?
253
+ return ''
254
+ else
255
+ raise "no svg named '#{get_alias(file)}' exists at #{config[:source]}"
256
+ end
243
257
  end
244
258
  end
245
259
 
260
+ alias :svg_icon :use
261
+
246
262
  def dimensions(input)
247
- dimension = input.scan(/<svg.+(viewBox=["'](.+?)["'])/).flatten
248
- viewbox = dimension.first
249
- coords = dimension.last.split(' ')
263
+ viewbox = input.scan(/<svg.+(viewBox=["'](.+?)["'])/).flatten.last
264
+ coords = viewbox.split(' ')
250
265
 
251
- width = coords[2].to_i - coords[0].to_i
252
- height = coords[3].to_i - coords[1].to_i
253
- %Q{#{viewbox} width="#{width}" height="#{height}"}
266
+ {
267
+ viewbox: viewbox,
268
+ width: coords[2].to_i - coords[0].to_i,
269
+ height: coords[3].to_i - coords[1].to_i
270
+ }
254
271
  end
255
272
 
256
- def use_icon(name)
257
- name = get_alias(name)
258
- svg_cache[name][:use]
273
+ def attributes(hash)
274
+ att = []
275
+ hash.each do |key, value|
276
+ att << %Q{#{key}="#{value}"} unless value.nil?
277
+ end
278
+ att.join(' ')
259
279
  end
260
280
 
261
- def exist?(name)
262
- name = get_alias(name)
263
- !svg_cache[name].nil?
281
+ def exist?(name, fallback=nil)
282
+ name = get_alias dasherize(name)
283
+
284
+ if svgs[name].nil?
285
+ exist?(fallback) if fallback
286
+ else
287
+ name
288
+ end
289
+
264
290
  end
265
291
 
266
292
  alias_method :exists?, :exist?
@@ -277,12 +303,6 @@ module Esvg
277
303
  input.gsub(/[\W,_]/, '-').sub(/^-/,'').gsub(/-{2,}/, '-')
278
304
  end
279
305
 
280
- def find_files
281
- path = File.expand_path(File.join(config[:path], '**/*.svg'))
282
- Dir[path].uniq
283
- end
284
-
285
-
286
306
  def title(options)
287
307
  if options[:title]
288
308
  "<title>#{options[:title]}</title>"
@@ -299,104 +319,224 @@ module Esvg
299
319
  end
300
320
  end
301
321
 
302
- def write
303
- return if @files.empty?
304
- write_paths = case config[:format]
305
- when "html"
306
- write_html
307
- when "js"
308
- write_js
322
+ def version(key)
323
+ svg_symbols[key][:version]
324
+ end
325
+
326
+ def build
327
+ paths = write_files svg_symbols.values
328
+
329
+ if config[:core]
330
+ path = File.join config[:assets], "_esvg.js"
331
+ write_file(path, js_core)
332
+ paths << path
309
333
  end
310
334
 
311
- write_paths.each do |path|
312
- puts "Written to #{log_path path}" if config[:cli]
335
+ paths
336
+ end
337
+
338
+ def write_files(files)
339
+ paths = []
340
+
341
+ files.each do |file|
342
+ if file[:asset] || !File.exist?(file[:path])
343
+ write_file(file[:path], js(file[:name]))
344
+ puts "Writing #{file[:path]}" if config[:print]
345
+ paths << file[:path]
346
+
347
+ if !file[:asset] && gz = compress(file[:path])
348
+ puts "Writing #{gz}" if config[:print]
349
+ paths << gz
350
+ end
351
+ end
313
352
  end
314
353
 
315
- write_paths
354
+ paths
316
355
  end
317
356
 
318
- def log_path(path)
319
- File.expand_path(path).sub(File.expand_path(Dir.pwd), '').sub(/^\//,'')
357
+ def symbols(keys)
358
+ symbols = valid_keys(keys).map { |key|
359
+ svg_symbols[key][:symbols]
360
+ }.join.gsub(/\n/,'')
361
+
362
+ %Q{<svg id="esvg-#{key_id(keys)}" version="1.1" style="height:0;position:absolute">#{symbols}</svg>}
320
363
  end
321
364
 
322
- def write_svg(svg)
323
- path = File.join((config[:tmp_path] || config[:path]), '.esvg-tmp')
324
- write_file path, svg
325
- path
365
+ def js(key)
366
+ keys = valid_keys(key)
367
+ return if keys.empty?
368
+
369
+ script key_id(keys), symbols(keys).gsub('/n','').gsub("'"){"\\'"}
326
370
  end
327
371
 
328
- def write_js
329
- paths = []
372
+ def script(id, symbols)
373
+ %Q{(function(){
330
374
 
331
- unless config[:js_core_path] == false
332
- path = config[:js_core_path]
333
- write_file path, js_core
375
+ function embed() {
376
+ if (!document.querySelector('#esvg-#{id}')) {
377
+ document.querySelector('body').insertAdjacentHTML('afterbegin', '#{symbols}')
378
+ }
379
+ }
334
380
 
335
- paths.push path
336
- end
381
+ // If DOM is already ready, embed SVGs
382
+ if (document.readyState == 'interactive') { embed() }
337
383
 
338
- svgs.each do |key, files|
339
- path = write_path(:js_path, key)
340
- write_file path, js(key)
341
- paths.push path
342
- end
384
+ // Handle Turbolinks page change events
385
+ if ( window.Turbolinks ) {
386
+ document.addEventListener("turbolinks:load", function(event) { embed() })
387
+ }
343
388
 
344
- paths
389
+ // Handle standard DOM ready events
390
+ document.addEventListener("DOMContentLoaded", function(event) { embed() })
391
+ })()}
345
392
  end
346
393
 
347
- def write_html
348
- paths = []
394
+ def js_core
395
+ %Q{(function(){
396
+ var names
349
397
 
350
- svgs.each do |key, files|
351
- path = write_path(:html_path, key)
352
- write_file path, html(key)
353
- paths.push path
354
- end
398
+ function attr( source, name ){
399
+ if (typeof source == 'object')
400
+ return name+'="'+source.getAttribute(name)+'" '
401
+ else
402
+ return name+'="'+source+'" ' }
355
403
 
356
- paths
357
- end
404
+ function dasherize( input ) {
405
+ return input.replace(/[\\W,_]/g, '-').replace(/-{2,}/g, '-')
406
+ }
358
407
 
359
- def write_file(path, contents)
360
- FileUtils.mkdir_p(File.expand_path(File.dirname(path)))
361
- File.open(path, 'w') do |io|
362
- io.write(contents)
363
- end
408
+ function svgName( name ) {
409
+ #{if config[:namespace_before]
410
+ %Q{return "#{config[:namespace]}-"+dasherize( name )}
411
+ else
412
+ %Q{return dasherize( name )+"-#{config[:namespace]}"}
413
+ end}
414
+ }
415
+
416
+ function use( name, options ) {
417
+ options = options || {}
418
+ var id = dasherize( svgName( name ) )
419
+ var symbol = svgs()[id]
420
+
421
+ if ( symbol ) {
422
+ var svg = document.createRange().createContextualFragment( '<svg><use xlink:href="#'+id+'"/></svg>' ).firstChild;
423
+ svg.setAttribute( 'class', '#{config[:class]} '+id+' '+( options.classname || '' ).trim() )
424
+ svg.setAttribute( 'viewBox', symbol.getAttribute( 'viewBox' ) )
425
+
426
+ if ( !( options.width || options.height || options.scale ) ) {
427
+
428
+ svg.setAttribute('width', symbol.getAttribute('width'))
429
+ svg.setAttribute('height', symbol.getAttribute('height'))
430
+
431
+ } else {
432
+
433
+ if ( options.width ) svg.setAttribute( 'width', options.width )
434
+ if ( options.height ) svg.setAttribute( 'height', options.height )
435
+ }
436
+
437
+ return svg
438
+ } else {
439
+ console.error('Cannot find "'+name+'" svg symbol. Ensure that svg scripts are loaded')
440
+ }
441
+ }
442
+
443
+ function svgs(){
444
+ if ( !names ) {
445
+ names = {}
446
+ for( var symbol of document.querySelectorAll( 'svg[id^=esvg] symbol' ) ) {
447
+ names[symbol.id] = symbol
448
+ }
449
+ }
450
+ return names
451
+ }
452
+
453
+ var esvg = {
454
+ svgs: svgs,
455
+ use: use
456
+ }
457
+
458
+ // Handle Turbolinks page change events
459
+ if ( window.Turbolinks ) {
460
+ document.addEventListener( "turbolinks:load", function( event ) { names = null; esvg.svgs() })
461
+ }
462
+
463
+ if( typeof( module ) != 'undefined' ) { module.exports = esvg }
464
+ else window.esvg = esvg
465
+
466
+ })()}
364
467
  end
365
468
 
366
- def build_path(key)
367
- dir = config[:js_build_dir] || File.dirname(config[:js_path])
368
-
369
- if config[:js_build_version]
370
- key = "#{key}-#{config[:js_build_version]}"
469
+ private
470
+
471
+ def dir_key(path)
472
+ dir = File.dirname(flatten_path(path))
473
+
474
+ # Flattened paths which should be treated as assets will use '_' as their dir key
475
+ if dir == '.' && ( sub_path(path).start_with?('_') || config[:filename].start_with?('_') )
476
+ '_'
477
+ else
478
+ dir
371
479
  end
480
+ end
372
481
 
373
- File.join(dir, key+'.js')
482
+ def sub_path(path)
483
+ path.sub("#{config[:source]}/",'')
374
484
  end
375
485
 
376
- def write_path(path, key)
377
- # Write root svgs
378
- return config[path] if key == "."
486
+ def flatten_path(path)
487
+ sub_path(path).sub(Regexp.new(config[:flatten]), '')
488
+ end
379
489
 
380
- if !key.start_with?('_') && path.to_s.start_with?('js')
381
- build_path(key)
490
+ def file_key(path)
491
+ dasherize flatten_path(path).sub('.svg', '')
492
+ end
493
+
494
+ def file_keys(paths)
495
+ paths.flatten.map { |p| file_key(p) }
496
+ end
497
+
498
+ def write_path(key)
499
+ name = if key == '_' # Root level asset file
500
+ "_#{config[:filename]}".sub(/_+/, '_')
501
+ elsif key == '.' # Root level build file
502
+ config[:filename]
382
503
  else
383
- config[path].sub(/[^\/]+?\./, key+'.')
504
+ "#{key}"
505
+ end
506
+
507
+ # Is it an asset, or a build file
508
+ if name.start_with?('_')
509
+ File.join config[:assets], "#{name}.js"
510
+ else
511
+ File.join config[:build], "#{name}-#{version(key)}.js"
384
512
  end
385
513
  end
386
514
 
515
+ def prep_svg(content, attr)
516
+ content = content.gsub(/<?.+\?>/,'').gsub(/<!.+?>/,'') # Get rid of doctypes and comments
517
+ .gsub(/\n/, '') # Remove endlines
518
+ .gsub(/\s{2,}/, ' ') # Remove whitespace
519
+ .gsub(/>\s+</, '><') # Remove whitespace between tags
520
+ .gsub(/\s?fill="(#0{3,6}|black|rgba?\(0,0,0\))"/,'') # Strip black fill
521
+ .gsub(/style="([^"]*?)fill:(.+?);/m, 'fill="\2" style="\1') # Make fill a property instead of a style
522
+ .gsub(/style="([^"]*?)fill-opacity:(.+?);/m, 'fill-opacity="\2" style="\1') # Move fill-opacity a property instead of a style
523
+
524
+ sub_def_ids(content, attr[:class])
525
+ end
526
+
387
527
  # Scans <def> blocks for IDs
388
528
  # If urls(#id) are used, ensure these IDs are unique to this file
389
529
  # Only replace IDs if urls exist to avoid replacing defs
390
530
  # used in other svg files
391
531
  #
392
- def sub_def_ids(file, content)
532
+ def sub_def_ids(content, classname)
393
533
  return content unless !!content.match(/<defs>/)
394
534
 
395
535
  content.scan(/<defs>.+<\/defs>/m).flatten.each do |defs|
396
536
  defs.scan(/id="(.+?)"/).flatten.uniq.each_with_index do |id, index|
397
537
 
398
538
  if content.match(/url\(##{id}\)/)
399
- new_id = "#{classname(file)}-ref#{index}"
539
+ new_id = "#{classname}-def#{index}"
400
540
 
401
541
  content = content.gsub(/id="#{id}"/, %Q{class="#{new_id}"})
402
542
  .gsub(/url\(##{id}\)/, "url(##{new_id})" )
@@ -409,127 +549,106 @@ module Esvg
409
549
  content
410
550
  end
411
551
 
412
- def prep_svg(file, content)
413
- content = content.gsub(/<svg.+?>/, %Q{<svg class="#{classname(file)}" #{dimensions(content)}>}) # convert svg to symbols
414
- .gsub(/\n/, '') # Remove endlines
415
- .gsub(/\s{2,}/, ' ') # Remove whitespace
416
- .gsub(/>\s+</, '><') # Remove whitespace between tags
417
- .gsub(/\s?fill="(#0{3,6}|black|rgba?\(0,0,0\))"/,'') # Strip black fill
418
- .gsub(/style="([^"]*?)fill:(.+?);/m, 'fill="\2" style="\1') # Make fill a property instead of a style
419
- .gsub(/style="([^"]*?)fill-opacity:(.+?);/m, 'fill-opacity="\2" style="\1') # Move fill-opacity a property instead of a style
552
+ def optimize?
553
+ !!(config[:optimize] && svgo_cmd)
554
+ end
420
555
 
421
- sub_def_ids(file, content)
556
+ def svgo_cmd
557
+ find_node_module('svgo')
422
558
  end
423
559
 
424
- def optimize(svg)
425
- if config[:optimize] && svgo_path = find_node_module('svgo')
426
- path = write_svg(svg)
427
- svg = `#{svgo_path} --disable=removeUselessDefs '#{path}' -o -`
428
- FileUtils.rm(path)
560
+
561
+ def optimize(svg, attributes)
562
+ if optimize?
563
+ path = write_tmp '.svgo-tmp', svg
564
+ command = "#{svgo_cmd} --disable=removeUselessDefs '#{path}' -o -"
565
+ svg = `#{command}`
566
+ FileUtils.rm(path) if File.exist? path
429
567
  end
430
568
 
431
- svg
569
+ id_symbols(svg, attributes)
432
570
  end
433
571
 
434
- def html(key)
435
- if svgs[key].empty?
436
- ''
437
- else
438
- symbols = []
439
- svgs[key].each do |name, data|
440
- symbols << prep_svg(name, data[:content])
441
- end
442
-
443
- symbols = optimize(symbols.join).gsub(/class=/,'id=').gsub(/svg/,'symbol')
444
-
445
- %Q{<svg id="esvg-#{key_id(key)}" version="1.1" style="height:0;position:absolute">#{symbols}</svg>}
572
+ def id_symbols(svg, attr)
573
+ svg.gsub(/<svg.+?>/).with_index do |match, index|
574
+ %Q{<symbol #{attributes(attr[index])}>} # Remove clutter from svg declaration
446
575
  end
576
+ .gsub(/<\/svg/,'</symbol') # Replace svgs with symbols
577
+ .gsub(/class=/,'id=') # Replace classes with ids (classes are generated here)
578
+ .gsub(/\w+=""/,'') # Remove empty attributes
447
579
  end
448
580
 
449
- def key_id(key)
450
- (key == '.') ? 'symbols' : classname(key)
451
- end
581
+ def compress(file)
582
+ return if !config[:compress]
452
583
 
453
- def svg_to_js(key)
454
- html(key).gsub(/\n/,'').gsub("'"){"\\'"}
455
- end
584
+ mtime = File.mtime(file)
585
+ gz_file = "#{file}.gz"
456
586
 
457
- def js_core
458
- %Q{var esvg = {
459
- icon: function(name, classnames) {
460
- var svgName = this.iconName(name)
461
- var element = document.querySelector('#'+svgName)
587
+ return if (File.exist?(gz_file) && File.mtime(gz_file) >= mtime)
462
588
 
463
- if (element) {
464
- return '<svg class="#{config[:base_class]} '+svgName+' '+(classnames || '')+'" '+this.dimensions(element)+'><use xlink:href="#'+svgName+'"/></svg>'
465
- } else {
466
- console.error('File not found: "'+name+'.svg" at #{log_path(File.join(config[:path],''))}/')
467
- }
468
- },
469
- iconName: function(name) {
470
- var before = #{config[:namespace_before]}
471
- if (before) {
472
- return "#{config[:namespace]}-"+this.dasherize(name)
473
- } else {
474
- return name+"-#{config[:namespace]}"
475
- }
476
- },
477
- dimensions: function(el) {
478
- return 'viewBox="'+el.getAttribute('viewBox')+'" width="'+el.getAttribute('width')+'" height="'+el.getAttribute('height')+'"'
479
- },
480
- dasherize: function(input) {
481
- return input.replace(/[\W,_]/g, '-').replace(/-{2,}/g, '-')
482
- },
483
- aliases: #{config[:aliases].to_json},
484
- alias: function(name) {
485
- var aliased = this.aliases[name]
486
- if (typeof(aliased) != "undefined") {
487
- return aliased
488
- } else {
489
- return name
490
- }
491
- }
492
- }
589
+ File.open(gz_file, "wb") do |dest|
590
+ gz = ::Zlib::GzipWriter.new(dest, Zlib::BEST_COMPRESSION)
591
+ gz.mtime = mtime.to_i
592
+ IO.copy_stream(open(file), gz)
593
+ gz.close
594
+ end
493
595
 
494
- // Work with module exports:
495
- if(typeof(module) != 'undefined') { module.exports = esvg }
496
- }
596
+ File.utime(mtime, mtime, gz_file)
597
+
598
+ gz_file
497
599
  end
498
600
 
499
- def js(key)
500
- %Q{(function(){
601
+ def write_tmp(name, content)
602
+ path = File.join(config[:temp], name)
603
+ FileUtils.mkdir_p(File.dirname(path))
604
+ write_file path, content
605
+ path
606
+ end
501
607
 
502
- function embed() {
503
- if (!document.querySelector('#esvg-#{key_id(key)}')) {
504
- document.querySelector('body').insertAdjacentHTML('afterbegin', '#{svg_to_js(key)}')
505
- }
506
- }
608
+ def read_tmp(name)
609
+ path = File.join(config[:temp], name)
610
+ if File.exist? path
611
+ File.read path
612
+ else
613
+ ''
614
+ end
615
+ end
507
616
 
508
- // If DOM is already ready, embed SVGs
509
- if (document.readyState == 'interactive') { embed() }
617
+ def log_path(path)
618
+ File.expand_path(path).sub(config[:pwd], '').sub(/^\//,'')
619
+ end
510
620
 
511
- // Handle Turbolinks page change events
512
- if ( window.Turbolinks ) {
513
- document.addEventListener("turbolinks:load", function(event) { embed() })
514
- }
621
+ def write_file(path, contents)
622
+ FileUtils.mkdir_p(File.expand_path(File.dirname(path)))
623
+ File.open(path, 'w') do |io|
624
+ io.write(contents)
625
+ end
626
+ end
515
627
 
516
- // Handle standard DOM ready events
517
- document.addEventListener("DOMContentLoaded", function(event) { embed() })
518
- })()}
628
+ def key_id(keys)
629
+ keys.map do |key|
630
+ (key == '.') ? 'symbols' : classname(key)
631
+ end.join('-')
519
632
  end
520
633
 
634
+ # Determine if an NPM module is installed by checking paths with `npm bin`
635
+ # Returns path to binary if installed
521
636
  def find_node_module(cmd)
522
637
  require 'open3'
523
638
 
524
- response = Open3.capture3("npm ls #{cmd}")
639
+ return @modules[cmd] unless @modules[cmd].nil?
525
640
 
526
- # Check for error
527
- if response[1].empty?
528
- "$(npm bin)/#{cmd}"
641
+ @modules[cmd] = begin
642
+ local = "$(npm bin)/#{cmd}"
643
+ global = "$(npm -g bin)/#{cmd}"
529
644
 
530
- # Attempt global module path
531
- elsif Open3.capture3("npm ls -g #{cmd}")[1].empty?
532
- cmd
645
+ if Open3.capture3(local)[2].success?
646
+ local
647
+ elsif Open3.capture3(global)[2].success?
648
+ global
649
+ else
650
+ false
651
+ end
533
652
  end
534
653
  end
535
654
 
@@ -539,5 +658,43 @@ if(typeof(module) != 'undefined') { module.exports = esvg }
539
658
  h
540
659
  end
541
660
 
661
+ # Return non-empty key names for groups of svgs
662
+ def valid_keys(keys)
663
+ if keys.nil? || keys.empty?
664
+ svg_symbols.keys
665
+ else
666
+ keys = [keys].flatten.map { |k| dasherize k }
667
+ svg_symbols.keys.select { |k| keys.include? dasherize(k) }
668
+ end
669
+ end
670
+
671
+ # Load aliases from configuration.
672
+ # returns a hash of aliasees mapped to a name.
673
+ # Converts configuration YAML:
674
+ # alias:
675
+ # foo: bar
676
+ # baz: zip, zop
677
+ # To output:
678
+ # { :bar => "foo", :zip => "baz", :zop => "baz" }
679
+ #
680
+ def load_aliases(aliases)
681
+ a = {}
682
+ aliases.each do |name,alternates|
683
+ alternates.split(',').each do |val|
684
+ a[dasherize(val.strip).to_sym] = dasherize(name.to_s)
685
+ end
686
+ end
687
+ a
688
+ end
689
+
690
+ def get_alias(name)
691
+ config[:aliases][dasherize(name).to_sym] || name
692
+ end
693
+
694
+ def production?
695
+ config[:produciton] || if Esvg.rails?
696
+ Rails.env.production?
697
+ end
698
+ end
542
699
  end
543
700
  end
data/lib/esvg/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Esvg
2
- VERSION = "3.2.0"
2
+ VERSION = "4.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: esvg
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Mathis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-05-04 00:00:00.000000000 Z
11
+ date: 2017-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler