habiter 0.2 → 0.3
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.
- data/README.rdoc +70 -0
- data/bin/habiter +126 -85
- metadata +3 -3
- data/README +0 -54
data/README.rdoc
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
habiter is a simple tool for maintaining good habits
|
2
|
+
|
3
|
+
It can be used with the 'don't break the chain' technique, like
|
4
|
+
Joe's Goals or Sciral Consistency, but adopts more positive
|
5
|
+
techniques and uses a readable and editable YAML text file as
|
6
|
+
storage.
|
7
|
+
|
8
|
+
= Installation
|
9
|
+
|
10
|
+
% gem install habiter -s http://gemcutter.org/
|
11
|
+
|
12
|
+
= Demo
|
13
|
+
|
14
|
+
~$ habiter did clean_room
|
15
|
+
clean_room is not a habit yet. create and complete it? (y/n) y
|
16
|
+
~$ habiter did jog
|
17
|
+
jog is not a habit yet. create and complete it? (y/n) y
|
18
|
+
~$ habiter log
|
19
|
+
jog |----------------------------+|(1/28)
|
20
|
+
project |-------------------------++-+|(3/28)
|
21
|
+
practice_music |-----------------------+++++-|(5/28)
|
22
|
+
read_30m |-----------------------+--++-|(3/28)
|
23
|
+
clean_room |-----------------------+-+---|(2/28)
|
24
|
+
|
25
|
+
= Scalars
|
26
|
+
|
27
|
+
Habiter also now supports scalars: you can input a value for your
|
28
|
+
activity, like
|
29
|
+
|
30
|
+
habiter did jog 5.5
|
31
|
+
habiter did heartbeat 78
|
32
|
+
|
33
|
+
And it will be recorded. There is no representation of this data yet,
|
34
|
+
and the representation within habiter will be minimal, only good output to
|
35
|
+
formats which are easily visualized.
|
36
|
+
|
37
|
+
= Install
|
38
|
+
|
39
|
+
Install with 'gem install habiter' if you have gemcutter set up as a
|
40
|
+
repository
|
41
|
+
|
42
|
+
= FAQ
|
43
|
+
|
44
|
+
* How do I add past data?
|
45
|
+
How do I delete recorded data?
|
46
|
+
How do I modify stuff I've recorded?
|
47
|
+
|
48
|
+
Edit ~/.habiter.yaml
|
49
|
+
|
50
|
+
* How do I reset habiter?
|
51
|
+
|
52
|
+
echo > ~/.habiter.yaml
|
53
|
+
|
54
|
+
= Changelog
|
55
|
+
|
56
|
+
== 3.0 (roadmap)
|
57
|
+
|
58
|
+
* Dump to CSV
|
59
|
+
|
60
|
+
== 2.5
|
61
|
+
|
62
|
+
* Allows 'did' in past and future
|
63
|
+
* Restructuring and cleanup internally
|
64
|
+
* Internal documentation
|
65
|
+
|
66
|
+
== 2.0
|
67
|
+
|
68
|
+
* Scalar input
|
69
|
+
|
70
|
+
Dedicated to Bryn Bellomy :)
|
data/bin/habiter
CHANGED
@@ -21,112 +21,153 @@ class String
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
@habiter_file = File.expand_path(
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
class Habiter
|
25
|
+
def initialize(data_path = '~/.habiter.yaml')
|
26
|
+
@habiter_file = File.expand_path(data_path)
|
27
|
+
begin
|
28
|
+
@habits = YAML::load_file(@habiter_file)
|
29
|
+
rescue
|
30
|
+
File.open(@habiter_file, 'w').close()
|
31
|
+
end
|
32
|
+
@habits = {} unless @habits
|
33
|
+
end
|
32
34
|
end
|
33
35
|
|
34
|
-
@habits = {} unless @habits
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
37
|
+
class HabiterBin < Habiter
|
38
|
+
def dump(options={})
|
39
|
+
# Output this YAML datastore as normalized CSV for processing by
|
40
|
+
# whatever wants it
|
41
|
+
cols = @habits.length
|
42
|
+
i = 0 # TODO: AVOID THIS
|
43
|
+
rotated = {}
|
44
|
+
@habits.each do |habit, times|
|
45
|
+
times.collect! do |time|
|
46
|
+
this_time = normalize_time(time)
|
47
|
+
day = rotated.fetch(this_time, Array.new(cols, 0))
|
48
|
+
day[i] = (day[i].nil?) ? 1 : day[i] + 1
|
49
|
+
rotated[this_time] = day
|
50
|
+
end
|
51
|
+
i += 1
|
52
|
+
end
|
53
|
+
puts rotated.sort.keys[0]
|
46
54
|
end
|
47
|
-
end
|
48
55
|
|
49
|
-
def did(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
+
def did(key, scalar = false, yester = 0)
|
57
|
+
# Record that somebody did something. Expects an unadulterated
|
58
|
+
# args array, from which it takes the second and optionally third
|
59
|
+
# parameters
|
60
|
+
day = Date.today - yester
|
61
|
+
new_record = (scalar) ? {day => scalar} : day
|
62
|
+
if not @habits.has_key?(key)
|
63
|
+
print "#{key} is not a habit yet. create and complete it? (y/n) "
|
64
|
+
if STDIN.gets.chomp == 'y'
|
65
|
+
@habits[key] = [new_record]
|
66
|
+
end
|
67
|
+
else
|
68
|
+
@habits[key] << new_record
|
69
|
+
end
|
70
|
+
File.open(@habiter_file, 'w') do |f|
|
71
|
+
YAML.dump(@habits, f)
|
56
72
|
end
|
57
|
-
else
|
58
|
-
@habits[key] << new_record
|
59
|
-
end
|
60
|
-
File.open(@habiter_file, 'w') do |f|
|
61
|
-
YAML.dump(@habits, f)
|
62
73
|
end
|
63
|
-
end
|
64
74
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
75
|
+
def log(options={})
|
76
|
+
# Display a basic visualization of recent task completion
|
77
|
+
options[:days] = 28 unless options[:days]
|
78
|
+
name_column_width = @habits.max {|a,b| a[0].length <=> b[0].length}[0].length
|
79
|
+
day = Date.today - options[:days]
|
80
|
+
if options[:verbose]
|
81
|
+
print " " * (name_column_width + 2)
|
82
|
+
(0..options[:days]).each do |i|
|
83
|
+
print((day + (i)).strftime('%a')[0].chr.swapcase.colorize(37))
|
84
|
+
end
|
85
|
+
puts
|
74
86
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
times.collect! do |time|
|
82
|
-
if time.is_a? Hash
|
83
|
-
# For scalar values, normalize
|
84
|
-
time = time.keys.first
|
87
|
+
daily_counts = Array.new(29, 0)
|
88
|
+
@habits.each do |habit, times|
|
89
|
+
day = Date.today - (options[:days])
|
90
|
+
print((sprintf("%#{name_column_width}s ", habit) + "|").colorize(37))
|
91
|
+
times.collect! do |time|
|
92
|
+
normalize_time(time)
|
85
93
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
94
|
+
done_in_last_28, week, pre_week = 0, 0, 0
|
95
|
+
(0..options[:days]).each do |i|
|
96
|
+
if times.include? day
|
97
|
+
print "+".colorize(47)
|
98
|
+
daily_counts[i] += 1
|
99
|
+
done_in_last_28 += 1
|
100
|
+
pre_week += 1 unless i >= 20
|
101
|
+
week += 1 unless i < 20
|
102
|
+
else
|
103
|
+
print "-".colorize(37)
|
104
|
+
end
|
105
|
+
day += 1
|
91
106
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
if times.include? day
|
96
|
-
print "+".colorize(47)
|
97
|
-
daily_counts[i] += 1
|
98
|
-
done_in_last_28 += 1
|
99
|
-
pre_week += 1 unless i >= 20
|
100
|
-
week += 1 unless i < 20
|
107
|
+
if ((pre_week / 3) > (week + 2))
|
108
|
+
print "|".colorize(37)
|
109
|
+
print "(#{done_in_last_28}/28)".colorize(31)
|
101
110
|
else
|
102
|
-
print "
|
111
|
+
print "|".colorize(37)
|
112
|
+
print "(#{done_in_last_28}/28)".colorize(32)
|
103
113
|
end
|
104
|
-
|
114
|
+
puts
|
105
115
|
end
|
106
|
-
if
|
107
|
-
print "
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
116
|
+
if options[:verbose]
|
117
|
+
print " " * (name_column_width + 2)
|
118
|
+
(0..options[:days]).each do |i|
|
119
|
+
print((daily_counts[i] > 0) ? daily_counts[i] : " ")
|
120
|
+
end
|
121
|
+
puts
|
112
122
|
end
|
113
|
-
puts
|
114
123
|
end
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
124
|
+
end
|
125
|
+
|
126
|
+
def normalize_time(time)
|
127
|
+
if time.is_a? Hash
|
128
|
+
time = time.keys.first
|
129
|
+
end
|
130
|
+
if time.is_a? Time
|
131
|
+
time.to_date
|
132
|
+
elsif time.is_a? Date
|
133
|
+
time
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
hm = HabiterBin.new
|
141
|
+
|
142
|
+
#
|
143
|
+
# Command-line handling
|
144
|
+
#
|
145
|
+
|
146
|
+
options = {}
|
147
|
+
optparse = OptionParser.new do |opts|
|
148
|
+
opts.banner =
|
149
|
+
"Usage:
|
150
|
+
habiter did your_habit
|
151
|
+
habiter log"
|
152
|
+
options[:verbose] = false
|
153
|
+
opts.on('-v', '--verbose', "Show weekday letters for log") do
|
154
|
+
options[:verbose] = true
|
155
|
+
end
|
156
|
+
options[:yester] = 0
|
157
|
+
opts.on('-y', '--yester [DAYS]',
|
158
|
+
"Log an event in the past, given a number of days") do |days|
|
159
|
+
options[:yester] = days.to_i
|
121
160
|
end
|
122
161
|
end
|
123
162
|
|
124
163
|
optparse.parse!
|
125
164
|
|
126
165
|
if ARGV[0] == 'did'
|
127
|
-
|
166
|
+
key = ARGV[1]
|
167
|
+
scalar = (ARGV.length > 3) ? ARGV[2] : false
|
168
|
+
hm.did(key, scalar, options[:yester])
|
128
169
|
elsif ARGV[0] == 'log'
|
129
|
-
log(options)
|
130
|
-
|
131
|
-
|
170
|
+
hm.log(options)
|
171
|
+
elsif ARGV[0] == 'dump'
|
172
|
+
hm.dump(options)
|
132
173
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: habiter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.3"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom MacWright
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-13 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -23,7 +23,7 @@ extra_rdoc_files: []
|
|
23
23
|
|
24
24
|
files:
|
25
25
|
- bin/habiter
|
26
|
-
- README
|
26
|
+
- README.rdoc
|
27
27
|
has_rdoc: true
|
28
28
|
homepage: http://github.com/tmcw/habiter
|
29
29
|
licenses: []
|
data/README
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
habiter is a simple tool for maintaining good habits
|
2
|
-
|
3
|
-
It can be used with the 'don't break the chain' technique, like
|
4
|
-
Joe's Goals or Sciral Consistency, but adopts more positive
|
5
|
-
techniques and uses a readable and editable YAML text file as
|
6
|
-
storage.
|
7
|
-
|
8
|
-
Demo
|
9
|
-
----
|
10
|
-
|
11
|
-
~$ habiter did clean_room
|
12
|
-
clean_room is not a habit yet. create and complete it? (y/n) y
|
13
|
-
~$ habiter did jog
|
14
|
-
jog is not a habit yet. create and complete it? (y/n) y
|
15
|
-
~$ habiter log
|
16
|
-
jog |----------------------------+|(1/28)
|
17
|
-
project |-------------------------++-+|(3/28)
|
18
|
-
practice_music |-----------------------+++++-|(5/28)
|
19
|
-
read_30m |-----------------------+--++-|(3/28)
|
20
|
-
clean_room |-----------------------+-+---|(2/28)
|
21
|
-
|
22
|
-
Scalars
|
23
|
-
-------
|
24
|
-
|
25
|
-
Habiter also now supports scalars: you can input a value for your
|
26
|
-
activity, like
|
27
|
-
|
28
|
-
habiter did jog 5.5
|
29
|
-
habiter did heartbeat 78
|
30
|
-
|
31
|
-
And it will be recorded. There is no representation of this data yet,
|
32
|
-
and the representation within habiter will be minimal, only good output to
|
33
|
-
formats which are easily visualized.
|
34
|
-
|
35
|
-
Install
|
36
|
-
-------
|
37
|
-
|
38
|
-
Install with 'gem install habiter' if you have gemcutter set up as a
|
39
|
-
repository
|
40
|
-
|
41
|
-
FAQ
|
42
|
-
---
|
43
|
-
|
44
|
-
* How do I add past data?
|
45
|
-
How do I delete recorded data?
|
46
|
-
How do I modify stuff I've recorded?
|
47
|
-
|
48
|
-
Edit ~/.habiter.yaml
|
49
|
-
|
50
|
-
* How do I reset habiter?
|
51
|
-
|
52
|
-
echo > ~/.habiter.yaml
|
53
|
-
|
54
|
-
Dedicated to Bryn Bellomy :)
|