jsonlint 0.1.0 → 0.2.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9440424c5ce5284432200d4275be3ef815c55a87
4
+ data.tar.gz: b85712a8defcd3df4d0846d4993e0a967cc2283d
5
+ SHA512:
6
+ metadata.gz: d2f64c73f7f4e8512dee6abc20228ad68de118a339fd45313766828c118e9bb0f370b067575017566d3138915ca0e6cce644b5db46e0bcba85f47f422585dea3
7
+ data.tar.gz: 1dd3cea2cca3437c3dc766b57d63d79690cdb5fcdb7588c2d071ab2c281431492c18ef079439b596527fb3b844509a81a4113de1a8ffdf73404d3fcd365767fd
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.0
3
+ - 2.0.0-p648
4
+ - 2.1.9
5
+ - 2.2.5
6
+ - 2.3.1
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # JsonLint
1
+ # jsonlint
2
2
 
3
- TODO: Write a gem description
3
+ [![Build Status](https://travis-ci.org/PagerDuty/jsonlint.svg?branch=master)](https://travis-ci.org/PagerDuty/jsonlint)
4
+ [![Gem Version](http://img.shields.io/gem/v/jsonlint.svg)](https://rubygems.org/gems/jsonlint)
5
+
6
+ jsonlint checks your JSON files for syntax errors or silly mistakes. Currently it checks for:
7
+
8
+ * Valid JSON syntax
9
+ * Overlapping key definitions in JSON files, where the last definition would win
4
10
 
5
11
  ## Installation
6
12
 
@@ -16,13 +22,52 @@ Or install it yourself as:
16
22
 
17
23
  $ gem install jsonlint
18
24
 
19
- ## Usage
25
+ ## CLI Usage
26
+
27
+ You can run jsonlint against a set of files in the command line. Any errors will be printed and the process will exit with a non-zero exit code.
28
+
29
+ ```
30
+ $ jsonlint spec/data/*
31
+ spec/data/deep_overlap.json
32
+ The same key is defined twice: foo.bar
33
+ spec/data/missing_brace.json
34
+ Hash/Object not terminated at line 5, column 2 [sparse.c:782]
35
+ spec/data/missing_comma.json
36
+ expected comma, not a string at line 3, column 8 [sparse.c:386]
37
+ spec/data/overlapping_keys.json
38
+ The same key is defined twice: foo
39
+ ```
40
+
41
+ ## Rake task
42
+
43
+ You can integrate jsonlint into your build process by adding a Rake task to your project
44
+
45
+ ```ruby
46
+ require 'jsonlint/rake_task'
47
+ JsonLint::RakeTask.new do |t|
48
+ t.paths = %w(
49
+ spec/**/*.json
50
+ )
51
+ end
52
+ ```
53
+
54
+ Then run the rake task.
20
55
 
21
- TODO: Write usage instructions here
56
+ ```
57
+ $ rake jsonlint
58
+ spec/data/deep_overlap.json
59
+ The same key is defined twice: foo.bar
60
+ spec/data/missing_brace.json
61
+ Hash/Object not terminated at line 5, column 2 [sparse.c:782]
62
+ spec/data/missing_comma.json
63
+ expected comma, not a string at line 3, column 8 [sparse.c:386]
64
+ spec/data/overlapping_keys.json
65
+ The same key is defined twice: foo
66
+ ```
22
67
 
23
68
  ## Contributing
24
69
 
25
- 1. Fork it ( https://github.com/[my-github-username]/jsonlint/fork )
70
+ 1. Fork it ( https://github.com/PagerDuty/jsonlint/fork )
26
71
  2. Create your feature branch (`git checkout -b my-new-feature`)
27
72
  3. Commit your changes (`git commit -am 'Add some feature'`)
28
73
  4. Push to the branch (`git push origin my-new-feature`)
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
 
3
3
  require 'rspec/core/rake_task'
4
4
 
@@ -18,8 +18,10 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_dependency 'oj', '~> 2'
21
+ spec.add_dependency 'trollop', '~> 2'
21
22
 
22
23
  spec.add_development_dependency "bundler", "~> 1.6"
23
24
  spec.add_development_dependency "rake"
24
25
  spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "aruba"
25
27
  end
@@ -4,7 +4,6 @@ require 'jsonlint/version'
4
4
  require 'jsonlint/linter'
5
5
 
6
6
  module JsonLint
7
- # Your code goes here...
8
7
  def self.logger
9
8
  @logger ||= Logger.new(STDOUT).tap do |l|
10
9
  l.level = Logger::INFO
@@ -1,3 +1,5 @@
1
+ require 'trollop'
2
+
1
3
  module JsonLint
2
4
  class CLI
3
5
  def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
@@ -5,14 +7,40 @@ module JsonLint
5
7
  end
6
8
 
7
9
  def execute!
10
+ parse_options
11
+
8
12
  files_to_check = @argv
9
13
 
14
+ Trollop::die 'need at least one JSON file to check' if files_to_check.empty?
15
+
10
16
  linter = JsonLint::Linter.new
11
- linter.check_all(files_to_check)
17
+ begin
18
+ if files_to_check == ['-']
19
+ linter.check_stream(STDIN)
20
+ else
21
+ linter.check_all(files_to_check)
22
+ end
23
+ rescue JsonLint::FileNotFoundError => e
24
+ @stderr.puts e.message
25
+ exit(1)
26
+ rescue => e
27
+ @stderr.puts e.message
28
+ exit(1)
29
+ end
30
+
31
+ return unless linter.errors?
32
+ linter.display_errors
33
+ @kernel.exit(1)
34
+ end
35
+
36
+ private
12
37
 
13
- if linter.has_errors?
14
- linter.display_errors
15
- @kernel.exit(1)
38
+ def parse_options
39
+ @opts = Trollop.options(@argv) do
40
+ banner 'Usage: jsonlint [options] file1.json [file2.json ...]'
41
+ version(JsonLint::VERSION)
42
+ banner ''
43
+ banner 'Options:'
16
44
  end
17
45
  end
18
46
  end
@@ -8,21 +8,38 @@ module JsonLint
8
8
  attr_reader :errors
9
9
 
10
10
  def initialize
11
- @errors = Hash.new {|h,k| h[k] = [] }
11
+ @errors = {}
12
12
  end
13
13
 
14
14
  def check_all(*files_to_check)
15
- files_to_check.flatten.each {|f| check(f) }
15
+ files_to_check.flatten.each { |f| check(f) }
16
16
  end
17
17
 
18
18
  def check(path)
19
- raise FileNotFoundError, "#{path} does not exist" unless File.exist?(path)
19
+ fail FileNotFoundError, "#{path}: no such file" unless File.exist?(path)
20
20
 
21
- check_syntax_valid(path) && check_overlapping_keys(path)
21
+ valid = false
22
+ File.open(path, 'r') do |f|
23
+ error_array = []
24
+ valid = check_data(f.read, error_array)
25
+ errors[path] = error_array unless error_array.empty?
26
+ end
27
+
28
+ valid
22
29
  end
23
30
 
24
- def has_errors?
25
- ! errors.empty?
31
+ def check_stream(io_stream)
32
+ json_data = io_stream.read
33
+ error_array = []
34
+
35
+ valid = check_data(json_data, error_array)
36
+ errors[''] = error_array unless error_array.empty?
37
+
38
+ valid
39
+ end
40
+
41
+ def errors?
42
+ !errors.empty?
26
43
  end
27
44
 
28
45
  def display_errors
@@ -36,11 +53,31 @@ module JsonLint
36
53
 
37
54
  private
38
55
 
39
- def check_syntax_valid(path)
40
- Oj.load_file(path, nilnil: false)
56
+ def check_data(json_data, errors_array)
57
+ valid = check_not_empty?(json_data, errors_array)
58
+ valid &&= check_syntax_valid?(json_data, errors_array)
59
+ valid &&= check_overlapping_keys?(json_data, errors_array)
60
+
61
+ valid
62
+ end
63
+
64
+ def check_not_empty?(json_data, errors_array)
65
+ if json_data.empty?
66
+ errors_array << 'The JSON should not be an empty string'
67
+ false
68
+ elsif json_data.strip.empty?
69
+ errors_array << 'The JSON should not just be spaces'
70
+ false
71
+ else
72
+ true
73
+ end
74
+ end
75
+
76
+ def check_syntax_valid?(json_data, errors_array)
77
+ Oj.load(json_data, nilnil: false)
41
78
  true
42
79
  rescue Oj::ParseError => e
43
- errors[path] << e.message
80
+ errors_array << e.message
44
81
  false
45
82
  end
46
83
 
@@ -120,28 +157,26 @@ module JsonLint
120
157
  end
121
158
 
122
159
  private
160
+
123
161
  def check_for_overlap!
124
162
  full_key = @key_components.dup
125
163
  JsonLint.logger.debug { "Checking #{full_key.join('.')} for overlap" }
126
164
 
127
- unless @seen_keys.add?(full_key)
128
- JsonLint.logger.debug { "Overlapping key #{full_key.join('.')}" }
129
- @overlapping_keys << full_key
130
- end
165
+ return if @seen_keys.add?(full_key)
166
+ JsonLint.logger.debug { "Overlapping key #{full_key.join('.')}" }
167
+ @overlapping_keys << full_key
131
168
  end
132
169
  end
133
170
 
134
- def check_overlapping_keys(path)
171
+ def check_overlapping_keys?(json_data, errors_array)
135
172
  overlap_detector = KeyOverlapDetector.new
136
- File.open(path, 'r') do |f|
137
- Oj.saj_parse(overlap_detector, f)
138
- end
173
+ Oj.saj_parse(overlap_detector, StringIO.new(json_data))
139
174
 
140
175
  overlap_detector.overlapping_keys.each do |key|
141
- errors[path] << "The same key is defined twice: #{key.join('.')}"
176
+ errors_array << "The same key is defined more than once: #{key.join('.')}"
142
177
  end
143
178
 
144
- !! overlap_detector.overlapping_keys.empty?
179
+ !!overlap_detector.overlapping_keys.empty?
145
180
  end
146
181
  end
147
182
  end
@@ -19,15 +19,15 @@ module JsonLint
19
19
  private
20
20
 
21
21
  def define_task
22
- desc 'Run jsonlint' unless ::Rake.application.last_comment
22
+ desc 'Run jsonlint' unless ::Rake.application.last_description
23
23
 
24
- task(self.name) do
25
- files_to_check = Rake::FileList.new(self.paths)
24
+ task(name) do
25
+ files_to_check = Rake::FileList.new(paths)
26
26
 
27
27
  linter = ::JsonLint::Linter.new
28
28
  linter.check_all(files_to_check)
29
29
 
30
- if linter.has_errors?
30
+ if linter.errors?
31
31
  linter.display_errors
32
32
  abort('JSON lint found')
33
33
  end
@@ -1,3 +1,3 @@
1
1
  module JsonLint
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'jsonlint' do
4
+ it 'should print usage if run with no args' do
5
+ jsonlint
6
+ expect(last_command_started).to_not be_successfully_executed
7
+ expect(last_command_started).to have_output(/Error: need at least one JSON file to check./)
8
+ end
9
+
10
+ it '-h should print usage' do
11
+ jsonlint '-h'
12
+ expect(last_command_started).to be_successfully_executed
13
+ expect(last_command_started).to have_output(/Usage: jsonlint/)
14
+ end
15
+
16
+ it '--help should print usage' do
17
+ jsonlint '--help'
18
+ expect(last_command_started).to be_successfully_executed
19
+ expect(last_command_started).to have_output(/Usage: jsonlint/)
20
+ end
21
+
22
+ it '-v should print its version' do
23
+ jsonlint '-v'
24
+ expect(last_command_started).to be_successfully_executed
25
+ expect(last_command_started).to have_output JsonLint::VERSION
26
+ end
27
+
28
+ it '--version should print its version' do
29
+ jsonlint '--version'
30
+ expect(last_command_started).to be_successfully_executed
31
+ expect(last_command_started).to have_output JsonLint::VERSION
32
+ end
33
+
34
+ it 'should exit successfully with good JSON' do
35
+ jsonlint spec_data('valid.json')
36
+ expect(last_command_started).to be_successfully_executed
37
+ end
38
+
39
+ it 'should fail with bad JSON' do
40
+ jsonlint spec_data('missing_comma.json')
41
+ expect(last_command_started).to_not be_successfully_executed
42
+ end
43
+
44
+ it 'should fail with a path that does not exist' do
45
+ jsonlint '/does/not/exist'
46
+ expect(last_command_started).to_not be_successfully_executed
47
+ expect(last_command_started).to have_output(/no such file/)
48
+ end
49
+
50
+ it 'should fail with a path that is unreadable' do
51
+ run_simple('mkdir -p tmp')
52
+ run_simple('touch tmp/unreadable_file.json')
53
+ run_simple('chmod -r tmp/unreadable_file.json')
54
+
55
+ jsonlint 'tmp/unreadable_file.json'
56
+ expect(last_command_started).to_not be_successfully_executed
57
+ expect(last_command_started).to have_output(/Permission denied/)
58
+ end
59
+
60
+ it 'should be able to lint good JSON from STDIN' do
61
+ run "#{jsonlint_bin} -"
62
+ pipe_in_file('../../spec/data/valid.json') and close_input
63
+ expect(last_command_started).to be_successfully_executed
64
+ end
65
+
66
+ it 'should be able to lint bad JSON from STDIN' do
67
+ run "#{jsonlint_bin} -"
68
+ pipe_in_file('../../spec/data/missing_comma.json') and close_input
69
+ expect(last_command_started).to_not be_successfully_executed
70
+ end
71
+ end
File without changes
@@ -5,7 +5,7 @@ describe 'JsonLint::Linter' do
5
5
  let(:linter) { JsonLint::Linter.new }
6
6
 
7
7
  it 'should throw an exception if given a bogus path' do
8
- expect { linter.check('/does/not/exist') }.to raise_error
8
+ expect { linter.check('/does/not/exist') }.to raise_error(JsonLint::FileNotFoundError)
9
9
  end
10
10
 
11
11
  it 'should be happy with a valid JSON file' do
@@ -22,4 +22,20 @@ describe 'JsonLint::Linter' do
22
22
  expect(linter.check(spec_data('overlapping_keys.json'))).to be(false)
23
23
  expect(linter.check(spec_data('deep_overlap.json'))).to be(false)
24
24
  end
25
+
26
+ it 'should be able to check an IO stream' do
27
+ valid_stream = File.open(spec_data('valid.json'))
28
+ expect(linter.check_stream(valid_stream)).to be(true)
29
+
30
+ invalid_stream = File.open(spec_data('missing_comma.json'))
31
+ expect(linter.check_stream(invalid_stream)).to be(false)
32
+ end
33
+
34
+ it 'should be unhappy with an empty JSON file' do
35
+ expect(linter.check(spec_data('empty_file.json'))).to be(false)
36
+ end
37
+
38
+ it 'should be unhapy with a JSON file full of spaces' do
39
+ expect(linter.check(spec_data('lots_of_spaces.json'))).to be(false)
40
+ end
25
41
  end
@@ -1,5 +1,8 @@
1
1
  require 'rspec'
2
2
 
3
+ require 'aruba'
4
+ require 'aruba/rspec'
5
+
3
6
  require 'jsonlint'
4
7
 
5
8
  module SpecHelpers
@@ -8,6 +11,21 @@ module SpecHelpers
8
11
  end
9
12
  end
10
13
 
14
+ module CliSpecHelpers
15
+ def jsonlint(args = nil)
16
+ run_simple("#{jsonlint_bin} #{args}", fail_on_error: false)
17
+ end
18
+
19
+ def jsonlint_bin
20
+ File.expand_path('../../bin/jsonlint', __FILE__)
21
+ end
22
+ end
23
+
11
24
  RSpec.configure do |config|
12
25
  config.include SpecHelpers
26
+ config.include CliSpecHelpers
27
+ config.include Aruba::Api
28
+
29
+ config.run_all_when_everything_filtered = true
30
+ config.filter_run :focus
13
31
  end
metadata CHANGED
@@ -1,78 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonlint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
5
- prerelease:
4
+ version: 0.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Doug Barth
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-07-03 00:00:00.000000000 Z
11
+ date: 2016-08-07 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: oj
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
19
  version: '2'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: trollop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
28
39
  - !ruby/object:Gem::Version
29
40
  version: '2'
30
41
  - !ruby/object:Gem::Dependency
31
42
  name: bundler
32
43
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
44
  requirements:
35
- - - ~>
45
+ - - "~>"
36
46
  - !ruby/object:Gem::Version
37
47
  version: '1.6'
38
48
  type: :development
39
49
  prerelease: false
40
50
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
51
  requirements:
43
- - - ~>
52
+ - - "~>"
44
53
  - !ruby/object:Gem::Version
45
54
  version: '1.6'
46
55
  - !ruby/object:Gem::Dependency
47
56
  name: rake
48
57
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
58
  requirements:
51
- - - ! '>='
59
+ - - ">="
52
60
  - !ruby/object:Gem::Version
53
61
  version: '0'
54
62
  type: :development
55
63
  prerelease: false
56
64
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
65
  requirements:
59
- - - ! '>='
66
+ - - ">="
60
67
  - !ruby/object:Gem::Version
61
68
  version: '0'
62
69
  - !ruby/object:Gem::Dependency
63
70
  name: rspec
64
71
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
72
  requirements:
67
- - - ! '>='
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: aruba
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
68
88
  - !ruby/object:Gem::Version
69
89
  version: '0'
70
90
  type: :development
71
91
  prerelease: false
72
92
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
93
  requirements:
75
- - - ! '>='
94
+ - - ">="
76
95
  - !ruby/object:Gem::Version
77
96
  version: '0'
78
97
  description: Checks JSON files for correct syntax and no silly mistakes
@@ -83,8 +102,8 @@ executables:
83
102
  extensions: []
84
103
  extra_rdoc_files: []
85
104
  files:
86
- - .gitignore
87
- - .travis.yml
105
+ - ".gitignore"
106
+ - ".travis.yml"
88
107
  - Gemfile
89
108
  - LICENSE.txt
90
109
  - README.md
@@ -97,7 +116,10 @@ files:
97
116
  - lib/jsonlint/linter.rb
98
117
  - lib/jsonlint/rake_task.rb
99
118
  - lib/jsonlint/version.rb
119
+ - spec/cli_spec.rb
100
120
  - spec/data/deep_overlap.json
121
+ - spec/data/empty_file.json
122
+ - spec/data/lots_of_spaces.json
101
123
  - spec/data/missing_brace.json
102
124
  - spec/data/missing_comma.json
103
125
  - spec/data/overlapping_keys.json
@@ -109,36 +131,32 @@ files:
109
131
  homepage:
110
132
  licenses:
111
133
  - MIT
134
+ metadata: {}
112
135
  post_install_message:
113
136
  rdoc_options: []
114
137
  require_paths:
115
138
  - lib
116
139
  required_ruby_version: !ruby/object:Gem::Requirement
117
- none: false
118
140
  requirements:
119
- - - ! '>='
141
+ - - ">="
120
142
  - !ruby/object:Gem::Version
121
143
  version: '0'
122
- segments:
123
- - 0
124
- hash: -4326837317699917705
125
144
  required_rubygems_version: !ruby/object:Gem::Requirement
126
- none: false
127
145
  requirements:
128
- - - ! '>='
146
+ - - ">="
129
147
  - !ruby/object:Gem::Version
130
148
  version: '0'
131
- segments:
132
- - 0
133
- hash: -4326837317699917705
134
149
  requirements: []
135
150
  rubyforge_project:
136
- rubygems_version: 1.8.24
151
+ rubygems_version: 2.5.1
137
152
  signing_key:
138
- specification_version: 3
153
+ specification_version: 4
139
154
  summary: JSON lint checker
140
155
  test_files:
156
+ - spec/cli_spec.rb
141
157
  - spec/data/deep_overlap.json
158
+ - spec/data/empty_file.json
159
+ - spec/data/lots_of_spaces.json
142
160
  - spec/data/missing_brace.json
143
161
  - spec/data/missing_comma.json
144
162
  - spec/data/overlapping_keys.json
@@ -147,3 +165,4 @@ test_files:
147
165
  - spec/data/valid_array_of_objects.json
148
166
  - spec/linter_spec.rb
149
167
  - spec/spec_helper.rb
168
+ has_rdoc: