#=================================================================== -*-perl-*- # # Lingua::ManagementSpeak # # DESCRIPTION # # This tool generates grammatically correct, managerial-sounding text # that means absolutely nothing. It can output sentences, paragraphs, and # whole documents with embedded structure. The goal is to easily provide # filler or lorem ipsen content that has the potential to pass as real. # # AUTHOR # Gryphon Shafer # # COPYRIGHT # Copyright (C) 2005 by Gryphon Shafer # # This library is free software; you can redistribute it and/or modify it # under the same terms as Perl itself, either Perl version 5.8.4 or, at # your option, any later version of Perl 5 you may have available. # #============================================================================== package Lingua::ManagementSpeak; use 5.006; use strict; use warnings; our $VERSION = '0.01'; #------------------------------------------------------------------------------ # $self = new() # # Instantiation subroutine. Returns object that includes the data for all the # various word types. These data sets need to be updated and improved, and # eventually there should be an accessor mechanism to allow easy alteration # without bumping around in the internals. Also, the defaults for the other # functions should get centralized to parameters here. #------------------------------------------------------------------------------ sub new { my $self = shift; return bless({ pronouns => [qw(I we they)], articles => [qw(the your my our this its)], sub_conjuncs => [ 'after', 'although', 'as', 'as if', 'as long as', 'as though', 'because', 'before', 'even if', 'even though', 'if', 'if only', 'in order that', 'now that', 'once', 'rather than', 'since', 'so that', 'though', 'unless', 'until', 'when', 'whenever', 'where', 'whereas', 'wherever', 'while' ], power_words => [qw( accomplished dealt implemented projected achieved debated improved promoted acquired decided included proofed adjusted defined increased purchased administered delegated indicated qualified advised delivered initiated questioned analyzed demonstrated inspected rated applied designed instructed received appraised determined insured recognized arranged developed interpreted recommended assessed devised interviewed recorded assisted directed introduced recruited assured discovered investigated reduced awarded dispensed joined rehabilitated bought displayed kept related briefed distributed launched renovated brought earned led repaired budgeted edited located reported calculated educated maintained represented cataloged elected managed researched chaired encouraged maximized reviewed changed enlisted measured revised classified ensured mediated selected closed entertained modified served coached established motivated simplified combined evaluated named sketched communicated examined negotiated sold compared excelled observed solved completed executed obtained spearheaded computed exhibited operated specified conceived expanded ordered started concluded expedited organized streamlined conducted explained paid strengthened confronted facilitated participated studied constructed financed perceived suggested continued forecast performed summarized contracted formulated persuaded supervised controlled gained placed targeted convinced gathered planned taught coordinated graded predicted tested corrected greeted prepared trained corresponded guided presented translated counseled handled processed treated created helped produced updated critiqued identified programmed wrote )], verbs => [qw( aggregate architect benchmark brand cultivate deliver deploy disintermediate drive e-enable embrace empower enable engage engineer enhance envision evolve expedite exploit extend facilitate generate grow harness implement incentivize incubate innovate integrate iterate leverage maximize mesh monetize morph optimize orchestrate recontextualize reintermediate reinvent repurpose revolutionize scale seize strategize streamline syndicate synergize synthesize target transform transition unleash utilize visualize whiteboard )], aux_verbs => [ 'will', 'shall', 'may', 'might', 'can', 'could', 'must', 'ought to', 'should', 'would', 'need to' ], adjectives => [qw( 24/365 24/7 B2B B2C back-end best-of-breed bleeding-edge bricks-and-clicks clicks-and-mortar collaborative compelling cross-platform cross-media customized cutting-edge distributed dot-com dynamic e-business efficient end-to-end enterprise extensible frictionless front-end global granular holistic impactful innovative integrated interactive intuitive killer leading-edge magnetic mission-critical next-generation one-to-one open-source out-of-the-box plug-and-play proactive real-time revolutionary robust scalable seamless sexy sticky strategic synergistic transparent turn-key ubiquitous user-centric value-added vertical viral virtual visionary web-enabled wireless world-class )], nouns => [qw( action-items applications architectures bandwidth channels communities content convergence deliverables e-business e-commerce e-markets e-services e-tailers experiences eyeballs functionalities infomediaries infrastructures initiatives interfaces markets methodologies metrics mindshare models networks niches paradigms partnerships platforms portals relationships ROI synergies web-readiness schemas solutions supply-chains systems technologies users vortals )], conj_adverbs => [qw(however moreover nevertheless consequently)], conjuntors => [qw(though although notwithstanding yet still)] }, ref($self) || $self); } #------------------------------------------------------------------------------ # $number = _random($high_number, $low_number) # # After coding a bit, I noticed I was inconsistently using rand(), and it was # causing stupid effects. _random() is because I'm lazy and don't want to # think about how I'm generating random numbers. Pass in high and low # integers, and it returns a random whole number that's in that range. # If the integers are omitted, it picks a number between 5 and 1 inclusive. #------------------------------------------------------------------------------ sub _random { my $high = shift || 5; my $low = shift || 1; int( rand( $high - $low + 1 ) ) + $low; } #------------------------------------------------------------------------------ # $words = $self->words($meta) # # Using a text string of meta words, this returns a management-speak # block of text. It parses the meta string and converts each meta word into # a randomly picked associated real word. # # Currently supported meta words or blocks include: # pronoun article power_word verb aux_verb adjective noun conj_adverb # conjuntor sub_conjunc adverb to_be phrase maybe_n/n_word # # "to_be" looks at the preceding word and conjugates itself accordingly # "phrase" is a keyword for: "conjuntor article noun to_be power_word" # "maybe_n/n_word" will insert "word" every n/n times #------------------------------------------------------------------------------ sub words { my ($self, $meta) = @_; # Deal with "maybe_n/n_word" meta words while ($meta =~ /maybe[_-](\d+)\/(\d+)[_-](\w+)\S*/) { my $word = (_random($2, $1) == $1) ? $3 : ''; if ($word) { $meta =~ s/maybe[_-]\d+\/\d+[_-]\w+(\S*)/$word$1/; } else { $meta =~ s/maybe[_-]\d+\/\d+[_-]\w+\S*\s*//; } } # Convert "phrase" into phrase meta words $meta =~ s/(\w)\s+phrase/$1, phrase/g; $meta =~ s/phrase/conjuntor article noun to_be power_word/g; while ($meta =~ /( pronoun|article|sub_conjuc|power_word|verb|aux_verb| adjective|noun|conj_adverb|conjuntor|sub_conjunc|adverb )/x) { # If the word is an adverb, we have to pick a verb and add "ing" to it. # This is newbie-like code. Should get rewritten eventually. my ($t1, $t2) = ($1, $1); $t2 = 'verb' if ($t1 eq 'adverb'); my $word = $self->{$t2 . 's'}[ _random($#{$self->{$t2 . 's'}}, 0) ]; $word =~ s/[e]*$/ing/ if ($t1 eq 'adverb'); $meta =~ s/$t1/$word/; } # Convert "to_be" into the proper conjugated form while ($meta =~ /\b(\w+)\s+to_be/) { if ($1 =~ /ess$/) { $meta =~ s/to_be/is/; } elsif ($1 =~ /s$/) { $meta =~ s/to_be/are/; } else { $meta =~ s/to_be/is/; } } $meta =~ s/^\s+|\s+$//; $meta; } #------------------------------------------------------------------------------ # $sentence = $self->sentence(1 || 0) # # Returns a fully-formed sentence randomly selected from a set of patterns. # Accepts either true or false parameter. If true, the returned sentence is # assumed to be the lead sentence of a paragraph and will therefore not # contain a leading conjunctive adverb. The default is false, which means # there is a 1/4 change of a leading conjunctive adverb. # # In future versions, there should be a way to read and set the sentence # meta strings. #------------------------------------------------------------------------------ sub sentence { my ($self, $meta) = (shift, undef); my $is_first = shift || 0; my $type = _random(7, 1); if ($type == 1) { $meta = 'article noun to_be power_word sub_conjunc pronoun power_word ' . 'article maybe_1/2_adjective noun maybe_1/2_phrase'; } elsif ($type == 2) { $meta = 'sub_conjunc pronoun power_word article maybe_1/2_adjective ' . 'noun, article maybe_1/2_adjective noun power_word article ' . 'maybe_1/2_adjective noun maybe_1/3_phrase'; } elsif ($type == 3) { $meta = 'pronoun aux_verb verb article maybe_1/2_adjective noun ' . 'sub_conjunc article adjective noun aux_verb verb article ' . 'maybe_1/2_adjective noun maybe_1/3_phrase'; } elsif ($type == 4) { $meta = 'sub_conjunc pronoun verb article maybe_1/2_adjective noun, ' . 'pronoun can verb article ' . 'maybe_1/2_adjective noun maybe_1/3_phrase'; } elsif ($type == 5) { $meta = 'pronoun aux_verb verb article maybe_1/2_adjective noun ' . 'sub_conjunc pronoun verb article ' . 'maybe_1/2_adjective noun maybe_1/4_phrase'; } elsif ($type == 6) { $meta = 'article noun verbs adjective noun'; } elsif ($type == 7) { $meta = "article noun to_be a adjective noun sub_conjuncs, sub_conjuncs " . 'article noun verbs article noun'; } $meta = 'maybe_1/4_conj_adverb, ' . $meta if (not $is_first); return ucfirst($self->words($meta)) . '.'; } #------------------------------------------------------------------------------ # $paragraph = $self->paragraph($low, $high) # $paragraph = $self->paragraph($count) # # Returns a paragraph with a certain number of constructed sentences. # Accepts either two or one integers. If passed two, it returns a paragraph # consisting of n sentences where n is between the two integers. If passed # only one, then it returns that number of sentences. If no integers are # passed, it returns between 7 and 4 sentences. #------------------------------------------------------------------------------ sub paragraph { my ($self, $low, $high) = @_; my $count = 0; if (not defined $low) { $count = _random(7, 4); } elsif (not defined $high) { $count = $low; } else { $count = _random($high, $low); } $count--; join ' ', $self->sentence(1), map { $self->sentence } (1 .. $count); } #------------------------------------------------------------------------------ # @paragraphs = $self->paragraphs($total_paragraphs, $sentence_count) # @paragraphs = $self->paragraphs($total_paragraphs, $sentence_min, $sentence_max) # # Returns a given number of paragraphs. You can optionally supply sentence # parameters like sentence count per paragraph or a range for sentence count. #------------------------------------------------------------------------------ sub paragraphs { my $self = shift; my $count = shift || 2; my ($low, $high) = @_; map { $self->paragraph($low, $high) } (1 .. $count); } #------------------------------------------------------------------------------ # @bullets = $self->bullets($number, $type) # # Returns a given number of bullet items. Internally, it randomly picks a # style, then it returns the number of bullets requested based on that style. # For internal use only, $type specifies the type of style. #------------------------------------------------------------------------------ sub bullets { my ($self, $meta) = (shift, ''); my $count = shift || 5; my $type = shift || _random(4, 1); if ($type == 1) { $meta = 'verb article adjective noun'; } elsif ($type == 2) { $meta = 'power_word adjective noun'; } elsif ($type == 3) { $meta = 'power_word adjective noun and power_word adjective noun'; } elsif ($type == 4) { $meta = 'verb article noun power_word by article noun'; } map { ucfirst $self->words($meta) } (1 .. $count); } #------------------------------------------------------------------------------ # \@data = $self->body() # # Returns a reference to an array where each element is a reference to a hash # containing two keys: type and text. Type will be either paragraph or bullet. # Accepts parameters in a reference to a hash as follows: # # { # p_min => 2, # Minimum number of paragraphs to return # p_max => 4, # Maximum number of paragraphs to return # p_s_min => 1, # Minimum number of sentences in a paragraph # p_s_max => 1, # Maximum number of sentences in a paragraph # b_freq => 20, # % chance of a bulletted list after each paragraph # b_min => 4, # Minimum number of bullet items in a bulletted list # b_max => 6 # Maximum number of bullet items in a bulletted list # } #------------------------------------------------------------------------------ sub body { my ($self, $params) = @_; $params->{p_min} = 1 if (not exists $params->{p_min}); $params->{p_max} = 3 if (not exists $params->{p_max}); $params->{b_freq} = 25 if (not exists $params->{b_freq}); $params->{b_min} = 3 if (not exists $params->{b_min}); $params->{b_max} = 6 if (not exists $params->{b_max}); my @data = (); my $p_count = _random($params->{p_max}, $params->{p_min}); foreach (1 .. $p_count) { push @data, { type => 'paragraph', text => $self->paragraphs(1, $params->{p_s_min}, $params->{p_s_max}) }; # A bulletted list should never be first in a body block. if ( ($_ != $p_count) and (_random(100, 1) < $params->{b_freq}) ) { my $type = _random(4, 1); push @data, { type => 'bullet', # Must specify only 1 bullet at a time using this coding style. # Probably want to rewrite this later so that we can fetch all the # bullets at once, then map them into the right places. text => $self->bullets(1, $type) } foreach (1 .. _random($params->{b_max}, $params->{b_min})); } } \@data; } #------------------------------------------------------------------------------ # $header = $self->header($type) # # Returns a correctly formatted (certain words in upper-case) text string # for use as a header. Accepts a single whole number integer between # 5 and 1 that represents the "level" of header. Header 1 is highest, etc. # If no integer is given, it will randomly pick a number between 5 and 1. #------------------------------------------------------------------------------ sub header { my ($self, $meta) = (shift, ''); my $type = shift || _random(5, 1); # I'm not a fan of how these turn out in practice. I think the meta forms # for these should get some additional attention. if ($type == 1) { my $subtype = _random(3, 1); if ($subtype == 1) { $meta = 'noun and noun'; } elsif ($subtype == 2) { $meta = 'noun'; } elsif ($subtype == 3) { $meta = 'article noun'; } } elsif ($type == 2) { $meta = 'power_word noun'; } elsif ($type == 3) { $meta = 'adverb power_word noun'; } elsif ($type == 4) { $meta = 'adverb power_word adjective noun'; } elsif ($type == 5) { $meta = 'power_word adjective noun adverb noun'; } # Capitalize every word with the exception of: of, and, or join(' ', map { join('-', map { ($_ !~ /^(of|and|or)$/) ? ucfirst : $_ } split('-')) } split(' ', $self->words($meta))); } #------------------------------------------------------------------------------ # @structure = $self->structure($block_limit, $depth_limit, $mimimum_length) # # Returns an array of numbers, each number representing a "heading level" # for a document structure. $block_limit is the maximum of any similar # heading level within the same parent level. $depth_limit is how deep # the levels are allowed to nest. $minimum_length is, well, the minimum length. #------------------------------------------------------------------------------ sub structure { my ($self, $depth, $last_push) = (shift, 1, 0); my $block_limit = shift || 3; my $depth_limit = shift || 3; my $mimimum_length = shift || 10; my @structure = (); # Need to recursively call a function and keep some variables in scope. # This section should DEFINATELY get rewritten, but I'm not smart enough. my $structure = undef; $structure = sub { my $block = 0; while ( ($block < _random($block_limit, 1)) and ((_random(4, 1) > 1) or ($last_push ne $depth)) # 1/4 chance of exiting early ) { push @structure, $depth; my $last_push = $depth; $block++; if ( ($depth < $depth_limit) and (_random(5, 1) > 1) # 1/5 chance of not nesting ) { $depth++; $structure->(); } } $depth--; }; while (@structure < $mimimum_length) { # This is commented-out to keep us from going into an infinite loop. # Eventually, I think this should get recoded. # @structure = (); ($last_push, $depth) = (0, 1); $structure->(); } @structure; } #------------------------------------------------------------------------------ # \@document = $self->document(\@structure, \%body_parameters) # # Returns a reference to an array containing hash references, similar to body. # This is the data for a complete document of management-speak. # Optionally accepts a reference to an array with the document structure # and a reference to a hash containing body parameters. #------------------------------------------------------------------------------ sub document { my $self = shift; my $structure = shift || [ $self->structure ]; my $params = shift || undef; [ map { { type => 'header' . $_, text => $self->header($_) }, @{$self->body($params)} } (@{$structure}) ]; } #------------------------------------------------------------------------------ # $output = $self->to_html($self->body || $self->document) # # Accepts either a body() or document() result and converts it into mostly # good HTML. #------------------------------------------------------------------------------ sub to_html { my ($self, $data) = @_; my ($inside_list, $output) = (0, ''); foreach (@{$data}) { if (($_->{type} ne 'bullet') and ($inside_list)) { $inside_list = 0; $output .= "\n"; } if ($_->{type} =~ /header(\d+)/) { $output .= '' . $_->{text} . '\n"; } elsif ($_->{type} eq 'paragraph') { $output .= '

' . $_->{text} . "

\n"; } elsif ($_->{type} eq 'bullet') { my $ul = (not $inside_list) ? "