esvg 4.1.6 → 4.2.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 +4 -4
- data/exe/esvg +1 -1
- data/lib/esvg/helpers.rb +3 -27
- data/lib/esvg/svg-old.rb +717 -0
- data/lib/esvg/svg.rb +48 -696
- data/lib/esvg/svgs.rb +320 -0
- data/lib/esvg/symbol.rb +249 -0
- data/lib/esvg/utils.rb +52 -0
- data/lib/esvg/version.rb +1 -1
- data/lib/esvg.rb +62 -3
- metadata +6 -3
- data/lib/esvg/symbols.rb +0 -10
data/lib/esvg/svgs.rb
ADDED
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
require 'zlib'
|
4
|
+
require 'digest'
|
5
|
+
require 'esvg/symbol'
|
6
|
+
|
7
|
+
module Esvg
|
8
|
+
class Svgs
|
9
|
+
include Esvg::Utils
|
10
|
+
|
11
|
+
attr_reader :symbols
|
12
|
+
|
13
|
+
CONFIG = {
|
14
|
+
filename: 'svgs',
|
15
|
+
class: 'svg-symbol',
|
16
|
+
prefix: 'svg',
|
17
|
+
core: true,
|
18
|
+
optimize: false,
|
19
|
+
gzip: false,
|
20
|
+
fingerprint: true,
|
21
|
+
throttle_read: 4,
|
22
|
+
flatten: [],
|
23
|
+
alias: {}
|
24
|
+
}
|
25
|
+
|
26
|
+
CONFIG_RAILS = {
|
27
|
+
source: "app/assets/svgs",
|
28
|
+
assets: "app/assets/javascripts",
|
29
|
+
build: "public/javascripts",
|
30
|
+
temp: "tmp"
|
31
|
+
}
|
32
|
+
|
33
|
+
def initialize(options={})
|
34
|
+
config(options)
|
35
|
+
@symbols = []
|
36
|
+
@svgs = []
|
37
|
+
@last_read = nil
|
38
|
+
read_cache
|
39
|
+
|
40
|
+
load_files
|
41
|
+
end
|
42
|
+
|
43
|
+
def config(options={})
|
44
|
+
@config ||= begin
|
45
|
+
paths = [options[:config_file], 'config/esvg.yml', 'esvg.yml'].compact
|
46
|
+
|
47
|
+
config = CONFIG.dup
|
48
|
+
|
49
|
+
if Esvg.rails? || options[:rails]
|
50
|
+
config.merge!(CONFIG_RAILS)
|
51
|
+
end
|
52
|
+
|
53
|
+
if path = paths.select{ |p| File.exist?(p)}.first
|
54
|
+
config.merge!(symbolize_keys(YAML.load(File.read(path) || {})))
|
55
|
+
end
|
56
|
+
|
57
|
+
config.merge!(options)
|
58
|
+
|
59
|
+
config[:filename] = File.basename(config[:filename], '.*')
|
60
|
+
|
61
|
+
config[:pwd] = File.expand_path Dir.pwd
|
62
|
+
config[:source] = File.expand_path config[:source] || config[:pwd]
|
63
|
+
config[:build] = File.expand_path config[:build] || config[:pwd]
|
64
|
+
config[:assets] = File.expand_path config[:assets] || config[:pwd]
|
65
|
+
|
66
|
+
config[:temp] = config[:pwd] if config[:temp].nil?
|
67
|
+
config[:temp] = File.expand_path File.join(config[:temp], '.esvg-cache')
|
68
|
+
|
69
|
+
config[:aliases] = load_aliases(config[:alias])
|
70
|
+
config[:flatten] = [config[:flatten]].flatten.map { |dir| File.join(dir, '/') }.join('|')
|
71
|
+
|
72
|
+
config
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_files
|
77
|
+
if !@last_read.nil? && (Time.now.to_i - @last_read) < config[:throttle_read]
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
files = Dir[File.join(config[:source], '**/*.svg')].uniq.sort
|
82
|
+
|
83
|
+
if files.empty? && config[:print]
|
84
|
+
puts "No svgs found at #{config[:source]}"
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
# Remove deleted files
|
89
|
+
@symbols.reject(&:read).each { |s| @symbols.delete(s) }
|
90
|
+
|
91
|
+
files.each do |path|
|
92
|
+
unless @symbols.find { |s| s.path == path }
|
93
|
+
@symbols << Symbol.new(path, config)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
@svgs.clear
|
98
|
+
|
99
|
+
sort(@symbols.group_by(&:group)).each do |name, symbols|
|
100
|
+
@svgs << Svg.new(name, symbols, config)
|
101
|
+
end
|
102
|
+
|
103
|
+
@last_read = Time.now.to_i
|
104
|
+
|
105
|
+
puts "Read #{@symbols.size} files from #{config[:source]}" if config[:print]
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
def build
|
110
|
+
|
111
|
+
paths = []
|
112
|
+
|
113
|
+
if config[:core]
|
114
|
+
path = File.join config[:assets], "_esvg.js"
|
115
|
+
write_file path, js_core
|
116
|
+
paths << path
|
117
|
+
end
|
118
|
+
|
119
|
+
@svgs.each do |file|
|
120
|
+
write_file file.path, js(file.embed)
|
121
|
+
|
122
|
+
puts "Writing #{file.path}" if config[:print]
|
123
|
+
paths << file.path
|
124
|
+
|
125
|
+
if !file.asset && config[:gzip] && gz = compress(file[:path])
|
126
|
+
puts "Writing #{gz}" if config[:print]
|
127
|
+
paths << gz
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
write_cache
|
132
|
+
paths
|
133
|
+
end
|
134
|
+
|
135
|
+
def write_cache
|
136
|
+
write_tmp '.symbols', @symbols.map(&:data).to_yaml
|
137
|
+
end
|
138
|
+
|
139
|
+
def read_cache
|
140
|
+
(YAML.load(read_tmp '.symbols') || []).each do |s|
|
141
|
+
@symbols << Symbol.new(s[:path], config)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Embed only build scripts
|
146
|
+
def embed_script(names=nil)
|
147
|
+
embeds = buildable_svgs(names).map(&:embed)
|
148
|
+
if !embeds.empty?
|
149
|
+
"<script>#{js(embeds.join("\n"))}</script>"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def build_paths(names=nil)
|
154
|
+
buildable_svgs(names).map{ |f| File.basename(f.path) }
|
155
|
+
end
|
156
|
+
|
157
|
+
def find_symbol(name, fallback=nil)
|
158
|
+
name = get_alias dasherize(name)
|
159
|
+
|
160
|
+
if svg = @symbols.find { |s| s.name == name }
|
161
|
+
svg
|
162
|
+
elsif fallback
|
163
|
+
find_symbol(fallback)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def find_svgs(names=nil)
|
168
|
+
return @svgs if names.nil? || names.empty?
|
169
|
+
|
170
|
+
@svgs.select { |svg| svg.named?(names) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def buildable_svgs(names=nil)
|
174
|
+
find_svgs(names).reject(&:asset)
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def js(embed)
|
180
|
+
%Q{(function(){
|
181
|
+
|
182
|
+
function embed() {
|
183
|
+
#{embed}
|
184
|
+
}
|
185
|
+
|
186
|
+
// If DOM is already ready, embed SVGs
|
187
|
+
if (document.readyState == 'interactive') { embed() }
|
188
|
+
|
189
|
+
// Handle Turbolinks page change events
|
190
|
+
if ( window.Turbolinks ) {
|
191
|
+
document.addEventListener("turbolinks:load", function(event) { embed() })
|
192
|
+
}
|
193
|
+
|
194
|
+
// Handle standard DOM ready events
|
195
|
+
document.addEventListener("DOMContentLoaded", function(event) { embed() })
|
196
|
+
})()}
|
197
|
+
end
|
198
|
+
|
199
|
+
def js_core
|
200
|
+
%Q{(function(){
|
201
|
+
var names
|
202
|
+
|
203
|
+
function attr( source, name ){
|
204
|
+
if (typeof source == 'object')
|
205
|
+
return name+'="'+source.getAttribute(name)+'" '
|
206
|
+
else
|
207
|
+
return name+'="'+source+'" ' }
|
208
|
+
|
209
|
+
function dasherize( input ) {
|
210
|
+
return input.replace(/[\\W,_]/g, '-').replace(/-{2,}/g, '-')
|
211
|
+
}
|
212
|
+
|
213
|
+
function svgName( name ) {
|
214
|
+
return "#{config[:prefix]}-"+dasherize( name )
|
215
|
+
}
|
216
|
+
|
217
|
+
function use( name, options ) {
|
218
|
+
options = options || {}
|
219
|
+
var id = dasherize( name )
|
220
|
+
var symbol = svgs()[id]
|
221
|
+
|
222
|
+
if ( symbol ) {
|
223
|
+
var parent = symbol.parentElement
|
224
|
+
var prefix = parent.dataset.prefix
|
225
|
+
var base = parent.dataset.symbolClass
|
226
|
+
|
227
|
+
var svg = document.createRange().createContextualFragment( '<svg><use xlink:href="#'+id+'"/></svg>' ).firstChild;
|
228
|
+
svg.setAttribute( 'class', base + ' ' + prefix + '-' + id + ' ' + ( options.class || '' ).trim() )
|
229
|
+
svg.setAttribute( 'viewBox', symbol.getAttribute( 'viewBox' ) )
|
230
|
+
|
231
|
+
if ( !( options.width || options.height || options.scale ) ) {
|
232
|
+
|
233
|
+
svg.setAttribute('width', symbol.getAttribute('width'))
|
234
|
+
svg.setAttribute('height', symbol.getAttribute('height'))
|
235
|
+
|
236
|
+
} else {
|
237
|
+
|
238
|
+
if ( options.width ) svg.setAttribute( 'width', options.width )
|
239
|
+
if ( options.height ) svg.setAttribute( 'height', options.height )
|
240
|
+
}
|
241
|
+
|
242
|
+
return svg
|
243
|
+
} else {
|
244
|
+
console.error('Cannot find "'+name+'" svg symbol. Ensure that svg scripts are loaded')
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
248
|
+
function svgs(){
|
249
|
+
if ( !names ) {
|
250
|
+
names = {}
|
251
|
+
for( var symbol of document.querySelectorAll( 'svg[id^=esvg] symbol' ) ) {
|
252
|
+
names[symbol.dataset.name] = symbol
|
253
|
+
}
|
254
|
+
}
|
255
|
+
return names
|
256
|
+
}
|
257
|
+
|
258
|
+
var esvg = {
|
259
|
+
svgs: svgs,
|
260
|
+
use: use
|
261
|
+
}
|
262
|
+
|
263
|
+
// Handle Turbolinks page change events
|
264
|
+
if ( window.Turbolinks ) {
|
265
|
+
document.addEventListener( "turbolinks:load", function( event ) { names = null; esvg.svgs() })
|
266
|
+
}
|
267
|
+
|
268
|
+
if( typeof( module ) != 'undefined' ) { module.exports = esvg }
|
269
|
+
else window.esvg = esvg
|
270
|
+
|
271
|
+
})()}
|
272
|
+
end
|
273
|
+
|
274
|
+
def get_alias(name)
|
275
|
+
config[:aliases][dasherize(name).to_sym] || name
|
276
|
+
end
|
277
|
+
|
278
|
+
# Load aliases from configuration.
|
279
|
+
# returns a hash of aliasees mapped to a name.
|
280
|
+
# Converts configuration YAML:
|
281
|
+
# alias:
|
282
|
+
# foo: bar
|
283
|
+
# baz: zip, zop
|
284
|
+
# To output:
|
285
|
+
# { :bar => "foo", :zip => "baz", :zop => "baz" }
|
286
|
+
#
|
287
|
+
def load_aliases(aliases)
|
288
|
+
a = {}
|
289
|
+
aliases.each do |name,alternates|
|
290
|
+
alternates.split(',').each do |val|
|
291
|
+
a[dasherize(val.strip).to_sym] = dasherize(name.to_s)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
a
|
295
|
+
end
|
296
|
+
|
297
|
+
def write_tmp(name, content)
|
298
|
+
path = File.join(config[:temp], name)
|
299
|
+
FileUtils.mkdir_p(File.dirname(path))
|
300
|
+
write_file path, content
|
301
|
+
path
|
302
|
+
end
|
303
|
+
|
304
|
+
def read_tmp(name)
|
305
|
+
path = File.join(config[:temp], name)
|
306
|
+
if File.exist? path
|
307
|
+
File.read path
|
308
|
+
else
|
309
|
+
''
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def write_file(path, contents)
|
314
|
+
FileUtils.mkdir_p(File.expand_path(File.dirname(path)))
|
315
|
+
File.open(path, 'w') do |io|
|
316
|
+
io.write(contents)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
data/lib/esvg/symbol.rb
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Esvg
|
4
|
+
class Symbol
|
5
|
+
attr_reader :name, :id, :path, :content, :optimized, :size, :group, :mtime
|
6
|
+
|
7
|
+
include Esvg::Utils
|
8
|
+
|
9
|
+
def initialize(path, config={})
|
10
|
+
@config = config
|
11
|
+
@path = path
|
12
|
+
load_data
|
13
|
+
read
|
14
|
+
end
|
15
|
+
|
16
|
+
def read
|
17
|
+
return if !File.exist?(@path)
|
18
|
+
|
19
|
+
time = last_modified
|
20
|
+
if @mtime != time
|
21
|
+
@mtime = time
|
22
|
+
@content = pre_optimize File.read(@path)
|
23
|
+
@size = dimensions
|
24
|
+
@optimized = nil
|
25
|
+
end
|
26
|
+
@group = dir_key
|
27
|
+
@name = file_name
|
28
|
+
@id = file_id file_key
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def data
|
34
|
+
{
|
35
|
+
path: @path,
|
36
|
+
id: @id,
|
37
|
+
name: @name,
|
38
|
+
group: @group,
|
39
|
+
last_modified: @mtime,
|
40
|
+
size: @size,
|
41
|
+
content: @content,
|
42
|
+
optimized: @optimized,
|
43
|
+
optimized_at: @optimized_at
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def attr
|
48
|
+
{ id: @id, 'data-name': @name }.merge @size
|
49
|
+
end
|
50
|
+
|
51
|
+
def use(options={})
|
52
|
+
if options[:color]
|
53
|
+
options[:style] ||= ''
|
54
|
+
options[:style] += "color:#{options[:color]};#{options[:style]}"
|
55
|
+
end
|
56
|
+
|
57
|
+
use_attr = {
|
58
|
+
class: [@config[:class], @config[:prefix]+"-"+@name, options[:class]].compact.join(' '),
|
59
|
+
viewBox: @size[:viewBox],
|
60
|
+
style: options[:style],
|
61
|
+
fill: options[:fill]
|
62
|
+
}
|
63
|
+
|
64
|
+
# If user doesn't pass a size or set scale: true
|
65
|
+
if !(options[:width] || options[:height] || options[:scale])
|
66
|
+
|
67
|
+
# default to svg dimensions
|
68
|
+
use_attr[:width] = @size[:width]
|
69
|
+
use_attr[:height] = @size[:height]
|
70
|
+
else
|
71
|
+
|
72
|
+
# Add sizes (nil options will be stripped)
|
73
|
+
use_attr[:width] = options[:width]
|
74
|
+
use_attr[:height] = options[:height]
|
75
|
+
end
|
76
|
+
|
77
|
+
%Q{<svg #{attributes(use_attr)}>#{use_tag}#{title(options)}#{desc(options)}#{options[:content]||''}</svg>}
|
78
|
+
end
|
79
|
+
|
80
|
+
def use_tag(options={})
|
81
|
+
options["xlink:href"] = "##{@id}"
|
82
|
+
%Q{<use #{attributes(options)}/>}
|
83
|
+
end
|
84
|
+
|
85
|
+
def title(options)
|
86
|
+
if options[:title]
|
87
|
+
"<title>#{options[:title]}</title>"
|
88
|
+
else
|
89
|
+
''
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def desc(options)
|
94
|
+
if options[:desc]
|
95
|
+
"<desc>#{options[:desc]}</desc>"
|
96
|
+
else
|
97
|
+
''
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def optimize
|
102
|
+
# Only optimize again if the file has changed
|
103
|
+
return @optimized if @optimized_at && @optimized_at > @mtime
|
104
|
+
|
105
|
+
@optimized = @content
|
106
|
+
sub_def_ids
|
107
|
+
|
108
|
+
if @config[:optimize] && Esvg.node_module('svgo')
|
109
|
+
response = Open3.capture3(%Q{#{Esvg.node_module('svgo')} --disable=removeUselessDefs -s '#{@optimized}' -o -})
|
110
|
+
@optimized = response[0] if response[2].success?
|
111
|
+
end
|
112
|
+
|
113
|
+
post_optimize
|
114
|
+
@optimized_at = Time.now.to_i
|
115
|
+
|
116
|
+
@optimized
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def load_data
|
122
|
+
if @config[:cache]
|
123
|
+
@config.delete(:cache).each do |name, value|
|
124
|
+
set_instance name.to_s, value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def last_modified
|
130
|
+
File.mtime(@path).to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
def file_id(name)
|
134
|
+
dasherize "#{@config[:prefix]}-#{name}"
|
135
|
+
end
|
136
|
+
|
137
|
+
def local_path
|
138
|
+
@local_path ||= sub_path(@config[:source], @path)
|
139
|
+
end
|
140
|
+
|
141
|
+
def file_name
|
142
|
+
dasherize flatten_path.sub('.svg','')
|
143
|
+
end
|
144
|
+
|
145
|
+
def file_key
|
146
|
+
dasherize local_path.sub('.svg','')
|
147
|
+
end
|
148
|
+
|
149
|
+
def dir_key
|
150
|
+
dir = File.dirname(flatten_path)
|
151
|
+
|
152
|
+
# Flattened paths which should be treated as assets will use '_' as their dir key
|
153
|
+
# - flatten: _foo - _foo/icon.svg will have a dirkey of _
|
154
|
+
# - filename: _icons - treats all root or flattened files as assets
|
155
|
+
if dir == '.' && ( local_path.start_with?('_') || @config[:filename].start_with?('_') )
|
156
|
+
'_'
|
157
|
+
else
|
158
|
+
dir
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def flatten_path
|
163
|
+
@flattened_path ||= local_path.sub(Regexp.new(@config[:flatten]), '')
|
164
|
+
end
|
165
|
+
|
166
|
+
def name_key(key)
|
167
|
+
if key == '_' # Root level asset file
|
168
|
+
"_#{@config[:filename]}".sub(/_+/, '_')
|
169
|
+
elsif key == '.' # Root level build file
|
170
|
+
@config[:filename]
|
171
|
+
else
|
172
|
+
"#{key}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def dimensions
|
177
|
+
if viewbox = @content.scan(/<svg.+(viewBox=["'](.+?)["'])/).flatten.last
|
178
|
+
coords = viewbox.split(' ')
|
179
|
+
|
180
|
+
{
|
181
|
+
viewBox: viewbox,
|
182
|
+
width: coords[2].to_i - coords[0].to_i,
|
183
|
+
height: coords[3].to_i - coords[1].to_i
|
184
|
+
}
|
185
|
+
else
|
186
|
+
{}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def pre_optimize(svg)
|
191
|
+
# Generate a regex of attributes to be removed
|
192
|
+
att = Regexp.new %w(xmlns xmlns:xlink xml:space version).map { |m| "#{m}=\".+?\"" }.join('|')
|
193
|
+
|
194
|
+
svg.strip
|
195
|
+
.gsub(att, '') # Remove unwanted attributes
|
196
|
+
.sub(/.+?<svg/,'<svg') # Get rid of doctypes and comments
|
197
|
+
.gsub(/style="([^"]*?)fill:(.+?);/m, 'fill="\2" style="\1') # Make fill a property instead of a style
|
198
|
+
.gsub(/style="([^"]*?)fill-opacity:(.+?);/m, 'fill-opacity="\2" style="\1') # Move fill-opacity a property instead of a style
|
199
|
+
.gsub(/\n/, '') # Remove endlines
|
200
|
+
.gsub(/\s{2,}/, ' ') # Remove whitespace
|
201
|
+
.gsub(/>\s+</, '><') # Remove whitespace between tags
|
202
|
+
.gsub(/\s?fill="(#0{3,6}|black|rgba?\(0,0,0\))"/,'') # Strip black fill
|
203
|
+
end
|
204
|
+
|
205
|
+
def post_optimize
|
206
|
+
@optimized = set_attributes
|
207
|
+
.gsub(/<\/svg/,'</symbol') # Replace svgs with symbols
|
208
|
+
.gsub(/class="def-/,'id="def-') # Replace <def> classes with ids (classes are generated in sub_def_ids)
|
209
|
+
.gsub(/\w+=""/,'') # Remove empty attributes
|
210
|
+
end
|
211
|
+
|
212
|
+
def set_attributes
|
213
|
+
attr.keys.each do |key|
|
214
|
+
@optimized.sub!(/ #{key}=".+?"/,'')
|
215
|
+
end
|
216
|
+
|
217
|
+
@optimized.sub!(/<svg/, "<symbol #{attributes(attr)}")
|
218
|
+
end
|
219
|
+
|
220
|
+
# Scans <def> blocks for IDs
|
221
|
+
# If urls(#id) are used, ensure these IDs are unique to this file
|
222
|
+
# Only replace IDs if urls exist to avoid replacing defs
|
223
|
+
# used in other svg files
|
224
|
+
#
|
225
|
+
def sub_def_ids
|
226
|
+
@optimized.scan(/<defs>.+<\/defs>/m).flatten.each do |defs|
|
227
|
+
defs.scan(/id="(.+?)"/).flatten.uniq.each_with_index do |id, index|
|
228
|
+
|
229
|
+
# If there are urls which refer to
|
230
|
+
# ids be sure to update both
|
231
|
+
#
|
232
|
+
if @optimized.match(/url\(##{id}\)/)
|
233
|
+
new_id = "def-#{@id}-#{index}"
|
234
|
+
|
235
|
+
@optimized.gsub! /id="#{id}"/, %Q{class="#{new_id}"}
|
236
|
+
@optimized.gsub! /url\(##{id}\)/, "url(##{new_id})"
|
237
|
+
|
238
|
+
# Otherwise just leave the IDs of the
|
239
|
+
# defs and change them to classes to
|
240
|
+
# avoid SVGO ID mangling
|
241
|
+
#
|
242
|
+
else
|
243
|
+
@optimized.gsub! /id="#{id}"/, %Q{class="#{id}"}
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
data/lib/esvg/utils.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Esvg
|
2
|
+
module Utils
|
3
|
+
def dasherize(input)
|
4
|
+
input.gsub(/[\W,_]/, '-').sub(/^-/,'').gsub(/-{2,}/, '-')
|
5
|
+
end
|
6
|
+
|
7
|
+
def sub_path(root, path)
|
8
|
+
path.sub(File.join(root,''),'')
|
9
|
+
end
|
10
|
+
|
11
|
+
def symbolize_keys(hash)
|
12
|
+
h = {}
|
13
|
+
hash.each {|k,v| h[k.to_sym] = v }
|
14
|
+
h
|
15
|
+
end
|
16
|
+
|
17
|
+
def attributes(hash)
|
18
|
+
att = []
|
19
|
+
hash.each do |key, value|
|
20
|
+
att << %Q{#{key}="#{value}"} unless value.nil?
|
21
|
+
end
|
22
|
+
att.join(' ')
|
23
|
+
end
|
24
|
+
|
25
|
+
def sort(hash)
|
26
|
+
sorted = {}
|
27
|
+
hash.sort.each do |h|
|
28
|
+
sorted[h.first] = h.last
|
29
|
+
end
|
30
|
+
sorted
|
31
|
+
end
|
32
|
+
|
33
|
+
def compress(file)
|
34
|
+
mtime = File.mtime(file)
|
35
|
+
gz_file = "#{file}.gz"
|
36
|
+
|
37
|
+
return if (File.exist?(gz_file) && File.mtime(gz_file) >= mtime)
|
38
|
+
|
39
|
+
File.open(gz_file, "wb") do |dest|
|
40
|
+
gz = ::Zlib::GzipWriter.new(dest, Zlib::BEST_COMPRESSION)
|
41
|
+
gz.mtime = mtime.to_i
|
42
|
+
IO.copy_stream(open(file), gz)
|
43
|
+
gz.close
|
44
|
+
end
|
45
|
+
|
46
|
+
File.utime(mtime, mtime, gz_file)
|
47
|
+
|
48
|
+
gz_file
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
data/lib/esvg/version.rb
CHANGED
data/lib/esvg.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "fileutils"
|
2
2
|
|
3
3
|
require "esvg/version"
|
4
|
+
require "esvg/utils"
|
5
|
+
require "esvg/svgs"
|
4
6
|
require "esvg/svg"
|
5
7
|
|
6
8
|
if defined?(Rails)
|
@@ -12,21 +14,60 @@ module Esvg
|
|
12
14
|
extend self
|
13
15
|
|
14
16
|
def new(options={})
|
15
|
-
@svgs
|
17
|
+
@svgs ||=[]
|
18
|
+
@svgs << Svgs.new(options)
|
19
|
+
@svgs.last
|
16
20
|
end
|
17
21
|
|
18
22
|
def svgs
|
19
23
|
@svgs
|
20
24
|
end
|
21
25
|
|
22
|
-
def
|
23
|
-
|
26
|
+
def use(name, options={})
|
27
|
+
if symbol = find_symbol(name, options)
|
28
|
+
html_safe symbol.use options
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def use_tag(name, options={})
|
33
|
+
if symbol = find_symbol(name, options)
|
34
|
+
html_safe symbol.use_tag options
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def embed(names=nil)
|
39
|
+
if rails? && Rails.env.production?
|
40
|
+
html_safe build_paths(names).each do |path|
|
41
|
+
javascript_include_tag(path, async: true)
|
42
|
+
end.join("\n")
|
43
|
+
else
|
44
|
+
html_safe find_svgs(names).map{|s| s.embed_script(names) }.join
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_paths(names=nil)
|
49
|
+
find_svgs(names).map{|s| s.build_paths(names) }.flatten
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_svgs(names=nil)
|
53
|
+
@svgs.select {|s| s.buildable_svgs(names) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_symbol(name, options={})
|
57
|
+
if group = @svgs.find {|s| s.find_symbol(name, options[:fallback]) }
|
58
|
+
group.find_symbol(name, options[:fallback])
|
59
|
+
end
|
24
60
|
end
|
25
61
|
|
26
62
|
def rails?
|
27
63
|
defined?(Rails)
|
28
64
|
end
|
29
65
|
|
66
|
+
def html_safe(input)
|
67
|
+
input = input.html_safe if rails?
|
68
|
+
input
|
69
|
+
end
|
70
|
+
|
30
71
|
def build(options={})
|
31
72
|
new(options).build
|
32
73
|
end
|
@@ -38,4 +79,22 @@ module Esvg
|
|
38
79
|
end
|
39
80
|
end
|
40
81
|
end
|
82
|
+
|
83
|
+
# Determine if an NPM module is installed by checking paths with `npm bin`
|
84
|
+
# Returns path to binary if installed
|
85
|
+
def node_module(cmd)
|
86
|
+
@modules ||={}
|
87
|
+
return @modules[cmd] if !@modules[cmd].nil?
|
88
|
+
|
89
|
+
local = "$(npm bin)/#{cmd}"
|
90
|
+
global = "$(npm -g bin)/#{cmd}"
|
91
|
+
|
92
|
+
@modules[cmd] = if Open3.capture3(local)[2].success?
|
93
|
+
local
|
94
|
+
elsif Open3.capture3(global)[2].success?
|
95
|
+
global
|
96
|
+
else
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
41
100
|
end
|