backup commit.
[SMMID.git] / lib / SMMID / Controller / REST / SMID.pm
blobb386443a1176ae3016f1fc5b3845218c88290bc4
1 package SMMID::Controller::REST::SMID;
3 use Moose;
4 use utf8;
5 use Unicode::Normalize;
6 use Chemistry::Mol;
7 use Chemistry::File::SMILES;
8 use JSON::XS;
9 #use lib '~/SMMID/local-lib/lib/perl5/Chemistry/MolecularMass';
10 #use Chemistry::MolecularMass;
12 BEGIN { extends 'Catalyst::Controller::REST' };
14 use Data::Dumper;
16 __PACKAGE__->config(
17 default => 'application/json',
18 stash_key => 'rest',
19 map => { 'application/json' => 'JSON' },
23 =head1 NAME
25 SMMID::Controller::REST::SMID - REST-based controller to manage SMIDs
27 =head1 DESCRIPTION
29 Catalyst Controller.
31 =head1 METHODS
33 =cut
35 sub rest : Chained('/') PathPart('rest') CaptureArgs(0) {
36 print STDERR "found rest...\n";
39 sub browse :Chained('rest') PathPart('browse') Args(0) {
40 my ($self, $c) = @_;
42 print STDERR "found rest/browse...\n";
44 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->search( {}, { order_by => { -asc => 'smid_id' } } );
46 my @data;
47 while (my $r = $rs->next()) {
49 my $cur_char = "<p style=\"color:green\"><b>\x{2713}</b></p>";
50 if(!defined($r->curation_status()) || $r->curation_status() eq "unverified"){$cur_char = "<p style=\"color:red\">Unverified</p>";}
51 elsif($r->curation_status() eq "review"){$cur_char = "<p style=\"color:blue\">Marked for Review</p>";}
53 push @data, ["<a href=\"/smid/".$r->compound_id()."\">".$r->smid_id()."</a>", $r->formula(), $r->molecular_weight(), $cur_char ];
56 $c->stash->{rest} = { data => \@data };
59 ###Deprecated
60 sub molecular_weight {
61 #...
62 #The default variable will be used as the chemical Formula
63 $_ = shift(@_);
65 my %elements = (
66 "H" => 1.00794,
67 "D" => 2.014101,
68 "T" => 3.016049,
69 "He" => 4.002602,
70 "Li" => 6.941,
71 "Be" => 9.012182,
72 "B" => 10.811,
73 "C" => 12.0107,
74 "N" => 14.00674,
75 "O" => 15.9994,
76 "F" => 18.9984032,
77 "Ne" => 20.1797,
78 "Na" => 22.989770,
79 "Mg" => 24.3050,
80 "Al" => 26.981538,
81 "Si" => 28.0855,
82 "P" => 30.973761,
83 "S" => 32.066,
84 "Cl" => 35.4527,
85 "Ar" => 39.948,
86 "K" => 39.0983,
87 "Ca" => 40.078,
88 "Sc" => 44.955910,
89 "Ti" => 47.867,
90 "V" => 50.9415,
91 "Cr" => 51.9961,
92 "Mn" => 54.938049,
93 "Fe" => 55.845,
94 "Co" => 58.933200,
95 "Ni" => 58.6934,
96 "Cu" => 63.546,
97 "Zn" => 65.39,
98 "Ga" => 69.723,
99 "Ge" => 72.61,
100 "As" => 74.92160,
101 "Se" => 78.96,
102 "Br" => 79.904,
103 "Kr" => 83.80,
104 "Rb" => 85.4678,
105 "Sr" => 87.62,
106 "Y" => 88.90585,
107 "Zr" => 91.224,
108 "Nb" => 92.90638,
109 "Mo" => 95.94,
110 "Tc" => 98,
111 "Ru" => 101.07,
112 "Rh" => 102.90550,
113 "Pd" => 106.42,
114 "Ag" => 107.8682,
115 "Cd" => 112.411,
116 "In" => 114.818,
117 "Sn" => 118.710,
118 "Sb" => 121.760,
119 "Te" => 127.60,
120 "I" => 126.90447,
121 "Xe" => 131.29,
122 "Cs" => 132.90545,
123 "Ba" => 137.327,
124 "La" => 138.9055,
125 "Ce" => 140.116,
126 "Pr" => 140.90765,
127 "Nd" => 144.24,
128 "Pm" => 145,
129 "Sm" => 150.36,
130 "Eu" => 151.964,
131 "Gd" => 157.25,
132 "Tb" => 158.92534,
133 "Dy" => 162.50,
134 "Ho" => 164.93032,
135 "Er" => 167.26,
136 "Tm" => 168.93421,
137 "Yb" => 173.04,
138 "Lu" => 174.967,
139 "Hf" => 178.49,
140 "Ta" => 180.9479,
141 "W" => 183.84,
142 "Re" => 186.207,
143 "Os" => 190.23,
144 "Ir" => 192.217,
145 "Pt" => 195.078,
146 "Au" => 196.96655,
147 "Hg" => 200.59,
148 "Tl" => 204.3833,
149 "Pb" => 207.2,
150 "Bi" => 208.98038,
151 "Po" => 209,
152 "At" => 210,
153 "Rn" => 222,
154 "Fr" => 223,
155 "Ra" => 226,
156 "Ac" => 227,
157 "Th" => 232.038,
158 "Pa" => 231.03588,
159 "U" => 238.0289,
160 "Np" => 237,
161 "Pu" => 244,
162 "Am" => 243,
163 "Cm" => 247,
164 "Bk" => 247,
165 "Cf" => 251,
166 "Es" => 252,
167 "Fm" => 257,
168 "Md" => 258,
169 "No" => 259,
170 "Lr" => 262,
171 "Rf" => 261,
172 "Db" => 262,
173 "Sg" => 266,
174 "Bh" => 264,
175 "Hs" => 269,
176 "Mt" => 268,
177 "Uun" => 271,
178 "Uuu" => 272
180 my $weight = 0;
182 my @pairs = /([CHONPS][0-9]*)/g;
183 foreach my $pair (@pairs){
184 if (length($pair)==1){
185 $weight += $elements{substr($pair, 0, 1)};
186 }else{
187 $weight += $elements{substr($pair, 0, 1)}*substr($pair, 1);
190 return $weight;
193 #Inserting a subroutine for the curator interface. At first, it will be a clone of the browse tab.
194 sub curator : Chained('rest') PathPart('curator') Args(0) {
195 my ($self, $c) = @_;
197 print STDERR "found rest/curator...\n";
199 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->search({}, { order_by => { -asc => 'smid_id'}});
200 my @data;
201 while (my $r = $rs->next()) {
203 my $button = "<button id=\"unverify_".$r->compound_id()."\" onclick=\"mark_smid_for_review(".$r->compound_id().")\" type=\"button\" class=\"btn btn-primary\">Mark for Review</button>";
204 my $cur_status = "<p style=\"color:green\"><b>\x{2713}</b></p>";
205 my $disabled = "";
206 my $advice = "Approve and Curate";
207 my @missing;
209 my $hplcexperiments = $c->model("SMIDDB")->resultset("SMIDDB::Result::Experiment")->search({compound_id => $r->compound_id(), experiment_type => "hplc_ms"});
210 my $msmsexperiments = $c->model("SMIDDB")->resultset("SMIDDB::Result::Experiment")->search({compound_id => $r->compound_id(), experiment_type => "ms_spectrum"});
212 if (!$r->organisms()){push(@missing, "organisms");}
213 if (!$r->formula()){push(@missing, "Molecular Formula");}
214 if (!$r->smid_id()){push(@missing, "SMID ID");}
215 # ...requirement for HPLC-MS and MS/MS data
216 if (!$hplcexperiments->next()){push(@missing, "HPLC-MS Data");}
217 if (!$msmsexperiments->next()){push(@missing, "MS/MS Data");}
219 my $missinglist = "(Missing: ";
220 $missinglist .= join(", ", @missing);
221 $missinglist .= ")";
222 if ($missinglist eq "(Missing: )"){$missinglist = "";} else {$advice = "Curation not Reccommended"; $disabled = "disabled";}
224 if(!defined($r->curation_status()) || $r->curation_status() eq "unverified"){
225 $cur_status = "<p style=\"color:red\">Unverified $missinglist </p>";
226 $button = "<button id=\"curate_".$r->compound_id()."\" onclick=\"curate_smid(".$r->compound_id().")\" type=\"button\" class=\"btn btn-primary\" $disabled>$advice</button>";
228 elsif($r->curation_status() eq "review"){
229 $cur_status = "<p style=\"color:blue\">Marked for Review $missinglist </p> ";
230 $button = "<button id=\"curate_".$r->compound_id()."\" onclick=\"curate_smid(".$r->compound_id().")\" type=\"button\" class=\"btn btn-primary\" $disabled>$advice</button>";
233 push @data, [ $r->compound_id(), "<a href=\"/smid/".$r->compound_id()."/edit\">".$r->smid_id()."</a>", $r->formula(), $r->smiles(), $button, $cur_status];
236 $c->stash->{rest} = { data => \@data };
240 #If I am correct, this subroutine formats the data, while the above subroutine collects the data
241 sub curator_format :Chained('rest') PathPart('curator') Args(1) {
242 my $self = shift;
243 my $c = shift;
244 my $format = shift;
246 $self->curator($c);
248 if ($format eq "html") {
250 print STDERR "found the curator html...\n";
252 my $html = "<table border=\"1\" width=\"100%\" cellpadding=\"10\" >\n
253 <thead><th>SMID ID</th><th>Formula</th><th>SMILES</th><th><a width=\"50\"></a>Status</th></thead>\n";
255 foreach my $smid (@{$c->stash->{rest}->{data}}) {
256 $html .= "<tr><td><a href=\"/smid/$smid->[0]\">$smid->[1]</a></td><td>$smid->[2]</td><td>$smid->[3]</td><td><button id=\"curate_smid".$smid->compound_id()."\" disabled=\"false\" class=\"btn btn-primary\">Approve and Curate</button></td></tr>\n";
258 $html .= "</table>\n";
260 $c->stash->{rest} = { html => $html };
263 if ($format eq "datatable") {
264 #...
265 print STDERR "found the curator data...\n";
267 my @data = $c->stash->{rest}->{data};
271 sub browse_format :Chained('rest') PathPart('browse') Args(1) {
272 my $self = shift;
273 my $c = shift;
274 my $format = shift;
276 $self->browse($c);
278 if ($format eq "html") {
279 my $html = "<table border=\"1\" width=\"100%\" cellpadding=\"10\" >\n
280 <thead><th>SMID ID</th><th>Formula</th><th>SMILES</th></thead>\n";
282 foreach my $smid (@{$c->stash->{rest}->{data}}) {
283 $html .= "<tr><td><a href=\"/smid/$smid->[0]\">$smid->[1]</a></td><td>$smid->[2]</td><td>$smid->[3]</td></tr>\n";
285 $html .= "</table>\n";
287 $c->stash->{rest} = { html => $html };
290 if ($format eq "datatable") {
291 #...
293 print STDERR "found the browse data...\n";
295 my @data = $c->stash->{rest}->{data};
301 sub clean {
302 my $self = shift;
303 my $str = shift;
305 # remove script tags
306 $str =~ s/\<script\>//gi;
307 $str =~ s/\<\/script\>//gi;
309 return $str;
313 sub store :Chained('rest') PathPart('smid/store') Args(0) {
314 my $self = shift;
315 my $c = shift;
317 if (! $c->user()) {
318 $c->stash->{rest} = { error => "Login required for updating SMIDs." };
319 return;
322 my $user_id = $c->user()->get_object()->dbuser_id();
324 my $smid_id = $self->clean($c->req->param("smid_id"));
325 my $iupac_name = $self->clean($c->req->param("iupac_name"));
327 print STDERR "IUPAC name = $iupac_name\n";
328 my $smiles_string = $self->clean($c->req->param("smiles_string"));
330 print STDERR "SMILES = $smiles_string\n";
332 my $formula = $self->clean($c->req->param("formula"));
333 my $organisms = $self->clean($c->req->param("organisms"));
334 my $description = $self->clean($c->req->param("description"));
335 my $synonyms = $self->clean($c->req->param("synonyms"));
336 my $curation_status = $self->clean($c->req->param("curation_status"));
337 my $doi = $self->clean($c->req->param("doi"));
339 #my $mm = new Chemistry::MolecularMass;
340 my $molecular_weight = molecular_weight($formula);
342 my $errors = "";
343 if (!$smid_id) { $errors .= "Need smid id. "; }
344 if (!$iupac_name) { $errors .= "Need a IUPAC name. "; }
345 if (!$smiles_string) { $errors .= "Need smiles_string. "; }
346 if (!$formula) { $errors .= "Need formula. "; }
348 if ($errors) {
349 $c->stash->{rest} = { error => $errors };
350 return;
353 my $row = {
354 smid_id => $smid_id,
355 formula => $formula,
356 smiles => $smiles_string,
357 organisms => $organisms,
358 doi => $doi,
359 iupac_name => $iupac_name,
360 curation_status => $curation_status,
361 dbuser_id => $user_id,
362 description => $description,
363 synonyms => $synonyms,
364 create_date => 'now()',
365 molecular_weight => $molecular_weight,
366 last_modified_date => 'now()',
369 my $compound_id;
370 eval {
371 my $new = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->new($row);
372 $new->insert();
373 $compound_id = $new->compound_id();
376 if ($@) {
377 $c->stash->{rest} = { error => "Sorry, an error occurred storing the smid ($@)" };
378 return;
381 $c->stash->{rest} = {
382 compound_id => $compound_id,
383 message => "Successfully stored the smid $smid_id"
388 sub smid :Chained('rest') PathPart('smid') CaptureArgs(1) {
389 my $self = shift;
390 my $c = shift;
392 my $compound_id = shift;
394 $c->stash->{compound_id} = $compound_id;
397 sub delete_smid :Chained('smid') PathPart('delete') Args(0) {
398 my $self = shift;
399 my $c = shift;
407 #This is where the backend function will go to curate a smid. Use buttons modeled on smid_detail.js for help
408 #Note that this function will both curate a smid and mark it as unverified depending on the parameters sent!
409 sub curate_smid :Chained('smid') PathPart('curate_smid') Args(0){
411 my $self = shift;
412 my $c = shift;
414 my $curation_status = $self->clean($c->req->param("curation_status"));
415 my $compound_id = $c->stash->{compound_id};
416 my $curator_id = $c->user()->get_object()->dbuser_id();
417 my $row = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id => $compound_id} );
419 if (!$row){
420 $c->stash->{rest} = { error => "The SMID with id $compound_id does not exist." };
421 return;
424 my $smid_id = $row->smid_id();
426 my $data = {
427 smid_id => $smid_id,
429 curation_status => $curation_status,
431 last_modified_date => 'now()',
433 last_curated_time => 'now()',
435 curator_id => $curator_id
438 eval{
439 $row->update($data);
442 $c->stash->{rest} ={
443 message => "Successfully updated the curation status of smid $smid_id"
446 print STDERR "Smid ".$smid_id." curation status updated to $curation_status\n";
447 return;
451 #Note that this one does not update curator id, last edited time, or last curated time. It is being marked for
452 #review, so it is inappropriate to say that the smid has been edited or curated.
453 sub mark_for_review : Chained('smid') PathPart('mark_for_review') Args(0) {
455 my $self = shift;
456 my $c = shift;
458 my $curation_status = $self->clean($c->req->param("curation_status"));
459 my $compound_id = $c->stash->{compound_id};
460 my $row = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id => $compound_id} );
462 if (!$row){
463 $c->stash->{rest} = { error => "The SMID with id $compound_id does not exist." };
464 return;
467 my $smid_id = $row->smid_id();
469 my $data = {
470 smid_id => $smid_id,
471 curation_status => $curation_status,
474 eval{
475 $row->update($data);
478 $c->stash->{rest} ={
479 message => "Successfully updated the curation status of smid $smid_id"
482 print STDERR "Smid ".$smid_id." curation status updated to $curation_status NOTE TO RYAN\n";
483 return;
486 sub update :Chained('smid') PathPart('update') Args(0) {
487 my $self = shift;
488 my $c = shift;
490 if (! $c->user()) {
491 $c->stash->{rest} = { error => "Login required for updating SMIDs." };
492 return;
495 my $compound_id = $c->stash->{compound_id};
496 my $smid_row = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id => $compound_id } );
498 if (! $smid_row) {
499 $c->stash->{rest} = { error => "The SMID with id $compound_id does not exist." };
500 return;
503 my $user_id = $c->user()->get_object()->dbuser_id();
504 my $smid_owner_id = $smid_row->dbuser_id();
507 if ( ($user_id != $smid_owner_id) && ($c->user->get_object()->user_type() ne "curator") ) {
508 $c->stash->{rest} = { error => "The SMID with id $compound_id is (owned by $smid_owner_id) not owned by you ($user_id) and you cannot modify it." };
509 return;
512 my $smid_id = $self->clean($c->req->param("smid_id"));
513 my $smiles_string = $self->clean($c->req->param("smiles_string"));
514 my $formula = $self->clean($c->req->param("formula"));
515 my $organisms = $self->clean($c->req->param("organisms"));
516 my $iupac_name = $self->clean($c->req->param("iupac_name"));
517 my $curation_status = $self->clean($c->req->param("curation_status"));
518 my $synonyms = $self->clean($c->req->param("synonyms"));
519 my $description = $self->clean($c->req->param("description"));
520 my $doi = $self->clean($c->req->param("doi"));
521 #my $mm <- new Chemistry::MolecularMass;
522 my $molecular_weight = molecular_weight($formula);
524 my $errors = "";
525 if (!$compound_id) { $errors .= "Need compound id. "; }
526 if (!$iupac_name) { $errors .= "Need IUPAC name. "; }
527 if (!$smid_id) { $errors .= "Need smid id. "; }
528 #if (!$smiles_string) { $errors .= "Need smiles_string. "; }
529 if (!$formula) { $errors .= "Need formula. "; }
531 if (my $smiles_error = $self->check_smiles($smiles_string)) {
532 $errors .= $smiles_error;
535 if ($errors) {
536 $c->stash->{rest} = { error => $errors };
537 return;
540 my $data = {
541 smid_id => $smid_id,
542 formula => $formula,
543 smiles => $smiles_string,
544 organisms => $organisms,
545 doi => $doi,
546 iupac_name => $iupac_name,
547 curation_status => $curation_status,
548 description => $description,
549 synonyms => $synonyms,
550 molecular_weight => $molecular_weight,
551 last_modified_date => 'now()',
554 eval {
555 $smid_row->update($data);
558 if ($@) {
559 $c->stash->{rest} = { error => "Sorry, an error occurred storing the smid ($@)" };
560 return;
563 $c->stash->{rest} ={
564 compound_id => $compound_id,
565 message => "Successfully stored the smid $smid_id"
570 sub check_smiles {
571 my $self = shift;
572 my $smiles = shift;
574 eval {
575 Chemistry::Mol->parse($smiles, format => 'smiles');
578 my $error;
579 if ($@) {
580 $error = $@;
583 return $error;
588 sub detail :Chained('smid') PathPart('details') Args(0) {
589 my $self = shift;
590 my $c = shift;
592 my $s = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id => $c->stash->{compound_id} });
594 if (! $s) {
595 $c->stash->{rest} = { error => "Can't find smid with id ".$c->stash->{compound_id}."\n" };
596 return;
599 my $data;
600 $data->{smid_id} = $s->smid_id();
601 $data->{compound_id} = $s->compound_id();
602 $data->{formula}= $s->formula();
603 $data->{organisms} = $s->organisms();
604 $data->{doi} = $s->doi();
605 $data->{iupac_name} = $s->iupac_name();
606 $data->{smiles_string} = $s->smiles();
607 $data->{curation_status} = $s->curation_status();
608 $data->{last_modified_date} = $s->last_modified_date();
609 $data->{create_date} = $s->create_date();
610 $data->{curator_id} = $s->curator_id();
611 $data->{last_curated_time} = $s->last_curated_time();
612 $data->{description} = $s->description();
613 $data->{synonyms} = $s->synonyms();
614 $data->{molecular_weight} = $s->molecular_weight();
616 $c->stash->{rest} = { data => $data };
618 print STDERR "Found smid details...\n";
622 sub smid_dbxref :Chained('smid') PathPart('dbxrefs') Args(0) {
623 my $self = shift;
624 my $c = shift;
626 my $rs;
627 if ($c->stash->{compound_id}) {
628 $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Dbxref")->search( { 'compound_dbxrefs.compound_id' => $c->stash->{compound_id} }, { join => 'compound_dbxrefs' , { join => 'db' }});
630 else {
631 $c->stash->{rest} = { data => [] };
632 return;
635 my $data = [];
637 while (my $dbxref = $rs->next()) {
638 print STDERR "Retrieved: ". $dbxref->dbxref_id()."...\n";
640 my $db_name = "";
641 my $display_url = "";
643 if ($dbxref->db()) {
644 $db_name = $dbxref->db->name();
645 my $url = $dbxref->db->url();
646 my $urlprefix = $dbxref->db->urlprefix();
647 $display_url = join("", $urlprefix, $url, $dbxref->accession());
650 my $delete_link = "X";
652 if ($c->user()) {
653 $delete_link = "<a href=\"javascript:delete_dbxref(".$dbxref->dbxref_id().")\" ><font color=\"red\">X</font></a>";
655 push @$data, [ $db_name, $dbxref->accession(), $display_url , $delete_link ];
657 $c->stash->{rest} = { data => $data };
660 sub results : Chained('smid') PathPart('results') Args(0) {
661 my $self = shift;
662 my $c = shift;
664 my $experiment_type = $c->req->param("experiment_type");
666 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Experiment")->search( { compound_id => $c->stash->{compound_id}, experiment_type => $experiment_type } );
668 print STDERR "Retrieved ".$rs->count()." rows...\n";
669 my @data;
671 my $delete_link = "X";
672 while (my $row = $rs->next()) {
673 my $experiment_id = $row->experiment_id();
674 if ($c->user()) {
675 $delete_link = "<a href=\"javascript:delete_experiment($experiment_id)\"><font color=\"red\">X</font></a>";
678 if ($experiment_type eq "hplc_ms") {
679 my $json = $row->data();
680 my $hash = JSON::XS->new()->decode($json);
681 push @data, [ $hash->{hplc_ms_author}, $hash->{hplc_ms_method_type}, $hash->{hplc_ms_retention_time}, $hash->{hplc_ms_ionization_mode}, $hash->{hplc_ms_adducts_detected}, $hash->{hplc_ms_scan_number}, $hash->{hplc_ms_link}, $delete_link ];
683 if ($experiment_type eq "ms_spectrum") {
684 my $json = $row->data();
685 my $hash = JSON::XS->new()->decode($json);
686 push @data, [ $hash->{ms_spectrum_author}, $hash->{ms_spectrum_ionization_mode}, $hash->{ms_spectrum_collision_energy}, $hash->{ms_spectrum_adduct_fragmented}, "<a href=\"/experiment/".$row->experiment_id()."\">Details</a>", $hash->{ms_spectrum_link}, $delete_link ];
690 $c->stash->{rest} = { data => \@data };
694 sub compound_images :Chained('smid') PathPart('images') Args(1) {
695 my $self = shift;
696 my $c = shift;
697 my $size = shift;
699 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::CompoundImage")->search( { compound_id => $c->stash->{compound_id} });
701 my @source_tags;
702 while (my $row = $rs->next()) {
703 my $image = SMMID::Image->new( { schema => $c->model("SMIDDB"), image_id => $row->image_id() });
705 my $delete_link = "";
706 if ($c->user()) {
707 $delete_link = "<a href=\"javascript:delete_image(".$row->image_id().", ".$c->stash->{compound_id}.")\">X</a>";
711 my $file = "medium";
712 if ($size =~ m/thumbnail|small|medium|large/) { $file = $size.".png"; }
713 my $image_full_url = "/".$c->config->{image_url}."/".$image->image_subpath()."/".$file;
714 push @source_tags, "<img src=\"$image_full_url\" />$delete_link";
716 print STDERR "returning images for compound ".$c->stash->{compound_id} ." with size $size.\n";
717 $c->stash->{rest} = { html => \@source_tags };
720 =head1 AUTHOR
722 Lukas Mueller,,,
724 =head1 LICENSE
726 This library is free software, you can redistribute it and/or modify
727 it under the same terms as Perl itself.
729 =cut