mirror of https://github.com/chipsenkbeil/distant
Refactor into unified settings (#172)
parent
093b4d2ec4
commit
4b983b0229
@ -1,106 +0,0 @@
|
||||
use crate::{data::Cmd, DistantMsg, DistantRequestData};
|
||||
use clap::{
|
||||
error::{Error, ErrorKind},
|
||||
Arg, ArgAction, ArgMatches, Args, Command, FromArgMatches, Subcommand,
|
||||
};
|
||||
|
||||
impl FromArgMatches for Cmd {
|
||||
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, Error> {
|
||||
let mut matches = matches.clone();
|
||||
Self::from_arg_matches_mut(&mut matches)
|
||||
}
|
||||
fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result<Self, Error> {
|
||||
let cmd = matches.get_one::<String>("cmd").ok_or_else(|| {
|
||||
Error::raw(
|
||||
ErrorKind::MissingRequiredArgument,
|
||||
"program must be specified",
|
||||
)
|
||||
})?;
|
||||
let args: Vec<String> = matches
|
||||
.get_many::<String>("arg")
|
||||
.unwrap_or_default()
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
Ok(Self::new(format!("{cmd} {}", args.join(" "))))
|
||||
}
|
||||
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> {
|
||||
let mut matches = matches.clone();
|
||||
self.update_from_arg_matches_mut(&mut matches)
|
||||
}
|
||||
fn update_from_arg_matches_mut(&mut self, _matches: &mut ArgMatches) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Args for Cmd {
|
||||
fn augment_args(cmd: Command) -> Command {
|
||||
cmd.arg(
|
||||
Arg::new("cmd")
|
||||
.required(true)
|
||||
.value_name("CMD")
|
||||
.action(ArgAction::Set),
|
||||
)
|
||||
.trailing_var_arg(true)
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.value_name("ARGS")
|
||||
.num_args(1..)
|
||||
.action(ArgAction::Append),
|
||||
)
|
||||
}
|
||||
fn augment_args_for_update(cmd: Command) -> Command {
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArgMatches for DistantMsg<DistantRequestData> {
|
||||
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, Error> {
|
||||
match matches.subcommand() {
|
||||
Some(("single", args)) => Ok(Self::Single(DistantRequestData::from_arg_matches(args)?)),
|
||||
Some((_, _)) => Err(Error::raw(
|
||||
ErrorKind::InvalidSubcommand,
|
||||
"Valid subcommand is `single`",
|
||||
)),
|
||||
None => Err(Error::raw(
|
||||
ErrorKind::MissingSubcommand,
|
||||
"Valid subcommand is `single`",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> {
|
||||
match matches.subcommand() {
|
||||
Some(("single", args)) => {
|
||||
*self = Self::Single(DistantRequestData::from_arg_matches(args)?)
|
||||
}
|
||||
Some((_, _)) => {
|
||||
return Err(Error::raw(
|
||||
ErrorKind::InvalidSubcommand,
|
||||
"Valid subcommand is `single`",
|
||||
))
|
||||
}
|
||||
None => (),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Subcommand for DistantMsg<DistantRequestData> {
|
||||
fn augment_subcommands(cmd: Command) -> Command {
|
||||
cmd.subcommand(DistantRequestData::augment_subcommands(Command::new(
|
||||
"single",
|
||||
)))
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
fn augment_subcommands_for_update(cmd: Command) -> Command {
|
||||
cmd.subcommand(DistantRequestData::augment_subcommands(Command::new(
|
||||
"single",
|
||||
)))
|
||||
.subcommand_required(true)
|
||||
}
|
||||
|
||||
fn has_subcommand(name: &str) -> bool {
|
||||
matches!(name, "single")
|
||||
}
|
||||
}
|
@ -1,26 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
|
||||
mod client;
|
||||
mod generate;
|
||||
mod manager;
|
||||
mod server;
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum DistantSubcommand {
|
||||
/// Perform client commands
|
||||
#[clap(subcommand)]
|
||||
Client(client::ClientSubcommand),
|
||||
|
||||
/// Perform manager commands
|
||||
#[clap(subcommand)]
|
||||
Manager(manager::ManagerSubcommand),
|
||||
|
||||
/// Perform server commands
|
||||
#[clap(subcommand)]
|
||||
Server(server::ServerSubcommand),
|
||||
|
||||
/// Perform generation commands
|
||||
#[clap(subcommand)]
|
||||
Generate(generate::GenerateSubcommand),
|
||||
}
|
||||
pub mod client;
|
||||
mod common;
|
||||
pub mod generate;
|
||||
pub mod manager;
|
||||
pub mod server;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,8 @@
|
||||
mod buf;
|
||||
mod format;
|
||||
mod link;
|
||||
pub mod stdin;
|
||||
|
||||
pub use buf::*;
|
||||
pub use format::*;
|
||||
pub use link::*;
|
@ -1,107 +1,72 @@
|
||||
use crate::{
|
||||
cli::Opt,
|
||||
config::{Config, GenerateConfig},
|
||||
CliResult,
|
||||
};
|
||||
use crate::options::{Config, GenerateSubcommand};
|
||||
use crate::{CliResult, Options};
|
||||
use anyhow::Context;
|
||||
use clap::{CommandFactory, Subcommand};
|
||||
use clap_complete::{generate as clap_generate, Shell};
|
||||
use distant_core::{
|
||||
net::common::{Request, Response},
|
||||
DistantMsg, DistantRequestData, DistantResponseData,
|
||||
};
|
||||
use std::{fs, io, path::PathBuf};
|
||||
use clap::CommandFactory;
|
||||
use clap_complete::generate as clap_generate;
|
||||
use distant_core::net::common::{Request, Response};
|
||||
use distant_core::{DistantMsg, DistantRequestData, DistantResponseData};
|
||||
use std::{fs, io};
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum GenerateSubcommand {
|
||||
/// Generate configuration file with base settings
|
||||
Config {
|
||||
/// Path to where the configuration file should be created
|
||||
file: PathBuf,
|
||||
},
|
||||
|
||||
/// Generate JSON schema for server request/response
|
||||
Schema {
|
||||
/// If specified, will output to the file at the given path instead of stdout
|
||||
#[clap(long)]
|
||||
file: Option<PathBuf>,
|
||||
},
|
||||
|
||||
// Generate completion info for CLI
|
||||
Completion {
|
||||
/// If specified, will output to the file at the given path instead of stdout
|
||||
#[clap(long)]
|
||||
file: Option<PathBuf>,
|
||||
|
||||
/// Specific shell to target for the generated output
|
||||
#[clap(value_enum, value_parser)]
|
||||
shell: Shell,
|
||||
},
|
||||
pub fn run(cmd: GenerateSubcommand) -> CliResult {
|
||||
let rt = tokio::runtime::Runtime::new().context("Failed to start up runtime")?;
|
||||
rt.block_on(async_run(cmd))
|
||||
}
|
||||
|
||||
impl GenerateSubcommand {
|
||||
pub fn run(self, _config: GenerateConfig) -> CliResult {
|
||||
let rt = tokio::runtime::Runtime::new().context("Failed to start up runtime")?;
|
||||
rt.block_on(Self::async_run(self))
|
||||
}
|
||||
|
||||
async fn async_run(self) -> CliResult {
|
||||
match self {
|
||||
Self::Config { file } => tokio::fs::write(file, Config::default_raw_str())
|
||||
.await
|
||||
.context("Failed to write default config to {file:?}")?,
|
||||
async fn async_run(cmd: GenerateSubcommand) -> CliResult {
|
||||
match cmd {
|
||||
GenerateSubcommand::Config { file } => tokio::fs::write(file, Config::default_raw_str())
|
||||
.await
|
||||
.context("Failed to write default config to {file:?}")?,
|
||||
|
||||
Self::Schema { file } => {
|
||||
let request_schema =
|
||||
serde_json::to_value(&Request::<DistantMsg<DistantRequestData>>::root_schema())
|
||||
.context("Failed to serialize request schema")?;
|
||||
let response_schema = serde_json::to_value(&Response::<
|
||||
DistantMsg<DistantResponseData>,
|
||||
>::root_schema())
|
||||
.context("Failed to serialize response schema")?;
|
||||
GenerateSubcommand::Schema { file } => {
|
||||
let request_schema =
|
||||
serde_json::to_value(&Request::<DistantMsg<DistantRequestData>>::root_schema())
|
||||
.context("Failed to serialize request schema")?;
|
||||
let response_schema =
|
||||
serde_json::to_value(&Response::<DistantMsg<DistantResponseData>>::root_schema())
|
||||
.context("Failed to serialize response schema")?;
|
||||
|
||||
let schema = serde_json::json!({
|
||||
"request": request_schema,
|
||||
"response": response_schema,
|
||||
});
|
||||
let schema = serde_json::json!({
|
||||
"request": request_schema,
|
||||
"response": response_schema,
|
||||
});
|
||||
|
||||
if let Some(path) = file {
|
||||
serde_json::to_writer_pretty(
|
||||
&mut fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.with_context(|| format!("Failed to open {path:?}"))?,
|
||||
&schema,
|
||||
)
|
||||
.context("Failed to write to {path:?}")?;
|
||||
} else {
|
||||
serde_json::to_writer_pretty(&mut io::stdout(), &schema)
|
||||
.context("Failed to print to stdout")?;
|
||||
}
|
||||
if let Some(path) = file {
|
||||
serde_json::to_writer_pretty(
|
||||
&mut fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.with_context(|| format!("Failed to open {path:?}"))?,
|
||||
&schema,
|
||||
)
|
||||
.context("Failed to write to {path:?}")?;
|
||||
} else {
|
||||
serde_json::to_writer_pretty(&mut io::stdout(), &schema)
|
||||
.context("Failed to print to stdout")?;
|
||||
}
|
||||
}
|
||||
|
||||
Self::Completion { file, shell } => {
|
||||
let name = "distant";
|
||||
let mut cmd = Opt::command();
|
||||
GenerateSubcommand::Completion { file, shell } => {
|
||||
let name = "distant";
|
||||
let mut cmd = Options::command();
|
||||
|
||||
if let Some(path) = file {
|
||||
clap_generate(
|
||||
shell,
|
||||
&mut cmd,
|
||||
name,
|
||||
&mut fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.with_context(|| format!("Failed to open {path:?}"))?,
|
||||
)
|
||||
} else {
|
||||
clap_generate(shell, &mut cmd, name, &mut io::stdout())
|
||||
}
|
||||
if let Some(path) = file {
|
||||
clap_generate(
|
||||
shell,
|
||||
&mut cmd,
|
||||
name,
|
||||
&mut fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.with_context(|| format!("Failed to open {path:?}"))?,
|
||||
)
|
||||
} else {
|
||||
clap_generate(shell, &mut cmd, name, &mut io::stdout())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
mod cache;
|
||||
mod client;
|
||||
mod manager;
|
||||
mod msg;
|
||||
mod spawner;
|
||||
|
||||
pub use cache::*;
|
||||
pub use client::*;
|
||||
pub use manager::*;
|
||||
pub use msg::*;
|
||||
pub use spawner::*;
|
@ -1,4 +1,4 @@
|
||||
use crate::paths::user::CACHE_FILE_PATH;
|
||||
use crate::constants::user::CACHE_FILE_PATH;
|
||||
use anyhow::Context;
|
||||
use distant_core::net::common::ConnectionId;
|
||||
use serde::{Deserialize, Serialize};
|
@ -1,8 +0,0 @@
|
||||
use clap::Args;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Args, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ClientActionConfig {
|
||||
/// Represents the maximum time (in seconds) to wait for a network request before timing out
|
||||
pub timeout: Option<f32>,
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
use crate::config::BindAddress;
|
||||
use clap::Args;
|
||||
use distant_core::net::common::Map;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ClientLaunchConfig {
|
||||
#[clap(flatten)]
|
||||
#[serde(flatten)]
|
||||
pub distant: ClientLaunchDistantConfig,
|
||||
|
||||
/// Additional options to provide, typically forwarded to the handler within the manager
|
||||
/// facilitating the launch of a distant server. Options are key-value pairs separated by
|
||||
/// comma.
|
||||
///
|
||||
/// E.g. `key="value",key2="value2"`
|
||||
#[clap(long, default_value_t)]
|
||||
pub options: Map,
|
||||
}
|
||||
|
||||
impl From<Map> for ClientLaunchConfig {
|
||||
fn from(mut map: Map) -> Self {
|
||||
Self {
|
||||
distant: ClientLaunchDistantConfig {
|
||||
bin: map.remove("distant.bin"),
|
||||
bind_server: map
|
||||
.remove("distant.bind_server")
|
||||
.and_then(|x| x.parse::<BindAddress>().ok()),
|
||||
args: map.remove("distant.args"),
|
||||
},
|
||||
options: map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientLaunchConfig> for Map {
|
||||
fn from(config: ClientLaunchConfig) -> Self {
|
||||
let mut this = Self::new();
|
||||
|
||||
if let Some(x) = config.distant.bin {
|
||||
this.insert("distant.bin".to_string(), x);
|
||||
}
|
||||
|
||||
if let Some(x) = config.distant.bind_server {
|
||||
this.insert("distant.bind_server".to_string(), x.to_string());
|
||||
}
|
||||
|
||||
if let Some(x) = config.distant.args {
|
||||
this.insert("distant.args".to_string(), x);
|
||||
}
|
||||
|
||||
this.extend(config.options);
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ClientLaunchDistantConfig {
|
||||
/// Path to distant program on remote machine to execute via ssh;
|
||||
/// by default, this program needs to be available within PATH as
|
||||
/// specified when compiling ssh (not your login shell)
|
||||
#[clap(name = "distant", long)]
|
||||
pub bin: Option<String>,
|
||||
|
||||
/// Control the IP address that the server binds to.
|
||||
///
|
||||
/// The default is `ssh', in which case the server will reply from the IP address that the SSH
|
||||
/// connection came from (as found in the SSH_CONNECTION environment variable). This is
|
||||
/// useful for multihomed servers.
|
||||
///
|
||||
/// With --bind-server=any, the server will reply on the default interface and will not bind to
|
||||
/// a particular IP address. This can be useful if the connection is made through sslh or
|
||||
/// another tool that makes the SSH connection appear to come from localhost.
|
||||
///
|
||||
/// With --bind-server=IP, the server will attempt to bind to the specified IP address.
|
||||
#[clap(name = "distant-bind-server", long, value_name = "ssh|any|IP")]
|
||||
pub bind_server: Option<BindAddress>,
|
||||
|
||||
/// Additional arguments to provide to the server
|
||||
#[clap(name = "distant-args", long, allow_hyphen_values(true))]
|
||||
pub args: Option<String>,
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
use clap::Args;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Args, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ClientReplConfig {
|
||||
/// Represents the maximum time (in seconds) to wait for a network request before timing out
|
||||
pub timeout: Option<f32>,
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
use super::{AccessControl, CommonConfig, NetworkConfig};
|
||||
use clap::Args;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents configuration settings for the distant manager
|
||||
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ManagerConfig {
|
||||
/// Type of access to apply to created unix socket or windows pipe
|
||||
#[clap(long, value_enum)]
|
||||
pub access: Option<AccessControl>,
|
||||
|
||||
#[clap(flatten)]
|
||||
#[serde(flatten)]
|
||||
pub common: CommonConfig,
|
||||
|
||||
#[clap(flatten)]
|
||||
#[serde(flatten)]
|
||||
pub network: NetworkConfig,
|
||||
}
|
@ -1,5 +1,121 @@
|
||||
use directories::ProjectDirs;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Represents the maximum size (in bytes) that data will be read from pipes
|
||||
/// per individual `read` call
|
||||
///
|
||||
/// Current setting is 16k size
|
||||
pub const MAX_PIPE_CHUNK_SIZE: usize = 16384;
|
||||
|
||||
/// Internal name to use for socket files.
|
||||
const SOCKET_FILE_STR: &str = "distant.sock";
|
||||
|
||||
/// User-oriented paths.
|
||||
pub mod user {
|
||||
use super::*;
|
||||
|
||||
/// Root project directory used to calculate other paths
|
||||
static PROJECT_DIR: Lazy<ProjectDirs> = Lazy::new(|| {
|
||||
ProjectDirs::from("", "", "distant").expect("Could not determine valid $HOME path")
|
||||
});
|
||||
|
||||
/// Path to configuration settings for distant client/manager/server
|
||||
pub static CONFIG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.config_dir().join("config.toml"));
|
||||
|
||||
/// Path to cache file used for arbitrary CLI data
|
||||
pub static CACHE_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("cache.toml"));
|
||||
|
||||
pub static CACHE_FILE_PATH_STR: Lazy<String> =
|
||||
Lazy::new(|| CACHE_FILE_PATH.to_string_lossy().to_string());
|
||||
|
||||
/// Path to log file for distant client
|
||||
pub static CLIENT_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("client.log"));
|
||||
|
||||
/// Path to log file for distant manager
|
||||
pub static MANAGER_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("manager.log"));
|
||||
|
||||
/// Path to log file for distant server
|
||||
pub static SERVER_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("server.log"));
|
||||
|
||||
/// Path to log file for distant generate
|
||||
pub static GENERATE_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("generate.log"));
|
||||
|
||||
/// For Linux & BSD, this uses the runtime path. For Mac, this uses the tmp path
|
||||
///
|
||||
/// * `/run/user/1001/distant/{user}.distant.sock` on Linux
|
||||
/// * `/var/run/{user}.distant.sock` on BSD
|
||||
/// * `/tmp/{user}.distant.dock` on MacOS
|
||||
pub static UNIX_SOCKET_PATH: Lazy<PathBuf> = Lazy::new(|| {
|
||||
// Form of {user}.distant.sock
|
||||
let mut file_name = whoami::username_os();
|
||||
file_name.push(".");
|
||||
file_name.push(SOCKET_FILE_STR);
|
||||
|
||||
PROJECT_DIR
|
||||
.runtime_dir()
|
||||
.map(std::path::Path::to_path_buf)
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
.join(file_name)
|
||||
});
|
||||
|
||||
/// Name of the pipe used by Windows in the form of `{user}.distant`
|
||||
pub static WINDOWS_PIPE_NAME: Lazy<String> =
|
||||
Lazy::new(|| format!("{}.distant", whoami::username()));
|
||||
}
|
||||
|
||||
/// Global paths.
|
||||
pub mod global {
|
||||
use super::*;
|
||||
|
||||
/// Windows ProgramData directory from from the %ProgramData% environment variable
|
||||
#[cfg(windows)]
|
||||
static PROGRAM_DATA_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
||||
PathBuf::from(std::env::var("ProgramData").expect("Could not determine %ProgramData%"))
|
||||
});
|
||||
|
||||
/// Configuration directory for windows: `%ProgramData%\distant`.
|
||||
#[cfg(windows)]
|
||||
static CONFIG_DIR: Lazy<PathBuf> = Lazy::new(|| PROGRAM_DATA_DIR.join("distant"));
|
||||
|
||||
/// Configuration directory for unix: `/etc/distant`.
|
||||
#[cfg(unix)]
|
||||
static CONFIG_DIR: Lazy<PathBuf> = Lazy::new(|| PathBuf::from("/etc").join("distant"));
|
||||
|
||||
/// Path to configuration settings for distant client/manager/server.
|
||||
pub static CONFIG_FILE_PATH: Lazy<PathBuf> = Lazy::new(|| CONFIG_DIR.join("config.toml"));
|
||||
|
||||
/// For Linux & BSD, this uses the runtime path. For Mac, this uses the tmp path
|
||||
///
|
||||
/// * `/run/distant.sock` on Linux
|
||||
/// * `/var/run/distant.sock` on BSD
|
||||
/// * `/tmp/distant.dock` on MacOS
|
||||
/// * `@TERMUX_PREFIX@/var/run/distant.sock` on Android (Termux)
|
||||
pub static UNIX_SOCKET_PATH: Lazy<PathBuf> = Lazy::new(|| {
|
||||
if cfg!(target_os = "macos") {
|
||||
std::env::temp_dir().join(SOCKET_FILE_STR)
|
||||
} else if cfg!(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd"
|
||||
)) {
|
||||
PathBuf::from("/var").join("run").join(SOCKET_FILE_STR)
|
||||
} else if cfg!(target_os = "android") {
|
||||
PathBuf::from("@TERMUX_PREFIX@/var")
|
||||
.join("run")
|
||||
.join(SOCKET_FILE_STR)
|
||||
} else {
|
||||
PathBuf::from("/run").join(SOCKET_FILE_STR)
|
||||
}
|
||||
});
|
||||
|
||||
/// Name of the pipe used by Windows.
|
||||
pub static WINDOWS_PIPE_NAME: Lazy<String> = Lazy::new(|| "distant".to_string());
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,13 @@
|
||||
mod address;
|
||||
mod cmd;
|
||||
mod logging;
|
||||
mod network;
|
||||
mod search;
|
||||
mod value;
|
||||
|
||||
pub use address::*;
|
||||
pub use cmd::*;
|
||||
pub use logging::*;
|
||||
pub use network::*;
|
||||
pub use search::*;
|
||||
pub use value::*;
|
@ -0,0 +1,146 @@
|
||||
use clap::Args;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Represents some command with arguments to execute.
|
||||
///
|
||||
/// NOTE: Must be derived with `#[clap(flatten)]` to properly take effect.
|
||||
#[derive(Args, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Cmd {
|
||||
/// The command to execute.
|
||||
#[clap(name = "CMD")]
|
||||
cmd: String,
|
||||
|
||||
/// Arguments to provide to the command.
|
||||
#[clap(name = "ARGS")]
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
impl Cmd {
|
||||
/// Creates a new command from the given `cmd`.
|
||||
pub fn new<C, I, A>(cmd: C, args: I) -> Self
|
||||
where
|
||||
C: Into<String>,
|
||||
I: Iterator<Item = A>,
|
||||
A: Into<String>,
|
||||
{
|
||||
Self {
|
||||
cmd: cmd.into(),
|
||||
args: args.map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cmd> for String {
|
||||
fn from(cmd: Cmd) -> Self {
|
||||
cmd.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Cmd {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.cmd)?;
|
||||
for arg in self.args.iter() {
|
||||
write!(f, " {arg}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Cmd {
|
||||
/// Parses `s` into [`Cmd`], or panics if unable to parse.
|
||||
fn from(s: &'a str) -> Self {
|
||||
s.parse().expect("Failed to parse into cmd")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Cmd {
|
||||
type Err = Box<dyn std::error::Error>;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let tokens = if cfg!(unix) {
|
||||
shell_words::split(s)?
|
||||
} else if cfg!(windows) {
|
||||
winsplit::split(s)
|
||||
} else {
|
||||
unreachable!(
|
||||
"FromStr<Cmd>: Unsupported operating system outside Unix and Windows families!"
|
||||
);
|
||||
};
|
||||
|
||||
// If we get nothing, then we want an empty command
|
||||
if tokens.is_empty() {
|
||||
return Ok(Self {
|
||||
cmd: String::new(),
|
||||
args: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut it = tokens.into_iter();
|
||||
Ok(Self {
|
||||
cmd: it.next().unwrap(),
|
||||
args: it.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl FromArgMatches for Cmd {
|
||||
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, Error> {
|
||||
let mut matches = matches.clone();
|
||||
Self::from_arg_matches_mut(&mut matches)
|
||||
}
|
||||
fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result<Self, Error> {
|
||||
let cmd = matches.get_one::<String>("cmd").ok_or_else(|| {
|
||||
Error::raw(
|
||||
ErrorKind::MissingRequiredArgument,
|
||||
"program must be specified",
|
||||
)
|
||||
})?;
|
||||
let args: Vec<String> = matches
|
||||
.get_many::<String>("arg")
|
||||
.unwrap_or_default()
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
Ok(Self::new(format!("{cmd} {}", args.join(" "))))
|
||||
}
|
||||
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> {
|
||||
let mut matches = matches.clone();
|
||||
self.update_from_arg_matches_mut(&mut matches)
|
||||
}
|
||||
fn update_from_arg_matches_mut(&mut self, _matches: &mut ArgMatches) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Args for Cmd {
|
||||
fn augment_args(cmd: Command) -> Command {
|
||||
cmd.arg(
|
||||
Arg::new("cmd")
|
||||
.required(true)
|
||||
.value_name("CMD")
|
||||
.help("")
|
||||
.action(ArgAction::Set),
|
||||
)
|
||||
.trailing_var_arg(true)
|
||||
.arg(
|
||||
Arg::new("arg")
|
||||
.value_name("ARGS")
|
||||
.num_args(1..)
|
||||
.action(ArgAction::Append),
|
||||
)
|
||||
}
|
||||
fn augment_args_for_update(cmd: Command) -> Command {
|
||||
Self::augment_args(cmd)
|
||||
}
|
||||
} */
|
||||
|
||||
/* #[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn verify_cmd() {
|
||||
Cmd::augment_args(Command::new("distant")).debug_assert();
|
||||
}
|
||||
} */
|
@ -0,0 +1,81 @@
|
||||
use clap::{Args, ValueEnum};
|
||||
use distant_core::data::FileType;
|
||||
use distant_core::data::{SearchQueryOptions, SearchQueryTarget};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub use distant_core::data::SearchQueryCondition as CliSearchQueryCondition;
|
||||
|
||||
/// Options to customize the search results.
|
||||
#[derive(Args, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct CliSearchQueryOptions {
|
||||
/// Restrict search to only these file types (otherwise all are allowed)
|
||||
#[clap(skip)]
|
||||
pub allowed_file_types: HashSet<FileType>,
|
||||
|
||||
/// Regex to use to filter paths being searched to only those that match the include condition
|
||||
#[clap(long)]
|
||||
pub include: Option<CliSearchQueryCondition>,
|
||||
|
||||
/// Regex to use to filter paths being searched to only those that do not match the exclude
|
||||
/// condition
|
||||
#[clap(long)]
|
||||
pub exclude: Option<CliSearchQueryCondition>,
|
||||
|
||||
/// Search should follow symbolic links
|
||||
#[clap(long)]
|
||||
pub follow_symbolic_links: bool,
|
||||
|
||||
/// Maximum results to return before stopping the query
|
||||
#[clap(long)]
|
||||
pub limit: Option<u64>,
|
||||
|
||||
/// Maximum depth (directories) to search
|
||||
///
|
||||
/// The smallest depth is 0 and always corresponds to the path given to the new function on
|
||||
/// this type. Its direct descendents have depth 1, and their descendents have depth 2, and so
|
||||
/// on.
|
||||
///
|
||||
/// Note that this will not simply filter the entries of the iterator, but it will actually
|
||||
/// avoid descending into directories when the depth is exceeded.
|
||||
#[clap(long)]
|
||||
pub max_depth: Option<u64>,
|
||||
|
||||
/// Amount of results to batch before sending back excluding final submission that will always
|
||||
/// include the remaining results even if less than pagination request
|
||||
#[clap(long)]
|
||||
pub pagination: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<CliSearchQueryOptions> for SearchQueryOptions {
|
||||
fn from(x: CliSearchQueryOptions) -> Self {
|
||||
Self {
|
||||
allowed_file_types: x.allowed_file_types,
|
||||
include: x.include,
|
||||
exclude: x.exclude,
|
||||
follow_symbolic_links: x.follow_symbolic_links,
|
||||
limit: x.limit,
|
||||
max_depth: x.max_depth,
|
||||
pagination: x.pagination,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kind of data to examine using conditions
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
|
||||
#[clap(rename_all = "snake_case")]
|
||||
pub enum CliSearchQueryTarget {
|
||||
/// Checks path of file, directory, or symlink
|
||||
Path,
|
||||
|
||||
/// Checks contents of files
|
||||
Contents,
|
||||
}
|
||||
|
||||
impl From<CliSearchQueryTarget> for SearchQueryTarget {
|
||||
fn from(x: CliSearchQueryTarget) -> Self {
|
||||
match x {
|
||||
CliSearchQueryTarget::Contents => Self::Contents,
|
||||
CliSearchQueryTarget::Path => Self::Path,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
use derive_more::{Display, IsVariant};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Represents a value for some CLI option or config. This exists to support optional values that
|
||||
/// have a default value so we can distinguish if a CLI value was a default or explicitly defined.
|
||||
#[derive(Copy, Clone, Debug, Display, IsVariant)]
|
||||
pub enum Value<T> {
|
||||
/// Value is a default representation.
|
||||
Default(T),
|
||||
|
||||
/// Value is explicitly defined by the user.
|
||||
Explicit(T),
|
||||
}
|
||||
|
||||
impl<T> Value<T> {
|
||||
pub fn into_inner(self) -> T {
|
||||
match self {
|
||||
Self::Default(x) => x,
|
||||
Self::Explicit(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Value<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
match self {
|
||||
Value::Default(x) => x,
|
||||
Value::Explicit(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for Value<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
match self {
|
||||
Value::Default(x) => x,
|
||||
Value::Explicit(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Value<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
AsRef::as_ref(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Value<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
AsMut::as_mut(self)
|
||||
}
|
||||
}
|
||||
/*
|
||||
impl<T> Into<T> for Value<T> {
|
||||
fn into(self) -> T {
|
||||
match self {
|
||||
Self::Default(x) => x,
|
||||
Self::Explicit(x) => x,
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
impl<T> PartialEq for Value<T>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
AsRef::as_ref(self) == AsRef::as_ref(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq<T> for Value<T>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
AsRef::as_ref(self) == other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStr for Value<T>
|
||||
where
|
||||
T: FromStr,
|
||||
{
|
||||
type Err = T::Err;
|
||||
|
||||
/// Parses `s` into [Value], placing the result into the explicit variant.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::Explicit(T::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Serialize for Value<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
/// Serializes the underlying data within [Value]. The origin of the value (default vs
|
||||
/// explicit) is not stored as config files using serialization are all explicitly set.
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
T::serialize(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for Value<T>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
{
|
||||
/// Deserializes into an explicit variant of [Value]. It is assumed that any value coming from
|
||||
/// a format like a config.toml is explicitly defined and not a default, even though we have a
|
||||
/// default config.toml available.
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(Self::Explicit(T::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
@ -1,27 +1,24 @@
|
||||
use super::{CommonConfig, NetworkConfig};
|
||||
use super::common::{self, LoggingSettings, NetworkSettings};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod action;
|
||||
mod api;
|
||||
mod connect;
|
||||
mod launch;
|
||||
mod repl;
|
||||
|
||||
pub use action::*;
|
||||
pub use api::*;
|
||||
pub use connect::*;
|
||||
pub use launch::*;
|
||||
pub use repl::*;
|
||||
|
||||
/// Represents configuration settings for the distant client
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ClientConfig {
|
||||
#[serde(flatten)]
|
||||
pub common: CommonConfig,
|
||||
pub logging: LoggingSettings,
|
||||
|
||||
pub action: ClientActionConfig,
|
||||
#[serde(flatten)]
|
||||
pub network: NetworkSettings,
|
||||
|
||||
pub api: ClientApiConfig,
|
||||
pub connect: ClientConnectConfig,
|
||||
pub launch: ClientLaunchConfig,
|
||||
pub repl: ClientReplConfig,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub network: NetworkConfig,
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ClientApiConfig {
|
||||
pub timeout: Option<f32>,
|
||||
}
|
@ -1,14 +1,8 @@
|
||||
use clap::Args;
|
||||
use distant_core::net::common::Map;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ClientConnectConfig {
|
||||
/// Additional options to provide, typically forwarded to the handler within the manager
|
||||
/// facilitating the connection. Options are key-value pairs separated by comma.
|
||||
///
|
||||
/// E.g. `key="value",key2="value2"`
|
||||
#[clap(long, default_value_t)]
|
||||
pub options: Map,
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
use super::common::BindAddress;
|
||||
use distant_core::net::common::Map;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ClientLaunchConfig {
|
||||
#[serde(flatten)]
|
||||
pub distant: ClientLaunchDistantConfig,
|
||||
pub options: Map,
|
||||
}
|
||||
|
||||
impl From<Map> for ClientLaunchConfig {
|
||||
fn from(mut map: Map) -> Self {
|
||||
Self {
|
||||
distant: ClientLaunchDistantConfig {
|
||||
bin: map.remove("distant.bin"),
|
||||
bind_server: map
|
||||
.remove("distant.bind_server")
|
||||
.and_then(|x| x.parse::<BindAddress>().ok()),
|
||||
args: map.remove("distant.args"),
|
||||
},
|
||||
options: map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientLaunchConfig> for Map {
|
||||
fn from(config: ClientLaunchConfig) -> Self {
|
||||
let mut this = Self::new();
|
||||
|
||||
if let Some(x) = config.distant.bin {
|
||||
this.insert("distant.bin".to_string(), x);
|
||||
}
|
||||
|
||||
if let Some(x) = config.distant.bind_server {
|
||||
this.insert("distant.bind_server".to_string(), x.to_string());
|
||||
}
|
||||
|
||||
if let Some(x) = config.distant.args {
|
||||
this.insert("distant.args".to_string(), x);
|
||||
}
|
||||
|
||||
this.extend(config.options);
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ClientLaunchDistantConfig {
|
||||
pub bin: Option<String>,
|
||||
pub bind_server: Option<BindAddress>,
|
||||
pub args: Option<String>,
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
use super::CommonConfig;
|
||||
use super::common::LoggingSettings;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents configuration settings for the distant generate
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GenerateConfig {
|
||||
#[serde(flatten)]
|
||||
pub common: CommonConfig,
|
||||
pub logging: LoggingSettings,
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
use super::common::{AccessControl, LoggingSettings, NetworkSettings};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents configuration settings for the distant manager
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ManagerConfig {
|
||||
#[serde(flatten)]
|
||||
pub logging: LoggingSettings,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub network: NetworkSettings,
|
||||
|
||||
pub access: Option<AccessControl>,
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
use crate::options::BindAddress;
|
||||
use distant_core::net::common::{Map, PortRange};
|
||||
use distant_core::net::server::Shutdown;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ServerListenConfig {
|
||||
pub host: Option<BindAddress>,
|
||||
pub port: Option<PortRange>,
|
||||
pub use_ipv6: bool,
|
||||
pub shutdown: Option<Shutdown>,
|
||||
pub current_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl From<Map> for ServerListenConfig {
|
||||
fn from(mut map: Map) -> Self {
|
||||
Self {
|
||||
host: map
|
||||
.remove("host")
|
||||
.and_then(|x| x.parse::<BindAddress>().ok()),
|
||||
port: map.remove("port").and_then(|x| x.parse::<PortRange>().ok()),
|
||||
use_ipv6: map
|
||||
.remove("use_ipv6")
|
||||
.and_then(|x| x.parse::<bool>().ok())
|
||||
.unwrap_or_default(),
|
||||
shutdown: map
|
||||
.remove("shutdown")
|
||||
.and_then(|x| x.parse::<Shutdown>().ok()),
|
||||
current_dir: map
|
||||
.remove("current_dir")
|
||||
.and_then(|x| x.parse::<PathBuf>().ok()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServerListenConfig> for Map {
|
||||
fn from(config: ServerListenConfig) -> Self {
|
||||
let mut this = Self::new();
|
||||
|
||||
if let Some(x) = config.host {
|
||||
this.insert("host".to_string(), x.to_string());
|
||||
}
|
||||
|
||||
if let Some(x) = config.port {
|
||||
this.insert("port".to_string(), x.to_string());
|
||||
}
|
||||
|
||||
this.insert("use_ipv6".to_string(), config.use_ipv6.to_string());
|
||||
|
||||
if let Some(x) = config.shutdown {
|
||||
this.insert("shutdown".to_string(), x.to_string());
|
||||
}
|
||||
|
||||
if let Some(x) = config.current_dir {
|
||||
this.insert("current_dir".to_string(), x.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
use directories::ProjectDirs;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const SOCKET_FILE_STR: &str = "distant.sock";
|
||||
|
||||
/// User-oriented paths
|
||||
pub mod user {
|
||||
use super::*;
|
||||
|
||||
/// Root project directory used to calculate other paths
|
||||
static PROJECT_DIR: Lazy<ProjectDirs> = Lazy::new(|| {
|
||||
ProjectDirs::from("", "", "distant").expect("Could not determine valid $HOME path")
|
||||
});
|
||||
|
||||
/// Path to configuration settings for distant client/manager/server
|
||||
pub static CONFIG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.config_dir().join("config.toml"));
|
||||
|
||||
/// Path to cache file used for arbitrary CLI data
|
||||
pub static CACHE_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("cache.toml"));
|
||||
|
||||
pub static CACHE_FILE_PATH_STR: Lazy<String> =
|
||||
Lazy::new(|| CACHE_FILE_PATH.to_string_lossy().to_string());
|
||||
|
||||
/// Path to log file for distant client
|
||||
pub static CLIENT_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("client.log"));
|
||||
|
||||
/// Path to log file for distant manager
|
||||
pub static MANAGER_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("manager.log"));
|
||||
|
||||
/// Path to log file for distant server
|
||||
pub static SERVER_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("server.log"));
|
||||
|
||||
/// Path to log file for distant generate
|
||||
pub static GENERATE_LOG_FILE_PATH: Lazy<PathBuf> =
|
||||
Lazy::new(|| PROJECT_DIR.cache_dir().join("generate.log"));
|
||||
|
||||
/// For Linux & BSD, this uses the runtime path. For Mac, this uses the tmp path
|
||||
///
|
||||
/// * `/run/user/1001/distant/{user}.distant.sock` on Linux
|
||||
/// * `/var/run/{user}.distant.sock` on BSD
|
||||
/// * `/tmp/{user}.distant.dock` on MacOS
|
||||
pub static UNIX_SOCKET_PATH: Lazy<PathBuf> = Lazy::new(|| {
|
||||
// Form of {user}.distant.sock
|
||||
let mut file_name = whoami::username_os();
|
||||
file_name.push(".");
|
||||
file_name.push(SOCKET_FILE_STR);
|
||||
|
||||
PROJECT_DIR
|
||||
.runtime_dir()
|
||||
.map(std::path::Path::to_path_buf)
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
.join(file_name)
|
||||
});
|
||||
|
||||
/// Name of the pipe used by Windows in the form of `{user}.distant`
|
||||
pub static WINDOWS_PIPE_NAME: Lazy<String> =
|
||||
Lazy::new(|| format!("{}.distant", whoami::username()));
|
||||
}
|
||||
|
||||
/// Global paths
|
||||
pub mod global {
|
||||
use super::*;
|
||||
|
||||
/// Windows ProgramData directory from from the %ProgramData% environment variable
|
||||
#[cfg(windows)]
|
||||
static PROGRAM_DATA_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
||||
PathBuf::from(std::env::var("ProgramData").expect("Could not determine %ProgramData%"))
|
||||
});
|
||||
|
||||
#[cfg(windows)]
|
||||
static CONFIG_DIR: Lazy<PathBuf> = Lazy::new(|| PROGRAM_DATA_DIR.join("distant"));
|
||||
|
||||
#[cfg(unix)]
|
||||
static CONFIG_DIR: Lazy<PathBuf> = Lazy::new(|| PathBuf::from("/etc").join("distant"));
|
||||
|
||||
/// Path to configuration settings for distant client/manager/server
|
||||
pub static CONFIG_FILE_PATH: Lazy<PathBuf> = Lazy::new(|| CONFIG_DIR.join("config.toml"));
|
||||
|
||||
/// For Linux & BSD, this uses the runtime path. For Mac, this uses the tmp path
|
||||
///
|
||||
/// * `/run/distant.sock` on Linux
|
||||
/// * `/var/run/distant.sock` on BSD
|
||||
/// * `/tmp/distant.dock` on MacOS
|
||||
pub static UNIX_SOCKET_PATH: Lazy<PathBuf> = Lazy::new(|| {
|
||||
if cfg!(target_os = "macos") {
|
||||
std::env::temp_dir().join(SOCKET_FILE_STR)
|
||||
} else if cfg!(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd"
|
||||
)) {
|
||||
PathBuf::from("/var").join("run").join(SOCKET_FILE_STR)
|
||||
} else {
|
||||
PathBuf::from("/run").join(SOCKET_FILE_STR)
|
||||
}
|
||||
});
|
||||
|
||||
/// Name of the pipe used by Windows
|
||||
pub static WINDOWS_PIPE_NAME: Lazy<String> = Lazy::new(|| "distant".to_string());
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
use crate::cli::{fixtures::*, utils::FAILURE_LINE};
|
||||
use assert_cmd::Command;
|
||||
use assert_fs::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#;
|
||||
|
||||
const APPENDED_FILE_CONTENTS: &str = r#"
|
||||
even more
|
||||
file contents
|
||||
"#;
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_report_ok_when_done(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str(FILE_CONTENTS).unwrap();
|
||||
|
||||
// distant action file-append {path} -- {contents}
|
||||
action_cmd
|
||||
.args([
|
||||
"file-append",
|
||||
file.to_str().unwrap(),
|
||||
"--",
|
||||
APPENDED_FILE_CONTENTS,
|
||||
])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-dir").child("missing-file");
|
||||
|
||||
// distant action file-append {path} -- {contents}
|
||||
action_cmd
|
||||
.args([
|
||||
"file-append",
|
||||
file.to_str().unwrap(),
|
||||
"--",
|
||||
APPENDED_FILE_CONTENTS,
|
||||
])
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(FAILURE_LINE.clone());
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(predicates::path::missing());
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
use crate::cli::{fixtures::*, utils::FAILURE_LINE};
|
||||
use assert_cmd::Command;
|
||||
use assert_fs::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#;
|
||||
|
||||
const APPENDED_FILE_CONTENTS: &str = r#"
|
||||
even more
|
||||
file contents
|
||||
"#;
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_report_ok_when_done(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str(FILE_CONTENTS).unwrap();
|
||||
|
||||
// distant action file-append-text {path} -- {contents}
|
||||
action_cmd
|
||||
.args([
|
||||
"file-append-text",
|
||||
file.to_str().unwrap(),
|
||||
"--",
|
||||
APPENDED_FILE_CONTENTS,
|
||||
])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-dir").child("missing-file");
|
||||
|
||||
// distant action file-append-text {path} -- {contents}
|
||||
action_cmd
|
||||
.args([
|
||||
"file-append-text",
|
||||
file.to_str().unwrap(),
|
||||
"--",
|
||||
APPENDED_FILE_CONTENTS,
|
||||
])
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(FAILURE_LINE.clone());
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(predicates::path::missing());
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
use crate::cli::{fixtures::*, utils::FAILURE_LINE};
|
||||
use assert_cmd::Command;
|
||||
use assert_fs::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#;
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_print_out_file_contents(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str(FILE_CONTENTS).unwrap();
|
||||
|
||||
// distant action file-read {path}
|
||||
action_cmd
|
||||
.args(["file-read", file.to_str().unwrap()])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(format!("{}\n", FILE_CONTENTS))
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
|
||||
// distant action file-read {path}
|
||||
action_cmd
|
||||
.args(["file-read", file.to_str().unwrap()])
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(FAILURE_LINE.clone());
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
use crate::cli::{fixtures::*, utils::FAILURE_LINE};
|
||||
use assert_cmd::Command;
|
||||
use assert_fs::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#;
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_print_out_file_contents(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str(FILE_CONTENTS).unwrap();
|
||||
|
||||
// distant action file-read-text {path}
|
||||
action_cmd
|
||||
.args(["file-read-text", file.to_str().unwrap()])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(format!("{}\n", FILE_CONTENTS))
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
|
||||
// distant action file-read-text {path}
|
||||
action_cmd
|
||||
.args(["file-read-text", file.to_str().unwrap()])
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(FAILURE_LINE.clone());
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
use crate::cli::{fixtures::*, utils::FAILURE_LINE};
|
||||
use assert_cmd::Command;
|
||||
use assert_fs::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#;
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_report_ok_when_done(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
action_cmd
|
||||
.args(["file-write", file.to_str().unwrap(), "--", FILE_CONTENTS])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(FILE_CONTENTS);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-dir").child("missing-file");
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
action_cmd
|
||||
.args(["file-write", file.to_str().unwrap(), "--", FILE_CONTENTS])
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(FAILURE_LINE.clone());
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(predicates::path::missing());
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use crate::cli::{fixtures::*, utils::FAILURE_LINE};
|
||||
use assert_cmd::Command;
|
||||
use assert_fs::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#;
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_report_ok_when_done(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
|
||||
// distant action file-write-text {path} -- {contents}
|
||||
action_cmd
|
||||
.args([
|
||||
"file-write-text",
|
||||
file.to_str().unwrap(),
|
||||
"--",
|
||||
FILE_CONTENTS,
|
||||
])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(FILE_CONTENTS);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(mut action_cmd: CtxCommand<Command>) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-dir").child("missing-file");
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
action_cmd
|
||||
.args([
|
||||
"file-write-text",
|
||||
file.to_str().unwrap(),
|
||||
"--",
|
||||
FILE_CONTENTS,
|
||||
])
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(FAILURE_LINE.clone());
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(predicates::path::missing());
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
use crate::cli::fixtures::*;
|
||||
use assert_fs::prelude::*;
|
||||
use indoc::indoc;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = indoc! {r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#};
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_print_out_file_contents(ctx: DistantManagerCtx) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str(FILE_CONTENTS).unwrap();
|
||||
|
||||
// distant fs read {path}
|
||||
ctx.new_assert_cmd(["fs", "read"])
|
||||
.args([file.to_str().unwrap()])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(FILE_CONTENTS)
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(ctx: DistantManagerCtx) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-file");
|
||||
|
||||
// distant fs read {path}
|
||||
ctx.new_assert_cmd(["fs", "read"])
|
||||
.args([file.to_str().unwrap()])
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(predicates::str::is_empty().not());
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
use crate::cli::fixtures::*;
|
||||
use assert_fs::prelude::*;
|
||||
use indoc::indoc;
|
||||
use predicates::prelude::*;
|
||||
use rstest::*;
|
||||
|
||||
const FILE_CONTENTS: &str = indoc! {r#"
|
||||
some text
|
||||
on multiple lines
|
||||
that is a file's contents
|
||||
"#};
|
||||
|
||||
const APPENDED_FILE_CONTENTS: &str = indoc! {r#"
|
||||
even more
|
||||
file contents
|
||||
"#};
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_support_writing_stdin_to_file(ctx: DistantManagerCtx) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
ctx.new_assert_cmd(["fs", "write"])
|
||||
.args([file.to_str().unwrap()])
|
||||
.write_stdin(FILE_CONTENTS)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(FILE_CONTENTS);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_support_appending_stdin_to_file(ctx: DistantManagerCtx) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str(FILE_CONTENTS).unwrap();
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
ctx.new_assert_cmd(["fs", "write"])
|
||||
.args(["--append", file.to_str().unwrap()])
|
||||
.write_stdin(APPENDED_FILE_CONTENTS)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_support_writing_argument_to_file(ctx: DistantManagerCtx) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
ctx.new_assert_cmd(["fs", "write"])
|
||||
.args([file.to_str().unwrap(), "--"])
|
||||
.arg(FILE_CONTENTS)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(FILE_CONTENTS);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn should_support_appending_argument_to_file(ctx: DistantManagerCtx) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("test-file");
|
||||
file.write_str(FILE_CONTENTS).unwrap();
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
ctx.new_assert_cmd(["fs", "write"])
|
||||
.args(["--append", file.to_str().unwrap(), "--"])
|
||||
.arg(APPENDED_FILE_CONTENTS)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("")
|
||||
.stderr("");
|
||||
|
||||
// NOTE: We wait a little bit to give the OS time to fully write to file
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(format!("{}{}", FILE_CONTENTS, APPENDED_FILE_CONTENTS));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[test_log::test]
|
||||
fn yield_an_error_when_fails(ctx: DistantManagerCtx) {
|
||||
let temp = assert_fs::TempDir::new().unwrap();
|
||||
let file = temp.child("missing-dir").child("missing-file");
|
||||
|
||||
// distant action file-write {path} -- {contents}
|
||||
ctx.new_assert_cmd(["fs", "write"])
|
||||
.args([file.to_str().unwrap(), "--"])
|
||||
.arg(FILE_CONTENTS)
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout("")
|
||||
.stderr(predicates::str::is_empty().not());
|
||||
|
||||
// Because we're talking to a local server, we can verify locally
|
||||
file.assert(predicates::path::missing());
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
mod capabilities;
|
||||
mod fs_copy;
|
||||
mod fs_exists;
|
||||
mod fs_make_dir;
|
||||
mod fs_metadata;
|
||||
mod fs_read_directory;
|
||||
mod fs_read_file;
|
||||
mod fs_remove;
|
||||
mod fs_rename;
|
||||
mod fs_search;
|
||||
mod fs_watch;
|
||||
mod fs_write;
|
||||
mod spawn;
|
||||
mod system_info;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue