Compare commits
10 Commits
b5781619db
...
be19b88a41
Author | SHA1 | Date | |
---|---|---|---|
be19b88a41 | |||
c0cb7eac82 | |||
b6d61b5ee9 | |||
e98a5dd002 | |||
0adf14e8d4 | |||
3995a47078 | |||
d5af968cb9 | |||
adbff32546 | |||
2e84cdb3d2 | |||
20172f5f27 |
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -85,6 +85,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"structopt",
|
||||
"time",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@ -106,6 +107,15 @@ version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
@ -271,6 +281,16 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num_threads",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
|
@ -7,10 +7,9 @@ description = "A CLI habit tracker."
|
||||
readme = "./README.md"
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
structopt = "0.3"
|
||||
uuid = { version = "1.1", features = [ "v4", "fast-rng", "macro-diagnostics" ] }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_json = "1.0"
|
||||
time = { version = "0.3", features = [ "local-offset" ] }
|
||||
|
7
PRAYER.md
Normal file
7
PRAYER.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Project Prayer
|
||||
|
||||
Lord God, you who knows the weakness of Men, and that only by cooperation with
|
||||
your grace may we overcome the shackles of sin, bless the users of this program
|
||||
that by a disciplined adherence to the habits they have set forth to form and to
|
||||
reject, that they may succeed in their enterprise and grow in both faith &
|
||||
virtue. In the name of Jesus Christ our Lord. Amen.
|
@ -18,7 +18,8 @@ commands:
|
||||
- `help` or `h`: show help information
|
||||
- `list` or `ls`: list the habits available for today
|
||||
- `modify` or `mod`: modify the settings for a habit
|
||||
- `statistics` or `stats`: show statistics on current habits
|
||||
- `stats`: show statistics on current habits
|
||||
- `vacation` or `vac`: toggle vacation mode
|
||||
|
||||
## License
|
||||
|
||||
|
59
src/habit.rs
59
src/habit.rs
@ -18,6 +18,7 @@
|
||||
|
||||
use uuid::Uuid;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Habit
|
||||
@ -25,28 +26,76 @@ pub struct Habit
|
||||
uid:String,
|
||||
name:String,
|
||||
bad:bool,
|
||||
weight:u8,
|
||||
priority:char,
|
||||
// Day 0 is Monday. Use number_days_from_monday() to determine index.
|
||||
days_active:[bool;7],
|
||||
done:bool,
|
||||
streak:i32,
|
||||
}
|
||||
|
||||
impl Habit
|
||||
{
|
||||
pub fn new(name:String, bad:bool, weight:u8) -> Self
|
||||
pub fn new(name:String, bad:bool, priority:char, days_active:[bool;7]) -> Self
|
||||
{
|
||||
if ![ 'H','L','M' ].contains(&priority)
|
||||
{
|
||||
panic!("Unknown priority {}", priority);
|
||||
}
|
||||
Self
|
||||
{
|
||||
uid: Uuid::new_v4().hyphenated().to_string(),
|
||||
name,
|
||||
bad,
|
||||
weight,
|
||||
priority,
|
||||
days_active,
|
||||
done: false,
|
||||
streak: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active_days_string(&self) -> String
|
||||
{
|
||||
let mut res:String = String::new();
|
||||
|
||||
for (i,v) in self.days_active.iter().enumerate()
|
||||
{
|
||||
if *v
|
||||
{
|
||||
res.push_str(match i {
|
||||
0 => "mon",
|
||||
1 => "tue",
|
||||
2 => "wed",
|
||||
3 => "thu",
|
||||
4 => "fri",
|
||||
5 => "sat",
|
||||
6 => "sun",
|
||||
_ => "unk", // this one will never occur, but the compiler complains
|
||||
});
|
||||
res.push(',');
|
||||
}
|
||||
}
|
||||
res.pop();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
pub fn active_today(&self) -> bool
|
||||
{
|
||||
return self.days_active[OffsetDateTime::now_local().unwrap()
|
||||
.weekday().number_days_from_monday() as usize]
|
||||
}
|
||||
|
||||
pub fn get_uid(&self) -> &String { &self.uid }
|
||||
pub fn get_name(&self) -> &String { &self.name }
|
||||
pub fn get_bad(&self) -> bool { self.bad }
|
||||
pub fn get_weight(&self) -> u8 { self.weight }
|
||||
pub fn get_priority(&self) -> char { self.priority }
|
||||
pub fn get_done(&self) -> bool { self.done }
|
||||
pub fn get_streak(&self) -> i32 { self.streak }
|
||||
|
||||
pub fn set_name(&mut self, name:String) { self.name = name; }
|
||||
pub fn set_bad(&mut self, bad:bool) { self.bad = bad; }
|
||||
pub fn set_weight(&mut self, weight:u8) { self.weight = weight; }
|
||||
pub fn set_priority(&mut self, priority:char) { self.priority = priority; }
|
||||
pub fn set_days(&mut self, days:[bool;7]) { self.days_active = days; }
|
||||
pub fn set_done(&mut self, done:bool) { self.done = done; }
|
||||
// TODO: whether set_streak or inc/dec/reset_streak
|
||||
}
|
||||
|
112
src/habitmgr.rs
112
src/habitmgr.rs
@ -19,6 +19,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
use time::Weekday;
|
||||
|
||||
use crate::habit::Habit;
|
||||
|
||||
@ -55,23 +56,27 @@ impl HabitMgr
|
||||
habits_path.display(), e);
|
||||
});
|
||||
}
|
||||
Self
|
||||
{
|
||||
habits: Vec::new(),
|
||||
habits_path,
|
||||
}
|
||||
}
|
||||
|
||||
fn import_habits(&mut self)
|
||||
{
|
||||
/*
|
||||
* The list of active habits is necessary for all functions, thus they're
|
||||
* imported here, allowing member functions which only wish to access
|
||||
* information to be constant.
|
||||
*/
|
||||
let habits_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.habits_path)
|
||||
.open(&habits_path)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Error opening file {}:\n{}", self.habits_path.display(), e);
|
||||
panic!("Error opening file {}:\n{}", habits_path.display(), e);
|
||||
});
|
||||
let habits_reader = BufReader::new(&habits_file);
|
||||
self.habits = serde_json::from_reader(habits_reader).unwrap();
|
||||
|
||||
let habits:Vec<Habit> = serde_json::from_reader(habits_reader).unwrap();
|
||||
|
||||
Self
|
||||
{
|
||||
habits,
|
||||
habits_path,
|
||||
}
|
||||
}
|
||||
|
||||
fn export_habits(&self)
|
||||
@ -91,10 +96,40 @@ impl HabitMgr
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add(&mut self, name:String, bad:bool, weight:u8, _days:String)
|
||||
fn days_array_from_string(days:String) -> [bool;7]
|
||||
{
|
||||
self.import_habits();
|
||||
self.habits.push(Habit::new(name.clone(), bad, weight));
|
||||
let days_list = days.split(",");
|
||||
let mut days_active:[bool;7] = [ false; 7 ];
|
||||
for i in days_list
|
||||
{
|
||||
match i
|
||||
{
|
||||
"mon" =>
|
||||
days_active[Weekday::Monday.number_days_from_monday() as usize] = true,
|
||||
"tue" =>
|
||||
days_active[Weekday::Tuesday.number_days_from_monday() as usize] = true,
|
||||
"wed" =>
|
||||
days_active[Weekday::Wednesday.number_days_from_monday() as usize] = true,
|
||||
"thu" =>
|
||||
days_active[Weekday::Thursday.number_days_from_monday() as usize] = true,
|
||||
"fri" =>
|
||||
days_active[Weekday::Friday.number_days_from_monday() as usize] = true,
|
||||
"sat" =>
|
||||
days_active[Weekday::Saturday.number_days_from_monday() as usize] = true,
|
||||
"sun" =>
|
||||
days_active[Weekday::Sunday.number_days_from_monday() as usize] = true,
|
||||
_ =>
|
||||
panic!("Day {} not recognized!", i),
|
||||
}
|
||||
}
|
||||
|
||||
return days_active;
|
||||
}
|
||||
|
||||
pub fn add(&mut self, name:String, bad:bool, priority:char, days:String)
|
||||
{
|
||||
let days_active = HabitMgr::days_array_from_string(days);
|
||||
self.habits.push(Habit::new(name.clone(), bad, priority, days_active));
|
||||
self.export_habits();
|
||||
|
||||
println!("New habit {} added.", &name);
|
||||
@ -102,30 +137,30 @@ impl HabitMgr
|
||||
|
||||
pub fn delete(&mut self, id:usize)
|
||||
{
|
||||
self.import_habits();
|
||||
let old_habit = self.habits.remove(id);
|
||||
self.export_habits();
|
||||
println!("Removed habit {}", old_habit.get_name());
|
||||
}
|
||||
|
||||
pub fn habit_info(&mut self, id:usize)
|
||||
pub fn habit_info(&self, id:usize)
|
||||
{
|
||||
self.import_habits();
|
||||
let habit = &self.habits[id];
|
||||
println!("Name: {}", habit.get_name());
|
||||
println!("ID: {}", id);
|
||||
println!("UID: {}", habit.get_uid());
|
||||
println!("Active Days: {}", habit.active_days_string());
|
||||
println!("Bad: {}", habit.get_bad());
|
||||
println!("Weight: {}", habit.get_weight());
|
||||
println!("Weight: {}", habit.get_priority());
|
||||
println!("Done: {}", habit.get_done());
|
||||
println!("Streak: {}", habit.get_streak());
|
||||
}
|
||||
|
||||
pub fn modify(&mut self, id:usize,
|
||||
name:Option<String>,
|
||||
toggle_bad:bool,
|
||||
weight:Option<u8>,
|
||||
_days:Option<String>)
|
||||
priority:Option<char>,
|
||||
days:Option<String>)
|
||||
{
|
||||
self.import_habits();
|
||||
if name.is_some()
|
||||
{
|
||||
self.habits[id].set_name(name.unwrap());
|
||||
@ -135,31 +170,44 @@ impl HabitMgr
|
||||
let is_bad = self.habits[id].get_bad();
|
||||
self.habits[id].set_bad(!is_bad);
|
||||
}
|
||||
if weight.is_some()
|
||||
if priority.is_some()
|
||||
{
|
||||
self.habits[id].set_weight(weight.unwrap());
|
||||
self.habits[id].set_priority(priority.unwrap());
|
||||
}
|
||||
if days.is_some()
|
||||
{
|
||||
let days_active = HabitMgr::days_array_from_string(days.unwrap());
|
||||
self.habits[id].set_days(days_active);
|
||||
}
|
||||
self.export_habits();
|
||||
}
|
||||
|
||||
pub fn list(&mut self, _all:bool, _verbose:bool)
|
||||
pub fn list(&self, all:bool)
|
||||
{
|
||||
self.import_habits();
|
||||
if self.habits.is_empty()
|
||||
{
|
||||
println!("There are no habits. Add one!");
|
||||
}
|
||||
else if !all && self.habits.iter().all(|i| !i.active_today())
|
||||
{
|
||||
println!("No active habits available today!");
|
||||
}
|
||||
else
|
||||
{
|
||||
println!(" {0: <3} | {1: <5} | {2: <6} | {3}",
|
||||
"id", "bad", "weight", "name");
|
||||
println!(" {0: <3} | {1: <5} | {2: <8} | {3: <5} | {4: <6} | {5}",
|
||||
"id", "bad", "priority", "done", "streak", "name");
|
||||
for (i, habit) in self.habits.iter().enumerate()
|
||||
{
|
||||
println!(" {0: <3} | {1: <5} | {2: <6} | {3}",
|
||||
i,
|
||||
habit.get_bad(),
|
||||
habit.get_weight(),
|
||||
habit.get_name());
|
||||
if all || habit.active_today()
|
||||
{
|
||||
println!(" {0: <3} | {1: <5} | {2: <8} | {3: <5} | {4: <6} | {5}",
|
||||
i,
|
||||
habit.get_bad(),
|
||||
habit.get_priority(),
|
||||
habit.get_done(),
|
||||
habit.get_streak(),
|
||||
habit.get_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
48
src/main.rs
48
src/main.rs
@ -42,14 +42,18 @@ enum Command
|
||||
#[structopt(alias = "a")]
|
||||
Add
|
||||
{
|
||||
#[structopt(help = "name of the new habit")]
|
||||
#[structopt(help = "Name of the new habit")]
|
||||
name:String,
|
||||
#[structopt(long, short, default_value = "mon,tue,wed,thu,fri,sat,sun")]
|
||||
#[structopt(long, short, help = "Days the new habit is active",
|
||||
default_value = "mon,tue,wed,thu,fri,sat,sun")]
|
||||
days:String,
|
||||
#[structopt(long)]
|
||||
#[structopt(long, help = "Assign habit as a bad habit (negative points)")]
|
||||
bad:bool,
|
||||
#[structopt(short, long, default_value = "5")]
|
||||
weight:u8,
|
||||
#[structopt(short,
|
||||
long,
|
||||
help = "Priority of the new habit (L, M, or H)",
|
||||
default_value = "M")]
|
||||
priority:char,
|
||||
},
|
||||
Commit { },
|
||||
#[structopt(alias = "del")]
|
||||
@ -59,31 +63,31 @@ enum Command
|
||||
},
|
||||
#[structopt(alias = "i")]
|
||||
Info {
|
||||
#[structopt(help = "ID of the habit to show information for")]
|
||||
id:usize,
|
||||
},
|
||||
#[structopt(alias = "ls")]
|
||||
List
|
||||
{
|
||||
#[structopt(short, long, help = "list all active habits")]
|
||||
#[structopt(short, long, help = "List all active habits")]
|
||||
all:bool,
|
||||
#[structopt(short, long, help = "show UUIDs")]
|
||||
verbose:bool,
|
||||
},
|
||||
#[structopt(alias = "mod")]
|
||||
Modify {
|
||||
#[structopt(help = "ID of the habit to modify")]
|
||||
id:usize,
|
||||
#[structopt(short, long)]
|
||||
#[structopt(short, long, help = "New name for the habit")]
|
||||
name:Option<String>,
|
||||
#[structopt(short, long)]
|
||||
weight:Option<u8>,
|
||||
#[structopt(long, short)]
|
||||
#[structopt(short, long, help = "New priority for the habit (L, M, or H)")]
|
||||
priority:Option<char>,
|
||||
#[structopt(long, short, help = "New days the habit is active")]
|
||||
days:Option<String>,
|
||||
#[structopt(long)]
|
||||
#[structopt(long, help = "Toggle the 'bad' value of the habit")]
|
||||
toggle_bad:bool,
|
||||
},
|
||||
#[structopt(alias = "stats")]
|
||||
Statistics { },
|
||||
Stats { },
|
||||
#[structopt(alias = "vac")]
|
||||
Vacation { },
|
||||
}
|
||||
|
||||
fn main()
|
||||
@ -112,20 +116,20 @@ fn main()
|
||||
|
||||
match opts.cmd
|
||||
{
|
||||
None => hmgr.list(false, false),
|
||||
None => hmgr.list(false),
|
||||
Some(c) =>
|
||||
match c
|
||||
{
|
||||
Command::Add { name, days, bad, weight } =>
|
||||
hmgr.add(name, bad, weight, days),
|
||||
Command::Add { name, days, bad, priority } =>
|
||||
hmgr.add(name, bad, priority, days),
|
||||
Command::Delete { id } =>
|
||||
hmgr.delete(id),
|
||||
Command::Info { id } =>
|
||||
hmgr.habit_info(id),
|
||||
Command::List { all, verbose } =>
|
||||
hmgr.list(all, verbose),
|
||||
Command::Modify { id, name, weight, days, toggle_bad } =>
|
||||
hmgr.modify(id, name, toggle_bad, weight, days),
|
||||
Command::List { all } =>
|
||||
hmgr.list(all),
|
||||
Command::Modify { id, name, priority, days, toggle_bad } =>
|
||||
hmgr.modify(id, name, toggle_bad, priority, days),
|
||||
_ => todo!(),
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user