epuber-stylus 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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