diff --git a/.travis.yml b/.travis.yml index 488b19d..56da410 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,8 @@ language: crystal crystal: - latest + - nightly +script: + - crystal spec + - crystal run examples/test.cr + - crystal run examples/test2.cr diff --git a/examples/test.cr b/examples/test.cr index 5be62fc..94deca1 100644 --- a/examples/test.cr +++ b/examples/test.cr @@ -23,9 +23,15 @@ puts Faker::Address.longitude puts "\n### Faker::Commerce\n\n" puts Faker::Commerce.color +puts Faker::Commerce.unique_color puts Faker::Commerce.department +puts Faker::Commerce.unique_department +puts Faker::Commerce.unique_department(2) +puts Faker::Commerce.unique_department(2, true) puts Faker::Commerce.product_name puts Faker::Commerce.price +puts Faker::Commerce.unique_price +puts Faker::Commerce.unique_price(0.0..10_000.00) puts "\n### Faker::Company\n\n" @@ -38,10 +44,16 @@ puts "\n\t### Faker::Internet\n\n" puts Faker::Internet.email puts Faker::Internet.email("Nancy") +puts Faker::Internet.unique_email("Nancy") +puts Faker::Internet.unique_email("Nancy") puts Faker::Internet.free_email +puts Faker::Internet.unique_free_email puts Faker::Internet.free_email("Nancy") +puts Faker::Internet.unique_free_email("Nancy") puts Faker::Internet.safe_email +puts Faker::Internet.unique_safe_email puts Faker::Internet.safe_email("Nancy") +puts Faker::Internet.unique_safe_email("Nancy") puts Faker::Internet.user_name puts Faker::Internet.user_name("Nancy") @@ -88,12 +100,25 @@ puts Faker::Name.prefix puts Faker::Name.suffix puts Faker::Name.title +puts "\n\t### Faker::Avatar\n\n" + +puts Faker::Avatar.image +puts Faker::Avatar.image("borp") +puts Faker::Avatar.unique_image("borp") +puts Faker::Avatar.unique_image("borp") + puts "\n\t### Faker::Number\n\n" puts Faker::Number.number(10) +# puts Faker::Number.unique_number(10) +# puts Faker::Number.unique_number(10) +# puts Faker::Number.unique_number(10) puts Faker::Number.decimal(2) puts Faker::Number.decimal(2, 3) +puts Faker::Number.unique_decimal(2) +# puts Faker::Number.unique_decimal(2, 3) puts Faker::Number.digit +puts Faker::Number.unique_digit puts "\n\t### Faker::PhoneNumber\n\n" @@ -101,6 +126,10 @@ puts Faker::PhoneNumber.phone_number puts "\n\t### Faker::Business\n\n" -p Faker::Business.credit_card_number -p Faker::Business.credit_card_expiry_date -p Faker::Business.credit_card_type +puts Faker::Business.credit_card_number +puts Faker::Business.credit_card_expiry_date +puts Faker::Business.credit_card_type + +puts Faker::Business.unique_credit_card_number +puts Faker::Business.unique_credit_card_expiry_date +puts Faker::Business.unique_credit_card_type diff --git a/spec/address_spec.cr b/spec/address_spec.cr index 876f4eb..046462b 100644 --- a/spec/address_spec.cr +++ b/spec/address_spec.cr @@ -21,4 +21,15 @@ describe Faker::Address do Faker::Address.latitude.should eq "-23.850993082533705" Faker::Address.longitude.should eq "-4.488135572134695" end + + it "should generate unique result" do + Faker.seed 123456 + vals = Array(String).new + + 10_000.times do |t| + vals << Faker::Address.unique_city + end + + vals.size.should eq vals.uniq.size + end end diff --git a/spec/internet_spec.cr b/spec/internet_spec.cr index 83ae3b1..72f1db6 100644 --- a/spec/internet_spec.cr +++ b/spec/internet_spec.cr @@ -70,7 +70,7 @@ describe Faker::Internet do it "password_max_with_integer_arg" do (1..32).to_a.each do |min_length| max_length = min_length + 4 - (Faker::Internet.password(min_length, max_length).size <= max_length).should be_true + (Faker::Internet.password(min_length, max_length).size <= max_length).should be_true end end diff --git a/src/faker.cr b/src/faker.cr index 8fb0d16..af31f12 100644 --- a/src/faker.cr +++ b/src/faker.cr @@ -1,4 +1,5 @@ require "./data.cr" +require "./faker/base" require "./faker/*" module Faker diff --git a/src/faker/address.cr b/src/faker/address.cr index 9a9720d..934133f 100644 --- a/src/faker/address.cr +++ b/src/faker/address.cr @@ -1,19 +1,24 @@ module Faker - class Address + class Address < Base def self.zip_code Faker.numerify(["#####", "#####-####"].sample(Faker.rng)) end + uniquify_builder(zip_code) + {% for data_type in %w(state state_abbr city_suffix city_prefix country street_suffix country_code) %} def self.{{data_type.id}} Faker.fetch(Data["address"]["{{data_type.id}}"]) end + uniquify_builder({{data_type.id}}) {% end %} def self.city Faker.fetch(Data["address"]["city"]) end + uniquify_builder(city) + def self.street_name [ ->{ [Name.last_name, street_suffix].join(" ") }, @@ -21,6 +26,8 @@ module Faker ].sample(Faker.rng).call end + uniquify_builder(street_name) + def self.street_address Faker.numerify([ ->{ "##### %s" % street_name }, @@ -32,14 +39,20 @@ module Faker ].sample(Faker.rng).call) end + uniquify_builder(street_address) + def self.secondary_address Faker.numerify(Faker.fetch(Data["address"]["secondary_address"])) end + uniquify_builder(street_address) + def self.building_number Faker.bothify(Faker.fetch(Data["address"]["building_number"])) end + uniquify_builder(building_number) + def self.postcode Faker.bothify([ ->{ "??# #??" }, @@ -47,16 +60,24 @@ module Faker ].sample(Faker.rng).call) end + uniquify_builder(postcode) + def self.latitude ((Faker.rng.rand * 180) - 90).to_s end + uniquify_builder(latitude) + def self.longitude ((Faker.rng.rand * 360) - 180).to_s end + uniquify_builder(longitude) + def self.time_zone Faker.fetch(Data["address"]["time_zone"]) end + + uniquify_builder(time_zone) end end diff --git a/src/faker/avatar.cr b/src/faker/avatar.cr index 13d63e5..280d55c 100644 --- a/src/faker/avatar.cr +++ b/src/faker/avatar.cr @@ -1,8 +1,10 @@ module Faker - class Avatar + class Avatar < Base def self.image(slug = nil) slug ||= Faker::Lorem.word "http://robohash.org/#{slug}" end + + uniquify_builder(image, slug = nil) end end diff --git a/src/faker/base.cr b/src/faker/base.cr new file mode 100644 index 0000000..5b4fe3a --- /dev/null +++ b/src/faker/base.cr @@ -0,0 +1,25 @@ +module Faker + class NonUniqueValue < Exception + end + + class Base + alias Any = String | Int32 | Float64 | Time + + macro uniquify_builder(attribute_name, *modified_method_attributes) + @@__unique_vals_for_{{attribute_name}} = Array(Any).new + + def self.unique_{{attribute_name}}({% if !modified_method_attributes.empty? %}{{*modified_method_attributes}},{% end %} max_retries = 10_0000) + max_retries.times do |t| + val = self.{{attribute_name}}({{*modified_method_attributes}}) + + if !@@__unique_vals_for_{{attribute_name}}.includes?(val) + @@__unique_vals_for_{{attribute_name}} << val + return val + end + end + + raise NonUniqueValue.new("Unable to generate unique value for {{attribute_name}}") + end + end + end +end diff --git a/src/faker/business.cr b/src/faker/business.cr index 01179aa..f62cde3 100644 --- a/src/faker/business.cr +++ b/src/faker/business.cr @@ -1,16 +1,22 @@ module Faker - class Business + class Business < Base def self.credit_card_number Faker.fetch(Data["business"]["credit_card_numbers"]) end + uniquify_builder(credit_card_number) + def self.credit_card_expiry_date credit_card_expiry_date = Faker.fetch(Data["business"]["credit_card_expiry_dates"]).as String Time.parse_local(credit_card_expiry_date, "%Y-%m-%d") end + uniquify_builder(credit_card_expiry_date) + def self.credit_card_type Faker.fetch(Data["business"]["credit_card_types"]) end + + uniquify_builder(credit_card_type) end end diff --git a/src/faker/commerce.cr b/src/faker/commerce.cr index 92426a3..4e42e36 100644 --- a/src/faker/commerce.cr +++ b/src/faker/commerce.cr @@ -1,9 +1,11 @@ module Faker - class Commerce + class Commerce < Base def self.color Faker.fetch(Data["color"]["name"]) end + uniquify_builder(color) + def self.department(max = 3, fixed_amount = false) num = max if fixed_amount num ||= 1 + Faker.rng.rand(max) @@ -17,20 +19,28 @@ module Faker end end + uniquify_builder(department, max = 3, fixed_amount = false) + def self.material product_name = Data["commerce"]["product_name"].as Hash Faker.fetch(product_name["material"]) end + uniquify_builder(material) + def self.product_name product_name = Data["commerce"]["product_name"].as Hash Faker.fetch(product_name["adjective"]) + " " + Faker.fetch(product_name["material"]) + " " + Faker.fetch(product_name["product"]) end + uniquify_builder(product_name) + def self.price(range = 0.0..100.0) (Faker.rng.rand(range) * 100).floor/100.0 end + uniquify_builder(price, range = 0.0..100.0) + private def self.categories(num) categories = [] of String while categories.size < num diff --git a/src/faker/company.cr b/src/faker/company.cr index 22e4874..d5d581e 100644 --- a/src/faker/company.cr +++ b/src/faker/company.cr @@ -1,26 +1,36 @@ module Faker - class Company + class Company < Base def self.name Faker.fetch(Data["company"]["name"]) end + uniquify_builder(name) + def self.suffix Faker.fetch(Data["company"]["suffix"]) end + uniquify_builder(suffix) + def self.catch_phrase data = Data["company"]["buzzwords"].as Array(Array(String)) Faker.fetch(data.flatten) end + uniquify_builder(catch_phrase) + def self.bs data = Data["company"]["bs"].as Array(Array(String)) Faker.fetch(data.flatten) end + uniquify_builder(bs) + def self.logo rand_num = Faker.rng.rand(13) + 1 "https://pigment.github.io/fake-logos/logos/medium/color/#{rand_num}.png" end + + uniquify_builder(logo) end end diff --git a/src/faker/finance.cr b/src/faker/finance.cr index 2b78951..6063167 100644 --- a/src/faker/finance.cr +++ b/src/faker/finance.cr @@ -1,5 +1,5 @@ module Faker - class Finance + class Finance < Base CREDIT_CARD_TYPES = (Data["credit_card"].as(Hash)).keys def self.credit_card(types : Array = [] of Array(String)) @@ -21,5 +21,6 @@ module Faker template = template.gsub "L", luhn_digit.to_s template end + # TODO: uniquify_builder(credit_card, types : Array = [] of Array(String)) end end diff --git a/src/faker/hacker.cr b/src/faker/hacker.cr index 7f6cfb9..c5ddec1 100644 --- a/src/faker/hacker.cr +++ b/src/faker/hacker.cr @@ -1,14 +1,17 @@ # Port of http://shinytoylabs.com/jargon/ module Faker - class Hacker + class Hacker < Base def self.say_something_smart phrases.sample(Faker.rng) end + uniquify_builder(say_something_smart) + {% for data_type in %w(abbreviation adjective noun verb ingverb) %} def self.{{data_type.id}} Faker.fetch(Data["hacker"]["{{data_type.id}}"]) end + uniquify_builder({{data_type.id}}) {% end %} def self.phrases diff --git a/src/faker/internet.cr b/src/faker/internet.cr index a4f21d3..10ee564 100644 --- a/src/faker/internet.cr +++ b/src/faker/internet.cr @@ -1,17 +1,23 @@ module Faker - class Internet + class Internet < Base def self.email(name = nil) [user_name(name), domain_name].join("@") end + uniquify_builder(email, name = nil) + def self.free_email(name = nil) [user_name(name), Faker.fetch(Data["internet"]["free_email"])].join("@") end + uniquify_builder(free_email, name = nil) + def self.safe_email(name = nil) [user_name(name), "example." + %w(org com net).shuffle(Faker.rng).first].join("@") end + uniquify_builder(safe_email, name = nil) + def self.user_name(specifier = nil, separators = %w(. _)) if specifier.is_a? String return specifier.scan(/\w+/).map { |s| s[0] }.shuffle(Faker.rng).join(separators.sample(Faker.rng)).downcase @@ -51,18 +57,26 @@ module Faker ].sample(Faker.rng).call end + uniquify_builder(user_name, specifier = nil, separators = %w(. _)) + def self.domain_name [domain_word, domain_suffix].join(".") end + uniquify_builder(domain_name) + def self.domain_word Company.name.split(" ").first.gsub(/\W/, "").downcase end + uniquify_builder(domain_word) + def self.domain_suffix Faker.fetch(Data["internet"]["domain_suffix"]) end + uniquify_builder(domain_suffix) + def self.ip_v4_address [ (2..254).to_a.sample(Faker.rng), @@ -72,27 +86,37 @@ module Faker ].join('.') end + uniquify_builder(ip_v4_address) + def self.ip_v6_address ip_v6_space = (0..65535).to_a container = (1..8).map { |_| ip_v6_space.sample(Faker.rng) } container.map { |n| n.to_s(16) }.join(':') end + uniquify_builder(ip_v6_address) + def self.mac_address(prefix = "") prefix_digits = prefix.split(":").map { |d| d.to_i?(16) ? d.to_i?(16) : 0 } address_digits = (1..(6 - prefix_digits.size)).map { Faker.rng.rand(256) } (prefix_digits + address_digits).map { |d| "%02x" % d }.join(":") end + uniquify_builder(mac_address, prefix = "") + def self.url(host = domain_name, path = "/#{user_name}") "http://#{host}#{path}" end + uniquify_builder(url, host = domain_name, path = "/#{user_name}") + def self.slug(words = nil, glue = nil) glue ||= %w(- _ .).sample(Faker.rng) (words || Lorem.words(2).join(' ')).gsub(' ', glue).downcase end + uniquify_builder(slug, words = nil, glue = nil) + def self.password(min_length = 8, max_length = 16, mix_case = true, special_chars = false) temp = Lorem.characters(min_length) diff_length = max_length - min_length @@ -115,5 +139,7 @@ module Faker return temp end + + uniquify_builder(password, min_length = 8, max_length = 16, mix_case = true, special_chars = false) end end diff --git a/src/faker/lorem.cr b/src/faker/lorem.cr index f69711f..6ed5bf5 100644 --- a/src/faker/lorem.cr +++ b/src/faker/lorem.cr @@ -1,20 +1,26 @@ module Faker # Based on Perl"s Text::Lorem - class Lorem + class Lorem < Base def self.character characters(1) end + uniquify_builder(character) + def self.characters(char_count = 255) chars = ("a".."z").to_a + (0..9).to_a Array(String).new(char_count < 0 ? 0 : char_count, "").map { chars.sample(Faker.rng) }.join("") end + uniquify_builder(characters) + def self.word words = Data["lorem"]["words"].as Array Faker.fetch(words) end + uniquify_builder(word) + def self.words(num = 3, supplemental = false) words = Data["lorem"]["words"].as Array(String) words += (Data["lorem"]["supplemental"].as Array(String)) if supplemental @@ -22,10 +28,14 @@ module Faker words.shuffle(Faker.rng)[0, num] end + uniquify_builder(words) + def self.sentence(word_count = 4, supplemental = false, random_words_to_add = 6) words(word_count + Faker.rng.rand(random_words_to_add.to_i).to_i, supplemental).join(" ").capitalize + "." end + uniquify_builder(sentence) + def self.sentences(sentence_count = 3, supplemental = false) ([] of String).tap do |sentences| 1.upto(sentence_count) do @@ -34,10 +44,14 @@ module Faker end end + uniquify_builder(sentences) + def self.paragraph(sentence_count = 3, supplemental = false, random_sentences_to_add = 3) sentences(sentence_count + Faker.rng.rand(random_sentences_to_add.to_i).to_i, supplemental).join(" ") end + uniquify_builder(paragraph) + def self.paragraphs(paragraph_count = 3, supplemental = false) ([] of String).tap do |paragraphs| 1.upto(paragraph_count) do @@ -45,5 +59,7 @@ module Faker end end end + + uniquify_builder(paragraphs) end end diff --git a/src/faker/name.cr b/src/faker/name.cr index 007fec0..69ddce5 100644 --- a/src/faker/name.cr +++ b/src/faker/name.cr @@ -1,14 +1,17 @@ module Faker - class Name + class Name < Base {% for data_type in %w(first_name last_name name prefix suffix) %} def self.{{data_type.id}} Faker.fetch(Data["name"]["{{data_type.id}}"]) end + uniquify_builder({{data_type.id}}) {% end %} def self.title title = Data["name"]["title"].as Hash Faker.fetch(title["descriptor"]) + " " + Faker.fetch(title["level"]) + " " + Faker.fetch(title["job"]) end + + uniquify_builder(title) end end diff --git a/src/faker/number.cr b/src/faker/number.cr index 1f68446..20c05d8 100644 --- a/src/faker/number.cr +++ b/src/faker/number.cr @@ -1,39 +1,53 @@ module Faker - class Number + class Number < Base def self.number(digits) (1..digits).map { digit }.join "" end + uniquify_builder(number, digits) + def self.digit Faker.rng.rand(10).to_s end + uniquify_builder(digit) + def self.decimal(l_digits, r_digits = 2) l_d = number(l_digits) r_d = number(r_digits) "#{l_d}.#{r_d}" end + uniquify_builder(decimal, l_digits, r_digits = 2) + def self.between(from = 1.00, to = 5000.00) Faker.rand_in_range(from, to) end + uniquify_builder(between) + def self.positive(from = 1.00, to = 5000.00) random_number = between(from, to) greater_than_zero(random_number) end + uniquify_builder(positive) + def self.negative(from = -5000.00, to = -1.00) random_number = between(from, to) less_than_zero(random_number) end + uniquify_builder(negative) + def self.hexadecimal(digits) hex = "" digits.times { hex += Faker.rng.rand(15).to_s(16) } hex end + uniquify_builder(hexadecimal) + private def self.greater_than_zero(number) if number > 0 number diff --git a/src/faker/phone_number.cr b/src/faker/phone_number.cr index 699b0d6..7f9ddb9 100644 --- a/src/faker/phone_number.cr +++ b/src/faker/phone_number.cr @@ -1,7 +1,9 @@ module Faker - class PhoneNumber + class PhoneNumber < Base def self.phone_number Faker.numerify(Faker.fetch(Data["phone_number"]["formats"])) end + + uniquify_builder(phone_number) end end diff --git a/src/faker/team.cr b/src/faker/team.cr index fb4779f..453785e 100644 --- a/src/faker/team.cr +++ b/src/faker/team.cr @@ -1,15 +1,21 @@ module Faker - class Team + class Team < Base def self.name Faker.fetch(Data["team"]["name"]) end + uniquify_builder(name) + def self.creature Faker.fetch(Data["team"]["creature"]) end + uniquify_builder(creature) + def self.state Faker.fetch(Data["address"]["state"]) end + + uniquify_builder(state) end end