ci-syntax-tool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/LICENSE +27 -0
  4. data/README.md +109 -0
  5. data/Rakefile +43 -0
  6. data/bin/ci-syntax-tool +12 -0
  7. data/ci-syntax-tool.gemspec +27 -0
  8. data/lib/ci-syntax-tool.rb +6 -0
  9. data/lib/ci-syntax-tool/checker.rb +63 -0
  10. data/lib/ci-syntax-tool/command_line.rb +229 -0
  11. data/lib/ci-syntax-tool/format/base.rb +50 -0
  12. data/lib/ci-syntax-tool/format/junit.rb +54 -0
  13. data/lib/ci-syntax-tool/format/progress.rb +60 -0
  14. data/lib/ci-syntax-tool/format_factory.rb +55 -0
  15. data/lib/ci-syntax-tool/language/base.rb +56 -0
  16. data/lib/ci-syntax-tool/language/yaml.rb +41 -0
  17. data/lib/ci-syntax-tool/language_factory.rb +41 -0
  18. data/lib/ci-syntax-tool/result.rb +134 -0
  19. data/lib/ci-syntax-tool/version.rb +10 -0
  20. data/rubocop.yml +11 -0
  21. data/test/features/.keep +0 -0
  22. data/test/features/command-line-help.feature +15 -0
  23. data/test/features/format-junit.feature +29 -0
  24. data/test/features/language-yaml.feature +34 -0
  25. data/test/features/pluggable-formatters.feature +42 -0
  26. data/test/features/pluggable-languages.feature +15 -0
  27. data/test/features/require-ruby.feature +38 -0
  28. data/test/features/step_definitions/cli_steps.rb +46 -0
  29. data/test/features/step_definitions/format_steps.rb +63 -0
  30. data/test/features/step_definitions/junit_steps.rb +57 -0
  31. data/test/features/step_definitions/language_steps.rb +39 -0
  32. data/test/features/step_definitions/require_steps.rb +38 -0
  33. data/test/features/support/feature_helper.rb +142 -0
  34. data/test/fixtures/.keep +0 -0
  35. data/test/fixtures/files/clean/README.md +6 -0
  36. data/test/fixtures/files/clean/ansiblish.yaml +12 -0
  37. data/test/fixtures/files/clean/kitchenish.yml +17 -0
  38. data/test/fixtures/files/clean/rubocopish.yaml +11 -0
  39. data/test/fixtures/files/error/bad-indentation.yaml +5 -0
  40. data/test/fixtures/files/error/missing-array-element.yaml +5 -0
  41. data/test/fixtures/files/error/unquoted-jinja-template.yaml +3 -0
  42. data/test/fixtures/files/error/very-high-yaml-version.yaml +3 -0
  43. data/test/fixtures/require/invalid.rb +6 -0
  44. data/test/fixtures/require/mock_format.rb +10 -0
  45. data/test/fixtures/require/second.rb +10 -0
  46. data/test/fixtures/require/valid.rb +10 -0
  47. data/test/unit/.keep +0 -0
  48. data/test/unit/format_factory_spec.rb +46 -0
  49. data/test/unit/language_factory_spec.rb +46 -0
  50. data/test/unit/result_spec.rb +18 -0
  51. data/test/unit/spec_helper.rb +31 -0
  52. metadata +201 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 12e0cbc5efe12dc146a5ede4094ea62f0137915d
