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)