bootstrap-sass 2.3.2.2 → 3.0.0.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.

Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.travis.yml +8 -2
  4. data/CHANGELOG.md +4 -0
  5. data/CONTRIBUTING.md +12 -4
  6. data/README.md +87 -74
  7. data/Rakefile +31 -3
  8. data/bootstrap-sass.gemspec +14 -2
  9. data/lib/bootstrap-sass/engine.rb +1 -1
  10. data/lib/bootstrap-sass/version.rb +4 -0
  11. data/tasks/converter.rb +829 -0
  12. data/templates/project/_variables.scss.erb +6 -0
  13. data/templates/project/manifest.rb +8 -13
  14. data/templates/project/styles.scss +1 -6
  15. data/test/compass_test.rb +8 -0
  16. data/test/compilation_test.rb +1 -1
  17. data/test/dummy/README.rdoc +3 -0
  18. data/test/dummy/Rakefile +6 -0
  19. data/test/dummy/app/assets/images/.keep +0 -0
  20. data/test/dummy/app/assets/javascripts/application.js +2 -0
  21. data/test/dummy/app/assets/stylesheets/application.css.sass +1 -0
  22. data/test/dummy/app/controllers/application_controller.rb +5 -0
  23. data/test/dummy/app/controllers/pages_controller.rb +4 -0
  24. data/test/dummy/app/helpers/application_helper.rb +2 -0
  25. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/test/dummy/app/views/pages/root.html.slim +11 -0
  27. data/test/dummy/bin/bundle +3 -0
  28. data/test/dummy/bin/rails +4 -0
  29. data/test/dummy/bin/rake +4 -0
  30. data/test/dummy/config.ru +4 -0
  31. data/test/dummy/config/application.rb +17 -0
  32. data/test/dummy/config/boot.rb +5 -0
  33. data/test/dummy/config/environment.rb +5 -0
  34. data/test/dummy/config/environments/development.rb +26 -0
  35. data/test/dummy/config/environments/production.rb +76 -0
  36. data/test/dummy/config/environments/test.rb +30 -0
  37. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  39. data/test/dummy/config/initializers/inflections.rb +16 -0
  40. data/test/dummy/config/initializers/mime_types.rb +5 -0
  41. data/test/dummy/config/initializers/secret_token.rb +18 -0
  42. data/test/dummy/config/initializers/session_store.rb +3 -0
  43. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  44. data/test/dummy/config/locales/en.yml +3 -0
  45. data/test/dummy/config/locales/es.yml +3 -0
  46. data/test/dummy/config/routes.rb +3 -0
  47. data/test/dummy/db/test.sqlite3 +0 -0
  48. data/test/dummy/lib/assets/.keep +0 -0
  49. data/test/dummy/log/.keep +0 -0
  50. data/test/dummy/log/development.log +0 -0
  51. data/test/dummy/public/404.html +58 -0
  52. data/test/dummy/public/422.html +58 -0
  53. data/test/dummy/public/500.html +57 -0
  54. data/test/dummy/public/favicon.ico +0 -0
  55. data/test/gemfiles/sass_3_2.gemfile +1 -1
  56. data/test/gemfiles/sass_3_3.gemfile +6 -0
  57. data/test/pages_test.rb +14 -0
  58. data/test/support/integration_test.rb +29 -0
  59. data/test/test_helper.rb +26 -1
  60. data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
  61. data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.svg +228 -0
  62. data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  63. data/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
  64. data/vendor/assets/javascripts/bootstrap.js +12 -13
  65. data/vendor/assets/javascripts/bootstrap/affix.js +126 -0
  66. data/vendor/assets/javascripts/bootstrap/alert.js +98 -0
  67. data/vendor/assets/javascripts/bootstrap/button.js +109 -0
  68. data/vendor/assets/javascripts/bootstrap/carousel.js +217 -0
  69. data/vendor/assets/javascripts/bootstrap/collapse.js +179 -0
  70. data/vendor/assets/javascripts/bootstrap/dropdown.js +154 -0
  71. data/vendor/assets/javascripts/bootstrap/modal.js +246 -0
  72. data/vendor/assets/javascripts/bootstrap/popover.js +117 -0
  73. data/vendor/assets/javascripts/bootstrap/scrollspy.js +158 -0
  74. data/vendor/assets/javascripts/bootstrap/tab.js +135 -0
  75. data/vendor/assets/javascripts/bootstrap/tooltip.js +386 -0
  76. data/vendor/assets/javascripts/bootstrap/transition.js +56 -0
  77. data/vendor/assets/stylesheets/bootstrap/_alerts.scss +46 -58
  78. data/vendor/assets/stylesheets/bootstrap/_badges.scss +51 -0
  79. data/vendor/assets/stylesheets/bootstrap/_breadcrumbs.scss +8 -9
  80. data/vendor/assets/stylesheets/bootstrap/_button-groups.scss +173 -154
  81. data/vendor/assets/stylesheets/bootstrap/_buttons.scss +97 -165
  82. data/vendor/assets/stylesheets/bootstrap/_carousel.scss +116 -65
  83. data/vendor/assets/stylesheets/bootstrap/_close.scss +11 -8
  84. data/vendor/assets/stylesheets/bootstrap/_code.scss +16 -21
  85. data/vendor/assets/stylesheets/bootstrap/_component-animations.scss +10 -3
  86. data/vendor/assets/stylesheets/bootstrap/_dropdowns.scss +94 -148
  87. data/vendor/assets/stylesheets/bootstrap/_forms.scss +222 -559
  88. data/vendor/assets/stylesheets/bootstrap/_glyphicons.scss +232 -0
  89. data/vendor/assets/stylesheets/bootstrap/_grid.scss +336 -11
  90. data/vendor/assets/stylesheets/bootstrap/_input-groups.scss +127 -0
  91. data/vendor/assets/stylesheets/bootstrap/_jumbotron.scss +40 -0
  92. data/vendor/assets/stylesheets/bootstrap/_labels.scss +58 -0
  93. data/vendor/assets/stylesheets/bootstrap/_list-group.scss +90 -0
  94. data/vendor/assets/stylesheets/bootstrap/_media.scss +8 -7
  95. data/vendor/assets/stylesheets/bootstrap/_mixins.scss +468 -434
  96. data/vendor/assets/stylesheets/bootstrap/_modals.scss +103 -52
  97. data/vendor/assets/stylesheets/bootstrap/_navbar.scss +511 -383
  98. data/vendor/assets/stylesheets/bootstrap/_navs.scss +169 -349
  99. data/vendor/assets/stylesheets/bootstrap/_normalize.scss +396 -0
  100. data/vendor/assets/stylesheets/bootstrap/_pager.scss +45 -33
  101. data/vendor/assets/stylesheets/bootstrap/_pagination.scss +65 -105
  102. data/vendor/assets/stylesheets/bootstrap/_panels.scss +148 -0
  103. data/vendor/assets/stylesheets/bootstrap/_popovers.scss +51 -51
  104. data/vendor/assets/stylesheets/bootstrap/_print.scss +100 -0
  105. data/vendor/assets/stylesheets/bootstrap/_progress-bars.scss +28 -55
  106. data/vendor/assets/stylesheets/bootstrap/_responsive-utilities.scss +180 -45
  107. data/vendor/assets/stylesheets/bootstrap/_scaffolding.scss +101 -24
  108. data/vendor/assets/stylesheets/bootstrap/_tables.scss +169 -168
  109. data/vendor/assets/stylesheets/bootstrap/_theme.scss +232 -0
  110. data/vendor/assets/stylesheets/bootstrap/_thumbnails.scss +11 -33
  111. data/vendor/assets/stylesheets/bootstrap/_tooltip.scss +45 -20
  112. data/vendor/assets/stylesheets/bootstrap/_type.scss +101 -110
  113. data/vendor/assets/stylesheets/bootstrap/_utilities.scss +19 -22
  114. data/vendor/assets/stylesheets/bootstrap/_variables.scss +498 -179
  115. data/vendor/assets/stylesheets/bootstrap/_wells.scss +7 -7
  116. data/vendor/assets/stylesheets/bootstrap/bootstrap.scss +29 -33
  117. metadata +201 -34
  118. data/asseturl.patch +0 -15
  119. data/templates/project/_variables.scss +0 -301
  120. data/update-bootstrap.sh +0 -25
  121. data/vendor/assets/images/glyphicons-halflings-white.png +0 -0
  122. data/vendor/assets/images/glyphicons-halflings.png +0 -0
  123. data/vendor/assets/javascripts/bootstrap-affix.js +0 -117
  124. data/vendor/assets/javascripts/bootstrap-alert.js +0 -99
  125. data/vendor/assets/javascripts/bootstrap-button.js +0 -105
  126. data/vendor/assets/javascripts/bootstrap-carousel.js +0 -207
  127. data/vendor/assets/javascripts/bootstrap-collapse.js +0 -167
  128. data/vendor/assets/javascripts/bootstrap-dropdown.js +0 -169
  129. data/vendor/assets/javascripts/bootstrap-modal.js +0 -247
  130. data/vendor/assets/javascripts/bootstrap-popover.js +0 -114
  131. data/vendor/assets/javascripts/bootstrap-scrollspy.js +0 -162
  132. data/vendor/assets/javascripts/bootstrap-tab.js +0 -144
  133. data/vendor/assets/javascripts/bootstrap-tooltip.js +0 -361
  134. data/vendor/assets/javascripts/bootstrap-transition.js +0 -60
  135. data/vendor/assets/javascripts/bootstrap-typeahead.js +0 -335
  136. data/vendor/assets/stylesheets/bootstrap-responsive.scss +0 -1
  137. data/vendor/assets/stylesheets/bootstrap/_accordion.scss +0 -34
  138. data/vendor/assets/stylesheets/bootstrap/_hero-unit.scss +0 -25
  139. data/vendor/assets/stylesheets/bootstrap/_labels-badges.scss +0 -83
  140. data/vendor/assets/stylesheets/bootstrap/_layouts.scss +0 -16
  141. data/vendor/assets/stylesheets/bootstrap/_reset.scss +0 -216
  142. data/vendor/assets/stylesheets/bootstrap/_responsive-1200px-min.scss +0 -28
  143. data/vendor/assets/stylesheets/bootstrap/_responsive-767px-max.scss +0 -193
  144. data/vendor/assets/stylesheets/bootstrap/_responsive-768px-979px.scss +0 -19
  145. data/vendor/assets/stylesheets/bootstrap/_responsive-navbar.scss +0 -189
  146. data/vendor/assets/stylesheets/bootstrap/_sprites.scss +0 -197
  147. data/vendor/assets/stylesheets/bootstrap/responsive.scss +0 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a23f672a81c16df2426e6a9e7a1cd43bf8fddac
