Skip to content

Commit d82b3d1

Browse files
committed
feat: support env overrides
1 parent ff6a9e2 commit d82b3d1

4 files changed

Lines changed: 80 additions & 3 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ Puddle is a CLI tool to interact with the [Raindrop.io API](https://developer.ra
2626
- Copy your `Client ID` and `Client secret` into the CLI inputs
2727
- Try running `puddle me`, you should see your account details and you're all set!
2828

29+
## Environment Variables
30+
`puddle` can also load configuration from environment variables instead of `config.toml`.
31+
32+
Supported variables:
33+
- `PUDDLE_CLIENT_ID`
34+
- `PUDDLE_CLIENT_SECRET`
35+
- `PUDDLE_REDIRECT_URI`
36+
- `PUDDLE_ACCESS_TOKEN`
37+
- `PUDDLE_REFRESH_TOKEN`
38+
2939
# Development
3040
Make sure you have [Rust](https://rust-lang.org/tools/install/) installed.
3141

src/app.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::config::{Config, global_config_path};
1+
use crate::config::Config;
22
use puddle::RaindropClient;
33

44
pub(crate) struct CliApp {
@@ -7,8 +7,7 @@ pub(crate) struct CliApp {
77

88
impl CliApp {
99
pub(crate) fn new() -> Result<Self, Box<dyn std::error::Error>> {
10-
let config_path = global_config_path()?;
11-
let config = Config::from_path(&config_path)?;
10+
let config = Config::load()?;
1211

1312
Ok(Self {
1413
client: RaindropClient::new(config.access_token)?,

src/config.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::constants::{
2+
ENV_ACCESS_TOKEN, ENV_CLIENT_ID, ENV_CLIENT_SECRET, ENV_REDIRECT_URI, ENV_REFRESH_TOKEN,
23
TOML_ACCESS_TOKEN, TOML_CLIENT_ID, TOML_CLIENT_SECRET, TOML_REDIRECT_URI, TOML_REFRESH_TOKEN,
34
};
45
use std::env;
@@ -7,6 +8,7 @@ use std::io;
78
use std::path::{Path, PathBuf};
89
use toml::Value;
910

11+
#[derive(Debug)]
1012
pub struct Config {
1113
pub client_id: String,
1214
pub client_secret: String,
@@ -16,6 +18,15 @@ pub struct Config {
1618
}
1719

1820
impl Config {
21+
pub fn load() -> io::Result<Self> {
22+
if let Some(config) = Self::from_env()? {
23+
return Ok(config);
24+
}
25+
26+
let config_path = global_config_path()?;
27+
Self::from_path(&config_path)
28+
}
29+
1930
pub fn from_path(path: impl AsRef<Path>) -> io::Result<Self> {
2031
let path = path.as_ref();
2132
let content = fs::read_to_string(path).map_err(|err| {
@@ -73,6 +84,57 @@ impl Config {
7384
Ok(())
7485
}
7586

87+
fn from_env() -> io::Result<Option<Self>> {
88+
let entries = [
89+
ENV_CLIENT_ID,
90+
ENV_CLIENT_SECRET,
91+
ENV_REDIRECT_URI,
92+
ENV_ACCESS_TOKEN,
93+
ENV_REFRESH_TOKEN,
94+
]
95+
.map(|key| {
96+
(
97+
key,
98+
env::var(key).ok().filter(|value| !value.trim().is_empty()),
99+
)
100+
});
101+
102+
if entries.iter().all(|(_, value)| value.is_none()) {
103+
return Ok(None);
104+
}
105+
106+
let [
107+
(_, Some(client_id)),
108+
(_, Some(client_secret)),
109+
(_, Some(redirect_uri)),
110+
(_, Some(access_token)),
111+
(_, Some(refresh_token)),
112+
] = entries
113+
else {
114+
let missing = entries
115+
.iter()
116+
.filter(|(_, value)| value.is_none())
117+
.map(|(env_key, _)| *env_key)
118+
.collect::<Vec<_>>()
119+
.join(", ");
120+
121+
return Err(io::Error::new(
122+
io::ErrorKind::InvalidInput,
123+
format!(
124+
"partial PUDDLE_* environment configuration detected. Missing or empty: {missing}"
125+
),
126+
));
127+
};
128+
129+
Ok(Some(Self {
130+
client_id,
131+
client_secret,
132+
redirect_uri,
133+
access_token,
134+
refresh_token,
135+
}))
136+
}
137+
76138
fn required_toml_string(table: &toml::Table, key: &str, path: &Path) -> io::Result<String> {
77139
let value = table.get(key).ok_or_else(|| {
78140
io::Error::new(

src/constants.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ pub const TOML_REDIRECT_URI: &str = "redirect_uri";
44
pub const TOML_ACCESS_TOKEN: &str = "access_token";
55
pub const TOML_REFRESH_TOKEN: &str = "refresh_token";
66

7+
pub const ENV_CLIENT_ID: &str = "PUDDLE_CLIENT_ID";
8+
pub const ENV_CLIENT_SECRET: &str = "PUDDLE_CLIENT_SECRET";
9+
pub const ENV_REDIRECT_URI: &str = "PUDDLE_REDIRECT_URI";
10+
pub const ENV_ACCESS_TOKEN: &str = "PUDDLE_ACCESS_TOKEN";
11+
pub const ENV_REFRESH_TOKEN: &str = "PUDDLE_REFRESH_TOKEN";
12+
713
pub const RAINDROP_INTEGRATIONS_URI: &str = "https://app.raindrop.io/settings/integrations";
814
pub const DEFAULT_OAUTH_DEBUG_REDIRECT_URI: &str = "https://oauthdebugger.com/debug";
915
pub const AUTHORIZE_URL_BASE: &str = "https://raindrop.io/oauth/authorize";

0 commit comments

Comments
 (0)