Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'release/1.3029_01'

  • Loading branch information...
commit fe91f07443819ede3a8e642e4fd840ed121e453f 2 parents 61aa0c0 + a11f7f5
Alexis Sukrieh authored
Showing with 3,560 additions and 1,090 deletions.
  1. +40 −7 .gitignore
  2. +1 −0  AUTHORS
  3. +238 −0 CHANGES
  4. +28 −2 MANIFEST
  5. +1 −1  Makefile.PL
  6. +280 −135 lib/Dancer.pm
  7. +35 −17 lib/Dancer/Config.pm
  8. +166 −38 lib/Dancer/Cookbook.pod
  9. +89 −16 lib/Dancer/Cookie.pm
  10. +17 −14 lib/Dancer/Cookies.pm
  11. +31 −7 lib/Dancer/Deployment.pod
  12. +83 −0 lib/Dancer/Deprecation.pm
  13. +4 −4 lib/Dancer/Development/Integration.pod
  14. +14 −14 lib/Dancer/Error.pm
  15. +47 −13 lib/Dancer/FileUtils.pm
  16. +38 −16 lib/Dancer/Handler.pm
  17. +6 −2 lib/Dancer/Handler/PSGI.pm
  18. +0 −112 lib/Dancer/Helpers.pm
  19. +28 −4 lib/Dancer/Logger.pm
  20. +1 −0  lib/Dancer/Logger/Abstract.pm
  21. +76 −0 lib/Dancer/Logger/Capture.pm
  22. +76 −0 lib/Dancer/Logger/Capture/Trap.pm
  23. +61 −0 lib/Dancer/Logger/Diag.pm
  24. +35 −17 lib/Dancer/Logger/File.pm
  25. +64 −0 lib/Dancer/Logger/Note.pm
  26. +42 −0 lib/Dancer/Logger/Null.pm
  27. +127 −45 lib/Dancer/MIME.pm
  28. +15 −1 lib/Dancer/Object.pm
  29. +5 −1 lib/Dancer/Plugin.pm
  30. +36 −42 lib/Dancer/Renderer.pm
  31. +83 −19 lib/Dancer/Request.pm
  32. +20 −0 lib/Dancer/Request/Upload.pm
  33. +80 −130 lib/Dancer/Response.pm
  34. +19 −20 lib/Dancer/Route.pm
  35. +3 −2 lib/Dancer/Route/Cache.pm
  36. +1 −1  lib/Dancer/Route/Registry.pm
  37. +5 −1 lib/Dancer/Serializer/JSON.pm
  38. +2 −0  lib/Dancer/Session.pm
  39. +40 −26 lib/Dancer/Session/Abstract.pm
  40. +4 −2 lib/Dancer/Session/YAML.pm
  41. +15 −2 lib/Dancer/SharedData.pm
  42. +48 −1 lib/Dancer/Template/Abstract.pm
  43. +53 −5 lib/Dancer/Template/TemplateToolkit.pm
  44. +110 −35 lib/Dancer/Test.pm
  45. +1 −1  lib/Dancer/Tutorial.pod
  46. +56 −10 script/dancer
  47. +1 −1  t/00_base/003_syntax.t
  48. +17 −0 t/00_base/008_export.t
  49. +15 −0 t/00_base/009_syntax_export.t
  50. +9 −0 t/00_base/010_export_script.t
  51. +244 −0 t/00_base/14_changelog.t
  52. +21 −2 t/00_base/dancer_test.t
  53. +9 −8 t/01_config/02_mime_type.t
  54. +14 −13 t/01_config/03_logger.t
  55. +1 −1  t/01_config/04_config_file.t
  56. +1 −1  t/01_config/06_config_api.t
  57. +5 −5 t/01_config/06_stack_trace.t
  58. +25 −0 t/02_request/04_forward.t
  59. +5 −2 t/02_request/11_accessors.t
  60. +23 −12 t/02_request/14_uploads.t
  61. +1 −1  t/02_request/15_headers.t
  62. +31 −0 t/02_request/17_uri_base.t
  63. +2 −1  t/03_route_handler/00_route_object.t
  64. +1 −1  t/03_route_handler/02_before_filter.t
  65. +7 −3 t/03_route_handler/03_passing.t
  66. +5 −14 t/03_route_handler/04_wildcards.t
  67. +1 −7 t/03_route_handler/06_regexp.t
  68. +2 −2 t/03_route_handler/07_compilation_warning.t
  69. +3 −3 t/03_route_handler/11_redirect.t
  70. +22 −0 t/03_route_handler/11_redirect_absolute.t
  71. +28 −37 t/03_route_handler/12_response.t
  72. +2 −0  t/03_route_handler/13_any_route_handler.t
  73. +4 −6 t/03_route_handler/18_auto_page.t
  74. +1 −1  t/03_route_handler/28_plack_mount.t
  75. +13 −3 t/03_route_handler/29_forward.t
  76. +1 −1  t/03_route_handler/29_redirect_immediately.t
  77. +1 −1  t/03_route_handler/30_bug_gh190.t
  78. +15 −0 t/03_route_handler/32_gh_393.t
  79. +41 −0 t/03_route_handler/33_vars.t
  80. +39 −13 t/04_static_file/002_mime_types.t
  81. +1 −1  t/04_static_file/003_mime_types_reinit.t
  82. +5 −6 t/04_static_file/03_get_mime_type.t
  83. +5 −13 t/05_views/002_view_rendering.t
  84. +5 −12 t/05_views/03_layout.t
  85. +25 −3 t/06_helpers/01_send_file.t
  86. +5 −15 t/06_helpers/03_content_type.t
  87. +5 −10 t/06_helpers/04_status.t
  88. +1 −1  t/08_session/03_http_requests.t
  89. +1 −1  t/08_session/05_yaml.t
  90. +32 −0 t/08_session/11_session_secure.t
  91. +27 −0 t/08_session/12_session_name.t
  92. +1 −24 t/09_cookies/02_cookie_object.t
  93. +1 −1  t/09_cookies/03_persistence.t
  94. +0 −32 t/09_cookies/04_has_changed.t
  95. +32 −0 t/09_cookies/04_secure.t
  96. +59 −0 t/09_cookies/06_expires.t
  97. +4 −1 t/10_template/05_template_toolkit.t
  98. +21 −1 t/10_template/06_before_template_hook.t
  99. +3 −0  t/10_template/views/layouts/main.tt
  100. +1 −1  t/11_logger/02_factory.t
  101. +2 −2 t/11_logger/03_file.t
  102. +26 −0 t/11_logger/06_null.t
  103. +28 −0 t/11_logger/07_diag.t
  104. +32 −0 t/11_logger/08_serialize.t
  105. +26 −0 t/11_logger/09_capture.t
  106. +28 −0 t/11_logger/10_note.t
  107. +4 −3 t/12_response/01_CRLF_injection.t
  108. +24 −43 t/12_response/02_headers.t
  109. +6 −6 t/12_response/03_charset.t
  110. +4 −2 t/12_response/05_api.t
  111. +4 −1 t/12_response/06_filter_halt_status.t
  112. +41 −0 t/12_response/07_cookies.t
  113. +52 −0 t/12_response/08_drop_content.t
  114. +22 −0 t/12_response/09_headers_to_array.t
  115. +4 −4 t/14_serializer/02_json.t
  116. +23 −0 t/14_serializer/16_bug_gh_299.t
  117. +1 −1  t/15_plugins/02_config.t
  118. +12 −2 t/17_apps/03_prefix.t
  119. +1 −1  t/19_dancer/01_script.t
  120. +1 −1  t/19_dancer/02_script_version_from.t
  121. +77 −0 t/20_deprecation/01_api.t
  122. +74 −0 t/21_dependents/Dancer-Session-Cookie.t
