ci-syntax-tool 0.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 (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