epuber-stylus 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/LICENSE +22 -0
  4. data/README.md +154 -0
  5. data/lib/epuber-stylus.rb +174 -0
  6. data/lib/epuber-stylus/import_processor.rb +71 -0
  7. data/lib/epuber-stylus/railtie.rb +32 -0
  8. data/lib/epuber-stylus/runtime.rb +54 -0
  9. data/lib/epuber-stylus/runtime/compiler.js +40 -0
  10. data/lib/epuber-stylus/runtime/runner.js +20 -0
  11. data/lib/epuber-stylus/sprockets.rb +58 -0
  12. data/lib/epuber-stylus/tilt.rb +2 -0
  13. data/lib/epuber-stylus/tilt/rails.rb +51 -0
  14. data/lib/epuber-stylus/tilt/stylus.rb +52 -0
  15. data/lib/epuber-stylus/version.rb +3 -0
  16. data/lib/rails/generators/epuber-stylus/assets/assets_generator.rb +13 -0
  17. data/lib/rails/generators/epuber-stylus/assets/templates/stylesheet.css.styl +3 -0
  18. data/lib/rails/generators/epuber-stylus/scaffold/scaffold_generator.rb +11 -0
  19. data/spec/generators/assets_generator_spec.rb +11 -0
  20. data/spec/generators/controller_generator_spec.rb +12 -0
  21. data/spec/generators/scaffold_generator_spec.rb +17 -0
  22. data/spec/import_processor_spec.rb +67 -0
  23. data/spec/rails_spec.rb +46 -0
  24. data/spec/runtime_spec.rb +11 -0
  25. data/spec/spec_helper.rb +30 -0
  26. data/spec/sprockets_spec.rb +36 -0
  27. data/spec/stylus_spec.rb +118 -0
  28. data/spec/support/generators/test_case.rb +59 -0
  29. data/spec/support/generators/tmp/app/controllers/posts_controller.rb +58 -0
  30. data/spec/support/generators/tmp/app/helpers/posts_helper.rb +2 -0
  31. data/spec/support/generators/tmp/config/routes.rb +0 -0
  32. data/spec/support/helpers.rb +63 -0
  33. data/spec/support/matchers.rb +5 -0
  34. data/spec/tilt/rails_spec.rb +64 -0
  35. data/spec/tilt/stylus_spec.rb +24 -0
  36. metadata +137 -0
