less_to_sass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +70 -0
  4. data/bin/less2sass +12 -0
  5. data/bin/sass2less +12 -0
  6. data/lib/less2sass/constants.rb +5 -0
  7. data/lib/less2sass/error.rb +100 -0
  8. data/lib/less2sass/exec/base.rb +83 -0
  9. data/lib/less2sass/exec/conversion.rb +102 -0
  10. data/lib/less2sass/exec.rb +2 -0
  11. data/lib/less2sass/js/less_parser.js +48 -0
  12. data/lib/less2sass/less/ast_handler.rb +33 -0
  13. data/lib/less2sass/less/environment.rb +212 -0
  14. data/lib/less2sass/less/parser.rb +131 -0
  15. data/lib/less2sass/less/tree/alpha_node.rb +9 -0
  16. data/lib/less2sass/less/tree/anonymous_node.rb +43 -0
  17. data/lib/less2sass/less/tree/assignment_node.rb +10 -0
  18. data/lib/less2sass/less/tree/attribute_node.rb +11 -0
  19. data/lib/less2sass/less/tree/call_node.rb +65 -0
  20. data/lib/less2sass/less/tree/color_node.rb +27 -0
  21. data/lib/less2sass/less/tree/combinator_node.rb +18 -0
  22. data/lib/less2sass/less/tree/comment_node.rb +58 -0
  23. data/lib/less2sass/less/tree/condition_node.rb +13 -0
  24. data/lib/less2sass/less/tree/detached_ruleset_node.rb +10 -0
  25. data/lib/less2sass/less/tree/dimension_node.rb +26 -0
  26. data/lib/less2sass/less/tree/directive_node.rb +40 -0
  27. data/lib/less2sass/less/tree/element_node.rb +32 -0
  28. data/lib/less2sass/less/tree/expression_node.rb +73 -0
  29. data/lib/less2sass/less/tree/extend_node.rb +14 -0
  30. data/lib/less2sass/less/tree/import_node.rb +14 -0
  31. data/lib/less2sass/less/tree/keyword_node.rb +49 -0
  32. data/lib/less2sass/less/tree/media_node.rb +12 -0
  33. data/lib/less2sass/less/tree/mixin_call_node.rb +13 -0
  34. data/lib/less2sass/less/tree/mixin_definition_node.rb +24 -0
  35. data/lib/less2sass/less/tree/negative_node.rb +9 -0
  36. data/lib/less2sass/less/tree/node.rb +212 -0
  37. data/lib/less2sass/less/tree/operation_node.rb +63 -0
  38. data/lib/less2sass/less/tree/paren_node.rb +9 -0
  39. data/lib/less2sass/less/tree/quoted_node.rb +64 -0
  40. data/lib/less2sass/less/tree/rule_node.rb +119 -0
  41. data/lib/less2sass/less/tree/ruleset_call_node.rb +9 -0
  42. data/lib/less2sass/less/tree/ruleset_node.rb +82 -0
  43. data/lib/less2sass/less/tree/selector_node.rb +27 -0
  44. data/lib/less2sass/less/tree/unicode_descriptor_node.rb +9 -0
  45. data/lib/less2sass/less/tree/unit_node.rb +17 -0
  46. data/lib/less2sass/less/tree/url_node.rb +22 -0
  47. data/lib/less2sass/less/tree/value_node.rb +53 -0
  48. data/lib/less2sass/less/tree/variable_node.rb +43 -0
  49. data/lib/less2sass/less/tree.rb +34 -0
  50. data/lib/less2sass/less.rb +2 -0
  51. data/lib/less2sass/sass/ast_handler.rb +57 -0
  52. data/lib/less2sass/sass/parser.rb +20 -0
  53. data/lib/less2sass/sass.rb +2 -0
  54. data/lib/less2sass/util.rb +36 -0
  55. data/lib/less2sass.rb +9 -0
  56. metadata +163 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 73f59ec66f3b140eb5e446edf2b163b6b4050bee
