Renderer.pm 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package Trog::Renderer;
  2. use strict;
  3. use warnings;
  4. no warnings 'experimental';
  5. use feature qw{signatures state};
  6. use Carp::Always;
  7. use Trog::Vars;
  8. use Trog::Log qw{:all};
  9. use Trog::Renderer::text;
  10. use Trog::Renderer::html;
  11. use Trog::Renderer::json;
  12. use Trog::Renderer::blob;
  13. use Trog::Renderer::css;
  14. =head1 Trog::Renderer
  15. Idea here is to have a renderer per known/supported content-type we need to output that is also theme-aware.
  16. We have an abstraction here, render() which you feed everything to.
  17. =cut;
  18. our %renderers = (
  19. text => \&Trog::Renderer::text::render,
  20. html => \&Trog::Renderer::html::render,
  21. json => \&Trog::Renderer::json::render,
  22. blob => \&Trog::Renderer::blob::render,
  23. xsl => \&Trog::Renderer::text::render,
  24. xml => \&Trog::Renderer::text::render,
  25. rss => \&Trog::Renderer::html::render,
  26. css => \&Trog::Renderer::css::render,
  27. );
  28. =head2 Trog::Renderer->render(%options)
  29. Returns either the 3-arg arrayref suitable to emit at the end of a PSGI session or a response body if the component field of options is truthy.
  30. The idea is that components will be concatenated to other rendered templates until we finish having everything ready.
  31. =cut
  32. sub render ($class, %options) {
  33. local $@;
  34. my $renderer;
  35. return _yeet($renderer, "Renderer requires a valid content type to be passed", %options) unless $options{contenttype};
  36. my $rendertype = $Trog::Vars::byct{$options{contenttype}};
  37. return _yeet($renderer, "Renderer requires a known content type (used $options{contenttype}) to be passed", %options) unless $rendertype;
  38. $renderer = $renderers{$rendertype};
  39. return _yeet($renderer, "Renderer for $rendertype is not defined!", %options) unless $renderer;
  40. return _yeet($renderer, "Status code not provided", %options) if !$options{code} && !$options{component};
  41. return _yeet($renderer, "Template data not provided", %options) unless $options{data};
  42. return _yeet($renderer, "Template not provided", %options) unless $options{template};
  43. #TODO future - save the components too and then compose them?
  44. my $skip_save = !$options{component} || !$options{data}{route} || $options{data}{has_query} || $options{data}{user} || ($options{code} // 0) != 200 || Trog::Log::is_debug();
  45. my $ret;
  46. local $@;
  47. eval {
  48. $ret = $renderer->(%options);
  49. save_render( $options{data}, $ret->[2], %{$ret->[1]}) unless $skip_save;
  50. 1;
  51. } or do {
  52. return _yeet($renderer, $@, %options);
  53. };
  54. return $ret;
  55. }
  56. sub _yeet ($renderer, $error, %options) {
  57. WARN($error);
  58. # All-else fails error page
  59. my $ret;
  60. local $@;
  61. eval {
  62. $ret = $renderer->(
  63. code => 500,
  64. template => '500.tx',
  65. contenttype => 'text/html',
  66. data => { %options, content => "<h1>500 Internal Server Error</h1>$error" },
  67. );
  68. 1;
  69. } or do {
  70. my $msg = $error;
  71. $msg .= " and subsequently during render of error template, $@" if $renderer;
  72. INFO("$options{data}{method} 500 $options{data}{route}");
  73. FATAL($msg);
  74. };
  75. return $ret;
  76. }
  77. sub save_render ( $vars, $body, %headers ) {
  78. Path::Tiny::path( "www/statics/" . dirname( $vars->{route} ) )->mkpath;
  79. my $file = "www/statics/$vars->{route}";
  80. my $verb = -f $file ? 'Overwrite' : 'Write';
  81. DEBUG("$verb static for $vars->{route}");
  82. open( my $fh, '>', $file ) or die "Could not open $file for writing";
  83. print $fh "HTTP/1.1 $vars->{code} OK\n";
  84. foreach my $h ( keys(%headers) ) {
  85. print $fh "$h:$headers{$h}\n" if $headers{$h};
  86. }
  87. print $fh "\n";
  88. print $fh $body;
  89. close $fh;
  90. }
  91. 1;