You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

317 lines
4.9 KiB

5 days ago
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use constant
  5. {
  6. PROG_EDIT => 'vim',
  7. };
  8. sub open_smart;
  9. sub edit;
  10. sub stdin_to_editor;
  11. sub reveal;
  12. sub header_edit;
  13. sub wait_or_not;
  14. sub usage
  15. {
  16. print STDERR <<"!";
  17. Usage: $0 -[efWwRh]
  18. -e: edit
  19. -f: stdin-edit
  20. -W: wait for exit (true by default for editing)
  21. -w: don't wait for exit
  22. -R: reveal
  23. -h: header search
  24. !
  25. exit 1;
  26. }
  27. my $cmd = \&open_smart;
  28. my(@files, @args);
  29. my %opts = (
  30. 'e' => 0,
  31. 'f' => 0,
  32. 'W' => 0,
  33. 'R' => 0,
  34. 'h' => 0,
  35. );
  36. my $wait_set = 0;
  37. usage() unless @ARGV;
  38. for(my $i = 0; $i < @ARGV; ++$i){
  39. $_ = $ARGV[$i];
  40. if($_ eq '--'){
  41. push @files, @ARGV[$i + 1 .. $#ARGV];
  42. last;
  43. }
  44. if(/^-([a-z])$/i){
  45. my $k = $1;
  46. if(exists $opts{$k}){
  47. $opts{$k} = 1;
  48. $wait_set = 1 if $k eq 'W';
  49. }elsif($k eq 'w'){
  50. $opts{W} = 0;
  51. $wait_set = 1;
  52. }else{
  53. usage();
  54. }
  55. }elsif($_ eq '--args'){
  56. push @args, @ARGV[$i + 1 .. $#ARGV];
  57. last;
  58. }elsif(/^-/){
  59. usage();
  60. }else{
  61. push @files, $_;
  62. }
  63. }
  64. if($opts{e} + $opts{f} + $opts{R} + $opts{h} > 1){
  65. print STDERR "Can't combine -e, -f, -R and -h\n";
  66. usage();
  67. }
  68. my $should_wait = 1;
  69. if($opts{e}){
  70. $cmd = \&edit;
  71. }elsif($opts{f}){
  72. # <STDIN> | $EDITOR -
  73. $cmd = \&stdin_to_editor;
  74. }elsif($opts{R}){
  75. # open with rox
  76. $cmd = \&reveal;
  77. $should_wait = 0;
  78. }elsif($opts{h}){
  79. # search /usr/include/$_ for @files
  80. $cmd = \&header_edit;
  81. }
  82. $opts{W} = 1 if $should_wait and not $wait_set;
  83. exit(&{$cmd}((
  84. wait => !!$opts{W},
  85. args => [@args],
  86. files => [@files])));
  87. # end ---
  88. sub read_maps
  89. {
  90. my $rc = "$ENV{HOME}/.openrc";
  91. open F, '<', $rc or die "open $rc: $!\n";
  92. my %maps;
  93. my $prog_reveal = '';
  94. my $mode = 0;
  95. while(<F>){
  96. chomp;
  97. s/#.*//;
  98. if(/^\[(.*)\]$/){
  99. if($1 eq 'full'){
  100. $mode = $1;
  101. }elsif($1 eq 'suffix'){
  102. $mode = $1;
  103. }elsif($1 eq 'directories'){
  104. $mode = $1;
  105. }else{
  106. die "invalid section \"$1\" in $rc\n";
  107. }
  108. }elsif(!($mode eq 'directories') and my($prog, $matches) = /^([^:]+): *(.*)/){
  109. sub getenv
  110. {
  111. my $k = shift;
  112. return $ENV{$k} if $ENV{$k};
  113. my %backup = (
  114. "TERM" => "urxvt",
  115. "VISUAL" => "vim",
  116. );
  117. return $backup{$k} if $backup{$k};
  118. return "\$$k";
  119. }
  120. my @matches = split / *, */, $matches;
  121. $prog =~ s/\$([A-Z_]+)/getenv($1)/e;
  122. my $key = $prog;
  123. if($mode eq 'suffix'){
  124. # compare file extensions case insensitively
  125. $key = lc $key;
  126. }
  127. push @{$maps{$key}}, [ $_, $mode ] for @matches;
  128. }elsif($mode eq 'directories' && length){
  129. if(length($prog_reveal)){
  130. die "already have dir program, in $rc\n";
  131. }
  132. $prog_reveal = $_;
  133. }elsif(length){
  134. die "invalid confiuration line: \"$1\" in $rc\n";
  135. }
  136. }
  137. if(!length($prog_reveal)){
  138. die "no directory program specified, in $rc\n";
  139. }
  140. close F;
  141. return $prog_reveal, \%maps;
  142. }
  143. sub open_smart
  144. {
  145. my $ec = 0;
  146. my %h = @_;
  147. my @to_open;
  148. my ($prog_reveal, $maps) = read_maps();
  149. file:
  150. for my $fnam (@{$h{files}}){
  151. #print "maps:\n";
  152. if(-d $fnam){
  153. push @to_open, [($h{wait}, $prog_reveal, @{$h{args}}, $fnam)];
  154. next file;
  155. }
  156. (my $fnam_for_test = $fnam) =~ s/\.[a-zA-Z]+$/lc($&)/e;
  157. for my $prog (keys %$maps){
  158. #print " $_:\n";
  159. for(@{$maps->{$prog}}){
  160. my($reg, $mode) = ($_->[0], $_->[1]);
  161. if($mode eq 'suffix'){
  162. $reg = "\\.$reg\$";
  163. }
  164. #print " $reg\n"
  165. if($fnam_for_test =~ /$reg/){
  166. push @to_open, [($h{wait}, $prog, @{$h{args}}, $fnam)];
  167. next file;
  168. }
  169. }
  170. }
  171. die "no program found for $fnam\n";
  172. }
  173. wait_or_not(@{$_}) for @to_open;
  174. return 0;
  175. }
  176. sub wait_or_not
  177. {
  178. my($wait, @rest) = @_;
  179. my $pid = fork();
  180. die "fork(): $!\n" unless defined $pid;
  181. if($pid == 0){
  182. if($rest[0] =~ / /){
  183. my $a = shift @rest;
  184. unshift @rest, split / +/, $a;
  185. }
  186. exec @rest;
  187. die;
  188. }else{
  189. # parent
  190. if($wait){
  191. my $reaped = wait();
  192. my $ret = $?;
  193. die "wait(): $!\n" if $reaped == -1;
  194. warn "unexpected dead child $reaped (expected $pid)\n" if $reaped != $pid;
  195. return $ret;
  196. }
  197. }
  198. }
  199. sub edit
  200. {
  201. my %h = @_;
  202. my $e = $ENV{VISUAL} || $ENV{EDITOR} || PROG_EDIT;
  203. return wait_or_not($h{wait}, $e, @{$h{args}}, @{$h{files}});
  204. }
  205. sub stdin_to_editor
  206. {
  207. my $tmp = "/tmp/stdin_$$";
  208. open F, '>', $tmp or die "open $tmp: $!\n";
  209. print F $_ while <STDIN>;
  210. close F;
  211. my %h = @_;
  212. push @{$h{files}}, $tmp;
  213. my $r = edit(%h);
  214. unlink $tmp;
  215. return $r;
  216. }
  217. sub reveal
  218. {
  219. my %h = @_;
  220. my ($prog_reveal) = read_maps();
  221. return wait_or_not($h{wait}, $prog_reveal, @{$h{args}}, @{$h{files}});
  222. }
  223. sub header_edit
  224. {
  225. my %h = @_;
  226. my @files = @{$h{files}};
  227. @{$h{files}} = ();
  228. for my $name (@files){
  229. sub find_header
  230. {
  231. my @inc = ("", "arpa", "net", "sys");
  232. my $r = shift;
  233. my @matches;
  234. for(my @tmp = @inc){
  235. push @inc, "x86_64-linux-gnu/$_";
  236. }
  237. for my $inc (@inc){
  238. $inc = "/usr/include/$inc";
  239. opendir D, $inc or next;
  240. push @matches, map { "$inc/$_" } grep /$r/, readdir D;
  241. closedir D;
  242. }
  243. return @matches;
  244. }
  245. my @paths = find_header($name);
  246. push @{$h{files}}, @paths if @paths;
  247. }
  248. return edit(%h);
  249. }