todos
parent
62cdf23a32
commit
ce3cfc2c60
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
|
@ -1,12 +1,28 @@
|
|||
[package]
|
||||
name = "pop_pass"
|
||||
name = "pop_launch_utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.31", features = ["alloc", "serde"] }
|
||||
date_time_parser = "0.2.0"
|
||||
num = "0.4.1"
|
||||
num-derive = "0.4.1"
|
||||
num-traits = "0.2.17"
|
||||
radix_trie = "0.2.1"
|
||||
reqwest = { version = "0.11.23", features = ["blocking"] }
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
serde_json = "1.0.108"
|
||||
serde_with = "3.4.0"
|
||||
time = "0.3.31"
|
||||
uuid = { version = "1.6.1", features = ["v4"] }
|
||||
|
||||
[[bin]]
|
||||
name = "pass"
|
||||
path = "src/pass.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "todo"
|
||||
path = "src/todo.rs"
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
PLUGINS="todo pass"
|
||||
|
||||
cargo build
|
||||
|
||||
for plug in $PLUGINS; do
|
||||
DIR=~/.local/share/pop-launcher/plugins/$plug
|
||||
BIN=target/debug/$plug
|
||||
RON=./$plug.ron
|
||||
|
||||
if [ ! -d $DIR ] ; then
|
||||
echo Creating $DIR
|
||||
mkdir -p $DIR
|
||||
fi
|
||||
echo Installing $RON "->" $DIR/plugin.ron
|
||||
cp $RON $DIR/plugin.ron
|
||||
echo Installing $BIN "->" $DIR
|
||||
cp $BIN $DIR
|
||||
done
|
|
@ -1,7 +1,7 @@
|
|||
(
|
||||
name: "Pop Pass",
|
||||
name: "Pass Password Manager",
|
||||
description: "Syntax: pass [account]",
|
||||
bin: (path: "pop_pass"),
|
||||
bin: (path: "pass"),
|
||||
icon: Name("dialog-password"),
|
||||
query: (
|
||||
regex: "^pass\\s.*",
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error
|
||||
{
|
||||
StringErr(String),
|
||||
IOErr(io::Error),
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error
|
||||
{
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IOErr(err)
|
||||
}
|
||||
}
|
||||
impl From<String> for Error
|
||||
{
|
||||
fn from(err: String) -> Error {
|
||||
Error::StringErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
75
src/main.rs
75
src/main.rs
|
@ -1,75 +0,0 @@
|
|||
mod launcher;
|
||||
mod pass;
|
||||
mod menu;
|
||||
|
||||
use std::io;
|
||||
use menu::Menu;
|
||||
use launcher::{ Request, PluginResponse };
|
||||
use std::result::Result;
|
||||
|
||||
fn respond(response: &PluginResponse) -> io::Result<()>
|
||||
{
|
||||
let encoded = serde_json::to_string(&response)?;
|
||||
print!("{}\n", encoded);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_request(request_str: &String, menu: &Menu) -> Result<bool, String>
|
||||
{
|
||||
let request: Request = serde_json::from_str(&request_str).map_err(|err| err.to_string())?;
|
||||
|
||||
match request {
|
||||
Request::Activate(id) => {
|
||||
menu.activate(id)?;
|
||||
respond(&PluginResponse::Close).map_err(|err| err.to_string())?
|
||||
}
|
||||
Request::ActivateContext { id:_id, context:_context } =>
|
||||
(),
|
||||
Request::Complete(_id) =>
|
||||
(),
|
||||
Request::Context(id) =>
|
||||
respond(
|
||||
&PluginResponse::Context { id: id, options: Vec::new() }
|
||||
).map_err(|err| err.to_string())?,
|
||||
Request::Exit => {
|
||||
return Ok(false);
|
||||
}
|
||||
Request::Interrupt =>
|
||||
(),
|
||||
Request::Quit(_id) =>
|
||||
(),
|
||||
Request::Search(term) => {
|
||||
for entry in menu.search(term.trim_start_matches("pass "))
|
||||
{
|
||||
respond(&PluginResponse::Append(entry)).map_err(|err| err.to_string())?
|
||||
}
|
||||
respond(&PluginResponse::Finished).map_err(|err| err.to_string())?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let mut keep_going = true;
|
||||
|
||||
let mut buffer = String::new();
|
||||
let stdin = io::stdin();
|
||||
|
||||
let passwords = pass::ls().map_err(|err| err.to_string())?;
|
||||
let menu = Menu::build(passwords);
|
||||
|
||||
while keep_going {
|
||||
if stdin.read_line(&mut buffer).map_err(|err| err.to_string())? > 0
|
||||
{
|
||||
keep_going = process_request(&buffer, &menu).map_err(|err| err.to_string())?;
|
||||
buffer.clear();
|
||||
} else {
|
||||
keep_going = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
127
src/pass.rs
127
src/pass.rs
|
@ -1,77 +1,80 @@
|
|||
|
||||
use std::vec::Vec;
|
||||
use std::option::Option;
|
||||
use std::fs;
|
||||
mod launcher;
|
||||
mod pass_lib;
|
||||
mod pass_menu;
|
||||
|
||||
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
use pass_menu::Menu;
|
||||
use launcher::{ Request, PluginResponse };
|
||||
use std::result::Result;
|
||||
|
||||
pub type PassList = Vec<PassEntry>;
|
||||
|
||||
pub struct PassEntry
|
||||
fn respond(response: &PluginResponse) -> io::Result<()>
|
||||
{
|
||||
pub name: String,
|
||||
pub children: Option<PassList>
|
||||
let encoded = serde_json::to_string(&response)?;
|
||||
print!("{}\n", encoded);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_dir(dir: &Path) -> io::Result<PassList>
|
||||
fn process_request(request_str: &String, menu: &Menu) -> Result<bool, String>
|
||||
{
|
||||
let mut list:PassList = Vec::new();
|
||||
let request: Request = serde_json::from_str(&request_str).map_err(|err| err.to_string())?;
|
||||
|
||||
for entry in fs::read_dir(dir)?
|
||||
{
|
||||
let entry = entry?.path();
|
||||
if entry.is_dir()
|
||||
{
|
||||
match entry.file_stem()
|
||||
.and_then( |x| x.to_str() )
|
||||
{
|
||||
None => (),
|
||||
Some(filename) =>
|
||||
list.push(
|
||||
PassEntry {
|
||||
name: String::from(filename),
|
||||
children: Some(list_dir(&entry.as_path())?)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
} else if entry.extension()
|
||||
.and_then( |x| x.to_str() )
|
||||
.unwrap_or(&"nogo") == "gpg"
|
||||
{
|
||||
match entry.file_stem()
|
||||
.and_then( |x| x.to_str() )
|
||||
{
|
||||
None => (),
|
||||
Some(filename) =>
|
||||
list.push(
|
||||
PassEntry {
|
||||
name: String::from(filename),
|
||||
children: None
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
match request {
|
||||
Request::Activate(id) => {
|
||||
respond(&PluginResponse::Close).map_err(|err| err.to_string())?;
|
||||
menu.activate(id)?;
|
||||
}
|
||||
Request::ActivateContext { id, context } =>
|
||||
menu.activate_context(id, context)?,
|
||||
Request::Complete(_id) =>
|
||||
(),
|
||||
Request::Context(id) =>
|
||||
respond(
|
||||
&PluginResponse::Context {
|
||||
id: id,
|
||||
options: menu.context(id)
|
||||
}
|
||||
).map_err(|err| err.to_string())?,
|
||||
Request::Exit => {
|
||||
return Ok(false);
|
||||
}
|
||||
Request::Interrupt =>
|
||||
(),
|
||||
Request::Quit(_id) =>
|
||||
(),
|
||||
Request::Search(term) => {
|
||||
for entry in menu.search(term.trim_start_matches("pass "))
|
||||
{
|
||||
respond(&PluginResponse::Append(entry)).map_err(|err| err.to_string())?
|
||||
}
|
||||
respond(&PluginResponse::Finished).map_err(|err| err.to_string())?
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Ok(list)
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn ls() -> io::Result<PassList>
|
||||
{
|
||||
// home_dir has a deprecation warning because it is broken on
|
||||
// windows... but PopOS is linux only.
|
||||
#[allow(deprecated)]
|
||||
let mut dir =
|
||||
match env::home_dir() {
|
||||
Some(dir) => dir,
|
||||
None => return Err(io::Error::new(io::ErrorKind::NotFound, "Can't find Password Directory (no home directory)"))
|
||||
};
|
||||
|
||||
dir.push(".password-store");
|
||||
fn main() -> Result<(), String> {
|
||||
let mut keep_going = true;
|
||||
|
||||
Ok(list_dir(&dir.as_path())?)
|
||||
}
|
||||
let mut buffer = String::new();
|
||||
let stdin = io::stdin();
|
||||
|
||||
let passwords = pass_lib::ls().map_err(|err| err.to_string())?;
|
||||
let menu = Menu::build(passwords);
|
||||
|
||||
while keep_going {
|
||||
if stdin.read_line(&mut buffer).map_err(|err| err.to_string())? > 0
|
||||
{
|
||||
keep_going = process_request(&buffer, &menu).map_err(|err| err.to_string())?;
|
||||
buffer.clear();
|
||||
} else {
|
||||
keep_going = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
use std::vec::Vec;
|
||||
use std::option::Option;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
|
||||
pub type PassList = Vec<PassEntry>;
|
||||
|
||||
pub struct PassEntry
|
||||
{
|
||||
pub name: String,
|
||||
pub children: Option<PassList>
|
||||
}
|
||||
|
||||
fn list_dir(dir: &Path) -> io::Result<PassList>
|
||||
{
|
||||
let mut list:PassList = Vec::new();
|
||||
|
||||
for entry in fs::read_dir(dir)?
|
||||
{
|
||||
let entry = entry?.path();
|
||||
if entry.is_dir()
|
||||
{
|
||||
match entry.file_name()
|
||||
.and_then( |x| x.to_str() )
|
||||
{
|
||||
None => (),
|
||||
Some(filename) =>
|
||||
list.push(
|
||||
PassEntry {
|
||||
name: String::from(filename),
|
||||
children: Some(list_dir(&entry.as_path())?)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
} else if entry.extension()
|
||||
.and_then( |x| x.to_str() )
|
||||
.unwrap_or(&"nogo") == "gpg"
|
||||
{
|
||||
match entry.file_stem()
|
||||
.and_then( |x| x.to_str() )
|
||||
{
|
||||
None => (),
|
||||
Some(filename) =>
|
||||
list.push(
|
||||
PassEntry {
|
||||
name: String::from(filename),
|
||||
children: None
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Ok(list)
|
||||
}
|
||||
|
||||
pub fn ls() -> io::Result<PassList>
|
||||
{
|
||||
// home_dir has a deprecation warning because it is broken on
|
||||
// windows... but PopOS is linux only.
|
||||
#[allow(deprecated)]
|
||||
let mut dir =
|
||||
match env::home_dir() {
|
||||
Some(dir) => dir,
|
||||
None => return Err(io::Error::new(io::ErrorKind::NotFound, "Can't find Password Directory (no home directory)"))
|
||||
};
|
||||
|
||||
dir.push(".password-store");
|
||||
|
||||
Ok(list_dir(&dir.as_path())?)
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
use std::vec::Vec;
|
||||
use crate::pass::PassList;
|
||||
use crate::pass_lib::PassList;
|
||||
use crate::launcher::{self, PluginSearchResult};
|
||||
use std::process::Command;
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
pub struct MenuItem
|
||||
{
|
||||
|
@ -14,6 +15,13 @@ pub struct Menu
|
|||
entries: Vec<MenuItem>
|
||||
}
|
||||
|
||||
#[derive(FromPrimitive)]
|
||||
enum ContextAction
|
||||
{
|
||||
Copy = 1,
|
||||
Edit,
|
||||
}
|
||||
|
||||
impl Menu
|
||||
{
|
||||
fn add_all(&mut self, entries: PassList, prefix: &str, id: &mut u16)
|
||||
|
@ -92,6 +100,7 @@ impl Menu
|
|||
{
|
||||
let idx = usize::try_from(id)
|
||||
.map_err(|_err| "Invalid index")?;
|
||||
if idx >= self.entries.len() { return Err(String::from("Invalid index")) }
|
||||
let entry = &self.entries[idx];
|
||||
|
||||
Command::new("pass")
|
||||
|
@ -102,4 +111,48 @@ impl Menu
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn edit(&self, id: u32) -> Result<(),String>
|
||||
{
|
||||
let idx = usize::try_from(id)
|
||||
.map_err(|_err| "Invalid index")?;
|
||||
if idx >= self.entries.len() { return Err(String::from("Invalid index")) }
|
||||
let entry = &self.entries[idx];
|
||||
|
||||
Command::new("pass")
|
||||
.arg("edit")
|
||||
.arg(entry.full_name.as_str())
|
||||
.spawn()
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn context(&self, _id: u32) -> Vec<launcher::ContextOption>
|
||||
{
|
||||
let mut ret:Vec<launcher::ContextOption> = Vec::new();
|
||||
ret.push(launcher::ContextOption{
|
||||
id: ContextAction::Copy as u32,
|
||||
name: String::from("Copy")
|
||||
});
|
||||
ret.push(launcher::ContextOption{
|
||||
id: ContextAction::Edit as u32,
|
||||
name: String::from("Edit")
|
||||
});
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
pub fn activate_context(&self, id: u32, action: u32) -> Result<(), String>
|
||||
{
|
||||
match num::FromPrimitive::from_u32(action)
|
||||
{
|
||||
Some(ContextAction::Copy) => self.activate(id)?,
|
||||
Some(ContextAction::Edit) => self.edit(id)?,
|
||||
None => return Err(String::from("Invalid context action"))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
|
||||
mod launcher;
|
||||
#[cfg(test)] mod todo_tests;
|
||||
|
||||
use chrono::Local;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::NaiveDate;
|
||||
use launcher::{ Request, PluginResponse, PluginSearchResult, IconSource };
|
||||
use std::io;
|
||||
use std::error;
|
||||
use date_time_parser::DateParser;
|
||||
use reqwest::blocking::Client;
|
||||
use uuid::Uuid;
|
||||
|
||||
use std::result;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
type Result<T> = result::Result<T, Box<dyn error::Error>>;
|
||||
|
||||
struct Todo
|
||||
{
|
||||
summary: String,
|
||||
due: Option<NaiveDate>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct Config
|
||||
{
|
||||
url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
calendars: Vec<String>
|
||||
}
|
||||
|
||||
struct Session
|
||||
{
|
||||
todo: Todo,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
fn respond(response: &PluginResponse) -> Result<()>
|
||||
{
|
||||
let encoded = serde_json::to_string(&response)?;
|
||||
print!("{}\n", encoded);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn task_string(session: &Session) -> String
|
||||
{
|
||||
format!("{}{}",
|
||||
session.todo.summary,
|
||||
session.todo.due.map(|d| " ".to_owned() + &d.to_string()).unwrap_or("".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_menu(session: &Session) -> Result<()>
|
||||
{
|
||||
let task = task_string(session);
|
||||
for (idx, cal) in session.config.calendars.iter().enumerate()
|
||||
{
|
||||
respond(&PluginResponse::Append(
|
||||
PluginSearchResult {
|
||||
id: idx as u32,
|
||||
name: "Create in ".to_owned()+cal.as_str(),
|
||||
description: task.clone(),
|
||||
keywords: None,
|
||||
icon: Some(IconSource::Name("appointment-new".to_string())),
|
||||
exec: None,
|
||||
window: None
|
||||
}
|
||||
))?;
|
||||
}
|
||||
respond(&PluginResponse::Finished)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_task(sentence: &String, session: &mut Session) -> Result<()>
|
||||
{
|
||||
let mut summary = String::new();
|
||||
let mut date:Option<NaiveDate> = None;
|
||||
for term in sentence.strip_prefix("todo").unwrap_or(sentence).split(" ")
|
||||
{
|
||||
if term.is_empty()
|
||||
{
|
||||
()
|
||||
}
|
||||
else if term.starts_with("@")
|
||||
{
|
||||
if let Some(parsed) = term.get(1..).and_then(|d| { DateParser::parse(d.replace("_"," ").as_str()) })
|
||||
{
|
||||
date = Some(parsed)
|
||||
} else {
|
||||
if !summary.is_empty() { summary.push(' '); }
|
||||
summary.push_str(term)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if !summary.is_empty() { summary.push(' '); }
|
||||
summary.push_str(term)
|
||||
}
|
||||
}
|
||||
session.todo.summary = summary;
|
||||
session.todo.due = date;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_caldav(todo: &Todo, uuid: Uuid) -> String
|
||||
{
|
||||
let due = match &todo.due
|
||||
{
|
||||
None => "".to_string(),
|
||||
Some(due) => format!("DUE:{}\n", due.format("%Y%m%dT000000").to_string())
|
||||
};
|
||||
|
||||
let mut caldav = String::new();
|
||||
caldav.push_str("BEGIN:VCALENDAR\n");
|
||||
caldav.push_str("PRODID:-//okennedy//pop_todo//EN\n");
|
||||
caldav.push_str("VERSION:2.0\n");
|
||||
caldav.push_str("BEGIN:VTODO\n");
|
||||
caldav.push_str(format!("UID:{}\n", uuid).as_str());
|
||||
caldav.push_str(format!("DTSTAMP:{}\n", Local::now().format("%Y%m%dT%H%M%S").to_string()).as_str());
|
||||
caldav.push_str(due.as_str());
|
||||
caldav.push_str(format!("SUMMARY:{}\n", todo.summary).as_str());
|
||||
caldav.push_str("END:VTODO\n");
|
||||
caldav.push_str("END:VCALENDAR\n");
|
||||
|
||||
return caldav
|
||||
}
|
||||
|
||||
fn publish_todo(todo: &Todo, base_url: &String, username: &String, password: &String, calendar: &String) -> Result<()>
|
||||
{
|
||||
let client = Client::new();
|
||||
let uuid = Uuid::new_v4();
|
||||
|
||||
let url = format!("{}/calendars/{}/{}/{}.ics", base_url, username, calendar, uuid);
|
||||
let caldav = gen_caldav(todo, uuid);
|
||||
|
||||
println!("{}", url);
|
||||
|
||||
let resp =
|
||||
client.put(url)
|
||||
.body(caldav)
|
||||
.basic_auth(username, Some(password))
|
||||
.header("Content-Type", "text/calendar")
|
||||
.send()?;
|
||||
|
||||
println!("Status: {}\n{}", resp.status(), resp.text()?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_request(request: &Request, session: &mut Session) -> Result<bool>
|
||||
{
|
||||
match request {
|
||||
Request::Activate(id) => {
|
||||
respond(&PluginResponse::Clear)?;
|
||||
publish_todo(
|
||||
&session.todo,
|
||||
&session.config.url,
|
||||
&session.config.username,
|
||||
&session.config.password,
|
||||
&session.config.calendars[*id as usize]
|
||||
)?;
|
||||
respond(&PluginResponse::Close)?;
|
||||
}
|
||||
Request::Exit => {
|
||||
return Ok(false);
|
||||
}
|
||||
Request::Search(term) => {
|
||||
parse_task(term, session)?;
|
||||
build_menu(session)?;
|
||||
}
|
||||
Request::ActivateContext { id:_id, context:_context } => (),
|
||||
Request::Complete(_) => (),
|
||||
Request::Context(_) => (),
|
||||
Request::Interrupt => (),
|
||||
Request::Quit(_) => (),
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
||||
// home_dir has a deprecation warning because it is broken on
|
||||
// windows... but PopOS is linux only.
|
||||
#[allow(deprecated)]
|
||||
|
||||
let mut config = env::home_dir().unwrap();
|
||||
config.push(".config");
|
||||
config.push("pop_todo.json");
|
||||
let config_string = fs::read_to_string(config)?;
|
||||
let config = serde_json::from_str(config_string.as_str())?;
|
||||
|
||||
let mut session = Session {
|
||||
todo: Todo {
|
||||
summary: "No Description".to_string(),
|
||||
due: None,
|
||||
},
|
||||
config,
|
||||
};
|
||||
|
||||
let mut keep_going = true;
|
||||
|
||||
let mut buffer = String::new();
|
||||
let stdin = io::stdin();
|
||||
|
||||
|
||||
while keep_going {
|
||||
if stdin.read_line(&mut buffer)? > 0
|
||||
{
|
||||
let request: Request = serde_json::from_str(&buffer)?;
|
||||
keep_going = process_request(&request, &mut session)?;
|
||||
buffer.clear();
|
||||
} else {
|
||||
keep_going = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
use date_time_parser::DateParser;
|
||||
use chrono::{ Local, Datelike, Duration };
|
||||
use chrono::naive::NaiveDate;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Session, Todo, parse_task, gen_caldav};
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_date_creation()
|
||||
{
|
||||
let date1 = DateParser::parse("today");
|
||||
let date1cmp = Local::now();
|
||||
assert_eq!(NaiveDate::from_ymd_opt(date1cmp.year(), date1cmp.month(), date1cmp.day()), date1);
|
||||
|
||||
let date2 = DateParser::parse("tomorrow");
|
||||
let date2cmp = Local::now() + Duration::days(1);
|
||||
|
||||
assert_eq!(NaiveDate::from_ymd_opt(date2cmp.year(), date2cmp.month(), date2cmp.day()), date2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser()
|
||||
{
|
||||
let mut session = Session {
|
||||
todo: Todo {
|
||||
summary: "".to_string(),
|
||||
due: None
|
||||
},
|
||||
config: crate::Config {
|
||||
url: "https://foo".to_string(),
|
||||
username: "".to_string(),
|
||||
password: "".to_string(),
|
||||
calendars: Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let ret = parse_task(&"do the thing @tomorrow".to_string(), &mut session);
|
||||
assert!(ret.is_ok());
|
||||
|
||||
assert_eq!("do the thing", session.todo.summary);
|
||||
let tomorrow = DateParser::parse("tomorrow");
|
||||
assert_eq!(tomorrow, session.todo.due);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incremental_parser()
|
||||
{
|
||||
let mut session = Session {
|
||||
todo: Todo {
|
||||
summary: "".to_string(),
|
||||
due: None
|
||||
},
|
||||
config: crate::Config {
|
||||
url: "https://foo".to_string(),
|
||||
username: "".to_string(),
|
||||
password: "".to_string(),
|
||||
calendars: Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
let mut task = "".to_string();
|
||||
for chr in "do the thing @tomorrow".chars()
|
||||
{
|
||||
task.push(chr);
|
||||
let ret =
|
||||
parse_task(&task, &mut session);
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
|
||||
assert_eq!("do the thing", session.todo.summary);
|
||||
let tomorrow = DateParser::parse("tomorrow");
|
||||
assert_eq!(tomorrow, session.todo.due);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gen_caldav()
|
||||
{
|
||||
let todo = Todo { summary: "Hello World".to_string(), due: DateParser::parse("tomorrow") };
|
||||
let uuid = Uuid::new_v4();
|
||||
|
||||
let test = gen_caldav(&todo, uuid);
|
||||
|
||||
assert!(test.contains("SUMMARY:Hello World"));
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue