tailor 0.0.2

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.
Files changed (37) hide show
  1. data/History.txt +19 -0
  2. data/Manifest.txt +36 -0
  3. data/PostInstall.txt +2 -0
  4. data/README.rdoc +62 -0
  5. data/Rakefile +78 -0
  6. data/bin/tailor +18 -0
  7. data/features/case_checking.feature +38 -0
  8. data/features/development.feature +13 -0
  9. data/features/spacing.feature +40 -0
  10. data/features/step_definitions/case_checking_steps.rb +42 -0
  11. data/features/step_definitions/common_steps.rb +175 -0
  12. data/features/step_definitions/spacing_steps.rb +93 -0
  13. data/features/support/1_file_with_camel_case_class/camel_case_class.rb +5 -0
  14. data/features/support/1_file_with_camel_case_method/camel_case_method.rb +3 -0
  15. data/features/support/1_file_with_hard_tabs/hard_tab.rb +3 -0
  16. data/features/support/1_file_with_long_lines/long_lines.rb +5 -0
  17. data/features/support/1_file_with_snake_case_class/snake_case_class.rb +5 -0
  18. data/features/support/1_file_with_snake_case_method/snake_case_method.rb +3 -0
  19. data/features/support/1_file_with_trailing_whitespace/trailing_whitespace.rb +5 -0
  20. data/features/support/1_good_simple_file/my_project.rb +7 -0
  21. data/features/support/common.rb +29 -0
  22. data/features/support/env.rb +15 -0
  23. data/features/support/matchers.rb +11 -0
  24. data/features/support/world.rb +53 -0
  25. data/lib/tailor.rb +143 -0
  26. data/lib/tailor/file_line.rb +228 -0
  27. data/lib/tailor/indentation_checker.rb +27 -0
  28. data/ruby-style-checker.rb +136 -0
  29. data/script/console +10 -0
  30. data/script/destroy +14 -0
  31. data/script/generate +14 -0
  32. data/spec/file_line_spec.rb +136 -0
  33. data/spec/spec.opts +1 -0
  34. data/spec/spec_helper.rb +10 -0
  35. data/spec/tailor_spec.rb +27 -0
  36. data/tasks/rspec.rake +21 -0
  37. metadata +218 -0
