epuber-stylus 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE +22 -0
- data/README.md +154 -0
- data/lib/epuber-stylus.rb +174 -0
- data/lib/epuber-stylus/import_processor.rb +71 -0
- data/lib/epuber-stylus/railtie.rb +32 -0
- data/lib/epuber-stylus/runtime.rb +54 -0
- data/lib/epuber-stylus/runtime/compiler.js +40 -0
- data/lib/epuber-stylus/runtime/runner.js +20 -0
- data/lib/epuber-stylus/sprockets.rb +58 -0
- data/lib/epuber-stylus/tilt.rb +2 -0
- data/lib/epuber-stylus/tilt/rails.rb +51 -0
- data/lib/epuber-stylus/tilt/stylus.rb +52 -0
- data/lib/epuber-stylus/version.rb +3 -0
- data/lib/rails/generators/epuber-stylus/assets/assets_generator.rb +13 -0
- data/lib/rails/generators/epuber-stylus/assets/templates/stylesheet.css.styl +3 -0
- data/lib/rails/generators/epuber-stylus/scaffold/scaffold_generator.rb +11 -0
- data/spec/generators/assets_generator_spec.rb +11 -0
- data/spec/generators/controller_generator_spec.rb +12 -0
- data/spec/generators/scaffold_generator_spec.rb +17 -0
- data/spec/import_processor_spec.rb +67 -0
- data/spec/rails_spec.rb +46 -0
- data/spec/runtime_spec.rb +11 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/sprockets_spec.rb +36 -0
- data/spec/stylus_spec.rb +118 -0
- data/spec/support/generators/test_case.rb +59 -0
- data/spec/support/generators/tmp/app/controllers/posts_controller.rb +58 -0
- data/spec/support/generators/tmp/app/helpers/posts_helper.rb +2 -0
- data/spec/support/generators/tmp/config/routes.rb +0 -0
- data/spec/support/helpers.rb +63 -0
- data/spec/support/matchers.rb +5 -0
- data/spec/tilt/rails_spec.rb +64 -0
- data/spec/tilt/stylus_spec.rb +24 -0
- 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,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,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,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
|
data/spec/rails_spec.rb
ADDED
@@ -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
|