diff --git a/modules/default.nix b/modules/default.nix index cb3b9f14f..1c569ef6b 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -19,6 +19,7 @@ let ./programs/bash.nix ./programs/beets.nix ./programs/browserpass.nix + ./programs/command-not-found/command-not-found.nix ./programs/eclipse.nix ./programs/emacs.nix ./programs/feh.nix diff --git a/modules/misc/news.nix b/modules/misc/news.nix index b52b2b2cd..70b320d2a 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -222,6 +222,16 @@ in }; ''; } + + { + time = "2017-09-27T07:28:54+00:00"; + message = '' + A new program module is available: 'programs.command-not-found'. + + Note, this differs from the NixOS system command-not-found + tool in that NIX_AUTO_INSTALL is not supported. + ''; + } ]; }; } diff --git a/modules/programs/command-not-found/command-not-found.nix b/modules/programs/command-not-found/command-not-found.nix new file mode 100644 index 000000000..0053fe36a --- /dev/null +++ b/modules/programs/command-not-found/command-not-found.nix @@ -0,0 +1,57 @@ +# Adapted from Nixpkgs. + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.command-not-found; + commandNotFound = pkgs.substituteAll { + name = "command-not-found"; + dir = "bin"; + src = ./command-not-found.pl; + isExecutable = true; + inherit (pkgs) perl; + inherit (cfg) dbPath; + perlFlags = concatStrings (map (path: "-I ${path}/lib/perl5/site_perl ") + [ pkgs.perlPackages.DBI pkgs.perlPackages.DBDSQLite pkgs.perlPackages.StringShellQuote ]); + }; + + shInit = commandNotFoundHandlerName: '' + # This function is called whenever a command is not found. + ${commandNotFoundHandlerName}() { + local p=${commandNotFound}/bin/command-not-found + if [ -x $p -a -f ${cfg.dbPath} ]; then + # Run the helper program. + $p "$@" + else + echo "$1: command not found" >&2 + return 127 + fi + } + ''; + +in + +{ + options.programs.command-not-found = { + enable = mkEnableOption "command-not-found hook for interactive shell"; + + dbPath = mkOption { + default = "/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite" ; + description = '' + Absolute path to programs.sqlite. By + default this file will be provided by your channel + (nixexprs.tar.xz). + ''; + type = types.path; + }; + }; + + config = mkIf cfg.enable { + programs.bash.initExtra = shInit "command_not_found_handle"; + programs.zsh.initExtra = shInit "command_not_found_handler"; + + home.packages = [ commandNotFound ]; + }; +} diff --git a/modules/programs/command-not-found/command-not-found.pl b/modules/programs/command-not-found/command-not-found.pl new file mode 100644 index 000000000..997dfec64 --- /dev/null +++ b/modules/programs/command-not-found/command-not-found.pl @@ -0,0 +1,44 @@ +#! @perl@/bin/perl -w @perlFlags@ + +use strict; +use DBI; +use DBD::SQLite; +use String::ShellQuote; +use Config; + +my $program = $ARGV[0]; + +my $dbPath = "@dbPath@"; + +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_RUN"} // "") { + exec("nix-shell", "-p", $package, "--run", shell_quote("exec", @ARGV)); + } else { + print STDERR <{package}\n" foreach @$res; +} + +exit 127;