@@ -0,0 +1,54 @@
1
+ require 'execjs'
2
+
3
+ module Stylus
4
+ # Internal: Module responsible for the ExecJS interaction. Besides handling
5
+ # the compilation execution, this module provide a runtime validation to ensure
6
+ # that the Node.JS binary is available to use.
7
+ module Runtime
8
+ # Internal: Calls a specific function on the Node.JS context.
9
+ #
10
+ # Example
11
+ # exec('version', 2) # => '2'
12
+ #
13
+ # Returns The function returned value.
14
+ def exec(*arguments)
15
+ check_availability!
16
+ context.call(*arguments)
17
+ end
18
+
19
+ private
20
+ # Internal: Queries the runtime for it's availability and raises a 'RuntimeError'
21
+ # if the runtime isn't available. Otherwise, this is a noop.
22
+ def check_availability!
23
+ unless runtime.available?
24
+ message = 'The Node.JS runtime is not available to Stylus.'
25
+ message << 'Ensure that the "node" (or "nodejs") executable is present in your $PATH.'
26
+ raise RuntimeError, message
27
+ end
28
+ end
29
+
30
+ # Internal: Compile the Stylus compilation script into a execution context
31
+ # to execute functions into.
32
+ #
33
+ # Returns the compiled context.
34
+ def context
35
+ @context ||= runtime.compile(script)
36
+ end
37
+
38
+ # Internal: The custom compilation script body.
39
+ def script
40
+ File.read(File.expand_path('../runtime/compiler.js',__FILE__))
41
+ end
42
+
43
+ # Internal: Create the ExecJS external runtime with a old runner script that
44
+ # maintains the state of 'require', so we can use it to load modules like on
45
+ # any Node.JS program.
46
+ def runtime
47
+ @runtime ||= ExecJS::ExternalRuntime.new(
48
+ name: 'Node.js (V8)',
49
+ command: ['nodejs', 'node'],
50
+ runner_path: File.expand_path('../runtime/runner.js', __FILE__)
51
+ )
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,40 @@
1
+ var stylus = require('stylus');
2
+
3
+ function compile(str, options, plugins, imports, definitions) {
4
+ var style = stylus(str, options);
5
+ var output = '';
6
+
7
+ for(var name in plugins) {
8
+ var fn = require(name);
9
+ style.use(fn(plugins[name]));
10
+ }
11
+
12
+ imports.forEach(function(path) {
13
+ style.import(path);
14
+ })
15
+
16
+ for(var definition in definitions) {
17
+ obj = definitions[definition];
18
+ value = obj.value
19
+
20
+ if(obj.literal) {
21
+ value = new stylus.nodes.Literal(value);
22
+ }
23
+
24
+ style.define(definition, value);
25
+ }
26
+
27
+ style.render(function(error, css) {
28
+ if(error) throw error;
29
+ output = css;
30
+ })
31
+ return output;
32
+ }
33
+
34
+ function convert(str) {
35
+ return stylus.convertCSS(str);
36
+ }
37
+
38
+ function version() {
39
+ return stylus.version;
40
+ }
@@ -0,0 +1,20 @@
1
+ (function(program, execJS) { execJS(program) })(function() { #{source}
2
+ }, function(program) {
3
+ var output, print = function(string) {
4
+ process.stdout.write('' + string);
5
+ };
6
+ try {
7
+ result = program();
8
+ if (typeof result == 'undefined' && result !== null) {
9
+ print('["ok"]');
10
+ } else {
11
+ try {
12
+ print(JSON.stringify(['ok', result]));
13
+ } catch (err) {
14
+ print('["err"]');
15
+ }
16
+ }
17
+ } catch (err) {
18
+ print(JSON.stringify(['err', '' + err]));
19
+ }
20
+ });
@@ -0,0 +1,58 @@
1
+ require 'epuber-stylus'
2
+ require 'epuber-stylus/tilt/stylus'
3
+ require 'epuber-stylus/tilt/rails'
4
+ require 'epuber-stylus/import_processor'
5
+ # Public: The setup logic to configure both Stylus and Sprockets on any
6
+ # kind of application - Rails, Sinatra or Rack.
7
+ #
8
+ # Example
9
+ #
10
+ # # mounting Sprockets as a Rack application with Stylus
11
+ # assets = Sprockets::Environment.new
12
+ # assets.append_path 'stylesheets'
13
+ # Stylus.setup(assets)
14
+ # run assets.index
15
+ module Stylus
16
+ # Public: Configure a Sprockets environment with Stylus Tilt engine
17
+ # and the ImportProcessor. It also accept a configuration Hash to
18
+ # setup the load path and flags of the Stylus module.
19
+ #
20
+ # environment - A instance of Sprockets::Environment.
21
+ # options - The configuration Hash (default: {})
22
+ # :rails - a flag to inform that the current application is a Rails app.
23
+ # :paths - An Array of paths to use the '@import' directive, defaults
24
+ # to the `paths` attribute on the environment object.
25
+ # :debug - The Boolean value for the debug flag.
26
+ # :compress - The Boolean value for the debug compress.
27
+ #
28
+ # Example
29
+ #
30
+ # assets = Sprockets::Environment.new
31
+ # Stylus.setup(assets, compress: settings.production?)
32
+ #
33
+ # Returns nothing.
34
+ def self.setup(environment, options = {})
35
+ paths = options[:paths] || environment.paths
36
+
37
+ Stylus.paths.concat(paths)
38
+
39
+ Stylus.debug = options.fetch(:debug, Stylus.debug)
40
+ Stylus.compress = options.fetch(:compress, Stylus.compress)
41
+ template = detect_template_hander(options)
42
+ environment.register_engine('.styl', template)
43
+ environment.register_preprocessor('text/css', Stylus::ImportProcessor)
44
+ end
45
+
46
+ # Internal: Gets the desired Tilt template handler to the current configuration.
47
+ # If a 'rails' option is present then the Rails specific template will be
48
+ # returned instead of the default Stylus Tilt template.
49
+ #
50
+ # Returns a Tilt::Template children class.
51
+ def self.detect_template_hander(options = {})
52
+ if options[:rails]
53
+ Stylus::Rails::StylusTemplate
54
+ else
55
+ Tilt::StylusTemplate
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,2 @@
1
+ # require compatibility with older versions.
2
+ require 'epuber-stylus/tilt/stylus'
@@ -0,0 +1,51 @@
1
+ require 'epuber-stylus/tilt/stylus'
2
+ # Public: A Tilt template to compile Stylus stylesheets with asset helpers.
3
+ module Stylus
4
+ module Rails
5
+ class StylusTemplate < ::Tilt::StylusTemplate
6
+
7
+ # Public: The default mime type for stylesheets.
8
+ self.default_mime_type = 'text/css'
9
+
10
+ # Internal: Appends stylus mixin for asset_url and asset_path support
11
+ def evaluate(scope, locals, &block)
12
+ @data = build_mixin_body(scope) + data
13
+ super
14
+ end
15
+
16
+ protected
17
+
18
+ # Internal: Builds body of a mixin
19
+ #
20
+ # Returns string representation of a mixin with asset helper functions
21
+ def build_mixin_body(scope)
22
+ @mixin_body ||= if assets_hash(scope).values.all? {|value| value != '' }
23
+ <<-STYL
24
+ asset-url(key)
25
+ return pair[1] if pair[0] == key for pair in #{assets_hash(scope)[:url]} ()
26
+ asset-path(key)
27
+ return pair[1] if pair[0] == key for pair in #{assets_hash(scope)[:path]} ()
28
+ STYL
29
+ else
30
+ ''
31
+ end
32
+ end
33
+
34
+ # Internal: Construct Hash with absolute/relative paths in stylus syntax.
35
+ #
36
+ # Returns string representations of hash in Stylus syntax
37
+ def assets_hash(scope)
38
+ @assets_hash ||= scope.environment.each_logical_path.each_with_object({ :url => '', :path => '' }) do |logical_path, assets_hash|
39
+ unless File.extname(logical_path) =~ /^(\.(css|js)|)$/
40
+ path_to_asset = scope.path_to_asset(logical_path)
41
+ assets_hash[:url] << "('#{logical_path}' url(\"#{path_to_asset}\")) "
42
+ assets_hash[:path] << "('#{logical_path}' \"#{path_to_asset}\") "
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
51
+ Tilt.register ::Stylus::Rails::StylusTemplate, 'styl'
@@ -0,0 +1,52 @@
1
+ require 'tilt'
2
+ # Public: A Tilt template to compile Stylus stylesheets.
3
+ #
4
+ # Examples
5
+ #
6
+ # template = Tilt::StylusTemplate.new { |t| File.read('app.styl') }
7
+ # template.render # => the compiled CSS from the app.styl file.
8
+ #
9
+ # Options should assigned on the template constructor.
10
+ # template = Tilt::StylusTemplate.new(compress: true) { |t| File.read('app.styl') }
11
+ # template.render # => the compiled CSS with compression enabled.
12
+ module Tilt
13
+ class StylusTemplate < Template
14
+
15
+ # Public: The default mime type for stylesheets.
16
+ self.default_mime_type = 'text/css'
17
+
18
+ # Internal: Checks if the Stylus module has been properly defined.
19
+ #
20
+ # Returns true if the 'Stylus' module is present.
21
+ def self.engine_initialized?
22
+ defined? ::Stylus
23
+ end
24
+
25
+ # Internal: Require the 'stylus' file to load the Stylus module.
26
+ #
27
+ # Returns nothing.
28
+ def initialize_engine
29
+ require_template_library 'stylus'
30
+ end
31
+
32
+ # Internal: Caches the filename as an option entry if it's present.
33
+ #
34
+ # Returns nothing.
35
+ def prepare
36
+ if self.file
37
+ options[:filename] ||= self.file
38
+ end
39
+ end
40
+
41
+ # Internal: Compile the template Stylus using this instance options.
42
+ # The current 'scope' and given 'locals' are ignored and the output
43
+ # is cached.
44
+ #
45
+ # Returns a String with the compiled stylesheet with CSS syntax.
46
+ def evaluate(scope, locals, &block)
47
+ @output ||= Stylus.compile(data, options)
48
+ end
49
+ end
50
+ end
51
+
52
+ Tilt.register Tilt::StylusTemplate, 'styl'
@@ -0,0 +1,3 @@
1
+ module Stylus
2
+ VERSION = '1.1.0'
3
+ end
@@ -0,0 +1,13 @@
1
+ require 'rails/generators/named_base'
2
+
3
+ module Stylus
4
+ module Generators
5
+ class AssetsGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ def copy_stylus
9
+ template 'stylesheet.css.styl', File.join('app/assets/stylesheets', class_path, "#{file_name}.css.styl")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ // Place all the styles related to the matching controller here.
2
+ // They will automatically be included in application.css.
3
+ // You can use Stylus syntax here: http://learnboost.github.io/stylus
@@ -0,0 +1,11 @@
1
+ require 'rails/generators/css/scaffold/scaffold_generator'
2
+
3
+ module Stylus
4
+ module Generators
5
+ # Just inherit from the original Generator from Rails
6
+ # because `scaffold.css` it's just to help people to start up
7
+ # their Rails applications.
8
+ class ScaffoldGenerator < Css::Generators::ScaffoldGenerator
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'rails/generators/epuber-stylus/assets/assets_generator'
3
+
4
+ describe Stylus::Generators::AssetsGenerator do
5
+ include Generators::TestCase
6
+ arguments %w(posts)
7
+
8
+ it 'generates a .styl file' do
9
+ expect(file('app/assets/stylesheets/posts.css.styl')).to exist
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'rails/generators/rails/controller/controller_generator'
3
+ require 'rails/generators/epuber-stylus/assets/assets_generator'
4
+
5
+ describe Rails::Generators::ControllerGenerator do
6
+ include Generators::TestCase
7
+ arguments %w(posts --stylesheet-engine=stylus)
8
+
9
+ it 'generates a .styl file' do
10
+ expect(file('app/assets/stylesheets/posts.css.styl')).to exist
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'rails/generators/rails/scaffold/scaffold_generator'
3
+ require 'rails/generators/epuber-stylus/assets/assets_generator'
4
+ require 'rails/generators/epuber-stylus/scaffold/scaffold_generator'
5
+
6
+ describe Rails::Generators::ScaffoldGenerator do
7
+ include Generators::TestCase
8
+ arguments %w(posts --stylesheet-engine=stylus --orm=false)
9
+
10
+ it 'generates the default scaffold stylesheet' do
11
+ expect(file('app/assets/stylesheets/scaffold.css')).to exist
12
+ end
13
+
14
+ it 'generates a named .styl file' do
15
+ expect(file('app/assets/stylesheets/posts.css.styl')).to exist
16
+ end
17
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stylus::ImportProcessor do
4
+ let(:env) do
5
+ Sprockets::Environment.new do |assets|
6
+ assets.append_path fixture_root
7
+ Stylus.setup(assets)
8
+ end
9
+ end
10
+
11
+ it 'adds an imported stylesheet as a dependency' do
12
+ asset = env['import']
13
+ dependencies = dependencies_on(asset)
14
+
15
+ expect(dependencies).to include(fixture_path('mixins/vendor'))
16
+ end
17
+
18
+ context 'nested dependencies' do
19
+ it 'walks the dependency chain of imported files' do
20
+ asset = env['nested_import']
21
+ dependencies = dependencies_on(asset)
22
+
23
+ expect(dependencies).to include(fixture_path('mixins/nested'))
24
+ expect(dependencies).to include(fixture_path('mixins/vendor'))
25
+ end
26
+
27
+ it "adds files referenced in a directory's index file" do
28
+ asset = env['indexed_nested_import']
29
+ dependencies = dependencies_on(asset)
30
+
31
+ expect(dependencies).to include(fixture_path('indexed/index'))
32
+ expect(dependencies).to include(fixture_path('indexed/first_dep'))
33
+ expect(dependencies).to include(fixture_path('indexed/second_dep'))
34
+ end
35
+
36
+ it 'walks dependency chains through indexes' do
37
+ asset = env['indexed_recursive_import']
38
+ dependencies = dependencies_on(asset)
39
+
40
+ expect(dependencies).to include(fixture_path('indexed_nested_import'))
41
+ expect(dependencies).to include(fixture_path('indexed/index'))
42
+ expect(dependencies).to include(fixture_path('indexed/first_dep'))
43
+ expect(dependencies).to include(fixture_path('indexed/second_dep'))
44
+ end
45
+ end
46
+
47
+ it 'does not process non-stylus files' do
48
+ source = '@import "nib"'
49
+ template = Stylus::ImportProcessor.new('stylesheet.scss') { source }
50
+ context = double
51
+
52
+ expect(context).to_not receive(:depend_on)
53
+ template.render(context)
54
+ end
55
+
56
+ it 'swallows errors from files outside the Sprockets paths' do
57
+ source = '@import "nib"'
58
+ template = Stylus::ImportProcessor.new { source }
59
+ sprockets = double
60
+ expect(sprockets).to receive(:resolve).twice.and_raise(::Sprockets::FileNotFound)
61
+ expect(template).to receive(:stylus_file?).and_return(true)
62
+
63
+ expect {
64
+ template.render(sprockets)
65
+ }.to_not raise_error
66
+ end
67
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Rails integration' do
4
+ it "copies all folders from the Sprockets load path" do
5
+ app = create_app
6
+ Stylus.paths.should include fixture_root
7
+ Stylus.paths.should == app.assets.paths
8
+ end
9
+
10
+ it 'process .styl files with the asset pipeline' do
11
+ result = fixture(:simple).last
12
+
13
+ app = create_app
14
+ app.assets['simple'].to_s.should == result
15
+ end
16
+
17
+ it 'enables @import definitions' do
18
+ result = fixture(:import).last
19
+
20
+ app = create_app
21
+ app.assets['import'].to_s.should == result
22
+ end
23
+
24
+ it 'skips debug info by default' do
25
+ app = create_app
26
+ asset = app.assets['simple']
27
+ asset.to_s.should_not match(/line 1 : #{asset.pathname}/)
28
+ end
29
+
30
+ it 'provides debug info if required' do
31
+ app = create_app(:debug => true)
32
+ asset = app.assets['simple']
33
+ asset.to_s.should match(/line 1 : #{asset.pathname}/)
34
+ end
35
+
36
+ it 'compress the output if Rails is configured to compress them too' do
37
+ result = fixture(:compressed).last
38
+
39
+ app = create_app(:compress => true)
40
+ app.assets['compressed'].to_s.should == result.rstrip
41
+ end
42
+
43
+ it 'loads the app normally even when the asset pipeline is disabled' do
44
+ pending "TODO: supress the sprockets-rails railtie to test this."
45
+ end
46
+ end