genny 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 775a8ea859c1d9e327238c283dccee5bc11018bf
4
+ data.tar.gz: 683a7a9a6549e6accbb29e87ab497a55db71d157
5
+ SHA512:
6
+ metadata.gz: b12781c213a72a7f8ba5fb27a61195162d93f7ff8ee4f3ecbc1a315e975c2aabc134398b42be3c1d64de6098768e28469bdf8ec03c0efae6370857595872196b
7
+ data.tar.gz: baf4d7cecd6da46d907f45ab14c5e30c08606d9b46d96f30368663d0fe62c282d22c6bdd2e22352794ec50f44ab6c1dce43b55322c881e0d8bff222d29fd312d
data/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # Genny
2
+
3
+ Genny likes making things up. Unlike other faker libraries this one is based around generating whole data structures using JSONSchema as a basis.
4
+
5
+ This gem can be used without any additional gems. The following gems will be used if they can be loaded, but not having them installed/in your bundle will reduce functionality rather than prevent the library from working:
6
+
7
+ - [json-schema](https://rubygems.org/gems/json-schema) - for JSONSchema validation
8
+ - [faker](https://rubygems.org/gems/faker) - for better hinted strings
9
+
10
+ ## Examples
11
+
12
+ ### Extending core classes
13
+
14
+ Extending the core classes to have the `genny` class method is handy, but may not be desired. If you wish to use this style then you must initialise it:
15
+
16
+ ```
17
+ Genny::String.genny
18
+ # => "pymliestqk"
19
+ String.respond_to?(:genny)
20
+ # => false
21
+
22
+ Genny.extend_core
23
+ # => [NilClass, URI, Time, Date, Array, Float, String, Regexp, Integer, Hash]
24
+
25
+ String.respond_to?(:genny)
26
+ # => true
27
+ String.genny
28
+ # => "oniqpsxubm"
29
+ ```
30
+
31
+ ### Strings
32
+
33
+ ```
34
+ Genny::String.genny
35
+ # => "fusmtzavyi"
36
+ Genny::String.genny(format: "ipv4")
37
+ # => "171.199.220.179"
38
+
39
+ # You can define your own formats
40
+ Genny::String.format("tla") do |opts|
41
+ 3.times.map { ('A'..'Z').to_a.sample }.join
42
+ end
43
+ Genny::String.genny(format: "tla")
44
+ # => "XSE"
45
+ ```
46
+
47
+ ### Objects
48
+
49
+ ```
50
+ # Genny makes JSONSchema a first class... class. It acts like a hash but has a genny instance method.
51
+ # If the json-schema gem can be loaded then the new method will raise an error if it's an invalid schema.
52
+ js = JSONSchema.new(
53
+ "type" => "object",
54
+ "properties" => {
55
+ "key" => {
56
+ "type" => "string"
57
+ }
58
+ }
59
+ )
60
+ # => {"type"=>"object", "properties"=>{"key"=>{"type"=>"string"}}, "definitions"=>{}}
61
+ js.genny
62
+ # => {"key"=>"gcqhsanwzd"}
63
+ ```
64
+
65
+ ### Arrays
66
+
67
+ A generated array will always be empty unless a `:classes` option has been defined. Any class in that array that responds to `genny` may be picked as the prototype for an element of the generated array.
68
+
69
+ ```
70
+ Genny::Array.genny
71
+ # => []
72
+
73
+ Genny::Array.genny(classes: [Genny::String, Genny::Integer])
74
+ # => ["mcztjgoriq", "nohfcavjyz", 739]
75
+ ```
76
+
77
+ #### Hinting
78
+
79
+ ```
80
+ # Hinting for strings allows you to try and indicate what kind of string you'd like back.
81
+ Genny::String.genny(hint: "first name")
82
+ # => "Dean"
83
+ Genny::String.genny(hint: "name")
84
+ # => "Garrison O'Kon"
85
+
86
+ # And when an object value type is a string, the key will be used as a hint
87
+ JSONSchema.new(
88
+ "type" => "object",
89
+ "properties" => {
90
+ "name" => {
91
+ "type" => "string"
92
+ }
93
+ }
94
+ ).genny
95
+ # => { "name" => "Everett Schmitt" }
96
+
97
+ # You can define your own hinters. Hints defined earlier will take precidence
98
+ Genny::String.hint do |opts|
99
+ next unless opts[:hint].include?("answer")
100
+ "42"
101
+ end
102
+ Genny::String.genny(hint: "the answer")
103
+ # => "42"
104
+ ```
105
+
106
+ ### Numbers
107
+
108
+ ```
109
+ Genny::Integer.genny
110
+ # => 5
111
+ Genny::Integer.genny(maximum: 100)
112
+ # => 88
113
+ Genny::Integer.genny(minimum: 90, maximum: 100)
114
+ # => 92
115
+
116
+ Genny::Float.genny
117
+ # => 234.2934215006394
118
+ Genny::Float.genny(minimum: 90, maximum: 100)
119
+ # => 96.6691946757789
120
+ ```
121
+
122
+ ### Dates & Times
123
+
124
+ ```
125
+ Genny::Date.genny
126
+ # => #<Date: 1987-03-01 ((2446856j,0s,0n),+0s,2299161j)>
127
+ Genny::Time.genny
128
+ # => 1988-11-23 05:48:04 UTC
129
+
130
+ # These are also a format of string (for JSONSchema purposes)
131
+ Genny::String.genny(format: "date")
132
+ # => "1975-07-24"
133
+ Genny::String.genny(format: "date-time")
134
+ # => "1994-10-11T07:32:55Z"
135
+ ```
136
+
137
+ ### Boolean
138
+
139
+ There being no core `Boolean` class this must be called directly even if the core classes have been extended.
140
+
141
+ ```
142
+ Genny::Boolean.genny
143
+ # => false
144
+ Genny::Boolean.genny
145
+ # => true
146
+ ```
147
+
148
+ ### URIs
149
+
150
+ ```
151
+ Genny::URI.genny
152
+ # => #<URI::HTTP:0x007f8c1c164f90 URL:http://example.com/erichqlvjx>
153
+
154
+ # URIs are also a format of string (for JSONSchema purposes)
155
+ Genny::String.genny(format: "uri")
156
+ # => "http://example.com/itgxsewckm"
157
+ ```
158
+
159
+ ### Regular expressions
160
+
161
+ Generating a regular expression is a bit useless (it always returns `/.*/` which matches most strings) but there is also an instance `genny` method defined which will, when I implement it, make a best effort at creating a string which matches the given regular expression.
162
+
163
+ ```
164
+ Genny::Regexp.genny
165
+ # => /.*/
166
+
167
+ # The following hasn't been implemented yet. But soon!
168
+ /[a-f0-9]{2}/.extend(Genny::Regexp).genny
169
+ # => "a7"
170
+ Genny.extend_core
171
+ /[a-f0-9]{2}/.genny
172
+ # => "5c"
173
+ ```
174
+
175
+ ## Contributing
176
+
177
+ Yes please! Pull requests would be lovely.
178
+
179
+ ## Contact
180
+
181
+ I am [@jphastings](https://twitter.com/jphastings) and I work at [blinkbox Books](https://github.com/blinkboxbooks), do get in touch.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,19 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ module Array
5
+ def self.genny(opts = {})
6
+ opts = Genny.symbolize(opts)
7
+ opts[:classes] ||= []
8
+ raise ArgumentError, "classes must be an array" unless opts[:classes].respond_to?(:select)
9
+ klasses = [*opts[:classes]].select { |klass| klass.respond_to?(:genny) }
10
+ return [] if klasses.empty?
11
+ min_count = opts[:minItems] || 1
12
+ max_count = opts[:maxItems] || 5
13
+ count = Random.rand(max_count - min_count + 1) + min_count
14
+ return count.times.map do
15
+ klasses.sample.genny(opts[:items] || {})
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/genny/base.rb ADDED
@@ -0,0 +1,29 @@
1
+ require "genny/version"
2
+
3
+ module Genny
4
+ # Extend all the Ruby bbase classes with the `genny` method.
5
+ #
6
+ # @return [Array<klass>] Returns the classes which have been extended
7
+ def self.extend_core
8
+ self.constants.map do |k|
9
+ genny_module = self.const_get(k)
10
+ if Kernel.const_defined?(k) && genny_module.is_a?(Module) && genny_module.respond_to?(:genny)
11
+ Kernel.const_get(k).include(genny_module).instance_eval do
12
+ def genny(opts = {})
13
+ raise NoMethodError, "#{self.name} isn't supported by Genny" unless Genny.constants.include?(self.name.to_sym)
14
+ Genny.const_get(self.name).genny(opts)
15
+ end
16
+ end
17
+ Kernel.const_get(k)
18
+ else
19
+ nil
20
+ end
21
+ end.compact
22
+ end
23
+
24
+ def self.symbolize(hash)
25
+ ::Hash[hash.map { |key, val|
26
+ [key.to_sym, val]
27
+ }]
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ module Boolean
5
+ def self.genny(_opts = {})
6
+ [true, false].sample
7
+ end
8
+ end
9
+ end
data/lib/genny/date.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ module Date
5
+ def self.genny(opts = {})
6
+ opts = Genny.symbolize(opts)
7
+ opts[:from] ||= ::Date.new(1970, 1, 1)
8
+ opts[:until] ||= ::Date.today
9
+ ::Date.jd(Random.rand(opts[:until].jd - opts[:from].jd) + opts[:from].jd)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ begin
2
+ require "faker"
3
+
4
+ module Genny::String
5
+ # First name
6
+ hint do |opts|
7
+ hint = opts[:hint].downcase
8
+ next unless hint.match("first") && hint.match("name")
9
+ # TODO: ensure the length limits are met
10
+ Faker::Name.first_name
11
+ end
12
+
13
+ # Last name
14
+ hint do |opts|
15
+ hint = opts[:hint].downcase
16
+ next unless hint.match("last") && hint.match("name")
17
+ # TODO: ensure the length limits are met
18
+ Faker::Name.last_name
19
+ end
20
+
21
+ # Full name
22
+ hint do |opts|
23
+ next unless opts[:hint].downcase.match("name")
24
+ # TODO: ensure the length limits are met
25
+ Faker::Name.name
26
+ end
27
+
28
+ # Email address
29
+ hint do |opts|
30
+ next unless opts[:hint].downcase.match("email")
31
+ Faker::Internet.safe_email
32
+ end
33
+ end
34
+ rescue LoadError
35
+ end
@@ -0,0 +1,13 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ module Float
5
+ def self.genny(opts = {})
6
+ opts = Genny.symbolize(opts)
7
+ min = opts[:minimum] || 10
8
+ max = opts[:maximum] || 1000
9
+ # TODO: exclusive minimum/maximum
10
+ Random.rand * (max - min) + min
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ module Integer
5
+ def self.genny(opts = {})
6
+ opts = Genny.symbolize(opts)
7
+ min = opts[:minimum] || 10
8
+ max = opts[:maximum] || 1000
9
+ Random.rand(max - min + 1) + min
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,85 @@
1
+ require "genny/base"
2
+ require "delegate"
3
+
4
+ class JSONSchema < SimpleDelegator
5
+ VALIDATE = begin
6
+ require "json-schema"
7
+ true
8
+ rescue LoadError
9
+ false
10
+ end
11
+
12
+ def initialize(hash, validate: VALIDATE, definitions: {})
13
+ # TODO: ensure it's a valid schema if VALIDATE
14
+ hash['definitions'] = (definitions || {}).merge(hash['definitions'] || {})
15
+ super(hash)
16
+ end
17
+
18
+ def genny
19
+ opts = Genny.symbolize(self)
20
+ return opts[:enum].sample if opts.has_key?(:enum)
21
+ return schema_from_ref(opts[:'$ref']).genny if opts[:'$ref']
22
+
23
+ klass = {
24
+ "array" => Genny::Array,
25
+ "boolean" => Genny::Boolean,
26
+ "number" => Genny::Float,
27
+ "object" => Genny::Hash,
28
+ "integer" => Genny::Integer,
29
+ "null" => Genny::NilClass,
30
+ "string" => Genny::String,
31
+ nil => Genny::Hash
32
+ }[opts[:type]]
33
+ raise "Cannot generate JSON Schema object of type '#{opts[:type]}'." unless klass.respond_to?(:genny)
34
+ klass.genny(opts)
35
+ end
36
+
37
+ private
38
+
39
+ # Deals with JSON-Schema valid "#/definitions/xyz" style references and Swagger style "Thing" style.
40
+ def schema_from_ref(name)
41
+ if self['definitions'][name]
42
+ subschema = self['definitions'][name]
43
+ elsif name.match(%r{^#/(.+)$})
44
+ subschema = self.dup
45
+ Regexp.last_match[1].split("/").each { |key| subschema = subschema[key] }
46
+ end
47
+
48
+ return JSONSchema.new(
49
+ subschema,
50
+ definitions: self['definitions'],
51
+ validate: false
52
+ ) if subschema
53
+ raise "Unknown definition"
54
+ rescue
55
+ raise "Cannot find definition #{name}"
56
+ end
57
+ end
58
+
59
+ module Genny
60
+ module Hash
61
+ @@additional_properties = 1
62
+
63
+ def self.additional_properties=(chance)
64
+ raise ArgumentError, "chance must be a probability between 0 and 1" if chance < 0 || chance > 1
65
+ @@additional_properties = chance
66
+ end
67
+
68
+ def self.genny(opts = {})
69
+ opts = Genny.symbolize(opts)
70
+ ::Hash[(opts[:properties] || {}).map { |key, schema_object|
71
+ next nil if !(opts[:required] || []).include?(key) && Random.rand > @@additional_properties
72
+ [
73
+ key,
74
+ JSONSchema.new(
75
+ schema_object.merge(
76
+ hint: [key, opts[:hint]].compact.join(" ")
77
+ ),
78
+ definitions: opts[:definitions],
79
+ validate: false
80
+ ).genny
81
+ ]
82
+ }.compact]
83
+ end
84
+ end
85
+ end
data/lib/genny/nil.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ # This one's a bit useless, but it makes mapping the JsonSchema stuff easier
5
+ module NilClass
6
+ def self.genny(_opts = {})
7
+ nil
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ module Regexp
5
+ # A bit unecessary, but who knows, maybe someone will have use for it
6
+ def self.genny(_opts = {})
7
+ %r{.*}
8
+ end
9
+
10
+ def genny(opts = {})
11
+ raise NotImplementedError, "I gotta get some sleep. Genny can't make strings which fit Regexps... yet."
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ require "genny/base"
2
+
3
+ module Genny
4
+ module String
5
+ @@formats = {}
6
+ @@hints = []
7
+
8
+ def self.format(format, &block)
9
+ @@formats[format] = block
10
+ end
11
+
12
+ def self.hint(&block)
13
+ @@hints.push(block)
14
+ end
15
+
16
+ def self.genny(opts = {})
17
+ opts = Genny.symbolize(opts)
18
+ viable_formats = @@formats.keys & [*opts[:format]]
19
+ return @@formats[viable_formats.sample].call(opts) unless viable_formats.empty?
20
+ guess = @@hints.inject(nil) { |guess, hint|
21
+ break guess if !(guess = hint.call(opts)).nil?
22
+ } if opts[:hint]
23
+ return guess unless guess.nil?
24
+ opts[:minLength] ||= 10
25
+ opts[:maxLength] ||= 10
26
+ length = Random.rand(opts[:maxLength] - opts[:minLength] + 1) + opts[:minLength]
27
+ ('a'..'z').to_a.sample(length).join
28
+ end
29
+
30
+ format("date-time") { |opts| Genny::Time.genny(opts).iso8601 }
31
+ format("date") { |opts| Genny::Date.genny(opts).iso8601 }
32
+ format("ipv4") { |opts| 4.times.map { Random.rand(255) }.join(".") }
33
+ format("ipv6") { |opts| 8.times.map { Random.rand(65536).to_s(16).rjust(4, "0") }.join(":") }
34
+ format("uri") { |opts| Genny::URI.genny(opts).to_s }
35
+ end
36
+ end
data/lib/genny/time.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "genny/base"
2
+ require "time"
3
+
4
+ module Genny
5
+ module Time
6
+ def self.genny(_opts = {})
7
+ ::Time.at(Random.rand(::Time.now.to_i)).utc
8
+ end
9
+ end
10
+ end
data/lib/genny/uri.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "genny/base"
2
+ require "uri"
3
+
4
+ module Genny
5
+ module URI
6
+ def self.genny(opts = {})
7
+ opts = Genny.symbolize(opts)
8
+ ::URI::HTTP.build(host: opts[:host] || 'example.com', path: "/#{Genny::String.genny}")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Genny
2
+ VERSION = begin
3
+ File.read(File.join(__dir__, "../../../VERSION")).strip
4
+ rescue Errno::ENOENT
5
+ "0.0.0-unknown"
6
+ end
7
+ end
data/lib/genny.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "genny/base"
2
+
3
+ require "genny/nil"
4
+ require "genny/uri"
5
+ require "genny/time"
6
+ require "genny/date"
7
+ require "genny/array"
8
+ require "genny/float"
9
+ require "genny/string"
10
+ require "genny/regexp"
11
+ require "genny/integer"
12
+ require "genny/boolean"
13
+ require "genny/json_schema"
14
+
15
+ require "genny/engines/faker"
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: genny
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - JP Hastings-Spital
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Genny likes to make things up. It generates ruby objects, mainly from
56
+ JSON Schema.
57
+ email:
58
+ - jphastings@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files:
62
+ - README.md
63
+ files:
64
+ - README.md
65
+ - VERSION
66
+ - lib/genny.rb
67
+ - lib/genny/array.rb
68
+ - lib/genny/base.rb
69
+ - lib/genny/boolean.rb
70
+ - lib/genny/date.rb
71
+ - lib/genny/engines/faker.rb
72
+ - lib/genny/float.rb
73
+ - lib/genny/integer.rb
74
+ - lib/genny/json_schema.rb
75
+ - lib/genny/nil.rb
76
+ - lib/genny/regexp.rb
77
+ - lib/genny/string.rb
78
+ - lib/genny/time.rb
79
+ - lib/genny/uri.rb
80
+ - lib/genny/version.rb
81
+ homepage: ''
82
+ licenses: []
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.2.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Genny likes to make things up. It generates ruby objects, mainly from JSON
104
+ Schema.
105
+ test_files: []