beeminder 0.1.0

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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in beeminder-gem.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1 @@
1
+ Do whatever you want. This is just a simple convenience gem, not some military secret.
data/README ADDED
@@ -0,0 +1,47 @@
1
+ # Beeminder
2
+
3
+ Convenient access to [Beeminder](http://www.beeminder.com)'s API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'beeminder'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install beeminder-gem
18
+
19
+ ## Usage
20
+
21
+ First, get your token [here](https://www.beeminder.com/api/v1/auth_token.json) and log in:
22
+
23
+ bee = Beeminder::User.new "username", "token"
24
+
25
+ Now you can do a bunch of stuff. You'll probably want to send a new datapoint:
26
+
27
+ # short form
28
+ bee.send "weight", 86.3
29
+
30
+ # long form
31
+ goal = bee.goal "weight"
32
+ dp = Beeminder::Datapoint.new :value => 86.3, :comment => "I loves cheeseburgers :3"
33
+ goal.add dp
34
+
35
+ Or you can find all goals of a certain type:
36
+
37
+ odometer_goals = bee.goals.select {|g| g.goal_type == :biker}
38
+
39
+ Or maybe show the last updated graph in a widget somewhere:
40
+
41
+ puts bee.goals.max_by(:updated_at).graph_url
42
+
43
+ There's also a simple tool called `beemind` to update graphs:
44
+
45
+ $ beemind pushups 4
46
+
47
+ Check the [gem doc]() and [API](https://www.beeminder.com/api-docs) for what else you can do.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ * TODO [3/5]
2
+ - [X] document code
3
+ - [ ] handle oauth tokens?
4
+ - [X] beemind binary
5
+ - [X] implement actual API calls
6
+ - [ ] add a few more sanity checks
data/beeminder.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'beeminder/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "beeminder"
8
+ gem.version = Beeminder::VERSION
9
+ gem.authors = ["muflax"]
10
+ gem.email = ["mail@muflax.com"]
11
+ gem.description = "Convenient access to Beeminder's API."
12
+ gem.summary = "access Beeminder API"
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_dependency 'highline', '~> 1.6'
20
+ end
data/bin/beemind ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'yaml'
5
+
6
+ # load library
7
+ file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
8
+ lib = File.join File.dirname(file), "/../lib/beeminder"
9
+
10
+ if File.exists? lib
11
+ # using local version
12
+ require lib
13
+ else
14
+ require 'beeminder'
15
+ end
16
+
17
+ config = "#{Dir.home}/.beeminderrc"
18
+ if not File.exists? config
19
+ # create config
20
+ require 'highline/import'
21
+ yaml = {
22
+ "account" => ask("Beeminder account:"),
23
+ "token" => ask("Auth token:")
24
+ }
25
+ File.open(config, "w+") {|f| YAML.dump yaml, f}
26
+ File.chmod 0600, config
27
+ puts "Written config to '#{config}.'"
28
+ end
29
+
30
+ # load config
31
+ yaml = YAML.load File.open(config)
32
+
33
+ # login
34
+ bee = Beeminder::User.new yaml["account"], yaml["token"]
35
+
36
+ if not (2..3).include? ARGV.size
37
+ puts "usage: beemind goal value [comment]"
38
+ exit 1
39
+ end
40
+
41
+ goal, value, comment = ARGV
42
+
43
+ bee.send goal, value, comment || ""
data/lib/beeminder.rb ADDED
@@ -0,0 +1,11 @@
1
+ # coding: utf-8
2
+
3
+ require 'date'
4
+ require 'json'
5
+ require 'net/https'
6
+ require 'uri'
7
+
8
+ # local libs
9
+ Dir["#{File.join(File.dirname(__FILE__), "beeminder")}/*.rb"].each do |lib|
10
+ require lib
11
+ end
@@ -0,0 +1,198 @@
1
+ # coding: utf-8
2
+
3
+ module Beeminder
4
+ class Goal
5
+ # @return [String] The final part of the URL of the goal, used as an identifier.
6
+ attr_accessor :slug
7
+
8
+ # @return [DateTime] Last time this goal was updated.
9
+ attr_reader :updated_at
10
+
11
+ # @return [String] The title that the user specified for the goal.
12
+ attr_accessor :title
13
+
14
+ # @return [DateTime] Goal date.
15
+ attr_reader :goaldate
16
+
17
+ # @return [Numeric] Goal value.
18
+ attr_reader :goalval
19
+
20
+ # @return [Numeric] The slope of the (final section of the) yellow brick road.
21
+ attr_reader :rate
22
+
23
+ # @return [Symbol] One of the following symbols:
24
+ # - `:fatloser`: Weight loss
25
+ # - `:hustler`: Do More
26
+ # - `:biker`: Odometer
27
+ # - `:inboxer`: Inbox Fewer
28
+ # - `:gainer`: Gain Weight
29
+ # - `:drinker`: Set a Limit
30
+ # - `:custom`: Full access to the underlying goal parameters
31
+ attr_reader :goal_type
32
+
33
+ # @return [DateTime] Date of derailment.
34
+ attr_reader :losedate
35
+
36
+ # @return [String] URL for the goal's graph image.
37
+ attr_reader :graph_url
38
+
39
+ # @return [Numeric] Panic threshold. How long before derailment to panic.
40
+ attr_accessor :panic
41
+
42
+ # @return [true|false] Whether the graph is currently being updated to reflect new data.
43
+ attr_reader :queued
44
+
45
+ # @return [true|false] Whether the graph was created in test mode.
46
+ attr_accessor :ephem
47
+
48
+ # @return [true|false] Whether you have to be signed in as owner of the goal to view it.
49
+ attr_accessor :secret
50
+
51
+ # @return [true|false] Whether you have to be signed in as the owner of the goal to view the datapoints.
52
+ attr_accessor :datapublic
53
+
54
+ # @return [Beeminder::User] User that owns this goal.
55
+ attr_reader :user
56
+
57
+ def initialize user, name
58
+ @user = user
59
+ @slug = name
60
+
61
+ reload
62
+ end
63
+
64
+ # Reload data from Beeminder.
65
+ 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')
78
+ end
79
+
80
+ # List of datapoints.
81
+ #
82
+ # @return [Array<Beeminder::Datapoint>] returns list of datapoints
83
+ def datapoints
84
+ info = @user.get "users/#{@user.name}/goals/#{slug}/datapoints.json"
85
+ datapoints = info.map{|d| Datapoint.new d}
86
+
87
+ datapoints
88
+ end
89
+
90
+ # Send updated meta-data to Beeminder.
91
+ def update
92
+ data = {
93
+ "slug" => @slug,
94
+ "title" => @title,
95
+ "ephem" => @ephem || false,
96
+ "panic" => @panic || 86400,
97
+ "secret" => @secret || false,
98
+ "datapublic" => @datapublic || false,
99
+ }
100
+
101
+ @user.put "users/#{@user.name}/goals/#{@slug}.json", data
102
+ end
103
+
104
+ # Send new road setting to Beeminder.
105
+ #
106
+ # @param dials [Hash] Set exactly two of `"rate"`, `"goaldate"` and `"goalval"`. The third is implied.
107
+ def dial_road dials={}
108
+ raise "Set exactly two dials." if dials.keys.size != 2
109
+
110
+ dials["goaldate"] = dials["goaldate"].strftime('%s') unless dials["goaldate"].nil?
111
+
112
+ @user.post "users/#{@user.name}/goals/#{@slug}/dial_road.json", dials
113
+ end
114
+
115
+ # Add one or more datapoints to the goal.
116
+ #
117
+ # @param datapoints [Beeminder::Datapoint, Array<Beeminder::Datapoint>] one or more datapoints to add to goal
118
+ def add datapoints, opts={}
119
+ datapoints = [*datapoints]
120
+
121
+ # FIXME create_all doesn't work because Ruby's POST encoding of arrays is broken.
122
+ datapoints.each do |dp|
123
+ data = {
124
+ "sendmail" => opts[:sendmail] || false
125
+ }.merge(dp.to_hash)
126
+
127
+ @user.post "users/#{@user.name}/goals/#{@slug}/datapoints.json", data
128
+ end
129
+ end
130
+
131
+ # Delete one or more datapoints from the goal.
132
+ #
133
+ # @param datapoints [Beeminder::Datapoint, Array<Beeminder::Datapoint>] one or more datapoints to delete
134
+ def delete datapoints
135
+ datapoints = [*datapoints]
136
+ datapoints.each do |dp|
137
+ @user.delete "/users/#{@user.name}/goals/#{@slug}/datapoints/#{dp.id}.json"
138
+ end
139
+ end
140
+
141
+ # Convert datapoint to hash for POSTing.
142
+ # @return [Hash]
143
+ def to_hash
144
+ {
145
+ "slug" => @slug,
146
+ "title" => @title,
147
+ "goal_type" => @goal_type.to_s,
148
+ "ephem" => @ephem || false,
149
+ "panic" => @panic || 86400,
150
+ "secret" => @secret || false,
151
+ "datapublic" => @datapublic || false,
152
+ }
153
+ end
154
+ end
155
+
156
+ class Datapoint
157
+ # @return [DateTime] Time of the datapoint.
158
+ attr_accessor :timestamp
159
+
160
+ # @return [Numeric] Value of the datapoint.
161
+ attr_accessor :value
162
+
163
+ # @return [String] An optional comment about the datapoint.
164
+ attr_accessor :comment
165
+
166
+ # @return [String] A unique ID, used to identify a datapoint when deleting or editing it.
167
+ attr_reader :id
168
+
169
+ # @return [DateTime] The time that this datapoint was entered or last updated.
170
+ attr_reader :updated_at
171
+
172
+ def initialize info
173
+ default = {
174
+ timestamp: DateTime.now.strftime('%s')
175
+ }
176
+ info = default.merge(info)
177
+
178
+ # set variables
179
+ info.each do |k,v|
180
+ instance_variable_set "@#{k}", v
181
+ end
182
+
183
+ # some conversions
184
+ @timestamp = DateTime.strptime(@timestamp.to_s, '%s')
185
+ @updated_at = DateTime.strptime(@updated_at.to_s, '%s') unless @updated_at.nil?
186
+ end
187
+
188
+ # Convert datapoint to hash for POSTing.
189
+ # @return [Hash]
190
+ def to_hash
191
+ {
192
+ "timestamp" => @timestamp.strftime('%s'),
193
+ "value" => @value || 0,
194
+ "comment" => @comment || "",
195
+ }
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,142 @@
1
+ # coding: utf-8
2
+
3
+ module Beeminder
4
+ class User
5
+ # @return [String] User name.
6
+ attr_reader :name
7
+
8
+ # @return [String] Auth token.
9
+ attr_reader :token
10
+
11
+ # @return [DateTime] Last time user made any changes.
12
+ attr_reader :updated_at
13
+
14
+ # @return [String] Timezone.
15
+ attr_reader :timezone
16
+
17
+ def initialize name, token
18
+ @name = name
19
+ @token = token
20
+
21
+ info = get "users/#{@name}.json"
22
+
23
+ @timezone = info["timezone"]
24
+ @updated_at = DateTime.strptime(info["updated_at"].to_s, '%s')
25
+ end
26
+
27
+ # List of goals.
28
+ #
29
+ # @param filter [Symbol] filter goals, can be `:all` (default), `:frontburner` or `:backburner`
30
+ # @ return [Array<Beeminder::Goal>] returns list of goals
31
+ def goals filter=:all
32
+ raise "invalid goal filter: #{filter}" unless [:all, :frontburner, :backburner].include? filter
33
+
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?
38
+
39
+ goals || []
40
+ end
41
+
42
+ # Return specific goal.
43
+ #
44
+ # @param name [String] Name of the goal.
45
+ # @return [Beeminder::Goal] Returns goal.
46
+ def goal name
47
+ Beeminder::Goal.new self, name
48
+ end
49
+
50
+ # Convenience function to add datapoint to a goal.
51
+ #
52
+ # @param name [String] Goal name.
53
+ # @param value [Numeric] Datapoint value.
54
+ # @param comment [String] Optional comment.
55
+ def send name, value, comment=""
56
+ goal = self.goal name
57
+ dp = Beeminder::Datapoint.new value: value, comment: comment
58
+ goal.add dp
59
+ end
60
+
61
+ # Create new goal.
62
+ #
63
+ # @param opts [Hash] Goal options.
64
+ def create_goal opts={}
65
+ post "users/#{@name}/goals.json", opts
66
+ end
67
+
68
+ # Send GET request to API.
69
+ #
70
+ # @param cmd [String] the API command, like `users/#{user.name}.json`
71
+ # @param data [Hash] data to send; auth_token is included by default (optional)
72
+ def get cmd, data={}
73
+ _connection :get, cmd, data
74
+ end
75
+
76
+ # Send POST request to API.
77
+ #
78
+ # @param cmd [String] the API command, like `users/#{user.name}.json`
79
+ # @param data [Hash] data to send; auth_token is included by default (optional)
80
+ def post cmd, data={}
81
+ _connection :post, cmd, data
82
+ end
83
+
84
+ # Send DELETE request to API.
85
+ #
86
+ # @param cmd [String] the API command, like `users/#{user.name}.json`
87
+ # @param data [Hash] data to send; auth_token is included by default (optional)
88
+ def delete cmd, data={}
89
+ _connection :delete, cmd, data
90
+ end
91
+
92
+ # Send PUT request to API.
93
+ #
94
+ # @param cmd [String] the API command, like `users/#{user.name}.json`
95
+ # @param data [Hash] data to send; auth_token is included by default (optional)
96
+ def put cmd, data={}
97
+ _connection :put, cmd, data
98
+ end
99
+
100
+ private
101
+
102
+ # Establish HTTPS connection to API.
103
+ def _connection type, cmd, data
104
+ api = "https://www.beeminder.com/api/v1/#{cmd}"
105
+ data = {"auth_token" => @token}.merge(data)
106
+
107
+ url = URI.parse(api)
108
+ http = Net::HTTP.new(url.host, url.port)
109
+ http.read_timeout = 8640
110
+ http.use_ssl = true
111
+
112
+ json = ""
113
+ http.start do |http|
114
+ req = case type
115
+ when :post
116
+ Net::HTTP::Post.new(url.path)
117
+ when :get
118
+ Net::HTTP::Get.new(url.path)
119
+ when :delete
120
+ Net::HTTP::Delete.new(url.path)
121
+ when :put
122
+ Net::HTTP::Put.new(url.path)
123
+ else
124
+ raise "invalid connection type"
125
+ end
126
+ req.set_form_data(data)
127
+
128
+ res = http.request(req)
129
+ if not res.is_a? Net::HTTPSuccess
130
+ raise "request failed: #{res.body}"
131
+ end
132
+
133
+ json = res.body
134
+ end
135
+
136
+ # parse json
137
+ json = JSON.load(json)
138
+
139
+ json
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module Beeminder
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: beeminder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - muflax
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: highline
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.6'
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: '1.6'
30
+ description: Convenient access to Beeminder's API.
31
+ email:
32
+ - mail@muflax.com
33
+ executables:
34
+ - beemind
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - LICENSE.txt
41
+ - README
42
+ - Rakefile
43
+ - TODO
44
+ - beeminder.gemspec
45
+ - bin/beemind
46
+ - lib/beeminder.rb
47
+ - lib/beeminder/goals.rb
48
+ - lib/beeminder/user.rb
49
+ - lib/beeminder/version.rb
50
+ homepage: ''
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ segments:
63
+ - 0
64
+ hash: 350792412057985642
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ segments:
72
+ - 0
73
+ hash: 350792412057985642
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.23
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: access Beeminder API
80
+ test_files: []