tailor 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.tailor +1 -0
  2. data/Gemfile.lock +1 -1
  3. data/History.rdoc +34 -0
  4. data/README.rdoc +17 -1
  5. data/features/valid_ruby.feature +1 -1
  6. data/lib/ext/string_ext.rb +0 -4
  7. data/lib/tailor/cli/options.rb +9 -2
  8. data/lib/tailor/configuration.rb +103 -150
  9. data/lib/tailor/configuration/file_set.rb +110 -0
  10. data/lib/tailor/formatters/text.rb +108 -79
  11. data/lib/tailor/rake_task.rb +148 -0
  12. data/lib/tailor/tailorrc.erb +1 -1
  13. data/lib/tailor/version.rb +1 -1
  14. data/spec/functional/configuration_spec.rb +244 -0
  15. data/spec/functional/horizontal_spacing/braces_spec.rb +238 -0
  16. data/spec/functional/horizontal_spacing/brackets_spec.rb +88 -0
  17. data/spec/functional/horizontal_spacing/comma_spacing_spec.rb +68 -0
  18. data/spec/functional/horizontal_spacing/hard_tabs_spec.rb +110 -0
  19. data/spec/functional/horizontal_spacing/long_lines_spec.rb +51 -0
  20. data/spec/functional/horizontal_spacing/parens_spec.rb +102 -0
  21. data/spec/functional/horizontal_spacing/trailing_whitespace_spec.rb +66 -0
  22. data/spec/functional/horizontal_spacing_spec.rb +59 -0
  23. data/spec/functional/indentation_spacing/bad_indentation_spec.rb +372 -0
  24. data/spec/functional/indentation_spacing_spec.rb +85 -0
  25. data/spec/functional/naming/camel_case_methods_spec.rb +56 -0
  26. data/spec/functional/naming/screaming_snake_case_classes_spec.rb +83 -0
  27. data/spec/functional/naming_spec.rb +35 -0
  28. data/spec/functional/vertical_spacing/class_length_spec.rb +67 -0
  29. data/spec/functional/vertical_spacing/method_length_spec.rb +61 -0
  30. data/spec/functional/vertical_spacing_spec.rb +35 -0
  31. data/spec/support/bad_indentation_cases.rb +265 -0
  32. data/{features/support/file_cases/indentation_cases.rb → spec/support/good_indentation_cases.rb} +6 -266
  33. data/spec/support/horizontal_spacing_cases.rb +136 -0
  34. data/spec/support/naming_cases.rb +26 -0
  35. data/{features/support/file_cases → spec/support}/vertical_spacing_cases.rb +0 -33
  36. data/spec/{tailor → unit/tailor}/cli_spec.rb +1 -1
  37. data/spec/{tailor → unit/tailor}/composite_observable_spec.rb +1 -1
  38. data/spec/unit/tailor/configuration/file_set_spec.rb +65 -0
  39. data/spec/{tailor → unit/tailor}/configuration/style_spec.rb +1 -1
  40. data/spec/{tailor → unit/tailor}/configuration_spec.rb +1 -59
  41. data/spec/{tailor → unit/tailor}/critic_spec.rb +1 -1
  42. data/spec/{tailor → unit/tailor}/formatter_spec.rb +1 -1
  43. data/spec/{tailor → unit/tailor}/lexed_line_spec.rb +1 -1
  44. data/spec/{tailor → unit/tailor}/lexer/token_spec.rb +1 -1
  45. data/spec/{tailor → unit/tailor}/lexer_spec.rb +1 -2
  46. data/spec/{tailor → unit/tailor}/options_spec.rb +1 -1
  47. data/spec/{tailor → unit/tailor}/problem_spec.rb +1 -1
  48. data/spec/{tailor → unit/tailor}/reporter_spec.rb +1 -1
  49. data/spec/{tailor → unit/tailor}/ruler_spec.rb +1 -1
  50. data/spec/{tailor → unit/tailor}/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +1 -1
  51. data/spec/{tailor → unit/tailor}/rulers/indentation_spaces_ruler_spec.rb +1 -1
  52. data/spec/{tailor → unit/tailor}/rulers/spaces_after_comma_ruler_spec.rb +1 -1
  53. data/spec/{tailor → unit/tailor}/rulers/spaces_after_lbrace_ruler_spec.rb +1 -1
  54. data/spec/{tailor → unit/tailor}/rulers/spaces_before_lbrace_ruler_spec.rb +1 -1
  55. data/spec/{tailor → unit/tailor}/rulers/spaces_before_rbrace_ruler_spec.rb +1 -1
  56. data/spec/{tailor → unit/tailor}/rulers_spec.rb +1 -1
  57. data/spec/unit/tailor/version_spec.rb +6 -0
  58. data/spec/{tailor_spec.rb → unit/tailor_spec.rb} +1 -1
  59. data/tasks/spec.rake +8 -3
  60. metadata +121 -93
  61. data/features/horizontal_spacing.feature +0 -263
  62. data/features/indentation/bad_files_with_no_trailing_newline.feature +0 -91
  63. data/features/indentation/good_files_with_no_trailing_newline.feature +0 -219
  64. data/features/name_detection.feature +0 -72
  65. data/features/support/file_cases/horizontal_spacing_cases.rb +0 -266
  66. data/features/support/file_cases/naming_cases.rb +0 -51
  67. data/features/vertical_spacing.feature +0 -135
  68. data/m.rb +0 -15
  69. data/spec/tailor/version_spec.rb +0 -6
