123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. package SugarTrail::Template::Master;
  2. # Master template holder
  3. # new()
  4. # parse()
  5. # _parse_headers()
  6. # _parse_body()
  7. use strict;
  8. use warnings;
  9. use Carp;
  10. use SugarTrail::Template::Slave;
  11. use SugarTrail::Template::Condition;
  12. sub new {
  13. my $class = shift;
  14. my $args = { @_ };
  15. my $self = {};
  16. $self->{args} = $args;
  17. $self->{parsed} = 0;
  18. return bless $self, $class;
  19. }
  20. sub parse {
  21. my $self = shift;
  22. my ($head, $body) = split "\n\n", $self->{text}, 2;
  23. $self->{head} = $head;
  24. $self->{body} = $body;
  25. my @counts = (
  26. $self->_parse_headers(),
  27. $self->_parse_body()
  28. );
  29. $self->{parsed}++;
  30. delete $self->{text};
  31. return @counts;
  32. }
  33. sub _parse_headers {
  34. my $self = shift;
  35. foreach (split "\n", $self->{head}) {
  36. my ($n, $v) = split ": ", $_, 2;
  37. $self->{meta}->{$n} = $v;
  38. }
  39. delete $self->{head};
  40. return scalar keys %{ $self->{meta} };
  41. }
  42. sub _parse_body {
  43. my $self = shift;
  44. $self->{steps} = [];
  45. foreach (split "\n", $self->{body}) {
  46. chomp; s/ *$//;
  47. my @conds;
  48. my ($line, $cond_block) = $_ =~ m/^(.*?)(\{(.*)\})?$/;
  49. $line =~ s/ *$//;
  50. if ($cond_block) {
  51. $cond_block =~ s/\}$//;
  52. $cond_block =~ s/^\{//;
  53. @conds = split m/\s*;\s*/, $cond_block;
  54. }
  55. my $step = { line => $line };
  56. $step->{conds} = \@conds if @conds;
  57. push @{ $self->{steps} }, $step;
  58. }
  59. delete $self->{body};
  60. return scalar @{ $self->{steps} };
  61. }
  62. sub generate_slave {
  63. my $self = shift;
  64. my $args = shift;
  65. my $slave = SugarTrail::Template::Slave->new($args);
  66. $self->parse() unless $self->{parsed};
  67. my @slave_steps;
  68. STEP: foreach my $master_step (@{ $self->{steps} }) {
  69. my $accept = 1; # accept by default
  70. my $slave_step = {
  71. line => $master_step->{line},
  72. };
  73. # Test against each condition from the pre-parsed condblock.
  74. # * refuse step only in case all conditions passed without
  75. # warnings
  76. # * in all other cases (fail, warning), accept
  77. if ($master_step->{conds}) {
  78. COND: foreach my $condstr (@{ $master_step->{conds} }) {
  79. my $c = SugarTrail::Template::Condition->new($condstr);
  80. unless ($c) {
  81. $self->{error} = "condition error: $c->{error}";
  82. return;
  83. }
  84. my ($r, $w) = $c->match($args);
  85. # if warnings, store them, prepare to accept and ignore result
  86. if (scalar @$w) {
  87. $accept = 1;
  88. push @{$slave_step->{warnings}}, @$w;
  89. next COND;
  90. } else {
  91. # point of no warnings -- refusal should be valid here
  92. $accept = 0 unless $r;
  93. }
  94. }
  95. }
  96. push @slave_steps, $slave_step
  97. if ($accept || defined $slave_step->{warnings}
  98. && scalar @{$slave_step->{warnings}});
  99. }
  100. $slave->{meta}->{'Master'} = $self->{source};
  101. $slave->{meta}->{'Master Revision'} = $self->{revision};
  102. $slave->{meta}->{'Params'} = mkmymime($args);
  103. $slave->{steps} = \@slave_steps;
  104. return $slave;
  105. }
  106. sub mkmymime {
  107. my $d = shift;
  108. my $d1 = "; "; my $d2 = "=";
  109. my @t;
  110. foreach (sort keys %$d) {
  111. push @t, $_ . $d2 . $d->{$_};
  112. }
  113. return join $d1, @t;
  114. }
  115. sub source { return $_[0]->{text} }
  116. 1;