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
d30563648c
commit
5449d10a62
7 changed files with 110 additions and 42 deletions
|
|
@ -16,6 +16,8 @@ glob = "0.3"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
chrono = {version = "0.4.19", features = ["serde"]}
|
chrono = {version = "0.4.19", features = ["serde"]}
|
||||||
rofi = {version = "0.2", optional = true}
|
rofi = {version = "0.2", optional = true}
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
directories = "4.0.1"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "ibis"
|
name = "ibis"
|
||||||
|
|
|
||||||
1
notes.md
1
notes.md
|
|
@ -2,7 +2,6 @@
|
||||||
- `check` complains about some of the defaults. Make sure default settings conform with opinions of `check`
|
- `check` complains about some of the defaults. Make sure default settings conform with opinions of `check`
|
||||||
- refactor to move functionality like rofi to shell scripts
|
- refactor to move functionality like rofi to shell scripts
|
||||||
Instead provide more scriptable functionality
|
Instead provide more scriptable functionality
|
||||||
- actual config instead of hardcoding values
|
|
||||||
|
|
||||||
# Things to think about
|
# Things to think about
|
||||||
- Do I want to copy templates when creating a course?
|
- Do I want to copy templates when creating a course?
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,83 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use directories::{BaseDirs, ProjectDirs};
|
||||||
|
use serde::{de::Error, Deserialize, Deserializer, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
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 {
|
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,
|
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,
|
pub files_root: PathBuf,
|
||||||
|
/// File extensions that should be treated as lecture notes
|
||||||
pub note_extensions: Vec<String>,
|
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)]
|
#[derive(StructOpt, Default)]
|
||||||
pub struct ListOptions {
|
pub struct ListOptions {
|
||||||
#[structopt(short, long)]
|
#[structopt(short, long)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config::{Config, ListOptions};
|
use crate::config::{ListOptions, CONFIG};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
|
|
@ -53,16 +53,17 @@ impl Course {
|
||||||
&self.meetings
|
&self.meetings
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, conf: &Config) -> Result<()> {
|
pub fn set(&self) -> Result<()> {
|
||||||
remove_file(&conf.link_path)?;
|
// TODO: make sure this is actually a link, and we are not removing some other file of the user
|
||||||
symlink(&self.path, &conf.link_path)?;
|
remove_file(&CONFIG.link_path)?;
|
||||||
|
symlink(&self.path, &CONFIG.link_path)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list(conf: &Config, options: &ListOptions) -> Result<CourseList> {
|
pub fn list(options: &ListOptions) -> Result<CourseList> {
|
||||||
let mut courses: Vec<Course> = vec![];
|
let mut courses: Vec<Course> = vec![];
|
||||||
let pattern = [
|
let pattern = [
|
||||||
conf.files_root.canonicalize().unwrap().to_str().unwrap(),
|
CONFIG.files_root.canonicalize().unwrap().to_str().unwrap(),
|
||||||
"/*/course.toml",
|
"/*/course.toml",
|
||||||
]
|
]
|
||||||
.concat();
|
.concat();
|
||||||
|
|
@ -82,9 +83,9 @@ impl Course {
|
||||||
Ok(CourseList::new(courses))
|
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 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");
|
println!("Checking course.toml's");
|
||||||
for c in toml_list.iter() {
|
for c in toml_list.iter() {
|
||||||
let mut problems = Vec::new();
|
let mut problems = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod courses;
|
pub mod courses;
|
||||||
pub mod rofi;
|
pub mod rofi;
|
||||||
|
|
|
||||||
45
src/main.rs
45
src/main.rs
|
|
@ -4,8 +4,8 @@ use std::io::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use ibis::config::{Config, ListOptions};
|
use ibis::config::{ListOptions, CONFIG};
|
||||||
use ibis::courses::Course;
|
use ibis::courses::{Course, Meeting};
|
||||||
use ibis::{rofi, util};
|
use ibis::{rofi, util};
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
|
|
@ -18,6 +18,7 @@ struct Cli {
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
enum Command {
|
enum Command {
|
||||||
|
ShowConfig,
|
||||||
ListCourses(ListOptions),
|
ListCourses(ListOptions),
|
||||||
ListLectures(ListOptions),
|
ListLectures(ListOptions),
|
||||||
SelectCourse(ListOptions),
|
SelectCourse(ListOptions),
|
||||||
|
|
@ -41,42 +42,36 @@ enum Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
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();
|
let args = Cli::from_args();
|
||||||
match args.cmd {
|
match args.cmd {
|
||||||
// Some(subcommand) => match subcommand {
|
|
||||||
Command::ListCourses(options) => {
|
Command::ListCourses(options) => {
|
||||||
for c in Course::list(&conf, &options)?.iter() {
|
for c in Course::list(&options)?.iter() {
|
||||||
println!("{}", c);
|
println!("{}", c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ListLectures(options) => {
|
Command::ListLectures(options) => {
|
||||||
for l in list_lectures(&conf, options)? {
|
for l in list_lectures(options)? {
|
||||||
println!("{:?}", l);
|
println!("{:?}", l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::AddCourse(course) => add(course, &conf, args.debug)?,
|
Command::AddCourse(course) => add(course, args.debug)?,
|
||||||
Command::AddLecture => (),
|
Command::AddLecture => (),
|
||||||
Command::SelectCourse(options) => rofi::select_course(&conf, &options)?,
|
Command::SelectCourse(options) => rofi::select_course(&options)?,
|
||||||
Command::SelectLecture => (),
|
Command::SelectLecture => (),
|
||||||
Command::Init => (),
|
Command::Init => (),
|
||||||
Command::Check(list_options) => Course::check(&conf, &list_options)?,
|
Command::Check(list_options) => Course::check(&list_options)?,
|
||||||
Command::Meetings(options) => rofi::select_meeting(&conf, &options)?,
|
Command::Meetings(options) => rofi::select_meeting(&options)?,
|
||||||
//},
|
Command::ShowConfig => println!("{:?}", *CONFIG),
|
||||||
// None => println!("No command given"),
|
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(course: Course, conf: &Config, debug: bool) -> Result<()> {
|
fn add(course: Course, debug: bool) -> Result<()> {
|
||||||
let toml = toml::to_string(&course)?;
|
let toml = toml::to_string(&course)?;
|
||||||
println!("{}", toml);
|
println!("{}", toml);
|
||||||
let target_dir = [
|
let target_dir = [
|
||||||
conf.files_root
|
CONFIG
|
||||||
|
.files_root
|
||||||
.canonicalize()?
|
.canonicalize()?
|
||||||
.to_str()
|
.to_str()
|
||||||
.expect("I hate paths"), // TODO
|
.expect("I hate paths"), // TODO
|
||||||
|
|
@ -86,9 +81,9 @@ fn add(course: Course, conf: &Config, debug: bool) -> Result<()> {
|
||||||
.concat();
|
.concat();
|
||||||
let toml_path = PathBuf::from([&target_dir, "/course.toml"].concat());
|
let toml_path = PathBuf::from([&target_dir, "/course.toml"].concat());
|
||||||
if !(cfg!(debug_assertions) || debug) {
|
if !(cfg!(debug_assertions) || debug) {
|
||||||
if !conf.files_root.is_dir() {
|
if !CONFIG.files_root.is_dir() {
|
||||||
std::fs::create_dir_all(conf.files_root.canonicalize().with_context(|| {
|
std::fs::create_dir_all(CONFIG.files_root.canonicalize().with_context(|| {
|
||||||
format!("The root path {:?} seems malformed", conf.files_root)
|
format!("The root path {:?} seems malformed", CONFIG.files_root)
|
||||||
})?)?;
|
})?)?;
|
||||||
}
|
}
|
||||||
if !PathBuf::from(&target_dir).is_dir() {
|
if !PathBuf::from(&target_dir).is_dir() {
|
||||||
|
|
@ -104,12 +99,12 @@ fn add(course: Course, conf: &Config, debug: bool) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_lectures(conf: &Config, options: ListOptions) -> Result<Vec<PathBuf>> {
|
fn list_lectures(options: ListOptions) -> Result<Vec<PathBuf>> {
|
||||||
let lectures = util::glob_paths(
|
let lectures = util::glob_paths(
|
||||||
if options.all {
|
if options.all {
|
||||||
&conf.files_root
|
&CONFIG.files_root
|
||||||
} else {
|
} else {
|
||||||
&conf.link_path
|
&CONFIG.link_path
|
||||||
},
|
},
|
||||||
"/**/lecture*",
|
"/**/lecture*",
|
||||||
);
|
);
|
||||||
|
|
@ -119,7 +114,7 @@ fn list_lectures(conf: &Config, options: ListOptions) -> Result<Vec<PathBuf>> {
|
||||||
.filter(|path| {
|
.filter(|path| {
|
||||||
if let Some(ext) = path.extension() {
|
if let Some(ext) = path.extension() {
|
||||||
let ext = ext.to_owned().to_string_lossy().into();
|
let ext = ext.to_owned().to_string_lossy().into();
|
||||||
conf.note_extensions.contains(&ext)
|
CONFIG.note_extensions.contains(&ext)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
src/rofi.rs
11
src/rofi.rs
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::config::Config;
|
|
||||||
use crate::{config::ListOptions, courses::*};
|
use crate::{config::ListOptions, courses::*};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rofi::Rofi;
|
use rofi::Rofi;
|
||||||
|
|
@ -6,18 +5,18 @@ use std::io::Write;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
pub fn select_course(conf: &Config, options: &ListOptions) -> Result<()> {
|
pub fn select_course(options: &ListOptions) -> Result<()> {
|
||||||
let courses = Course::list(conf, options)?;
|
let courses = Course::list(options)?;
|
||||||
let course_names: Vec<_> = courses.iter().map(|c| &c.name).collect();
|
let course_names: Vec<_> = courses.iter().map(|c| &c.name).collect();
|
||||||
let element = Rofi::new(&course_names).run_index()?;
|
let element = Rofi::new(&course_names).run_index()?;
|
||||||
courses
|
courses
|
||||||
.get(element)
|
.get(element)
|
||||||
.expect("Could not find selected Course")
|
.expect("Could not find selected Course")
|
||||||
.set(conf)
|
.set()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_meeting(conf: &Config, options: &ListOptions) -> Result<()> {
|
pub fn select_meeting(options: &ListOptions) -> Result<()> {
|
||||||
let meetings: Vec<_> = Course::list(conf, options)?
|
let meetings: Vec<_> = Course::list(options)?
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.flat_map(|c: Course| {
|
.flat_map(|c: Course| {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue