1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
use std::{
env, fs,
path::{Path, PathBuf},
};
use anyhow::{bail, Context, Result};
use clap::Parser;
use const_format::{formatcp, map_ascii_case, Case};
use serde::Deserialize;
use crate::{
actions::AnyActionConfig, blockchain::ElectrumConfig, message::MessageConfig,
wallets::WalletConfig,
};
#[derive(Parser, Debug)]
#[command(version, about)]
pub struct Args {
/// Path to toml configuration file
config: Option<String>,
/// Perform configured actions on a test notification
#[arg(short, long)]
test: bool,
/// Notify for every past transaction (careful: if you have a long transaction history, this
/// can SPAM your configured actions
#[arg(short, long)]
notify_past_txs: bool,
}
impl Args {
pub fn config(&self) -> Option<&str> {
self.config.as_deref()
}
pub fn test(&self) -> bool {
self.test
}
pub fn notify_past_txs(&self) -> bool {
self.notify_past_txs
}
}
fn get_config_filename() -> &'static str {
formatcp!("{}.toml", env!("CARGO_PKG_NAME"))
}
fn get_config_env_var() -> &'static str {
formatcp!(
"{}_CONFIG",
map_ascii_case!(Case::Upper, env!("CARGO_PKG_NAME"))
)
}
fn get_cwd_config_path() -> PathBuf {
PathBuf::from(".").join(get_config_filename())
}
fn get_config_path_impl(user_config_dir: &Path) -> PathBuf {
user_config_dir
.join(env!("CARGO_PKG_NAME"))
.join(get_config_filename())
}
fn get_user_config_path() -> Option<PathBuf> {
dirs::config_dir().map(|p| get_config_path_impl(&p))
}
fn get_system_config_path() -> PathBuf {
get_config_path_impl(&systemd_directories::config_dir().unwrap_or(PathBuf::from("/etc")))
}
fn get_config_path(maybe_arg_config: &Option<&str>) -> Result<PathBuf> {
if let Some(arg_path) = maybe_arg_config {
return Ok(PathBuf::from(arg_path));
}
if let Ok(env_path) = env::var(get_config_env_var()) {
return Ok(PathBuf::from(env_path));
}
let cwd_config_path = get_cwd_config_path();
if cwd_config_path.try_exists().is_ok_and(|x| x) {
return Ok(cwd_config_path);
}
if let Some(user_config_path) = get_user_config_path() {
if user_config_path.try_exists().is_ok_and(|x| x) {
return Ok(user_config_path);
}
}
let system_config_path = get_system_config_path();
if system_config_path.try_exists().is_ok_and(|x| x) {
return Ok(system_config_path);
}
bail!(
"no configuration file was passed as first argument, nor by the '{}' environment variable, nor did one exist in the default search paths: '{}', '{}', '{}'",
get_config_env_var(),
get_cwd_config_path().display(),
get_user_config_path().unwrap_or_default().display(),
get_system_config_path().display()
);
}
#[derive(Deserialize, Debug)]
pub struct Config {
wallets: Vec<WalletConfig>,
#[serde(default)]
electrum: ElectrumConfig,
#[serde(default)]
message: MessageConfig,
#[serde(default)]
actions: Vec<AnyActionConfig>,
}
impl Config {
pub fn electrum(&self) -> &ElectrumConfig {
&self.electrum
}
pub fn wallets(&self) -> &[WalletConfig] {
&self.wallets
}
pub fn message(&self) -> &MessageConfig {
&self.message
}
pub fn actions(&self) -> &[AnyActionConfig] {
&self.actions
}
}
pub fn get_config(maybe_arg_config: &Option<&str>) -> Result<Config> {
let config_path = get_config_path(maybe_arg_config)?;
info!("reading configuration from '{}'", config_path.display());
let config_content = fs::read_to_string(&config_path)
.with_context(|| format!("could not read config file '{}'", config_path.display()))?;
toml::from_str(&config_content)
.with_context(|| format!("could not parse config file '{}'", config_path.display(),))
}
|