@@ -0,0 +1,93 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'tailor/indentation_checker'
3
+ require 'tailor/file_line'
4
+
5
+ include Tailor
6
+ include Tailor::IndentationChecker
7
+
8
+ #-----------------------------------------------------------------------------
9
+ # "Given" statements
10
+ #-----------------------------------------------------------------------------
11
+ Given /^that file contains lines with hard tabs$/ do
12
+ @ruby_source = File.open(@file_list[0], 'r')
13
+ contains_hard_tabs = false
14
+ @ruby_source.each_line do |line|
15
+ source_line = Tailor::FileLine.new line
16
+ if source_line.hard_tabbed?
17
+ contains_hard_tabs = true
18
+ break
19
+ end
20
+ end
21
+ contains_hard_tabs.should be_true
22
+ end
23
+
24
+ Given /^that file does not contain any "([^\"]*)" statements$/ do |keyword|
25
+ @ruby_source = File.open(@file_list[0], 'r')
26
+
27
+ count = count_keywords(@ruby_source, keyword)
28
+ count.should == 0
29
+ end
30
+
31
+ Given /^that file is indented properly$/ do
32
+ @file_list.each do |file|
33
+ Tailor::IndentationChecker.validate_indentation file
34
+ end
35
+ end
36
+
37
+ Given /^that file contains lines with trailing whitespace$/ do
38
+ @ruby_source = File.open(@file_list[0], 'r')
39
+
40
+ @ruby_source.each_line do |line|
41
+ source_line = Tailor::FileLine.new line
42
+
43
+ @whitespace_count = source_line.trailing_whitespace_count
44
+
45
+ @whitespace_count.should > 0
46
+ end
47
+ end
48
+
49
+ Given /^that file contains lines longer than 80 characters$/ do
50
+ @ruby_source = File.open(@file_list[0], 'r')
51
+
52
+ @ruby_source.each_line do |line|
53
+ source_line = Tailor::FileLine.new line
54
+
55
+ if source_line.too_long?
56
+ too_long = true
57
+ break
58
+ else
59
+ too_long = false
60
+ end
61
+
62
+ too_long.should be_true
63
+ end
64
+ end
65
+
66
+
67
+ #-----------------------------------------------------------------------------
68
+ # "When" statements
69
+ #-----------------------------------------------------------------------------
70
+ When "I run the checker on the project" do
71
+ @result = `#{@tailor} #{@project_dir}`
72
+ end
73
+
74
+ #-----------------------------------------------------------------------------
75
+ # "Then" statements
76
+ #-----------------------------------------------------------------------------
77
+ Then /^the checker should tell me each line that has a hard tab$/ do
78
+ @result.should include("Line is hard-tabbed")
79
+ end
80
+
81
+ Then "the checker should tell me my indentation is OK" do
82
+ pending
83
+ end
84
+
85
+ Then /^the checker should tell me each line has trailing whitespace$/ do
86
+ message= "Line contains #{@whitespace_count} trailing whitespace(s)"
87
+ @result.should include message
88
+ end
89
+
90
+ Then /^the checker should tell me each line is too long$/ do
91
+ msg = "Line is greater than #{Tailor::FileLine::LINE_LENGTH_MAX} characters"
92
+ @result.should include msg
93
+ end
@@ -0,0 +1,5 @@
1
+ class CamelCase
2
+ def my_method
3
+ return
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ def doSomething
2
+ return
3
+ end
@@ -0,0 +1,3 @@
1
+ def my_method
2
+ return
3
+ end
@@ -0,0 +1,5 @@
1
+ # This method has a line in it that is way too freaking long. Seriously, why would you do
2
+ # something like this?
3
+ def do_something and_stuff
4
+ this_is_a_long_variable_name = Hash.new(:thing1 => 'something', :thing2 => 'something')
5
+ end
@@ -0,0 +1,5 @@
1
+ class A_Class
2
+ def do_something
3
+ return
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ def do_something
2
+ return
3
+ end
@@ -0,0 +1,5 @@
1
+ class MyClass
2
+ def do_something
3
+ return
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ # This file contains 1 method, no class, and is properly indented
2
+ # in order to test a related scenario.
3
+ def a_method
4
+ an_array = Array.new
5
+ a_hash = Hash.new
6
+ end
7
+
@@ -0,0 +1,29 @@
1
+ module CommonHelpers
2
+ def in_tmp_folder(&block)
3
+ FileUtils.chdir(@tmp_root, &block)
4
+ end
5
+
6
+ def in_project_folder(&block)
7
+ project_folder = @active_project_folder || @tmp_root
8
+ FileUtils.chdir(project_folder, &block)
9
+ end
10
+
11
+ def in_home_folder(&block)
12
+ FileUtils.chdir(@home_path, &block)
13
+ end
14
+
15
+ def force_local_lib_override(project_name = @project_name)
16
+ rakefile = File.read(File.join(project_name, 'Rakefile'))
17
+ File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
18
+ f << "$:.unshift('#{@lib_path}')\n"
19
+ f << rakefile
20
+ end
21
+ end
22
+
23
+ def setup_active_project_folder project_name
24
+ @active_project_folder = File.join(@tmp_root, project_name)
25
+ @project_name = project_name
26
+ end
27
+ end
28
+
29
+ World(CommonHelpers)
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + "/../../lib/tailor"
2
+
3
+ gem 'cucumber'
4
+ require 'cucumber'
5
+ gem 'rspec'
6
+ require 'spec'
7
+
8
+ Before do
9
+ @tmp_root = File.dirname(__FILE__) + "/../../tmp"
10
+ @home_path = File.expand_path(File.join(@tmp_root, "home"))
11
+ @lib_path = File.expand_path(File.dirname(__FILE__) + "/../../lib")
12
+ FileUtils.rm_rf @tmp_root
13
+ FileUtils.mkdir_p @home_path
14
+ ENV['HOME'] = @home_path
15
+ end
@@ -0,0 +1,11 @@
1
+ module Matchers
2
+ def contain(expected)
3
+ simple_matcher("contain #{expected.inspect}") do |given, matcher|
4
+ matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
5
+ matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
6
+ given.index expected
7
+ end
8
+ end
9
+ end
10
+
11
+ World(Matchers)
@@ -0,0 +1,53 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
2
+
3
+ require 'tailor'
4
+
5
+ include Tailor
6
+
7
+
8
+ # Counts keywords in the file provided.
9
+ #
10
+ # @param [String] file Path to the file to check
11
+ # @param [String] keyword Keyword to count
12
+ # @return [Number] Number of keywords counted
13
+ def count_keywords file, keyword
14
+ ruby_source = File.open(file, 'r')
15
+
16
+ count = 0
17
+ ruby_source.each_line do |line|
18
+ if line =~ /^#{keyword}/
19
+ count =+ 1
20
+ end
21
+ end
22
+ ruby_source.close
23
+ count
24
+ end
25
+
26
+
27
+ # Prep for the testing
28
+ Before do
29
+ @tailor = "#{File.dirname(__FILE__)}/../../bin/tailor"
30
+ end
31
+
32
+
33
+ #-----------------------------------------------------------------------------
34
+ # "Given" statements
35
+ #-----------------------------------------------------------------------------
36
+ Given /^I have a project directory "([^\"]*)"$/ do |project_dir|
37
+ project_dir = "support/#{project_dir}"
38
+ File.exists?(project_dir).should be_true
39
+ File.directory?(project_dir).should be_true
40
+ @project_dir = project_dir
41
+ end
42
+
43
+ Given /^the file contains only "([^\"]*)" "([^\"]*)" statement$/ do
44
+ |count_in_spec, keyword|
45
+ #count_in_file = count_keywords(@ruby_source, keyword)
46
+ count_in_file = count_keywords(@file_list[0], keyword)
47
+ count_in_file.should == count_in_spec.to_i
48
+ end
49
+
50
+ Given /^I have "([^\"]*)" file in my project$/ do |file_count|
51
+ @file_list = Dir.glob("#{@project_dir}/*")
52
+ @file_list.length.should == file_count.to_i
53
+ end
data/lib/tailor.rb ADDED
@@ -0,0 +1,143 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'tailor/file_line'
7
+
8
+ module Tailor
9
+ VERSION = '0.0.2'
10
+
11
+ RUBY_KEYWORDS_WITH_END = [
12
+ 'begin',
13
+ 'case',
14
+ 'class',
15
+ 'def',
16
+ 'do',
17
+ 'if',
18
+ 'unless',
19
+ 'until',
20
+ 'while'
21
+ ]
22
+
23
+ # Check all files in a directory for style problems.
24
+ #
25
+ # @param [String] project_base_dir Path to a directory to recurse into and
26
+ # look for problems in.
27
+ # @return [Hash] Returns a hash that contains file_name => problem_count.
28
+ def self.check project_base_dir
29
+ # Get the list of files to process
30
+ ruby_files_in_project = project_file_list(project_base_dir)
31
+
32
+ files_and_problems = Hash.new
33
+
34
+ # Process each file
35
+ ruby_files_in_project.each do |file_name|
36
+ problems = find_problems file_name
37
+ files_and_problems[file_name] = problems
38
+ end
39
+
40
+ files_and_problems
41
+ end
42
+
43
+ # Gets a list of .rb files in the project. This gets each file's absolute
44
+ # path in order to alleviate any possible confusion.
45
+ #
46
+ # @param [String] base_dir Directory to start recursing from to look for .rb
47
+ # files
48
+ # @return [Array] Sorted list of absolute file paths in the project
49
+ def self.project_file_list base_dir
50
+ if File.directory? base_dir
51
+ FileUtils.cd base_dir
52
+ end
53
+
54
+ # Get the .rb files
55
+ ruby_files_in_project = Dir.glob(File.join('*', '**', '*.rb'))
56
+ Dir.glob(File.join('*.rb')).each { |f| ruby_files_in_project << f }
57
+
58
+ # Expand paths to all files in the list
59
+ list_with_absolute_paths = Array.new
60
+ ruby_files_in_project.each do |file|
61
+ list_with_absolute_paths << File.expand_path(file)
62
+ end
63
+
64
+ list_with_absolute_paths.sort
65
+ end
66
+
67
+ # Checks a sing file for all defined styling parameters.
68
+ #
69
+ # @param [String] file_name Path to a file to check styling on.
70
+ # @return [Number] Returns the number of errors on the file.
71
+ def self.find_problems file_name
72
+ source = File.open(file_name, 'r')
73
+ file_path = Pathname.new(file_name)
74
+
75
+ puts
76
+ puts "#-------------------------------------------------------------------"
77
+ puts "# Looking for bad style in:"
78
+ puts "# \t'#{file_path}'"
79
+ puts "#-------------------------------------------------------------------"
80
+
81
+ problem_count = 0
82
+ line_number = 1
83
+ source.each_line do |source_line|
84
+ line = FileLine.new source_line
85
+
86
+ # Check for hard tabs
87
+ if line.hard_tabbed?
88
+ puts "Line is hard-tabbed:"
89
+ puts "\t#{file_path.relative_path_from(Pathname.pwd)}: #{line_number}"
90
+ problem_count += 1
91
+ end
92
+
93
+ # Check for camel-cased methods
94
+ if line.method? and line.camel_case_method?
95
+ puts "Method name uses camel case:"
96
+ puts "\t#{file_path.relative_path_from(Pathname.pwd)}: #{line_number}"
97
+ problem_count += 1
98
+ end
99
+
100
+ # Check for non-camel-cased classes
101
+ if line.class? and !line.camel_case_class?
102
+ puts "Class name does NOT use camel case:"
103
+ puts "\t#{file_path.relative_path_from(Pathname.pwd)}: #{line_number}"
104
+ problem_count += 1
105
+ end
106
+
107
+ # Check for trailing whitespace
108
+ count = line.trailing_whitespace_count
109
+ if count > 0
110
+ puts "Line contains #{count} trailing whitespace(s):"
111
+ puts "\t#{file_path.relative_path_from(Pathname.pwd)}: #{line_number}"
112
+ problem_count += 1
113
+ end
114
+
115
+ # Check for long lines
116
+ if line.too_long?
117
+ puts "Line is greater than #{FileLine::LINE_LENGTH_MAX} characters:"
118
+ puts "\t#{file_path.relative_path_from(Pathname.pwd)}: #{line_number}"
119
+ problem_count += 1
120
+ end
121
+
122
+ line_number += 1
123
+ end
124
+
125
+ problem_count
126
+ end
127
+
128
+ # Prints a summary report that shows which files had how many problems.
129
+ #
130
+ # @param [Hash] files_and_problems Returns a hash that contains
131
+ # file_name => problem_count.
132
+ def self.print_report files_and_problems
133
+ puts
134
+ puts "The following files are out of style:"
135
+
136
+ files_and_problems.each_pair do |file, problem_count|
137
+ file_path = Pathname.new(file)
138
+ unless problem_count == 0
139
+ puts "\t#{file_path.relative_path_from(Pathname.pwd)}: #{problem_count} problems"
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,228 @@
1
+ module Tailor
2
+
3
+ # Calling modules will get the Ruby file to check, then read by line. This
4
+ # class allows for checking of line-specific style by Represents a single
5
+ # line of a file of Ruby code. Inherits from String so "self" can be used.
6
+ class FileLine < String
7
+
8
+ LINE_LENGTH_MAX = 80
9
+
10
+ # This passes the line of code to String (the parent) so that it can act
11
+ # like a standard string.
12
+ #
13
+ # @param [String] line_of_code Line from a Ruby file that will be checked
14
+ # for styling.
15
+ # @return [String] Returns a String that includes all of the methods
16
+ # defined here.
17
+ def initialize line_of_code
18
+ super line_of_code
19
+ end
20
+
21
+ # Determines the number of spaces the line is indented.
22
+ #
23
+ # @return [Number] Returns the number of spaces the line is indented.
24
+ def indented_spaces
25
+ # Find out how many spaces exist at the beginning of the line
26
+ spaces = self.scan(/^\x20+/).first
27
+
28
+ unless spaces.nil?
29
+ return spaces.length
30
+ else
31
+ return 0
32
+ end
33
+ end
34
+
35
+ # Checks to see if the source code line is tabbed
36
+ #
37
+ # @return [Boolean] Returns true if the file contains a tab
38
+ # character before any others in that line.
39
+ def hard_tabbed?
40
+ result = case self
41
+ # The line starts with a tab
42
+ when /^\t/ then true
43
+ # The line starts with spaces, then has a tab
44
+ when /^\s+\t/ then true
45
+ else false
46
+ end
47
+ end
48
+
49
+ # Checks to see if the method is using camel case.
50
+ #
51
+ # @return [Boolean] Returns true if the method name is camel case.
52
+ def camel_case_method?
53
+ words = self.split(/ /)
54
+
55
+ # If we're dealing with a method, check for uppercase chars
56
+ if self.method?
57
+
58
+ # The 2nd word is the method name, so evaluate that for caps chars.
59
+ if words[1] =~ /[A-Z]/
60
+ return true
61
+ else
62
+ return false
63
+ end
64
+ # If we're dealing with a class, check for an underscore.
65
+ else
66
+ return nil
67
+ end
68
+ end
69
+
70
+ # Checks to see if the class is using camel case.
71
+ #
72
+ # @return [Boolean] Returns true if the class name is camel case.
73
+ def camel_case_class?
74
+ words = self.split(/ /)
75
+
76
+ # If we're dealing with a class, check for an underscore.
77
+ if self.class?
78
+ if words[1] =~ /_/
79
+ return false
80
+ else
81
+ return true
82
+ end
83
+ else
84
+ return nil
85
+ end
86
+ end
87
+
88
+ # Checks to see if the line is the start of a method's definition.
89
+ #
90
+ # @return [Boolean] Returns true if the line contains 'def' and the second
91
+ # word begins with a lowercase letter.
92
+ def method?
93
+ words = self.split(/ /)
94
+ if words[0].eql? "def" and starts_with_lowercase?(words[1])
95
+ return true
96
+ else
97
+ return false
98
+ end
99
+ end
100
+
101
+ # Checks to see if the line is the start of a class's definition.
102
+ #
103
+ # @return [Boolean] Returns true if the line contains 'class' and the
104
+ # second word begins with a uppercase letter.
105
+ def class?
106
+ words = self.split(/ /)
107
+ if words[0].eql? "class" and starts_with_uppercase?(words[1])
108
+ return true
109
+ else
110
+ return false
111
+ end
112
+ end
113
+
114
+ # Checks to see if the whole line is a basic comment line. This doesn't
115
+ # check for trailing-line comments (@see #trailing_comment?).
116
+ #
117
+ # @return [Boolean] Returns true if the line begins with a pound symbol.
118
+ def line_comment?
119
+ if self.scan(/\s+#/).empty?
120
+ return false
121
+ else
122
+ return true
123
+ end
124
+ end
125
+
126
+ # Checks to see if the line has trailing whitespace at the end of it. Note
127
+ # that this excludes empty lines that have spaces on them!
128
+ #
129
+ # @return [Number] Returns the number of trailing spaces at the end of the
130
+ # line.
131
+ def trailing_whitespace_count
132
+ spaces = self.scan(/(\x20+|\x09+)$/)
133
+ if spaces.first.eql? nil
134
+ return 0
135
+ else
136
+ return spaces.first.first.length
137
+ end
138
+ end
139
+
140
+ # Checks to see if a single space exists after a comma in uncomented code.
141
+ # This method doesn't check if the line is a comment, so this should be
142
+ # done before calling this method. @see #line_comment?.
143
+ #
144
+ # @return [Boolean] Returns true if only 1 space exists after a comma.
145
+ def two_or_more_spaces_after_comma?
146
+ if self.scan(/\w\,\s{2,}/).empty?
147
+ return false
148
+ else
149
+ return true
150
+ end
151
+ end
152
+
153
+ ##
154
+ # Checks to see if there's no spaces after a comma.
155
+ #
156
+ # @return [Boolean] Returns true if there isn't a space after a comma.
157
+ def no_space_after_comma?
158
+ if self.scan(/\w\,\w/).empty?
159
+ return false
160
+ else
161
+ return true
162
+ end
163
+ end
164
+
165
+ ##
166
+ # Checks to see if there's no space before a comma.
167
+ #
168
+ # @return [Boolean] Returns true if there's no space before a comma.
169
+ def no_space_before_comma?
170
+ if self.scan(/\w\s\,/)
171
+ return true
172
+ else
173
+ return false
174
+ end
175
+ end
176
+
177
+ # Counts the number of spaces around a comma and returns before and after
178
+ # values as a hash.
179
+ #
180
+ # @return [Hash<:before,:after>] Returns a Hash with values for :before and
181
+ # :after.
182
+ def spaces_around_comma
183
+ spaces = Hash.new
184
+
185
+ spaces[:before] = self.scan(/(\x20+),/)
186
+ spaces[:after] = self.scan(/,(\x20+)/)
187
+
188
+ spaces
189
+ end
190
+
191
+ ##
192
+ # Checks to see if the line is greater than the defined max (80 chars is
193
+ # default).
194
+ #
195
+ # @return [Boolean] Returns true if the line length exceeds the allowed
196
+ # length.
197
+ def too_long?
198
+ self.length > LINE_LENGTH_MAX ? true : false
199
+ end
200
+
201
+ #-----------------------------------------------------------------
202
+ # Private methods
203
+ #-----------------------------------------------------------------
204
+ private
205
+
206
+ # Checks to see if a word begins with a lowercase letter.
207
+ #
208
+ # @param [String] word The word to check case on.
209
+ def starts_with_lowercase? word
210
+ if word =~ /^[a-z]/
211
+ return true
212
+ else
213
+ return false
214
+ end
215
+ end
216
+
217
+ # Checks to see if a word begins with an uppercase letter.
218
+ #
219
+ # @param [String] word The word to check case on.
220
+ def starts_with_uppercase? word
221
+ if word =~ /^[A-Z]/
222
+ return true
223
+ else
224
+ return false
225
+ end
226
+ end
227
+ end
228
+ end