diff options
-rw-r--r-- | Changes | 89 | ||||
-rw-r--r-- | LICENSE | 32 | ||||
-rw-r--r-- | MANIFEST | 26 | ||||
-rw-r--r-- | META.json | 492 | ||||
-rw-r--r-- | META.yml | 358 | ||||
-rw-r--r-- | Makefile.PL | 70 | ||||
-rw-r--r-- | README | 16 | ||||
-rw-r--r-- | dist.ini | 30 | ||||
-rw-r--r-- | lib/Try/Tiny.pm | 677 | ||||
-rw-r--r-- | maint/bench.pl | 14 | ||||
-rw-r--r-- | t/00-compile.t | 50 | ||||
-rw-r--r-- | t/basic.t | 166 | ||||
-rw-r--r-- | t/context.t | 71 | ||||
-rw-r--r-- | t/erroneous_usage.t | 77 | ||||
-rw-r--r-- | t/finally.t | 131 | ||||
-rw-r--r-- | t/given_when.t | 40 | ||||
-rw-r--r-- | t/global_destruction_forked.t | 57 | ||||
-rw-r--r-- | t/global_destruction_load.t | 18 | ||||
-rw-r--r-- | t/lib/TryUser.pm | 9 | ||||
-rw-r--r-- | t/named.t | 37 | ||||
-rw-r--r-- | t/when.t | 37 | ||||
-rw-r--r-- | xt/release/eol.t | 8 | ||||
-rw-r--r-- | xt/release/no-tabs.t | 25 | ||||
-rw-r--r-- | xt/release/pod-coverage.t | 7 | ||||
-rw-r--r-- | xt/release/pod-syntax.t | 6 |
25 files changed, 2543 insertions, 0 deletions
@@ -0,0 +1,89 @@ +Revision history for Try-Tiny + +0.22 2014-04-29 + - add optional test deps as recommended prereqs (Karen Etheridge, #18) + +0.21 2014-04-15 + - also skip the test if Capture::Tiny is too old (Martin Popel, #17) + +0.20 2014-03-21 + - documentation updates (Flimm, #15) + +0.19 2014-01-22 + - fix an obscure issue with loading modules during global destruction + (ilmari, #11) + - documentation updates (anaxagoras, #12) + +0.18 2013-08-17 + - fix tests for pre-Test-More-0.88 (Paul Howarth, #10) + +0.17 2013-08-16 + - work around [rt.perl #119311] which was causing incorrect error messages in + some cases during global destruction (Graham Knop, #9) + +0.16 2013-07-10 + - remove accidental Sub::Name test dep + +0.15 2013-07-08 + - optionally use Sub::Name to name the try/catch/finally blocks, if available + (Mark Fowler) + +0.14 2013-07-05 + - also throw an exception for catch/finally in scalar context (RT#81070) + +0.13 2013-07-04 + - fix tests failing on 5.6.x due to differing DESTROY semantics + - excise superfluous local($@) call - 7% speedup + - fix (fsvo) broken URLs (RT#55659) + - proper exception on erroneous usage of bare catch/finally (RT#81070) + - proper exception on erroneous use of multiple catch{} blocks + - clarify exception occuring on unterminated try block (RT#75712) + - fix the prototypes shown in docs to match code (RT#79590; thanks, Pushtaev + Vadim) + - warn loudly on exceptions in finally() blocks + - dzilify + +0.12 2013-01-02 + - doc fixes + +0.11 2011-08-30 + - fix broken dist + +0.10 2011-04-27 + - clarify some docs + +0.09 2010-11-28 + - don't index Try::Tiny::ScopeGuard + +0.08 2010-11-28 + - fix require vs use issue in blead (RT63410) + +0.07 2010-10-21 + - allow multiple finally blocks + - pass the error, if any, to finally blocks when called + +0.06 2010-05-27 + - in t/given_when.t use a plan instead of done_testing for more backwards + compatibility + +0.05 2010-05-26 + - Documentation fixes and clarifications + +0.04 2010-01-22 + - Restore list context propagation for catch blocks + - Fix a bug where finally blocks weren't always invoked + +0.03 2010-01-22 + - Support for 'finally' blocks (Andy Yates) + - More documentation and tests (many people) + - Sets $@ to the previous value at the beginning of the eval, to allow + the capture of an error stack when calling die. + +0.02 2009-09-02 + - Doc fixes from chromatic + - Various minor fixes from Adam Kennedy + - Additional documentation and code clarifications + - 5.005_04 compatibility + +0.01 2009-08-31 + - Initial release @@ -0,0 +1,32 @@ +This software is Copyright (c) 2014 by Yuval Kogman. + +This is free software, licensed under: + + The MIT (X11) License + +The MIT License + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to +whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall +be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT +WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..56994ad --- /dev/null +++ b/MANIFEST @@ -0,0 +1,26 @@ +# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.014. +Changes +LICENSE +MANIFEST +META.json +META.yml +Makefile.PL +README +dist.ini +lib/Try/Tiny.pm +maint/bench.pl +t/00-compile.t +t/basic.t +t/context.t +t/erroneous_usage.t +t/finally.t +t/given_when.t +t/global_destruction_forked.t +t/global_destruction_load.t +t/lib/TryUser.pm +t/named.t +t/when.t +xt/release/eol.t +xt/release/no-tabs.t +xt/release/pod-coverage.t +xt/release/pod-syntax.t diff --git a/META.json b/META.json new file mode 100644 index 0000000..7464c6e --- /dev/null +++ b/META.json @@ -0,0 +1,492 @@ +{ + "abstract" : "minimal try/catch with proper preservation of $@", + "author" : [ + "Yuval Kogman <nothingmuch@woobling.org>", + "Jesse Luehrs <doy@tozt.net>" + ], + "dynamic_config" : 0, + "generated_by" : "Dist::Zilla version 5.014, CPAN::Meta::Converter version 2.140640", + "license" : [ + "mit" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Try-Tiny", + "prereqs" : { + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "6.30" + } + }, + "develop" : { + "requires" : { + "Pod::Coverage::TrustPod" : "0", + "Test::More" : "0", + "Test::NoTabs" : "0", + "Test::Pod" : "1.41", + "Test::Pod::Coverage" : "1.08" + } + }, + "runtime" : { + "requires" : { + "Carp" : "0", + "Exporter" : "5.57", + "constant" : "0", + "perl" : "5.006", + "strict" : "0", + "warnings" : "0" + } + }, + "test" : { + "recommends" : { + "Capture::Tiny" : "0.12", + "Sub::Name" : "0", + "perl" : "5.010" + }, + "requires" : { + "File::Spec" : "0", + "IO::Handle" : "0", + "IPC::Open3" : "0", + "Test::More" : "0", + "if" : "0" + } + } + }, + "provides" : { + "Try::Tiny" : { + "file" : "lib/Try/Tiny.pm", + "version" : "0.22" + } + }, + "release_status" : "stable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/doy/try-tiny/issues" + }, + "homepage" : "http://metacpan.org/release/Try-Tiny", + "repository" : { + "type" : "git", + "url" : "git://github.com/doy/try-tiny.git", + "web" : "https://github.com/doy/try-tiny" + } + }, + "version" : "0.22", + "x_Dist_Zilla" : { + "perl" : { + "version" : "5.018002" + }, + "plugins" : [ + { + "class" : "Dist::Zilla::Plugin::GatherDir", + "name" : "@DOY/GatherDir", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::PruneCruft", + "name" : "@DOY/PruneCruft", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::ManifestSkip", + "name" : "@DOY/ManifestSkip", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::MetaYAML", + "name" : "@DOY/MetaYAML", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::License", + "name" : "@DOY/License", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::Readme", + "name" : "@DOY/Readme", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::RunExtraTests", + "config" : { + "Dist::Zilla::Role::TestRunner" : { + "default_jobs" : 1 + } + }, + "name" : "@DOY/RunExtraTests", + "version" : "0.018" + }, + { + "class" : "Dist::Zilla::Plugin::ExecDir", + "name" : "@DOY/ExecDir", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::ShareDir", + "name" : "@DOY/ShareDir", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::MakeMaker", + "config" : { + "Dist::Zilla::Role::TestRunner" : { + "default_jobs" : 1 + } + }, + "name" : "@DOY/MakeMaker", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::Manifest", + "name" : "@DOY/Manifest", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::TestRelease", + "name" : "@DOY/TestRelease", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::ConfirmRelease", + "name" : "@DOY/ConfirmRelease", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::MetaConfig", + "name" : "@DOY/MetaConfig", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::MetaJSON", + "name" : "@DOY/MetaJSON", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::NextRelease", + "name" : "@DOY/NextRelease", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::CheckChangesHasContent", + "name" : "@DOY/CheckChangesHasContent", + "version" : "0.006" + }, + { + "class" : "Dist::Zilla::Plugin::PkgVersion", + "name" : "@DOY/PkgVersion", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::Authority", + "name" : "@DOY/Authority", + "version" : "1.006" + }, + { + "class" : "Dist::Zilla::Plugin::PodCoverageTests", + "name" : "@DOY/PodCoverageTests", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::PodSyntaxTests", + "name" : "@DOY/PodSyntaxTests", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::NoTabsTests", + "config" : { + "Dist::Zilla::Plugin::Test::NoTabs" : { + "module_finder" : [ + ":InstallModules" + ], + "script_finder" : [ + ":ExecFiles", + ":TestFiles" + ] + } + }, + "name" : "@DOY/NoTabsTests", + "version" : "0.07" + }, + { + "class" : "Dist::Zilla::Plugin::EOLTests", + "name" : "@DOY/EOLTests", + "version" : "0.02" + }, + { + "class" : "Dist::Zilla::Plugin::Test::Compile", + "config" : { + "Dist::Zilla::Plugin::Test::Compile" : { + "filename" : "t/00-compile.t", + "module_finder" : [ + ":InstallModules" + ], + "script_finder" : [ + ":ExecFiles" + ] + } + }, + "name" : "@DOY/Test::Compile", + "version" : "2.039" + }, + { + "class" : "Dist::Zilla::Plugin::Metadata", + "name" : "@DOY/Metadata", + "version" : "3.03" + }, + { + "class" : "Dist::Zilla::Plugin::MetaResources", + "name" : "@DOY/MetaResources", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Check", + "name" : "@DOY/Git::Check", + "version" : "2.020" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Commit", + "name" : "@DOY/Git::Commit", + "version" : "2.020" + }, + { + "class" : "Dist::Zilla::Plugin::Git::Tag", + "name" : "@DOY/Git::Tag", + "version" : "2.020" + }, + { + "class" : "Dist::Zilla::Plugin::Git::NextVersion", + "name" : "@DOY/Git::NextVersion", + "version" : "2.020" + }, + { + "class" : "Dist::Zilla::Plugin::ContributorsFromGit", + "name" : "@DOY/ContributorsFromGit", + "version" : "0.006" + }, + { + "class" : "Dist::Zilla::Plugin::MetaProvides::Package", + "config" : { + "Dist::Zilla::Plugin::MetaProvides::Package" : { + "finder_objects" : [ + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : "@DOY/MetaProvides::Package/AUTOVIV/:InstallModulesPM", + "version" : "5.014" + } + ] + }, + "Dist::Zilla::Role::MetaProvider::Provider" : { + "inherit_missing" : "1", + "inherit_version" : "1", + "meta_noindex" : "1" + } + }, + "name" : "@DOY/MetaProvides::Package", + "version" : "2.000001" + }, + { + "class" : "Dist::Zilla::Plugin::PodWeaver", + "config" : { + "Dist::Zilla::Plugin::PodWeaver" : { + "finder" : [ + ":InstallModules", + ":ExecFiles" + ], + "plugins" : [ + { + "class" : "Pod::Weaver::Plugin::EnsurePod5", + "name" : "@CorePrep/EnsurePod5", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Plugin::H1Nester", + "name" : "@CorePrep/H1Nester", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Plugin::SingleEncoding", + "name" : "@Default/SingleEncoding", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Name", + "name" : "@Default/Name", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Version", + "name" : "@Default/Version", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Region", + "name" : "@Default/prelude", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Generic", + "name" : "SYNOPSIS", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Generic", + "name" : "DESCRIPTION", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Generic", + "name" : "OVERVIEW", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Collect", + "name" : "ATTRIBUTES", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Collect", + "name" : "METHODS", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Collect", + "name" : "FUNCTIONS", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Leftovers", + "name" : "@Default/Leftovers", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Region", + "name" : "@Default/postlude", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Authors", + "name" : "@Default/Authors", + "version" : "4.006" + }, + { + "class" : "Pod::Weaver::Section::Legal", + "name" : "@Default/Legal", + "version" : "4.006" + } + ] + } + }, + "name" : "@DOY/PodWeaver", + "version" : "4.005" + }, + { + "class" : "Dist::Zilla::Plugin::UploadToCPAN", + "name" : "@DOY/UploadToCPAN", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::AutoPrereqs", + "name" : "AutoPrereqs", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::Prereqs", + "config" : { + "Dist::Zilla::Plugin::Prereqs" : { + "phase" : "runtime", + "type" : "requires" + } + }, + "name" : "Prereqs", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::Prereqs", + "config" : { + "Dist::Zilla::Plugin::Prereqs" : { + "phase" : "test", + "type" : "recommends" + } + }, + "name" : "TestRecommends", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":InstallModules", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":IncModules", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":TestFiles", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":ExecFiles", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":ShareFiles", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":MainModule", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":AllFiles", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : ":NoFiles", + "version" : "5.014" + }, + { + "class" : "Dist::Zilla::Plugin::FinderCode", + "name" : "@DOY/MetaProvides::Package/AUTOVIV/:InstallModulesPM", + "version" : "5.014" + } + ], + "zilla" : { + "class" : "Dist::Zilla::Dist::Builder", + "config" : { + "is_trial" : "0" + }, + "version" : "5.014" + } + }, + "x_authority" : "cpan:NUFFIN", + "x_contributors" : [ + "Alex <alex@koban.(none)>", + "Andrew Yates <ayates@haddock.local>", + "Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>", + "David Lowe <davidl@lokku.com>", + "Glenn Fowler <cebjyre@cpan.org>", + "Graham Knop <haarg@haarg.org>", + "Hans Dieter Pearcey <hdp@weftsoar.net>", + "Jonathan Yu <JAWNSY@cpan.org>", + "Karen Etheridge <ether@cpan.org>", + "Marc Mims <marc@questright.com>", + "Mark Fowler <mark@twoshortplanks.com>", + "Mark Stosberg <mark@stosberg.com>", + "Paul Howarth <paul@city-fan.org>", + "Peter Rabbitson <ribasushi@cpan.org>", + "Ricardo Signes <rjbs@cpan.org>", + "anaxagoras <walkeraj@gmail.com>", + "awalker <awalker@sourcefire.com>", + "chromatic <chromatic@wgz.org>" + ] +} + diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..1c9bbde --- /dev/null +++ b/META.yml @@ -0,0 +1,358 @@ +--- +abstract: 'minimal try/catch with proper preservation of $@' +author: + - 'Yuval Kogman <nothingmuch@woobling.org>' + - 'Jesse Luehrs <doy@tozt.net>' +build_requires: + File::Spec: '0' + IO::Handle: '0' + IPC::Open3: '0' + Test::More: '0' + if: '0' +configure_requires: + ExtUtils::MakeMaker: '6.30' +dynamic_config: 0 +generated_by: 'Dist::Zilla version 5.014, CPAN::Meta::Converter version 2.140640' +license: mit +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: Try-Tiny +provides: + Try::Tiny: + file: lib/Try/Tiny.pm + version: '0.22' +requires: + Carp: '0' + Exporter: '5.57' + constant: '0' + perl: '5.006' + strict: '0' + warnings: '0' +resources: + bugtracker: https://github.com/doy/try-tiny/issues + homepage: http://metacpan.org/release/Try-Tiny + repository: git://github.com/doy/try-tiny.git +version: '0.22' +x_Dist_Zilla: + perl: + version: '5.018002' + plugins: + - + class: Dist::Zilla::Plugin::GatherDir + name: '@DOY/GatherDir' + version: '5.014' + - + class: Dist::Zilla::Plugin::PruneCruft + name: '@DOY/PruneCruft' + version: '5.014' + - + class: Dist::Zilla::Plugin::ManifestSkip + name: '@DOY/ManifestSkip' + version: '5.014' + - + class: Dist::Zilla::Plugin::MetaYAML + name: '@DOY/MetaYAML' + version: '5.014' + - + class: Dist::Zilla::Plugin::License + name: '@DOY/License' + version: '5.014' + - + class: Dist::Zilla::Plugin::Readme + name: '@DOY/Readme' + version: '5.014' + - + class: Dist::Zilla::Plugin::RunExtraTests + config: + Dist::Zilla::Role::TestRunner: + default_jobs: 1 + name: '@DOY/RunExtraTests' + version: '0.018' + - + class: Dist::Zilla::Plugin::ExecDir + name: '@DOY/ExecDir' + version: '5.014' + - + class: Dist::Zilla::Plugin::ShareDir + name: '@DOY/ShareDir' + version: '5.014' + - + class: Dist::Zilla::Plugin::MakeMaker + config: + Dist::Zilla::Role::TestRunner: + default_jobs: 1 + name: '@DOY/MakeMaker' + version: '5.014' + - + class: Dist::Zilla::Plugin::Manifest + name: '@DOY/Manifest' + version: '5.014' + - + class: Dist::Zilla::Plugin::TestRelease + name: '@DOY/TestRelease' + version: '5.014' + - + class: Dist::Zilla::Plugin::ConfirmRelease + name: '@DOY/ConfirmRelease' + version: '5.014' + - + class: Dist::Zilla::Plugin::MetaConfig + name: '@DOY/MetaConfig' + version: '5.014' + - + class: Dist::Zilla::Plugin::MetaJSON + name: '@DOY/MetaJSON' + version: '5.014' + - + class: Dist::Zilla::Plugin::NextRelease + name: '@DOY/NextRelease' + version: '5.014' + - + class: Dist::Zilla::Plugin::CheckChangesHasContent + name: '@DOY/CheckChangesHasContent' + version: '0.006' + - + class: Dist::Zilla::Plugin::PkgVersion + name: '@DOY/PkgVersion' + version: '5.014' + - + class: Dist::Zilla::Plugin::Authority + name: '@DOY/Authority' + version: '1.006' + - + class: Dist::Zilla::Plugin::PodCoverageTests + name: '@DOY/PodCoverageTests' + version: '5.014' + - + class: Dist::Zilla::Plugin::PodSyntaxTests + name: '@DOY/PodSyntaxTests' + version: '5.014' + - + class: Dist::Zilla::Plugin::NoTabsTests + config: + Dist::Zilla::Plugin::Test::NoTabs: + module_finder: + - ':InstallModules' + script_finder: + - ':ExecFiles' + - ':TestFiles' + name: '@DOY/NoTabsTests' + version: '0.07' + - + class: Dist::Zilla::Plugin::EOLTests + name: '@DOY/EOLTests' + version: '0.02' + - + class: Dist::Zilla::Plugin::Test::Compile + config: + Dist::Zilla::Plugin::Test::Compile: + filename: t/00-compile.t + module_finder: + - ':InstallModules' + script_finder: + - ':ExecFiles' + name: '@DOY/Test::Compile' + version: '2.039' + - + class: Dist::Zilla::Plugin::Metadata + name: '@DOY/Metadata' + version: '3.03' + - + class: Dist::Zilla::Plugin::MetaResources + name: '@DOY/MetaResources' + version: '5.014' + - + class: Dist::Zilla::Plugin::Git::Check + name: '@DOY/Git::Check' + version: '2.020' + - + class: Dist::Zilla::Plugin::Git::Commit + name: '@DOY/Git::Commit' + version: '2.020' + - + class: Dist::Zilla::Plugin::Git::Tag + name: '@DOY/Git::Tag' + version: '2.020' + - + class: Dist::Zilla::Plugin::Git::NextVersion + name: '@DOY/Git::NextVersion' + version: '2.020' + - + class: Dist::Zilla::Plugin::ContributorsFromGit + name: '@DOY/ContributorsFromGit' + version: '0.006' + - + class: Dist::Zilla::Plugin::MetaProvides::Package + config: + Dist::Zilla::Plugin::MetaProvides::Package: + finder_objects: + - + class: Dist::Zilla::Plugin::FinderCode + name: '@DOY/MetaProvides::Package/AUTOVIV/:InstallModulesPM' + version: '5.014' + Dist::Zilla::Role::MetaProvider::Provider: + inherit_missing: '1' + inherit_version: '1' + meta_noindex: '1' + name: '@DOY/MetaProvides::Package' + version: '2.000001' + - + class: Dist::Zilla::Plugin::PodWeaver + config: + Dist::Zilla::Plugin::PodWeaver: + finder: + - ':InstallModules' + - ':ExecFiles' + plugins: + - + class: Pod::Weaver::Plugin::EnsurePod5 + name: '@CorePrep/EnsurePod5' + version: '4.006' + - + class: Pod::Weaver::Plugin::H1Nester + name: '@CorePrep/H1Nester' + version: '4.006' + - + class: Pod::Weaver::Plugin::SingleEncoding + name: '@Default/SingleEncoding' + version: '4.006' + - + class: Pod::Weaver::Section::Name + name: '@Default/Name' + version: '4.006' + - + class: Pod::Weaver::Section::Version + name: '@Default/Version' + version: '4.006' + - + class: Pod::Weaver::Section::Region + name: '@Default/prelude' + version: '4.006' + - + class: Pod::Weaver::Section::Generic + name: SYNOPSIS + version: '4.006' + - + class: Pod::Weaver::Section::Generic + name: DESCRIPTION + version: '4.006' + - + class: Pod::Weaver::Section::Generic + name: OVERVIEW + version: '4.006' + - + class: Pod::Weaver::Section::Collect + name: ATTRIBUTES + version: '4.006' + - + class: Pod::Weaver::Section::Collect + name: METHODS + version: '4.006' + - + class: Pod::Weaver::Section::Collect + name: FUNCTIONS + version: '4.006' + - + class: Pod::Weaver::Section::Leftovers + name: '@Default/Leftovers' + version: '4.006' + - + class: Pod::Weaver::Section::Region + name: '@Default/postlude' + version: '4.006' + - + class: Pod::Weaver::Section::Authors + name: '@Default/Authors' + version: '4.006' + - + class: Pod::Weaver::Section::Legal + name: '@Default/Legal' + version: '4.006' + name: '@DOY/PodWeaver' + version: '4.005' + - + class: Dist::Zilla::Plugin::UploadToCPAN + name: '@DOY/UploadToCPAN' + version: '5.014' + - + class: Dist::Zilla::Plugin::AutoPrereqs + name: AutoPrereqs + version: '5.014' + - + class: Dist::Zilla::Plugin::Prereqs + config: + Dist::Zilla::Plugin::Prereqs: + phase: runtime + type: requires + name: Prereqs + version: '5.014' + - + class: Dist::Zilla::Plugin::Prereqs + config: + Dist::Zilla::Plugin::Prereqs: + phase: test + type: recommends + name: TestRecommends + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':InstallModules' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':IncModules' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':TestFiles' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':ExecFiles' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':ShareFiles' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':MainModule' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':AllFiles' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: ':NoFiles' + version: '5.014' + - + class: Dist::Zilla::Plugin::FinderCode + name: '@DOY/MetaProvides::Package/AUTOVIV/:InstallModulesPM' + version: '5.014' + zilla: + class: Dist::Zilla::Dist::Builder + config: + is_trial: '0' + version: '5.014' +x_authority: cpan:NUFFIN +x_contributors: + - 'Alex <alex@koban.(none)>' + - 'Andrew Yates <ayates@haddock.local>' + - 'Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>' + - 'David Lowe <davidl@lokku.com>' + - 'Glenn Fowler <cebjyre@cpan.org>' + - 'Graham Knop <haarg@haarg.org>' + - 'Hans Dieter Pearcey <hdp@weftsoar.net>' + - 'Jonathan Yu <JAWNSY@cpan.org>' + - 'Karen Etheridge <ether@cpan.org>' + - 'Marc Mims <marc@questright.com>' + - 'Mark Fowler <mark@twoshortplanks.com>' + - 'Mark Stosberg <mark@stosberg.com>' + - 'Paul Howarth <paul@city-fan.org>' + - 'Peter Rabbitson <ribasushi@cpan.org>' + - 'Ricardo Signes <rjbs@cpan.org>' + - 'anaxagoras <walkeraj@gmail.com>' + - 'awalker <awalker@sourcefire.com>' + - 'chromatic <chromatic@wgz.org>' diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..521700b --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,70 @@ + +# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.014. +use strict; +use warnings; + +use 5.006; + +use ExtUtils::MakeMaker 6.30; + + + +my %WriteMakefileArgs = ( + "ABSTRACT" => "minimal try/catch with proper preservation of \$\@", + "AUTHOR" => "Yuval Kogman <nothingmuch\@woobling.org>, Jesse Luehrs <doy\@tozt.net>", + "BUILD_REQUIRES" => {}, + "CONFIGURE_REQUIRES" => { + "ExtUtils::MakeMaker" => "6.30" + }, + "DISTNAME" => "Try-Tiny", + "EXE_FILES" => [], + "LICENSE" => "mit", + "NAME" => "Try::Tiny", + "PREREQ_PM" => { + "Carp" => 0, + "Exporter" => "5.57", + "constant" => 0, + "strict" => 0, + "warnings" => 0 + }, + "TEST_REQUIRES" => { + "File::Spec" => 0, + "IO::Handle" => 0, + "IPC::Open3" => 0, + "Test::More" => 0, + "if" => 0 + }, + "VERSION" => "0.22", + "test" => { + "TESTS" => "t/*.t" + } +); + + +my %FallbackPrereqs = ( + "Carp" => 0, + "Exporter" => "5.57", + "File::Spec" => 0, + "IO::Handle" => 0, + "IPC::Open3" => 0, + "Test::More" => 0, + "constant" => 0, + "if" => 0, + "strict" => 0, + "warnings" => 0 +); + + +unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { + delete $WriteMakefileArgs{TEST_REQUIRES}; + delete $WriteMakefileArgs{BUILD_REQUIRES}; + $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; +} + +delete $WriteMakefileArgs{CONFIGURE_REQUIRES} + unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; + +WriteMakefile(%WriteMakefileArgs); + + + @@ -0,0 +1,16 @@ + + +This archive contains the distribution Try-Tiny, +version 0.22: + + minimal try/catch with proper preservation of $@ + +This software is Copyright (c) 2014 by Yuval Kogman. + +This is free software, licensed under: + + The MIT (X11) License + + +This README file was generated by Dist::Zilla::Plugin::Readme v5.014. + diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..000b1b3 --- /dev/null +++ b/dist.ini @@ -0,0 +1,30 @@ +name = Try-Tiny +author = Yuval Kogman <nothingmuch@woobling.org> +author = Jesse Luehrs <doy@tozt.net> +license = MIT +copyright_holder = Yuval Kogman + +[@DOY] +:version = 0.14 +dist = Try-Tiny +repository = github +authority = cpan:NUFFIN +done_testing = 0 +Git::Tag_tag_format = %N-%v +Git::NextVersion_version_regexp = ^Try-Tiny-(.+)$ + +[AutoPrereqs] +; tests optionally require 5.010 +skip = ^perl$ +; tests for optional Sub::Name stuff +skip = ^Sub::Name$ +; tests optionally require Capture::Tiny +skip = ^Capture::Tiny$ + +[Prereqs] +perl = 5.006 + +[Prereqs / TestRecommends] +Capture::Tiny = 0.12 ; capture_stderr +perl = 5.010 +Sub::Name = 0 diff --git a/lib/Try/Tiny.pm b/lib/Try/Tiny.pm new file mode 100644 index 0000000..300561e --- /dev/null +++ b/lib/Try/Tiny.pm @@ -0,0 +1,677 @@ +package Try::Tiny; +BEGIN { + $Try::Tiny::AUTHORITY = 'cpan:NUFFIN'; +} +$Try::Tiny::VERSION = '0.22'; +use 5.006; +# ABSTRACT: minimal try/catch with proper preservation of $@ + +use strict; +use warnings; + +use Exporter 5.57 'import'; +our @EXPORT = our @EXPORT_OK = qw(try catch finally); + +use Carp; +$Carp::Internal{+__PACKAGE__}++; + +BEGIN { eval "use Sub::Name; 1" or *{subname} = sub {1} } + +# Need to prototype as @ not $$ because of the way Perl evaluates the prototype. +# Keeping it at $$ means you only ever get 1 sub because we need to eval in a list +# context & not a scalar one + +sub try (&;@) { + my ( $try, @code_refs ) = @_; + + # we need to save this here, the eval block will be in scalar context due + # to $failed + my $wantarray = wantarray; + + # work around perl bug by explicitly initializing these, due to the likelyhood + # this will be used in global destruction (perl rt#119311) + my ( $catch, @finally ) = (); + + # find labeled blocks in the argument list. + # catch and finally tag the blocks by blessing a scalar reference to them. + foreach my $code_ref (@code_refs) { + + if ( ref($code_ref) eq 'Try::Tiny::Catch' ) { + croak 'A try() may not be followed by multiple catch() blocks' + if $catch; + $catch = ${$code_ref}; + } elsif ( ref($code_ref) eq 'Try::Tiny::Finally' ) { + push @finally, ${$code_ref}; + } else { + croak( + 'try() encountered an unexpected argument (' + . ( defined $code_ref ? $code_ref : 'undef' ) + . ') - perhaps a missing semi-colon before or' + ); + } + } + + # FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's + # not perfect, but we could provide a list of additional errors for + # $catch->(); + + # name the blocks if we have Sub::Name installed + my $caller = caller; + subname("${caller}::try {...} " => $try); + subname("${caller}::catch {...} " => $catch) if $catch; + subname("${caller}::finally {...} " => $_) foreach @finally; + + # save the value of $@ so we can set $@ back to it in the beginning of the eval + # and restore $@ after the eval finishes + my $prev_error = $@; + + my ( @ret, $error ); + + # failed will be true if the eval dies, because 1 will not be returned + # from the eval body + my $failed = not eval { + $@ = $prev_error; + + # evaluate the try block in the correct context + if ( $wantarray ) { + @ret = $try->(); + } elsif ( defined $wantarray ) { + $ret[0] = $try->(); + } else { + $try->(); + }; + + return 1; # properly set $fail to false + }; + + # preserve the current error and reset the original value of $@ + $error = $@; + $@ = $prev_error; + + # set up a scope guard to invoke the finally block at the end + my @guards = + map { Try::Tiny::ScopeGuard->_new($_, $failed ? $error : ()) } + @finally; + + # at this point $failed contains a true value if the eval died, even if some + # destructor overwrote $@ as the eval was unwinding. + if ( $failed ) { + # if we got an error, invoke the catch block. + if ( $catch ) { + # This works like given($error), but is backwards compatible and + # sets $_ in the dynamic scope for the body of C<$catch> + for ($error) { + return $catch->($error); + } + + # in case when() was used without an explicit return, the C<for> + # loop will be aborted and there's no useful return value + } + + return; + } else { + # no failure, $@ is back to what it was, everything is fine + return $wantarray ? @ret : $ret[0]; + } +} + +sub catch (&;@) { + my ( $block, @rest ) = @_; + + croak 'Useless bare catch()' unless wantarray; + + return ( + bless(\$block, 'Try::Tiny::Catch'), + @rest, + ); +} + +sub finally (&;@) { + my ( $block, @rest ) = @_; + + croak 'Useless bare finally()' unless wantarray; + + return ( + bless(\$block, 'Try::Tiny::Finally'), + @rest, + ); +} + +{ + package # hide from PAUSE + Try::Tiny::ScopeGuard; + + use constant UNSTABLE_DOLLARAT => ($] < '5.013002') ? 1 : 0; + + sub _new { + shift; + bless [ @_ ]; + } + + sub DESTROY { + my ($code, @args) = @{ $_[0] }; + + local $@ if UNSTABLE_DOLLARAT; + eval { + $code->(@args); + 1; + } or do { + warn + "Execution of finally() block $code resulted in an exception, which " + . '*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. ' + . 'Your program will continue as if this event never took place. ' + . "Original exception text follows:\n\n" + . (defined $@ ? $@ : '$@ left undefined...') + . "\n" + ; + } + } +} + +__PACKAGE__ + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Try::Tiny - minimal try/catch with proper preservation of $@ + +=head1 VERSION + +version 0.22 + +=head1 SYNOPSIS + +You can use Try::Tiny's C<try> and C<catch> to expect and handle exceptional +conditions, avoiding quirks in Perl and common mistakes: + + # handle errors with a catch handler + try { + die "foo"; + } catch { + warn "caught error: $_"; # not $@ + }; + +You can also use it like a standalone C<eval> to catch and ignore any error +conditions. Obviously, this is an extreme measure not to be undertaken +lightly: + + # just silence errors + try { + die "foo"; + }; + +=head1 DESCRIPTION + +This module provides bare bones C<try>/C<catch>/C<finally> statements that are designed to +minimize common mistakes with eval blocks, and NOTHING else. + +This is unlike L<TryCatch> which provides a nice syntax and avoids adding +another call stack layer, and supports calling C<return> from the C<try> block to +return from the parent subroutine. These extra features come at a cost of a few +dependencies, namely L<Devel::Declare> and L<Scope::Upper> which are +occasionally problematic, and the additional catch filtering uses L<Moose> +type constraints which may not be desirable either. + +The main focus of this module is to provide simple and reliable error handling +for those having a hard time installing L<TryCatch>, but who still want to +write correct C<eval> blocks without 5 lines of boilerplate each time. + +It's designed to work as correctly as possible in light of the various +pathological edge cases (see L</BACKGROUND>) and to be compatible with any style +of error values (simple strings, references, objects, overloaded objects, etc). + +If the C<try> block dies, it returns the value of the last statement executed in +the C<catch> block, if there is one. Otherwise, it returns C<undef> in scalar +context or the empty list in list context. The following examples all +assign C<"bar"> to C<$x>: + + my $x = try { die "foo" } catch { "bar" }; + my $x = try { die "foo" } || { "bar" }; + my $x = (try { die "foo" }) // { "bar" }; + + my $x = eval { die "foo" } || "bar"; + +You can add C<finally> blocks, yielding the following: + + my $x; + try { die 'foo' } finally { $x = 'bar' }; + try { die 'foo' } catch { warn "Got a die: $_" } finally { $x = 'bar' }; + +C<finally> blocks are always executed making them suitable for cleanup code +which cannot be handled using local. You can add as many C<finally> blocks to a +given C<try> block as you like. + +Note that adding a C<finally> block without a preceding C<catch> block +suppresses any errors. This behaviour is consistent with using a standalone +C<eval>, but it is not consistent with C<try>/C<finally> patterns found in +other programming languages, such as Java, Python, Javascript or C#. If you +learnt the C<try>/C<finally> pattern from one of these languages, watch out for +this. + +=head1 EXPORTS + +All functions are exported by default using L<Exporter>. + +If you need to rename the C<try>, C<catch> or C<finally> keyword consider using +L<Sub::Import> to get L<Sub::Exporter>'s flexibility. + +=over 4 + +=item try (&;@) + +Takes one mandatory C<try> subroutine, an optional C<catch> subroutine and C<finally> +subroutine. + +The mandatory subroutine is evaluated in the context of an C<eval> block. + +If no error occurred the value from the first block is returned, preserving +list/scalar context. + +If there was an error and the second subroutine was given it will be invoked +with the error in C<$_> (localized) and as that block's first and only +argument. + +C<$@> does B<not> contain the error. Inside the C<catch> block it has the same +value it had before the C<try> block was executed. + +Note that the error may be false, but if that happens the C<catch> block will +still be invoked. + +Once all execution is finished then the C<finally> block, if given, will execute. + +=item catch (&;@) + +Intended to be used in the second argument position of C<try>. + +Returns a reference to the subroutine it was given but blessed as +C<Try::Tiny::Catch> which allows try to decode correctly what to do +with this code reference. + + catch { ... } + +Inside the C<catch> block the caught error is stored in C<$_>, while previous +value of C<$@> is still available for use. This value may or may not be +meaningful depending on what happened before the C<try>, but it might be a good +idea to preserve it in an error stack. + +For code that captures C<$@> when throwing new errors (i.e. +L<Class::Throwable>), you'll need to do: + + local $@ = $_; + +=item finally (&;@) + + try { ... } + catch { ... } + finally { ... }; + +Or + + try { ... } + finally { ... }; + +Or even + + try { ... } + finally { ... } + catch { ... }; + +Intended to be the second or third element of C<try>. C<finally> blocks are always +executed in the event of a successful C<try> or if C<catch> is run. This allows +you to locate cleanup code which cannot be done via C<local()> e.g. closing a file +handle. + +When invoked, the C<finally> block is passed the error that was caught. If no +error was caught, it is passed nothing. (Note that the C<finally> block does not +localize C<$_> with the error, since unlike in a C<catch> block, there is no way +to know if C<$_ == undef> implies that there were no errors.) In other words, +the following code does just what you would expect: + + try { + die_sometimes(); + } catch { + # ...code run in case of error + } finally { + if (@_) { + print "The try block died with: @_\n"; + } else { + print "The try block ran without error.\n"; + } + }; + +B<You must always do your own error handling in the C<finally> block>. C<Try::Tiny> will +not do anything about handling possible errors coming from code located in these +blocks. + +Furthermore B<exceptions in C<finally> blocks are not trappable and are unable +to influence the execution of your program>. This is due to limitation of +C<DESTROY>-based scope guards, which C<finally> is implemented on top of. This +may change in a future version of Try::Tiny. + +In the same way C<catch()> blesses the code reference this subroutine does the same +except it bless them as C<Try::Tiny::Finally>. + +=back + +=head1 BACKGROUND + +There are a number of issues with C<eval>. + +=head2 Clobbering $@ + +When you run an C<eval> block and it succeeds, C<$@> will be cleared, potentially +clobbering an error that is currently being caught. + +This causes action at a distance, clearing previous errors your caller may have +not yet handled. + +C<$@> must be properly localized before invoking C<eval> in order to avoid this +issue. + +More specifically, C<$@> is clobbered at the beginning of the C<eval>, which +also makes it impossible to capture the previous error before you die (for +instance when making exception objects with error stacks). + +For this reason C<try> will actually set C<$@> to its previous value (the one +available before entering the C<try> block) in the beginning of the C<eval> +block. + +=head2 Localizing $@ silently masks errors + +Inside an C<eval> block, C<die> behaves sort of like: + + sub die { + $@ = $_[0]; + return_undef_from_eval(); + } + +This means that if you were polite and localized C<$@> you can't die in that +scope, or your error will be discarded (printing "Something's wrong" instead). + +The workaround is very ugly: + + my $error = do { + local $@; + eval { ... }; + $@; + }; + + ... + die $error; + +=head2 $@ might not be a true value + +This code is wrong: + + if ( $@ ) { + ... + } + +because due to the previous caveats it may have been unset. + +C<$@> could also be an overloaded error object that evaluates to false, but +that's asking for trouble anyway. + +The classic failure mode is: + + sub Object::DESTROY { + eval { ... } + } + + eval { + my $obj = Object->new; + + die "foo"; + }; + + if ( $@ ) { + + } + +In this case since C<Object::DESTROY> is not localizing C<$@> but still uses +C<eval>, it will set C<$@> to C<"">. + +The destructor is called when the stack is unwound, after C<die> sets C<$@> to +C<"foo at Foo.pm line 42\n">, so by the time C<if ( $@ )> is evaluated it has +been cleared by C<eval> in the destructor. + +The workaround for this is even uglier than the previous ones. Even though we +can't save the value of C<$@> from code that doesn't localize, we can at least +be sure the C<eval> was aborted due to an error: + + my $failed = not eval { + ... + + return 1; + }; + +This is because an C<eval> that caught a C<die> will always return a false +value. + +=head1 SHINY SYNTAX + +Using Perl 5.10 you can use L<perlsyn/"Switch statements">. + +The C<catch> block is invoked in a topicalizer context (like a C<given> block), +but note that you can't return a useful value from C<catch> using the C<when> +blocks without an explicit C<return>. + +This is somewhat similar to Perl 6's C<CATCH> blocks. You can use it to +concisely match errors: + + try { + require Foo; + } catch { + when (/^Can't locate .*?\.pm in \@INC/) { } # ignore + default { die $_ } + }; + +=head1 CAVEATS + +=over 4 + +=item * + +C<@_> is not available within the C<try> block, so you need to copy your +arglist. In case you want to work with argument values directly via C<@_> +aliasing (i.e. allow C<$_[1] = "foo">), you need to pass C<@_> by reference: + + sub foo { + my ( $self, @args ) = @_; + try { $self->bar(@args) } + } + +or + + sub bar_in_place { + my $self = shift; + my $args = \@_; + try { $_ = $self->bar($_) for @$args } + } + +=item * + +C<return> returns from the C<try> block, not from the parent sub (note that +this is also how C<eval> works, but not how L<TryCatch> works): + + sub parent_sub { + try { + die; + } + catch { + return; + }; + + say "this text WILL be displayed, even though an exception is thrown"; + } + +Instead, you should capture the return value: + + sub parent_sub { + my $success = try { + die; + 1; + }; + return unless $success; + + say "This text WILL NEVER appear!"; + } + # OR + sub parent_sub_with_catch { + my $success = try { + die; + 1; + } + catch { + # do something with $_ + return undef; #see note + }; + return unless $success; + + say "This text WILL NEVER appear!"; + } + +Note that if you have a C<catch> block, it must return C<undef> for this to work, +since if a C<catch> block exists, its return value is returned in place of C<undef> +when an exception is thrown. + +=item * + +C<try> introduces another caller stack frame. L<Sub::Uplevel> is not used. L<Carp> +will not report this when using full stack traces, though, because +C<%Carp::Internal> is used. This lack of magic is considered a feature. + +=item * + +The value of C<$_> in the C<catch> block is not guaranteed to be the value of +the exception thrown (C<$@>) in the C<try> block. There is no safe way to +ensure this, since C<eval> may be used unhygenically in destructors. The only +guarantee is that the C<catch> will be called if an exception is thrown. + +=item * + +The return value of the C<catch> block is not ignored, so if testing the result +of the expression for truth on success, be sure to return a false value from +the C<catch> block: + + my $obj = try { + MightFail->new; + } catch { + ... + + return; # avoid returning a true value; + }; + + return unless $obj; + +=item * + +C<$SIG{__DIE__}> is still in effect. + +Though it can be argued that C<$SIG{__DIE__}> should be disabled inside of +C<eval> blocks, since it isn't people have grown to rely on it. Therefore in +the interests of compatibility, C<try> does not disable C<$SIG{__DIE__}> for +the scope of the error throwing code. + +=item * + +Lexical C<$_> may override the one set by C<catch>. + +For example Perl 5.10's C<given> form uses a lexical C<$_>, creating some +confusing behavior: + + given ($foo) { + when (...) { + try { + ... + } catch { + warn $_; # will print $foo, not the error + warn $_[0]; # instead, get the error like this + } + } + } + +Note that this behavior was changed once again in L<Perl5 version 18 +|https://metacpan.org/module/perldelta#given-now-aliases-the-global-_>. +However, since the entirety of lexical C<$_> is now L<considired experimental +|https://metacpan.org/module/perldelta#Lexical-_-is-now-experimental>, it +is unclear whether the new version 18 behavior is final. + +=back + +=head1 SEE ALSO + +=over 4 + +=item L<TryCatch> + +Much more feature complete, more convenient semantics, but at the cost of +implementation complexity. + +=item L<autodie> + +Automatic error throwing for builtin functions and more. Also designed to +work well with C<given>/C<when>. + +=item L<Throwable> + +A lightweight role for rolling your own exception classes. + +=item L<Error> + +Exception object implementation with a C<try> statement. Does not localize +C<$@>. + +=item L<Exception::Class::TryCatch> + +Provides a C<catch> statement, but properly calling C<eval> is your +responsibility. + +The C<try> keyword pushes C<$@> onto an error stack, avoiding some of the +issues with C<$@>, but you still need to localize to prevent clobbering. + +=back + +=head1 LIGHTNING TALK + +I gave a lightning talk about this module, you can see the slides (Firefox +only): + +L<http://web.archive.org/web/20100628040134/http://nothingmuch.woobling.org/talks/takahashi.xul> + +Or read the source: + +L<http://web.archive.org/web/20100305133605/http://nothingmuch.woobling.org/talks/yapc_asia_2009/try_tiny.yml> + +=head1 VERSION CONTROL + +L<http://github.com/doy/try-tiny/> + +=head1 AUTHORS + +=over 4 + +=item * + +Yuval Kogman <nothingmuch@woobling.org> + +=item * + +Jesse Luehrs <doy@tozt.net> + +=back + +=head1 COPYRIGHT AND LICENSE + +This software is Copyright (c) 2014 by Yuval Kogman. + +This is free software, licensed under: + + The MIT (X11) License + +=cut diff --git a/maint/bench.pl b/maint/bench.pl new file mode 100644 index 0000000..215097c --- /dev/null +++ b/maint/bench.pl @@ -0,0 +1,14 @@ +#!/usr/bin/env perl + +use warnings; +use strict; + +use Benchmark::Dumb ':all'; +use Try::Tiny; + +my $max = 10_000; + +cmpthese('0.003', { + eval => sub { do { local $@; eval { die 'foo' } } for (1..$max) }, + try => sub { do { try { die 'foo' } } for (1..$max) }, +}); diff --git a/t/00-compile.t b/t/00-compile.t new file mode 100644 index 0000000..f9cf666 --- /dev/null +++ b/t/00-compile.t @@ -0,0 +1,50 @@ +use 5.006; +use strict; +use warnings; + +# this test was generated with Dist::Zilla::Plugin::Test::Compile 2.039 + +use Test::More tests => 1 + ($ENV{AUTHOR_TESTING} ? 1 : 0); + + + +my @module_files = ( + 'Try/Tiny.pm' +); + + + +# no fake home requested + +my $inc_switch = -d 'blib' ? '-Mblib' : '-Ilib'; + +use File::Spec; +use IPC::Open3; +use IO::Handle; + +open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!"; + +my @warnings; +for my $lib (@module_files) +{ + # see L<perlfaq8/How can I capture STDERR from an external command?> + my $stderr = IO::Handle->new; + + my $pid = open3($stdin, '>&STDERR', $stderr, $^X, $inc_switch, '-e', "require q[$lib]"); + binmode $stderr, ':crlf' if $^O eq 'MSWin32'; + my @_warnings = <$stderr>; + waitpid($pid, 0); + is($?, 0, "$lib loaded ok"); + + if (@_warnings) + { + warn @_warnings; + push @warnings, @_warnings; + } +} + + + +is(scalar(@warnings), 0, 'no warnings found') if $ENV{AUTHOR_TESTING}; + + diff --git a/t/basic.t b/t/basic.t new file mode 100644 index 0000000..3f0e2ac --- /dev/null +++ b/t/basic.t @@ -0,0 +1,166 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 25; + +use Try::Tiny; + +sub _eval { + local $@; + local $Test::Builder::Level = $Test::Builder::Level + 2; + return ( scalar(eval { $_[0]->(); 1 }), $@ ); +} + + +sub lives_ok (&$) { + my ( $code, $desc ) = @_; + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ( $ok, $error ) = _eval($code); + + ok($ok, $desc ); + + diag "error: $@" unless $ok; +} + +sub throws_ok (&$$) { + my ( $code, $regex, $desc ) = @_; + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ( $ok, $error ) = _eval($code); + + if ( $ok ) { + fail($desc); + } else { + like($error || '', $regex, $desc ); + } +} + + +my $prev; + +lives_ok { + try { + die "foo"; + }; +} "basic try"; + +throws_ok { + try { + die "foo"; + } catch { die $_ }; +} qr/foo/, "rethrow"; + + +{ + local $@ = "magic"; + is( try { 42 }, 42, "try block evaluated" ); + is( $@, "magic", '$@ untouched' ); +} + +{ + local $@ = "magic"; + is( try { die "foo" }, undef, "try block died" ); + is( $@, "magic", '$@ untouched' ); +} + +{ + local $@ = "magic"; + like( (try { die "foo" } catch { $_ }), qr/foo/, "catch block evaluated" ); + is( $@, "magic", '$@ untouched' ); +} + +is( scalar(try { "foo", "bar", "gorch" }), "gorch", "scalar context try" ); +is_deeply( [ try {qw(foo bar gorch)} ], [qw(foo bar gorch)], "list context try" ); + +is( scalar(try { die } catch { "foo", "bar", "gorch" }), "gorch", "scalar context catch" ); +is_deeply( [ try { die } catch {qw(foo bar gorch)} ], [qw(foo bar gorch)], "list context catch" ); + + +{ + my ($sub) = catch { my $a = $_; }; + is(ref($sub), 'Try::Tiny::Catch', 'Checking catch subroutine scalar reference is correctly blessed'); +} + +{ + my ($sub) = finally { my $a = $_; }; + is(ref($sub), 'Try::Tiny::Finally', 'Checking finally subroutine scalar reference is correctly blessed'); +} + +lives_ok { + try { + die "foo"; + } catch { + my $err = shift; + + try { + like $err, qr/foo/; + } catch { + fail("shouldn't happen"); + }; + + pass "got here"; + } +} "try in try catch block"; + +throws_ok { + try { + die "foo"; + } catch { + my $err = shift; + + try { } catch { }; + + die "rethrowing $err"; + } +} qr/rethrowing foo/, "rethrow with try in catch block"; + + +sub Evil::DESTROY { + eval { "oh noes" }; +} + +sub Evil::new { bless { }, $_[0] } + +{ + local $@ = "magic"; + local $_ = "other magic"; + + try { + my $object = Evil->new; + die "foo"; + } catch { + pass("catch invoked"); + local $TODO = "i don't think we can ever make this work sanely, maybe with SIG{__DIE__}" if $] < 5.014; + like($_, qr/foo/); + }; + + is( $@, "magic", '$@ untouched' ); + is( $_, "other magic", '$_ untouched' ); +} + +{ + my ( $caught, $prev ); + + { + local $@; + + eval { die "bar\n" }; + + is( $@, "bar\n", 'previous value of $@' ); + + try { + die { + prev => $@, + } + } catch { + $caught = $_; + $prev = $@; + } + } + + is_deeply( $caught, { prev => "bar\n" }, 'previous value of $@ available for capture' ); + is( $prev, "bar\n", 'previous value of $@ also available in catch block' ); +} diff --git a/t/context.t b/t/context.t new file mode 100644 index 0000000..1a57ba0 --- /dev/null +++ b/t/context.t @@ -0,0 +1,71 @@ +use strict; +use warnings; + +use Test::More; + +use Try::Tiny; + +plan tests => + (4+1) * 2 # list/scalar with exception (try + catch + 2 x finally) + is_deeply ++ 4 # void with exception ++ (3+1) * 2 # list/scalar no exception (try + 2 x finally) + is_deeply ++ 3 # void no exception +; + +my $ctx_index = { + VOID => undef, + LIST => 1, + SCALAR => '', +}; +my ($ctx, $die); + +for (sort keys %$ctx_index) { + $ctx = $_; + for (0,1) { + $die = $_; + if ($ctx_index->{$ctx}) { + is_deeply( + [ run() ], + [ $die ? 'catch' : 'try' ], + ); + } + elsif (defined $ctx_index->{$ctx}) { + is_deeply( + [ scalar run() ], + [ $die ? 'catch' : 'try' ], + ); + } + else { + run(); + 1; + } + } +} + +sub run { + try { + is (wantarray, $ctx_index->{$ctx}, "Proper context $ctx in try{}"); + die if $die; + return 'try'; + } + catch { + is (wantarray, $ctx_index->{$ctx}, "Proper context $ctx in catch{}"); + return 'catch'; + } + finally { + SKIP: { + skip "DESTROY() not called in void context on perl $]", 1 + if $] < '5.008'; + is (wantarray, undef, "Proper VOID context in finally{} 1"); + } + return 'finally'; + } + finally { + SKIP: { + skip "DESTROY() not called in void context on perl $]", 1 + if $] < '5.008'; + is (wantarray, undef, "Proper VOID context in finally{} 2"); + } + return 'finally'; + }; +} diff --git a/t/erroneous_usage.t b/t/erroneous_usage.t new file mode 100644 index 0000000..c8cc478 --- /dev/null +++ b/t/erroneous_usage.t @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 8; + +use Try::Tiny; + +sub _eval { + local $@; + local $Test::Builder::Level = $Test::Builder::Level + 2; + return ( scalar(eval { $_[0]->(); 1 }), $@ ); +} + +sub throws_ok (&$$) { + my ( $code, $regex, $desc ) = @_; + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ( $ok, $error ) = _eval($code); + + if ( $ok ) { + fail($desc); + } else { + like($error || '', $regex, $desc ); + } +} + +throws_ok { + try { 1 }; catch { 2 }; +} qr/\QUseless bare catch()/, 'Bare catch() detected'; + +throws_ok { + try { 1 }; finally { 2 }; +} qr/\QUseless bare finally()/, 'Bare finally() detected'; + +throws_ok { + try { 1 }; catch { 2 } finally { 2 }; +} qr/\QUseless bare catch()/, 'Bare catch()/finally() detected'; + +throws_ok { + try { 1 }; finally { 2 } catch { 2 }; +} qr/\QUseless bare finally()/, 'Bare finally()/catch() detected'; + + +throws_ok { + try { 1 } catch { 2 } catch { 3 } finally { 4 } finally { 5 } +} qr/\QA try() may not be followed by multiple catch() blocks/, 'Multi-catch detected'; + + +throws_ok { + try { 1 } catch { 2 } + do { 2 } +} qr/\Qtry() encountered an unexpected argument (2) - perhaps a missing semi-colon before or at/, + 'Unterminated try detected'; + +sub foo { + try { 0 }; catch { 2 } +} + +throws_ok { + if (foo()) { + # ... + } +} qr/\QUseless bare catch/, + 'Bare catch at the end of a function call'; + +sub bar { + try { 0 }; finally { 2 } +} + +throws_ok { + if (bar()) { + # ... + } +} qr/\QUseless bare finally/, + 'Bare finally at the end of a function call'; diff --git a/t/finally.t b/t/finally.t new file mode 100644 index 0000000..2624cc9 --- /dev/null +++ b/t/finally.t @@ -0,0 +1,131 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 27; + +use Try::Tiny; + +try { + my $a = 1+1; +} catch { + fail('Cannot go into catch block because we did not throw an exception') +} finally { + pass('Moved into finally from try'); +}; + +try { + die('Die'); +} catch { + ok($_ =~ /Die/, 'Error text as expected'); + pass('Into catch block as we died in try'); +} finally { + pass('Moved into finally from catch'); +}; + +try { + die('Die'); +} finally { + pass('Moved into finally from catch'); +} catch { + ok($_ =~ /Die/, 'Error text as expected'); +}; + +try { + die('Die'); +} finally { + pass('Moved into finally block when try throws an exception and we have no catch block'); +}; + +try { + die('Die'); +} finally { + pass('First finally clause run'); +} finally { + pass('Second finally clause run'); +}; + +try { + # do not die +} finally { + if (@_) { + fail("errors reported: @_"); + } else { + pass("no error reported") ; + } +}; + +try { + die("Die\n"); +} finally { + is_deeply(\@_, [ "Die\n" ], "finally got passed the exception"); +}; + +try { + try { + die "foo"; + } + catch { + die "bar"; + } + finally { + pass("finally called"); + }; +}; + +$_ = "foo"; +try { + is($_, "foo", "not localized in try"); +} +catch { +} +finally { + is(scalar(@_), 0, "nothing in \@_ (finally)"); + is($_, "foo", "\$_ not localized (finally)"); +}; +is($_, "foo", "same afterwards"); + +$_ = "foo"; +try { + is($_, "foo", "not localized in try"); + die "bar\n"; +} +catch { + is($_[0], "bar\n", "error in \@_ (catch)"); + is($_, "bar\n", "error in \$_ (catch)"); +} +finally { + is(scalar(@_), 1, "error in \@_ (finally)"); + is($_[0], "bar\n", "error in \@_ (finally)"); + is($_, "foo", "\$_ not localized (finally)"); +}; +is($_, "foo", "same afterwards"); + +{ + my @warnings; + local $SIG{__WARN__} = sub { + $_[0] =~ /\QExecution of finally() block CODE(0x\E.+\Q) resulted in an exception/ + ? push @warnings, @_ + : warn @_ + }; + + try { + die 'tring' + } finally { + die 'fin 1' + } finally { + pass('fin 2 called') + } finally { + die 'fin 3' + }; + + is( scalar @warnings, 2, 'warnings from both fatal finally blocks' ); + + my @originals = sort map { $_ =~ /Original exception text follows:\n\n(.+)/s } @warnings; + + like $originals[0], qr/fin 1 at/, 'First warning contains original exception'; + like $originals[1], qr/fin 3 at/, 'Second warning contains original exception'; +} + +1; diff --git a/t/given_when.t b/t/given_when.t new file mode 100644 index 0000000..afa0733 --- /dev/null +++ b/t/given_when.t @@ -0,0 +1,40 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + plan skip_all => "Perl 5.10 is required" unless eval { require 5.010 }; + plan tests => 2; +} + +use Try::Tiny; + +use 5.010; +no if $] >= 5.017011, warnings => 'experimental::smartmatch'; + +my ( $error, $topic ); + +given ("foo") { + when (qr/./) { + try { + die "blah\n"; + } catch { + $topic = $_; + $error = $_[0]; + } + }; +} + +is( $error, "blah\n", "error caught" ); + +{ + local $TODO = "perhaps a workaround can be found" + if $] < 5.017003; + is( $topic, $error, 'error is also in $_' ); +} + +# ex: set sw=4 et: + diff --git a/t/global_destruction_forked.t b/t/global_destruction_forked.t new file mode 100644 index 0000000..a533000 --- /dev/null +++ b/t/global_destruction_forked.t @@ -0,0 +1,57 @@ +use strict; +use warnings; +use Test::More tests => 3; +use Try::Tiny; + +{ + package WithCatch; + use Try::Tiny; + + sub DESTROY { + try {} + catch {}; + return; + } +} + +{ + package WithFinally; + use Try::Tiny; + + sub DESTROY { + try {} + finally {}; + return; + } +} + +my $parent = $$; + +try { + my $pid = fork; + unless ($pid) { + my $o = bless {}, 'WithCatch'; + $SIG{__DIE__} = sub { + exit 1 + if $_[0] =~ /A try\(\) may not be followed by multiple catch\(\) blocks/; + exit 2; + }; + exit 0; + } + waitpid $pid, 0; + is $?, 0, 'nested try in cleanup after fork does not maintain outer catch block'; +} +catch {}; + +try { + my $pid = fork; + unless ($pid) { + my $o = bless {}, 'WithFinally'; + exit 0; + } + waitpid $pid, 0; + is $?, 0, 'nested try in cleanup after fork does not maintain outer finally block'; +} +finally { exit 1 if $parent != $$ }; + +pass("Didn't just exit"); diff --git a/t/global_destruction_load.t b/t/global_destruction_load.t new file mode 100644 index 0000000..9c73850 --- /dev/null +++ b/t/global_destruction_load.t @@ -0,0 +1,18 @@ +use strict; +use warnings; +use Test::More; + +BEGIN { + plan skip_all => 'Capture::Tiny 0.12 required' + unless eval { require Capture::Tiny; Capture::Tiny->VERSION(0.12); 1 }; + plan tests => 3; + Capture::Tiny->import(qw(capture_stderr)); +} + +for my $func (qw(try catch finally)) { + is capture_stderr { + system $^X, qw(-It/lib -we), + qq{sub DESTROY { require TryUser; TryUser->test_$func }} . + q{our $o; $o = bless []}; + }, '', "$func gets installed when loading Try::Tiny during global destruction"; +} diff --git a/t/lib/TryUser.pm b/t/lib/TryUser.pm new file mode 100644 index 0000000..c44d36b --- /dev/null +++ b/t/lib/TryUser.pm @@ -0,0 +1,9 @@ +package TryUser; + +use Try::Tiny; + +sub test_try { try { } } +sub test_catch { try { } catch { } } +sub test_finally { try { } finally { } } + +1; diff --git a/t/named.t b/t/named.t new file mode 100644 index 0000000..7de53b1 --- /dev/null +++ b/t/named.t @@ -0,0 +1,37 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + plan skip_all => "Sub::Name required" + unless eval { require Sub::Name; 1 }; + plan tests => 3; +} + +use Try::Tiny; + +my $name; +try { + $name = (caller(0))[3]; +}; +is $name, "main::try {...} ", "try name"; # note extra space + +try { + die "Boom"; +} catch { + $name = (caller(0))[3]; +}; +is $name, "main::catch {...} ", "catch name"; # note extra space + +try { + die "Boom"; +} catch { + # noop +} finally { + $name = (caller(0))[3]; +}; +is $name, "main::finally {...} ", "finally name"; # note extra space + diff --git a/t/when.t b/t/when.t new file mode 100644 index 0000000..54fd678 --- /dev/null +++ b/t/when.t @@ -0,0 +1,37 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + plan skip_all => "Perl 5.10 required" unless eval { require 5.010; 1 }; + plan tests => 5; +} + +use Try::Tiny; + +use 5.010; +no if $] >= 5.017011, warnings => 'experimental::smartmatch'; + +my ( $foo, $bar, $other ); + +$_ = "magic"; + +try { + die "foo"; +} catch { + + like( $_, qr/foo/ ); + + when (/bar/) { $bar++ }; + when (/foo/) { $foo++ }; + default { $other++ }; +}; + +is( $_, "magic", '$_ not clobbered' ); + +ok( !$bar, "bar didn't match" ); +ok( $foo, "foo matched" ); +ok( !$other, "fallback didn't match" ); diff --git a/xt/release/eol.t b/xt/release/eol.t new file mode 100644 index 0000000..d13c49d --- /dev/null +++ b/xt/release/eol.t @@ -0,0 +1,8 @@ +use strict; +use warnings; +use Test::More; + +eval 'use Test::EOL'; +plan skip_all => 'Test::EOL required' if $@; + +all_perl_files_ok({ trailing_whitespace => 1 }); diff --git a/xt/release/no-tabs.t b/xt/release/no-tabs.t new file mode 100644 index 0000000..7619ba2 --- /dev/null +++ b/xt/release/no-tabs.t @@ -0,0 +1,25 @@ +use strict; +use warnings; + +# this test was generated with Dist::Zilla::Plugin::NoTabsTests 0.07 + +use Test::More 0.88; +use Test::NoTabs; + +my @files = ( + 'lib/Try/Tiny.pm', + 't/00-compile.t', + 't/basic.t', + 't/context.t', + 't/erroneous_usage.t', + 't/finally.t', + 't/given_when.t', + 't/global_destruction_forked.t', + 't/global_destruction_load.t', + 't/lib/TryUser.pm', + 't/named.t', + 't/when.t' +); + +notabs_ok($_) foreach @files; +done_testing; diff --git a/xt/release/pod-coverage.t b/xt/release/pod-coverage.t new file mode 100644 index 0000000..66b3b64 --- /dev/null +++ b/xt/release/pod-coverage.t @@ -0,0 +1,7 @@ +#!perl +# This file was automatically generated by Dist::Zilla::Plugin::PodCoverageTests. + +use Test::Pod::Coverage 1.08; +use Pod::Coverage::TrustPod; + +all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); diff --git a/xt/release/pod-syntax.t b/xt/release/pod-syntax.t new file mode 100644 index 0000000..f0468f1 --- /dev/null +++ b/xt/release/pod-syntax.t @@ -0,0 +1,6 @@ +#!perl +# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. +use Test::More; +use Test::Pod 1.41; + +all_pod_files_ok(); |