kss 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/kss.rb CHANGED
@@ -1,7 +1,4 @@
1
- require 'sass'
2
- require 'v8'
3
- require 'less'
4
-
1
+ require 'kss/comment_parser'
5
2
  require 'kss/modifier'
6
3
  require 'kss/parser'
7
4
  require 'kss/section'
@@ -0,0 +1,164 @@
1
+ module Kss
2
+ # Public: Takes a file path of a text file and extracts comments from it.
3
+ # Currently accepts two formats:
4
+ #
5
+ # // Single line style.
6
+ # /* Multi-line style. */
7
+ class CommentParser
8
+
9
+ # Public: Is this a single-line comment? // This style
10
+ #
11
+ # line - A String of one line of text.
12
+ #
13
+ # Returns a boolean.
14
+ def self.single_line_comment?(line)
15
+ !!(line =~ /^\s*\/\//)
16
+ end
17
+
18
+ # Public: Is this the start of a multi-line comment? /* This style */
19
+ #
20
+ # line - A String of one line of text.
21
+ #
22
+ # Returns a boolean.
23
+ def self.start_multi_line_comment?(line)
24
+ !!(line =~ /^\s*\/\*/)
25
+ end
26
+
27
+ # Public: Is this the end of a multi-line comment? /* This style */
28
+ #
29
+ # line - A String of one line of text.
30
+ #
31
+ # Returns a boolean.
32
+ def self.end_multi_line_comment?(line)
33
+ return false if self.single_line_comment?(line)
34
+ !!(line =~ /.*\*\//)
35
+ end
36
+
37
+ # Public: Removes comment identifiers for single-line comments.
38
+ #
39
+ # line - A String of one line of text.
40
+ #
41
+ # Returns a String.
42
+ def self.parse_single_line(line)
43
+ cleaned = line.to_s.sub(/\s*\/\//, '')
44
+ cleaned.rstrip
45
+ end
46
+
47
+ # Public: Remove comment identifiers for multi-line comments.
48
+ #
49
+ # line - A String of one line of text.
50
+ #
51
+ # Returns a String.
52
+ def self.parse_multi_line(line)
53
+ cleaned = line.to_s.sub(/\s*\/\*/, '')
54
+ cleaned = cleaned.sub(/\*\//, '')
55
+ cleaned.rstrip
56
+ end
57
+
58
+ # Public: Initializes a new comment parser object. Does not parse on
59
+ # initialization.
60
+ #
61
+ # file_path - The location of the file to parse as a String.
62
+ # options - Optional options hash.
63
+ # :preserve_whitespace - Preserve the whitespace before/after comment
64
+ # markers (default:false).
65
+ #
66
+ def initialize(file_path, options={})
67
+ @options = options
68
+ @options[:preserve_whitespace] = false if @options[:preserve_whitespace].nil?
69
+ @file_path = file_path
70
+ @blocks = []
71
+ @parsed = false
72
+ end
73
+
74
+ # Public: The different sections of parsed comment text. A section is
75
+ # either a multi-line comment block's content, or consecutive lines of
76
+ # single-line comments.
77
+ #
78
+ # Returns an Array of parsed comment Strings.
79
+ def blocks
80
+ @parsed ? @blocks : parse_blocks
81
+ end
82
+
83
+
84
+ # Parse the file for comment blocks and populate them into @blocks.
85
+ #
86
+ # Returns an Array of parsed comment Strings.
87
+ def parse_blocks
88
+ File.open @file_path do |file|
89
+ current_block = nil
90
+ inside_single_line_block = false
91
+ inside_multi_line_block = false
92
+
93
+ file.each_line do |line|
94
+ # Parse single-line style
95
+ if self.class.single_line_comment?(line)
96
+ parsed = self.class.parse_single_line line
97
+ if inside_single_line_block
98
+ current_block += "\n#{parsed}"
99
+ else
100
+ current_block = parsed.to_s
101
+ inside_single_line_block = true
102
+ end
103
+ end
104
+
105
+ # Parse multi-lines tyle
106
+ if self.class.start_multi_line_comment?(line) || inside_multi_line_block
107
+ parsed = self.class.parse_multi_line line
108
+ if inside_multi_line_block
109
+ current_block += "\n#{parsed}"
110
+ else
111
+ current_block = parsed
112
+ inside_multi_line_block = true
113
+ end
114
+ end
115
+
116
+ # End a multi-line block if detected
117
+ inside_multi_line_block = false if self.class.end_multi_line_comment?(line)
118
+
119
+ # Store the current block if we're done
120
+ unless self.class.single_line_comment?(line) || inside_multi_line_block
121
+ @blocks << normalize(current_block) unless current_block.nil?
122
+
123
+ inside_single_line_block = false
124
+ current_block = nil
125
+ end
126
+ end
127
+ end
128
+
129
+ @parsed = true
130
+ @blocks
131
+ end
132
+
133
+ # Normalizes the comment block to ignore any consistent preceding
134
+ # whitespace. Consistent means the same amount of whitespace on every line
135
+ # of the comment block. Also strips any whitespace at the start and end of
136
+ # the whole block.
137
+ #
138
+ # Returns a String of normalized text.
139
+ def normalize(text_block)
140
+ return text_block if @options[:preserve_whitespace]
141
+
142
+ # Strip out any preceding [whitespace]* that occur on every line. Not
143
+ # the smartest, but I wonder if I care.
144
+ text_block = text_block.gsub(/^(\s*\*+)/, '')
145
+
146
+ # Strip consistent indenting by measuring first line's whitespace
147
+ indent_size = nil
148
+ unindented = text_block.split("\n").collect do |line|
149
+ preceding_whitespace = line.scan(/^\s*/)[0].to_s.size
150
+ indent_size = preceding_whitespace if indent_size.nil?
151
+ if line == ""
152
+ ""
153
+ elsif indent_size <= preceding_whitespace && indent_size > 0
154
+ line.slice(indent_size, line.length - 1)
155
+ else
156
+ line
157
+ end
158
+ end.join("\n")
159
+
160
+ unindented.strip
161
+ end
162
+
163
+ end
164
+ end
@@ -2,7 +2,10 @@ module Kss
2
2
  # Public: The main KSS parser. Takes a directory full of SASS / SCSS / CSS
3
3
  # files and parses the KSS within them.
4
4
  class Parser
5
-
5
+
6
+ # Public: Returns a hash of Sections.
7
+ attr_accessor :sections
8
+
6
9
  # Public: Initializes a new parser based on a directory of files. Scans
7
10
  # within the directory recursively for any comment blocks that look like
8
11
  # KSS.
@@ -12,73 +15,17 @@ module Kss
12
15
  @sections = {}
13
16
 
14
17
  Dir["#{base_path}/**/*.*"].each do |filename|
15
- if File.extname(filename) == ".less"
16
- tree = Less::Parser.new(:paths => [base_path], :filename => filename).
17
- # HACK: get the internal JS tree object
18
- parse(File.read(filename)).instance_variable_get "@tree"
19
-
20
- # inject less.js into a new V8 context
21
- less = Less.instance_variable_get "@less"
22
- cxt = V8::Context.new
23
- cxt['less'] = less
24
-
25
- # parse node tree for comments
26
- parse_v8_node(cxt, tree, filename)
27
- else
28
- if filename.match(/\.css$/)
29
- root_node = Sass::SCSS::Parser.new(File.read(filename), filename).parse
30
- else
31
- root_node = Sass::Engine.for_file(filename, {}).to_tree
32
- end
33
- parse_sass_node(root_node, filename)
18
+ parser = CommentParser.new(filename)
19
+ parser.blocks.each do |comment_block|
20
+ add_section comment_block, filename if self.class.kss_block?(comment_block)
34
21
  end
35
22
  end
36
23
  end
37
24
 
38
- # Given a Sass::Tree::Node, find all CommentNodes and populate @sections
39
- # with parsed Section objects.
40
- #
41
- # parent_node - A Sass::Tree::Node to start at.
42
- # filename - The filename String this node is found at.
43
- #
44
- # Returns the Sass::Tree::Node given.
45
- def parse_sass_node parent_node, filename
46
- parent_node.children.each do |node|
47
- unless node.is_a? Sass::Tree::CommentNode
48
- parse_sass_node(node, filename) if node.has_children
49
- next
50
- end
51
- comment_text = self.class.clean_comments node.to_scss
52
- add_section node.to_scss, filename
53
- end
54
- parent_node
55
- end
56
-
57
- def parse_v8_node cxt, parent_node, filename
58
- parent_node.rules.each do |node|
59
- # inject the current to into JS context
60
- cxt['node'] = node
61
-
62
- unless cxt.eval "node instanceof less.tree.Comment"
63
- parse_v8_node(cxt, node, filename) if cxt.eval 'node.rules != null'
64
-
65
- next
66
- end
67
-
68
- add_section node.value, filename
69
- end
70
-
71
- parent_node
72
- end
73
-
74
- def add_section comment, filename
75
- comment_text = self.class.clean_comments comment
76
-
77
- if self.class.kss_block? comment_text
78
- base_name = File.basename(filename)
79
- section = Section.new(comment_text, base_name)
80
- @sections[section.section] = section
81
- end
25
+ def add_section comment_text, filename
26
+ base_name = File.basename(filename)
27
+ section = Section.new(comment_text, base_name)
28
+ @sections[section.section] = section
82
29
  end
83
30
 
84
31
  # Public: Takes a cleaned (no comment syntax like // or /* */) comment
@@ -92,32 +39,6 @@ module Kss
92
39
  possible_reference =~ /Styleguide \d/
93
40
  end
94
41
 
95
- # Takes the raw comment text including comment syntax and strips all
96
- # comment syntax and normalizes the indention and whitespace to generate
97
- # a clean comment block.
98
- #
99
- # Returns a claned comment String.
100
- def self.clean_comments(text)
101
- text.strip!
102
-
103
- # SASS generated multi-line comment syntax
104
- text.gsub!(/(\/\* )?( \*\/)?/, '') # [/* + space] or [space + */]
105
- text.gsub!(/\n\s\* ?/, "\n") # * in front of every line
106
-
107
- # SASS generated single-line comment syntax
108
- text.gsub!(/\/\/ /, '') # [// + space]
109
- text.gsub!(/\/\/\n/, "\n") # [// + carriage return]
110
-
111
- # Manual generated comment syntax
112
- text.gsub!(/^\/\*/, '') # starting block
113
- text.gsub!(/\*\/$/, '') # ending block
114
-
115
- text.gsub!(/^[ \t]+/, '') # remove leading whitespace
116
-
117
- text.strip!
118
- text
119
- end
120
-
121
42
  # Public: Finds the Section for a given styleguide reference.
122
43
  #
123
44
  # Returns a Section for a reference, or a blank Section if none found.
@@ -1,3 +1,3 @@
1
1
  module Kss
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,73 @@
1
+ require 'test/helper'
2
+
3
+ class CommentParser < Kss::Test
4
+
5
+ def setup
6
+ loc = 'test/fixtures/comments.txt'
7
+ @parsed_comments = Kss::CommentParser.new(loc).blocks
8
+ end
9
+
10
+ test "detects single-line comment syntax" do
11
+ assert Kss::CommentParser.single_line_comment?("// yuuuuup")
12
+ assert !Kss::CommentParser.single_line_comment?("nooooope")
13
+ end
14
+
15
+ test "detects start of multi--line comment syntax" do
16
+ assert Kss::CommentParser.start_multi_line_comment?("/* yuuuuup")
17
+ assert !Kss::CommentParser.start_multi_line_comment?("nooooope")
18
+ end
19
+
20
+ test "detects end of multi-line comment syntax" do
21
+ assert Kss::CommentParser.end_multi_line_comment?(" yuuuuup */")
22
+ assert !Kss::CommentParser.end_multi_line_comment?("nooooope")
23
+ end
24
+
25
+ test "parses the single-line comment syntax" do
26
+ assert_equal " yuuuuup", Kss::CommentParser.parse_single_line("// yuuuuup")
27
+ end
28
+
29
+ test "parses the multi-line comment syntax" do
30
+ assert_equal " yuuuup", Kss::CommentParser.parse_multi_line("/* yuuuup */")
31
+ end
32
+
33
+ test "finds single-line comment styles" do
34
+ expected = <<comment
35
+ This comment block has comment identifiers on every line.
36
+
37
+ Fun fact: this is Kyle's favorite comment syntax!
38
+ comment
39
+ assert @parsed_comments.include? expected.rstrip
40
+ end
41
+
42
+ test "finds block-style comment styles" do
43
+ expected = <<comment
44
+ This comment block is a block-style comment syntax.
45
+
46
+ There's only two identifier across multiple lines.
47
+ comment
48
+ assert @parsed_comments.include? expected.rstrip
49
+
50
+
51
+ expected = <<comment
52
+ This is another common multi-line comment style.
53
+
54
+ It has stars at the begining of every line.
55
+ comment
56
+ assert @parsed_comments.include? expected.rstrip
57
+
58
+ end
59
+
60
+ test "handles mixed styles" do
61
+ expected = "This comment has a /* comment */ identifier inside of it!"
62
+ assert @parsed_comments.include? expected
63
+
64
+ expected = "Look at my //cool// comment art!"
65
+ assert @parsed_comments.include? expected
66
+ end
67
+
68
+ test "handles indented comments" do
69
+ assert @parsed_comments.include? "Indented single-line comment."
70
+ assert @parsed_comments.include? "Indented block comment."
71
+ end
72
+
73
+ end
@@ -0,0 +1,33 @@
1
+ This file is used for generic comment parsing across CSS, SCSS, SASS & LESS.
2
+
3
+ There's single-line comment styles:
4
+
5
+ // This comment block has comment identifiers on every line.
6
+ //
7
+ // Fun fact: this is Kyle's favorite comment syntax!
8
+
9
+
10
+ There's block comment styles:
11
+
12
+ /* This comment block is a block-style comment syntax.
13
+
14
+ There's only two identifier across multiple lines. */
15
+
16
+ /* This is another common multi-line comment style.
17
+ *
18
+ * It has stars at the begining of every line.
19
+ */
20
+
21
+
22
+ Some people do crazy things like mix comment styles:
23
+
24
+ // This comment has a /* comment */ identifier inside of it!
25
+
26
+ /* Look at my //cool// comment art! */
27
+
28
+
29
+ Indented comments:
30
+
31
+ // Indented single-line comment.
32
+
33
+ /* Indented block comment. */
@@ -85,17 +85,6 @@ comment
85
85
  @css_parsed.section('2.1.1').description
86
86
  end
87
87
 
88
- test "cleans css comments" do
89
- assert_equal @cleaned_css_comment,
90
- Kss::Parser.clean_comments(@css_comment)
91
- assert_equal @cleaned_css_comment,
92
- Kss::Parser.clean_comments(@starred_css_comment)
93
- assert_equal @cleaned_css_comment,
94
- Kss::Parser.clean_comments(@slashed_css_comment)
95
- assert_equal @cleaned_css_comment,
96
- Kss::Parser.clean_comments(@indented_css_comment)
97
- end
98
-
99
88
  test "parses nested SCSS documents" do
100
89
  assert_equal "Your standard form element.", @scss_parsed.section('3.0.0').description
101
90
  assert_equal "Your standard text input box.", @scss_parsed.section('3.0.1').description
@@ -111,4 +100,8 @@ comment
111
100
  assert_equal "Your standard text input box.", @sass_parsed.section('3.0.1').description
112
101
  end
113
102
 
103
+ test "public sections returns hash of sections" do
104
+ assert_equal 2, @css_parsed.sections.count
105
+ end
106
+
114
107
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kss
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Kyle Neath
@@ -15,39 +15,10 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-01-22 00:00:00 -08:00
18
+ date: 2012-02-06 00:00:00 -08:00
19
19
  default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- name: sass
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 5
30
- segments:
31
- - 3
32
- - 1
33
- version: "3.1"
34
- type: :runtime
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: less
38
- prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
42
- - - ">="
43
- - !ruby/object:Gem::Version
44
- hash: 3
45
- segments:
46
- - 2
47
- - 0
48
- version: "2.0"
49
- type: :runtime
50
- version_requirements: *id002
20
+ dependencies: []
21
+
51
22
  description: " Inspired by TomDoc, KSS attempts to provide a methodology for writing\n maintainable, documented CSS within a team. Specifically, KSS is a CSS\n structure, documentation specification, and styleguide format.\n\n This is a ruby library for parsing KSS documented CSS and generating\n styleguides.\n"
52
23
  email: kneath@gmail.com
53
24
  executables: []
@@ -60,12 +31,15 @@ files:
60
31
  - README.md
61
32
  - Rakefile
62
33
  - LICENSE
34
+ - lib/kss/comment_parser.rb
63
35
  - lib/kss/modifier.rb
64
36
  - lib/kss/parser.rb
65
37
  - lib/kss/section.rb
66
38
  - lib/kss/version.rb
67
39
  - lib/kss.coffee
68
40
  - lib/kss.rb
41
+ - test/comment_parser_test.rb
42
+ - test/fixtures/comments.txt
69
43
  - test/fixtures/css/buttons.css
70
44
  - test/fixtures/less/_label.less
71
45
  - test/fixtures/less/buttons.less