Add lots of functionality
This is in a usable state now and I have been using it for the last months.
This commit is contained in:
parent
526d6a0c7d
commit
06d283b17c
8 changed files with 429 additions and 60 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
.vscode
|
||||||
21
Cargo.toml
21
Cargo.toml
|
|
@ -4,10 +4,23 @@ version = "0.1.0"
|
||||||
authors = ["Jana Lemke <dev@jana-lemke.de>"]
|
authors = ["Jana Lemke <dev@jana-lemke.de>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[features]
|
||||||
|
default = ["rofi-menu"]
|
||||||
|
rofi-menu = ["rofi"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
structopt = "0.3.16"
|
structopt = "0.3"
|
||||||
serde = { version = "1.0", features = ["derive"]}
|
serde = { version = "1.0", features = ["derive"]}
|
||||||
toml = "0.5.7"
|
toml = "0.5"
|
||||||
glob = "0.3.0"
|
glob = "0.3"
|
||||||
|
anyhow = "1"
|
||||||
|
chrono = {version = "0.4.19", features = ["serde"]}
|
||||||
|
rofi = {version = "0.2", optional = true}
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "ibis"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ibis"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
|
||||||
22
src/config.rs
Normal file
22
src/config.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub link_path: PathBuf,
|
||||||
|
pub files_root: PathBuf,
|
||||||
|
pub note_extensions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub struct ListOptions {
|
||||||
|
#[structopt(short, long)]
|
||||||
|
pub all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ListOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
ListOptions { all: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
188
src/courses.rs
Normal file
188
src/courses.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
use crate::config::{Config, ListOptions};
|
||||||
|
use crate::util;
|
||||||
|
use anyhow::Result;
|
||||||
|
use glob::glob;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::os::unix::fs::symlink;
|
||||||
|
use std::{
|
||||||
|
fs::{remove_file, File},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)]
|
||||||
|
pub struct Course {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
#[structopt(skip)]
|
||||||
|
pub path: PathBuf,
|
||||||
|
short: String,
|
||||||
|
#[serde(default)]
|
||||||
|
#[structopt(long)]
|
||||||
|
url: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[structopt(long)]
|
||||||
|
moodle: Option<String>,
|
||||||
|
#[serde(deserialize_with = "string_or_vec", alias = "semester")]
|
||||||
|
semesters: Vec<String>,
|
||||||
|
#[structopt(skip)]
|
||||||
|
meetings: Option<Vec<Meeting>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)]
|
||||||
|
pub struct Meeting {
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Course {
|
||||||
|
/// Get a reference to the course's semesters.
|
||||||
|
pub fn semesters(&self) -> &Vec<String> {
|
||||||
|
&self.semesters
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if course is part of current semester
|
||||||
|
pub fn is_current(&self) -> bool {
|
||||||
|
self.semesters()
|
||||||
|
.iter()
|
||||||
|
.any(|x| x == &util::get_current_semester())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn meetings(&self) -> &Option<Vec<Meeting>> {
|
||||||
|
&self.meetings
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, conf: &Config) -> Result<()> {
|
||||||
|
remove_file(&conf.link_path)?;
|
||||||
|
symlink(&self.path, &conf.link_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(conf: &Config, options: &ListOptions) -> Result<CourseList> {
|
||||||
|
let mut courses: Vec<Course> = vec![];
|
||||||
|
let pattern = [
|
||||||
|
conf.files_root.canonicalize().unwrap().to_str().unwrap(),
|
||||||
|
"/*/course.toml",
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
let mut handle;
|
||||||
|
for entry in glob(pattern.as_str()).unwrap() {
|
||||||
|
let path = entry.unwrap();
|
||||||
|
let mut coursestring = String::new();
|
||||||
|
handle = File::open(&path).unwrap();
|
||||||
|
handle.read_to_string(&mut coursestring).unwrap();
|
||||||
|
let mut course: Course = toml::from_str(&coursestring).expect(&format!("{:?}", &path));
|
||||||
|
course.path = path.parent().unwrap().to_path_buf();
|
||||||
|
if options.all || course.is_current() {
|
||||||
|
courses.push(course);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CourseList::new(courses))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(conf: &Config, list_options: &ListOptions) -> Result<()> {
|
||||||
|
let mut problematic_course_count = 0;
|
||||||
|
let toml_list = Self::list(conf, list_options)?;
|
||||||
|
println!("Checking course.toml's");
|
||||||
|
for c in toml_list.iter() {
|
||||||
|
let mut problems = Vec::new();
|
||||||
|
if c.name.is_empty() {
|
||||||
|
problems.push(" Name is empty")
|
||||||
|
}
|
||||||
|
if c.short.is_empty() {
|
||||||
|
problems.push(" Short is empty")
|
||||||
|
}
|
||||||
|
if c.url.is_none() {
|
||||||
|
problems.push(" No URL (set empty string to mark nonexistant website)")
|
||||||
|
}
|
||||||
|
if c.moodle.is_none() {
|
||||||
|
problems.push(" No Moodle-URL (set empty string to mark nonexistant website)")
|
||||||
|
}
|
||||||
|
if c.semesters.is_empty() {
|
||||||
|
problems.push(" Course is not in any semesters")
|
||||||
|
}
|
||||||
|
if !problems.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"Found {} problems with {}/course.toml",
|
||||||
|
problems.len(),
|
||||||
|
c.path.to_str().unwrap()
|
||||||
|
);
|
||||||
|
for p in problems {
|
||||||
|
eprintln!("{}", p)
|
||||||
|
}
|
||||||
|
problematic_course_count += 1;
|
||||||
|
} else {
|
||||||
|
println!("No problems found with {:?}", c.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Found {} courses with problems", problematic_course_count);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum StringOrVec {
|
||||||
|
String(String),
|
||||||
|
Vec(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
match StringOrVec::deserialize(deserializer)? {
|
||||||
|
StringOrVec::String(s) => Ok(vec![s]),
|
||||||
|
StringOrVec::Vec(vec) => Ok(vec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct CourseList(Vec<Course>);
|
||||||
|
|
||||||
|
impl std::fmt::Display for CourseList {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for c in &self.0 {
|
||||||
|
writeln!(f, "{}", c)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CourseList {
|
||||||
|
pub fn new(inner: Vec<Course>) -> Self {
|
||||||
|
CourseList(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> std::slice::Iter<Course> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, element: usize) -> Option<&Course> {
|
||||||
|
self.0.get(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Course {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[derive(Debug)]
|
||||||
|
// pub enum ParseSemesterError{
|
||||||
|
// ParseIntError(ParseIntError),
|
||||||
|
// NoFormatFound,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl std::fmt::Display for ParseSemesterError{
|
||||||
|
// fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// todo!()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl std::convert::From<ParseIntError> for ParseSemesterError{
|
||||||
|
// fn from(e: ParseIntError) -> Self {
|
||||||
|
// Self::ParseIntError(e)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod config;
|
||||||
|
pub mod courses;
|
||||||
|
pub mod rofi;
|
||||||
|
pub mod util;
|
||||||
153
src/main.rs
153
src/main.rs
|
|
@ -1,90 +1,133 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use anyhow::{Context, Result};
|
||||||
use structopt::StructOpt;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use glob::glob;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
use ibis::config::{Config, ListOptions};
|
||||||
struct Config {
|
use ibis::courses::Course;
|
||||||
link_path: PathBuf,
|
use ibis::{rofi, util};
|
||||||
files_root: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Course{
|
|
||||||
name: String,
|
|
||||||
short: String,
|
|
||||||
semester: String,
|
|
||||||
url: Option<String>,
|
|
||||||
moodle: Option<String>
|
|
||||||
}
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
#[structopt(short, long)]
|
||||||
|
debug: bool,
|
||||||
#[structopt(subcommand)]
|
#[structopt(subcommand)]
|
||||||
cmd: Option<Command>,
|
cmd: Option<Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
enum Command {
|
enum Command {
|
||||||
List(ObjectStruct),
|
ListCourses(ListOptions),
|
||||||
Select(ObjectStruct),
|
ListLectures(ListOptions),
|
||||||
Add(ObjectStruct),
|
SelectCourse(ListOptions),
|
||||||
|
SelectLecture,
|
||||||
|
AddCourse(Course),
|
||||||
|
AddLecture,
|
||||||
Init,
|
Init,
|
||||||
|
Check(ListOptions),
|
||||||
|
Meetings(ListOptions),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
// #[derive(StructOpt)]
|
||||||
struct ObjectStruct {
|
// struct ObjectStruct {
|
||||||
#[structopt(subcommand)]
|
// #[structopt(subcommand)]
|
||||||
object: Option<Object>,
|
// object: Option<Object>,
|
||||||
}
|
// }
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
enum Object {
|
enum Object {
|
||||||
Courses,
|
Courses,
|
||||||
Lectures,
|
Lectures,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
let conf = Config{link_path: PathBuf::from("/home/jana/current-course"), files_root: PathBuf::from("/home/jana/uni")};
|
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 {
|
Some(subcommand) => match subcommand {
|
||||||
Command::List(object) => list(object.object, conf),
|
Command::ListCourses(options) => {
|
||||||
Command::Add(object) => (),
|
for c in Course::list(&conf, &options)?.iter() {
|
||||||
Command::Select(object) => (),
|
println!("{}", c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::ListLectures(options) => {
|
||||||
|
for l in list_lectures(&conf, options)? {
|
||||||
|
println!("{:?}", l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::AddCourse(course) => add(course, &conf, args.debug)?,
|
||||||
|
Command::AddLecture => (),
|
||||||
|
Command::SelectCourse(options) => rofi::select_course(&conf, &options)?,
|
||||||
|
Command::SelectLecture => (),
|
||||||
Command::Init => (),
|
Command::Init => (),
|
||||||
|
Command::Check(list_options) => Course::check(&conf, &list_options)?,
|
||||||
|
Command::Meetings(options) => rofi::select_meeting(&conf, &options)?,
|
||||||
},
|
},
|
||||||
None => println!("No command given"),
|
None => println!("No command given"),
|
||||||
}
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(object: Option<Object>, conf: Config) {
|
fn add(course: Course, conf: &Config, debug: bool) -> Result<()> {
|
||||||
if object.is_some() {
|
let toml = toml::to_string(&course)?;
|
||||||
let object = object.unwrap();
|
println!("{}", toml);
|
||||||
match object {
|
let target_dir = [
|
||||||
Object::Courses => {list_courses(conf);},
|
conf.files_root
|
||||||
Object::Lectures => list_lectures(),
|
.canonicalize()?
|
||||||
|
.to_str()
|
||||||
|
.expect("I hate paths"), // TODO
|
||||||
|
"/",
|
||||||
|
util::to_snail_case(&course.name).as_ref(),
|
||||||
|
]
|
||||||
|
.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 !PathBuf::from(&target_dir).is_dir() {
|
||||||
|
std::fs::create_dir_all(&target_dir)?;
|
||||||
|
} else {
|
||||||
|
panic!("Folder {} already exists!", &target_dir);
|
||||||
}
|
}
|
||||||
|
let mut toml_handle = File::create(&toml_path).unwrap();
|
||||||
|
write!(toml_handle, "{}", toml).unwrap();
|
||||||
|
println!("Created files!")
|
||||||
|
}
|
||||||
|
println!("Path was {:?}", &toml_path);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_courses(conf: Config) -> Vec<Course>{
|
fn list_lectures(conf: &Config, options: ListOptions) -> Result<Vec<PathBuf>> {
|
||||||
let mut courses: Vec<Course> = vec![];
|
let lectures = util::glob_paths(
|
||||||
let pattern = [conf.files_root.canonicalize().unwrap().to_str().unwrap(), "/**/course.toml"].concat();
|
if options.all {
|
||||||
let mut handle;
|
&conf.files_root
|
||||||
for entry in glob(pattern.as_str()).unwrap(){
|
} else {
|
||||||
let path = entry.unwrap();
|
&conf.link_path
|
||||||
let mut coursestring = String::new();
|
},
|
||||||
handle = File::open(path).unwrap();
|
"/**/lecture*",
|
||||||
handle.read_to_string(&mut coursestring);
|
);
|
||||||
courses.push(toml::from_str(&coursestring).unwrap());
|
|
||||||
}
|
|
||||||
for c in &courses{
|
|
||||||
println!("Course {}({}) in {}, url {:?}, moodle {:?}", c.name, c.short, c.semester, c.url, c.moodle);
|
|
||||||
}
|
|
||||||
courses
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_lectures() {
|
let lectures = lectures?
|
||||||
println!("Listing lectures")
|
.into_iter()
|
||||||
|
.filter_map(|path| {
|
||||||
|
if let Some(ext) = path.extension() {
|
||||||
|
let ext = ext.to_owned().to_string_lossy().into();
|
||||||
|
if conf.note_extensions.contains(&ext) {
|
||||||
|
Some(path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(lectures)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
src/rofi.rs
Normal file
53
src/rofi.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::{config::ListOptions, courses::*};
|
||||||
|
use anyhow::Result;
|
||||||
|
use rofi::Rofi;
|
||||||
|
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)?;
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_meeting(conf: &Config, options: &ListOptions) -> Result<()> {
|
||||||
|
let meetings: Vec<_> = Course::list(conf, options)?
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|c: Course| {
|
||||||
|
if let Some(meetings) = c.meetings() {
|
||||||
|
meetings.clone()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
.iter()
|
||||||
|
.map(move |m| (c.clone(), m.clone()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
let meeting_names: Vec<_> = meetings
|
||||||
|
.iter()
|
||||||
|
.map(|(c, m)| format!("{:<30} {:>30}", &c.name, &m.name))
|
||||||
|
.collect();
|
||||||
|
let element = Rofi::new(&meeting_names).run_index()?;
|
||||||
|
let mut clip = Command::new("xclip")
|
||||||
|
.args(&["-sel", "clip"])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("Could not spawn xclip process");
|
||||||
|
let url = &meetings[element].1.url;
|
||||||
|
{
|
||||||
|
let pipe = clip.stdin.as_mut().expect("Could not write to xclip stdin");
|
||||||
|
pipe.write_all(url.as_bytes())
|
||||||
|
.expect("Writing to xclip stdin failed");
|
||||||
|
}
|
||||||
|
clip.wait()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
45
src/util.rs
Normal file
45
src/util.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use chrono::{DateTime, Datelike, Local};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
pub fn to_snail_case(from: impl Into<String>) -> String {
|
||||||
|
from.into().replace(" ", "_").to_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn to_title_case(from: &str) -> String {
|
||||||
|
// from.to_string().split(|c| c ==' ' || c == '_').map(|s| {if s.len() >= 1 {s[0 as usize] = s[0 as usize].to_uppercase()}}).collect()
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn get_current_semester() -> String {
|
||||||
|
let today = DateTime::<Local>::from(SystemTime::now())
|
||||||
|
.date()
|
||||||
|
.naive_local();
|
||||||
|
let (part, year) = if today.month() >= 4 && today.month() < 10 {
|
||||||
|
("SS", today.year())
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
"WS",
|
||||||
|
if today.month() >= 10 {
|
||||||
|
today.year()
|
||||||
|
} else {
|
||||||
|
today.year() - 1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
format!("{}{}", part, year - 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn glob_paths<P: AsRef<Path>, S: AsRef<str>>(path: P, pattern: S) -> Result<Vec<PathBuf>> {
|
||||||
|
let mut paths = vec![];
|
||||||
|
let pattern = [
|
||||||
|
(*(path.as_ref())).canonicalize()?.to_str().unwrap(),
|
||||||
|
pattern.as_ref(),
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
for entry in glob::glob(pattern.as_str())? {
|
||||||
|
let path = entry?;
|
||||||
|
paths.push(path);
|
||||||
|
}
|
||||||
|
Ok(paths)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue