Commit Diff


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 (<FILE>) {
+		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 = <FILE>) {
+	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 = <FILE>;
+		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 .