4
- data.tar.gz: 85d50e27e13386722d5b08cd214857f9489c73bf
3
+ metadata.gz: aafebd3e6a2ed9f1ee3cdb4ab1a426484735e7af
4
+ data.tar.gz: 54c98b77cc87298118101f5f323cf9772511097b
5
5
  SHA512:
6
- metadata.gz: 1342264f9628e83adfca2c05909933ceea53d127af0b338eac73bb8e9d4be7aeaa8c5b01c13c6c152b3401e70e3ee1d8d51b60abc2c363e96e603aa1b78c555a
7
- data.tar.gz: 13e407364fedeaba9f45bce33c96a14a1a063d0b520adb38498e9ba2044c044350dd3ef9058eac1882a8142e7660979994ac730fd6a7d5530ce1456ec53b3219
6
+ metadata.gz: 72f10b7ae37034721741b38985095bbbf481d3d992beb077ac843815dae2a7d483a39d97770c87f074edf76b129482ae6bd5f5ffb20fec97d4f6d6c66f949d7e
7
+ data.tar.gz: 4a3cdebd4dbba31327b25ecec5b42e2cf2de2de46f9fa00adb6d098855de7588067c948c8d6d3defb3be08ac2b533e75a8bb72b48e7a68204f7c720fd058513d
data/.gitignore CHANGED
@@ -4,3 +4,12 @@ bootstrap.css
4
4
  bootstrap-responsive.css
