From: Charles McGarvey Date: Tue, 21 Jan 2020 17:14:25 +0000 (-0700) Subject: Release 0.48 X-Git-Tag: solo-0.48 X-Git-Url: https://git.brokenzipper.com/gitweb?a=commitdiff_plain;h=3b5416ea7238067e45d01b5029531de7117cbed8;p=chaz%2Fgit-codeowners Release 0.48 --- diff --git a/README b/README index 0b9aa08..9d0b26e 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ NAME VERSION - version 0.47 + version 0.48 SYNOPSIS diff --git a/git-codeowners b/git-codeowners index 84ddaad..197076f 100755 --- a/git-codeowners +++ b/git-codeowners @@ -10,10 +10,10 @@ BEGIN { my %fatpacked; $fatpacked{"App/Codeowners.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS'; - package App::Codeowners;use v5.10.1;use utf8;use warnings;use strict;use App::Codeowners::Formatter;use App::Codeowners::Options;use App::Codeowners::Util qw(find_codeowners_in_directory run_git git_ls_files git_toplevel);use Color::ANSI::Util 0.03 qw(ansifg);use Encode qw(encode);use File::Codeowners;use Path::Tiny;our$VERSION='0.47';sub main {my$class=shift;my$self=bless {},$class;my$opts=App::Codeowners::Options->new(@_);my$color=$opts->{color};local$ENV{NO_COLOR}=1 if defined$color &&!$color;my$command=$opts->command;my$handler=$self->can("_command_$command")or die "Unknown command: $command\n";$self->$handler($opts);exit 0}sub _command_show {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my ($proc,$cdup)=run_git(qw{rev-parse --show-cdup});$proc->wait and exit 1;my$show_projects=$opts->{projects}// scalar @{$codeowners->projects};my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| ' * %-50F %O',handle=>*STDOUT,columns=>['File',$opts->{patterns}? 'Pattern' : (),'Owner',$show_projects ? 'Project' : (),],);my%filter_owners=map {$_=>1}@{$opts->{owner}};my%filter_projects=map {$_=>1}@{$opts->{project}};my%filter_patterns=map {$_=>1}@{$opts->{pattern}};$proc=git_ls_files('.',$opts->args);while (my$filepath=$proc->next){my$match=$codeowners->match(path($filepath)->relative($cdup));if (%filter_owners){for my$owner (@{$match->{owners}}){goto ADD_RESULT if$filter_owners{$owner}}next}if (%filter_patterns){goto ADD_RESULT if$filter_patterns{$match->{pattern}|| ''};next}if (%filter_projects){goto ADD_RESULT if$filter_projects{$match->{project}|| ''};next}ADD_RESULT: $formatter->add_result([$filepath,$opts->{patterns}? $match->{pattern}: (),$match->{owners},$show_projects ? $match->{project}: (),])}$proc->wait and exit 1}sub _command_owners {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my$results=$codeowners->owners($opts->{pattern});my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| '%O',handle=>*STDOUT,columns=>[qw(Owner)],);$formatter->add_result(map {[$_]}@$results)}sub _command_patterns {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my$results=$codeowners->patterns($opts->{owner});my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| '%T',handle=>*STDOUT,columns=>[qw(Pattern)],);$formatter->add_result(map {[$_]}@$results)}sub _command_projects {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my$results=$codeowners->projects;my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| '%P',handle=>*STDOUT,columns=>[qw(Project)],);$formatter->add_result(map {[$_]}@$results)}sub _command_create {goto&_command_update}sub _command_update {my$self=shift;my$opts=shift;my ($filepath)=$opts->args;my$path=path($filepath || '.');my$repopath;die "Does not exist: $path\n" if!$path->parent->exists;if ($path->is_dir){$repopath=$path;$path=find_codeowners_in_directory($path)|| $repopath->child('CODEOWNERS')}my$is_new=!$path->is_file;my$codeowners;if ($is_new){$codeowners=File::Codeowners->new;my$template=<<'END';for my$line (split(/\n/,$template)){$codeowners->append(comment=>$line)}}else {$codeowners=File::Codeowners->parse_from_filepath($path)}if ($repopath){my ($proc,@filepaths)=git_ls_files($repopath);$proc->wait and exit 1;$codeowners->clear_unowned;$codeowners->add_unowned(grep {!$codeowners->match($_)}@filepaths)}$codeowners->write_to_filepath($path);print STDERR "Wrote $path\n"}1; + package App::Codeowners;use v5.10.1;use utf8;use warnings;use strict;use App::Codeowners::Formatter;use App::Codeowners::Options;use App::Codeowners::Util qw(find_codeowners_in_directory run_git git_ls_files git_toplevel);use Color::ANSI::Util 0.03 qw(ansifg);use Encode qw(encode);use File::Codeowners;use Path::Tiny;our$VERSION='0.48';sub main {my$class=shift;my$self=bless {},$class;my$opts=App::Codeowners::Options->new(@_);my$color=$opts->{color};local$ENV{NO_COLOR}=1 if defined$color &&!$color;my$command=$opts->command;my$handler=$self->can("_command_$command")or die "Unknown command: $command\n";$self->$handler($opts);exit 0}sub _command_show {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my ($proc,$cdup)=run_git(qw{rev-parse --show-cdup});$proc->wait and exit 1;my$show_projects=$opts->{projects}// scalar @{$codeowners->projects};my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| ' * %-50F %O',handle=>*STDOUT,columns=>['File',$opts->{patterns}? 'Pattern' : (),'Owner',$show_projects ? 'Project' : (),],);my%filter_owners=map {$_=>1}@{$opts->{owner}};my%filter_projects=map {$_=>1}@{$opts->{project}};my%filter_patterns=map {$_=>1}@{$opts->{pattern}};$proc=git_ls_files('.',$opts->args);while (my$filepath=$proc->next){my$match=$codeowners->match(path($filepath)->relative($cdup));if (%filter_owners){for my$owner (@{$match->{owners}}){goto ADD_RESULT if$filter_owners{$owner}}next}if (%filter_patterns){goto ADD_RESULT if$filter_patterns{$match->{pattern}|| ''};next}if (%filter_projects){goto ADD_RESULT if$filter_projects{$match->{project}|| ''};next}ADD_RESULT: $formatter->add_result([$filepath,$opts->{patterns}? $match->{pattern}: (),$match->{owners},$show_projects ? $match->{project}: (),])}$proc->wait and exit 1}sub _command_owners {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my$results=$codeowners->owners($opts->{pattern});my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| '%O',handle=>*STDOUT,columns=>[qw(Owner)],);$formatter->add_result(map {[$_]}@$results)}sub _command_patterns {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my$results=$codeowners->patterns($opts->{owner});my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| '%T',handle=>*STDOUT,columns=>[qw(Pattern)],);$formatter->add_result(map {[$_]}@$results)}sub _command_projects {my$self=shift;my$opts=shift;my$toplevel=git_toplevel('.')or die "Not a git repo\n";my$codeowners_path=find_codeowners_in_directory($toplevel)or die "No CODEOWNERS file in $toplevel\n";my$codeowners=File::Codeowners->parse_from_filepath($codeowners_path);my$results=$codeowners->projects;my$formatter=App::Codeowners::Formatter->new(format=>$opts->{format}|| '%P',handle=>*STDOUT,columns=>[qw(Project)],);$formatter->add_result(map {[$_]}@$results)}sub _command_create {goto&_command_update}sub _command_update {my$self=shift;my$opts=shift;my ($filepath)=$opts->args;my$path=path($filepath || '.');my$repopath;die "Does not exist: $path\n" if!$path->parent->exists;if ($path->is_dir){$repopath=$path;$path=find_codeowners_in_directory($path)|| $repopath->child('CODEOWNERS')}my$is_new=!$path->is_file;my$codeowners;if ($is_new){$codeowners=File::Codeowners->new;my$template=<<'END';for my$line (split(/\n/,$template)){$codeowners->append(comment=>$line)}}else {$codeowners=File::Codeowners->parse_from_filepath($path)}if ($repopath){my ($proc,@filepaths)=git_ls_files($repopath);$proc->wait and exit 1;$codeowners->clear_unowned;$codeowners->add_unowned(grep {!$codeowners->match($_)}@filepaths)}$codeowners->write_to_filepath($path);print STDERR "Wrote $path\n"}1; This file shows mappings between subdirs/files and the individuals and teams who own them. You can read this file yourself or use tools to query it, - so you can quickly determine who to speak with or send pull requests to. ❤️ + so you can quickly determine who to speak with or send pull requests to. Simply write a gitignore pattern followed by one or more names/emails/groups. Examples: @@ -23,35 +23,35 @@ $fatpacked{"App/Codeowners.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<' APP_CODEOWNERS $fatpacked{"App/Codeowners/Formatter.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_FORMATTER'; - package App::Codeowners::Formatter;use warnings;use strict;our$VERSION='0.47';use Module::Load;sub new {my$class=shift;my$args={@_==1 && ref $_[0]eq 'HASH' ? %{$_[0]}: @_};$args->{results}=[];($class,my$format)=$class->_best_formatter($args->{format})if$args->{format};$args->{format}=$format;my$self=bless$args,$class;$self->start;return$self}sub _best_formatter {my$class=shift;my$type=shift || '';return ($class,$type)if$class ne __PACKAGE__;my ($name,$format)=$type =~ /^([A-Za-z]+)(?::(.*))?$/;if (!$name){$name='';$format=''}$name=lc($name);$name =~ s/:.*//;my@formatters=$class->formatters;my$package=__PACKAGE__.'::String';for my$formatter (@formatters){my$module=lc($formatter);$module =~ s/.*:://;if ($module eq $name){$package=$formatter;$type=$format;last}}load$package;return ($package,$type)}sub DESTROY {my$self=shift;my$global_destruction=shift;return if$global_destruction;my$results=$self->{results};$self->finish($results)if$results;delete$self->{results}}sub handle {shift->{handle}}sub format {shift->{format}|| ''}sub columns {shift->{columns}|| []}sub results {shift->{results}}sub add_result {my$self=shift;$self->stream($_)for @_}sub start {}sub stream {push @{$_[0]->results},$_[1]}sub finish {}sub formatters {return qw(App::Codeowners::Formatter::CSV App::Codeowners::Formatter::JSON App::Codeowners::Formatter::String App::Codeowners::Formatter::TSV App::Codeowners::Formatter::Table App::Codeowners::Formatter::YAML)}1; + package App::Codeowners::Formatter;use warnings;use strict;our$VERSION='0.48';use Module::Load;sub new {my$class=shift;my$args={@_==1 && ref $_[0]eq 'HASH' ? %{$_[0]}: @_};$args->{results}=[];($class,my$format)=$class->_best_formatter($args->{format})if$args->{format};$args->{format}=$format;my$self=bless$args,$class;$self->start;return$self}sub _best_formatter {my$class=shift;my$type=shift || '';return ($class,$type)if$class ne __PACKAGE__;my ($name,$format)=$type =~ /^([A-Za-z]+)(?::(.*))?$/;if (!$name){$name='';$format=''}$name=lc($name);$name =~ s/:.*//;my@formatters=$class->formatters;my$package=__PACKAGE__.'::String';for my$formatter (@formatters){my$module=lc($formatter);$module =~ s/.*:://;if ($module eq $name){$package=$formatter;$type=$format;last}}load$package;return ($package,$type)}sub DESTROY {my$self=shift;my$global_destruction=shift;return if$global_destruction;my$results=$self->{results};$self->finish($results)if$results;delete$self->{results}}sub handle {shift->{handle}}sub format {shift->{format}|| ''}sub columns {shift->{columns}|| []}sub results {shift->{results}}sub add_result {my$self=shift;$self->stream($_)for @_}sub start {}sub stream {push @{$_[0]->results},$_[1]}sub finish {}sub formatters {return qw(App::Codeowners::Formatter::CSV App::Codeowners::Formatter::JSON App::Codeowners::Formatter::String App::Codeowners::Formatter::TSV App::Codeowners::Formatter::Table App::Codeowners::Formatter::YAML)}1; APP_CODEOWNERS_FORMATTER $fatpacked{"App/Codeowners/Formatter/CSV.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_FORMATTER_CSV'; - package App::Codeowners::Formatter::CSV;use warnings;use strict;our$VERSION='0.47';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(stringify);use Encode qw(encode);sub start {my$self=shift;$self->text_csv->print($self->handle,$self->columns)}sub stream {my$self=shift;my$result=shift;$self->text_csv->print($self->handle,[map {encode('UTF-8',stringify($_))}@$result])}sub text_csv {my$self=shift;$self->{text_csv}||= do {eval {require Text::CSV}or die "Missing dependency: Text::CSV\n";my%options;$options{escape_char}=$self->escape_char if$self->escape_char;$options{quote}=$self->quote if$self->quote;$options{sep}=$self->sep if$self->sep;if ($options{sep}&& $options{sep}eq ($options{quote}|| '"')){die "Invalid separator value for CSV format.\n"}Text::CSV->new({binary=>1,eol=>$/,%options})}or die "Failed to construct Text::CSV object"}sub sep {$_[0]->{sep}|| $_[0]->format}sub quote {$_[0]->{quote}}sub escape_char {$_[0]->{escape_char}}1; + package App::Codeowners::Formatter::CSV;use warnings;use strict;our$VERSION='0.48';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(stringify);use Encode qw(encode);sub start {my$self=shift;$self->text_csv->print($self->handle,$self->columns)}sub stream {my$self=shift;my$result=shift;$self->text_csv->print($self->handle,[map {encode('UTF-8',stringify($_))}@$result])}sub text_csv {my$self=shift;$self->{text_csv}||= do {eval {require Text::CSV}or die "Missing dependency: Text::CSV\n";my%options;$options{escape_char}=$self->escape_char if$self->escape_char;$options{quote}=$self->quote if$self->quote;$options{sep}=$self->sep if$self->sep;if ($options{sep}&& $options{sep}eq ($options{quote}|| '"')){die "Invalid separator value for CSV format.\n"}Text::CSV->new({binary=>1,eol=>$/,%options})}or die "Failed to construct Text::CSV object"}sub sep {$_[0]->{sep}|| $_[0]->format}sub quote {$_[0]->{quote}}sub escape_char {$_[0]->{escape_char}}1; APP_CODEOWNERS_FORMATTER_CSV $fatpacked{"App/Codeowners/Formatter/JSON.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_FORMATTER_JSON'; - package App::Codeowners::Formatter::JSON;use warnings;use strict;our$VERSION='0.47';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(zip);sub finish {my$self=shift;my$results=shift;eval {require JSON::MaybeXS}or die "Missing dependency: JSON::MaybeXS\n";my%options;$options{pretty}=1 if lc($self->format)eq 'pretty';my$json=JSON::MaybeXS->new(canonical=>1,utf8=>1,%options);my$columns=$self->columns;$results=[map {+{zip @$columns,@$_}}@$results];print {$self->handle}$json->encode($results)}1; + package App::Codeowners::Formatter::JSON;use warnings;use strict;our$VERSION='0.48';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(zip);sub finish {my$self=shift;my$results=shift;eval {require JSON::MaybeXS}or die "Missing dependency: JSON::MaybeXS\n";my%options;$options{pretty}=1 if lc($self->format)eq 'pretty';my$json=JSON::MaybeXS->new(canonical=>1,utf8=>1,%options);my$columns=$self->columns;$results=[map {+{zip @$columns,@$_}}@$results];print {$self->handle}$json->encode($results)}1; APP_CODEOWNERS_FORMATTER_JSON $fatpacked{"App/Codeowners/Formatter/String.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_FORMATTER_STRING'; - package App::Codeowners::Formatter::String;use warnings;use strict;our$VERSION='0.47';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(stringf zip);use Color::ANSI::Util 0.03 qw(ansifg);use Encode qw(encode);sub stream {my$self=shift;my$result=shift;$result={zip @{$self->columns},@$result};my%info=(F=>$self->_create_filterer->($result->{File},undef),O=>$self->_create_filterer->($result->{Owner},$self->_owner_colorgen),P=>$self->_create_filterer->($result->{Project},undef),T=>$self->_create_filterer->($result->{Pattern},undef),);my$text=stringf($self->format,%info);print {$self->handle}encode('UTF-8',$text),"\n"}sub _expand_filter_args {my$arg=shift || '';my@filters=split(/,/,$arg);my$color_override;for (my$i=0;$i < @filters;++$i){my$filter=$filters[$i]or next;if ($filter =~ /^(?:nocolor|color:([0-9a-fA-F]{3,6}))$/){$color_override=$1 || '';splice(@filters,$i,1);redo}}return (\@filters,$color_override)}sub _ansi_reset {"\033[0m"}sub _colored {my$text=shift;my$rgb=shift or return$text;return$text if$ENV{NO_COLOR}|| (defined$ENV{COLOR_DEPTH}&&!$ENV{COLOR_DEPTH});$rgb =~ s/^(.)(.)(.)$/$1$1$2$2$3$3/;if ($rgb !~ m/^[0-9a-fA-F]{6}$/){warn "Color value must be in 'ffffff' or 'fff' form.\n";return$text}my ($begin,$end)=(ansifg($rgb),_ansi_reset);return "${begin}${text}${end}"}sub _create_filterer {my$self=shift;my%filter=(quote=>sub {local $_=$_[0];s/"/\"/s;"\"$_\""},);return sub {my$value=shift || '';my$color=shift || '';my$gencolor=ref($color)eq 'CODE' ? $color : sub {$color};return sub {my$arg=shift;my ($filters,$color)=_expand_filter_args($arg);if (ref($value)eq 'ARRAY'){$value=join(',',map {_colored($_,$color // $gencolor->($_))}@$value)}else {$value=_colored($value,$color // $gencolor->($value))}for my$key (@$filters){if (my$filter=$filter{$key}){$value=$filter->($value)}else {warn "Unknown filter: $key\n"}}$value || ''}}}sub _owner_colorgen {my$self=shift;my@contrasting_colors=qw(e6194b 3cb44b ffe119 4363d8 f58231 911eb4 42d4f4 f032e6 bfef45 fabebe 469990 e6beff 9a6324 fffac8 800000 aaffc3 808000 ffd8b1 000075 a9a9a9);my%owner_colors;my$num=-1;$self->{owner_color}||= sub {my$owner=shift or return;$owner_colors{$owner}||= do {$num=($num + 1)% scalar@contrasting_colors;$contrasting_colors[$num]}}}1; + package App::Codeowners::Formatter::String;use warnings;use strict;our$VERSION='0.48';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(stringf zip);use Color::ANSI::Util 0.03 qw(ansifg);use Encode qw(encode);sub stream {my$self=shift;my$result=shift;$result={zip @{$self->columns},@$result};my%info=(F=>$self->_create_filterer->($result->{File},undef),O=>$self->_create_filterer->($result->{Owner},$self->_owner_colorgen),P=>$self->_create_filterer->($result->{Project},undef),T=>$self->_create_filterer->($result->{Pattern},undef),);my$text=stringf($self->format,%info);print {$self->handle}encode('UTF-8',$text),"\n"}sub _expand_filter_args {my$arg=shift || '';my@filters=split(/,/,$arg);my$color_override;for (my$i=0;$i < @filters;++$i){my$filter=$filters[$i]or next;if ($filter =~ /^(?:nocolor|color:([0-9a-fA-F]{3,6}))$/){$color_override=$1 || '';splice(@filters,$i,1);redo}}return (\@filters,$color_override)}sub _ansi_reset {"\033[0m"}sub _colored {my$text=shift;my$rgb=shift or return$text;return$text if$ENV{NO_COLOR}|| (defined$ENV{COLOR_DEPTH}&&!$ENV{COLOR_DEPTH});$rgb =~ s/^(.)(.)(.)$/$1$1$2$2$3$3/;if ($rgb !~ m/^[0-9a-fA-F]{6}$/){warn "Color value must be in 'ffffff' or 'fff' form.\n";return$text}my ($begin,$end)=(ansifg($rgb),_ansi_reset);return "${begin}${text}${end}"}sub _create_filterer {my$self=shift;my%filter=(quote=>sub {local $_=$_[0];s/"/\"/s;"\"$_\""},);return sub {my$value=shift || '';my$color=shift || '';my$gencolor=ref($color)eq 'CODE' ? $color : sub {$color};return sub {my$arg=shift;my ($filters,$color)=_expand_filter_args($arg);if (ref($value)eq 'ARRAY'){$value=join(',',map {_colored($_,$color // $gencolor->($_))}@$value)}else {$value=_colored($value,$color // $gencolor->($value))}for my$key (@$filters){if (my$filter=$filter{$key}){$value=$filter->($value)}else {warn "Unknown filter: $key\n"}}$value || ''}}}sub _owner_colorgen {my$self=shift;my@contrasting_colors=qw(e6194b 3cb44b ffe119 4363d8 f58231 911eb4 42d4f4 f032e6 bfef45 fabebe 469990 e6beff 9a6324 fffac8 800000 aaffc3 808000 ffd8b1 000075 a9a9a9);my%owner_colors;my$num=-1;$self->{owner_color}||= sub {my$owner=shift or return;$owner_colors{$owner}||= do {$num=($num + 1)% scalar@contrasting_colors;$contrasting_colors[$num]}}}1; APP_CODEOWNERS_FORMATTER_STRING $fatpacked{"App/Codeowners/Formatter/TSV.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_FORMATTER_TSV'; - package App::Codeowners::Formatter::TSV;use warnings;use strict;our$VERSION='0.47';use parent 'App::Codeowners::Formatter::CSV';sub sep {"\t"}1; + package App::Codeowners::Formatter::TSV;use warnings;use strict;our$VERSION='0.48';use parent 'App::Codeowners::Formatter::CSV';sub sep {"\t"}1; APP_CODEOWNERS_FORMATTER_TSV $fatpacked{"App/Codeowners/Formatter/Table.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_FORMATTER_TABLE'; - package App::Codeowners::Formatter::Table;use warnings;use strict;our$VERSION='0.47';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(stringify);use Encode qw(encode);sub finish {my$self=shift;my$results=shift;eval {require Text::Table::Any}or die "Missing dependency: Text::Table::Any\n";my$table=Text::Table::Any::table(header_row=>1,rows=>[$self->columns,map {[map {stringify($_)}@$_]}@$results],backend=>$ENV{PERL_TEXT_TABLE},);print {$self->handle}encode('UTF-8',$table)}1; + package App::Codeowners::Formatter::Table;use warnings;use strict;our$VERSION='0.48';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(stringify);use Encode qw(encode);sub finish {my$self=shift;my$results=shift;eval {require Text::Table::Any}or die "Missing dependency: Text::Table::Any\n";my$table=Text::Table::Any::table(header_row=>1,rows=>[$self->columns,map {[map {stringify($_)}@$_]}@$results],backend=>$ENV{PERL_TEXT_TABLE},);print {$self->handle}encode('UTF-8',$table)}1; APP_CODEOWNERS_FORMATTER_TABLE $fatpacked{"App/Codeowners/Formatter/YAML.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_FORMATTER_YAML'; - package App::Codeowners::Formatter::YAML;use warnings;use strict;our$VERSION='0.47';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(zip);sub finish {my$self=shift;my$results=shift;eval {require YAML}or die "Missing dependency: YAML\n";my$columns=$self->columns;$results=[map {+{zip @$columns,@$_}}@$results];print {$self->handle}YAML::Dump($results)}1; + package App::Codeowners::Formatter::YAML;use warnings;use strict;our$VERSION='0.48';use parent 'App::Codeowners::Formatter';use App::Codeowners::Util qw(zip);sub finish {my$self=shift;my$results=shift;eval {require YAML}or die "Missing dependency: YAML\n";my$columns=$self->columns;$results=[map {+{zip @$columns,@$_}}@$results];print {$self->handle}YAML::Dump($results)}1; APP_CODEOWNERS_FORMATTER_YAML $fatpacked{"App/Codeowners/Options.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_OPTIONS'; - package App::Codeowners::Options;use v5.10.1;use warnings;use strict;use Getopt::Long 2.39 ();use Path::Tiny;our$VERSION='0.47';sub pod2usage {eval {require Pod::Usage};if ($@){my$ref=$VERSION eq '9999.999' ? 'master' : "v$VERSION";my$exit=(@_==1 && $_[0]=~ /^\d+$/ && $_[0])// (@_ % 2==0 && {@_}->{'-exitval'})// 2;print STDERR <(-t STDOUT ? 1 : 0),'format|f=s'=>undef,'help|h|?'=>0,'manual|man'=>0,'shell-completion:s'=>undef,'version|v'=>0,}}sub command_options {return {'create'=>{},'owners'=>{'pattern=s'=>'',},'patterns'=>{'owner=s'=>'',},'projects'=>{},'show'=>{'owner=s@'=>[],'pattern=s@'=>[],'project=s@'=>[],'patterns!'=>0,'projects!'=>undef,},'update'=>{},}}sub commands {my$self=shift;my@commands=sort keys %{$self->command_options};return@commands}sub options {my$self=shift;my@command_options;if (my$command=$self->{command}){@command_options=keys %{$self->command_options->{$command}|| {}}}return (keys %{$self->early_options},@command_options)}sub new {my$class=shift;my@args=@_;my$self=bless {},$class;my@args_copy=@args;my$opts=$self->get_options(args=>\@args,spec=>$self->early_options,config=>'pass_through',)or pod2usage(2);if ($ENV{CODEOWNERS_COMPLETIONS}){$self->{command}=$args[0]|| '';my$cword=$ENV{CWORD};my$cur=$ENV{CUR}|| '';while (0 < --$cword){last if$cur eq ($args_copy[$cword]|| '')}$self->completions($cword,@args_copy);exit 0}if ($opts->{version}){my$progname=path($0)->basename;print "${progname} ${VERSION}\n";exit 0}if ($opts->{help}){pod2usage(-exitval=>0,-verbose=>99,-sections=>[qw(NAME SYNOPSIS OPTIONS COMMANDS)])}if ($opts->{manual}){pod2usage(-exitval=>0,-verbose=>2)}if (defined$opts->{shell_completion}){$self->shell_completion($opts->{shell_completion});exit 0}my$command=shift@args;my$command_options=$self->command_options->{$command || ''};if (!$command_options){unshift@args,$command if defined$command;$command='show';$command_options=$self->command_options->{$command}}my$more_opts=$self->get_options(args=>\@args,spec=>$command_options,)or pod2usage(2);%$self=(%$opts,%$more_opts,command=>$command,args=>\@args);return$self}sub command {my$self=shift;my$command=$self->{command};my@commands=sort keys %{$self->command_options};return if not grep {$_ eq $command}@commands;$command =~ s/[^a-z]/_/g;return$command}sub args {my$self=shift;return @{$self->{args}|| []}}sub get_options {my$self=shift;my$args={@_==1 && ref $_[0]eq 'HASH' ? %{$_[0]}: @_};my%options;my%results;while (my ($opt,$default_value)=each %{$args->{spec}}){my ($name)=$opt =~ /^([^=:!|]+)/;$name =~ s/-/_/g;$results{$name}=$default_value;$options{$opt}=\$results{$name}}if (my$fn=$args->{callback}){$options{'<>'}=sub {my$arg=shift;$fn->($arg,\%results)}}my$p=Getopt::Long::Parser->new;$p->configure($args->{config}|| 'default');return if!$p->getoptionsfromarray($args->{args},%options);return \%results}sub shell_completion {my$self=shift;my$type=lc(shift || 'bash');if ($type eq 'bash'){print <<'END'}else {warn "No such shell completion: $type\n"}}sub completions {my$self=shift;my$cword=shift;my@words=@_;my$current=$words[$cword]|| '';my$prev=$words[$cword - 1]|| '';my$reply;if ($prev eq '--format' || $prev eq '-f'){$reply=$self->_completion_formats}elsif ($current =~ /^-/){$reply=$self->_completion_options}else {if (!$self->command){$reply=[$self->commands,@{$self->_completion_options([keys %{$self->early_options}])}]}else {print 'file';exit 9}}local $,="\n";print grep {/^\Q$current\E/}@$reply;exit 0}sub _completion_options {my$self=shift;my$opts=shift || [$self->options];my@options;for my$option (@$opts){my ($names,$op,$vtype)=$option =~ /^([^=:!]+)([=:!]?)(.*)$/;my@names=split(/\|/,$names);for my$name (@names){if ($op eq '!'){push@options,"--$name","--no-$name"}else {if (length($name)> 1){push@options,"--$name"}else {push@options,"-$name"}}}}return [sort@options]}sub _completion_formats {[qw(csv json json:pretty tsv yaml)]}1; + package App::Codeowners::Options;use v5.10.1;use warnings;use strict;use Getopt::Long 2.39 ();use Path::Tiny;our$VERSION='0.48';sub pod2usage {eval {require Pod::Usage};if ($@){my$ref=$VERSION eq '9999.999' ? 'master' : "v$VERSION";my$exit=(@_==1 && $_[0]=~ /^\d+$/ && $_[0])// (@_ % 2==0 && {@_}->{'-exitval'})// 2;print STDERR <(-t STDOUT ? 1 : 0),'format|f=s'=>undef,'help|h|?'=>0,'manual|man'=>0,'shell-completion:s'=>undef,'version|v'=>0,}}sub command_options {return {'create'=>{},'owners'=>{'pattern=s'=>'',},'patterns'=>{'owner=s'=>'',},'projects'=>{},'show'=>{'owner=s@'=>[],'pattern=s@'=>[],'project=s@'=>[],'patterns!'=>0,'projects!'=>undef,},'update'=>{},}}sub commands {my$self=shift;my@commands=sort keys %{$self->command_options};return@commands}sub options {my$self=shift;my@command_options;if (my$command=$self->{command}){@command_options=keys %{$self->command_options->{$command}|| {}}}return (keys %{$self->early_options},@command_options)}sub new {my$class=shift;my@args=@_;my$self=bless {},$class;my@args_copy=@args;my$opts=$self->get_options(args=>\@args,spec=>$self->early_options,config=>'pass_through',)or pod2usage(2);if ($ENV{CODEOWNERS_COMPLETIONS}){$self->{command}=$args[0]|| '';my$cword=$ENV{CWORD};my$cur=$ENV{CUR}|| '';while (0 < --$cword){last if$cur eq ($args_copy[$cword]|| '')}$self->completions($cword,@args_copy);exit 0}if ($opts->{version}){my$progname=path($0)->basename;print "${progname} ${VERSION}\n";exit 0}if ($opts->{help}){pod2usage(-exitval=>0,-verbose=>99,-sections=>[qw(NAME SYNOPSIS OPTIONS COMMANDS)])}if ($opts->{manual}){pod2usage(-exitval=>0,-verbose=>2)}if (defined$opts->{shell_completion}){$self->shell_completion($opts->{shell_completion});exit 0}my$command=shift@args;my$command_options=$self->command_options->{$command || ''};if (!$command_options){unshift@args,$command if defined$command;$command='show';$command_options=$self->command_options->{$command}}my$more_opts=$self->get_options(args=>\@args,spec=>$command_options,)or pod2usage(2);%$self=(%$opts,%$more_opts,command=>$command,args=>\@args);return$self}sub command {my$self=shift;my$command=$self->{command};my@commands=sort keys %{$self->command_options};return if not grep {$_ eq $command}@commands;$command =~ s/[^a-z]/_/g;return$command}sub args {my$self=shift;return @{$self->{args}|| []}}sub get_options {my$self=shift;my$args={@_==1 && ref $_[0]eq 'HASH' ? %{$_[0]}: @_};my%options;my%results;while (my ($opt,$default_value)=each %{$args->{spec}}){my ($name)=$opt =~ /^([^=:!|]+)/;$name =~ s/-/_/g;$results{$name}=$default_value;$options{$opt}=\$results{$name}}if (my$fn=$args->{callback}){$options{'<>'}=sub {my$arg=shift;$fn->($arg,\%results)}}my$p=Getopt::Long::Parser->new;$p->configure($args->{config}|| 'default');return if!$p->getoptionsfromarray($args->{args},%options);return \%results}sub shell_completion {my$self=shift;my$type=lc(shift || 'bash');if ($type eq 'bash'){print <<'END'}else {warn "No such shell completion: $type\n"}}sub completions {my$self=shift;my$cword=shift;my@words=@_;my$current=$words[$cword]|| '';my$prev=$words[$cword - 1]|| '';my$reply;if ($prev eq '--format' || $prev eq '-f'){$reply=$self->_completion_formats}elsif ($current =~ /^-/){$reply=$self->_completion_options}else {if (!$self->command){$reply=[$self->commands,@{$self->_completion_options([keys %{$self->early_options}])}]}else {print 'file';exit 9}}local $,="\n";print grep {/^\Q$current\E/}@$reply;exit 0}sub _completion_options {my$self=shift;my$opts=shift || [$self->options];my@options;for my$option (@$opts){my ($names,$op,$vtype)=$option =~ /^([^=:!]+)([=:!]?)(.*)$/;my@names=split(/\|/,$names);for my$name (@names){if ($op eq '!'){push@options,"--$name","--no-$name"}else {if (length($name)> 1){push@options,"--$name"}else {push@options,"-$name"}}}}return [sort@options]}sub _completion_formats {[qw(csv json json:pretty tsv yaml)]}1; Online documentation is available at: https://github.com/chazmcgarvey/git-codeowners/blob/$ref/README.md @@ -91,7 +91,7 @@ $fatpacked{"App/Codeowners/Options.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\ APP_CODEOWNERS_OPTIONS $fatpacked{"App/Codeowners/Util.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'APP_CODEOWNERS_UTIL'; - package App::Codeowners::Util;use warnings;use strict;use Encode qw(decode);use Exporter qw(import);use Path::Tiny;our@EXPORT_OK=qw(colorstrip find_codeowners_in_directory find_nearest_codeowners git_ls_files git_toplevel run_command run_git stringf stringify unbackslash zip);our$VERSION='0.47';sub find_nearest_codeowners {my$path=path(shift || '.')->absolute;while (!$path->is_rootdir){my$filepath=find_codeowners_in_directory($path);return$filepath if$filepath;$path=$path->parent}}sub find_codeowners_in_directory {my$path=path(shift)or die;my@tries=([qw(CODEOWNERS)],[qw(docs CODEOWNERS)],[qw(.bitbucket CODEOWNERS)],[qw(.github CODEOWNERS)],[qw(.gitlab CODEOWNERS)],);for my$parts (@tries){my$try=$path->child(@$parts);return$try if$try->is_file}}sub run_command {my$filter;$filter=pop if ref($_[-1])eq 'CODE';print STDERR "# @_\n" if$ENV{GIT_CODEOWNERS_DEBUG};my ($child_in,$child_out);require IPC::Open2;my$pid=IPC::Open2::open2($child_out,$child_in,@_);close($child_in);binmode($child_out,':encoding(UTF-8)');my$proc=App::Codeowners::Util::Process->new(pid=>$pid,fh=>$child_out,filter=>$filter,);return wantarray ? ($proc,@{$proc->all}): $proc}sub run_git {return run_command('git',@_)}sub git_ls_files {my$dir=shift || '.';return run_git('-C',$dir,'ls-files',@_,\&_unescape_git_filepath)}sub _unescape_git_filepath {return $_ if $_ !~ /^"(.+)"$/;return decode('UTF-8',unbackslash($1))}sub git_toplevel {my$dir=shift || '.';my ($proc,$path)=run_git('-C',$dir,qw{rev-parse --show-toplevel});return if$proc->wait!=0 ||!$path;return path($path)}sub colorstrip {my$str=shift || '';$str =~ s/\e\[[\d;]*m//g;return$str}sub stringify {my$item=shift;return ref($item)eq 'ARRAY' ? join(',',@$item): $item}sub zip (\@\@) {my$max=-1;$max < $#$_ && ($max=$#$_)foreach @_;map {my$ix=$_;map $_->[$ix],@_}0 .. $max}sub _replace {my ($args,$orig,$alignment,$min_width,$max_width,$passme,$formchar)=@_;return$orig unless defined$args->{$formchar};$alignment='+' unless defined$alignment;my$replacement=$args->{$formchar};if (ref$replacement eq 'CODE'){$passme ||= "";$passme =~ tr/{}//d;$replacement=$replacement->($passme)}my$replength;if (eval {require Unicode::GCString}){my$gcstring=Unicode::GCString->new(colorstrip($replacement));$replength=$gcstring->columns}else {$replength=length colorstrip($replacement)}$min_width ||= $replength;$max_width ||= $replength;if (($replength > $min_width)&& ($replength < $max_width)){return$replacement}if ($replength > $max_width){return substr($replacement,0,$max_width)}my$padding=$min_width - $replength;$padding=0 if$padding < 0;if ($alignment eq '-'){return$replacement .' ' x $padding}return ' ' x $padding .$replacement}my$regex=qr/ + package App::Codeowners::Util;use warnings;use strict;use Encode qw(decode);use Exporter qw(import);use Path::Tiny;our@EXPORT_OK=qw(colorstrip find_codeowners_in_directory find_nearest_codeowners git_ls_files git_toplevel run_command run_git stringf stringify unbackslash zip);our$VERSION='0.48';sub find_nearest_codeowners {my$path=path(shift || '.')->absolute;while (!$path->is_rootdir){my$filepath=find_codeowners_in_directory($path);return$filepath if$filepath;$path=$path->parent}}sub find_codeowners_in_directory {my$path=path(shift)or die;my@tries=([qw(CODEOWNERS)],[qw(docs CODEOWNERS)],[qw(.bitbucket CODEOWNERS)],[qw(.github CODEOWNERS)],[qw(.gitlab CODEOWNERS)],);for my$parts (@tries){my$try=$path->child(@$parts);return$try if$try->is_file}}sub run_command {my$filter;$filter=pop if ref($_[-1])eq 'CODE';print STDERR "# @_\n" if$ENV{GIT_CODEOWNERS_DEBUG};my ($child_in,$child_out);require IPC::Open2;my$pid=IPC::Open2::open2($child_out,$child_in,@_);close($child_in);binmode($child_out,':encoding(UTF-8)');my$proc=App::Codeowners::Util::Process->new(pid=>$pid,fh=>$child_out,filter=>$filter,);return wantarray ? ($proc,@{$proc->all}): $proc}sub run_git {return run_command('git',@_)}sub git_ls_files {my$dir=shift || '.';return run_git('-C',$dir,'ls-files',@_,\&_unescape_git_filepath)}sub _unescape_git_filepath {return $_ if $_ !~ /^"(.+)"$/;return decode('UTF-8',unbackslash($1))}sub git_toplevel {my$dir=shift || '.';my ($proc,$path)=run_git('-C',$dir,qw{rev-parse --show-toplevel});return if$proc->wait!=0 ||!$path;return path($path)}sub colorstrip {my$str=shift || '';$str =~ s/\e\[[\d;]*m//g;return$str}sub stringify {my$item=shift;return ref($item)eq 'ARRAY' ? join(',',@$item): $item}sub zip (\@\@) {my$max=-1;$max < $#$_ && ($max=$#$_)foreach @_;map {my$ix=$_;map $_->[$ix],@_}0 .. $max}sub _replace {my ($args,$orig,$alignment,$min_width,$max_width,$passme,$formchar)=@_;return$orig unless defined$args->{$formchar};$alignment='+' unless defined$alignment;my$replacement=$args->{$formchar};if (ref$replacement eq 'CODE'){$passme ||= "";$passme =~ tr/{}//d;$replacement=$replacement->($passme)}my$replength;if (eval {require Unicode::GCString}){my$gcstring=Unicode::GCString->new(colorstrip($replacement));$replength=$gcstring->columns}else {$replength=length colorstrip($replacement)}$min_width ||= $replength;$max_width ||= $replength;if (($replength > $min_width)&& ($replength < $max_width)){return$replacement}if ($replength > $max_width){return substr($replacement,0,$max_width)}my$padding=$min_width - $replength;$padding=0 if$padding < 0;if ($alignment eq '-'){return$replacement .' ' x $padding}return ' ' x $padding .$replacement}my$regex=qr/ (% # leading '%' (-)? # left-align, rather than right (\d*)? # (optional) minimum field width @@ -132,7 +132,7 @@ $fatpacked{"Color/RGB/Util.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<' COLOR_RGB_UTIL $fatpacked{"File/Codeowners.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'FILE_CODEOWNERS'; - package File::Codeowners;use v5.10.1;use warnings;use strict;use Encode qw(encode);use Path::Tiny 0.089;use Scalar::Util qw(openhandle);use Text::Gitignore qw(build_gitignore_matcher);our$VERSION='0.47';sub _croak {require Carp;Carp::croak(@_)}sub _usage {_croak("Usage: @_\n")}sub new {my$class=shift;my$self=bless {},$class}sub parse {my$self=shift;my$input=shift or _usage(q{$codeowners->parse($input)});return$self->parse_from_array($input,@_)if @_;return$self->parse_from_array($input)if ref($input)eq 'ARRAY';return$self->parse_from_string($input)if ref($input)eq 'SCALAR';return$self->parse_from_fh($input)if openhandle($input);return$self->parse_from_filepath($input)}sub parse_from_filepath {my$self=shift;my$path=shift or _usage(q{$codeowners->parse_from_filepath($filepath)});$self=bless({},$self)if!ref($self);return$self->parse_from_fh(path($path)->openr_utf8)}sub parse_from_fh {my$self=shift;my$fh=shift or _usage(q{$codeowners->parse_from_fh($fh)});$self=bless({},$self)if!ref($self);my@lines;my$parse_unowned;my%unowned;my$current_project;while (my$line=<$fh>){my$lineno=$. - 1;chomp$line;if ($line eq '### UNOWNED (File::Codeowners)'){$parse_unowned++;last}elsif ($line =~ /^\h*#(.*)/){my$comment=$1;if ($comment =~ /^\h*Project:\h*(.+?)\h*$/i){$current_project=$1 || undef}$lines[$lineno]={comment=>$comment,}}elsif ($line =~ /^\h*$/){}elsif ($line =~ /^\h*(.+?)(?$pattern,owners=>\@owners,$current_project ? (project=>$current_project): (),}}else {die "Parse error on line $.: $line\n"}}if ($parse_unowned){while (my$line=<$fh>){chomp$line;if ($line =~ /# (.+)/){my$filepath=$1;$unowned{$filepath}++}}}$self->{lines}=\@lines;$self->{unowned}=\%unowned;return$self}sub parse_from_array {my$self=shift;my$arr=shift or _usage(q{$codeowners->parse_from_array(\@lines)});$self=bless({},$self)if!ref($self);$arr=[$arr,@_]if @_;my$str=join("\n",@$arr);return$self->parse_from_string(\$str)}sub parse_from_string {my$self=shift;my$str=shift or _usage(q{$codeowners->parse_from_string(\$string)});$self=bless({},$self)if!ref($self);my$ref=ref($str)eq 'SCALAR' ? $str : \$str;open(my$fh,'<:encoding(UTF-8)',$ref)or die "open failed: $!";return$self->parse_from_fh($fh)}sub write_to_filepath {my$self=shift;my$path=shift or _usage(q{$codeowners->write_to_filepath($filepath)});path($path)->spew_utf8([map {"$_\n"}@{$self->write_to_array('')}])}sub write_to_fh {my$self=shift;my$fh=shift or _usage(q{$codeowners->write_to_fh($fh)});for my$line (@{$self->write_to_array}){print$fh "$line\n"}}sub write_to_string {my$self=shift;my$str=join("\n",@{$self->write_to_array})."\n";return \$str}sub write_to_array {my$self=shift;my$charset=shift // 'UTF-8';my@format;for my$line (@{$self->_lines}){if (my$comment=$line->{comment}){push@format,"#$comment"}elsif (my$pattern=$line->{pattern}){my$owners=join(' ',@{$line->{owners}});push@format,"$pattern $owners"}else {push@format,''}}my@unowned=sort keys %{$self->_unowned};if (@unowned){push@format,'' if$format[-1];push@format,'### UNOWNED (File::Codeowners)';for my$unowned (@unowned){push@format,"# $unowned"}}if ($charset){$_=encode($charset,$_)for@format}return \@format}sub match {my$self=shift;my$filepath=shift or _usage(q{$codeowners->match($filepath)});my$lines=$self->{match_lines}||= [reverse grep {($_ || {})->{pattern}}@{$self->_lines}];for my$line (@$lines){my$matcher=$line->{matcher}||= build_gitignore_matcher([$line->{pattern}]);return {pattern=>$line->{pattern},owners=>[@{$line->{owners}|| []}],$line->{project}? (project=>$line->{project}): (),}if$matcher->($filepath)}return undef}sub owners {my$self=shift;my$pattern=shift;return$self->{owners}if!$pattern && $self->{owners};my%owners;for my$line (@{$self->_lines}){next if$pattern && $line->{pattern}&& $pattern ne $line->{pattern};$owners{$_}++ for (@{$line->{owners}|| []})}my$owners=[sort keys%owners];$self->{owners}=$owners if!$pattern;return$owners}sub patterns {my$self=shift;my$owner=shift;return$self->{patterns}if!$owner && $self->{patterns};my%patterns;for my$line (@{$self->_lines}){next if$owner &&!grep {$_ eq $owner}@{$line->{owners}|| []};my$pattern=$line->{pattern};$patterns{$pattern}++ if$pattern}my$patterns=[sort keys%patterns];$self->{patterns}=$patterns if!$owner;return$patterns}sub projects {my$self=shift;return$self->{projects}if$self->{projects};my%projects;for my$line (@{$self->_lines}){my$project=$line->{project};$projects{$project}++ if$project}my$projects=[sort keys%projects];$self->{projects}=$projects;return$projects}sub update_owners {my$self=shift;my$pattern=shift;my$owners=shift;$pattern && $owners or _usage(q{$codeowners->update_owners($pattern => \@owners)});$owners=[$owners]if ref($owners)ne 'ARRAY';$self->_clear;for my$line (@{$self->_lines}){next if!$line->{pattern};next if$pattern ne $line->{pattern};$line->{owners}=[@$owners]}}sub append {my$self=shift;$self->_clear;push @{$self->_lines},(@_ ? {@_}: undef)}sub prepend {my$self=shift;$self->_clear;unshift @{$self->_lines},(@_ ? {@_}: undef)}sub unowned {my$self=shift;[sort keys %{$self->{unowned}|| {}}]}sub add_unowned {my$self=shift;$self->_unowned->{$_}++ for @_}sub remove_unowned {my$self=shift;delete$self->_unowned->{$_}for @_}sub is_unowned {my$self=shift;my$filepath=shift;$self->_unowned->{$filepath}}sub clear_unowned {my$self=shift;$self->{unowned}={}}sub _lines {shift->{lines}||= []}sub _unowned {shift->{unowned}||= {}}sub _clear {my$self=shift;delete$self->{match_lines};delete$self->{owners};delete$self->{patterns};delete$self->{projects}}1; + package File::Codeowners;use v5.10.1;use warnings;use strict;use Encode qw(encode);use Path::Tiny 0.089;use Scalar::Util qw(openhandle);use Text::Gitignore qw(build_gitignore_matcher);our$VERSION='0.48';sub _croak {require Carp;Carp::croak(@_)}sub _usage {_croak("Usage: @_\n")}sub new {my$class=shift;my$self=bless {},$class}sub parse {my$self=shift;my$input=shift or _usage(q{$codeowners->parse($input)});return$self->parse_from_array($input,@_)if @_;return$self->parse_from_array($input)if ref($input)eq 'ARRAY';return$self->parse_from_string($input)if ref($input)eq 'SCALAR';return$self->parse_from_fh($input)if openhandle($input);return$self->parse_from_filepath($input)}sub parse_from_filepath {my$self=shift;my$path=shift or _usage(q{$codeowners->parse_from_filepath($filepath)});$self=bless({},$self)if!ref($self);return$self->parse_from_fh(path($path)->openr_utf8)}sub parse_from_fh {my$self=shift;my$fh=shift or _usage(q{$codeowners->parse_from_fh($fh)});$self=bless({},$self)if!ref($self);my@lines;my$parse_unowned;my%unowned;my$current_project;while (my$line=<$fh>){my$lineno=$. - 1;chomp$line;if ($line eq '### UNOWNED (File::Codeowners)'){$parse_unowned++;last}elsif ($line =~ /^\h*#(.*)/){my$comment=$1;my$project;if ($comment =~ /^\h*Project:\h*(.+?)\h*$/i){$project=$current_project=$1 || undef}$lines[$lineno]={comment=>$comment,$project ? (project=>$project): (),}}elsif ($line =~ /^\h*$/){}elsif ($line =~ /^\h*(.+?)(?$pattern,owners=>\@owners,$current_project ? (project=>$current_project): (),}}else {die "Parse error on line $.: $line\n"}}if ($parse_unowned){while (my$line=<$fh>){chomp$line;if ($line =~ /# (.+)/){my$filepath=$1;$unowned{$filepath}++}}}$self->{lines}=\@lines;$self->{unowned}=\%unowned;return$self}sub parse_from_array {my$self=shift;my$arr=shift or _usage(q{$codeowners->parse_from_array(\@lines)});$self=bless({},$self)if!ref($self);$arr=[$arr,@_]if @_;my$str=join("\n",@$arr);return$self->parse_from_string(\$str)}sub parse_from_string {my$self=shift;my$str=shift or _usage(q{$codeowners->parse_from_string(\$string)});$self=bless({},$self)if!ref($self);my$ref=ref($str)eq 'SCALAR' ? $str : \$str;open(my$fh,'<:encoding(UTF-8)',$ref)or die "open failed: $!";return$self->parse_from_fh($fh)}sub write_to_filepath {my$self=shift;my$path=shift or _usage(q{$codeowners->write_to_filepath($filepath)});path($path)->spew_utf8([map {"$_\n"}@{$self->write_to_array('')}])}sub write_to_fh {my$self=shift;my$fh=shift or _usage(q{$codeowners->write_to_fh($fh)});for my$line (@{$self->write_to_array}){print$fh "$line\n"}}sub write_to_string {my$self=shift;my$str=join("\n",@{$self->write_to_array})."\n";return \$str}sub write_to_array {my$self=shift;my$charset=shift // 'UTF-8';my@format;for my$line (@{$self->_lines}){if (my$comment=$line->{comment}){push@format,"#$comment"}elsif (my$pattern=$line->{pattern}){my$owners=join(' ',@{$line->{owners}});push@format,"$pattern $owners"}else {push@format,''}}my@unowned=sort keys %{$self->_unowned};if (@unowned){push@format,'' if$format[-1];push@format,'### UNOWNED (File::Codeowners)';for my$unowned (@unowned){push@format,"# $unowned"}}if ($charset){$_=encode($charset,$_)for@format}return \@format}sub match {my$self=shift;my$filepath=shift or _usage(q{$codeowners->match($filepath)});my$lines=$self->{match_lines}||= [reverse grep {($_ || {})->{pattern}}@{$self->_lines}];for my$line (@$lines){my$matcher=$line->{matcher}||= build_gitignore_matcher([$line->{pattern}]);return {pattern=>$line->{pattern},owners=>[@{$line->{owners}|| []}],$line->{project}? (project=>$line->{project}): (),}if$matcher->($filepath)}return undef}sub owners {my$self=shift;my$pattern=shift;return$self->{owners}if!$pattern && $self->{owners};my%owners;for my$line (@{$self->_lines}){next if$pattern && $line->{pattern}&& $pattern ne $line->{pattern};$owners{$_}++ for (@{$line->{owners}|| []})}my$owners=[sort keys%owners];$self->{owners}=$owners if!$pattern;return$owners}sub patterns {my$self=shift;my$owner=shift;return$self->{patterns}if!$owner && $self->{patterns};my%patterns;for my$line (@{$self->_lines}){next if$owner &&!grep {$_ eq $owner}@{$line->{owners}|| []};my$pattern=$line->{pattern};$patterns{$pattern}++ if$pattern}my$patterns=[sort keys%patterns];$self->{patterns}=$patterns if!$owner;return$patterns}sub projects {my$self=shift;return$self->{projects}if$self->{projects};my%projects;for my$line (@{$self->_lines}){my$project=$line->{project};$projects{$project}++ if$project}my$projects=[sort keys%projects];$self->{projects}=$projects;return$projects}sub update_owners {my$self=shift;my$pattern=shift;my$owners=shift;$pattern && $owners or _usage(q{$codeowners->update_owners($pattern => \@owners)});$owners=[$owners]if ref($owners)ne 'ARRAY';$self->_clear;my$count=0;for my$line (@{$self->_lines}){next if!$line->{pattern};next if$pattern ne $line->{pattern};$line->{owners}=[@$owners];++$count}return$count}sub update_owners_by_project {my$self=shift;my$project=shift;my$owners=shift;$project && $owners or _usage(q{$codeowners->update_owners_by_project($project => \@owners)});$owners=[$owners]if ref($owners)ne 'ARRAY';$self->_clear;my$count=0;for my$line (@{$self->_lines}){next if!$line->{project}||!$line->{owners};next if$project ne $line->{project};$line->{owners}=[@$owners];++$count}return$count}sub rename_project {my$self=shift;my$old_project=shift;my$new_project=shift;$old_project && $new_project or _usage(q{$codeowners->rename_project($project => $new_project)});$self->_clear;my$count=0;for my$line (@{$self->_lines}){next if!exists$line->{project}|| $old_project ne $line->{project};$line->{project}=$new_project;$line->{comment}=" Project: $new_project" if exists$line->{comment};++$count}return$count}sub append {my$self=shift;$self->_clear;push @{$self->_lines},(@_ ? {@_}: undef)}sub prepend {my$self=shift;$self->_clear;unshift @{$self->_lines},(@_ ? {@_}: undef)}sub unowned {my$self=shift;[sort keys %{$self->{unowned}|| {}}]}sub add_unowned {my$self=shift;$self->_unowned->{$_}++ for @_}sub remove_unowned {my$self=shift;delete$self->_unowned->{$_}for @_}sub is_unowned {my$self=shift;my$filepath=shift;$self->_unowned->{$filepath}}sub clear_unowned {my$self=shift;$self->{unowned}={}}sub _lines {shift->{lines}||= []}sub _unowned {shift->{unowned}||= {}}sub _clear {my$self=shift;delete$self->{match_lines};delete$self->{owners};delete$self->{patterns};delete$self->{projects}}1; FILE_CODEOWNERS $fatpacked{"File/Which.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'FILE_WHICH'; @@ -217,7 +217,7 @@ $fatpacked{"JSON/PP/Boolean.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<< JSON_PP_BOOLEAN $fatpacked{"Path/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PATH_TINY'; - use 5.008001;use strict;use warnings;package Path::Tiny;our$VERSION='0.108';use Config;use Exporter 5.57 (qw/import/);use File::Spec 0.86 ();use Carp ();our@EXPORT=qw/path/;our@EXPORT_OK=qw/cwd rootdir tempfile tempdir/;use constant {PATH=>0,CANON=>1,VOL=>2,DIR=>3,FILE=>4,TEMP=>5,IS_WIN32=>($^O eq 'MSWin32'),};use overload (q{""}=>sub {$_[0]->[PATH]},bool=>sub () {1},fallback=>1,);sub FREEZE {return $_[0]->[PATH]}sub THAW {return path($_[2])}{no warnings 'once';*TO_JSON=*FREEZE};my$HAS_UU;sub _check_UU {local$SIG{__DIE__};!!eval {require Unicode::UTF8;Unicode::UTF8->VERSION(0.58);1}}my$HAS_PU;sub _check_PU {local$SIG{__DIE__};!!eval {require Encode;require PerlIO::utf8_strict;PerlIO::utf8_strict->VERSION(0.003);1}}my$HAS_FLOCK=$Config{d_flock}|| $Config{d_fcntl_can_lock}|| $Config{d_lockf};my$SLASH=qr{[\\/]};my$NOTSLASH=qr{[^\\/]};my$DRV_VOL=qr{[a-z]:}i;my$UNC_VOL=qr{$SLASH $SLASH $NOTSLASH+ $SLASH $NOTSLASH+}x;my$WIN32_ROOT=qr{(?: $UNC_VOL $SLASH | $DRV_VOL $SLASH | $SLASH )}x;sub _win32_vol {my ($path,$drv)=@_;require Cwd;my$dcwd=eval {Cwd::getdcwd($drv)};$dcwd="$drv" unless defined$dcwd && length$dcwd;$dcwd =~ s{$SLASH?$}{/};$path =~ s{^$DRV_VOL}{$dcwd};return$path}sub _is_root {return IS_WIN32()? ($_[0]=~ /^$WIN32_ROOT$/): ($_[0]eq '/')}BEGIN {*_same=IS_WIN32()? sub {lc($_[0])eq lc($_[1])}: sub {$_[0]eq $_[1]}}my%MODEBITS=(om=>0007,gm=>0070,um=>0700);{my$m=0;$MODEBITS{$_}=(1 << $m++)for qw/ox ow or gx gw gr ux uw ur/};sub _symbolic_chmod {my ($mode,$symbolic)=@_;for my$clause (split /,\s*/,$symbolic){if ($clause =~ m{\A([augo]+)([=+-])([rwx]+)\z}){my ($who,$action,$perms)=($1,$2,$3);$who =~ s/a/ugo/g;for my$w (split //,$who){my$p=0;$p |= $MODEBITS{"$w$_"}for split //,$perms;if ($action eq '='){$mode=($mode & ~$MODEBITS{"${w}m"})| $p}else {$mode=$action eq "+" ? ($mode | $p): ($mode & ~$p)}}}else {Carp::croak("Invalid mode clause '$clause' for chmod()")}}return$mode}{package flock;use warnings::register}my$WARNED_NO_FLOCK=0;sub _throw {my ($self,$function,$file,$msg)=@_;if ($function =~ /^flock/ && $! =~ /operation not supported|function not implemented/i &&!warnings::fatal_enabled('flock')){if (!$WARNED_NO_FLOCK){warnings::warn(flock=>"Flock not available: '$!': continuing in unsafe mode");$WARNED_NO_FLOCK++}}else {$msg=$! unless defined$msg;Path::Tiny::Error->throw($function,(defined$file ? $file : $self->[PATH]),$msg)}return}sub _get_args {my ($raw,@valid)=@_;if (defined($raw)&& ref($raw)ne 'HASH'){my (undef,undef,undef,$called_as)=caller(1);$called_as =~ s{^.*::}{};Carp::croak("Options for $called_as must be a hash reference")}my$cooked={};for my$k (@valid){$cooked->{$k}=delete$raw->{$k}if exists$raw->{$k}}if (keys %$raw){my (undef,undef,undef,$called_as)=caller(1);$called_as =~ s{^.*::}{};Carp::croak("Invalid option(s) for $called_as: " .join(", ",keys %$raw))}return$cooked}sub path {my$path=shift;Carp::croak("Path::Tiny paths require defined, positive-length parts")unless 1 + @_==grep {defined && length}$path,@_;if (!@_ && ref($path)eq __PACKAGE__ &&!$path->[TEMP]){return$path}$path="$path";if (IS_WIN32()){$path=_win32_vol($path,$1)if$path =~ m{^($DRV_VOL)(?:$NOTSLASH|$)};$path .= "/" if$path =~ m{^$UNC_VOL$}}if (@_){$path .= (_is_root($path)? "" : "/").join("/",@_)}my$cpath=$path=File::Spec->canonpath($path);$path =~ tr[\\][/] if IS_WIN32();$path="/" if$path eq '/..';$path .= "/" if IS_WIN32()&& $path =~ m{^$UNC_VOL$};if (_is_root($path)){$path =~ s{/?$}{/}}else {$path =~ s{/$}{}}if ($path =~ m{^(~[^/]*).*}){require File::Glob;my ($homedir)=File::Glob::bsd_glob($1);$homedir =~ tr[\\][/] if IS_WIN32();$path =~ s{^(~[^/]*)}{$homedir}}bless [$path,$cpath ],__PACKAGE__}sub new {shift;path(@_)}sub cwd {require Cwd;return path(Cwd::getcwd())}sub rootdir {path(File::Spec->rootdir)}sub tempfile {shift if @_ && $_[0]eq 'Path::Tiny';my$opts=(@_ && ref $_[0]eq 'HASH')? shift @_ : {};$opts=_get_args($opts,qw/realpath/);my ($maybe_template,$args)=_parse_file_temp_args(@_);$args->{TEMPLATE}=$maybe_template->[0]if @$maybe_template;require File::Temp;my$temp=File::Temp->new(TMPDIR=>1,%$args);close$temp;my$self=$opts->{realpath}? path($temp)->realpath : path($temp)->absolute;$self->[TEMP]=$temp;return$self}sub tempdir {shift if @_ && $_[0]eq 'Path::Tiny';my$opts=(@_ && ref $_[0]eq 'HASH')? shift @_ : {};$opts=_get_args($opts,qw/realpath/);my ($maybe_template,$args)=_parse_file_temp_args(@_);require File::Temp;my$temp=File::Temp->newdir(@$maybe_template,TMPDIR=>1,%$args);my$self=$opts->{realpath}? path($temp)->realpath : path($temp)->absolute;$self->[TEMP]=$temp;$temp->{REALNAME}=$self->[CANON]if IS_WIN32;return$self}sub _parse_file_temp_args {my$leading_template=(scalar(@_)% 2==1 ? shift(@_): '');my%args=@_;%args=map {uc($_),$args{$_}}keys%args;my@template=(exists$args{TEMPLATE}? delete$args{TEMPLATE}: $leading_template ? $leading_template : ());return (\@template,\%args)}sub _splitpath {my ($self)=@_;@{$self}[VOL,DIR,FILE ]=File::Spec->splitpath($self->[PATH])}sub _resolve_symlinks {my ($self)=@_;my$new=$self;my ($count,%seen)=0;while (-l $new->[PATH]){if ($seen{$new->[PATH]}++){$self->_throw('readlink',$self->[PATH],"symlink loop detected")}if (++$count > 100){$self->_throw('readlink',$self->[PATH],"maximum symlink depth exceeded")}my$resolved=readlink$new->[PATH]or $new->_throw('readlink',$new->[PATH]);$resolved=path($resolved);$new=$resolved->is_absolute ? $resolved : $new->sibling($resolved)}return$new}sub absolute {my ($self,$base)=@_;if (IS_WIN32){return$self if length$self->volume;if ($self->is_absolute){require Cwd;my ($drv)=Win32::GetCwd()=~ /^($DRV_VOL | $UNC_VOL)/x;return path($drv .$self->[PATH])}}else {return$self if$self->is_absolute}require Cwd;return path(Cwd::getcwd(),$_[0]->[PATH])unless defined$base;$base=path($base);return path(($base->is_absolute ? $base : $base->absolute),$_[0]->[PATH])}sub append {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode truncate/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open>'}unless defined$binmode;my$mode=$args->{truncate}? ">" : ">>";my$fh=$self->filehandle({locked=>1 },$mode,$binmode);print {$fh}map {ref eq 'ARRAY' ? @$_ : $_}@data;close$fh or $self->_throw('close')}sub append_raw {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode truncate/);$args->{binmode}=':unix';append($self,$args,@data)}sub append_utf8 {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode truncate/);if (defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU())){$args->{binmode}=":unix";append($self,$args,map {Unicode::UTF8::encode_utf8($_)}@data)}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){$args->{binmode}=":unix:utf8_strict";append($self,$args,@data)}else {$args->{binmode}=":unix:encoding(UTF-8)";append($self,$args,@data)}}sub assert {my ($self,$assertion)=@_;return$self unless$assertion;if (ref$assertion eq 'CODE'){local $_=$self;$assertion->()or Path::Tiny::Error->throw("assert",$self->[PATH],"failed assertion")}else {Carp::croak("argument to assert must be a code reference argument")}return$self}sub basename {my ($self,@suffixes)=@_;$self->_splitpath unless defined$self->[FILE];my$file=$self->[FILE];for my$s (@suffixes){my$re=ref($s)eq 'Regexp' ? qr/$s$/ : qr/\Q$s\E$/;last if$file =~ s/$re//}return$file}sub canonpath {$_[0]->[CANON]}sub cached_temp {my$self=shift;$self->_throw("cached_temp",$self,"has no cached File::Temp object")unless defined$self->[TEMP];return$self->[TEMP]}sub child {my ($self,@parts)=@_;return path($self->[PATH],@parts)}sub children {my ($self,$filter)=@_;my$dh;opendir$dh,$self->[PATH]or $self->_throw('opendir');my@children=readdir$dh;closedir$dh or $self->_throw('closedir');if (not defined$filter){@children=grep {$_ ne '.' && $_ ne '..'}@children}elsif ($filter && ref($filter)eq 'Regexp'){@children=grep {$_ ne '.' && $_ ne '..' && $_ =~ $filter}@children}else {Carp::croak("Invalid argument '$filter' for children()")}return map {path($self->[PATH],$_)}@children}sub chmod {my ($self,$new_mode)=@_;my$mode;if ($new_mode =~ /\d/){$mode=($new_mode =~ /^0/ ? oct($new_mode): $new_mode)}elsif ($new_mode =~ /[=+-]/){$mode=_symbolic_chmod($self->stat->mode & 07777,$new_mode)}else {Carp::croak("Invalid mode argument '$new_mode' for chmod()")}CORE::chmod($mode,$self->[PATH])or $self->_throw("chmod");return 1}sub copy {my ($self,$dest)=@_;require File::Copy;File::Copy::copy($self->[PATH],$dest)or Carp::croak("copy failed for $self to $dest: $!");return -d $dest ? path($dest,$self->basename): path($dest)}sub digest {my ($self,@opts)=@_;my$args=(@opts && ref$opts[0]eq 'HASH')? shift@opts : {};$args=_get_args($args,qw/chunk_size/);unshift@opts,'SHA-256' unless@opts;require Digest;my$digest=Digest->new(@opts);if ($args->{chunk_size}){my$fh=$self->filehandle({locked=>1 },"<",":unix");my$buf;$digest->add($buf)while read$fh,$buf,$args->{chunk_size}}else {$digest->add($self->slurp_raw)}return$digest->hexdigest}sub dirname {my ($self)=@_;$self->_splitpath unless defined$self->[DIR];return length$self->[DIR]? $self->[DIR]: "."}sub edit {my$self=shift;my$cb=shift;my$args=_get_args(shift,qw/binmode/);Carp::croak("Callback for edit() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';local $_=$self->slurp(exists($args->{binmode})? {binmode=>$args->{binmode}}: ());$cb->();$self->spew($args,$_);return}sub edit_utf8 {my ($self,$cb)=@_;Carp::croak("Callback for edit_utf8() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';local $_=$self->slurp_utf8;$cb->();$self->spew_utf8($_);return}sub edit_raw {$_[2]={binmode=>":unix" };goto&edit}sub edit_lines {my$self=shift;my$cb=shift;my$args=_get_args(shift,qw/binmode/);Carp::croak("Callback for edit_lines() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open>'}unless defined$binmode;my$resolved_path=$self->_resolve_symlinks;my$temp=path($resolved_path .$$ .int(rand(2**31)));my$temp_fh=$temp->filehandle({exclusive=>1,locked=>1 },">",$binmode);my$in_fh=$self->filehandle({locked=>1 },'<',$binmode);local $_;while (<$in_fh>){$cb->();$temp_fh->print($_)}close$temp_fh or $self->_throw('close',$temp);close$in_fh or $self->_throw('close');return$temp->move($resolved_path)}sub edit_lines_raw {$_[2]={binmode=>":unix" };goto&edit_lines}sub edit_lines_utf8 {$_[2]={binmode=>":raw:encoding(UTF-8)" };goto&edit_lines}sub exists {-e $_[0]->[PATH]}sub is_file {-e $_[0]->[PATH]&&!-d _}sub is_dir {-d $_[0]->[PATH]}sub filehandle {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked exclusive/);$args->{locked}=1 if$args->{exclusive};my ($opentype,$binmode)=@args;$opentype="<" unless defined$opentype;Carp::croak("Invalid file mode '$opentype'")unless grep {$opentype eq $_}qw/< +< > +> >> +>>/;$binmode=((caller(0))[10]|| {})->{'open' .substr($opentype,-1,1)}unless defined$binmode;$binmode="" unless defined$binmode;my ($fh,$lock,$trunc);if ($HAS_FLOCK && $args->{locked}&&!$ENV{PERL_PATH_TINY_NO_FLOCK}){require Fcntl;if (grep {$opentype eq $_}qw(> +>)){my$flags=$opentype eq ">" ? Fcntl::O_WRONLY(): Fcntl::O_RDWR();$flags |= Fcntl::O_CREAT();$flags |= Fcntl::O_EXCL()if$args->{exclusive};sysopen($fh,$self->[PATH],$flags)or $self->_throw("sysopen");if ($binmode =~ s/^:unix//){binmode($fh,":raw")or $self->_throw("binmode (:raw)");while (1 < (my$layers=()=PerlIO::get_layers($fh,output=>1))){binmode($fh,":pop")or $self->_throw("binmode (:pop)")}}if (length$binmode){binmode($fh,$binmode)or $self->_throw("binmode ($binmode)")}$lock=Fcntl::LOCK_EX();$trunc=1}elsif ($^O eq 'aix' && $opentype eq "<"){if (-w $self->[PATH]){$opentype="+<";$lock=Fcntl::LOCK_EX()}}else {$lock=$opentype eq "<" ? Fcntl::LOCK_SH(): Fcntl::LOCK_EX()}}unless ($fh){my$mode=$opentype .$binmode;open$fh,$mode,$self->[PATH]or $self->_throw("open ($mode)")}do {flock($fh,$lock)or $self->_throw("flock ($lock)")}if$lock;do {truncate($fh,0)or $self->_throw("truncate")}if$trunc;return$fh}sub is_absolute {substr($_[0]->dirname,0,1)eq '/'}sub is_relative {substr($_[0]->dirname,0,1)ne '/'}sub is_rootdir {my ($self)=@_;$self->_splitpath unless defined$self->[DIR];return$self->[DIR]eq '/' && $self->[FILE]eq ''}sub iterator {my$self=shift;my$args=_get_args(shift,qw/recurse follow_symlinks/);my@dirs=$self;my$current;return sub {my$next;while (@dirs){if (ref$dirs[0]eq 'Path::Tiny'){if (!-r $dirs[0]){shift@dirs and next}$current=$dirs[0];my$dh;opendir($dh,$current->[PATH])or $self->_throw('opendir',$current->[PATH]);$dirs[0]=$dh;if (-l $current->[PATH]&&!$args->{follow_symlinks}){shift@dirs and next}}while (defined($next=readdir$dirs[0])){next if$next eq '.' || $next eq '..';my$path=$current->child($next);push@dirs,$path if$args->{recurse}&& -d $path &&!(!$args->{follow_symlinks}&& -l $path);return$path}shift@dirs}return}}sub lines {my$self=shift;my$args=_get_args(shift,qw/binmode chomp count/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open<'}unless defined$binmode;my$fh=$self->filehandle({locked=>1 },"<",$binmode);my$chomp=$args->{chomp};if ($args->{count}){my ($counter,$mod,@result)=(0,abs($args->{count}));while (my$line=<$fh>){$line =~ s/(?:\x{0d}?\x{0a}|\x{0d})$// if$chomp;$result[$counter++ ]=$line;last if$counter==$args->{count};$counter %= $mod}splice(@result,0,0,splice(@result,$counter))if@result==$mod && $counter % $mod;return@result}elsif ($chomp){return map {s/(?:\x{0d}?\x{0a}|\x{0d})$//;$_}<$fh>}else {return wantarray ? <$fh> : (my$count=()=<$fh>)}}sub lines_raw {my$self=shift;my$args=_get_args(shift,qw/binmode chomp count/);if ($args->{chomp}&&!$args->{count}){return split /\n/,slurp_raw($self)}else {$args->{binmode}=":raw";return lines($self,$args)}}my$CRLF=qr/(?:\x{0d}?\x{0a}|\x{0d})/;sub lines_utf8 {my$self=shift;my$args=_get_args(shift,qw/binmode chomp count/);if ((defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU()))&& $args->{chomp}&&!$args->{count}){my$slurp=slurp_utf8($self);$slurp =~ s/$CRLF$//;return split$CRLF,$slurp,-1}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){$args->{binmode}=":unix:utf8_strict";return lines($self,$args)}else {$args->{binmode}=":raw:encoding(UTF-8)";return lines($self,$args)}}sub mkpath {my ($self,$args)=@_;$args={}unless ref$args eq 'HASH';my$err;$args->{error}=\$err unless defined$args->{error};require File::Path;my@dirs=File::Path::make_path($self->[PATH],$args);if ($err && @$err){my ($file,$message)=%{$err->[0]};Carp::croak("mkpath failed for $file: $message")}return@dirs}sub move {my ($self,$dst)=@_;return rename($self->[PATH],$dst)|| $self->_throw('rename',$self->[PATH]."' -> '$dst")}my%opens=(opena=>">>",openr=>"<",openw=>">",openrw=>"+<");while (my ($k,$v)=each%opens){no strict 'refs';*{$k}=sub {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked/);my ($binmode)=@args;$binmode=((caller(0))[10]|| {})->{'open' .substr($v,-1,1)}unless defined$binmode;$self->filehandle($args,$v,$binmode)};*{$k ."_raw"}=sub {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked/);$self->filehandle($args,$v,":raw")};*{$k ."_utf8"}=sub {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked/);$self->filehandle($args,$v,":raw:encoding(UTF-8)")}}sub parent {my ($self,$level)=@_;$level=1 unless defined$level && $level > 0;$self->_splitpath unless defined$self->[FILE];my$parent;if (length$self->[FILE]){if ($self->[FILE]eq '.' || $self->[FILE]eq ".."){$parent=path($self->[PATH]."/..")}else {$parent=path(_non_empty($self->[VOL].$self->[DIR]))}}elsif (length$self->[DIR]){if ($self->[DIR]=~ m{(?:^\.\./|/\.\./|/\.\.$)}){$parent=path($self->[VOL].$self->[DIR]."/..")}else {(my$dir=$self->[DIR])=~ s{/[^\/]+/$}{/};$parent=path($self->[VOL].$dir)}}else {$parent=path(_non_empty($self->[VOL]))}return$level==1 ? $parent : $parent->parent($level - 1)}sub _non_empty {my ($string)=shift;return ((defined($string)&& length($string))? $string : ".")}sub realpath {my$self=shift;$self=$self->_resolve_symlinks;require Cwd;$self->_splitpath if!defined$self->[FILE];my$check_parent=length$self->[FILE]&& $self->[FILE]ne '.' && $self->[FILE]ne '..';my$realpath=eval {local$SIG{__WARN__}=sub {};Cwd::realpath($check_parent ? $self->parent->[PATH]: $self->[PATH])};$self->_throw("resolving realpath")unless defined$realpath && length$realpath && -e $realpath;return ($check_parent ? path($realpath,$self->[FILE]): path($realpath))}sub relative {my ($self,$base)=@_;$base=path(defined$base && length$base ? $base : '.');$self=$self->absolute if$self->is_relative;$base=$base->absolute if$base->is_relative;$self=$self->absolute if!length$self->volume && length$base->volume;$base=$base->absolute if length$self->volume &&!length$base->volume;if (!_same($self->volume,$base->volume)){Carp::croak("relative() can't cross volumes: '$self' vs '$base'")}return path(".")if _same($self->[PATH],$base->[PATH]);if ($base->subsumes($self)){$base="" if$base->is_rootdir;my$relative="$self";$relative =~ s{\A\Q$base/}{};return path($relative)}my (@common,@self_parts,@base_parts);@base_parts=split /\//,$base->_just_filepath;if ($self->is_rootdir){@common=("");shift@base_parts}else {@self_parts=split /\//,$self->_just_filepath;while (@self_parts && @base_parts && _same($self_parts[0],$base_parts[0])){push@common,shift@base_parts;shift@self_parts}}if (my$new_base=$self->_resolve_between(\@common,\@base_parts)){return$self->relative($new_base)}my@new_path=(("..")x (0+ @base_parts),@self_parts);return path(@new_path)}sub _just_filepath {my$self=shift;my$self_vol=$self->volume;return "$self" if!length$self_vol;(my$self_path="$self")=~ s{\A\Q$self_vol}{};return$self_path}sub _resolve_between {my ($self,$common,$base)=@_;my$path=$self->volume .join("/",@$common);my$changed=0;for my$p (@$base){$path .= "/$p";if ($p eq '..'){$changed=1;if (-e $path){$path=path($path)->realpath->[PATH]}else {$path =~ s{/[^/]+/..$}{/}}}if (-l $path){$changed=1;$path=path($path)->realpath->[PATH]}}return$changed ? path($path): undef}sub remove {my$self=shift;return 0 if!-e $self->[PATH]&&!-l $self->[PATH];return unlink($self->[PATH])|| $self->_throw('unlink')}sub remove_tree {my ($self,$args)=@_;return 0 if!-e $self->[PATH]&&!-l $self->[PATH];$args={}unless ref$args eq 'HASH';my$err;$args->{error}=\$err unless defined$args->{error};$args->{safe}=1 unless defined$args->{safe};require File::Path;my$count=File::Path::remove_tree($self->[PATH],$args);if ($err && @$err){my ($file,$message)=%{$err->[0]};Carp::croak("remove_tree failed for $file: $message")}return$count}sub sibling {my$self=shift;return path($self->parent->[PATH],@_)}sub slurp {my$self=shift;my$args=_get_args(shift,qw/binmode/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open<'}unless defined$binmode;my$fh=$self->filehandle({locked=>1 },"<",$binmode);if ((defined($binmode)? $binmode : "")eq ":unix" and my$size=-s $fh){my$buf;read$fh,$buf,$size;return$buf}else {local $/;return scalar <$fh>}}sub slurp_raw {$_[1]={binmode=>":unix" };goto&slurp}sub slurp_utf8 {if (defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU())){return Unicode::UTF8::decode_utf8(slurp($_[0],{binmode=>":unix" }))}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){$_[1]={binmode=>":unix:utf8_strict" };goto&slurp}else {$_[1]={binmode=>":raw:encoding(UTF-8)" };goto&slurp}}sub spew {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open>'}unless defined$binmode;my$resolved_path=$self->_resolve_symlinks;my$temp=path($resolved_path .$$ .int(rand(2**31)));my$fh=$temp->filehandle({exclusive=>1,locked=>1 },">",$binmode);print {$fh}map {ref eq 'ARRAY' ? @$_ : $_}@data;close$fh or $self->_throw('close',$temp->[PATH]);return$temp->move($resolved_path)}sub spew_raw {splice @_,1,0,{binmode=>":unix" };goto&spew}sub spew_utf8 {if (defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU())){my$self=shift;spew($self,{binmode=>":unix" },map {Unicode::UTF8::encode_utf8($_)}map {ref eq 'ARRAY' ? @$_ : $_}@_)}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){splice @_,1,0,{binmode=>":unix:utf8_strict" };goto&spew}else {splice @_,1,0,{binmode=>":unix:encoding(UTF-8)" };goto&spew}}sub stat {my$self=shift;require File::stat;return File::stat::stat($self->[PATH])|| $self->_throw('stat')}sub lstat {my$self=shift;require File::stat;return File::stat::lstat($self->[PATH])|| $self->_throw('lstat')}sub stringify {$_[0]->[PATH]}sub subsumes {my$self=shift;Carp::croak("subsumes() requires a defined, positive-length argument")unless defined $_[0];my$other=path(shift);if ($self->is_absolute &&!$other->is_absolute){$other=$other->absolute}elsif ($other->is_absolute &&!$self->is_absolute){$self=$self->absolute}if (length$self->volume &&!length$other->volume){$other=$other->absolute}elsif (length$other->volume &&!length$self->volume){$self=$self->absolute}if ($self->[PATH]eq '.'){return!!1}elsif ($self->is_rootdir){return$other->[PATH]=~ m{^\Q$self->[PATH]\E}}else {return$other->[PATH]=~ m{^\Q$self->[PATH]\E(?:/|$)}}}sub touch {my ($self,$epoch)=@_;if (!-e $self->[PATH]){my$fh=$self->openw;close$fh or $self->_throw('close')}if (defined$epoch){utime$epoch,$epoch,$self->[PATH]or $self->_throw("utime ($epoch)")}else {utime undef,undef,$self->[PATH]or $self->_throw("utime ()")}return$self}sub touchpath {my ($self)=@_;my$parent=$self->parent;$parent->mkpath unless$parent->exists;$self->touch}sub visit {my$self=shift;my$cb=shift;my$args=_get_args(shift,qw/recurse follow_symlinks/);Carp::croak("Callback for visit() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';my$next=$self->iterator($args);my$state={};while (my$file=$next->()){local $_=$file;my$r=$cb->($file,$state);last if ref($r)eq 'SCALAR' &&!$$r}return$state}sub volume {my ($self)=@_;$self->_splitpath unless defined$self->[VOL];return$self->[VOL]}package Path::Tiny::Error;our@CARP_NOT=qw/Path::Tiny/;use overload (q{""}=>sub {(shift)->{msg}},fallback=>1);sub throw {my ($class,$op,$file,$err)=@_;chomp(my$trace=Carp::shortmess);my$msg="Error $op on '$file': $err$trace\n";die bless {op=>$op,file=>$file,err=>$err,msg=>$msg },$class}1; + use 5.008001;use strict;use warnings;package Path::Tiny;our$VERSION='0.110';use Config;use Exporter 5.57 (qw/import/);use File::Spec 0.86 ();use Carp ();our@EXPORT=qw/path/;our@EXPORT_OK=qw/cwd rootdir tempfile tempdir/;use constant {PATH=>0,CANON=>1,VOL=>2,DIR=>3,FILE=>4,TEMP=>5,IS_WIN32=>($^O eq 'MSWin32'),};use overload (q{""}=>sub {$_[0]->[PATH]},bool=>sub () {1},fallback=>1,);sub FREEZE {return $_[0]->[PATH]}sub THAW {return path($_[2])}{no warnings 'once';*TO_JSON=*FREEZE};my$HAS_UU;sub _check_UU {local$SIG{__DIE__};!!eval {require Unicode::UTF8;Unicode::UTF8->VERSION(0.58);1}}my$HAS_PU;sub _check_PU {local$SIG{__DIE__};!!eval {require Encode;require PerlIO::utf8_strict;PerlIO::utf8_strict->VERSION(0.003);1}}my$HAS_FLOCK=$Config{d_flock}|| $Config{d_fcntl_can_lock}|| $Config{d_lockf};my$SLASH=qr{[\\/]};my$NOTSLASH=qr{[^\\/]};my$DRV_VOL=qr{[a-z]:}i;my$UNC_VOL=qr{$SLASH $SLASH $NOTSLASH+ $SLASH $NOTSLASH+}x;my$WIN32_ROOT=qr{(?: $UNC_VOL $SLASH | $DRV_VOL $SLASH | $SLASH )}x;sub _win32_vol {my ($path,$drv)=@_;require Cwd;my$dcwd=eval {Cwd::getdcwd($drv)};$dcwd="$drv" unless defined$dcwd && length$dcwd;$dcwd =~ s{$SLASH?$}{/};$path =~ s{^$DRV_VOL}{$dcwd};return$path}sub _is_root {return IS_WIN32()? ($_[0]=~ /^$WIN32_ROOT$/): ($_[0]eq '/')}BEGIN {*_same=IS_WIN32()? sub {lc($_[0])eq lc($_[1])}: sub {$_[0]eq $_[1]}}my%MODEBITS=(om=>0007,gm=>0070,um=>0700);{my$m=0;$MODEBITS{$_}=(1 << $m++)for qw/ox ow or gx gw gr ux uw ur/};sub _symbolic_chmod {my ($mode,$symbolic)=@_;for my$clause (split /,\s*/,$symbolic){if ($clause =~ m{\A([augo]+)([=+-])([rwx]+)\z}){my ($who,$action,$perms)=($1,$2,$3);$who =~ s/a/ugo/g;for my$w (split //,$who){my$p=0;$p |= $MODEBITS{"$w$_"}for split //,$perms;if ($action eq '='){$mode=($mode & ~$MODEBITS{"${w}m"})| $p}else {$mode=$action eq "+" ? ($mode | $p): ($mode & ~$p)}}}else {Carp::croak("Invalid mode clause '$clause' for chmod()")}}return$mode}{package flock;use warnings::register}my$WARNED_NO_FLOCK=0;sub _throw {my ($self,$function,$file,$msg)=@_;if ($function =~ /^flock/ && $! =~ /operation not supported|function not implemented/i &&!warnings::fatal_enabled('flock')){if (!$WARNED_NO_FLOCK){warnings::warn(flock=>"Flock not available: '$!': continuing in unsafe mode");$WARNED_NO_FLOCK++}}else {$msg=$! unless defined$msg;Path::Tiny::Error->throw($function,(defined$file ? $file : $self->[PATH]),$msg)}return}sub _get_args {my ($raw,@valid)=@_;if (defined($raw)&& ref($raw)ne 'HASH'){my (undef,undef,undef,$called_as)=caller(1);$called_as =~ s{^.*::}{};Carp::croak("Options for $called_as must be a hash reference")}my$cooked={};for my$k (@valid){$cooked->{$k}=delete$raw->{$k}if exists$raw->{$k}}if (keys %$raw){my (undef,undef,undef,$called_as)=caller(1);$called_as =~ s{^.*::}{};Carp::croak("Invalid option(s) for $called_as: " .join(", ",keys %$raw))}return$cooked}sub path {my$path=shift;Carp::croak("Path::Tiny paths require defined, positive-length parts")unless 1 + @_==grep {defined && length}$path,@_;if (!@_ && ref($path)eq __PACKAGE__ &&!$path->[TEMP]){return$path}$path="$path";if (IS_WIN32()){$path=_win32_vol($path,$1)if$path =~ m{^($DRV_VOL)(?:$NOTSLASH|$)};$path .= "/" if$path =~ m{^$UNC_VOL$}}if (@_){$path .= (_is_root($path)? "" : "/").join("/",@_)}my$cpath=$path=File::Spec->canonpath($path);$path =~ tr[\\][/] if IS_WIN32();$path="/" if$path eq '/..';$path .= "/" if IS_WIN32()&& $path =~ m{^$UNC_VOL$};if (_is_root($path)){$path =~ s{/?$}{/}}else {$path =~ s{/$}{}}if ($path =~ m{^(~[^/]*).*}){require File::Glob;my ($homedir)=File::Glob::bsd_glob($1);$homedir =~ tr[\\][/] if IS_WIN32();$path =~ s{^(~[^/]*)}{$homedir}}bless [$path,$cpath ],__PACKAGE__}sub new {shift;path(@_)}sub cwd {require Cwd;return path(Cwd::getcwd())}sub rootdir {path(File::Spec->rootdir)}sub tempfile {shift if @_ && $_[0]eq 'Path::Tiny';my$opts=(@_ && ref $_[0]eq 'HASH')? shift @_ : {};$opts=_get_args($opts,qw/realpath/);my ($maybe_template,$args)=_parse_file_temp_args(@_);$args->{TEMPLATE}=$maybe_template->[0]if @$maybe_template;require File::Temp;my$temp=File::Temp->new(TMPDIR=>1,%$args);close$temp;my$self=$opts->{realpath}? path($temp)->realpath : path($temp)->absolute;$self->[TEMP]=$temp;return$self}sub tempdir {shift if @_ && $_[0]eq 'Path::Tiny';my$opts=(@_ && ref $_[0]eq 'HASH')? shift @_ : {};$opts=_get_args($opts,qw/realpath/);my ($maybe_template,$args)=_parse_file_temp_args(@_);require File::Temp;my$temp=File::Temp->newdir(@$maybe_template,TMPDIR=>1,%$args);my$self=$opts->{realpath}? path($temp)->realpath : path($temp)->absolute;$self->[TEMP]=$temp;$temp->{REALNAME}=$self->[CANON]if IS_WIN32;return$self}sub _parse_file_temp_args {my$leading_template=(scalar(@_)% 2==1 ? shift(@_): '');my%args=@_;%args=map {uc($_),$args{$_}}keys%args;my@template=(exists$args{TEMPLATE}? delete$args{TEMPLATE}: $leading_template ? $leading_template : ());return (\@template,\%args)}sub _splitpath {my ($self)=@_;@{$self}[VOL,DIR,FILE ]=File::Spec->splitpath($self->[PATH])}sub _resolve_symlinks {my ($self)=@_;my$new=$self;my ($count,%seen)=0;while (-l $new->[PATH]){if ($seen{$new->[PATH]}++){$self->_throw('readlink',$self->[PATH],"symlink loop detected")}if (++$count > 100){$self->_throw('readlink',$self->[PATH],"maximum symlink depth exceeded")}my$resolved=readlink$new->[PATH]or $new->_throw('readlink',$new->[PATH]);$resolved=path($resolved);$new=$resolved->is_absolute ? $resolved : $new->sibling($resolved)}return$new}sub absolute {my ($self,$base)=@_;if (IS_WIN32){return$self if length$self->volume;if ($self->is_absolute){require Cwd;my ($drv)=Win32::GetCwd()=~ /^($DRV_VOL | $UNC_VOL)/x;return path($drv .$self->[PATH])}}else {return$self if$self->is_absolute}require Cwd;return path(Cwd::getcwd(),$_[0]->[PATH])unless defined$base;$base=path($base);return path(($base->is_absolute ? $base : $base->absolute),$_[0]->[PATH])}sub append {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode truncate/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open>'}unless defined$binmode;my$mode=$args->{truncate}? ">" : ">>";my$fh=$self->filehandle({locked=>1 },$mode,$binmode);print {$fh}map {ref eq 'ARRAY' ? @$_ : $_}@data;close$fh or $self->_throw('close')}sub append_raw {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode truncate/);$args->{binmode}=':unix';append($self,$args,@data)}sub append_utf8 {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode truncate/);if (defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU())){$args->{binmode}=":unix";append($self,$args,map {Unicode::UTF8::encode_utf8($_)}@data)}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){$args->{binmode}=":unix:utf8_strict";append($self,$args,@data)}else {$args->{binmode}=":unix:encoding(UTF-8)";append($self,$args,@data)}}sub assert {my ($self,$assertion)=@_;return$self unless$assertion;if (ref$assertion eq 'CODE'){local $_=$self;$assertion->()or Path::Tiny::Error->throw("assert",$self->[PATH],"failed assertion")}else {Carp::croak("argument to assert must be a code reference argument")}return$self}sub basename {my ($self,@suffixes)=@_;$self->_splitpath unless defined$self->[FILE];my$file=$self->[FILE];for my$s (@suffixes){my$re=ref($s)eq 'Regexp' ? qr/$s$/ : qr/\Q$s\E$/;last if$file =~ s/$re//}return$file}sub canonpath {$_[0]->[CANON]}sub cached_temp {my$self=shift;$self->_throw("cached_temp",$self,"has no cached File::Temp object")unless defined$self->[TEMP];return$self->[TEMP]}sub child {my ($self,@parts)=@_;return path($self->[PATH],@parts)}sub children {my ($self,$filter)=@_;my$dh;opendir$dh,$self->[PATH]or $self->_throw('opendir');my@children=readdir$dh;closedir$dh or $self->_throw('closedir');if (not defined$filter){@children=grep {$_ ne '.' && $_ ne '..'}@children}elsif ($filter && ref($filter)eq 'Regexp'){@children=grep {$_ ne '.' && $_ ne '..' && $_ =~ $filter}@children}else {Carp::croak("Invalid argument '$filter' for children()")}return map {path($self->[PATH],$_)}@children}sub chmod {my ($self,$new_mode)=@_;my$mode;if ($new_mode =~ /\d/){$mode=($new_mode =~ /^0/ ? oct($new_mode): $new_mode)}elsif ($new_mode =~ /[=+-]/){$mode=_symbolic_chmod($self->stat->mode & 07777,$new_mode)}else {Carp::croak("Invalid mode argument '$new_mode' for chmod()")}CORE::chmod($mode,$self->[PATH])or $self->_throw("chmod");return 1}sub copy {my ($self,$dest)=@_;require File::Copy;File::Copy::copy($self->[PATH],$dest)or Carp::croak("copy failed for $self to $dest: $!");return -d $dest ? path($dest,$self->basename): path($dest)}sub digest {my ($self,@opts)=@_;my$args=(@opts && ref$opts[0]eq 'HASH')? shift@opts : {};$args=_get_args($args,qw/chunk_size/);unshift@opts,'SHA-256' unless@opts;require Digest;my$digest=Digest->new(@opts);if ($args->{chunk_size}){my$fh=$self->filehandle({locked=>1 },"<",":unix");my$buf;$digest->add($buf)while read$fh,$buf,$args->{chunk_size}}else {$digest->add($self->slurp_raw)}return$digest->hexdigest}sub dirname {my ($self)=@_;$self->_splitpath unless defined$self->[DIR];return length$self->[DIR]? $self->[DIR]: "."}sub edit {my$self=shift;my$cb=shift;my$args=_get_args(shift,qw/binmode/);Carp::croak("Callback for edit() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';local $_=$self->slurp(exists($args->{binmode})? {binmode=>$args->{binmode}}: ());$cb->();$self->spew($args,$_);return}sub edit_utf8 {my ($self,$cb)=@_;Carp::croak("Callback for edit_utf8() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';local $_=$self->slurp_utf8;$cb->();$self->spew_utf8($_);return}sub edit_raw {$_[2]={binmode=>":unix" };goto&edit}sub edit_lines {my$self=shift;my$cb=shift;my$args=_get_args(shift,qw/binmode/);Carp::croak("Callback for edit_lines() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open>'}unless defined$binmode;my$resolved_path=$self->_resolve_symlinks;my$temp=path($resolved_path .$$ .int(rand(2**31)));my$temp_fh=$temp->filehandle({exclusive=>1,locked=>1 },">",$binmode);my$in_fh=$self->filehandle({locked=>1 },'<',$binmode);local $_;while (<$in_fh>){$cb->();$temp_fh->print($_)}close$temp_fh or $self->_throw('close',$temp);close$in_fh or $self->_throw('close');return$temp->move($resolved_path)}sub edit_lines_raw {$_[2]={binmode=>":unix" };goto&edit_lines}sub edit_lines_utf8 {$_[2]={binmode=>":raw:encoding(UTF-8)" };goto&edit_lines}sub exists {-e $_[0]->[PATH]}sub is_file {-e $_[0]->[PATH]&&!-d _}sub is_dir {-d $_[0]->[PATH]}sub filehandle {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked exclusive/);$args->{locked}=1 if$args->{exclusive};my ($opentype,$binmode)=@args;$opentype="<" unless defined$opentype;Carp::croak("Invalid file mode '$opentype'")unless grep {$opentype eq $_}qw/< +< > +> >> +>>/;$binmode=((caller(0))[10]|| {})->{'open' .substr($opentype,-1,1)}unless defined$binmode;$binmode="" unless defined$binmode;my ($fh,$lock,$trunc);if ($HAS_FLOCK && $args->{locked}&&!$ENV{PERL_PATH_TINY_NO_FLOCK}){require Fcntl;if (grep {$opentype eq $_}qw(> +>)){my$flags=$opentype eq ">" ? Fcntl::O_WRONLY(): Fcntl::O_RDWR();$flags |= Fcntl::O_CREAT();$flags |= Fcntl::O_EXCL()if$args->{exclusive};sysopen($fh,$self->[PATH],$flags)or $self->_throw("sysopen");if ($binmode =~ s/^:unix//){binmode($fh,":raw")or $self->_throw("binmode (:raw)");while (1 < (my$layers=()=PerlIO::get_layers($fh,output=>1))){binmode($fh,":pop")or $self->_throw("binmode (:pop)")}}if (length$binmode){binmode($fh,$binmode)or $self->_throw("binmode ($binmode)")}$lock=Fcntl::LOCK_EX();$trunc=1}elsif ($^O eq 'aix' && $opentype eq "<"){if (-w $self->[PATH]){$opentype="+<";$lock=Fcntl::LOCK_EX()}}else {$lock=$opentype eq "<" ? Fcntl::LOCK_SH(): Fcntl::LOCK_EX()}}unless ($fh){my$mode=$opentype .$binmode;open$fh,$mode,$self->[PATH]or $self->_throw("open ($mode)")}do {flock($fh,$lock)or $self->_throw("flock ($lock)")}if$lock;do {truncate($fh,0)or $self->_throw("truncate")}if$trunc;return$fh}sub is_absolute {substr($_[0]->dirname,0,1)eq '/'}sub is_relative {substr($_[0]->dirname,0,1)ne '/'}sub is_rootdir {my ($self)=@_;$self->_splitpath unless defined$self->[DIR];return$self->[DIR]eq '/' && $self->[FILE]eq ''}sub iterator {my$self=shift;my$args=_get_args(shift,qw/recurse follow_symlinks/);my@dirs=$self;my$current;return sub {my$next;while (@dirs){if (ref$dirs[0]eq 'Path::Tiny'){if (!-r $dirs[0]){shift@dirs and next}$current=$dirs[0];my$dh;opendir($dh,$current->[PATH])or $self->_throw('opendir',$current->[PATH]);$dirs[0]=$dh;if (-l $current->[PATH]&&!$args->{follow_symlinks}){shift@dirs and next}}while (defined($next=readdir$dirs[0])){next if$next eq '.' || $next eq '..';my$path=$current->child($next);push@dirs,$path if$args->{recurse}&& -d $path &&!(!$args->{follow_symlinks}&& -l $path);return$path}shift@dirs}return}}sub lines {my$self=shift;my$args=_get_args(shift,qw/binmode chomp count/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open<'}unless defined$binmode;my$fh=$self->filehandle({locked=>1 },"<",$binmode);my$chomp=$args->{chomp};if ($args->{count}){my ($counter,$mod,@result)=(0,abs($args->{count}));while (my$line=<$fh>){$line =~ s/(?:\x{0d}?\x{0a}|\x{0d})$// if$chomp;$result[$counter++ ]=$line;last if$counter==$args->{count};$counter %= $mod}splice(@result,0,0,splice(@result,$counter))if@result==$mod && $counter % $mod;return@result}elsif ($chomp){return map {s/(?:\x{0d}?\x{0a}|\x{0d})$//;$_}<$fh>}else {return wantarray ? <$fh> : (my$count=()=<$fh>)}}sub lines_raw {my$self=shift;my$args=_get_args(shift,qw/binmode chomp count/);if ($args->{chomp}&&!$args->{count}){return split /\n/,slurp_raw($self)}else {$args->{binmode}=":raw";return lines($self,$args)}}my$CRLF=qr/(?:\x{0d}?\x{0a}|\x{0d})/;sub lines_utf8 {my$self=shift;my$args=_get_args(shift,qw/binmode chomp count/);if ((defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU()))&& $args->{chomp}&&!$args->{count}){my$slurp=slurp_utf8($self);$slurp =~ s/$CRLF$//;return split$CRLF,$slurp,-1}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){$args->{binmode}=":unix:utf8_strict";return lines($self,$args)}else {$args->{binmode}=":raw:encoding(UTF-8)";return lines($self,$args)}}sub mkpath {my ($self,$args)=@_;$args={}unless ref$args eq 'HASH';my$err;$args->{error}=\$err unless defined$args->{error};require File::Path;my@dirs=File::Path::make_path($self->[PATH],$args);if ($err && @$err){my ($file,$message)=%{$err->[0]};Carp::croak("mkpath failed for $file: $message")}return@dirs}sub move {my ($self,$dst)=@_;return rename($self->[PATH],$dst)|| $self->_throw('rename',$self->[PATH]."' -> '$dst")}my%opens=(opena=>">>",openr=>"<",openw=>">",openrw=>"+<");while (my ($k,$v)=each%opens){no strict 'refs';*{$k}=sub {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked/);my ($binmode)=@args;$binmode=((caller(0))[10]|| {})->{'open' .substr($v,-1,1)}unless defined$binmode;$self->filehandle($args,$v,$binmode)};*{$k ."_raw"}=sub {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked/);$self->filehandle($args,$v,":raw")};*{$k ."_utf8"}=sub {my ($self,@args)=@_;my$args=(@args && ref$args[0]eq 'HASH')? shift@args : {};$args=_get_args($args,qw/locked/);$self->filehandle($args,$v,":raw:encoding(UTF-8)")}}sub parent {my ($self,$level)=@_;$level=1 unless defined$level && $level > 0;$self->_splitpath unless defined$self->[FILE];my$parent;if (length$self->[FILE]){if ($self->[FILE]eq '.' || $self->[FILE]eq ".."){$parent=path($self->[PATH]."/..")}else {$parent=path(_non_empty($self->[VOL].$self->[DIR]))}}elsif (length$self->[DIR]){if ($self->[DIR]=~ m{(?:^\.\./|/\.\./|/\.\.$)}){$parent=path($self->[VOL].$self->[DIR]."/..")}else {(my$dir=$self->[DIR])=~ s{/[^\/]+/$}{/};$parent=path($self->[VOL].$dir)}}else {$parent=path(_non_empty($self->[VOL]))}return$level==1 ? $parent : $parent->parent($level - 1)}sub _non_empty {my ($string)=shift;return ((defined($string)&& length($string))? $string : ".")}sub realpath {my$self=shift;$self=$self->_resolve_symlinks;require Cwd;$self->_splitpath if!defined$self->[FILE];my$check_parent=length$self->[FILE]&& $self->[FILE]ne '.' && $self->[FILE]ne '..';my$realpath=eval {local$SIG{__WARN__}=sub {};Cwd::realpath($check_parent ? $self->parent->[PATH]: $self->[PATH])};$self->_throw("resolving realpath")unless defined$realpath && length$realpath && -e $realpath;return ($check_parent ? path($realpath,$self->[FILE]): path($realpath))}sub relative {my ($self,$base)=@_;$base=path(defined$base && length$base ? $base : '.');$self=$self->absolute if$self->is_relative;$base=$base->absolute if$base->is_relative;$self=$self->absolute if!length$self->volume && length$base->volume;$base=$base->absolute if length$self->volume &&!length$base->volume;if (!_same($self->volume,$base->volume)){Carp::croak("relative() can't cross volumes: '$self' vs '$base'")}return path(".")if _same($self->[PATH],$base->[PATH]);if ($base->subsumes($self)){$base="" if$base->is_rootdir;my$relative="$self";$relative =~ s{\A\Q$base/}{};return path($relative)}my (@common,@self_parts,@base_parts);@base_parts=split /\//,$base->_just_filepath;if ($self->is_rootdir){@common=("");shift@base_parts}else {@self_parts=split /\//,$self->_just_filepath;while (@self_parts && @base_parts && _same($self_parts[0],$base_parts[0])){push@common,shift@base_parts;shift@self_parts}}if (my$new_base=$self->_resolve_between(\@common,\@base_parts)){return$self->relative($new_base)}my@new_path=(("..")x (0+ @base_parts),@self_parts);return path(@new_path)}sub _just_filepath {my$self=shift;my$self_vol=$self->volume;return "$self" if!length$self_vol;(my$self_path="$self")=~ s{\A\Q$self_vol}{};return$self_path}sub _resolve_between {my ($self,$common,$base)=@_;my$path=$self->volume .join("/",@$common);my$changed=0;for my$p (@$base){$path .= "/$p";if ($p eq '..'){$changed=1;if (-e $path){$path=path($path)->realpath->[PATH]}else {$path =~ s{/[^/]+/..$}{/}}}if (-l $path){$changed=1;$path=path($path)->realpath->[PATH]}}return$changed ? path($path): undef}sub remove {my$self=shift;return 0 if!-e $self->[PATH]&&!-l $self->[PATH];return unlink($self->[PATH])|| $self->_throw('unlink')}sub remove_tree {my ($self,$args)=@_;return 0 if!-e $self->[PATH]&&!-l $self->[PATH];$args={}unless ref$args eq 'HASH';my$err;$args->{error}=\$err unless defined$args->{error};$args->{safe}=1 unless defined$args->{safe};require File::Path;my$count=File::Path::remove_tree($self->[PATH],$args);if ($err && @$err){my ($file,$message)=%{$err->[0]};Carp::croak("remove_tree failed for $file: $message")}return$count}sub sibling {my$self=shift;return path($self->parent->[PATH],@_)}sub slurp {my$self=shift;my$args=_get_args(shift,qw/binmode/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open<'}unless defined$binmode;my$fh=$self->filehandle({locked=>1 },"<",$binmode);if ((defined($binmode)? $binmode : "")eq ":unix" and my$size=-s $fh){my$buf;read$fh,$buf,$size;return$buf}else {local $/;return scalar <$fh>}}sub slurp_raw {$_[1]={binmode=>":unix" };goto&slurp}sub slurp_utf8 {if (defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU())){return Unicode::UTF8::decode_utf8(slurp($_[0],{binmode=>":unix" }))}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){$_[1]={binmode=>":unix:utf8_strict" };goto&slurp}else {$_[1]={binmode=>":raw:encoding(UTF-8)" };goto&slurp}}sub spew {my ($self,@data)=@_;my$args=(@data && ref$data[0]eq 'HASH')? shift@data : {};$args=_get_args($args,qw/binmode/);my$binmode=$args->{binmode};$binmode=((caller(0))[10]|| {})->{'open>'}unless defined$binmode;my$resolved_path=$self->_resolve_symlinks;my$temp=path($resolved_path .$$ .int(rand(2**31)));my$fh=$temp->filehandle({exclusive=>1,locked=>1 },">",$binmode);print {$fh}map {ref eq 'ARRAY' ? @$_ : $_}@data;close$fh or $self->_throw('close',$temp->[PATH]);return$temp->move($resolved_path)}sub spew_raw {splice @_,1,0,{binmode=>":unix" };goto&spew}sub spew_utf8 {if (defined($HAS_UU)? $HAS_UU : ($HAS_UU=_check_UU())){my$self=shift;spew($self,{binmode=>":unix" },map {Unicode::UTF8::encode_utf8($_)}map {ref eq 'ARRAY' ? @$_ : $_}@_)}elsif (defined($HAS_PU)? $HAS_PU : ($HAS_PU=_check_PU())){splice @_,1,0,{binmode=>":unix:utf8_strict" };goto&spew}else {splice @_,1,0,{binmode=>":unix:encoding(UTF-8)" };goto&spew}}sub stat {my$self=shift;require File::stat;return File::stat::stat($self->[PATH])|| $self->_throw('stat')}sub lstat {my$self=shift;require File::stat;return File::stat::lstat($self->[PATH])|| $self->_throw('lstat')}sub stringify {$_[0]->[PATH]}sub subsumes {my$self=shift;Carp::croak("subsumes() requires a defined, positive-length argument")unless defined $_[0];my$other=path(shift);if ($self->is_absolute &&!$other->is_absolute){$other=$other->absolute}elsif ($other->is_absolute &&!$self->is_absolute){$self=$self->absolute}if (length$self->volume &&!length$other->volume){$other=$other->absolute}elsif (length$other->volume &&!length$self->volume){$self=$self->absolute}if ($self->[PATH]eq '.'){return!!1}elsif ($self->is_rootdir){return$other->[PATH]=~ m{^\Q$self->[PATH]\E}}else {return$other->[PATH]=~ m{^\Q$self->[PATH]\E(?:/|$)}}}sub touch {my ($self,$epoch)=@_;if (!-e $self->[PATH]){my$fh=$self->openw;close$fh or $self->_throw('close')}if (defined$epoch){utime$epoch,$epoch,$self->[PATH]or $self->_throw("utime ($epoch)")}else {utime undef,undef,$self->[PATH]or $self->_throw("utime ()")}return$self}sub touchpath {my ($self)=@_;my$parent=$self->parent;$parent->mkpath unless$parent->exists;$self->touch}sub visit {my$self=shift;my$cb=shift;my$args=_get_args(shift,qw/recurse follow_symlinks/);Carp::croak("Callback for visit() must be a code reference")unless defined($cb)&& ref($cb)eq 'CODE';my$next=$self->iterator($args);my$state={};while (my$file=$next->()){local $_=$file;my$r=$cb->($file,$state);last if ref($r)eq 'SCALAR' &&!$$r}return$state}sub volume {my ($self)=@_;$self->_splitpath unless defined$self->[VOL];return$self->[VOL]}package Path::Tiny::Error;our@CARP_NOT=qw/Path::Tiny/;use overload (q{""}=>sub {(shift)->{msg}},fallback=>1);sub throw {my ($class,$op,$file,$err)=@_;chomp(my$trace=Carp::shortmess);my$msg="Error $op on '$file': $err$trace\n";die bless {op=>$op,file=>$file,err=>$err,msg=>$msg },$class}1; PATH_TINY $fatpacked{"Proc/Find/Parents.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'PROC_FIND_PARENTS'; @@ -257,15 +257,15 @@ $fatpacked{"Text/CSV_PP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TEX TEXT_CSV_PP $fatpacked{"Text/Gitignore.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TEXT_GITIGNORE'; - package Text::Gitignore;use strict;use warnings;use base 'Exporter';our@EXPORT_OK=qw(match_gitignore build_gitignore_matcher);our$VERSION="0.02";sub match_gitignore {my ($patterns,@paths)=@_;my$matcher=build_gitignore_matcher($patterns);my@matched;for my$path (@paths){push@matched,$path if$matcher->($path)}return@matched}sub build_gitignore_matcher {my ($patterns)=@_;$patterns=[$patterns]unless ref$patterns eq 'ARRAY';$patterns=[grep {!/^#/}@$patterns ];for my$pattern (@$patterns){$pattern =~ s{(?!\\)\s+$}{};$pattern =~ s{^\\#}{#}}$patterns=[grep {length $_}@$patterns ];my$build_pattern=sub {my ($pattern)=@_;$pattern=quotemeta$pattern;$pattern =~ s{\\\*\\\*\\/}{.*}g;$pattern =~ s{\\\*\\\*}{.*}g;$pattern =~ s{\\\*}{[^/]*}g;$pattern =~ s{\\\?}{[^/]}g;$pattern =~ s{^\\\/}{^};$pattern =~ s{\\\[(.*?)\\\]}{ + package Text::Gitignore;use strict;use warnings;use base 'Exporter';our@EXPORT_OK=qw(match_gitignore build_gitignore_matcher);our$VERSION="0.03";sub match_gitignore {my ($patterns,@paths)=@_;my$matcher=build_gitignore_matcher($patterns);my@matched;for my$path (@paths){push@matched,$path if$matcher->($path)}return@matched}sub build_gitignore_matcher {my ($patterns)=@_;$patterns=[$patterns]unless ref$patterns eq 'ARRAY';$patterns=[grep {!/^#/}@$patterns ];for my$pattern (@$patterns){$pattern =~ s{(?!\\)\s+$}{};$pattern =~ s{^\\#}{#}}$patterns=[grep {length $_}@$patterns ];my$build_pattern=sub {my ($pattern)=@_;$pattern=quotemeta$pattern;$pattern =~ s{\\\*\\\*\\/}{.*}g;$pattern =~ s{\\\*\\\*}{.*}g;$pattern =~ s{\\\*}{[^/]*}g;$pattern =~ s{\\\?}{[^/]}g;$pattern =~ s{^\\\/}{^};$pattern =~ s{\\\[(.*?)\\\]}{ '[' . do { my $c = $1; $c =~ s{^\\!}{} ? '^' : '' } . do { my $c = $1; $c =~ s/\\\-/\-/; $c } . ']' - }eg;$pattern .= '$' unless$pattern =~ m{\/$};return$pattern};my@patterns_re;for my$pattern (@$patterns){if ($pattern =~ m/^!/){my$re=$build_pattern->(substr$pattern,1);push@patterns_re,{re=>$re,negative=>1 }}else {$pattern =~ s{^\\!}{!};push@patterns_re,{re=>$build_pattern->($pattern)}}}my@negatives=grep {/^!/}@$patterns;return sub {my$path=shift;my$match=0;for my$pattern (@patterns_re){my$re=$pattern->{re};next if$match &&!$pattern->{negative};if ($pattern->{negative}){if ($path =~ m/$re/){$match=0}}else {$match=!!($path =~ m/$re/);if ($match &&!@negatives){return$match}}}return$match}}1; + }eg;$pattern .= '(\/|$)' unless$pattern =~ m{\/$};return$pattern};my@patterns_re;for my$pattern (@$patterns){if ($pattern =~ m/^!/){my$re=$build_pattern->(substr$pattern,1);push@patterns_re,{re=>$re,negative=>1 }}else {$pattern =~ s{^\\!}{!};push@patterns_re,{re=>$build_pattern->($pattern)}}}my@negatives=grep {/^!/}@$patterns;return sub {my$path=shift;my$match=0;for my$pattern (@patterns_re){my$re=$pattern->{re};next if$match &&!$pattern->{negative};if ($pattern->{negative}){if ($path =~ m/$re/){$match=0}}else {$match=!!($path =~ m/$re/);if ($match &&!@negatives){return$match}}}return$match}}1; TEXT_GITIGNORE $fatpacked{"Text/Table/Any.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TEXT_TABLE_ANY'; - package Text::Table::Any;our$DATE='2019-02-17';our$VERSION='0.095';our@BACKENDS=qw(Text::Table::Tiny Text::Table::TinyColor Text::Table::TinyColorWide Text::Table::TinyWide Text::Table::Org Text::Table::CSV Text::Table::TSV Text::Table::LTSV Text::Table::ASV Text::Table::HTML Text::Table::HTML::DataTables Text::Table::Paragraph Text::ANSITable Text::ASCIITable Text::FormatTable Text::MarkdownTable Text::Table Text::TabularDisplay Text::Table::XLSX Term::TablePrint);sub _encode {my$val=shift;$val =~ s/([\\"])/\\$1/g;"\"$val\""}sub backends {@BACKENDS}sub table {my%params=@_;my$rows=$params{rows}or die "Must provide rows!";my$backend=$params{backend}|| 'Text::Table::Tiny';my$header_row=$params{header_row}// 1;if ($backend eq 'Text::Table::Tiny'){require Text::Table::Tiny;return Text::Table::Tiny::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::TinyColor'){require Text::Table::TinyColor;return Text::Table::TinyColor::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::TinyColorWide'){require Text::Table::TinyColorWide;return Text::Table::TinyColorWide::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::TinyWide'){require Text::Table::TinyWide;return Text::Table::TinyWide::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::Org'){require Text::Table::Org;return Text::Table::Org::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::CSV'){require Text::Table::CSV;return Text::Table::CSV::table(rows=>$rows)}elsif ($backend eq 'Text::Table::TSV'){require Text::Table::TSV;return Text::Table::TSV::table(rows=>$rows)}elsif ($backend eq 'Text::Table::LTSV'){require Text::Table::LTSV;return Text::Table::LTSV::table(rows=>$rows)}elsif ($backend eq 'Text::Table::ASV'){require Text::Table::ASV;return Text::Table::ASV::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::HTML'){require Text::Table::HTML;return Text::Table::HTML::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::HTML::DataTables'){require Text::Table::HTML::DataTables;return Text::Table::HTML::DataTables::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::Paragraph'){require Text::Table::Paragraph;return Text::Table::Paragraph::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::ANSITable'){require Text::ANSITable;my$t=Text::ANSITable->new(use_utf8=>0,use_box_chars=>0,use_color=>0,border_style=>'Default::single_ascii',);if ($header_row){$t->columns($rows->[0]);$t->add_row($rows->[$_])for 1..@$rows-1}else {$t->columns([map {"col$_"}0..$#{$rows->[0]}]);$t->add_row($_)for @$rows}return$t->draw}elsif ($backend eq 'Text::ASCIITable'){require Text::ASCIITable;my$t=Text::ASCIITable->new();if ($header_row){$t->setCols(@{$rows->[0]});$t->addRow(@{$rows->[$_]})for 1..@$rows-1}else {$t->setCols(map {"col$_"}0..$#{$rows->[0]});$t->addRow(@$_)for @$rows}return "$t"}elsif ($backend eq 'Text::FormatTable'){require Text::FormatTable;my$t=Text::FormatTable->new(join('|',('l')x @{$rows->[0]}));$t->head(@{$rows->[0]});$t->row(@{$rows->[$_]})for 1..@$rows-1;return$t->render}elsif ($backend eq 'Text::MarkdownTable'){require Text::MarkdownTable;my$out="";my$fields=$header_row ? $rows->[0]: [map {"col$_"}0..$#{$rows->[0]}];my$t=Text::MarkdownTable->new(file=>\$out,columns=>$fields);for (($header_row ? 1:0).. $#{$rows}){my$row=$rows->[$_];$t->add({map {$fields->[$_]=>$row->[$_]}0..@$fields-1 })}$t->done;return$out}elsif ($backend eq 'Text::Table'){require Text::Table;my$t=Text::Table->new(@{$rows->[0]});$t->load(@{$rows}[1..@$rows-1]);return$t}elsif ($backend eq 'Text::TabularDisplay'){require Text::TabularDisplay;my$t=Text::TabularDisplay->new(@{$rows->[0]});$t->add(@{$rows->[$_]})for 1..@$rows-1;return$t->render ."\n"}elsif ($backend eq 'Text::Table::XLSX'){require Text::Table::XLSX;return Text::Table::XLSX::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Term::TablePrint'){require Term::TablePrint;my$rows2;if ($header_row){$rows2=$rows}else {$rows2=[@$rows];shift @$rows2}return Term::TablePrint::print_table($rows)}else {die "Unknown backend '$backend'"}}1; + package Text::Table::Any;our$DATE='2019-11-29';our$VERSION='0.096';our@BACKENDS=qw(Text::Table::Tiny Text::Table::TinyColor Text::Table::TinyColorWide Text::Table::TinyWide Text::Table::Org Text::Table::CSV Text::Table::TSV Text::Table::LTSV Text::Table::ASV Text::Table::HTML Text::Table::HTML::DataTables Text::Table::Paragraph Text::ANSITable Text::ASCIITable Text::FormatTable Text::MarkdownTable Text::Table Text::TabularDisplay Text::Table::XLSX Term::TablePrint);sub _encode {my$val=shift;$val =~ s/([\\"])/\\$1/g;"\"$val\""}sub backends {@BACKENDS}sub table {my%params=@_;my$rows=$params{rows}or die "Must provide rows!";my$backend=$params{backend}|| 'Text::Table::Tiny';my$header_row=$params{header_row}// 1;if ($backend eq 'Text::Table::Tiny'){require Text::Table::Tiny;return Text::Table::Tiny::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::TinyColor'){require Text::Table::TinyColor;return Text::Table::TinyColor::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::TinyColorWide'){require Text::Table::TinyColorWide;return Text::Table::TinyColorWide::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::TinyWide'){require Text::Table::TinyWide;return Text::Table::TinyWide::table(rows=>$rows,header_row=>$header_row)."\n"}elsif ($backend eq 'Text::Table::Org'){require Text::Table::Org;return Text::Table::Org::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::CSV'){require Text::Table::CSV;return Text::Table::CSV::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::TSV'){require Text::Table::TSV;return Text::Table::TSV::table(rows=>$rows)}elsif ($backend eq 'Text::Table::LTSV'){require Text::Table::LTSV;return Text::Table::LTSV::table(rows=>$rows)}elsif ($backend eq 'Text::Table::ASV'){require Text::Table::ASV;return Text::Table::ASV::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::HTML'){require Text::Table::HTML;return Text::Table::HTML::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::HTML::DataTables'){require Text::Table::HTML::DataTables;return Text::Table::HTML::DataTables::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::Table::Paragraph'){require Text::Table::Paragraph;return Text::Table::Paragraph::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Text::ANSITable'){require Text::ANSITable;my$t=Text::ANSITable->new(use_utf8=>0,use_box_chars=>0,use_color=>0,border_style=>'Default::single_ascii',);if ($header_row){$t->columns($rows->[0]);$t->add_row($rows->[$_])for 1..@$rows-1}else {$t->columns([map {"col$_"}0..$#{$rows->[0]}]);$t->add_row($_)for @$rows}return$t->draw}elsif ($backend eq 'Text::ASCIITable'){require Text::ASCIITable;my$t=Text::ASCIITable->new();if ($header_row){$t->setCols(@{$rows->[0]});$t->addRow(@{$rows->[$_]})for 1..@$rows-1}else {$t->setCols(map {"col$_"}0..$#{$rows->[0]});$t->addRow(@$_)for @$rows}return "$t"}elsif ($backend eq 'Text::FormatTable'){require Text::FormatTable;my$t=Text::FormatTable->new(join('|',('l')x @{$rows->[0]}));$t->head(@{$rows->[0]});$t->row(@{$rows->[$_]})for 1..@$rows-1;return$t->render}elsif ($backend eq 'Text::MarkdownTable'){require Text::MarkdownTable;my$out="";my$fields=$header_row ? $rows->[0]: [map {"col$_"}0..$#{$rows->[0]}];my$t=Text::MarkdownTable->new(file=>\$out,columns=>$fields);for (($header_row ? 1:0).. $#{$rows}){my$row=$rows->[$_];$t->add({map {$fields->[$_]=>$row->[$_]}0..@$fields-1 })}$t->done;return$out}elsif ($backend eq 'Text::Table'){require Text::Table;my$t=Text::Table->new(@{$rows->[0]});$t->load(@{$rows}[1..@$rows-1]);return$t}elsif ($backend eq 'Text::TabularDisplay'){require Text::TabularDisplay;my$t=Text::TabularDisplay->new(@{$rows->[0]});$t->add(@{$rows->[$_]})for 1..@$rows-1;return$t->render ."\n"}elsif ($backend eq 'Text::Table::XLSX'){require Text::Table::XLSX;return Text::Table::XLSX::table(rows=>$rows,header_row=>$header_row)}elsif ($backend eq 'Term::TablePrint'){require Term::TablePrint;my$rows2;if ($header_row){$rows2=$rows}else {$rows2=[@$rows];shift @$rows2}return Term::TablePrint::print_table($rows)}else {die "Unknown backend '$backend'"}}1; TEXT_TABLE_ANY $fatpacked{"Text/Table/Tiny.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TEXT_TABLE_TINY'; @@ -492,7 +492,7 @@ use strict; use App::Codeowners; -our $VERSION = '0.47'; # VERSION +our $VERSION = '0.48'; # VERSION App::Codeowners->main(@ARGV); @@ -508,7 +508,7 @@ git-codeowners - A tool for managing CODEOWNERS files =head1 VERSION -version 0.47 +version 0.48 =head1 SYNOPSIS