tasks/converter/less_conversion.rb in bootstrap-sass-3.1.0.1 vs tasks/converter/less_conversion.rb in bootstrap-sass-3.1.0.2
- old
+ new
@@ -1,13 +1,11 @@
require_relative 'char_string_scanner'
-# This module transforms LESS into SCSS.
-# It is implemented via lots of string manipulation: scanning back and forwards for regexps and doing substitions.
-# Since it does not parse the LESS into an AST, bits of it may assume LESS to be formatted a certain way, and only limited,
-# static analysis can be performed. This approach has so far been mostly enough to automatically convert most all of twbs/bootstrap.
-# There is some bootstrap-specific to make up for lack of certain features in Sass 3.2 (recursion, mixin namespacing)
-# and vice versa in LESS (vararg mixins).
+# This is the script used to automatically convert all of twbs/bootstrap LESS to Sass.
+#
+# Most differences are fixed by regexps and other forms of string substitution.
+# There are Bootstrap-specific workarounds for the lack of parent selectors, recursion, mixin namespaces, extend within @media, etc in Sass 3.2.
class Converter
module LessConversion
# Some regexps for matching bits of SCSS:
SELECTOR_CHAR = '\[\]$\w\-{}#,.:&>@'
# 1 selector (the part before the {)
@@ -52,10 +50,11 @@
end
def process_stylesheet_assets
log_status 'Processing stylesheets...'
files = read_files('less', bootstrap_less_files)
+ save_to = @save_to[:scss]
log_status ' Converting LESS files to Scss:'
files.each do |name, file|
log_processing name
# apply common conversions
@@ -80,13 +79,11 @@
# see https://github.com/twbs/bootstrap-sass/issues/518
file = replace_all file, '$ratio, $ratio-y', '$scale-args'
file = convert_grid_mixins file
when 'responsive-utilities.less'
- file = apply_mixin_parent_selector(file, '&\.(visible|hidden)')
- file = apply_mixin_parent_selector(file, '(?<!&)\.(visible|hidden)')
- file = replace_rules(file, ' @media') { |r| unindent(r, 2) }
+ file = apply_mixin_parent_selector file, '\.(?:visible|hidden)'
when 'variables.less'
file = insert_default_vars(file)
file = unindent <<-SCSS + file, 14
// a flag to toggle asset pipeline / compass integration
// defaults to true if twbs-font-path function is present (no function => twbs-font-path('') parsed as string == right side)
@@ -120,15 +117,18 @@
# .bg-primary will not get patched automatically as it includes an additional rule. fudge for now
file = replace_all(file, " @include bg-variant($brand-primary);\n}", "}\n@include bg-variant('.bg-primary', $brand-primary);")
end
name = name.sub(/\.less$/, '.scss')
- save_to = @save_to[:scss]
path = "#{save_to}/#{'_' unless name == 'bootstrap.scss'}#{name}"
save_file(path, file)
log_processed File.basename(path)
end
+
+ # generate imports valid relative to both load path and file directory
+ save_file File.expand_path("#{save_to}/../bootstrap.scss"),
+ File.read("#{save_to}/bootstrap.scss").gsub(/ "/, ' "bootstrap/')
end
def bootstrap_less_files
@bootstrap_less_files ||= get_paths_by_type('less', /\.less$/)
end
@@ -296,11 +296,15 @@
replace_rules(file, '^\s*@mixin\s*' + rule_sel) do |mxn_css|
mxn_css.sub! /(?=@mixin)/, "// [converter] $parent hack\n"
# insert param into mixin def
mxn_css.sub!(/(@mixin [\w-]+)\(([\$\w\-,\s]*)\)/) { "#{$1}(#{param}#{', ' if $2 && !$2.empty?}#{$2})" }
# wrap properties in #{$parent} { ... }
- replace_properties(mxn_css) { |props| props.strip.empty? ? props : " \#{#{param}} { #{props.strip} }\n " }
+ replace_properties(mxn_css) { |props|
+ next props if props.strip.empty?
+ spacer = ' ' * indent_width(props)
+ "#{spacer}\#{#{param}} {\n#{indent(props.sub(/\s+\z/, ''), 2)}\n#{spacer}}"
+ }
# change nested& rules to nested#{$parent}
replace_rules(mxn_css, /.*&[ ,:]/) { |rule| replace_in_selector rule, /&/, "\#{#{param}}" }
end
end
@@ -334,20 +338,21 @@
def apply_mixin_parent_selector(file, rule_sel)
log_transform rule_sel
replace_rules file, '\s*' + rule_sel, comments: false do |rule, rule_pos, css|
body = unwrap_rule_block(rule.dup).strip
next rule unless body =~ /^@include \w+/m || body =~ /^@media/ && body =~ /\{\s*@include/
- rule =~ /(#{COMMENT_RE}*)([#{SELECTOR_CHAR}]+?)\s*#{RULE_OPEN_BRACE_RE}/
+ rule =~ /(#{COMMENT_RE}*)([#{SELECTOR_CHAR}\s*]+?)#{RULE_OPEN_BRACE_RE}/
cmt, sel = $1, $2.strip
# take one up selector chain if this is an &. selector
if sel.start_with?('&')
parent_sel = selector_for_pos(css, rule_pos.begin)
sel = parent_sel + sel[1..-1]
end
# unwrap, and replace @include
unindent unwrap_rule_block(rule).gsub(/(@include [\w-]+)\(([\$\w\-,\s]*)\)/) {
- "#{cmt}#{$1}('#{sel}'#{', ' if $2 && !$2.empty?}#{$2})"
+ args = $2
+ "#{cmt}#{$1}('#{sel.gsub(/\s+/, ' ')}'#{', ' if args && !args.empty?}#{args})"
}
end
end
# #gradient > { @mixin horizontal ... }
@@ -568,24 +573,13 @@
# replace first level properties in the css with yields
# replace_properties("a { color: white }") { |props| props.gsub 'white', 'red' }
def replace_properties(css, &block)
s = CharStringScanner.new(css)
s.skip_until /#{RULE_OPEN_BRACE_RE}\n?/
- prev_pos = s.pos
- depth = 0
- pos = []
- while (b = s.scan_next(/#{SELECTOR_RE}#{RULE_OPEN_BRACE_RE}|#{RULE_CLOSE_BRACE_RE}/m))
- s_pos = s.pos
- depth += (b == '}' ? -1 : +1)
- if depth == 1
- if b == '}'
- prev_pos = s_pos
- else
- pos << (prev_pos .. s_pos - b.length - 1)
- end
- end
- end
- replace_substrings_at css, pos, &block
+ from = s.pos
+ m = s.scan_next(/\s*#{SELECTOR_RE}#{RULE_OPEN_BRACE_RE}/) || s.scan_next(/\s*#{RULE_CLOSE_BRACE_RE}/)
+ to = s.pos - m.length - 1
+ replace_substrings_at css, [(from .. to)], &block
end
# immediate selector of css at pos
def selector_for_pos(css, pos, depth = -1)