Read config from ~/.config/asciinema/config.toml

This commit is contained in:
Marcin Kulik
2023-12-27 23:14:24 +01:00
parent 2d7963a0f9
commit e2ba8085be
7 changed files with 226 additions and 30 deletions

112
Cargo.lock generated
View File

@@ -17,6 +17,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "anstream"
version = "0.6.4"
@@ -77,6 +88,7 @@ version = "3.0.0-alpha.3"
dependencies = [
"anyhow",
"clap",
"config",
"mio",
"nix",
"reqwest",
@@ -102,6 +114,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "async-trait"
version = "0.1.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -223,6 +246,21 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "config"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca"
dependencies = [
"async-trait",
"lazy_static",
"nom",
"pathdiff",
"rust-ini",
"serde",
"toml",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -248,6 +286,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "dlv-list"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
[[package]]
name = "encoding_rs"
version = "0.8.33"
@@ -405,6 +449,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@@ -521,7 +574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.3",
]
[[package]]
@@ -545,6 +598,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.149"
@@ -585,6 +644,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@@ -626,6 +691,16 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
@@ -657,6 +732,22 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "ordered-multimap"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
dependencies = [
"dlv-list",
"hashbrown 0.12.3",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "percent-encoding"
version = "2.3.1"
@@ -778,6 +869,16 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "rust-ini"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
dependencies = [
"cfg-if",
"ordered-multimap",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@@ -1075,6 +1176,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.2"

View File

@@ -23,3 +23,4 @@ signal-hook = "0.3.17"
uuid = { version = "1.6.1", features = ["v4"] }
reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "rustls-tls", "multipart", "gzip", "json"] }
rustyline = "13.0.0"
config = { version = "0.13.4", default-features = false, features = ["toml", "ini"] }

View File

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

View File

@@ -1,3 +1,4 @@
use crate::config::Config;
use crate::util;
use anyhow::{anyhow, Result};
use clap::Args;
@@ -21,12 +22,12 @@ struct UploadResponse {
}
impl Cli {
pub fn run(self, server_url: &Option<String>) -> Result<()> {
pub fn run(self, config: &Config) -> Result<()> {
let client = Client::new();
let form = Form::new().file("asciicast", self.filename)?;
let response = client
.post(api_url(server_url)?)
.post(api_url(config.server_url())?)
.multipart(form)
.basic_auth(get_username(), Some(util::get_install_id()?))
.header(header::USER_AGENT, build_user_agent())
@@ -56,8 +57,8 @@ impl Cli {
}
}
fn api_url(server_url: &Option<String>) -> Result<Url> {
let mut url = util::get_server_url(server_url.as_ref())?;
fn api_url(server_url: Option<&String>) -> Result<Url> {
let mut url = util::get_server_url(server_url)?;
url.set_path("api/asciicasts");
Ok(url)

92
src/config.rs Normal file
View File

@@ -0,0 +1,92 @@
use anyhow::{anyhow, Result};
use serde::Deserialize;
use std::env;
use std::path::{Path, PathBuf};
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Config {
server: Server,
api: Api,
cmd: Cmd,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Server {
url: Option<String>,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Api {
url: Option<String>,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Cmd {
rec: Rec,
play: Play,
}
#[derive(Debug, Deserialize, Default)]
#[allow(unused)]
pub struct Rec {
pub input: bool,
pub command: Option<String>,
pub env: String,
pub idle_time_limit: Option<f64>,
pub prefix_key: Option<String>,
pub pause_key: String,
pub add_marker_key: Option<String>,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Play {
pub speed: f64,
pub idle_time_limit: Option<f64>,
pub pause_key: String,
pub step_key: String,
pub next_marker_key: String,
}
impl Config {
pub fn new(server_url: Option<String>) -> Result<Self> {
let user_config_file = home()?.join("config.toml");
let mut config = config::Config::builder()
.set_default("server.url", None::<Option<String>>)?
.set_default("api.url", None::<Option<String>>)?
.set_default("cmd.rec.input", false)?
.set_default("cmd.rec.env", "SHELL,TERM")?
.set_default("cmd.rec.pause_key", "C-\\")?
.set_default("cmd.play.speed", 1.0)?
.set_default("cmd.play.pause_key", " ")?
.set_default("cmd.play.step_key", ".")?
.set_default("cmd.play.next_marker_key", "]")?
.add_source(
config::File::with_name(&user_config_file.to_string_lossy()).required(false),
)
.add_source(config::Environment::with_prefix("asciinema").separator("_"));
if let Some(url) = server_url {
config = config.set_override("server.url", Some(url))?;
}
Ok(config.build()?.try_deserialize()?)
}
pub fn server_url(&self) -> Option<&String> {
self.server.url.as_ref().or(self.api.url.as_ref())
}
}
pub 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

@@ -1,9 +1,11 @@
mod cmd;
mod config;
mod format;
mod locale;
mod pty;
mod recorder;
mod util;
use crate::config::Config;
use anyhow::Result;
use clap::{Parser, Subcommand};
@@ -40,13 +42,13 @@ enum Commands {
fn main() -> Result<()> {
let cli = Cli::parse();
let server_url = &cli.server_url;
let config = Config::new(cli.server_url.clone())?;
match cli.command {
Commands::Record(record) => record.run(),
Commands::Play(play) => play.run(),
Commands::Cat(cat) => cat.run(),
Commands::Upload(upload) => upload.run(server_url),
Commands::Auth(auth) => auth.run(server_url),
Commands::Upload(upload) => upload.run(&config),
Commands::Auth(auth) => auth.run(&config),
}
}

View File

@@ -1,3 +1,4 @@
use crate::config;
use anyhow::{anyhow, bail, Result};
use reqwest::Url;
use std::{env, fs, io::ErrorKind, path::Path, path::PathBuf};
@@ -29,7 +30,7 @@ fn create_install_id() -> Result<String> {
}
fn read_legacy_install_id() -> Result<Option<String>> {
let path = config_home()?.join(INSTALL_ID_FILENAME);
let path = config::home()?.join(INSTALL_ID_FILENAME);
match fs::read_to_string(path) {
Ok(s) => Ok(Some(s.trim().to_string())),
@@ -44,17 +45,14 @@ fn read_legacy_install_id() -> Result<Option<String>> {
}
}
pub fn get_server_url<S: ToString>(server_url: Option<S>) -> Result<Url> {
let mut url_opt = server_url
.map(|s| s.to_string())
.or(env::var("ASCIINEMA_SERVER_URL").ok())
.or(env::var("ASCIINEMA_API_URL").ok());
pub fn get_server_url(server_url: Option<&String>) -> Result<Url> {
let mut url = server_url.cloned();
if url_opt.is_none() {
url_opt = read_state_file(DEFAULT_SERVER_URL_FILENAME)?;
if url.is_none() {
url = read_state_file(DEFAULT_SERVER_URL_FILENAME)?;
}
match url_opt {
match url {
Some(url) => Ok(Url::parse(&url)?),
None => {
@@ -106,14 +104,6 @@ fn write_state_file(filename: &str, contents: &str) -> Result<()> {
Ok(())
}
fn config_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"))
}
fn state_home() -> Result<PathBuf> {
env::var("ASCIINEMA_STATE_HOME")
.map(PathBuf::from)