use serde::{Deserialize, Serialize};
-use xdg::BaseDirectories;
+
+use super::config::ConfigLocation;
#[derive(Debug)]
pub enum AuthError {
}
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 '{}': {}",
use std::io::Read;
use super::auth::{AuthConfig,AuthError};
+use super::config::ConfigLocation;
use super::types::*;
#[derive(Hash, Debug, PartialEq, Eq, Clone, Copy)]
}
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(),
--- /dev/null
+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)
+ }
+}
pub mod types;
pub mod auth;
+pub mod config;
pub mod html;
pub mod scan_re;
pub mod coloured_string;
-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)
}
}
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;
}
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();
}
});
- 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,
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)?;
}
}
+ 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;