json2ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,4 @@
1
+ coverage
2
+ testjson
3
+ classes
4
+ *.gem
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ before_install: gem install bundler -v 1.10.5
5
+ sudo: false
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,78 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ json2ruby (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coveralls (0.8.2)
10
+ json (~> 1.8)
11
+ rest-client (>= 1.6.8, < 2)
12
+ simplecov (~> 0.10.0)
13
+ term-ansicolor (~> 1.3)
14
+ thor (~> 0.19.1)
15
+ diff-lcs (1.2.5)
16
+ docile (1.1.5)
17
+ domain_name (0.5.24)
18
+ unf (>= 0.0.5, < 1.0.0)
19
+ http-cookie (1.0.2)
20
+ domain_name (~> 0.5)
21
+ json (1.8.3)
22
+ mime-types (2.6.1)
23
+ netrc (0.10.3)
24
+ rake (10.4.2)
25
+ rdoc (4.2.0)
26
+ json (~> 1.4)
27
+ rest-client (1.8.0)
28
+ http-cookie (>= 1.0.2, < 2.0)
29
+ mime-types (>= 1.16, < 3.0)
30
+ netrc (~> 0.7)
31
+ rspec (3.3.0)
32
+ rspec-core (~> 3.3.0)
33
+ rspec-expectations (~> 3.3.0)
34
+ rspec-mocks (~> 3.3.0)
35
+ rspec-core (3.3.2)
36
+ rspec-support (~> 3.3.0)
37
+ rspec-expectations (3.3.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.3.0)
40
+ rspec-mocks (3.3.2)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.3.0)
43
+ rspec-support (3.3.0)
44
+ sdoc (0.4.1)
45
+ json (~> 1.7, >= 1.7.7)
46
+ rdoc (~> 4.0)
47
+ simplecov (0.10.0)
48
+ docile (~> 1.1.0)
49
+ json (~> 1.8)
50
+ simplecov-html (~> 0.10.0)
51
+ simplecov-html (0.10.0)
52
+ simplecov-rcov (0.2.3)
53
+ simplecov (>= 0.4.1)
54
+ term-ansicolor (1.3.2)
55
+ tins (~> 1.0)
56
+ thor (0.19.1)
57
+ tins (1.6.0)
58
+ unf (0.1.4)
59
+ unf_ext
60
+ unf_ext (0.0.7.1)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ bundler (~> 1.10)
67
+ coveralls
68
+ json2ruby!
69
+ rake (~> 10.0)
70
+ rdoc (~> 4.1)
71
+ rspec (~> 3.1)
72
+ rspec-mocks (~> 3.1)
73
+ sdoc (~> 0.4)
74
+ simplecov (~> 0.10.0)
75
+ simplecov-rcov (~> 0.2)
76
+
77
+ BUNDLED WITH
78
+ 1.10.5
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Tom Cully
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,78 @@
1
+ # json2ruby
2
+
3
+ [![Build Status](https://travis-ci.org/tomdionysus/json2ruby.svg?branch=master)](https://travis-ci.org/tomdionysus/json2ruby) [![Coverage Status](https://coveralls.io/repos/tomdionysus/json2ruby/badge.svg?branch=master&service=github)](https://coveralls.io/github/tomdionysus/json2ruby?branch=master)
4
+
5
+ A ruby rool for generating POROs from JSON data. It is intended to generate ruby model classes/modules from existing JSON data, e.g. responses from an API.
6
+
7
+ The tool will 'fold down' objects with identical fields - i.e. if an object has exactly the same field names and types as another, it will assume they are the same type.
8
+
9
+ 'Root' entities are named after the files that they are parsed from. Entities with no obvious name (items in an array, for instance) are named `Unknown<x>` where `x` increments from 1.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ git clone git@github.com:tomdionysus/json2ruby.git
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ json2ruby.rb [options] <file.json> [<file.json>....]
21
+ ```
22
+
23
+ | Option Flags | Default | Description |
24
+ |:-------------------------|:-----------------|:-------------------------------------------------|
25
+ | `-o, --outputdir` | `./classes` | The output directory for Ruby files |
26
+ | `-m, --modulename` | | The Ruby module for files |
27
+ | `-s, --superclass` | | The superclass for classes |
28
+ | `-r, --require` | | Add ruby `require` to files |
29
+ | `-i, --include` | | Add ruby `include` to files |
30
+ | `-e, --extend` | | Add ruby `extend` to files |
31
+ | `-M, --modules` | | Generate Ruby modules, not classes |
32
+ | `-a, --attributemethod` | `attr_accessor` | Use a custom attribute definition method |
33
+ | `-c, --collectionmethod` | `attr_accessor` | Use a custom collection definition method |
34
+ | `-t, --types` | | Include type name in attribute definition call |
35
+ | `-b, --baseless` | | Don't generate for the root object in each file |
36
+ | `-f, --forceoverwrite` | | Overwrite Existing files |
37
+ | `-N, --forcenumeric` | | Use Numeric instead of Integer/Float |
38
+ | `-v, --verbose` | | Be verbose, List every operation/file |
39
+
40
+ ## Example
41
+
42
+ Generate a simple set of POROs from an API response JSON file, be verbose:
43
+
44
+ ```bash
45
+ ./json2ruby.rb -v data.json
46
+ ```
47
+
48
+ Generate a (very) basic set of [apotonick/representable](https://github.com/apotonick/representable) compatible representer modules:
49
+
50
+ ```bash
51
+ ./json2ruby.rb -r representable/json-i Representable::JSON -M -a property -c collection data.json
52
+ ```
53
+
54
+ ## Notes
55
+
56
+ The option `-m, --modulename` can take path module names in `FirstModule::Submodule::SubSubModule` format, which will produce classes like so:
57
+
58
+ ```ruby
59
+ module FirstModule
60
+ module Submodule
61
+ module SubSubModule
62
+ class JSONObject
63
+ ...
64
+ end
65
+ end
66
+ end
67
+ end
68
+ ```
69
+
70
+ The option `-N, --forcenumeric` can be useful to fold down identical types where an attribute which is a float happens to have an integer value, to avoid generating two identical types.
71
+
72
+ ## Contributing
73
+
74
+ 1. Fork it
75
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
76
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
77
+ 4. Push to the branch (`git push origin my-new-feature`)
78
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "#{File.expand_path("../lib/json2ruby",File.dirname(__FILE__))}"
4
+
5
+ JSON2Ruby::CLI.run
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/json2ruby/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.authors = ["Tom Cully"]
6
+ s.email = ["tomhughcully@gmail.com"]
7
+ s.summary = 'A ruby rool for generating POROs from JSON data'
8
+ s.description = 'json2ruby is intended to generate ruby model classes/modules from existing JSON data, e.g. responses from an API.'
9
+ s.homepage = "http://github.com/tomdionysus/json2ruby"
10
+
11
+ s.files = `git ls-files`.split($\)
12
+ s.executables = ["json2ruby"]
13
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
14
+ s.name = "json2ruby"
15
+ s.require_paths = ["lib"]
16
+ s.version = JSON2Ruby::VERSION
17
+
18
+ s.add_development_dependency "coveralls"
19
+ s.add_development_dependency "bundler", "~> 1.10"
20
+ s.add_development_dependency "rake", "~> 10.0"
21
+ s.add_development_dependency 'simplecov', '~> 0.10.0'
22
+ s.add_development_dependency 'simplecov-rcov', '~> 0.2'
23
+ s.add_development_dependency 'rdoc', '~> 4.1'
24
+ s.add_development_dependency 'sdoc', '~> 0.4'
25
+ s.add_development_dependency 'rspec', '~> 3.1'
26
+ s.add_development_dependency 'rspec-mocks', '~> 3.1'
27
+ end
@@ -0,0 +1,9 @@
1
+ files = [
2
+ 'version',
3
+ 'primitive',
4
+ 'attribute',
5
+ 'collection',
6
+ 'entity',
7
+ 'ruby_writer',
8
+ 'cli',
9
+ ].each { |file| require "#{File.dirname(__FILE__)}/json2ruby/#{file}" }
@@ -0,0 +1,25 @@
1
+ require 'digest/md5'
2
+
3
+ module JSON2Ruby
4
+ class Attribute
5
+ attr_accessor :name, :original_name, :ruby_type
6
+
7
+ def self.short_name
8
+ "Attribute"
9
+ end
10
+
11
+ def initialize(name, ruby_type = nil)
12
+ @name = name
13
+ @ruby_type = ruby_type || "_unknown"
14
+ end
15
+
16
+ def attr_hash
17
+ Digest::MD5.hexdigest("#{@name}:#{@ruby_type}")
18
+ end
19
+
20
+ def ==(other)
21
+ return false if other.class != self.class
22
+ attr_hash == other.attr_hash
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,183 @@
1
+ require 'digest/md5'
2
+ require 'json'
3
+ require 'optparse'
4
+
5
+ module JSON2Ruby
6
+
7
+ class CLI
8
+ def self.run
9
+
10
+ puts "json2ruby v#{VERSION}\n"
11
+
12
+ # Do the cmdline options
13
+ options = get_cli_options
14
+
15
+ # Ensure Output Directory
16
+ options[:outputdir] = File.expand_path(options[:outputdir], File.dirname(__FILE__))
17
+ ensure_output_dir(options)
18
+
19
+ # Parse Files
20
+ rootclasses = parse_files(options)
21
+
22
+ # Write out Ruby (see what I'm doing here?)
23
+ writer = JSON2Ruby::RubyWriter
24
+
25
+ # Write Output
26
+ write_files(rootclasses, writer, options)
27
+ end
28
+
29
+ def self.ensure_output_dir(options)
30
+ puts "Output Directory: #{options[:outputdir]}" if options[:verbose]
31
+ unless Dir.exists?(options[:outputdir])
32
+ puts "Creating Output Directory..." if options[:verbose]
33
+ Dir.mkdir(options[:outputdir])
34
+ end
35
+ end
36
+
37
+ def self.get_cli_options
38
+
39
+ options = {}
40
+ OptionParser.new do |opts|
41
+ opts.banner = "Usage: #{$0} [options] <file.json> [<file.json>....]"
42
+
43
+ opts.on("-o", "--outputdir OUTPUTDIR", "Output directory") do |v|
44
+ options[:outputdir] = v
45
+ end
46
+
47
+ opts.on("-n", "--namespace MODULENAME", "Module namespace path") do |v|
48
+ options[:namespace] = v
49
+ end
50
+
51
+ opts.on("-s", "--superclass SUPERCLASS", "Class ancestor") do |v|
52
+ options[:superclass_name] = v
53
+ end
54
+
55
+ opts.on("-r", "--require REQUIRE", "Require module in file") do |v|
56
+ options[:require] ||= []
57
+ options[:require] << v
58
+ end
59
+
60
+ opts.on("-i", "--include INCLUDE", "Include Class/Module in file") do |v|
61
+ options[:include] ||= []
62
+ options[:include] << v
63
+ end
64
+
65
+ opts.on("-e", "--extend EXTEND", "Extend from Class/Module in file") do |v|
66
+ options[:extend] ||= []
67
+ options[:extend] << v
68
+ end
69
+
70
+ opts.on("-M", "--modules", "Create Modules, not classes") do |v|
71
+ options[:modules] = true
72
+ end
73
+
74
+ opts.on("-a", "--attributemethod METHODNAME", "Use attribute method instead of attr_accessor") do |v|
75
+ options[:attributemethod] = v
76
+ end
77
+
78
+ opts.on("-c", "--collectionmethod METHODNAME", "Use collection method instead of attr_accessor") do |v|
79
+ options[:collectionmethod] = v
80
+ end
81
+
82
+ opts.on("-t", "--types", "Include type in attribute definition call") do |v|
83
+ options[:includetypes] = true
84
+ end
85
+
86
+ opts.on("-b", "--baseless", "Don't generate classes/modules for the root JSON in each file") do |v|
87
+ options[:baseless] = true
88
+ end
89
+
90
+ opts.on("-f", "--forceoverwrite", "Overwrite Existing files") do |v|
91
+ options[:forceoverwrite] = v
92
+ end
93
+
94
+ opts.on("-N", "--forcenumeric", "Use Numeric instead of Integer/Float") do |v|
95
+ options[:forcenumeric] = v
96
+ end
97
+
98
+ opts.on("-v", "--verbose", "Verbose") do |v|
99
+ options[:verbose] = v
100
+ end
101
+ end.parse!
102
+
103
+ # Defaults
104
+ options[:outputdir] ||= File.expand_path("./classes")
105
+ options[:namespace] ||= ""
106
+ options[:attributemethod] ||= "attr_accessor"
107
+ options[:collectionmethod] ||= "attr_accessor"
108
+ options[:includetypes] ||= false
109
+ options[:baseless] ||= false
110
+ options[:forceoverwrite] ||= false
111
+ options[:verbose] ||= false
112
+
113
+ options[:modulenames] = options[:namespace].split("::")
114
+
115
+ options
116
+ end
117
+
118
+ def self.parse_files(options)
119
+ # Reset the object cache
120
+ Entity.reset_parse
121
+
122
+ # Load and parse each JSON file
123
+ puts "Parsing Files..." if options[:verbose]
124
+
125
+ rootclasses = []
126
+
127
+ ARGV.each do |filename|
128
+ filename = File.expand_path(filename)
129
+ puts "Processing: #{filename}" if options[:verbose]
130
+
131
+ file = File.read(filename)
132
+ data_hash = JSON.parse(file)
133
+
134
+ rootclasses << Entity.parse_from(File.basename(filename,'.*'), data_hash, options)
135
+ end
136
+
137
+ rootclasses
138
+ end
139
+
140
+ def self.write_files(rootclasses, writer, options)
141
+ files = 0
142
+ Entity.entities.each do |k,v|
143
+ next if options[:baseless] and rootclasses.include?(v)
144
+
145
+ display_entity(k,v) if options[:verbose] && !v.is_a?(Primitive)
146
+
147
+ if v.is_a?(Entity)
148
+ indent = 0
149
+ out = ""
150
+ options[:modulenames].each do |v|
151
+ out += (' '*indent)+"module #{v}\r\n"
152
+ indent += 2
153
+ end
154
+ out += writer.to_code(v, indent,options)
155
+ while indent>0
156
+ indent -= 2
157
+ out += (' '*indent)+"end\r\n"
158
+ end
159
+
160
+ filename = options[:outputdir]+"/#{v.name}.rb"
161
+ if File.exists?(filename) && !options[:forceoverwrite]
162
+ $stderr.puts "File #{filename} exists. Use -f to overwrite."
163
+ else
164
+ File.write(filename, out)
165
+ files += 1
166
+ end
167
+ end
168
+ end
169
+
170
+ # Done
171
+ puts "Done, Generated #{files} file#{files==1 ? '' : 's'}"
172
+ end
173
+
174
+ def self.display_entity(hsh, ent)
175
+ puts "- #{ent.name} (#{ent.class.short_name} - #{hsh})"
176
+ if ent.is_a?(Entity)
177
+ ent.attributes.each { |ak,av| puts " #{ak}: #{av.name}" }
178
+ elsif ent.is_a?(Collection)
179
+ puts " (Types): #{ent.ruby_types.map { |h,ent| ent.name }.join(',')}"
180
+ end
181
+ end
182
+ end
183
+ end