Toby Inkster avatar Toby Inkster committed dcc78cd

merge IO-Detect into Scalar-Does

Comments (0)

Files changed (13)

IO-Detect-Changes.txt

+IO-Detect
+=========
+
+Created:      2012-07-09
+Home page:    <https://metacpan.org/release/IO-Detect>
+Bug tracker:  <http://rt.cpan.org/Dist/Display.html?Queue=IO-Detect>
+Maintainer:   Toby Inkster <mailto:tobyink@cpan.org>
+
+0.005  2012-09-05
+
+ - (Addition) as_filehandle function.
+ - Perl 5.8 compatibility.
+
+0.004  2012-07-11
+
+ - (Packaging) Require version 1.24 of Scalar::Util.
+
+0.003  2012-07-10
+
+ - (Bugfix) Re-implementation of IO::Detect::_oneline.
+
+0.002  2012-07-09
+
+ - (Addition) Export configurable ducktype function.
+ - (Bugfix) Add space between two words in qw() list.
+
+0.001  2012-07-09  # Initial release
+
+
+2013-03-07:
+- Merged IO-Detect distribution into Scalar-Does.
+package IO::Detect;
+
+use 5.008;
+use constant { false => !1, true => !0 };
+use strict;
+use warnings;
+use utf8;
+
+BEGIN {
+	$IO::Detect::AUTHORITY = 'cpan:TOBYINK';
+	$IO::Detect::VERSION   = '0.013';
+	
+	*UNIVERSAL::DOES = sub { shift->isa(@_) }
+		unless UNIVERSAL->can('DOES');
+}
+
+# This is kinda dumb, but Perl 5.8 doesn't grok the _ prototype.
+BEGIN {
+	if ($] < 5.010)
+		{ eval "sub $_ (\$);" for qw(is_filehandle is_fileuri is_filename) }
+	else
+		{ eval "sub $_ (_);" for qw(is_filehandle is_fileuri is_filename) }
+}
+
+use Sub::Exporter -setup => {
+	exports => [
+		qw( is_filehandle is_filename is_fileuri ),
+		qw( FileHandle FileName FileUri ),
+		ducktype      => \&_build_ducktype,
+		as_filehandle => \&_build_as_filehandle,
+	],
+	groups => {
+		default    => [qw( is_filehandle is_filename is_fileuri )],
+		smartmatch => [qw( FileHandle FileName FileUri )],
+	},
+};
+
+use overload qw<>;
+use Scalar::Util qw< blessed openhandle reftype >;
+use Carp qw<croak>;
+use URI::file;
+
+sub _subpt (&;$)
+{
+	my ($code, $proto) = @_;
+	$proto =~ s/_/\$/g if $] < 5.010;
+	no warnings;
+	return &Scalar::Util::set_prototype($code, $proto);
+}
+
+sub _ducktype
+{
+	my ($object, $methods) = @_;
+	return unless blessed $object;
+	
+	foreach my $m (@{ $methods || [] })
+	{
+		return unless $object->can($m);
+	}
+	
+	return true;
+}
+
+sub _build_ducktype
+{
+	my ($class, $name, $arg) = @_;
+	my $methods = $arg->{methods};
+	return _subpt { _ducktype((@_?shift:$_), $methods) } '_';
+}
+
+my $expected_methods = [
+	qw(close eof fcntl fileno getc getline getlines ioctl read print stat)
+];
+*is_filehandle = _subpt
+{
+	my $fh = @_ ? shift : $_;
+	
+	return true if openhandle $fh;
+	
+	# Logic from IO::Handle::Util
+	{
+		my $reftype = reftype($fh);
+		$reftype = '' unless defined $reftype;
+		
+		if ($reftype eq 'IO'
+		or  $reftype eq 'GLOB' && *{$fh}{IO})
+		{
+			for ($fh->fileno, fileno($fh))
+			{
+				return unless defined;
+				return unless $_ >= 0;
+			}
+			
+			return true;
+		}
+	}
+	
+	return true if blessed $fh && $fh->DOES('IO::Handle');
+	return true if blessed $fh && $fh->DOES('FileHandle');
+	return true if blessed $fh && $fh->DOES('IO::All');
+	
+	return _ducktype $fh, $expected_methods; 
+} '_';
+
+sub _oneline ($)
+{
+	!! ( $_[0] !~ /\r?\n|\r/s )
+}
+
+*is_filename = _subpt
+{
+	my $f = @_ ? shift : $_;
+	return true if blessed $f && $f->DOES('IO::All');
+	return true if blessed $f && $f->DOES('Path::Class::Entity');
+	return ( length "$f" and _oneline "$f" )
+		if blessed $f && overload::Method($f, q[""]);
+	return ( length $f and _oneline $f )
+		if defined $f && !ref $f;
+	return;
+} '_';
+
+*is_fileuri = _subpt
+{
+	my $f = @_ ? shift : $_;
+	return $f if blessed $f && $f->DOES('URI::file');
+	return URI::file->new($f->uri) if blessed $f && $f->DOES('RDF::Trine::Node::Resource');
+	return URI::file->new($f) if $f =~ m{^file://\S+}i;
+	return;
+} '_';
+
+
+sub _build_as_filehandle
+{
+	my ($class, $name, $arg) = @_;
+	my $default_mode = $arg->{mode} || '<';
+	
+	return _subpt
+	{
+		my $f = @_ ? shift : $_;
+		return $f if is_filehandle($f);
+		
+		if (my $uri = is_fileuri($f))
+			{ $f = $uri->file }
+		
+		my $mode = shift || $default_mode;
+		open my $fh, $mode, $f
+			or croak "Cannot open '$f' with mode '$mode': $!, died";
+		return $fh;
+	} '_;$'
+}
+
+*as_filehandle = __PACKAGE__->_build_as_filehandle('as_filehandle', +{});
+
+{
+	package IO::Detect::SmartMatcher;
+	BEGIN {
+		$IO::Detect::SmartMatcher::AUTHORITY = 'cpan:TOBYINK';
+		$IO::Detect::SmartMatcher::VERSION   = '0.013';
+	}
+	use Scalar::Util qw< blessed >;
+	use overload (); no warnings 'overload';  # '~~' unavailable in Perl 5.8
+	use overload
+		'""'     => 'to_string',
+		'~~'     => 'check',
+		'=='     => 'check',
+		'eq'     => 'check',
+		fallback => 1;
+	sub check
+	{
+		my ($self, $thing) = @_;
+		$self->[1]->($thing);
+	}
+	sub to_string
+	{
+		shift->[0]
+	}
+	sub new
+	{
+		my $proto = shift;
+		if (blessed $proto and $proto->isa(__PACKAGE__))
+		{
+			return "$proto"->new(@_);
+		}
+		bless \@_ => $proto;
+	}
+}
+
+use constant FileHandle => IO::Detect::SmartMatcher::->new(FileHandle => \&is_filehandle);
+use constant FileName   => IO::Detect::SmartMatcher::->new(FileName   => \&is_filename);
+use constant FileUri    => IO::Detect::SmartMatcher::->new(FileUri    => \&is_fileuri);
+
+true;
+
+__END__
+
+=encoding utf8
+
+=head1 NAME
+
+IO::Detect - is this a frickin' filehandle or what?!
+
+=head1 SYNOPSIS
+
+	use IO::Detect;
+	
+	if (is_filehandle $fh)
+	{
+		my $line = <$fh>;
+	}
+
+=head1 DESCRIPTION
+
+It is stupidly complicated to detect whether a given scalar is
+a filehandle (or something filehandle like) in Perl. This module
+attempts to do so, but probably falls short in some cases. The
+primary advantage of using this module is that it gives you
+somebody to blame (me) if your code can't detect a filehandle.
+
+The main use case for IO::Detect is for when you are writing
+functions and you want to allow the caller to pass a file as
+an argument without being fussy as to whether they pass a file
+name or a file handle.
+
+=head2 Functions
+
+Each function takes a single argument, or if called with no
+argument, operates on C<< $_ >>.
+
+=over
+
+=item C<< is_filehandle $thing >>
+
+Theoretically returns true if and only if $thing is a file handle,
+or may be treated as a filehandle. That includes blessed references
+to filehandles, things that inherit from IO::Handle, etc.
+
+It's never going to work 100%. What Perl allows you to use as a
+filehandle is mysterious and somewhat context-dependent, as the
+following code illustrates.
+
+	my $fh = "STD" . "OUT";
+	print $fh "Hello World!\n";
+
+=item C<< is_filename $thing >>
+
+Returns true if $thing is a L<IO::All> object or L<Path::Class::Entity>
+or L<any non-reference, non-zero-length string with no line breaks>.
+That's because depending on your operating system, virtually anything
+can be used as a filename. (In fact, on many systems, including Linux,
+filenames can contain line breaks. However, this is unlikely to be
+intentional.)
+
+This function doesn't tell you whether $thing is an existing file on
+your system. It attempts to tell you whether $thing could possibly be
+a filename on some system somewhere.
+
+=item C<< is_fileuri $thing >>
+
+Returns true if $thing is a URI beginning with "file://". It
+allows for L<URI> objects, L<RDF::Trine::Node::Resource> objects,
+strings and objects that overload stringification.
+
+This function actually returns an "interesting value of true". The
+value returned is a L<URI::file> object.
+
+=item C<< as_filehandle $thing, $mode >>
+
+Returns $thing if it is a filehandle; otherwise opens it with mode
+$mode (croaking if it cannot be opened). $mode defaults to "<" (read
+access).
+
+This function is not exported by default, but needs to be requested
+explicitly:
+
+	use IO::Detect qw(as_filehandle);
+
+You may even specify a different default mode, or import it several
+times with different names:
+
+	use IO::Detect 
+	  as_filehandle => { -as => 'as_filehandle_read',  mode => '<' },
+	  as_filehandle => { -as => 'as_filehandle_write', mode => '>' };
+
+=back
+
+=head2 Smart Matching
+
+You can import three constants for use in smart matching:
+
+	use IO::Detect -smartmatch;
+
+These constants are:
+
+=over
+
+=item C<< FileHandle >>
+
+=item C<< FileName >>
+
+=item C<< FileUri >>
+
+=back
+
+They can be used like this:
+
+	if ($file ~~ FileHandle)
+	{
+		...
+	}
+
+Note that there does exist a L<FileHandle> package in Perl core. This
+module attempts to do the right thing so that C<< FileHandle->new >>
+still works, but there are conveivably places this could go wrong, or
+be plain old confusing.
+
+Although C<is_filehandle> and its friends support Perl 5.8 and above,
+smart match is only available in Perl 5.10 onwards.
+
+=head2 Precedence
+
+Because there is some overlap/ambiguity between what is a filehandle
+and what is a filename, etc, if you need to detect between them, I
+recommend checking C<is_filehandle> first, then C<is_fileuri> and
+falling back to C<is_filename>.
+
+	for ($file)
+	{
+		when (FileHandle)  { ... }
+		when (FileUri)     { ... }
+		when (FileName)    { ... }
+		default            { die "$file is not a file!" }
+	}
+
+=head2 Duck Typing
+
+In some cases you might be happy to accept something less than a
+complete file handle. In this case you can import a customised
+"duck type" test...
+
+	use IO::Detect
+		-default,
+		ducktype => {
+			-as     => 'is_slurpable',
+			methods => [qw(getlines close)],
+		};
+	
+	sub do_something_with_a_file
+	{
+		my $f = shift;
+		if ( is_filehandle $f or is_slurpable $f )
+			{ ... }
+		elsif ( is_filename $f )
+			{ ... }
+	}
+
+Duck type test functions only test that the argument is blessed
+and can do all of the specified methods. They don't test any other
+aspect of "filehandliness".
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=IO-Detect>.
+
+=head1 SEE ALSO
+
+This module is an attempt to capture some of the wisdom from this
+PerlMonks thread L<http://www.perlmonks.org/?node_id=980665> into
+executable code.
+
+Various other modules that may be of interest, in no particular
+order...
+L<Scalar::Util>,
+L<Scalar::Does>,
+L<FileHandle>,
+L<IO::Handle>,
+L<IO::Handle::Util>,
+L<IO::All>,
+L<Path::Class>,
+L<URI::file>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

lib/Scalar/Does.pm

 our %_CONSTANTS;
 BEGIN {
 	$Scalar::Does::AUTHORITY = 'cpan:TOBYINK';
-	$Scalar::Does::VERSION   = '0.012';
+	$Scalar::Does::VERSION   = '0.013';
 	
 	%_CONSTANTS = (
 		BOOLEAN    => q[bool],
 BEGIN {
 	package Scalar::Does::RoleChecker;
 	$Scalar::Does::RoleChecker::AUTHORITY = 'cpan:TOBYINK';
-	$Scalar::Does::RoleChecker::VERSION   = '0.012';
+	$Scalar::Does::RoleChecker::VERSION   = '0.013';
 	use overload
 		q[""]    => 'name',
 		q[&{}]   => 'code',

lib/Scalar/Does/MooseTypes.pm

 package Scalar::Does::MooseTypes;
 
 our $AUTHORITY = 'cpan:TOBYINK';
-our $VERSION   = '0.012';
+our $VERSION   = '0.013';
 
 use Scalar::Does qw( blessed does looks_like_number -make );
 
+=head1 PURPOSE
+
+Check IO::Detect loads.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012-2013 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+use Test::More tests => 1;
+BEGIN { use_ok('IO::Detect') };
+

t/iod-02filehandles.t

+=head1 PURPOSE
+
+Check IO::Detect can detect filehandle-like things.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Greg Bacon
+
+=head1 SEE ALSO
+
+L<http://stackoverflow.com/questions/3214647/what-is-the-best-way-to-determine-if-a-scalar-holds-a-filehandle>.
+
+=cut
+
+# These tests are largely stolen from Greg Bacon's answer to the following StackOverflow question...
+# http://stackoverflow.com/questions/3214647/what-is-the-best-way-to-determine-if-a-scalar-holds-a-filehandle
+#
+
+use FileHandle;
+use IO::File;
+use IO::Socket::INET;
+
+use IO::Detect qw( is_filehandle FileHandle );
+
+use Test::More;
+
+plan skip_all => "only works on Linux" unless $^O =~ /linux/i;
+
+my $SLEEP = 5;
+my $FIFO  = "/tmp/myfifo";
+
+unlink $FIFO;
+my $pid = fork;
+die "$0: fork" unless defined $pid;
+if ($pid == 0) {
+	system("mknod", $FIFO, "p") == 0 or die "$0: mknod failed";
+	open my $fh, ">", $FIFO;
+	sleep $SLEEP;
+	exit 0;
+}
+else {
+	sleep 1 while !-e $FIFO;
+}
+
+my @handles = (
+	[0, "1",           1],
+	[0, "hashref",     {}],
+	[0, "arrayref",    []],
+	[0, "globref",     \*INC],
+	[1, "in-memory",   do {{ my $buf; open my $fh, "<", \$buf; $fh }}],
+	[1, "FH1 glob",    do {{ open FH1, "<", "/dev/null"; *FH1 }}],
+	[1, "FH2 globref", do {{ open FH2, "<", "/dev/null"; \*FH2 }}],
+#	[1, "FH3 string",  do {{ open FH3, "<", "/dev/null"; "FH3" }}],
+	[1, "STDIN glob",  \*STDIN],
+	[1, "plain read",  do {{ open my $fh, "<", "/dev/null"; $fh }}],
+	[1, "plain write", do {{ open my $fh, ">", "/dev/null"; $fh }}],
+	[1, "FH read",     FileHandle->new("< /dev/null")],
+	[1, "FH write",    FileHandle->new("> /dev/null")],
+	[1, "I::F read",   IO::File->new("< /dev/null")],
+	[1, "I::F write",  IO::File->new("> /dev/null")],
+	[1, "pipe read",   do {{ open my $fh, "sleep $SLEEP |"; $fh }}],
+	[1, "pipe write",  do {{ open my $fh, "| sleep $SLEEP"; $fh }}],
+	[1, "FIFO read",   do {{ open my $fh, "<", $FIFO; $fh }}],
+	[1, "socket",      IO::Socket::INET->new(LocalAddr => sprintf('localhost:%d', 10000 + rand 20000))],
+);
+
+foreach (@handles)
+{
+	my ($truth, $label, $fh) = @$_;
+	
+	if ($truth)
+	{
+		ok is_filehandle($fh), "positive for $label"
+	}
+	else
+	{
+		ok !is_filehandle($fh), "negitive for $label"
+	}
+}
+
+if ($] >= 5.010)
+{
+	foreach (@handles)
+	{
+		my ($truth, $label, $fh) = @$_;
+		
+		if ($truth)
+		{
+			eval q[ ok($fh ~~ FileHandle, "smart match positive for $label") ];
+		}
+		else
+		{
+			eval q[ ok(not($fh ~~ FileHandle), "smart match negitive for $label") ];
+		}
+	}
+}
+
+done_testing();

t/iod-03filenames.t

+=head1 PURPOSE
+
+Check IO::Detect can detect filename-like things.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012-2013 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+use Test::More;
+use IO::Detect qw( is_filename FileName );
+
+my @filenames = qw(
+	0
+	/dev/null
+	readme.txt
+	README
+	C:\Windows\Notepad.exe
+	C:\Windows\
+);
+
+{
+	package Local::Stringifier;
+	use overload q[""], sub { $_[0][0] };
+	sub new { bless \@_, shift }
+}
+
+push @filenames, Local::Stringifier->new(__FILE__);
+
+ok !is_filename([]), 'is_filename ARRAY';
+ok !is_filename(undef), 'is_filename undef';
+ok !is_filename(''), 'is_filename empty string';
+
+if ($] >= 5.010)
+{
+	eval q[
+		ok(is_filename, "is_filename $_") for @filenames;
+
+		ok not([]    ~~ FileName), 'ARRAY ~~ FileName';
+		ok not(undef ~~ FileName), 'undef ~~ FileName';
+		ok not(''    ~~ FileName), 'empty string ~~ FileName';
+
+		for (@filenames)
+			{ ok $_ ~~ FileName, "$_ ~~ FileName" };
+	];
+}
+
+done_testing();

t/iod-04fileuris.t

+=head1 PURPOSE
+
+Check IO::Detect can detect URI-like things.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012-2013 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+use Test::More;
+use IO::Detect qw( is_fileuri FileUri );
+use URI;
+
+{
+	package Local::Stringifier;
+	use overload q[""], sub { $_[0][0] };
+	sub new { bless \@_, shift }
+}
+
+my @uris = qw(
+	file://localhost/etc/fstab
+	file:///etc/fstab
+	file:///c:/WINDOWS/clock.avi
+	file://localhost/c|/WINDOWS/clock.avi
+	file:///c|/WINDOWS/clock.avi
+	file://localhost/c:/WINDOWS/clock.avi
+	file://localhost///remotehost/share/dir/file.txt
+	file://///remotehost/share/dir/file.txt 
+);
+
+@uris = (
+	@uris,
+	(map { Local::Stringifier->new($_) } @uris),
+	(map { URI->new($_) } @uris),
+);
+
+if ($] >= 5.010)
+{
+	eval q[
+		ok(is_fileuri, sprintf("is_fileuri %s %s", ref $_, $_)) foreach @uris;
+		ok($_ ~~ FileUri, sprintf("is_fileuri %s %s", ref $_, $_)) foreach @uris;
+	];
+}
+
+ok not is_fileuri 'http://localhost/';
+
+ok not is_fileuri "http://localhost/\nfile://";
+
+done_testing();

t/iod-05ducktype.t

+=head1 PURPOSE
+
+Test IO::Detect's C<ducktype> function.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012-2013 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+use Test::More tests => 2;
+use IO::Detect ducktype => { -as => 'can_dump', methods => ['Dump'] };
+
+use Data::Dumper;
+use IO::Handle;
+
+ok  can_dump(Data::Dumper->new([]));
+ok !can_dump(IO::Handle->new);
+
+=head1 PURPOSE
+
+Check how IO::Detect handles L<IO::All> objects.
+
+This test is skipped if IO::All is not available.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012-2013 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+use strict;
+use Test::More;
+BEGIN {
+	eval("use IO::All 'io'; 1") && ($] >= 5.010)
+	or plan skip_all => "Need IO::All and Perl 5.10 for this test.";
+};
+
+use IO::Detect;
+plan tests => 3;
+
+$_ = io('Makefile.PL');
+
+ok is_filehandle, "is_filehandle";
+ok is_filename, "is_filename";
+ok not(is_fileuri), "is_fileuri";
+

