PageRenderTime 52ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Dancer2/Core/DSL.pm

http://github.com/PerlDancer/Dancer2
Perl | 533 lines | 398 code | 101 blank | 34 comment | 20 complexity | 6916f857de25b395b633990361231e43 MD5 | raw file
  1. # ABSTRACT: Dancer2's Domain Specific Language (DSL)
  2. package Dancer2::Core::DSL;
  3. use Moo;
  4. use Carp;
  5. use Module::Runtime 'require_module';
  6. use Ref::Util qw< is_arrayref >;
  7. use Dancer2::Core::Hook;
  8. use Dancer2::FileUtils;
  9. use Dancer2::Core::Response::Delayed;
  10. with 'Dancer2::Core::Role::DSL';
  11. sub hook_aliases { +{} }
  12. sub supported_hooks { () }
  13. sub _add_postponed_plugin_hooks {
  14. my ( $self, $postponed_hooks) = @_;
  15. $postponed_hooks = $postponed_hooks->{'plugin'};
  16. return unless defined $postponed_hooks;
  17. for my $plugin ( keys %{$postponed_hooks} ) {
  18. for my $name ( keys %{$postponed_hooks->{$plugin} } ) {
  19. my $hook = $postponed_hooks->{$plugin}{$name}{hook};
  20. my $caller = $postponed_hooks->{$plugin}{$name}{caller};
  21. $self->has_hook($name)
  22. or croak "plugin $plugin does not support the hook `$name'. ("
  23. . join( ", ", @{$caller} ) . ")";
  24. $self->add_hook($hook);
  25. }
  26. }
  27. }
  28. sub dsl_keywords {
  29. # the flag means : 1 = is global, 0 = is not global. global means can be
  30. # called from anywhere. not global means must be called from within a route
  31. # handler
  32. { any => { is_global => 1 },
  33. app => { is_global => 1 },
  34. captures => { is_global => 0 },
  35. config => { is_global => 1 },
  36. content => { is_global => 0 },
  37. content_type => { is_global => 0 },
  38. context => { is_global => 0 },
  39. cookie => { is_global => 0 },
  40. cookies => { is_global => 0 },
  41. dance => { is_global => 1 },
  42. dancer_app => { is_global => 1 },
  43. dancer_version => { is_global => 1 },
  44. dancer_major_version => { is_global => 1 },
  45. debug => { is_global => 1 },
  46. decode_json => { is_global => 1 },
  47. del => { is_global => 1 },
  48. delayed => {
  49. is_global => 0, prototype => '&@',
  50. },
  51. dirname => { is_global => 1 },
  52. done => { is_global => 0 },
  53. dsl => { is_global => 1 },
  54. encode_json => { is_global => 1 },
  55. engine => { is_global => 1 },
  56. error => { is_global => 1 },
  57. false => { is_global => 1 },
  58. flush => { is_global => 0 },
  59. forward => { is_global => 0 },
  60. from_dumper => { is_global => 1 },
  61. from_json => { is_global => 1 },
  62. from_yaml => { is_global => 1 },
  63. get => { is_global => 1 },
  64. halt => { is_global => 0 },
  65. header => { is_global => 0 },
  66. headers => { is_global => 0 },
  67. hook => { is_global => 1 },
  68. info => { is_global => 1 },
  69. log => { is_global => 1 },
  70. mime => { is_global => 1 },
  71. options => { is_global => 1 },
  72. param => { is_global => 0 },
  73. params => { is_global => 0 },
  74. query_parameters => { is_global => 0 },
  75. body_parameters => { is_global => 0 },
  76. route_parameters => { is_global => 0 },
  77. pass => { is_global => 0 },
  78. patch => { is_global => 1 },
  79. path => { is_global => 1 },
  80. post => { is_global => 1 },
  81. prefix => { is_global => 1 },
  82. prepare_app => {
  83. is_global => 1, prototype => '&',
  84. },
  85. psgi_app => { is_global => 1 },
  86. push_header => { is_global => 0 },
  87. push_response_header => { is_global => 0 },
  88. put => { is_global => 1 },
  89. redirect => { is_global => 0 },
  90. request => { is_global => 0 },
  91. request_data => { is_global => 0 },
  92. request_header => { is_global => 0 },
  93. response => { is_global => 0 },
  94. response_header => { is_global => 0 },
  95. response_headers => { is_global => 0 },
  96. runner => { is_global => 1 },
  97. send_as => { is_global => 0 },
  98. send_error => { is_global => 0 },
  99. send_file => { is_global => 0 },
  100. session => { is_global => 0 },
  101. set => { is_global => 1 },
  102. setting => { is_global => 1 },
  103. splat => { is_global => 0 },
  104. start => { is_global => 1 },
  105. status => { is_global => 0 },
  106. template => { is_global => 1 },
  107. to_app => { is_global => 1 },
  108. to_dumper => { is_global => 1 },
  109. to_json => { is_global => 1 },
  110. to_yaml => { is_global => 1 },
  111. true => { is_global => 1 },
  112. upload => { is_global => 0 },
  113. uri_for => { is_global => 0 },
  114. var => { is_global => 0 },
  115. vars => { is_global => 0 },
  116. warning => { is_global => 1 },
  117. };
  118. }
  119. sub dancer_app { shift->app }
  120. sub dancer_version { Dancer2->VERSION }
  121. sub dancer_major_version {
  122. return ( split /\./, dancer_version )[0];
  123. }
  124. sub log { shift->app->log( @_ ) }
  125. sub debug { shift->app->log( debug => @_ ) }
  126. sub info { shift->app->log( info => @_ ) }
  127. sub warning { shift->app->log( warning => @_ ) }
  128. sub error { shift->app->log( error => @_ ) }
  129. sub true {1}
  130. sub false {0}
  131. sub dirname { shift and Dancer2::FileUtils::dirname(@_) }
  132. sub path { shift and Dancer2::FileUtils::path(@_) }
  133. sub config { shift->app->settings }
  134. sub engine { shift->app->engine(@_) }
  135. sub setting { shift->app->setting(@_) }
  136. sub set { shift->setting(@_) }
  137. sub template { shift->app->template(@_) }
  138. sub session {
  139. my ( $self, $key, $value ) = @_;
  140. # shortcut reads if no session exists, so we don't
  141. # instantiate sessions for no reason
  142. if ( @_ == 2 ) {
  143. return unless $self->app->has_session;
  144. }
  145. my $session = $self->app->session
  146. || croak "No session available, a session engine needs to be set";
  147. $self->app->setup_session;
  148. # return the session object if no key
  149. @_ == 1 and return $session;
  150. # read if a key is provided
  151. @_ == 2 and return $session->read($key);
  152. # write to the session or delete if value is undef
  153. if ( defined $value ) {
  154. $session->write( $key => $value );
  155. }
  156. else {
  157. $session->delete($key);
  158. }
  159. }
  160. sub send_as { shift->app->send_as(@_) }
  161. sub send_error { shift->app->send_error(@_) }
  162. sub send_file { shift->app->send_file(@_) }
  163. #
  164. # route handlers & friends
  165. #
  166. sub hook {
  167. my ( $self, $name, $code ) = @_;
  168. $self->app->add_hook(
  169. Dancer2::Core::Hook->new( name => $name, code => $code ) );
  170. }
  171. sub prefix {
  172. my $app = shift->app;
  173. @_ == 1
  174. ? $app->prefix(@_)
  175. : $app->lexical_prefix(@_);
  176. }
  177. sub halt { shift->app->halt(@_) }
  178. sub del { shift->_normalize_route( [qw/delete /], @_ ) }
  179. sub get { shift->_normalize_route( [qw/get head/], @_ ) }
  180. sub options { shift->_normalize_route( [qw/options /], @_ ) }
  181. sub patch { shift->_normalize_route( [qw/patch /], @_ ) }
  182. sub post { shift->_normalize_route( [qw/post /], @_ ) }
  183. sub put { shift->_normalize_route( [qw/put /], @_ ) }
  184. sub prepare_app { push @{ shift->app->prep_apps }, @_ }
  185. sub any {
  186. my $self = shift;
  187. # If they've supplied their own list of methods,
  188. # expand del, otherwise give them the default list.
  189. if ( is_arrayref($_[0]) ) {
  190. s/^del$/delete/ for @{ $_[0] };
  191. }
  192. else {
  193. unshift @_, [qw/delete get head options patch post put/];
  194. }
  195. $self->_normalize_route(@_);
  196. }
  197. sub _normalize_route {
  198. my $app = shift->app;
  199. my $methods = shift;
  200. my %args;
  201. # Options are optional, deduce their presence from arg length.
  202. # @_ = ( REGEXP, OPTIONS, CODE )
  203. # or
  204. # @_ = ( REGEXP, CODE )
  205. @args{qw/regexp options code/} = @_ == 3 ? @_ : ( $_[0], {}, $_[1] );
  206. return map $app->add_route( %args, method => $_ ), @{$methods};
  207. }
  208. #
  209. # Server startup
  210. #
  211. # access to the runner singleton
  212. # will be populated on-the-fly when needed
  213. # this singleton contains anything needed to start the application server
  214. sub runner { Dancer2->runner }
  215. # start the server
  216. sub start { shift->runner->start }
  217. sub dance { shift->start(@_) }
  218. sub psgi_app {
  219. my $self = shift;
  220. $self->app->to_app;
  221. }
  222. sub to_app { shift->app->to_app }
  223. #
  224. # Response alterations
  225. #
  226. sub status {
  227. $Dancer2::Core::Route::RESPONSE->status( $_[1] );
  228. }
  229. sub push_header {
  230. carp "DEPRECATED: push_header keyword. Please use the 'push_response_header' keyword instead of 'push_header'";
  231. goto &push_response_header;
  232. }
  233. sub push_response_header {
  234. shift;
  235. $Dancer2::Core::Route::RESPONSE->push_header(@_);
  236. }
  237. sub header {
  238. carp "DEPRECATED: header keyword. Please use the 'response_header' keyword instead of 'header'";
  239. goto &response_header;
  240. }
  241. sub response_header {
  242. shift;
  243. $Dancer2::Core::Route::RESPONSE->header(@_);
  244. }
  245. sub headers {
  246. carp "DEPRECATED: headers keyword. Please use the 'response_headers' keyword instead of 'headers'";
  247. goto &response_headers;
  248. }
  249. sub response_headers {
  250. shift;
  251. $Dancer2::Core::Route::RESPONSE->header(@_);
  252. }
  253. sub content {
  254. my $dsl = shift;
  255. # simple synchronous response
  256. my $responder = $Dancer2::Core::Route::RESPONDER
  257. or croak 'Cannot use content keyword outside delayed response';
  258. # flush if wasn't flushed before
  259. if ( !$Dancer2::Core::Route::WRITER ) {
  260. $Dancer2::Core::Route::WRITER = $responder->([
  261. $Dancer2::Core::Route::RESPONSE->status,
  262. $Dancer2::Core::Route::RESPONSE->headers_to_array,
  263. ]);
  264. }
  265. eval {
  266. $Dancer2::Core::Route::WRITER->write(@_);
  267. 1;
  268. } or do {
  269. my $error = $@ || 'Zombie Error';
  270. $Dancer2::Core::Route::ERROR_HANDLER
  271. ? $Dancer2::Core::Route::ERROR_HANDLER->($error)
  272. : $dsl->app->logger_engine->log(
  273. warning => "Error in delayed response: $error"
  274. );
  275. };
  276. }
  277. sub content_type {
  278. shift;
  279. $Dancer2::Core::Route::RESPONSE->content_type(@_);
  280. }
  281. sub delayed {
  282. my ( $dsl, $cb, @args ) = @_;
  283. @args % 2 == 0
  284. or croak 'Arguments to delayed() keyword must be key/value pairs';
  285. # first time, responder doesn't exist yet
  286. my %opts = @args;
  287. $Dancer2::Core::Route::RESPONDER
  288. or return Dancer2::Core::Response::Delayed->new(
  289. cb => $cb,
  290. request => $Dancer2::Core::Route::REQUEST,
  291. response => $Dancer2::Core::Route::RESPONSE,
  292. ( error_cb => $opts{'on_error'} )x!! $opts{'on_error'},
  293. );
  294. # we're in an async request process
  295. my $request = $Dancer2::Core::Route::REQUEST;
  296. my $response = $Dancer2::Core::Route::RESPONSE;
  297. my $responder = $Dancer2::Core::Route::RESPONDER;
  298. my $writer = $Dancer2::Core::Route::WRITER;
  299. my $handler = $Dancer2::Core::Route::ERROR_HANDLER;
  300. return sub {
  301. local $Dancer2::Core::Route::REQUEST = $request;
  302. local $Dancer2::Core::Route::RESPONSE = $response;
  303. local $Dancer2::Core::Route::RESPONDER = $responder;
  304. local $Dancer2::Core::Route::WRITER = $writer;
  305. local $Dancer2::Core::Route::ERROR_HANDLER = $handler;
  306. $cb->(@_);
  307. };
  308. }
  309. sub flush {
  310. my $responder = $Dancer2::Core::Route::RESPONDER
  311. or croak 'flush() called outside streaming response';
  312. my $response = $Dancer2::Core::Route::RESPONSE;
  313. $Dancer2::Core::Route::WRITER = $responder->([
  314. $response->status, $response->headers_to_array,
  315. ]);
  316. }
  317. sub done {
  318. my $writer = $Dancer2::Core::Route::WRITER
  319. or croak 'done() called outside streaming response';
  320. $writer->close;
  321. }
  322. sub pass { shift->app->pass }
  323. #
  324. # Route handler helpers
  325. #
  326. sub context {
  327. carp "DEPRECATED: context keyword. Please use the 'app' keyword instead of 'context'";
  328. shift->app;
  329. }
  330. sub request { $Dancer2::Core::Route::REQUEST }
  331. sub request_header { shift; $Dancer2::Core::Route::REQUEST->headers->header(@_) }
  332. sub response { $Dancer2::Core::Route::RESPONSE }
  333. sub upload { shift; $Dancer2::Core::Route::REQUEST->upload(@_); }
  334. sub captures { $Dancer2::Core::Route::REQUEST->captures }
  335. sub uri_for { shift; $Dancer2::Core::Route::REQUEST->uri_for(@_); }
  336. sub splat { $Dancer2::Core::Route::REQUEST->splat }
  337. sub params { shift; $Dancer2::Core::Route::REQUEST->params(@_); }
  338. sub param { shift; $Dancer2::Core::Route::REQUEST->param(@_); }
  339. sub query_parameters { shift; $Dancer2::Core::Route::REQUEST->query_parameters(@_); }
  340. sub body_parameters { shift; $Dancer2::Core::Route::REQUEST->body_parameters(@_); }
  341. sub route_parameters { shift; $Dancer2::Core::Route::REQUEST->route_parameters(@_); }
  342. sub request_data { shift; $Dancer2::Core::Route::REQUEST->body_data(@_); }
  343. sub redirect { shift->app->redirect(@_) }
  344. sub forward { shift->app->forward(@_) }
  345. sub vars { $Dancer2::Core::Route::REQUEST->vars }
  346. sub var { shift; $Dancer2::Core::Route::REQUEST->var(@_); }
  347. sub cookies { $Dancer2::Core::Route::REQUEST->cookies }
  348. sub cookie { shift->app->cookie(@_) }
  349. sub mime {
  350. my $self = shift;
  351. if ( $self->app ) {
  352. return $self->app->mime_type;
  353. }
  354. else {
  355. my $runner = $self->runner;
  356. $runner->mime_type->reset_default;
  357. return $runner->mime_type;
  358. }
  359. }
  360. #
  361. # engines
  362. #
  363. sub from_json {
  364. shift; # remove first element
  365. require_module('Dancer2::Serializer::JSON');
  366. Dancer2::Serializer::JSON::from_json(@_);
  367. }
  368. sub to_json {
  369. shift; # remove first element
  370. require_module('Dancer2::Serializer::JSON');
  371. Dancer2::Serializer::JSON::to_json(@_);
  372. }
  373. sub decode_json {
  374. shift; # remove first element
  375. require_module('Dancer2::Serializer::JSON');
  376. Dancer2::Serializer::JSON::decode_json(@_);
  377. }
  378. sub encode_json {
  379. shift; # remove first element
  380. require_module('Dancer2::Serializer::JSON');
  381. Dancer2::Serializer::JSON::encode_json(@_);
  382. }
  383. sub from_yaml {
  384. shift; # remove first element
  385. require_module('Dancer2::Serializer::YAML');
  386. Dancer2::Serializer::YAML::from_yaml(@_);
  387. }
  388. sub to_yaml {
  389. shift; # remove first element
  390. require_module('Dancer2::Serializer::YAML');
  391. Dancer2::Serializer::YAML::to_yaml(@_);
  392. }
  393. sub from_dumper {
  394. shift; # remove first element
  395. require_module('Dancer2::Serializer::Dumper');
  396. Dancer2::Serializer::Dumper::from_dumper(@_);
  397. }
  398. sub to_dumper {
  399. shift; # remove first element
  400. require_module('Dancer2::Serializer::Dumper');
  401. Dancer2::Serializer::Dumper::to_dumper(@_);
  402. }
  403. 1;
  404. __END__
  405. =func setting
  406. Lets you define settings and access them:
  407. setting('foo' => 42);
  408. setting('foo' => 42, 'bar' => 43);
  409. my $foo=setting('foo');
  410. If settings were defined returns number of settings.
  411. =func set ()
  412. alias for L<setting>:
  413. set('foo' => '42');
  414. my $port=set('port');
  415. =head1 SEE ALSO
  416. L<http://advent.perldancer.org/2010/18>
  417. =cut