michel-randexp 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,23 @@
1
+ == 0.1.5 "Michel de Graaf" 2010-01-16
2
+ * added anychar *
3
+
4
+ == 0.1.4 "Wally Wisoky" 2008-10-08
5
+ * Added realistic name generation (Matt Aimonetti)
6
+ * Fixed loadpath issues (Gerrit Kaiser)
7
+
8
+ == 0.1.3 "Oological" 2008-07-08
9
+ * Randgen.word should not return a string that does not match /^\w+$/
10
+
11
+ == 0.1.2 "I'm Not Saying It's Not Beta"
12
+ * Changed rand to Kernel#rand to avoid conflicting with rails (thanks agile!)
13
+
14
+ == 0.1.1 "Still Quite Beta" 2008-07-20
15
+ * Added Range#of method.
16
+ * Heavy refactoring of the Parser.parse method.
17
+ * Fixed the /\./ bug.
18
+
19
+ == 0.1.0 "Very Beta" 2008-07-08
20
+ * Initial version of randexp!
21
+ * Has support for very simple regular expressions.
22
+ * Randgen has limited methods.
23
+ * Dictionary is reading from the local words file.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Ben Burkert
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,82 @@
1
+ Randexp
2
+ by Ben Burkert
3
+ http://github.com/benburkert/randexp
4
+
5
+ == DESCRIPTION:
6
+
7
+ andexp makes it easy to generate random string from most regular expressions.
8
+
9
+ == REQUIREMENTS:
10
+
11
+ * none!
12
+
13
+ == INSTALL:
14
+
15
+ $ sudo gem install randexp
16
+
17
+ == USAGE:
18
+
19
+ Randexp adds the #generate (or #gen, for short) method to the Regexp class,
20
+ which generates a 'random' string that will match your regular expression.
21
+
22
+ /abc|def/.gen
23
+ # => "def"
24
+
25
+ == Valid Regexp's
26
+
27
+ Randexp can only generate matching string from simple regular expression.
28
+ Except for a few circumstances, wildcards are generally not allowed in the
29
+ regular expression. is pretty domain specific, so trying to guess when to
30
+ terminate a random pattern would produce unhelpful data:
31
+
32
+ >> /Aa{3}h*!/.gen
33
+ # => RuntimeError: Sorry, "h*" is too vague, try setting a range: "h{0,3}"
34
+ >> /Aa{3}h{3,15}!/.gen
35
+ => "Aaaahhhhh!"
36
+
37
+ >> /(never gonna (give you up|let you down), )*/.gen
38
+ => RuntimeError: Sorry, "(...)*" is too vague, try setting a range: "(...){0, 3}"
39
+ >> /(never gonna (give you up|let you down), ){3,5}/.gen
40
+ => "never gonna give you up, never gonna let you down, never gonna give you up, never gonna give you up, "
41
+
42
+ The exception being word characters (\w), which generate a random word from the Dictionary class.
43
+
44
+ >> /\w+/.gen
45
+ => "groveling"
46
+
47
+ = Primitives & Complex matches
48
+
49
+ The single character matchers supported are words(\w), whitespace(\s), and digits(\d).
50
+
51
+ >> /\d{50}/.gen
52
+ => "50315410741096763188525493528315906035878741451037"
53
+
54
+ When a multiplicity constraint is placed on a word character, a word with the valid length is generated.
55
+
56
+ >> /\w{10}/.gen # a word with 10 letters
57
+ => "Chaucerism"
58
+
59
+ >> /\w{5,15}/.gen
60
+ => "cabalistic"
61
+
62
+ Complex matchers use the [:...:] syntax within the regular expression.
63
+
64
+ >> /[:sentence:]/.gen
65
+ => "Nonhearer demetricize toppiece filicic possessedness rhodizite zoomagnetism earwigginess steady"
66
+
67
+ Complex matchers can also be added by extending the Randgen class.
68
+
69
+ class Randgen
70
+ def self.serial_number(options = {})
71
+ /XX\d{4}-\w-\d{5}/.gen
72
+ end
73
+ end
74
+
75
+ >> /[:serial_number:]/.gen
76
+ => "XX3770-M-33114"
77
+
78
+ = Dictionary
79
+
80
+ The Dictionary loads the local users' words file, allowing randomly generated words to be chosen from
81
+ thousands of entries to the words file. Words are mapped by their length to allow words to be randomly
82
+ chosen based on size.
data/Rakefile ADDED
@@ -0,0 +1,111 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require "spec/rake/spectask"
6
+ require 'rake/rdoctask'
7
+
8
+ PROJECT_NAME = "randexp"
9
+ GEM = "michel-randexp"
10
+ GEM_VERSION = "0.1.4"
11
+ AUTHOR = "Ben Burkert,Michel de Graaf"
12
+ EMAIL = "ben@benburkert.com, michel@re-invention.nl"
13
+ HOMEPAGE = "http://github.com/michel/randexp"
14
+ TITLE = "Randexp Gem"
15
+ SUMMARY = "Library for generating random strings."
16
+ FILES = %w(LICENSE README README Rakefile TODO CHANGELOG) + Dir.glob("{lib,spec}/**/*") + Dir.glob("wordlists/**/*")
17
+ RDOC_FILES = %w(LICENSE README README Rakefile TODO CHANGELOG) + Dir.glob("lib/**/*")
18
+
19
+ RUBYFORGE_USER = "benburkert"
20
+
21
+ spec = Gem::Specification.new do |s|
22
+ s.name = GEM
23
+ s.version = GEM_VERSION
24
+ s.platform = Gem::Platform::RUBY
25
+ s.has_rdoc = true
26
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
27
+ s.summary = SUMMARY
28
+ s.description = s.summary
29
+ s.author = AUTHOR
30
+ s.email = EMAIL
31
+ s.homepage = HOMEPAGE
32
+
33
+ s.require_path = 'lib'
34
+ s.autorequire = GEM
35
+ s.files = FILES
36
+ end
37
+
38
+ Rake::GemPackageTask.new(spec) do |package|
39
+ package.gem_spec = spec
40
+ package.need_zip = true
41
+ package.need_tar = true
42
+ end
43
+
44
+ desc "install the gem locally"
45
+ task :install => [:package] do
46
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
47
+ end
48
+
49
+ desc "create a gemspec file"
50
+ task :make_spec do
51
+ File.open("#{GEM}.gemspec", "w") do |file|
52
+ file.puts spec.to_ruby
53
+ end
54
+ end
55
+
56
+ ##############################################################################
57
+ # rSpec & rcov
58
+ ##############################################################################
59
+ desc "Run all unit specs"
60
+ Spec::Rake::SpecTask.new("specs:unit") do |t|
61
+ t.spec_opts = ["--format", "specdoc", "--colour"]
62
+ t.spec_files = Dir["spec/unit/**/*_spec.rb"].sort
63
+ t.rcov = true
64
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
65
+ t.rcov_opts << '--only-uncovered'
66
+ t.rcov_opts << '--output coverage/unit'
67
+
68
+ end
69
+
70
+ desc "Run all regression specs"
71
+ Spec::Rake::SpecTask.new("specs:regression") do |t|
72
+ t.spec_opts = ["--format", "specdoc", "--colour"]
73
+ t.spec_files = Dir["spec/regression/**/*_spec.rb"].sort
74
+ t.rcov = true
75
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
76
+ t.rcov_opts << '--only-uncovered'
77
+ t.rcov_opts << '--output coverage/integration'
78
+ end
79
+
80
+ task :specs => ['specs:unit', 'specs:regression']
81
+
82
+ ##############################################################################
83
+ # Documentation
84
+ ##############################################################################
85
+ task :doc => "doc:rerdoc"
86
+ namespace :doc do
87
+
88
+ Rake::RDocTask.new do |rdoc|
89
+ rdoc.rdoc_files.add(RDOC_FILES)
90
+ rdoc.main = 'README'
91
+ rdoc.title = TITLE
92
+ rdoc.rdoc_dir = "rdoc"
93
+ rdoc.options << '--line-numbers' << '--inline-source'
94
+ end
95
+
96
+ desc "rdoc to rubyforge"
97
+ task :rubyforge => :doc do
98
+ sh %{chmod -R 755 rdoc}
99
+ sh %{/usr/bin/scp -r -p rdoc/* #{RUBYFORGE_USER}@rubyforge.org:/var/www/gforge-projects/#{PROJECT_NAME}/#{GEM}}
100
+ end
101
+ end
102
+
103
+ ##############################################################################
104
+ # release
105
+ ##############################################################################
106
+ task :release => [:specs, :package, :doc] do
107
+ sh %{rubyforge add_release #{PROJECT_NAME} #{GEM} "#{GEM_VERSION}" pkg/#{GEM}-#{GEM_VERSION}.gem}
108
+ %w[zip tgz].each do |ext|
109
+ sh %{rubyforge add_file #{PROJECT_NAME} #{GEM} "#{GEM_VERSION}" pkg/#{GEM}-#{GEM_VERSION}.#{ext}}
110
+ end
111
+ end
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ == Todo list
2
+ * add a ~/.randexp dir for configuration
3
+ * add [] syntax: /[aeiou]{4}/.gen
4
+ * more generators for Randgen
data/lib/randexp.rb ADDED
@@ -0,0 +1,21 @@
1
+ class Randexp
2
+ attr_accessor :sexp
3
+
4
+ def initialize(source)
5
+ @sexp = Randexp::Parser[source]
6
+ end
7
+
8
+ def reduce
9
+ Reducer[@sexp.dup]
10
+ end
11
+ end
12
+
13
+ dir = File.dirname(__FILE__) + '/randexp'
14
+ require dir + '/core_ext'
15
+ require dir + '/dictionary'
16
+ require dir + '/parser'
17
+ require dir + '/randgen'
18
+ require dir + '/reducer'
19
+ require dir + '/wordlists/female_names'
20
+ require dir + '/wordlists/male_names'
21
+ require dir + '/wordlists/real_name'
@@ -0,0 +1,6 @@
1
+ dir = File.dirname(__FILE__)
2
+
3
+ require dir + '/core_ext/array'
4
+ require dir + '/core_ext/integer'
5
+ require dir + '/core_ext/range'
6
+ require dir + '/core_ext/regexp'
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def pick
3
+ at Kernel.rand(size)
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Integer
2
+ def of
3
+ (1..self).to_a.map { yield }
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class Range
2
+ def pick
3
+ to_a.pick
4
+ end
5
+
6
+ def of
7
+ pick.of { yield }
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ class Regexp
2
+ def generate
3
+ Randexp.new(source).reduce
4
+ end
5
+
6
+ alias_method :gen, :generate
7
+ end
@@ -0,0 +1,24 @@
1
+ class Randexp::Dictionary
2
+ def self.load_dictionary
3
+ if File.exists?("/usr/share/dict/words")
4
+ File.read("/usr/share/dict/words").split
5
+ elsif File.exists?("/usr/dict/words")
6
+ File.read("/usr/dict/words").split
7
+ else
8
+ raise "words file not found"
9
+ end
10
+ end
11
+
12
+ def self.words(options = {})
13
+ case
14
+ when options.has_key?(:length)
15
+ words_by_length[options[:length]]
16
+ else
17
+ @@words ||= load_dictionary
18
+ end
19
+ end
20
+
21
+ def self.words_by_length
22
+ @@words_by_length ||= words.inject({}) {|h, w| (h[w.size] ||= []) << w; h }
23
+ end
24
+ end
@@ -0,0 +1,99 @@
1
+ class Randexp
2
+ class Parser
3
+ def self.parse(source)
4
+ case
5
+ when source =~ /^(.*)(\*|\*\?|\+|\+\?|\?)$/ && balanced?($1, $2)
6
+ parse_quantified($1, $2.to_sym) # ends with *, +, or ?: /(..)?/
7
+ when source =~ /^(.*)\{(\d+)\,(\d+)\}$/ && balanced?($1, $2)
8
+ parse_quantified($1, ($2.to_i)..($3.to_i)) #ends with a range: /(..){..,..}/
9
+ when source =~ /^(.*)\{(\d+)\}$/ && balanced?($1, $2)
10
+ parse_quantified($1, $2.to_i) #ends with a range: /..(..){..}/
11
+ when source =~ /^\((.*)\)\((.*)\)$/ && balanced?($1, $2)
12
+ union(parse($1), parse($2)) #balanced union: /(..)(..)/
13
+ when source =~ /^(\(.*\))\|(\(.*\))$/ && balanced?($1, $2)
14
+ intersection(parse($1), parse($2)) #balanced intersection: /(..)|(..)/
15
+ when source =~ /^(.*)\|(.*)$/ && balanced?($1, $2)
16
+ intersection(parse($1), parse($2)) #implied intersection: /..|../
17
+ when source =~ /^(.*)\|\((\(.*\))\)$/ && balanced?($1, $2)
18
+ intersection(parse($1), parse($2)) #unbalanced intersection: /(..)|((...))/
19
+ when source =~ /^(.+)(\(.*\))$/ && balanced?($1, $2)
20
+ union(parse($1), parse($2)) #unbalanced union: /...(...)/
21
+ when source =~ /^\((.*)\)$/ && balanced?($1)
22
+ union(parse($1)) #explicit group: /(..)/
23
+ when source =~ /^([^()]*)(\(.*\))$/ && balanced?($1, $2)
24
+ union(parse($1), parse($2)) #implied group: /..(..)/
25
+ when source =~ /^(.*)\[\:(.*)\:\]$/
26
+ union(parse($1), random($2)) #custom random: /[:word:]/
27
+ when source =~ /(.*)\\(\.)$/ #-----
28
+ union(parse($1), literal($2)) # \.literal
29
+ when source =~ /^(.*)\\([wsdc])$/ || source =~ /(.*)(\.)$/
30
+ union(parse($1), random($2)) #reserved random: /..\w/ and .
31
+ when source =~ /^(.*)\\(.)$/ || source =~ /(.*)(.|\s)$/
32
+ union(parse($1), literal($2)) #end with literal or space: /... /
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def self.parse_quantified(source, multiplicity)
39
+ case source
40
+ when /^[^()]*$/ then quantify_rhs(parse(source), multiplicity) #implied union: /...+/
41
+ when /^(\(.*\))$/ then quantify(parse(source), multiplicity) #group: /(...)?/
42
+ when /^(.*\))$/ then quantify_rhs(parse(source), multiplicity) #implied union: /...(...)?/
43
+ when /^(.*[^)]+)$/ then quantify_rhs(parse(source), multiplicity) #implied union: /...(...)...?/
44
+ else quantify(parse(source), multiplicity)
45
+ end
46
+ end
47
+
48
+ class << self
49
+ alias_method :[], :parse
50
+ end
51
+
52
+ def self.balanced?(*args)
53
+ args.all? {|s| s.count('(') == s.count(')')}
54
+ end
55
+
56
+ def self.quantify_rhs(sexp, multiplicity)
57
+ case sexp.first
58
+ when :union
59
+ rhs = sexp.pop
60
+ sexp << quantify(rhs, multiplicity)
61
+ else
62
+ quantify(sexp, multiplicity)
63
+ end
64
+ end
65
+
66
+ def self.quantify(lhs, sym)
67
+ [:quantify, lhs, sym]
68
+ end
69
+
70
+ def self.union(lhs, *rhs)
71
+ if lhs.nil?
72
+ union(*rhs)
73
+ elsif rhs.empty?
74
+ lhs
75
+ elsif lhs.first == :union
76
+ rhs.each {|s| lhs << s}
77
+ lhs
78
+ else
79
+ [:union, lhs, *rhs]
80
+ end
81
+ end
82
+
83
+ def self.intersection(lhs, rhs)
84
+ if rhs.first == :intersection
85
+ [:intersection, lhs] + rhs[1..-1]
86
+ else
87
+ [:intersection, lhs, rhs]
88
+ end
89
+ end
90
+
91
+ def self.random(char)
92
+ [:random, char.to_sym]
93
+ end
94
+
95
+ def self.literal(word)
96
+ [:literal, word]
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,84 @@
1
+ require 'enumerator'
2
+ class Randgen
3
+ WORDS_PER_SENTENCE = 3..20
4
+ SENTENCES_PER_PARAGRAPH = 3..8
5
+
6
+ def self.bool(options = {})
7
+ ['true', 'false'].pick
8
+ end
9
+
10
+ def self.any(options = {})
11
+ length = options[:length] || 1
12
+ s = ""
13
+ length.enum_for(:times).inject(s) do |result, index|
14
+ s << rand(93) + 33
15
+ end
16
+ end
17
+
18
+ def self.lchar(options = {})
19
+ ('a'..'z').to_a.pick
20
+ end
21
+
22
+ def self.uchar(options = {})
23
+ ('A'..'Z').to_a.pick
24
+ end
25
+
26
+ def self.char(options = {})
27
+ [lchar, uchar].pick
28
+ end
29
+
30
+ def self.whitespace(options = {})
31
+ ["\t", "\n", "\r", "\f"].pick
32
+ end
33
+
34
+ def self.digit(options = {})
35
+ ('0'..'9').to_a.pick
36
+ end
37
+
38
+ def self.alpha_numeric(options = {})
39
+ [char, digit].pick
40
+ end
41
+
42
+ def self.word(options = {})
43
+ begin
44
+ word = Randexp::Dictionary.words(options).pick
45
+ rescue
46
+ word = ''
47
+ options[:length].times { |iterator| word += alpha_numeric }
48
+ end until word =~ /^\w+$/
49
+
50
+ word
51
+ end
52
+
53
+ def self.first_name(options = {})
54
+ RealName.first_names(options).pick
55
+ end
56
+
57
+ def self.surname(options = {})
58
+ RealName.surnames(options).pick
59
+ end
60
+
61
+ class << self
62
+ alias_method :last_name, :surname
63
+ end
64
+
65
+ def self.name(options = {})
66
+ "#{first_name(options)} #{surname(options)}"
67
+ end
68
+
69
+ def self.sentence(options = {})
70
+ ((options[:length] || WORDS_PER_SENTENCE.pick).of { word } * " ").capitalize
71
+ end
72
+
73
+ def self.paragraph(options = {})
74
+ ((options[:length] || SENTENCES_PER_PARAGRAPH.pick).of { sentence } * ". ") + "."
75
+ end
76
+
77
+ def self.phone_number(options = {})
78
+ case options[:length]
79
+ when 7 then /\d{3}-\d{4}/.gen
80
+ when 10 then /\d{3}-\d{3}-\d{4}/.gen
81
+ else /(\d{3}-)?\d{3}-\d{4}/.gen
82
+ end
83
+ end
84
+ end