4
+ data.tar.gz: 3a3080ed5b728f6bd80007fb9a998e992b6fe95d
5
+ SHA512:
6
+ metadata.gz: aad48dc3288693a590fd535a05286dd3732222c1d8688cacbe1f4e770095314c57e2b9aaac7e3a17836f7865a772e83c589814b7f8cd87468695ebf4feefd6a9
7
+ data.tar.gz: 940d33cfed16ba2b475e68b00fd5b993a9c26ed9d0832d992efa2ba6d3e6335bd63b62918bb9a5b133aae31eede77a82af62db5b4f975b51726dec41baf609c0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Attila Večerek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # less2sass
2
+
3
+ LESS and SASS are two dynamic style sheet languages with some minor differences in syntax and huge differences in their semantics. The goal of this project is to create a converter application between these formats.
4
+
5
+ There are some converters available on the Internet, but all of them are working on the search and replace principle and can not produce 100% correct conversion. Less2sass is an AST-based converter, which heavily uses the Less.js and Sass engines in order to correctly parse Less code and generate Sass or SCSS stylesheets, respectively.
6
+
7
+ ## Installing
8
+
9
+ 1. Install *Node.js* v6, if possible, according to the [official instructions](https://nodejs.org/en/download/package-manager/).
10
+ - On Debian (Ubunu/Mint) set an alias in ~/.bashrc file for nodejs.
11
+
12
+ ```
13
+ alias node='nodejs'
14
+ ```
15
+ 2. Set the ```NODE_PATH``` environment variable to store the install location of nodejs in your bash profile. Usually, it is the folder /usr/lib/node_modules.
16
+
17
+ ```
18
+ export NODE_PATH=/usr/lib/node_modules:$NODE_PATH
19
+ ```
20
+ 3. Install *Less.js* using the Node.js package manager.
21
+
22
+ ```
23
+ $ sudo npm install -g less@2.7.1
24
+ ```
25
+ 4. You can check, if Less.js has been installed successfully by entering the interactive node shell and trying to require it. The require should return the path to the module's index file. Usually ```/usr/lib/node_modules/less/index.js```.
26
+
27
+ ```
28
+ $ node
29
+ > require.resolve('less')
30
+ ```
31
+ 5. Install Less2Sass. Note that the name of the gem is less_to_sass, since the alternative with the number was already taken.
32
+
33
+ ```ruby
34
+ $ gem install less_to_sass
35
+ ```
36
+
37
+ ## Usage
38
+ ```
39
+ $ less2sass <INPUT> [OUTPUT] [options]
40
+ ```
41
+ Even though the name of the gem is less_to_sass, the executable is used according to the repo's name.
42
+
43
+ ## Development
44
+
45
+ 1. Install Bundler.
46
+
47
+ ```ruby
48
+ $ gem install bundler
49
+ ```
50
+ 2. Clone git repo and enter it.
51
+
52
+ ```
53
+ $ git clone https://github.com/vecerek/less2sass
54
+ $ cd less2sass
55
+ ```
56
+ 3. Install development dependencies using bundler.
57
+
58
+ ```
59
+ $ bundle install
60
+ ```
61
+ 4. Run the tests and enjoy the development!
62
+
63
+ ```
64
+ $ bundle exec rspec spec/compare_spec.rb
65
+ ```
66
+
67
+ ## Language differences
68
+
69
+ There is an entire [wiki page] (https://github.com/vecerek/less2sass/wiki/Language-Differences), that deals with the language differences. It will be continuously updated as new releases of Less and Sass will be published.
70
+ The current version of the document describes the differences between Less v2.7.1 and Sass v3.4.21
data/bin/less2sass ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # A command line Less to Sass converter
3
+
4
+ begin
5
+ # require works with RubyMine, not with terminal
6
+ require_relative '../lib/less2sass'
7
+ rescue LoadError
8
+ require 'less2sass'
9
+ end
10
+
11
+ opts = Less2Sass::Exec::Conversion.new(ARGV, :less2sass)
12
+ opts.parse!
data/bin/sass2less ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # A command line Less to Sass converter
3
+
4
+ begin
5
+ # require works with RubyMine, not with terminal
6
+ require_relative '../lib/less2sass'
7
+ rescue LoadError
8
+ require 'less2sass'
9
+ end
10
+
11
+ opts = Less2Sass::Exec::Conversion.new(ARGV, :sass2less)
12
+ opts.parse!
@@ -0,0 +1,5 @@
1
+ module Less2Sass
2
+ # The root directory of the Less2Sass source tree.
3
+ ROOT_DIR = File.expand_path(File.join(__FILE__, '../../..'))
4
+ VERSION = '0.1.0'.freeze
5
+ end
@@ -0,0 +1,100 @@
1
+ module Less2Sass
2
+ class Less2SassError < StandardError; end
3
+
4
+ class ArgumentUnspecifiedError < ArgumentError
5
+ def initialize(arg)
6
+ @arg = arg
7
+ end
8
+
9
+ def message
10
+ "Argument Error: #{@arg} can't be left unspecified. Choose sass or scss."
11
+ end
12
+ end
13
+
14
+ class InvalidArgumentError < ArgumentError
15
+ def initialize(arg, val)
16
+ @arg = arg
17
+ @val = val
18
+ end
19
+
20
+ def message
21
+ "#{@val} is not a valid value for #{arg}"
22
+ end
23
+ end
24
+
25
+ class JSLessParserError < Less2SassError
26
+ def initialize(err)
27
+ @err = err
28
+ end
29
+
30
+ def message
31
+ "\n#{yield} #{@err['filename']}: #{@err['message']} on line #{@err['line']}, index #{@err['index']}. " +
32
+ (@err['callLine'].nil? ? '' : "Called from line #{@err['callLine']}")
33
+ end
34
+ end
35
+
36
+ class LessSyntaxError < JSLessParserError
37
+ def message
38
+ super { 'Syntax error found in' }
39
+ end
40
+ end
41
+
42
+ class LessImportNotFoundError < JSLessParserError
43
+ def message
44
+ super { 'The specified import file has not been found in' }
45
+ end
46
+ end
47
+
48
+ class LessASTParserError < Less2SassError
49
+ def initialize(klass)
50
+ @klass = klass
51
+ end
52
+
53
+ def message
54
+ "Unexpected class type #{@klass} during Less' AST JSON parsing."
55
+ end
56
+ end
57
+
58
+ class LessCompilationError < Less2SassError
59
+ def message
60
+ "\n" + super
61
+ end
62
+ end
63
+
64
+ # Abstract error class for Less2Sass conversion errors
65
+ class ConversionError < Less2SassError
66
+ def initialize
67
+ raise NotImplementedError
68
+ end
69
+
70
+ def message
71
+ 'Unsupported '
72
+ end
73
+ end
74
+
75
+ class OperatorConversionError < ConversionError
76
+ def initialize(operator)
77
+ @op = operator
78
+ end
79
+
80
+ def message
81
+ super + "operator #{@op}"
82
+ end
83
+ end
84
+
85
+ class FeatureConversionError < ConversionError
86
+ def initialize(obj)
87
+ @klass = obj.class
88
+ end
89
+
90
+ def message
91
+ super + "feature when converting #{@klass}"
92
+ end
93
+ end
94
+
95
+ class UnknownError < Less2SassError
96
+ def message
97
+ "Something unexpected just happened.\n" + super
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,83 @@
1
+ require 'optparse'
2
+
3
+ module Less2Sass
4
+ module Exec
5
+ # The abstract base class for Less2Sass executables.
6
+ class Base
7
+ # @param args [Array<String>] command-line arguments
8
+ def initialize(args)
9
+ @args = args
10
+ @options = {}
11
+ end
12
+
13
+ # Parses the command-line arguments and runs the executable.
14
+ # Calls `Kernel#exit` at the end, so it never returns.
15
+ #
16
+ # @see #parse
17
+ def parse!
18
+ begin
19
+ parse
20
+ rescue Exception => e
21
+ raise e if @options[:trace] || e.is_a?(SystemExit)
22
+ $stderr.puts "#{e.class}: " + e.message.to_s
23
+ exit 1
24
+ end
25
+ exit 0
26
+ end
27
+
28
+ # Parses the command-line arguments and runs the executable.
29
+ def parse
30
+ # @opts = OptionParser.new(&method(:set_opts))
31
+ OptionParser.new do |opts|
32
+ set_opts(opts)
33
+ end.parse!(@args)
34
+
35
+ process_args
36
+
37
+ @options
38
+ end
39
+
40
+ protected
41
+
42
+ # Tells optparse how to parse the arguments
43
+ # available for all executables.
44
+ #
45
+ # The method is being overridden by subclasses
46
+ # to add their own options.
47
+ #
48
+ # @param opts [OptionParser]
49
+ def set_opts(_opts = nil)
50
+ raise NotImplementedError.new("#{obj.class} must implement ##{caller_info[2]}")
51
+ end
52
+
53
+ def open_file(filename, flag = 'r')
54
+ return if filename.nil?
55
+ File.open(filename, flag)
56
+ end
57
+
58
+ # Processes the options set by the command-line arguments -
59
+ # `@options[:input]` and `@options[:output]` are being set
60
+ # to appropriate IO streams.
61
+ #
62
+ # This method is being overridden by subclasses
63
+ # to run their respective programs.
64
+ def process_args
65
+ input = @options[:input]
66
+ output = @options[:output]
67
+ args = @args.dup
68
+ input ||=
69
+ begin
70
+ filename = args.shift
71
+ @options[:input_filename] = filename
72
+ open_file(filename) || $stdin
73
+ end
74
+ @options[:output_filename] = args.shift
75
+ output ||= @options[:output_filename] || $stdout
76
+ @options[:input] = input
77
+ @options[:output] = output
78
+
79
+ run
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,102 @@
1
+ require 'less2sass/less'
2
+ require 'yaml'
3
+
4
+ module Less2Sass
5
+ module Exec
6
+ # TODO: rethink the concept, rather use 2 classes - Less2Sass and SassToLess instead of one (Conversion)
7
+ class Conversion < Base
8
+ attr_reader :conversion
9
+
10
+ def initialize(args, conversion)
11
+ super(args)
12
+ @options[:target_syntax] = :scss
13
+ @conversion = conversion
14
+ end
15
+
16
+ # Tells optparse how to parse the arguments.
17
+ #
18
+ # @param opts [OptionParser]
19
+ def set_opts(opts)
20
+ opts.banner = <<END
21
+ Usage: #{conversion} [options] [INPUT] [OUTPUT]
22
+
23
+ Description:
24
+ #{get_banner_desc}
25
+ END
26
+
27
+ common_options(opts)
28
+ input_and_output(opts)
29
+ miscellaneous(opts)
30
+ end
31
+
32
+ private
33
+
34
+ def get_banner_desc
35
+ 'Converts ' + (conversion == :less2sass ?
36
+ 'Less project to Sass' : 'Sass project to Less')
37
+ end
38
+
39
+ def common_options(opts)
40
+ opts.on('-?', '-h', '--help', 'Show this help message.') do
41
+ puts opts
42
+ exit
43
+ end
44
+
45
+ opts.on('-v', '--version', 'Print the Sass version.') do
46
+ puts("Less2Sass #{Less2Sass.version[:string]}")
47
+ exit
48
+ end
49
+ end
50
+
51
+ def input_and_output(opts)
52
+ opts.separator ''
53
+ opts.separator 'Input and Output:'
54
+
55
+ if conversion == :less2sass
56
+ opts.on(:OPTIONAL, '--target-syntax SYNTAX',
57
+ 'Which Sass syntax should the Less project be converted to.',
58
+ ' - scss (default): Use the CSS-superset SCSS syntax.',
59
+ ' - sass: Use the indented Sass syntax.'
60
+ ) do |syntax|
61
+ if syntax.nil?
62
+ raise ArgumentUnspecifiedError, '--target-syntax'
63
+ elsif syntax && !%w(scss sass).include?(syntax)
64
+ raise InvalidArgumentError, '--target-syntax', syntax
65
+ end
66
+ @options[:target_syntax] = (syntax || :scss).to_sym
67
+ end
68
+ end
69
+
70
+ opts.on('-s', '--stdin', :NONE,
71
+ 'Read input from standard input instead of an input file.',
72
+ 'This is the default if no input file is specified.') do
73
+ @options[:input] = $stdin
74
+ end
75
+ end
76
+
77
+ def miscellaneous(opts)
78
+ opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error.') do
79
+ @options[:trace] = true
80
+ end
81
+
82
+ opts.on('-q', '--quiet', 'Silence warnings and status messages during conversion.') do
83
+ @options[:for_engine][:quiet] = true
84
+ end
85
+ end
86
+
87
+ # Runs the appropriate conversion (parse, transform, code gen)
88
+ def run
89
+ if conversion == :less2sass
90
+ sass = Less2Sass::Less::ASTHandler.new(@options[:input], @options[:target_syntax])
91
+ .transform_tree
92
+ .to_sass
93
+
94
+ sass.code_gen(@options[:output])
95
+ elsif conversion == :sass2less
96
+ # TODO: Implement Sass to Less conversion
97
+ # Raise some error but this line should never be reached
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,2 @@
1
+ require 'less2sass/exec/base'
2
+ require 'less2sass/exec/conversion'
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @author Attila Večerek <xvecer17@stud.fit.vutbr.cz>
3
+ */
4
+ var less = require('less')
5
+ , fs = require('fs')
6
+ , path = require('path');
7
+
8
+ Less = {
9
+ parse: function(src, isStdin) {
10
+ // Parses a Less file and outputs its AST
11
+
12
+ var visit = function(o) {
13
+ // Attaches the node type to the necessary objects
14
+ for (var p in o) {
15
+ if (typeof o[p] == 'object') visit(o[p]);
16
+ }
17
+ if (o != null && o != "") {
18
+ if ("type" in o) o["class"] = o.type;
19
+ }
20
+ };
21
+
22
+ var code = src;
23
+ var options = {};
24
+
25
+ if (!isStdin) {
26
+ code = fs.readFileSync(src, 'utf8').toString();
27
+ options = {
28
+ filename: path.resolve(src)
29
+ }
30
+ }
31
+
32
+ return less.parse(code, options, function(e, tree) {
33
+ if (!e) {
34
+ visit(tree);
35
+ console.log(JSON.stringify(tree, null, 2));
36
+ } else {
37
+ e.class = "error"
38
+ console.log(JSON.stringify(e));
39
+ }
40
+ });
41
+ }
42
+ };
43
+
44
+ if (process.argv[2] === "-stdin") {
45
+ Less.parse(process.argv[3], process.argv[2]);
46
+ } else {
47
+ Less.parse(process.argv[2]);
48
+ }
@@ -0,0 +1,33 @@
1
+ require 'less2sass/less/parser'
2
+
3
+ module Less2Sass
4
+ module Less
5
+ # Handler of the Less AST.
6
+ class ASTHandler
7
+ # @param [File, IO] input the root file of the Less project
8
+ # @param [Symbol] target_syntax the specified Sass syntax
9
+ # for the conversion.
10
+ def initialize(input, target_syntax)
11
+ @target_syntax = target_syntax
12
+ parser = Parser.new(input)
13
+ # TODO: check syntax only if specified in the command-line options
14
+ # parser.check_syntax unless input == $stdin
15
+ @tree = parser.parse
16
+ end
17
+
18
+ # Transforms the Less AST, so it can be converted to
19
+ # an equivalent Sass AST representation.
20
+ def transform_tree
21
+ @tree.transform
22
+ self
23
+ end
24
+
25
+ # Converts the transformed Less AST to Sass AST.
26
+ #
27
+ # @return [Less2Sass::Sass::ASTHandler] a wrapped Sass AST
28
+ def to_sass
29
+ Less2Sass::Sass::ASTHandler.new(@tree.to_sass(:syntax => @target_syntax))
30
+ end
31
+ end
32
+ end
33
+ end