File Coverage

blib/lib/Text/Spintax.pm
Criterion Covered Total %
statement 74 81 91.3
branch 11 16 68.7
condition n/a
subroutine 15 17 88.2
pod 2 11 18.1
total 102 125 81.6


line stmt bran cond sub pod time code
1             package Text::Spintax;
2              
3 2     2   31038 use 5.006;
  2         5  
4 2     2   10 use strict;
  2         5  
  2         55  
5 2     2   9 use warnings FATAL => 'all';
  2         5  
  2         89  
6 2     2   1130 use Text::Spintax::grammar;
  2         5  
  2         85  
7 2     2   995 use Text::Spintax::RenderNode;
  2         4  
  2         71  
8 2     2   1469 use Parse::Lex;
  2         51038  
  2         2170  
9              
10             =head1 NAME
11              
12             Text::Spintax - A parser and renderer for spintax formatted text.
13              
14             =head1 VERSION
15              
16             Version 0.06
17              
18             =cut
19              
20             our $VERSION = '0.06';
21              
22              
23             =head1 SYNOPSIS
24              
25             use Text::Spintax;
26              
27             my $node = Text::Spintax->new->parse("This {is|was|will be} some {varied|random} text");
28             $node->equal_path_weight;
29             my $text = $node->render;
30              
31             =head1 DESCRIPTION
32              
33             Text::Spintax implements a parser and renderer for spintax formatted text. Spintax is a commonly used method for
34             generating "randomized" text. For example,
35              
36             This {is|was} a test
37              
38             would be rendered as
39              
40             * This is a test
41             * This was a test
42              
43             Spintax can be nested indefinitely, for example:
44              
45             This is nested {{very|quite} deeply|deep}.
46              
47             would be rendered as
48              
49             * This is nested very deeply.
50             * This is nested quite deeply.
51             * This is nested deep.
52              
53             The number of possible combinations is easy to calculate, but the proportion of renders has two options. The initial Text::Spintax::RenderNode has weight 1 for all nodes, meaning that for the previous example the probabilities of each render would be
54              
55             25% This is nested very deeply.
56             25% This is nested quite deeply.
57             50% This is nested deep.
58              
59             If you want every possible outcome to be equally likely, then call equal_path_weight on the Text::Spintax::RenderNode object and you'll get this outcome:
60              
61             33% This is nested very deeply.
62             33% This is nested quite deeply.
63             33% This is nested deep.
64              
65             =cut
66              
67 1 50   1 0 6 sub root { scalar @_ == 2 and $_[0]->{root} = $_[1]; return $_[0]->{root} }
  1         2  
68 19 100   19 0 36 sub curr { scalar @_ == 2 and $_[0]->{curr} = $_[1]; return $_[0]->{curr} }
  19         74  