@@ -0,0 +1,110 @@
1
+ require_relative '../runtime_error'
2
+ require_relative '../logger'
3
+ require_relative 'style'
4
+
5
+ class Tailor
6
+ class Configuration
7
+ class FileSet < Hash
8
+ include Tailor::Logger::Mixin
9
+
10
+ DEFAULT_GLOB = 'lib/**/*.rb'
11
+
12
+ attr_reader :style
13
+ attr_accessor :file_list
14
+
15
+ # @param [Hash] style Style options to merge into the default Style
16
+ # settings.
17
+ # @param [String,Array] file_expression
18
+ def initialize(file_expression=nil, style=nil)
19
+ @style = if style
20
+ Style.new.to_hash.merge(style)
21
+ else
22
+ Style.new.to_hash
23
+ end
24
+
25
+ self[:style] = @style
26
+
27
+ file_expression ||= DEFAULT_GLOB
28
+ @file_list = build_file_list(file_expression)
29
+ self[:file_list] = @file_list
30
+ end
31
+
32
+ def update_file_list(file_expression)
33
+ new_list = build_file_list(file_expression)
34
+ @file_list.concat(new_list).uniq!
35
+ end
36
+
37
+ def update_style(new_style)
38
+ @style.to_hash.merge!(new_style)
39
+ end
40
+
41
+ def [](key)
42
+ if key == :style
43
+ @style.to_hash
44
+ elsif key == :file_list
45
+ @file_list
46
+ else
47
+ raise Tailor::RuntimeError, "Invalid key requested: #{key}"
48
+ end
49
+ end
50
+
51
+ def file_list=(file_expression)
52
+ @file_list = build_file_list(file_expression)
53
+ end
54
+
55
+ private
56
+
57
+ # The list of the files in the project to check.
58
+ #
59
+ # @param [String] file_expression Path to the file, directory or file_expression to check.
60
+ # @return [Array] The list of files to check.
61
+ def build_file_list(file_expression)
62
+ files_in_project = if file_expression.is_a? Array
63
+ log "Configured file_expression is an Array: #{file_expression}"
64
+
65
+ file_expression.map do |e|
66
+ if File.directory?(e)
67
+ all_files_in_dir(e)
68
+ else
69
+ e
70
+ end
71
+ end.flatten.uniq
72
+ elsif File.directory? file_expression
73
+ log "Configured file_expression is an directory: #{file_expression}"
74
+ all_files_in_dir(file_expression)
75
+ elsif File.file? file_expression
76
+ log "Configured file_expression is a single-file: #{file_expression}"
77
+ [file_expression]
78
+ else
79
+ log "Configured file_expression is a glob: #{file_expression}"
80
+ Dir.glob file_expression
81
+ end
82
+
83
+ list_with_absolute_paths = []
84
+
85
+ files_in_project.each do |file|
86
+ new_file = File.expand_path(file)
87
+ log "file: #{new_file}"
88
+
89
+ if File.exists? new_file
90
+ list_with_absolute_paths << new_file
91
+ end
92
+ end
93
+
94
+ list_with_absolute_paths.sort
95
+ end
96
+
97
+ # Gets a list of only files that are in +base_dir+.
98
+ #
99
+ # @param [String] base_dir The directory to get the file list for.
100
+ # @return [Array<String>] The List of files.
101
+ def all_files_in_dir(base_dir)
102
+ files = Dir.glob(File.join(base_dir, '**', '*')).find_all do |file|
103
+ file if File.file?(file)
104
+ end
105
+
106
+ files
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,30 +1,28 @@
1
- require 'text-table'
2
1
  require 'pathname'
2
+ require 'term/ansicolor'
3
3
  require_relative '../formatter'
4
4
 
