chiark / gitweb /
Reorganise startup to pass in a config location.
authorSimon Tatham <anakin@pobox.com>
Sun, 31 Dec 2023 11:26:32 +0000 (11:26 +0000)
committerSimon Tatham <anakin@pobox.com>
Sun, 31 Dec 2023 13:05:07 +0000 (13:05 +0000)
This begins to set up for specifying a non-default one on the command
line, which I'm going to need in order to start doing testing that
depends on a local test Mastodon instance.

src/auth.rs
src/client.rs
src/config.rs [new file with mode: 0644]
src/lib.rs
src/main.rs
src/tui.rs

index 3cfae9774e16892c46ba5d16faa9c72dd28826df..098db84ca4d75fd4748add574bee255fd642c210 100644 (file)
@@ -1,5 +1,6 @@
 use serde::{Deserialize, Serialize};
-use xdg::BaseDirectories;
+
+use super::config::ConfigLocation;
 
 #[derive(Debug)]
 pub enum AuthError {
@@ -34,14 +35,8 @@ pub struct AuthConfig {
 }
 
 impl AuthConfig {
-    pub fn load() -> Result<Self, AuthError> {
-        let xdg_dirs = match BaseDirectories::with_prefix("mastodonochrome") {
-            Err(e) => Err(AuthError::Bad(
-                format!("unable to get config directory: {}", e))),
-            Ok(d) => Ok(d),
-        }?;
-
-        let authfile = xdg_dirs.get_config_file("auth");
+    pub fn load(cfgloc: &ConfigLocation) -> Result<Self, AuthError> {
+        let authfile = cfgloc.get_path("auth");
         let authdata = match std::fs::read_to_string(&authfile) {
             Err(e) => Err(AuthError::Nonexistent(
                 format!("unable to read config file '{}': {}",
index de523db9d8433c43bb37d55f87c6073075818b44..6a366c2fa3b2cc7b22dc005de80105b1f3e6388d 100644 (file)
@@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet, VecDeque};
 use std::io::Read;
 
 use super::auth::{AuthConfig,AuthError};
+use super::config::ConfigLocation;
 use super::types::*;
 
 #[derive(Hash, Debug, PartialEq, Eq, Clone, Copy)]
@@ -150,9 +151,9 @@ pub enum FeedExtend {
 }
 
 impl Client {
-    pub fn new() -> Result<Self, AuthError> {
+    pub fn new(cfgloc: &ConfigLocation) -> Result<Self, AuthError> {
         Ok(Client {
-            auth: AuthConfig::load()?,
+            auth: AuthConfig::load(cfgloc)?,
             client: reqwest::blocking::Client::new(),
             accounts: HashMap::new(),
             statuses: HashMap::new(),
diff --git a/src/config.rs b/src/config.rs
new file mode 100644 (file)
index 0000000..549ae0d
--- /dev/null
@@ -0,0 +1,39 @@
+use std::path::PathBuf;
+
+#[derive(Debug)]
+pub enum ConfigError {
+    XDG(xdg::BaseDirectoriesError),
+}
+
+impl std::fmt::Display for ConfigError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) ->
+        Result<(), std::fmt::Error>
+    {
+        match self {
+            ConfigError::XDG(e) => { e.fmt(f) },
+        }
+    }
+}
+
+impl From<xdg::BaseDirectoriesError> for ConfigError {
+    fn from(err: xdg::BaseDirectoriesError) -> Self {
+        ConfigError::XDG(err)
+    }
+}
+
+pub struct ConfigLocation {
+    dir: PathBuf,
+}
+
+impl ConfigLocation {
+    pub fn default() -> Result<Self, ConfigError> {
+        let base_dirs = xdg::BaseDirectories::with_prefix("mastodonochrome")?;
+        Ok(ConfigLocation {
+            dir: base_dirs.get_config_home(),
+        })
+    }
+
+    pub fn get_path(&self, leaf: &str) -> PathBuf {
+        self.dir.join(leaf)
+    }
+}
index 0b185a4003f59f596c5d3c80b7062963a2402cf1..9acfb73d6fefa0443014c9ff5385093fd9cf0731 100644 (file)
@@ -1,5 +1,6 @@
 pub mod types;
 pub mod auth;
+pub mod config;
 pub mod html;
 pub mod scan_re;
 pub mod coloured_string;
index 3ac0e40adb7a3bfc0a42696b89591782c9e8c4c5..4a2cfd8806702bbfcf24173763e8bccbf975c40a 100644 (file)
@@ -1,14 +1,46 @@
-use std::io::Write;
-use std::io::stderr;
+use std::fmt::Display;
 use std::process::ExitCode;
 
-use mastodonochrome::tui::Tui;
+use mastodonochrome::config::{ConfigLocation, ConfigError};
+use mastodonochrome::tui::{Tui, TuiError};
+
+#[derive(Debug)]
+pub struct TopLevelError {
+    message: String,
+}
+
+impl Display for TopLevelError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) ->
+        Result<(), std::fmt::Error>
+    {
+        write!(f, "mastodonochrome: error: {}", self.message)
+    }
+}
+
+trait TopLevelErrorCandidate: Display {}
+
+impl<E: TopLevelErrorCandidate> From<E> for TopLevelError {
+    fn from(err: E) -> Self {
+        TopLevelError {
+            message: err.to_string(),
+        }
+    }
+}
+
+impl TopLevelErrorCandidate for ConfigError {}
+impl TopLevelErrorCandidate for TuiError {}
+
+fn main_inner() -> Result<(), TopLevelError> {
+    let cfgloc = ConfigLocation::default()?;
+    Tui::run(&cfgloc)?;
+    Ok(())
+}
 
 fn main() -> ExitCode {
-    match Tui::run() {
+    match main_inner() {
         Ok(_) => ExitCode::from(0),
         Err(e) => {
-            let _ = writeln!(&mut stderr(), "mastodonochrome: error: {}", e);
+            let _ = eprintln!("{}", e);
             ExitCode::from(1)
         }
     }
index 5fd893d15bc17ced068f1ff77fd28693486a70fc..6f78573033b27d288a232d8f0103c244ba98f3f4 100644 (file)
@@ -17,6 +17,7 @@ use unicode_width::UnicodeWidthStr;
 use super::activity_stack::*;
 use super::client::{Client, ClientError, FeedId, StreamId, StreamUpdate};
 use super::coloured_string::{ColouredString, ColouredStringSlice};
+use super::config::ConfigLocation;
 use super::menu::*;
 use super::file::*;
 use super::auth::AuthError;
@@ -192,12 +193,7 @@ impl std::fmt::Display for TuiError {
 }
 
 impl Tui {
-    pub fn run() -> Result<(), TuiError> {
-        stdout().execute(EnterAlternateScreen)?;
-        enable_raw_mode()?;
-        let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
-        terminal.clear()?;
-
+    pub fn run(cfgloc: &ConfigLocation) -> Result<(), TuiError> {
         let (sender, receiver) = std::sync::mpsc::sync_channel(1);
 
         let input_sender = sender.clone();
@@ -211,7 +207,12 @@ impl Tui {
             }
         });
 
-        let client = Client::new()?;
+        let client = Client::new(cfgloc)?;
+
+        stdout().execute(EnterAlternateScreen)?;
+        enable_raw_mode()?;
+        let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
+        terminal.clear()?;
 
         let mut tui = Tui {
             terminal: terminal,
@@ -221,22 +222,7 @@ impl Tui {
             client: client,
         };
 
-        {
-            let sender = tui.subthread_sender.clone();
-            tui.client.start_streaming_thread(
-                &StreamId::User, Box::new(move |update| {
-                    if let Err(_) = sender.send(
-                        SubthreadEvent::StreamEv(update)) {
-                    // It would be nice to do something about this
-                    // error, but what _can_ we do? We can hardly send
-                    // an error notification back to the main thread,
-                    // because that communication channel is just what
-                    // we've had a problem with.
-                }
-            }))?;
-        }
-
-        let result = tui.main_loop();
+        let result = tui.run_inner();
 
         disable_raw_mode()?;
         stdout().execute(LeaveAlternateScreen)?;
@@ -294,6 +280,25 @@ impl Tui {
         }
     }
 
+    fn run_inner(&mut self) -> Result<(), TuiError> {
+        {
+            let sender = self.subthread_sender.clone();
+            self.client.start_streaming_thread(
+                &StreamId::User, Box::new(move |update| {
+                    if let Err(_) = sender.send(
+                        SubthreadEvent::StreamEv(update)) {
+                    // It would be nice to do something about this
+                    // error, but what _can_ we do? We can hardly send
+                    // an error notification back to the main thread,
+                    // because that communication channel is just what
+                    // we've had a problem with.
+                }
+            }))?;
+        }
+
+        self.main_loop()
+    }
+
     fn main_loop(&mut self) -> Result<(), TuiError> {
         'outer: loop {
             let state = &mut self.state;