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.
@@ -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(hbt, options)
11
- @percentage = false
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 list_all(@printer) if @all
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
- opts.on('-p', '--percentage', 'List habit(s) with completion rate') do
31
- @percentage = true
32
- end
33
-
34
- opts.on('-a', '--all', 'List all habits') do
35
- @all = true
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 list(name, printer)
46
- habit = @hbt.find(name) do
47
- return ErrorHandler.raise_habit_not_found(name)
48
- end
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 list_all(printer)
58
- return Util.blue 'No habits added yet.' if @hbt.habits.empty?
59
-
60
- title = Util.title Util.current_month
61
- progress = @hbt.habits.each_with_index.map do |h, index|
62
- space = @hbt.longest_name.length - h.name_length
63
- "#{index + 1}. " \
64
- "#{printer.print_latest_progress(h, space)}"
65
- end.join("\n")
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
- "#{title}#{progress}\n#{footer}"
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(hbt, options)
10
- super(hbt, options)
8
+ def initialize(store_path, options)
9
+ super(store_path, options)
11
10
  end
12
11
 
13
12
  def execute
14
- return remove(@names) if @names
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 remove(names)
25
- names.each do |name|
26
- habit = @hbt.find(name) do
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
- @hbt.habits.delete(habit)
31
- end
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
- Store.new(@hbt.habits, @hbt.output_file_name).save
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(hbt, options, is_done)
10
- @day = Date.today
8
+ def initialize(store_path, options, is_done)
9
+ @day = DateTime.now
11
10
  @is_done = is_done
12
- @remaining = false
13
- super(hbt, options)
11
+ @db = false
12
+ super(store_path, options)
14
13
  end
15
14
 
16
15
  def execute
17
- return update_remaining(@day, @is_done) if @remaining
18
- return update_all(@day, @is_done) if @all
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 update(names, day, is_done)
53
- names.each do |name|
54
- habit = if is_done
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
- Hbtrack::Util.green("#{action(is_done)} #{names.join(',')}!")
47
+ add_or_update_entry(store, id, day, is_done)
48
+ Hbtrack::Util.green("Update successfully!")
68
49
  end
69
50
 
70
- def update_all(day, is_done)
71
- @hbt.habits.each { |habit| habit.done(is_done, day) }
72
-
73
- Store.new(@hbt.habits, @hbt.output_file_name).save
74
-
75
- Hbtrack::Util.green("#{action(is_done)} all habits!")
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 update_remaining(day, is_done)
79
- @hbt.habits.each do |habit|
80
- habit.done(is_done, day) if habit.done_for(date: day).nil?
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
- private
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
- def action(is_done)
91
- is_done ? 'Done' : 'Undone'
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
@@ -10,4 +10,7 @@ module Hbtrack
10
10
 
11
11
  # File to store your data
12
12
  FILE_NAME = Dir.home + '/.habit'
13
+
14
+ # DB file
15
+ DB_PATH = Dir.home + '/.habit.db'
13
16
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hbtrack
4
+ module Database
5
+ # Data Abstraction
6
+ Habit = Struct.new(:title, :display_order)
7
+ Entry = Struct.new(:timestamp, :type)
8
+ end
9
+ end
@@ -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