tailor 1.0.1 → 1.1.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.
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