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 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