mediawiki merging, row identification, tests
Robert 'phaylon' Sedlacek [Mon, 23 Jul 2012 21:49:08 +0000 (21:49 +0000)]
lib/System/Introspector/Report/Builder/Packages/Apt/ByHost.pm
lib/System/Introspector/Report/Builder/Packages/Apt/ByPackage.pm
lib/System/Introspector/Report/Builder/Perls.pm
lib/System/Introspector/Report/Publish/MediaWiki.pm
lib/System/Introspector/Report/Publish/MediaWiki/Connection.pm
lib/System/Introspector/Report/Publish/MediaWiki/Page.pm
lib/System/Introspector/Report/Publish/MediaWiki/Producer.pm
lib/System/Introspector/Report/Updater.pm [new file with mode: 0644]
t/data/foo.txt [new file with mode: 0644]
t/data/result/foo.txt [new file with mode: 0644]
t/publish_mediawiki.t [new file with mode: 0644]

index 5c9a088..9398c98 100644 (file)
@@ -40,6 +40,7 @@ sub render_reports {
       columns => \@columns,
       title => "Packages on $remote",
       id => ['packages-apt-by-remote', $remote],
+      rowid => [qw( package )],
       meta => { remote => $remote },
       rows => [map {
         my $package = $_;
index 12c8b80..8d03722 100644 (file)
@@ -43,6 +43,7 @@ sub render_reports {
       columns => \@columns,
       title => "$package Installations",
       id => ['packages-apt-by-package', $package],
+      rowid => [qw( remote )],
       meta => { package => $package },
       rows => [ map {
         my $remote  = $_;
index 041e33a..ce3e03c 100644 (file)
@@ -47,6 +47,7 @@ sub render_reports {
   return +{
     title => 'Perl Installations',
     id => 'perls',
+    rowid => [qw( remote location )],
     columns => [
       { key => 'remote',   label => 'Remote' },
       { key => 'hostname', label => 'Hostname' },
index 839a2e3..edbead7 100644 (file)
@@ -1,6 +1,5 @@
 package System::Introspector::Report::Publish::MediaWiki;
 use Moo;
-use aliased 'System::Introspector::Report::Publish::MediaWiki::Producer';
 use aliased 'System::Introspector::Report::Publish::MediaWiki::Connection';
 
 has page_options => (
@@ -9,10 +8,6 @@ has page_options => (
   init_arg => 'page',
 );
 
-has producer => (is => 'lazy', handles => {
-  _render_page => 'render',
-});
-
 has connection => (is => 'ro', lazy => 1, builder => 1, handles => {
   get_page => 'get',
   put_page => 'put',
@@ -48,11 +43,11 @@ sub _sort_reports {
 }
 
 sub _publish_page {
-  my ($self, $reports, $page, $options) = @_;
+  my ($self, $reports, $page_name, $options) = @_;
   my $sorted = $self->_sort_reports($reports, $options->{report} || []);
-  print $self->_render_page($sorted);
-#  print $self->render_report($_)
-#    for @$sorted;
+  my $page = $self->get_page($page_name);
+  $page->update($sorted);
+  $self->put_page($page);
   return 1;
 }
 
index e0f19d6..8871f64 100644 (file)
@@ -1,8 +1,12 @@
 package System::Introspector::Report::Publish::MediaWiki::Connection;
 use Moo;
 
-sub get { }
+sub get {
+  my ($self, $page_name) = @_;
+}
 
-sub put { }
+sub put {
+  my ($self, $page) = @_;
+}
 
 1;
index 0244eef..b39b6c7 100644 (file)
@@ -1,11 +1,24 @@
 package System::Introspector::Report::Publish::MediaWiki::Page;
 use Moo;
 
+use aliased 'System::Introspector::Report::Publish::MediaWiki::Parser';
+use aliased 'System::Introspector::Report::Publish::MediaWiki::Producer';
+use aliased 'System::Introspector::Report::Updater';
+
+has name      => (is => 'ro', required => 1);
 has timestamp => (is => 'ro', required => 1);
-has content   => (is => 'ro', required => 1);
+has content   => (is => 'rwp', required => 1);
+
+my $_producer = Producer->new;
+my $_parser   = Parser->new;
+my $_updater  = Updater->new;
 
 sub update {
   my ($self, $update_stream) = @_;
+  my $original_stream = $_parser->parse($self->content);
+  my $merged_stream   = $_updater->merge($original_stream, $update_stream);
+  $self->_set_content($_producer->render($merged_stream));
+  return 1;
 }
 
 1;
index fa43a43..f6c252c 100644 (file)
@@ -4,9 +4,15 @@ use HTML::Zoom;
 
 sub render {
   my ($self, $stream) = @_;
-  return join "\n", map {
+  return $self->_clear_body(join "\n", map {
     ref($_) ? $self->_render_table($_) : $_;
-  } @$stream;
+  } @$stream);
+}
+
+sub _clear_body {
+  my ($self, $string) = @_;
+  $string =~ s{\n\n+}{\n\n}g;
+  return $string;
 }
 
 sub _wrap {
@@ -23,7 +29,6 @@ sub _render_table {
   my $id     = $report->{id};
   my $str_id = ref($id) ? join(':', @$id) : $id;
   my $markup = $self->_load_markup;
-  $markup = $self->_apply_identity($markup, $report);
   $markup = $self->_apply_table_head($markup, $report);
   $markup = $self->_apply_table_body($markup, $report);
   my $description = $report->{description} || [''];
@@ -39,16 +44,6 @@ sub _render_title {
   return sprintf '== %s ==', $report->{title};
 }
 
-sub _apply_identity {
-  my ($self, $markup, $report) = @_;
-  my $id = $report->{id};
-  return $markup
-    ->select('table')
-    ->set_attribute(id => 'si-id-' . join '__',
-      ref($id) ? @$id : $id,
-    );
-}
-
 sub _apply_table_body {
   my ($self, $markup, $report) = @_;
   my $index = 0;
@@ -77,9 +72,15 @@ sub _apply_table_body {
         };
       },
     });
-  });
+  })->memoize;
 }
 
+my $_trim = sub {
+  my $string = shift;
+  $string =~ s{(?:^\s+|\s+$)}{}g;
+  return $string;
+};
+
 sub _apply_table_head {
   my ($self, $markup, $report) = @_;
   my @cols = @{$report->{columns}};
@@ -87,23 +88,27 @@ sub _apply_table_head {
     ->repeat('th', [map {
       my $col_idx = $_;
       my $col = $cols[$_];
+      my $label = $col->{label};
       sub {
         $_->apply_if($col->{key} !~ m{^__}, sub {
             $_->add_to_attribute('th', class => 'si-colhead-' . $col->{key});
           })
-          ->replace_content('th', $col->{label})
+          ->replace_content('th', defined($label) ? $label->$_trim : 'Unnamed')
           ->apply_if($col_idx != $#cols, sub {
             $_->add_after('th', "\n    ");
           });
       };
     } 0 .. $#cols ])
+    ->memoize;
 }
 
+my $_template = do {
+  local $/;
+  scalar <DATA>;
+};
+
 sub _load_markup {
-  return HTML::Zoom->from_html(do {
-    local $/;
-    scalar <DATA>;
-  });
+  return HTML::Zoom->from_html($_template);
 }
 
 1;
diff --git a/lib/System/Introspector/Report/Updater.pm b/lib/System/Introspector/Report/Updater.pm
new file mode 100644 (file)
index 0000000..a2c96d7
--- /dev/null
@@ -0,0 +1,102 @@
+package System::Introspector::Report::Updater;
+use Moo;
+
+my $_flatten_id = sub {
+  my $value = $_[0];
+  return ref($value) ? join("\0", @$value) : $value;
+};
+
+sub merge {
+  my ($self, $original, $update) = @_;
+  my $old = $self->_map_reports_by_id($original);
+  my $new = $self->_map_reports_by_id($update);
+  my %add = map { ($_ => $new->{$_}) } grep { not($old->{$_}) } keys %$new;
+  return $self->_weave_additional([ map {
+    (ref($_) eq 'HASH') ? do {
+      my $report = $_;
+      my $id = $report->{id}->$_flatten_id;
+      $new->{$id}
+        ? $self->_merge_reports($report, $new->{$id})
+        : $report;
+    } : $_;
+  } @$original ], \%add, $self->_calc_sequence($update));
+}
+
+sub _weave_additional {
+  my ($self, $stream, $additional, $sequence) = @_;
+  my @items = @$stream;
+  if (my $begin = $sequence->{''}) {
+    if (my $item = delete $additional->{$begin}) {
+      unshift @items, $item;
+    }
+  }
+  my @done;
+  while (defined( my $item = shift @items )) {
+    if (ref($item) eq 'HASH') {
+      my $item_id = $item->{id}->$_flatten_id;
+      if (my $after = $sequence->{$item_id}) {
+        if (my $insert = $additional->{$after}) {
+          unshift @items, $insert;
+        }
+      }
+    }
+    push @done, $item;
+  }
+  return \@done;
+}
+
+sub _calc_sequence {
+  my ($self, $stream) = @_;
+  my @ids = map {
+    $_->{id}->$_flatten_id;
+  } grep {
+    ref($_) eq 'HASH';
+  } @$stream;
+  my %sequence;
+  for my $index (0 .. $#ids) {
+    $sequence{ $index ? $ids[$index - 1] : '' } = $ids[$index];
+  }
+  return \%sequence;
+}
+
+sub _merge_reports {
+  my ($self, $original, $update) = @_;
+  my $rowid = $update->{rowid};
+  my $ident = sub {
+    my $row = shift;
+    join "\0", map $row->{$_}, @$rowid;
+  };
+  my %current;
+  $current{$_->$ident} = $_
+    for @{$original->{rows}};
+  return {
+    title => $update->{title},
+    id => $update->{id},
+    rowid => $update->{rowid},
+    meta => $update->{meta} || {},
+    columns => [
+      @{$update->{columns}},
+      (grep {
+        $_->{key} =~ m{^__};
+      } @{$original->{columns}}),
+    ],
+    rows => [ map {
+      my $row = $_;
+      my $id = $row->$ident;
+      +{ %{$current{$id}||{}}, %$row };
+    } @{$update->{rows}} ],
+  };
+}
+
+sub _map_reports_by_id {
+  my ($self, $stream) = @_;
+  return {
+    map {
+      ($_->{id}->$_flatten_id, $_);
+    } grep {
+      ref($_) eq 'HASH';
+    } @$stream,
+  };
+}
+
+1;
diff --git a/t/data/foo.txt b/t/data/foo.txt
new file mode 100644 (file)
index 0000000..2f3a68b
--- /dev/null
@@ -0,0 +1,63 @@
+Previous text.
+
+<!-- SI:TITLE begin foo:a -->
+== Foo X ==
+<!-- SI:TITLE end foo:a -->
+
+User description.
+
+<!-- SI:TABLE begin foo:a -->
+<table class="si-report">
+  <tr>
+    <th class="si-colhead-bar">Bar</th>
+    <th class="si-colhead-baz">Baz</th>
+    <th>
+        Qux
+    </th>
+  </tr>
+  <tr>
+    <td class="si-column-bar">231</td>
+    <td class="si-column-baz">421</td>
+    <td>171</td>
+  </tr>
+  <tr>
+    <td class="si-column-bar">232</td>
+    <td class="si-column-baz">422</td>
+    <td>172</td>
+    <td>173</td>
+  </tr>
+</table>
+<!-- SI:TABLE end foo:a -->
+
+More text.
+
+<!-- SI:TITLE begin foo:c -->
+== Foo Y ==
+<!-- SI:TITLE end foo:c -->
+
+User description.
+
+<!-- SI:TABLE begin foo:c -->
+<table class="si-report">
+  <tr>
+    <th class="si-colhead-bar">Bar</th>
+    <th class="si-colhead-baz">Baz</th>
+    <th>
+        Qux
+    </th>
+  </tr>
+  <tr>
+    <td class="si-column-bar">231</td>
+    <td class="si-column-baz">421</td>
+    <td>171</td>
+  </tr>
+  <tr>
+    <td class="si-column-bar">232</td>
+    <td class="si-column-baz">422</td>
+    <td>172</td>
+    <td>173</td>
+  </tr>
+</table>
+<!-- SI:TABLE end foo:c -->
+
+End Text
diff --git a/t/data/result/foo.txt b/t/data/result/foo.txt
new file mode 100644 (file)
index 0000000..a75fa43
--- /dev/null
@@ -0,0 +1,82 @@
+Previous text.
+
+<!-- SI:TITLE begin foo:a -->
+== Foo A ==
+<!-- SI:TITLE end foo:a -->
+
+<!-- SI:TABLE begin foo:a -->
+<table class="si-report">
+  <tr>
+    <th class="si-colhead-bar">Bar</th>
+    <th class="si-colhead-baz">Baz</th>
+    <th>Qux</th>
+    <th>Unnamed</th>
+  </tr>
+  <tr class="data-row">
+    <td class="si-column-bar">231</td>
+    <td class="si-column-baz">421</td>
+    <td>171</td>
+    <td></td>
+  </tr><tr class="data-row">
+    <td class="si-column-bar">232</td>
+    <td class="si-column-baz">884</td>
+    <td>172</td>
+    <td>173</td>
+  </tr><tr class="data-row">
+    <td class="si-column-bar">332</td>
+    <td class="si-column-baz">784</td>
+    <td></td>
+    <td></td>
+  </tr>
+</table>
+<!-- SI:TABLE end foo:a -->
+
+<!-- SI:TITLE begin foo:b -->
+== Foo B ==
+<!-- SI:TITLE end foo:b -->
+
+<!-- SI:TABLE begin foo:b -->
+<table class="si-report">
+  <tr>
+    <th class="si-colhead-bar">Bar</th>
+    <th class="si-colhead-baz">Baz</th>
+  </tr>
+  <tr class="data-row">
+    <td class="si-column-bar">231</td>
+    <td class="si-column-baz">421</td>
+  </tr><tr class="data-row">
+    <td class="si-column-bar">332</td>
+    <td class="si-column-baz">784</td>
+  </tr>
+</table>
+<!-- SI:TABLE end foo:b -->
+
+More text.
+
+<!-- SI:TITLE begin foo:c -->
+== Foo C ==
+<!-- SI:TITLE end foo:c -->
+
+<!-- SI:TABLE begin foo:c -->
+<table class="si-report">
+  <tr>
+    <th class="si-colhead-bar">New Bar</th>
+    <th class="si-colhead-baz">New Baz</th>
+    <th>Qux</th>
+    <th>Unnamed</th>
+  </tr>
+  <tr class="data-row">
+    <td class="si-column-bar">232</td>
+    <td class="si-column-baz">884</td>
+    <td>172</td>
+    <td>173</td>
+  </tr><tr class="data-row">
+    <td class="si-column-bar">332</td>
+    <td class="si-column-baz">784</td>
+    <td></td>
+    <td></td>
+  </tr>
+</table>
+<!-- SI:TABLE end foo:c -->
+
+End Text
\ No newline at end of file
diff --git a/t/publish_mediawiki.t b/t/publish_mediawiki.t
new file mode 100644 (file)
index 0000000..8d5f7d3
--- /dev/null
@@ -0,0 +1,94 @@
+use strictures 1;
+use Test::More;
+use IO::All;
+use FindBin;
+
+use aliased 'System::Introspector::Report::Publish::MediaWiki';
+
+my %result;
+
+do {
+  package TestConnection;
+  use Moo;
+  use IO::All;
+  use aliased 'System::Introspector::Report::Publish::MediaWiki::Page';
+
+  sub get {
+    my ($self, $name) = @_;
+    return Page->new(
+      name      => $name,
+      timestamp => 23,
+      content   => scalar(io("$FindBin::Bin/data/$name.txt")->slurp),
+    );
+  }
+
+  sub put {
+    my ($self, $page) = @_;
+    $result{$page->name} = $page->content;
+  }
+};
+
+my $conn = TestConnection->new;
+my $wiki = MediaWiki->new(
+  connection => $conn,
+  page => {
+    foo => {
+      report => ['foo:*'],
+    },
+  },
+);
+
+ok $wiki->publish([
+  { id => [qw( foo a )],
+    title => "Foo A",
+    rowid => ['bar'],
+    columns => [
+      { key => 'bar', label => 'Bar' },
+      { key => 'baz', label => 'Baz' },
+    ],
+    rows => [
+      { bar => 231, baz => 421 },
+      { bar => 232, baz => 884 },
+      { bar => 332, baz => 784 },
+    ],
+  },
+  { id => [qw( foo b )],
+    title => "Foo B",
+    rowid => ['bar'],
+    columns => [
+      { key => 'bar', label => 'Bar' },
+      { key => 'baz', label => 'Baz' },
+    ],
+    rows => [
+      { bar => 231, baz => 421 },
+      { bar => 332, baz => 784 },
+    ],
+  },
+  { id => [qw( foo c )],
+    title => "Foo C",
+    rowid => ['bar'],
+    columns => [
+      { key => 'bar', label => 'New Bar' },
+      { key => 'baz', label => 'New Baz' },
+    ],
+    rows => [
+      { bar => 232, baz => 884 },
+      { bar => 332, baz => 784 },
+    ],
+  },
+]), 'publish ok';
+
+my $_despace = sub {
+  my $string = shift;
+  $string =~ s{[\s\n]+}{}g;
+  return $string;
+};
+
+## uncomment to regenerate result file
+# do { no warnings; $result{foo} > io("$FindBin::Bin/data/result/foo.txt") };
+
+is $result{foo}->$_despace,
+   scalar(io("$FindBin::Bin/data/result/foo.txt")->slurp)->$_despace,
+   'resulting page looks correct';
+
+done_testing;