Add Config System instead of hardcoding values
Config is read from `$XDG_CONFIG_DIRECTORY/ibis/config.toml` falling back to `~/.config/ibis/config.toml` if `$XDG_CONFIG_DIRECTORY` is not set.
This commit is contained in:
parent
8ba9f22430
commit
fbc00044e8
6 changed files with 110 additions and 41 deletions
|
|
@ -16,6 +16,8 @@ glob = "0.3"
|
|||
anyhow = "1"
|
||||
chrono = {version = "0.4.19", features = ["serde"]}
|
||||
rofi = {version = "0.2", optional = true}
|
||||
lazy_static = "1.4.0"
|
||||
directories = "4.0.1"
|
||||
|
||||
[lib]
|
||||
name = "ibis"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,83 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use directories::{BaseDirs, ProjectDirs};
|
||||
use serde::{de::Error, Deserialize, Deserializer, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Config = {
|
||||
let config_path = ProjectDirs::from("", "Phobos", "ibis")
|
||||
.expect("no valid home directory found")
|
||||
.config_dir()
|
||||
.to_owned()
|
||||
.join("config.toml");
|
||||
if !config_path.exists() {
|
||||
return Config::default();
|
||||
}
|
||||
let config_str = std::fs::read_to_string(&config_path);
|
||||
if let Ok(config_str) = config_str {
|
||||
toml::from_str(&config_str).unwrap_or_else(|e| {
|
||||
panic!("Error while parsing config at {:?} \n{}", &config_path, e);
|
||||
})
|
||||
} else {
|
||||
panic!(
|
||||
"Error while reading config at {:?}\n{}",
|
||||
&config_path,
|
||||
config_str.unwrap_err()
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A struct representing the config options
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
/// The location of the link pointing to the current course.
|
||||
/// This is treated as the single source of truth as to what the current course is.
|
||||
/// This needs to be a absolute path.
|
||||
#[serde(deserialize_with = "ensure_absolute_path")]
|
||||
pub link_path: PathBuf,
|
||||
/// The folder under which all other folders of "courses" are located.
|
||||
/// "courses" are folders containing a `course.toml`
|
||||
/// This needs to be an absolute path.
|
||||
#[serde(deserialize_with = "ensure_absolute_path")]
|
||||
pub files_root: PathBuf,
|
||||
/// File extensions that should be treated as lecture notes
|
||||
pub note_extensions: Vec<String>,
|
||||
}
|
||||
|
||||
fn ensure_absolute_path<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
D::Error: Error,
|
||||
{
|
||||
let path = PathBuf::deserialize(deserializer)?;
|
||||
if path.is_absolute() {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(D::Error::invalid_value(
|
||||
serde::de::Unexpected::Str(path.to_string_lossy().as_ref()),
|
||||
&"an absolute path",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let home = BaseDirs::new()
|
||||
.expect("no valid home directory found")
|
||||
.home_dir()
|
||||
.to_owned();
|
||||
Self {
|
||||
link_path: home.join("current-course"),
|
||||
files_root: home
|
||||
.join("uni")
|
||||
.canonicalize()
|
||||
.expect("\"~/uni\" could not be canonicalized"),
|
||||
note_extensions: vec![String::from("tex"), String::from("md"), String::from("org")],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Default)]
|
||||
pub struct ListOptions {
|
||||
#[structopt(short, long)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::config::{Config, ListOptions};
|
||||
use crate::config::{ListOptions, CONFIG};
|
||||
use crate::util;
|
||||
use anyhow::Result;
|
||||
use glob::glob;
|
||||
|
|
@ -53,16 +53,17 @@ impl Course {
|
|||
&self.meetings
|
||||
}
|
||||
|
||||
pub fn set(&self, conf: &Config) -> Result<()> {
|
||||
remove_file(&conf.link_path)?;
|
||||
symlink(&self.path, &conf.link_path)?;
|
||||
pub fn set(&self) -> Result<()> {
|
||||
// TODO: make sure this is actually a link, and we are not removing some other file of the user
|
||||
remove_file(&CONFIG.link_path)?;
|
||||
symlink(&self.path, &CONFIG.link_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list(conf: &Config, options: &ListOptions) -> Result<CourseList> {
|
||||
pub fn list(options: &ListOptions) -> Result<CourseList> {
|
||||
let mut courses: Vec<Course> = vec![];
|
||||
let pattern = [
|
||||
conf.files_root.canonicalize().unwrap().to_str().unwrap(),
|
||||
CONFIG.files_root.canonicalize().unwrap().to_str().unwrap(),
|
||||
"/*/course.toml",
|
||||
]
|
||||
.concat();
|
||||
|
|
@ -82,9 +83,9 @@ impl Course {
|
|||
Ok(CourseList::new(courses))
|
||||
}
|
||||
|
||||
pub fn check(conf: &Config, list_options: &ListOptions) -> Result<()> {
|
||||
pub fn check(list_options: &ListOptions) -> Result<()> {
|
||||
let mut problematic_course_count = 0;
|
||||
let toml_list = Self::list(conf, list_options)?;
|
||||
let toml_list = Self::list(list_options)?;
|
||||
println!("Checking course.toml's");
|
||||
for c in toml_list.iter() {
|
||||
let mut problems = Vec::new();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
pub mod config;
|
||||
pub mod courses;
|
||||
pub mod rofi;
|
||||
|
|
|
|||
45
src/main.rs
45
src/main.rs
|
|
@ -4,8 +4,8 @@ use std::io::prelude::*;
|
|||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use ibis::config::{Config, ListOptions};
|
||||
use ibis::courses::Course;
|
||||
use ibis::config::{ListOptions, CONFIG};
|
||||
use ibis::courses::{Course, Meeting};
|
||||
use ibis::{rofi, util};
|
||||
|
||||
#[derive(StructOpt)]
|
||||
|
|
@ -18,6 +18,7 @@ struct Cli {
|
|||
|
||||
#[derive(StructOpt)]
|
||||
enum Command {
|
||||
ShowConfig,
|
||||
ListCourses(ListOptions),
|
||||
ListLectures(ListOptions),
|
||||
SelectCourse(ListOptions),
|
||||
|
|
@ -41,42 +42,36 @@ enum Object {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let conf = Config {
|
||||
link_path: PathBuf::from("/home/jana/current-course"),
|
||||
files_root: PathBuf::from("/home/jana/uni"),
|
||||
note_extensions: vec![String::from("tex"), String::from("md"), String::from("org")],
|
||||
};
|
||||
let args = Cli::from_args();
|
||||
match args.cmd {
|
||||
// Some(subcommand) => match subcommand {
|
||||
Command::ListCourses(options) => {
|
||||
for c in Course::list(&conf, &options)?.iter() {
|
||||
for c in Course::list(&options)?.iter() {
|
||||
println!("{}", c);
|
||||
}
|
||||
}
|
||||
Command::ListLectures(options) => {
|
||||
for l in list_lectures(&conf, options)? {
|
||||
for l in list_lectures(options)? {
|
||||
println!("{:?}", l);
|
||||
}
|
||||
}
|
||||
Command::AddCourse(course) => add(course, &conf, args.debug)?,
|
||||
Command::AddCourse(course) => add(course, args.debug)?,
|
||||
Command::AddLecture => (),
|
||||
Command::SelectCourse(options) => rofi::select_course(&conf, &options)?,
|
||||
Command::SelectCourse(options) => rofi::select_course(&options)?,
|
||||
Command::SelectLecture => (),
|
||||
Command::Init => (),
|
||||
Command::Check(list_options) => Course::check(&conf, &list_options)?,
|
||||
Command::Meetings(options) => rofi::select_meeting(&conf, &options)?,
|
||||
//},
|
||||
// None => println!("No command given"),
|
||||
Command::Check(list_options) => Course::check(&list_options)?,
|
||||
Command::Meetings(options) => rofi::select_meeting(&options)?,
|
||||
Command::ShowConfig => println!("{:?}", *CONFIG),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add(course: Course, conf: &Config, debug: bool) -> Result<()> {
|
||||
fn add(course: Course, debug: bool) -> Result<()> {
|
||||
let toml = toml::to_string(&course)?;
|
||||
println!("{}", toml);
|
||||
let target_dir = [
|
||||
conf.files_root
|
||||
CONFIG
|
||||
.files_root
|
||||
.canonicalize()?
|
||||
.to_str()
|
||||
.expect("I hate paths"), // TODO
|
||||
|
|
@ -86,9 +81,9 @@ fn add(course: Course, conf: &Config, debug: bool) -> Result<()> {
|
|||
.concat();
|
||||
let toml_path = PathBuf::from([&target_dir, "/course.toml"].concat());
|
||||
if !(cfg!(debug_assertions) || debug) {
|
||||
if !conf.files_root.is_dir() {
|
||||
std::fs::create_dir_all(conf.files_root.canonicalize().with_context(|| {
|
||||
format!("The root path {:?} seems malformed", conf.files_root)
|
||||
if !CONFIG.files_root.is_dir() {
|
||||
std::fs::create_dir_all(CONFIG.files_root.canonicalize().with_context(|| {
|
||||
format!("The root path {:?} seems malformed", CONFIG.files_root)
|
||||
})?)?;
|
||||
}
|
||||
if !PathBuf::from(&target_dir).is_dir() {
|
||||
|
|
@ -104,12 +99,12 @@ fn add(course: Course, conf: &Config, debug: bool) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn list_lectures(conf: &Config, options: ListOptions) -> Result<Vec<PathBuf>> {
|
||||
fn list_lectures(options: ListOptions) -> Result<Vec<PathBuf>> {
|
||||
let lectures = util::glob_paths(
|
||||
if options.all {
|
||||
&conf.files_root
|
||||
&CONFIG.files_root
|
||||
} else {
|
||||
&conf.link_path
|
||||
&CONFIG.link_path
|
||||
},
|
||||
"/**/lecture*",
|
||||
);
|
||||
|
|
@ -119,7 +114,7 @@ fn list_lectures(conf: &Config, options: ListOptions) -> Result<Vec<PathBuf>> {
|
|||
.filter(|path| {
|
||||
if let Some(ext) = path.extension() {
|
||||
let ext = ext.to_owned().to_string_lossy().into();
|
||||
conf.note_extensions.contains(&ext)
|
||||
CONFIG.note_extensions.contains(&ext)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
11
src/rofi.rs
11
src/rofi.rs
|
|
@ -1,4 +1,3 @@
|
|||
use crate::config::Config;
|
||||
use crate::{config::ListOptions, courses::*};
|
||||
use anyhow::Result;
|
||||
use rofi::Rofi;
|
||||
|
|
@ -6,18 +5,18 @@ use std::io::Write;
|
|||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
|
||||
pub fn select_course(conf: &Config, options: &ListOptions) -> Result<()> {
|
||||
let courses = Course::list(conf, options)?;
|
||||
pub fn select_course(options: &ListOptions) -> Result<()> {
|
||||
let courses = Course::list(options)?;
|
||||
let course_names: Vec<_> = courses.iter().map(|c| &c.name).collect();
|
||||
let element = Rofi::new(&course_names).run_index()?;
|
||||
courses
|
||||
.get(element)
|
||||
.expect("Could not find selected Course")
|
||||
.set(conf)
|
||||
.set()
|
||||
}
|
||||
|
||||
pub fn select_meeting(conf: &Config, options: &ListOptions) -> Result<()> {
|
||||
let meetings: Vec<_> = Course::list(conf, options)?
|
||||
pub fn select_meeting(options: &ListOptions) -> Result<()> {
|
||||
let meetings: Vec<_> = Course::list(options)?
|
||||
.iter()
|
||||
.cloned()
|
||||
.flat_map(|c: Course| {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue