esvg 3.2.0 → 4.0.0

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.
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