View
47 .gitignore
@@ -1,12 +1,6 @@
-Makefile
-.last_cover_stats
-MANIFEST.bak
+# Dancer Specific
*.old
-*.swp
*~
-blib
-pm_to_blib
-cover_db
example/logs
t/*/logs
t/*/sessions
@@ -14,3 +8,42 @@ logs
TestApp
t/sessions/
tags
+MYMETA.yml
+
+
+# From: https://github.com/github/gitignore/blob/master/Global/Linux.gitignore
+.*
+!.gitignore
+*~
+*.sw[a-p]
+.directory
+
+
+# From: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+Thumbs.db
+Desktop.ini
+
+
+# From https://github.com/github/gitignore/blob/master/Global/OSX.gitignore
+.DS_Store
+Icon?
+._*
+.Spotlight-V100
+.Trashes
+
+
+# From https://github.com/github/gitignore/blob/master/Perl.gitignore
+blib/
+_build/
+cover_db/
+inc/
+Build
+Build.bat
+.last_cover_stats
+Makefile
+Makefile.old
+MANIFEST.bak
+META.yml
+MYMETA.yml
+nytprof.out
+pm_to_blib
View
1  AUTHORS
@@ -5,3 +5,4 @@
Sawyer X <xsawyerx@cpan.org>
Franck Cuny <franck@lumberjaph.net>
Naveed Massjouni <naveedm9@gmail.com>
+ Damien 'dams' Krotkine <dams@cpan.org>
View
238 CHANGES
@@ -1,3 +1,241 @@
+1.3029_01 01.04.2011
+
+ [ BUG FIXES ]
+ * Fix t/14_serializer/02_json.t to work with older JSON (relates to GH#416)
+ (Damien Krotkine)
+ * the Changelog test now supports Codenames. It suggests to add codenames
+ for table releaes as well
+ (Damien Krotkine)
+ * GH #420: Extra content generated
+ (Alberto Simões, Damien Krotkine)
+ * GH #409: If pass has no more matching routes, return 404.
+ (Alberto Simões)
+
+ [ ENHANCEMENTS ]
+ * GH #396: Test that Dancer::Session::Cookie isn't broken
+ (Michael G. Schwern)
+ * GH #399: Make sure session can have their name changed.
+ (Michael G. Schwern)
+ * Dancer::Test tests assumes 'GET' if their first argument is scalar.
+ (Yanick Champoux)
+ * send_file accepts optional content-type declaration, to override guessed
+ MIME type, e.g. send_file $filename, content_type => 'image/png'
+ (Alberto Simões, requested by Michael G Schwern)
+ * send_file accepts optional absolute option, to send an absolute path
+ (Alberto Simões)
+ * Have `dancer` cmd tool create MANIFEST and MANIFEST.SKIP.
+ (Alberto Simões)
+ * mime_type is deprecated; new keyword 'mime'; new config key
+ 'default_mime_type';
+ (Alberto Simões and Michael G. Schwern)
+ * Recognize absolute redirects
+ (Yanick Champoux)
+
+ [ DOCUMENTATION ]
+ * Add documentation to send_file optional argument
+ (Alberto Simões)
+ * Fix plack_middlewares example in the cookbook
+ (Michael G. Schwern)
+ * Extend the POD on plugin_setting to prevent a pitfall with plugin
+ modules more than 3 levels deep.
+ (Stefan Hornburg)
+ * GH #400: Documenting the plack_middlewares_map.
+ (Michael G. Schwern, Sawyer X)
+ * GH #422: Documenting no need for caret when you have a prefix.
+ (Sawyer X)
+
+1.3020 21.03.2011
+ ** Codename: The Schwern Cometh // Michael G. Schwern **
+
+ [ ENHANCEMENTS ]
+ * No functional changes, just releasing as stable.
+
+1.3019_02 14.03.2011
+
+ [ BUG FIXES ]
+ * GH #354: Tokens are not passed to layout if no params are passed to
+ template.
+ (Damien Krotkine)
+
+1.3019_01 13.03.2011
+
+ [ BUG FIXES ]
+ * GH #393: Reset vars for each new request.
+ (Franck Cuny)
+
+ [ ENHANCEMENTS ]
+ * GH #391: Dancer::Logger::Note now exists. :)
+ (Sawyer X)
+ * Porting documentation on WRAPPER to Dancer::Template::TemplateToolkit.
+ (Sawyer X)
+ * GH #387: Document views and appdir in Dancer::Config.
+ (Michael G. Schwern)
+ * Add a new symbol to exporter ':script'.
+ (Franck Cuny)
+ * GH #397: Support cookie expire times like "+2h".
+ (Michael G. Schwern)
+
+1.3014_01 10.03.2011
+
+ [ BUG FIXES ]
+ * GH #373: Display valid path to the main app file in the welcome screen.
+ (Franck Cuny)
+ * GH #152, GH #170, GH #362: Log dir is not created when logger is not set
+ to 'file', and setting log_path works as expected.
+ (Franck Cuny)
+ * GH #308: Use request's uri_base.
+ (Sawyer X)
+ * GH #378: Some routes with prefix where wrongly matched.
+ (Franck Cuny)
+
+ [ ENHANCEMENTS ]
+ * GH #351: Explicitly exclude some keywords when important Dancer's syntax,
+ add modes that also excludes some keywords (:moose, :tests).
+ (Sawyer X, Naveed Massjouni, Michael G. Schwern, Franck Cuny)
+ * All logging options accept any number of variables.
+ (Sawyer X)
+ * GH #297: All logging options can automatically serialize references.
+ (Sawyer X)
+ * Add Dancer::Logger::Capture to capture and read log messages during
+ testing.
+ (Michael G. Schwern)
+ * Dancer::Cookie make secure (https only) cookies. It also adds the
+ "session_secure" setting instructing sessions to use secure cookies.
+ (Michael G. Schwern)
+ * Adding uri_base to Request.pm.
+ (Sawyer X)
+ * Make Dancer::Test use the capture logger
+ (Michael G. Schwern)
+
+ [ DOCUMENTATION ]
+ * Dancing on command line.
+ (Maurice Mengel)
+ * Improve Dancer::Cookbook.
+ (Maurice Mengel)
+
+1.3014 04.03.2011
+
+ [ BUG FIXES ]
+ * YAML Session UTF-8 Fix
+ (Roman Galeev)
+ * Tests and documentations for Dancer::Request::Upload + type method in
+ Dancer::Request::Upload
+ (Michael G. Schwern)
+ * Dancer::Test::dancer_response handles correctly its 'body' parameter
+ We can now pass a hash ref as the body of dancer_response, it will
+ automatically be serialized as an URL-encoded string with the appropriate
+ content_type header.
+ (Alexis Sukrieh)
+
+1.3013 01.03.2011
+
+ [ ENHANCEMENTS ]
+ * Fix test suite: the changelog test is skipped if not under
+ RELEASE_TESTING environment.
+
+1.3012 01.03.2011
+
+ [ BUG FIXES ]
+ * Fix cookies disappearing when more than one is set.
+ Complete refactoring of the cookie handling.
+ (Chris Andrews, Geistteufel)
+ * Properly set the settings in Dancer::Test only after config loading.
+ (Sawyer X)
+ * Fix possible loss of last directory in path.
+ (Sawyer X)
+ * No need for default upper directory in Dancer::Test. This fixes an issue
+ raised on the list about the default scaffolded test failing.
+ (Sawyer X)
+ * Fix anti UNC substitution under Cygwin
+ (Rowan Thorpe)
+ * GH#299 Return appropriate headers on HEAD request (content-type, ...)
+ (franck cuny)
+ * Use the dancer_version variable in scaffolded app.
+ (Sawyer X, reported by Brian E. Lozier)
+
+ [ ENHANCEMENTS ]
+ * Fix manifest
+ (Damien Krotkine)
+ * Various packaging, changelog and test fixes
+ (Damien Krotkine)
+ * Add a new accessor to Dancer::Request: ->uri.
+ (it's an alias to ->request_uri)
+ (Franck Cuny)
+ * Removes Dancer::Helpers, refactor Dancer.pm accordingly.
+ (Franck Cuny)
+ * Introduce changelog test of hell.
+ (Damien Krotkine)
+ * Add Dancer::Logger::Null.
+ (Sawyer X)
+ * Add Dancer::Logger::Diag.
+ (Sawyer X)
+ * Refactor Dancer::Response
+ (franck cuny)
+ * Allow to use a subclass of Template::Toolkit.
+ (Michael G. Schwern)
+ * Dancer::Test now uses Dancer::Logger::Null instead of ::File.
+ (Sawyer X)
+ * Add Dancer::Deprecation. (handle deprecation messages)
+ (franck cuny)
+ * Introduce new timestamp format in logger (%T)
+ (Roman Galeev)
+ * Refactoring of the forward method
+ (Alex Kalderimis)
+ * Refactoring of internal objects in the core,
+ use more of Dancer::Object. Introduce attributes_defaults
+ (Damien Krotkine)
+ * Add a perl_version variable to all templates, used in scaffolded app.
+ (Sawyer X, reported by Brian E. Lozier)
+ * Better output when template file is missing.
+ (Brian E. Lozier, Sawyer X)
+
+ [ DOCUMENTATION ]
+ * Add missing methods (e.g. "referer"), sorting, clean up.
+ (Flavio Poletti)
+ * Complete working example of deployment under Nginx/Starman in
+ Deployment.pod
+ (Geistteufel)
+
+1.3010_01 12.02.2011
+
+ [ BUG FIXES ]
+ * GH#136: fix again Mime::Type issues in preforking environment
+ (Chris Andrews)
+ * GH#220: fix for path issues under MacOS X and Windows platforms.
+ A new function is provided by Dancer::FileUtils: path_no_verify()
+ (Rowan Thorpe)
+ * Fix for infinite loops detection in before filters
+ (Flavio Poletti)
+
+ [ ENHANCEMENTS ]
+ * Better detection of the application layout under non-UNIX platforms.
+ (Rowan Thorpe, Alexis Sukrieh)
+
+ [ DOCUMENTATION ]
+ * Fix a typo in Dancer::Request::Upload's POD
+ (Rowan Thorpe)
+ * Better documentation for the before filters, explanations about the
+ potential infinite loops that can happen when using before filters (and
+ what Dancer does in that case).
+ (Flavio Poletti)
+
+1.3011 14.02.2011
+
+ [ BUG FIXES ]
+ * Set binmode in write_data_to_file() to fix image corruption in
+ Windows
+ (Rowan Thorpe)
+ * GH#319, GH#278, GH#276, GH#217: Fix file issues on Cygwin and
+ Win32 platforms
+ (Rowan Thorpe)
+ * GH#322: Detect errors in scaffolded dispatchers
+ (Alberto Simões)
+ * Fix tests so that they don't fail if JSON is not installed
+ (Damien Krotkine)
+
+ [ DOCUMENTATION ]
+ * Small spaces fix (Alberto Simões).
+
1.3010_01 12.02.2011
[ BUG FIXES ]
View
30 MANIFEST
@@ -17,6 +17,7 @@ lib/Dancer/Cookbook.pod
lib/Dancer/Cookie.pm
lib/Dancer/Cookies.pm
lib/Dancer/Deployment.pod
+lib/Dancer/Deprecation.pm
lib/Dancer/Development.pod
lib/Dancer/Development/Integration.pod
lib/Dancer/Engine.pm
@@ -27,13 +28,17 @@ lib/Dancer/Handler.pm
lib/Dancer/Handler/Debug.pm
lib/Dancer/Handler/PSGI.pm
lib/Dancer/Handler/Standalone.pm
-lib/Dancer/Helpers.pm
lib/Dancer/HTTP.pm
lib/Dancer/Introduction.pod
lib/Dancer/Logger.pm
lib/Dancer/Logger/Abstract.pm
+lib/Dancer/Logger/Capture.pm
+lib/Dancer/Logger/Capture/Trap.pm
lib/Dancer/Logger/Console.pm
+lib/Dancer/Logger/Diag.pm
lib/Dancer/Logger/File.pm
+lib/Dancer/Logger/Note.pm
+lib/Dancer/Logger/Null.pm
lib/Dancer/MIME.pm
lib/Dancer/ModuleLoader.pm
lib/Dancer/Object.pm
@@ -81,12 +86,16 @@ t/00_base/003_syntax.t
t/00_base/004_args.t
t/00_base/005_module_loader.t
t/00_base/007_load_syntax.t
+t/00_base/008_export.t
+t/00_base/009_syntax_export.t
+t/00_base/010_export_script.t
t/00_base/06_dancer_object.t
t/00_base/08_pod_coverage_dancer.t
t/00_base/09_load_app.t
t/00_base/11_file_utils.t
t/00_base/12_utf8_charset.t
t/00_base/13_dancer_singleton.t
+t/00_base/14_changelog.t
t/00_base/config.t
t/00_base/dancer_test.t
t/00_base/lib/AppWithError.pm
@@ -108,6 +117,7 @@ t/02_request/01_load.t
t/02_request/02_get_params.t
t/02_request/03_post_params.t
t/02_request/04_custom.t
+t/02_request/04_forward.t
t/02_request/05_cgi_pm_compat.t
t/02_request/06_init_env.t
t/02_request/07_raw_data.t
@@ -119,6 +129,7 @@ t/02_request/13_ajax.t
t/02_request/14_uploads.t
t/02_request/15_headers.t
t/02_request/16_delete.t
+t/02_request/17_uri_base.t
t/03_route_handler/000_create_fake_env.t
t/03_route_handler/00_http_methods.t
t/03_route_handler/00_route_object.t
@@ -152,6 +163,8 @@ t/03_route_handler/29_forward.t
t/03_route_handler/29_redirect_immediately.t
t/03_route_handler/30_bug_gh190.t
t/03_route_handler/31_infinite_loop.t
+t/03_route_handler/32_gh_393.t
+t/03_route_handler/33_vars.t
t/03_route_handler/public/404.html
t/03_route_handler/views/hello.tt
t/04_static_file/001_base.t
@@ -198,12 +211,14 @@ t/08_session/07_session_expires.t
t/08_session/08_simple.t
t/08_session/09_session.t
t/08_session/10_filter.t
+t/08_session/11_session_secure.t
t/09_cookies/000_create_fake_env.t
t/09_cookies/01_use.t
t/09_cookies/02_cookie_object.t
t/09_cookies/03_persistence.t
-t/09_cookies/04_has_changed.t
+t/09_cookies/04_secure.t
t/09_cookies/05_api.t
+t/09_cookies/06_expires.t
t/10_template/000_create_fake_env.t
t/10_template/01_factory.t
t/10_template/02_abstract_class.t
@@ -213,12 +228,18 @@ t/10_template/06_before_template_hook.t
t/10_template/index.txt
t/10_template/template.t
t/10_template/views/index.tt
+t/10_template/views/layouts/main.tt
t/11_logger/000_create_fake_env.t
t/11_logger/01_abstract.t
t/11_logger/02_factory.t
t/11_logger/03_file.t
t/11_logger/04_console.t
t/11_logger/05_format.t
+t/11_logger/06_null.t
+t/11_logger/07_diag.t
+t/11_logger/08_serialize.t
+t/11_logger/09_capture.t
+t/11_logger/10_note.t
t/12_response/000_create_fake_env.t
t/12_response/01_CRLF_injection.t
t/12_response/02_headers.t
@@ -226,6 +247,9 @@ t/12_response/03_charset.t
t/12_response/04_charset_server.t
t/12_response/05_api.t
t/12_response/06_filter_halt_status.t
+t/12_response/07_cookies.t
+t/12_response/08_drop_content.t
+t/12_response/09_headers_to_array.t
t/13_engines/00_load.t
t/13_engines/02_template_init.t
t/14_serializer/000_create_fake_env.t
@@ -244,6 +268,7 @@ t/14_serializer/12_bug_gh106.t
t/14_serializer/13_xml.t
t/14_serializer/14_api.t
t/14_serializer/14_show_errors.t
+t/14_serializer/16_bug_gh_299.t
t/14_serializer/handler-helper.t
t/15_plugins/000_create_fake_env.t
t/15_plugins/01_register.t
@@ -267,6 +292,7 @@ t/18_main_dsl/01_config.t
t/18_main_dsl/uri_for.t
t/19_dancer/01_script.t
t/19_dancer/02_script_version_from.t
+t/20_deprecation/01_api.t
t/lib/EasyMocker.pm
t/lib/Forum.pm
t/lib/LinkBlocker.pm
View
2  Makefile.PL
@@ -32,7 +32,7 @@ WriteMakefile1(
},
BUILD_REQUIRES => {
- 'Test::More' => '0.88',
+ 'Test::More' => '0.94',
},
PREREQ_PM => {
View
415 lib/Dancer.pm
@@ -3,22 +3,19 @@ package Dancer;
use strict;
use warnings;
use Carp;
-use Cwd 'abs_path', 'realpath';
+use Cwd 'realpath';
-use vars qw($VERSION $AUTHORITY @EXPORT);
-
-$VERSION = '1.3010_01';
-$AUTHORITY = 'SUKRIA';
+our $VERSION = '1.3029_01';
+our $AUTHORITY = 'SUKRIA';
+use Dancer::App;
use Dancer::Config;
+use Dancer::Cookies;
use Dancer::FileUtils;
use Dancer::GetOpt;
use Dancer::Error;
-use Dancer::Helpers;
use Dancer::Logger;
-use Dancer::Plugin;
use Dancer::Renderer;
-use Dancer::Response;
use Dancer::Route;
use Dancer::Serializer::JSON;
use Dancer::Serializer::YAML;
@@ -27,14 +24,13 @@ use Dancer::Serializer::Dumper;
use Dancer::Session;
use Dancer::SharedData;
use Dancer::Handler;
-use Dancer::ModuleLoader;
use Dancer::MIME;
+
use File::Spec;
-use File::Basename 'basename';
use base 'Exporter';
-@EXPORT = qw(
+our @EXPORT = qw(
after
any
before
@@ -57,11 +53,13 @@ use base 'Exporter';
get
halt
header
+ push_header
headers
layout
load
load_app
logger
+ mime
mime_type
options
params
@@ -106,77 +104,109 @@ sub before_template { Dancer::Route::Registry->hook('before_template', @_) }
sub captures { Dancer::SharedData->request->params->{captures} }
sub cookies { Dancer::Cookies->cookies }
sub config { Dancer::Config::settings() }
-sub content_type { Dancer::Response->content_type(@_) }
-sub dance { Dancer::start(@_) }
+sub content_type { Dancer::SharedData->response->content_type(@_) }
+sub dance { goto &start }
sub debug { goto &Dancer::Logger::debug }
+sub del { Dancer::App->current->registry->universal_add('delete', @_) }
sub dirname { Dancer::FileUtils::dirname(@_) }
sub engine { Dancer::Engine->engine(@_) }
sub error { goto &Dancer::Logger::error }
-sub send_error { Dancer::Helpers->error(@_) }
sub false { 0 }
-sub forward { Dancer::Response->forward(shift) }
+sub forward { Dancer::SharedData->response->forward(shift) }
sub from_dumper { Dancer::Serializer::Dumper::from_dumper(@_) }
sub from_json { Dancer::Serializer::JSON::from_json(@_) }
-sub from_yaml { Dancer::Serializer::YAML::from_yaml(@_) }
sub from_xml { Dancer::Serializer::XML::from_xml(@_) }
+sub from_yaml { Dancer::Serializer::YAML::from_yaml(@_) }
sub get { map { my $r = $_; Dancer::App->current->registry->universal_add($r, @_) } qw(head get) }
-sub halt { Dancer::Response->halt(@_) }
-sub headers { Dancer::Response->headers(@_) }
+sub halt { Dancer::SharedData->response->halt(@_) }
sub header { goto &headers }
+sub push_header { Dancer::SharedData->response->push_header(@_); }
+sub headers { Dancer::SharedData->response->headers(@_); }
sub layout { set(layout => shift) }
sub load { require $_ for @_ }
+sub load_app { goto &_load_app } # goto doesn't add a call frame. So caller() will work as expected
sub logger { set(logger => @_) }
+sub mime { Dancer::MIME->instance() }
sub mime_type {
- my $mime = Dancer::MIME->instance();
- if (scalar(@_)==2) { $mime->add_mime_type(@_) }
- elsif (scalar(@_)==1) { $mime->mime_type_for(@_) }
- else { $mime->aliases }
+ Dancer::Deprecation->deprecated(reason => "use 'mime' from Dancer.pm",fatal => 1)
}
+sub options { Dancer::App->current->registry->universal_add('options', @_) }
sub params { Dancer::SharedData->request->params(@_) }
-sub pass { Dancer::Response->pass }
+sub pass { Dancer::SharedData->response->pass(1) }
sub path { realpath(Dancer::FileUtils::path(@_)) }
sub post { Dancer::App->current->registry->universal_add('post', @_) }
sub prefix { Dancer::App->current->set_prefix(@_) }
-sub del { Dancer::App->current->registry->universal_add('delete', @_) }
-sub options { Dancer::App->current->registry->universal_add('options', @_) }
sub put { Dancer::App->current->registry->universal_add('put', @_) }
-sub r { croak "'r' is DEPRECATED, use qr{} instead" }
-sub redirect { Dancer::Helpers::redirect(@_) }
-sub render_with_layout { Dancer::Helpers::render_with_layout(@_) }
+sub redirect { goto &_redirect }
+sub render_with_layout { Dancer::Template::Abstract->_render_with_layout(@_) }
sub request { Dancer::SharedData->request }
-sub send_file { Dancer::Helpers::send_file(@_) }
+sub send_error { Dancer::Error->new(message => $_[0], code => $_[1] || 500)->render() }
+sub send_file { goto &_send_file }
sub set { goto &setting }
+sub set_cookie { Dancer::Cookies->set_cookie(@_) }
sub setting { Dancer::App->applications ? Dancer::App->current->setting(@_) : Dancer::Config::setting(@_) }
-sub set_cookie { Dancer::Helpers::set_cookie(@_) }
-
-sub session {
- croak "Must specify session engine in settings prior to using 'session' keyword" unless setting('session');
- if (@_ == 0) {
- return Dancer::Session->get;
- }
- else {
- return (@_ == 1)
- ? Dancer::Session->read(@_)
- : Dancer::Session->write(@_);
- }
-}
+sub session { goto &_session }
sub splat { @{ Dancer::SharedData->request->params->{splat} || [] } }
-sub status { Dancer::Response->status(@_) }
-sub template { Dancer::Helpers::template(@_) }
-sub true { 1 }
+sub start { goto &_start }
+sub status { Dancer::SharedData->response->status(@_) }
+sub template { Dancer::Template::Abstract->template(@_) }
sub to_dumper { Dancer::Serializer::Dumper::to_dumper(@_) }
sub to_json { Dancer::Serializer::JSON::to_json(@_) }
-sub to_yaml { Dancer::Serializer::YAML::to_yaml(@_) }
sub to_xml { Dancer::Serializer::XML::to_xml(@_) }
+sub to_yaml { Dancer::Serializer::YAML::to_yaml(@_) }
+sub true { 1 }
sub upload { Dancer::SharedData->request->upload(@_) }
sub uri_for { Dancer::SharedData->request->uri_for(@_) }
sub var { Dancer::SharedData->var(@_) }
sub vars { Dancer::SharedData->vars }
sub warning { goto &Dancer::Logger::warning }
+# When importing the package, strict and warnings pragma are loaded,
+# and the appdir detection is performed.
+sub import {
+ my ($class, @args) = @_;
+ my ($package, $script) = caller;
+
+ strict->import;
+ utf8->import;
+
+ my @final_args;
+ my $syntax_only = 0;
+ my $as_script = 0;
+ foreach (@args) {
+ if ( $_ eq ':moose' ) {
+ push @final_args, '!before', '!after';
+ }
+ elsif ( $_ eq ':tests' ) {
+ push @final_args, '!pass';
+ }
+ elsif ( $_ eq ':syntax' ) {
+ $syntax_only = 1;
+ }
+ elsif ($_ eq ':script') {
+ $as_script = 1;
+ } else {
+ push @final_args, $_;
+ }
+ }
+
+ $class->export_to_level(1, $class, @final_args);
+
+ # if :syntax option exists, don't change settings
+ return if $syntax_only;
+
+ Dancer::GetOpt->process_args() if !$as_script;
+
+ _init_script_dir($script);
+ Dancer::Config->load;
+}
+
+# private code
+
# FIXME handle previous usage of load_app with multiple app names
-sub load_app {
+sub _load_app {
my ($app_name, %options) = @_;
+ my $script = (caller)[1];
Dancer::Logger::core("loading application $app_name");
# set the application
@@ -186,10 +216,8 @@ sub load_app {
$app->prefix($options{prefix}) if $options{prefix};
$app->settings($options{settings}) if $options{settings};
-
# load the application
- my ($package, $script) = caller;
- _init($script);
+ _init_script_dir($script);
my ($res, $error) = Dancer::ModuleLoader->load($app_name);
$res or croak "unable to load application $app_name : $error";
@@ -197,50 +225,25 @@ sub load_app {
Dancer::App->set_running_app('main');
}
-# When importing the package, strict and warnings pragma are loaded,
-# and the appdir detection is performed.
-sub import {
- my ($class, $symbol) = @_;
- my ($package, $script) = caller;
-
- strict->import;
- utf8->import;
- $class->export_to_level(1, $class, @EXPORT);
-
- # if :syntax option exists, don't change settings
- if ($symbol && $symbol eq ':syntax') {
- return;
- }
-
- Dancer::GetOpt->process_args();
-
- _init($script);
- Dancer::Config->load;
-}
-
-# Start/Run the application with the chosen apphandler
-sub start {
- my ($class, $request) = @_;
- Dancer::Config->load;
-
- # Backward compatibility for app.psgi that has sub { Dancer->dance($req) }
- if ($request) {
- return Dancer::Handler->handle_request($request);
- }
-
- my $handler = Dancer::Handler->get_handler;
- Dancer::Logger::core("loading handler '".ref($handler)."'");
- return $handler->dance;
-}
-
-
-sub _init {
- my $script = shift;
+sub _init_script_dir {
+ my ($script) = @_;
my ($script_vol, $script_dirs, $script_name) =
File::Spec->splitpath(File::Spec->rel2abs($script));
+
+ # normalize
+ if ( -d ( my $fulldir = File::Spec->catdir( $script_dirs, $script_name ) ) ) {
+ $script_dirs = $fulldir;
+ $script_name = '';
+ }
+
my @script_dirs = File::Spec->splitdir($script_dirs);
- my $script_path = File::Spec->catdir($script_vol, $script_dirs);
+ my $script_path;
+ if ($script_vol) {
+ $script_path = Dancer::path($script_vol, $script_dirs);
+ } else {
+ $script_path = Dancer::path($script_dirs);
+ }
my $LAYOUT_PRE_DANCER_1_2 = 1;
@@ -249,34 +252,90 @@ sub _init {
if ($script_dirs[$#script_dirs - 1] eq 'bin')
or ($script_dirs[$#script_dirs - 1] eq 'public');
- setting appdir => $ENV{DANCER_APPDIR}
- || (
+ my $appdir = $ENV{DANCER_APPDIR} || (
$LAYOUT_PRE_DANCER_1_2
? $script_path
- : File::Spec->rel2abs(path($script_path, '..'))
- );
+ : File::Spec->rel2abs(Dancer::path($script_path, '..'))
+ );
+ Dancer::setting(appdir => $appdir);
# once the dancer_appdir have been defined, we export to env
- $ENV{DANCER_APPDIR} = setting('appdir');
-
- Dancer::Logger::core(
- "initializing appdir to: `" . setting('appdir') . "'");
+ $ENV{DANCER_APPDIR} = $appdir;
- setting confdir => $ENV{DANCER_CONFDIR}
- || setting('appdir');
+ Dancer::Logger::core("initializing appdir to: `$appdir'");
- setting public => $ENV{DANCER_PUBLIC}
- || Dancer::FileUtils::path_no_verify(setting('appdir'), 'public');
+ Dancer::setting(confdir => $ENV{DANCER_CONFDIR}
+ || $appdir);
- setting views => $ENV{DANCER_VIEWS}
- || Dancer::FileUtils::path_no_verify(setting('appdir'), 'views');
+ Dancer::setting(public => $ENV{DANCER_PUBLIC}
+ || Dancer::FileUtils::path_no_verify($appdir, 'public'));
- setting logger => 'file';
+ Dancer::setting(views => $ENV{DANCER_VIEWS}
+ || Dancer::FileUtils::path_no_verify($appdir, 'views'));
- my ($res, $error) = Dancer::ModuleLoader->use_lib(Dancer::FileUtils::path_no_verify(setting('appdir'), 'lib'));
+ my ($res, $error) = Dancer::ModuleLoader->use_lib(Dancer::FileUtils::path_no_verify($appdir, 'lib'));
$res or croak "unable to set libdir : $error";
}
+sub _redirect {
+ my ($destination, $status) = @_;
+ if ($destination !~ m!^(\w+:/)?/!) {
+ # no absolute uri here, build one, RFC 2616 forces us to do so
+ my $request = Dancer::SharedData->request;
+ $destination = $request->uri_for($destination, {}, 1);
+ }
+ my $response = Dancer::SharedData->response;
+ $response->status($status || 302);
+ $response->headers('Location' => $destination);
+}
+
+sub _session {
+ engine 'session'
+ or croak "Must specify session engine in settings prior to using 'session' keyword";
+ @_ == 0 ? Dancer::Session->get
+ : @_ == 1 ? Dancer::Session->read(@_)
+ : Dancer::Session->write(@_);
+}
+
+sub _send_file {
+ my ($path, %options) = @_;
+
+ my $request = Dancer::Request->new_for_request('GET' => $path);
+ Dancer::SharedData->request($request);
+
+ if (exists($options{content_type})) {
+ $request->content_type($options{content_type});
+ }
+
+ my $resp;
+ if ($options{absolute} && -f $path) {
+ $resp = Dancer::Renderer->get_file_response_for_path($path);
+ } else {
+ $resp = Dancer::Renderer->get_file_response();
+ }
+ return $resp if $resp;
+
+ Dancer::Error->new(
+ code => 404,
+ message => "No such file: `$path'"
+ )->render();
+}
+
+# Start/Run the application with the chosen apphandler
+sub _start {
+ my ($class, $request) = @_;
+ Dancer::Config->load;
+
+ # Backward compatibility for app.psgi that has sub { Dancer->dance($req) }
+ if ($request) {
+ return Dancer::Handler->handle_request($request);
+ }
+
+ my $handler = Dancer::Handler->get_handler;
+ Dancer::Logger::core("loading handler '".ref($handler)."'");
+ return $handler->dance;
+}
+
1;
__END__
@@ -338,7 +397,62 @@ involving Dancer and Plack, see L<Dancer::Deployment>.
You can find out more about the many useful plugins available for Dancer in
L<Dancer::Plugins>.
-=head1 METHODS
+
+=head1 EXPORTS
+
+By default, C<use Dancer> exports all the functions below plus sets up
+your app. You can control the exporting through the normal
+L<Exporter> means. For example:
+
+ # Just export the route controllers
+ use Dancer qw(before after get post);
+
+ # Export everything but pass to avoid clashing with Test::More
+ use Test::More;
+ use Dancer qw(!pass);
+
+There are also some special tags to control exports and behavior.
+
+=head2 :moose
+
+This will export everything except those functions which clash with
+Moose. Currently that is L<after> and L<before>.
+
+=head2 :syntax
+
+This tells Dancer to just export symbols and not set up your app.
+This is most useful for writing Dancer code outside of your main route
+handler.
+
+=head2 :tests
+
+This will export everything except those functions which clash with
+commonly used testing modules. Currently that is L<pass>.
+
+These can be combined. For example, while testing...
+
+ use Test::More;
+ use Dancer qw(:syntax :tests);
+
+ # Test::Most also exports "set" and "any"
+ use Test::Most;
+ use Dancer qw(:syntax :tests !set !any);
+
+ # Alternatively, if you want to use Dancer's set and any...
+ use Test::Most qw(!set !any);
+ use Dancer qw(:syntax :tests);
+
+=head2 :script
+
+This will export all the keywords, and will also load the configuration.
+
+This is useful when you want to use your Dancer application from a script.
+
+ use MyApp;
+ use Dancer ':script';
+ MyApp::schema('DBSchema')->deploy();
+
+=head1 FUNCTIONS
=head2 after
@@ -584,10 +698,20 @@ Adds custom headers to responses:
=head2 header
-Adds a custom header to response:
+adds a custom header to response:
get '/send/header', sub {
- header 'X-My-Header' => 'shazam!';
+ header 'x-my-header' => 'shazam!';
+ }
+
+=head2 push_header
+
+Do the same as C<header>, but allow for multiple headers with the same name.
+
+ get '/send/header', sub {
+ push_header 'x-my-header' => '1';
+ push_header 'x-my-header' => '2';
+ will result in two headers "x-my-header" in the response
}
=head2 layout
@@ -625,17 +749,32 @@ C<:syntax> option, in order not to change the application directory
=head2 mime_type
-Returns all the user-defined mime-types when called without parameters.
-Behaves as a setter/getter when given parameters
+Deprecated. Check C<mime> bellow.
+
+=head2 mime
- # get the global hash of user-defined mime-types:
- my $mimes = mime_types;
+Shortcut to access the instance object of L<Dancer::MIME>. You should
+check its man page for details, as this entry just summarize the most
+relevant methods.
- # set a mime-type
- mime_types foo => 'text/foo';
+ # set a new mime type
+ mime->add_type( foo => 'text/foo' );
- # get a mime-type
- my $m = mime_types 'foo';
+ # set a mime type alias
+ mime->add_alias( f => 'foo' );
+
+ # get mime type for an alias
+ my $m = mime->for_name( 'f' );
+
+ # get mime type for a file (based on extension)
+ my $m = mime->for_file( "foo.bar" );
+
+ # get current defined default mime type
+ my $d = mime->default;
+
+ # set the default mime type using config.yml
+ # or using the set keyword
+ set default_mime_type => 'text/plain';
=head2 params
@@ -687,6 +826,12 @@ You can unset the prefix value:
prefix undef;
get '/page1' => sub {}; will match /page1
+B<Notice:> once you have a prefix set, do not add a caret to the regex:
+
+ prefix '/foo';
+ get qr{^/bar} => sub { ... } # BAD BAD BAD
+ get qr{/bar} => sub { ... } # Good!
+
=head2 del
Defines a route for HTTP B<DELETE> requests to the given URL:
@@ -705,21 +850,6 @@ Defines a route for HTTP B<PUT> requests to the given URL:
put '/resource' => sub { ... };
-=head2 r
-
-Defines a route pattern as a regular Perl regexp.
-
-This method is B<DEPRECATED>. Dancer now supports real Perl Regexp objects
-instead. You should not use r() but qr{} instead:
-
-Don't do this:
-
- get r('/some/pattern(.*)') => sub { };
-
-But rather this:
-
- get qr{/some/pattern(.*)} => sub { };
-
=head2 redirect
Generates a HTTP redirect (302). You can either redirect to a complete
@@ -795,16 +925,31 @@ saying C<return send_error(...)> instead.
=head2 send_file
-Lets the current route handler send a file to the client.
+Lets the current route handler send a file to the client. Note that
+the path of the file must be relative to the B<public> directory.
get '/download/:file' => sub {
send_file(params->{file});
}
The content-type will be set depending on the current mime-types definition
-(see C<mime_type> if you want to define your own).
+(see C<mime> if you want to define your own).
+
+If your filename does not have an extension, or you need to force a
+specific mime type, you can pass it to C<send_file> as follows:
+
+ send_file(params->{file}, content_type => 'image/png');
+
+Also, you can use your aliases or file extension names on
+C<content_type>, like this:
+
+ send_file(params->{file}, content_type => 'png');
+
+For files outside your B<public> folder, you can use the C<absolute>
+switch. Just bear in mind that its use needs caution as it can be
+dangerous.
-The path of the file must be relative to the B<public> directory.
+ send_file('/etc/passwd', absolute => 1);
=head2 set
View
52 lib/Dancer/Config.pm
@@ -5,6 +5,7 @@ use warnings;
use base 'Exporter';
use vars '@EXPORT_OK';
+use Dancer::Deprecation;
use Dancer::Template;
use Dancer::ModuleLoader;
use Dancer::FileUtils 'path';
@@ -56,7 +57,7 @@ my $setters = {
'get', '/:page',
sub {
my $params = Dancer::SharedData->request->params;
- Dancer::Helpers::template($params->{'page'});
+ Dancer::template($params->{'page'});
}
);
}
@@ -85,11 +86,10 @@ my $normalizers = {
};
sub mime_types {
- carp "DEPRECATED: use 'mime_type' from Dancer.pm";
- my $mime = Dancer::MIME->instance();
- if (scalar(@_)==2) { $mime->add_mime_type(@_) }
- elsif (scalar(@_)==1) { $mime->mime_type_for(@_) }
- else { $mime->aliases }
+ Dancer::Deprecation->deprecated(
+ reason => "use 'mime' from Dancer.pm",
+ fatal => 1,
+ );
}
sub normalize_setting {
@@ -206,8 +206,9 @@ sub load_default_settings {
$SETTINGS->{warnings} ||= $ENV{DANCER_WARNINGS} || 0;
$SETTINGS->{auto_reload} ||= $ENV{DANCER_AUTO_RELOAD} || 0;
$SETTINGS->{traces} ||= $ENV{DANCER_TRACES} || 0;
- $SETTINGS->{environment}
- ||= $ENV{DANCER_ENVIRONMENT}
+ $SETTINGS->{logger} ||= $ENV{DANCER_LOGGER} || 'file';
+ $SETTINGS->{environment} ||=
+ $ENV{DANCER_ENVIRONMENT}
|| $ENV{PLACK_ENV}
|| 'development';
@@ -308,19 +309,34 @@ Also, since automatically serialized JSON responses have
C<application/json> Content-Type, you should always encode them by
hand.
-=head2 public (string)
+=head2 appdir (directory)
-This is the path of the public directory, where static files are stored. Any
-existing file in that directory will be served as a static file, before
-mathcing any route.
+This is the path where your application will live. It's where Dancer
+will look by default for your config files, templates and static
+content.
-By default, it points to APPDIR/public where APPDIR is the directory that
-contains your Dancer script.
+It is typically set by C<use Dancer> to use the same directory as your
+script.
+
+=head2 public (directory)
+
+This is the directory, where static files are stored. Any existing
+file in that directory will be served as a static file, before
+matching any route.
+
+By default, it points to $appdir/public.
+
+=head2 views (directory)
+
+This is the directory where your templates and layouts live. It's the
+"view" part of MVC (model, view, controller).
+
+This defaults to $appdir/views.
=head2 layout (string)
-name of the layout to use when rendering view. Dancer will look for
-a matching template in the directory $appdir/views/layout.
+The name of the layout to use when rendering view. Dancer will look for
+a matching template in the directory $views/layout.
=head2 warnings (boolean)
@@ -334,10 +350,12 @@ occurs. (Internally sets Carp::Verbose). Default to false.
=head2 log (enum)
Tells which log messages should be actullay logged. Possible values are
-B<debug>, B<warning> or B<error>.
+B<core>, B<debug>, B<warning> or B<error>.
=over 4
+=item B<core> : all messages are logged, including some from Dancer itself
+
=item B<debug> : all messages are logged
=item B<warning> : only warning and error messages are logged
View
204 lib/Dancer/Cookbook.pod
@@ -7,7 +7,9 @@ Dancer::Cookbook - a quick-start guide to the Dancer web framework
A quick-start guide with examples to get you up and running with the Dancer web
framework.
-=head1 RECIPES
+
+
+=head1 BEGINNER'S DANCE
=head2 Your first Dancer web app
@@ -39,6 +41,8 @@ route specification, whose value is made available through C<params>
Note that you don't need to use the C<strict> and C<warnings> pragma, they are
already loaded by Dancer.
+
+
=head2 Starting a Dancer project
The first simple example is fine for trivial projects, but for anything more
@@ -80,6 +84,9 @@ live), a module containing the actual guts of your application, a script to
start it - or to run your web app via Plack/PSGI - more on that later.
+
+=head1 DANCE ROUTINES: ROUTES
+
=head2 Declaring routes
To control what happens when a web request is received by your webapp, you'll
@@ -122,6 +129,7 @@ parameters supplied on the query string, within the path itself (with named
placeholders), and, for HTTTP POST requests, the content of the POST body.
+
=head2 Named parameters in route path declarations
As seen above, you can use C<:somename> in a route's path to capture part of the
@@ -136,6 +144,7 @@ use something like:
};
+
=head2 Wildcard path matching and splat
You can also declare wildcards in a path, and retrieve the values they matched
@@ -154,6 +163,7 @@ with C<splat>:
};
+
=head2 Before filters - processed before a request
A C<before> filter declares code which should be handled before a request is
@@ -175,6 +185,7 @@ request to C</foo/oversee>; this means that, whatever path was requested, it
will be treated as though the path requested was C</foo/oversee>.
+
=head2 Default route
In case you want to avoid a I<404 error>, or handle multiple routes in the same
@@ -216,6 +227,8 @@ Simply enable auto_page in your config:
Then, if you request C</foo/bar>, Dancer will look in the views dir for
C</foo/bar.tt>.
+
+
=head2 Why should I use the Ajax plugin
As an Ajax query is just a HTTP query, it's similar to a GET or POST
@@ -257,6 +270,8 @@ and
Because it's an ajax query, you know you need to return a xml content,
so the content type of the response is set for you.
+
+
=head2 Using the prefix feature to split your application
For better maintainability, you may want to separate some of your application
@@ -289,6 +304,10 @@ The following routes will be generated for us:
- head /
- head /admin/
+
+
+=head1 MUSCLE MEMORY: STORING DATA
+
=head2 Handling sessions
It's common to want to use sessions to give your web applications state; for
@@ -328,6 +347,7 @@ Storing data in the session is as easy as:
session varname => 'value';
+
=head3 Retrieving data from the session
Retrieving data from the session is as easy as:
@@ -339,6 +359,7 @@ Or, alternatively,
session->{varname}
+
=head3 Controlling where sessions are stored
For disc-based session back ends like L<Dancer::Session::YAML>,
@@ -354,6 +375,8 @@ and easily within your config file, for example:
If the directory you specify does not exist, Dancer will attempt to create it
for you.
+
+
=head3 Destroying a session
When you're done with your session, you can destroy it:
@@ -414,7 +437,7 @@ wouldn't store your users passwords in the clear, would you?)) follows:
warning "Failed login for unrecognised user " . params->{user};
redirect '/login?failed=1';
} else {
- if (Crypt::SaltedHash->validate($user->{password}, params->{pass}))
+ if (Crypt::SaltedHash->validate($user->{password}, params->{pass}))
{
debug "Password correct";
# Logged in successfully
@@ -429,12 +452,16 @@ wouldn't store your users passwords in the clear, would you?)) follows:
+=head1 APPEARANCE
+
=head2 Using templates - views and layouts
Returning plain content is all well and good for examples or trivial apps, but
soon you'll want to use templates to maintain separation between your code and
your content. Dancer makes this easy.
+
+
=head3 Views
It's possible to render the action's content with a template, this is called a
@@ -480,6 +507,8 @@ The template 'hello.tt' could contain, for example:
<p>You're logged in as <% session.username %>
<% END %>
+
+
=head3 Layouts
A layout is a special view, located in the 'layouts' directory (inside the views
@@ -531,6 +560,8 @@ THen in your layout, modify your css inclusion as follows:
From now on, you can mount your application wherever you want, without
any further modification of the css inclusion
+
+
=head3 template and unicode
If you use L<Plack> and have some unicode problem with your Dancer application,
@@ -543,11 +574,13 @@ config.yml will look like this:
template_toolkit:
ENCODING: utf8
+
+
=head3 TT's WRAPPER directive in Dancer (META variables, SETs)
-Dancer provides a WRAPPER ability, which we call a "layout". The reason we do
-not use TT's WRAPPER (and that it is incompatible with it) is since not all
-template systems support it. Actually, most don't.
+Dancer already provides a WRAPPER-like ability, which we call a "layout". The
+reason we do not use TT's WRAPPER (which also makes it incompatible with it) is
+because not all template systems support it. Actually, most don't.
However, you might want to use it, and be able to define META variables and
regular L<Template::Toolkit> variables.
@@ -581,6 +614,10 @@ Change the configuration of the template to Template Toolkit:
Done! Everything will work fine out of the box, including variables and META
variables.
+
+
+=head1 SETTING THE STAGE: CONFIGURATION AND LOGGING
+
=head2 Configuration and environments
Configuring a Dancer application can be done in many ways. The easiest one (and
@@ -624,6 +661,7 @@ And in a production one:
show_errors: 0
+
=head2 Accessing configuration information from your app
A Dancer application can use the 'config' keyword to easily access the settings
@@ -636,38 +674,77 @@ within its config file, for instance:
This makes keeping your application's settings all in one place simple and easy
- you shouldn't need to worry about implementing all that yourself :)
-=head2 Plack middlewares
-If you deploy with Plack and use some Plack middlewares, you can enable them
-directly from Dancer's configuration files.
-To enable middlewares in Dancer, you just have to set the plack_middlewares
-setting like the following:
+=head2 Accessing configuration information from a separate script
- set plack_middlewares => [
- [ 'SomeMiddleware' => [ qw(some options for somemiddleware) ]],
- ];
+You may well want to access your webapp's configuration from outside your
+webapp. You could, of course, use the YAML module of your choice and load your
+webapps's config.yml, but chances are that this is not convenient.
-For instance, if you want to enable L<Plack::Middleware::Debug> in your Dancer
-application, all you have to do is to set C<plack_middlewares> like that:
+Use Dancer instead. Without any ado, magic or too big jumps, you can use the
+values from config.yml and some additional default values:
- set plack_middlewares => [
- [ 'Debug' => [ 'panels' => [ qw(DBITrace Memory Timer) ]]],
- ];
+ # bin/script1.pl
+ use Dancer ':syntax';
+ print "template:".config->{template}."\n"; #simple
+ print "log:".config->{log}."\n"; #undef
+
+Note that config->{log} should result undef error on a default scaffold since
+you did not load the environment and in the default scaffold log is defined in
+the environment and not in config.yml. Hence undef.
+
+If you want to load an environment you need to tell Dancer where to look for it.
+One way to do so, is to tell Dancer where the webapp lives. From there Dancer
+deducts where the config.yml file is (typically $webapp/config.yml).
+
+ # bin/script2.pl
+ use FindBin;
+ use Cwd qw/realpath/;
+ use Dancer ':syntax';
+
+ #tell the Dancer where the app lives
+ my $appdir=realpath( "$FindBin::Bin/..");
+
+ Dancer::Config::setting('appdir',$appdir);
+ Dancer::Config::load();
+
+ #getter
+ print "environment:".config->{environment}."\n"; #development
+ print "log:".config->{log}."\n"; #value from development environment
+
+By default Dancer loads development environment (typically
+$webapp/environment/development.yml). In contrast to the example before, you
+do have a value from the development environment (environment/development.yml)
+now. Also note that in the above example Cwd and FindBin are used. They are
+likely to be already loaded by Dancer anyways, so it's not a big overhead. You
+could just as well hand over a simple path for the app if you like that better,
+e.g.:
+
+ Dancer::Config::setting('appdir','/path/to/app/dir');
+
+If you want to load an environment other than the default, try this:
+
+ # bin/script2.pl
+ use Dancer ':syntax';
+
+ #tell the Dancer where the app lives
+ Dancer::Config::setting('appdir','/path/to/app/dir');
+
+ #which environment to load
+ config->{environment}='production';
+
+ Dancer::Config::load();
+
+ #getter
+ print "log:".config->{log}."\n"; #has value from production environment
+
+By the way, you not only get values, you can also set values straightforward
+like we do above with config->{environment}='production'. Of course, this value
+does not get written in any file; it only lives in memory and your webapp
+doesn't have access to it, but you can use it inside your script.
-Of course, you can also put this configuration into your config.yml file, or
-even in your environment configuration files:
- # environments/development.yml
- ...
- plack_middlewares:
- -
- - Debug # first element of the array is the name of the middleware
- - panels # following elements are the configuration ofthe middleware
- -
- - DBITrace
- - Memory
- - Timer
=head2 Logging
@@ -702,9 +779,13 @@ Just call C<debug>, C<warning> or C<error> with your message:
debug "This is a debug message from my app.";
+
+
+=head1 RESTING
+
=head2 Writing a REST application
-With Dancer, it's easy to write REST applications. Dancer provides helpers to
+With Dancer, it's easy to write REST applications. Dancer provides helpers to
serialize and deserialize for the following data formats:
=over 4
@@ -728,8 +809,8 @@ Or right in your code:
set serializer => 'JSON';
-From now, all hash ref or array ref returned by a route will be serialized to
-the format you chose, and all data received from B<POST> or B<PUT> requests
+From now, all hash ref or array ref returned by a route will be serialized to
+the format you chose, and all data received from B<POST> or B<PUT> requests
will be automatically deserialized.
get '/hello/:name' => sub {
@@ -738,13 +819,13 @@ will be automatically deserialized.
return {name => params->{name}};
};
-It's possible to let the client choose which serializer he want to use. For
-this, use the B<mutable> serializer, and an appropriate serializer will be
+It's possible to let the client choose which serializer he want to use. For
+this, use the B<mutable> serializer, and an appropriate serializer will be
chosen from the B<Content-Type> header.
-It's also possible to return custom error, using the C<send_error> function.
-When you don't use a serializer, the C<send_error> function will take a string
-as first parameter (the message), and an optional HTTP code. When using a
+It's also possible to return custom error, using the C<send_error> function.
+When you don't use a serializer, the C<send_error> function will take a string
+as first parameter (the message), and an optional HTTP code. When using a
serializer, the message can be a string, an arrayref or a hashref:
get '/hello/:name' => sub {
@@ -764,6 +845,53 @@ proxy/load-balancing software, and using common web servers including Apache to
run via CGI/FastCGI etc, see L<Dancer::Deployment>.
+
+=head1 DANCER ON THE STAGE: DEPLOYMENT
+
+=head2 Plack middlewares
+
+If you deploy with Plack and use some Plack middlewares, you can enable them
+directly from Dancer's configuration files.
+
+=head3 Generic middlewares
+
+To enable middlewares in Dancer, you just have to set the plack_middlewares
+setting like the following:
+
+ set plack_middlewares => [
+ [ 'SomeMiddleware' => [ qw(some options for somemiddleware) ]],
+ ];
+
+For instance, if you want to enable L<Plack::Middleware::Debug> in your Dancer
+application, all you have to do is to set C<plack_middlewares> like that:
+
+ set plack_middlewares => [
+ [ 'Debug' => [ 'panels' => qw(DBITrace Memory Timer) ]],
+ ];
+
+Of course, you can also put this configuration into your config.yml file, or
+even in your environment configuration files:
+
+ # environments/development.yml
+ ...
+ plack_middlewares:
+ -
+ - Debug # first element of the array is the name of the middleware
+ - panels # following elements are the configuration ofthe middleware
+ -
+ - DBITrace
+ - Memory
+ - Timer
+
+=head3 Path-based middlewares
+
+If you want to setup a middleware for a specific path, you can do that using
+C<plack_middlewares_map>. You'll need L<Plack::App::URLMap> to do that.
+
+ plack_middlewares_map:
+ '/': ['Debug']
+ '/timer': ['Timer'],
+
=head1 AUTHORS
Dancer contributors - see AUTHORS file.
View
105 lib/Dancer/Cookie.pm
@@ -5,14 +5,19 @@ use warnings;
use URI::Escape;
use base 'Dancer::Object';
-__PACKAGE__->attributes('name', 'expires', 'domain', 'path');
+__PACKAGE__->attributes('name', 'expires', 'domain', 'path', "secure");
sub init {
my ($self, %args) = @_;
$self->value($args{value});
- if ($self->expires) {
- $self->expires(_epoch_to_gmtstring($self->expires))
- if $self->expires =~ /^\d+$/;
+ if (my $time = $self->expires) {
+ # First, normalize things like +2h to # of seconds
+ $time = _parse_duration($time) if $time !~ /^\d+$/;
+
+ # Then translate to a gmt string, if it isn't one already
+ $time = _epoch_to_gmtstring($time) if $time =~ /^\d+$/;
+
+ $self->expires($time);
}
$self->path('/') unless defined $self->path;
}
@@ -22,11 +27,15 @@ sub to_header {
my $header = '';
my $value = join('&', map {uri_escape($_)} $self->value);
- $header .= $self->name . '=' . $value . '; ';
- $header .= "path=" . $self->path . "; " if $self->path;
- $header .= "expires=" . $self->expires . "; " if $self->expires;
- $header .= "domain=" . $self->domain . "; " if $self->domain;
- $header .= 'HttpOnly';
+
+ my @headers = $self->name . '=' . $value;
+ push @headers, "path=" . $self->path if $self->path;
+ push @headers, "expires=" . $self->expires if $self->expires;
+ push @headers, "domain=" . $self->domain if $self->domain;
+ push @headers, "Secure" if $self->secure;
+ push @headers, 'HttpOnly';
+
+ return join '; ', @headers;
}
sub value {
@@ -56,6 +65,52 @@ sub _epoch_to_gmtstring {
$hour, $min, $sec;
}
+# This map is taken from Cache and Cache::Cache
+# map of expiration formats to their respective time in seconds
+my %Units = ( map(($_, 1), qw(s second seconds sec secs)),
+ map(($_, 60), qw(m minute minutes min mins)),
+ map(($_, 60*60), qw(h hr hour hours)),
+ map(($_, 60*60*24), qw(d day days)),
+ map(($_, 60*60*24*7), qw(w week weeks)),
+ map(($_, 60*60*24*30), qw(M month months)),
+ map(($_, 60*60*24*365), qw(y year years)) );
+
+# This code is taken from Time::Duration::Parse, except if it isn't
+# understood it just passes it through and it adds the current time.
+sub _parse_duration {
+ my $timespec = shift;
+ my $orig_timespec = $timespec;
+
+ # Treat a plain number as a number of seconds (and parse it later)
+ if ($timespec =~ /^\s*([-+]?\d+(?:[.,]\d+)?)\s*$/) {
+ $timespec = "$1s";
+ }
+
+ # Convert hh:mm(:ss)? to something we understand
+ $timespec =~ s/\b(\d+):(\d\d):(\d\d)\b/$1h $2m $3s/g;
+ $timespec =~ s/\b(\d+):(\d\d)\b/$1h $2m/g;
+
+ my $duration = 0;
+ while ($timespec =~ s/^\s*([-+]?\d+(?:[.,]\d+)?)\s*([a-zA-Z]+)(?:\s*(?:,|and)\s*)*//i) {
+ my($amount, $unit) = ($1, $2);
+ $unit = lc($unit) unless length($unit) == 1;
+
+ if (my $value = $Units{$unit}) {
+ $amount =~ s/,/./;
+ $duration += $amount * $value;
+ } else {
+ return $orig_timespec;
+ }
+ }
+
+ if ($timespec =~ /\S/) {
+ return $orig_timespec;
+ }
+
+ return sprintf "%.0f", $duration + time;
+}
+
+
1;
__END__
@@ -80,17 +135,34 @@ Dancer::Cookie provides a HTTP cookie object to work with cookies.
=head1 ATTRIBUTES
-=head3 name
+=head2 name
The cookie's name.
-=head3 value
+=head2 value
The cookie's value.
=head2 expires
-The cookie's expiration date.
+The cookie's expiration date. There are several formats.
+
+Unix epoch time like 1288817656 to mean "Wed, 03-Nov-2010 20:54:16 GMT"
+
+A human readable offset from the current time such as "2 hours". It currently
+understands...
+
+ s second seconds sec secs
+ m minute minutes min mins
+ h hr hour hours
+ d day days
+ w week weeks
+ M month months
+ y year years
+
+Months and years are currently fixed at 30 and 365 days. This may change.
+
+Anything else is used verbatum.
=head2 domain
@@ -100,6 +172,11 @@ The cookie's domain.
The cookie's path.
+=head2 secure
+
+If true, it instructs the client to only serve the cookie over secure
+connections such as https.
+
=head1 METHODS/SUBROUTINES
=head2 new
@@ -116,10 +193,6 @@ Runs an expiration test and sets a default path if not set.
Creates a proper HTTP cookie header from the content.
-=head2 _epoch_to_gmtstring
-
-Internal method to convert the time from Epoch to GMT.
-
=head1 AUTHOR
Alexis Sukrieh
View
31 lib/Dancer/Cookies.pm
@@ -23,7 +23,7 @@ sub parse_cookie_from_env {
return {} unless defined $env_str;
my $cookies = {};
- foreach my $cookie ( split( '; ', $env_str ) ) {
+ foreach my $cookie ( split( /[,;]\s/, $env_str ) ) {
my ( $name, $value ) = split( '=', $cookie );
my @values;
if ( $value ne '' ) {
@@ -36,15 +36,23 @@ sub parse_cookie_from_env {
return $cookies;
}
-# return true if the given cookie is not the same as the one sent by the client
-sub has_changed {
- my ($self, $cookie) = @_;
- my ($name, $value) = ($cookie->{name}, $cookie->{value});
+# set_cookie name => value,
+# expires => time() + 3600, domain => '.foo.com'
+sub set_cookie {
+ my ( $class, $name, $value, %options ) = @_;
+ my $cookie = Dancer::Cookie->new(
+ name => $name,
+ value => $value,
+ %options
+ );
+ Dancer::Cookies->set_cookie_object($name => $cookie);
+}
- my $client_cookies = parse_cookie_from_env();
- my $search = $client_cookies->{$name};
- return 1 unless defined $search;
- return $search->value ne $value;
+sub set_cookie_object {
+ my ($class, $name, $cookie) = @_;
+ Dancer::SharedData->response->push_header(
+ 'Set-Cookie' => $cookie->to_header);
+ Dancer::Cookies->cookies->{$name} = $cookie;
}
1;
@@ -94,11 +102,6 @@ of all cookies.
It also returns all the hashref it created.
-=head2 has_changed
-
-Accepts a cookie and returns true if the given cookie is not the same as the one
-sent by the user.
-
=head1 AUTHOR
Alexis Sukrieh
View
38 lib/Dancer/Deployment.pod
@@ -323,18 +323,42 @@ This configuration will proxy all request to the B</application> path to the pat
with Nginx:
- upstream backend {
- server 10.0.0.1:8080;
- server 10.0.0.2:8080;
- ...
+ upstream backendurl {
+ server unix:THE_PATH_OF_YOUR_PLACKUP_SOCKET_HERE.sock;
}
server {
- location / {
- proxy_pass http://backend;
- }
+ listen 80;
+ server_name YOUR_HOST_HERE;
+
+ access_log /var/log/YOUR_ACCESS_LOG_HERE.log;
+ error_log /var/log/YOUR_ERROR_LOG_HERE.log info;
+
+ root YOUR_ROOT_PROJECT/public;
+ location / {
+ try_files $uri @proxy;
+ access_log off;
+ expires max;
+ }
+
+ location @proxy {
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_pass http://backendurl;
+ }
+
}
+You will need plackup to start a worker listening on a socket :
+
+ cd YOUR_PROJECT_PATH
+ sudo -u www plackup -E production -s Starman --workers=2 -l THE_PATH_OF_YOUR_PLACKUP_SOCKET_HERE.sock -a bin/app.pl
+
+A good way to start this is to use C<daemontools> and place this line with
+all environments variables in the "run" file.
+
=head2 Running from Apache
You can run your Dancer app from Apache using the following examples:
View
83 lib/Dancer/Deprecation.pm
@@ -0,0 +1,83 @@
+package Dancer::Deprecation;
+
+use strict;
+use warnings;
+use Carp qw/croak carp/;
+
+sub deprecated {
+ my ($class, %args) = @_;
+
+ my ( $package, undef, undef, $sub ) = caller(1);
+
+ unless ( defined $args{feature} ) {
+ $args{feature} = $sub;
+ }
+
+ my $deprecated_at = defined $args{version} ? $args{version} : undef;
+
+ my $msg;
+ if ( defined $args{message} ) {
+ $msg = $args{message};
+ }
+ else {
+ $msg = "$args{feature} has been deprecated";
+ }
+ $msg .= " since version $deprecated_at" if defined $deprecated_at;
+ $msg .= ". " . $args{reason} if defined $args{reason};
+
+ croak($msg) if $args{fatal};
+ carp($msg);
+}
+
+1;
+
+=head1 NAME
+
+Dancer::Deprecation - handle deprecation messages
+
+=head1 SYNOPSIS
+
+ Dancer::Deprecation->deprecated(
+ feature => 'sub_name',
+ version => '1.3000',
+ reason => '...',
+ );
+
+=head1 DESCRIPTION
+
+=head2 METHODS
+
+=head3 deprecated
+
+List of possible parameters:
+
+=over 4
+
+=item B<feature> name of the feature to deprecate
+
+=item B<version> from which version the feature is deprecated
+
+=item B<message> message to display
+
+=item B<fatal> if set to true, croak instead of carp
+
+item B<reason> why is the feature deprecated
+
+=back
+
+You can call the method with no arguments, and a default message using informations from C<caller> will be build for you.
+
+=head1 LICENSE
+
+This module is free software and is distributed under the same terms as Perl
+itself.
+
+=head1 AUTHOR
+
+This module has been written by Alexis Sukrieh <sukria@sukria.net>
+
+=head1 SEE ALSO
+
+L<Package::DeprecationManager>
+
+=cut
View
8 lib/Dancer/Development/Integration.pod
@@ -77,7 +77,7 @@ First, we make sure we are in sync with C<origin/devel>
Then, from that branch we create a I<temp> sandbox
- git co -b temp
+ git checkout -b temp
We pull here from I<$user>
@@ -90,8 +90,8 @@ commits by hand.
From devel, we first create the final C<review> branch:
- git co devel
- git co -b review/$user
+ git checkout devel
+ git checkout -b review/$user
Then we cherry-pick all the commits we want. To know them, we just
have to go into I<temp> and inspect the history (with C<git log>).
@@ -111,7 +111,7 @@ commits to change something.
When we're happy with the change set, we can merge into devel:
- git co devel
+ git checkout devel
git merge --no-ff review/$user
Note the C<--no-ff> switch is used to make sure we'll see a nice
View
28 lib/Dancer/Error.pm
@@ -4,6 +4,8 @@ use strict;
use warnings;
use Carp;
+use base 'Dancer::Object';
+
use Dancer::Response;
use Dancer::Renderer;
use Dancer::Config 'setting';
@@ -11,24 +13,22 @@ use Dancer::Logger;
use Dancer::Session;
use Dancer::FileUtils qw(open_file);
-sub new {
- my ($class, %params) = @_;
- my $self = \%params;
- bless $self, $class;
+sub init {
+ my ($self) = @_;
- $self->{title} ||= "Error " . $self->code;
- $self->{type} ||= "runtime error";
+ $self->attributes_defaults(
+ title => 'Error ' . $self->code,
+ type => 'runtime error',
+ );
- if (!$self->has_serializer) {
- my $html_output = "<h2>" . $self->{type} . "</h2>";
- $html_output .= $self->backtrace;
- $html_output .= $self->environment;
+ $self->has_serializer
+ and return;
- $self->{message} = $html_output;
- }
+ my $html_output = "<h2>" . $self->{type} . "</h2>";
+ $html_output .= $self->backtrace;
+ $html_output .= $self->environment;
- Dancer::Logger::core("new Error object: [".$self->code."] ".$self->message);
- return $self;
+ $self->{message} = $html_output;
}
sub has_serializer { setting('serializer') }
View
60 lib/Dancer/FileUtils.pm
@@ -11,31 +11,65 @@ use Cwd 'realpath';
use base 'Exporter';
use vars '@EXPORT_OK';
-@EXPORT_OK = qw(path dirname read_file_content read_glob_content open_file);
-
-sub path {
- File::Spec->catfile(@_);
+@EXPORT_OK = qw(path dirname read_file_content read_glob_content open_file set_file_mode);
+
+# Undo UNC special-casing catfile-voodoo on cygwin
+sub _trim_UNC {
+ if ($^O eq 'cygwin') {
+ return if ($#_ < 0);
+ my ($slashes, $part, @parts) = (0, undef, @_);
+ while(defined($part = shift(@parts))) { last if ($part); $slashes++ }
+ $slashes += ($part =~ s/^[\/\\]+//);
+ if ($slashes == 2) {
+ return("/" . $part, @parts);
+ } else {
+ my $slashstr = '';
+ $slashstr .= '/' for (1 .. $slashes);
+ return($slashstr . $part, @parts);
+ }
+ }
+ return(@_);
}
+sub d_catfile { File::Spec->catfile(_trim_UNC(@_)) }
+sub d_catdir { File::Spec->catdir(_trim_UNC(@_)) }
+sub d_canonpath { File::Spec->canonpath(_trim_UNC(@_)) }
+sub d_catpath { File::Spec->catpath(_trim_UNC(@_)) }
+sub d_splitpath { File::Spec->splitpath(_trim_UNC(@_)) }
+
+sub path { d_catfile(@_) }
sub path_no_verify {
- my @nodes = @_;
+ my @nodes = File::Spec->splitpath(d_catdir(@_)); # 0=vol,1=dirs,2=file
+ my $path = '';
# [0->?] path(must exist),[last] file(maybe exists)
- return realpath(File::Spec->catdir(@nodes[0 .. ($#nodes - 1)])) . '/'
- . $nodes[-1];
+ if($nodes[1]) {
+ $path = realpath(File::Spec->catpath(@nodes[0 .. 1],'')) . '/';
+ } else {
+ $path = Cwd::cwd . '/';
+ }
+ $path .= $nodes[2];
+ return $path;
}
sub dirname { File::Basename::dirname(@_) }
+sub set_file_mode {
+ my ($fh) = @_;
+ require Dancer::Config;
+ my $charset = Dancer::Config::setting('charset') || 'utf-8';
+
+ if($charset) {
+ binmode($fh, ":encoding($charset)");
+ }
+ return $fh;
+}
+
sub open_file {
my ($mode, $filename) = @_;
- require Dancer::Config;
- my $charset = Dancer::Config::setting('charset');
- length($charset || '')
- and $mode .= ":encoding($charset)";
open(my $fh, $mode, $filename)
or croak "$! while opening '$filename' using mode '$mode'";
- return $fh;
+ return set_file_mode($fh);
}
sub read_file_content {
@@ -44,7 +78,7 @@ sub read_file_content {
if ($file) {
$fh = open_file('<', $file);
-
+
return wantarray ? read_glob_content($fh) : scalar read_glob_content($fh);
}
else {