5
+
5
6
  class Tailor
6
7
  module Formatters
7
8
  class Text < Tailor::Formatter
9
+ include Term::ANSIColor
10
+
8
11
  PROBLEM_LEVEL_COLORS = {
9
- error: :red,
10
- warn: :yellow
12
+ error: 'red',
13
+ warn: 'yellow'
11
14
  }
12
15
 
13
- # @return [String] A line of "#-----", with length determined by +length+.
14
- def line(length=79)
15
- "##{'-' * length}\n"
16
+ # @return [String] A line of "#----#-", with length determined by +length+.
17
+ def line(length=78)
18
+ "##{'-' * length}#\n"
16
19
  end
17
20
 
18
21
  # @return [String] The portion of the header that displays the file info.
19
22
  def file_header(file)
20
23
  file = Pathname(file)
21
- message = ""
22
- message << if defined? Term::ANSIColor
23
- "# #{'File:'.underscore}\n"
24
- else
25
- "# File:\n"
26
- end
27
-
24
+ message = "# "
25
+ message << underscore { "File:\n" }
28
26
  message << "# #{file.relative_path_from(@pwd)}\n"
29
27
  message << "#\n"
30
28
 
@@ -34,13 +32,8 @@ class Tailor
34
32
  # @return [String] The portion of the header that displays the file_set
35
33
  # label info.
36
34
  def file_set_header(file_set)
37
- message = ""
38
- message << if defined? Term::ANSIColor
39
- "# #{'File Set:'.underscore}\n"
40
- else
41
- "# File Set:\n"
42
- end
43
-
35
+ message = "# "
36
+ message << underscore { "File Set:\n" }
44
37
  message << "# #{file_set}\n"
45
38
  message << "#\n"
46
39
 
@@ -50,46 +43,32 @@ class Tailor
50
43
  # @return [String] The portion of the report that displays all of the
51
44
  # problems for the file.
52
45
  def problems_header(problem_list)
53
- message = ""
54
- message << if defined? Term::ANSIColor
55
- "# #{'Problems:'.underscore}\n"
56
- else
57
- "# Problems:\n"
58
- end
46
+ message = "# "
47
+ message << underscore { "Problems:\n" }
59
48
 
60
49
  problem_list.each_with_index do |problem, i|
61
- color = PROBLEM_LEVEL_COLORS[problem[:level]] || :white
62
-
63
- position = if problem[:line] == '<EOF>'
64
- '<EOF>'
65
- else
66
- if defined? Term::ANSIColor
67
- msg = "#{problem[:line].to_s.send(color).bold}:"
68
- msg << "#{problem[:column].to_s.send(color).bold}"
69
- msg
70
- else
71
- "#{problem[:line]}:#{problem[:column]}"
72
- end
73
- end
74
-
75
- message << if defined? Term::ANSIColor
76
- %Q{# #{(i + 1).to_s.bold}.
77
- # * position: #{position}
78
- # * property: #{problem[:type].to_s.send(color)}
79
- # * message: #{problem[:message].send(color)}
80
- }
81
- else
82
- %Q{# #{(i + 1)}.
83
- # * position: #{position}
84
- # * type: #{problem[:type]}
85
- # * message: #{problem[:message]}
86
- }
87
- end
50
+ color = PROBLEM_LEVEL_COLORS[problem[:level]] || 'white'
51
+
52
+ position = position(problem[:line], problem[:column])
53
+ message << "# " + bold { "#{(i + 1)}." } + "\n"
54
+ message << "# * position: "
55
+ message << bold { instance_eval("#{color} position") } + "\n"
56
+ message << "# * property: "
57
+ message << instance_eval("#{color} problem[:type].to_s") + "\n"
58
+ message << "# * message: "
59
+ message << instance_eval("#{color} problem[:message].to_s") + "\n"
88
60
  end
89
61
 
90
62
  message
91
63
  end
92
64
 
65
+ # @param [Fixnum] line
66
+ # @param [Fixnum] column
67
+ # @return [String] The position the problem was found at.
68
+ def position(line, column)
69
+ line == '<EOF>' ? '<EOF>' : "#{line}:#{column}"
70
+ end
71
+
93
72
  # Prints the report on the file that just got checked.
94
73
  #
95
74
  # @param [Hash] problems Value should be the file name; keys should be
@@ -104,47 +83,97 @@ class Tailor
104
83
  message << file_set_header(file_set_label)
105
84
  message << problems_header(problem_list)
106
85
 
107
- message << <<-MSG
108
- #
109
- #-------------------------------------------------------------------------------
110
- MSG
86
+ message << "#\n"
87
+ message << line
111
88
 
