All patches applied upstream
[moreutils.git] / vidir
blobc7f12392fe90ef5c31ebf4a2952922127559073c
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 <id@joeyh.name>
75 Licensed under the GNU GPL.
77 =cut
79 use File::Basename;
80 use File::Path qw(make_path);
81 use File::Spec;
82 use File::Temp;
83 use Getopt::Long;
85 my $error=0;
87 my $verbose=0;
88 if (! GetOptions("verbose|v" => \$verbose)) {
89 die "Usage: $0 [--verbose] [directory|file|-]\n";
92 my @dir;
93 if (! @ARGV) {
94 push @ARGV, "."
96 foreach my $item (@ARGV) {
97 if ($item eq "-") {
98 push @dir, map { chomp; $_ } <STDIN>;
99 close STDIN;
100 open(STDIN, "/dev/tty") || die "reopen: $!\n";
102 elsif (-d $item) {
103 $item =~ s{/?$}{/};
104 opendir(DIR, $item) || die "$0: cannot read $item: $!\n";
105 push @dir, map { "$item$_" } sort readdir(DIR);
106 closedir DIR;
108 else {
109 push @dir, $item;
113 if (grep(/[[:cntrl:]]/, @dir)) {
114 die "$0: control characters in filenames are not supported\n";
117 my $tmp=File::Temp->new(TEMPLATE => "dirXXXXX", DIR => File::Spec->tmpdir);
118 open (OUT, ">".$tmp->filename) || die "$0: cannot create ".$tmp->filename.": $!\n";
120 my %item;
121 my $c=0;
122 foreach (@dir) {
123 next if /^(.*\/)?\.$/ || /^(.*\/)?\.\.$/;
124 $item{++$c}=$_;
125 print OUT "$c\t$_\n";
127 @dir=();
128 close OUT || die "$0: cannot write ".$tmp->filename.": $!\n";
130 my @editor="vi";
131 if (-x "/usr/bin/editor") {
132 @editor="/usr/bin/editor";
134 if (exists $ENV{EDITOR}) {
135 @editor=split(' ', $ENV{EDITOR});
137 if (exists $ENV{VISUAL}) {
138 @editor=split(' ', $ENV{VISUAL});
140 $ret=system(@editor, $tmp);
141 if ($ret != 0) {
142 die "@editor exited nonzero, aborting\n";
145 open (IN, $tmp->filename) || die "$0: cannot read ".$tmp->filename.": $!\n";
146 while (<IN>) {
147 chomp;
148 if (/^(\d+)\t{0,1}(.*)/) {
149 my $num=int($1);
150 my $name=$2;
151 if (! exists $item{$num}) {
152 die "$0: unknown item number $num\n";
154 elsif ($name ne $item{$num}) {
155 next unless length $name;
156 my $src=$item{$num};
157 my $dir=dirname($name);
159 if (! (-e $src || -l $src) ) {
160 print STDERR "$0: $src does not exist\n";
161 delete $item{$num};
162 next;
165 # deal with swaps
166 if (-e $name || -l $name) {
167 my $tmp=$name."~";
168 my $c=0;
169 while (-e $tmp || -l $tmp) {
170 $c++;
171 $tmp=$name."~$c";
173 if (! rename($name, $tmp)) {
174 print STDERR "$0: failed to rename $name to $tmp: $!\n";
175 $error=1;
177 elsif ($verbose) {
178 print "'$name' -> '$tmp'\n";
180 foreach my $item (keys %item) {
181 if ($item{$item} eq $name) {
182 $item{$item}=$tmp;
187 if ((! -d $dir) && (! make_path($dir, {
188 verbose => $verbose,
189 }))) {
190 print STDERR "$0: failed to create directory tree $dir: $!\n";
191 $error=1;
193 elsif (! rename($src, $name)) {
194 print STDERR "$0: failed to rename $src to $name: $!\n";
195 $error=1;
197 else {
198 if (-d $name) {
199 foreach (values %item) {
200 s,^\Q$src\E($|/),$name$1,;
203 if ($verbose) {
204 print "'$src' => '$name'\n";
208 delete $item{$num};
210 elsif (/^\s*$/) {
211 # skip empty line
213 else {
214 die "$0: unable to parse line \"$_\", aborting\n";
217 close IN || die "$0: cannot read ".$tmp->filename.": $!\n";
218 unlink($tmp.'~') if -e $tmp.'~';
220 sub rm {
221 my $file = shift;
223 if (-d $file && ! -l $file) {
224 return rmdir $file;
226 else {
227 return unlink $file;
231 foreach my $item (reverse sort values %item) {
232 if (! rm($item)) {
233 print STDERR "$0: failed to remove $item: $!\n";
234 $error=1;
236 if ($verbose) {
237 print "removed '$item'\n";
241 exit $error;