| #!/usr/bin/perl | |
| use strict; | |
| use warnings; | |
| 
 | |
| use constant | |
| { | |
| 	PROG_EDIT   => 'vim', | |
| }; | |
| 
 | |
| sub open_smart; | |
| sub edit; | |
| sub stdin_to_editor; | |
| sub reveal; | |
| sub header_edit; | |
| sub wait_or_not; | |
| 
 | |
| sub usage | |
| { | |
| 	print STDERR <<"!"; | |
| Usage: $0 -[efWwRh] | |
| -e: edit | |
| -f: stdin-edit | |
| -W: wait for exit (true by default for editing) | |
| -w: don't wait for exit | |
| -R: reveal | |
| -h: header search | |
| ! | |
| 	exit 1; | |
| } | |
|  | |
| my $cmd = \&open_smart; | |
| my(@files, @args); | |
|  | |
| my %opts = ( | |
| 	'e'    => 0, | |
| 	'f'    => 0, | |
| 	'W'    => 0, | |
| 	'R'    => 0, | |
| 	'h'    => 0, | |
| ); | |
|  | |
| my $wait_set = 0; | |
|  | |
| usage() unless @ARGV; | |
|  | |
| for(my $i = 0; $i < @ARGV; ++$i){ | |
| 	$_ = $ARGV[$i]; | |
|  | |
| 	if($_ eq '--'){ | |
| 		push @files, @ARGV[$i + 1 .. $#ARGV]; | |
| 		last; | |
| 	} | |
|  | |
| 	if(/^-([a-z])$/i){ | |
| 		my $k = $1; | |
|  | |
| 		if(exists $opts{$k}){ | |
| 			$opts{$k} = 1; | |
| 			$wait_set = 1 if $k eq 'W'; | |
| 		}elsif($k eq 'w'){ | |
| 			$opts{W} = 0; | |
| 			$wait_set = 1; | |
| 		}else{ | |
| 			usage(); | |
| 		} | |
|  | |
| 	}elsif($_ eq '--args'){ | |
| 		push @args, @ARGV[$i + 1 .. $#ARGV]; | |
| 		last; | |
|  | |
| 	}elsif(/^-/){ | |
| 		usage(); | |
|  | |
| 	}else{ | |
| 		push @files, $_; | |
|  | |
| 	} | |
| } | |
|  | |
| if($opts{e} + $opts{f} + $opts{R} + $opts{h} > 1){ | |
| 	print STDERR "Can't combine -e, -f, -R and -h\n"; | |
| 	usage(); | |
| } | |
|  | |
| my $should_wait = 1; | |
| if($opts{e}){ | |
| 	$cmd = \&edit; | |
|  | |
| }elsif($opts{f}){ | |
| 	# <STDIN> | $EDITOR - | |
| 	$cmd = \&stdin_to_editor; | |
|  | |
| }elsif($opts{R}){ | |
| 	# open with rox | |
| 	$cmd = \&reveal; | |
| 	$should_wait = 0; | |
|  | |
| }elsif($opts{h}){ | |
| 	# search /usr/include/$_ for @files | |
| 	$cmd = \&header_edit; | |
|  | |
| } | |
|  | |
| $opts{W} = 1 if $should_wait and not $wait_set; | |
|  | |
| exit(&{$cmd}(( | |
| 		wait  => !!$opts{W}, | |
| 		args  => [@args], | |
| 		files => [@files]))); | |
|  | |
| # end --- | |
|  | |
| sub read_maps | |
| { | |
| 	my $rc = "$ENV{HOME}/.openrc"; | |
| 	open F, '<', $rc or die "open $rc: $!\n"; | |
|  | |
| 	my %maps; | |
| 	my $prog_reveal = ''; | |
|  | |
| 	my $mode = 0; | |
| 	while(<F>){ | |
| 		chomp; | |
| 		s/#.*//; | |
|  | |
| 		if(/^\[(.*)\]$/){ | |
| 			if($1 eq 'full'){ | |
| 				$mode = $1; | |
| 			}elsif($1 eq 'suffix'){ | |
| 				$mode = $1; | |
| 			}elsif($1 eq 'directories'){ | |
| 				$mode = $1; | |
| 			}else{ | |
| 				die "invalid section \"$1\" in $rc\n"; | |
| 			} | |
| 
 | |
| 		}elsif(!($mode eq 'directories') and my($prog, $matches) = /^([^:]+): *(.*)/){ | |
| 			sub getenv | |
| 			{ | |
| 				my $k = shift; | |
| 				return $ENV{$k} if $ENV{$k}; | |
| 				my %backup = ( | |
| 					"TERM" => "urxvt", | |
| 					"VISUAL" => "vim", | |
| 				); | |
| 				return $backup{$k} if $backup{$k}; | |
| 				return "\$$k"; | |
| 			} | |
| 
 | |
| 			my @matches = split / *, */, $matches; | |
| 
 | |
| 			$prog =~ s/\$([A-Z_]+)/getenv($1)/e; | |
| 
 | |
| 			my $key = $prog; | |
| 			if($mode eq 'suffix'){ | |
| 				# compare file extensions case insensitively | |
| 				$key = lc $key; | |
| 			} | |
| 
 | |
| 			push @{$maps{$key}}, [ $_, $mode ] for @matches; | |
| 
 | |
| 		}elsif($mode eq 'directories' && length){ | |
| 			if(length($prog_reveal)){ | |
| 				die "already have dir program, in $rc\n"; | |
| 			} | |
| 			$prog_reveal = $_; | |
| 
 | |
| 		}elsif(length){ | |
| 			die "invalid confiuration line: \"$1\" in $rc\n"; | |
| 		} | |
| 	} | |
| 
 | |
| 	if(!length($prog_reveal)){ | |
| 		die "no directory program specified, in $rc\n"; | |
| 	} | |
| 
 | |
| 	close F; | |
| 
 | |
| 	return $prog_reveal, \%maps; | |
| } | |
| 
 | |
| sub open_smart | |
| { | |
| 	my $ec = 0; | |
| 	my %h = @_; | |
| 
 | |
| 	my @to_open; | |
| 
 | |
| 	my ($prog_reveal, $maps) = read_maps(); | |
| 
 | |
| file: | |
| 	for my $fnam (@{$h{files}}){ | |
| 		#print "maps:\n"; | |
| 
 | |
| 		if(-d $fnam){ | |
| 			push @to_open, [($h{wait}, $prog_reveal, @{$h{args}}, $fnam)]; | |
| 			next file; | |
| 		} | |
| 
 | |
| 		(my $fnam_for_test = $fnam) =~ s/\.[a-zA-Z]+$/lc($&)/e; | |
| 
 | |
| 		for my $prog (keys %$maps){ | |
| 			#print "  $_:\n"; | |
| 			for(@{$maps->{$prog}}){ | |
| 				my($reg, $mode) = ($_->[0], $_->[1]); | |
| 				if($mode eq 'suffix'){ | |
| 					$reg = "\\.$reg\$"; | |
| 				} | |
| 				#print "    $reg\n" | |
| 
 | |
| 				if($fnam_for_test =~ /$reg/){ | |
| 					push @to_open, [($h{wait}, $prog, @{$h{args}}, $fnam)]; | |
| 					next file; | |
| 				} | |
| 			} | |
| 		} | |
| 
 | |
| 		die "no program found for $fnam\n"; | |
| 	} | |
| 
 | |
| 	wait_or_not(@{$_}) for @to_open; | |
| 
 | |
| 	return 0; | |
| } | |
| 
 | |
| sub wait_or_not | |
| { | |
| 	my($wait, @rest) = @_; | |
| 	my $pid = fork(); | |
| 
 | |
| 	die "fork(): $!\n" unless defined $pid; | |
| 
 | |
| 	if($pid == 0){ | |
| 		if($rest[0] =~ / /){ | |
| 			my $a = shift @rest; | |
| 			unshift @rest, split / +/, $a; | |
| 		} | |
| 
 | |
| 		exec @rest; | |
| 		die; | |
| 	}else{ | |
| 		# parent | |
| 		if($wait){ | |
| 			my $reaped = wait(); | |
| 			my $ret = $?; | |
| 
 | |
| 			die "wait(): $!\n" if $reaped == -1; | |
| 			warn "unexpected dead child $reaped (expected $pid)\n" if $reaped != $pid; | |
| 
 | |
| 			return $ret; | |
| 		} | |
| 	} | |
| } | |
| 
 | |
| sub edit | |
| { | |
| 	my %h = @_; | |
| 	my $e = $ENV{VISUAL} || $ENV{EDITOR} || PROG_EDIT; | |
| 	return wait_or_not($h{wait}, $e, @{$h{args}}, @{$h{files}}); | |
| } | |
| 
 | |
| sub stdin_to_editor | |
| { | |
| 	my $tmp = "/tmp/stdin_$$"; | |
| 
 | |
| 	open F, '>', $tmp or die "open $tmp: $!\n"; | |
| 	print F $_ while <STDIN>; | |
| 	close F; | |
| 
 | |
| 	my %h = @_; | |
| 	push @{$h{files}}, $tmp; | |
| 	my $r = edit(%h); | |
| 	unlink $tmp; | |
| 	return $r; | |
| } | |
| 
 | |
| sub reveal | |
| { | |
| 	my %h = @_; | |
| 	my ($prog_reveal) = read_maps(); | |
| 	return wait_or_not($h{wait}, $prog_reveal, @{$h{args}}, @{$h{files}}); | |
| } | |
| 
 | |
| sub header_edit | |
| { | |
| 	my %h = @_; | |
| 	my @files = @{$h{files}}; | |
| 	@{$h{files}} = (); | |
| 
 | |
| 	for my $name (@files){ | |
| 		sub find_header | |
| 		{ | |
| 			my @inc = ("", "arpa", "net", "sys"); | |
| 			my $r = shift; | |
| 			my @matches; | |
| 
 | |
| 			for(my @tmp = @inc){ | |
| 				push @inc, "x86_64-linux-gnu/$_"; | |
| 			} | |
| 
 | |
| 			for my $inc (@inc){ | |
| 				$inc = "/usr/include/$inc"; | |
| 
 | |
| 				opendir D, $inc or next; | |
| 				push @matches, map { "$inc/$_" } grep /$r/, readdir D; | |
| 				closedir D; | |
| 			} | |
| 
 | |
| 			return @matches; | |
| 		} | |
| 
 | |
| 		my @paths = find_header($name); | |
| 		push @{$h{files}}, @paths if @paths; | |
| 	} | |
| 
 | |
| 	return edit(%h); | |
| }
 |