kss 0.2.0 → 0.3.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.
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