first cut at repeater
Matt S Trout [Thu, 18 Feb 2010 00:52:17 +0000 (00:52 +0000)]
lib/HTML/Zoom/CodeStream.pm
lib/HTML/Zoom/FilterBuilder.pm
lib/HTML/Zoom/FilterStream.pm
lib/HTML/Zoom/StreamBase.pm
t/actions.t

index 585e05c..2232f28 100644 (file)
@@ -17,17 +17,6 @@ sub new {
   bless({ _code => $args->{code} }, $class);
 }
 
-sub peek {
-  my ($self) = @_;
-  if (exists $self->{_peeked}) {
-    return ($self->{_peeked});
-  }
-  if (my ($peeked) = $self->next) {
-    return ($self->{_peeked} = $peeked);
-  }
-  return;
-}
-
 sub next {
   my ($self) = @_;
 
index 19cc818..27721e2 100644 (file)
@@ -17,20 +17,24 @@ sub _stream_from_array {
   HTML::Zoom::CodeStream->from_array(@_)
 }
 
+sub _stream_from_proto {
+  my ($self, $proto) = @_;
+  my $ref = ref $proto;
+  if (not $ref) {
+    return $self->_stream_from_array({ type => 'TEXT', raw => $proto });
+  } elsif ($ref eq 'ARRAY') {
+    return $self->_stream_from_array(@$proto);
+  } elsif ($ref eq 'CODE') {
+    return $proto->();
+  } elsif ($ref eq 'SCALAR') {
+    require HTML::Zoom::Parser::BuiltIn;
+    return HTML::Zoom::Parser::BuiltIn->html_to_stream($$proto);
+  }
+  die "What the hell is $proto and how should I turn a $ref into a stream?";
+}
+
 sub _stream_concat {
-  shift; # lose $self
-  my @streams = @_;
-  my $cur_stream = shift(@streams) or die "No streams passed";
-  HTML::Zoom::CodeStream->new({
-    code => sub {
-      return unless $cur_stream;
-      my $evt;
-      until (($evt) = $cur_stream->next) {
-        return unless $cur_stream = shift(@streams);
-      }
-      return $evt;
-    }
-  });
+  shift->_stream_from_array(@_)->flatten;
 }
 
 sub set_attribute {
@@ -171,11 +175,11 @@ sub append_inside {
 }
 
 sub replace {
-  my ($self, $events, $options) = @_;
+  my ($self, $replace_with, $options) = @_;
   my $coll_proto = $self->collect($options);
   sub {
     my ($evt, $stream) = @_;
-    my $emit = $self->_stream_from_array(@$events);
+    my $emit = $self->_stream_from_proto($replace_with);
     my $coll = &$coll_proto;
     # For a straightforward replace operation we can, in fact, do the emit
     # -before- the collect, and my first cut did so. However in order to
@@ -194,4 +198,19 @@ sub replace {
   };
 }
 
+sub repeat {
+  my ($self, $repeat_for, $options) = @_;
+  $options->{into} = \my @into;
+  my $map_repeat = sub {
+    local $_ = $self->_stream_from_array(@into);
+    $_[0]->($_)
+  };
+  my $repeater = sub {
+    $self->_stream_from_proto($repeat_for)
+         ->map($map_repeat)
+         ->flatten
+  };
+  $self->replace($repeater, $options);
+}
+
 1;
index 641144f..ecab5df 100644 (file)
@@ -16,17 +16,6 @@ sub new {
   );
 }
 
-sub peek {
-  my ($self) = @_;
-  if (exists $self->{_peeked}) {
-    return ($self->{_peeked});
-  }
-  if (my ($peeked) = $self->next) {
-    return ($self->{_peeked} = $peeked);
-  }
-  return;
-}
-
 sub next {
   my ($self) = @_;
 
index 7584ed6..96edbf0 100644 (file)
@@ -3,4 +3,56 @@ package HTML::Zoom::StreamBase;
 use strict;
 use warnings FATAL => 'all';
 
+sub peek {
+  my ($self) = @_;
+  if (exists $self->{_peeked}) {
+    return ($self->{_peeked});
+  }
+  if (my ($peeked) = $self->next) {
+    return ($self->{_peeked} = $peeked);
+  }
+  return;
+}
+
+sub flatten {
+  my $source_stream = shift;
+  require HTML::Zoom::CodeStream;
+  my $cur_stream;
+  HTML::Zoom::CodeStream->new({
+    code => sub {
+      return unless $source_stream;
+      my $next;
+      until (($next) = ($cur_stream ? $cur_stream->next : ())) {
+#::Dwarn $source_stream;
+        unless (($cur_stream) = $source_stream->next) {
+          undef $source_stream; return;
+        }
+      }
+#::Dwarn $next;
+      return $next;
+    }
+  });
+}
+
+sub map {
+  my ($source_stream, $map_func) = @_;
+  require HTML::Zoom::CodeStream;
+  HTML::Zoom::CodeStream->new({
+    code => sub {
+      return unless $source_stream;
+      # If we were aiming for a "true" perl-like map then we should
+      # elegantly handle the case where the map function returns 0 events
+      # and the case where it returns >1 - if you're reading this comment
+      # because you wanted it to do that, now would be the time to fix it :)
+      if (my ($next) = $source_stream->next) {
+        #### XXXX collapsing this into a return doesn't work. what the
+        #### flying fornication ... -- mst
+        my $mapped = do { local $_ = $next; $map_func->($next) };
+        return $mapped;
+      }
+      undef $source_stream; return;
+    }
+  });
+}
+
 1;
index 03082bf..3bab29c 100644 (file)
@@ -2,6 +2,8 @@ use strict;
 use warnings FATAL => 'all';
 use Test::More;
 
+use Devel::Dwarn;
+
 use HTML::Zoom::Parser::BuiltIn;
 use HTML::Zoom::Producer::BuiltIn;
 use HTML::Zoom::SelectorParser;
@@ -196,4 +198,44 @@ is(
   'append inside ok'
 );
 
+if (1) {
+
+warn "\n\n----\n\n";
+
+my $r_inside = sub { my $r = shift; sub { $_->replace($r, { inside => 1 }) } };
+
+is(
+  run_for {
+    $_->repeat(
+      [
+        sub {
+          filter
+            filter($_ => '.name' => $r_inside->('mst'))
+            => '.career' => $r_inside->('Chainsaw Wielder')
+        },
+        sub {
+          filter
+            filter($_ => '.name' => $r_inside->('mdk'))
+            => '.career' => $r_inside->('Adminion')
+        },
+      ]
+    )
+  },
+  q{<body>
+  <div class="main">
+    <span class="hilight name">mst</span>
+    <span class="career">Chainsaw Wielder</span>
+    <hr />
+  </div><div class="main">
+    <span class="hilight name">mdk</span>
+    <span class="career">Adminion</span>
+    <hr />
+  </div>
+</body>
+},
+  'repeat ok'
+);
+
+}
+
 done_testing;