5
5
  Gemfile.lock
6
6
  .rvmrc
7
+ .rbenv-version
8
+
9
+ # Ignore bundler config
10
+ /.bundle
11
+ /vendor/cache
12
+ /vendor/bundle
13
+ tmp/
14
+ test/screenshots/
15
+ test/dummy/log/test.log
@@ -1,7 +1,13 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.0.0
3
4
  - 1.9.3
4
- - jruby-19mode
5
+ - jruby
6
+ - rbx-19mode
5
7
  gemfile:
8
+ - test/gemfiles/sass_3_3.gemfile
6
9
  - test/gemfiles/sass_3_2.gemfile
7
- - test/gemfiles/sass_head.gemfile
10
+ - test/gemfiles/sass_head.gemfile
11
+ matrix:
12
+ allow_failures:
13
+ - gemfile: test/gemfiles/sass_head.gemfile
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.0.0
4
+ * Ported rake task from vwall/compass-twitter-bootstrap to convert Bootstrap upstream - *Peter Gumeson*
5
+ * Moved javascripts from `bootstrap-component.js` to `bootstrap/component.js` - *Peter Gumeson*
6
+
3
7
  ## 2.3.2.2
4
8
 
5
9
  * Allow sass-rails `>= 3.2` - *Thomas McDonald*
