bootstrap-sass 3.0.0.0 → 3.0.1.0.rc
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bootstrap-sass might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/bootstrap-sass/version.rb +2 -2
- data/tasks/converter.rb +16 -773
- data/tasks/converter/char_string_scanner.rb +38 -0
- data/tasks/converter/fonts_conversion.rb +12 -0
- data/tasks/converter/js_conversion.rb +22 -0
- data/tasks/converter/less_conversion.rb +612 -0
- data/tasks/converter/logger.rb +64 -0
- data/tasks/converter/network.rb +110 -0
- data/test/compilation_test.rb +5 -1
- data/test/dummy/app/views/pages/root.html.slim +10 -2
- data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
- data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.svg +200 -199
- data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
- data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
- data/vendor/assets/javascripts/bootstrap/affix.js +2 -2
- data/vendor/assets/javascripts/bootstrap/alert.js +1 -1
- data/vendor/assets/javascripts/bootstrap/button.js +1 -1
- data/vendor/assets/javascripts/bootstrap/carousel.js +2 -2
- data/vendor/assets/javascripts/bootstrap/collapse.js +2 -2
- data/vendor/assets/javascripts/bootstrap/dropdown.js +2 -2
- data/vendor/assets/javascripts/bootstrap/modal.js +2 -2
- data/vendor/assets/javascripts/bootstrap/popover.js +2 -2
- data/vendor/assets/javascripts/bootstrap/scrollspy.js +2 -2
- data/vendor/assets/javascripts/bootstrap/tab.js +3 -3
- data/vendor/assets/javascripts/bootstrap/tooltip.js +2 -2
- data/vendor/assets/javascripts/bootstrap/transition.js +1 -1
- data/vendor/assets/stylesheets/bootstrap/_alerts.scss +1 -1
- data/vendor/assets/stylesheets/bootstrap/_breadcrumbs.scss +2 -2
- data/vendor/assets/stylesheets/bootstrap/_button-groups.scss +6 -1
- data/vendor/assets/stylesheets/bootstrap/_buttons.scss +2 -4
- data/vendor/assets/stylesheets/bootstrap/_carousel.scss +32 -10
- data/vendor/assets/stylesheets/bootstrap/_code.scss +5 -8
- data/vendor/assets/stylesheets/bootstrap/_dropdowns.scss +1 -2
- data/vendor/assets/stylesheets/bootstrap/_forms.scss +16 -3
- data/vendor/assets/stylesheets/bootstrap/_glyphicons.scss +16 -11
- data/vendor/assets/stylesheets/bootstrap/_grid.scss +32 -285
- data/vendor/assets/stylesheets/bootstrap/_input-groups.scss +9 -0
- data/vendor/assets/stylesheets/bootstrap/_jumbotron.scss +2 -2
- data/vendor/assets/stylesheets/bootstrap/_list-group.scss +15 -17
- data/vendor/assets/stylesheets/bootstrap/_mixins.scss +160 -29
- data/vendor/assets/stylesheets/bootstrap/_modals.scss +2 -16
- data/vendor/assets/stylesheets/bootstrap/_navbar.scss +10 -7
- data/vendor/assets/stylesheets/bootstrap/_navs.scss +53 -20
- data/vendor/assets/stylesheets/bootstrap/_normalize.scss +16 -6
- data/vendor/assets/stylesheets/bootstrap/_pagination.scss +2 -0
- data/vendor/assets/stylesheets/bootstrap/_panels.scss +31 -7
- data/vendor/assets/stylesheets/bootstrap/_print.scss +6 -1
- data/vendor/assets/stylesheets/bootstrap/_progress-bars.scss +4 -7
- data/vendor/assets/stylesheets/bootstrap/_responsive-utilities.scss +57 -68
- data/vendor/assets/stylesheets/bootstrap/_scaffolding.scss +1 -12
- data/vendor/assets/stylesheets/bootstrap/_tables.scss +40 -40
- data/vendor/assets/stylesheets/bootstrap/_theme.scss +26 -11
- data/vendor/assets/stylesheets/bootstrap/_thumbnails.scss +6 -6
- data/vendor/assets/stylesheets/bootstrap/_tooltip.scss +8 -8
- data/vendor/assets/stylesheets/bootstrap/_type.scss +71 -30
- data/vendor/assets/stylesheets/bootstrap/_utilities.scss +15 -1
- data/vendor/assets/stylesheets/bootstrap/_variables.scss +56 -39
- data/vendor/assets/stylesheets/bootstrap/bootstrap.scss +0 -10
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12e4060b2ae1d151a7e2de1e9fd65daedfc720a5
|
4
|
+
data.tar.gz: 6ea5e7065f2cdee8cd1f06918661dbe60d622139
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2e53efa06afc231a19ab780638ac312cb91a7c8cb1c80ad192cbffe70f50341a80fd1216b55e467e5ac56992f47855d9c3c20ec1a215767ff0e6f331ac0f452
|
7
|
+
data.tar.gz: 8cca5905200e5167e222b8eb863f00f2d4ed69c4d8d802662cfd2e23b866370e3de6c0c828573153246ac44eb0863be2b2aaf49c337e8002c157eea2e6df45a9
|
data/tasks/converter.rb
CHANGED
@@ -21,13 +21,23 @@ require 'forwardable'
|
|
21
21
|
require 'term/ansicolor'
|
22
22
|
require 'fileutils'
|
23
23
|
|
24
|
+
require_relative 'converter/fonts_conversion'
|
25
|
+
require_relative 'converter/less_conversion'
|
26
|
+
require_relative 'converter/js_conversion'
|
27
|
+
require_relative 'converter/logger'
|
28
|
+
require_relative 'converter/network'
|
29
|
+
|
24
30
|
class Converter
|
25
31
|
extend Forwardable
|
26
|
-
|
27
|
-
|
28
|
-
|
32
|
+
include Network
|
33
|
+
include LessConversion
|
34
|
+
include JsConversion
|
35
|
+
include FontsConversion
|
29
36
|
|
30
37
|
def initialize(branch)
|
38
|
+
@git_data_api_host = 'https://api.github.com/repos'
|
39
|
+
@git_raw_host = 'https://raw.github.com'
|
40
|
+
|
31
41
|
@repo = 'twbs/bootstrap'
|
32
42
|
@repo_url = "https://github.com/#@repo"
|
33
43
|
@branch = branch || 'master'
|
@@ -49,781 +59,14 @@ class Converter
|
|
49
59
|
store_version
|
50
60
|
end
|
51
61
|
|
52
|
-
def
|
53
|
-
|
54
|
-
files = read_files('fonts', bootstrap_font_files)
|
55
|
-
save_at = @save_at[:fonts]
|
56
|
-
files.each do |name, content|
|
57
|
-
save_file "#{save_at}/#{name}", content
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
NESTED_MIXINS = {'#gradient' => 'gradient'}
|
62
|
-
VARARG_MIXINS = %w(transition transition-transform box-shadow)
|
63
|
-
def process_stylesheet_assets
|
64
|
-
log_status "Processing stylesheets..."
|
65
|
-
files = read_files('less', bootstrap_less_files)
|
66
|
-
|
67
|
-
# read common mixin definitions (incl. nested mixins) from mixins.less
|
68
|
-
read_shared_mixins! files['mixins.less']
|
69
|
-
|
70
|
-
# convert each file
|
71
|
-
files.each do |name, file|
|
72
|
-
log_processing name
|
73
|
-
# apply common conversions
|
74
|
-
file = convert_to_scss(file)
|
75
|
-
case name
|
76
|
-
when 'mixins.less'
|
77
|
-
NESTED_MIXINS.each do |selector, prefix|
|
78
|
-
file = flatten_mixins(file, selector, prefix)
|
79
|
-
end
|
80
|
-
file = varargify_mixin_definitions(file, *VARARG_MIXINS)
|
81
|
-
file = deinterpolate_vararg_mixins(file)
|
82
|
-
file = parameterize_mixin_parent_selector file, 'responsive-(in)?visibility'
|
83
|
-
file = parameterize_mixin_parent_selector file, 'input-size'
|
84
|
-
file = replace_ms_filters(file)
|
85
|
-
file = replace_all file, /\.\$state/, '.#{$state}'
|
86
|
-
file = replace_all file, /,\s*\.open \.dropdown-toggle& \{(.*?)\}/m,
|
87
|
-
" {\\1}\n .open & { &.dropdown-toggle {\\1} }"
|
88
|
-
when 'responsive-utilities.less'
|
89
|
-
file = apply_mixin_parent_selector(file, '&\.(visible|hidden)')
|
90
|
-
file = apply_mixin_parent_selector(file, '(?<!&)\.(visible|hidden)')
|
91
|
-
file = replace_rules(file, ' @media') { |r| unindent(r, 2) }
|
92
|
-
when 'variables.less'
|
93
|
-
file = insert_default_vars(file)
|
94
|
-
file = replace_all file, /(\$icon-font-path:).*(!default)/, '\1 "bootstrap/" \2'
|
95
|
-
when 'close.less'
|
96
|
-
# extract .close { button& {...} } rule
|
97
|
-
file = extract_nested_rule file, 'button&'
|
98
|
-
when 'modals.less'
|
99
|
-
file = replace_all file, /body&,(.*?)(\{.*?\})/m, "\\1\\2\nbody& \\2"
|
100
|
-
file = extract_nested_rule file, 'body&'
|
101
|
-
when 'dropdowns.less'
|
102
|
-
file = replace_all file, /(\s*)@extend \.pull-right-dropdown-menu;/, "\\1right: 0;\\1left: auto;"
|
103
|
-
when 'forms.less'
|
104
|
-
file = extract_nested_rule file, 'textarea&'
|
105
|
-
file = apply_mixin_parent_selector(file, '\.input-(?:sm|lg)')
|
106
|
-
when 'navbar.less'
|
107
|
-
file = replace_all file, /(\s*)\.navbar-(right|left)\s*\{\s*@extend\s*\.pull-(right|left);\s*/, "\\1.navbar-\\2 {\\1 float: \\2 !important;\\1"
|
108
|
-
when 'tables.less'
|
109
|
-
file = replace_all file, /(@include\s*table-row-variant\()(\w+)/, "\\1'\\2'"
|
110
|
-
when 'list-group.less'
|
111
|
-
file = extract_nested_rule file, 'a&'
|
112
|
-
when 'glyphicons.less'
|
113
|
-
file = replace_rules(file, '@font-face') { |rule|
|
114
|
-
rule = replace_all rule, /(\$icon-font-\w+)/, '#{\1}'
|
115
|
-
replace_all rule, /url\(/, 'font-url('
|
116
|
-
}
|
117
|
-
end
|
118
|
-
|
119
|
-
name = name.sub(/\.less$/, '.scss')
|
120
|
-
save_at = @save_at[:scss]
|
121
|
-
path = "#{save_at}/#{'_' unless name == 'bootstrap.scss'}#{name}"
|
122
|
-
save_file(path, file)
|
123
|
-
log_processed File.basename(path)
|
124
|
-
end
|
62
|
+
def save_file(path, content, mode='w')
|
63
|
+
File.open(path, mode) { |file| file.write(content) }
|
125
64
|
end
|
126
65
|
|
66
|
+
# Update version.rb file with BOOTSTRAP_SHA
|
127
67
|
def store_version
|
128
68
|
path = 'lib/bootstrap-sass/version.rb'
|
129
69
|
content = File.read(path).sub(/BOOTSTRAP_SHA\s*=\s*['"][\w]+['"]/, "BOOTSTRAP_SHA = '#@branch_sha'")
|
130
70
|
File.open(path, 'w') { |f| f.write(content) }
|
131
71
|
end
|
132
|
-
|
133
|
-
def process_javascript_assets
|
134
|
-
log_status "Processing javascripts..."
|
135
|
-
save_at = @save_at[:js]
|
136
|
-
read_files('js', bootstrap_js_files).each do |name, file|
|
137
|
-
save_file("#{save_at}/#{name}", file)
|
138
|
-
end
|
139
|
-
log_processed "#{bootstrap_js_files * ' '}"
|
140
|
-
|
141
|
-
log_status "Updating javascript manifest"
|
142
|
-
content = ''
|
143
|
-
bootstrap_js_files.each do |name|
|
144
|
-
name = name.gsub(/\.js$/, '')
|
145
|
-
content << "//= require bootstrap/#{name}\n"
|
146
|
-
end
|
147
|
-
path = "vendor/assets/javascripts/bootstrap.js"
|
148
|
-
save_file(path, content)
|
149
|
-
log_processed path
|
150
|
-
end
|
151
|
-
|
152
|
-
private
|
153
|
-
|
154
|
-
def read_files(path, files)
|
155
|
-
full_path = "#{GIT_RAW}/#@repo/#@branch_sha/#{path}"
|
156
|
-
if (contents = read_cached_files(path, files))
|
157
|
-
log_http_get_files files, full_path, true
|
158
|
-
else
|
159
|
-
log_http_get_files files, full_path, false
|
160
|
-
contents = {}
|
161
|
-
files.map do |name|
|
162
|
-
Thread.start {
|
163
|
-
content = open("#{full_path}/#{name}").read
|
164
|
-
Thread.exclusive { contents[name] = content }
|
165
|
-
}
|
166
|
-
end.each(&:join)
|
167
|
-
write_cached_files path, contents
|
168
|
-
end
|
169
|
-
contents
|
170
|
-
end
|
171
|
-
|
172
|
-
def read_cached_files(path, files)
|
173
|
-
full_path = "#@cache_path/#@branch_sha/#{path}"
|
174
|
-
contents = {}
|
175
|
-
if File.directory?(full_path)
|
176
|
-
files.each do |name|
|
177
|
-
contents[name] = File.read("#{full_path}/#{name}", mode: 'rb') || ''
|
178
|
-
end
|
179
|
-
contents
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def write_cached_files(path, files)
|
184
|
-
full_path = "./#@cache_path/#@branch_sha/#{path}"
|
185
|
-
FileUtils.mkdir_p full_path
|
186
|
-
files.each do |name, content|
|
187
|
-
File.open("#{full_path}/#{name}", 'wb') { |f| f.write content}
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
|
192
|
-
def get_file(url)
|
193
|
-
cache_path = "./#@cache_path#{URI(url).path}"
|
194
|
-
FileUtils.mkdir_p File.dirname(cache_path)
|
195
|
-
if File.exists?(cache_path)
|
196
|
-
log_http_get_file url, true
|
197
|
-
File.read(cache_path, mode: 'rb')
|
198
|
-
else
|
199
|
-
log_http_get_file url, false
|
200
|
-
content = open(url).read
|
201
|
-
File.open(cache_path, 'wb') { |f| f.write content }
|
202
|
-
content
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
# get sha of the branch (= the latest commit)
|
207
|
-
def get_branch_sha
|
208
|
-
cmd = "git ls-remote '#@repo_url' | awk '/#@branch/ {print $1}'"
|
209
|
-
puts cmd
|
210
|
-
@branch_sha ||= %x[#{cmd}].chomp
|
211
|
-
raise 'Could not get branch sha!' unless $?.success?
|
212
|
-
@branch_sha
|
213
|
-
end
|
214
|
-
|
215
|
-
# Get the sha of a dir
|
216
|
-
def get_tree_sha(dir)
|
217
|
-
get_trees['tree'].find { |t| t['path'] == dir }['sha']
|
218
|
-
end
|
219
|
-
|
220
|
-
def get_trees
|
221
|
-
@trees ||= get_json("#{GIT_DATA}/#@repo/git/trees/#@branch_sha")
|
222
|
-
end
|
223
|
-
|
224
|
-
def bootstrap_font_files
|
225
|
-
@bootstrap_font_files ||= begin
|
226
|
-
files = get_json "#{GIT_DATA}/#@repo/git/trees/#{get_tree_sha('fonts')}"
|
227
|
-
files['tree'].select { |f| f['type'] == 'blob' && f['path'] =~ /\.(eot|svg|ttf|woff)$/ }.map { |f| f['path'] }
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def bootstrap_less_files
|
232
|
-
@bootstrap_less_files ||= begin
|
233
|
-
files = get_json "#{GIT_DATA}/#@repo/git/trees/#{get_tree_sha('less')}"
|
234
|
-
files['tree'].select { |f| f['type'] == 'blob' && f['path'] =~ /\.less$/ }.map { |f| f['path'] }
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def bootstrap_js_files
|
239
|
-
@bootstrap_js_files ||= begin
|
240
|
-
files = get_json "#{GIT_DATA}/#@repo/git/trees/#{get_tree_sha('js')}"
|
241
|
-
files = files['tree'].select { |f| f['type'] == 'blob' && f['path'] =~ /\.js$/ }.map { |f| f['path'] }
|
242
|
-
files.sort_by { |f|
|
243
|
-
case f
|
244
|
-
# tooltip depends on popover and must be loaded earlier
|
245
|
-
when /tooltip/ then 1
|
246
|
-
when /popover/ then 2
|
247
|
-
else
|
248
|
-
0
|
249
|
-
end
|
250
|
-
}
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
# We need to keep a list of shared mixin names in order to convert the includes correctly
|
255
|
-
# Before doing any processing we read shared mixins from a file
|
256
|
-
# If a mixin is nested, it gets prefixed in the list (e.g. #gradient > .horizontal to 'gradient-horizontal')
|
257
|
-
def read_shared_mixins!(mixins_file)
|
258
|
-
log_status " Reading shared mixins from mixins.less"
|
259
|
-
@shared_mixins = get_mixin_names(mixins_file, silent: true)
|
260
|
-
NESTED_MIXINS.each do |selector, prefix|
|
261
|
-
# we use replace_rules without replacing anything just to use the parsing algorithm
|
262
|
-
replace_rules(mixins_file, selector) { |rule|
|
263
|
-
@shared_mixins += get_mixin_names(unindent(unwrap_rule_block(rule)), silent: true).map { |name| "#{prefix}-#{name}" }
|
264
|
-
rule
|
265
|
-
}
|
266
|
-
end
|
267
|
-
@shared_mixins.sort!
|
268
|
-
log_file_info "shared mixins: #{@shared_mixins * ', '}"
|
269
|
-
@shared_mixins
|
270
|
-
end
|
271
|
-
|
272
|
-
def get_mixin_names(file, opts = {})
|
273
|
-
names = get_css_selectors(file).join("\n" * 2).scan(/^\.([\w-]+)\(#{LESS_MIXIN_DEF_ARGS_RE}\)[ ]*\{/).map(&:first).uniq.sort
|
274
|
-
log_file_info "mixin defs: #{names * ', '}" unless opts[:silent] || names.empty?
|
275
|
-
names
|
276
|
-
end
|
277
|
-
|
278
|
-
def convert_to_scss(file)
|
279
|
-
# mixins may also be defined in the file. get mixin names before doing any processing
|
280
|
-
mixin_names = (@shared_mixins + get_mixin_names(file)).uniq
|
281
|
-
file = replace_vars(file)
|
282
|
-
file = replace_file_imports(file)
|
283
|
-
file = replace_mixin_definitions file
|
284
|
-
file = replace_mixins file, mixin_names
|
285
|
-
# replace_less_extend does not seem to do anything. @glebm
|
286
|
-
file = replace_less_extend(file)
|
287
|
-
file = replace_spin(file)
|
288
|
-
file = replace_image_urls(file)
|
289
|
-
file = replace_image_paths(file)
|
290
|
-
file = replace_escaping(file)
|
291
|
-
file = convert_less_ampersand(file)
|
292
|
-
file = deinterpolate_vararg_mixins(file)
|
293
|
-
file = replace_calculation_semantics(file)
|
294
|
-
file
|
295
|
-
end
|
296
|
-
|
297
|
-
# margin: a -b
|
298
|
-
# LESS: sets 2 values
|
299
|
-
# SASS: sets 1 value (a-b)
|
300
|
-
# This wraps a and -b so they evaluates to 2 values in SASS
|
301
|
-
def replace_calculation_semantics(file)
|
302
|
-
# split_prop_val.call('(@navbar-padding-vertical / 2) -@navbar-padding-horizontal')
|
303
|
-
# #=> ["(navbar-padding-vertical / 2)", "-navbar-padding-horizontal"]
|
304
|
-
split_prop_val = proc { |val|
|
305
|
-
s = CharStringScanner.new(val)
|
306
|
-
r = []
|
307
|
-
buff = ''
|
308
|
-
d = 0
|
309
|
-
prop_char = %r([\$\w\-/\*\+%!])
|
310
|
-
while (token = s.scan_next(/([\)\(]|\s+|#{prop_char}+)/))
|
311
|
-
buff << token
|
312
|
-
case token
|
313
|
-
when '('
|
314
|
-
d += 1
|
315
|
-
when ')'
|
316
|
-
d -= 1
|
317
|
-
if d == 0
|
318
|
-
r << buff
|
319
|
-
buff = ''
|
320
|
-
end
|
321
|
-
when /\s/
|
322
|
-
if d == 0 && !buff.strip.empty?
|
323
|
-
r << buff
|
324
|
-
buff = ''
|
325
|
-
end
|
326
|
-
end
|
327
|
-
end
|
328
|
-
r << buff unless buff.empty?
|
329
|
-
r.map(&:strip)
|
330
|
-
}
|
331
|
-
|
332
|
-
replace_rules file do |rule|
|
333
|
-
replace_properties rule do |props|
|
334
|
-
props.gsub /(?<!\w)([\w-]+):(.*?);/ do |m|
|
335
|
-
prop, vals = $1, split_prop_val.call($2)
|
336
|
-
next m unless vals.length >= 2 && vals.any? { |v| v =~ /^[\+\-]\$/ }
|
337
|
-
transformed = vals.map { |v| v.strip =~ %r(^\(.*\)$) ? v : "(#{v})" }
|
338
|
-
log_transform "property #{prop}: #{transformed * ' '}"
|
339
|
-
"#{prop}: #{transformed * ' '};"
|
340
|
-
end
|
341
|
-
end
|
342
|
-
end
|
343
|
-
end
|
344
|
-
|
345
|
-
def save_file(path, content, mode='w')
|
346
|
-
File.open(path, mode) { |file| file.write(content) }
|
347
|
-
end
|
348
|
-
|
349
|
-
# @import "file.less" to "#{target_path}file;"
|
350
|
-
def replace_file_imports(less, target_path = 'bootstrap/')
|
351
|
-
less.gsub %r([@\$]import ["|']([\w-]+).less["|'];),
|
352
|
-
%Q(@import "#{target_path}\\1";)
|
353
|
-
end
|
354
|
-
|
355
|
-
def replace_all(file, regex, replacement = nil, &block)
|
356
|
-
log_transform regex, replacement
|
357
|
-
new_file = file.gsub(regex, replacement, &block)
|
358
|
-
raise "replace_all #{regex}, #{replacement} NO MATCH" if file == new_file
|
359
|
-
new_file
|
360
|
-
end
|
361
|
-
|
362
|
-
# @mixin a() { tr& { color:white } }
|
363
|
-
# to:
|
364
|
-
# @mixin a($parent) { tr#{$parent} { color: white } }
|
365
|
-
def parameterize_mixin_parent_selector(file, rule_sel)
|
366
|
-
log_transform rule_sel
|
367
|
-
param = '$parent'
|
368
|
-
replace_rules(file, '^[ \t]*@mixin\s*' + rule_sel) do |mxn_css|
|
369
|
-
mxn_css.sub! /(?=@mixin)/, "// [converter] $parent hack\n"
|
370
|
-
# insert param into mixin def
|
371
|
-
mxn_css.sub!(/(@mixin [\w-]+)\(([\$\w\-,\s]*)\)/) { "#{$1}(#{param}#{', ' if $2 && !$2.empty?}#{$2})" }
|
372
|
-
# wrap properties in #{$parent} { ... }
|
373
|
-
replace_properties(mxn_css) { |props| " \#{#{param}} { #{props.strip} }\n " }
|
374
|
-
# change nested& rules to nested#{$parent}
|
375
|
-
replace_rules(mxn_css, /.*[^\s ]&/) { |rule| replace_in_selector rule, /&/, "\#{#{param}}" }
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
# extracts rule immediately after it's parent, and adjust the selector
|
380
|
-
# .x { textarea& { ... }}
|
381
|
-
# to:
|
382
|
-
# .x { ... }
|
383
|
-
# textarea.x { ... }
|
384
|
-
def extract_nested_rule(file, selector, new_selector = nil)
|
385
|
-
matches = []
|
386
|
-
# first find the rules, and remove them
|
387
|
-
file = replace_rules(file, "\s*#{selector}", comments: true) { |rule, pos, css|
|
388
|
-
matches << [rule, pos]
|
389
|
-
new_selector ||= "#{get_selector(rule).sub(/&$/, '')}#{selector_for_pos(css, pos.begin)}"
|
390
|
-
indent "// [converter] extracted #{get_selector(rule)} to #{new_selector}", indent_width(rule)
|
391
|
-
}
|
392
|
-
log_transform selector, new_selector
|
393
|
-
# replace rule selector with new_selector
|
394
|
-
matches.each do |m|
|
395
|
-
m[0].sub! /(#{COMMENT_RE}*)^(\s*).*?(\s*){/m, "\\1\\2#{new_selector}\\3{"
|
396
|
-
end
|
397
|
-
replace_substrings_at file,
|
398
|
-
matches.map { |_, pos| close_brace_pos(file, pos.begin, 1) + 1 },
|
399
|
-
matches.map { |rule, _| "\n\n" + unindent(rule) }
|
400
|
-
end
|
401
|
-
|
402
|
-
# .visible-sm { @include responsive-visibility() }
|
403
|
-
# to:
|
404
|
-
# @include responsive-visibility('.visible-sm')
|
405
|
-
def apply_mixin_parent_selector(file, rule_sel)
|
406
|
-
log_transform rule_sel
|
407
|
-
replace_rules file, '\s*' + rule_sel, comments: false do |rule, rule_pos, css|
|
408
|
-
body = unwrap_rule_block(rule.dup).strip
|
409
|
-
next rule unless body =~ /^@include \w+/m || body =~ /^@media/ && body =~ /\{\s*@include/
|
410
|
-
rule =~ /(#{COMMENT_RE}*)(#{SELECTOR_RE})\{/
|
411
|
-
cmt, sel = $1, $2.strip
|
412
|
-
# take one up selector chain if this is an &. selector
|
413
|
-
if sel.start_with?('&')
|
414
|
-
parent_sel = selector_for_pos(css, rule_pos.begin)
|
415
|
-
sel = parent_sel + sel[1..-1]
|
416
|
-
end
|
417
|
-
# unwrap, and replace @include
|
418
|
-
unindent unwrap_rule_block(rule).gsub(/(@include [\w-]+)\(([\$\w\-,\s]*)\)/) {
|
419
|
-
"#{cmt}#{$1}('#{sel}'#{', ' if $2 && !$2.empty?}#{$2})"
|
420
|
-
}
|
421
|
-
end
|
422
|
-
end
|
423
|
-
|
424
|
-
# #gradient > { @mixin horizontal ... }
|
425
|
-
# to:
|
426
|
-
# @mixin gradient-horizontal
|
427
|
-
def flatten_mixins(file, container, prefix)
|
428
|
-
log_transform container, prefix
|
429
|
-
replace_rules file, Regexp.escape(container) do |mixins_css|
|
430
|
-
unindent unwrap_rule_block(mixins_css).gsub(/@mixin\s*([\w-]+)/, "@mixin #{prefix}-\\1")
|
431
|
-
end
|
432
|
-
end
|
433
|
-
|
434
|
-
# Replaces the following:
|
435
|
-
# .mixin() -> @include mixin()
|
436
|
-
# #scope > .mixin() -> @include scope-mixin()
|
437
|
-
def replace_mixins(less, mixin_names)
|
438
|
-
mixin_pattern = /(\s+)(([#|\.][\w-]+\s*>\s*)*)\.([\w-]+\(.*\))(?!\s\{)/
|
439
|
-
|
440
|
-
less.gsub(mixin_pattern) do |match|
|
441
|
-
matches = match.scan(mixin_pattern).flatten
|
442
|
-
scope = matches[1] || ''
|
443
|
-
if scope != ''
|
444
|
-
scope = scope.scan(/[\w-]+/).join('-') + '-'
|
445
|
-
end
|
446
|
-
mixin_name = match.scan(/\.([\w-]+)\(.*\)\s?\{?/).first
|
447
|
-
if mixin_name && mixin_names.include?("#{scope}#{mixin_name.first}")
|
448
|
-
"#{matches.first}@include #{scope}#{matches.last}".gsub(/; \$/, ", $").sub(/;\)$/, ')')
|
449
|
-
else
|
450
|
-
"#{matches.first}@extend .#{scope}#{matches.last.gsub(/\(\)/, '')}"
|
451
|
-
end
|
452
|
-
end
|
453
|
-
end
|
454
|
-
|
455
|
-
# change Microsoft filters to SASS calling convention
|
456
|
-
def replace_ms_filters(file)
|
457
|
-
log_transform
|
458
|
-
file.gsub(
|
459
|
-
/filter: e\(%\("progid:DXImageTransform.Microsoft.gradient\(startColorstr='%d', endColorstr='%d', GradientType=(\d)\)",argb\(([\-$\w]+)\),argb\(([\-$\w]+)\)\)\);/,
|
460
|
-
%Q(filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='\#{ie-hex-str(\\2)}', endColorstr='\#{ie-hex-str(\\3)}', GradientType=\\1);)
|
461
|
-
)
|
462
|
-
end
|
463
|
-
|
464
|
-
# unwraps topmost rule block
|
465
|
-
# #sel { a: b; }
|
466
|
-
# to:
|
467
|
-
# a: b;
|
468
|
-
def unwrap_rule_block(css)
|
469
|
-
css[(css =~ RULE_OPEN_BRACE_RE) + 1..-1].sub(/\n?}\s*\z/m, '')
|
470
|
-
end
|
471
|
-
|
472
|
-
def replace_mixin_definitions(less)
|
473
|
-
less.gsub(/^(\s*)\.([\w-]+\(.*\))(\s*\{)/) { |match|
|
474
|
-
"#{$1}@mixin #{$2.tr(';', ',')}#{$3}".sub(/,\)/, ')')
|
475
|
-
}
|
476
|
-
end
|
477
|
-
|
478
|
-
def replace_vars(less)
|
479
|
-
less = less.dup
|
480
|
-
# skip header comment
|
481
|
-
less =~ %r(\A/\*(.*?)\*/)m
|
482
|
-
from = $~ ? $~.to_s.length : 0
|
483
|
-
less[from..-1] = less[from..-1].
|
484
|
-
gsub(/(?!@mixin|@media|@page|@keyframes|@font-face|@-\w)@/, '$').
|
485
|
-
# variables that would be ignored by gsub above: e.g. @page-header-border-color
|
486
|
-
gsub(/@(page[\w-]+)/, '$\1')
|
487
|
-
less
|
488
|
-
end
|
489
|
-
|
490
|
-
# #gradient > .horizontal()
|
491
|
-
# to:
|
492
|
-
# @include .horizontal-gradient()
|
493
|
-
def replace_less_extend(less)
|
494
|
-
less.gsub(/\#(\w+) \> \.([\w-]*)(\(.*\));?/, '@include \1-\2\3;')
|
495
|
-
end
|
496
|
-
|
497
|
-
def replace_spin(less)
|
498
|
-
less.gsub(/(?![\-$@.])spin(?!-)/, 'adjust-hue')
|
499
|
-
end
|
500
|
-
|
501
|
-
def replace_image_urls(less)
|
502
|
-
less.gsub(/background-image: url\("?(.*?)"?\);/) {|s| "background-image: image-url(\"#{$1}\");" }
|
503
|
-
end
|
504
|
-
|
505
|
-
def replace_image_paths(less)
|
506
|
-
less.gsub('../img/', '')
|
507
|
-
end
|
508
|
-
|
509
|
-
def replace_escaping(less)
|
510
|
-
less = less.gsub(/\~"([^"]+)"/, '#{\1}') # Get rid of ~"" escape
|
511
|
-
less.gsub!(/\$\{([^}]+)\}/, '$\1') # Get rid of @{} escape
|
512
|
-
less.gsub!(/"([^"\n]*)(\$[\w\-]+)([^"\n]*)"/, '"\1#{\2}\3"') # interpolate variable in string, e.g. url("$file-1x") => url("#{$file-1x}")
|
513
|
-
less.gsub(/(\W)e\(%\("?([^"]*)"?\)\)/, '\1\2') # Get rid of e(%("")) escape
|
514
|
-
end
|
515
|
-
|
516
|
-
def insert_default_vars(scss)
|
517
|
-
log_transform
|
518
|
-
scss.gsub(/^(\$.+);/, '\1 !default;')
|
519
|
-
end
|
520
|
-
|
521
|
-
# Converts &-
|
522
|
-
def convert_less_ampersand(less)
|
523
|
-
regx = /^\.badge\s*\{[\s\/\w\(\)]+(&{1}-{1})\w.*?^}$/m
|
524
|
-
|
525
|
-
tmp = ''
|
526
|
-
less.scan(/^(\s*&)(-[\w\[\]]+\s*{.+})$/) do |ampersand, css|
|
527
|
-
tmp << ".badge#{css}\n"
|
528
|
-
end
|
529
|
-
|
530
|
-
less.gsub(regx, tmp)
|
531
|
-
end
|
532
|
-
|
533
|
-
# unindent by n spaces
|
534
|
-
def unindent(txt, n = 2)
|
535
|
-
txt.gsub /^[ ]{#{n}}/, ''
|
536
|
-
end
|
537
|
-
|
538
|
-
# indent by n spaces
|
539
|
-
def indent(txt, n = 2)
|
540
|
-
"#{' ' * n}#{txt}"
|
541
|
-
end
|
542
|
-
|
543
|
-
# get indent length from the first line of txt
|
544
|
-
def indent_width(txt)
|
545
|
-
txt.match(/\A\s*/).to_s.length
|
546
|
-
end
|
547
|
-
|
548
|
-
# @mixin transition($transition) {
|
549
|
-
# to:
|
550
|
-
# @mixin transition($transition...) {
|
551
|
-
def varargify_mixin_definitions(scss, *mixins)
|
552
|
-
log_transform *mixins
|
553
|
-
scss = scss.dup
|
554
|
-
mixins.each do |mixin|
|
555
|
-
scss.gsub! /(@mixin\s*#{Regexp.quote(mixin)})\((#{SCSS_MIXIN_DEF_ARGS_RE})\)/, '\1(\2...)'
|
556
|
-
end
|
557
|
-
scss
|
558
|
-
end
|
559
|
-
|
560
|
-
# @include transition(#{border-color ease-in-out .15s, box-shadow ease-in-out .15s})
|
561
|
-
# to
|
562
|
-
# @include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s)
|
563
|
-
def deinterpolate_vararg_mixins(scss)
|
564
|
-
scss = scss.dup
|
565
|
-
VARARG_MIXINS.each do |mixin|
|
566
|
-
if scss.gsub! /(@include\s*#{Regexp.quote(mixin)})\(\s*\#\{([^}]+)\}\s*\)/, '\1(\2)'
|
567
|
-
log_transform mixin
|
568
|
-
end
|
569
|
-
end
|
570
|
-
scss
|
571
|
-
end
|
572
|
-
|
573
|
-
# get full selector for rule_block
|
574
|
-
def get_selector(rule_block)
|
575
|
-
/^\s*(#{SELECTOR_RE}?)\s*\{/.match(rule_block) && $1 && $1.strip
|
576
|
-
end
|
577
|
-
|
578
|
-
# replace CSS rule blocks matching rule_prefix with yield(rule_block, rule_pos)
|
579
|
-
# will also include immediately preceding comments in rule_block
|
580
|
-
#
|
581
|
-
# option :comments -- include immediately preceding comments in rule_block
|
582
|
-
#
|
583
|
-
# replace_rules(".a{ \n .b{} }", '.b') { |rule, pos| ">#{rule}<" } #=> ".a{ \n >.b{}< }"
|
584
|
-
def replace_rules(less, rule_prefix = SELECTOR_RE, options = {}, &block)
|
585
|
-
options = {comments: true}.merge(options || {})
|
586
|
-
less = less.dup
|
587
|
-
s = CharStringScanner.new(less)
|
588
|
-
rule_re = /(?:#{rule_prefix}[^{]*#{RULE_OPEN_BRACE_RE})/
|
589
|
-
if options[:comments]
|
590
|
-
rule_start_re = /(?:#{COMMENT_RE}*)^#{rule_re}/
|
591
|
-
else
|
592
|
-
rule_start_re = /^#{rule_re}/
|
593
|
-
end
|
594
|
-
|
595
|
-
positions = []
|
596
|
-
while (rule_start = s.scan_next(rule_start_re))
|
597
|
-
pos = s.pos
|
598
|
-
positions << (pos - rule_start.length..close_brace_pos(less, pos - 1))
|
599
|
-
end
|
600
|
-
replace_substrings_at(less, positions, &block)
|
601
|
-
less
|
602
|
-
end
|
603
|
-
|
604
|
-
# Get a all top-level selectors (with {)
|
605
|
-
def get_css_selectors(css, opts = {})
|
606
|
-
s = CharStringScanner.new(css)
|
607
|
-
selectors = []
|
608
|
-
while s.scan_next(RULE_OPEN_BRACE_RE)
|
609
|
-
brace_pos = s.pos
|
610
|
-
def_pos = css_def_pos(css, brace_pos+1, -1)
|
611
|
-
sel = css[def_pos.begin..brace_pos - 1].dup
|
612
|
-
sel.strip! if opts[:strip]
|
613
|
-
selectors << sel
|
614
|
-
sel.dup.strip
|
615
|
-
s.pos = close_brace_pos(css, brace_pos, 1) + 1
|
616
|
-
end
|
617
|
-
selectors
|
618
|
-
end
|
619
|
-
|
620
|
-
# replace in the top-level selector
|
621
|
-
# replace_in_selector('a {a: {a: a} } a {}', /a/, 'b') => 'b {a: {a: a} } b {}'
|
622
|
-
def replace_in_selector(css, pattern, sub)
|
623
|
-
# scan for selector positions in css
|
624
|
-
s = CharStringScanner.new(css)
|
625
|
-
prev_pos = 0
|
626
|
-
sel_pos = []
|
627
|
-
while (brace = s.scan_next(RULE_OPEN_BRACE_RE))
|
628
|
-
pos = s.pos
|
629
|
-
sel_pos << (prev_pos .. pos - 1)
|
630
|
-
s.pos = close_brace_pos(css, s.pos - 1) + 1
|
631
|
-
prev_pos = pos
|
632
|
-
end
|
633
|
-
replace_substrings_at(css, sel_pos) { |s| s.gsub(pattern, sub) }
|
634
|
-
end
|
635
|
-
|
636
|
-
|
637
|
-
sel_chars = '\[\]$\w\-{}#,.:&>@'
|
638
|
-
SELECTOR_RE = /[#{sel_chars}]+[#{sel_chars}\s]*/
|
639
|
-
COMMENT_RE = %r((?:^[ \t]*//[^\n]*\n))
|
640
|
-
RULE_OPEN_BRACE_RE = /(?<![@#\$])\{/
|
641
|
-
RULE_OPEN_BRACE_RE_REVERSE = /\{(?![@#\$])/
|
642
|
-
RULE_CLOSE_BRACE_RE = /(?<!\w)\}(?![.'"])/
|
643
|
-
RULE_CLOSE_BRACE_RE_REVERSE = /(?<![.'"])\}(?!\w)/
|
644
|
-
BRACE_RE = /#{RULE_OPEN_BRACE_RE}|#{RULE_CLOSE_BRACE_RE}/m
|
645
|
-
BRACE_RE_REVERSE = /#{RULE_OPEN_BRACE_RE_REVERSE}|#{RULE_CLOSE_BRACE_RE_REVERSE}/m
|
646
|
-
SCSS_MIXIN_DEF_ARGS_RE = /[\w\-,\s$:#%]*/
|
647
|
-
LESS_MIXIN_DEF_ARGS_RE = /[\w\-,;\s@:#%]*/
|
648
|
-
|
649
|
-
# replace first level properties in the css with yields
|
650
|
-
# replace_properties("a { color: white }") { |props| props.gsub 'white', 'red' }
|
651
|
-
def replace_properties(css, &block)
|
652
|
-
s = CharStringScanner.new(css)
|
653
|
-
s.skip_until /#{RULE_OPEN_BRACE_RE}\n?/
|
654
|
-
prev_pos = s.pos
|
655
|
-
depth = 0
|
656
|
-
pos = []
|
657
|
-
while (b = s.scan_next(/#{SELECTOR_RE}#{RULE_OPEN_BRACE_RE}|#{RULE_CLOSE_BRACE_RE}/m))
|
658
|
-
s_pos = s.pos
|
659
|
-
depth += (b == '}' ? -1 : +1)
|
660
|
-
if depth == 1
|
661
|
-
if b == '}'
|
662
|
-
prev_pos = s_pos
|
663
|
-
else
|
664
|
-
pos << (prev_pos .. s_pos - b.length - 1)
|
665
|
-
end
|
666
|
-
end
|
667
|
-
end
|
668
|
-
replace_substrings_at css, pos, &block
|
669
|
-
end
|
670
|
-
|
671
|
-
|
672
|
-
# immediate selector of css at pos
|
673
|
-
def selector_for_pos(css, pos, depth = -1)
|
674
|
-
css[css_def_pos(css, pos, depth)].dup.strip
|
675
|
-
end
|
676
|
-
|
677
|
-
# get the pos of css def at pos (search backwards)
|
678
|
-
def css_def_pos(css, pos, depth = -1)
|
679
|
-
to = open_brace_pos(css, pos, depth)
|
680
|
-
prev_def = to - (css[0..to].reverse.index('}') || to) + 1
|
681
|
-
from = prev_def + 1 + (css[prev_def + 1..-1] =~ %r(^\s*[^\s/]))
|
682
|
-
(from..to - 1)
|
683
|
-
end
|
684
|
-
|
685
|
-
# next matching brace for brace at from
|
686
|
-
def close_brace_pos(css, from, depth = 0)
|
687
|
-
s = CharStringScanner.new(css[from..-1])
|
688
|
-
while (b = s.scan_next(BRACE_RE))
|
689
|
-
depth += (b == '}' ? -1 : +1)
|
690
|
-
break if depth.zero?
|
691
|
-
end
|
692
|
-
raise "match not found for {" unless depth.zero?
|
693
|
-
from + s.pos - 1
|
694
|
-
end
|
695
|
-
|
696
|
-
# opening brace position from +from+ (search backwards)
|
697
|
-
def open_brace_pos(css, from, depth = 0)
|
698
|
-
s = CharStringScanner.new(css[0..from].reverse)
|
699
|
-
while (b = s.scan_next(BRACE_RE_REVERSE))
|
700
|
-
depth += (b == '{' ? +1 : -1)
|
701
|
-
break if depth.zero?
|
702
|
-
end
|
703
|
-
raise "matching { brace not found" unless depth.zero?
|
704
|
-
from - s.pos + 1
|
705
|
-
end
|
706
|
-
|
707
|
-
# insert substitutions into text at positions (Range or Fixnum)
|
708
|
-
# substitutions can be passed as array or as yields from the &block called with |substring, position, text|
|
709
|
-
# position is a range (begin..end)
|
710
|
-
def replace_substrings_at(text, positions, replacements = nil, &block)
|
711
|
-
offset = 0
|
712
|
-
positions.each_with_index do |p, i|
|
713
|
-
p = (p...p) if p.is_a?(Fixnum)
|
714
|
-
from = p.begin + offset
|
715
|
-
to = p.end + offset
|
716
|
-
p = p.exclude_end? ? (from...to) : (from..to)
|
717
|
-
# block returns the substitution, e.g.: { |text, pos| text[pos].upcase }
|
718
|
-
r = replacements ? replacements[i] : block.call(text[p], p, text)
|
719
|
-
text[p] = r
|
720
|
-
# add the change in length to offset
|
721
|
-
offset += r.size - (p.end - p.begin + (p.exclude_end? ? 0 : 1))
|
722
|
-
end
|
723
|
-
text
|
724
|
-
end
|
725
|
-
|
726
|
-
def get_json(url)
|
727
|
-
JSON.parse get_file(url)
|
728
|
-
end
|
729
|
-
|
730
|
-
# regular string scanner works with bytes
|
731
|
-
# this one works with chars and provides #scan_next
|
732
|
-
class CharStringScanner
|
733
|
-
extend Forwardable
|
734
|
-
|
735
|
-
def initialize(*args)
|
736
|
-
@s = StringScanner.new(*args)
|
737
|
-
end
|
738
|
-
|
739
|
-
def_delegators :@s, :scan_until, :skip_until, :string
|
740
|
-
|
741
|
-
# advance scanner to pos after the next match of pattern and return the match
|
742
|
-
def scan_next(pattern)
|
743
|
-
return unless @s.scan_until(pattern)
|
744
|
-
@s.matched
|
745
|
-
end
|
746
|
-
|
747
|
-
def pos
|
748
|
-
byte_to_str_pos @s.pos
|
749
|
-
end
|
750
|
-
|
751
|
-
def pos=(i)
|
752
|
-
@s.pos = str_to_byte_pos i
|
753
|
-
i
|
754
|
-
end
|
755
|
-
|
756
|
-
private
|
757
|
-
|
758
|
-
def byte_to_str_pos(pos)
|
759
|
-
@s.string.byteslice(0, pos).length
|
760
|
-
end
|
761
|
-
|
762
|
-
def str_to_byte_pos(pos)
|
763
|
-
@s.string.slice(0, pos).bytesize
|
764
|
-
end
|
765
|
-
end
|
766
|
-
|
767
|
-
class Logger
|
768
|
-
include Term::ANSIColor
|
769
|
-
|
770
|
-
def initialize(env)
|
771
|
-
@env = env
|
772
|
-
puts bold "Convert Bootstrap LESS to SASS"
|
773
|
-
puts " repo : #{env[:repo]}"
|
774
|
-
puts " branch : #{env[:branch]} #{dark "#{env[:repo]}/tree/#{env[:branch_sha]}"}"
|
775
|
-
puts " save to: #{@env[:save_at].to_json}"
|
776
|
-
puts " twbs cache: #{@env[:cache_path]}"
|
777
|
-
puts dark "-" * 60
|
778
|
-
end
|
779
|
-
|
780
|
-
def log_status(status)
|
781
|
-
puts bold status
|
782
|
-
end
|
783
|
-
|
784
|
-
def log_file_info(s)
|
785
|
-
puts " #{magenta s}"
|
786
|
-
end
|
787
|
-
|
788
|
-
def log_transform(*args)
|
789
|
-
puts "#{cyan " #{caller[1][/`.*'/][1..-2].sub(/^block in /, '')}"}#{cyan ": #{args * ', '}" unless args.empty?}"
|
790
|
-
end
|
791
|
-
|
792
|
-
def log_processing(name)
|
793
|
-
puts yellow " #{File.basename(name)}"
|
794
|
-
end
|
795
|
-
|
796
|
-
def log_processed(name)
|
797
|
-
puts green " #{name}"
|
798
|
-
end
|
799
|
-
|
800
|
-
def log_http_get_file(url, cached = false)
|
801
|
-
s = " #{'CACHED ' if cached}GET #{url}..."
|
802
|
-
if cached
|
803
|
-
puts dark green s
|
804
|
-
else
|
805
|
-
puts dark cyan s
|
806
|
-
end
|
807
|
-
end
|
808
|
-
|
809
|
-
def log_http_get_files(files, from, cached = false)
|
810
|
-
s = " #{'CACHED ' if cached}GET #{files.length} files from #{from} #{files * ' '}..."
|
811
|
-
if cached
|
812
|
-
puts dark green s
|
813
|
-
else
|
814
|
-
puts dark cyan s
|
815
|
-
end
|
816
|
-
end
|
817
|
-
|
818
|
-
def puts(*args)
|
819
|
-
STDOUT.puts *args unless @silence
|
820
|
-
end
|
821
|
-
|
822
|
-
def silence_log
|
823
|
-
@silence = true
|
824
|
-
yield
|
825
|
-
ensure
|
826
|
-
@silence = false
|
827
|
-
end
|
828
|
-
end
|
829
72
|
end
|