mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-03 01:50:12 +00:00
fix(config): use the config default_channel
field as a fallback when no channel is specified (#524)
Fixes #520 fyi @lalvarezt
This commit is contained in:
parent
7bbf538898
commit
dfbdd65107
@ -12,7 +12,7 @@ use television::{
|
||||
action::Action,
|
||||
channels::{
|
||||
entry::{into_ranges, Entry},
|
||||
prototypes::{Cable, ChannelPrototype},
|
||||
prototypes::Cable,
|
||||
},
|
||||
config::{Config, ConfigEnv},
|
||||
screen::{colors::ResultsColorscheme, results::build_results_list},
|
||||
@ -464,6 +464,8 @@ pub fn draw(c: &mut Criterion) {
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
||||
let cable = Cable::default();
|
||||
|
||||
c.bench_function("draw", |b| {
|
||||
b.to_async(&rt).iter_batched(
|
||||
// FIXME: this is kind of hacky
|
||||
@ -472,7 +474,7 @@ pub fn draw(c: &mut Criterion) {
|
||||
let backend = TestBackend::new(width, height);
|
||||
let terminal = Terminal::new(backend).unwrap();
|
||||
let (tx, _) = tokio::sync::mpsc::unbounded_channel();
|
||||
let channel_prototype = ChannelPrototype::default();
|
||||
let channel_prototype = cable.get_channel("files");
|
||||
// Wait for the channel to finish loading
|
||||
let mut tv = Television::new(
|
||||
tx,
|
||||
|
@ -4,7 +4,10 @@ use std::{
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use crate::{cable::CableSpec, channels::preview::PreviewCommand};
|
||||
use crate::{
|
||||
cable::CableSpec, channels::preview::PreviewCommand,
|
||||
cli::unknown_channel_exit,
|
||||
};
|
||||
|
||||
/// A prototype for cable channels.
|
||||
///
|
||||
@ -73,19 +76,19 @@ impl ChannelPrototype {
|
||||
preview_command: preview,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_preview(self, preview_command: Option<PreviewCommand>) -> Self {
|
||||
Self::new(
|
||||
&self.name,
|
||||
&self.source_command,
|
||||
self.interactive,
|
||||
preview_command,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub const DEFAULT_PROTOTYPE_NAME: &str = "files";
|
||||
|
||||
impl Default for ChannelPrototype {
|
||||
fn default() -> Self {
|
||||
Cable::default()
|
||||
.get(DEFAULT_PROTOTYPE_NAME)
|
||||
.cloned()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ChannelPrototype {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
@ -109,12 +112,14 @@ impl Deref for Cable {
|
||||
}
|
||||
|
||||
impl Cable {
|
||||
pub fn default_channel(&self) -> ChannelPrototype {
|
||||
self.get(DEFAULT_PROTOTYPE_NAME)
|
||||
pub fn get_channel(&self, name: &str) -> ChannelPrototype {
|
||||
self.get(name)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Default channel '{DEFAULT_PROTOTYPE_NAME}' not found")
|
||||
})
|
||||
.unwrap_or_else(|| unknown_channel_exit(name))
|
||||
}
|
||||
|
||||
pub fn has_channel(&self, name: &str) -> bool {
|
||||
self.contains_key(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ pub mod args;
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PostProcessedCli {
|
||||
pub channel: ChannelPrototype,
|
||||
pub channel: Option<String>,
|
||||
pub preview_command: Option<PreviewCommand>,
|
||||
pub no_preview: bool,
|
||||
pub tick_rate: Option<f64>,
|
||||
@ -40,7 +40,7 @@ pub struct PostProcessedCli {
|
||||
impl Default for PostProcessedCli {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
channel: ChannelPrototype::default(),
|
||||
channel: None,
|
||||
preview_command: None,
|
||||
no_preview: false,
|
||||
tick_rate: None,
|
||||
@ -60,87 +60,59 @@ impl Default for PostProcessedCli {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cli> for PostProcessedCli {
|
||||
fn from(cli: Cli) -> Self {
|
||||
// parse literal keybindings passed through the CLI
|
||||
let keybindings: Option<KeyBindings> = cli.keybindings.map(|kb| {
|
||||
parse_keybindings_literal(&kb, CLI_KEYBINDINGS_DELIMITER)
|
||||
.map_err(|e| {
|
||||
cli_parsing_error_exit(&e.to_string());
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
pub fn post_process(cli: Cli, cable: &Cable) -> PostProcessedCli {
|
||||
// Parse literal keybindings passed through the CLI
|
||||
let keybindings = cli.keybindings.as_ref().map(|kb| {
|
||||
parse_keybindings_literal(kb, CLI_KEYBINDINGS_DELIMITER)
|
||||
.unwrap_or_else(|e| cli_parsing_error_exit(&e.to_string()))
|
||||
});
|
||||
|
||||
// parse the preview command if provided
|
||||
let preview_command = cli.preview.map(|preview| PreviewCommand {
|
||||
command: preview,
|
||||
delimiter: cli.delimiter.clone(),
|
||||
offset_expr: cli.preview_offset.clone(),
|
||||
});
|
||||
// Parse the preview command if provided
|
||||
let preview_command = cli.preview.as_ref().map(|preview| PreviewCommand {
|
||||
command: preview.clone(),
|
||||
delimiter: cli.delimiter.clone(),
|
||||
offset_expr: cli.preview_offset.clone(),
|
||||
});
|
||||
|
||||
let mut channel: ChannelPrototype;
|
||||
let working_directory: Option<String>;
|
||||
|
||||
let cable = cable::load_cable().unwrap_or_default();
|
||||
if cli.channel.is_none() {
|
||||
channel = cable.default_channel();
|
||||
working_directory = cli.working_directory;
|
||||
} else {
|
||||
let cli_channel = cli.channel.as_ref().unwrap().to_owned();
|
||||
match parse_channel(&cli_channel, &cable) {
|
||||
Ok(p) => {
|
||||
channel = p;
|
||||
working_directory = cli.working_directory;
|
||||
}
|
||||
Err(_) => {
|
||||
// if the path is provided as first argument and it exists, use it as the working
|
||||
// directory and default to the files channel
|
||||
if cli.working_directory.is_none()
|
||||
&& Path::new(&cli_channel).exists()
|
||||
{
|
||||
channel = cable.default_channel();
|
||||
working_directory = Some(cli.channel.unwrap().clone());
|
||||
} else {
|
||||
unknown_channel_exit(&cli.channel.unwrap());
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
// Determine channel and working_directory
|
||||
let (channel, working_directory) = match &cli.channel {
|
||||
Some(c) if !cable.has_channel(c) => {
|
||||
if cli.working_directory.is_none() && Path::new(c).exists() {
|
||||
(None, Some(c.clone()))
|
||||
} else {
|
||||
unknown_channel_exit(c);
|
||||
}
|
||||
}
|
||||
_ => (cli.channel.clone(), cli.working_directory.clone()),
|
||||
};
|
||||
|
||||
// override the default previewer
|
||||
if let Some(preview_cmd) = &preview_command {
|
||||
channel.preview_command = Some(preview_cmd.clone());
|
||||
}
|
||||
|
||||
Self {
|
||||
channel,
|
||||
preview_command,
|
||||
no_preview: cli.no_preview,
|
||||
tick_rate: cli.tick_rate,
|
||||
frame_rate: cli.frame_rate,
|
||||
input: cli.input,
|
||||
custom_header: cli.custom_header,
|
||||
command: cli.command,
|
||||
working_directory,
|
||||
autocomplete_prompt: cli.autocomplete_prompt,
|
||||
keybindings,
|
||||
exact: cli.exact,
|
||||
select_1: cli.select_1,
|
||||
no_remote: cli.no_remote,
|
||||
no_help: cli.no_help,
|
||||
ui_scale: cli.ui_scale,
|
||||
}
|
||||
PostProcessedCli {
|
||||
channel,
|
||||
preview_command,
|
||||
no_preview: cli.no_preview,
|
||||
tick_rate: cli.tick_rate,
|
||||
frame_rate: cli.frame_rate,
|
||||
input: cli.input,
|
||||
custom_header: cli.custom_header,
|
||||
command: cli.command,
|
||||
working_directory,
|
||||
autocomplete_prompt: cli.autocomplete_prompt,
|
||||
keybindings,
|
||||
exact: cli.exact,
|
||||
select_1: cli.select_1,
|
||||
no_remote: cli.no_remote,
|
||||
no_help: cli.no_help,
|
||||
ui_scale: cli.ui_scale,
|
||||
}
|
||||
}
|
||||
|
||||
fn cli_parsing_error_exit(message: &str) {
|
||||
fn cli_parsing_error_exit(message: &str) -> ! {
|
||||
eprintln!("Error parsing CLI arguments: {message}\n");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
fn unknown_channel_exit(channel: &str) {
|
||||
eprintln!("Unknown channel: {channel}\n");
|
||||
pub fn unknown_channel_exit(channel: &str) -> ! {
|
||||
eprintln!("Channel not found: {channel}\n");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@ -165,20 +137,6 @@ fn parse_keybindings_literal(
|
||||
toml::from_str(&toml_definition).map_err(|e| anyhow!(e))
|
||||
}
|
||||
|
||||
pub fn parse_channel(
|
||||
channel: &str,
|
||||
cable_channels: &Cable,
|
||||
) -> Result<ChannelPrototype> {
|
||||
// try to parse the channel as a cable channel
|
||||
match cable_channels
|
||||
.iter()
|
||||
.find(|(k, _)| k.to_lowercase() == channel)
|
||||
{
|
||||
Some((_, v)) => Ok(v.clone()),
|
||||
None => Err(anyhow!("The following channel wasn't found among cable channels: {channel}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_channels() {
|
||||
for c in cable::load_cable().unwrap_or_default().keys() {
|
||||
println!("\t{c}");
|
||||
@ -212,17 +170,17 @@ pub fn guess_channel_from_prompt(
|
||||
prompt: &str,
|
||||
command_mapping: &FxHashMap<String, String>,
|
||||
fallback_channel: &str,
|
||||
cable_channels: &Cable,
|
||||
) -> Result<ChannelPrototype> {
|
||||
cable: &Cable,
|
||||
) -> ChannelPrototype {
|
||||
debug!("Guessing channel from prompt: {}", prompt);
|
||||
// git checkout -qf
|
||||
// --- -------- --- <---------
|
||||
let fallback = cable_channels
|
||||
let fallback = cable
|
||||
.get(fallback_channel)
|
||||
.expect("Fallback channel not found in cable channels")
|
||||
.clone();
|
||||
if prompt.trim().is_empty() {
|
||||
return Ok(fallback);
|
||||
return fallback;
|
||||
}
|
||||
let rev_prompt_words = prompt.split_whitespace().rev();
|
||||
let mut stack = Vec::new();
|
||||
@ -236,7 +194,7 @@ pub fn guess_channel_from_prompt(
|
||||
for word in rev_prompt_words.clone() {
|
||||
// if the stack is empty, we have a match
|
||||
if stack.is_empty() {
|
||||
return parse_channel(channel, cable_channels);
|
||||
return cable.get_channel(channel);
|
||||
}
|
||||
// if the word matches the top of the stack, pop it
|
||||
if stack.last() == Some(&word) {
|
||||
@ -245,14 +203,14 @@ pub fn guess_channel_from_prompt(
|
||||
}
|
||||
// if the stack is empty, we have a match
|
||||
if stack.is_empty() {
|
||||
return parse_channel(channel, cable_channels);
|
||||
return cable.get_channel(channel);
|
||||
}
|
||||
// reset the stack
|
||||
stack.clear();
|
||||
}
|
||||
|
||||
debug!("No match found, falling back to default channel");
|
||||
Ok(fallback)
|
||||
fallback
|
||||
}
|
||||
|
||||
const VERSION_MESSAGE: &str = env!("CARGO_PKG_VERSION");
|
||||
@ -304,18 +262,9 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let post_processed_cli: PostProcessedCli = cli.into();
|
||||
let cable = cable::load_cable().unwrap_or_default();
|
||||
let post_processed_cli = post_process(cli, &cable);
|
||||
|
||||
let expected = ChannelPrototype {
|
||||
preview_command: Some(PreviewCommand {
|
||||
command: "bat -n --color=always {}".to_string(),
|
||||
delimiter: ":".to_string(),
|
||||
offset_expr: None,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(post_processed_cli.channel, expected,);
|
||||
assert_eq!(
|
||||
post_processed_cli.preview_command,
|
||||
Some(PreviewCommand {
|
||||
@ -341,9 +290,9 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let post_processed_cli: PostProcessedCli = cli.into();
|
||||
let cable = cable::load_cable().unwrap_or_default();
|
||||
let post_processed_cli = post_process(cli, &cable);
|
||||
|
||||
assert_eq!(post_processed_cli.channel, ChannelPrototype::default(),);
|
||||
assert_eq!(
|
||||
post_processed_cli.working_directory,
|
||||
Some(".".to_string())
|
||||
@ -364,7 +313,8 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let post_processed_cli: PostProcessedCli = cli.into();
|
||||
let cable = cable::load_cable().unwrap_or_default();
|
||||
let post_processed_cli = post_process(cli, &cable);
|
||||
|
||||
let mut expected = KeyBindings::default();
|
||||
expected.insert(Action::Quit, Binding::SingleKey(Key::Esc));
|
||||
@ -402,8 +352,7 @@ mod tests {
|
||||
&command_mapping,
|
||||
fallback,
|
||||
&channels,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
assert_eq!(channel.name, "files");
|
||||
}
|
||||
@ -420,8 +369,7 @@ mod tests {
|
||||
&command_mapping,
|
||||
fallback,
|
||||
&channels,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
assert_eq!(channel.name, fallback);
|
||||
}
|
||||
@ -438,8 +386,7 @@ mod tests {
|
||||
&command_mapping,
|
||||
fallback,
|
||||
&channels,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
assert_eq!(channel.name, fallback);
|
||||
}
|
||||
|
@ -5,8 +5,9 @@ use std::process::exit;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use television::cable::load_cable;
|
||||
use television::cli::post_process;
|
||||
use television::{
|
||||
cable,
|
||||
channels::prototypes::{Cable, ChannelPrototype},
|
||||
utils::clipboard::CLIPBOARD,
|
||||
};
|
||||
@ -35,15 +36,16 @@ async fn main() -> Result<()> {
|
||||
// process the CLI arguments
|
||||
let cli = Cli::parse();
|
||||
debug!("CLI: {:?}", cli);
|
||||
let args: PostProcessedCli = cli.into();
|
||||
debug!("PostProcessedCli: {:?}", args);
|
||||
|
||||
// load the configuration file
|
||||
debug!("Loading configuration...");
|
||||
let mut config = Config::new(&ConfigEnv::init()?)?;
|
||||
|
||||
debug!("Loading cable channels...");
|
||||
let cable_channels = cable::load_cable().unwrap_or_default();
|
||||
let cable = load_cable().unwrap_or_default();
|
||||
|
||||
let args = post_process(cli, &cable);
|
||||
debug!("PostProcessedCli: {:?}", args);
|
||||
|
||||
// optionally handle subcommands
|
||||
debug!("Handling subcommands...");
|
||||
@ -60,12 +62,8 @@ async fn main() -> Result<()> {
|
||||
|
||||
// determine the channel to use based on the CLI arguments and configuration
|
||||
debug!("Determining channel...");
|
||||
let channel_prototype = determine_channel(
|
||||
args.clone(),
|
||||
&config,
|
||||
is_readable_stdin(),
|
||||
&cable_channels,
|
||||
)?;
|
||||
let channel_prototype =
|
||||
determine_channel(&args, &config, is_readable_stdin(), &cable);
|
||||
|
||||
CLIPBOARD.with(<_>::default);
|
||||
|
||||
@ -77,13 +75,8 @@ async fn main() -> Result<()> {
|
||||
args.no_help,
|
||||
config.application.tick_rate,
|
||||
);
|
||||
let mut app = App::new(
|
||||
&channel_prototype,
|
||||
config,
|
||||
args.input,
|
||||
options,
|
||||
&cable_channels,
|
||||
);
|
||||
let mut app =
|
||||
App::new(&channel_prototype, config, args.input, options, &cable);
|
||||
stdout().flush()?;
|
||||
debug!("Running application...");
|
||||
let output = app.run(stdout().is_terminal(), false).await?;
|
||||
@ -156,27 +149,38 @@ pub fn handle_subcommands(command: &Command, config: &Config) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn determine_channel(
|
||||
args: PostProcessedCli,
|
||||
args: &PostProcessedCli,
|
||||
config: &Config,
|
||||
readable_stdin: bool,
|
||||
cable_channels: &Cable,
|
||||
) -> Result<ChannelPrototype> {
|
||||
cable: &Cable,
|
||||
) -> ChannelPrototype {
|
||||
if readable_stdin {
|
||||
debug!("Using stdin channel");
|
||||
Ok(ChannelPrototype::stdin(args.preview_command))
|
||||
} else if let Some(prompt) = args.autocomplete_prompt {
|
||||
ChannelPrototype::stdin(args.preview_command.clone())
|
||||
} else if let Some(prompt) = &args.autocomplete_prompt {
|
||||
debug!("Using autocomplete prompt: {:?}", prompt);
|
||||
let channel_prototype = guess_channel_from_prompt(
|
||||
&prompt,
|
||||
prompt,
|
||||
&config.shell_integration.commands,
|
||||
&config.shell_integration.fallback_channel,
|
||||
cable_channels,
|
||||
)?;
|
||||
cable,
|
||||
);
|
||||
debug!("Using guessed channel: {:?}", channel_prototype);
|
||||
Ok(channel_prototype)
|
||||
channel_prototype
|
||||
} else {
|
||||
debug!("Using {:?} channel", args.channel);
|
||||
Ok(args.channel)
|
||||
let channel = args
|
||||
.channel
|
||||
.as_ref()
|
||||
.unwrap_or(&config.application.default_channel)
|
||||
.clone();
|
||||
|
||||
let mut prototype = cable.get_channel(&channel);
|
||||
// use cli preview command if any
|
||||
if let Some(pc) = &args.preview_command {
|
||||
prototype.preview_command = Some(pc.clone());
|
||||
}
|
||||
|
||||
prototype
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,7 +188,8 @@ pub fn determine_channel(
|
||||
mod tests {
|
||||
use rustc_hash::FxHashMap;
|
||||
use television::{
|
||||
cable::load_cable, channels::prototypes::ChannelPrototype,
|
||||
cable::load_cable,
|
||||
channels::{preview::PreviewCommand, prototypes::ChannelPrototype},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -199,8 +204,7 @@ mod tests {
|
||||
let channels: Cable =
|
||||
cable_channels.unwrap_or_else(|| load_cable().unwrap_or_default());
|
||||
let channel =
|
||||
determine_channel(args.clone(), config, readable_stdin, &channels)
|
||||
.unwrap();
|
||||
determine_channel(args, config, readable_stdin, &channels);
|
||||
|
||||
assert_eq!(
|
||||
channel.name, expected_channel.name,
|
||||
@ -209,14 +213,10 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test]
|
||||
/// Test that the channel is stdin when stdin is readable
|
||||
async fn test_determine_channel_readable_stdin() {
|
||||
let channel = ChannelPrototype::default();
|
||||
let args = PostProcessedCli {
|
||||
channel,
|
||||
..Default::default()
|
||||
};
|
||||
fn test_determine_channel_readable_stdin() {
|
||||
let args = PostProcessedCli::default();
|
||||
let config = Config::default();
|
||||
assert_is_correct_channel(
|
||||
&args,
|
||||
@ -227,8 +227,8 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_determine_channel_autocomplete_prompt() {
|
||||
#[test]
|
||||
fn test_determine_channel_autocomplete_prompt() {
|
||||
let autocomplete_prompt = Some("cd".to_string());
|
||||
let expected_channel =
|
||||
ChannelPrototype::new("dirs", "ls {}", false, None);
|
||||
@ -261,9 +261,9 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_determine_channel_standard_case() {
|
||||
let channel = ChannelPrototype::new("dirs", "", false, None);
|
||||
#[test]
|
||||
fn test_determine_channel_standard_case() {
|
||||
let channel = Some(String::from("dirs"));
|
||||
let args = PostProcessedCli {
|
||||
channel,
|
||||
..Default::default()
|
||||
@ -278,6 +278,50 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_determine_channel_config_fallback() {
|
||||
let cable = Cable::default();
|
||||
let args = PostProcessedCli {
|
||||
channel: None,
|
||||
..Default::default()
|
||||
};
|
||||
let mut config = Config::default();
|
||||
config.application.default_channel = String::from("dirs");
|
||||
assert_is_correct_channel(
|
||||
&args,
|
||||
&config,
|
||||
false,
|
||||
&cable.get_channel("dirs"),
|
||||
Some(cable),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_determine_channel_with_cli_preview() {
|
||||
let cable = Cable::default();
|
||||
|
||||
let preview_command = PreviewCommand::new("echo hello", ",", None);
|
||||
|
||||
let args = PostProcessedCli {
|
||||
channel: Some(String::from("dirs")),
|
||||
preview_command: Some(preview_command),
|
||||
..Default::default()
|
||||
};
|
||||
let config = Config::default();
|
||||
|
||||
let expected_prototype = cable
|
||||
.get_channel("dirs")
|
||||
.set_preview(args.preview_command.clone());
|
||||
|
||||
assert_is_correct_channel(
|
||||
&args,
|
||||
&config,
|
||||
false,
|
||||
&expected_prototype,
|
||||
Some(cable),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_cli_overrides() {
|
||||
let mut config = Config::default();
|
||||
|
@ -33,7 +33,7 @@ fn setup_app(
|
||||
.join("tests")
|
||||
.join("target_dir");
|
||||
std::env::set_current_dir(&target_dir).unwrap();
|
||||
ChannelPrototype::default()
|
||||
Cable::default().get("files").unwrap().clone()
|
||||
});
|
||||
let mut config = default_config_from_file().unwrap();
|
||||
// this speeds up the tests
|
||||
|
Loading…
x
Reference in New Issue
Block a user