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 ;
use std ::fs ::{ File , create_dir_all } ;
use std ::io ::Read ;
use serde ::{ Serialize , Deserialize } ;
2019-09-12 20:14:41 +00:00
use crate ::event ::KeyModifier ;
2019-09-09 15:59:44 +00:00
use std ::collections ::HashSet ;
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-09 15:59:44 +00:00
pub ( crate ) mod runtime ;
// TODO: add documentation link
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-09-23 20:08:46 +00:00
const DEFAULT_CONFIG_FILE_NAME : & str = " default.yml " ;
2019-09-23 21:34:55 +00:00
const USER_CONFIGS_FOLDER_NAME : & str = " user " ;
const PACKAGES_FOLDER_NAME : & str = " packages " ;
2019-09-09 15:59:44 +00:00
// Default values for primitives
fn default_name ( ) -> String { " default " . to_owned ( ) }
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_disabled ( ) -> bool { false }
fn default_log_level ( ) -> i32 { 0 }
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 }
fn default_toggle_interval ( ) -> u32 { 230 }
fn default_backspace_limit ( ) -> i32 { 3 }
2019-09-14 20:14:39 +00:00
fn default_exclude_parent_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 ,
#[ 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_disabled " ) ]
2019-09-09 15:59:44 +00:00
pub disabled : bool ,
2019-09-14 08:30:51 +00:00
#[ serde(default = " default_log_level " ) ]
pub log_level : i32 ,
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 ,
#[ serde(default) ]
pub toggle_key : KeyModifier ,
#[ serde(default = " default_toggle_interval " ) ]
pub toggle_interval : u32 ,
#[ serde(default = " default_backspace_limit " ) ]
pub backspace_limit : i32 ,
#[ serde(default) ]
pub backend : BackendType ,
2019-09-14 20:14:39 +00:00
#[ serde(default = " default_exclude_parent_matches " ) ]
pub exclude_parent_matches : bool ,
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 ( ) ) ;
2019-09-09 16:56:55 +00:00
validate_field! ( result , self . toggle_key , KeyModifier ::default ( ) ) ;
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-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 {
fn default ( ) -> Self {
BackendType ::Inject
}
}
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 ) ;
if let Err ( _ ) = res {
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
}
}
}
#[ 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 {
pub fn load ( dir_path : & Path ) -> Result < ConfigSet , ConfigLoadError > {
if ! dir_path . is_dir ( ) {
return Err ( ConfigLoadError ::InvalidConfigDirectory )
}
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
// Load default configuration
2019-09-10 20:53:45 +00:00
let default_file = dir_path . join ( DEFAULT_CONFIG_FILE_NAME ) ;
let default = Configs ::load_config ( default_file . as_path ( ) ) ? ;
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
// Load user defined configurations
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
// TODO: loading with parent merging
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
let mut specific = Vec ::new ( ) ;
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
let specific_dir = dir_path . join ( USER_CONFIGS_FOLDER_NAME ) ;
if specific_dir . exists ( ) {
// Used to make sure no duplicates are present
let mut name_set = HashSet ::new ( ) ; // TODO: think about integration with packages
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
let dir_entry = fs ::read_dir ( specific_dir ) ;
if dir_entry . is_err ( ) {
return Err ( ConfigLoadError ::UnableToReadFile )
}
let dir_entry = dir_entry . unwrap ( ) ;
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
for entry in dir_entry {
let entry = entry ;
if let Ok ( entry ) = entry {
let path = entry . path ( ) ;
2019-09-15 13:51:29 +00:00
2019-09-23 21:34:55 +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-23 21:34:55 +00:00
let mut config = Configs ::load_config ( path . as_path ( ) ) ? ;
2019-09-09 16:56:55 +00:00
2019-09-23 21:34:55 +00:00
if ! config . validate_user_defined_config ( ) {
return Err ( ConfigLoadError ::InvalidParameter ( path . to_owned ( ) ) )
}
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
if config . name = = " default " {
return Err ( ConfigLoadError ::MissingName ( path . to_owned ( ) ) ) ;
}
2019-09-09 15:59:44 +00:00
2019-09-23 21:34:55 +00:00
if name_set . contains ( & config . name ) {
return Err ( ConfigLoadError ::NameDuplicate ( path . to_owned ( ) ) ) ;
}
2019-09-14 20:14:39 +00:00
2019-09-23 21:34:55 +00:00
// Compute new match set, merging the parent's matches.
// Note: if an app-specific redefines a trigger already present in the
// default config, the latter gets overwritten.
if ! config . exclude_parent_matches {
let mut merged_matches = config . matches . clone ( ) ;
let mut trigger_set = HashSet ::new ( ) ;
merged_matches . iter ( ) . for_each ( | m | {
trigger_set . insert ( m . trigger . clone ( ) ) ;
} ) ;
let parent_matches : Vec < Match > = default . matches . iter ( ) . filter ( | & m | {
! trigger_set . contains ( & m . trigger )
} ) . map ( | m | m . clone ( ) ) . collect ( ) ;
merged_matches . extend ( parent_matches ) ;
config . matches = merged_matches ;
}
2019-09-10 21:32:43 +00:00
2019-09-23 21:34:55 +00:00
// TODO: check if it contains at least a filter, and warn the user about the problem
name_set . insert ( config . name . clone ( ) ) ;
specific . push ( config ) ;
}
2019-09-10 20:53:45 +00:00
}
2019-09-09 15:59:44 +00:00
}
2019-09-10 20:53:45 +00:00
Ok ( ConfigSet {
default ,
2019-09-23 21:34:55 +00:00
specific : specific
2019-09-10 20:53:45 +00:00
} )
2019-09-09 15:59:44 +00:00
}
2019-09-10 20:53:45 +00:00
pub fn load_default ( ) -> Result < ConfigSet , ConfigLoadError > {
2019-09-09 15:59:44 +00:00
let res = dirs ::home_dir ( ) ;
if let Some ( home_dir ) = res {
let espanso_dir = home_dir . join ( " .espanso " ) ;
// Create the espanso dir if id doesn't exist
let res = create_dir_all ( espanso_dir . as_path ( ) ) ;
if let Ok ( _ ) = res {
let default_file = espanso_dir . join ( DEFAULT_CONFIG_FILE_NAME ) ;
// If config file does not exist, create one from template
if ! default_file . exists ( ) {
2019-09-10 20:53:45 +00:00
let result = fs ::write ( & default_file , DEFAULT_CONFIG_FILE_CONTENT ) ;
if result . is_err ( ) {
return Err ( ConfigLoadError ::UnableToCreateDefaultConfig )
}
2019-09-09 15:59:44 +00:00
}
2019-09-23 21:34:55 +00:00
// Create auxiliary directories
let user_config_dir = espanso_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 )
}
}
let packages_dir = espanso_dir . join ( PACKAGES_FOLDER_NAME ) ;
if ! packages_dir . exists ( ) {
let res = create_dir_all ( packages_dir . as_path ( ) ) ;
if res . is_err ( ) {
return Err ( ConfigLoadError ::UnableToCreateDefaultConfig )
}
}
2019-09-09 15:59:44 +00:00
return ConfigSet ::load ( espanso_dir . as_path ( ) )
}
}
2019-09-10 20:53:45 +00:00
return Err ( ConfigLoadError ::UnableToCreateDefaultConfig )
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 ) ,
MissingName ( 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 ( ) ) ,
ConfigLoadError ::MissingName ( path ) = > write! ( f , " The 'name' field is required in user defined configurations, but it's missing in '{}' " , 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 " ,
ConfigLoadError ::MissingName ( _ ) = > " The 'name' field is required in user defined configurations, but it's missing " ,
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-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
#[ test ]
fn test_config_set_default_content_should_work_correctly ( ) {
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , DEFAULT_CONFIG_FILE_CONTENT ) ;
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) ;
assert! ( config_set . is_ok ( ) ) ;
}
#[ test ]
fn test_config_set_load_fail_bad_directory ( ) {
let config_set = ConfigSet ::load ( Path ::new ( " invalid/path " ) ) ;
assert_eq! ( config_set . is_err ( ) , true ) ;
assert_eq! ( config_set . unwrap_err ( ) , ConfigLoadError ::InvalidConfigDirectory ) ;
}
#[ test ]
fn test_config_set_missing_default_file ( ) {
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) ;
assert_eq! ( config_set . is_err ( ) , true ) ;
assert_eq! ( config_set . unwrap_err ( ) , ConfigLoadError ::FileNotFound ) ;
}
#[ test ]
fn test_config_set_invalid_yaml_syntax ( ) {
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
let default_path_copy = default_path . clone ( ) ;
fs ::write ( default_path , TEST_CONFIG_FILE_WITH_BAD_YAML ) ;
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) ;
match config_set {
Ok ( _ ) = > { assert! ( false ) } ,
Err ( e ) = > {
match e {
ConfigLoadError ::InvalidYAML ( p , _ ) = > assert_eq! ( p , default_path_copy ) ,
_ = > assert! ( false ) ,
}
assert! ( true ) ;
} ,
}
}
#[ test ]
fn test_config_set_specific_file_with_reserved_fields ( ) {
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , DEFAULT_CONFIG_FILE_CONTENT ) ;
2019-09-23 21:34:55 +00:00
let user_defined_path = create_user_config_file ( tmp_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
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) ;
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 ]
fn test_config_set_specific_file_missing_name ( ) {
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , DEFAULT_CONFIG_FILE_CONTENT ) ;
2019-09-23 21:34:55 +00:00
let user_defined_path = create_user_config_file ( tmp_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
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) ;
assert! ( config_set . is_err ( ) ) ;
2019-09-23 21:34:55 +00:00
assert_eq! ( config_set . unwrap_err ( ) , ConfigLoadError ::MissingName ( user_defined_path_copy ) )
2019-09-10 20:53:45 +00:00
}
2019-09-10 21:32:43 +00:00
pub fn create_temp_espanso_directory ( ) -> TempDir {
2019-09-10 20:53:45 +00:00
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , DEFAULT_CONFIG_FILE_CONTENT ) ;
2019-09-10 21:32:43 +00:00
tmp_dir
}
2019-09-23 21:34:55 +00:00
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 ) ;
}
2019-09-10 21:32:43 +00:00
2019-09-23 21:34:55 +00:00
create_temp_file_in_dir ( & user_config_dir , name , content )
2019-09-10 21:32:43 +00:00
}
#[ test ]
fn test_config_set_specific_file_duplicate_name ( ) {
let tmp_dir = create_temp_espanso_directory ( ) ;
2019-09-23 21:34:55 +00:00
let user_defined_path = create_user_config_file ( tmp_dir . path ( ) , " specific.yml " , r ###"
2019-09-10 20:53:45 +00:00
name : specific1
" ###);
2019-09-23 21:34:55 +00:00
let user_defined_path2 = create_user_config_file ( tmp_dir . path ( ) , " specific2.yml " , r ###"
2019-09-10 20:53:45 +00:00
name : specific1
" ###);
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) ;
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-09-14 20:14:39 +00:00
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , r ###"
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###);
2019-09-23 21:34:55 +00:00
let user_defined_path = create_user_config_file ( tmp_dir . path ( ) , " specific1.yml " , r ###"
2019-09-14 20:14:39 +00:00
name : specific1
matches :
- trigger : " hello "
replace : " newstring "
" ###);
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) . unwrap ( ) ;
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-09-14 20:14:39 +00:00
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , r ###"
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###);
2019-09-23 21:34:55 +00:00
let user_defined_path2 = create_user_config_file ( tmp_dir . path ( ) , " specific2.yml " , r ###"
2019-09-14 20:14:39 +00:00
name : specific1
matches :
- trigger : " :lol "
replace : " newstring "
" ###);
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) . unwrap ( ) ;
assert_eq! ( config_set . default . matches . len ( ) , 2 ) ;
assert_eq! ( config_set . specific [ 0 ] . matches . len ( ) , 2 ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | x . trigger = = " :lol " & & x . replace = = " newstring " ) . 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_exclude_merge_with_parent_matches ( ) {
2019-09-14 20:14:39 +00:00
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , r ###"
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###);
2019-09-23 21:34:55 +00:00
let user_defined_path2 = create_user_config_file ( tmp_dir . path ( ) , " specific2.yml " , r ###"
2019-09-14 20:14:39 +00:00
name : specific1
exclude_parent_matches : true
matches :
- trigger : " hello "
replace : " newstring "
" ###);
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) . unwrap ( ) ;
assert_eq! ( config_set . default . matches . len ( ) , 2 ) ;
assert_eq! ( config_set . specific [ 0 ] . matches . len ( ) , 1 ) ;
assert! ( config_set . specific [ 0 ] . matches . iter ( ) . find ( | x | x . trigger = = " hello " & & x . replace = = " newstring " ) . is_some ( ) ) ;
}
2019-09-15 13:51:29 +00:00
#[ test ]
fn test_only_yaml_files_are_loaded_from_config ( ) {
let tmp_dir = TempDir ::new ( ) . expect ( " unable to create temp directory " ) ;
let default_path = tmp_dir . path ( ) . join ( DEFAULT_CONFIG_FILE_NAME ) ;
fs ::write ( default_path , r ###"
matches :
- trigger : " :lol "
replace : " LOL "
- trigger : " :yess "
replace : " Bob "
" ###);
2019-09-23 21:34:55 +00:00
let user_defined_path2 = create_user_config_file ( tmp_dir . path ( ) , " specific.zzz " , r ###"
2019-09-15 13:51:29 +00:00
name : specific1
exclude_parent_matches : true
matches :
- trigger : " hello "
replace : " newstring "
" ###);
let config_set = ConfigSet ::load ( tmp_dir . path ( ) ) . unwrap ( ) ;
assert_eq! ( config_set . specific . len ( ) , 0 ) ;
}
2019-09-09 15:59:44 +00:00
}