112
89
  puts message
113
90
  end
114
91
 
92
+ MAX_STRING_SIZE = 68
93
+
115
94
  # Prints the report on all of the files that just got checked.
116
95
  #
117
96
  # @param [Hash] problems Values are filenames; keys are problems for each
118
97
  # of those files.
119
98
  def summary_report(problems)
120
- if problems.empty?
121
- puts "Your files are in style."
122
- else
123
- summary_table = ::Text::Table.new
124
- summary_table.head = [{ value: "Tailor Summary", colspan: 2 }]
125
- summary_table.rows << [{ value: "File", align: :center },
126
- { value: "Total Problems", align: :center }]
127
- summary_table.rows << :separator
128
-
129
- problems.each do |file, problem_list|
130
- file = Pathname(file)
131
- summary_table.rows << [
132
- file.relative_path_from(@pwd), problem_list.size
133
- ]
134
- end
99
+ summary_table = summary_header
100
+ i = 0
135
101
 
136
- summary_table.rows << :separator
102
+ problems.each do |file, problem_list|
103
+ report_line = summary_file_line(file, problem_list)
137
104
 
138
- problem_levels(problems).inject(summary_table.rows) do |result, level|
139
- result << [level.capitalize,
140
- problems_at_level(problems, level).size]
105
+ report_line = if i % 2 == 1
106
+ on_intense_black { report_line }
107
+ else
108
+ bold { report_line }
141
109
  end
142
110
 
143
- summary_table.rows << :separator
144
- summary_table.rows << ['TOTAL', problems.values.flatten.size]
111
+ summary_table << "# " << report_line << reset << "|\n"
112
+ i += 1
113
+ end
114
+
115
+ summary_table << line
116
+ summary_table << summary_level_totals(problems)
117
+ summary_table << "# " << bold{ summary_first_col('TOTAL', 67) }
118
+ summary_table << "|"
119
+ summary_table << bold { total_problems(problems).to_s.rjust(6) }
120
+ summary_table << " |\n"
121
+ summary_table << line
122
+
123
+ puts summary_table
124
+ end
125
+
126
+ def summary_header
127
+ summary_table = line
128
+ summary_table << "# "
129
+ summary_table << bold { 'Tailor Summary'.rjust(40) }
130
+ summary_table << "|\n".rjust(39)
131
+ summary_table << line
132
+ summary_table << '# ' << summary_first_col('File', 67) + '| '
133
+ summary_table << 'Probs'.rjust(1)
134
+ summary_table << " |\n".rjust(2)
135
+ summary_table << line
136
+
137
+ summary_table
138
+ end
139
+
140
+ def summary_file_line(file, problem_list)
141
+ file = Pathname(file)
142
+ relative_path = file.relative_path_from(@pwd)
143
+ problem_count = problem_list.size
144
+
145
+ "#{summary_first_col(relative_path)} | " + problem_count.to_s.rjust(5) + " "
146
+ end
145
147
 
146
- puts summary_table
148
+ def summary_first_col(path, string_size=MAX_STRING_SIZE)
149
+ fp = path.to_s.ljust(string_size)
150
+ offset = fp.size - string_size
151
+ end_of_string = fp[offset..-1]
152
+ end_of_string.sub!(/^.{3}/, '...') unless offset.zero?
153
+
154
+ end_of_string
155
+ end
156
+
157
+ def summary_level_totals(problems)
158
+ return "" if total_problems(problems).zero?
159
+
160
+ output = problem_levels(problems).inject("") do |result, level|
161
+ color = PROBLEM_LEVEL_COLORS[level] || 'white'
162
+
163
+ result << "# "
164
+ result << instance_eval("#{color} { summary_first_col(level.capitalize, 67) }")
165
+ result << "|"
166
+ result << instance_eval("#{color} { problems_at_level(problems, level).size.to_s.rjust(6) }")
167
+ result << " |\n"
147
168
  end
169
+
170
+ output << line
171
+
172
+ output
173
+ end
174
+
175
+ def total_problems(problems)
176
+ problems.values.flatten.size
148
177
  end
149
178
  end
150
179
  end
