Cap sillyness. Closes: #570815
[moreutils.git] / vidir
bloba77739f27d8cab6843471de92857fe5064f9ace4
1 #!/usr/bin/perl
3 =head1 NAME
5 vidir - edit directory
7 =head1 SYNOPSIS
9 B<vidir> [--verbose] [directory|file|-] ...
11 =head1 DESCRIPTION
13 vidir allows editing of the contents of a directory in a text editor. If no
14 directory is specified, the current directory is edited.
16 When editing a directory, each item in the directory will appear on its own
17 numbered line. These numbers are how vidir keeps track of what items are
18 changed. Delete lines to remove files from the directory, or
19 edit filenames to rename files. You can also switch pairs of numbers to
20 swap filenames.
22 Note that if "-" is specified as the directory to edit, it reads a list of
23 filenames from stdin and displays those for editing. Alternatively, a list
24 of files can be specified on the command line.
26 =head1 OPTIONS
28 =over 4
30 =item -v, --verbose
32 Verbosely display the actions taken by the program.
34 =back
36 =head1 EXAMPLES
38 =over 4
40 =item vidir
42 =item vidir *.jpeg
44 Typical uses.
46 =item find | vidir -
48 Edit subdirectory contents too. To delete subdirectories,
49 delete all their contents and the subdirectory itself in the editor.
51 =item find -type f | vidir -
53 Edit all files under the current directory and subdirectories.
55 =back
57 =head1 ENVIRONMENT VARIABLES
59 =over 4
61 =item EDITOR
63 Editor to use.
65 =item VISUAL
67 Also supported to determine what editor to use.
69 =back
71 =head1 AUTHOR
73 Copyright 2006 by Joey Hess <joey@kitenet.net>
75 Licensed under the GNU GPL.
77 =cut
79 use File::Spec;
80 use File::Temp;
81 use Getopt::Long;
83 my $error=0;
85 my $verbose=0;
86 if (! GetOptions("verbose|v" => \$verbose)) {
87 die "Usage: $0 [--verbose] [directory|file|-]\n";
90 my @dir;
91 if (! @ARGV) {
92 push @ARGV, "."
94 foreach my $item (@ARGV) {
95 if ($item eq "-") {
96 push @dir, map { chomp; $_ } <STDIN>;
97 close STDIN;
98 open(STDIN, "/dev/tty") || die "reopen: $!\n";
100 elsif (-d $item) {
101 $item =~ s{/?$}{/};
102 opendir(DIR, $item) || die "$0: cannot read $item: $!\n";
103 push @dir, map { "$item$_" } sort readdir(DIR);
104 closedir DIR;
106 else {
107 push @dir, $item;
111 if (grep(/[[:cntrl:]]/, @dir)) {
112 die "$0: control characters in filenames are not supported\n";
115 my $tmp=File::Temp->new(TEMPLATE => "dirXXXXX", DIR => File::Spec->tmpdir);
116 open (OUT, ">".$tmp->filename) || die "$0: cannot create ".$tmp->filename.": $!\n";
118 my %item;
119 my $c=0;
120 foreach (@dir) {
121 next if /^(.*\/)?\.$/ || /^(.*\/)?\.\.$/;
122 $item{++$c}=$_;
123 print OUT "$c\t$_\n";
125 @dir=();
126 close OUT || die "$0: cannot write ".$tmp->filename.": $!\n";
128 my @editor="vi";
129 if (-x "/usr/bin/editor") {
130 @editor="/usr/bin/editor";
132 if (exists $ENV{EDITOR}) {
133 @editor=split(' ', $ENV{EDITOR});
135 if (exists $ENV{VISUAL}) {
136 @editor=split(' ', $ENV{VISUAL});
138 $ret=system(@editor, $tmp);
139 if ($ret != 0) {
140 die "@editor exited nonzero, aborting\n";
143 open (IN, $tmp->filename) || die "$0: cannot read ".$tmp->filename.": $!\n";
144 while (<IN>) {
145 chomp;
146 if (/^(\d+)\t{0,1}(.*)/) {
147 my $num=int($1);
148 my $name=$2;
149 if (! exists $item{$num}) {
150 die "$0: unknown item number $num\n";
152 elsif ($name ne $item{$num}) {
153 next unless length $name;
154 my $src=$item{$num};
156 if (! (-e $src || -l $src) ) {
157 print STDERR "$0: $src does not exist\n";
158 delete $item{$num};
159 next;
162 # deal with swaps
163 if (-e $name || -l $name) {
164 my $tmp=$name."~";
165 my $c=0;
166 while (-e $tmp || -l $tmp) {
167 $c++;
168 $tmp=$name."~$c";
170 if (! rename($name, $tmp)) {
171 print STDERR "$0: failed to rename $name to $tmp: $!\n";
172 $error=1;
174 elsif ($verbose) {
175 print "'$name' -> '$tmp'\n";
177 foreach my $item (keys %item) {
178 if ($item{$item} eq $name) {
179 $item{$item}=$tmp;
184 if (! rename($src, $name)) {
185 print STDERR "$0: failed to rename $src to $name: $!\n";
186 $error=1;
188 else {
189 if (-d $name) {
190 foreach (values %item) {
191 s/^\Q$src\E/$name/;
194 if ($verbose) {
195 print "'$src' => '$name'\n";
199 delete $item{$num};
201 elsif (/^\s*$/) {
202 # skip empty line
204 else {
205 die "$0: unable to parse line \"$_\", aborting\n";
208 close IN || die "$0: cannot read ".$tmp->filename.": $!\n";
209 unlink($tmp.'~') if -e $tmp.'~';
211 sub rm {
212 my $file = shift;
214 if (-d $file && ! -l $file) {
215 return rmdir $file;
217 else {
218 return unlink $file;
222 foreach my $item (reverse sort values %item) {
223 if (! rm($item)) {
224 print STDERR "$0: failed to remove $item: $!\n";
225 $error=1;
227 if ($verbose) {
228 print "removed '$item'\n";
232 exit $error;