tailor 0.1.5 → 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/.gitignore +9 -1
  2. data/.rspec +2 -1
  3. data/.tailor +6 -0
  4. data/Gemfile.lock +47 -78
  5. data/{ChangeLog.rdoc → History.rdoc} +0 -0
  6. data/README.rdoc +157 -24
  7. data/Rakefile +0 -9
  8. data/bin/tailor +16 -69
  9. data/features/configurable.feature +78 -0
  10. data/features/horizontal_spacing.feature +262 -0
  11. data/features/indentation.feature +17 -21
  12. data/features/indentation/bad_files_with_no_trailing_newline.feature +90 -0
  13. data/features/indentation/good_files_with_no_trailing_newline.feature +206 -0
  14. data/features/name_detection.feature +72 -0
  15. data/features/step_definitions/indentation_steps.rb +10 -133
  16. data/features/support/env.rb +7 -15
  17. data/features/support/file_cases/horizontal_spacing_cases.rb +265 -0
  18. data/features/support/file_cases/indentation_cases.rb +972 -0
  19. data/features/support/file_cases/naming_cases.rb +52 -0
  20. data/features/support/file_cases/vertical_spacing_cases.rb +70 -0
  21. data/features/support/hooks.rb +8 -0
  22. data/features/support/{1_file_with_bad_operator_spacing → legacy}/bad_op_spacing.rb +0 -0
  23. data/features/support/{1_file_with_bad_ternary_colon_spacing → legacy}/bad_ternary_colon_spacing.rb +0 -0
  24. data/features/support/{1_long_file_with_indentation/my_project.rb → legacy/long_file_with_indentation.rb} +1 -1
  25. data/features/support/world.rb +14 -0
  26. data/features/vertical_spacing.feature +114 -0
  27. data/lib/ext/string_ext.rb +5 -0
  28. data/lib/tailor.rb +6 -252
  29. data/lib/tailor/cli.rb +49 -0
  30. data/lib/tailor/cli/options.rb +251 -0
  31. data/lib/tailor/composite_observable.rb +56 -0
  32. data/lib/tailor/configuration.rb +263 -0
  33. data/lib/tailor/critic.rb +162 -0
  34. data/lib/tailor/formatters/text.rb +126 -0
  35. data/lib/tailor/lexed_line.rb +246 -0
  36. data/lib/tailor/lexer.rb +428 -0
  37. data/lib/tailor/lexer/token.rb +103 -0
  38. data/lib/tailor/lexer_constants.rb +75 -0
  39. data/lib/tailor/logger.rb +28 -0
  40. data/lib/tailor/problem.rb +100 -0
  41. data/lib/tailor/reporter.rb +48 -0
  42. data/lib/tailor/ruler.rb +39 -0
  43. data/lib/tailor/rulers.rb +7 -0
  44. data/lib/tailor/rulers/allow_camel_case_methods_ruler.rb +30 -0
  45. data/lib/tailor/rulers/allow_hard_tabs_ruler.rb +22 -0
  46. data/lib/tailor/rulers/allow_screaming_snake_case_classes_ruler.rb +32 -0
  47. data/lib/tailor/rulers/allow_trailing_line_spaces_ruler.rb +33 -0
  48. data/lib/tailor/rulers/indentation_spaces_ruler.rb +199 -0
  49. data/lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb +362 -0
  50. data/lib/tailor/rulers/max_code_lines_in_class_ruler.rb +84 -0
  51. data/lib/tailor/rulers/max_code_lines_in_method_ruler.rb +84 -0
  52. data/lib/tailor/rulers/max_line_length_ruler.rb +31 -0
  53. data/lib/tailor/rulers/spaces_after_comma_ruler.rb +83 -0
  54. data/lib/tailor/rulers/spaces_after_lbrace_ruler.rb +114 -0
  55. data/lib/tailor/rulers/spaces_after_lbracket_ruler.rb +123 -0
  56. data/lib/tailor/rulers/spaces_after_lparen_ruler.rb +116 -0
  57. data/lib/tailor/rulers/spaces_before_comma_ruler.rb +67 -0
  58. data/lib/tailor/rulers/spaces_before_lbrace_ruler.rb +93 -0
  59. data/lib/tailor/rulers/spaces_before_rbrace_ruler.rb +98 -0
  60. data/lib/tailor/rulers/spaces_before_rbracket_ruler.rb +70 -0
  61. data/lib/tailor/rulers/spaces_before_rparen_ruler.rb +70 -0
  62. data/lib/tailor/rulers/spaces_in_empty_braces_ruler.rb +94 -0
  63. data/lib/tailor/rulers/trailing_newlines_ruler.rb +36 -0
  64. data/lib/tailor/runtime_error.rb +3 -0
  65. data/lib/tailor/tailorrc.erb +88 -0
  66. data/lib/tailor/version.rb +2 -2
  67. data/spec/spec_helper.rb +7 -5
  68. data/spec/tailor/cli_spec.rb +94 -0
  69. data/spec/tailor/configuration_spec.rb +147 -0
  70. data/spec/tailor/critic_spec.rb +63 -0
  71. data/spec/tailor/lexed_line_spec.rb +569 -0
  72. data/spec/tailor/lexer/token_spec.rb +46 -0
  73. data/spec/tailor/lexer_spec.rb +181 -0
  74. data/spec/tailor/options_spec.rb +6 -0
  75. data/spec/tailor/problem_spec.rb +74 -0
  76. data/spec/tailor/reporter_spec.rb +53 -0
  77. data/spec/tailor/ruler_spec.rb +56 -0
  78. data/spec/tailor/rulers/indentation_spaces_ruler/indentation_manager_spec.rb +454 -0
  79. data/spec/tailor/rulers/indentation_spaces_ruler_spec.rb +128 -0
  80. data/spec/tailor/rulers/spaces_after_comma_spec.rb +31 -0
  81. data/spec/tailor/rulers/spaces_after_lbrace_ruler_spec.rb +145 -0
  82. data/spec/tailor/rulers/spaces_before_lbrace_ruler_spec.rb +63 -0
  83. data/spec/tailor/rulers/spaces_before_rbrace_ruler_spec.rb +63 -0
  84. data/spec/tailor/rulers_spec.rb +9 -0
  85. data/spec/tailor/version_spec.rb +6 -0
  86. data/spec/tailor_spec.rb +9 -21
  87. data/tailor.gemspec +22 -35
  88. data/tasks/features.rake +7 -0
  89. data/tasks/roodi.rake +9 -0
  90. data/tasks/roodi_config.yaml +14 -0
  91. data/tasks/spec.rake +16 -0
  92. data/tasks/yard.rake +14 -0
  93. metadata +224 -77
  94. data/features/case_checking.feature +0 -38
  95. data/features/spacing.feature +0 -97
  96. data/features/spacing/commas.feature +0 -44
  97. data/features/step_definitions/case_checking_steps.rb +0 -42
  98. data/features/step_definitions/spacing_steps.rb +0 -156
  99. data/features/support/1_file_with_bad_comma_spacing/bad_comma_spacing.rb +0 -43
  100. data/features/support/1_file_with_bad_curly_brace_spacing/bad_curly_brace_spacing.rb +0 -60
  101. data/features/support/1_file_with_bad_parenthesis/bad_parenthesis.rb +0 -4
  102. data/features/support/1_file_with_bad_square_brackets/bad_square_brackets.rb +0 -62
  103. data/features/support/1_file_with_camel_case_class/camel_case_class.rb +0 -5
  104. data/features/support/1_file_with_camel_case_method/camel_case_method.rb +0 -3
  105. data/features/support/1_file_with_hard_tabs/hard_tab.rb +0 -3
  106. data/features/support/1_file_with_long_lines/long_lines.rb +0 -5
  107. data/features/support/1_file_with_snake_case_class/snake_case_class.rb +0 -5
  108. data/features/support/1_file_with_snake_case_method/snake_case_method.rb +0 -3
  109. data/features/support/1_file_with_trailing_whitespace/trailing_whitespace.rb +0 -5
  110. data/features/support/1_good_simple_file/simple_project.rb +0 -5
  111. data/features/support/common.rb +0 -102
  112. data/features/support/matchers.rb +0 -11
  113. data/lib/tailor/file_line.rb +0 -220
  114. data/lib/tailor/indentation.rb +0 -245
  115. data/lib/tailor/spacing.rb +0 -237
  116. data/spec/file_line_spec.rb +0 -70
  117. data/spec/indentation_spec.rb +0 -259
  118. data/spec/spacing/colon_spacing_spec.rb +0 -71
  119. data/spec/spacing/comma_spacing_spec.rb +0 -159
  120. data/spec/spacing/curly_brace_spacing_spec.rb +0 -257
  121. data/spec/spacing/parentheses_spacing_spec.rb +0 -28
  122. data/spec/spacing/square_bracket_spacing_spec.rb +0 -116
  123. data/spec/spacing_spec.rb +0 -167
  124. data/tasks/metrics.rake +0 -23
