Permalink
Browse files

implementation of some parts

  • Loading branch information...
1 parent 6a9009b commit 74b88a6845411b2668aea4fe85e8b9402d958f25 @muflax muflax committed Sep 6, 2012
Showing with 326 additions and 12 deletions.
  1. +1 −0 .gitignore
  2. +22 −10 README
  3. +5 −0 TODO
  4. +2 −0 beeminder-gem.gemspec
  5. +37 −0 bin/beemind
  6. +11 −2 lib/beeminder.rb
  7. +149 −0 lib/beeminder/goals.rb
  8. +99 −0 lib/beeminder/user.rb
View
@@ -15,3 +15,4 @@ spec/reports
test/tmp
test/version_tmp
tmp
+vendor
View
@@ -1,12 +1,12 @@
-# Beeminder::Gem
+# Beeminder
-TODO: Write a gem description
+Convenient access to [Beeminder]()'s API.
## Installation
Add this line to your application's Gemfile:
- gem 'beeminder-gem'
+ gem 'beeminder'
And then execute:
@@ -18,12 +18,24 @@ Or install it yourself as:
## Usage
-TODO: Write usage instructions here
+First, get your token [here]() and log in:
-## Contributing
+ bee = Beeminder::User.new "username", "token"
-1. Fork it
-2. Create your feature branch (`git checkout -b my-new-feature`)
-3. Commit your changes (`git commit -am 'Add some feature'`)
-4. Push to the branch (`git push origin my-new-feature`)
-5. Create new Pull Request
+Now you can do a bunch of stuff. You'll probably want to send a new datapoint:
+
+ bee.send "weight", 86.3
+
+Or you can find all goals of a certain type:
+
+ odometer_goals = bee.goals.select {|g| g.goal_type = :biker}
+
+Or maybe show the last updated graph in a widget somewhere:
+
+ puts bee.goals.max_by(:updated_at).graph_url
+
+There's also a simple tool called `beemind` to update graphs:
+
+ $ beemind pushups 4
+
+Check the [gem doc]() and [API]() for what else you can do.
View
@@ -0,0 +1,5 @@
+* TODO [1/4]
+- [X] document code
+- [ ] handle oauth tokens?
+- [ ] beemind binary
+- [ ] implement actual API calls
@@ -15,4 +15,6 @@ Gem::Specification.new do |gem|
gem.files = `git ls-files`.split($/)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.require_paths = ["lib"]
+
+ gem.add_dependency 'trollop', '~> 2.0'
end
View
@@ -0,0 +1,37 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
+require 'yaml'
+
+# load library
+file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+lib = File.join File.dirname(file), "/../lib/beeminder"
+
+if File.exists? lib
+ # using local version
+ require lib
+else
+ require 'beeminder'
+end
+
+config = "#{Dir.home}/.beeminderrc"
+if not File.exists? config
+ # create config
+ require 'highline/import'
+ yaml = {
+ "account" => ask("Beeminder account:"),
+ "token" => ask("Auth token:")
+ }
+ File.open(config, "w+") {|f| YAML.dump yaml, f}
+ File.chmod 0600, config
+ puts "Written config to '#{config}.'"
+end
+
+# load config
+yaml = YAML.load File.open(config)
+
+# login
+bee = Beeminder::User.new yaml["account"], yaml["token"]
+
+# show goals
+# p bee.goals
View
@@ -1,2 +1,11 @@
-require "beeminder/version"
-require "beeminder/beeminder"
+# coding: utf-8
+
+require 'date'
+require 'json'
+require 'net/https'
+require 'uri'
+
+# local libs
+Dir["#{File.join(File.dirname(__FILE__), "beeminder")}/*.rb"].each do |lib|
+ require lib
+end
View
@@ -0,0 +1,149 @@
+# coding: utf-8
+
+module Beeminder
+ class Goal
+ # @return [String] The final part of the URL of the goal, used as an identifier.
+ attr_accessor :slug
+
+ # @return [DateTime] Last time this goal was updated.
+ attr_reader :updated_at
+
+ # @return [String] The title that the user specified for the goal.
+ attr_accessor :title
+
+ # @return [DateTime] Goal date.
+ attr_reader :goaldate
+
+ # @return [Numeric] Goal value.
+ attr_reader :goalval
+
+ # @return [Numeric] The slope of the (final section of the) yellow brick road.
+ attr_reader :rate
+
+ # @return [Symbol] One of the following symbols:
+ # - `:fatloser`: Weight loss
+ # - `:hustler`: Do More
+ # - `:biker`: Odometer
+ # - `:inboxer`: Inbox Fewer
+ # - `:gainer`: Gain Weight
+ # - `:drinker`: Set a Limit
+ # - `:custom`: Full access to the underlying goal parameters
+ attr_reader :goal_type
+
+ # @return [DateTime] Date of derailment.
+ attr_reader :losedate
+
+ # @return [String] URL for the goal's graph image.
+ attr_reader :graph_url
+
+ # @return [Numeric] Panic threshold. How long before derailment to panic.
+ attr_accessor :panic
+
+ # @return [true|false] Whether the graph is currently being updated to reflect new data.
+ attr_reader :queued
+
+ # @return [true|false] Whether the graph was created in test mode.
+ attr_accessor :ephem
+
+ # @return [true|false] Whether you have to be signed in as owner of the goal to view it.
+ attr_accessor :secret
+
+ # @return [true|false] Whether you have to be signed in as the owner of the goal to view the datapoints.
+ attr_accessor :datapublic
+
+ # @return [Beeminder::User] User that owns this goal.
+ attr_reader :user
+
+ def initialize user, name
+ @user = user
+ @slug = name
+
+ reload
+ end
+
+ # Reload data from Beeminder.
+ def reload
+ info = @user.get "users/#{@user.name}/goals/#{@slug}.json"
+
+ # set variables
+ info.each do |k,v|
+ instance_variable_set "@#{k}", v
+ end
+
+ # some conversions
+ @goaldate = DateTime.strptime(@goaldate.to_s, '%s') unless @goaldate.nil?
+ @goal_type = @goal_type.to_sym unless @goal_type.nil?
+ @losedate = DateTime.strptime(@losedate.to_s, '%s') unless @losedate.nil?
+ @updated_at = DateTime.strptime(@updated_at.to_s, '%s')
+ end
+
+ # List of datapoints.
+ #
+ # @return [Array<Beeminder::Datapoint>] returns list of datapoints
+ def datapoints
+ info = @user.get "users/#{@user.name}/goals/#{slug}.json", "datapoints" => true
+ datapoints = info["datapoints"].map{|d| Datapoint.new d}
+
+ datapoints
+ end
+
+ # Send updated info to Beeminder.
+ def update
+ end
+
+ # Add one or more datapoints to the goal.
+ #
+ # @param datapoints [Beeminder::Datapoint, Array<Beeminder::Datapoint>] one or more datapoints to add to goal
+ def add datapoints, opts={}
+ opts = {:sendmail => false}.merge(opts)
+ datapoints = [*datapoints]
+ end
+
+ # Delete one or more datapoints from the goal.
+ #
+ # @param datapoints [Beeminder::Datapoint, Array<Beeminder::Datapoint>] one or more datapoints to delete
+ def delete datapoints
+ datapoints = [*datapoints]
+ datapoints.each{|d| d.delete}
+ end
+ end
+
+ class Datapoint
+ # @return [DateTime] Time of the datapoint.
+ attr_accessor :timestamp
+
+ # @return [Numeric] Value of the datapoint.
+ attr_accessor :value
+
+ # @return [String] An optional comment about the datapoint.
+ attr_accessor :comment
+
+ # @return [String] A unique ID, used to identify a datapoint when deleting or editing it.
+ attr_reader :id
+
+ # @return [DateTime] The time that this datapoint was entered or last updated.
+ attr_reader :updated_at
+
+ def initialize info
+ # set variables
+ info.each do |k,v|
+ instance_variable_set "@#{k}", v
+ end
+
+ # some conversions
+ @timestamp = DateTime.strptime(@timestamp.to_s, '%s')
+ @updated_at = DateTime.strptime(@updated_at.to_s, '%s')
+ end
+
+ # Send updated info to Beeminder.
+ def update
+
+ # update
+ @updated_at = DateTime.strptime(ret["updated_at"].to_s, '%s')
+ end
+
+ # Delete datapoint.
+ def delete
+ end
+ end
+end
View
@@ -0,0 +1,99 @@
+# coding: utf-8
+
+module Beeminder
+ class User
+ # @return [String] User name.
+ attr_reader :name
+
+ # @return [String] Auth token.
+ attr_reader :token
+
+ # @return [DateTime] Last time user made any changes.
+ attr_reader :updated_at
+
+ # @return [String] Timezone.
+ attr_reader :timezone
+
+ def initialize name, token
+ @name = name
+ @token = token
+
+ info = get "users/#{@name}.json"
+
+ @timezone = info["Timezone"]
+ @updated_at = DateTime.strptime(info["updated_at"].to_s, '%s')
+ end
+
+ # List of goals.
+ #
+ # @param filter [Symbol] filter goals, can be `:all` (default), `:frontburner` or `:backburner`
+ # @ return [Array<Beeminder::Goal>] returns list of goals
+ def goals filter=:all
+ raise "invalid goal filter: #{filter}" unless [:all, :frontburner, :backburner].include? filter
+
+ info = get "users/#{@name}.json", :filter => filter.to_s
+ goals = info["goals"].map do |goal|
+ Beeminder::Goal.new self, goal
+ end unless info["goals"].nil?
+
+ goals || []
+ end
+
+ # Create new goal.
+ def create_goal
+ end
+
+ # Send GET request to API.
+ #
+ # @param cmd [String] the API command, like `users/#{user.name}.json`
+ # @param data [Hash] data to send; auth_token is included by default (optional)
+ def get cmd, data={}
+ _connection :get, cmd, data
+ end
+
+ # Send POST request to API.
+ #
+ # @param cmd [String] the API command, like `users/#{user.name}.json`
+ # @param data [Hash] data to send; auth_token is included by default (optional)
+ def post cmd, data={}
+ _connection :post, cmd, data
+ end
+
+ private
+
+ # Establish HTTPS connection to API.
+ def _connection type, cmd, data
+ api = "https://www.beeminder.com/api/v1/#{cmd}"
+ data = {"auth_token" => @token}.merge(data)
+
+ url = URI.parse(api)
+ http = Net::HTTP.new(url.host, url.port)
+ http.read_timeout = 8640
+ http.use_ssl = true
+
+ json = ""
+ http.start do |http|
+ req = case type
+ when :post
+ req = Net::HTTP::Post.new(url.path)
+ when :get
+ req = Net::HTTP::Get.new(url.path)
+ else
+ raise "invalid connection type"
+ end
+ req.set_form_data(data)
+ res = http.request(req)
+ if not res.is_a? Net::HTTPSuccess
+ raise "request failed: #{res.body}"
+ end
+
+ json = res.body
+ end
+
+ # parse json
+ json = JSON.load(json)
+
+ json
+ end
+ end
+end

0 comments on commit 74b88a6

Please sign in to comment.