commit 043c6bbc3d2cef289b90ff7a8b4837d2e39bd205 from: Alexander Arkhipov date: Mon Feb 20 17:23:34 2023 UTC first version of rmc commit - 420c083d295615d49b59ac800cadc4c594c4a334 commit + 043c6bbc3d2cef289b90ff7a8b4837d2e39bd205 blob - /dev/null blob + 613a510a5f363b036bca0d728c1307e249d3fa77 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,30 @@ +include config.mk + +BIN = rmc +MAN = $(BIN:=.1) + +all: + @echo edit config.mk and type \`make install\' to install rmc + +install: + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp $(BIN) $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/$(BIN) + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + cp $(MAN) $(DESTDIR)$(MANPREFIX)/man1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(MAN) + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) + rm -f $(DESTDIR)$(MANPREFIX)/man1/$(MAN) + +clean: + -rm -f *.tar.gz + +dist: clean + mkdir -p rmc-$(VERSION) + cp -rf Makefile config.mk README $(MAN) $(BIN) rmc-$(VERSION) + tar cf - rmc-$(VERSION) | gzip >rmc-$(VERSION).tar.gz + rm -rf rmc-$(VERSION) + +.PHONY: install uninstall clean dist blob - /dev/null blob + b205c460e64be80f2da5bfd06a8776e939b8e3d9 (mode 644) --- /dev/null +++ README @@ -0,0 +1,3 @@ +rmc is a perl script that executes a program to view, edit, compose or print a +file, directory or url based on the mailcap and mime.types files. To install, +simply edit the config.mk file as necessary and run `make install'. blob - /dev/null blob + 06e1ad8748934dc390829dcb743c829e6b70f9fb (mode 644) --- /dev/null +++ config.mk @@ -0,0 +1,6 @@ +VERSION = 0.0 + +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/man +# Linux +#MANPREFIX = $(PREFIX)/share/man blob - /dev/null blob + 0f84bb064f496c86c4b0373e3660a4de9f0bd7fb (mode 755) --- /dev/null +++ rmc @@ -0,0 +1,291 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Getopt::Std; + +our ($opt_e, $opt_c, $opt_p) = 0; + +# usage() prints usage information to stderr and dies. +sub usage { + die "usage: $0 [-ecp] file\n"; +} + +# shquote($s) returns string $s with all shell metacharacters quoted. +sub shquote { + my $s = shift(@_); + $s =~ s/'/'\\''/g; + return "'$s'"; +} + +# gettyp ($file, $typfile) attempts to find a type corresponding to $file in +# mime.types file $typfile. Returns the type string on success, or empty +# string otherwise. +# +# mime.types format: +# - Empty lines, lines, containing only whitespace and lines whose first +# non-whitespace character is number-sign (#) are ignored; +# - The rest contain several whitespace-separated fields. +# - If the first field contains slash (/), it corresponds to the MIME type, and +# the others to extensions; +# - Otherwise, the first field is "URI type", and the others are schemes; +# - First match is used; +sub gettyp { + my ($file, $typfile) = @_; + my $ext = $1 if $file =~ /\.([^.]+)$/; + my $sch = $1 if $file =~ /^([^:]+):/; + defined $ext || defined $sch or return 0; + + open(FILE, '<', $typfile) or return 0; + while () { + my ($typ, @fld) = split; + for (@fld) { + if ((defined $sch && $typ =~ m=^scheme/.*= && + $_ eq $sch) || (defined $ext && $_ eq $ext)) { + close FILE; + return $typ; + } + } + } + + close FILE; + return ''; +} + +# docmd($file, $ln, $typ, [$styp]) processes line $ln from the cap file. If +# $styp is empty, and $typ is not "directory", $file is assumed to be a URL, +# otherwised it's processed as a normal file. On success executes a command and +# exits. +# +# Differences from RFC 1524: +# - Named parameters are ignored; +# - textualnewlines is ignored; +sub docmd { + my ($file, $ln, $typ, $styp) = @_; + $ln =~ s/^[ \t]*[^;]+;[ \t]*//; # remove the first field + + my ($quoted, $param, $nparam, $copious, $term, $isfile) = 0; + my ($view, $template, $test, $compose, $edit, $print) = ''; + my $qfile = shquote $file; + + $isfile = 1 unless $typ eq 'scheme'; + + local *donefld = sub { + my $s = shift(@_); + if (!length $view) { + $view = $s; + } elsif (lc($s) =~ /^needsterminal[ \t]*$/) { + $term = 1; + } elsif (lc($s) =~ /^copiousoutput[ \t]*$/) { + $copious = 1; + } elsif ($s =~ /^([^=]*)=(.*)/) { + my $name = lc($1); + if ($name eq "nametemplate") { + $template = $2; + } elsif ($name eq "test") { + $test = $2; + } elsif ($name eq "compose") { + $compose = $2; + } elsif ($name eq "edit") { + $edit = $2; + } elsif ($name eq "print") { + $print = $2; + } + } + }; + + local *expand = sub { + my ($ln, $file) = @_; + my $s = ''; + my ($quoted, $param, $nparam) = 0; + + for (my @chars = split(//, shift(@_))) { + if ($quoted && !$param) { + $s .= $_ unless $nparam; + $quoted = 0; + } elsif (!$quoted && $_ eq '\\') { + $quoted = 1; + } elsif ($param) { + if ($_ eq '{') { + $nparam = 1; + } elsif ($_ eq 's') { + $s .= $file; + } elsif ($typ && $_ eq 't') { + $s .= $typ; + $s .= "/$styp" if length $styp; + } + $param = $quoted = 0; + } elsif ($nparam) { + $nparam = 0 if $_ eq '}'; + } elsif ($_ eq '%') { + $param = 1; + } else { + $s .= $_; + } + } + + return $s; + }; + + local *rmtemp = sub { + length $template && $file =~ m=^/tmp/rmc.$$/= or return; + unlink $file or warn "$0: can't unlink file $file\n"; + rmdir "/tmp/rmc.$$" or + warn "$0: can't remove directory '/tmp/rmc.$$'\n"; + }; + + my $new = 1; + my $s = ''; + for (my @chars = split(//, $ln)) { + next if $new && /[ \t]/; + $new = 0; + + if ($quoted) { + $quoted = 0; + } elsif ($_ eq '\\') { + $quoted = 1; + } elsif ($_ eq ';') { + donefld($s); + $s = ''; + $new = 1; + next; + } + $s .= $_; + } + donefld($s) unless $ln =~ /;[ \t]*$/; + + return if length $test && system(expand($test, $qfile) . + ">/dev/null 2>&1") != 0; + + my $cmd; + if ($opt_c) { + length $compose or return; + $cmd = $compose; + } elsif ($opt_e) { + length $edit or return; + $cmd = $edit + } elsif ($opt_p) { + length $print or return; + $cmd = $print; + } else { + $cmd = $view; + } + + if ($isfile && length $template) { + mkdir("/tmp/rmc.$$", 0700) or + die "$0: can't create directory /tmp/rmc.$$\n"; + my $f = "/tmp/rmc.$$/" . expand($template, + $file =~ m=([^/]*)$= ? $1 : ''); + unless (symlink $file =~ m=^/= ? $f : "$ENV{PWD}/$file", $f) { + rmdir "/tmp/rmc.$$" or + warn "$0: can't remove directory /tmp/rmc.$$"; + die "$0: can't symlink $file to $f\n"; + } + $file = $f; + $qfile = shquote $file; + } + + $cmd = expand($cmd, $qfile); + + if ($copious && $term && !$opt_c && !$opt_e && !$opt_p) { + my $pager = $ENV{'PAGER'}; + length $pager or $pager = 'less'; + $cmd .= "|$pager"; + } + + my $dofork = 0; + if (!$term) { + $dofork = 1; + } elsif ($opt_p && !(-t && -t STDOUT)) { + $dofork = 1; + unless (exists $ENV{'DISPLAY'}) { + rmtemp(); + return; + } + my $terminal = $ENV{'TERMINAL'}; + length $terminal or $terminal = 'xterm'; + $cmd = "$terminal -e sh -c " . shquote $cmd; + } + $cmd .= ">/dev/null 2>&1" if $dofork; + + my $pid; + if ($dofork) { + $pid = fork; + defined $pid or die "fork: $!"; + } + if (!$dofork || !$pid) { + system $cmd; + rmtemp(); + } + exit; +} + +my $file; +my ($typ, $styp) = ''; + +usage if !getopts('ecp') || $#ARGV < 0; +$file = $ARGV[0]; + +my $typfile = $ENV{'MIMETYPES'}; +unless (length($typfile)) { + my $hometf = "$ENV{HOME}/.mime.types"; + $typfile = -f $hometf ? $hometf : "/etc/mime.types"; +} + +unless ($typ = gettyp $file, $typfile) { + if (-d $file) { + $typ = 'directory'; + } else { + my $qfile = shquote $file; + $typ = lc `file -i $qfile`; + } +} + +unless ($typ eq 'directory') { + die "bad type $typ\n" unless $typ =~ m=(.*: )?([^/]+)(/[^/]+)=; + $typ = $2; + $styp = $3; + $styp =~ s=^/==; +} + +my $mailcap = "$ENV{HOME}/.mailcap"; +open(FILE, '<', $mailcap) or next; +my ($cont, $match, $n) = 0; +my ($rec, $ctyp, $cstyp) = ''; + +while (my $ln = ) { + chomp $ln; + next if $ln =~ /^[ \t]*(#.*)?$/; + $n++; + + unless ($cont) { + $cont = 1 if $ln =~ /\\$/; + if ($ln =~ m=^[ \t]*([^;/]+)(/[^;]+)?;=) { + $ctyp = $1; + $cstyp = $2; + $cstyp =~ s=^/== if length $cstyp; + } else { + warn "$0: syntax error on line $n of $mailcap\n"; + next; + } + $match = 1 if $ctyp eq $typ && (!length $cstyp || + $cstyp eq "*" || $cstyp eq $styp); + } + + if ($ln =~ /^(.*)\\$/) { + $rec .= "$1 " if $match; + $cont = 1; + $ln = ; + redo; + } + + if ($match) { + $rec .= $ln; + docmd $file, $rec, $typ, $styp; + } +} continue { + $rec = ''; + $n = $match = $cont = 0; +} + +close FILE; blob - /dev/null blob + 80f325c3f15e09358d80738187331d4fd72f4861 (mode 644) --- /dev/null +++ rmc.1 @@ -0,0 +1,237 @@ +.Dd February 19, 2023 +.Dt RMC 1 +.Os +.Sh NAME +.Nm rmc +.Nd view, edit, compose or print files, directories and URLs. +.Sh SYNOPSIS +.Nm +.Op Fl ecp +.Ar file +.Sh DESCRIPTION +The +.Nm +command views, edits, composes or prints a local file, directory or URL. +In this manual page simply +.Dq file +to denote either of the three, while specifically +.Dq local file +means a file actually present in the local file hierarchy. Commands to +execute are chosen from the first matching entry in the mailcap file +(see +.Sx the mailcap file ) , +based on the local file/directory's MIME type or the URL's scheme. The +view-command of a matching mailcap entry is executed by default. File +extensions and URI schemes can be mapped via the mime.types file (see +.Sx the mime.types file ) . +If no such mapping is found, directories are automatically recognised to +have the pseudo-MIME type +.Dq directory , +and normal files are recognised using the +.Xr file 1 +command. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl e +Use the edit-command istead of the view-command. +.It Fl c +Use the compose-command istead of the view-command. +.It Fl p +Use the print-command istead of the view-command. +.Be +.El +.Ss The mailcap file +The mailcap file describes commands to execute depending on the file's +MIME type. Empty lines and lines, whose first non-whitespace character +is a number sign (#) are ignored. Other lines consist of fields, +separated by semicolons (;), optionally continued on the next line if +the last character is a backslash (\\). Backslashes may also be used for +quoting characters: if a character (including another backslash) is +preceeded by a backslash, it is stripped of its special meaning, and is +used literally. Semicolon following the last field is optional. +.Pp +There are two types of variables: single-letter variables, and named +variables. Named variables begin with +.Dq %{ +and end with +.Dq } . +Currently +.Nm +simply ignores such strings. +.Nm +recognises two single-letter variables: +.Bl -tag -width Ds +.Pp +.It %t +Is replaced with the file's MIME type. +.It %s +Is replaced with the file's name. +.El +.Pp +The first field always contains the MIME type to match against. Subtype +may be ommited by either only specifying the type, or writing an +asterisk (*) in subtype's place, in which case the subtype is +disregarded when matching. For example, consider these fields: +.Bd -literal -offset indent +text/* +text +text/plain +.Ed +.Pp +The first two fields will match all files with type +.Dq text +regardless of the subtype, while the last one will only match files, +whose MIME type is +.Dq text/plain . +.Pp +The second field is always the view-command. The others are either +flags or named fields. +.Pp +Flag fields are as follows: +.Bl -tag -width needsterminalx +.It needsterminal +If both the standard input and standard output are associated with a +terminal, the command is unchanged. Otherwise if +.Nm +is running under X11, the command is executed in a new terminal. +Finally, if neither is true, the match fails. Not used when executing +the print-command. +.It copiousoutput +If the view-command is being executed, the output is piped into a +pager. Only used when needsterminal is specified as well. +.El +.Pp +Named fields contain the name of the field, followed by the equality +sign (=), followed by the field's value, e.g.: +.Pp +.Dl edit=$VISUAL %s +.Pp +Following named fields are recognised: +.Bl -tag -width composex +.It nametemplate +If nametemplate is set, before executing the command, +.Nm +will create a symlink to file, named as the value of nametemplate. It +can be useful with programs that expect files to be named in a certain +way. Only used with local files. +.It test +The value of test is a command to be executed before the main command. +If this commands returns >0, the entry doesn't match. +.It compose +The compose-command. +.It edit +The edit-command. +.It print +The print-command. +.El +.Pp +If there is no named field for the requested command, the mailcap entry +doesn't match. Unrecognised fields are ignored. +.Ss The mime.types file +The mime.types file contains mappings of file extensions and URL schemes +to MIME types. Empty lines, and lines whose first non-whitespace +character is the number sign (#) are ignored. Other lines are divided +into a number of whitespace separated fields. The first field always +denotes a MIME type. If the type is +.Dq scheme , +the other fields correspond URL schemes; otherwise they denote +filename extensions. +.Pp +For example, given the entries +.Bd -literal -offset indent +scheme/http http https +text/html html htm +.Ed +.Pp +strings starting with +.Dq http: +or +.Dq https: +will be recognised as scheme/http; otherwise, strings ending with +.Dq .html +or +.Dq .htm +will be recognised as text/html. +.Sh ENVIRONMENT +.Bl -tag -width TERMINALX +.It Ev TERMINAL +Terminal command to use. Default is +.Dq xterm . +.It Ev PAGER +Pager command to use. Default is +.Dq less . +.El +.Sh FILES +.Bl -tag -width /etc/mime.typesx +.It Pa ~/.mailcap +The mailcap file. +.It Pa /etc/mime.types +System-wide MIME type mappings. Unused if +.Pa ~/.mime.types +is present. +.It Pa ~/.mime.types +User-specific MIME types mappings. +.El +.Sh EXAMPLES +With the following +.Pa ~/.mime.types : +.Bd -literal -offset indent +scheme/mailto mailto +text/org org +.Ed +.Pp +and the following +.Pa ~/.mailcap : +.Bd -literal -offset indent +scheme/mailto; guimail %s +text/org; less %s; needsterminal +text; cat %s; edit=vi %s; needsterminal +.Ed +.Pp +typing +.Pp +.Dl $ rmc mailto:user@site.org +.Pp +will execute the GUI program guimail with the argument +.Dq mailto:user@site.org , +while typing +.Pp +.Dl $ rmc ./mailto:user@site.org +.Pp +will execute +.Xr less 1 +with the arguments +.Dq -X +and +.Dq ./mailto:user@site.org , +and finally, typing +.Pp +.Dl $ rmc -e ./mailto:user@site.org +.Pp +will execute +.Xr vi 1 +with the argument +.Dq ./mailto:user@site.org . +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr file 1 +.Sh STANDARDS +.Rs +.%A N. Borenstein +.%D September 1993 +.%R RFC 1524 +.%T A User Agent Configuration Mechanism For Multimedia Mail Format Information +.Re +.Sh CAVEATS +According to RFC 1524 all files in the +.Ev MAILCAPS +or +.Pa ~/.mailcap : Ns Pa /etc/mailcap : Ns Pa /usr/etc/mailcap : Ns Pa /usr/local/etc/mailcap +path should be searched until a match is found. This behaviour is not +considered to be useful, intuitive, or secure, so only the +.Pa ~/.mailcap +file is searched. +.Sh AUTHORS +.An Alexander Arkhipov Aq Mt aa_src@manpager.net .