hbtrack 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +9 -1
- data/README.md +15 -68
- data/bin/irb +7 -0
- data/exe/hbtrack +3 -1
- data/hbtrack.gemspec +3 -1
- data/lib/hbtrack.rb +4 -44
- data/lib/hbtrack/cli/cli.rb +53 -0
- data/lib/hbtrack/cli/view.rb +87 -0
- data/lib/hbtrack/command.rb +8 -3
- data/lib/hbtrack/command/add_command.rb +25 -19
- data/lib/hbtrack/command/import_command.rb +43 -0
- data/lib/hbtrack/command/list_command.rb +30 -36
- data/lib/hbtrack/command/remove_command.rb +10 -13
- data/lib/hbtrack/command/show_command.rb +40 -0
- data/lib/hbtrack/command/update_command.rb +40 -47
- data/lib/hbtrack/config.rb +3 -0
- data/lib/hbtrack/database/model.rb +9 -0
- data/lib/hbtrack/database/sequel_store.rb +146 -0
- data/lib/hbtrack/importer/abstract_importer.rb +34 -0
- data/lib/hbtrack/importer/hbtrack_importer.rb +76 -0
- data/lib/hbtrack/importer/streaks_importer.rb +56 -0
- data/lib/hbtrack/stat_formatter.rb +1 -1
- data/lib/hbtrack/util.rb +5 -12
- data/lib/hbtrack/version.rb +1 -1
- metadata +55 -7
- data/lib/hbtrack/habit.rb +0 -182
- data/lib/hbtrack/habit_printer.rb +0 -53
- data/lib/hbtrack/habit_tracker.rb +0 -91
- data/lib/hbtrack/store.rb +0 -18
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'hbtrack/command'
|
5
|
+
require 'hbtrack/importer/streaks_importer'
|
6
|
+
require 'hbtrack/importer/hbtrack_importer'
|
7
|
+
require 'hbtrack/database/sequel_store'
|
8
|
+
require 'hbtrack/util'
|
9
|
+
|
10
|
+
module Hbtrack
|
11
|
+
# ImportCommand class is responsible for handling
|
12
|
+
# `hbtrack import` command in CLI
|
13
|
+
class ImportCommand < Command
|
14
|
+
def initialize(store_path, options)
|
15
|
+
@importer = Hbtrack::Importer::HbtrackImporter.new
|
16
|
+
super(store_path, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute
|
20
|
+
return import(@names[0])
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_option_parser
|
25
|
+
OptionParser.new do |opts|
|
26
|
+
opts.banner = 'Usage: hbtrack import <file_path> <options>'
|
27
|
+
opts.on('--streaks', 'Import data from streaks') do
|
28
|
+
@importer = Hbtrack::Importer::StreaksImporter.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def import(file_path)
|
34
|
+
@importer.import_from(file_path)
|
35
|
+
@importer.store_in(local_store)
|
36
|
+
Hbtrack::Util.green "Succesfully imported from #{file_path}"
|
37
|
+
rescue => e
|
38
|
+
Hbtrack::Util.red "Error: #{e}"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -2,22 +2,19 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'hbtrack/command'
|
5
|
+
require 'hbtrack/cli/view'
|
5
6
|
|
6
7
|
module Hbtrack
|
7
8
|
class ListCommand < Command
|
8
|
-
attr_reader :printer, :formatter
|
9
|
+
attr_reader :printer, :formatter, :month
|
9
10
|
|
10
|
-
def initialize(
|
11
|
-
@
|
12
|
-
|
13
|
-
super(hbt, options)
|
14
|
-
@formatter = @percentage ? CompletionRateSF.new : CompleteSF.new
|
15
|
-
@printer = HabitPrinter.new(@formatter)
|
11
|
+
def initialize(store_path, options)
|
12
|
+
@month = Date.today.strftime("%Y,%-m").to_sym
|
13
|
+
super(store_path, options)
|
16
14
|
end
|
17
15
|
|
18
16
|
def execute
|
19
|
-
return
|
20
|
-
return list(@names[0], @printer) unless @names.empty?
|
17
|
+
return list_from_db(local_store, @names)
|
21
18
|
super
|
22
19
|
end
|
23
20
|
|
@@ -27,12 +24,12 @@ module Hbtrack
|
|
27
24
|
opts.separator ''
|
28
25
|
opts.separator 'Options:'
|
29
26
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
@
|
27
|
+
# TODO: Renamed to better describe the functionality
|
28
|
+
# as in this case user are required toe enter
|
29
|
+
# the input in the form of <year>,<month>
|
30
|
+
opts.on('-m', '--month MONTH', 'List habit(s) according to month provided') do |month|
|
31
|
+
@month = month.to_sym
|
32
|
+
@year, @mon = month.split(',')
|
36
33
|
end
|
37
34
|
|
38
35
|
opts.on_tail('-h', '--help', 'Prints this help') do
|
@@ -42,30 +39,27 @@ module Hbtrack
|
|
42
39
|
end
|
43
40
|
end
|
44
41
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
title = Util.title habit.name
|
51
|
-
progress = printer.print_all_progress(habit)
|
52
|
-
footer = "\n" + habit.overall_stat_description(printer.formatter)
|
53
|
-
|
54
|
-
"#{title}#{progress}\n#{footer}"
|
42
|
+
def list_from_db(store, names)
|
43
|
+
habits = []
|
44
|
+
habits, entries = get_habits_from_db(store)
|
45
|
+
Hbtrack::CLI::View.list_all_habits(habits, entries, @month)
|
55
46
|
end
|
56
47
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
footer = "\n" + @hbt.overall_stat_description(@formatter)
|
48
|
+
def get_habits_from_db(store)
|
49
|
+
habits = []
|
50
|
+
entries = {}
|
51
|
+
habits = store.get_all_habits
|
52
|
+
habits.each do |habit|
|
53
|
+
entries[habit[:title]] = get_entry_from_db(store, habit[:id])
|
54
|
+
end
|
55
|
+
[habits, entries]
|
56
|
+
end
|
67
57
|
|
68
|
-
|
58
|
+
def get_entry_from_db(store, id)
|
59
|
+
month = @mon.to_i >= 1 ? @mon.to_i : Date.today.month
|
60
|
+
year = @year.to_i >= 1 ? @year.to_i : Date.today.year
|
61
|
+
store.get_entries_of_month(id, month, year)
|
69
62
|
end
|
63
|
+
|
70
64
|
end
|
71
65
|
end
|
@@ -2,16 +2,15 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'hbtrack/command'
|
5
|
-
require 'hbtrack/store'
|
6
5
|
|
7
6
|
module Hbtrack
|
8
7
|
class RemoveCommand < Command
|
9
|
-
def initialize(
|
10
|
-
super(
|
8
|
+
def initialize(store_path, options)
|
9
|
+
super(store_path, options)
|
11
10
|
end
|
12
11
|
|
13
12
|
def execute
|
14
|
-
return
|
13
|
+
return remove_from_db(@names, local_store)
|
15
14
|
super
|
16
15
|
end
|
17
16
|
|
@@ -21,17 +20,15 @@ module Hbtrack
|
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
|
-
def
|
25
|
-
names.
|
26
|
-
|
27
|
-
return ErrorHandler.raise_if_habit_error(name)
|
28
|
-
end
|
23
|
+
def feedback(names)
|
24
|
+
Hbtrack::Util.blue("Remove #{names.join(',')}!")
|
25
|
+
end
|
29
26
|
|
30
|
-
|
31
|
-
|
27
|
+
def remove_from_db(names, store)
|
28
|
+
status = store.delete_habit(names)
|
29
|
+
return ErrorHandler.raise_if_habit_error(names) if status == 0
|
32
30
|
|
33
|
-
|
34
|
-
Hbtrack::Util.blue("Remove #{names.join(',')}!")
|
31
|
+
feedback(names)
|
35
32
|
end
|
36
33
|
end
|
37
34
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'hbtrack/command'
|
5
|
+
require 'hbtrack/cli/view'
|
6
|
+
|
7
|
+
module Hbtrack
|
8
|
+
# ShowCommand class is responsible for handling
|
9
|
+
# `hbtrack import` command in CLI
|
10
|
+
class ShowCommand < Command
|
11
|
+
def initialize(store_path, options)
|
12
|
+
super(store_path, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
return show(local_store, @names[0]) if @names[0]
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_option_parser
|
21
|
+
OptionParser.new do |opts|
|
22
|
+
opts.banner = 'Usage: hbtrack show <habit_name>'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def show(store, title)
|
27
|
+
habit = store.get_habit_by_title(title)
|
28
|
+
return ErrorHandler.raise_habit_not_found(title) unless habit
|
29
|
+
|
30
|
+
entries = get_entries_from_db(store, habit)
|
31
|
+
Hbtrack::CLI::View.show_habit(habit, entries)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_entries_from_db(store, habit)
|
35
|
+
entries = store.get_entries_of(habit[:id]).all
|
36
|
+
entries.group_by { |e| e[:timestamp].strftime('%Y-%m') }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -2,21 +2,19 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'hbtrack/command'
|
5
|
-
require 'hbtrack/store'
|
6
5
|
|
7
6
|
module Hbtrack
|
8
7
|
class UpdateCommand < Command
|
9
|
-
def initialize(
|
10
|
-
@day =
|
8
|
+
def initialize(store_path, options, is_done)
|
9
|
+
@day = DateTime.now
|
11
10
|
@is_done = is_done
|
12
|
-
@
|
13
|
-
super(
|
11
|
+
@db = false
|
12
|
+
super(store_path, options)
|
14
13
|
end
|
15
14
|
|
16
15
|
def execute
|
17
|
-
return
|
18
|
-
return
|
19
|
-
return update(@names, @day, @is_done) if @names
|
16
|
+
return update_all_in_db(local_store, @day, @is_done) if @all
|
17
|
+
return update_in_db(local_store, @names, @day, @is_done)
|
20
18
|
super
|
21
19
|
end
|
22
20
|
|
@@ -26,18 +24,11 @@ module Hbtrack
|
|
26
24
|
opts.banner = "Usage: hbtrack #{action.downcase} [<habit_name>] [options]"
|
27
25
|
opts.separator ''
|
28
26
|
opts.separator 'Options:'
|
27
|
+
|
29
28
|
opts.on('-a', '--all', "#{action} all habits") do
|
30
29
|
@all = true
|
31
30
|
end
|
32
31
|
|
33
|
-
opts.on('-r', '--remaining', "#{action} remaining habits") do
|
34
|
-
@remaining = true
|
35
|
-
end
|
36
|
-
|
37
|
-
opts.on('-y', '--yesterday', "#{action} habit(s) for yesterday") do
|
38
|
-
@day = Date.today - 1
|
39
|
-
end
|
40
|
-
|
41
32
|
opts.on('--day DAY', Integer, "#{action} habit(s) for specific day") do |day|
|
42
33
|
@day = Date.new(Date.today.year, Date.today.month, day.to_i)
|
43
34
|
end
|
@@ -49,46 +40,48 @@ module Hbtrack
|
|
49
40
|
end
|
50
41
|
end
|
51
42
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
@hbt.find_or_create(name)
|
56
|
-
else
|
57
|
-
@hbt.find(name) do
|
58
|
-
return ErrorHandler.raise_if_habit_error(name)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
habit.done(is_done, day)
|
63
|
-
end
|
64
|
-
|
65
|
-
Store.new(@hbt.habits, @hbt.output_file_name).save
|
43
|
+
def update_in_db(store, name, day, is_done)
|
44
|
+
id = store.get_habit_id_for(name)
|
45
|
+
return ErrorHandler.raise_habit_not_found(name) unless id
|
66
46
|
|
67
|
-
|
47
|
+
add_or_update_entry(store, id, day, is_done)
|
48
|
+
Hbtrack::Util.green("Update successfully!")
|
68
49
|
end
|
69
50
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
Hbtrack::Util.green("
|
51
|
+
def update_all_in_db(store, day, is_done)
|
52
|
+
habits = store.get_all_habits
|
53
|
+
habits.each do |h|
|
54
|
+
add_or_update_entry(store, h[:id], day, is_done)
|
55
|
+
end
|
56
|
+
Hbtrack::Util.green("Update successfully!")
|
76
57
|
end
|
77
58
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
59
|
+
def add_or_update_entry(store, id, day, is_done)
|
60
|
+
entry = store.get_latest_entry_of(id)
|
61
|
+
type = is_done ? 'completed' : 'missed'
|
62
|
+
unless entry_exist?(entry, day)
|
63
|
+
entry = Hbtrack::Database::Entry.new(DateTime.now, type)
|
64
|
+
store.add_entry_of(id, entry)
|
65
|
+
else
|
66
|
+
store.update_entry_of(id, day, type)
|
81
67
|
end
|
82
|
-
|
83
|
-
Store.new(@hbt.habits, @hbt.output_file_name).save
|
84
|
-
|
85
|
-
Hbtrack::Util.green("#{action(is_done)} remaining habit(s)!")
|
86
68
|
end
|
87
69
|
|
88
|
-
|
70
|
+
# Check if the entry timestamp are within
|
71
|
+
# the same day
|
72
|
+
def entry_exist?(entry, day)
|
73
|
+
return false unless entry
|
74
|
+
year, month , day = extract_date(day)
|
75
|
+
time = entry[:timestamp]
|
76
|
+
y, m, d = extract_date(time)
|
77
|
+
return y == year && m == month && d == day
|
78
|
+
end
|
89
79
|
|
90
|
-
|
91
|
-
|
80
|
+
# Extract out the year, month and day of
|
81
|
+
# a Date or Time object.
|
82
|
+
def extract_date(day)
|
83
|
+
[day.year, day.month, day.day]
|
92
84
|
end
|
85
|
+
|
93
86
|
end
|
94
87
|
end
|
data/lib/hbtrack/config.rb
CHANGED
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hbtrack
|
4
|
+
module Database
|
5
|
+
class SequelStore
|
6
|
+
require 'sequel'
|
7
|
+
|
8
|
+
attr_reader :db
|
9
|
+
def initialize(name: 'hbtrack.db')
|
10
|
+
@db = Sequel.sqlite(name)
|
11
|
+
create_table?
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add a habit
|
15
|
+
def add_habit(habit)
|
16
|
+
habits.insert(
|
17
|
+
title: habit.title,
|
18
|
+
display_order: habit.display_order
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Delete a habit
|
23
|
+
def delete_habit(title)
|
24
|
+
habits.where(title: title).delete
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get habit by id
|
28
|
+
def get_habit(id)
|
29
|
+
habits.filter(id: id).first
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get ID of a habit by title
|
33
|
+
def get_habit_id_for(title)
|
34
|
+
get_habit_by_title(title)&.fetch(:id)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get habit by title
|
38
|
+
def get_habit_by_title(title)
|
39
|
+
habits.filter(title: title).first
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get all habits
|
43
|
+
def get_all_habits
|
44
|
+
habits.all
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get count of habits
|
48
|
+
def get_habits_count
|
49
|
+
habits.count
|
50
|
+
end
|
51
|
+
|
52
|
+
# Data Abstrack for Entry
|
53
|
+
# Entry = Struct.new(:timestamp, :type, :habit_id)
|
54
|
+
|
55
|
+
# Add a entry of a habit
|
56
|
+
def add_entry_of(habit_id, entry)
|
57
|
+
entries.insert(
|
58
|
+
timestamp: entry.timestamp,
|
59
|
+
type: entry.type,
|
60
|
+
habit_id: habit_id
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_entry_of(habit_id, time, type)
|
65
|
+
entries.where(habit_id: habit_id)
|
66
|
+
.where(timestamp: day_range_for(time))
|
67
|
+
.update(type: type, timestamp: time)
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_latest_entry_of(habit_id)
|
71
|
+
entries.where(habit_id: habit_id)
|
72
|
+
.order(Sequel.desc(:timestamp)).first
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get all entries of a habit
|
76
|
+
def get_entries_of(habit_id)
|
77
|
+
entries.where(habit_id: habit_id)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get entries count of a habit
|
81
|
+
def get_entries_count_of(habit_id)
|
82
|
+
get_entries_of(habit_id).count
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get entries of a habit in a period of month
|
86
|
+
# according to month and year given.
|
87
|
+
def get_entries_of_month(habit_id, month, year)
|
88
|
+
get_entries_of(habit_id)
|
89
|
+
.where(timestamp: month_range(month, year))
|
90
|
+
.all
|
91
|
+
end
|
92
|
+
|
93
|
+
# Create a range of date from the first day
|
94
|
+
# to the last day of a month
|
95
|
+
def month_range(month, year)
|
96
|
+
next_month = month == 12 ? 1 : month + 1
|
97
|
+
next_year = month == 12 ? year + 1 : year
|
98
|
+
Date.new(year, month, 1)..Date.new(next_year, next_month, 1)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Create a range from the start of a day
|
102
|
+
# to the end of a day according to the
|
103
|
+
# DateTime object given.
|
104
|
+
def day_range_for(time)
|
105
|
+
year = time.year
|
106
|
+
month = time.month
|
107
|
+
day = time.day
|
108
|
+
timezone = Time.new.zone
|
109
|
+
|
110
|
+
Range.new(
|
111
|
+
DateTime.new(year, month, day, 0, 0, 0, timezone),
|
112
|
+
DateTime.new(year, month, day, 23, 59, 59, timezone)
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
# Create Habits and Entries table if doesn't exist
|
118
|
+
def create_table?
|
119
|
+
db.create_table? :habits do
|
120
|
+
primary_key :id
|
121
|
+
String :title
|
122
|
+
Integer :display_order
|
123
|
+
end
|
124
|
+
|
125
|
+
db.create_table? :entries do
|
126
|
+
primary_key :id
|
127
|
+
String :type
|
128
|
+
DateTime :timestamp
|
129
|
+
foreign_key :habit_id, :habits, on_delete: :cascade
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get Habits dataset
|
134
|
+
def habits
|
135
|
+
return @habits if @habit
|
136
|
+
@habits = db[:habits]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get Entries dataset
|
140
|
+
def entries
|
141
|
+
return @entries if @entries
|
142
|
+
@entries = db[:entries]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|