@@ -0,0 +1,49 @@
1
+ require_relative 'configuration'
2
+ require_relative 'critic'
3
+ require_relative 'cli/options'
4
+ require_relative 'logger'
5
+ require_relative 'reporter'
6
+
7
+ class Tailor
8
+
9
+ # The Command-Line Interface worker. Execution from the command line should
10
+ # come through here.
11
+ class CLI
12
+ include LogSwitch::Mixin
13
+
14
+ # The main method of execution from the command line.
15
+ def self.run(args)
16
+ new(args).execute!
17
+ end
18
+
19
+ def initialize(args)
20
+ Tailor::Logger.log = false
21
+ options = Options.parse!(args)
22
+
23
+ @configuration = Configuration.new(args, options)
24
+ @configuration.load!
25
+
26
+ if options.show_config
27
+ @configuration.show
28
+ end
29
+
30
+ @critic = Critic.new(@configuration.file_sets)
31
+ @reporter = Reporter.new(@configuration.formatters)
32
+ end
33
+
34
+ # This checks all of the files detected during the configuration gathering
35
+ # process, then hands results over to the {Reporter} to be reported.
36
+ #
37
+ # @return [Boolean] +true+ if no problems were detected; false if there
38
+ # were.
39
+ def execute!
40
+ @critic.critique do |problems_for_file, label|
41
+ @reporter.file_report(problems_for_file, label)
42
+ end
43
+
44
+ @reporter.summary_report(@critic.problems)
45
+
46
+ @critic.problem_count > 0
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,251 @@
1
+ require 'erb'
2
+ require 'optparse'
3
+ require 'ostruct'
4
+ require 'text-table'
5
+ require_relative '../version'
6
+ require_relative '../configuration'
7
+
8
+ class Tailor
9
+ class CLI
10
+ class Options
11
+ @output_color = true
12
+
13
+ def self.parse!(args)
14
+ options = OpenStruct.new
15
+ options.config_file = ''
16
+ options.formatters = []
17
+ options.show_config = false
18
+ options.style = {}
19
+
20
+ opts = OptionParser.new do |opt|
21
+ opt.banner = self.banner
22
+
23
+ opt.separator ""
24
+ opt.separator ""
25
+ opt.separator "Config file options:"
26
+ opt.on('-s', '--show-config', 'Show your current config.') do
27
+ options.show_config = true
28
+ end
29
+
30
+ opt.on('-c', '--config-file FILE',
31
+ "Use a specific config file.") do |config|
32
+ options.config_file = config
33
+ end
34
+
35
+ opt.on('--create-config', 'Create a new .tailor file') do
36
+ if create_config
37
+ msg = "Your new tailor config file was created at "
38
+ msg << "#{Dir.pwd}."
39
+ $stdout.puts msg
40
+ exit
41
+ else
42
+ $stderr.puts "Creation of .tailor failed!"
43
+ exit 1
44
+ end
45
+ end
46
+
47
+ #---------------------------------------------------------------------
48
+ # Style options
49
+ #---------------------------------------------------------------------
50
+ opt.separator ""
51
+ opt.separator "Style Options:"
52
+ opt.separator " (Any option that doesn't have an explicit way of"
53
+ opt.separator " turning it off can be done so simply by passing"
54
+ opt.separator " passing it 'false'.)"
55
+
56
+ opt.separator ""
57
+ opt.separator " * Horizontal Spacing:"
58
+ opt.on('--allow-hard-tabs BOOL',
59
+ 'Check for hard tabs? (default: true)') do |c|
60
+ options.style[:allow_hard_tabs] = c
61
+ end
62
+
63
+ opt.on('--allow-trailing-line-spaces BOOL',
64
+ 'Check for trailing spaces at the end of lines?',
65
+ '(default: true)') do |c|
66
+ options.style[:allow_trailing_line_spaces] = c
67
+ end
68
+
69
+ opt.on('--indentation-spaces NUMBER',
70
+ 'Spaces to expect indentation. (default: 2)') do |c|
71
+ options.style[:indentation_spaces] = c
72
+ end
73
+
74
+ opt.on('--max-line-length NUMBER',
75
+ 'Max characters in a line. (default: 80)') do |c|
76
+ options.style[:max_line_length] = c
77
+ end
78
+
79
+ opt.on('--spaces-after-comma NUMBER',
80
+ 'Spaces to expect after a comma. (default: 1)') do |c|
81
+ options.style[:spaces_after_comma] = c
82
+ end
83
+
84
+ opt.on('--spaces-before-comma NUMBER',
85
+ 'Spaces to expect before a comma. (default: 0)') do |c|
86
+ options.style[:spaces_before_comma] = c
87
+ end
88
+
89
+ opt.on('--spaces-after-lbrace NUMBER',
90
+ 'Spaces to expect after a {. (default: 1)') do |c|
91
+ options.style[:spaces_after_lbrace] = c
92
+ end
93
+
94
+ opt.on('--spaces-before-lbrace NUMBER',
95
+ 'Spaces to expect before a {. (default: 1)') do |c|
96
+ options.style[:spaces_before_lbrace] = c
97
+ end
98
+
99
+ opt.on('--spaces-before-rbrace NUMBER', Integer,
100
+ 'Spaces to expect before a }. (default: 1)') do |c|
101
+ options.style[:spaces_before_rbrace] = c
102
+ end
103
+
104
+ opt.on('--spaces-in-empty-braces NUMBER', Integer,
105
+ 'Spaces to expect between a { and }. (default: 0)') do |c|
106
+ options.style[:spaces_in_empty_braces] = c
107
+ end
108
+
109
+ opt.on('--spaces-after-lbracket NUMBER', Integer,
110
+ 'Spaces to expect after a [. (default: 0)') do |c|
111
+ options.style[:spaces_after_lbracket] = c
112
+ end
113
+
114
+ opt.on('--spaces-before-rbracket NUMBER', Integer,
115
+ 'Spaces to expect before a ]. (default: 0)') do |c|
116
+ options.style[:spaces_before_rbracket] = c
117
+ end
118
+
119
+ opt.on('--spaces-after-lparen NUMBER', Integer,
120
+ 'Spaces to expect after a (. (default: 0)') do |c|
121
+ options.style[:spaces_after_lparen] = c
122
+ end
123
+
124
+ opt.on('--spaces-before-rparen NUMBER', Integer,
125
+ 'Spaces to expect before a ). (default: 0)') do |c|
126
+ options.style[:spaces_before_rparen] = c
127
+ end
128
+
129
+ opt.separator ""
130
+ opt.separator ""
131
+
132
+ opt.separator " * Naming:"
133
+
134
+ opt.on('--allow-camel-case-methods BOOL',
135
+ 'Check for camel-case method names?', '(default: true)') do |c|
136
+ options.style[:allow_camel_case_methods] = instance_eval(c)
137
+ end
138
+
139
+ opt.on('--allow-screaming-snake-case-classes BOOL',
140
+ 'Check for classes like "My_Class"?', '(default: true)') do |c|
141
+ options.style[:allow_screaming_snake_case_classes] =
142
+ instance_eval(c)
143
+ end
144
+
145
+ opt.separator ""
146
+ opt.separator ""
147
+ opt.separator " * Vertical Spacing"
148
+
149
+ opt.on('--max-code-lines-in-class NUMBER', Integer,
150
+ 'Max number lines of code in a class.', '(default: 300)') do |c|
151
+ options.style[:max_code_lines_in_class] = c
152
+ end
153
+
154
+ opt.on('--max-code-lines-in-method NUMBER', Integer,
155
+ 'Max number lines of code in a method.', '(default: 30)') do |c|
156
+ options.style[:max_code_lines_in_method] = c
157
+ end
158
+
159
+ opt.on('--trailing-newlines NUMBER', Integer,
160
+ 'Newlines to expect at the end of the file.', '(default: 1)') do |c|
161
+ options.style[:trailing_newlines] = c
162
+ end
163
+
164
+ #---------------------------------------------------------------------
165
+ # Common options
166
+ #---------------------------------------------------------------------
167
+ opt.separator ""
168
+ opt.separator "Common options:"
169
+
170
+ opt.on('-f', '--format FORMATTER') do |format|
171
+ options.formatters << format
172
+ end
173
+
174
+ opt.on('--[no-]color', "Output in color") do |color|
175
+ @output_color = color
176
+ end
177
+
178
+ opt.on_tail('-v', '--version', "Show the version") do
179
+ puts version
180
+ exit
181
+ end
182
+
183
+ opt.on_tail('-d', '--debug', "Turn on debug logging") do
184
+ Tailor::Logger.log = true
185
+ end
186
+
187
+ opt.on_tail('-h', '--help', 'Show this message') do |help|
188
+ puts opt
189
+ exit
190
+ end
191
+ end
192
+
193
+ opts.parse!(args)
194
+ require_relative '../../ext/string_ext' if @output_color
195
+
196
+ options
197
+ end
198
+
199
+ # @return [String]
200
+ def self.banner
201
+ ruler + about + "\r\n" + usage + "\r\n"
202
+ end
203
+
204
+ # @return [String]
205
+ def self.version
206
+ ruler + about + "\r\n"
207
+ end
208
+
209
+ # @return [String]
210
+ def self.ruler
211
+ <<-RULER
212
+ _________________________________________________________________________
213
+ | | | | | | | | | | | | | | | | | | | | | | | | |
214
+ | | | | | | | | | | | | |
215
+ | | | | | | |
216
+ | 1 2 3 4 5 |
217
+ | |
218
+ -------------------------------------------------------------------------
219
+ RULER
220
+ end
221
+
222
+ # @return [String]
223
+ def self.about
224
+ <<-ABOUT
225
+ tailor (v#{Tailor::VERSION}). \t\tA Ruby style checker.
226
+ \t\t\t\t\thttp://github.com/turboladen/tailor
227
+ ABOUT
228
+ end
229
+
230
+ # @return [String]
231
+ def self.usage
232
+ " Usage: tailor [FILE|DIR]"
233
+ end
234
+
235
+ def self.create_config
236
+ if File.exists? Dir.pwd + '/.tailor'
237
+ $stderr.puts "Can't create new config; it already exists."
238
+ false
239
+ else
240
+ erb_file = File.expand_path(
241
+ File.dirname(__FILE__) + '/../tailorrc.erb')
242
+ formatters = Tailor::Configuration.default.formatters
243
+ file_list = 'lib/**/*.rb'
244
+ style = Tailor::Configuration.default.file_sets[:default][:style]
245
+ default_config_file = ERB.new(File.read(erb_file)).result(binding)
246
+ File.open('.tailor', 'w') { |f| f.write default_config_file }
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,56 @@
1
+ class Tailor
2
+ module CompositeObservable
3
+ def self.define_observer(name)
4
+ define_method("add_#{name}_observer") do |observer|
5
+ @notifiers = {} unless defined? @notifiers
6
+ @notifiers[name] = {} unless @notifiers[name]
7
+
8
+ call_back = "#{name}_update".to_sym
9
+
10
+ unless observer.respond_to? call_back
11
+ raise NoMethodError, "observer does not respond to '#{call_back}'"
12
+ end
13
+
14
+ @notifiers[name][observer] = call_back
15
+ end
16
+
17
+ define_method("notify_#{name}_observers") do |*args|
18
+ if defined? @notifier_state and @notifier_state
19
+ if @notifier_state[name]
20
+ if defined? @notifiers and @notifiers[name]
21
+ @notifiers[name].each do |k, v|
22
+ k.send(v, *args)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ define_method("#{name}_changed") do
30
+ @notifier_state = {} unless defined? @notifier_state
31
+ @notifier_state[name] = true
32
+ end
33
+ end
34
+
35
+ define_observer :comma
36
+ define_observer :comment
37
+ define_observer :const
38
+ define_observer :embexpr_beg
39
+ define_observer :embexpr_end
40
+ define_observer :file
41
+ define_observer :ident
42
+ define_observer :ignored_nl
43
+ define_observer :kw
44
+ define_observer :lbrace
45
+ define_observer :lbracket
46
+ define_observer :lparen
47
+ define_observer :nl
48
+ define_observer :period
49
+ define_observer :rbrace
50
+ define_observer :rbracket
51
+ define_observer :rparen
52
+ define_observer :sp
53
+ define_observer :tstring_beg
54
+ define_observer :tstring_end
55
+ end
56
+ end
@@ -0,0 +1,263 @@
1
+ require_relative '../tailor'
2
+ require_relative 'runtime_error'
3
+ require_relative 'logger'
4
+
5
+ class Tailor
6
+
7
+ # Pulls in any configuration from the places configuration can be set:
8
+ # 1. ~/.tailorrc
9
+ # 2. CLI options
10
+ # 3. Default options
11
+ #
12
+ # It then basically represents a list of "file sets" and the rulers that
13
+ # should be applied against each file set.
14
+ class Configuration
15
+ include Tailor::Logger::Mixin
16
+
17
+ DEFAULT_GLOB = 'lib/**/*.rb'
18
+ DEFAULT_RC_FILE = Dir.home + '/.tailorrc'
19
+ DEFAULT_PROJECT_CONFIG = Dir.pwd + '/.tailor'
20
+ DEFAULT_STYLE = {
21
+ allow_camel_case_methods: false,
22
+ allow_hard_tabs: false,
23
+ allow_screaming_snake_case_classes: false,
24
+ allow_trailing_line_spaces: false,
25
+ indentation_spaces: 2,
26
+ max_code_lines_in_class: 300,
27
+ max_code_lines_in_method: 30,
28
+ max_line_length: 80,
29
+ spaces_after_comma: 1,
30
+ spaces_before_comma: 0,
31
+ spaces_before_lbrace: 1,
32
+ spaces_after_lbrace: 1,
33
+ spaces_before_rbrace: 1,
34
+ spaces_in_empty_braces: 0,
35
+ spaces_after_lbracket: 0,
36
+ spaces_before_rbracket: 0,
37
+ spaces_after_lparen: 0,
38
+ spaces_before_rparen: 0,
39
+ trailing_newlines: 1
40
+ }
41
+
42
+ # @return [Hash]
43
+ def self.default
44
+ new
45
+ end
46
+
47
+ attr_reader :file_sets
48
+ attr_reader :formatters
49
+
50
+ # @param [Array] runtime_file_list
51
+ # @param [OpenStruct] options
52
+ # @option options [String] config_file
53
+ # @option options [Array] formatters
54
+ # @option options [Hash] style
55
+ def initialize(runtime_file_list=nil, options=nil)
56
+ @formatters = ['text']
57
+ @file_sets = {
58
+ default: {
59
+ file_list: file_list(DEFAULT_GLOB),
60
+ style: DEFAULT_STYLE
61
+ }
62
+ }
63
+
64
+ @runtime_file_list = runtime_file_list
65
+ log "Got runtime file list: #{@runtime_file_list}"
66
+ @options = options
67
+ log "Got options: #{@options}"
68
+ end
69
+
70
+ # Call this to load settings from the config file and from CLI options.
71
+ def load!
72
+ # Get config file settings
73
+ @config_file = @options.config_file unless @options.config_file.empty?
74
+ load_from_config_file(config_file)
75
+
76
+ if @config_file
77
+
78
+ if @rc_file_config
79
+ # Get formatters from config file
80
+ unless @rc_file_config.formatters.empty?
81
+ @formatters = @rc_file_config.formatters
82
+ log "@formatters is now #{@formatters}"
83
+ end
84
+
85
+ # Get file sets from config file
86
+ unless @rc_file_config.file_sets.empty?
87
+ @rc_file_config.file_sets.each do |label, file_set|
88
+ unless @file_sets[label]
89
+ @file_sets[label] = { style: DEFAULT_STYLE }
90
+ end
91
+
92
+ @file_sets[label][:file_list].concat file_set[:file_list]
93
+ @file_sets[label][:file_list].uniq!
94
+ @file_sets[label][:style].merge! file_set[:style]
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # Get formatters from CLI options
101
+ unless @options.formatters.empty? || @options.formatters.nil?
102
+ @formatters = @options.formatters
103
+ log "@formatters is now #{@formatters}"
104
+ end
105
+
106
+ # Get file sets from CLI options
107
+ unless @runtime_file_list.nil? || @runtime_file_list.empty?
108
+ # Only use options set for the :default file set because the user gave
109
+ # a different set of files to measure.
110
+ @file_sets.delete_if { |k, v| k != :default }
111
+ @file_sets[:default][:file_list] = file_list(@runtime_file_list)
112
+ end
113
+
114
+ # Get style overrides from CLI options
115
+ @file_sets[:default][:style].merge!(@options.style)
116
+
117
+ if @file_sets[:default][:file_list].empty?
118
+ @file_sets[:default][:file_list] = file_list(DEFAULT_GLOB)
119
+ end
120
+
121
+ end
122
+
123
+ # @return [String] Name of the config file to use.
124
+ def config_file
125
+ return @config_file if @config_file
126
+
127
+ if File.exists?(DEFAULT_PROJECT_CONFIG)
128
+ return @config_file = DEFAULT_PROJECT_CONFIG
129
+ end
130
+
131
+ if File.exists?(DEFAULT_RC_FILE)
132
+ return @config_file = DEFAULT_RC_FILE
133
+ end
134
+ end
135
+
136
+ # @return [Array] The list of formatters.
137
+ def formatters(*new_formatters)
138
+ @formatters = new_formatters unless new_formatters.empty?
139
+
140
+ @formatters
141
+ end
142
+
143
+ # @param [String] file_glob The String that represents the file set. This
144
+ # can be a file, directory, or a glob.
145
+ # @param [Symbol] label The label that represents the file set.
146
+ def file_set(file_glob=DEFAULT_GLOB, label=:default, &block)
147
+ log "file set label #{label}"
148
+
149
+ @temp_style = {}
150
+ instance_eval(&block) if block_given?
151
+
152
+ if @file_sets[label]
153
+ @file_sets[label][:file_list].concat file_list(file_glob)
154
+ log "file set: #{@file_sets}"
155
+ @file_sets[label][:file_list].uniq!
156
+ @file_sets[label][:style].merge @temp_style
157
+ else
158
+ @file_sets[label] = {
159
+ file_list: file_list(file_glob),
160
+ style: DEFAULT_STYLE.merge(@temp_style)
161
+ }
162
+ end
163
+
164
+ @temp_style = {}
165
+ end
166
+
167
+ # Implemented for {file_set}, this converts the config file lines that look
168
+ # like methods into a Hash.
169
+ #
170
+ # @return [Hash] The new style as defined by the config file.
171
+ def method_missing(meth, *args, &blk)
172
+ ok_methods = DEFAULT_STYLE.keys
173
+
174
+ if ok_methods.include? meth
175
+ @temp_style[meth] = args.first
176
+ else
177
+ super(meth, args, blk)
178
+ end
179
+ end
180
+
181
+ # Tries to open the file at the path given at +config_file+ and read in
182
+ # the configuration given there.
183
+ #
184
+ # @param [String] config_file Path to the config file to use.
185
+ def load_from_config_file(config_file)
186
+ user_config_file = File.expand_path(config_file)
187
+
188
+ if File.exists? user_config_file
189
+ log "Loading config from file: #{user_config_file}"
190
+ begin
191
+ config = instance_eval File.read(user_config_file)
192
+ rescue LoadError => ex
193
+ raise Tailor::RuntimeError,
194
+ "Couldn't load config file: #{user_config_file}"
195
+ end
196
+ else
197
+ log "No config file found at #{user_config_file}."
198
+ end
199
+
200
+ if config
201
+ log "Got new config from file: #{config}"
202
+ @rc_file_config = config
203
+ end
204
+ end
205
+
206
+ # Gets a list of only files that are in +base_dir+.
207
+ #
208
+ # @param [String] base_dir The directory to get the file list for.
209
+ # @return [Array<String>] The List of files.
210
+ def all_files_in_dir(base_dir)
211
+ files = Dir.glob(File.join(base_dir, '**', '*')).find_all do |file|
212
+ file if File.file?(file)
213
+ end
214
+
215
+ files
216
+ end
217
+
218
+ # The list of the files in the project to check.
219
+ #
220
+ # @param [String] glob Path to the file, directory or glob to check.
221
+ # @return [Array] The list of files to check.
222
+ def file_list(glob)
223
+ files_in_project = if glob.is_a? Array
224
+ log "Configured glob is an Array: #{glob}"
225
+
226
+ glob.map do |e|
227
+ if File.directory?(e)
228
+ all_files_in_dir(e)
229
+ else
230
+ e
231
+ end
232
+ end.flatten.uniq
233
+ elsif File.directory? glob
234
+ log "Configured glob is an directory: #{glob}"
235
+ all_files_in_dir(glob)
236
+ else
237
+ log "Configured glob is a glob/single-file: #{glob}"
238
+ Dir.glob glob
239
+ end
240
+
241
+ list_with_absolute_paths = []
242
+
243
+ files_in_project.each do |file|
244
+ list_with_absolute_paths << File.expand_path(file)
245
+ end
246
+
247
+ log "All files: #{list_with_absolute_paths}"
248
+
249
+ list_with_absolute_paths.sort
250
+ end
251
+
252
+ def show
253
+ table = Text::Table.new(horizontal_padding: 4)
254
+ table.head = [{ value: 'Configuration', colspan: 2, align: :center }]
255
+ table.rows << :separator
256
+ table.rows << ['Style', @file_sets.inspect]
257
+ table.rows << :separator
258
+ table.rows << ['Formatters', @formatters]
259
+
260
+ puts table
261
+ end
262
+ end
263
+ end