4
+ data.tar.gz: 480b4eff820df5aac1ad736330918492f5a9324b
5
+ SHA512:
6
+ metadata.gz: c77c377d030d48e67134ef124633a1d2307288cbe0f6f00cc5fdc9bd4636c966b530ad65bf2971b0ba2cdac55d055f9ec32da99c193fb661cf59fc2aed49b555
7
+ data.tar.gz: 55f16c7324455770784844f16243c2038b2371e1cd1a95602658c3d122c815204458bd6507d2d0944850b91f3bcba8ef52fecee80009cf99a28baf4303eda837
@@ -0,0 +1,3 @@
1
+ *~
2
+ pkg/*
3
+ test/tmp/*
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2015, Clinton Wolfe
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice, this
11
+ list of conditions and the following disclaimer in the documentation and/or
12
+ other materials provided with the distribution.
13
+
14
+ * Neither the name of 'ci-syntax-tool' nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,109 @@
1
+ # CI Syntax Tool
2
+
3
+ ## Overview
4
+
5
+ Checks various source file types for syntax errors, and formats the results in a flexible way.
6
+
7
+ ## Command Line Options
8
+
9
+ | Short | Long | Description |
10
+ |-------|------|-------------|
11
+ | -V | --version | Show ci-syntax-tool version |
12
+ | -h | --help | Show this help message |
13
+ | -l | --lang LANG| Select this language for checking. Repeatable. Default, all languages.|
14
+ | | --list-languages | List available languages and exit |
15
+ | -f | --format FORMAT | Use this format for output. Repeatable, but if repeated, must have an equal number of --output options.|
16
+ | -o | --output PATH | Write formatted output to this location. Use "-" to represent STDOUT. Defaults to STDOUT if zero or one --format option used. Repeatable with an equal number of --format options.'|
17
+ | | --list-formats | List available formats and exit.|
18
+ | -r | --require RUBYFILE | Load additional Ruby code, perhaps for a custom language or format. Repeatable. |
19
+ | -d | --debug | Provide debug-level output to STDOUT.|
20
+
21
+ ## Exit codes
22
+
23
+ ### 0
24
+
25
+ Success - no files contained errors, or an option was given that did not involve a scan.
26
+
27
+ ### 1
28
+
29
+ At least one file contained an error.
30
+
31
+ ### 2
32
+
33
+ Reserved.
34
+
35
+ ### 3
36
+
37
+ All usage errors related to selecting or loading a Language plugin. See STDERR for details, and possibly add --debug.
38
+
39
+ ### 4
40
+
41
+ All usage errors related to selecting or loading a Format plugin. See STDERR for details, and possibly add --debug.
42
+
43
+ ### 5
44
+
45
+ All usage errors related to the --require option. See STDERR for details, and possibly add --debug.
46
+
47
+ ### 6
48
+
49
+ You specified a path to check that does not exist.
50
+
51
+ ## Examples
52
+
53
+ ### With no options
54
+
55
+ `ci-syntax-tool`
56
+
57
+ Loads all core syntax plugins, then uses their globs to match all files below the current directory, printing a report to STDOUT using the Progress format. If any errors are seen, exit code 1, otherwise exit 0.
58
+
59
+ This might be good enough for a commit hook.
60
+
61
+ ### Force a language to match a particular file
62
+
63
+ `ci-syntax-tool --lang YAML SomeYamlFile.txt
64
+
65
+ If a file doesn't have an extension that would match the usual glob, you can specify one or more filenames, bypassing the globbing mechanism. Use --lang to keep it restricted to only the language you know you want. Again, the default Progress format sends a report to STDOUT.
66
+
67
+ ## Language Syntax Plugins
68
+
69
+ So far, we support:
70
+
71
+ ### YAML
72
+
73
+ Using the ruby Psych parser.
74
+
75
+ ### Adding Your Own
76
+
77
+ Extend CI::Syntax::Tool::Language::Base, and use --require to include your library.
78
+
79
+ ## Output Format Plugins
80
+
81
+ So far, we support:
82
+
83
+ ### JUnit
84
+
85
+ Produces an XML report that is jUnit/nUnit/SureFire compatible, probably. Many CI engines can read this, but how they interpret it varies.
86
+
87
+ ### Progress
88
+
89
+ The default format. Prints a '.', '*', or 'x' for each file that is clean, has nonzero warnings, or nonzero errors, respectively, followed by a list of problematic files.
90
+
91
+ ### Adding Your Own
92
+
93
+ Extend CI::Syntax::Tool::Format::Base, and use --require to include your library.
94
+
95
+ ## Bugs and Defects
96
+
97
+ Perfect, AFAIK.
98
+
99
+ ## Author
100
+
101
+ Clinton Wolfe
102
+
103
+ ## Contributing
104
+
105
+ 1. Fork it (https://github.com/omniti-labs/ci-syntax-tool)
106
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
107
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
108
+ 4. Push to the branch (`git push origin my-new-feature`)
109
+ 5. Create new Pull Request at (https://github.com/omniti-labs/ci-syntax-tool)
@@ -0,0 +1,43 @@
1
+ # -*-ruby-*-
2
+
3
+ require 'rake'
4
+ require 'bundler'
5
+ require 'bundler/gem_tasks'
6
+ require 'cucumber'
7
+ require 'cucumber/rake/task'
8
+ require 'rubocop/rake_task'
9
+ require 'rspec/core/rake_task'
10
+
11
+ namespace :test do
12
+ desc 'Checks ruby files for syntax errors'
13
+ task :syntax do
14
+ puts '------------Syntax-----------'
15
+ Dir.glob('**/*.rb').each do |f|
16
+ system("/bin/echo -n '#{f}: '; ruby -c #{f}")
17
+ end
18
+ end
19
+
20
+ desc 'Run unit tests'
21
+ RSpec::Core::RakeTask.new(:unit) do |t|
22
+ t.pattern = 'test/unit/**/*_spec.rb'
23
+ end
24
+
25
+ desc 'Run RuboCop'
26
+ RuboCop::RakeTask.new(:rubocop) do |task|
27
+ task.options = ['-c', 'rubocop.yml']
28
+ task.patterns = ['bin/*', 'lib/**/*.rb', 'Rakefile']
29
+ end
30
+
31
+ Cucumber::Rake::Task.new(:features) do |t|
32
+ t.cucumber_opts = ['-f', 'progress', '--strict']
33
+ unless ENV.key?('FC_FORK_PROCESS') \
34
+ && ENV['FC_FORK_PROCESS'] == true.to_s
35
+ t.cucumber_opts += ['-t', '~@build']
36
+ t.cucumber_opts += ['-t', '~@context']
37
+ end
38
+ t.cucumber_opts += ['test/features']
39
+ end
40
+ end
41
+
42
+ desc 'Runs all development tests'
43
+ task test: [:'test:syntax', :'test:rubocop', :'test:unit', :'test:features']
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ require 'ci-syntax-tool'
3
+ module CI
4
+ module Syntax
5
+ # CI::Syntax::Tool - binstub
6
+ module Tool
7
+ cmd_line = CommandLine.new(ARGV)
8
+ status = Checker.new(cmd_line).run
9
+ exit status.to_i
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8 # -*-ruby-*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ci-syntax-tool/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ci-syntax-tool"
8
+ spec.version = CI::Syntax::Tool::VERSION
9
+ spec.authors = ["Clinton Wolfe"]
10
+ spec.email = ["clinton@omniti.com"]
11
+ spec.description = %q{Checks YAML, JSON, Ruby, ERB, and other syntaxes, then reports errors and OKs in a nice way for your CI system.}
12
+ spec.summary = %q{A Rubygem implementing a suite of source code syntax checkers, with well-behaved output fo use with CI engines.}
13
+ spec.homepage = "https://github.com/clintoncwolfe/ci-syntax-tool"
14
+ spec.license = "BSD (3-clause)"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake", '~> 0.8'
23
+ spec.add_development_dependency "cucumber", '~> 2.0'
24
+ spec.add_development_dependency "rubocop", '~> 0.28'
25
+ spec.add_runtime_dependency "nokogiri", '~> 1.6'
26
+
27
+ end
@@ -0,0 +1,6 @@
1
+ require 'ci-syntax-tool/version'
2
+ require 'ci-syntax-tool/command_line'
3
+ require 'ci-syntax-tool/result'
4
+ require 'ci-syntax-tool/checker'
5
+ require 'ci-syntax-tool/language_factory'
6
+ require 'ci-syntax-tool/format_factory'
@@ -0,0 +1,63 @@
1
+ module CI
2
+ module Syntax
3
+ module Tool
4
+ # CI::Syntax::Tool::Checker
5
+ # Main driver of the tool. For each language,
6
+ # locates files, runs the check, and outputs
7
+ # to each formatter.
8
+ class Checker
9
+ attr_reader :cmd_line
10
+ attr_reader :overall_result
11
+
12
+ def initialize(cmd_line)
13
+ @cmd_line = cmd_line
14
+ end
15
+
16
+ def run
17
+
18
+ formats = []
19
+ cmd_line.options[:formats].each_with_index do |fmt_name, idx|
20
+ formats << FormatFactory.create(fmt_name, cmd_line.options[:outputs][idx])
21
+ end
22
+
23
+ @overall_result = Result::OverallResult.new()
24
+ formats.each { |fmt| fmt.overall_started(overall_result) }
25
+
26
+ cmd_line.options[:languages].each do |lang_name|
27
+ language_result = overall_result.add_language_result(lang_name)
28
+ lang = LanguageFactory.create(lang_name)
29
+
30
+ lang.check_starting(language_result)
31
+ formats.each { |fmt| fmt.lang_started(language_result) }
32
+
33
+ cmd_line.file_args.each do |argpath|
34
+ if FileTest.directory?(argpath)
35
+ Dir.chdir(argpath) do
36
+ Dir.glob(lang.combined_globs).each do |path|
37
+ file_result = language_result.add_file_result(path)
38
+ formats.each { |fmt| fmt.file_started(file_result) }
39
+ lang.check_file(file_result)
40
+ formats.each { |fmt| fmt.file_finished(file_result) }
41
+ end
42
+ end
43
+ else
44
+ # No glob check here - user explicitly specified a file
45
+ file_result = language_result.add_file_result(argpath)
46
+ formats.each { |fmt| fmt.file_started(file_result) }
47
+ lang.check_file(file_result)
48
+ formats.each { |fmt| fmt.file_finished(file_result) }
49
+ end
50
+ end
51
+ lang.check_ending(language_result)
52
+ formats.each { |fmt| fmt.lang_finished(language_result) }
53
+ end
54
+
55
+ formats.each { |fmt| fmt.overall_finished(overall_result) }
56
+ return overall_result.report_failure? ? 1 : 0
57
+
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,229 @@
1
+
2
+ require 'optparse'
3
+
4
+ module CI
5
+ module Syntax
6
+ module Tool
7
+ # CI::Syntax::Tool::CommandLine
8
+ # a class to parse and represent the CLI options
9
+ # also handles degenerate CLI cases, like --help,
10
+ # --version, --list-languages, --list-formatters
11
+ class CommandLine
12
+ attr_accessor :non_runnable_exit_status
13
+ attr_reader :languages
14
+ attr_reader :formatters
15
+ attr_reader :options
16
+ attr_reader :file_args
17
+
18
+ def initialize(args)
19
+ @args = args.dup
20
+ @runnable = true
21
+ @non_runnable_exit_status = 0
22
+ @options = {
23
+ languages: [],
24
+ requires: [],
25
+ formats: [],
26
+ outputs: [],
27
+ }
28
+ @file_args = []
29
+
30
+ @parser = make_parser
31
+
32
+ parse_args
33
+ validate_file_args if runnable?
34
+ load_requires if runnable?
35
+ validate_languages if runnable?
36
+ validate_formats if runnable?
37
+ list_languages if runnable? && @options[:list_languages]
38
+ list_formats if runnable? && @options[:list_formats]
39
+
40
+ end
41
+
42
+ # rubocop: disable Style/TrivialAccessors
43
+ def runnable?
44
+ @runnable
45
+ end
46
+ # rubocop: enable Style/TrivialAccessors
47
+
48
+ private
49
+
50
+ # rubocop: disable MethodLength
51
+ def make_parser
52
+ ::OptionParser.new do |opts|
53
+ opts.banner = 'ci-syntax-tool [options] [path]'
54
+
55
+ opts.on('-V', '--version',
56
+ 'Show ci-syntax-tool version') do
57
+ @options[:version] = true
58
+ end
59
+
60
+ opts.on('-h', '--help',
61
+ 'Show this help message') do
62
+ @options[:help] = true
63
+ end
64
+
65
+ opts.on('-l', '--lang LANG',
66
+ 'Select this language for checking. Repeatable.' \
67
+ ' Default, all languages.') do |lang|
68
+ @options[:languages] << lang
69
+ end
70
+
71
+ opts.on('--list-languages',
72
+ 'List available languages and exit.') do
73
+ @options[:list_languages] = true
74
+ end
75
+
76
+ opts.on('-d', '--debug',
77
+ 'Provide debug-level output to STDOUT.') do
78
+ @options[:debug] = true
79
+ end
80
+
81
+ opts.on('-r', '--require RUBYFILE',
82
+ 'Load additional Ruby code, perhaps for a custom ' \
83
+ 'language or format. Repeatable.') do |r|
84
+ @options[:requires] << r
85
+ end
86
+
87
+ opts.on('-f', '--format FORMAT',
88
+ 'Use this format for output. Repeatable, but if ' \
89
+ 'repeated, must have an equal number of --output ' \
90
+ 'options.') do |fmt|
91
+ @options[:formats] << fmt
92
+ end
93
+
94
+ opts.on('-o', '--output PATH',
95
+ 'Write formatted output to this location. Use "-" '\
96
+ 'to represent STDOUT. Defaults to STDOUT if zero or ' \
97
+ 'one --format option used. Repeatable with an equal ' \
98
+ 'number of --format options.') do |path|
99
+ @options[:outputs] << path
100
+ end
101
+
102
+ opts.on('--list-formats',
103
+ 'List available formats and exit.') do
104
+ @options[:list_formats] = true
105
+ end
106
+
107
+ end
108
+ end
109
+ # rubocop: enable MethodLength
110
+
111
+ def parse_args
112
+ if @args.include?('-V') || @args.include?('--version')
113
+ # Handle version case, which otherwise is eaten by optionparser
114
+ @runnable = false
115
+ @non_runnable_exit_status = 0
116
+ puts 'ci-syntax-tool ' + CI::Syntax::Tool::VERSION
117
+
118
+ elsif @args.include?('-h') || @args.include?('--help')
119
+ # Handle help case, which otherwise is eaten by optionparser
120
+ @runnable = false
121
+ @non_runnable_exit_status = 0
122
+ puts @parser.help
123
+ else
124
+ begin
125
+ @parser.parse!(@args)
126
+ @file_args = @args
127
+ rescue OptionParser::InvalidOption => e
128
+ e.recover @args
129
+ end
130
+ end
131
+ end
132
+
133
+ def list_languages
134
+ LanguageFactory.all_language_names.sort.each do |lang_name|
135
+ puts lang_name
136
+ end
137
+ @runnable = false
138
+ end
139
+
140
+ def list_formats
141
+ FormatFactory.all_format_names.sort.each do |fmt_name|
142
+ puts fmt_name
143
+ end
144
+ @runnable = false
145
+ end
146
+
147
+ def validate_languages
148
+ @options[:languages].each do |lang_opt|
149
+ next if LanguageFactory.valid_language?(lang_opt)
150
+
151
+ $stderr.puts "'#{lang_opt}' is not a valid language"
152
+ @runnable = false
153
+ @non_runnable_exit_status = 3
154
+ break
155
+ end
156
+
157
+ # If no languages were specified, do all languages
158
+ @options[:languages] = LanguageFactory.all_language_names if @options[:languages].empty?
159
+ end
160
+
161
+ def validate_file_args
162
+ # TODO: need feature tests on this
163
+ file_args.each do |path|
164
+ next if File.exist?(path)
165
+ $stderr.puts "'#{path}' does not exist"
166
+ @runnable = false
167
+ @non_runnable_exit_status = 6
168
+ break
169
+ end
170
+ file_args << '.' if file_args.empty?
171
+ end
172
+
173
+ def validate_formats
174
+
175
+ # If there are zero formats, assume 'Progress'
176
+ @options[:formats] << 'Progress' if @options[:formats].empty?
177
+
178
+ @options[:formats].each do |lang_opt|
179
+ next if FormatFactory.valid_format?(lang_opt)
180
+
181
+ $stderr.puts "'#{lang_opt}' is not a valid format"
182
+ @runnable = false
183
+ @non_runnable_exit_status = 4
184
+ return
185
+ end
186
+
187
+ # If there are zero outputs, assume '-'
188
+ @options[:outputs] << '-' if @options[:outputs].empty?
189
+
190
+ # Insist that the number of outputs
191
+ # match the number of formats
192
+ unless @options[:outputs].length == @options[:formats].length
193
+ $stderr.puts "Must have exactly the same number of outputs as formats."
194
+ @runnable = false
195
+ @non_runnable_exit_status = 4
196
+ end
197
+
198
+ end
199
+
200
+ def load_requires
201
+ @options[:requires].each do |require_path|
202
+
203
+ if File.exist?(require_path)
204
+ begin
205
+ Kernel.require(require_path)
206
+ rescue Exception => e
207
+ if options[:debug]
208
+ $stderr.puts e.class.name
209
+ $stderr.puts e.to_s
210
+ $stderr.puts e.backtrace.join("\n")
211
+ end
212
+ $stderr.puts "Could not load #{require_path} because it appears to be invalid."
213
+ @runnable = false
214
+ @non_runnable_exit_status = 5
215
+ break
216
+ end
217
+ else
218
+ $stderr.puts "Could not load #{require_path} because it appears to be missing."
219
+ @runnable = false
220
+ @non_runnable_exit_status = 5
221
+ break
222
+ end
223
+ end
224
+ end
225
+
226
+ end
227
+ end
228
+ end
229
+ end