Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 81 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use std::{
io::{self, Write},
os::unix::prelude::CommandExt,
path::Path,
path::PathBuf,
process::{self, Command, ExitCode, Stdio},
};

use cache::{Cache, CacheEntry};
use clap::crate_version;
use clap::Parser;
use clap::{Args, Parser, Subcommand};
use log::{debug, error, trace};

fn pick(picker: &str, derivations: &[String]) -> Option<String> {
Expand Down Expand Up @@ -230,6 +231,15 @@ fn confirmer(run_cmd: &Command) -> bool {
}
}

fn open_manpage(manpage: &Path) -> Command {
let mut command = Command::new("man");

command.args([manpage.as_os_str()]);

trace!("run man: {command:?}");
command
}

fn main() -> ExitCode {
env_logger::init();

Expand Down Expand Up @@ -264,16 +274,19 @@ fn main() -> ExitCode {
}
}

if args.cmd.is_empty() {
if args.cmd.is_empty() && args.subcmds.is_none() {
return if args.empty_cache {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
};
}

let command = &args.cmd[0];
let trail = &args.cmd[1..];
let (command, trail) = if let Some(SubCmds::Man(ManArgs { ref cmd })) = args.subcmds {
(&cmd[0], &cmd[1..])
} else {
(&args.cmd[0], &args.cmd[1..])
};

if args.delete_entry {
if let Some(ref mut cache) = cache {
Expand Down Expand Up @@ -358,6 +371,50 @@ fn main() -> ExitCode {
&args.nixpkgs_flake,
);
println!("{path}");
} else if args.subcmds.is_some() {
debug!("Opening manpage for {command}");

let path = get_command_path_from_cache(
&mut cache,
&entry,
use_channel,
command,
&args.nixpkgs_flake,
);
let mut path: PathBuf = path.into();
// Truncate to
// /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaa/
path.pop();
path.pop();
// Push to
// /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaa/share/man/man1
// as that is where the program manfiles reside. There are a few
// that are at `share/man`, but they are just improperly placed
path.push("share/man/man1");
// /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaa/share/man/man1/thing
path.push(command);
// /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaa/share/man/man1/thing.1
path.set_extension("1");
match std::fs::exists(path.as_path()) {
// Open the manpage
Ok(true) => {
let mut cmd = open_manpage(&path);
// Replace this process with the man program
let err = cmd.exec();
// This code will only run if an error occurs
eprintln!("{err:?}");
return ExitCode::FAILURE;
}
// No manpage
Ok(false) => {
eprintln!("No manpage for \"{command}\" found.");
}
// Some error
Err(err) => {
eprintln!("{err:?}");
return ExitCode::FAILURE;
}
}
Comment on lines +375 to +417
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works
nix shell "nixpkgs#fish" --command man fish

Idk where in nix that is made to work
nix shell source code https://github.com/NixOS/nix/blob/master/src/nix/env.cc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting. The nix3 command works, but then something like nix-shell -p fish --run "man fish" does not work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you can run nix-shell -p hello --run "man hello, so it seems to depends on if the man page is in a seperate output or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently nix-shell -p package --run "man somepage" only works
occasionally and only due to a behavior of GNU's man-db that is not
documented: It tries to guess the man directory corresponding to every
entry in PATH which only works if the packages added to nix-shell have a
bin directory that gets added to PATH and is not supported by other
man page viewers like mandoc.

NixOS/nix#4642

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure how it works with separate outputs for nix3 though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I think it's because nix3 adds the man output as an output to install or something, because in the logs I saw that it was add the man output to the shell with nix3 in the verbose logs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, considering that it is an undocumented feature of the default manpager, I'm not convinced we should use that logic.

} else {
let mut run_cmd = run_command_from_cache(
&mut cache,
Expand All @@ -383,6 +440,7 @@ fn main() -> ExitCode {
/// Runs programs without installing them
#[derive(Parser)]
#[clap(version = crate_version!(), trailing_var_arg = true)]
#[command(subcommand_negates_reqs = true)]
struct Opt {
/// Generate the man page, then exit
#[clap(long, hide = true)]
Expand Down Expand Up @@ -438,4 +496,23 @@ struct Opt {
/// Command to run
#[clap(required_unless_present_any = ["empty_cache", "mangen"], name = "cmd")]
cmd: Vec<String>,

#[clap(subcommand)]
subcmds: Option<SubCmds>,
}

#[derive(Subcommand)]
#[clap(disable_help_subcommand = true)]
enum SubCmds {
/// Show the manpage if it exists instead of running the executable
///
/// Currently only supports Section 1 pages for programs.
Man(ManArgs),
}

#[derive(Args)]
struct ManArgs {
/// Command to show manpage for
#[clap(required = true, name = "cmd")]
cmd: Vec<String>,
}