Unify config

This commit is contained in:
Marcin Kulik
2023-12-28 16:30:09 +01:00
parent 52e7bfe067
commit 08d83ad5a7
5 changed files with 95 additions and 135 deletions

View File

@@ -1,4 +1,4 @@
use crate::{config::Config, util}; use crate::config::Config;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use clap::Args; use clap::Args;
use reqwest::Url; use reqwest::Url;
@@ -8,8 +8,9 @@ pub struct Cli {}
impl Cli { impl Cli {
pub fn run(self, config: &Config) -> Result<()> { pub fn run(self, config: &Config) -> Result<()> {
let auth_url = auth_url(config.server_url())?; let server_url = config.get_server_url()?;
let server_hostname = auth_url.host().ok_or(anyhow!("invalid server URL"))?; let server_hostname = server_url.host().ok_or(anyhow!("invalid server URL"))?;
let auth_url = auth_url(&server_url, &config.get_install_id()?);
println!("Open the following URL in a web browser to authenticate this asciinema CLI with your {server_hostname} user account:\n"); println!("Open the following URL in a web browser to authenticate this asciinema CLI with your {server_hostname} user account:\n");
println!("{}\n", auth_url); println!("{}\n", auth_url);
@@ -19,9 +20,9 @@ impl Cli {
} }
} }
fn auth_url(server_url: Option<&String>) -> Result<Url> { fn auth_url(server_url: &Url, install_id: &str) -> Url {
let mut url = util::get_server_url(server_url)?; let mut url = server_url.clone();
url.set_path(&format!("connect/{}", util::get_install_id()?)); url.set_path(&format!("connect/{install_id}"));
Ok(url) url
} }

View File

@@ -1,5 +1,4 @@
use crate::config::Config; use crate::config::Config;
use crate::util;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use clap::Args; use clap::Args;
use reqwest::{ use reqwest::{
@@ -27,9 +26,9 @@ impl Cli {
let form = Form::new().file("asciicast", self.filename)?; let form = Form::new().file("asciicast", self.filename)?;
let response = client let response = client
.post(api_url(config.server_url())?) .post(api_url(&config.get_server_url()?))
.multipart(form) .multipart(form)
.basic_auth(get_username(), Some(util::get_install_id()?)) .basic_auth(get_username(), Some(config.get_install_id()?))
.header(header::USER_AGENT, build_user_agent()) .header(header::USER_AGENT, build_user_agent())
.header(header::ACCEPT, "application/json") .header(header::ACCEPT, "application/json")
.send()?; .send()?;
@@ -57,11 +56,11 @@ impl Cli {
} }
} }
fn api_url(server_url: Option<&String>) -> Result<Url> { fn api_url(server_url: &Url) -> Url {
let mut url = util::get_server_url(server_url)?; let mut url = server_url.clone();
url.set_path("api/asciicasts"); url.set_path("api/asciicasts");
Ok(url) url
} }
fn get_username() -> String { fn get_username() -> String {

View File

@@ -1,8 +1,14 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, bail, Result};
use reqwest::Url;
use serde::Deserialize; use serde::Deserialize;
use std::env; use std::env;
use std::fs; use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uuid::Uuid;
const DEFAULT_SERVER_URL: &str = "https://asciinema.org";
const INSTALL_ID_FILENAME: &str = "install-id";
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(unused)] #[allow(unused)]
@@ -80,12 +86,46 @@ impl Config {
Ok(config.build()?.try_deserialize()?) Ok(config.build()?.try_deserialize()?)
} }
pub fn server_url(&self) -> Option<&String> { pub fn get_server_url(&self) -> Result<Url> {
self.server.url.as_ref().or(self.api.url.as_ref()) match self.server.url.as_ref() {
Some(url) => Ok(Url::parse(url)?),
None => {
let url = Url::parse(&ask_for_server_url()?)?;
save_default_server_url(url.as_ref())?;
Ok(url)
}
}
}
pub fn get_install_id(&self) -> Result<String> {
let path = install_id_path()?;
if let Some(id) = read_install_id(&path)? {
Ok(id)
} else {
let id = create_install_id();
save_install_id(&path, &id)?;
Ok(id)
}
} }
} }
pub fn save_default_server_url(url: &str) -> Result<()> { fn ask_for_server_url() -> Result<String> {
println!("No asciinema server configured for this CLI.");
let mut rl = rustyline::DefaultEditor::new()?;
let url = rl.readline_with_initial(
"Enter the server URL to use by default: ",
(DEFAULT_SERVER_URL, ""),
)?;
println!();
Ok(url)
}
fn save_default_server_url(url: &str) -> Result<()> {
let path = user_defaults_path()?; let path = user_defaults_path()?;
if let Some(dir) = path.parent() { if let Some(dir) = path.parent() {
@@ -97,12 +137,32 @@ pub fn save_default_server_url(url: &str) -> Result<()> {
Ok(()) Ok(())
} }
pub fn home() -> Result<PathBuf> { fn read_install_id(path: &PathBuf) -> Result<Option<String>> {
env::var("ASCIINEMA_CONFIG_HOME") match fs::read_to_string(path) {
.map(PathBuf::from) Ok(s) => Ok(Some(s.trim().to_string())),
.or(env::var("XDG_CONFIG_HOME").map(|home| Path::new(&home).join("asciinema")))
.or(env::var("HOME").map(|home| Path::new(&home).join(".config").join("asciinema"))) Err(e) => {
.map_err(|_| anyhow!("need $HOME or $XDG_CONFIG_HOME or $ASCIINEMA_CONFIG_HOME")) if e.kind() == ErrorKind::NotFound {
Ok(None)
} else {
bail!(e)
}
}
}
}
fn create_install_id() -> String {
Uuid::new_v4().to_string()
}
fn save_install_id(path: &PathBuf, id: &str) -> Result<()> {
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
fs::write(path, &id)?;
Ok(())
} }
fn user_config_path() -> Result<PathBuf> { fn user_config_path() -> Result<PathBuf> {
@@ -112,3 +172,15 @@ fn user_config_path() -> Result<PathBuf> {
fn user_defaults_path() -> Result<PathBuf> { fn user_defaults_path() -> Result<PathBuf> {
Ok(home()?.join("defaults.toml")) Ok(home()?.join("defaults.toml"))
} }
fn install_id_path() -> Result<PathBuf> {
Ok(home()?.join(INSTALL_ID_FILENAME))
}
fn home() -> Result<PathBuf> {
env::var("ASCIINEMA_CONFIG_HOME")
.map(PathBuf::from)
.or(env::var("XDG_CONFIG_HOME").map(|home| Path::new(&home).join("asciinema")))
.or(env::var("HOME").map(|home| Path::new(&home).join(".config").join("asciinema")))
.map_err(|_| anyhow!("need $HOME or $XDG_CONFIG_HOME or $ASCIINEMA_CONFIG_HOME"))
}

View File

@@ -4,7 +4,6 @@ mod format;
mod locale; mod locale;
mod pty; mod pty;
mod recorder; mod recorder;
mod util;
use crate::config::Config; use crate::config::Config;
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};

View File

@@ -1,111 +0,0 @@
use crate::config;
use anyhow::{anyhow, bail, Result};
use reqwest::Url;
use std::{env, fs, io::ErrorKind, path::Path, path::PathBuf};
use uuid::Uuid;
const DEFAULT_SERVER_URL: &str = "https://asciinema.org";
const INSTALL_ID_FILENAME: &str = "install-id";
pub fn get_install_id() -> Result<String> {
if let Some(install_id) = read_install_id()? {
Ok(install_id)
} else if let Some(install_id) = read_legacy_install_id()? {
Ok(install_id)
} else {
create_install_id()
}
}
fn read_install_id() -> Result<Option<String>> {
read_state_file(INSTALL_ID_FILENAME)
}
fn create_install_id() -> Result<String> {
let id = Uuid::new_v4().to_string();
write_state_file(INSTALL_ID_FILENAME, &id)?;
Ok(id)
}
fn read_legacy_install_id() -> Result<Option<String>> {
let path = config::home()?.join(INSTALL_ID_FILENAME);
match fs::read_to_string(path) {
Ok(s) => Ok(Some(s.trim().to_string())),
Err(e) => {
if e.kind() == ErrorKind::NotFound {
Ok(None)
} else {
bail!(e)
}
}
}
}
pub fn get_server_url(server_url: Option<&String>) -> Result<Url> {
match server_url {
Some(url) => Ok(Url::parse(&url)?),
None => {
let url = Url::parse(&ask_for_server_url()?)?;
config::save_default_server_url(url.as_ref())?;
Ok(url)
}
}
}
fn ask_for_server_url() -> Result<String> {
println!("No asciinema server configured for this CLI.");
let mut rl = rustyline::DefaultEditor::new()?;
let url = rl.readline_with_initial(
"Enter the server URL to use by default: ",
(DEFAULT_SERVER_URL, ""),
)?;
println!();
Ok(url)
}
fn read_state_file(filename: &str) -> Result<Option<String>> {
let path = state_home()?.join(filename);
match fs::read_to_string(path) {
Ok(s) => Ok(Some(s.trim().to_string())),
Err(e) => {
if e.kind() == ErrorKind::NotFound {
Ok(None)
} else {
bail!(e)
}
}
}
}
fn write_state_file(filename: &str, contents: &str) -> Result<()> {
let path = state_home()?.join(filename);
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
fs::write(path, contents)?;
Ok(())
}
fn state_home() -> Result<PathBuf> {
env::var("ASCIINEMA_STATE_HOME")
.map(PathBuf::from)
.or(env::var("XDG_STATE_HOME").map(|home| Path::new(&home).join("asciinema")))
.or(env::var("HOME").map(|home| {
Path::new(&home)
.join(".local")
.join("state")
.join("asciinema")
}))
.map_err(|_| anyhow!("need $HOME or $XDG_STATE_HOME or $ASCIINEMA_STATE_HOME"))
}