├── .rspec ├── .gitignore ├── lib ├── sifter │ ├── version.rb │ ├── account.rb │ └── project.rb └── sifter.rb ├── Gemfile ├── spec ├── fixtures │ ├── statuses.json │ ├── priorities.json │ ├── people.json │ ├── milestones.json │ ├── projects.json │ └── issues.json ├── spec_helper.rb └── sifter │ ├── account_spec.rb │ └── project_spec.rb ├── Rakefile ├── sifter.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | .bundle 4 | Gemfile.lock 5 | -------------------------------------------------------------------------------- /lib/sifter/version.rb: -------------------------------------------------------------------------------- 1 | class Sifter 2 | VERSION = "0.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | gemspec 3 | 4 | group :development, :test do 5 | gem 'rspec', '~> 2.5.0' 6 | gem 'fakeweb', '~> 1.3.0' 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/statuses.json: -------------------------------------------------------------------------------- 1 | { 2 | "statuses": { 3 | "Open": 1, 4 | "Closed": 4, 5 | "Resolved": 3, 6 | "Reopened": 2 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /spec/fixtures/priorities.json: -------------------------------------------------------------------------------- 1 | { 2 | "priorities": { 3 | "Trivial": 5, 4 | "High": 2, 5 | "Normal": 3, 6 | "Critical": 1, 7 | "Low": 4 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rspec/core/rake_task' 3 | require 'rake/rdoctask' 4 | 5 | Bundler::GemHelper.install_tasks 6 | 7 | task :default => :spec 8 | RSpec::Core::RakeTask.new(:spec) do |t| 9 | t.rspec_opts = ["--format documentation"] 10 | end 11 | 12 | Rake::RDocTask.new do |t| 13 | t.main = "README.md" 14 | t.rdoc_files.include("README.md", "lib/**/*.rb") 15 | t.options << "--accessor=property" 16 | end 17 | -------------------------------------------------------------------------------- /spec/fixtures/people.json: -------------------------------------------------------------------------------- 1 | { 2 | "people": [ 3 | { 4 | "username": "gdimon", 5 | "first_name": "Garrett", 6 | "last_name": "Dimon", 7 | "email": "garrett@nextupdate.com" 8 | }, 9 | { 10 | "username": "lpage", 11 | "first_name": "Larry", 12 | "last_name": "Page", 13 | "email": "larry@garrettdimon.com" 14 | }, 15 | { 16 | "username": "sbrin", 17 | "first_name": "Sergey", 18 | "last_name": "Brin", 19 | "email": "sergey@garrettdimon.com" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /spec/fixtures/milestones.json: -------------------------------------------------------------------------------- 1 | { 2 | "milestones": [ 3 | { 4 | "name": "Private Release", 5 | "due_date": "2010/04/15", 6 | "issues_url": "https://example.sifterapp.com/projects/189113590/issues?m=1", 7 | "api_issues_url": "https://example.sifterapp.com/api/projects/189113590/issues?m=1" 8 | }, 9 | { 10 | "name": "Public Release", 11 | "due_date": "2010/05/15", 12 | "issues_url": "https://example.sifterapp.com/projects/189113590/issues?m=2", 13 | "api_issues_url": "https://example.sifterapp.com/api/projects/189113590/issues?m=2" 14 | } 15 | 16 | ] 17 | } 18 | 19 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "fakeweb" 2 | require "sifter" 3 | 4 | FakeWeb.allow_net_connect = false 5 | 6 | RSpec.configure do 7 | 8 | # Gratuitously borrowed from Wynn Netherland's gowalla gem 9 | def stub_get(url, filename, options={}) 10 | opts = { 11 | :body => fixture_file(filename), 12 | :content_type => 'application/json; charset=utf-8' 13 | }.merge(options) 14 | FakeWeb.register_uri(:get, sifter_url(url), opts) 15 | end 16 | 17 | def fixture_file(filename) 18 | return filename if filename == '' 19 | file_path = File.expand_path( 20 | File.dirname(__FILE__) + '/fixtures/' + filename 21 | ) 22 | File.read(file_path) 23 | end 24 | 25 | def sifter_url(url) 26 | url =~ /^http/ ? url : "https://example.sifterapp.com#{url}" 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /spec/sifter/account_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Sifter::Account do 4 | 5 | let(:account) { Sifter::Account.new("example.sifterapp.com", "abc123") } 6 | 7 | it "fetches status codes" do 8 | stub_get("/api/statuses", "statuses.json") 9 | 10 | statuses = account.statuses 11 | 12 | statuses["Open"].should == 1 13 | statuses.length.should == 4 14 | end 15 | 16 | it "fetches priority codes" do 17 | stub_get("/api/priorities", "priorities.json") 18 | 19 | priorities = account.priorities 20 | 21 | priorities["High"].should == 2 22 | priorities.length.should == 5 23 | end 24 | 25 | it "fetches all projects" do 26 | stub_get("/api/projects", "projects.json") 27 | 28 | projects = account.projects 29 | 30 | projects.length.should == 2 31 | projects.first.should be_kind_of(Sifter::Project) 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /sifter.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "sifter/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "sifter" 7 | s.version = Sifter::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Adam Keys"] 10 | s.email = ["adam@nextupdate.com"] 11 | s.homepage = "http://github.com/nextupdate/sifter-ruby" 12 | s.summary = %q{Wrapper for the Sifter API} 13 | s.description = %q{Query, fetch, create and modify issues stored in Sifter.} 14 | 15 | s.rubyforge_project = "sifter" 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.require_paths = ["lib"] 21 | 22 | s.add_dependency 'httparty', '~> 0.6.1' 23 | s.add_dependency 'hashie', '~> 0.4.0' 24 | end 25 | -------------------------------------------------------------------------------- /lib/sifter.rb: -------------------------------------------------------------------------------- 1 | require "httparty" 2 | require "hashie" 3 | 4 | class Sifter 5 | 6 | include HTTParty 7 | 8 | autoload :Account, "sifter/account" 9 | autoload :Project, "sifter/project" 10 | 11 | # Wrapper around an issue. 12 | class Issue < Hashie::Dash 13 | 14 | property :number 15 | property :subject 16 | property :description 17 | property :priority 18 | property :status 19 | property :assignee_name 20 | property :category_name 21 | property :milestone_name 22 | property :opener_name 23 | property :url 24 | property :api_url 25 | 26 | property :commments 27 | end 28 | 29 | # Wrapper around a milestone. 30 | class Milestone < Hashie::Dash 31 | 32 | property :name 33 | property :due_date 34 | property :url 35 | property :api_url 36 | property :issues_url 37 | property :api_issues_url 38 | 39 | end 40 | 41 | # Wrapper around a person. 42 | class Person < Hashie::Dash 43 | 44 | property :username 45 | property :first_name 46 | property :last_name 47 | property :email 48 | 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/sifter/account.rb: -------------------------------------------------------------------------------- 1 | # Wrapper around an a Sifter account. Use this as the root for accessing the 2 | # Sifter API. 3 | class Sifter::Account 4 | 5 | # Connect to a Sifter account. The host is the subdomain for your Sifter 6 | # account (yourhost.sifterapp.com), you can get a token by going to 7 | # Profile->Access Keys. 8 | def initialize(host, token) 9 | Sifter.default_options[:base_uri] = "https://" << host 10 | Sifter.default_options[:headers] = {"X-Sifter-Token" => token} 11 | end 12 | 13 | # Fetch the possible issue statuses for this project. This is currently hardcoded 14 | # in the app, so it's unlikely they'll change anytime soon. 15 | def statuses 16 | Sifter. 17 | get("/api/statuses"). 18 | parsed_response["statuses"] 19 | end 20 | 21 | 22 | # Fetch the possible issue priorities for this project. This is currently hardcoded 23 | # in the app, so it's unlikely they'll change anytime soon. 24 | def priorities 25 | Sifter. 26 | get("/api/priorities"). 27 | parsed_response["priorities"] 28 | end 29 | 30 | # Fetch all projects that you have access to for this account. Returns an 31 | # array of Sifter::Project objects. 32 | def projects 33 | Sifter. 34 | get("/api/projects"). 35 | parsed_response["projects"]. 36 | map { |p| Sifter::Project.new(p) } 37 | end 38 | 39 | end 40 | 41 | -------------------------------------------------------------------------------- /lib/sifter/project.rb: -------------------------------------------------------------------------------- 1 | # Wrapper for a Sifter project. Fetch projects using Sifter::Account. 2 | class Sifter::Project < Hashie::Dash 3 | 4 | property :name 5 | property :archived 6 | property :primary_company_name 7 | property :url 8 | property :api_url 9 | property :issues_url 10 | property :milestones_url 11 | property :people_url 12 | property :api_milestones_url 13 | property :api_people_url 14 | property :api_issues_url 15 | property :api_categories_url 16 | 17 | # Fetch all the issues on this project. Returns an array of Sifter::Issue 18 | # objects. 19 | def issues 20 | Sifter. 21 | get(api_issues_url). 22 | fetch("issues", []). 23 | map { |i| Sifter::Issue.new(i) } 24 | end 25 | 26 | def issue(issue_id) 27 | project_id = api_url.split('/').last 28 | 29 | Sifter. 30 | get("/api/projects/#{project_id}/issues/#{issue_id}")["issue"] 31 | end 32 | 33 | # Fetch all the milestones for this project. Returns an array of 34 | # Sifter::Milestone objects. 35 | def milestones 36 | Sifter. 37 | get(api_milestones_url). 38 | fetch("milestones", []). 39 | map { |m| Sifter::Milestone.new(m) } 40 | end 41 | 42 | # Fetch all the people linked to this project. Returns an array of 43 | # Sifter::Person objects. 44 | def people 45 | Sifter. 46 | get(api_people_url). 47 | fetch("people", []). 48 | map { |p| Sifter::Person.new(p) } 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sifter-ruby 2 | 3 | It's an API wrapper for the [Sifter API][http://sifterapp.com/developer]. You 4 | can use it to fetch projects, issues, milestones, and people from your Sifter 5 | accounts. 6 | 7 | ## Getting started 8 | 9 | ## Fetching projects 10 | 11 | ## Fetching issues 12 | 13 | ## Fetching milestones 14 | 15 | ## License 16 | 17 | The MIT License 18 | 19 | Copyright (c) 2011 Next Update 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in 29 | all copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 37 | THE SOFTWARE. 38 | 39 | -------------------------------------------------------------------------------- /spec/fixtures/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ 3 | { 4 | "name": "Elephant", 5 | "primary_company_name": "Apple", 6 | "archived": false, 7 | "url": "https://example.sifterapp.com/projects/189113590", 8 | "issues_url": "https://example.sifterapp.com/projects/189113590/issues", 9 | "milestones_url": "https://example.sifterapp.com/projects/189113590/milestones", 10 | "api_url": "https://example.sifterapp.com/api/projects/189113590", 11 | "api_issues_url": "https://example.sifterapp.com/api/projects/189113590/issues", 12 | "api_milestones_url": "https://example.sifterapp.com/api/projects/189113590/milestones", 13 | "api_categories_url": "https://example.sifterapp.com/api/projects/189113590/categories", 14 | "api_people_url": "https://example.sifterapp.com/api/projects/189113590/people" 15 | }, 16 | { 17 | "name": "Zebra", 18 | "primary_company_name": "Google", 19 | "archived": true, 20 | "url": "https://example.sifterapp.com/projects/358047158", 21 | "issues_url": "https://example.sifterapp.com/projects/358047158/issues", 22 | "milestones_url": "https://example.sifterapp.com/projects/358047158/milestones", 23 | "api_url": "https://example.sifterapp.com/api/projects/358047158", 24 | "api_issues_url": "https://example.sifterapp.com/api/projects/358047158/issues", 25 | "api_milestones_url": "https://example.sifterapp.com/api/projects/358047158/milestones", 26 | "api_categories_url": "https://example.sifterapp.com/api/projects/358047158/categories", 27 | "api_people_url": "https://example.sifterapp.com/api/projects/358047158/people" 28 | } 29 | ] 30 | } 31 | 32 | -------------------------------------------------------------------------------- /spec/fixtures/issues.json: -------------------------------------------------------------------------------- 1 | { 2 | "issues": [ 3 | { 4 | "number": 57, 5 | "category_name": "Enhancement", 6 | "priority": "Trivial", 7 | "subject": "Donec vel neque", 8 | "description": "Nam id libero quis ipsum feugiat elementum. Vivamus vitae mauris ut ipsum mattis malesuada. Sed vel massa. Mauris lobortis. Nunc egestas, massa id scelerisque dapibus, urna mi accumsan purus, vel aliquam nunc sapien eget nisi. Quisque vitae nibh. Proin interdum mollis leo. Fusce sed nisi. Integer ut tortor.", 9 | "milestone_name": null, 10 | "opener_name": "Adam Keys", 11 | "assignee_name": "Garrett Dimon", 12 | "status": "Open", 13 | "url": "https://example.sifterapp.com/projects/189113590/issues/46809840", 14 | "api_url": "https://example.sifterapp.com/api/projects/189113590/issues/46809840" 15 | }, 16 | { 17 | "number": 63, 18 | "category_name": "Bug", 19 | "priority": "Trivial", 20 | "subject": "Sed lectus nunc, egestas tincidunt", 21 | "description": "Proin dictum dignissim metus. Vivamus vel risus sed augue venenatis sodales. Quisque tempus dictum lorem. Sed a turpis eu turpis lobortis pellentesque. Nunc sem mi, ullamcorper non, dignissim eu, pellentesque eget, justo. Donec mollis neque quis tortor. In hac habitasse platea dictumst. Mauris at velit non erat scelerisque iaculis.", 22 | "milestone_name": null, 23 | "opener_name": "Adam Keys", 24 | "assignee_name": null, 25 | "status": "Open", 26 | "url": "https://example.sifterapp.com/projects/189113590/issues/465769292", 27 | "api_url": "https://example.sifterapp.com/api/projects/189113590/issues/465769292" 28 | } 29 | ], 30 | "per_page": 10, 31 | "page": 1, 32 | "total_pages": 2, 33 | "next_page_url": "https://example.sifterapp.com/api/projects/189113590/issues?per_page=10&page=1", 34 | "previous_page_url": null 35 | } 36 | 37 | -------------------------------------------------------------------------------- /spec/sifter/project_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Sifter::Project do 4 | 5 | before do 6 | stub_get("/api/projects", "projects.json") 7 | end 8 | 9 | let(:account) { Sifter::Account.new("example.sifterapp.com", "abc123") } 10 | let(:project) { account.projects.select { |p| p.name == "Elephant" }.first } 11 | 12 | it "initializes from a hash" do 13 | attrs = { 14 | "name"=>"Elephant", 15 | "issues_url"=>"https://example.sifterapp.com/projects/189113590/issues", 16 | "api_milestones_url"=> 17 | "https://example.sifterapp.com/api/projects/189113590/milestones", 18 | "primary_company_name"=>"Apple", 19 | "api_people_url"=> 20 | "https://example.sifterapp.com/api/projects/189113590/people", 21 | "url"=>"https://example.sifterapp.com/projects/189113590", 22 | "archived"=>false, 23 | "api_issues_url"=> 24 | "https://example.sifterapp.com/api/projects/189113590/issues", 25 | "api_categories_url"=> 26 | "https://example.sifterapp.com/api/projects/189113590/categories", 27 | "api_url"=>"https://example.sifterapp.com/api/projects/189113590", 28 | "milestones_url"=> 29 | "https://example.sifterapp.com/projects/189113590/milestones" 30 | } 31 | 32 | project = Sifter::Project.new(attrs) 33 | attrs.each { |k, v| project[k].should == v } 34 | end 35 | 36 | it "fetches issues" do 37 | stub_get(project.api_issues_url, "issues.json") 38 | 39 | issues = project.issues 40 | 41 | issues.length.should == 2 42 | issues.first.should be_a(Sifter::Issue) 43 | end 44 | 45 | it "fetches milestones" do 46 | stub_get(project.api_milestones_url, "milestones.json") 47 | 48 | milestones = project.milestones 49 | 50 | milestones.length.should == 2 51 | milestones.first.should be_a(Sifter::Milestone) 52 | end 53 | 54 | it "fetches people" do 55 | stub_get(project.api_people_url, "people.json") 56 | 57 | people = project.people 58 | 59 | people.length.should == 3 60 | people.first.should be_a(Sifter::Person) 61 | end 62 | 63 | end 64 | --------------------------------------------------------------------------------