forked from extern/nushell
Slim down configuration readings and nu_cli clean up. (#2559)
We continue refactoring nu_cli and slim down a bit configuration readings with a naive metadata `modified` field check.
This commit is contained in:
parent
50cbf91bc5
commit
10d4edc7af
@ -7,7 +7,7 @@ use crate::prelude::*;
|
||||
use crate::shell::Helper;
|
||||
use crate::EnvironmentSyncer;
|
||||
use futures_codec::FramedRead;
|
||||
use nu_errors::{ProximateShellError, ShellDiagnostic, ShellError};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments};
|
||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
|
||||
|
||||
@ -20,19 +20,6 @@ use std::iter::Iterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
use std::env;
|
||||
|
||||
@ -64,15 +51,8 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
search_paths
|
||||
}
|
||||
|
||||
pub fn create_default_context(
|
||||
syncer: &mut crate::EnvironmentSyncer,
|
||||
interactive: bool,
|
||||
) -> Result<Context, Box<dyn Error>> {
|
||||
syncer.load_environment();
|
||||
|
||||
pub fn create_default_context(interactive: bool) -> Result<Context, Box<dyn Error>> {
|
||||
let mut context = Context::basic()?;
|
||||
syncer.sync_env_vars(&mut context);
|
||||
syncer.sync_path_vars(&mut context);
|
||||
|
||||
{
|
||||
use crate::commands::*;
|
||||
@ -299,282 +279,62 @@ pub async fn run_vec_of_pipelines(
|
||||
pipelines: Vec<String>,
|
||||
redirect_stdin: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = crate::EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(&mut syncer, false)?;
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(false)?;
|
||||
let config = syncer.get_config();
|
||||
|
||||
let _ = register_plugins(&mut context);
|
||||
context.configure(&config, |_, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
}
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ = run_pipeline_standalone(
|
||||
pipeline_string,
|
||||
false,
|
||||
&mut context,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("warning: expected a table of pipeline strings as startup commands");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = register_plugins(ctx);
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &config).await;
|
||||
|
||||
for pipeline in pipelines {
|
||||
run_pipeline_standalone(pipeline, redirect_stdin, &mut context, true).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_pipeline_standalone(
|
||||
pipeline: String,
|
||||
redirect_stdin: bool,
|
||||
context: &mut Context,
|
||||
exit_on_error: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
let error_code = {
|
||||
let errors = context.current_errors.clone();
|
||||
let errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if error_code != 0 && exit_on_error {
|
||||
std::process::exit(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if exit_on_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_rustyline_configuration() -> (Editor<Helper>, IndexMap<String, Value>) {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlLeft,
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlRight,
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
let config = match config::config(Tag::unknown()) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
eprintln!("Config could not be loaded.");
|
||||
if let ShellError {
|
||||
error: ProximateShellError::Diagnostic(ShellDiagnostic { diagnostic }),
|
||||
..
|
||||
} = e
|
||||
{
|
||||
eprintln!("{}", diagnostic.message);
|
||||
}
|
||||
IndexMap::new()
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(config) = config::config(Tag::unknown()) {
|
||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => {
|
||||
rustyline::config::EditMode::Emacs
|
||||
}
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => {
|
||||
rustyline::config::BellStyle::None
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => {
|
||||
rustyline::ColorMode::Disabled
|
||||
}
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(rl, config)
|
||||
}
|
||||
|
||||
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input.
|
||||
pub async fn cli(
|
||||
mut syncer: EnvironmentSyncer,
|
||||
mut context: Context,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let configuration = nu_data::config::NuConfig::new();
|
||||
pub async fn cli(mut context: Context) -> Result<(), Box<dyn Error>> {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let configuration = syncer.get_config();
|
||||
|
||||
let mut rl = default_rustyline_editor_configuration();
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
syncer.load_environment();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = configure_ctrl_c(ctx);
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
|
||||
let helper = Some(nu_line_editor_helper(ctx, config));
|
||||
rl.set_helper(helper);
|
||||
});
|
||||
|
||||
let _ = run_startup_commands(&mut context, &configuration).await;
|
||||
|
||||
let history_path = crate::commands::history::history_path(&configuration);
|
||||
|
||||
let (mut rl, config) = create_rustyline_configuration();
|
||||
|
||||
// we are ok if history does not exist
|
||||
let _ = rl.load_history(&history_path);
|
||||
|
||||
let skip_welcome_message = config
|
||||
.get("skip_welcome_message")
|
||||
let skip_welcome_message = configuration
|
||||
.var("skip_welcome_message")
|
||||
.map(|x| x.is_true())
|
||||
.unwrap_or(false);
|
||||
if !skip_welcome_message {
|
||||
@ -589,44 +349,8 @@ pub async fn cli(
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
}
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ = run_pipeline_standalone(
|
||||
pipeline_string,
|
||||
false,
|
||||
&mut context,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("warning: expected a table of pipeline strings as startup commands");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if context.ctrl_c.load(Ordering::SeqCst) {
|
||||
context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
@ -635,12 +359,8 @@ pub async fn cli(
|
||||
|
||||
let cwd = context.shell_manager.path();
|
||||
|
||||
let hinter = init_hinter(&config);
|
||||
|
||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone(), hinter)));
|
||||
|
||||
let colored_prompt = {
|
||||
if let Some(prompt) = config.get("prompt") {
|
||||
if let Some(prompt) = configuration.var("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
match nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from) {
|
||||
@ -729,9 +449,20 @@ pub async fn cli(
|
||||
// Check the config to see if we need to update the path
|
||||
// TODO: make sure config is cached so we don't path this load every call
|
||||
// FIXME: we probably want to be a bit more graceful if we can't set the environment
|
||||
syncer.reload();
|
||||
syncer.sync_env_vars(&mut context);
|
||||
syncer.sync_path_vars(&mut context);
|
||||
|
||||
context.configure(&configuration, |config, ctx| {
|
||||
if syncer.did_config_change() {
|
||||
syncer.reload();
|
||||
syncer.sync_env_vars(ctx);
|
||||
syncer.sync_path_vars(ctx);
|
||||
}
|
||||
|
||||
if let Err(reason) = syncer.autoenv(ctx) {
|
||||
print_err(reason, &Text::from(""));
|
||||
}
|
||||
|
||||
let _ = configure_rustyline_editor(&mut rl, config);
|
||||
});
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
@ -784,15 +515,279 @@ pub async fn cli(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_hinter(config: &IndexMap<String, Value>) -> Option<rustyline::hint::HistoryHinter> {
|
||||
// Show hints unless explicitly disabled in config
|
||||
if let Some(line_editor_vars) = config.get("line_editor") {
|
||||
pub fn register_plugins(context: &mut Context) -> Result<(), ShellError> {
|
||||
if let Ok(plugins) = crate::plugin::scan(search_paths()) {
|
||||
context.add_commands(
|
||||
plugins
|
||||
.into_iter()
|
||||
.filter(|p| !context.is_command_registered(p.name()))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_ctrl_c(_context: &mut Context) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "ctrlc")]
|
||||
{
|
||||
let cc = _context.ctrl_c.clone();
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
cc.store(true, Ordering::SeqCst);
|
||||
})?;
|
||||
|
||||
if _context.ctrl_c.load(Ordering::SeqCst) {
|
||||
_context.ctrl_c.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_startup_commands(
|
||||
context: &mut Context,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(commands) = config.var("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
..
|
||||
} => {
|
||||
for pipeline in pipelines {
|
||||
if let Ok(pipeline_string) = pipeline.as_string() {
|
||||
let _ =
|
||||
run_pipeline_standalone(pipeline_string, false, context, false).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::untagged_runtime_error(
|
||||
"expected a table of pipeline strings as startup commands",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run_pipeline_standalone(
|
||||
pipeline: String,
|
||||
redirect_stdin: bool,
|
||||
context: &mut Context,
|
||||
exit_on_error: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let line = process_line(Ok(pipeline), context, redirect_stdin, false).await;
|
||||
|
||||
match line {
|
||||
LineResult::Success(line) => {
|
||||
let error_code = {
|
||||
let errors = context.current_errors.clone();
|
||||
let errors = errors.lock();
|
||||
|
||||
if errors.len() > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if error_code != 0 && exit_on_error {
|
||||
std::process::exit(error_code);
|
||||
}
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
context.with_host(|_host| {
|
||||
print_err(err, &Text::from(line.clone()));
|
||||
});
|
||||
|
||||
context.maybe_print_errors(Text::from(line));
|
||||
if exit_on_error {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_rustyline_editor_configuration() -> Editor<Helper> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
let config = Config::builder().color_mode(ColorMode::Forced).build();
|
||||
let mut rl: Editor<_> = Editor::with_config(config);
|
||||
|
||||
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlLeft,
|
||||
Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
|
||||
);
|
||||
rl.bind_sequence(
|
||||
KeyPress::ControlRight,
|
||||
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
|
||||
);
|
||||
|
||||
// Let's set the defaults up front and then override them later if the user indicates
|
||||
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
|
||||
rl.set_max_history_size(100);
|
||||
rl.set_history_ignore_dups(true);
|
||||
rl.set_history_ignore_space(false);
|
||||
rl.set_completion_type(DEFAULT_COMPLETION_MODE);
|
||||
rl.set_completion_prompt_limit(100);
|
||||
rl.set_keyseq_timeout(-1);
|
||||
rl.set_edit_mode(rustyline::config::EditMode::Emacs);
|
||||
rl.set_auto_add_history(false);
|
||||
rl.set_bell_style(rustyline::config::BellStyle::default());
|
||||
rl.set_color_mode(rustyline::ColorMode::Enabled);
|
||||
rl.set_tab_stop(8);
|
||||
|
||||
if let Err(e) = crate::keybinding::load_keybindings(&mut rl) {
|
||||
println!("Error loading keybindings: {:?}", e);
|
||||
}
|
||||
|
||||
rl
|
||||
}
|
||||
|
||||
fn configure_rustyline_editor(
|
||||
rl: &mut Editor<Helper>,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> Result<(), ShellError> {
|
||||
#[cfg(windows)]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::Circular;
|
||||
#[cfg(not(windows))]
|
||||
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
|
||||
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
match idx.as_ref() {
|
||||
"max_history_size" => {
|
||||
if let Ok(max_history_size) = value.as_u64() {
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
}
|
||||
}
|
||||
"history_duplicates" => {
|
||||
// history_duplicates = match value.as_string() {
|
||||
// Ok(s) if s.to_lowercase() == "alwaysadd" => {
|
||||
// rustyline::config::HistoryDuplicates::AlwaysAdd
|
||||
// }
|
||||
// Ok(s) if s.to_lowercase() == "ignoreconsecutive" => {
|
||||
// rustyline::config::HistoryDuplicates::IgnoreConsecutive
|
||||
// }
|
||||
// _ => rustyline::config::HistoryDuplicates::AlwaysAdd,
|
||||
// };
|
||||
if let Ok(history_duplicates) = value.as_bool() {
|
||||
rl.set_history_ignore_dups(history_duplicates);
|
||||
}
|
||||
}
|
||||
"history_ignore_space" => {
|
||||
if let Ok(history_ignore_space) = value.as_bool() {
|
||||
rl.set_history_ignore_space(history_ignore_space);
|
||||
}
|
||||
}
|
||||
"completion_type" => {
|
||||
let completion_type = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "circular" => {
|
||||
rustyline::config::CompletionType::Circular
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "list" => {
|
||||
rustyline::config::CompletionType::List
|
||||
}
|
||||
#[cfg(all(unix, feature = "with-fuzzy"))]
|
||||
Ok(s) if s.to_lowercase() == "fuzzy" => {
|
||||
rustyline::config::CompletionType::Fuzzy
|
||||
}
|
||||
_ => DEFAULT_COMPLETION_MODE,
|
||||
};
|
||||
rl.set_completion_type(completion_type);
|
||||
}
|
||||
"completion_prompt_limit" => {
|
||||
if let Ok(completion_prompt_limit) = value.as_u64() {
|
||||
rl.set_completion_prompt_limit(completion_prompt_limit as usize);
|
||||
}
|
||||
}
|
||||
"keyseq_timeout_ms" => {
|
||||
if let Ok(keyseq_timeout_ms) = value.as_u64() {
|
||||
rl.set_keyseq_timeout(keyseq_timeout_ms as i32);
|
||||
}
|
||||
}
|
||||
"edit_mode" => {
|
||||
let edit_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "vi" => rustyline::config::EditMode::Vi,
|
||||
Ok(s) if s.to_lowercase() == "emacs" => rustyline::config::EditMode::Emacs,
|
||||
_ => rustyline::config::EditMode::Emacs,
|
||||
};
|
||||
rl.set_edit_mode(edit_mode);
|
||||
// Note: When edit_mode is Emacs, the keyseq_timeout_ms is set to -1
|
||||
// no matter what you may have configured. This is so that key chords
|
||||
// can be applied without having to do them in a given timeout. So,
|
||||
// it essentially turns off the keyseq timeout.
|
||||
}
|
||||
"auto_add_history" => {
|
||||
if let Ok(auto_add_history) = value.as_bool() {
|
||||
rl.set_auto_add_history(auto_add_history);
|
||||
}
|
||||
}
|
||||
"bell_style" => {
|
||||
let bell_style = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "audible" => {
|
||||
rustyline::config::BellStyle::Audible
|
||||
}
|
||||
Ok(s) if s.to_lowercase() == "none" => rustyline::config::BellStyle::None,
|
||||
Ok(s) if s.to_lowercase() == "visible" => {
|
||||
rustyline::config::BellStyle::Visible
|
||||
}
|
||||
_ => rustyline::config::BellStyle::default(),
|
||||
};
|
||||
rl.set_bell_style(bell_style);
|
||||
}
|
||||
"color_mode" => {
|
||||
let color_mode = match value.as_string() {
|
||||
Ok(s) if s.to_lowercase() == "enabled" => rustyline::ColorMode::Enabled,
|
||||
Ok(s) if s.to_lowercase() == "forced" => rustyline::ColorMode::Forced,
|
||||
Ok(s) if s.to_lowercase() == "disabled" => rustyline::ColorMode::Disabled,
|
||||
_ => rustyline::ColorMode::Enabled,
|
||||
};
|
||||
rl.set_color_mode(color_mode);
|
||||
}
|
||||
"tab_stop" => {
|
||||
if let Ok(tab_stop) = value.as_u64() {
|
||||
rl.set_tab_stop(tab_stop as usize);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn nu_line_editor_helper(
|
||||
context: &mut Context,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
) -> crate::shell::Helper {
|
||||
let hinter = rustyline_hinter(config);
|
||||
crate::shell::Helper::new(context.clone(), hinter)
|
||||
}
|
||||
|
||||
fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hint::HistoryHinter> {
|
||||
if let Some(line_editor_vars) = config.var("line_editor") {
|
||||
for (idx, value) in line_editor_vars.row_entries() {
|
||||
if idx == "show_hints" && value.expect_string() == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(rustyline::hint::HistoryHinter {})
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ pub trait ConfigExtensions: Debug + Send {
|
||||
}
|
||||
|
||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||
let vars = config.vars.lock();
|
||||
let vars = &config.vars;
|
||||
|
||||
if let Some(mode) = vars.get("pivot_mode") {
|
||||
let mode = match mode.as_string() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::NuConfig;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
|
||||
use std::fs::File;
|
||||
@ -9,9 +9,7 @@ use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_LOCATION: &str = "history.txt";
|
||||
|
||||
pub fn history_path(config: &NuConfig) -> PathBuf {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
pub fn history_path(config: &dyn Conf) -> PathBuf {
|
||||
let default_path = nu_data::config::user_data()
|
||||
.map(|mut p| {
|
||||
p.push(DEFAULT_LOCATION);
|
||||
@ -19,7 +17,8 @@ pub fn history_path(config: &NuConfig) -> PathBuf {
|
||||
})
|
||||
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
|
||||
|
||||
vars.get("history-path")
|
||||
config
|
||||
.var("history-path")
|
||||
.map_or(default_path.clone(), |custom_path| {
|
||||
match custom_path.as_string() {
|
||||
Ok(path) => PathBuf::from(path),
|
||||
@ -54,7 +53,7 @@ impl WholeStreamCommand for History {
|
||||
}
|
||||
|
||||
fn history(args: CommandArgs, _registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let config = NuConfig::new();
|
||||
let config: Box<dyn Conf> = Box::new(NuConfig::new());
|
||||
let tag = args.call_info.name_tag;
|
||||
let path = history_path(&config);
|
||||
let file = File::open(path);
|
||||
|
@ -27,7 +27,7 @@ pub fn header_alignment_from_value(align_value: Option<&Value>) -> nu_table::Ali
|
||||
}
|
||||
|
||||
pub fn get_color_from_key_and_subkey(config: &NuConfig, key: &str, subkey: &str) -> Option<Value> {
|
||||
let vars = config.vars.lock();
|
||||
let vars = &config.vars;
|
||||
|
||||
if let Some(config_vars) = vars.get(key) {
|
||||
for (kee, value) in config_vars.row_entries() {
|
||||
@ -47,7 +47,7 @@ pub fn header_bold_from_value(bold_value: Option<&Value>) -> bool {
|
||||
}
|
||||
|
||||
pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
||||
let vars = config.vars.lock();
|
||||
let vars = &config.vars;
|
||||
|
||||
vars.get("table_mode")
|
||||
.map_or(nu_table::Theme::compact(), |mode| match mode.as_string() {
|
||||
@ -62,7 +62,7 @@ pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
||||
}
|
||||
|
||||
pub fn disabled_indexes(config: &NuConfig) -> bool {
|
||||
let vars = config.vars.lock();
|
||||
let vars = &config.vars;
|
||||
|
||||
vars.get("disable_table_indexes")
|
||||
.map_or(false, |x| x.as_bool().unwrap_or(false))
|
||||
|
@ -210,6 +210,14 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn configure<T>(
|
||||
&mut self,
|
||||
config: &dyn nu_data::config::Conf,
|
||||
block: impl FnOnce(&dyn nu_data::config::Conf, &mut Self) -> T,
|
||||
) {
|
||||
block(config, &mut *self);
|
||||
}
|
||||
|
||||
pub(crate) fn with_host<T>(&mut self, block: impl FnOnce(&mut dyn Host) -> T) -> T {
|
||||
let mut host = self.host.lock();
|
||||
|
||||
|
152
crates/nu-cli/src/env/environment_syncer.rs
vendored
152
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,14 +1,13 @@
|
||||
use crate::context::Context;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
|
||||
use crate::env::environment::{Env, Environment};
|
||||
use nu_source::Text;
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
use nu_errors::ShellError;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct EnvironmentSyncer {
|
||||
pub env: Arc<Mutex<Box<Environment>>>,
|
||||
pub config: Arc<Box<dyn Conf>>,
|
||||
pub config: Arc<Mutex<Box<dyn Conf>>>,
|
||||
}
|
||||
|
||||
impl Default for EnvironmentSyncer {
|
||||
@ -18,42 +17,60 @@ impl Default for EnvironmentSyncer {
|
||||
}
|
||||
|
||||
impl EnvironmentSyncer {
|
||||
pub fn with_config(config: Box<dyn Conf>) -> Self {
|
||||
EnvironmentSyncer {
|
||||
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> EnvironmentSyncer {
|
||||
EnvironmentSyncer {
|
||||
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
|
||||
config: Arc::new(Box::new(NuConfig::new())),
|
||||
config: Arc::new(Mutex::new(Box::new(NuConfig::new()))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_config(&mut self, config: Box<dyn Conf>) {
|
||||
self.config = Arc::new(config);
|
||||
self.config = Arc::new(Mutex::new(config));
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> Box<dyn Conf> {
|
||||
self.config.clone().clone_box()
|
||||
let config = self.config.lock();
|
||||
|
||||
config.clone_box()
|
||||
}
|
||||
|
||||
pub fn load_environment(&mut self) {
|
||||
let config = self.config.clone();
|
||||
let config = self.config.lock();
|
||||
|
||||
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
|
||||
}
|
||||
|
||||
pub fn did_config_change(&mut self) -> bool {
|
||||
let config = self.config.lock();
|
||||
config.is_modified().unwrap_or_else(|_| false)
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
self.config.reload();
|
||||
let mut config = self.config.lock();
|
||||
config.reload();
|
||||
|
||||
let mut environment = self.env.lock();
|
||||
environment.morph(&*self.config);
|
||||
environment.morph(&*config);
|
||||
}
|
||||
|
||||
pub fn autoenv(&self, ctx: &mut Context) -> Result<(), ShellError> {
|
||||
let mut environment = self.env.lock();
|
||||
let auto = environment.autoenv(ctx.user_recently_used_autoenv_untrust);
|
||||
ctx.user_recently_used_autoenv_untrust = false;
|
||||
auto
|
||||
}
|
||||
|
||||
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
|
||||
let mut environment = self.env.lock();
|
||||
|
||||
if let Err(e) = environment.autoenv(ctx.user_recently_used_autoenv_untrust) {
|
||||
crate::cli::print_err(e, &Text::from(""));
|
||||
}
|
||||
ctx.user_recently_used_autoenv_untrust = false;
|
||||
if environment.env().is_some() {
|
||||
for (name, value) in ctx.with_host(|host| host.vars()) {
|
||||
if name != "path" && name != "PATH" {
|
||||
@ -142,6 +159,113 @@ mod tests {
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
// This test fails on Linux.
|
||||
// It's possible it has something to do with the fake configuration
|
||||
// TODO: More tests.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[test]
|
||||
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
|
||||
{
|
||||
let mut ctx = Context::basic()?;
|
||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||
|
||||
let mut expected = IndexMap::new();
|
||||
expected.insert(
|
||||
"SHELL".to_string(),
|
||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||
);
|
||||
|
||||
Playground::setup("syncs_env_from_config_updated_test_1", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
FileWithContent(
|
||||
"configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
"#,
|
||||
),
|
||||
FileWithContent(
|
||||
"updated_configuration.toml",
|
||||
r#"
|
||||
[env]
|
||||
SHELL = "/usr/bin/you_already_made_the_nu_choice"
|
||||
USER = "NUNO"
|
||||
"#,
|
||||
),
|
||||
]);
|
||||
|
||||
let file = dirs.test().join("configuration.toml");
|
||||
let new_file = dirs.test().join("updated_configuration.toml");
|
||||
|
||||
let fake_config = FakeConfig::new(&file);
|
||||
let mut actual = EnvironmentSyncer::with_config(Box::new(fake_config.clone()));
|
||||
|
||||
// Here, the environment variables from the current session
|
||||
// are cleared since we will load and set them from the
|
||||
// configuration file
|
||||
actual.clear_env_vars(&mut ctx);
|
||||
|
||||
// Nu loads the environment variables from the configuration file
|
||||
actual.load_environment();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
{
|
||||
let environment = actual.env.lock();
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!actual.did_config_change());
|
||||
|
||||
// Replacing the newer configuration file to the existing one.
|
||||
let new_config_contents = std::fs::read_to_string(new_file).expect("Failed");
|
||||
std::fs::write(&file, &new_config_contents).expect("Failed");
|
||||
|
||||
// A change has happened
|
||||
assert!(actual.did_config_change());
|
||||
|
||||
// Syncer should reload and add new envs
|
||||
actual.reload();
|
||||
actual.sync_env_vars(&mut ctx);
|
||||
|
||||
expected.insert("USER".to_string(), "NUNO".to_string());
|
||||
|
||||
{
|
||||
let environment = actual.env.lock();
|
||||
let mut vars = IndexMap::new();
|
||||
environment
|
||||
.env()
|
||||
.expect("No variables in the environment.")
|
||||
.row_entries()
|
||||
.for_each(|(name, value)| {
|
||||
vars.insert(
|
||||
name.to_string(),
|
||||
value.as_string().expect("Couldn't convert to string"),
|
||||
);
|
||||
});
|
||||
|
||||
for k in expected.keys() {
|
||||
assert!(vars.contains_key(k));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
|
||||
{
|
||||
|
@ -47,7 +47,8 @@ pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value,
|
||||
UntaggedValue::path(keybinding_path).into_value(&tag),
|
||||
);
|
||||
|
||||
let history = crate::commands::history::history_path(&nu_data::config::NuConfig::new());
|
||||
let config: Box<dyn nu_data::config::Conf> = Box::new(nu_data::config::NuConfig::new());
|
||||
let history = crate::commands::history::history_path(&config);
|
||||
nu_dict.insert_value(
|
||||
"history-path",
|
||||
UntaggedValue::path(history).into_value(&tag),
|
||||
|
@ -204,6 +204,33 @@ pub fn user_data() -> Result<PathBuf, ShellError> {
|
||||
Ok(std::path::PathBuf::from("/"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Status {
|
||||
LastModified(std::time::SystemTime),
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
impl Default for Status {
|
||||
fn default() -> Self {
|
||||
Status::Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_modified(at: &Option<PathBuf>) -> Result<Status, Box<dyn std::error::Error>> {
|
||||
let filename = default_path()?;
|
||||
|
||||
let filename = match at {
|
||||
None => filename,
|
||||
Some(ref file) => file.clone(),
|
||||
};
|
||||
|
||||
if let Ok(time) = filename.metadata()?.modified() {
|
||||
return Ok(Status::LastModified(time));
|
||||
}
|
||||
|
||||
Ok(Status::Unavailable)
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
tag: impl Into<Tag>,
|
||||
at: &Option<PathBuf>,
|
||||
|
@ -2,13 +2,23 @@ use nu_protocol::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Conf: Debug + Send {
|
||||
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>>;
|
||||
fn var(&self, key: &str) -> Option<Value>;
|
||||
fn env(&self) -> Option<Value>;
|
||||
fn path(&self) -> Option<Value>;
|
||||
fn reload(&self);
|
||||
fn reload(&mut self);
|
||||
fn clone_box(&self) -> Box<dyn Conf>;
|
||||
}
|
||||
|
||||
impl Conf for Box<dyn Conf> {
|
||||
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
(**self).is_modified()
|
||||
}
|
||||
|
||||
fn var(&self, key: &str) -> Option<Value> {
|
||||
(**self).var(key)
|
||||
}
|
||||
|
||||
fn env(&self) -> Option<Value> {
|
||||
(**self).env()
|
||||
}
|
||||
@ -17,7 +27,7 @@ impl Conf for Box<dyn Conf> {
|
||||
(**self).path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
fn reload(&mut self) {
|
||||
(**self).reload();
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,24 @@
|
||||
use crate::config::{read, Conf};
|
||||
use crate::config::{last_modified, read, Conf, Status};
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::Value;
|
||||
use nu_source::Tag;
|
||||
use parking_lot::Mutex;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NuConfig {
|
||||
pub vars: Arc<Mutex<IndexMap<String, Value>>>,
|
||||
pub vars: IndexMap<String, Value>,
|
||||
pub modified_at: Status,
|
||||
}
|
||||
|
||||
impl Conf for NuConfig {
|
||||
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
self.is_modified()
|
||||
}
|
||||
|
||||
fn var(&self, key: &str) -> Option<Value> {
|
||||
self.var(key)
|
||||
}
|
||||
|
||||
fn env(&self) -> Option<Value> {
|
||||
self.env()
|
||||
}
|
||||
@ -20,11 +27,17 @@ impl Conf for NuConfig {
|
||||
self.path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
let mut vars = self.vars.lock();
|
||||
fn reload(&mut self) {
|
||||
let vars = &mut self.vars;
|
||||
|
||||
if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||
vars.extend(variables);
|
||||
|
||||
self.modified_at = if let Ok(status) = last_modified(&None) {
|
||||
status
|
||||
} else {
|
||||
Status::Unavailable
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +47,24 @@ impl Conf for NuConfig {
|
||||
}
|
||||
|
||||
impl NuConfig {
|
||||
pub fn with(config_file: Option<std::path::PathBuf>) -> NuConfig {
|
||||
match &config_file {
|
||||
None => NuConfig::new(),
|
||||
Some(_) => {
|
||||
let vars = if let Ok(variables) = read(Tag::unknown(), &config_file) {
|
||||
variables
|
||||
} else {
|
||||
IndexMap::default()
|
||||
};
|
||||
|
||||
NuConfig {
|
||||
vars,
|
||||
modified_at: NuConfig::get_last_modified(&config_file),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> NuConfig {
|
||||
let vars = if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||
variables
|
||||
@ -42,12 +73,45 @@ impl NuConfig {
|
||||
};
|
||||
|
||||
NuConfig {
|
||||
vars: Arc::new(Mutex::new(vars)),
|
||||
vars,
|
||||
modified_at: NuConfig::get_last_modified(&None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_last_modified(config_file: &Option<std::path::PathBuf>) -> Status {
|
||||
if let Ok(status) = last_modified(config_file) {
|
||||
status
|
||||
} else {
|
||||
Status::Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let modified_at = &self.modified_at;
|
||||
|
||||
Ok(match (NuConfig::get_last_modified(&None), modified_at) {
|
||||
(Status::LastModified(left), Status::LastModified(right)) => {
|
||||
let left = left.duration_since(std::time::UNIX_EPOCH)?;
|
||||
let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
|
||||
|
||||
left != right
|
||||
}
|
||||
(_, _) => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn var(&self, key: &str) -> Option<Value> {
|
||||
let vars = &self.vars;
|
||||
|
||||
if let Some(value) = vars.get(key) {
|
||||
return Some(value.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn env(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
let vars = &self.vars;
|
||||
|
||||
if let Some(env_vars) = vars.get("env") {
|
||||
return Some(env_vars.clone());
|
||||
@ -57,7 +121,7 @@ impl NuConfig {
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
let vars = &self.vars;
|
||||
|
||||
if let Some(env_vars) = vars.get("path") {
|
||||
return Some(env_vars.clone());
|
||||
|
@ -1,17 +1,22 @@
|
||||
use crate::config::{read, Conf, NuConfig};
|
||||
use indexmap::IndexMap;
|
||||
use crate::config::{Conf, NuConfig, Status};
|
||||
use nu_protocol::Value;
|
||||
use nu_source::Tag;
|
||||
use parking_lot::Mutex;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FakeConfig {
|
||||
pub config: NuConfig,
|
||||
source_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Conf for FakeConfig {
|
||||
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
self.is_modified()
|
||||
}
|
||||
|
||||
fn var(&self, key: &str) -> Option<Value> {
|
||||
self.config.var(key)
|
||||
}
|
||||
|
||||
fn env(&self) -> Option<Value> {
|
||||
self.config.env()
|
||||
}
|
||||
@ -20,8 +25,8 @@ impl Conf for FakeConfig {
|
||||
self.config.path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
// no-op
|
||||
fn reload(&mut self) {
|
||||
self.reload()
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn Conf> {
|
||||
@ -31,18 +36,31 @@ impl Conf for FakeConfig {
|
||||
|
||||
impl FakeConfig {
|
||||
pub fn new(config_file: &Path) -> FakeConfig {
|
||||
let config_file = PathBuf::from(config_file);
|
||||
|
||||
let vars = if let Ok(variables) = read(Tag::unknown(), &Some(config_file)) {
|
||||
variables
|
||||
} else {
|
||||
IndexMap::default()
|
||||
};
|
||||
let config_file = Some(PathBuf::from(config_file));
|
||||
|
||||
FakeConfig {
|
||||
config: NuConfig {
|
||||
vars: Arc::new(Mutex::new(vars)),
|
||||
},
|
||||
config: NuConfig::with(config_file.clone()),
|
||||
source_file: config_file,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let modified_at = &self.config.modified_at;
|
||||
|
||||
Ok(
|
||||
match (NuConfig::get_last_modified(&self.source_file), modified_at) {
|
||||
(Status::LastModified(left), Status::LastModified(right)) => {
|
||||
let left = left.duration_since(std::time::UNIX_EPOCH)?;
|
||||
let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
|
||||
|
||||
left != right
|
||||
}
|
||||
(_, _) => false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
self.config = NuConfig::with(self.source_file.clone());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use clap::{App, Arg};
|
||||
use log::LevelFilter;
|
||||
use nu_cli::create_default_context;
|
||||
use nu_cli::utils::test_bins as binaries;
|
||||
use nu_cli::{create_default_context, EnvironmentSyncer};
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::{prelude::*, BufReader};
|
||||
@ -160,14 +160,13 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
None => {
|
||||
let mut syncer = EnvironmentSyncer::new();
|
||||
let mut context = create_default_context(&mut syncer, true)?;
|
||||
let mut context = create_default_context(true)?;
|
||||
|
||||
if !matches.is_present("skip-plugins") {
|
||||
let _ = nu_cli::register_plugins(&mut context);
|
||||
}
|
||||
|
||||
futures::executor::block_on(nu_cli::cli(syncer, context))?;
|
||||
futures::executor::block_on(nu_cli::cli(context))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user