genny 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.
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: []