A Ruby script I wrote quite some time back (that was before the RPN parser, even). Probably the most complex potentially useful thing I wrote in Ruby. The only potentially useful thing.
class StringGenerator # Parses an input file. If none given, it parses the DATA lines instead (see bottom of file). def initialize(filename) file, @patterns, @groups, @capitalize = (filename == nil ? DATA.readlines : IO::readlines(filename)), Array.new, Hash.new([""], true file.each do |line| @groups[line[/ \w /].strip] = eval("%w" + line[line.index(/ \w /) + 2, line.length - 1].strip) if line[/^group /] @patterns << line[7, line.length - 1].strip if line[/^pattern /] end end # Generates one entry. Is also used to evaluate subexpressions in forks. def generate(pattern, final = true) pattern = @patterns[rand(@patterns.size)] if pattern == nil result, iterator = "", 0 while iterator < pattern.length case pattern[iterator] when '-' # Does nothing. when '~' then @capitalize = false when '^' then @capitalize = true when '/' iterator += 1 (result += pattern[iterator]; iterator += 1; @capitalize = false) while pattern[iterator] != '/' when ':' then result, @capitalize = result + " ", true when '(' substring, iterator, depth = "", iterator + 1, (pattern[iterator + 1] == '(') ? (2) : (1) while depth > 0 iterator, substring = iterator + 1, substring + pattern[iterator] depth += 1 if pattern[iterator] == '(' depth -= 1 if pattern[iterator] == ')' end result += generate(random_expression(substring), false) else if @capitalize result, @capitalize = result + "#{@groups[pattern[iterator]][rand(@groups[pattern[iterator]].length)]}".split(' ').collect { |s| s.capitalize + " " }.join.strip, false else result += "#{@groups[pattern[iterator]][rand(@groups[pattern[iterator]].length)]}".downcase end end iterator += 1 end (@capitalize = true; result.strip!) if final return result end # Parses forks and picks one random subexpression from them. def random_expression(string) iterator, sub, depth, result = 0, "", 0, Array.new until iterator == string.length sub += string[iterator, 1] depth += 1 if string[iterator, 1] == '(' depth -= 1 if string[iterator, 1] == ')' iterator += 1 (result << sub; sub = ""; iterator += 1 unless string[iterator] == '(') if string[iterator] == '|' and depth == 0 end result << sub return result[rand(result.length)] end end
if ARGV.size == 0 puts "\nnamegen [-i path] [-o path] [-p pattern] [-t number]\n\n" puts "-i [path] \t Defines the path of the input file. For more information, use the -h switch. If none provided, the default (example) is used." puts "-o [path] \t Redirects the output from the console to the file with the given path." puts "-p [pattern] \t Use a specific pattern for generating. For information on pattern syntax, use the -h switch." puts "-t [number] \t Defines how many lines of output should be generated.\n\n" puts "-h \t\t Prints a full explanation on how to use the generator." elsif ARGV.include? "-h" puts DATA.readlines else gen, i, p, result = nil, 1, nil, "" gen = StringGenerator.new(ARGV.include?("-i") ? ARGV[ARGV.index("-i") + 1] : nil) i = ARGV[ARGV.index("-t") + 1].to_i if ARGV.include? "-t" p = ARGV[ARGV.index("-p") + 1] if ARGV.include? "-p" i.times { |i| result += gen.generate(p) + "\n" } if ARGV.include? "-o" File.open(ARGV[ARGV.index("-o") + 1], "w") { |file| file << result } else puts result end end
__END__
The generator works using patterns defined in an external file. Patterns describe how predefined groups of words / syllables / letters are used to form the final result.
In each file, there should be both group and pattern definitions. Scheme of a group definition: group i ( word1 word2 word3 a b c kek kor word4 qqqqq) [Note: From there on, the term "word" will be used for any chain of characters that forms one entry in one of the defined groups. So "word1" is a word, "a" is a word, and so is "kek" or "qqqqq".] Every group definition starts with the group keyword - in it's own line, and without any preceding whitespaces, tabs or whatever. Then comes the group identifier, a single, case-sensitive alphanumeric character, and after that a list of words enclosed by parentheses. Each group definition must occupy exactly one line. Usually each word seperated by a space is a new word, but if you want to add expressions with more than one word to the list (like the "very long" in the example group "a"), escape the space, ie. precede it with a backslash \. Some pre-defined example groups:
group a ( short normal long very\ long ) group b ( sword spear ) group c ( destruction doom death ) group d ( fiery frozen )
group e ( ka ne wi do ru so mos )
[Note: When you define multiple groups with the same ID, the one defined last will be used. This is just a side-effect of how the script works, not actual design, though.] Pattern definitions are somewhat more complex, but still not really hard. pattern a:b Each alphanumeric character in the pattern (in this case "a" and "b") is substituted with a random word from the corresponding group. This pattern may, for example, generate "Short Spear" using the predefined groups. The colon ":" is substituted with a single space. pattern ab [Note: This may generate the highly nonsensical "Very Longspear". But fantasy item names tend to be silly anyways, so who cares ] This pattern would therefore generate things like "Shortspear", without the space. In addition, you can also add constant words to patterns, string literals. They look like this: /word/. pattern b:/of/:c This could generate "Spear of Doom". Unlike after colons, words after string literals are NOT capitalized. Therefore pattern b:/of /c would generate "Spear of doom", not "Spear of Doom". Forks are the most complex (but still not hard to comprehend) feature in patterns. pattern (a|d):b (a|d) is a fork. It contains two expressions, "a" and "d". Pipes | seperate each expression. A fork may contain any amount of expression, but at least one (in which case it is pointless.... When a fork is encountered, a random expression is chosen, with an equal chance for each to be picked. The example may therefore evaluate to either pattern a:b or pattern d:b
Expressions in forks can be of any complexity, they even may contains other forks (which in turn may contain other forks, and so on). There is another symbol, specifically to be used with forks: the dash -. It means exactly nothing. It's purpose is to allow empty expressions in forks, as shown in the example. pattern b:(/of/:c|-) [Note: When multiple patterns are defined in a file, a random one is used.] The generated string are capitalized using very simple rules: 1. The first word and words after colons (:, ie. spaces) are capitalized. 2. All others are not.
You can modify the capitalization with two special operators: ~ Don't capitalize the next word regardless of rules. ^ Capitalize the next word regardless of rules. They have no influence on string literals, which are copied as-is.
Some pre-defined patterns: [Note: All the above patterns and the first example group do not count as defined, because they have tabs in front of them. Also, there may be no line breaks within groups or patterns.]
This pattern generates a name using the syllables in group e, with a length of 2-4 syllables. pattern ee(-|e|ee)
A simple item generating patern with variable length. pattern (a|d):b(:/of/:c|-) A rather complex pattern generating named weapons with an optional "of xxx" extension. It shows off pretty much all functionality of the script. pattern /"/^(dc|ee(-|e|ee))/", the/:cb:(/of/:(~d:|-)c|-) [Final Note: The generated results will be very monotonous because of the limited vocabulary. It only serves as an example. Try to extend it on your own in order to draw any actual use from it. Also, as you probably noticed, it's safe to write comments in input files. They somewhat slow down the script if there are a lot of lines, as in a few millions. You could also write them without the tab, but it's safer to use tabs, because then, if you accidentally start a line with group or pattern, nothing bad happens.]
Pattern Operator Cheat Sheet: : space / start or end string literal ( start a fork | start new expression in fork ) end fork - do nothing (used in forks) ^ capitalize next word ~ don't capitalize next word
-- BY NARVIUS
(yes, all that text at the end is part of the script. The file was originally called namegen.rb, hence th "namegen" given as command in -h. 80 lines of code, including comments + 130 lines of tutorial text)
Using my extremely overloaded input file...
Myohyohiri, the Dusty Duchess of Electrocution Red Trumpet of the Short Avatar Piercing Plush Backpack Hetahuyi, the Smart Douchebag of Ice Retarded Coding Stone Pan of Evolution Konuirya, the Turquoise Robot of Fatality "Meltingstones", the Pen of Wind Dancing Statue of Hats "Swearingwater", the Healthrifle of the Blinking Housewive's Piercing Ice Wahireryoeshu, the Short Bandit of Investigation Misty Microphone of Cussing Rust Stone Mittens of Revenge Snoring Statue of Failing Lightning Kyuhekyosi, the Powerful Fag of Destruction Sowo, the Wiggly Sphinx of Earth
No, they make no sense.
|