feat: add info command line subcommand (#1330)

* feat: add `info` command line subcommand

Adding an info command allows the documentation to refer to it when
mentioning platform specific information. This gives users a nicer
experience since they don't need to think about how `ncspot` will behave
on their system, but can run `ncspot info` to get that information.

* fix: info command don't create runtime directory

* fix: don't print runtime path on Windows

Windows doesn't use the runtime path so it shouldn't be printed there.

* fix: make `info` command easier to parse

* docs: add back the default configuration directory
This commit is contained in:
Thomas Frans
2023-11-27 08:43:55 +01:00
committed by GitHub
parent 0c9be11357
commit 0cee99ba4c
10 changed files with 110 additions and 54 deletions

35
src/cli.rs Normal file
View File

@@ -0,0 +1,35 @@
use crate::config::{user_cache_directory, user_configuration_directory};
/// Print platform info like which platform directories will be used.
pub fn info() -> Result<(), String> {
let user_configuration_directory = user_configuration_directory();
let user_cache_directory = user_cache_directory();
println!(
"USER_CONFIGURATION_PATH {}",
user_configuration_directory
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("not found".into())
);
println!(
"USER_CACHE_PATH {}",
user_cache_directory
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("not found".into())
);
#[cfg(unix)]
{
use crate::utils::user_runtime_directory;
let user_runtime_directory = user_runtime_directory();
println!(
"USER_RUNTIME_PATH {}",
user_runtime_directory
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("not found".into())
);
}
Ok(())
}

View File

@@ -5,6 +5,7 @@ use std::{fs, process};
use cursive::theme::Theme;
use log::{debug, error};
use ncspot::CONFIGURATION_FILE_NAME;
use platform_dirs::AppDirs;
use crate::command::{SortDirection, SortKey};
@@ -192,7 +193,7 @@ impl Config {
/// Generate the configuration from the user configuration file and the runtime state file.
/// `filename` can be used to look for a differently named configuration file.
pub fn new(filename: Option<String>) -> Self {
let filename = filename.unwrap_or("config.toml".to_owned());
let filename = filename.unwrap_or(CONFIGURATION_FILE_NAME.to_owned());
let values = load(&filename).unwrap_or_else(|e| {
eprintln!("could not load config: {e}");
process::exit(1);
@@ -268,16 +269,6 @@ fn load(filename: &str) -> Result<ConfigValues, String> {
TOML.load_or_generate_default(path, || Ok(ConfigValues::default()), false)
}
/// Returns the platform app directories for ncspot.
///
/// # Panics
///
/// This panics if the project directories could not be determined. Use `try_proj_dirs` for a
/// non-panicking version.
fn proj_dirs() -> AppDirs {
try_proj_dirs().unwrap()
}
/// Returns the plaform app directories for ncspot if they could be determined,
/// or an error otherwise.
pub fn try_proj_dirs() -> Result<AppDirs, String> {
@@ -296,19 +287,32 @@ pub fn try_proj_dirs() -> Result<AppDirs, String> {
}
}
/// Return the path to the current user's configuration directory, or None if it couldn't be found.
/// This function does not guarantee correct permissions or ownership of the directory!
pub fn user_configuration_directory() -> Option<PathBuf> {
let project_directories = try_proj_dirs().ok()?;
Some(project_directories.config_dir)
}
/// Return the path to the current user's cache directory, or None if one couldn't be found. This
/// function does not guarantee correct permissions or ownership of the directory!
pub fn user_cache_directory() -> Option<PathBuf> {
let project_directories = try_proj_dirs().ok()?;
Some(project_directories.cache_dir)
}
/// Force create the configuration directory at the default project location, removing anything that
/// isn't a directory but has the same name. Return the path to the configuration file inside the
/// directory.
///
/// This doesn't create the file, only the containing directory.
pub fn config_path(file: &str) -> PathBuf {
let proj_dirs = proj_dirs();
let cfg_dir = &proj_dirs.config_dir;
let cfg_dir = user_configuration_directory().unwrap();
if cfg_dir.exists() && !cfg_dir.is_dir() {
fs::remove_file(cfg_dir).expect("unable to remove old config file");
fs::remove_file(&cfg_dir).expect("unable to remove old config file");
}
if !cfg_dir.exists() {
fs::create_dir_all(cfg_dir).expect("can't create config folder");
fs::create_dir_all(&cfg_dir).expect("can't create config folder");
}
let mut cfg = cfg_dir.to_path_buf();
cfg.push(file);
@@ -320,10 +324,9 @@ pub fn config_path(file: &str) -> PathBuf {
///
/// This doesn't create the file, only the containing directory.
pub fn cache_path(file: &str) -> PathBuf {
let proj_dirs = proj_dirs();
let cache_dir = &proj_dirs.cache_dir;
let cache_dir = user_cache_directory().unwrap();
if !cache_dir.exists() {
fs::create_dir_all(cache_dir).expect("can't create cache folder");
fs::create_dir_all(&cache_dir).expect("can't create cache folder");
}
let mut pb = cache_dir.to_path_buf();
pb.push(file);

View File

@@ -3,6 +3,7 @@ use librespot_playback::audio_backend;
pub const AUTHOR: &str = "Henrik Friedrichsen <henrik@affekt.org> and contributors";
pub const BIN_NAME: &str = "ncspot";
pub const CONFIGURATION_FILE_NAME: &str = "config.toml";
/// Return the [Command](clap::Command) that models the program's command line arguments. The
/// command can be used to parse the actual arguments passed to the program, or to automatically
@@ -40,6 +41,7 @@ pub fn program_arguments() -> clap::Command {
.long("config")
.value_name("FILE")
.help("Filename of config file in basepath")
.default_value("config.toml"),
.default_value(CONFIGURATION_FILE_NAME),
)
.subcommands([clap::Command::new("info").about("Print platform information like paths")])
}

View File

@@ -13,6 +13,7 @@ use ncspot::program_arguments;
mod application;
mod authentication;
mod cli;
mod command;
mod commands;
mod config;
@@ -56,9 +57,15 @@ fn main() -> Result<(), String> {
// path.
set_configuration_base_path(matches.get_one::<PathBuf>("basepath").cloned());
// Create the application.
let mut application = Application::new(matches.get_one::<String>("config").cloned())?;
match matches.subcommand() {
Some(("info", _subcommand_matches)) => cli::info(),
Some((_, _)) => unreachable!(),
None => {
// Create the application.
let mut application = Application::new(matches.get_one::<String>("config").cloned())?;
// Start the application event loop.
application.run()
// Start the application event loop.
application.run()
}
}
}

View File

@@ -5,6 +5,7 @@ use cursive::utils::markup::StyledString;
use cursive::view::ViewWrapper;
use cursive::views::{ScrollView, TextView};
use cursive::Cursive;
use ncspot::CONFIGURATION_FILE_NAME;
use crate::command::{Command, MoveAmount, MoveMode};
use crate::commands::CommandResult;
@@ -22,7 +23,9 @@ impl HelpView {
let note = format!(
"Custom bindings can be set in {} within the [keybindings] section.\n\n",
config_path("config.toml").to_str().unwrap_or_default()
config_path(CONFIGURATION_FILE_NAME)
.to_str()
.unwrap_or_default()
);
text.append(StyledString::styled(note, Effect::Italic));

View File

@@ -72,20 +72,7 @@ pub fn create_runtime_directory() -> Result<PathBuf, Box<dyn std::error::Error>>
os::unix::prelude::PermissionsExt,
};
let linux_runtime_directory =
PathBuf::from(format!("/run/user/{}/", unsafe { libc::getuid() }));
let unix_runtime_directory = PathBuf::from("/tmp/");
let user_runtime_directory = if let Some(xdg_runtime_directory) = xdg_runtime_directory() {
Some(xdg_runtime_directory.join("ncspot"))
} else if cfg!(linux) && linux_runtime_directory.exists() {
Some(linux_runtime_directory.join("ncspot"))
} else if unix_runtime_directory.exists() {
Some(unix_runtime_directory.join(format!("ncspot-{}", unsafe { libc::getuid() })))
} else {
None
}
.ok_or("no runtime directory found")?;
let user_runtime_directory = user_runtime_directory().ok_or("no runtime directory found")?;
let creation_result = fs::create_dir(&user_runtime_directory);
@@ -106,6 +93,25 @@ pub fn create_runtime_directory() -> Result<PathBuf, Box<dyn std::error::Error>>
}
}
/// Return the path to the current user's runtime directory, or None if it couldn't be found.
/// This function does not guarantee correct ownership or permissions of the directory.
#[cfg(unix)]
pub fn user_runtime_directory() -> Option<PathBuf> {
let linux_runtime_directory =
PathBuf::from(format!("/run/user/{}/", unsafe { libc::getuid() }));
let unix_runtime_directory = PathBuf::from("/tmp/");
if let Some(xdg_runtime_directory) = xdg_runtime_directory() {
Some(xdg_runtime_directory.join("ncspot"))
} else if cfg!(linux) && linux_runtime_directory.exists() {
Some(linux_runtime_directory.join("ncspot"))
} else if unix_runtime_directory.exists() {
Some(unix_runtime_directory.join(format!("ncspot-{}", unsafe { libc::getuid() })))
} else {
None
}
}
#[cfg(unix)]
fn xdg_runtime_directory() -> Option<PathBuf> {
std::env::var("XDG_RUNTIME_DIR").ok().map(Into::into)