From b89f941b2074b104be303c3e7e47cf4a97c3128d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 30 Jan 2013 14:42:30 +0100 Subject: [PATCH] Provide missing command suggestions, Ubuntu style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the user tries to run a program that doesn't exist from Bash, the program name is looked up in a database that maps to Nix package names. If it is found, we print out a message like: $ pdflatex The program ‘pdflatex’ is currently not installed. It is provided by several packages. You can install it by typing one of the following: nix-env -i tetex nix-env -i texlive-core If the environment variable $NIX_AUTO_INSTALL is set, the command is installed and executed automatically: $ hello --version The program ‘hello’ is currently not installed. It is provided by the package ‘hello’, which I will now install for you. installing `hello-2.8' hello (GNU hello) 2.8 Copyright (C) 2011 Free Software Foundation, Inc. ... To use this, you must currently manually put the SQLite programs database in /var/lib/nixos/programs.sqlite. In the future, this file should be provided as part of the NixOS channel so it gets updated automatically. To get a test version: $ curl http://nixos.org/~eelco/programs.sqlite.xz | xz -d > /var/lib/nixos/programs.sqlite --- modules/module-list.nix | 1 + modules/programs/bash/bash.nix | 2 +- modules/programs/bash/command-not-found.nix | 48 +++++++++++++++++++++ modules/programs/bash/command-not-found.pl | 48 +++++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 modules/programs/bash/command-not-found.nix create mode 100644 modules/programs/bash/command-not-found.pl diff --git a/modules/module-list.nix b/modules/module-list.nix index 9ad65594f4de..45facbfd1c70 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -36,6 +36,7 @@ ./misc/passthru.nix ./misc/version.nix ./programs/bash/bash.nix + ./programs/bash/command-not-found.nix ./programs/blcr.nix ./programs/info.nix ./programs/shadow.nix diff --git a/modules/programs/bash/bash.nix b/modules/programs/bash/bash.nix index 4f227ad1a27e..493df762e171 100644 --- a/modules/programs/bash/bash.nix +++ b/modules/programs/bash/bash.nix @@ -33,7 +33,7 @@ let options = { - environment.promptInit = mkOption { + environment.promptInit = mkOption { default = '' # Provide a nice prompt. PROMPT_COLOR="1;31m" diff --git a/modules/programs/bash/command-not-found.nix b/modules/programs/bash/command-not-found.nix new file mode 100644 index 000000000000..1aecd9051e51 --- /dev/null +++ b/modules/programs/bash/command-not-found.nix @@ -0,0 +1,48 @@ +# This module provides suggestions of packages to install if the user +# tries to run a missing command in Bash. This is implemented using a +# SQLite database that maps program names to Nix package names (e.g., +# "pdflatex" is mapped to "tetex"). + +{ config, pkgs, ... }: + +with pkgs.lib; + +let + + commandNotFound = pkgs.substituteAll { + name = "command-not-found"; + dir = "bin"; + src = ./command-not-found.pl; + isExecutable = true; + inherit (pkgs) perl; + perlFlags = concatStrings (map (path: "-I ${path}/lib/perl5/site_perl ") + [ pkgs.perlPackages.DBI pkgs.perlPackages.DBDSQLite ]); + }; + +in + +{ + + environment.interactiveShellInit = + '' + # This function is called whenever a command is not found. + command_not_found_handle() { + local p=/run/current-system/sw/bin/command-not-found + if [ -x $p -a -f /var/lib/nixos/programs.sqlite ]; then + # Run the helper program. + $p "$1" + # Retry the command if we just installed it. + if [ $? = 126 ]; then + "$@" + fi + else + echo "$1: command not found" >&2 + fi + } + ''; + + environment.systemPackages = [ commandNotFound ]; + + # TODO: tab completion for uninstalled commands! :-) + +} diff --git a/modules/programs/bash/command-not-found.pl b/modules/programs/bash/command-not-found.pl new file mode 100644 index 000000000000..b497b05d33ed --- /dev/null +++ b/modules/programs/bash/command-not-found.pl @@ -0,0 +1,48 @@ +#! @perl@/bin/perl -w @perlFlags@ + +use strict; +use DBI; +use DBD::SQLite; +use Config; + +my $program = $ARGV[0]; + +my $dbPath = "/var/lib/nixos/programs.sqlite"; + +my $dbh = DBI->connect("dbi:SQLite:dbname=$dbPath", "", "") + or die "cannot open database `$dbPath'"; +$dbh->{RaiseError} = 0; +$dbh->{PrintError} = 0; + +my $system = $ENV{"NIX_SYSTEM"} // $Config{myarchname}; + +my $res = $dbh->selectall_arrayref( + "select package from Programs where system = ? and name = ?", + { Slice => {} }, $system, $program); + +if (!defined $res || scalar @$res == 0) { + print STDERR "$program: command not found\n"; +} elsif (scalar @$res == 1) { + my $package = @$res[0]->{package}; + if ($ENV{"NIX_AUTO_INSTALL"} // "") { + print STDERR <{package}\n" foreach @$res; +} + +exit 127;