Compare commits

..

No commits in common. "be19b88a41d86fcb0818fe40feb5c254ed848811" and "b5781619db63146d298201c9d2ef0b1f0e37a118" have entirely different histories.

7 changed files with 64 additions and 192 deletions

20
Cargo.lock generated
View File

@ -85,7 +85,6 @@ dependencies = [
"serde",
"serde_json",
"structopt",
"time",
"uuid",
]
@ -107,15 +106,6 @@ 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"
@ -281,16 +271,6 @@ 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"

View File

@ -7,9 +7,10 @@ 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" ] }

View File

@ -1,7 +0,0 @@
# 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.

View File

@ -18,8 +18,7 @@ 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
- `stats`: show statistics on current habits
- `vacation` or `vac`: toggle vacation mode
- `statistics` or `stats`: show statistics on current habits
## License

View File

@ -18,7 +18,6 @@
use uuid::Uuid;
use serde::{Serialize, Deserialize};
use time::OffsetDateTime;
#[derive(Serialize, Deserialize, Debug)]
pub struct Habit
@ -26,76 +25,28 @@ pub struct Habit
uid:String,
name:String,
bad:bool,
priority:char,
// Day 0 is Monday. Use number_days_from_monday() to determine index.
days_active:[bool;7],
done:bool,
streak:i32,
weight:u8,
}
impl Habit
{
pub fn new(name:String, bad:bool, priority:char, days_active:[bool;7]) -> Self
pub fn new(name:String, bad:bool, weight:u8) -> Self
{
if ![ 'H','L','M' ].contains(&priority)
{
panic!("Unknown priority {}", priority);
}
Self
{
uid: Uuid::new_v4().hyphenated().to_string(),
name,
bad,
priority,
days_active,
done: false,
streak: 0,
weight,
}
}
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_priority(&self) -> char { self.priority }
pub fn get_done(&self) -> bool { self.done }
pub fn get_streak(&self) -> i32 { self.streak }
pub fn get_weight(&self) -> u8 { self.weight }
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_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
pub fn set_weight(&mut self, weight:u8) { self.weight = weight; }
}

View File

@ -19,7 +19,6 @@
use std::path::PathBuf;
use std::fs::OpenOptions;
use std::io::{BufReader, BufWriter};
use time::Weekday;
use crate::habit::Habit;
@ -56,29 +55,25 @@ impl HabitMgr
habits_path.display(), e);
});
}
/*
* 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(&habits_path)
.unwrap_or_else(|e| {
panic!("Error opening file {}:\n{}", habits_path.display(), e);
});
let habits_reader = BufReader::new(&habits_file);
let habits:Vec<Habit> = serde_json::from_reader(habits_reader).unwrap();
Self
{
habits,
habits: Vec::new(),
habits_path,
}
}
fn import_habits(&mut self)
{
let habits_file = OpenOptions::new()
.read(true)
.open(&self.habits_path)
.unwrap_or_else(|e| {
panic!("Error opening file {}:\n{}", self.habits_path.display(), e);
});
let habits_reader = BufReader::new(&habits_file);
self.habits = serde_json::from_reader(habits_reader).unwrap();
}
fn export_habits(&self)
{
let habits_file = OpenOptions::new()
@ -96,40 +91,10 @@ impl HabitMgr
});
}
fn days_array_from_string(days:String) -> [bool;7]
pub fn add(&mut self, name:String, bad:bool, weight:u8, _days:String)
{
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.import_habits();
self.habits.push(Habit::new(name.clone(), bad, weight));
self.export_habits();
println!("New habit {} added.", &name);
@ -137,30 +102,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(&self, id:usize)
pub fn habit_info(&mut 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_priority());
println!("Done: {}", habit.get_done());
println!("Streak: {}", habit.get_streak());
println!("Weight: {}", habit.get_weight());
}
pub fn modify(&mut self, id:usize,
name:Option<String>,
toggle_bad:bool,
priority:Option<char>,
days:Option<String>)
weight:Option<u8>,
_days:Option<String>)
{
self.import_habits();
if name.is_some()
{
self.habits[id].set_name(name.unwrap());
@ -170,44 +135,31 @@ impl HabitMgr
let is_bad = self.habits[id].get_bad();
self.habits[id].set_bad(!is_bad);
}
if priority.is_some()
if weight.is_some()
{
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.habits[id].set_weight(weight.unwrap());
}
self.export_habits();
}
pub fn list(&self, all:bool)
pub fn list(&mut self, _all:bool, _verbose: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: <8} | {3: <5} | {4: <6} | {5}",
"id", "bad", "priority", "done", "streak", "name");
println!(" {0: <3} | {1: <5} | {2: <6} | {3}",
"id", "bad", "weight", "name");
for (i, habit) in self.habits.iter().enumerate()
{
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());
}
println!(" {0: <3} | {1: <5} | {2: <6} | {3}",
i,
habit.get_bad(),
habit.get_weight(),
habit.get_name());
}
}
}

View File

@ -42,18 +42,14 @@ 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, help = "Days the new habit is active",
default_value = "mon,tue,wed,thu,fri,sat,sun")]
#[structopt(long, short, default_value = "mon,tue,wed,thu,fri,sat,sun")]
days:String,
#[structopt(long, help = "Assign habit as a bad habit (negative points)")]
#[structopt(long)]
bad:bool,
#[structopt(short,
long,
help = "Priority of the new habit (L, M, or H)",
default_value = "M")]
priority:char,
#[structopt(short, long, default_value = "5")]
weight:u8,
},
Commit { },
#[structopt(alias = "del")]
@ -63,31 +59,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, help = "New name for the habit")]
#[structopt(short, long)]
name:Option<String>,
#[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")]
#[structopt(short, long)]
weight:Option<u8>,
#[structopt(long, short)]
days:Option<String>,
#[structopt(long, help = "Toggle the 'bad' value of the habit")]
#[structopt(long)]
toggle_bad:bool,
},
Stats { },
#[structopt(alias = "vac")]
Vacation { },
#[structopt(alias = "stats")]
Statistics { },
}
fn main()
@ -116,20 +112,20 @@ fn main()
match opts.cmd
{
None => hmgr.list(false),
None => hmgr.list(false, false),
Some(c) =>
match c
{
Command::Add { name, days, bad, priority } =>
hmgr.add(name, bad, priority, days),
Command::Add { name, days, bad, weight } =>
hmgr.add(name, bad, weight, days),
Command::Delete { id } =>
hmgr.delete(id),
Command::Info { id } =>
hmgr.habit_info(id),
Command::List { all } =>
hmgr.list(all),
Command::Modify { id, name, priority, days, toggle_bad } =>
hmgr.modify(id, name, toggle_bad, priority, days),
Command::List { all, verbose } =>
hmgr.list(all, verbose),
Command::Modify { id, name, weight, days, toggle_bad } =>
hmgr.modify(id, name, toggle_bad, weight, days),
_ => todo!(),
},
}