t/iod-12pathclass.t

+=head1 PURPOSE
+
+Check how IO::Detect handles L<Path::Class> objects.
+
+This test is skipped if Path::Class is not available.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012-2013 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+use strict;
+use Test::More;
+BEGIN {
+	eval("use Path::Class 'file'; 1") && ($] >= 5.010)
+	or plan skip_all => "Need Path::Class for this test.";
+};
+
+use IO::Detect;
+plan tests => 3;
+
+$_ = file('Makefile.PL');
+
+ok not(is_filehandle), "is_filehandle";
+ok is_filename, "is_filename";
+ok not(is_fileuri), "is_fileuri";
+

t/iod-13pathtiny.t

+=head1 PURPOSE
+
+Check how IO::Detect handles L<Path::Tiny> objects.
+
+This test is skipped if Path::Tiny is not available.
+
+This file originally formed part of the IO-Detect test suite.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012-2013 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
+
+use strict;
+use Test::More;
+BEGIN {
+	eval("use Path::Tiny 'path'; 1") && ($] >= 5.010)
+	or plan skip_all => "Need Path::Tiny for this test.";
+};
+
+use IO::Detect;
+plan tests => 3;
+
+$_ = path('Makefile.PL');
+
+ok not(is_filehandle), "is_filehandle";
+ok is_filename, "is_filename";
+ok not(is_fileuri), "is_fileuri";
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.