2019-09-15 16:29:11 +00:00
/*
* This file is part of espanso .
*
* Copyright ( C ) 2019 Federico Terzi
*
* espanso is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* espanso is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with espanso . If not , see < https ://www.gnu.org/licenses/>.
* /
2019-09-09 15:59:44 +00:00
extern crate dirs ;
2019-09-10 20:53:45 +00:00
use std ::path ::{ Path , PathBuf } ;
2019-09-13 09:55:42 +00:00
use std ::{ fs } ;
2019-09-09 15:59:44 +00:00
use crate ::matcher ::Match ;
2019-09-27 21:41:15 +00:00
use std ::fs ::{ File , create_dir_all } ;
2019-09-09 15:59:44 +00:00
use std ::io ::Read ;
use serde ::{ Serialize , Deserialize } ;
2019-09-12 20:14:41 +00:00
use crate ::event ::KeyModifier ;
2019-11-29 21:09:02 +00:00
use crate ::keyboard ::PasteShortcut ;
2019-09-24 20:53:12 +00:00
use std ::collections ::{ HashSet , HashMap } ;
2019-09-14 19:38:47 +00:00
use log ::{ error } ;
2019-09-10 20:53:45 +00:00
use std ::fmt ;
use std ::error ::Error ;
2019-09-24 20:53:12 +00:00
use walkdir ::WalkDir ;
2019-09-09 15:59:44 +00:00
pub ( crate ) mod runtime ;
2019-09-23 20:08:46 +00:00
const DEFAULT_CONFIG_FILE_CONTENT : & str = include_str! ( " ../res/config.yml " ) ;
2019-09-09 15:59:44 +00:00
2019-11-08 21:20:32 +00:00
pub const DEFAULT_CONFIG_FILE_NAME : & str = " default.yml " ;
2019-09-23 21:34:55 +00:00
const USER_CONFIGS_FOLDER_NAME : & str = " user " ;
2019-09-09 15:59:44 +00:00
// Default values for primitives
fn default_name ( ) -> String { " default " . to_owned ( ) }
2019-09-24 20:53:12 +00:00
fn default_parent ( ) -> String { " self " . to_owned ( ) }
2019-09-09 15:59:44 +00:00
fn default_filter_title ( ) -> String { " " . to_owned ( ) }
fn default_filter_class ( ) -> String { " " . to_owned ( ) }
fn default_filter_exec ( ) -> String { " " . to_owned ( ) }
2019-09-14 08:30:51 +00:00
fn default_log_level ( ) -> i32 { 0 }
2020-01-21 20:58:10 +00:00
fn default_conflict_check ( ) -> bool { true }
2019-09-16 08:16:37 +00:00
fn default_ipc_server_port ( ) -> i32 { 34982 }
2019-09-16 22:11:31 +00:00
fn default_use_system_agent ( ) -> bool { true }
2019-09-09 15:59:44 +00:00
fn default_config_caching_interval ( ) -> i32 { 800 }
2019-10-26 20:37:38 +00:00
fn default_word_separators ( ) -> Vec < char > { vec! [ ' ' , ',' , '.' , '\r' , '\n' , 22 u8 as char ] }
2019-09-09 15:59:44 +00:00
fn default_toggle_interval ( ) -> u32 { 230 }
2020-01-18 23:30:30 +00:00
fn default_toggle_key ( ) -> KeyModifier { KeyModifier ::ALT }
2019-12-13 22:17:53 +00:00
fn default_preserve_clipboard ( ) -> bool { false }
2020-01-10 22:29:21 +00:00
fn default_passive_match_regex ( ) -> String { " (?P<name>: \\ p{L}+)(/(?P<args>.*)/)? " . to_owned ( ) }
2020-01-18 21:55:50 +00:00
fn default_passive_arg_delimiter ( ) -> char { '/' }
fn default_passive_arg_escape ( ) -> char { '\\' }
2020-01-18 23:30:30 +00:00
fn default_passive_key ( ) -> KeyModifier { KeyModifier ::OFF }
2020-01-26 21:30:54 +00:00
fn default_enable_passive ( ) -> bool { false }
fn default_enable_active ( ) -> bool { true }
2020-01-19 22:41:11 +00:00
fn default_action_noop_interval ( ) -> u128 { 500 }
2019-09-09 15:59:44 +00:00
fn default_backspace_limit ( ) -> i32 { 3 }
2019-09-24 20:53:12 +00:00
fn default_exclude_default_matches ( ) -> bool { false }
2019-09-09 15:59:44 +00:00
fn default_matches ( ) -> Vec < Match > { Vec ::new ( ) }
#[ derive(Clone, Debug, Serialize, Deserialize) ]
pub struct Configs {
#[ serde(default = " default_name " ) ]
pub name : String ,
2019-09-24 20:53:12 +00:00
#[ serde(default = " default_parent " ) ]
pub parent : String ,
2019-09-09 15:59:44 +00:00
#[ serde(default = " default_filter_title " ) ]
pub filter_title : String ,
#[ serde(default = " default_filter_class " ) ]
pub filter_class : String ,
#[ serde(default = " default_filter_exec " ) ]
pub filter_exec : String ,
2019-09-14 08:30:51 +00:00
#[ serde(default = " default_log_level " ) ]
pub log_level : i32 ,
2020-01-21 20:58:10 +00:00
#[ serde(default = " default_conflict_check " ) ]
pub conflict_check : bool ,
2019-09-16 08:16:37 +00:00
#[ serde(default = " default_ipc_server_port " ) ]
pub ipc_server_port : i32 ,
2019-09-16 22:11:31 +00:00
#[ serde(default = " default_use_system_agent " ) ]
pub use_system_agent : bool ,
2019-09-09 15:59:44 +00:00
#[ serde(default = " default_config_caching_interval " ) ]
pub config_caching_interval : i32 ,
2019-10-19 21:31:05 +00:00
#[ serde(default = " default_word_separators " ) ]
pub word_separators : Vec < char > , // TODO: add parsing test
2020-01-18 23:30:30 +00:00
#[ serde(default = " default_toggle_key " ) ]
2019-09-09 15:59:44 +00:00
pub toggle_key : KeyModifier ,
#[ serde(default = " default_toggle_interval " ) ]
pub toggle_interval : u32 ,
2019-12-13 22:17:53 +00:00
#[ serde(default = " default_preserve_clipboard " ) ]
pub preserve_clipboard : bool ,
2020-01-10 22:29:21 +00:00
#[ serde(default = " default_passive_match_regex " ) ]
pub passive_match_regex : String ,
2020-01-18 21:55:50 +00:00
#[ serde(default = " default_passive_arg_delimiter " ) ]
pub passive_arg_delimiter : char ,
#[ serde(default = " default_passive_arg_escape " ) ]
pub passive_arg_escape : char ,
2020-01-10 22:29:21 +00:00
2020-01-18 23:30:30 +00:00
#[ serde(default = " default_passive_key " ) ]
pub passive_key : KeyModifier ,
2020-01-26 21:30:54 +00:00
#[ serde(default = " default_enable_passive " ) ]
pub enable_passive : bool ,
#[ serde(default = " default_enable_active " ) ]
pub enable_active : bool ,
2020-01-19 22:41:11 +00:00
#[ serde(default = " default_action_noop_interval " ) ]
pub action_noop_interval : u128 ,
2019-11-29 21:09:02 +00:00
#[ serde(default) ]
pub paste_shortcut : PasteShortcut ,
2019-09-09 15:59:44 +00:00
#[ serde(default = " default_backspace_limit " ) ]
pub backspace_limit : i32 ,
#[ serde(default) ]
pub backend : BackendType ,
2019-09-24 20:53:12 +00:00
#[ serde(default = " default_exclude_default_matches " ) ]
pub exclude_default_matches : bool ,
2019-09-14 20:14:39 +00:00
2019-09-09 15:59:44 +00:00
#[ serde(default = " default_matches " ) ]
pub matches : Vec < Match >
}
2019-09-09 16:56:55 +00:00
// Macro used to validate config fields
#[ macro_export ]
macro_rules ! validate_field {
( $result :expr , $field :expr , $def_value :expr ) = > {
if $field ! = $def_value {
2019-09-10 20:53:45 +00:00
let mut field_name = stringify! ( $field ) ;
if field_name . starts_with ( " self. " ) {
field_name = & field_name [ 5 .. ] ; // Remove the 'self.' prefix
}
2019-09-23 20:08:46 +00:00
error! ( " Validation error, parameter '{}' is reserved and can be only used in the default.yml config file " , field_name ) ;
2019-09-09 16:56:55 +00:00
$result = false ;
}
} ;
}
impl Configs {
/*
* Validate the Config instance .
2019-09-23 21:34:55 +00:00
* It makes sure that user defined config instances do not define
2019-09-09 16:56:55 +00:00
* attributes reserved to the default config .
* /
2019-09-23 21:34:55 +00:00
fn validate_user_defined_config ( & self ) -> bool {
2019-09-09 16:56:55 +00:00
let mut result = true ;
validate_field! ( result , self . config_caching_interval , default_config_caching_interval ( ) ) ;
2019-09-14 08:30:51 +00:00
validate_field! ( result , self . log_level , default_log_level ( ) ) ;
2020-01-21 20:58:10 +00:00
validate_field! ( result , self . conflict_check , default_conflict_check ( ) ) ;
2020-01-18 23:30:30 +00:00
validate_field! ( result , self . toggle_key , default_toggle_key ( ) ) ;
2019-09-09 16:56:55 +00:00
validate_field! ( result , self . toggle_interval , default_toggle_interval ( ) ) ;
validate_field! ( result , self . backspace_limit , default_backspace_limit ( ) ) ;
2019-09-16 22:11:31 +00:00
validate_field! ( result , self . ipc_server_port , default_ipc_server_port ( ) ) ;
validate_field! ( result , self . use_system_agent , default_use_system_agent ( ) ) ;
2019-12-13 22:17:53 +00:00
validate_field! ( result , self . preserve_clipboard , default_preserve_clipboard ( ) ) ;
2020-01-10 22:29:21 +00:00
validate_field! ( result , self . passive_match_regex , default_passive_match_regex ( ) ) ;
2020-01-18 21:55:50 +00:00
validate_field! ( result , self . passive_arg_delimiter , default_passive_arg_delimiter ( ) ) ;
validate_field! ( result , self . passive_arg_escape , default_passive_arg_escape ( ) ) ;
2020-01-18 23:30:30 +00:00
validate_field! ( result , self . passive_key , default_passive_key ( ) ) ;
2020-01-19 22:41:11 +00:00
validate_field! ( result , self . action_noop_interval , default_action_noop_interval ( ) ) ;
2019-09-09 16:56:55 +00:00
result
}
}
2019-09-09 15:59:44 +00:00
#[ derive(Debug, Clone, PartialEq, Serialize, Deserialize) ]
pub enum BackendType {
Inject ,
Clipboard
}
impl Default for BackendType {
2019-10-10 17:19:26 +00:00
// The default backend varies based on the operating system.
// On Windows and macOS, the Inject backend is working great and should
// be preferred as it doesn't override the clipboard.
// On the other hand, on linux it has many problems due to the bugs
// of the libxdo used. For this reason, Clipboard will be the default
// backend on Linux from version v0.3.0
#[ cfg(not(target_os = " linux " )) ]
2019-09-09 15:59:44 +00:00
fn default ( ) -> Self {
BackendType ::Inject
}
2019-10-10 17:19:26 +00:00
#[ cfg(target_os = " linux " ) ]
fn default ( ) -> Self {
BackendType ::Clipboard
}
2019-09-09 15:59:44 +00:00
}
impl Configs {
2019-09-10 20:53:45 +00:00
fn load_config ( path : & Path ) -> Result < Configs , ConfigLoadError > {
2019-09-09 15:59:44 +00:00
let file_res = File ::open ( path ) ;
if let Ok ( mut file ) = file_res {
let mut contents = String ::new ( ) ;
2019-09-10 20:53:45 +00:00
let res = file . read_to_string ( & mut contents ) ;
2019-09-28 08:44:25 +00:00
if res . is_err ( ) {
2019-09-10 20:53:45 +00:00
return Err ( ConfigLoadError ::UnableToReadFile )
}
2019-09-09 15:59:44 +00:00
2019-09-09 16:56:55 +00:00
let config_res = serde_yaml ::from_str ( & contents ) ;
match config_res {
2019-09-10 20:53:45 +00:00
Ok ( config ) = > Ok ( config ) ,
2019-09-09 16:56:55 +00:00
Err ( e ) = > {
2019-09-10 20:53:45 +00:00
Err ( ConfigLoadError ::InvalidYAML ( path . to_owned ( ) , e . to_string ( ) ) )
2019-09-09 16:56:55 +00:00
}
}
2019-09-09 15:59:44 +00:00
} else {
2019-09-10 20:53:45 +00:00
Err ( ConfigLoadError ::FileNotFound )
2019-09-09 15:59:44 +00:00
}
}
2019-09-24 20:53:12 +00:00
fn merge_config ( & mut self , new_config : Configs ) {
let mut merged_matches = new_config . matches ;
let mut trigger_set = HashSet ::new ( ) ;
merged_matches . iter ( ) . for_each ( | m | {
trigger_set . insert ( m . trigger . clone ( ) ) ;
} ) ;
let parent_matches : Vec < Match > = self . matches . iter ( ) . filter ( | & m | {
! trigger_set . contains ( & m . trigger )
2019-09-28 08:44:25 +00:00
} ) . cloned ( ) . collect ( ) ;
2019-09-24 20:53:12 +00:00
merged_matches . extend ( parent_matches ) ;
self . matches = merged_matches ;
}
fn merge_default ( & mut self , default : & Configs ) {
let mut trigger_set = HashSet ::new ( ) ;
self . matches . iter ( ) . for_each ( | m | {
trigger_set . insert ( m . trigger . clone ( ) ) ;
} ) ;
let default_matches : Vec < Match > = default . matches . iter ( ) . filter ( | & m | {
! trigger_set . contains ( & m . trigger )
2019-09-28 08:44:25 +00:00
} ) . cloned ( ) . collect ( ) ;
2019-09-24 20:53:12 +00:00
self . matches . extend ( default_matches ) ;
}
2019-09-09 15:59:44 +00:00
}
#[ derive(Clone, Debug, Serialize, Deserialize) ]
pub struct ConfigSet {
2019-09-14 08:30:51 +00:00
pub default : Configs ,
pub specific : Vec < Configs > ,
2019-09-09 15:59:44 +00:00
}
2019-09-10 20:53:45 +00:00
impl ConfigSet {
2019-10-08 22:37:42 +00:00
pub fn load ( config_dir : & Path , package_dir : & Path ) -> Result < ConfigSet , ConfigLoadError > {
if ! config_dir . is_dir ( ) {
2019-09-10 20:53:45 +00:00
return Err ( ConfigLoadError ::InvalidConfigDirectory )
}
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
// Load default configuration
2019-10-08 22:37:42 +00:00
let default_file = config_dir . join ( DEFAULT_CONFIG_FILE_NAME ) ;
2019-09-10 20:53:45 +00:00
let default = Configs ::load_config ( default_file . as_path ( ) ) ? ;
2019-09-09 15:59:44 +00:00
2019-09-24 20:53:12 +00:00
// Analyze which config files has to be loaded
2019-09-09 15:59:44 +00:00
2019-09-24 20:53:12 +00:00
let mut target_files = Vec ::new ( ) ;
2019-09-09 15:59:44 +00:00
2019-10-08 22:37:42 +00:00
let specific_dir = config_dir . join ( USER_CONFIGS_FOLDER_NAME ) ;
2019-09-23 21:34:55 +00:00
if specific_dir . exists ( ) {
2019-09-24 20:53:12 +00:00
let dir_entry = WalkDir ::new ( specific_dir ) ;
target_files . extend ( dir_entry ) ;
}
2019-09-09 15:59:44 +00:00
2019-09-24 20:53:12 +00:00
if package_dir . exists ( ) {
let dir_entry = WalkDir ::new ( package_dir ) ;
target_files . extend ( dir_entry ) ;
}
2019-09-09 15:59:44 +00:00
2019-09-24 20:53:12 +00:00
// Load the user defined config files
2019-09-15 13:51:29 +00:00
2019-09-24 20:53:12 +00:00
let mut name_set = HashSet ::new ( ) ;
let mut children_map : HashMap < String , Vec < Configs > > = HashMap ::new ( ) ;
let mut root_configs = Vec ::new ( ) ;
root_configs . push ( default ) ;
2019-09-09 15:59:44 +00:00
2019-09-24 20:53:12 +00:00
for entry in target_files {
if let Ok ( entry ) = entry {
let path = entry . path ( ) ;
2019-09-09 16:56:55 +00:00
2019-09-24 20:53:12 +00:00
// Skip non-yaml config files
if path . extension ( ) . unwrap_or_default ( ) . to_str ( ) . unwrap_or_default ( ) ! = " yml " {
continue ;
}
2019-09-09 15:59:44 +00:00
2019-09-24 20:53:12 +00:00
let mut config = Configs ::load_config ( & path ) ? ;
2019-09-09 15:59:44 +00:00
2019-09-24 20:53:12 +00:00
// Make sure the config does not contain reserved fields
if ! config . validate_user_defined_config ( ) {
return Err ( ConfigLoadError ::InvalidParameter ( path . to_owned ( ) ) )
}
2019-09-14 20:14:39 +00:00
2019-09-24 20:53:12 +00:00
// No name specified, defaulting to the path name
if config . name = = " default " {
config . name = path . to_str ( ) . unwrap_or_default ( ) . to_owned ( ) ;
}
if name_set . contains ( & config . name ) {
return Err ( ConfigLoadError ::NameDuplicate ( path . to_owned ( ) ) ) ;
}
2019-09-10 21:32:43 +00:00
2019-09-24 20:53:12 +00:00
name_set . insert ( config . name . clone ( ) ) ;
2019-09-23 21:34:55 +00:00
2019-09-24 20:53:12 +00:00
if config . parent = = " self " { // No parent, root config
root_configs . push ( config ) ;
} else { // Children config
let children_vec = children_map . entry ( config . parent . clone ( ) ) . or_default ( ) ;
children_vec . push ( config ) ;
2019-09-23 21:34:55 +00:00
}
2019-09-24 20:53:12 +00:00
} else {
eprintln! ( " Warning: Unable to read config file: {} " , entry . unwrap_err ( ) )
}
}
// Merge the children config files
let mut configs = Vec ::new ( ) ;
for root_config in root_configs {
let config = ConfigSet ::reduce_configs ( root_config , & children_map ) ;
configs . push ( config ) ;
}
// Separate default from specific
let default = configs . get ( 0 ) . unwrap ( ) . clone ( ) ;
let mut specific = ( & configs [ 1 .. ] ) . to_vec ( ) . clone ( ) ;
// Add default matches to specific configs when needed
for config in specific . iter_mut ( ) {
if ! config . exclude_default_matches {
config . merge_default ( & default ) ;
2019-09-10 20:53:45 +00:00
}
2019-09-09 15:59:44 +00:00
}
2020-01-21 20:58:10 +00:00
// Check if some triggers are conflicting with each other
// For more information, see: https://github.com/federico-terzi/espanso/issues/135
if default . conflict_check {
for s in specific . iter ( ) {
let has_conflicts = Self ::has_conflicts ( & default , & specific ) ;
if has_conflicts {
eprintln! ( " Warning: some triggers had conflicts and may not behave as intended " ) ;
eprintln! ( " To turn off this check, add \" conflict_check: false \" in the configuration " ) ;
}
}
}
2019-09-10 20:53:45 +00:00
Ok ( ConfigSet {
default ,
2019-09-24 20:53:12 +00:00
specific
2019-09-10 20:53:45 +00:00
} )
2019-09-09 15:59:44 +00:00
}
2019-09-24 20:53:12 +00:00
fn reduce_configs ( target : Configs , children_map : & HashMap < String , Vec < Configs > > ) -> Configs {
if children_map . contains_key ( & target . name ) {
let mut target = target ;
for children in children_map . get ( & target . name ) . unwrap ( ) {
let children = Self ::reduce_configs ( children . clone ( ) , children_map ) ;
target . merge_config ( children ) ;
}
target
} else {
target
}
}
2019-09-10 20:53:45 +00:00
pub fn load_default ( ) -> Result < ConfigSet , ConfigLoadError > {
2019-10-08 22:37:42 +00:00
// Configuration related
2019-09-25 18:52:21 +00:00
2019-10-08 22:37:42 +00:00
let config_dir = crate ::context ::get_config_dir ( ) ;
2019-09-09 15:59:44 +00:00
2019-10-08 22:37:42 +00:00
let default_file = config_dir . join ( DEFAULT_CONFIG_FILE_NAME ) ;
2019-09-23 21:34:55 +00:00
2019-10-08 22:37:42 +00:00
// If config file does not exist, create one from template
if ! default_file . exists ( ) {
let result = fs ::write ( & default_file , DEFAULT_CONFIG_FILE_CONTENT ) ;
if result . is_err ( ) {
return Err ( ConfigLoadError ::UnableToCreateDefaultConfig )
2019-09-25 18:52:21 +00:00
}
2019-10-08 22:37:42 +00:00
}
2019-09-23 21:34:55 +00:00
2019-10-08 22:37:42 +00:00
// Create auxiliary directories
2019-09-23 21:34:55 +00:00
2019-10-08 22:37:42 +00:00
let user_config_dir = config_dir . join ( USER_CONFIGS_FOLDER_NAME ) ;
if ! user_config_dir . exists ( ) {
let res = create_dir_all ( user_config_dir . as_path ( ) ) ;
if res . is_err ( ) {
return Err ( ConfigLoadError ::UnableToCreateDefaultConfig )
2019-09-09 15:59:44 +00:00
}
}
2019-09-25 18:52:21 +00:00
2019-10-08 22:37:42 +00:00
// Packages
2019-09-25 18:52:21 +00:00
2019-10-08 22:37:42 +00:00
let package_dir = crate ::context ::get_package_dir ( ) ;
let res = create_dir_all ( package_dir . as_path ( ) ) ;
if res . is_err ( ) {
return Err ( ConfigLoadError ::UnableToCreateDefaultConfig ) // TODO: change error type
}
return ConfigSet ::load ( config_dir . as_path ( ) , package_dir . as_path ( ) ) ;
2019-09-25 18:52:21 +00:00
}
2020-01-21 20:58:10 +00:00
fn has_conflicts ( default : & Configs , specific : & Vec < Configs > ) -> bool {
let mut sorted_triggers : Vec < String > = default . matches . iter ( ) . map ( | t | {
t . trigger . clone ( )
} ) . collect ( ) ;
sorted_triggers . sort ( ) ;
let mut has_conflicts = Self ::list_has_conflicts ( & sorted_triggers ) ;
for s in specific . iter ( ) {
let mut specific_triggers : Vec < String > = s . matches . iter ( ) . map ( | t | {
t . trigger . clone ( )
} ) . collect ( ) ;
2020-01-21 22:11:14 +00:00
specific_triggers . sort ( ) ;
2020-01-21 20:58:10 +00:00
has_conflicts | = Self ::list_has_conflicts ( & specific_triggers ) ;
}
has_conflicts
}
fn list_has_conflicts ( sorted_list : & Vec < String > ) -> bool {
if sorted_list . len ( ) < = 1 {
return false
}
let mut has_conflicts = false ;
for ( i , item ) in sorted_list . iter ( ) . skip ( 1 ) . enumerate ( ) {
let previous = & sorted_list [ i ] ;
if item . starts_with ( previous ) {
has_conflicts = true ;
eprintln! ( " Warning: trigger ' {} ' is conflicting with ' {} ' and may not behave as intended " , item , previous ) ;
}
}
has_conflicts
}
2019-09-09 15:59:44 +00:00
}
pub trait ConfigManager < ' a > {
fn active_config ( & ' a self ) -> & ' a Configs ;
fn default_config ( & ' a self ) -> & ' a Configs ;
fn matches ( & ' a self ) -> & ' a Vec < Match > ;
2019-09-10 20:53:45 +00:00
}
// Error handling
#[ derive(Debug, PartialEq) ]
pub enum ConfigLoadError {
FileNotFound ,
UnableToReadFile ,
InvalidYAML ( PathBuf , String ) ,
InvalidConfigDirectory ,
InvalidParameter ( PathBuf ) ,
NameDuplicate ( PathBuf ) ,
UnableToCreateDefaultConfig ,
}
impl fmt ::Display for ConfigLoadError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
ConfigLoadError ::FileNotFound = > write! ( f , " File not found " ) ,
ConfigLoadError ::UnableToReadFile = > write! ( f , " Unable to read config file " ) ,
ConfigLoadError ::InvalidYAML ( path , e ) = > write! ( f , " Error parsing YAML file '{}', invalid syntax: {} " , path . to_str ( ) . unwrap_or_default ( ) , e ) ,
ConfigLoadError ::InvalidConfigDirectory = > write! ( f , " Invalid config directory " ) ,
2019-09-23 21:34:55 +00:00
ConfigLoadError ::InvalidParameter ( path ) = > write! ( f , " Invalid parameter in '{}', use of reserved parameters in used defined configs is not permitted " , path . to_str ( ) . unwrap_or_default ( ) ) ,
2019-09-10 20:53:45 +00:00
ConfigLoadError ::NameDuplicate ( path ) = > write! ( f , " Found duplicate 'name' in '{}', please use different names " , path . to_str ( ) . unwrap_or_default ( ) ) ,
ConfigLoadError ::UnableToCreateDefaultConfig = > write! ( f , " Could not generate default config file " ) ,
}
}
}
impl Error for ConfigLoadError {
fn description ( & self ) -> & str {
match self {
ConfigLoadError ::FileNotFound = > " File not found " ,
ConfigLoadError ::UnableToReadFile = > " Unable to read config file " ,
ConfigLoadError ::InvalidYAML ( _ , _ ) = > " Error parsing YAML file, invalid syntax " ,
ConfigLoadError ::InvalidConfigDirectory = > " Invalid config directory " ,
2019-09-23 21:34:55 +00:00
ConfigLoadError ::InvalidParameter ( _ ) = > " Invalid parameter, use of reserved parameters in user defined configs is not permitted " ,
2019-09-10 20:53:45 +00:00
ConfigLoadError ::NameDuplicate ( _ ) = > " Found duplicate 'name' in some configurations, please use different names " ,
ConfigLoadError ::UnableToCreateDefaultConfig = > " Could not generate default config file " ,
}
}
}
#[ cfg(test) ]
mod tests {
use super ::* ;
use std ::io ::Write ;
use tempfile ::{ NamedTempFile , TempDir } ;
2019-09-13 09:50:39 +00:00
use std ::any ::Any ;
2019-11-28 21:56:00 +00:00
use crate ::matcher ::{ TextContent , MatchContentType } ;
2019-09-10 20:53:45 +00:00
2019-09-23 20:08:46 +00:00
const TEST_WORKING_CONFIG_FILE : & str = include_str! ( " ../res/test/working_config.yml " ) ;
const TEST_CONFIG_FILE_WITH_BAD_YAML : & str = include_str! ( " ../res/test/config_with_bad_yaml.yml " ) ;
2019-09-10 20:53:45 +00:00
// Test Configs
fn create_tmp_file ( string : & str ) -> NamedTempFile {
let file = NamedTempFile ::new ( ) . unwrap ( ) ;
file . as_file ( ) . write_all ( string . as_bytes ( ) ) ;
file
}
2019-09-13 09:50:39 +00:00
fn variant_eq < T > ( a : & T , b : & T ) -> bool {
std ::mem ::discriminant ( a ) = = std ::mem ::discriminant ( b )
}
2019-09-10 20:53:45 +00:00
#[ test ]
fn test_config_file_not_found ( ) {
let config = Configs ::load_config ( Path ::new ( " invalid/path " ) ) ;
assert_eq! ( config . is_err ( ) , true ) ;
assert_eq! ( config . unwrap_err ( ) , ConfigLoadError ::FileNotFound ) ;
}
#[ test ]
fn test_config_file_with_bad_yaml_syntax ( ) {
let broken_config_file = create_tmp_file ( TEST_CONFIG_FILE_WITH_BAD_YAML ) ;
let config = Configs ::load_config ( broken_config_file . path ( ) ) ;
match config {
Ok ( _ ) = > { assert! ( false ) } ,
Err ( e ) = > {
match e {
ConfigLoadError ::InvalidYAML ( p , _ ) = > assert_eq! ( p , broken_config_file . path ( ) . to_owned ( ) ) ,
_ = > assert! ( false ) ,
}
assert! ( true ) ;
} ,
}
}
#[ test ]
fn test_validate_field_macro ( ) {
let mut result = true ;
validate_field! ( result , 3 , 3 ) ;
assert_eq! ( result , true ) ;
validate_field! ( result , 10 , 3 ) ;
assert_eq! ( result , false ) ;
validate_field! ( result , 3 , 3 ) ;
assert_eq! ( result , false ) ;
}
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_does_not_have_reserved_fields ( ) {
2019-09-10 20:53:45 +00:00
let working_config_file = create_tmp_file ( r ###"
backend : Clipboard
" ###);
let config = Configs ::load_config ( working_config_file . path ( ) ) ;
2019-09-23 21:34:55 +00:00
assert_eq! ( config . unwrap ( ) . validate_user_defined_config ( ) , true ) ;
2019-09-10 20:53:45 +00:00
}
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_has_reserved_fields_config_caching_interval ( ) {
2019-09-10 20:53:45 +00:00
let working_config_file = create_tmp_file ( r ###"
# This should not happen in an app - specific config
config_caching_interval : 100
" ###);
let config = Configs ::load_config ( working_config_file . path ( ) ) ;
2019-09-23 21:34:55 +00:00
assert_eq! ( config . unwrap ( ) . validate_user_defined_config ( ) , false ) ;
2019-09-10 20:53:45 +00:00
}
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_has_reserved_fields_toggle_key ( ) {
2019-09-10 20:53:45 +00:00
let working_config_file = create_tmp_file ( r ###"
# This should not happen in an app - specific config
toggle_key : CTRL
" ###);
let config = Configs ::load_config ( working_config_file . path ( ) ) ;
2019-09-23 21:34:55 +00:00
assert_eq! ( config . unwrap ( ) . validate_user_defined_config ( ) , false ) ;
2019-09-10 20:53:45 +00:00
}
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_has_reserved_fields_toggle_interval ( ) {
2019-09-10 20:53:45 +00:00
let working_config_file = create_tmp_file ( r ###"
# This should not happen in an app - specific config
toggle_interval : 1000
" ###);
let config = Configs ::load_config ( working_config_file . path ( ) ) ;
2019-09-23 21:34:55 +00:00
assert_eq! ( config . unwrap ( ) . validate_user_defined_config ( ) , false ) ;
2019-09-10 20:53:45 +00:00
}
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_has_reserved_fields_backspace_limit ( ) {
2019-09-10 20:53:45 +00:00
let working_config_file = create_tmp_file ( r ###"
# This should not happen in an app - specific config
backspace_limit : 10
" ###);
let config = Configs ::load_config ( working_config_file . path ( ) ) ;
2019-09-23 21:34:55 +00:00
assert_eq! ( config . unwrap ( ) . validate_user_defined_config ( ) , false ) ;
2019-09-10 20:53:45 +00:00
}
#[ test ]
fn test_config_loaded_correctly ( ) {
let working_config_file = create_tmp_file ( TEST_WORKING_CONFIG_FILE ) ;
let config = Configs ::load_config ( working_config_file . path ( ) ) ;
assert_eq! ( config . is_ok ( ) , true ) ;
}
// Test ConfigSet
2019-10-08 22:37:42 +00:00
pub fn create_temp_espanso_directories ( ) -> ( TempDir , TempDir ) {
create_temp_espanso_directories_with_default_content ( DEFAULT_CONFIG_FILE_CONTENT )
}
pub fn create_temp_espanso_directories_with_default_content ( default_content : & str ) -> ( TempDir , TempDir ) {
let data_dir = TempDir ::new ( ) . expect ( " unable to create data directory " ) ;
let package_dir = TempDir ::new ( ) . expect ( " unable to create package directory " ) ;
let default_path = data_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , default_content ) ;
( data_dir , package_dir )
}
pub fn create_temp_file_in_dir ( tmp_dir : & PathBuf , name : & str , content : & str ) -> PathBuf {
let user_defined_path = tmp_dir . join ( name ) ;
let user_defined_path_copy = user_defined_path . clone ( ) ;
fs ::write ( user_defined_path , content ) ;
user_defined_path_copy
}
pub fn create_user_config_file ( tmp_dir : & Path , name : & str , content : & str ) -> PathBuf {
let user_config_dir = tmp_dir . join ( USER_CONFIGS_FOLDER_NAME ) ;
if ! user_config_dir . exists ( ) {
create_dir_all ( & user_config_dir ) ;
}
create_temp_file_in_dir ( & user_config_dir , name , content )
}
pub fn create_package_file ( package_data_dir : & Path , package_name : & str , filename : & str , content : & str ) -> PathBuf {
let package_dir = package_data_dir . join ( package_name ) ;
if ! package_dir . exists ( ) {
create_dir_all ( & package_dir ) ;
}
create_temp_file_in_dir ( & package_dir , filename , content )
}
2019-09-10 20:53:45 +00:00
#[ test ]
fn test_config_set_default_content_should_work_correctly ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories ( ) ;
2019-09-10 20:53:45 +00:00
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) ;
2019-09-10 20:53:45 +00:00
assert! ( config_set . is_ok ( ) ) ;
}
#[ test ]
fn test_config_set_load_fail_bad_directory ( ) {
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( Path ::new ( " invalid/path " ) , Path ::new ( " invalid/path " ) ) ;
2019-09-10 20:53:45 +00:00
assert_eq! ( config_set . is_err ( ) , true ) ;
assert_eq! ( config_set . unwrap_err ( ) , ConfigLoadError ::InvalidConfigDirectory ) ;
}
#[ test ]
fn test_config_set_missing_default_file ( ) {
2019-10-08 22:37:42 +00:00
let data_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let package_dir = TempDir ::new ( ) . expect ( " unable to create package directory " ) ;
2019-09-10 20:53:45 +00:00
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) ;
2019-09-10 20:53:45 +00:00
assert_eq! ( config_set . is_err ( ) , true ) ;
assert_eq! ( config_set . unwrap_err ( ) , ConfigLoadError ::FileNotFound ) ;
}
#[ test ]
fn test_config_set_invalid_yaml_syntax ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content (
TEST_CONFIG_FILE_WITH_BAD_YAML
) ;
let default_path = data_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
2019-09-10 20:53:45 +00:00
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) ;
2019-09-10 20:53:45 +00:00
match config_set {
Ok ( _ ) = > { assert! ( false ) } ,
Err ( e ) = > {
match e {
2019-10-08 22:37:42 +00:00
ConfigLoadError ::InvalidYAML ( p , _ ) = > assert_eq! ( p , default_path ) ,
2019-09-10 20:53:45 +00:00
_ = > assert! ( false ) ,
}
assert! ( true ) ;
} ,
}
}
#[ test ]
fn test_config_set_specific_file_with_reserved_fields ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories ( ) ;
2019-09-10 20:53:45 +00:00
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-10 20:53:45 +00:00
config_caching_interval : 10000
" ###);
2019-09-23 21:34:55 +00:00
let user_defined_path_copy = user_defined_path . clone ( ) ;
2019-09-10 20:53:45 +00:00
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) ;
2019-09-10 20:53:45 +00:00
assert! ( config_set . is_err ( ) ) ;
2019-09-23 21:34:55 +00:00
assert_eq! ( config_set . unwrap_err ( ) , ConfigLoadError ::InvalidParameter ( user_defined_path_copy ) )
2019-09-10 20:53:45 +00:00
}
#[ test ]
2019-09-24 20:53:12 +00:00
fn test_config_set_specific_file_missing_name_auto_generated ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories ( ) ;
2019-09-10 20:53:45 +00:00
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-10 20:53:45 +00:00
backend : Clipboard
" ###);
2019-09-23 21:34:55 +00:00
let user_defined_path_copy = user_defined_path . clone ( ) ;
2019-09-10 20:53:45 +00:00
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) ;
2019-09-24 20:53:12 +00:00
assert! ( config_set . is_ok ( ) ) ;
assert_eq! ( config_set . unwrap ( ) . specific [ 0 ] . name , user_defined_path_copy . to_str ( ) . unwrap_or_default ( ) )
2019-09-10 20:53:45 +00:00
}
2019-09-10 21:32:43 +00:00
#[ test ]
fn test_config_set_specific_file_duplicate_name ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories ( ) ;
2019-09-10 21:32:43 +00:00
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-10 20:53:45 +00:00
name : specific1
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path2 = create_user_config_file ( data_dir . path ( ) , " specific2.yml " , r ###"
2019-09-10 20:53:45 +00:00
name : specific1
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) ;
2019-09-10 20:53:45 +00:00
assert! ( config_set . is_err ( ) ) ;
2019-09-13 09:50:39 +00:00
assert! ( variant_eq ( & config_set . unwrap_err ( ) , & ConfigLoadError ::NameDuplicate ( PathBuf ::new ( ) ) ) )
2019-09-10 20:53:45 +00:00
}
2019-09-14 20:14:39 +00:00
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_set_merge_with_parent_matches ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-14 20:14:39 +00:00
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific1.yml " , r ###"
2019-09-14 20:14:39 +00:00
name : specific1
matches :
- trigger : " hello "
replace : " newstring "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-14 20:14:39 +00:00
assert_eq! ( config_set . default . matches . len ( ) , 2 ) ;
assert_eq! ( config_set . specific [ 0 ] . matches . len ( ) , 3 ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | x . trigger = = " hello " ) . is_some ( ) ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | x . trigger = = " :lol " ) . is_some ( ) ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | x . trigger = = " :yess " ) . is_some ( ) ) ;
}
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_set_merge_with_parent_matches_child_priority ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-14 20:14:39 +00:00
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path2 = create_user_config_file ( data_dir . path ( ) , " specific2.yml " , r ###"
2019-09-14 20:14:39 +00:00
name : specific1
matches :
- trigger : " :lol "
replace : " newstring "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-14 20:14:39 +00:00
assert_eq! ( config_set . default . matches . len ( ) , 2 ) ;
assert_eq! ( config_set . specific [ 0 ] . matches . len ( ) , 2 ) ;
2019-11-28 21:56:00 +00:00
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | {
if let MatchContentType ::Text ( content ) = & x . content {
x . trigger = = " :lol " & & content . replace = = " newstring "
} else {
false
}
} ) . is_some ( ) ) ;
2019-09-14 20:14:39 +00:00
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | x . trigger = = " :yess " ) . is_some ( ) ) ;
}
#[ test ]
2019-09-23 21:34:55 +00:00
fn test_user_defined_config_set_exclude_merge_with_parent_matches ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-14 20:14:39 +00:00
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path2 = create_user_config_file ( data_dir . path ( ) , " specific2.yml " , r ###"
2019-09-14 20:14:39 +00:00
name : specific1
2019-09-24 20:53:12 +00:00
exclude_default_matches : true
2019-09-14 20:14:39 +00:00
matches :
- trigger : " hello "
replace : " newstring "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-14 20:14:39 +00:00
assert_eq! ( config_set . default . matches . len ( ) , 2 ) ;
assert_eq! ( config_set . specific [ 0 ] . matches . len ( ) , 1 ) ;
2019-11-28 21:56:00 +00:00
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | {
if let MatchContentType ::Text ( content ) = & x . content {
x . trigger = = " hello " & & content . replace = = " newstring "
} else {
false
}
} ) . is_some ( ) ) ;
2019-09-14 20:14:39 +00:00
}
2019-09-15 13:51:29 +00:00
#[ test ]
fn test_only_yaml_files_are_loaded_from_config ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content (
r ###"
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###
) ;
let user_defined_path2 = create_user_config_file ( data_dir . path ( ) , " specific.zzz " , r ###"
2019-09-15 13:51:29 +00:00
name : specific1
2019-09-24 20:53:12 +00:00
exclude_default_matches : true
2019-09-15 13:51:29 +00:00
matches :
- trigger : " hello "
replace : " newstring "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-15 13:51:29 +00:00
assert_eq! ( config_set . specific . len ( ) , 0 ) ;
}
2019-09-24 20:53:12 +00:00
#[ test ]
fn test_config_set_no_parent_configs_works_correctly ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories ( ) ;
2019-09-24 20:53:12 +00:00
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-24 20:53:12 +00:00
name : specific1
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path2 = create_user_config_file ( data_dir . path ( ) , " specific2.yml " , r ###"
2019-09-24 20:53:12 +00:00
name : specific2
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 2 ) ;
}
#[ test ]
fn test_config_set_default_parent_works_correctly ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : hasta
replace : Hasta la vista
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-24 20:53:12 +00:00
parent : default
matches :
- trigger : " hello "
replace : " world "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 0 ) ;
assert_eq! ( config_set . default . matches . len ( ) , 2 ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hasta " ) ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hello " ) ) ;
}
#[ test ]
fn test_config_set_no_parent_should_not_merge ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : hasta
replace : Hasta la vista
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : " hello "
replace : " world "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 1 ) ;
assert_eq! ( config_set . default . matches . len ( ) , 1 ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hasta " ) ) ;
assert! ( ! config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hello " ) ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . any ( | m | m . trigger = = " hello " ) ) ;
}
#[ test ]
fn test_config_set_default_nested_parent_works_correctly ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : hasta
replace : Hasta la vista
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-24 20:53:12 +00:00
name : custom1
parent : default
matches :
- trigger : " hello "
replace : " world "
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path2 = create_user_config_file ( data_dir . path ( ) , " specific2.yml " , r ###"
2019-09-24 20:53:12 +00:00
parent : custom1
matches :
- trigger : " super "
replace : " mario "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 0 ) ;
assert_eq! ( config_set . default . matches . len ( ) , 3 ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hasta " ) ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hello " ) ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " super " ) ) ;
}
#[ test ]
fn test_config_set_parent_merge_children_priority_should_be_higher ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : hasta
replace : Hasta la vista
" ###);
2019-10-08 22:37:42 +00:00
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
2019-09-24 20:53:12 +00:00
parent : default
matches :
- trigger : " hasta "
replace : " world "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 0 ) ;
assert_eq! ( config_set . default . matches . len ( ) , 1 ) ;
2019-11-28 21:56:00 +00:00
assert! ( config_set . default . matches . iter ( ) . any ( | m | {
if let MatchContentType ::Text ( content ) = & m . content {
m . trigger = = " hasta " & & content . replace = = " world "
} else {
false
}
} ) ) ;
2019-09-24 20:53:12 +00:00
}
#[ test ]
fn test_config_set_package_configs_default_merge ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : hasta
replace : Hasta la vista
" ###);
2019-10-08 22:37:42 +00:00
let package_path = create_package_file ( package_dir . path ( ) , " package1 " , " package.yml " , r ###"
2019-09-24 20:53:12 +00:00
parent : default
matches :
- trigger : " harry "
replace : " potter "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 0 ) ;
assert_eq! ( config_set . default . matches . len ( ) , 2 ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hasta " ) ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " harry " ) ) ;
}
#[ test ]
fn test_config_set_package_configs_without_merge ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : hasta
replace : Hasta la vista
" ###);
2019-10-08 22:37:42 +00:00
let package_path = create_package_file ( package_dir . path ( ) , " package1 " , " package.yml " , r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : " harry "
replace : " potter "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 1 ) ;
assert_eq! ( config_set . default . matches . len ( ) , 1 ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hasta " ) ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . any ( | m | m . trigger = = " harry " ) ) ;
}
#[ test ]
fn test_config_set_package_configs_multiple_files ( ) {
2019-10-08 22:37:42 +00:00
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
2019-09-24 20:53:12 +00:00
matches :
- trigger : hasta
replace : Hasta la vista
" ###);
2019-10-08 22:37:42 +00:00
let package_path = create_package_file ( package_dir . path ( ) , " package1 " , " package.yml " , r ###"
2019-09-24 20:53:12 +00:00
name : package1
matches :
- trigger : " harry "
replace : " potter "
" ###);
2019-10-08 22:37:42 +00:00
let package_path2 = create_package_file ( package_dir . path ( ) , " package1 " , " addon.yml " , r ###"
2019-09-24 20:53:12 +00:00
parent : package1
matches :
- trigger : " ron "
replace : " weasley "
" ###);
2019-10-08 22:37:42 +00:00
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
2019-09-24 20:53:12 +00:00
assert_eq! ( config_set . specific . len ( ) , 1 ) ;
assert_eq! ( config_set . default . matches . len ( ) , 1 ) ;
assert! ( config_set . default . matches . iter ( ) . any ( | m | m . trigger = = " hasta " ) ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . any ( | m | m . trigger = = " harry " ) ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . any ( | m | m . trigger = = " ron " ) ) ;
}
2020-01-21 22:11:14 +00:00
#[ test ]
fn test_list_has_conflict_no_conflict ( ) {
assert_eq! ( ConfigSet ::list_has_conflicts ( & vec! ( " :ab " . to_owned ( ) , " :bc " . to_owned ( ) ) ) , false ) ;
}
#[ test ]
fn test_list_has_conflict_conflict ( ) {
let mut list = vec! ( " ac " . to_owned ( ) , " ab " . to_owned ( ) , " abc " . to_owned ( ) ) ;
list . sort ( ) ;
assert_eq! ( ConfigSet ::list_has_conflicts ( & list ) , true ) ;
}
#[ test ]
fn test_has_conflict_no_conflict ( ) {
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
matches :
- trigger : ac
replace : Hasta la vista
- trigger : bc
replace : Jon
" ###);
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
name : specific1
matches :
- trigger : " hello "
replace : " world "
" ###);
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
assert_eq! ( ConfigSet ::has_conflicts ( & config_set . default , & config_set . specific ) , false ) ;
}
#[ test ]
fn test_has_conflict_conflict_in_default ( ) {
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
matches :
- trigger : ac
replace : Hasta la vista
- trigger : bc
replace : Jon
- trigger : acb
replace : Error
" ###);
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
name : specific1
matches :
- trigger : " hello "
replace : " world "
" ###);
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
assert_eq! ( ConfigSet ::has_conflicts ( & config_set . default , & config_set . specific ) , true ) ;
}
#[ test ]
fn test_has_conflict_conflict_in_specific_and_default ( ) {
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
matches :
- trigger : ac
replace : Hasta la vista
- trigger : bc
replace : Jon
" ###);
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
name : specific1
matches :
- trigger : " bcd "
replace : " Conflict "
" ###);
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
assert_eq! ( ConfigSet ::has_conflicts ( & config_set . default , & config_set . specific ) , true ) ;
}
#[ test ]
fn test_has_conflict_no_conflict_in_specific_and_specific ( ) {
let ( data_dir , package_dir ) = create_temp_espanso_directories_with_default_content ( r ###"
matches :
- trigger : ac
replace : Hasta la vista
- trigger : bc
replace : Jon
" ###);
let user_defined_path = create_user_config_file ( data_dir . path ( ) , " specific.yml " , r ###"
name : specific1
matches :
- trigger : " bad "
replace : " Conflict "
" ###);
let user_defined_path2 = create_user_config_file ( data_dir . path ( ) , " specific2.yml " , r ###"
name : specific2
matches :
- trigger : " badass "
replace : " Conflict "
" ###);
let config_set = ConfigSet ::load ( data_dir . path ( ) , package_dir . path ( ) ) . unwrap ( ) ;
assert_eq! ( ConfigSet ::has_conflicts ( & config_set . default , & config_set . specific ) , false ) ;
}
2019-09-09 15:59:44 +00:00
}