@@ -0,0 +1,148 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+ require_relative 'critic'
4
+ require_relative 'configuration'
5
+ require_relative 'logger'
6
+ require_relative 'reporter'
7
+ require_relative 'cli/options'
8
+
9
+ begin
10
+ # Support Rake > 0.8.7
11
+ require 'rake/dsl_definition'
12
+ rescue LoadError
13
+ end
14
+
15
+ class Tailor
16
+
17
+ # This class lets you define Rake tasks to drive tailor. Specifying options
18
+ # is similar to specifying options in a configuration file.
19
+ #
20
+ # @example Use Tailor CLI Options
21
+ # Tailor::RakeTask.new do |task|
22
+ # task.tailor_opts = %w(--no-color --max-line-length=100)
23
+ # end
24
+ #
25
+ # @example A task specifically for features
26
+ # Tailor::RakeTask.new(:tailor_features) do |task|
27
+ # task.file_set 'features/**/*.rb', :features do |style|
28
+ # style.max_line_length 100, level: :warn
29
+ # style.trailing_newlines 2
30
+ # end
31
+ # end
32
+ #
33
+ # @example Use and override a configuration file
34
+ # Tailor::RakeTask.new do |task|
35
+ # task.config_file = 'hardcore_stylin.rb'
36
+ # task.file_set 'lib/**/*.rb' do |style|
37
+ # style.indentation_spaces 2, level: :warn
38
+ # end
39
+ # end
40
+ class RakeTask < ::Rake::TaskLib
41
+ include ::Rake::DSL if defined? ::Rake::DSL
42
+
43
+ # Use a specific configuration file. If you have a .tailor file, your
44
+ # RakeTask will automatically use that.
45
+ #
46
+ # @return [String] The path to the configuration file.
47
+ attr_accessor :config_file
48
+
49
+ # Specify any extra options (CLI options). These will override any options
50
+ # set in your config file.
51
+ attr_accessor :tailor_opts
52
+
53
+ attr_accessor :formatters
54
+
55
+ # @param [String] name The task name.
56
+ # @param [String] desc Description of the task.
57
+ def initialize(name = "tailor", desc = "Check style")
58
+ Tailor::Logger.log = false
59
+
60
+ @name, @desc = name, desc
61
+ @tailor_opts = []
62
+ @file_sets = []
63
+ @recursive_file_sets = []
64
+
65
+ yield self if block_given?
66
+
67
+ define_task
68
+ end
69
+
70
+ # Add a file set to critique, just like you would in a config file.
71
+ #
72
+ # @param [String] file_expression
73
+ # @param [Symbol] label
74
+ def file_set(file_expression, label=:default, &block)
75
+ @file_sets << [file_expression, label, block]
76
+ end
77
+
78
+ # Add a recursive file set to critique, just like you would in a config
79
+ # file.
80
+ #
81
+ # @param [String] file_expression
82
+ # @param [Symbol] label
83
+ def recursive_file_set(file_expression, label=:default, &block)
84
+ @recursive_file_sets << [file_expression, label, block]
85
+ end
86
+
87
+ private
88
+
89
+ def define_task
90
+ desc @desc
91
+ task @name do
92
+ if config_file
93
+ @tailor_opts.concat %W(--config-file=#{config_file})
94
+ end
95
+
96
+ configuration = create_config
97
+ @reporter = Tailor::Reporter.new(configuration.formatters)
98
+
99
+ create_file_sets_for configuration
100
+ create_recursive_file_sets_for configuration
101
+ check_default_file_set_in configuration
102
+
103
+ critic = Tailor::Critic.new
104
+
105
+ critic.critique(configuration.file_sets) do |problems_for_file, label|
106
+ @reporter.file_report(problems_for_file, label)
107
+ end
108
+
109
+ @reporter.summary_report(critic.problems)
110
+
111
+ critic.problem_count > 0
112
+ end
113
+ end
114
+
115
+ # @return [Tailor::Configuration]
116
+ def create_config
117
+ configuration = Tailor::Configuration.new([],
118
+ Tailor::CLI::Options.parse!(@tailor_opts))
119
+ configuration.load!
120
+ configuration.formatters(formatters) if formatters
121
+
122
+ configuration
123
+ end
124
+
125
+ # @param [Tailor::Configuration] config
126
+ def create_recursive_file_sets_for config
127
+ unless @recursive_file_sets.empty?
128
+ @recursive_file_sets.each do |fs|
129
+ config.recursive_file_set(fs[0], fs[1], &fs[2])
130
+ end
131
+ end
132
+ end
133
+
134
+ # @param [Tailor::Configuration] config
135
+ def create_file_sets_for config
136
+ unless @file_sets.empty?
137
+ @file_sets.each { |fs| config.file_set(fs[0], fs[1], &fs[2]) }
138
+ end
139
+ end
140
+
141
+ # @param [Tailor::Configuration] config
142
+ def check_default_file_set_in config
143
+ if @file_sets.none? { |fs| fs[1] == :default }
144
+ config.file_sets.delete(:default)
145
+ end
146
+ end
147
+ end
148
+ end