update physmem copyright
[moreutils.git] / vidir
blob7d382b237593dc88d471db016a3bdd8899f14ec8
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 =head1 ENVIRONMENT VARIABLES
57 =over 4
59 =item EDITOR
61 Editor to use.
63 =item VISUAL
65 Also supported to determine what editor to use.
67 =back
69 =head1 AUTHOR
71 Copyright 2006 by Joey Hess <joey@kitenet.net>
73 Licensed under the GNU GPL.
75 =cut
77 use File::Spec;
78 use File::Temp;
79 use Getopt::Long;
81 my $error=0;
83 my $verbose=0;
84 if (! GetOptions("verbose|v" => \$verbose)) {
85 die "Usage: $0 [--verbose] [directory|file|-]\n";
88 my @dir;
89 if (! @ARGV) {
90 push @ARGV, "."
92 foreach my $item (@ARGV) {
93 if ($item eq "-") {
94 push @dir, map { chomp; $_ } <STDIN>;
95 close STDIN;
96 open(STDIN, "/dev/tty") || die "reopen: $!\n";
98 elsif (-d $item) {
99 $item =~ s{/?$}{/};
100 opendir(DIR, $item) || die "$0: cannot read $item: $!\n";
101 push @dir, map { "$item$_" } sort readdir(DIR);
102 closedir DIR;
104 else {
105 push @dir, $item;
109 if (grep(/[[:cntrl:]]/, @dir)) {
110 die "$0: control characters in filenames are not supported\n";
113 my $tmp=File::Temp->new(TEMPLATE => "dirXXXXX", DIR => File::Spec->tmpdir);
114 open (OUT, ">".$tmp->filename) || die "$0: cannot create ".$tmp->filename.": $!\n";
116 my %item;
117 my $c=0;
118 foreach (@dir) {
119 next if /^(.*\/)?\.$/ || /^(.*\/)?\.\.$/;
120 $item{++$c}=$_;
121 print OUT "$c\t$_\n";
123 @dir=();
124 close OUT || die "$0: cannot write ".$tmp->filename.": $!\n";
126 my @editor="vi";
127 if (-x "/usr/bin/editor") {
128 @editor="/usr/bin/editor";
130 if (exists $ENV{EDITOR}) {
131 @editor=split(' ', $ENV{EDITOR});
133 if (exists $ENV{VISUAL}) {
134 @editor=split(' ', $ENV{VISUAL});
136 $ret=system(@editor, $tmp);
137 if ($ret != 0) {
138 die "@editor exited nonzero, aborting\n";
141 open (IN, $tmp->filename) || die "$0: cannot read ".$tmp->filename.": $!\n";
142 while (<IN>) {
143 chomp;
144 if (/^(\d+)\t{0,1}(.*)/) {
145 my $num=int($1);
146 my $name=$2;
147 if (! exists $item{$num}) {
148 die "$0: unknown item number $num\n";
150 elsif ($name ne $item{$num}) {
151 next unless length $name;
152 my $src=$item{$num};
154 if (! (-e $src || -l $src) ) {
155 print STDERR "$0: $src does not exist\n";
156 delete $item{$num};
157 next;
160 # deal with swaps
161 if (-e $name || -l $name) {
162 my $tmp=$name."~";
163 my $c=0;
164 while (-e $tmp || -l $tmp) {
165 $c++;
166 $tmp=$name."~$c";
168 if (! rename($name, $tmp)) {
169 print STDERR "$0: failed to rename $name to $tmp: $!\n";
170 $error=1;
172 elsif ($verbose) {
173 print "'$name' -> '$tmp'\n";
175 foreach my $item (keys %item) {
176 if ($item{$item} eq $name) {
177 $item{$item}=$tmp;
182 if (! rename($src, $name)) {
183 print STDERR "$0: failed to rename $src to $name: $!\n";
184 $error=1;
186 else {
187 if (-d $name) {
188 foreach (values %item) {
189 s/^\Q$src\E/$name/;
192 if ($verbose) {
193 print "'$src' => '$name'\n";
197 delete $item{$num};
199 elsif (/^\s*$/) {
200 # skip empty line
202 else {
203 die "$0: unable to parse line \"$_\", aborting\n";
206 close IN || die "$0: cannot read ".$tmp->filename.": $!\n";
207 unlink($tmp.'~') if -e $tmp.'~';
209 sub rm {
210 my $file = shift;
212 if (-d $file && ! -l $file) {
213 return rmdir $file;
215 else {
216 return unlink $file;
220 foreach my $item (reverse sort values %item) {
221 if (! rm($item)) {
222 print STDERR "$0: failed to remove $item: $!\n";
223 $error=1;
225 if ($verbose) {
226 print "removed '$item'\n";
230 exit $error;