beeminder 0.1.9 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.md ADDED
@@ -0,0 +1,11 @@
1
+ # Major Changes
2
+
3
+ ## 0.2 (2012/09/12)
4
+
5
+ - Don't need to specify user names any more. It's just `User.new token` now.
6
+ - Support for OAuth tokens with `:auth_type => :oauth`.
7
+ - Fewer unnecessary API calls.
8
+
9
+ ## 0.1 (2012/09/05)
10
+
11
+ - First release.
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
- bee = Beeminder::User.new "username", "token"
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/5]
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
- - [ ] don't load goal info twice (wtf was I thinking?)
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, "Path to config.", :type => :string, :default => "~/.beeminderrc"
24
- opt :list, "List all available goals."
25
- opt :user, "Beeminder account. (Optional. If not given, reads config or asks for it.)", :type => :string
26
- opt :token, "Auth token. (Optional. If not given, reads config or asks for it.)", :type => :string
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 File.exists? opts[:config]
36
- # read config to fill in the gaps
37
- auth = YAML.load File.open(opts[:config]) || {}
38
- opts[:user] ||= auth["user"]
39
- opts[:token] ||= auth["token"]
40
- end
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
- # ask for missing data
43
- if opts[:user].nil? or opts[:token].nil?
44
- opts[:user] ||= ask("Beeminder account:")
45
- opts[:token] ||= ask("Auth token:")
43
+ # still empty? ask!
44
+ if opts[:token].nil?
45
+ opts[:token] = ask("Auth token:")
46
46
 
47
- auth = {
48
- "user" => opts[:user],
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]}.'"
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[:user], opts[:token]
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
- bee.send goal, value, comment || ""
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
@@ -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, name
57
+ def initialize user, name_or_info
58
58
  @user = user
59
- @slug = name
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
- reload
70
+ _parse_info info
62
71
  end
63
72
 
64
73
  # Reload data from Beeminder.
65
74
  def reload
66
- info = @user.get "users/#{@user.name}/goals/#{@slug}.json"
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/#{@user.name}/goals/#{slug}/datapoints.json"
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/#{@user.name}/goals/#{@slug}.json", data
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/#{@user.name}/goals/#{@slug}/dial_road.json", dials
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/#{@user.name}/goals/#{@slug}/datapoints.json", data
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/#{@user.name}/goals/#{@slug}/datapoints/#{dp.id}.json"
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? Date
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
 
@@ -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
- info = get "users/#{@name}.json"
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
- info = get "users/#{@name}.json", :filter => filter.to_s
35
- goals = info["goals"].map do |goal|
36
- Beeminder::Goal.new self, goal
37
- end unless info["goals"].nil?
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 = {"auth_token" => @token}.merge(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 #{cmd} / #{data} failed: #{res.code} / #{res.body}"
149
+ raise "request failed: #{res.code} / #{res.body}"
132
150
  end
133
151
 
134
152
  json = res.body
@@ -1,3 +1,3 @@
1
1
  module Beeminder
2
- VERSION = "0.1.9"
2
+ VERSION = "0.2.1"
3
3
  end
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.9
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-09 00:00:00.000000000 Z
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: 2547600853104850137
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: 2547600853104850137
122
+ hash: 585995790058277298
106
123
  requirements: []
107
124
  rubyforge_project:
108
125
  rubygems_version: 1.8.23