2007-10-26 22:44:33 +00:00
|
|
|
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
|
|
|
|
package xCAT::NodeRange;
|
2008-04-05 14:53:35 +00:00
|
|
|
require xCAT::Table;
|
2007-10-26 22:44:33 +00:00
|
|
|
require Exporter;
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
#Perl implementation of noderange
|
|
|
|
our @ISA = qw(Exporter);
|
|
|
|
our @EXPORT = qw(noderange nodesmissed);
|
2009-04-14 18:27:43 +00:00
|
|
|
our @EXPORT_OK = qw(extnoderange abbreviate_noderange);
|
2007-10-26 22:44:33 +00:00
|
|
|
|
|
|
|
my $missingnodes=[];
|
|
|
|
my $nodelist; #=xCAT::Table->new('nodelist',-create =>1);
|
2008-02-08 21:53:41 +00:00
|
|
|
#my $nodeprefix = "node";
|
2008-07-07 22:47:38 +00:00
|
|
|
my @allnodeset;
|
|
|
|
my $retaincache=0;
|
2008-07-14 14:24:05 +00:00
|
|
|
my $recurselevel=0;
|
2007-10-26 22:44:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
sub subnodes (\@@) {
|
|
|
|
#Subtract set of nodes from the first list
|
|
|
|
my $nodes = shift;
|
|
|
|
my $node;
|
|
|
|
foreach $node (@_) {
|
|
|
|
@$nodes = (grep(!/^$node$/,@$nodes));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sub nodesmissed {
|
|
|
|
return @$missingnodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub expandatom {
|
|
|
|
my $atom = shift;
|
|
|
|
my $verify = (scalar(@_) == 1 ? shift : 1);
|
|
|
|
my @nodes= ();
|
2008-02-08 21:53:41 +00:00
|
|
|
#TODO: these env vars need to get passed by the client to xcatd
|
2007-10-26 22:44:33 +00:00
|
|
|
my $nprefix=(defined ($ENV{'XCAT_NODE_PREFIX'}) ? $ENV{'XCAT_NODE_PREFIX'} : 'node');
|
2008-02-08 21:53:41 +00:00
|
|
|
my $nsuffix=(defined ($ENV{'XCAT_NODE_SUFFIX'}) ? $ENV{'XCAT_NODE_SUFFIX'} : '');
|
|
|
|
if ($nodelist->getAttribs({node=>$atom},'node')) { #The atom is a plain old nodename
|
2007-10-26 22:44:33 +00:00
|
|
|
return ($atom);
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
if ($atom =~ /^\(.*\)$/) { # handle parentheses by recursively calling noderange()
|
2007-10-26 22:44:33 +00:00
|
|
|
$atom =~ s/^\((.*)\)$/$1/;
|
2008-07-14 14:24:05 +00:00
|
|
|
$recurselevel++;
|
2007-10-26 22:44:33 +00:00
|
|
|
return noderange($atom);
|
|
|
|
}
|
2008-07-17 13:44:56 +00:00
|
|
|
if ($atom =~ /@/) {
|
|
|
|
$recurselevel++;
|
|
|
|
return noderange($atom);
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
# Try to match groups?
|
2008-07-07 22:47:38 +00:00
|
|
|
foreach($nodelist->getAllAttribs('node','groups')) {
|
2007-10-26 22:44:33 +00:00
|
|
|
my @groups=split(/,/,$_->{groups}); #The where clause doesn't guarantee the atom is a full group name, only that it could be
|
|
|
|
if (grep { $_ eq "$atom" } @groups ) {
|
|
|
|
push @nodes,$_->{node};
|
|
|
|
}
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
if ($atom =~ m/^[0-9]+\z/) { # if only numbers, then add the prefix
|
2007-10-26 22:44:33 +00:00
|
|
|
my $nodename=$nprefix.$atom.$nsuffix;
|
|
|
|
return expandatom($nodename,$verify);
|
|
|
|
}
|
|
|
|
my $nodelen=@nodes;
|
|
|
|
if ($nodelen > 0) {
|
|
|
|
return @nodes;
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
if ($atom =~ m/^\//) { # A regular expression
|
|
|
|
unless ($verify) { # If not in verify mode, regex makes zero possible sense
|
|
|
|
return ($atom);
|
|
|
|
}
|
|
|
|
#TODO: check against all groups
|
|
|
|
$atom = substr($atom,1);
|
2008-07-07 22:47:38 +00:00
|
|
|
unless (scalar(@allnodeset)) {
|
|
|
|
@allnodeset = $nodelist->getAllAttribs('node');
|
|
|
|
}
|
|
|
|
foreach (@allnodeset) { #$nodelist->getAllAttribs('node')) {
|
2008-02-08 21:53:41 +00:00
|
|
|
if ($_->{node} =~ m/^${atom}$/) {
|
|
|
|
push(@nodes,$_->{node});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(@nodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atom =~ m/(.*)\[(.*)\](.*)/) { # square bracket range
|
2007-10-26 22:44:33 +00:00
|
|
|
#for the time being, we are only going to consider one [] per atom
|
|
|
|
#xcat 1.2 does no better
|
|
|
|
my @subelems = split(/([\,\-\:])/,$2);
|
|
|
|
my $subrange="";
|
|
|
|
while (my $subelem = shift @subelems) {
|
|
|
|
my $subop=shift @subelems;
|
|
|
|
$subrange=$subrange."$1$subelem$3$subop";
|
|
|
|
}
|
|
|
|
foreach (split /,/,$subrange) {
|
|
|
|
my @newnodes=expandatom($_,$verify);
|
|
|
|
@nodes=(@nodes,@newnodes);
|
|
|
|
}
|
|
|
|
return @nodes;
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
if ($atom =~ m/\+/) { # process the + operator
|
2009-04-23 14:53:56 +00:00
|
|
|
$atom =~ m/^(.*)([0-9]+)([^0-9\+]*)\+([0-9]+)/;
|
2007-10-26 22:44:33 +00:00
|
|
|
my $pref=$1;
|
|
|
|
my $startnum=$2;
|
|
|
|
my $suf=$3;
|
|
|
|
my $end=$4+$startnum;
|
|
|
|
my $endnum = sprintf("%d",$end);
|
|
|
|
if (length ($startnum) > length ($endnum)) {
|
|
|
|
$endnum = sprintf("%0".length($startnum)."d",$end);
|
|
|
|
}
|
|
|
|
if (($pref eq "") && ($suf eq "")) {
|
|
|
|
$pref=$nprefix;
|
|
|
|
$suf=$nsuffix;
|
|
|
|
}
|
|
|
|
foreach ("$startnum".."$endnum") {
|
|
|
|
my @addnodes=expandatom($pref.$_.$suf,$verify);
|
|
|
|
@nodes=(@nodes,@addnodes);
|
|
|
|
}
|
|
|
|
return (@nodes);
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
if ($atom =~ m/[-:]/) { # process the minus range operator
|
2007-10-26 22:44:33 +00:00
|
|
|
my $left;
|
|
|
|
my $right;
|
|
|
|
if ($atom =~ m/:/) {
|
|
|
|
($left,$right)=split /:/,$atom;
|
|
|
|
} else {
|
|
|
|
my $count= ($atom =~ tr/-//);
|
|
|
|
if (($count % 2)==0) { #can't understand even numbers of - in range context
|
|
|
|
if ($verify) {
|
|
|
|
push @$missingnodes,$atom;
|
2008-02-08 21:53:41 +00:00
|
|
|
return ();
|
2007-10-26 22:44:33 +00:00
|
|
|
} else { #but we might not really be in range context, if noverify
|
|
|
|
return ($atom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
my $expr="([^-]+?".("-[^-]*"x($count/2)).")-(.*)";
|
|
|
|
$atom =~ m/$expr/;
|
|
|
|
$left=$1;
|
|
|
|
$right=$2;
|
|
|
|
}
|
|
|
|
if ($left eq $right) { #if they said node1-node1 for some strange reason
|
|
|
|
return expandatom($left,$verify);
|
|
|
|
}
|
|
|
|
my @leftarr=split(/(\d+)/,$left);
|
|
|
|
my @rightarr=split(/(\d+)/,$right);
|
|
|
|
if (scalar(@leftarr) != scalar(@rightarr)) { #Mismatch formatting..
|
|
|
|
if ($verify) {
|
|
|
|
push @$missingnodes,$atom;
|
|
|
|
return (); #mismatched range, bail.
|
|
|
|
} else { #Not in verify mode, just have to guess it's meant to be a nodename
|
|
|
|
return ($atom);
|
|
|
|
}
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
my $prefix = "";
|
2007-10-26 22:44:33 +00:00
|
|
|
my $suffix = "";
|
|
|
|
foreach (0..$#leftarr) {
|
|
|
|
my $idx = $_;
|
|
|
|
if ($leftarr[$idx] =~ /^\d+$/ and $rightarr[$idx] =~ /^\d+$/) { #pure numeric component
|
|
|
|
if ($leftarr[$idx] ne $rightarr[$idx]) { #We have found the iterator (only supporting one for now)
|
|
|
|
my $prefix = join('',@leftarr[0..($idx-1)]); #Make a prefix of the pre-validated parts
|
|
|
|
my $luffix; #However, the remainder must still be validated to be the same
|
|
|
|
my $ruffix;
|
2008-02-08 21:53:41 +00:00
|
|
|
if ($idx eq $#leftarr) {
|
2007-10-26 22:44:33 +00:00
|
|
|
$luffix="";
|
|
|
|
$ruffix="";
|
|
|
|
} else {
|
|
|
|
$ruffix = join('',@rightarr[($idx+1)..$#rightarr]);
|
|
|
|
$luffix = join('',@leftarr[($idx+1)..$#leftarr]);
|
|
|
|
}
|
|
|
|
if ($luffix ne $ruffix) { #the suffixes mismatched..
|
|
|
|
if ($verify) {
|
|
|
|
push @$missingnodes,$atom;
|
|
|
|
return ();
|
|
|
|
} else {
|
|
|
|
return ($atom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
foreach ($leftarr[$idx]..$rightarr[$idx]) {
|
|
|
|
my @addnodes=expandatom($prefix.$_.$luffix,$verify);
|
|
|
|
@nodes=(@nodes,@addnodes);
|
|
|
|
}
|
|
|
|
return (@nodes); #the return has been built, return, exiting loop and all
|
|
|
|
}
|
|
|
|
} elsif ($leftarr[$idx] ne $rightarr[$idx]) {
|
|
|
|
if ($verify) {
|
|
|
|
push @$missingnodes,$atom;
|
|
|
|
return ();
|
|
|
|
} else {
|
|
|
|
return ($atom);
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
}
|
2007-10-26 22:44:33 +00:00
|
|
|
$prefix .= $leftarr[$idx]; #If here, it means that the pieces were the same, but more to come
|
|
|
|
}
|
|
|
|
#I cannot conceive how the code could possibly be here, but whatever it is, it must be questionable
|
|
|
|
if ($verify) {
|
|
|
|
push @$missingnodes,$atom;
|
|
|
|
return (); #mismatched range, bail.
|
|
|
|
} else { #Not in verify mode, just have to guess it's meant to be a nodename
|
|
|
|
return ($atom);
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
}
|
|
|
|
|
2007-10-26 22:44:33 +00:00
|
|
|
push @$missingnodes,$atom;
|
|
|
|
if ($verify) {
|
|
|
|
return ();
|
|
|
|
} else {
|
|
|
|
return ($atom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-07 22:47:38 +00:00
|
|
|
sub retain_cache { #A semi private operation to be used *ONLY* in the interesting Table<->NodeRange module interactions.
|
|
|
|
$retaincache=shift;
|
|
|
|
}
|
2008-09-26 22:29:02 +00:00
|
|
|
sub extnoderange { #An extended noderange function. Needed as the more straightforward function return format too simple for this.
|
|
|
|
my $range = shift;
|
|
|
|
my $namedopts = shift;
|
|
|
|
my $verify=1;
|
|
|
|
if ($namedopts->{skipnodeverify}) {
|
|
|
|
$verify=0;
|
|
|
|
}
|
|
|
|
my $return;
|
|
|
|
$retaincache=1;
|
2008-09-26 22:57:55 +00:00
|
|
|
$return->{node}=[noderange($range,$verify)];
|
2008-09-26 22:29:02 +00:00
|
|
|
if ($namedopts->{intersectinggroups}) {
|
|
|
|
my %grouphash=();
|
|
|
|
my $nlent;
|
2008-09-26 22:57:55 +00:00
|
|
|
foreach (@{$return->{node}}) {
|
2008-09-26 22:29:02 +00:00
|
|
|
$nlent=$nodelist->getNodeAttribs($_,['groups']);
|
|
|
|
if ($nlent and $nlent->{groups}) {
|
|
|
|
foreach (split /,/,$nlent->{groups}) {
|
|
|
|
$grouphash{$_}=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$return->{intersectinggroups}=[sort keys %grouphash];
|
|
|
|
}
|
|
|
|
$retaincache=0;
|
|
|
|
undef ($nodelist);
|
|
|
|
@allnodeset=();
|
|
|
|
return $return;
|
|
|
|
}
|
2009-04-14 18:27:43 +00:00
|
|
|
sub abbreviate_noderange {
|
|
|
|
#takes a list of nodes or a string and abbreviates
|
|
|
|
my $nodes=shift;
|
|
|
|
my %grouphash;
|
|
|
|
my %sizedgroups;
|
|
|
|
my %nodesleft;
|
|
|
|
my %targetelems;
|
|
|
|
unless (ref $nodes) {
|
|
|
|
$nodes = noderange($nodes);
|
|
|
|
}
|
|
|
|
%nodesleft = map { $_ => 1 } @{$nodes};
|
|
|
|
unless ($nodelist) {
|
|
|
|
$nodelist =xCAT::Table->new('nodelist',-create =>1);
|
|
|
|
}
|
|
|
|
my $group;
|
|
|
|
foreach($nodelist->getAllAttribs('node','groups')) {
|
|
|
|
my @groups=split(/,/,$_->{groups}); #The where clause doesn't guarantee the atom is a full group name, only that it could be
|
|
|
|
foreach $group (@groups) {
|
|
|
|
push @{$grouphash{$group}},$_->{node};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach $group (keys %grouphash) {
|
2009-04-24 12:45:06 +00:00
|
|
|
#skip single node sized groups, these outliers frequently pasted into non-noderange capable contexts
|
|
|
|
if (scalar @{$grouphash{$group}} < 2) { next; }
|
2009-04-14 18:27:43 +00:00
|
|
|
push @{$sizedgroups{scalar @{$grouphash{$group}}}},$group;
|
|
|
|
}
|
|
|
|
my $node;
|
|
|
|
use Data::Dumper;
|
|
|
|
print Dumper(\%sizedgroups);
|
|
|
|
foreach (reverse sort {$a <=> $b} keys %sizedgroups) {
|
|
|
|
GROUP: foreach $group (@{$sizedgroups{$_}}) {
|
|
|
|
foreach $node (@{$grouphash{$group}}) {
|
|
|
|
unless (grep $node eq $_,keys %nodesleft) {
|
|
|
|
#this group contains a node that isn't left, skip it
|
|
|
|
next GROUP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
foreach $node (@{$grouphash{$group}}){
|
|
|
|
delete $nodesleft{$node};
|
|
|
|
}
|
|
|
|
$targetelems{$group}=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (join ',',keys %targetelems,keys %nodesleft);
|
|
|
|
}
|
|
|
|
|
2007-10-26 22:44:33 +00:00
|
|
|
sub noderange {
|
|
|
|
$missingnodes=[];
|
|
|
|
#We for now just do left to right operations
|
|
|
|
my $range=shift;
|
|
|
|
my $verify = (scalar(@_) == 1 ? shift : 1);
|
2008-07-07 22:47:38 +00:00
|
|
|
unless ($nodelist) {
|
|
|
|
$nodelist =xCAT::Table->new('nodelist',-create =>1);
|
|
|
|
$nodelist->{_use_cache} = 0; #TODO: a more proper external solution
|
|
|
|
$nodelist->_build_cache(['node','groups']);
|
|
|
|
$nodelist->{_use_cache} = 1; #TODO: a more proper external solution
|
|
|
|
}
|
2007-10-26 22:44:33 +00:00
|
|
|
my %nodes = ();
|
|
|
|
my %delnodes = ();
|
|
|
|
my $op = ",";
|
2008-07-17 13:44:56 +00:00
|
|
|
my @elems = split(/(,(?![^[]*?])(?![^\(]*?\)))/,$range); # commas outside of [] or ()
|
|
|
|
if (scalar(@elems)==1) {
|
|
|
|
@elems = split(/(@(?![^\(]*?\)))/,$range); # only split on @ when no , are present (inner recursion)
|
|
|
|
}
|
2007-10-26 22:44:33 +00:00
|
|
|
|
|
|
|
while (my $atom = shift @elems) {
|
2008-02-08 21:53:41 +00:00
|
|
|
if ($atom =~ /^-/) { # if this is an exclusion, strip off the minus, but remember it
|
2007-10-26 22:44:33 +00:00
|
|
|
$atom = substr($atom,1);
|
|
|
|
$op = $op."-";
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
if ($atom =~ /^\^(.*)$/) { # get a list of nodes from a file
|
2007-10-26 22:44:33 +00:00
|
|
|
open(NRF,$1);
|
|
|
|
while (<NRF>) {
|
|
|
|
my $line=$_;
|
|
|
|
unless ($line =~ m/^[\^#]/) {
|
|
|
|
$line =~ m/^([^: ]*)/;
|
|
|
|
my $newrange = $1;
|
|
|
|
chomp($newrange);
|
2008-07-14 14:24:05 +00:00
|
|
|
$recurselevel++;
|
2007-10-26 22:44:33 +00:00
|
|
|
my @filenodes = noderange($newrange);
|
|
|
|
foreach (@filenodes) {
|
|
|
|
$nodes{$_}=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close(NRF);
|
|
|
|
next;
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
my %newset = map { $_ =>1 } expandatom($atom,$verify); # expand the atom and make each entry in the resulting array a key in newset
|
|
|
|
|
|
|
|
if ($op =~ /@/) { # compute the intersection of the current atom and the node list we have received before this
|
2007-10-26 22:44:33 +00:00
|
|
|
foreach (keys %nodes) {
|
|
|
|
unless ($newset{$_}) {
|
|
|
|
delete $nodes{$_};
|
|
|
|
}
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
} elsif ($op =~ /,-/) { # add the nodes from this atom to the exclude list
|
2007-10-26 22:44:33 +00:00
|
|
|
foreach (keys %newset) {
|
|
|
|
$delnodes{$_}=1; #delay removal to end
|
|
|
|
}
|
2008-02-08 21:53:41 +00:00
|
|
|
} else { # add the nodes from this atom to the total node list
|
2007-10-26 22:44:33 +00:00
|
|
|
foreach (keys %newset) {
|
|
|
|
$nodes{$_}=1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$op = shift @elems;
|
2008-02-08 21:53:41 +00:00
|
|
|
|
|
|
|
} # end of main while loop
|
|
|
|
|
|
|
|
# Now remove all the exclusion nodes
|
2007-10-26 22:44:33 +00:00
|
|
|
foreach (keys %nodes) {
|
|
|
|
if ($delnodes{$_}) {
|
|
|
|
delete $nodes{$_};
|
|
|
|
}
|
|
|
|
}
|
2008-07-14 14:24:05 +00:00
|
|
|
if ($recurselevel) {
|
|
|
|
$recurselevel--;
|
|
|
|
} else {
|
|
|
|
unless ($retaincache) {
|
|
|
|
undef $nodelist;
|
|
|
|
@allnodeset=();
|
|
|
|
}
|
2008-07-07 22:47:38 +00:00
|
|
|
}
|
2007-10-26 22:44:33 +00:00
|
|
|
return sort (keys %nodes);
|
2008-02-08 21:53:41 +00:00
|
|
|
|
2007-10-26 22:44:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
xCAT::NodeRange - Perl module for xCAT noderange expansion
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
use xCAT::NodeRange;
|
|
|
|
my @nodes=noderange("storage@rack1,node[1-200],^/tmp/nodelist,node300-node400,node401+10,500-550");
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
noderange interprets xCAT noderange formatted strings and returns a list of xCAT nodelists. The following two operations are supported on elements, and interpreted left to right:
|
|
|
|
|
|
|
|
, union next element with everything to the left.
|
|
|
|
|
|
|
|
@ take intersection of element to the right with everything on the left (i.e. mask out anything to the left not belonging to what is described to the right)
|
|
|
|
|
|
|
|
Each element can be a number of things:
|
|
|
|
|
|
|
|
A node name, i.e.:
|
|
|
|
|
|
|
|
=item * node1
|
|
|
|
|
|
|
|
A hyphenated node range (only one group of numbers may differ between the left and right hand side, and those numbers will increment in a base 10 fashion):
|
|
|
|
|
|
|
|
node1-node200 node1-compute-node200-compute
|
|
|
|
node1:node200 node1-compute:node200-compute
|
|
|
|
|
|
|
|
A noderange denoted by brackets:
|
|
|
|
|
|
|
|
node[1-200] node[001-200]
|
|
|
|
|
|
|
|
A regular expression describing the noderange:
|
|
|
|
|
|
|
|
/d(1.?.?|200)
|
|
|
|
|
|
|
|
A node plus offset (this increments the first number found in nodename):
|
|
|
|
|
|
|
|
node1+199
|
|
|
|
|
2008-02-08 21:53:41 +00:00
|
|
|
And most of the above substituting groupnames.
|
2007-10-26 22:44:33 +00:00
|
|
|
3C
|
|
|
|
3C
|
|
|
|
|
|
|
|
NodeRange tries to be intelligent about detecting padding, so you can:
|
|
|
|
node001-node200
|
|
|
|
And it will increment according to the pattern.
|
|
|
|
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
|
|
|
Jarrod Johnson (jbjohnso@us.ibm.com)
|
|
|
|
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
|
|
|
|
Copyright 2007 IBM Corp. All rights reserved.
|
|
|
|
|
|
|
|
|
|
|
|
=cut
|