├── .document ├── .gitignore ├── .rspec ├── .rvmrc.example ├── DESCRIPTION ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── barista.gemspec ├── lib ├── barista.rb ├── barista │ ├── capistrano.rb │ ├── compiler.rb │ ├── extensions.rb │ ├── filter.rb │ ├── framework.rb │ ├── haml_filter.rb │ ├── helpers.rb │ ├── hooks.rb │ ├── integration.rb │ ├── integration │ │ ├── rails2.rb │ │ ├── rails3.rb │ │ └── sinatra.rb │ ├── rake_task.rb │ ├── server.rb │ ├── tasks.rb │ ├── tasks │ │ └── barista.rake │ └── version.rb └── generators │ ├── barista │ └── install │ │ ├── USAGE │ │ ├── install_generator.rb │ │ └── templates │ │ └── initializer.rb │ └── barista_install_generator.rb └── spec ├── assets └── alert.coffee ├── barista_spec.rb └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | 21 | ## PROJECT::SPECIFIC 22 | 23 | .rvmrc 24 | public 25 | 26 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour -------------------------------------------------------------------------------- /.rvmrc.example: -------------------------------------------------------------------------------- 1 | rvm --create use "ree-1.8.7-2010.02@barista" 2 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Barista provides simple, integrated support for CoffeeScript in Rack and Rails applications. 2 | 3 | Much like Compass does for Sass, It also provides Frameworks (bundleable code which can be shared via Gems). 4 | 5 | Lastly, it also provides a Rack Application (which can be used to server compiled code), a around_filter-style precompiler (as Rack middleware) and simple helpers for rails and Haml. 6 | 7 | For more details, please see the the README file bundled with it. -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem 'coffee-script', '~> 2.2' 4 | 5 | group :development, :test do 6 | gem 'rails', '~> 3.0' 7 | gem 'jeweler', '~> 1.0' 8 | gem 'rspec', '~> 2.6' 9 | gem 'rspec-rails', '~> 2.6' 10 | gem 'rspec-core', '~> 2.6' 11 | gem 'rdoc', '~> 2.4' 12 | gem 'rr', '~> 1.0' 13 | gem 'ruby-debug', :platform => :ruby_18 14 | gem 'ruby-debug19', '~> 0.11', :platform => :ruby_19 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | abstract (1.0.0) 5 | actionmailer (3.0.8) 6 | actionpack (= 3.0.8) 7 | mail (~> 2.2.19) 8 | actionpack (3.0.8) 9 | activemodel (= 3.0.8) 10 | activesupport (= 3.0.8) 11 | builder (~> 2.1.2) 12 | erubis (~> 2.6.6) 13 | i18n (~> 0.5.0) 14 | rack (~> 1.2.1) 15 | rack-mount (~> 0.6.14) 16 | rack-test (~> 0.5.7) 17 | tzinfo (~> 0.3.23) 18 | activemodel (3.0.8) 19 | activesupport (= 3.0.8) 20 | builder (~> 2.1.2) 21 | i18n (~> 0.5.0) 22 | activerecord (3.0.8) 23 | activemodel (= 3.0.8) 24 | activesupport (= 3.0.8) 25 | arel (~> 2.0.10) 26 | tzinfo (~> 0.3.23) 27 | activeresource (3.0.8) 28 | activemodel (= 3.0.8) 29 | activesupport (= 3.0.8) 30 | activesupport (3.0.8) 31 | archive-tar-minitar (0.5.2) 32 | arel (2.0.10) 33 | builder (2.1.2) 34 | coffee-script (2.2.0) 35 | coffee-script-source 36 | execjs 37 | coffee-script-source (1.1.1) 38 | columnize (0.3.4) 39 | diff-lcs (1.1.2) 40 | erubis (2.6.6) 41 | abstract (>= 1.0.0) 42 | execjs (1.1.0) 43 | multi_json (~> 1.0) 44 | git (1.2.5) 45 | i18n (0.5.0) 46 | jeweler (1.6.2) 47 | bundler (~> 1.0) 48 | git (>= 1.2.5) 49 | rake 50 | linecache (0.46) 51 | rbx-require-relative (> 0.0.4) 52 | linecache19 (0.5.12) 53 | ruby_core_source (>= 0.1.4) 54 | mail (2.2.19) 55 | activesupport (>= 2.3.6) 56 | i18n (>= 0.4.0) 57 | mime-types (~> 1.16) 58 | treetop (~> 1.4.8) 59 | mime-types (1.16) 60 | multi_json (1.0.3) 61 | polyglot (0.3.1) 62 | rack (1.2.3) 63 | rack-mount (0.6.14) 64 | rack (>= 1.0.0) 65 | rack-test (0.5.7) 66 | rack (>= 1.0) 67 | rails (3.0.8) 68 | actionmailer (= 3.0.8) 69 | actionpack (= 3.0.8) 70 | activerecord (= 3.0.8) 71 | activeresource (= 3.0.8) 72 | activesupport (= 3.0.8) 73 | bundler (~> 1.0) 74 | railties (= 3.0.8) 75 | railties (3.0.8) 76 | actionpack (= 3.0.8) 77 | activesupport (= 3.0.8) 78 | rake (>= 0.8.7) 79 | thor (~> 0.14.4) 80 | rake (0.9.2) 81 | rbx-require-relative (0.0.5) 82 | rdoc (2.5.11) 83 | rr (1.0.2) 84 | rspec (2.6.0) 85 | rspec-core (~> 2.6.0) 86 | rspec-expectations (~> 2.6.0) 87 | rspec-mocks (~> 2.6.0) 88 | rspec-core (2.6.3) 89 | rspec-expectations (2.6.0) 90 | diff-lcs (~> 1.1.2) 91 | rspec-mocks (2.6.0) 92 | rspec-rails (2.6.1) 93 | actionpack (~> 3.0) 94 | activesupport (~> 3.0) 95 | railties (~> 3.0) 96 | rspec (~> 2.6.0) 97 | ruby-debug (0.10.4) 98 | columnize (>= 0.1) 99 | ruby-debug-base (~> 0.10.4.0) 100 | ruby-debug-base (0.10.4) 101 | linecache (>= 0.3) 102 | ruby-debug-base19 (0.11.25) 103 | columnize (>= 0.3.1) 104 | linecache19 (>= 0.5.11) 105 | ruby_core_source (>= 0.1.4) 106 | ruby-debug19 (0.11.6) 107 | columnize (>= 0.3.1) 108 | linecache19 (>= 0.5.11) 109 | ruby-debug-base19 (>= 0.11.19) 110 | ruby_core_source (0.1.5) 111 | archive-tar-minitar (>= 0.5.2) 112 | thor (0.14.6) 113 | treetop (1.4.9) 114 | polyglot (>= 0.3.1) 115 | tzinfo (0.3.29) 116 | 117 | PLATFORMS 118 | ruby 119 | 120 | DEPENDENCIES 121 | coffee-script (~> 2.2) 122 | jeweler (~> 1.0) 123 | rails (~> 3.0) 124 | rdoc (~> 2.4) 125 | rr (~> 1.0) 126 | rspec (~> 2.6) 127 | rspec-core (~> 2.6) 128 | rspec-rails (~> 2.6) 129 | ruby-debug 130 | ruby-debug19 (~> 0.11) 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Darcy Laycock 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Barista 2 | 3 | Barista is a set of tools to make using [CoffeeScript](http://jashkenas.github.com/coffee-script/) in Rails 3, Rails 2 and Rack applications 4 | easier. You can think of it as similar to [Compass](http://compass-style.org/), but for CoffeeScript instead of [Sass](http://sass-lang.com/). 5 | 6 | As an added bonus, Barista also gives: 7 | 8 | * Automatic support for a `:coffeescript` filter in [Haml](http://haml-lang.com/) (when Haml is loaded before Barista) — automatically converting inline CoffeeScript to JavaScript for you. 9 | * Where possible, support for `coffeescript_include_tag` and `coffeescript_tag`. 10 | * When possible, instead of pre-compiling in development and test modes, Barista will embed CoffeeScript in the page for you. 11 | * Support for Heroku via [therubyracer-heroku](https://github.com/aler/therubyracer-heroku) and either pre-compiled JS or, optionally, a lightweight Rack app that generates on request. 12 | 13 | ## Getting Started 14 | 15 | Out of the box, Barista has semi-automatic support for Rails 3.0, Rails 2 (currently untested) and Sinatra. With a minimal amount of effort, you can also make it work in any Rack-based framework. 16 | 17 | ### Rails 3 18 | 19 | Adding Barista to your Rails 3 application should as simple as adding two gems to your `Gemfile`, and running two commands. To get started, open up your `Gemfile` and add the following: 20 | 21 | gem "json" # Only needed if on Ruby 1.8 / a platform that ships without JSON 22 | gem "barista" 23 | 24 | Next, you'll need to run the the following: 25 | 26 | bundle install 27 | rails g barista:install 28 | 29 | This will install the gem into your application and will generate a file in `config/initializers/barista_config.rb` that contains a set of options to configure Barista options. 30 | 31 | Place your CoffeeScripts in `app/coffeescripts` and Barista will automatically compile them on change into `public/javascripts`. 32 | 33 | ### Rails 2 34 | 35 | Much like on Rails 3, Barista supports deep integration into Rails 2. The only thing missing (that is currently supported in the Rails 3 version) is built in support for generating a config file. If you're using bundler in your application, all you need to do is add: 36 | 37 | gem "json" # Only needed if on Ruby 1.8 / a platform that ships without JSON 38 | gem "barista" 39 | 40 | To your `Gemfile`. If you're not using bundler, doing `gem install json barista` and requiring barista both in your application should be enough to get you started. 41 | 42 | If you wish to change the barista configuration, take a look at the [Rails 3 initializer](https://github.com/Sutto/barista/blob/master/lib/generators/barista/install/templates/initializer.rb) and modify it to suite your application as needed. 43 | 44 | If you wish to use barista tasks with rails 2 project, add 45 | 46 | load "barista/tasks/barista.rake" 47 | 48 | To your `Rakefile`. 49 | 50 | ### Sinatra 51 | 52 | Adding Barista to a Sinatra application is a relatively straight forward affair. Like in Rails 2 and Rails 3, you first need to add and require the barista gem and (optionally, the json gem). Unlike Rails 2 and 3 (which set it up automatically), you must also register the extension in your application. So, in the scope of your app (either the top level scope or the `Sinatra::Application` subclass you're using), you then need to simple add: 53 | 54 | register Barista::Integration::Sinatra 55 | 56 | Which will automatically set up the Barista environment and other similar details (e.g. the automatic compilation filter). Since you don't have initializers like you do in Rails, you 57 | can then simply run your `Barista.configure` call and block anywhere before your application starts serving requests. 58 | 59 | ### Other Rack-based Frameworks 60 | 61 | Lastly, even though it is built out of the box to support Rails and Sinatra, Barista can also be used with any Rack-based framework. For proper integration, several things must be done. Namely, wherever you declare your middleware (e.g. in a `config.ru` file), you should register the two pieces of middleware barista uses. `Barista::Filter` should only be registered when 62 | Barista performs compilation (e.g. in development mode) and `Barista::Server::Proxy` should be registered if you want it to support automatic serving of a `coffeescript.js` file and / or 63 | on the fly (versus pre-request compilation) of CoffeeScripts. 64 | 65 | For example, your `config.ru` may look like: 66 | 67 | # Setup goes here... 68 | use Barista::Filter if Barista.add_filter? 69 | use Barista::Server::Proxy 70 | run MyRackApplication 71 | 72 | Next, you need to configure barista anywhere before your the above code is run. e.g by adding the following immediatly preceeding it: 73 | 74 | # Barista (for CoffeeScript Support) 75 | Barista.app_root = root 76 | Barista.root = File.join(root, 'coffeescripts') 77 | Barista.setup_defaults 78 | barista_config = root + '/barista_config.rb' 79 | require barista_config if File.exist?(barista_config) 80 | 81 | Hence, if you'e using, for example, [serve](https://github.com/jlong/serve) users should have a `config.ru` that looks similar to [this example](https://github.com/YouthTree/site-design/blob/master/config.ru). 82 | 83 | ### A Quick Note on the JSON Gem 84 | 85 | Barista indirectly requires the json gem via the coffee-script gem, but it isn't listed as a dependency for very 86 | good reasons. If you encounter errors relating to `require 'json'`, Then you'll need to add either `gem 'json'` 87 | or `gem 'json_pure'` to your Gemfile. 88 | 89 | If you're already running Ruby 1.9, this will be unnecessary as JSON is shipped as part of the standard library. 90 | 91 | ## General Information 92 | 93 | Barista transparently compiles CoffeeScript to JavaScript. When a `.coffee` file is changed and the page is refreshed, Barista first regenerates all `.js` files whose `.coffee` sources have been recently changed. This way, you can refresh immediately after saving the `.coffee` file and not worry about an old `.js` file being sent to the browser (as often happens when using `coffee --watch`). 94 | 95 | Barista supports using `therubyracer` when installed or, by default, using either the `node` executable or `jsc` (on OS X) to compile your scripts. There is 96 | no need for you to install the coffee-script executable in Node as having Node itself, or any of the alternatives available, is enough. 97 | 98 | When you want to deploy, you can simple run `rake barista:brew` to force the compilation of all JavaScripts for the current application. 99 | 100 | ## In Practice 101 | 102 | Barista not only supports compiling all JavaScripts on demand (via `rake barista:brew` as above, or `Barista.compile_all!`) but it 103 | also ships with a simple Rack server app that will compile on demand for platforms such as Heroku, meaning you don't need write access 104 | (although it is helpful). 105 | 106 | If you're using [Jammit](http://documentcloud.github.com/jammit/), the precompilation phase (e.g. `rake barista:brew` before running Jammit) will make it possible for your application 107 | to automatically bundle not only normal JavaScripts but also your CoffeeScripts. 108 | 109 | To add Barista to your project, simply add `gem 'barista', '~> 1.0'` to your Gemfile and run `bundle install`. 110 | 111 | Please note that for Jammit compatibility, in test and development mode (by default) it will 112 | automatically compile all CoffeeScripts that have changed before rendering the page. 113 | 114 | Barista works out of the box with Rails 3 (and theoretically, Rails 2) — with support for Rack if 115 | you're willing to set it up manually. More docs on how to set it up for other platforms 116 | will be posted in the near future. 117 | 118 | ## Sinatra 119 | 120 | To use Barista with [Sinatra](http://www.sinatrarb.com/), you'll need to first require the Barista gem in your application 121 | and then add the following to your application scope (e.g. if you're using a custom class, there): 122 | 123 | register Barista::Integration::Sinatra 124 | 125 | This will automatically setup the filter as needed, setup a server proxy for the `coffee-script.js` 126 | file and setup the defaults based on your applications environment 127 | 128 | ## Configuration 129 | 130 | Please note that Barista lets you configure several options. To do this, 131 | it's as simple as setting up an initializer with: 132 | 133 | rails generate barista:install 134 | 135 | Then editing `config/initializers/barista_config.rb`. The options available are: 136 | 137 | ### Boolean Options 138 | 139 | All of these come in the form of `#option?` (to check its status), `#option=(value)` (to set it) 140 | and `#option!` (to set the value to true): 141 | 142 | * `verbose` – Output debugging error messages. (Defaults to true in test / dev) 143 | * `bare` – Don't wrap the compiled JS in a Closure. 144 | * `add_filter` – Automatically add an around filter for processing changes. (Defaults to true in test / dev) 145 | * `add_preamble` – Add a time + path preamble to compiled JS. (Defaults to true in test / dev) 146 | * `exception_on_error` – Raise an exception on compilation errors (defaults to true) 147 | * `embedded_interpreter` – Embeds coffeescript + link to coffee file instead of compiling for include tags and haml filters. (Defaults to true in test / dev) 148 | * `auto_compile` – Automatically compile CoffeeScript to JS when CoffeeScript is newer than the generated JS file. After you turn it off, your server will use the generated JS file directly and won't depend on any CoffeeScript compilers. (Defaults is true) 149 | 150 | ### Path options 151 | 152 | * `root` – The folder path to read CoffeeScripts from. (Defaults to `app/coffeescripts`.) 153 | * `output_root` – The folder to write compiled JS files to. (Defaults to `public/javascripts`.) 154 | * `change_output_prefix!` – Method to change the output prefix for a framework. 155 | * `change_output_root!` - Method to change the output root for a given framework. 156 | * `verbose` – Whether or not Barista will add a preamble to files. 157 | * `js_path` – Path to the pure-JavaScript compiler. 158 | * `env` – The application environment. (Defaults to `Rails.env`.) 159 | * `app_root` – The application's root path. 160 | * `bin_path` – The path to the `node` executable if non-standard and not using `therubyracer`. 161 | * All of the hook methods mentioned below. 162 | 163 | ### Custom Preamble 164 | 165 | You can generate a custom preamble using a code block. For example, you can replace the location of the original `.coffee` file by a relative one to `Rails.root`. 166 | 167 | Barista.add_preamble do |location| 168 | "/* : DO NOT MODIFY - compiled from #{Pathname.new(location).relative_path_from(Rails.root).to_s}\n\n" 169 | end 170 | 171 | ## Frameworks 172 | 173 | One of the other main features Barista adds (over other tools) is frameworks similar 174 | to Compass. The idea being, you add CoffeeScripts at runtime from gems etc. To do this, 175 | in your gem just have a `coffeescript` directory and then in your gem add the following code: 176 | 177 | Barista::Framework.register 'name', 'full-path-to-directory' if defined?(Barista::Framework) 178 | 179 | For an example of this in practice, check out [bhm-google-maps](http://github.com/YouthTree/bhm-google-maps) 180 | or, the currently-in-development, [shuriken](http://github.com/Sutto/shuriken). The biggest advantage of this 181 | is you can then manage JS dependencies using existing tools like Bundler. 182 | 183 | In your `Barista.configure` block, you can also configure on a per-application basis the output directory 184 | for individual frameworks (e.g. put shuriken into `vendor/shuriken`, bhm-google-maps into `vendor/bhm-google-maps`): 185 | 186 | Barista.configure do |c| 187 | c.change_output_prefix! 'shuriken', 'vendor/shuriken' 188 | c.change_output_prefix! 'bhm-google-maps', 'vendor/bhm-google-maps' 189 | end 190 | 191 | Alternatively, to prefix all, you can use `Barista.each_framework` (if you pass true, it includes the 'default' framework 192 | which is your application root). 193 | 194 | Barista.configure do |c| 195 | c.each_framework do |framework| 196 | c.change_output_prefix! framework.name, "vendor/#{framework.name}" 197 | end 198 | end 199 | 200 | ## Hooks 201 | 202 | Barista lets you hook into the compilation at several stages, namely: 203 | 204 | * before compilation 205 | * after compilation 206 | * after compilation fails 207 | * after compilation complete 208 | 209 | To hook into these hooks, you can do the following: 210 | 211 | * `Barista.before_compilation { |path| puts "Barista: Compiling #{path}" }` 212 | * `Barista.on_compilation { |path| puts "Barista: Successfully compiled #{path}" }` 213 | * `Barista.on_compilation_with_warning { |path, output| puts "Barista: Compilation of #{path} had a warning:\n#{output}" }` 214 | * `Barista.on_compilation_error { |path, output| puts "Barista: Compilation of #{path} failed with:\n#{output}" }` 215 | * `Barista.on_compilation_complete { puts "Barista: Successfully compiled all files" }` 216 | 217 | These allow you to do things such as notify on compilation, automatically 218 | perform compression post compilation and a variety of other cool things. 219 | 220 | An excellent example of these hooks in use is [barista\_growl](http://github.com/TrevorBurnham/barista_growl), 221 | by Trevor Burnham — a gem perfect for development purposes that automatically shows Growl messages 222 | on compilation. 223 | 224 | ## Deployment 225 | 226 | Add `require 'barista/capistrano'` to your `deploy.rb`. 227 | 228 | # Contributors / Credits 229 | 230 | The following people have all contributed to Barista: 231 | 232 | * [Xavier Shay](https://github.com/xaviershay) – Added preamble text to generated text in verbose mode. 233 | * [einarmagnus](https://github.com/einarmagnus) – Fixed jruby support. 234 | * [Matt Dean](https://github.com/trabian) – Added `before_full_compilation` and `on_compilation_complete` hooks. 235 | * [Trevor Burnham](https://github.com/TrevorBurnham) – Misc. documentation tweaks and hooks idea. 236 | * [Sean McCullough](https://github.com/mcculloughsean) – Initial switch to support bare (vs. no\_wrap) 237 | * [Ben Atkin](https://github.com/benatkin) – Docs work. 238 | * [Ben Hoskings](https://github.com/benhoskings) – Misc. fixes, added preamble support. 239 | * [Kim Joar Bekkelund](https://github.com/kjbekkelund) – Docs work. 240 | * [Jeffrey ODell](https://github.com/jodell) - Fixed an issue with messages during autocompile. 241 | * [Paul McMahon](https://github.com/pwim) - Fixed a typo. 242 | * [Ravil Bayramgalin](https://github.com/brainopia) - Fixes for Rakefiles on Rails 2. 243 | * [Daniel Doubrovkine](https://github.com/dblock) - Dynamic Preambles, Making it easier to spec the application. 244 | 245 | Barista was originally heavily inspired by [Bistro Car](https://github.com/jnicklas/bistro_car), but has taken a few fundamentally 246 | different approach in a few areas. 247 | 248 | Barista builds upon the awesome [coffee-script](https://github.com/josh/ruby-coffee-script) gem. 249 | 250 | It's all possible thanks to [CoffeeScript](https://github.com/jashkenas/coffee-script) by Jeremy Ashkenas. 251 | 252 | If I've missed you're name and you've contributed to Barista, please let me know and I'll add you to the list (or 253 | fork this document and send a pull request). 254 | 255 | ## Note on Patches/Pull Requests ## 256 | 257 | 1. Fork the project. 258 | 2. Make your feature addition or bug fix. 259 | 3. Add tests for it. This is important so I don't break it in a future version unintentionally. 260 | 4. Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 261 | 5. Send me a pull request. Bonus points for topic branches. 262 | 263 | ## Copyright ## 264 | 265 | Copyright (c) 2010 Darcy Laycock. See LICENSE for details. 266 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | 4 | begin 5 | Bundler.setup(:default, :development) 6 | rescue Bundler::BundlerError => e 7 | $stderr.puts e.message 8 | $stderr.puts "Run `bundle install` to install missing gems." 9 | exit e.status_code 10 | end 11 | 12 | require 'rake' 13 | require 'rspec/core/rake_task' 14 | 15 | require 'barista/version' 16 | 17 | require 'jeweler' 18 | Jeweler::Tasks.new do |gem| 19 | gem.name = "barista" 20 | gem.summary = %Q{Simple, transparent coffeescript integration for Rails and Rack applications.} 21 | gem.description = File.read(File.expand_path('DESCRIPTION', File.dirname(__FILE__))) 22 | gem.email = "sutto@sutto.net" 23 | gem.homepage = "http://github.com/Sutto/barista" 24 | gem.version = Barista::Version::STRING 25 | gem.authors = ["Darcy Laycock"] 26 | end 27 | Jeweler::GemcutterTasks.new 28 | 29 | RSpec::Core::RakeTask.new(:spec) do |spec| 30 | spec.pattern = "spec/**/*_spec.rb" 31 | end 32 | 33 | require 'rdoc/task' 34 | RDoc::Task.new do |rdoc| 35 | version = Barista::Version::STRING 36 | rdoc.rdoc_dir = 'rdoc' 37 | rdoc.title = "barista #{version}" 38 | rdoc.rdoc_files.include('README*') 39 | rdoc.rdoc_files.include('lib/**/*.rb') 40 | end 41 | 42 | task :default => :spec 43 | 44 | require 'barista' 45 | 46 | 47 | -------------------------------------------------------------------------------- /barista.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "barista" 8 | s.version = "1.3.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Darcy Laycock"] 12 | s.date = "2012-07-23" 13 | s.description = "Barista provides simple, integrated support for CoffeeScript in Rack and Rails applications.\n\nMuch like Compass does for Sass, It also provides Frameworks (bundleable code which can be shared via Gems).\n\nLastly, it also provides a Rack Application (which can be used to server compiled code), a around_filter-style precompiler (as Rack middleware) and simple helpers for rails and Haml.\n\nFor more details, please see the the README file bundled with it." 14 | s.email = "sutto@sutto.net" 15 | s.extra_rdoc_files = [ 16 | "LICENSE", 17 | "README.md" 18 | ] 19 | s.files = [ 20 | ".document", 21 | ".rspec", 22 | ".rvmrc.example", 23 | "DESCRIPTION", 24 | "Gemfile", 25 | "Gemfile.lock", 26 | "LICENSE", 27 | "README.md", 28 | "Rakefile", 29 | "barista.gemspec", 30 | "lib/barista.rb", 31 | "lib/barista/capistrano.rb", 32 | "lib/barista/compiler.rb", 33 | "lib/barista/extensions.rb", 34 | "lib/barista/filter.rb", 35 | "lib/barista/framework.rb", 36 | "lib/barista/haml_filter.rb", 37 | "lib/barista/helpers.rb", 38 | "lib/barista/hooks.rb", 39 | "lib/barista/integration.rb", 40 | "lib/barista/integration/rails2.rb", 41 | "lib/barista/integration/rails3.rb", 42 | "lib/barista/integration/sinatra.rb", 43 | "lib/barista/rake_task.rb", 44 | "lib/barista/server.rb", 45 | "lib/barista/tasks.rb", 46 | "lib/barista/tasks/barista.rake", 47 | "lib/barista/version.rb", 48 | "lib/generators/barista/install/USAGE", 49 | "lib/generators/barista/install/install_generator.rb", 50 | "lib/generators/barista/install/templates/initializer.rb", 51 | "lib/generators/barista_install_generator.rb", 52 | "spec/assets/alert.coffee", 53 | "spec/barista_spec.rb", 54 | "spec/spec_helper.rb" 55 | ] 56 | s.homepage = "http://github.com/Sutto/barista" 57 | s.require_paths = ["lib"] 58 | s.rubygems_version = "1.8.24" 59 | s.summary = "Simple, transparent coffeescript integration for Rails and Rack applications." 60 | 61 | if s.respond_to? :specification_version then 62 | s.specification_version = 3 63 | 64 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 65 | s.add_runtime_dependency(%q, ["~> 2.2"]) 66 | s.add_development_dependency(%q, ["~> 3.0"]) 67 | s.add_development_dependency(%q, ["~> 1.0"]) 68 | s.add_development_dependency(%q, ["~> 2.6"]) 69 | s.add_development_dependency(%q, ["~> 2.6"]) 70 | s.add_development_dependency(%q, ["~> 2.6"]) 71 | s.add_development_dependency(%q, ["~> 2.4"]) 72 | s.add_development_dependency(%q, ["~> 1.0"]) 73 | s.add_development_dependency(%q, [">= 0"]) 74 | s.add_development_dependency(%q, ["~> 0.11"]) 75 | else 76 | s.add_dependency(%q, ["~> 2.2"]) 77 | s.add_dependency(%q, ["~> 3.0"]) 78 | s.add_dependency(%q, ["~> 1.0"]) 79 | s.add_dependency(%q, ["~> 2.6"]) 80 | s.add_dependency(%q, ["~> 2.6"]) 81 | s.add_dependency(%q, ["~> 2.6"]) 82 | s.add_dependency(%q, ["~> 2.4"]) 83 | s.add_dependency(%q, ["~> 1.0"]) 84 | s.add_dependency(%q, [">= 0"]) 85 | s.add_dependency(%q, ["~> 0.11"]) 86 | end 87 | else 88 | s.add_dependency(%q, ["~> 2.2"]) 89 | s.add_dependency(%q, ["~> 3.0"]) 90 | s.add_dependency(%q, ["~> 1.0"]) 91 | s.add_dependency(%q, ["~> 2.6"]) 92 | s.add_dependency(%q, ["~> 2.6"]) 93 | s.add_dependency(%q, ["~> 2.6"]) 94 | s.add_dependency(%q, ["~> 2.4"]) 95 | s.add_dependency(%q, ["~> 1.0"]) 96 | s.add_dependency(%q, [">= 0"]) 97 | s.add_dependency(%q, ["~> 0.11"]) 98 | end 99 | end 100 | 101 | -------------------------------------------------------------------------------- /lib/barista.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'time' # Required for httpdate 3 | require 'coffee_script' 4 | 5 | # Setup ExecJS extras if present 6 | if defined?(ExecJS::ExternalRuntime) 7 | ExecJS::ExternalRuntime.send :attr_accessor, :binary 8 | end 9 | 10 | module Barista 11 | 12 | Error = Class.new(StandardError) 13 | CompilationError = Class.new(Error) 14 | CompilerUnavailableError = Class.new(Error) 15 | 16 | autoload :Compiler, 'barista/compiler' 17 | autoload :Extensions, 'barista/extensions' 18 | autoload :Filter, 'barista/filter' 19 | autoload :Framework, 'barista/framework' 20 | autoload :HamlFilter, 'barista/haml_filter' 21 | autoload :Helpers, 'barista/helpers' 22 | autoload :Hooks, 'barista/hooks' 23 | autoload :Integration, 'barista/integration' 24 | autoload :Server, 'barista/server' 25 | 26 | class << self 27 | include Extensions 28 | 29 | def library_root 30 | @library_root ||= Pathname(__FILE__).dirname 31 | end 32 | 33 | # Hook methods 34 | # 35 | # Hooks are a generic way to define blocks that are executed at run time. 36 | # For a full list of hooks, see the readme. 37 | 38 | def hooks 39 | @hooks ||= Hooks.new 40 | end 41 | 42 | def on_hook(name, *args, &blk) 43 | hooks.on(name, *args, &blk) 44 | end 45 | 46 | def invoke_hook(name, *args) 47 | hooks.invoke(name, *args) 48 | end 49 | 50 | def has_hook?(name) 51 | hooks.has_hook?(name) 52 | end 53 | 54 | has_hook_method :on_compilation_error => :compilation_failed, 55 | :on_compilation => :compiled, 56 | :on_compilation_complete => :all_compiled, 57 | :on_compilation_with_warning => :compiled_with_warning, 58 | :before_full_compilation => :before_full_compilation, 59 | :before_compilation => :before_compilation 60 | 61 | # Configuration - Tweak how you use Barista. 62 | 63 | has_boolean_options :verbose, :bare, :add_filter, :add_preamble, :exception_on_error, :embedded_interpreter, :auto_compile 64 | has_delegate_methods Compiler, :bin_path, :bin_path=, :js_path, :js_path= 65 | has_delegate_methods Framework, :register 66 | has_deprecated_methods :compiler, :compiler=, :compiler_klass, :compiler_klass= 67 | 68 | def add_preamble(&blk) 69 | self.add_preamble = true 70 | if block_given? 71 | @preamble = blk 72 | end 73 | end 74 | 75 | def preamble 76 | @preamble 77 | end 78 | 79 | def configure 80 | yield self if block_given? 81 | end 82 | 83 | def env 84 | @env ||= default_for_env 85 | end 86 | 87 | def env=(value) 88 | @env = value.to_s.strip 89 | @env = nil if @env == '' 90 | end 91 | 92 | def logger 93 | @logger ||= default_for_logger 94 | end 95 | 96 | def logger=(value) 97 | @logger = value 98 | end 99 | 100 | def app_root 101 | @app_root ||= default_for_app_root 102 | end 103 | 104 | def app_root=(value) 105 | @app_root = value.nil? ? nil : Pathname(value.to_s) 106 | end 107 | 108 | def root 109 | @root ||= app_root.join("app", "coffeescripts") 110 | end 111 | 112 | def root=(value) 113 | @root = value.nil? ? nil : Pathname(value.to_s) 114 | Framework.default_framework = nil 115 | end 116 | 117 | def output_root 118 | @output_root ||= app_root.join("public", "javascripts") 119 | end 120 | 121 | def output_root=(value) 122 | @output_root = value.nil? ? nil : Pathname(value.to_s) 123 | end 124 | 125 | def no_wrap? 126 | deprecate! self, :no_wrap?, 'Please use bare? instead.' 127 | bare? 128 | end 129 | 130 | def no_wrap! 131 | deprecate! self, :no_wrap!, 'Please use bare! instead.' 132 | bare! 133 | end 134 | 135 | def no_wrap=(value) 136 | deprecate! self, :no_wrap=, 'Please use bare= instead.' 137 | self.bare = value 138 | end 139 | 140 | # Default configuration options 141 | 142 | def local_env? 143 | %w(test development).include? Barista.env 144 | end 145 | 146 | def default_for_env 147 | return Rails.env.to_s if defined?(Rails.env) 148 | ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development' 149 | end 150 | 151 | def default_for_app_root 152 | if defined?(Rails.root) 153 | Rails.root 154 | else 155 | Pathname(Dir.pwd) 156 | end 157 | end 158 | 159 | def default_for_logger 160 | if defined?(Rails.logger) 161 | Rails.logger 162 | else 163 | require 'logger' 164 | Logger.new(STDOUT) 165 | end 166 | end 167 | 168 | def default_for_verbose 169 | local_env? 170 | end 171 | 172 | def default_for_add_filter 173 | local_env? 174 | end 175 | 176 | def default_for_exception_on_error 177 | true 178 | end 179 | 180 | def default_for_embedded_interpreter 181 | false 182 | end 183 | 184 | def default_for_auto_compile 185 | true 186 | end 187 | 188 | 189 | # Actual tasks on the barista module. 190 | 191 | def compile_file!(file, force = false, silence_error = false) 192 | Compiler.autocompile_file file, force, silence_error 193 | end 194 | 195 | def compile_all!(force = false, silence_error = true) 196 | debug "Compiling all coffeescripts" if Barista.auto_compile? 197 | Barista.invoke_hook :before_full_compilation 198 | Framework.exposed_coffeescripts.each do |coffeescript| 199 | Compiler.autocompile_file coffeescript, force, silence_error 200 | end 201 | debug "Copying all javascripts" 202 | Framework.exposed_javascripts.each do |javascript| 203 | Compiler.autocompile_file javascript, force, silence_error 204 | end 205 | Barista.invoke_hook :all_compiled 206 | true 207 | end 208 | 209 | def change_output_prefix!(framework, prefix = nil) 210 | framework = Barista::Framework[framework] unless framework.is_a?(Barista::Framework) 211 | framework.output_prefix = prefix if framework 212 | end 213 | 214 | def change_output_root!(framework, root) 215 | framework = Barista::Framework[framework] unless framework.is_a?(Barista::Framework) 216 | framework.output_root = root if framework 217 | end 218 | 219 | def each_framework(include_default = false, &blk) 220 | Framework.all(include_default).each(&blk) 221 | end 222 | 223 | def output_path_for(file) 224 | output_root.join(file.to_s.gsub(/^\/+/, '')).to_s.gsub(/\.coffee$/, '.js') 225 | end 226 | 227 | def debug(message) 228 | logger.debug "[Barista] #{message}" if logger && verbose? 229 | end 230 | 231 | def setup_defaults 232 | Barista::HamlFilter.setup 233 | Barista::Compiler.setup_default_error_logger 234 | end 235 | 236 | end 237 | 238 | # Setup integration by default. 239 | Integration.setup 240 | 241 | end 242 | -------------------------------------------------------------------------------- /lib/barista/capistrano.rb: -------------------------------------------------------------------------------- 1 | Capistrano::Configuration.instance.load do 2 | 3 | before 'deploy:restart', 'barista:brew' 4 | 5 | _cset(:barista_role) { :app } 6 | 7 | namespace :barista do 8 | desc 'Compile CoffeeScripts.' 9 | task :brew, :roles => lambda { fetch(:barista_role) } do 10 | rails_env = fetch(:rails_env, "production") 11 | run("cd #{current_path} ; RAILS_ENV=#{rails_env} bundle exec rake barista:brew") 12 | end 13 | end 14 | end 15 | 16 | -------------------------------------------------------------------------------- /lib/barista/compiler.rb: -------------------------------------------------------------------------------- 1 | require 'digest/sha2' 2 | 3 | module Barista 4 | class Compiler 5 | 6 | UNAVAILABLE_MESSAGE = "No method of compiling coffee-script is currently available. Please see the ExecJS page (https://github.com/sstephenson/execjs) for details on how to set one up." 7 | 8 | # TODO: Deprecate. 9 | class << self 10 | 11 | def js_path 12 | CoffeeScript::Source.path 13 | end 14 | 15 | def js_path=(value) 16 | CoffeeScript::Source.path = value 17 | end 18 | 19 | def bin_path 20 | execjs_runtime_call :binary 21 | end 22 | 23 | def bin_path=(path) 24 | execjs_runtime_call :binary=, path 25 | end 26 | 27 | def execjs_runtime_call(method, *args) 28 | runtime = ExecJS.runtime 29 | if runtime.respond_to?(method, true) 30 | runtime.send method, *args 31 | else 32 | nil 33 | end 34 | end 35 | 36 | def available? 37 | ExecJS.runtime and ExecJS.runtime.available? 38 | end 39 | 40 | def check_availability!(silence = false) 41 | available = available? 42 | if !available && Barista.exception_on_error? && !silence 43 | raise CompilerUnavailableError, UNAVAILABLE_MESSAGE 44 | end 45 | available 46 | end 47 | 48 | def compile(content, options = {}) 49 | self.new(content, options).to_js 50 | end 51 | 52 | def autocompile_file(file, force = false, silence_error = false) 53 | # Expand the path from the framework. 54 | origin_path, framework = Framework.full_path_for(file) 55 | return if origin_path.nil? 56 | destination_path = framework.output_path_for(file) 57 | 58 | # read file directly if auto_compile is disabled 59 | if !Barista.auto_compile? 60 | if File.exist?(destination_path) 61 | return File.read(destination_path) 62 | else 63 | return nil 64 | end 65 | end 66 | 67 | return File.read(destination_path) unless dirty?(origin_path, destination_path) || force 68 | # Ensure we have a coffeescript compiler available. 69 | if !check_availability!(silence_error) 70 | Barista.debug UNAVAILABLE_MESSAGE 71 | return nil 72 | end 73 | Barista.debug "Compiling #{file} from framework '#{framework.name}'" 74 | compiler = new(origin_path, :silence_error => silence_error, :output_path => destination_path) 75 | content = compiler.to_js 76 | compiler.save 77 | content 78 | end 79 | 80 | def compile_as(file, type) 81 | origin_path, framework = Framework.full_path_for(file) 82 | return if origin_path.nil? 83 | if type == :coffeescript 84 | return File.read(origin_path), File.mtime(origin_path) 85 | else 86 | return autocompile_file(file), Time.now 87 | end 88 | end 89 | 90 | def dirty?(from, to) 91 | File.exist?(from) && (!File.exist?(to) || File.mtime(to) < File.mtime(from)) 92 | end 93 | 94 | def setup_default_error_logger 95 | Barista.on_compilation_error do |where, message| 96 | if Barista.verbose? 97 | Barista.debug "There was an error compiling coffeescript from #{where}:" 98 | message.each_line { |line| Barista.debug line.rstrip } 99 | end 100 | end 101 | end 102 | 103 | end 104 | 105 | def initialize(context, options = {}) 106 | @compiled = false 107 | @options = options 108 | setup_compiler_context context 109 | end 110 | 111 | def compile! 112 | location = @options.fetch(:origin, 'inline') 113 | @compiled_content = compile(@context, location) 114 | @compiled_content = preamble(location) + @compiled_content if location != 'inline' && Barista.add_preamble? 115 | @compiled = true 116 | end 117 | 118 | def to_js 119 | compile! unless defined?(@compiled) && @compiled 120 | @compiled_content 121 | end 122 | 123 | def copyable?(location) 124 | location != 'inline' && File.extname(location) == '.js' 125 | end 126 | 127 | def compile(script, where = 'inline') 128 | if copyable?(where) 129 | out = script 130 | else 131 | Barista.invoke_hook :before_compilation, where 132 | out = CoffeeScript.compile script, :bare => Barista.bare? 133 | Barista.invoke_hook :compiled, where 134 | end 135 | out 136 | rescue ExecJS::Error => e 137 | Barista.invoke_hook :compilation_failed, where, e.message 138 | if Barista.exception_on_error? && !@options[:silence] 139 | if e.is_a?(ExecJS::ProgramError) 140 | where_within_app = where.sub(/#{Regexp.escape(Barista.app_root.to_s)}\/?/, '') 141 | raise CompilationError, "Error: In #{where_within_app}, #{e.message}" 142 | else 143 | raise CompilationError, "CoffeeScript encountered an error compiling #{where}: #{e.message}" 144 | end 145 | end 146 | compilation_error_for where, e.message 147 | end 148 | 149 | def save(path = @options[:output_path]) 150 | return false unless path.is_a?(String) && !to_js.nil? 151 | FileUtils.mkdir_p File.dirname(path) 152 | File.open(path, "w+") { |f| f.write @compiled_content } 153 | true 154 | rescue Errno::EACCES 155 | false 156 | end 157 | 158 | protected 159 | 160 | def preamble(location) 161 | inner_message = copyable?(location) ? "copied" : "compiled" 162 | if Barista.preamble 163 | Barista.preamble.call(location) 164 | else 165 | "/* DO NOT MODIFY. This file was #{inner_message} #{Time.now.httpdate} from\n * #{location.strip}\n */\n\n" 166 | end 167 | end 168 | 169 | def compilation_error_for(location, message) 170 | details = "Compilation of '#{location}' failed:\n#{message}" 171 | Barista.verbose? ? "alert(#{details.to_json});" : nil 172 | end 173 | 174 | def setup_compiler_context(context) 175 | if context.respond_to?(:read) 176 | @context = context.read 177 | @type = :string 178 | default_path = context.respond_to?(:path) ? context.path : 'inline' 179 | @options[:origin] ||= default_path 180 | elsif !context.include?("\n") && File.exist?(context) 181 | @context = File.read(context) 182 | @type = :file 183 | @options[:origin] ||= context 184 | else 185 | @context = context.to_s 186 | @type = :string 187 | @options[:origin] ||= 'inline' 188 | end 189 | end 190 | 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /lib/barista/extensions.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Extensions 3 | 4 | def self.included(parent) 5 | parent.class_eval do 6 | extend ClassMethods 7 | include InstanceMethods 8 | end 9 | end 10 | 11 | module ClassMethods 12 | 13 | def has_boolean_options(*names) 14 | source = [] 15 | names.each do |name| 16 | source << <<-EOM 17 | 18 | def #{name}! 19 | @#{name} = true 20 | end 21 | 22 | def #{name}? 23 | defined?(@#{name}) ? @#{name} : default_for_#{name} 24 | end 25 | 26 | def #{name}=(value) 27 | @#{name} = !!value 28 | end 29 | 30 | def default_for_#{name} 31 | false 32 | end 33 | 34 | EOM 35 | end 36 | class_eval source.join("\n"), __FILE__, __LINE__ 37 | end 38 | 39 | def has_hook_method(options) 40 | source = [] 41 | options.each_pair do |name, event| 42 | source << <<-EOM 43 | def #{name}(&blk) 44 | on_hook #{event.to_sym.inspect}, &blk 45 | end 46 | EOM 47 | end 48 | class_eval source.join("\n"), __FILE__, __LINE__ 49 | end 50 | 51 | def has_delegate_methods(delegate, *args) 52 | source = [] 53 | args.each do |method| 54 | source << <<-EOM 55 | 56 | def #{method}(*args, &blk) 57 | #{delegate}.send(:#{method}, *args, &blk) 58 | end 59 | 60 | EOM 61 | end 62 | class_eval source.join("\n"), __FILE__, __LINE__ 63 | end 64 | 65 | def has_deprecated_methods(*args) 66 | source = [] 67 | args.each do |method| 68 | source << <<-EOM 69 | 70 | def #{method}(*args, &blk) 71 | Barista.deprecate!(self, :#{method}) 72 | nil 73 | end 74 | 75 | EOM 76 | end 77 | class_eval source.join("\n"), __FILE__, __LINE__ 78 | end 79 | 80 | end 81 | 82 | module InstanceMethods 83 | 84 | def deprecate!(object, method, other = nil) 85 | klass_prefix = (object.is_a?(Class) || object.is_a?(Module)) ? "#{object.name}." : "#{object.class.name}#" 86 | warn "#{klass_prefix}#{method} is deprecated and will be removed in 1.0. #{other}".strip 87 | end 88 | 89 | end 90 | 91 | end 92 | end -------------------------------------------------------------------------------- /lib/barista/filter.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | class Filter 3 | 4 | def initialize(app) 5 | @app = app 6 | end 7 | 8 | def call(env) 9 | dup._call(env) 10 | end 11 | 12 | def _call(env) 13 | Barista.debug 'Compiling all scripts for barista' if Barista.auto_compile? 14 | Barista.compile_all! 15 | # Now, actually call the app. 16 | @app.call env 17 | end 18 | 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/barista/framework.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | class Framework 3 | 4 | def self.default_framework 5 | @default_framework ||= self.new(:name => "default", :root => Barista.root) 6 | end 7 | 8 | def self.default_framework=(value) 9 | @default_framework = value 10 | end 11 | 12 | def self.all(include_default = false) 13 | (@all ||= []).dup.tap do |all| 14 | all.unshift default_framework if include_default 15 | end 16 | end 17 | 18 | def self.exposed_coffeescripts 19 | all(true).inject([]) do |collection, fw| 20 | collection + fw.exposed_coffeescripts 21 | end.uniq.sort_by { |f| f.length } 22 | end 23 | 24 | def self.exposed_javascripts 25 | all(true).inject([]) do |collection, fw| 26 | collection + fw.exposed_javascripts 27 | end.uniq.sort_by { |f| f.length } 28 | end 29 | 30 | def self.coffeescript_glob_paths 31 | all(true).map { |fw| fw.coffeescript_glob_path } 32 | end 33 | 34 | def self.full_path_for(script) 35 | javascript = script.to_s.gsub(/\.coffee$/, '.js').gsub(/^\/+/, '') 36 | coffeescript = script.to_s.gsub(/\.js$/, '.coffee').gsub(/^\/+/, '') 37 | all(true).each do |fw| 38 | full_path = fw.full_path_for(coffeescript) || fw.full_path_for(javascript) 39 | return full_path, fw if full_path 40 | end 41 | nil 42 | end 43 | 44 | def self.register(name, options = nil) 45 | if options.is_a?(Hash) 46 | framework = self.new(options.merge(:name => name)) 47 | else 48 | framework = self.new(:name => name, :root => options) 49 | end 50 | (@all ||= []) << framework 51 | end 52 | 53 | def self.[](name) 54 | name = name.to_s 55 | (@all ||= []).detect { |fw| fw.name == name } 56 | end 57 | 58 | attr_reader :name, :framework_root, :output_prefix 59 | 60 | def initialize(options, root = nil, output_prefix = nil) 61 | unless options.is_a?(Hash) 62 | Barista.deprecate! self, "initialize(name, root = nil, output_prefix = nil)", "Please use the option syntax instead." 63 | options = { 64 | :name => options, 65 | :root => root, 66 | :output_prefix => output_prefix 67 | } 68 | end 69 | # actually setup the framework. 70 | check_options! options, :name, :root 71 | @name = options[:name].to_s 72 | @output_prefix = options[:output_prefix] 73 | @framework_root = File.expand_path(options[:root].to_s) 74 | self.output_root = options[:output_root] 75 | end 76 | 77 | def coffeescripts 78 | Dir[coffeescript_glob_path] 79 | end 80 | 81 | def javascripts 82 | Dir[javascript_glob_path] 83 | end 84 | 85 | def coffeescript_glob_path 86 | @coffeescript_glob_path ||= File.join(@framework_root, "**", "*.coffee") 87 | end 88 | 89 | def javascript_glob_path 90 | @javascript_glob_path ||= File.join(@framework_root, "**", "*.js") 91 | end 92 | 93 | def short_name(script) 94 | short_name = remove_prefix script, @framework_root 95 | File.join(*[@output_prefix, short_name].compact) 96 | end 97 | 98 | def exposed_coffeescripts 99 | coffeescripts.map { |script| short_name(script) } 100 | end 101 | 102 | def exposed_javascripts 103 | javascripts.map { |script| short_name(script) } 104 | end 105 | 106 | def output_prefix=(value) 107 | value = value.to_s.gsub(/(^\/|\/$)/, '').strip 108 | @output_prefix = value.empty? ? nil : value 109 | end 110 | 111 | def full_path_for(name) 112 | full_path = File.join(@framework_root, remove_prefix(name, @output_prefix.to_s)) 113 | File.exist?(full_path) ? full_path : nil 114 | end 115 | 116 | def output_root 117 | @output_root || Barista.output_root 118 | end 119 | 120 | def output_root=(value) 121 | if value.nil? 122 | @output_root = nil 123 | else 124 | @output_root = Pathname(value.to_s) 125 | end 126 | end 127 | 128 | def output_path_for(file, format = 'js') 129 | # Strip the leading slashes 130 | file = file.to_s.gsub(/^\/+/, '') 131 | output_root.join(file).to_s.gsub(/\.[^\.]+$/, ".#{format}") 132 | end 133 | 134 | protected 135 | 136 | def remove_prefix(path, prefix) 137 | path.to_s.gsub(/^#{Regexp.escape(prefix.to_s)}\/?/, '') 138 | end 139 | 140 | def check_options!(options, *keys) 141 | keys.each do |option| 142 | raise ArgumentError, "#{option.inspect} is a required options." if options[option].nil? 143 | end 144 | end 145 | 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /lib/barista/haml_filter.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module HamlFilter 3 | module CoffeeScript 4 | 5 | def render_with_options(text, options) 6 | type = render_type 7 | case type 8 | when :coffeescript 9 | content_type = 'text/coffeescript' 10 | cdata_wrapper = '#%s' 11 | inner = text 12 | when :javascript 13 | content_type = 'text/javascript' 14 | cdata_wrapper = '//%s' 15 | inner = Barista::Compiler.compile(text) 16 | end 17 | output = [] 18 | output << "" 23 | output.join("\n") 24 | end 25 | 26 | def render_type 27 | Barista.embedded_interpreter? ? :coffeescript : :javascript 28 | end 29 | 30 | def content_type(render_type) 31 | Barista::Server::CONTENT_TYPE_MAPPING[render_type] 32 | end 33 | 34 | end 35 | 36 | def self.setup 37 | if defined?(Haml) 38 | require 'haml/filters' 39 | CoffeeScript.module_eval { include Haml::Filters::Base } 40 | end 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /lib/barista/helpers.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Helpers 3 | 4 | def coffeescript_interpreter_js 5 | return if defined?(@coffeescript_embedded) && @coffeescript_embedded 6 | check_for_helper_method! :javascript_include_tag 7 | @coffeescript_embedded = true 8 | if Barista.embedded_interpreter? 9 | javascript_include_tag 'coffeescript' 10 | end 11 | end 12 | 13 | def coffeescript_include_tag(*names) 14 | check_for_helper_method! :javascript_include_tag 15 | if Barista.embedded_interpreter? 16 | output = defined?(ActiveSupport::SafeBuffer) ? ActiveSupport::SafeBuffer.new : "" 17 | output << coffeescript_interpreter_js 18 | check_for_helper_method! :content_tag 19 | Array(names).each do |name| 20 | output << "\n" 21 | output << content_tag(:script, '', :type => 'text/coffeescript', :src => normalise_coffeescript_path(name.to_s)) 22 | end 23 | output 24 | else 25 | javascript_include_tag(*names) 26 | end 27 | end 28 | 29 | def coffeescript_tag(code, html_options = {}) 30 | check_for_helper_method! :javascript_tag 31 | if Barista.embedded_interpreter? 32 | check_for_helper_method! :content_tag 33 | output = defined?(ActiveSupport::SafeBuffer) ? ActiveSupport::SafeBuffer.new : "" 34 | output << coffeescript_interpreter_js 35 | embed = "\n#\n" 36 | embed = embed.html_safe if embed.respond_to?(:html_safe) 37 | output << content_tag(:script, embed, html_options.merge(:type => 'text/coffeescript')) 38 | output 39 | else 40 | javascript_tag Barista::Compiler.compile(code), html_options 41 | end 42 | end 43 | 44 | protected 45 | 46 | def normalise_coffeescript_path(path) 47 | if respond_to?(:compute_public_path) 48 | compute_public_path path, 'coffeescript', 'coffee' 49 | else 50 | path = path.gsub(/\.(js|coffee)$/, '') + '.coffee' 51 | path = "/coffeescripts/#{path}" unless path =~ /^\// 52 | path 53 | end 54 | end 55 | 56 | def check_for_helper_method!(name) 57 | raise "Please make sure #{name} is available." unless respond_to?(name) 58 | end 59 | 60 | end 61 | end -------------------------------------------------------------------------------- /lib/barista/hooks.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | class Hooks 3 | 4 | def initialize 5 | @callbacks = Hash.new { |h,k| h[k] = [] } 6 | end 7 | 8 | def on(name, &blk) 9 | @callbacks[name.to_sym] << blk 10 | end 11 | 12 | def invoke(name, *args) 13 | @callbacks[name.to_sym].each do |callback| 14 | break if callback.call(*args) == false 15 | end 16 | nil 17 | end 18 | 19 | def has_hook?(name) 20 | @callbacks.has_key?(name) 21 | end 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/barista/integration.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Integration 3 | 4 | autoload :Rails2, 'barista/integration/rails2' 5 | autoload :Rails3, 'barista/integration/rails3' 6 | autoload :Sinatra, 'barista/integration/sinatra' 7 | 8 | def self.setup 9 | setup_rails if defined?(Rails) 10 | end 11 | 12 | def self.setup_rails 13 | case Rails::VERSION::MAJOR 14 | when 3 15 | Rails3 16 | when 2 17 | # We need to manually call the initialiser stuff in Rails 2. 18 | Rails2.setup 19 | end 20 | end 21 | 22 | end 23 | end -------------------------------------------------------------------------------- /lib/barista/integration/rails2.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Integration 3 | module Rails2 4 | 5 | def self.setup 6 | ActionController::Dispatcher.middleware.tap do |middleware| 7 | middleware.use Barista::Filter if Barista.add_filter? 8 | middleware.use Barista::Server::Proxy 9 | end 10 | Barista.setup_defaults 11 | ActionController::Base.helper Barista::Helpers 12 | end 13 | 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /lib/barista/integration/rails3.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Integration 3 | module Rails3 4 | class Railtie < Rails::Railtie 5 | 6 | rake_tasks do 7 | load Barista.library_root.join('barista/tasks/barista.rake').to_s 8 | end 9 | 10 | initializer 'barista.wrap_filter' do 11 | config.app_middleware.use Barista::Filter if Barista.add_filter? 12 | config.app_middleware.use Barista::Server::Proxy 13 | end 14 | 15 | initializer 'barista.defaults' do 16 | Barista.setup_defaults 17 | end 18 | 19 | initializer 'barista.helpers' do 20 | ActionController::Base.helper Barista::Helpers 21 | end 22 | 23 | end 24 | end 25 | end 26 | end -------------------------------------------------------------------------------- /lib/barista/integration/sinatra.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Integration 3 | module Sinatra 4 | 5 | def self.registered(app) 6 | app.configure do |inner_app| 7 | setup_defaults inner_app 8 | inner_app.use Barista::Filter if Barista.add_filter? 9 | inner_app.use Barista::Server::Proxy 10 | Barista.setup_defaults 11 | end 12 | 13 | end 14 | 15 | def self.setup_defaults(app) 16 | Barista.configure do |c| 17 | c.env = app.environment.to_s 18 | end 19 | end 20 | 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /lib/barista/rake_task.rb: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/tasklib' 3 | 4 | module Barista 5 | class RakeTask < ::Rake::TaskLib 6 | include Rake::DSL 7 | 8 | attr_writer :namespace, :task_name 9 | attr_writer :environment, :input_directory, :output_directory, :rails 10 | 11 | def initialize 12 | yield self if block_given? 13 | @namespace ||= :barista 14 | @task_name ||= :brew 15 | @task_name = @task_name.to_sym 16 | @rails = defined?(Rails) if @rails.nil? 17 | 18 | task_declaration = (@rails ? {@task_name => :environment} : @task_name) 19 | 20 | namespace @namespace do 21 | desc "Compiles all CoffeeScript sources to JavaScript" 22 | task task_declaration do 23 | setup_barista 24 | check_availability 25 | puts "Compiling all CoffeeScripts to their JavaScript equivalent." 26 | Barista.compile_all! true, false 27 | end 28 | end 29 | end 30 | 31 | # Proxy methods for rake tasks 32 | 33 | def respond_to?(method, include_private = false) 34 | super || Barista.respond_to?(method, include_private) 35 | end 36 | 37 | def method_missing(method, *args, &blk) 38 | if Barista.respond_to?(method) 39 | Barista.send method, *args, &blk 40 | else 41 | super 42 | end 43 | end 44 | 45 | private 46 | 47 | def setup_barista 48 | require 'barista' 49 | 50 | Barista.env = @environment if @environment 51 | if @input_directory 52 | Barista.root = File.expand_path(@input_directory, Dir.pwd) 53 | end 54 | if @output_directory 55 | Barista.output_root = File.expand_path(@output_directory, Dir.pwd) 56 | end 57 | end 58 | 59 | def check_availability 60 | unless Barista::Compiler.available? 61 | warn Barista::Compiler::UNAVAILABLE_MESSAGE 62 | exit 1 63 | end 64 | end 65 | 66 | end 67 | end -------------------------------------------------------------------------------- /lib/barista/server.rb: -------------------------------------------------------------------------------- 1 | require 'rack/utils' 2 | 3 | module Barista 4 | class Server 5 | 6 | CACHE_FOR_SECONDS = 300 7 | JS_CONTENT_TYPE = 'text/javascript' 8 | COFFEE_CONTENT_TYPE = 'text/coffeescript' 9 | PATH_REGEXP = /^\/(coffee|java)scripts\// 10 | 11 | # Extensions to the type. 12 | EXTENSION_MAPPING = { 13 | '.coffee' => :coffeescript, 14 | '.js' => :javascript 15 | } 16 | 17 | # Content types for responses. 18 | CONTENT_TYPE_MAPPING = { 19 | :coffeescript => COFFEE_CONTENT_TYPE, 20 | :javascript => JS_CONTENT_TYPE 21 | } 22 | 23 | class Proxy 24 | 25 | def initialize(app) 26 | @app = app 27 | @server = Server.new 28 | end 29 | 30 | def call(env) 31 | result = @server.call(env) 32 | if result[0] == 404 33 | @app.call(env) 34 | else 35 | result 36 | end 37 | end 38 | 39 | end 40 | 41 | def initialize 42 | # Cache the responses for common errors. 43 | forbidden 44 | not_found 45 | end 46 | 47 | def call(env) 48 | dup._call(env) 49 | end 50 | 51 | def _call(env) 52 | @path_info = Rack::Utils.unescape(env['PATH_INFO'].to_s) 53 | return not_found unless @path_info =~ PATH_REGEXP 54 | # Strip the prefix. 55 | @path_info.gsub! PATH_REGEXP, '' 56 | # Check it's a valid path. 57 | return forbidden if @path_info.include?('..') 58 | 59 | # If coffeescript.js is the request, render the coffeescript compiler code. 60 | if @path_info == 'coffeescript.js' 61 | return response_for_text(CoffeeScript::Source.contents) 62 | end 63 | # Look up the type of the file based off of the extension. 64 | @result_type = EXTENSION_MAPPING[File.extname(@path_info)] 65 | return not_found if @result_type.nil? || (@result_type == :coffeescript && !Barista.embedded_interpreter?) 66 | # Process the difference in content type. 67 | content, last_modified = Barista::Compiler.compile_as(@path_info, @result_type) 68 | if content.nil? 69 | not_found 70 | else 71 | response_for_text content, CONTENT_TYPE_MAPPING[@result_type], last_modified 72 | end 73 | end 74 | 75 | protected 76 | 77 | def forbidden 78 | @_forbidden_response ||= begin 79 | body = "Forbidden\n" 80 | [403, { 81 | 'Content-Type' => 'text/plain', 82 | 'Content-Length' => Rack::Utils.bytesize(body).to_s, 83 | 'X-Cascade' => 'pass' 84 | }, [body]] 85 | end 86 | end 87 | 88 | def not_found 89 | @_not_found_response ||= begin 90 | body = "Not Found\n" 91 | [404, { 92 | 'Content-Type' => 'text/plain', 93 | 'Content-Length' => Rack::Utils.bytesize(body).to_s, 94 | 'X-Cascade' => 'pass' 95 | }, [body]] 96 | end 97 | end 98 | 99 | def response_for_text(content, content_type = 'text/javascript', modified_at = nil) 100 | headers = { 101 | 'Content-Type' => content_type, 102 | 'Content-Length' => Rack::Utils.bytesize(content).to_s, 103 | 'Cache-Control' => "public, max-age=#{CACHE_FOR_SECONDS}" 104 | } 105 | headers.merge!('Last-Modified' => modified_at.httpdate) unless modified_at.nil? 106 | [200, headers, [content]] 107 | end 108 | 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/barista/tasks.rb: -------------------------------------------------------------------------------- 1 | load 'barista/tasks/barista.rake' -------------------------------------------------------------------------------- /lib/barista/tasks/barista.rake: -------------------------------------------------------------------------------- 1 | require 'barista/rake_task' 2 | 3 | Barista::RakeTask.new do |t| 4 | t.namespace = :barista 5 | t.task_name = :brew 6 | t.rails = true 7 | end 8 | -------------------------------------------------------------------------------- /lib/barista/version.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Version 3 | MAJOR = 1 4 | MINOR = 3 5 | PATCH = 0 6 | STRING = [MAJOR, MINOR, PATCH].join(".") 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/generators/barista/install/USAGE: -------------------------------------------------------------------------------- 1 | To copy a Barista initializer to your Rails App, with some configuration values, just do: 2 | 3 | rails generate barista:install 4 | -------------------------------------------------------------------------------- /lib/generators/barista/install/install_generator.rb: -------------------------------------------------------------------------------- 1 | module Barista 2 | module Generators 3 | class InstallGenerator < Rails::Generators::Base 4 | 5 | source_root File.expand_path("templates", File.dirname(__FILE__)) 6 | 7 | def create_initializer 8 | copy_file 'initializer.rb', 'config/initializers/barista_config.rb' 9 | end 10 | 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /lib/generators/barista/install/templates/initializer.rb: -------------------------------------------------------------------------------- 1 | # Configure barista. 2 | Barista.configure do |c| 3 | 4 | # Change the root to use app/scripts 5 | # c.root = Rails.root.join("app", "scripts") 6 | 7 | # Change the output root, causing Barista to compile into public/coffeescripts 8 | # c.output_root = Rails.root.join("public", "coffeescripts") 9 | # 10 | # Disable auto compile, use generated file directly: 11 | # c.auto_compile = false 12 | 13 | # Add a new framework: 14 | 15 | # c.register :tests, :root => Rails.root.join('test', 'coffeescript'), :output_prefix => 'test' 16 | 17 | # Disable wrapping in a closure: 18 | # c.bare = true 19 | # ... or ... 20 | # c.bare! 21 | 22 | # Change the output root for a framework: 23 | 24 | # c.change_output_prefix! 'framework-name', 'output-prefix' 25 | 26 | # or for all frameworks... 27 | 28 | # c.each_framework do |framework| 29 | # c.change_output_prefix! framework, "vendor/#{framework.name}" 30 | # end 31 | 32 | # or, prefix the path for the app files: 33 | 34 | # c.change_output_prefix! :default, 'my-app-name' 35 | 36 | # or, change the directory the framework goes into full stop: 37 | 38 | # c.change_output_prefix! :tests, Rails.root.join('spec', 'javascripts') 39 | 40 | # or, hook into the compilation: 41 | 42 | # c.before_compilation { |path| puts "Barista: Compiling #{path}" } 43 | # c.on_compilation { |path| puts "Barista: Successfully compiled #{path}" } 44 | # c.on_compilation_error { |path, output| puts "Barista: Compilation of #{path} failed with:\n#{output}" } 45 | # c.on_compilation_with_warning { |path, output| puts "Barista: Compilation of #{path} had a warning:\n#{output}" } 46 | 47 | # Turn off preambles and exceptions on failure: 48 | 49 | # c.verbose = false 50 | 51 | # Or, make sure it is always on 52 | # c.verbose! 53 | 54 | # If you want to use a custom JS file, you can as well 55 | # e.g. vendoring CoffeeScript in your application: 56 | # c.js_path = Rails.root.join('public', 'javascripts', 'coffee-script.js') 57 | 58 | # Make helpers and the HAML filter output coffee-script instead of the compiled JS. 59 | # Used in combination with the coffeescript_interpreter_js helper in Rails. 60 | # c.embedded_interpreter = true 61 | 62 | end 63 | -------------------------------------------------------------------------------- /lib/generators/barista_install_generator.rb: -------------------------------------------------------------------------------- 1 | # Remove this file after deprecation 2 | if caller.none? { |l| l =~ %r{lib/rails/generators\.rb:(\d+):in `lookup!'$} } 3 | warn "[WARNING] `rails g barista_install` is deprecated, please use `rails g barista:install` instead." 4 | end -------------------------------------------------------------------------------- /spec/assets/alert.coffee: -------------------------------------------------------------------------------- 1 | hello -> 2 | alert 'hello world' 3 | 4 | -------------------------------------------------------------------------------- /spec/barista_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Barista do 4 | 5 | context 'hooks' 6 | 7 | context 'configuration' 8 | 9 | context 'setting app_root' do 10 | it "defaults to Rails.root" do 11 | Barista::app_root.should == Rails.root 12 | end 13 | it "can be set to another directory" do 14 | new_path = File.expand_path("../../public/javascripts", __FILE__) 15 | Barista.configure do |c| 16 | c.app_root = new_path 17 | end 18 | Barista::app_root.to_s.should == new_path 19 | end 20 | end 21 | 22 | context 'preamble' do 23 | before(:each) do 24 | @assets_path = File.expand_path("../assets", __FILE__) 25 | @public_path = File.expand_path("../public", __FILE__) 26 | Barista.configure do |c| 27 | c.root = @assets_path 28 | c.output_root = @public_path 29 | end 30 | FileUtils.rm_rf @public_path if File.directory?(@public_path) 31 | end 32 | it "is written by default" do 33 | Barista.add_preamble = true 34 | Barista::compile_all! 35 | alert_js = IO.read(File.join(@public_path, "alert.js")) 36 | alert_js.should include "DO NOT MODIFY" 37 | end 38 | it "can be disabled" do 39 | Barista.add_preamble = false 40 | Barista::compile_all! 41 | alert_js = IO.read(File.join(@public_path, "alert.js")) 42 | alert_js.should_not include "DO NOT MODIFY" 43 | end 44 | end 45 | 46 | context 'compiling files' 47 | 48 | context 'compiling all' do 49 | before(:each) do 50 | @assets_path = File.expand_path("../assets", __FILE__) 51 | @public_path = File.expand_path("../public", __FILE__) 52 | Barista.configure do |c| 53 | c.root = @assets_path 54 | c.output_root = @public_path 55 | end 56 | FileUtils.rm_rf @public_path if File.directory?(@public_path) 57 | end 58 | it "compiles nothing" do 59 | lambda { Barista::compile_all! false, false }.should_not raise_error 60 | end 61 | it "produces alert.js" do 62 | Barista::compile_all! 63 | File.exist?(File.join(@public_path, "alert.js")).should be_true 64 | end 65 | it "logs when verbose is true" do 66 | log = StringIO.new 67 | Barista.logger = Logger.new(log) 68 | Barista.compile_all! 69 | log.string.should =~ /\[Barista\].+/ 70 | end 71 | it "does not log when verbose is false" do 72 | log = StringIO.new 73 | Barista.logger = Logger.new(log) 74 | Barista.verbose = false 75 | Barista.compile_all! 76 | log.string.should be_empty 77 | end 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | ENV['RAILS_ENV'] ||= 'test' 4 | ENV['RAILS_ROOT'] ||= File.expand_path("..", __FILE__) 5 | 6 | require "action_controller/railtie" 7 | require "action_mailer/railtie" 8 | require "active_resource/railtie" 9 | 10 | require 'rspec/rails' 11 | 12 | Bundler.require(:default, Rails.env) if defined?(Bundler) 13 | 14 | class Application < Rails::Application; end 15 | 16 | require 'barista' 17 | 18 | RSpec.configure do |config| 19 | config.mock_with :rspec 20 | config.expect_with :rspec 21 | end 22 | 23 | --------------------------------------------------------------------------------