├── .rspec ├── Rakefile ├── Gemfile ├── .travis.yml ├── .yardopts ├── LICENSE ├── .gitignore ├── string_pattern.gemspec ├── lib ├── string_pattern.rb └── string │ └── pattern │ ├── analyze.rb │ ├── validate.rb │ ├── add_to_ruby.rb │ └── generate.rb ├── spec ├── string │ └── pattern │ │ ├── add_to_ruby_spec.rb │ │ ├── validate_spec.rb │ │ └── generate_spec.rb └── spec_helper.rb └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake" 2 | require "rspec/core/rake_task" 3 | RSpec::Core::RakeTask.new(:spec) do |t| 4 | t.pattern = Dir.glob("spec/**/*_spec.rb") 5 | t.rspec_opts = "--format documentation" 6 | end 7 | task default: :spec -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :test do 4 | gem 'rake' 5 | gem 'rspec' 6 | gem 'coveralls_reborn', '~> 0.27.0', require: false 7 | end 8 | 9 | # Specify your gem's dependencies in mygem.gemspec 10 | gemspec -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.7 4 | - 3.0 5 | - ruby-head 6 | branches: 7 | except: 8 | - "readme-edits" 9 | before_install: 10 | - gem update --system 11 | 12 | matrix: 13 | allow_failures: 14 | - rvm: ruby-head 15 | 16 | script: 17 | - bundle exec rake spec -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --readme README.md 2 | --title 'string_pattern - You can easily generate strings supplying a very simple pattern. Also you can validate if a text fulfill an specific pattern or even generate a string following a pattern and returning wrong length, value... for testing your applications.' 3 | --charset utf-8 4 | --markup markdown 5 | 'lib/**/*.rb' - '*.md' - 'LICENSE' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mario Ruiz 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.idea 4 | /.config 5 | /coverage/ 6 | /InstalledFiles 7 | /pkg/ 8 | /spec/reports/ 9 | /spec/examples.txt 10 | /test/tmp/ 11 | /test/version_tmp/ 12 | /tmp/ 13 | .vscode/ 14 | /data/spanish/black.txt 15 | /data/english/black.txt 16 | Gemfile.lock 17 | /Gemfile.lock 18 | .covered.msgpack 19 | *.log 20 | 21 | # Used by dotenv library to load environment variables. 22 | # .env 23 | 24 | ## Specific to RubyMotion: 25 | .dat* 26 | .repl_history 27 | build/ 28 | *.bridgesupport 29 | build-iPhoneOS/ 30 | build-iPhoneSimulator/ 31 | 32 | ## Specific to RubyMotion (use of CocoaPods): 33 | # 34 | # We recommend against adding the Pods directory to your .gitignore. However 35 | # you should judge for yourself, the pros and cons are mentioned at: 36 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 37 | # 38 | # vendor/Pods/ 39 | 40 | ## Documentation cache and generated files: 41 | /.yardoc/ 42 | /_yardoc/ 43 | /doc/ 44 | /rdoc/ 45 | 46 | ## Environment normalization: 47 | /.bundle/ 48 | /vendor/bundle 49 | /lib/bundler/man/ 50 | 51 | # for a library or gem, you might want to ignore these files since the code is 52 | # intended to run in multiple environments; otherwise, check them in: 53 | # Gemfile.lock 54 | # .ruby-version 55 | # .ruby-gemset 56 | 57 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 58 | .rvmrc 59 | -------------------------------------------------------------------------------- /string_pattern.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'string_pattern' 3 | s.version = '2.3.0' 4 | s.summary = "Generate easily random strings following a simple pattern or regular expression. '10-20:Xn/x/'.generate #>qBstvc6JN8ra. Also generate words in English or Spanish. Perfect to be used in test data factories." 5 | s.description = "Easily generate strings supplying a very simple pattern. '10-20:Xn/x/'.generate #>qBstvc6JN8ra. Generate random strings using a regular expression (Regexp): /[a-z0-9]{2,5}\w+/.gen . Also generate words in English or Spanish. Perfect to be used in test data factories. Also, validate if a text fulfills a specific pattern or even generate a string following a pattern and returning the wrong length, value... for testing your applications." 6 | s.authors = ["Mario Ruiz"] 7 | s.email = 'marioruizs@gmail.com' 8 | s.files = ["lib/string_pattern.rb","lib/string/pattern/add_to_ruby.rb", "lib/string/pattern/analyze.rb", 9 | "lib/string/pattern/generate.rb", "lib/string/pattern/validate.rb", 10 | "LICENSE","README.md",".yardopts", 11 | 'data/english/adjs.json', 'data/english/nouns.json', 'data/spanish/palabras0.json', 12 | 'data/spanish/palabras1.json','data/spanish/palabras2.json','data/spanish/palabras3.json', 13 | 'data/spanish/palabras4.json','data/spanish/palabras5.json','data/spanish/palabras6.json', 14 | 'data/spanish/palabras7.json','data/spanish/palabras8.json','data/spanish/palabras9.json', 15 | 'data/spanish/palabras10.json','data/spanish/palabras11.json',] 16 | s.extra_rdoc_files = ["LICENSE","README.md"] 17 | s.homepage = 'https://github.com/MarioRuiz/string_pattern' 18 | s.license = 'MIT' 19 | s.add_runtime_dependency 'regexp_parser', '~> 2.5', '>= 2.5.0' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /lib/string_pattern.rb: -------------------------------------------------------------------------------- 1 | SP_ADD_TO_RUBY = true if !defined?(SP_ADD_TO_RUBY) 2 | require_relative "string/pattern/add_to_ruby" if SP_ADD_TO_RUBY 3 | require_relative "string/pattern/analyze" 4 | require_relative "string/pattern/generate" 5 | require_relative "string/pattern/validate" 6 | 7 | # SP_ADD_TO_RUBY: (TrueFalse, default: true) You need to add this constant value before requiring the library if you want to modify the default. 8 | # If true it will add 'generate' and 'validate' methods to the classes: Array, String and Symbol. Also it will add 'generate' method to Kernel 9 | # aliases: 'gen' for 'generate' and 'val' for 'validate' 10 | # Examples of use: 11 | # "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) 980-65-05 12 | # %w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen #>(045) 448-63-09 13 | # ["1:L", "5-10:LN", "-", "3:N"].gen #>zqWihV-746 14 | # gen("10:N") #>3433409877 15 | # "20-30:@".gen #>dkj34MljjJD-df@jfdluul.dfu 16 | # "10:L/N/[/-./%d%]".validate("12ds6f--.s") #>[:value, :string_set_not_allowed] 17 | # "20-40:@".validate(my_email) 18 | # national_chars: (Array, default: english alphabet) 19 | # Set of characters that will be used when using T pattern 20 | # optimistic: (TrueFalse, default: true) 21 | # If true it will check on the strings of the array positions if they have the pattern format and assume in that case that is a pattern. 22 | # dont_repeat: (TrueFalse, default: false) 23 | # If you want to generate for example 1000 strings and be sure all those strings are different you can set it to true 24 | # default_infinite: (Integer, default: 10) 25 | # In case using regular expressions the maximum when using * or + for repetitions 26 | # word_separator: (String, default: '_') 27 | # When generating words using symbol types 'w' or 'p' the character to separate the english or spanish words. 28 | # block_list: (Array, default: empty) 29 | # Array of words to be avoided from resultant strings. 30 | # block_list_enabled: (TrueFalse, default: false) 31 | # If true block_list will be take in consideration 32 | class StringPattern 33 | class << self 34 | attr_accessor :national_chars, :optimistic, :dont_repeat, :cache, :cache_values, :default_infinite, :word_separator, :block_list, :block_list_enabled 35 | end 36 | @national_chars = (("a".."z").to_a + ("A".."Z").to_a).join 37 | @optimistic = true 38 | @cache = Hash.new() 39 | @cache_values = Hash.new() 40 | @dont_repeat = false 41 | @default_infinite = 10 42 | @word_separator = "_" 43 | @block_list_enabled = false 44 | @block_list = [] 45 | NUMBER_SET = ("0".."9").to_a 46 | SPECIAL_SET = [" ", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "_", "+", "=", "{", "}", "[", "]", "'", ";", ":", "?", ">", "<", "`", "|", "/", '"'] 47 | ALPHA_SET_LOWER = ("a".."z").to_a 48 | ALPHA_SET_CAPITAL = ("A".."Z").to_a 49 | @palabras = [] 50 | @palabras_camel = [] 51 | @words = [] 52 | @words_camel = [] 53 | @palabras_short = [] 54 | @words_short = [] 55 | @palabras_camel_short = [] 56 | @words_camel_short = [] 57 | 58 | Pattern = Struct.new(:min_length, :max_length, :symbol_type, :required_data, :excluded_data, :data_provided, 59 | :string_set, :all_characters_set, :unique) 60 | 61 | def self.national_chars=(par) 62 | @cache = Hash.new() 63 | @national_chars = par 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /spec/string/pattern/add_to_ruby_spec.rb: -------------------------------------------------------------------------------- 1 | require "string_pattern" 2 | 3 | RSpec.describe StringPattern, "#add_to_ruby" do 4 | describe "array class" do 5 | it "responds to generate" do 6 | expect(["uno", "2:n"].generate).to match(/uno[0-9]{2}/) 7 | end 8 | it "responds to gen" do 9 | expect(["uno", "2:n"].gen).to match(/uno[0-9]{2}/) 10 | end 11 | it "responds to validate" do 12 | expect(["uno", "2:n"].validate("uno33")).to eq true 13 | end 14 | it "responds to val" do 15 | expect(["uno", "2:n"].val("uno33")).to eq true 16 | end 17 | end 18 | 19 | describe "string class" do 20 | it "responds to generate" do 21 | expect("2:n".generate).to match(/[0-9]{2}/) 22 | end 23 | it "responds to gen" do 24 | expect("2:n".gen).to match(/[0-9]{2}/) 25 | end 26 | it "responds to validate" do 27 | expect("2:n".validate("33")).to eq [] 28 | end 29 | it "responds to val" do 30 | expect("2:n".val("33")).to eq [] 31 | end 32 | it "responds to to_camel_case" do 33 | expect("ccccc aaa".to_camel_case).to eq "CccccAaa" 34 | end 35 | it "responds to to_snake_case" do 36 | expect("Caaaa bjjAm, ; Djjáb".to_snake_case).to eq "caaaa_bjj_am_djj_b" 37 | end 38 | it 'returns camel case of Spanish characters' do 39 | expect("caña de albóndiga".to_camel_case).to eq "CañaDeAlbóndiga" 40 | end 41 | end 42 | 43 | describe "symbol class" do 44 | it "responds to generate" do 45 | expect(:'2:n'.generate).to match(/[0-9]{2}/) 46 | end 47 | it "responds to gen" do 48 | expect(:'2:n'.gen).to match(/[0-9]{2}/) 49 | end 50 | it "responds to validate" do 51 | expect(:'2:n'.validate("33")).to eq [] 52 | end 53 | it "responds to val" do 54 | expect(:'2:n'.val("33")).to eq [] 55 | end 56 | end 57 | 58 | describe "regexp class" do 59 | it "responds to generate" do 60 | expect(/\d{2}/.generate).to match(/[0-9]{2}/) 61 | end 62 | it "responds to gen" do 63 | expect(/\d{2}/.gen).to match(/[0-9]{2}/) 64 | end 65 | it "responds to to_sp" do 66 | expect(/\d{2}/.to_sp).to eq "2:n" 67 | end 68 | it "/[^abc]+/i.to_sp" do 69 | expect(/[^abc]+/i.to_sp).to eq "1-10:[%aAbBcC%]*" 70 | end 71 | it "/[abc]+/i.to_sp" do 72 | expect(/[abc]+/i.to_sp).to eq "1-10:[aAbBcC]" 73 | end 74 | it "/[a-z]+/i.to_sp" do 75 | expect(/[a-z]+/i.to_sp).to eq "1-10:L" 76 | end 77 | it "/[a-z]+/.to_sp" do 78 | expect(/[a-z]+/.to_sp).to eq "1-10:x" 79 | end 80 | it "/[m-z]+/i.to_sp" do 81 | expect(/[m-z]+/i.to_sp).to eq '1-10:[mnopqrstuvwxyzMNOPQRSTUVWXYZ]' 82 | end 83 | it '/[m-z]+\d+\w+[ab]+/i.to_sp' do 84 | expect(/[m-z]+\d+\w+[ab]+/i.to_sp).to eq ["1-10:[mnopqrstuvwxyzMNOPQRSTUVWXYZ]", "1-10:n", "1-10:Ln_", "1-10:[aAbB]"] 85 | end 86 | it '/a{3,}/.to_sp' do 87 | expect(/a{3,}/.to_sp).to eq '3-13:[a]' 88 | end 89 | it '/a{3,8}/.to_sp' do 90 | expect(/a{3,8}/.to_sp).to eq '3-8:[a]' 91 | end 92 | it '/a{3}/.to_sp' do 93 | expect(/a{3}/.to_sp).to eq '3:[a]' 94 | end 95 | it '/a{15,}/.to_sp' do 96 | expect(/a{15,}/.to_sp).to eq '15-25:[a]' 97 | end 98 | 99 | end 100 | 101 | describe "from Kernel" do 102 | it "responds to generate" do 103 | expect(generate("2:N")).to match(/[0-9]{2}/) 104 | end 105 | it "responds to gen" do 106 | expect(gen("2:N")).to match(/[0-9]{2}/) 107 | end 108 | it "display error when wrong type on array of patterns" do 109 | expect { generate(3)}.to output(/Kernel generate method: class not recognized/).to_stdout 110 | end 111 | 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /spec/string/pattern/validate_spec.rb: -------------------------------------------------------------------------------- 1 | require "string_pattern" 2 | 3 | RSpec.describe StringPattern, "#validate" do 4 | describe "generic" do 5 | it "returns empty when good validation" do 6 | expect("6:N".validate("333444")).to eq([]) 7 | end 8 | it "returns true when good validation for array of patterns" do 9 | expect(["3:n", "3:x"].validate("333xxx")).to eq(true) 10 | end 11 | it "accepts symbols as patterns" do 12 | expect(:"6:N".validate("333444")).to eq([]) 13 | end 14 | it "display error when wrong type on array of patterns" do 15 | expect(['1:N',3].validate('3')).to eq false 16 | expect { ['1:N',3].validate('3')}.to output(/String pattern class not supported/).to_stdout 17 | end 18 | end 19 | describe "length" do 20 | it "returns :min_length when wrong :min_length" do 21 | expect("6:N".validate("33344")).to include(:min_length) 22 | end 23 | it "returns :length when wrong :min_length" do 24 | expect("6:N".validate("33344")).to include(:length) 25 | end 26 | it "returns :max_length when wrong :max_length" do 27 | expect("6:N".validate("4433344")).to include(:max_length) 28 | end 29 | it "returns :length when wrong :max_length" do 30 | expect("6:N".validate("5466633344")).to include(:length) 31 | end 32 | it "returns false when array of patterns and wrong :min_length" do 33 | expect(["3:n", "3:x"].validate("333xx")).to eq false 34 | end 35 | it "returns false when array of patterns and wrong :max_length" do 36 | expect(["3:n", "3:x"].validate("3333xxxx")).to eq false 37 | end 38 | it "returns :min_length when wrong :min_length for email pattern" do 39 | expect("20:@".validate("hola@hola.com")).to include(:min_length) 40 | end 41 | it "returns :max_length when wrong :max_length for email pattern" do 42 | expect("20:@".validate("holafdsdfdsfsfd@hola.com")).to include(:max_length) 43 | end 44 | it "returns :length when wrong :length for email pattern" do 45 | expect("20:@".validate("holafdsdfdsfsfd@hola.com")).to include(:length) 46 | end 47 | end 48 | describe "value" do 49 | it "returns :value when wrong :value" do 50 | expect("6:N".validate("33d344")).to include(:value) 51 | end 52 | it "returns :excluded_data when including :excluded_data" do 53 | expect("6:N[%0%]".validate("330344")).to include(:excluded_data) 54 | end 55 | it "returns :required_data when missing :required_data" do 56 | expect("6:N[/0/]".validate("334344")).to include(:required_data) 57 | end 58 | it "returns :string_set_not_allowed when including string_set_not_allowed" do 59 | expect("6:N".validate("33a344")).to include(:string_set_not_allowed) 60 | end 61 | it "returns false when array of patterns and wrong :value" do 62 | expect(["3:n", "3:x"].validate("3x3xxx")).to eq false 63 | end 64 | it "returns :value when wrong :value for email pattern" do 65 | expect("20-40:@".validate("hola aaa@hola.com")).to include(:value) 66 | end 67 | end 68 | 69 | describe "expected_errors" do 70 | it "admits alias :errors" do 71 | expect("6:N".validate("335344", errors: [:value])).to eq(false) 72 | end 73 | it "returns false when good :value" do 74 | expect("6:N".validate("335344", expected_errors: [:value])).to eq(false) 75 | end 76 | it "returns true when wrong :value" do 77 | expect("6:N".validate("33d344", expected_errors: [:value])).to eq(true) 78 | end 79 | it "allows array of patterns" do 80 | expect(["3:n", "3:x"].validate("333xx3", errors: [:value])).to be true 81 | end 82 | end 83 | 84 | describe "not expected_errors" do 85 | it "admits alias :not_errors" do 86 | expect("6:N".validate("335344", not_errors: [:value])).to eq(true) 87 | end 88 | it "admits alias :non_expected_errors" do 89 | expect("6:N".validate("335344", non_expected_errors: [:value])).to eq(true) 90 | end 91 | it "returns true when good :value" do 92 | expect("6:N".validate("335344", not_expected_errors: [:value])).to eq(true) 93 | end 94 | it "returns false when wrong :value" do 95 | expect("6:N".validate("33d344", not_expected_errors: [:value])).to eq(false) 96 | end 97 | it "allows array of patterns" do 98 | expect(["3:n", "3:x"].validate("333xx", not_errors: [:value])).to be true 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "coveralls" 2 | Coveralls.wear! 3 | 4 | # This file was generated by the `rspec --init` command. Conventionally, all 5 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 6 | # The generated `.rspec` file contains `--require spec_helper` which will cause 7 | # this file to always be loaded, without a need to explicitly require it in any 8 | # files. 9 | # 10 | # Given that it is always loaded, you are encouraged to keep this file as 11 | # light-weight as possible. Requiring heavyweight dependencies from this file 12 | # will add to the boot time of your test suite on EVERY test run, even for an 13 | # individual file that may not need all of that loaded. Instead, consider making 14 | # a separate helper file that requires the additional dependencies and performs 15 | # the additional setup, and require it from the spec files that actually need 16 | # it. 17 | # 18 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 19 | RSpec.configure do |config| 20 | # rspec-expectations config goes here. You can use an alternate 21 | # assertion/expectation library such as wrong or the stdlib/minitest 22 | # assertions if you prefer. 23 | config.expect_with :rspec do |expectations| 24 | # This option will default to `true` in RSpec 4. It makes the `description` 25 | # and `failure_message` of custom matchers include text for helper methods 26 | # defined using `chain`, e.g.: 27 | # be_bigger_than(2).and_smaller_than(4).description 28 | # # => "be bigger than 2 and smaller than 4" 29 | # ...rather than: 30 | # # => "be bigger than 2" 31 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 32 | end 33 | 34 | # rspec-mocks config goes here. You can use an alternate test double 35 | # library (such as bogus or mocha) by changing the `mock_with` option here. 36 | config.mock_with :rspec do |mocks| 37 | # Prevents you from mocking or stubbing a method that does not exist on 38 | # a real object. This is generally recommended, and will default to 39 | # `true` in RSpec 4. 40 | mocks.verify_partial_doubles = true 41 | end 42 | 43 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 44 | # have no way to turn it off -- the option exists only for backwards 45 | # compatibility in RSpec 3). It causes shared context metadata to be 46 | # inherited by the metadata hash of host groups and examples, rather than 47 | # triggering implicit auto-inclusion in groups with matching metadata. 48 | config.shared_context_metadata_behavior = :apply_to_host_groups 49 | 50 | # The settings below are suggested to provide a good initial experience 51 | # with RSpec, but feel free to customize to your heart's content. 52 | =begin 53 | # This allows you to limit a spec run to individual examples or groups 54 | # you care about by tagging them with `:focus` metadata. When nothing 55 | # is tagged with `:focus`, all examples get run. RSpec also provides 56 | # aliases for `it`, `describe`, and `context` that include `:focus` 57 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 58 | config.filter_run_when_matching :focus 59 | 60 | # Allows RSpec to persist some state between runs in order to support 61 | # the `--only-failures` and `--next-failure` CLI options. We recommend 62 | # you configure your source control system to ignore this file. 63 | config.example_status_persistence_file_path = "spec/examples.txt" 64 | 65 | # Limits the available syntax to the non-monkey patched syntax that is 66 | # recommended. For more details, see: 67 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 68 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 69 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 70 | config.disable_monkey_patching! 71 | 72 | # This setting enables warnings. It's recommended, but in some cases may 73 | # be too noisy due to issues in dependencies. 74 | config.warnings = true 75 | 76 | # Many RSpec users commonly either run the entire suite or an individual 77 | # file, and it's useful to allow more verbose output when running an 78 | # individual spec file. 79 | if config.files_to_run.one? 80 | # Use the documentation formatter for detailed output, 81 | # unless a formatter has already been configured 82 | # (e.g. via a command-line flag). 83 | config.default_formatter = "doc" 84 | end 85 | 86 | # Print the 10 slowest examples and example groups at the 87 | # end of the spec run, to help surface which specs are running 88 | # particularly slow. 89 | config.profile_examples = 10 90 | 91 | # Run specs in random order to surface order dependencies. If you find an 92 | # order dependency and want to debug it, you can fix the order by providing 93 | # the seed, which is printed after each run. 94 | # --seed 1234 95 | config.order = :random 96 | 97 | # Seed global randomization in this process using the `--seed` CLI option. 98 | # Setting this allows you to use `--seed` to deterministically reproduce 99 | # test failures related to randomization by passing the same `--seed` value 100 | # as the one that triggered the failure. 101 | Kernel.srand config.seed 102 | =end 103 | end 104 | -------------------------------------------------------------------------------- /lib/string/pattern/analyze.rb: -------------------------------------------------------------------------------- 1 | class StringPattern 2 | ############################################### 3 | # Analyze the pattern supplied and returns an object of Pattern structure including: 4 | # min_length, max_length, symbol_type, required_data, excluded_data, data_provided, string_set, all_characters_set 5 | ############################################### 6 | def StringPattern.analyze(pattern, silent: false) 7 | #unless @cache[pattern.to_s].nil? 8 | # return Pattern.new(@cache[pattern.to_s].min_length.clone, @cache[pattern.to_s].max_length.clone, 9 | # @cache[pattern.to_s].symbol_type.clone, @cache[pattern.to_s].required_data.clone, 10 | # @cache[pattern.to_s].excluded_data.clone, @cache[pattern.to_s].data_provided.clone, 11 | # @cache[pattern.to_s].string_set.clone, @cache[pattern.to_s].all_characters_set.clone, @cache[pattern.to_s].unique.clone) 12 | #end 13 | return @cache[pattern.to_s].clone unless @cache[pattern.to_s].nil? 14 | min_length, max_length, symbol_type = pattern.to_s.scan(/(\d+)-(\d+):(.+)/)[0] 15 | if min_length.nil? 16 | min_length, symbol_type = pattern.to_s.scan(/^!?(\d+):(.+)/)[0] 17 | max_length = min_length 18 | if min_length.nil? 19 | puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}" unless silent 20 | return pattern.to_s 21 | end 22 | end 23 | if symbol_type[-1] == "&" 24 | symbol_type.chop! 25 | unique = true 26 | else 27 | unique = false 28 | end 29 | 30 | symbol_type = "!" + symbol_type if pattern.to_s[0] == "!" 31 | min_length = min_length.to_i 32 | max_length = max_length.to_i 33 | 34 | required_data = Array.new 35 | excluded_data = Array.new 36 | required = false 37 | excluded = false 38 | data_provided = Array.new 39 | a = symbol_type 40 | begin_provided = a.index("[") 41 | excluded_end_tag = false 42 | unless begin_provided.nil? 43 | c = begin_provided + 1 44 | until c == a.size or (a[c..c] == "]" and a[c..c + 1] != "]]") 45 | if a[c..c + 1] == "]]" 46 | data_provided.push("]") 47 | c = c + 2 48 | elsif a[c..c + 1] == "%%" and !excluded 49 | data_provided.push("%") 50 | c = c + 2 51 | else 52 | if a[c..c] == "/" and !excluded 53 | if a[c..c + 1] == "//" 54 | data_provided.push(a[c..c]) 55 | if required 56 | required_data.push([a[c..c]]) 57 | end 58 | c = c + 1 59 | else 60 | if !required 61 | required = true 62 | else 63 | required = false 64 | end 65 | end 66 | else 67 | if required 68 | required_data.push([a[c..c]]) 69 | else 70 | if a[c..c] == "%" 71 | if a[c..c + 1] == "%%" and excluded 72 | excluded_data.push([a[c..c]]) 73 | c = c + 1 74 | else 75 | if !excluded 76 | excluded = true 77 | else 78 | excluded = false 79 | excluded_end_tag = true 80 | end 81 | end 82 | else 83 | if excluded 84 | excluded_data.push([a[c..c]]) 85 | end 86 | end 87 | end 88 | if excluded == false and excluded_end_tag == false 89 | data_provided.push(a[c..c]) 90 | end 91 | excluded_end_tag = false 92 | end 93 | c = c + 1 94 | end 95 | end 96 | symbol_type = symbol_type[0..begin_provided].to_s + symbol_type[c..symbol_type.size].to_s 97 | end 98 | 99 | required = false 100 | required_symbol = "" 101 | if symbol_type.include?("/") 102 | symbol_type.chars.each { |stc| 103 | if stc == "/" 104 | if !required 105 | required = true 106 | else 107 | required = false 108 | end 109 | else 110 | if required 111 | required_symbol += stc 112 | end 113 | end 114 | } 115 | end 116 | 117 | national_set = @national_chars.chars 118 | 119 | if symbol_type.include?("L") 120 | alpha_set = ALPHA_SET_LOWER.clone + ALPHA_SET_CAPITAL.clone 121 | elsif symbol_type.include?("x") 122 | alpha_set = ALPHA_SET_LOWER.clone 123 | if symbol_type.include?("X") 124 | alpha_set = alpha_set + ALPHA_SET_CAPITAL.clone 125 | end 126 | elsif symbol_type.include?("X") 127 | alpha_set = ALPHA_SET_CAPITAL.clone 128 | else 129 | alpha_set = [] 130 | end 131 | if symbol_type.include?("T") 132 | alpha_set = alpha_set + national_set 133 | end 134 | 135 | unless required_symbol.nil? 136 | if required_symbol.include?("x") 137 | required_data.push ALPHA_SET_LOWER.clone 138 | end 139 | if required_symbol.include?("X") 140 | required_data.push ALPHA_SET_CAPITAL.clone 141 | end 142 | if required_symbol.include?("L") 143 | required_data.push(ALPHA_SET_CAPITAL.clone + ALPHA_SET_LOWER.clone) 144 | end 145 | if required_symbol.include?("T") 146 | required_data.push national_set 147 | end 148 | required_symbol = required_symbol.downcase 149 | end 150 | string_set = Array.new 151 | 152 | all_characters_set = ALPHA_SET_CAPITAL.clone + ALPHA_SET_LOWER.clone + NUMBER_SET.clone + SPECIAL_SET.clone + data_provided + national_set 153 | if symbol_type.include?("_") 154 | unless symbol_type.include?("$") 155 | string_set.push(" ") 156 | end 157 | if required_symbol.include?("_") 158 | required_data.push([" "]) 159 | end 160 | end 161 | 162 | #symbol_type = symbol_type.downcase 163 | 164 | if symbol_type.downcase.include?("x") or symbol_type.downcase.include?("l") or symbol_type.downcase.include?("t") 165 | string_set = string_set + alpha_set 166 | end 167 | if symbol_type.downcase.include?("n") 168 | string_set = string_set + NUMBER_SET 169 | end 170 | if symbol_type.include?("$") 171 | string_set = string_set + SPECIAL_SET 172 | end 173 | if symbol_type.include?("*") 174 | string_set = string_set + all_characters_set 175 | end 176 | if data_provided.size != 0 177 | string_set = string_set + data_provided 178 | end 179 | unless required_symbol.empty? 180 | if required_symbol.include?("n") 181 | required_data.push NUMBER_SET.clone 182 | end 183 | if required_symbol.include?("$") 184 | required_data.push SPECIAL_SET.clone 185 | end 186 | end 187 | unless excluded_data.empty? 188 | string_set = string_set - excluded_data.flatten 189 | end 190 | string_set.uniq! 191 | @cache[pattern.to_s] = Pattern.new(min_length, max_length, symbol_type, required_data, excluded_data, data_provided, 192 | string_set, all_characters_set, unique) 193 | return @cache[pattern.to_s].clone 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /lib/string/pattern/validate.rb: -------------------------------------------------------------------------------- 1 | class StringPattern 2 | ############################################## 3 | # This method is defined to validate if the text_to_validate supplied follows the pattern 4 | # It works also with array of patterns but in that case will return only true or false 5 | # input: 6 | # text (String) (synonyms: text_to_validate, validate) -- The text to validate 7 | # pattern -- symbol with this info: "length:symbol_type" or "min_length-max_length:symbol_type" 8 | # min_length -- minimum length of the string 9 | # max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided 10 | # symbol_type -- the type of the string we want. 11 | # expected_errors (Array of symbols) (optional) (synonyms: errors) -- :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed 12 | # not_expected_errors (Array of symbols) (optional) (synonyms: not_errors, non_expected_errors) -- :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed 13 | # example: 14 | # validate(text: "This text will be validated", pattern: :"10-20:Xn", expected_errors: [:value, :max_length]) 15 | # 16 | # Output: 17 | # if expected_errors and not_expected_errors are not supplied: an array with all detected errors 18 | # if expected_errors or not_expected_errors supplied: true or false 19 | # if array of patterns supplied, it will return true or false 20 | ############################################### 21 | def StringPattern.validate(text: "", pattern: "", expected_errors: [], not_expected_errors: [], **synonyms) 22 | text_to_validate = text 23 | text_to_validate = synonyms[:text_to_validate] if synonyms.keys.include?(:text_to_validate) 24 | text_to_validate = synonyms[:validate] if synonyms.keys.include?(:validate) 25 | expected_errors = synonyms[:errors] if synonyms.keys.include?(:errors) 26 | not_expected_errors = synonyms[:not_errors] if synonyms.keys.include?(:not_errors) 27 | not_expected_errors = synonyms[:non_expected_errors] if synonyms.keys.include?(:non_expected_errors) 28 | #:length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed 29 | if (expected_errors.include?(:min_length) or expected_errors.include?(:max_length)) and !expected_errors.include?(:length) 30 | expected_errors.push(:length) 31 | end 32 | if (not_expected_errors.include?(:min_length) or not_expected_errors.include?(:max_length)) and !not_expected_errors.include?(:length) 33 | not_expected_errors.push(:length) 34 | end 35 | if pattern.kind_of?(Array) and pattern.size == 1 36 | pattern = pattern[0] 37 | elsif pattern.kind_of?(Array) and pattern.size > 1 38 | total_min_length = 0 39 | total_max_length = 0 40 | all_errors_collected = Array.new 41 | result = true 42 | num_patt = 0 43 | patterns = Array.new 44 | pattern.each { |pat| 45 | if (pat.kind_of?(String) and (!StringPattern.optimistic or 46 | (StringPattern.optimistic and pat.to_s.scan(/(\d+)-(\d+):(.+)/).size == 0 and pat.to_s.scan(/^!?(\d+):(.+)/).size == 0))) #fixed text 47 | symbol_type = "" 48 | min_length = max_length = pat.length 49 | elsif pat.kind_of?(Symbol) or (pat.kind_of?(String) and StringPattern.optimistic and 50 | (pat.to_s.scan(/(\d+)-(\d+):(.+)/).size > 0 or pat.to_s.scan(/^!?(\d+):(.+)/).size > 0)) 51 | #patt = Marshal.load(Marshal.dump(StringPattern.analyze(pat))) #deep copy 52 | patt = StringPattern.analyze(pat).clone 53 | min_length = patt.min_length.clone 54 | max_length = patt.max_length.clone 55 | symbol_type = patt.symbol_type.clone 56 | else 57 | puts "String pattern class not supported (#{pat.class} for #{pat})" 58 | return false 59 | end 60 | 61 | patterns.push({ pattern: pat, min_length: min_length, max_length: max_length, symbol_type: symbol_type }) 62 | 63 | total_min_length += min_length 64 | total_max_length += max_length 65 | 66 | if num_patt == (pattern.size - 1) # i am in the last one 67 | if text_to_validate.length < total_min_length 68 | all_errors_collected.push(:length) 69 | all_errors_collected.push(:min_length) 70 | end 71 | 72 | if text_to_validate.length > total_max_length 73 | all_errors_collected.push(:length) 74 | all_errors_collected.push(:max_length) 75 | end 76 | end 77 | num_patt += 1 78 | } 79 | 80 | num_patt = 0 81 | patterns.each { |patt| 82 | tmp_result = false 83 | (patt[:min_length]..patt[:max_length]).each { |n| 84 | res = StringPattern.validate(text: text_to_validate[0..n - 1], pattern: patt[:pattern], not_expected_errors: not_expected_errors) 85 | if res.kind_of?(Array) 86 | all_errors_collected += res 87 | end 88 | 89 | if res.kind_of?(TrueClass) or (res.kind_of?(Array) and res.size == 0) #valid 90 | #we pass in the next one the rest of the pattern array list: pattern: pattern[num_patt+1..pattern.size] 91 | res = StringPattern.validate(text: text_to_validate[n..text_to_validate.length], pattern: pattern[num_patt + 1..pattern.size], expected_errors: expected_errors, not_expected_errors: not_expected_errors) 92 | 93 | if res.kind_of?(Array) 94 | if ((all_errors_collected + res) - expected_errors).size > 0 95 | tmp_result = false 96 | else 97 | all_errors_collected += res 98 | tmp_result = true 99 | end 100 | elsif res.kind_of?(TrueClass) 101 | tmp_result = true 102 | end 103 | return true if tmp_result 104 | end 105 | } 106 | 107 | unless tmp_result 108 | return false 109 | end 110 | num_patt += 1 111 | } 112 | return result 113 | end 114 | 115 | if (pattern.kind_of?(String) and (!StringPattern.optimistic or 116 | (StringPattern.optimistic and pattern.to_s.scan(/(\d+)-(\d+):(.+)/).size == 0 and pattern.to_s.scan(/^!?(\d+):(.+)/).size == 0))) #fixed text 117 | symbol_type = "" 118 | min_length = max_length = pattern.length 119 | else #symbol 120 | #patt = Marshal.load(Marshal.dump(StringPattern.analyze(pattern))) #deep copy 121 | patt = StringPattern.analyze(pattern).clone 122 | min_length = patt.min_length.clone 123 | max_length = patt.max_length.clone 124 | symbol_type = patt.symbol_type.clone 125 | 126 | required_data = patt.required_data.clone 127 | excluded_data = patt.excluded_data.clone 128 | string_set = patt.string_set.clone 129 | all_characters_set = patt.all_characters_set.clone 130 | 131 | required_chars = Array.new 132 | required_data.each { |rd| 133 | required_chars << rd if rd.size == 1 134 | } 135 | if (required_chars.flatten & excluded_data.flatten).size > 0 136 | puts "pattern argument not valid on StringPattern.validate, a character cannot be required and excluded at the same time: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 137 | return "" 138 | end 139 | end 140 | 141 | if text_to_validate.nil? 142 | return false 143 | end 144 | detected_errors = Array.new 145 | 146 | if text_to_validate.length < min_length 147 | detected_errors.push(:min_length) 148 | detected_errors.push(:length) 149 | end 150 | if text_to_validate.length > max_length 151 | detected_errors.push(:max_length) 152 | detected_errors.push(:length) 153 | end 154 | 155 | if symbol_type == "" #fixed text 156 | if pattern.to_s != text.to_s #not equal 157 | detected_errors.push(:value) 158 | detected_errors.push(:required_data) 159 | end 160 | else # pattern supplied 161 | if symbol_type != "@" 162 | if required_data.size > 0 163 | required_data.each { |rd| 164 | if (text_to_validate.chars & rd).size == 0 165 | detected_errors.push(:value) 166 | detected_errors.push(:required_data) 167 | break 168 | end 169 | } 170 | end 171 | if excluded_data.size > 0 172 | if (excluded_data.flatten & text_to_validate.chars).size > 0 173 | detected_errors.push(:value) 174 | detected_errors.push(:excluded_data) 175 | end 176 | end 177 | string_set_not_allowed = all_characters_set - string_set 178 | text_to_validate.chars.each { |st| 179 | if string_set_not_allowed.include?(st) 180 | detected_errors.push(:value) 181 | detected_errors.push(:string_set_not_allowed) 182 | break 183 | end 184 | } 185 | else #symbol_type=="@" 186 | string = text_to_validate 187 | wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@) 188 | if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings 189 | if string.index("@").to_i > 0 and 190 | string[0..(string.index("@") - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index("@") - 1)] and 191 | string[(string.index("@") + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index("@") + 1)..-1]] 192 | error_regular_expression = false 193 | else 194 | error_regular_expression = true 195 | end 196 | else 197 | error_regular_expression = true 198 | end 199 | 200 | if error_regular_expression 201 | detected_errors.push(:value) 202 | end 203 | end 204 | end 205 | 206 | if expected_errors.size == 0 and not_expected_errors.size == 0 207 | return detected_errors.uniq 208 | else 209 | if expected_errors & detected_errors == expected_errors 210 | if (not_expected_errors & detected_errors).size > 0 211 | return false 212 | else 213 | return true 214 | end 215 | else 216 | return false 217 | end 218 | end 219 | end 220 | end 221 | -------------------------------------------------------------------------------- /lib/string/pattern/add_to_ruby.rb: -------------------------------------------------------------------------------- 1 | class Array 2 | # It will generate an string following the pattern specified 3 | # The positions with string patterns need to be supplied like symbols: 4 | # [:"10:N", "fixed", :"10-20:XN/x/"].generate #> "1024320001fixed4OZjNMTnuBibwwj" 5 | def generate(expected_errors: [], **synonyms) 6 | StringPattern.generate(self, expected_errors: expected_errors, **synonyms) 7 | end 8 | 9 | alias gen generate 10 | 11 | # it will validate an string following the pattern specified 12 | def validate(string_to_validate, expected_errors: [], not_expected_errors: [], **synonyms) 13 | StringPattern.validate(text: string_to_validate, pattern: self, expected_errors: expected_errors, not_expected_errors: not_expected_errors, **synonyms) 14 | end 15 | 16 | alias val validate 17 | end 18 | 19 | class String 20 | # it will generate an string following the pattern specified 21 | def generate(expected_errors: [], **synonyms) 22 | StringPattern.generate(self, expected_errors: expected_errors, **synonyms) 23 | end 24 | 25 | alias gen generate 26 | 27 | # it will validate an string following the pattern specified 28 | def validate(string_to_validate, expected_errors: [], not_expected_errors: [], **synonyms) 29 | StringPattern.validate(text: string_to_validate, pattern: self, expected_errors: expected_errors, not_expected_errors: not_expected_errors, **synonyms) 30 | end 31 | 32 | alias val validate 33 | 34 | ######################################################## 35 | # Convert to CamelCase a string 36 | ######################################################## 37 | def to_camel_case 38 | return self if self !~ /_/ && self !~ /\s/ && self =~ /^[A-Z]+.*/ 39 | 40 | gsub(/[^a-zA-Z0-9ññÑáéíóúÁÉÍÓÚüÜ_]/, "_") 41 | .split("_").map(&:capitalize).join 42 | end 43 | 44 | ######################################################## 45 | # Convert to snake_case a string 46 | ######################################################## 47 | def to_snake_case 48 | gsub(/\W/, '_') 49 | .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') 50 | .gsub(/([a-z])([A-Z])/, '\1_\2') 51 | .downcase 52 | .gsub(/_+/, '_') 53 | end 54 | end 55 | 56 | class Symbol 57 | # it will generate an string following the pattern specified 58 | def generate(expected_errors: [], **synonyms) 59 | StringPattern.generate(self, expected_errors: expected_errors, **synonyms) 60 | end 61 | 62 | alias gen generate 63 | 64 | # it will validate an string following the pattern specified 65 | def validate(string_to_validate, expected_errors: [], not_expected_errors: [], **synonyms) 66 | StringPattern.validate(text: string_to_validate, pattern: to_s, expected_errors: expected_errors, not_expected_errors: not_expected_errors, **synonyms) 67 | end 68 | 69 | alias val validate 70 | end 71 | 72 | class Regexp 73 | 74 | # it will generate an string following the pattern specified 75 | def generate(expected_errors: [], **synonyms) 76 | StringPattern.generate(self, expected_errors: expected_errors, **synonyms) 77 | end 78 | 79 | alias gen generate 80 | 81 | # adds method to convert a Regexp to StringPattern 82 | # returns an array of string patterns or just one string pattern 83 | def to_sp 84 | regexp_s = self.to_s 85 | return StringPattern.cache[regexp_s] unless StringPattern.cache[regexp_s].nil? 86 | regexp = Regexp.new regexp_s 87 | require "regexp_parser" 88 | default_infinite = StringPattern.default_infinite 89 | pata = [] 90 | pats = "" 91 | patg = [] # for (aa|bb|cc) group 92 | set = false 93 | set_negate = false 94 | options = [] 95 | capture = false 96 | 97 | range = "" 98 | fixed_text = false 99 | options = regexp.to_s.scan(/\A\(\?([mix]*)\-[mix]*:/).join.split('') 100 | last_char = (regexp.to_s.gsub(/\A\(\?[mix]*\-[mix]*:/, "").length) - 2 101 | Regexp::Scanner.scan regexp do |type, token, text, ts, te| 102 | if type == :escape 103 | if token == :dot 104 | token = :literal 105 | text = "." 106 | elsif token == :literal and text.size == 2 107 | text = text[1] 108 | else 109 | puts "Report token not controlled: type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]" 110 | end 111 | end 112 | 113 | unless set || (token == :interval) || (token == :zero_or_one) || 114 | (token == :zero_or_more) || (token == :one_or_more) || (pats == "") 115 | if (pats[0] == "[") && (pats[-1] == "]") 116 | pats[0] = "" 117 | if (token == :alternation) || !patg.empty? 118 | if fixed_text 119 | if patg.size == 0 120 | patg << (pata.pop + pats.chop) 121 | else 122 | patg[-1] += pats.chop 123 | end 124 | else 125 | patg << pats.chop 126 | end 127 | else 128 | if fixed_text 129 | pata[-1] += pats.chop 130 | else 131 | if pats.size == 2 132 | pata << pats.chop 133 | else 134 | pata << "1:[#{pats}" 135 | end 136 | if last_char == te and type == :literal and token == :literal 137 | pata << text 138 | pats = "" 139 | next 140 | end 141 | end 142 | end 143 | else 144 | if (token == :alternation) || !patg.empty? 145 | patg << "1:#{pats}" 146 | else 147 | pata << "1:#{pats}" 148 | end 149 | end 150 | pats = "" 151 | end 152 | fixed_text = false 153 | case token 154 | when :open 155 | set = true 156 | pats += "[" 157 | when :close 158 | if type == :set 159 | set = false 160 | if pats[-1] == "[" 161 | pats.chop! 162 | else 163 | if set_negate 164 | pats+="%]*" 165 | set_negate = false 166 | else 167 | pats += "]" 168 | end 169 | 170 | end 171 | elsif type == :group 172 | capture = false 173 | unless patg.empty? 174 | patg << pats if pats.to_s != "" 175 | pata << patg 176 | patg = [] 177 | pats = "" 178 | end 179 | end 180 | when :negate 181 | if set and pats[-1] == '[' 182 | pats+="%" 183 | set_negate = true 184 | end 185 | when :capture 186 | capture = true if type == :group 187 | when :alternation 188 | if type == :meta 189 | if pats != "" 190 | patg << pats 191 | pats = "" 192 | elsif patg.empty? 193 | # for the case the first element was not added to patg and was on pata fex: (a+|b|c) 194 | patg << pata.pop 195 | end 196 | end 197 | when :range 198 | pats.chop! if options.include?('i') 199 | range = pats[-1] 200 | pats.chop! 201 | when :digit 202 | pats += "n" 203 | when :nondigit 204 | pats += "*[%0123456789%]" 205 | when :space 206 | pats += "_" 207 | when :nonspace 208 | pats += "*[% %]" 209 | when :word 210 | pats += "Ln_" 211 | when :nonword 212 | pats += "$" 213 | when :word_boundary 214 | pats += "$" 215 | when :dot 216 | pats += "*" 217 | when :literal 218 | if range == "" 219 | if text.size > 1 220 | fixed_text = true 221 | if !patg.empty? 222 | patg << text.chop 223 | else 224 | pata << text.chop 225 | end 226 | pats = text[-1] 227 | else 228 | pats += text 229 | pats += text.upcase if options.include?('i') 230 | end 231 | else 232 | range = range + "-" + text 233 | if range == "a-z" 234 | if options.include?('i') 235 | pats = "L" + pats 236 | else 237 | pats = "x" + pats 238 | end 239 | elsif range == "A-Z" 240 | if options.include?('i') 241 | pats = "L" + pats 242 | else 243 | pats = "X" + pats 244 | end 245 | elsif range == "0-9" 246 | pats = "n" + pats 247 | else 248 | if set 249 | pats += (range[0]..range[2]).to_a.join 250 | if options.include?('i') 251 | pats += (range[0]..range[2]).to_a.join.upcase 252 | end 253 | else 254 | trange = (range[0]..range[2]).to_a.join 255 | if options.include?('i') 256 | trange += trange.upcase 257 | end 258 | pats += "[" + trange + "]" 259 | end 260 | end 261 | range = "" 262 | end 263 | pats = "[" + pats + "]" unless set 264 | when :interval 265 | size = text.sub(",", "-").sub("{", "").sub("}", "") 266 | size+=(default_infinite+size.chop.to_i).to_s if size[-1] == "-" 267 | pats = size + ":" + pats 268 | if !patg.empty? 269 | patg << pats 270 | else 271 | pata << pats 272 | end 273 | pats = "" 274 | when :zero_or_one 275 | pats = "0-1:" + pats 276 | if !patg.empty? 277 | patg << pats 278 | else 279 | pata << pats 280 | end 281 | pats = "" 282 | when :zero_or_more 283 | pats = "0-#{default_infinite}:" + pats 284 | if !patg.empty? 285 | patg << pats 286 | else 287 | pata << pats 288 | end 289 | pats = "" 290 | when :one_or_more 291 | pats = "1-#{default_infinite}:" + pats 292 | if !patg.empty? 293 | patg << pats 294 | else 295 | pata << pats 296 | end 297 | pats = "" 298 | end 299 | end 300 | if pats != "" 301 | if pata.empty? 302 | if pats[0] == "[" and pats[-1] == "]" #fex: /[12ab]/ 303 | pata = ["1:#{pats}"] 304 | end 305 | else 306 | pata[-1] += pats[1] #fex: /allo/ 307 | end 308 | end 309 | if pata.size == 1 and pata[0].kind_of?(String) 310 | res = pata[0] 311 | else 312 | res = pata 313 | end 314 | StringPattern.cache[regexp_s] = res 315 | return res 316 | end 317 | end 318 | 319 | module Kernel 320 | public 321 | 322 | # if string or symbol supplied it will generate a string with the supplied pattern specified on the string 323 | # if array supplied then it will generate a string with the supplied patterns. If a position contains a pattern supply it as symbol, for example: [:"10:N", "fixed", :"10-20:XN/x/"] 324 | def generate(pattern, expected_errors: [], **synonyms) 325 | if pattern.is_a?(String) || pattern.is_a?(Array) || pattern.is_a?(Symbol) || pattern.is_a?(Regexp) 326 | StringPattern.generate(pattern, expected_errors: expected_errors, **synonyms) 327 | else 328 | puts " Kernel generate method: class not recognized:#{pattern.class}" 329 | end 330 | end 331 | 332 | alias gen generate 333 | end 334 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StringPattern 2 | 3 | [![Gem Version](https://badge.fury.io/rb/string_pattern.svg)](https://rubygems.org/gems/string_pattern) 4 | [![Build Status](https://travis-ci.com/MarioRuiz/string_pattern.svg?branch=master)](https://github.com/MarioRuiz/string_pattern) 5 | [![Coverage Status](https://coveralls.io/repos/github/MarioRuiz/string_pattern/badge.svg?branch=master)](https://coveralls.io/github/MarioRuiz/string_pattern?branch=master) 6 | ![Gem](https://img.shields.io/gem/dt/string_pattern) 7 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/y/MarioRuiz/string_pattern) 8 | ![GitHub last commit](https://img.shields.io/github/last-commit/MarioRuiz/string_pattern) 9 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/MarioRuiz/string_pattern) 10 | 11 | 12 | With this gem, you can easily generate strings supplying a very simple pattern. Even generate random words in English or Spanish. 13 | Also, you can validate if a text fulfills a specific pattern or even generate a string following a pattern and returning the wrong length, value... for testing your applications. Perfect to be used in test data factories. 14 | 15 | Also you can use regular expressions (Regexp) to generate strings: `/[a-z0-9]{2,5}\w+/.gen` 16 | 17 | To do even more take a look at [nice_hash gem](https://github.com/MarioRuiz/nice_hash) 18 | 19 | ## Installation 20 | 21 | Add this line to your application's Gemfile: 22 | 23 | ```ruby 24 | gem 'string_pattern' 25 | ``` 26 | 27 | And then execute: 28 | 29 | $ bundle 30 | 31 | Or install it yourself as: 32 | 33 | $ gem install string_pattern 34 | 35 | ## Usage 36 | 37 | ### What is a string pattern? 38 | 39 | A pattern is a string where we supply these elements "a-b:c" where a is min_length, b is max_length (optional) and c is a set of symbol_type 40 | 41 | min_length: minimum length of the string 42 | 43 | max_length (optional): maximum length of the string. If not provided, the result will be with the min_length provided 44 | 45 | symbol_type: The type of the string we want. 46 | x: from a to z (lowercase) 47 | X: A to Z (capital letters) 48 | L: A to Z and a to z 49 | T: National characters defined on StringPattern.national_chars 50 | n or N: for numbers. 0 to 9 51 | $: special characters, $%&#... (includes blank space) 52 | _: blank space 53 | *: all characters 54 | 0: empty string will be accepted. It needs to be at the beginning of the symbol_type string 55 | @: It will generate a valid email following the official algorithm. It cannot be used with other symbol_type 56 | W: for English words, capital and lower. It cannot be used with other symbol_type 57 | w: for English words only lower and words separated by underscore. It cannot be used with other symbol_type 58 | P: for Spanish words, capital and lower. It cannot be used with other symbol_type 59 | p: for Spanish words only lower and words separated by underscore. It cannot be used with other symbol_type 60 | 61 | ### How to generate a string following a pattern 62 | 63 | To generate a string following a pattern you can do it using directly the StringPattern class or the generate method in the class, be aware you can always use also the alias method: gen 64 | 65 | ```ruby 66 | require 'string_pattern' 67 | 68 | #StringPattern class 69 | p StringPattern.generate "10:N" 70 | #>3448910834 71 | p StringPattern.gen "5:X" 72 | #>JDDDK 73 | 74 | #String class 75 | p "4:Nx".gen 76 | #>xaa3 77 | 78 | #Symbol class 79 | p :"10:T".generate 80 | #>AccBdjklñD 81 | 82 | #Array class 83 | p [:"3:N", "fixed", :"3:N"].gen 84 | #>334fixed920 85 | p "(,3:N,) ,3:N,-,2:N,-,2:N".split(',').generate 86 | #>(937) 980-65-05 87 | 88 | #Kernel 89 | p gen "3:N" 90 | #>443 91 | ``` 92 | 93 | #### Generating unique strings 94 | 95 | If you want to generate for example 1000 strings and be sure all those strings are different you can use: 96 | 97 | ```ruby 98 | StringPattern.dont_repeat = true #default: false 99 | 1000.times { 100 | puts :"6-20:L/N/".gen 101 | } 102 | StringPattern.cache_values = Hash.new() #to clean the generated values from memory 103 | ``` 104 | 105 | Using dont_repeat all the generated string during the current run will be unique. 106 | 107 | In case you just want one particular string to be unique but not the rest then add to the pattern just in the end the symbol: & 108 | 109 | The pattern needs to be a symbol object. 110 | 111 | ```ruby 112 | 1000.times { 113 | puts :"6-20:L/N/&".gen #will be unique 114 | puts :"10:N".gen 115 | } 116 | ``` 117 | 118 | #### Generate words randomly in English or Spanish 119 | 120 | To generate a string of the length you want that will include only real words, use the symbol types: 121 | * W: generates English words following CamelCase ('ExampleOutput') 122 | * w: generates English words following snake_case ('example_output') 123 | * P: generates Spanish words following CamelCase ('EjemploSalida') 124 | * p: generates Spanish words following snake_case ('ejemplo_salida') 125 | 126 | ```ruby 127 | require 'string_pattern' 128 | 129 | puts '10-30:W'.gen 130 | #> FirstLieutenant 131 | puts '10-30:w'.gen 132 | #> paris_university 133 | puts '10-30:P'.gen 134 | #> SillaMetalizada 135 | puts '10-30:p'.gen 136 | #> despacho_grande 137 | ``` 138 | 139 | If you want to use a different word separator than "_" when using 'w' or 'p': 140 | 141 | ```ruby 142 | # blank space for example 143 | require 'string_pattern' 144 | 145 | StringPattern.word_separator = ' ' 146 | 147 | puts '10-30:w'.gen 148 | #> paris university 149 | puts '10-30:p'.gen 150 | #> despacho grande 151 | ``` 152 | 153 | The word list is loaded on the first request to generate words, after that the speed to generate words increases amazingly. 85000 English words and 250000 Spanish words. The vocabularies are a sample of public open sources. 154 | 155 | #### Generate strings using Regular Expressions (Regexp) 156 | 157 | Take in consideration this feature is not supporting all possibilities for Regular expressions but it is fully functional. If you find any bug or limitation please add it to issues: https://github.com/MarioRuiz/string_pattern/issues 158 | 159 | In case you want to change the default maximum for repetitions when using * or +: `StringPattern.default_infinite = 30` . By default is 10. 160 | 161 | If you want to translate a regular expression into an StringPattern use the method we added to Regexp class: `to_sp` 162 | 163 | Examples: 164 | 165 | ```ruby 166 | /[a-z0-9]{2-5}\w+/.to_sp 167 | #> ["2-5:nx", "1-10:Ln_"] 168 | 169 | #regular expression for UUID v4 170 | /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/.to_sp 171 | #> ["8:n[ABCDEF]", "-", "4:n[ABCDEF]", "-4", "3:n[ABCDEF]", "-", "1:[89AB]", "3:n[ABCDEF]", "-", "12:n[ABCDEF]"] 172 | ``` 173 | 174 | If you want to generate a random string following the regular expression, you can do it like a normal string pattern: 175 | 176 | ```ruby 177 | 178 | regexp = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/ 179 | 180 | # using StringPattern class 181 | puts StringPattern.generate(regexp) 182 | 183 | # using Kernel 184 | puts generate(regexp) 185 | 186 | # using generate method added to Regexp class 187 | puts regexp.generate 188 | 189 | #using the alias 'gen' 190 | puts regexp.gen 191 | 192 | # output: 193 | #>7009574B-6F2F-436E-BB7A-EA5FDA6B4E47 194 | #>5FB1718F-108A-4F62-8170-33C43FD86B1D 195 | #>05745B6F-93BA-475F-8118-DD56E5EAC4D1 196 | #>2D6FC189-8D50-45A8-B182-780193838502 197 | 198 | ``` 199 | 200 | ### String patterns 201 | 202 | #### How to generate one or another string 203 | 204 | In case you need to specify that the string is generated selecting one or another fixed string or pattern, you can do it by using Array of patterns and in the position you want you can add an array with the possible values 205 | 206 | ```ruby 207 | p ["uno:", :"5:N", ['.red','.green', :'3:L'] ].gen 208 | 209 | # first position a fixed string: "uno:" 210 | # second position 5 random numbers 211 | # third position one of these values: '.red', '.green' or 3 letters 212 | 213 | # example output: 214 | # 'uno:34322.red' 215 | # 'uno:44432.green' 216 | # 'uno:34322.red' 217 | # 'uno:28795xAB' 218 | 219 | ``` 220 | 221 | Take in consideration that this is only available to generate successful strings but not for validation 222 | 223 | #### Custom characters 224 | 225 | Also, it's possible to provide the characters we want. To do that we'll use the symbol_type [characters] 226 | 227 | If we want to add the character ] we have to write ]] 228 | 229 | Examples 230 | 231 | ```ruby 232 | # four chars from the ones provided: asDF9 233 | p "4:[asDF9]".gen #> aaaa, asFF, 9sFD 234 | 235 | # from 2 to 20 chars, capital and lower chars (Xx) and also valid the characters $#6 236 | p "2-20:[$#6]Xx".gen #> aaaa, asFF, 66, B$DkKL#9aDD 237 | 238 | # four chars from these: asDF]9 239 | p "4:[asDF]]9]".gen #> aa]a, asFF, 9s]D 240 | ``` 241 | 242 | #### Required characters or symbol types 243 | 244 | We'll use the symbol / to specify which characters or symbols we want to be included on the resulting string as required values /symbols or characters/ 245 | 246 | If we need to add the character / we'll use // 247 | 248 | Examples: 249 | 250 | ```ruby 251 | # four characters. optional: capitals and numbers, required: lower 252 | "4:XN/x/".gen # aaaa, FF9b, j4em, asdf, ADFt 253 | 254 | # from 6 to 15 chars. optional: numbers, capitals and the chars $ and Æ. required the chars: 23abCD 255 | "6-15:[/23abCD/$Æ]NX".gen # bCa$D32, 32DJIOKLaCb, b23aD568C 256 | 257 | # from 4 to 9 chars. optional: numbers and capitals. required: lowers and the characters $ and 5 258 | "4-9:[/$5/]XN/x/".generate # aa5$, F5$F9b, j$4em5, a5sdf$, $ADFt5 259 | ``` 260 | 261 | #### Excluded characters 262 | 263 | If we want to exclude a few characters in the result, we'll use the symbol %characters% 264 | 265 | If you need to exclude the character %, you should use %% 266 | 267 | Examples: 268 | 269 | ```ruby 270 | # from 2 to 20 characters. optional: Numbers and characters A, B and C. excluded: the characters 8 and 3 271 | "2-20:[%83%ABC]N".gen # B49, 22900, 9CAB, 22, 11CB6270C26C4572A50C 272 | 273 | # 10 chars. optional: Letters (capital and lower). required: numbers. excluded: the characters 0 and WXYzZ 274 | "10:L/n/[%0WXYzZ%]".gen # GoO2ukCt4l, Q1Je2remFL, qPg1T92T2H, 4445556781 275 | ``` 276 | 277 | #### Not fulfilling a pattern 278 | 279 | If we want our resulting string doesn't fulfill the pattern we supply, then we'll use the symbol ! at the beginning 280 | 281 | Examples: 282 | 283 | ```ruby 284 | "!4:XN/x/".gen # a$aaa, FF9B, j4DDDem, as, 2345 285 | 286 | "!10:N".gen # 123, 34899Add34, 3434234234234008, AAFj#kd2x 287 | ``` 288 | 289 | ### Generate a string with specific expected errors 290 | 291 | Usually, for testing purposes you need to generate strings that don't fulfill a specific pattern, then you can supply as a parameter expected_errors (alias: errors) 292 | 293 | The possible values you can specify is one or more of these ones: :length, :min_length, :max_length, :value, :required_data, :excluded_data, :string_set_not_allowed 294 | 295 | :length: wrong length, minimum or maximum 296 | :min_length: wrong minimum length 297 | :max_length: wrong maximum length 298 | :value: wrong resultant value 299 | :required_data: the output string won't include all necessary required data. It works only if required data supplied on the pattern. 300 | :excluded_data: the resultant string will include one or more characters that should be excluded. It works only if excluded data supplied on the pattern. 301 | :string_set_not_allowed: it will include one or more characters that are not supposed to be on the string. 302 | 303 | Examples: 304 | 305 | ```ruby 306 | "10-20:N".gen errors: [:min_length] 307 | #> 627, 098262, 3408 308 | 309 | "20:N".gen errors: [:length, :value] 310 | #> |13, tS1b)r-1) 1hwIw;v{KQ, mpk*l]!7:!, wocipgZt8@ 314 | 315 | ``` 316 | 317 | ### Validate if a string is following a pattern 318 | 319 | If you need to validate if a specific text is fulfilling the pattern you can use the validate method. 320 | 321 | If a string pattern supplied and no other parameters supplied the output will be an array with the errors detected. 322 | 323 | 324 | Possible output values, empty array (validation without errors detected) or one or more of: :min_length, :max_length, :length, :value, :string_set_not_allowed, :required_data, :excluded_data 325 | 326 | In case an array of patterns supplied it will return only true or false 327 | 328 | Examples: 329 | 330 | ```ruby 331 | #StringPattern class 332 | StringPattern.validate((text: "This text will be validated", pattern: :"10-20:Xn") 333 | #> [:max_length, :length, :value, :string_set_not_allowed] 334 | 335 | #String class 336 | "10:N".validate "333444" 337 | #> [:min_length, :length] 338 | 339 | #Symbol class 340 | :"10:N".validate("333444") 341 | #> [:min_length, :length] 342 | 343 | #Array class 344 | ["5:L","3:xn","4-10:n"].validate "DjkljFFc343444390" 345 | #> false 346 | ``` 347 | 348 | If we want to validate a string with a pattern and we are expecting to get specific errors, you can supply the parameter expected_errors (alias: errors) or not_expected_errors (aliases: non_expected_errors, not_errors). 349 | 350 | In this case, the validate method will return true or false. 351 | 352 | Examples: 353 | 354 | ```ruby 355 | "10:N".val "3445", errors: [:min_length] 356 | #> true 357 | 358 | "10:N/[09]/".validate "4434039440", errors: [:value] 359 | #> false 360 | 361 | "10-12:XN/x/".validate "FDDDDDAA343434", errors: [:max_length, :required_data] 362 | #> true 363 | ``` 364 | 365 | ### Configure 366 | 367 | #### SP_ADD_TO_RUBY 368 | 369 | This gem adds the methods generate (alias: gen) and validate (alias: val) to the Ruby classes: String, Array, and Symbol. 370 | 371 | Also adds the method generate (alias: gen) to Kernel. By default (true) it is always added. 372 | 373 | In case you don't want to be added, just before requiring the library set: 374 | 375 | ```ruby 376 | SP_ADD_TO_RUBY = false 377 | require 'string_pattern' 378 | ``` 379 | 380 | In case it is set to true (default) then you will be able to use: 381 | 382 | ```ruby 383 | require 'string_pattern' 384 | 385 | #String object 386 | "20-30:@".gen 387 | #>dkj34MljjJD-df@jfdluul.dfu 388 | 389 | "10:L/N/[/-./%d%]".validate("12ds6f--.s") 390 | #>[:value, :string_set_not_allowed] 391 | 392 | "20-40:@".validate(my_email) 393 | 394 | #Kernel 395 | gen "10:N" 396 | #>3433409877 397 | 398 | #Array object 399 | "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate 400 | #>(937) 980-65-05 401 | 402 | %w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen 403 | #>(045) 448-63-09 404 | 405 | ["1:L", "5-10:LN", "-", "3:N"].gen 406 | #>zqWihV-746 407 | ``` 408 | 409 | #### national_chars 410 | 411 | To specify which national characters will be used when using the symbol type: T, you use StringPattern.national_chars, by default is the English alphabet 412 | 413 | ```ruby 414 | StringPattern.national_chars = (('a'..'z').to_a + ('A'..'Z').to_a).join + "áéíóúÁÉÍÓÚüÜñÑ" 415 | "10-20:Tn".gen #>AAñ34Ef99éNOP 416 | ``` 417 | 418 | #### optimistic 419 | 420 | If true it will check on the strings of the array positions supplied if they have the pattern format and assume in that case that is a pattern. If not it will assume the patterns on the array will be supplied as symbols. By default is set to true. 421 | 422 | ```ruby 423 | StringPattern.optimistic = false 424 | ["5:X","fixedtext", "3:N"].generate 425 | #>5:Xfixedtext3:N 426 | [:"5:X","fixedtext", :"3:N"].generate 427 | #>AUJKJfixedtext454 428 | 429 | StringPattern.optimistic = true 430 | ["5:X","fixedtext", "3:N"].generate 431 | #>KKDMEfixedtext344 432 | [:"5:X","fixedtext", :"3:N"].generate 433 | #>SAAERfixedtext988 434 | ``` 435 | 436 | #### block_list 437 | 438 | To specify which words will be avoided from the results 439 | 440 | ```ruby 441 | StringPattern.block_list = ['example', 'wrong', 'ugly'] 442 | StringPattern.block_list_enabled = true 443 | "2-20:Tn".gen #>AAñ34Ef99éNOP 444 | ``` 445 | 446 | 447 | ## Contributing 448 | 449 | Bug reports and pull requests are welcome on GitHub at https://github.com/marioruiz/string_pattern. 450 | 451 | 452 | ## License 453 | 454 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 455 | 456 | -------------------------------------------------------------------------------- /spec/string/pattern/generate_spec.rb: -------------------------------------------------------------------------------- 1 | require "string_pattern" 2 | 3 | RSpec.describe StringPattern, "#generate" do 4 | describe "pattern" do 5 | describe "general" do 6 | it "pattern with wrong type" do 7 | expect(StringPattern.generate(5)).to eq "5" 8 | expect { StringPattern.generate(5) }.to output(/pattern argument not valid on StringPattern.generate/).to_stdout 9 | end 10 | end 11 | describe "length" do 12 | it "generates with fix length" do 13 | expect("10:L".gen.size).to eq 10 14 | end 15 | it "generates with fix length min and max" do 16 | expect("10-10:L".gen.size).to eq 10 17 | end 18 | it "generates with length between min and max" do 19 | expect("1-10:L".gen.size).to be_between(1, 10) 20 | end 21 | it "display error if non existing min_length" do 22 | #expect(':N'.gen).to eq '' 23 | expect { ':N'.gen}.to output(/pattern argument not valid on StringPattern.generate/).to_stdout 24 | end 25 | end 26 | 27 | describe "lower" do 28 | it "generates correct pattern" do 29 | expect("10:x".gen).to match(/^[a-z]{10}$/) 30 | end 31 | end 32 | 33 | describe "capital" do 34 | it "generates correct pattern" do 35 | expect("10:X".gen).to match(/^[A-Z]{10}$/) 36 | end 37 | end 38 | 39 | describe "letters" do 40 | it "generates correct pattern" do 41 | expect("10:L".gen).to match(/^[A-Za-z]{10}$/) 42 | end 43 | end 44 | 45 | describe "national_chars" do 46 | before(:all) do 47 | @national_chars = StringPattern.national_chars 48 | end 49 | 50 | after(:each) do 51 | StringPattern.national_chars = @national_chars 52 | end 53 | 54 | it "generates correct pattern when default national_chars" do 55 | expect("10:T".gen).to match(/^[a-zA-Z]{10}$/) 56 | end 57 | it "generates correct pattern when modified national_chars" do 58 | StringPattern.national_chars = "a" 59 | expect("10:T".gen).to match(/^[a]{10}$/) 60 | end 61 | end 62 | 63 | describe "number" do 64 | it "generates correct pattern" do 65 | expect("10:N".gen).to match(/^[0-9]{10}$/) 66 | expect("10:n".gen).to match(/^[0-9]{10}$/) 67 | end 68 | end 69 | 70 | describe "special characters" do 71 | it "generates correct pattern" do 72 | special_set = class StringPattern; SPECIAL_SET.join("\\"); end 73 | expect("10:$".gen).to match(/^[#{special_set}]{10}$/) 74 | end 75 | end 76 | 77 | describe "blank space" do 78 | it "generates correct pattern" do 79 | expect("10:_".gen).to match(/^[\s]{10}$/) 80 | end 81 | end 82 | 83 | describe "all characters" do 84 | it "generates correct pattern" do 85 | special_set = class StringPattern; SPECIAL_SET.join("\\"); end 86 | expect("10:*".gen).to match(/^[a-zA-Z0-9#{special_set}]{10}$/) 87 | end 88 | end 89 | 90 | describe "specific characters" do 91 | it "generates correct pattern" do 92 | expect("10:[b]".gen).to match(/^[b]{10}$/) 93 | end 94 | it "generates correct pattern when adding ]" do 95 | expect("10:[]]".gen).to match(/^[\]]{10}$/) 96 | end 97 | it "generates correct pattern when adding %" do 98 | expect("10:[%%]".gen).to match(/^[%]{10}$/) 99 | end 100 | it 'generates correct pattern when adding "' do 101 | expect("10:[\"]".gen).to match(/^[\"]{10}$/) 102 | end 103 | it 'generates correct pattern when adding \\' do 104 | expect("10:[\\]".gen).to match(/^[\\]{10}$/) 105 | end 106 | it "generates correct pattern when adding [" do 107 | expect("10:[\[]".gen).to match(/^[\[]{10}$/) 108 | end 109 | end 110 | 111 | describe "exclude characters" do 112 | it "generates correct pattern when specific and excluded" do 113 | expect("10:[ab%b%]".gen).to match(/^[a]{10}$/) 114 | end 115 | it "generates correct pattern when symbol and excluded" do 116 | expect("10:n[%012345678%]".gen).to match(/^[9]{10}$/) 117 | end 118 | it "generates correct pattern when adding %" do 119 | expect("10:[ab%b%%%]".gen).to match(/^[a]{10}$/) 120 | end 121 | end 122 | 123 | describe "required characters or symbols" do 124 | it "generates correct pattern when mandatory characters" do 125 | expect("10:n[/0/]".gen).to include("0") 126 | end 127 | it "generates correct pattern when mandatory character / added" do 128 | expect("10:n[/0///]".gen).to include("/") 129 | end 130 | it "generates correct pattern when mandatory symbol" do 131 | expect("10:L/N/".gen).to match(/[0-9]+/) 132 | end 133 | it "cannot require and exclude the same character" do 134 | expect("5:[b/a/%a%]".gen).to eq "" 135 | expect { "5:[b/a/%a%]".gen }.to output(/a character cannot be required and excluded/).to_stdout 136 | end 137 | end 138 | 139 | describe "allow empty string" do 140 | before(:all) do 141 | StringPattern.cache_values = {} 142 | end 143 | it "returns empty string" do 144 | values = [] 145 | #it needs to be a symbol :"xxxxx" 146 | 11.times do values << :"1:0N&".gen end 147 | expect(values).to include("") 148 | end 149 | end 150 | 151 | describe "unique strings" do 152 | before(:each) do 153 | StringPattern.cache_values = {} 154 | end 155 | after(:each) do 156 | StringPattern.dont_repeat = false 157 | end 158 | it "returns unique strings" do 159 | values = [] 160 | #it needs to be a symbol :"xxxxx" 161 | 10.times do values << :"1:N&".gen end 162 | values.uniq! 163 | expect(values.size).to eq(10) 164 | end 165 | it "returns empty string if not possible to generate" do 166 | values = [] 167 | #it needs to be a symbol :"xxxxx" 168 | 11.times do values << :"1:N&".gen end 169 | expect(values[-1]).to eq("") 170 | end 171 | it "is possible to use StringPattern.dont_repeat = true" do 172 | StringPattern.dont_repeat = true 173 | values = [] 174 | 10.times do values << "1:N".gen end 175 | values.uniq! 176 | expect(values.size).to eq(10) 177 | end 178 | it "is possible to use StringPattern.dont_repeat = false" do 179 | StringPattern.dont_repeat = false 180 | values = [] 181 | 15.times do values << "1:N".gen end 182 | values.delete("") 183 | expect(values.size).to eq(15) 184 | end 185 | it "display error when no more combinations allowed" do 186 | StringPattern.dont_repeat = true 187 | 10.times do '1:N'.gen end 188 | expect('1:N'.gen).to eq '' 189 | expect { '1:N'.gen}.to output(/Take in consideration if you are using StringPattern.dont_repeat=true/).to_stdout 190 | end 191 | end 192 | 193 | describe "email" do 194 | it "generates correct email" do 195 | expect("30:@".gen).to match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i) 196 | expect("30:@".gen.size).to eq(30) 197 | expect("30-40:@".gen.size).to be_between(30,40) 198 | end 199 | it "display error when not possible to generate email" do 200 | expect('3:@'.gen).to eq '' 201 | expect { '3:@'.gen}.to output(/Not possible to generate an email on StringPattern.generate/).to_stdout 202 | end 203 | it 'denies email pattern' do 204 | value = "30:!@".gen 205 | if value.size == 30 206 | expect(value).not_to match(/(\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z)/i) 207 | else 208 | expect(value.size).not_to eq 30 209 | end 210 | end 211 | it 'returns wrong email when expected_errors :value' do 212 | expect("30:@".gen(errors: :value)).not_to match(/(\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z)/i) 213 | end 214 | it 'returns wrong length when expected_errors :length' do 215 | expect("30:@".gen(errors: :length).size).not_to be 30 216 | end 217 | it 'returns wrong length when expected_errors :min_length' do 218 | expect("30:@".gen(errors: :min_length).size).to be < 30 219 | end 220 | it 'returns wrong length when expected_errors :max_length' do 221 | expect("30:@".gen(errors: :max_length).size).to be > 30 222 | end 223 | it 'returns wrong email when expected_errors :required_data' do 224 | expect("30:@".gen(errors: :required_data)).not_to match(/(\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z)/i) 225 | end 226 | it 'returns wrong email when expected_errors :string_set_not_allowed' do 227 | expect("30:@".gen(errors: :string_set_not_allowed)).not_to match(/(\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z)/i) 228 | end 229 | end 230 | 231 | describe "expected errors" do 232 | it "returns string with wrong length" do 233 | expect("30:L".gen(errors: :length).size).not_to be(30) 234 | end 235 | it "returns string with wrong min_length" do 236 | expect("30:L".gen(errors: :min_length).size).to be < 30 237 | end 238 | it "returns string with wrong max_length" do 239 | expect("30:L".gen(errors: :max_length).size).to be > 30 240 | end 241 | it "returns string with wrong value" do 242 | expect("30:L".gen(errors: :value)).not_to match(/^[a-zA-Z]+$/) 243 | end 244 | it "returns string with wrong required_data" do 245 | expect("30:L/n/".gen(errors: :required_data)).not_to match(/[0-9]/) 246 | end 247 | it "returns correct string with excluded_data" do 248 | expect("30:n[%5%]".gen(errors: :excluded_data)).to include("5") 249 | end 250 | it "returns correct string with string_set_not_allowed" do 251 | expect("30:n".gen(errors: :string_set_not_allowed)).not_to match(/^[0-9]+$/) 252 | end 253 | it "returns string not following the pattern" do 254 | expect("!30:n".gen).not_to match(/^[0-9]{30}$/) 255 | expect("30:!n".gen).not_to match(/^[0-9]{30}$/) 256 | end 257 | it 'allows 0 even when not following the pattern' do 258 | expect("30:!0n".gen).not_to match(/^([0-9]{30})?$/) 259 | end 260 | it "accepts alias expected errors" do 261 | expect("30:L".gen(expected_errors: :length).size).not_to be(30) 262 | end 263 | it "accepts alias errors" do 264 | expect("30:L".gen(errors: :length).size).not_to be(30) 265 | end 266 | it "accepts array of expected errors" do 267 | expect("30:L".gen(expected_errors: [:length, :value]).size).not_to be(30) 268 | expect("30:L".gen(expected_errors: [:length, :value])).not_to match(/^[a-zA-Z]+$/) 269 | end 270 | it "display error when expected error is :required_data but not :required_data on pattern" do 271 | expect('10:N'.gen(errors: :required_data)).to eq '' 272 | expect { '10:N'.gen(errors: :required_data) }.to output(/required data not supplied on pattern so it won't be possible to generate a wrong string/).to_stdout 273 | end 274 | it "display error when expected error is :excluded_data but not :excluded_data on pattern" do 275 | expect('10:N'.gen(errors: :excluded_data)).to eq '' 276 | expect { '10:N'.gen(errors: :excluded_data) }.to output(/excluded data not supplied on pattern so it won't be possible to generate a wrong string/).to_stdout 277 | end 278 | it "display error when expected error is :string_set_not_allowed but not :string_set_not_allowed on pattern" do 279 | expect('10:*'.gen(errors: :string_set_not_allowed)).to eq '' 280 | expect { '10:*'.gen(errors: :string_set_not_allowed) }.to output(/all characters are allowed so it won't be possible to generate a wrong string/).to_stdout 281 | end 282 | it "display error when expected error is :value but all chars accepted on pattern" do 283 | expect('10:*'.gen(errors: :value)).to eq '' 284 | expect { '10:*'.gen(errors: :value) }.to output(/Not possible to generate a non valid string on StringPattern.generate/).to_stdout 285 | end 286 | it "display error when expected error is :min_length but the min_length of the pattern is 0" do 287 | expect('0-2:N'.gen(errors: :min_length)).to eq '' 288 | expect { '0-2:N:*'.gen(errors: :min_length) }.to output(/min_length is 0 so it won't be possible to generate a wrong string smaller than 0 characters/).to_stdout 289 | end 290 | 291 | end 292 | 293 | describe "array of patterns" do 294 | after(:all) do 295 | StringPattern.optimistic = true 296 | end 297 | it "returns correct string" do 298 | pattern = ["uno:", :"5:N", "dos"] 299 | expect(pattern.gen).to match(/^uno:[0-9]{5}dos$/) 300 | end 301 | it "returns correct string with selection values" do 302 | pattern = ["uno:", :"5:N", ["dos", "tres", :'3:X']] 303 | expect(pattern.gen).to match(/^uno:[0-9]{5}(dos|tres|[A-Z]{3})$/) 304 | end 305 | it "accepts optimistic false" do 306 | StringPattern.optimistic = false 307 | expect(["5:X", "fixedtext", "3:N"].generate).to eq "5:Xfixedtext3:N" 308 | end 309 | it "returns random string when optimistic false and symbols supplied" do 310 | StringPattern.optimistic = false 311 | expect([:"5:X", "fixedtext", :"3:N"].generate).to match(/[A-Z]{5}fixedtext[0-9]{3}/) 312 | end 313 | it "accepts optimistic true" do 314 | StringPattern.optimistic = true 315 | expect(["5:X", "fixedtext", "3:N"].generate).to match(/[A-Z]{5}fixedtext[0-9]{3}/) 316 | end 317 | it "detects wrong array of patterns" do 318 | expect([:"5:X", 33].gen).to eq "" 319 | expect { [:"5:X", 33].gen }.to output(/StringPattern.generate: it seems you supplied wrong array of patterns/).to_stdout 320 | end 321 | it 'accepts :symbols not being patterns' do 322 | expect([:"5:N", :'ejemplo'].gen).to match(/\d{5}ejemplo/) 323 | end 324 | end 325 | end 326 | 327 | describe "words" do 328 | after(:each) do 329 | StringPattern.word_separator = "_" 330 | end 331 | describe "english" do 332 | it "generates capital and lower" do 333 | expect("30:W".gen.size).to eq(30) 334 | expect("30:W".gen).to match(/^([A-Z]+[a-z]*)+$/) 335 | end 336 | it "words only lower and words separated by underscore" do 337 | expect("30:w".gen.size).to eq(30) 338 | expect("30:w".gen).to match(/^[a-z_]+$/) 339 | end 340 | it "words only lower and words separated by character specified" do 341 | StringPattern.word_separator = "-" 342 | expect("30:w".gen.size).to eq(30) 343 | expect("30:w".gen).to match(/^[a-z\-]+$/) 344 | end 345 | end 346 | describe "spanish" do 347 | it "generates capital and lower" do 348 | expect("30:P".gen.size).to eq(30) 349 | expect("30:P".gen).to match(/^([A-ZÁÉÍÓÚÜÑ]+[a-záéíóúüñ]*)+$/) 350 | end 351 | it "words only lower and words separated by underscore" do 352 | expect("30:p".gen.size).to eq(30) 353 | expect("30:p".gen).to match(/^[a-z_áéíóúüñ]+$/) 354 | end 355 | it "words only lower and words separated by character specified" do 356 | StringPattern.word_separator = "-" 357 | expect("30:p".gen.size).to eq(30) 358 | expect("30:p".gen).to match(/^[a-záéíóúüñ\-]+$/) 359 | end 360 | end 361 | end 362 | describe "regexp" do 363 | it "generates correct pattern for simple regexp" do 364 | expect(/b{10}/.gen).to match(/^[b]{10}$/) 365 | end 366 | it "generates correct pattern for more complex regexp" do 367 | regexp = /^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$/ 368 | expect(regexp.gen).to match(regexp) 369 | end 370 | it 'accepts [abc]' do 371 | regexp = /[abc]/ 372 | expect(regexp.gen.match?(regexp)).to eq true 373 | end 374 | it 'accepts [^abc]' do 375 | regexp = /[^abc]/ 376 | expect(regexp.gen.match?(regexp)).to eq true 377 | end 378 | it 'accepts [a-z]' do 379 | regexp = /[a-z]/ 380 | expect(regexp.gen.match?(regexp)).to eq true 381 | end 382 | it 'accepts [c-f]' do 383 | regexp = /[c-f]/ 384 | expect(regexp.gen.match?(regexp)).to eq true 385 | end 386 | it 'accepts [A-Z]' do 387 | regexp = /[A-Z]+/ 388 | expect(regexp.gen.match?(regexp)).to eq true 389 | end 390 | it 'accepts [C-F]' do 391 | regexp = /[C-F]+/ 392 | expect(regexp.gen.match?(regexp)).to eq true 393 | end 394 | it 'accepts [a-zA-Z]' do 395 | regexp = /[a-zA-Z]+/ 396 | expect(regexp.gen.match?(regexp)).to eq true 397 | end 398 | it 'accepts [0-9]' do 399 | regexp = /[0-9]+/ 400 | expect(regexp.gen.match?(regexp)).to eq true 401 | end 402 | it 'accepts [3-9]' do 403 | regexp = /[3-9]+/ 404 | expect(regexp.gen.match?(regexp)).to eq true 405 | end 406 | it 'accepts start and end of line' do 407 | regexp = /^[a-z]+$/ 408 | puts regexp.gen 409 | expect(regexp.gen.match?(regexp)).to eq true 410 | end 411 | it 'accepts .' do 412 | regexp = /.+/ 413 | expect(regexp.gen.match?(regexp)).to eq true 414 | end 415 | it 'accepts \s' do 416 | regexp = /\s+/ 417 | expect(regexp.gen.match?(regexp)).to eq true 418 | end 419 | it 'accepts \S' do 420 | regexp = /\S+/ 421 | expect(regexp.gen.match?(regexp)).to eq true 422 | end 423 | it 'accepts \d' do 424 | regexp = /\d+/ 425 | expect(regexp.gen.match?(regexp)).to eq true 426 | end 427 | it 'accepts \D' do 428 | regexp = /\D+/ 429 | expect(regexp.gen.match?(regexp)).to eq true 430 | end 431 | it 'accepts \w' do 432 | regexp = /\w+/ 433 | expect(regexp.gen.match?(regexp)).to eq true 434 | end 435 | it 'accepts \W' do 436 | regexp = /\W+/ 437 | expect(regexp.gen.match?(regexp)).to eq true 438 | end 439 | it 'accepts \b' do 440 | regexp = /\b+/ 441 | expect(regexp.gen.match?(/[^a-zA-Z0-9_]+/)).to eq true 442 | end 443 | it 'accepts (...)' do 444 | regexp = /(\w)+/ 445 | expect(regexp.gen.match?(regexp)).to eq true 446 | end 447 | it 'accepts (a|b)' do 448 | regexp = /(a|b)+/ 449 | expect(regexp.gen.match?(regexp)).to eq true 450 | end 451 | it 'accepts a?' do 452 | regexp = /a?b+/ 453 | expect(regexp.gen.match?(regexp)).to eq true 454 | end 455 | it 'accepts a*' do 456 | regexp = /a*b+/ 457 | expect(regexp.gen.match?(regexp)).to eq true 458 | end 459 | it 'accepts a+' do 460 | regexp = /a+/ 461 | expect(regexp.gen.match?(regexp)).to eq true 462 | end 463 | it 'accepts a{3}' do 464 | regexp = /a{3}/ 465 | expect(regexp.gen.match?(regexp)).to eq true 466 | end 467 | it 'accepts a{3,}' do 468 | regexp = /a{3}/ 469 | expect(regexp.gen.match?(regexp)).to eq true 470 | end 471 | it 'accepts a{3,6}' do 472 | regexp = /a{3,6}/ 473 | expect(regexp.gen.match?(regexp)).to eq true 474 | end 475 | it 'accepts fixed text' do 476 | regexp = /fixeda{3,6}/ 477 | expect(regexp.gen.match?(regexp)).to eq true 478 | end 479 | 480 | describe "block_list" do 481 | before(:all) do 482 | StringPattern.block_list = [] 483 | StringPattern.block_list_enabled = false 484 | end 485 | 486 | after(:each) do 487 | StringPattern.block_list = [] 488 | StringPattern.block_list_enabled = false 489 | end 490 | 491 | it "doesn't return any of block list" do 492 | StringPattern.block_list_enabled = true 493 | StringPattern.block_list = ('a'..'x').to_a 494 | 5.times do 495 | expect("2:L".gen).to match(/^[yz]{2}$/i) 496 | end 497 | end 498 | end 499 | 500 | 501 | end 502 | end 503 | -------------------------------------------------------------------------------- /lib/string/pattern/generate.rb: -------------------------------------------------------------------------------- 1 | class StringPattern 2 | ############################################### 3 | # Generate a random string based on the pattern supplied 4 | # (if SP_ADD_TO_RUBY==true, by default is true) To simplify its use it is part of the String, Array, Symbol and Kernel Ruby so can be easily used also like this: 5 | # "10-15:Ln/x/".generate #generate method on String class (alias: gen) 6 | # ['(', :'3:N', ')', :'6-8:N'].generate #generate method on Array class (alias: gen) 7 | # generate("10-15:Ln/x/") #generate Ruby Kernel method 8 | # generate(['(', :'3:N', ')', :'6-8:N']) #generate Ruby Kernel method 9 | # "(,3:N,) ,3:N,-,2:N,-,2:N".split(",").generate #>(937) #generate method on Array class (alias: gen) 10 | # %w{( 3:N ) 1:_ 3:N - 2:N - 2:N}.gen #generate method on Array class, using alias gen method 11 | # Input: 12 | # pattern: array or string of different patterns. A pattern is a string with this info: 13 | # "length:symbol_type" or "min_length-max_length:symbol_type" 14 | # In case an array supplied, the positions using a string pattern should be supplied as symbols if StringPattern.optimistic==false 15 | # 16 | # These are the possible string patterns you will be able to supply: 17 | # If at the beginning we supply the character ! the resulting string won't fulfill the pattern. This character need to be the first character of the pattern. 18 | # min_length -- minimum length of the string 19 | # max_length (optional) -- maximum length of the string. If not provided the result will be with the min_length provided 20 | # symbol_type -- the type of the string we want. 21 | # you can use a combination of any ot these: 22 | # x for alpha in lowercase 23 | # X for alpha in capital letters 24 | # L for all kind of alpha in capital and lower letters 25 | # T for the national characters defined on StringPattern.national_chars 26 | # n for number 27 | # $ for special characters (includes space) 28 | # _ for space 29 | # * all characters 30 | # [characters] the characters we want. If we want to add also the ] character you have to write: ]]. If we want to add also the % character you have to write: %% 31 | # %characters% the characters we don't want on the resulting string. %% to exclude the character % 32 | # /symbols or characters/ If we want these characters to be included on the resulting string. If we want to add also the / character you have to write: // 33 | # We can supply 0 to allow empty strings, this character need to be at the beginning 34 | # If you want to include the character " use \" 35 | # If you want to include the character \ use \\ 36 | # If you want to include the character [ use \[ 37 | # Other uses: 38 | # @ for email 39 | # W for English words, capital and lower 40 | # w for English words only lower and words separated by underscore 41 | # P for Spanish words, capital and lower 42 | # p for Spanish words only lower and words separated by underscore 43 | # Examples: 44 | # [:"6:X", :"3-8:_N"] 45 | # # it will return a string starting with 6 capital letters and then a string containing numbers and space from 3 to 8 characters, for example: "LDJKKD34 555" 46 | # [:"6-15:L_N", "fixed text", :"3:N"] 47 | # # it will return a string of 6-15 characters containing Letters-spaces-numbers, then the text: 'fixed text' and at the end a string of 3 characters containing numbers, for example: ["L_N",6,15],"fixed text",["N",3] "3 Am399 afixed text882" 48 | # "10-20:LN[=#]" 49 | # # it will return a string of 10-20 characters containing Letters and/or numbers and/or the characters = and #, for example: eiyweQFWeL#do4Vl 50 | # "30:TN_[#=]/x/" 51 | # # it will return a string of 30 characters containing national characters defined on StringPattern.national_chars and/or numbers and/or spaces and/or the characters # = and it is necessary the resultant string includes lower alpha chars. For example: HaEdQTzJ3=OtXMh1mAPqv7NCy=upLy 52 | # "10:N[%0%]" 53 | # # 10 characters length containing numbers and excluding the character 0, for example: 3523497757 54 | # "10:N[%0%/AB/]" 55 | # # 10 characters length containing numbers and excluding the character 0 and necessary to contain the characters A B, for example: 3AA4AA57BB 56 | # "!10:N[%0%/AB/]" 57 | # # it will generate a string that doesn't fulfill the pattern supplied, examples: 58 | # # a6oMQ4JK9g 59 | # # /Y 0 87 | string << StringPattern.generate(pat.to_s, expected_errors: expected_errors) 88 | else 89 | string << pat.to_s 90 | end 91 | elsif pat.kind_of?(String) 92 | if @optimistic and pat.to_s.scan(/^!?\d+-?\d*:.+/).size > 0 93 | string << StringPattern.generate(pat.to_s, expected_errors: expected_errors) 94 | else 95 | string << pat 96 | end 97 | else 98 | puts "StringPattern.generate: it seems you supplied wrong array of patterns: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 99 | return "" 100 | end 101 | } 102 | return string 103 | elsif pattern.kind_of?(String) or pattern.kind_of?(Symbol) 104 | patt = StringPattern.analyze(pattern).clone 105 | return "" unless patt.kind_of?(Struct) 106 | 107 | min_length = patt.min_length.clone 108 | max_length = patt.max_length.clone 109 | symbol_type = patt.symbol_type.clone 110 | 111 | required_data = patt.required_data.clone 112 | excluded_data = patt.excluded_data.clone 113 | string_set = patt.string_set.clone 114 | all_characters_set = patt.all_characters_set.clone 115 | 116 | required_chars = Array.new 117 | unless required_data.size == 0 118 | required_data.each { |rd| 119 | required_chars << rd if rd.size == 1 120 | } 121 | unless excluded_data.size == 0 122 | if (required_chars.flatten & excluded_data.flatten).size > 0 123 | puts "pattern argument not valid on StringPattern.generate, a character cannot be required and excluded at the same time: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 124 | return "" 125 | end 126 | end 127 | end 128 | 129 | string_set_not_allowed = Array.new 130 | elsif pattern.kind_of?(Regexp) 131 | return generate(pattern.to_sp, expected_errors: expected_errors) 132 | else 133 | puts "pattern argument not valid on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 134 | return pattern.to_s 135 | end 136 | 137 | allow_empty = false 138 | deny_pattern = false 139 | if symbol_type[0..0] == "!" 140 | deny_pattern = true 141 | possible_errors = [:length, :value, :string_set_not_allowed] 142 | (rand(possible_errors.size) + 1).times { 143 | expected_errors << possible_errors.sample 144 | } 145 | expected_errors.uniq! 146 | if symbol_type[1..1] == "0" 147 | allow_empty = true 148 | end 149 | elsif symbol_type[0..0] == "0" 150 | allow_empty = true 151 | end 152 | 153 | if expected_errors.include?(:min_length) or expected_errors.include?(:length) or 154 | expected_errors.include?(:max_length) 155 | allow_empty = !allow_empty 156 | elsif expected_errors.include?(:value) or 157 | expected_errors.include?(:excluded_data) or 158 | expected_errors.include?(:required_data) or 159 | expected_errors.include?(:string_set_not_allowed) and allow_empty 160 | allow_empty = false 161 | end 162 | 163 | length = min_length 164 | symbol_type_orig = symbol_type 165 | 166 | expected_errors_left = expected_errors.dup 167 | 168 | symbol_type = symbol_type_orig 169 | 170 | unless deny_pattern 171 | if required_data.size == 0 and expected_errors_left.include?(:required_data) 172 | puts "required data not supplied on pattern so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 173 | return "" 174 | end 175 | 176 | if excluded_data.size == 0 and expected_errors_left.include?(:excluded_data) 177 | puts "excluded data not supplied on pattern so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 178 | return "" 179 | end 180 | 181 | if expected_errors_left.include?(:string_set_not_allowed) 182 | string_set_not_allowed = all_characters_set - string_set 183 | 184 | if string_set_not_allowed.size == 0 185 | puts "all characters are allowed so it won't be possible to generate a wrong string. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 186 | return "" 187 | end 188 | end 189 | end 190 | 191 | if expected_errors_left.include?(:min_length) or 192 | expected_errors_left.include?(:max_length) or 193 | expected_errors_left.include?(:length) 194 | if expected_errors_left.include?(:min_length) or 195 | (min_length > 0 and expected_errors_left.include?(:length) and rand(2) == 0) 196 | if min_length > 0 197 | if allow_empty 198 | length = rand(min_length).to_i 199 | else 200 | length = rand(min_length - 1).to_i + 1 201 | end 202 | if required_data.size > length and required_data.size < min_length 203 | length = required_data.size 204 | end 205 | expected_errors_left.delete(:length) 206 | expected_errors_left.delete(:min_length) 207 | else 208 | puts "min_length is 0 so it won't be possible to generate a wrong string smaller than 0 characters. StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 209 | return "" 210 | end 211 | elsif expected_errors_left.include?(:max_length) or expected_errors_left.include?(:length) 212 | length = max_length + 1 + rand(max_length).to_i 213 | expected_errors_left.delete(:length) 214 | expected_errors_left.delete(:max_length) 215 | end 216 | else 217 | if allow_empty and rand(7) == 1 218 | length = 0 219 | else 220 | if max_length == min_length 221 | length = min_length 222 | else 223 | length = min_length + rand(max_length - min_length + 1) 224 | end 225 | end 226 | end 227 | 228 | if deny_pattern 229 | if required_data.size == 0 and expected_errors_left.include?(:required_data) 230 | expected_errors_left.delete(:required_data) 231 | end 232 | 233 | if excluded_data.size == 0 and expected_errors_left.include?(:excluded_data) 234 | expected_errors_left.delete(:excluded_data) 235 | end 236 | 237 | if expected_errors_left.include?(:string_set_not_allowed) 238 | string_set_not_allowed = all_characters_set - string_set 239 | if string_set_not_allowed.size == 0 240 | expected_errors_left.delete(:string_set_not_allowed) 241 | end 242 | end 243 | 244 | if symbol_type == "!@" and expected_errors_left.size == 0 and !expected_errors.include?(:length) and 245 | (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data)) 246 | expected_errors_left.push(:value) 247 | end 248 | end 249 | 250 | string = "" 251 | if symbol_type != "@" and symbol_type != "!@" and length != 0 and string_set.size != 0 252 | if string_set.size != 0 253 | 1.upto(length) { |i| 254 | string << string_set.sample.to_s 255 | } 256 | end 257 | if required_data.size > 0 258 | positions_to_set = (0..(string.size - 1)).to_a 259 | required_data.each { |rd| 260 | if (string.chars & rd).size > 0 261 | rd_to_set = (string.chars & rd).sample 262 | else 263 | rd_to_set = rd.sample 264 | end 265 | if ((0...string.length).find_all { |i| string[i, 1] == rd_to_set }).size == 0 266 | if positions_to_set.size == 0 267 | puts "pattern not valid on StringPattern.generate, not possible to generate a valid string: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 268 | return "" 269 | else 270 | k = positions_to_set.sample 271 | string[k] = rd_to_set 272 | positions_to_set.delete(k) 273 | end 274 | else 275 | k = ((0...string.length).find_all { |i| string[i, 1] == rd_to_set }).sample 276 | positions_to_set.delete(k) 277 | end 278 | } 279 | end 280 | excluded_data.each { |ed| 281 | if (string.chars & ed).size > 0 282 | (string.chars & ed).each { |s| 283 | string.gsub!(s, string_set.sample) 284 | } 285 | end 286 | } 287 | 288 | if expected_errors_left.include?(:value) 289 | string_set_not_allowed = all_characters_set - string_set if string_set_not_allowed.size == 0 290 | 291 | if string_set_not_allowed.size == 0 292 | puts "Not possible to generate a non valid string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 293 | return "" 294 | end 295 | (rand(string.size) + 1).times { 296 | string[rand(string.size)] = (all_characters_set - string_set).sample 297 | } 298 | expected_errors_left.delete(:value) 299 | end 300 | 301 | if expected_errors_left.include?(:required_data) and required_data.size > 0 302 | (rand(required_data.size) + 1).times { 303 | chars_to_remove = required_data.sample 304 | chars_to_remove.each { |char_to_remove| 305 | string.gsub!(char_to_remove, (string_set - chars_to_remove).sample) 306 | } 307 | } 308 | expected_errors_left.delete(:required_data) 309 | end 310 | 311 | if expected_errors_left.include?(:excluded_data) and excluded_data.size > 0 312 | (rand(string.size) + 1).times { 313 | string[rand(string.size)] = excluded_data.sample.sample 314 | } 315 | expected_errors_left.delete(:excluded_data) 316 | end 317 | 318 | if expected_errors_left.include?(:string_set_not_allowed) 319 | string_set_not_allowed = all_characters_set - string_set if string_set_not_allowed.size == 0 320 | if string_set_not_allowed.size > 0 321 | (rand(string.size) + 1).times { 322 | string[rand(string.size)] = string_set_not_allowed.sample 323 | } 324 | expected_errors_left.delete(:string_set_not_allowed) 325 | end 326 | end 327 | elsif (symbol_type == "W" or symbol_type == "P" or symbol_type == "w" or symbol_type == "p") and length > 0 328 | words = [] 329 | words_short = [] 330 | if symbol_type == "W" 331 | if @words_camel.empty? 332 | require "pathname" 333 | require "json" 334 | filename = File.join Pathname(File.dirname(__FILE__)), "../../../data", "english/nouns.json" 335 | nouns = JSON.parse(File.read(filename)) 336 | filename = File.join Pathname(File.dirname(__FILE__)), "../../../data", "english/adjs.json" 337 | adjs = JSON.parse(File.read(filename)) 338 | nouns = nouns.map(&:to_camel_case) 339 | adjs = adjs.map(&:to_camel_case) 340 | @words_camel = adjs + nouns 341 | @words_camel_short = @words_camel.sample(2000) 342 | end 343 | words = @words_camel 344 | words_short = @words_camel_short 345 | elsif symbol_type == "w" 346 | if @words.empty? 347 | require "pathname" 348 | require "json" 349 | filename = File.join Pathname(File.dirname(__FILE__)), "../../../data", "english/nouns.json" 350 | nouns = JSON.parse(File.read(filename)) 351 | filename = File.join Pathname(File.dirname(__FILE__)), "../../../data", "english/adjs.json" 352 | adjs = JSON.parse(File.read(filename)) 353 | @words = adjs + nouns 354 | @words_short = @words.sample(2000) 355 | end 356 | words = @words 357 | words_short = @words_short 358 | elsif symbol_type == "P" 359 | if @palabras_camel.empty? 360 | require "pathname" 361 | require "json" 362 | filename = File.join Pathname(File.dirname(__FILE__)), "../../../data", "spanish/palabras#{rand(12)}.json" 363 | palabras = JSON.parse(File.read(filename)) 364 | palabras = palabras.map(&:to_camel_case) 365 | @palabras_camel = palabras 366 | @palabras_camel_short = @palabras_camel.sample(2000) 367 | end 368 | words = @palabras_camel 369 | words_short = @palabras_camel_short 370 | elsif symbol_type == "p" 371 | if @palabras.empty? 372 | require "pathname" 373 | require "json" 374 | filename = File.join Pathname(File.dirname(__FILE__)), "../../../data", "spanish/palabras#{rand(12)}.json" 375 | palabras = JSON.parse(File.read(filename)) 376 | @palabras = palabras 377 | @palabras_short = @palabras.sample(2000) 378 | end 379 | words = @palabras 380 | words_short = @palabras_short 381 | end 382 | 383 | wordr = "" 384 | wordr_array = [] 385 | tries = 0 386 | while wordr.length < min_length 387 | tries += 1 388 | length = max_length - wordr.length 389 | if tries > 1000 390 | wordr += "A" * length 391 | break 392 | end 393 | if symbol_type == "w" or symbol_type == "p" 394 | length = length - 1 if wordr_array.size > 0 395 | res = (words_short.select { |word| word.length <= length && word.length != length - 1 && word.length != length - 2 && word.length != length - 3 }).sample.to_s 396 | unless res.to_s == "" 397 | wordr_array << res 398 | wordr = wordr_array.join(@word_separator) 399 | end 400 | else 401 | wordr += (words_short.select { |word| word.length <= length && word.length != length - 1 && word.length != length - 2 && word.length != length - 3 }).sample.to_s 402 | end 403 | if (tries % 100) == 0 404 | words_short = words.sample(2000) 405 | end 406 | end 407 | good_result = true 408 | string = wordr 409 | elsif (symbol_type == "@" or symbol_type == "!@") and length > 0 410 | if min_length > 6 and length < 6 411 | length = 6 412 | end 413 | if deny_pattern and 414 | (expected_errors.include?(:required_data) or expected_errors.include?(:excluded_data) or 415 | expected_errors.include?(:string_set_not_allowed)) 416 | expected_errors_left.push(:value) 417 | expected_errors.push(:value) 418 | expected_errors.uniq! 419 | expected_errors_left.uniq! 420 | end 421 | 422 | expected_errors_left_orig = expected_errors_left.dup 423 | tries = 0 424 | 425 | begin 426 | expected_errors_left = expected_errors_left_orig.dup 427 | tries += 1 428 | string = "" 429 | alpha_set = ALPHA_SET_LOWER.clone + ALPHA_SET_CAPITAL.clone 430 | string_set = alpha_set + NUMBER_SET.clone + ["."] + ["_"] + ["-"] 431 | string_set_not_allowed = all_characters_set - string_set 432 | 433 | extension = "." 434 | at_sign = "@" 435 | 436 | if expected_errors_left.include?(:value) 437 | if rand(2) == 1 438 | extension = (all_characters_set - ["."]).sample.dup 439 | expected_errors_left.delete(:value) 440 | expected_errors_left.delete(:required_data) 441 | end 442 | 443 | if rand(2) == 1 444 | 1.upto(rand(7)) { |i| 445 | extension << alpha_set.sample.downcase 446 | } 447 | 448 | (rand(extension.size) + 1).times { 449 | extension[rand(extension.size)] = (string_set - alpha_set - ["."]).sample 450 | } 451 | 452 | expected_errors_left.delete(:value) 453 | else 454 | 1.upto(rand(3) + 2) { |i| 455 | extension << alpha_set.sample.downcase 456 | } 457 | end 458 | 459 | if rand(2) == 1 460 | at_sign = (string_set - ["@"]).sample.dup 461 | expected_errors_left.delete(:value) 462 | expected_errors_left.delete(:required_data) 463 | end 464 | else 465 | if length > 6 466 | 1.upto(rand(3) + 2) { |i| 467 | extension << alpha_set.sample.downcase 468 | } 469 | else 470 | 1.upto(2) { |i| 471 | extension << alpha_set.sample.downcase 472 | } 473 | end 474 | end 475 | length_e = length - extension.size - 1 476 | length1 = rand(length_e - 1) + 1 477 | length2 = length_e - length1 478 | 1.upto(length1) { |i| string << string_set.sample } 479 | 480 | string << at_sign 481 | 482 | domain = "" 483 | domain_set = alpha_set + NUMBER_SET.clone + ["."] + ["-"] 484 | 1.upto(length2) { |i| 485 | domain << domain_set.sample.downcase 486 | } 487 | 488 | if expected_errors.include?(:value) and rand(2) == 1 and domain.size > 0 489 | (rand(domain.size) + 1).times { 490 | domain[rand(domain.size)] = (all_characters_set - domain_set).sample 491 | } 492 | expected_errors_left.delete(:value) 493 | end 494 | 495 | string << domain << extension 496 | 497 | if expected_errors_left.include?(:value) or expected_errors_left.include?(:string_set_not_allowed) 498 | (rand(string.size) + 1).times { 499 | string[rand(string.size)] = string_set_not_allowed.sample 500 | } 501 | expected_errors_left.delete(:value) 502 | expected_errors_left.delete(:string_set_not_allowed) 503 | end 504 | 505 | error_regular_expression = false 506 | 507 | if deny_pattern and expected_errors.include?(:length) 508 | good_result = true #it is already with wrong length 509 | else 510 | # I'm doing this because many times the regular expression checking hangs with these characters 511 | wrong = %w(.. __ -- ._ _. .- -. _- -_ @. @_ @- .@ _@ -@ @@) 512 | if !(Regexp.union(*wrong) === string) #don't include any or the wrong strings 513 | if string.index("@").to_i > 0 and 514 | string[0..(string.index("@") - 1)].scan(/([a-z0-9]+([\+\._\-][a-z0-9]|)*)/i).join == string[0..(string.index("@") - 1)] and 515 | string[(string.index("@") + 1)..-1].scan(/([0-9a-z]+([\.-][a-z0-9]|)*)/i).join == string[string[(string.index("@") + 1)..-1]] 516 | error_regular_expression = false 517 | else 518 | error_regular_expression = true 519 | end 520 | else 521 | error_regular_expression = true 522 | end 523 | 524 | if expected_errors.size == 0 525 | if error_regular_expression 526 | good_result = false 527 | else 528 | good_result = true 529 | end 530 | elsif expected_errors_left.size == 0 and 531 | (expected_errors - [:length, :min_length, :max_length]).size == 0 532 | good_result = true 533 | elsif expected_errors != [:length] 534 | if !error_regular_expression 535 | good_result = false 536 | elsif expected_errors.include?(:value) 537 | good_result = true 538 | end 539 | end 540 | end 541 | end until good_result or tries > 100 542 | unless good_result 543 | puts "Not possible to generate an email on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 544 | return "" 545 | end 546 | end 547 | if @dont_repeat 548 | if @cache_values[pattern.to_s].nil? 549 | @cache_values[pattern.to_s] = Array.new() 550 | @cache_values[pattern.to_s].push(string) 551 | good_result = true 552 | elsif @cache_values[pattern.to_s].include?(string) 553 | good_result = false 554 | else 555 | @cache_values[pattern.to_s].push(string) 556 | good_result = true 557 | end 558 | end 559 | if pattern.kind_of?(Symbol) and patt.unique 560 | if @cache_values[pattern.__id__].nil? 561 | @cache_values[pattern.__id__] = Array.new() 562 | @cache_values[pattern.__id__].push(string) 563 | good_result = true 564 | elsif @cache_values[pattern.__id__].include?(string) 565 | good_result = false 566 | else 567 | @cache_values[pattern.__id__].push(string) 568 | good_result = true 569 | end 570 | end 571 | if @block_list_enabled 572 | if @block_list.is_a?(Array) 573 | @block_list.each do |bl| 574 | if string.match?(/#{bl}/i) 575 | good_result = false 576 | break 577 | end 578 | end 579 | end 580 | end 581 | end until good_result or tries > 10000 582 | unless good_result 583 | puts "Not possible to generate the string on StringPattern.generate: #{pattern.inspect}, expected_errors: #{expected_errors.inspect}" 584 | puts "Take in consideration if you are using StringPattern.dont_repeat=true that you don't try to generate more strings that are possible to be generated" 585 | return "" 586 | end 587 | return string 588 | end 589 | end 590 | --------------------------------------------------------------------------------