nov-stylus 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/stylus.rb ADDED
@@ -0,0 +1,174 @@
1
+ require 'stylus/runtime'
2
+ require 'stylus/source'
3
+ require 'stylus/version'
4
+ require 'stylus/railtie' if defined?(::Rails)
5
+ ## Stylus
6
+ #
7
+ # `stylus` is a bridge between your Ruby code and the [Stylus](https://github.com/LearnBoost/stylus)
8
+ # library that runs on Node.js. It's aims to be a replacement for the
9
+ # [stylus_rails](https://github.com/lucasmazza/stylus_rails) gem and to support the Rails 3.1 asset pipeline
10
+ # (via [Tilt](https://github.com/rtomayko/tilt)) and other scenarios,
11
+ # backed by the [ExecJS](https://github.com/sstephenson/execjs) gem.
12
+ #
13
+ ### Usage
14
+ #
15
+ # To compile a `.styl` file or an arbitrary String to .CSS using stylus, just use the `compile` method.
16
+ #
17
+ # `Stylus.compile(File.new('application.styl'))`
18
+ #
19
+ # A hash of options for the stylus API is accepted.
20
+ #
21
+ # `Stylus.compile(File.read('application.styl'), :compress => true)`
22
+ #
23
+ module Stylus
24
+ extend Runtime
25
+ class << self
26
+ @@compress = false
27
+ @@debug = false
28
+ @@paths = []
29
+ @@imports = []
30
+ @@definitions = {}
31
+ @@plugins = {}
32
+
33
+ # Stores a list of plugins to import inside `Stylus`, with an optional hash.
34
+ def use(*options)
35
+ arguments = options.last.is_a?(Hash) ? options.pop : {}
36
+ options.each do |plugin|
37
+ @@plugins[plugin] = arguments
38
+ end
39
+ end
40
+ alias :plugin :use
41
+
42
+ # Stores a list of stylesheets to import on every compile process.
43
+ def import(*paths)
44
+ if paths.any?
45
+ @@imports = @@imports.concat(paths)
46
+ end
47
+ @@imports
48
+ end
49
+ alias :imports :import
50
+
51
+
52
+ # Stores a list of defined variables to create on every compile process.
53
+ def define(variable, value, options = {})
54
+ literal = true if options[:literal]
55
+ @@definitions[variable] = { value: value, literal: literal }
56
+ end
57
+
58
+ # Retrieves all the registered plugins.
59
+ def plugins
60
+ @@plugins
61
+ end
62
+
63
+ # Retrieves all the registered variables.
64
+ def definitions
65
+ @@definitions
66
+ end
67
+
68
+ # Returns the global load path `Array` for your stylesheets.
69
+ def paths
70
+ @@paths
71
+ end
72
+
73
+ # Replaces the global load path `Array` of paths.
74
+ def paths=(val)
75
+ @@paths = Array(val)
76
+ end
77
+
78
+ # Returns the `debug` flag used to set the `linenos` and `firebug` option for Stylus.
79
+ def debug
80
+ @@debug
81
+ end
82
+ alias :debug? :debug
83
+
84
+ # Marks the `nib` plugin to be loaded and included on every stylesheet.
85
+ def nib=(flag)
86
+ if flag
87
+ use :nib
88
+ import :nib
89
+ end
90
+ end
91
+
92
+ # Sets the `debug` flag.
93
+ def debug=(val)
94
+ @@debug = val
95
+ end
96
+
97
+ # Returns the global compress flag.
98
+ def compress
99
+ @@compress
100
+ end
101
+ alias :compress? :compress
102
+
103
+ # Sets the global flag for the `compress` option.
104
+ def compress=(val)
105
+ @@compress = val
106
+ end
107
+
108
+ # Compiles a given input - a plain String, `File` or some sort of IO object that
109
+ # responds to `read`.
110
+ # It accepts a hash of options that will be merged with the global configuration.
111
+ # If the source has a `path`, it will be expanded and used as the :filename option
112
+ # So the debug options can be used.
113
+ def compile(source, options = {})
114
+ if source.respond_to?(:path) && source.path
115
+ options[:filename] ||= File.expand_path(source.path)
116
+ end
117
+ source = source.read if source.respond_to?(:read)
118
+ options = merge_options(options)
119
+ exec('compile', source, options, plugins, imports, definitions)
120
+ end
121
+
122
+ # Converts back an input of plain CSS to the `Stylus` syntax. The source object can be
123
+ # a `File`, `StringIO`, `String` or anything that responds to `read`.
124
+ def convert(source)
125
+ source = source.read if source.respond_to?(:read)
126
+ exec('convert', source)
127
+ end
128
+
129
+ # Returns a `Hash` of the given `options` merged with the default configuration.
130
+ # It also concats the global load path with a given `:paths` option.
131
+ def merge_options(options)
132
+ filename = options[:filename]
133
+
134
+ _paths = options.delete(:paths)
135
+ options = defaults.merge(options)
136
+ options[:paths] = paths.concat(Array(_paths))
137
+ if filename
138
+ options = options.merge(debug_options)
139
+ end
140
+ options
141
+ end
142
+
143
+ # Returns the default `Hash` of options:
144
+ # the compress flag and the global load path.
145
+ def defaults
146
+ { compress: self.compress?, paths: self.paths }
147
+ end
148
+
149
+ # Returns a Hash with the debug options to pass to
150
+ # Stylus.
151
+ def debug_options
152
+ { linenos: self.debug?, firebug: self.debug? }
153
+ end
154
+
155
+ # Return the gem version alongside with the current `Stylus` version of your system.
156
+ def version
157
+ "Stylus - gem #{VERSION} library #{exec('version')}"
158
+ end
159
+
160
+ protected
161
+ def bundled_path
162
+ File.dirname(Stylus::Source.bundled_path)
163
+ end
164
+ end
165
+
166
+ # Exports the `.node_modules` folder on the working directory so npm can
167
+ # require modules installed locally.
168
+ ENV['NODE_PATH'] = [
169
+ File.expand_path('node_modules'),
170
+ File.expand_path('vendor/node_modules'),
171
+ bundled_path,
172
+ ENV['NODE_PATH']
173
+ ].join(File::PATH_SEPARATOR)
174
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'rails/generators/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/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/stylus/assets/assets_generator'
4
+ require 'rails/generators/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
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
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stylus::Runtime do
4
+ it 'raises an error if the runtime is not available' do
5
+ allow(Stylus).to receive(:runtime) { double('An unavailable Runtime', available?: false) }
6
+
7
+ expect {
8
+ Stylus.version
9
+ }.to raise_error RuntimeError, %r[The Node.JS runtime is not available to Stylus.]
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'rails'
5
+ require 'rails/generators/test_case'
6
+
7
+ require 'active_support/railtie'
8
+ require 'action_controller/railtie'
9
+ require 'sprockets'
10
+ require 'sprockets/rails'
11
+ require 'stylus'
12
+
13
+ require 'active_support/core_ext/class/attribute_accessors'
14
+
15
+ require 'support/helpers'
16
+ require 'support/matchers'
17
+ require 'support/generators/test_case'
18
+
19
+ RSpec.configure do |config|
20
+ config.include Helpers
21
+
22
+ config.after :each do
23
+ Stylus.compress = false
24
+ Stylus.debug = false
25
+ Stylus.paths = []
26
+ Stylus.plugins.clear
27
+ Stylus.definitions.clear
28
+ Stylus.imports.clear
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Sprockets setup' do
4
+ let(:env) do
5
+ Sprockets::Environment.new do |assets|
6
+ assets.append_path fixture_root
7
+ assets.append_path 'javascripts'
8
+ end
9
+ end
10
+
11
+ it 'register the default Tilt template' do
12
+ expect(env).to receive(:register_engine).with('.styl', Tilt::StylusTemplate)
13
+ Stylus.setup(env)
14
+ end
15
+
16
+ it 'register a Rails specific Tilt template' do
17
+ expect(env).to receive(:register_engine).with('.styl', Stylus::Rails::StylusTemplate)
18
+ Stylus.setup(env, rails: true)
19
+ end
20
+
21
+ it 'register the import processor' do
22
+ expect(env).to receive(:register_preprocessor).with('text/css', Stylus::ImportProcessor)
23
+ Stylus.setup(env)
24
+ end
25
+
26
+ it 'copies the asset paths' do
27
+ Stylus.setup(env)
28
+ expect(Stylus.paths).to eq(env.paths)
29
+ end
30
+
31
+ it 'configure the debug and compress flags' do
32
+ Stylus.setup(env, debug: true, compress: true)
33
+ expect(Stylus.debug).to be_true
34
+ expect(Stylus.compress).to be_true
35
+ end
36
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stylus do
4
+ it 'compiles the given source' do
5
+ input, output = fixture(:simple)
6
+ expect(Stylus.compile(input)).to eq(output)
7
+ end
8
+
9
+ it 'accepts an IO object' do
10
+ input, output = fixture(:simple)
11
+ input = StringIO.new(input)
12
+ expect(Stylus.compile(input)).to eq(output)
13
+ end
14
+
15
+ it 'compress the file when the "compress" flag is given' do
16
+ input, output = fixture(:compressed)
17
+ expect(Stylus.compile(input, compress: true)).to eq(output)
18
+ end
19
+
20
+ it 'handles the compress flag globally' do
21
+ Stylus.compress = true
22
+ input, output = fixture(:compressed)
23
+ expect(Stylus.compile(input)).to eq(output)
24
+ end
25
+
26
+ it 'imports the given paths' do
27
+ path = fixture_root
28
+ input, output = fixture(:import)
29
+ expect(Stylus.compile(input, paths: path)).to eq(output)
30
+ end
31
+
32
+ it 'handles the import paths globally' do
33
+ Stylus.paths << fixture_root
34
+ input, output = fixture(:import)
35
+ expect(Stylus.compile(input)).to eq(output)
36
+ end
37
+
38
+ it 'implicit imports the given paths' do
39
+ path = File.expand_path('mixins/vendor.styl', fixture_root)
40
+ input, output = fixture(:implicit)
41
+ Stylus.import path
42
+ expect(Stylus.compile(input)).to eq(output)
43
+ end
44
+
45
+ it 'outputs both gem and library version' do
46
+ expect(Stylus.version).to match(/Stylus - gem .+ library .+/)
47
+ end
48
+
49
+ it 'converts CSS to Stylus' do
50
+ stylus, css = fixture(:stylesheet)
51
+ expect(Stylus.convert(css)).to eq(stylus)
52
+ end
53
+
54
+ it 'stores the given plugins' do
55
+ Stylus.use :one, :two, argument: true
56
+ expect(Stylus).to have(2).plugins
57
+ end
58
+
59
+ it 'includes the given plugins' do
60
+ Stylus.use :nib
61
+ input, output = fixture(:plugin)
62
+ expect(Stylus.compile(input)).to eq(output)
63
+ end
64
+
65
+ it 'stores the define calls' do
66
+ Stylus.define "mystring", "test"
67
+ expect(Stylus).to have(1).definitions
68
+ end
69
+
70
+ it 'defines a global variable string' do
71
+ Stylus.define "mystring", "test"
72
+ input, output = fixture(:definition)
73
+ expect(Stylus.compile(input)).to match(/content: 'test'/)
74
+ end
75
+
76
+ it 'defines a global variable literal' do
77
+ Stylus.define "mystring", "red", :literal => true
78
+ input, output = fixture(:definition)
79
+ expect(Stylus.compile(input)).to match(/content: red/)
80
+ end
81
+
82
+ it 'includes and imports "nib" automatically' do
83
+ Stylus.nib = true
84
+ input, output = fixture(:nib)
85
+ expect(Stylus.compile(input)).to eq(output)
86
+ end
87
+
88
+ it 'share variables between imported stylesheets' do
89
+ input, output = fixture(:variables)
90
+ path = fixture_root
91
+
92
+ expect(Stylus.compile(input, paths: path)).to eq(output)
93
+ end
94
+
95
+ describe 'The debug flag' do
96
+ let(:path) { fixture_path(:debug) }
97
+ let(:fixture) { File.read(path) }
98
+ let(:file) { File.new(path) }
99
+
100
+ before { Stylus.debug = true }
101
+
102
+ it 'turns the "linenos" option on' do
103
+ expect(Stylus.compile(file)).to match(/line 1 : #{path}/)
104
+ end
105
+
106
+ it 'skips the "linenos" option if no filename is given' do
107
+ expect(Stylus.compile(fixture)).to_not match(/line 1 : #{path}/)
108
+ end
109
+
110
+ it 'turns the "firebug" option on' do
111
+ expect(Stylus.compile(file)).to match(/@media -stylus-debug-info/)
112
+ end
113
+
114
+ it 'skips the "firebug" option if no filename is given' do
115
+ expect(Stylus.compile(fixture)).to_not match(/@media -stylus-debug-info/)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,59 @@
1
+ # Extracted from generator_spec https://github.com/stevehodgkiss/generator_spec
2
+ # But `RSpec::Rails::RailsExampleGroup` loads a truckload of things from rails and rspec-rails
3
+ # That we don't need.
4
+
5
+ module Generators
6
+ module TestCase
7
+ extend ActiveSupport::Concern
8
+ include FileUtils
9
+
10
+ included do
11
+ cattr_accessor :test_case, :test_case_instance
12
+
13
+ self.test_case = Class.new(Rails::Generators::TestCase) do
14
+ def fake_test_case; end
15
+ def add_assertion; end
16
+ end
17
+ self.test_case_instance = self.test_case.new(:fake_test_case)
18
+ self.test_case.tests described_class
19
+
20
+ before do
21
+ prepare_destination
22
+ create_routes
23
+ run_generator
24
+ end
25
+
26
+ destination File.expand_path('../tmp', __FILE__)
27
+ end
28
+
29
+ module ClassMethods
30
+ def tests(klass)
31
+ self.test_case.generator_class = klass
32
+ end
33
+
34
+ def arguments(array)
35
+ self.test_case.default_arguments = array
36
+ end
37
+
38
+ def destination(path)
39
+ self.test_case.destination_root = path
40
+ end
41
+ end
42
+
43
+ def file(relative)
44
+ File.expand_path(relative, destination_root)
45
+ end
46
+
47
+ def method_missing(method_sym, *arguments, &block)
48
+ self.test_case_instance.send(method_sym, *arguments, &block)
49
+ end
50
+
51
+ def respond_to?(method_sym, include_private = false)
52
+ if self.test_case_instance.respond_to?(method_sym)
53
+ true
54
+ else
55
+ super
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ module Helpers
2
+ def create_routes
3
+ destination = File.join(destination_root, 'config')
4
+
5
+ FileUtils.mkdir_p(destination)
6
+ FileUtils.touch File.join(destination, 'routes.rb')
7
+ end
8
+
9
+ def create_app(options = {})
10
+ Rails.application = nil
11
+
12
+ Class.new(Rails::Application).tap do |app|
13
+ config = app.config.assets
14
+ assets = app.assets
15
+ config.paths = []
16
+
17
+ assets.cache = ActiveSupport::Cache.lookup_store(:null_store)
18
+ config.compress = options[:compress]
19
+ config.debug = options[:debug]
20
+ config.paths << fixture_root
21
+ config.paths << File.expand_path('javascripts')
22
+ yield(app) if block_given?
23
+
24
+ app.config.eager_load = false
25
+ app.config.active_support.deprecation = :log
26
+ app.initialize!
27
+ end
28
+ end
29
+
30
+ def fixture_root
31
+ File.expand_path('../../stylesheets', __FILE__)
32
+ end
33
+
34
+ def images_root
35
+ File.expand_path('../../images', __FILE__)
36
+ end
37
+
38
+ def output_root
39
+ File.expand_path('../../cases', __FILE__)
40
+ end
41
+
42
+ def fixture(name)
43
+ source = fixture_path(name)
44
+ output = css_path(name)
45
+ [source, output].map do |path|
46
+ File.read(path) if File.file?(path)
47
+ end
48
+ end
49
+
50
+ def css_path(name)
51
+ File.join(output_root, "#{name}.css")
52
+ end
53
+
54
+ def fixture_path(name)
55
+ File.join(fixture_root, "#{name}.styl")
56
+ end
57
+
58
+ def dependencies_on(asset)
59
+ context = env.context_class.new(env, asset.logical_path, asset.pathname)
60
+ context.evaluate(asset.pathname)
61
+ context._dependency_paths
62
+ end
63
+ end
@@ -0,0 +1,5 @@
1
+ RSpec::Matchers.define :exist do
2
+ match do |file_path|
3
+ File.exists?(file_path)
4
+ end
5
+ end