michel-randexp 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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