@@ -1,4 +1,12 @@
1
- # Contributing to bootstrap-sass
1
+ # Contributing to bootstrap-sass
2
+
3
+ ## Asset Changes
4
+
5
+ Any changes to `bootstrap-sass` assets (scss, javascripts, fonts) should be checked against the `convert` rake task.
6
+ For usage instructions, see the [README](https://github.com/thomas-mcdonald/bootstrap-sass/blob/3/README.md).
7
+
8
+ If something is broken in the converter, it's preferable to update the converter along with the asset itself.
9
+
2
10
 
3
11
  ## Bugs
4
12
 
@@ -8,7 +16,7 @@ repository. Good bug reports are extremely helpful - thank you!
8
16
  Guidelines for bug reports:
9
17
 
10
18
  1. **Does it belong here?** — is this a problem with bootstrap-sass, or
11
- it an issue with [twitter/bootstrap](https://github.com/twitter/bootstrap)?
19
+ it an issue with [twbs/bootstrap](https://github.com/twbs/bootstrap)?
12
20
  We only distribute a direct port and will not modify files if they're not
13
21
  changed upstream.
14
22
 
@@ -49,7 +57,7 @@ Example:
49
57
 
50
58
  **We will not accept pull requests that modify the SCSS beyond fixing bugs caused by *our* code!**
51
59
 
52
- Most pull requests should go to [twitter/bootstrap](https://github.com/twitter/bootstrap) or [jlong/sass-twitter-bootstrap](https://github.com/jlong/sass-twitter-bootstrap)
60
+ Most pull requests should go to [twbs/bootstrap](https://github.com/twbs/bootstrap) or [jlong/sass-twitter-bootstrap](https://github.com/jlong/sass-twitter-bootstrap)
53
61
 
54
62
  Good pull requests - patches, improvements, new features - are a fantastic
55
63
  help. They should remain focused in scope and avoid containing unrelated
@@ -68,4 +76,4 @@ Please **do not** use the issue tracker for personal support requests (use
68
76
  Please **do not** derail or troll issues. Keep the
69
77
  discussion on topic and respect the opinions of others.
70
78
 
71
- *props [html5-boilerplate](https://github.com/h5bp/html5-boilerplate/blob/master/CONTRIBUTING.md)*
79
+ *props [html5-boilerplate](https://github.com/h5bp/html5-boilerplate/blob/master/CONTRIBUTING.md)*
data/README.md CHANGED
@@ -4,126 +4,139 @@
4
4
 
5
5
  `bootstrap-sass` is an Sass-powered version of [Bootstrap](http://github.com/twbs/bootstrap), ready to drop right into your Sass powered applications.
6
6
 
7
- Enjoy.
7
+ ## Installation
8
8
 
9
- ## Usage
9
+ Please see the appropriate guide for your environment of choice:
10
+
11
+ ### a. Rails
10
12
 
11
- ### Rails
13
+ `bootstrap-sass` is easy to drop into Rails with the asset pipeline.
12
14
 
13
- In your Gemfile:
15
+ In your Gemfile you need to add the `bootstrap-sass` gem, and ensure that the `sass-rails` gem is present - it is added to new Rails applications by default.
14
16
 
15
17
  ```ruby
16
- gem 'sass-rails', '=> 3.2'
17
- gem 'bootstrap-sass', '~> 2.3.2.2'
18
+ gem 'sass-rails', '>= 3.2' # sass-rails needs to be higher than 3.2
19
+ gem 'bootstrap-sass', '~> 3.0.0.0.rc'
18
20
  ```
19
21
 
20
- `bundle install` and restart your server to make the files available.
21
-
22
- #### CSS
22
+ `bundle install` and restart your server to make the files available through the pipeline.
23
23
 
24
- Import Bootstrap in an SCSS file (for example, `application.css.scss`) to get all of Bootstrap's styles, mixins and variables! We recommend against using `//= require` directives, since none of your other stylesheets will be [able to use](https://github.com/thomas-mcdonald/bootstrap-sass/issues/79#issuecomment-4428595) the awesome mixins that Bootstrap has defined.
24
+ ### b. Compass (no Rails)
25
25
 
26
- ```css
27
- @import "bootstrap";
26
+ Install the gem
27
+ ```console
28
+ gem install bootstrap-sass --pre
28
29
  ```
29
30
 
30
- #### Javascripts
31
-
32
- You can include the Bootstrap javascripts through two methods. In this case, Sprocket's `//= require` directives are useful, since there is no better alternative.
31
+ If you have an existing Compass project:
33
32
 
34
- We have a helper that includes all available javascripts:
33
+ ```ruby
34
+ # config.rb:
35
+ require 'bootstrap-sass'
36
+ ```
35
37
 
36
- ```js
37
- // Loads all Bootstrap javascripts
38
- //= require bootstrap
38
+ ```console
39
+ compass install bootstrap
39
40
  ```
40
41
 
41
- You can also load individual modules, provided you sort out any related dependencies.
42
+ If you are creating a new Compass project, you can generate it with bootstrap-sass support:
42
43
 
43
- ```js
44
- //= require bootstrap-scrollspy
45
- //= require bootstrap-modal
46
- //= require bootstrap-dropdown
44
+ ```console
45
+ compass create my-new-project -r bootstrap-sass --using bootstrap
47
46
  ```
48
47
 
49
- Simples.
48
+ This will create a new Compass project with the following files in it:
50
49
 
51
- ### Compass
50
+ * [_variables.scss](/templates/project/_variables.scss.erb) - all of bootstrap variables (override them here).
51
+ * [styles.scss](/templates/project/styles.scss) - main project SCSS file, import `variables` and `bootstrap`.
52
52
 
53
- `bootstrap-sass` 2.0 now comes with support for Compass, meaning projects that don't use Rails can get in on the fun Bootstrap web.
54
53
 
55
- #### New project
54
+ ## Usage
56
55
 
57
- Install the gem and create a new project using the gem.
56
+ ### CSS / SCSS / SASS
58
57
 
59
- ```console
60
- gem install bootstrap-sass
61
- compass create compass-test -r bootstrap-sass --using bootstrap
58
+ Import Bootstrap in an SCSS file (for example, `application.css.scss`) to get all of Bootstrap's styles, mixins and variables! We recommend against using `//= require` directives, since none of your other stylesheets will be [able to access][antirequire] the Bootstrap mixins or variables.
59
+
60
+ ```css
61
+ @import "bootstrap";
62
62
  ```
63
63
 
64
- This will sort a few things out:
64
+ You can also include optional bootstrap theme:
65
65
 
66
- * You'll get a starting `styles.scss` ready for your alterations
67
- * You'll get a compiled stylesheet compiled & ready to drop into your application
68
- * We'll also copy the Bootstrap javascripts & images into their respective folders for you, absolutely free of charge! How cool is that?
66
+ ```css
67
+ @import "bootstrap/theme";
68
+ ```
69
69
 
70
- #### Existing project
70
+ ### Javascript
71
71
 
72
- Install the gem, add the require statement to the top of your configuration file, and install the extension.
72
+ We have a helper that includes all Bootstrap javascripts. If you use Rails (or Sprockets separately),
73
+ put this in your Javascript manifest (usually in `application.js`) to load the files in the [correct order](/vendor/assets/javascripts/bootstrap.js):
73
74
 
74
- ```console
75
- gem install bootstrap-sass
75
+ ```js
76
+ // Loads all Bootstrap javascripts
77
+ //= require bootstrap
76
78
  ```
77
79
 
78
- ```ruby
79
- # In config.rb
80
- require 'bootstrap-sass'
81
- ```
80
+ You can also load individual modules, provided you also require any dependencies. You can check dependencies in the [Bootstrap JS documentation][jsdocs].
82
81
 
83
- ```console
84
- compass install bootstrap
82
+ ```js
83
+ //= require bootstrap/scrollspy
84
+ //= require bootstrap/modal
85
+ //= require bootstrap/dropdown
85
86
  ```
86
87
 
87
- You'll get the same benefits as those starting from scratch. Radical.
88
+ ## Development and Contributing
88
89
 
89
- ## Configuration
90
- Need to configure a variable or two? Simply define the value of the variable you want to change *before* importing Bootstrap. Sass will respect your existing definition rather than overwriting it with the Bootstrap defaults. A list of customisable variables can be found in the [Bootstrap documentation](http://twbs.github.io/bootstrap/customize/#variables).
90
+ If you'd like to help with the development of bootstrap-sass itself, read this section.
91
91
 
92
- ```scss
93
- $btnPrimaryBackground: #f00;
94
- @import "bootstrap";
95
- ```
92
+ ### Upstream Converter
96
93
 
97
- **Note**: It's important that the file you are importing is not named `bootstrap`, since this will cause an import loop. As a general rule, errors are something you should try to avoid.
94
+ Keeping bootstrap-sass in sync with upstream changes from Bootstrap used to be an error prone and time consuming manual process. With Bootstrap 3 we have introduced a converter that automates this.
98
95
 
99
- ### Passing multiple values to mixins
96
+ **Note: if you're just looking to *use* Bootstrap 3, see the [installation](#installation) section above.**
100
97
 
101
- Some CSS3 properties take multiple values, such as `box-shadow` or `text-shadow`. To pass multiple values to the Bootstrap mixins, you must escape the values or else the Sass parser will choke on the commas. Here's how to escape the values in Sass:
98
+ Upstream changes to the Bootstrap project can now be pulled in using the `convert` rake task.
102
99
 
103
- ```scss
104
- .selector {
105
- @include box-shadow(#{0 2px 5px rgba(0,0,0,.25) inset, 0 -2px 5px rgba(0,0,0,.25) inset});
106
- }
107
- ```
100
+ Here's an example run that would pull down the master branch from the main [twbs/bootstrap](https://github.com/twbs/bootstrap) repo:
101
+
102
+ rake convert
103
+
104
+ This will convert the latest LESS to SASS and update to the latest JS.
105
+ To convert a specific branch or version, pass the branch name or the commit hash as the first task argument:
106
+
107
+ rake convert[e8a1df5f060bf7e6631554648e0abde150aedbe4]
108
108
 
109
- ### Responsive styling?
110
- As per the Bootstrap project we don't include the responsive styles by default. `@import "bootstrap-responsive";` to get them.
109
+ The latest converter script is located [here][converter] and does the following:
111
110
 
112
- ## Versioning
113
- Bootstrap [claims](https://github.com/twbs/bootstrap#versioning) to use SemVer, although this is for values of public API that don't seem to include selectively requiring CSS components (see breaking change 2.0.2 -> 2.0.3). Since many people using bootstrap-sass *do* selectively require CSS components and I consider it part of the public API we can't really follow SemVer without becoming wildly out of sync with the Bootstrap version number, which is confusing for everyone involved. Further releases to bootstrap-sass will therefore have version numbers of the form `2.x.y.z`, where `2.x.y` is the release of Bootstrap we should be compatible with, and `z` is the patch version.
111
+ * Converts upstream bootstrap LESS files to its matching SCSS file.
112
+ * Copies all upstream JavaScript into `vendor/assets/javascripts/bootstrap`
113
+ * Generates a javascript manifest at `vendor/assets/javascripts/bootstrap.js`
114
+ * Copies all upstream font files into `vendor/assets/fonts/bootstrap`
115
+ * Sets `Bootstrap::BOOTSTRAP_SHA` in [version.rb][version] to the branch sha.
114
116
 
115
- Basically this means you should expect to append a separate patch version to the bootstrap version, which allows our versioning to stay more honest about changes.
117
+ This converter fully converts original LESS to SCSS. Conversion is automatic but requires instructions for certain transformations (see converter output).
118
+ Please submit GitHub issues tagged with `conversion`.
116
119
 
117
- ### Bundler?
120
+ ## Credits
118
121
 
119
- ```ruby
120
- gem 'bootstrap-sass', '~> 2.3.2.2'
121
- ```
122
+ bootstrap-sass has a number of major contributors:
122
123
 
123
- Don't use the standard `~> 2.x.y`. Your apps may break.
124
+ <!-- feel free to make these link wherever you wish -->
125
+ * [Thomas McDonald](https://twitter.com/thomasmcdonald_)
126
+ * [Tristan Harward](http://www.trisweb.com)
127
+ * Peter Gumeson
128
+ * [Gleb Mazovetskiy](https://github.com/glebm)
124
129
 
125
- ## Who
126
- bootstrap-sass is a project by [Thomas McDonald](https://twitter.com/#!/thomasmcdonald_), with support from [other awesome people](https://github.com/thomas-mcdonald/bootstrap-sass/graphs/contributors).
130
+ and a [significant number of other contributors][contrib].
127
131
 
128
132
  ## You're in good company
129
- bootstrap-sass is used to build some awesome projects, including [Diaspora](http://diasporaproject.org/), [rails_admin](https://github.com/sferik/rails_admin), Michael Hartl's [Rails Tutorial](http://railstutorial.org/), [gitlabhq](http://gitlabhq.com/) and [kandan](http://kandanapp.com/). Using bootstrap-sass? I'd love it if you let me know.
133
+ bootstrap-sass is used to build some awesome projects all over the web, including
134
+ [Diaspora](http://diasporaproject.org/), [rails_admin](https://github.com/sferik/rails_admin),
135
+ Michael Hartl's [Rails Tutorial](http://railstutorial.org/), [gitlabhq](http://gitlabhq.com/) and
136
+ [kandan](http://kandanapp.com/).
137
+
138
+ [converter]: https://github.com/thomas-mcdonald/bootstrap-sass/blob/3/tasks/converter.rb
139
+ [version]: https://github.com/thomas-mcdonald/bootstrap-sass/blob/3/lib/bootstrap-sass/version.rb
140
+ [contrib]: https://github.com/thomas-mcdonald/bootstrap-sass/graphs/contributors
141
+ [antirequire]: https://github.com/thomas-mcdonald/bootstrap-sass/issues/79#issuecomment-4428595
142
+ [jsdocs]: http://getbootstrap.com/javascript/#transitions
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rake/testtask'
2
2
  Rake::TestTask.new do |t|
3
3
  t.libs << "test"
4
4
  t.test_files = FileList['test/*_test.rb']
5
- t.verbose = true
5
+ t.verbose = true
6
6
  end
7
7
 
8
8
  desc 'Dumps output to a CSS file for testing'
@@ -11,10 +11,38 @@ task :debug do
11
11
  require './lib/bootstrap-sass/compass_functions'
12
12
  require './lib/bootstrap-sass/sass_functions'
13
13
  path = './vendor/assets/stylesheets'
14
- %w(bootstrap bootstrap-responsive).each do |file|
14
+ %w(bootstrap).each do |file|
15
15
  engine = Sass::Engine.for_file("#{path}/#{file}.scss", syntax: :scss, load_paths: [path])
16
16
  File.open("./#{file}.css", 'w') { |f| f.write(engine.render) }
17
17
  end
18
18
  end
19
19
 
20
- task default: :test
20
+ desc 'Convert bootstrap to bootstrap-sass'
21
+ task :convert, :branch do |t, args|
22
+ require './tasks/converter'
23
+ branch = args[:branch]
24
+ Converter.new(branch).process
25
+ end
26
+
27
+ desc 'Compile bootstrap-sass to tmp/ (or first arg)'
28
+ task :compile, :css_path do |t, args|
29
+ lib_path = File.join(File.dirname(__FILE__), 'lib')
30
+ $:.unshift(lib_path) unless $:.include?(lib_path)
31
+ require 'sass'
32
+ require 'bootstrap-sass/compass_functions'
33
+ require 'bootstrap-sass/sass_functions'
34
+ require 'term/ansicolor'
35
+
36
+ path = 'vendor/assets/stylesheets'
37
+ puts Term::ANSIColor.bold "Compiling SCSS in #{path}"
38
+ %w(bootstrap bootstrap/_theme).each do |file|
39
+ save_path = "#{args.with_defaults(css_path: 'tmp')[:css_path]}/#{file.sub(/(^|\/)?_+/, '\1').sub('/', '-')}.css"
40
+ puts Term::ANSIColor.cyan(" #{save_path}") + '...'
41
+ engine = Sass::Engine.for_file("#{path}/#{file}.scss", syntax: :scss, load_paths: [path])
42
+ css = engine.render
43
+ File.mkdir('tmp') unless File.directory?('tmp')
44
+ File.open(save_path, 'w') { |f| f.write css }
45
+ end
46
+ end
47
+
48
+ task default: :test
@@ -1,6 +1,10 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'bootstrap-sass/version'
4
+
1
5
  Gem::Specification.new do |s|
2
6
  s.name = "bootstrap-sass"
3
- s.version = '2.3.2.2'
7
+ s.version = Bootstrap::VERSION
4
8
  s.authors = ["Thomas McDonald"]
5
9
  s.email = 'tom@conceptcoding.co.uk'
6
10
  s.summary = "Twitter's Bootstrap, converted to Sass and ready to drop into Rails or Compass"
@@ -8,9 +12,17 @@ Gem::Specification.new do |s|
8
12
  s.license = "Apache 2.0"
9
13
 
10
14
  s.add_development_dependency 'compass'
15
+ s.add_development_dependency 'term-ansicolor'
11
16
  s.add_development_dependency 'sass-rails', '>= 3.2'
12
17
  s.add_runtime_dependency 'sass', '~> 3.2'
13
18
 
19
+
20
+ s.add_development_dependency 'capybara'
21
+ s.add_development_dependency 'poltergeist'
22
+ s.add_development_dependency 'tzinfo'
23
+ s.add_development_dependency 'jquery-rails'
24
+ s.add_development_dependency 'slim-rails'
25
+
14
26
  s.files = `git ls-files`.split("\n")
15
27
  s.test_files = `git ls-files -- test/*`.split("\n")
16
- end
28
+ end
@@ -2,7 +2,7 @@ module Bootstrap
2
2
  module Rails
3
3
  class Engine < ::Rails::Engine
4
4
  initializer "bootstrap-sass.assets.precompile" do |app|
5
- app.config.assets.precompile += %w(glyphicons-halflings.png glyphicons-halflings-white.png)
5
+ app.config.assets.precompile << %r(bootstrap/glyphicons-halflings-regular\.(?:eot|svg|ttf|woff)$)
6
6
  end
7
7
  end
8
8
  end
@@ -0,0 +1,4 @@
1
+ module Bootstrap
2
+ VERSION = '3.0.0.0.rc'
3
+ BOOTSTRAP_SHA = 'e8a1df5f060bf7e6631554648e0abde150aedbe4'
4
+ end
@@ -0,0 +1,829 @@
1
+ # coding: utf-8
2
+ # Based on convert script from vwall/compass-twitter-bootstrap gem.
3
+ # https://github.com/vwall/compass-twitter-bootstrap/blob/master/build/convert.rb
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this work except in compliance with the License.
7
+ # You may obtain a copy of the License in the LICENSE file, or at:
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'open-uri'
18
+ require 'json'
19
+ require 'strscan'
20
+ require 'forwardable'
21
+ require 'term/ansicolor'
22
+ require 'fileutils'
23
+
24
+ class Converter
25
+ extend Forwardable
26
+
27
+ GIT_DATA = 'https://api.github.com/repos'
28
+ GIT_RAW = 'https://raw.github.com'
29
+
30
+ def initialize(branch)
31
+ @repo = 'twbs/bootstrap'
32
+ @repo_url = "https://github.com/#@repo"
33
+ @branch = branch || 'master'
34
+ @branch_sha = get_branch_sha
35
+ @save_at = { js: 'vendor/assets/javascripts/bootstrap',
36
+ scss: 'vendor/assets/stylesheets/bootstrap',
37
+ fonts: 'vendor/assets/fonts/bootstrap' }
38
+ @save_at.each { |_,v| FileUtils.mkdir_p(v) }
39
+ @cache_path = 'tmp/converter-cache'
40
+ @logger = Logger.new(repo: @repo_url, branch: @branch, branch_sha: @branch_sha, save_at: @save_at, cache_path: @cache_path)
41
+ end
42
+
43
+ def_delegators :@logger, :log_status, :log_processing, :log_transform, :log_file_info, :log_processed, :log_http_get_file, :log_http_get_files, :silence_log
44
+
45
+ def process
46
+ process_stylesheet_assets
47
+ process_javascript_assets
48
+ process_font_assets
49
+ store_version
50
+ end
51
+
52
+ def process_font_assets
53
+ log_status "Processing fonts..."
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
125
+ end
126
+
127
+ def store_version
128
+ path = 'lib/bootstrap-sass/version.rb'
129
+ content = File.read(path).sub(/BOOTSTRAP_SHA\s*=\s*['"][\w]+['"]/, "BOOTSTRAP_SHA = '#@branch_sha'")
130
+ File.open(path, 'w') { |f| f.write(content) }
131
+ 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
+ end