beeminder 0.1.9 → 0.2.1
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/CHANGES.md +11 -0
- data/README.md +5 -1
- data/TODO +3 -6
- data/beeminder.gemspec +1 -0
- data/bin/beemind +30 -24
- data/lib/beeminder/goals.rb +35 -21
- data/lib/beeminder/user.rb +29 -11
- data/lib/beeminder/version.rb +1 -1
- metadata +21 -4
data/CHANGES.md
ADDED
data/README.md
CHANGED
@@ -20,7 +20,11 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
First, get your token [here](https://www.beeminder.com/api/v1/auth_token.json) and log in:
|
22
22
|
|
23
|
-
|
23
|
+
# normal login
|
24
|
+
bee = Beeminder::User.new "token"
|
25
|
+
|
26
|
+
# oauth
|
27
|
+
bee = Beeminder::User.new "token", :auth_type => :oauth
|
24
28
|
|
25
29
|
Now you can do a bunch of stuff. You'll probably want to send a new datapoint:
|
26
30
|
|
data/TODO
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
* TODO [3
|
2
|
-
- [X] document code
|
3
|
-
- [ ] use token to identify user, remove name argument
|
4
|
-
- [X] beemind binary
|
5
|
-
- [X] implement actual API calls
|
1
|
+
* TODO [0/3]
|
6
2
|
- [ ] add a few more sanity checks
|
7
|
-
- [ ]
|
3
|
+
- [ ] cache goals / datapoints
|
4
|
+
- [ ] add update function to Datapoint
|
data/beeminder.gemspec
CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
|
19
|
+
gem.add_dependency 'chronic', '~> 0.7'
|
19
20
|
gem.add_dependency 'json'
|
20
21
|
gem.add_dependency 'highline', '~> 1.6'
|
21
22
|
gem.add_dependency 'trollop', '~> 2'
|
data/bin/beemind
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# coding: utf-8
|
3
3
|
|
4
|
+
require 'chronic'
|
4
5
|
require 'highline/import'
|
5
6
|
require 'trollop'
|
6
7
|
require 'yaml'
|
@@ -20,41 +21,41 @@ usage = "usage: beemind goal value [comment]"
|
|
20
21
|
opts = Trollop::options do
|
21
22
|
banner usage
|
22
23
|
|
23
|
-
opt :config,
|
24
|
-
opt :list,
|
25
|
-
opt :
|
26
|
-
opt :
|
24
|
+
opt :config, "Path to config.", :type => :string, :default => "~/.beeminderrc"
|
25
|
+
opt :list, "List all available goals."
|
26
|
+
opt :token, "Use this auth token instead of config. (Optional. Will ask for it if none given.)", :type => :string
|
27
|
+
opt :date, "Set a manual date. Uses Chronic syntax.", :type => :string, :default => "now"
|
27
28
|
end
|
28
29
|
|
29
30
|
Trollop::die usage if not (2..3).include?(ARGV.size) and not opts[:list]
|
30
31
|
goal, value, comment = ARGV unless opts[:list]
|
31
32
|
|
32
33
|
opts[:config] = File.expand_path opts[:config]
|
33
|
-
opts[:no_config] = true if opts[:user] or opts[:token]
|
34
34
|
|
35
|
-
if
|
36
|
-
# read config
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
if not opts[:token]
|
36
|
+
# read config
|
37
|
+
if File.exists? opts[:config]
|
38
|
+
# read config to fill in the gaps
|
39
|
+
auth = YAML.load File.open(opts[:config]) || {}
|
40
|
+
opts[:token] ||= auth["token"]
|
41
|
+
end
|
41
42
|
|
42
|
-
#
|
43
|
-
if opts[:
|
44
|
-
|
45
|
-
opts[:token] ||= ask("Auth token:")
|
43
|
+
# still empty? ask!
|
44
|
+
if opts[:token].nil?
|
45
|
+
opts[:token] = ask("Auth token:")
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
# save config for later
|
48
|
+
auth = {
|
49
|
+
"token" => opts[:token]
|
50
|
+
}
|
51
|
+
File.open(opts[:config], "w+") {|f| YAML.dump auth, f}
|
52
|
+
File.chmod 0600, opts[:config]
|
53
|
+
puts "Written config to '#{opts[:config]}.'"
|
54
|
+
end
|
54
55
|
end
|
55
56
|
|
56
57
|
# login
|
57
|
-
bee = Beeminder::User.new opts[:
|
58
|
+
bee = Beeminder::User.new opts[:token]
|
58
59
|
|
59
60
|
if opts[:list]
|
60
61
|
# list all available goals
|
@@ -63,5 +64,10 @@ if opts[:list]
|
|
63
64
|
puts " #{goal.slug} (#{goal.title})"
|
64
65
|
end
|
65
66
|
else
|
66
|
-
|
67
|
+
date = Chronic.parse(opts[:date])
|
68
|
+
Trollop::die "invalid date" if date.nil?
|
69
|
+
|
70
|
+
g = bee.goal goal
|
71
|
+
dp = Beeminder::Datapoint.new :timestamp => date, :value => value, :comment => (comment || "")
|
72
|
+
g.add dp
|
67
73
|
end
|
data/lib/beeminder/goals.rb
CHANGED
@@ -54,34 +54,33 @@ module Beeminder
|
|
54
54
|
# @return [Beeminder::User] User that owns this goal.
|
55
55
|
attr_reader :user
|
56
56
|
|
57
|
-
def initialize user,
|
57
|
+
def initialize user, name_or_info
|
58
58
|
@user = user
|
59
|
-
|
59
|
+
|
60
|
+
info =
|
61
|
+
case name_or_info
|
62
|
+
when String
|
63
|
+
@user.get "users/me/goals/#{name_or_info}.json"
|
64
|
+
when Hash
|
65
|
+
name_or_info
|
66
|
+
else
|
67
|
+
raise ArgumentError, "`name_or_info` must be String (slug) or Hash (preloaded info)"
|
68
|
+
end
|
60
69
|
|
61
|
-
|
70
|
+
_parse_info info
|
62
71
|
end
|
63
72
|
|
64
73
|
# Reload data from Beeminder.
|
65
74
|
def reload
|
66
|
-
info = @user.get "users
|
67
|
-
|
68
|
-
# set variables
|
69
|
-
info.each do |k,v|
|
70
|
-
instance_variable_set "@#{k}", v
|
71
|
-
end
|
72
|
-
|
73
|
-
# some conversions
|
74
|
-
@goaldate = DateTime.strptime(@goaldate.to_s, '%s') unless @goaldate.nil?
|
75
|
-
@goal_type = @goal_type.to_sym unless @goal_type.nil?
|
76
|
-
@losedate = DateTime.strptime(@losedate.to_s, '%s') unless @losedate.nil?
|
77
|
-
@updated_at = DateTime.strptime(@updated_at.to_s, '%s')
|
75
|
+
info = @user.get "users/me/goals/#{@slug}.json"
|
76
|
+
parse_info info
|
78
77
|
end
|
79
78
|
|
80
79
|
# List of datapoints.
|
81
80
|
#
|
82
81
|
# @return [Array<Beeminder::Datapoint>] returns list of datapoints
|
83
82
|
def datapoints
|
84
|
-
info = @user.get "users
|
83
|
+
info = @user.get "users/me/goals/#{slug}/datapoints.json"
|
85
84
|
datapoints = info.map{|d| Datapoint.new d}
|
86
85
|
|
87
86
|
datapoints
|
@@ -98,7 +97,7 @@ module Beeminder
|
|
98
97
|
"datapublic" => @datapublic || false,
|
99
98
|
}
|
100
99
|
|
101
|
-
@user.put "users
|
100
|
+
@user.put "users/me/goals/#{@slug}.json", data
|
102
101
|
end
|
103
102
|
|
104
103
|
# Send new road setting to Beeminder.
|
@@ -109,7 +108,7 @@ module Beeminder
|
|
109
108
|
|
110
109
|
dials["goaldate"] = dials["goaldate"].strftime('%s') unless dials["goaldate"].nil?
|
111
110
|
|
112
|
-
@user.post "users
|
111
|
+
@user.post "users/me/goals/#{@slug}/dial_road.json", dials
|
113
112
|
end
|
114
113
|
|
115
114
|
# Add one or more datapoints to the goal.
|
@@ -124,7 +123,7 @@ module Beeminder
|
|
124
123
|
"sendmail" => opts[:sendmail] || false
|
125
124
|
}.merge(dp.to_hash)
|
126
125
|
|
127
|
-
@user.post "users
|
126
|
+
@user.post "users/me/goals/#{@slug}/datapoints.json", data
|
128
127
|
end
|
129
128
|
end
|
130
129
|
|
@@ -134,7 +133,7 @@ module Beeminder
|
|
134
133
|
def delete datapoints
|
135
134
|
datapoints = [*datapoints]
|
136
135
|
datapoints.each do |dp|
|
137
|
-
@user.delete "/users
|
136
|
+
@user.delete "/users/me/goals/#{@slug}/datapoints/#{dp.id}.json"
|
138
137
|
end
|
139
138
|
end
|
140
139
|
|
@@ -151,6 +150,21 @@ module Beeminder
|
|
151
150
|
"datapublic" => @datapublic || false,
|
152
151
|
}
|
153
152
|
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def _parse_info info
|
157
|
+
# set variables
|
158
|
+
info.each do |k,v|
|
159
|
+
instance_variable_set "@#{k}", v
|
160
|
+
end
|
161
|
+
|
162
|
+
# some conversions
|
163
|
+
@goaldate = DateTime.strptime(@goaldate.to_s, '%s') unless @goaldate.nil?
|
164
|
+
@goal_type = @goal_type.to_sym unless @goal_type.nil?
|
165
|
+
@losedate = DateTime.strptime(@losedate.to_s, '%s') unless @losedate.nil?
|
166
|
+
@updated_at = DateTime.strptime(@updated_at.to_s, '%s')
|
167
|
+
end
|
154
168
|
end
|
155
169
|
|
156
170
|
class Datapoint
|
@@ -180,7 +194,7 @@ module Beeminder
|
|
180
194
|
@comment ||= ""
|
181
195
|
|
182
196
|
# some conversions
|
183
|
-
@timestamp = DateTime.strptime(@timestamp.to_s, '%s') unless @timestamp.is_a?
|
197
|
+
@timestamp = DateTime.strptime(@timestamp.to_s, '%s') unless @timestamp.is_a?(Date) || @timestamp.is_a?(Time)
|
184
198
|
@updated_at = DateTime.strptime(@updated_at.to_s, '%s') unless @updated_at.nil?
|
185
199
|
end
|
186
200
|
|
data/lib/beeminder/user.rb
CHANGED
@@ -13,13 +13,31 @@ module Beeminder
|
|
13
13
|
|
14
14
|
# @return [String] Timezone.
|
15
15
|
attr_reader :timezone
|
16
|
-
|
17
|
-
def initialize name, token
|
18
|
-
@name = name
|
19
|
-
@token = token
|
20
16
|
|
21
|
-
|
17
|
+
# @return [Symbol] Type of user, can be `:personal` (default) or `:oauth`.
|
18
|
+
attr_reader :auth_type
|
19
|
+
|
20
|
+
def initialize token, opts={}
|
21
|
+
opts = {
|
22
|
+
:auth_type => :personal,
|
23
|
+
}.merge(opts)
|
24
|
+
|
25
|
+
@token = token
|
26
|
+
@auth_type = opts[:auth_type]
|
27
|
+
|
28
|
+
@token_type =
|
29
|
+
case @auth_type
|
30
|
+
when :personal
|
31
|
+
"auth_token"
|
32
|
+
when :oauth
|
33
|
+
"access_token"
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Auth type not supported, must be :personal or :oauth."
|
36
|
+
end
|
22
37
|
|
38
|
+
info = get "users/me.json"
|
39
|
+
|
40
|
+
@name = info["username"]
|
23
41
|
@timezone = info["timezone"]
|
24
42
|
@updated_at = DateTime.strptime(info["updated_at"].to_s, '%s')
|
25
43
|
end
|
@@ -31,10 +49,10 @@ module Beeminder
|
|
31
49
|
def goals filter=:all
|
32
50
|
raise "invalid goal filter: #{filter}" unless [:all, :frontburner, :backburner].include? filter
|
33
51
|
|
34
|
-
|
35
|
-
goals
|
36
|
-
Beeminder::Goal.new self,
|
37
|
-
end
|
52
|
+
goals = get("users/#{@name}/goals.json", :filter => filter.to_s) || []
|
53
|
+
goals.map! do |info|
|
54
|
+
Beeminder::Goal.new self, info
|
55
|
+
end
|
38
56
|
|
39
57
|
goals || []
|
40
58
|
end
|
@@ -102,7 +120,7 @@ module Beeminder
|
|
102
120
|
# Establish HTTPS connection to API.
|
103
121
|
def _connection type, cmd, data
|
104
122
|
api = "https://www.beeminder.com/api/v1/#{cmd}"
|
105
|
-
data = {
|
123
|
+
data = {@token_type => @token}.merge(data)
|
106
124
|
|
107
125
|
url = URI.parse(api)
|
108
126
|
http = Net::HTTP.new(url.host, url.port)
|
@@ -128,7 +146,7 @@ module Beeminder
|
|
128
146
|
|
129
147
|
res = http.request(req)
|
130
148
|
if not res.is_a? Net::HTTPSuccess
|
131
|
-
raise "request
|
149
|
+
raise "request failed: #{res.code} / #{res.body}"
|
132
150
|
end
|
133
151
|
|
134
152
|
json = res.body
|
data/lib/beeminder/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beeminder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: chronic
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.7'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.7'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: json
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,6 +84,7 @@ extensions: []
|
|
68
84
|
extra_rdoc_files: []
|
69
85
|
files:
|
70
86
|
- .gitignore
|
87
|
+
- CHANGES.md
|
71
88
|
- Gemfile
|
72
89
|
- LICENSE.txt
|
73
90
|
- README.md
|
@@ -93,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
110
|
version: '0'
|
94
111
|
segments:
|
95
112
|
- 0
|
96
|
-
hash:
|
113
|
+
hash: 585995790058277298
|
97
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
115
|
none: false
|
99
116
|
requirements:
|
@@ -102,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
119
|
version: '0'
|
103
120
|
segments:
|
104
121
|
- 0
|
105
|
-
hash:
|
122
|
+
hash: 585995790058277298
|
106
123
|
requirements: []
|
107
124
|
rubyforge_project:
|
108
125
|
rubygems_version: 1.8.23
|