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;
|
pub mod client;
|
||||||
|
mod common;
|
||||||
mod client;
|
pub mod generate;
|
||||||
mod generate;
|
pub mod manager;
|
||||||
mod manager;
|
pub mod server;
|
||||||
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),
|
|
||||||
}
|
|
||||||
|
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::{
|
use crate::options::{Config, GenerateSubcommand};
|
||||||
cli::Opt,
|
use crate::{CliResult, Options};
|
||||||
config::{Config, GenerateConfig},
|
|
||||||
CliResult,
|
|
||||||
};
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::{CommandFactory, Subcommand};
|
use clap::CommandFactory;
|
||||||
use clap_complete::{generate as clap_generate, Shell};
|
use clap_complete::generate as clap_generate;
|
||||||
use distant_core::{
|
use distant_core::net::common::{Request, Response};
|
||||||
net::common::{Request, Response},
|
use distant_core::{DistantMsg, DistantRequestData, DistantResponseData};
|
||||||
DistantMsg, DistantRequestData, DistantResponseData,
|
use std::{fs, io};
|
||||||
};
|
|
||||||
use std::{fs, io, path::PathBuf};
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
pub fn run(cmd: GenerateSubcommand) -> CliResult {
|
||||||
pub enum GenerateSubcommand {
|
let rt = tokio::runtime::Runtime::new().context("Failed to start up runtime")?;
|
||||||
/// Generate configuration file with base settings
|
rt.block_on(async_run(cmd))
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateSubcommand {
|
async fn async_run(cmd: GenerateSubcommand) -> CliResult {
|
||||||
pub fn run(self, _config: GenerateConfig) -> CliResult {
|
match cmd {
|
||||||
let rt = tokio::runtime::Runtime::new().context("Failed to start up runtime")?;
|
GenerateSubcommand::Config { file } => tokio::fs::write(file, Config::default_raw_str())
|
||||||
rt.block_on(Self::async_run(self))
|
.await
|
||||||
}
|
.context("Failed to write default config to {file:?}")?,
|
||||||
|
|
||||||
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:?}")?,
|
|
||||||
|
|
||||||
Self::Schema { file } => {
|
GenerateSubcommand::Schema { file } => {
|
||||||
let request_schema =
|
let request_schema =
|
||||||
serde_json::to_value(&Request::<DistantMsg<DistantRequestData>>::root_schema())
|
serde_json::to_value(&Request::<DistantMsg<DistantRequestData>>::root_schema())
|
||||||
.context("Failed to serialize request schema")?;
|
.context("Failed to serialize request schema")?;
|
||||||
let response_schema = serde_json::to_value(&Response::<
|
let response_schema =
|
||||||
DistantMsg<DistantResponseData>,
|
serde_json::to_value(&Response::<DistantMsg<DistantResponseData>>::root_schema())
|
||||||
>::root_schema())
|
.context("Failed to serialize response schema")?;
|
||||||
.context("Failed to serialize response schema")?;
|
|
||||||
|
|
||||||
let schema = serde_json::json!({
|
let schema = serde_json::json!({
|
||||||
"request": request_schema,
|
"request": request_schema,
|
||||||
"response": response_schema,
|
"response": response_schema,
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(path) = file {
|
if let Some(path) = file {
|
||||||
serde_json::to_writer_pretty(
|
serde_json::to_writer_pretty(
|
||||||
&mut fs::OpenOptions::new()
|
&mut fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&path)
|
.open(&path)
|
||||||
.with_context(|| format!("Failed to open {path:?}"))?,
|
.with_context(|| format!("Failed to open {path:?}"))?,
|
||||||
&schema,
|
&schema,
|
||||||
)
|
)
|
||||||
.context("Failed to write to {path:?}")?;
|
.context("Failed to write to {path:?}")?;
|
||||||
} else {
|
} else {
|
||||||
serde_json::to_writer_pretty(&mut io::stdout(), &schema)
|
serde_json::to_writer_pretty(&mut io::stdout(), &schema)
|
||||||
.context("Failed to print to stdout")?;
|
.context("Failed to print to stdout")?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self::Completion { file, shell } => {
|
GenerateSubcommand::Completion { file, shell } => {
|
||||||
let name = "distant";
|
let name = "distant";
|
||||||
let mut cmd = Opt::command();
|
let mut cmd = Options::command();
|
||||||
|
|
||||||
if let Some(path) = file {
|
if let Some(path) = file {
|
||||||
clap_generate(
|
clap_generate(
|
||||||
shell,
|
shell,
|
||||||
&mut cmd,
|
&mut cmd,
|
||||||
name,
|
name,
|
||||||
&mut fs::OpenOptions::new()
|
&mut fs::OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&path)
|
.open(&path)
|
||||||
.with_context(|| format!("Failed to open {path:?}"))?,
|
.with_context(|| format!("Failed to open {path:?}"))?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
clap_generate(shell, &mut cmd, name, &mut io::stdout())
|
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 anyhow::Context;
|
||||||
use distant_core::net::common::ConnectionId;
|
use distant_core::net::common::ConnectionId;
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
/// Represents the maximum size (in bytes) that data will be read from pipes
|
||||||
/// per individual `read` call
|
/// per individual `read` call
|
||||||
///
|
///
|
||||||
/// Current setting is 16k size
|
/// Current setting is 16k size
|
||||||
pub const MAX_PIPE_CHUNK_SIZE: usize = 16384;
|
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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
mod action;
|
mod api;
|
||||||
mod connect;
|
mod connect;
|
||||||
mod launch;
|
mod launch;
|
||||||
mod repl;
|
|
||||||
|
|
||||||
pub use action::*;
|
pub use api::*;
|
||||||
pub use connect::*;
|
pub use connect::*;
|
||||||
pub use launch::*;
|
pub use launch::*;
|
||||||
pub use repl::*;
|
|
||||||
|
|
||||||
/// Represents configuration settings for the distant client
|
/// Represents configuration settings for the distant client
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub common: CommonConfig,
|
pub logging: LoggingSettings,
|
||||||
|
|
||||||
pub action: ClientActionConfig,
|
#[serde(flatten)]
|
||||||
|
pub network: NetworkSettings,
|
||||||
|
|
||||||
|
pub api: ClientApiConfig,
|
||||||
pub connect: ClientConnectConfig,
|
pub connect: ClientConnectConfig,
|
||||||
pub launch: ClientLaunchConfig,
|
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 distant_core::net::common::Map;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ClientConnectConfig {
|
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,
|
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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Represents configuration settings for the distant generate
|
/// Represents configuration settings for the distant generate
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct GenerateConfig {
|
pub struct GenerateConfig {
|
||||||
#[serde(flatten)]
|
#[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