69              
70             =head1 SUBROUTINES/METHODS
71              
72             =head2 new
73              
74             Returns a Text::Spintax object
75              
76             =cut
77              
78             sub new {
79 1     1 1 562 my $class = shift;
80 1         3 my $self = bless {}, $class;
81 1         6 return $self;
82             }
83              
84             =head2 parse
85              
86             Parses the spintax and returns a Text::Spintax::Node that is suitable for rendering. Returns undef if the spintax couldn't be parsed.
87              
88             =cut
89              
90             our $lexer;
91              
92             sub parse {
93 1     1 1 3 my $self = shift;
94 1         3 my ($text) = @_;
95 1         7 my @lex = qw(
96             OBRACE {
97             EBRACE }
98             PIPE \|
99             TEXT [^{}|]+
100             );
101 1 50       5 if (not defined $lexer) {
102 1         19 $lexer = Parse::Lex->new(@lex);
103 1         604 $lexer->skip('');
104             }
105 1         31 $lexer->from($text);
106              
107 1         5401 my $parser = new Text::Spintax::grammar();
108 1         11 $parser->YYData->{lexer} = $lexer;
109 1         8 my $root = Text::Spintax::RenderNode->new(type => "sequence", weight => 1);
110 1         3 $self->root($root);
111 1         3 $self->curr($root);
112              
113 1         2 $parser->YYData->{tree} = $self;
114 1         2 eval {
115 1         9 my $value = $parser->YYParse(yylex => \&lexer, yyerror =>\&error);
116             };
117 1 50       3 if ($@) {
118 0         0 return undef;
119             }
120 1         27 return $root;
121             }
122              
123             sub last_child {
124 1     1 0 1 my $self = shift;
125 1         2 return $self->curr->{children}[-1];
126             }
127              
128             sub obrace {
129 1     1 0 3 my $self = shift;
130 1         4 my $child = Text::Spintax::RenderNode->new(parent => $self->curr, weight => 1);
131 1         2 push @{$self->curr->{children}}, $child;
  1         2  
132 1         11 $self->curr($child);
133             }
134              
135             sub ebrace {
136 1     1 0 1 my $self = shift;
137 1         14 my @groups = ([]);
138 1         2 foreach my $child (@{$self->curr->children}) {
  1         3  
139 3 100       64 if ($child->type eq "pipe") {
140 1         2 push @groups, [];
141             }
142             else {
143 2         3 push @{$groups[-1]}, $child;
  2         5  
144             }
145             }
146 1         1 my @children;
147 1         2 foreach my $group (@groups) {
148 2 50       4 if (scalar @$group == 1) {
149 2         3 push @children, $group->[0];
150             }
151             else {
152 0         0 push @children, Text::Spintax::RenderNode->new(parent => $self->curr, children => $group, type => "sequence", weight => 1);
153             }
154             }
155 1         3 $self->curr->children(\@children);
156 1         3 $self->curr($self->curr->parent);
157             }
158             sub add_child {
159 5     5 0 5 my $self = shift;
160 5         9 my ($type,$text,$offset) = @_;
161 5         10 my $child = Text::Spintax::RenderNode->new(parent => $self->curr, type => $type, text => $text, offset => $offset, weight => 1);
162 5         6 push @{$self->curr->{children}}, $child;
  5         8  
163             }
164              
165             sub type {
166 0     0 0 0 my $self = shift;
167 0         0 my ($type) = @_;
168 0         0 $self->curr->{type} = $type;
169             }
170              
171             sub lexer {
172 8     8 0 9 my $parser = shift;
173 8         17 my $lexer = $parser->YYData->{lexer};
174 8         16 my $token = $parser->YYData->{lexer}->next;
175 8 50       559 if (not defined $token) {
176 0         0 return ('', undef);
177             }
178             else {
179 8         25 $parser->YYData->{DATA} = [$token->name, $token->text, $lexer->offset];
180             }
181 8 100       26 return ('', undef) if $lexer->eoi;
182 7         36 return ($token->name, $token->text);
183             }
184              
185             sub error {
186 0     0 0   die "error parsing spintax\n";
187             }
188              
189             =head1 AUTHOR
190              
191             Dale Evans, C<< >> http://devans.mycanadapayday.com
192              
193             =head1 BUGS
194              
195             Please report any bugs or feature requests to C, or through
196             the web interface at L. I will be notified, and then you'll
197             automatically be notified of progress on your bug as I make changes.
198              
199              
200              
201              
202             =head1 SUPPORT
203              
204             You can find documentation for this module with the perldoc command.
205              
206             perldoc Text::Spintax
207              
208              
209             You can also look for information at:
210              
211             =over 4
212              
213             =item * RT: CPAN's request tracker (report bugs here)
214              
215             L
216              
217             =item * AnnoCPAN: Annotated CPAN documentation
218              
219             L
220              
221             =item * CPAN Ratings
222              
223             L
224              
225             =item * Search CPAN
226              
227             L
228              
229             =back
230              
231              
232             =head1 ACKNOWLEDGEMENTS
233              
234             Francois Desarmenien for writing Parse::YAPP
235             Philippe Verdret for writing Parse::Lex
236              
237             =head1 LICENSE AND COPYRIGHT
238              
239             Copyright 2013 Dale Evans.
240              
241             This program is free software; you can redistribute it and/or modify it
242             under the terms of the the Artistic License (2.0). You may obtain a
243             copy of the full license at:
244              
245             L
246              
247             Any use, modification, and distribution of the Standard or Modified
248             Versions is governed by this Artistic License. By using, modifying or
249             distributing the Package, you accept this license. Do not use, modify,
250             or distribute the Package, if you do not accept this license.
251              
252             If your Modified Version has been derived from a Modified Version made
253             by someone other than you, you are nevertheless required to ensure that
254             your Modified Version complies with the requirements of this license.
255              
256             This license does not grant you the right to use any trademark, service
257             mark, tradename, or logo of the Copyright Holder.
258              
259             This license includes the non-exclusive, worldwide, free-of-charge
260             patent license to make, have made, use, offer to sell, sell, import and
261             otherwise transfer the Package with respect to any patent claims
262             licensable by the Copyright Holder that are necessarily infringed by the
263             Package. If you institute patent litigation (including a cross-claim or
264             counterclaim) against any party alleging that the Package constitutes
265             direct or contributory patent infringement, then this Artistic License
266             to you shall terminate on the date that such litigation is filed.
267              
268             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
269             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
270             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
271             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
272             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
273             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
274             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
275             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
276              
277             The Parse::Yapp module and its related modules and shell scripts are copyright (c) 1998-2001 Francois Desarmenien, France. All rights reserved.
278              
279             You may use and distribute them under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
280              
281             =cut
282              
283             1; # End of Text::Spintax