grubber 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
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
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Matthew Werner
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,125 @@
1
+ # Grubber
2
+
3
+ Grubber helps you find a place to eat neaby.
4
+
5
+ ## Installation
6
+
7
+ Simply:
8
+
9
+ $ gem install grubber
10
+
11
+ ## API Access
12
+
13
+ Grubber uses the Yelp API to find nearby places. To use
14
+ grubber you must get yourself Yelp API access. You
15
+ can do so at: [http://www.yelp.com/developers](http://www.yelp.com/developers)
16
+
17
+ Once you have your tokens, run `grubber auth`. This will
18
+ prompt you for your credentials and get you the rest of the way.
19
+
20
+ ## Location Services
21
+
22
+ Grubber uses CoreLocation to find out where you're geographically operating.
23
+ It will request permission once and then cache your location. Grubber will automatically
24
+ find your coordinates when needed if you haven't previously located yourself.
25
+
26
+ #### Granting Permission
27
+
28
+ CoreLocation requires you grant ruby permission to use Location Services. You should
29
+ be prompted for permission once, and don't have to worry about it again.
30
+
31
+ #### Revoking Permission
32
+
33
+ To revoke previously granted permissions, you can uncheck ruby in:
34
+
35
+ System Preferences > Security & Privacy > Privacy > Location Services
36
+
37
+ #### Updating your location
38
+
39
+ You can request grubber to update its cached location by calling `locate`:
40
+
41
+ $ grubber locate
42
+ Updated coordinates to: 37.782487810714265, -122.40982359190473
43
+
44
+ *Developers Note: No stalkers please*
45
+
46
+ ## Usage
47
+
48
+ Grubber has two primary commands:
49
+
50
+ ### List
51
+
52
+ Grubber can list a bunch of nearby restaurants. Each entry has
53
+ the name, rating, address, category, phone and url.
54
+
55
+ **Protip:** If using iTerm, you can cmd+click on a url to open in your default browser
56
+
57
+ $ grubber list
58
+
59
+ Split Pea Seduction ★ ★ ★ ★ ☆
60
+ 138 6th St
61
+ [Sandwiches] +1-415-551-2223
62
+ http://www.yelp.com/biz/split-pea-seduction-san-francisco
63
+
64
+ Miss SaiGon ★ ★ ★ ★ ☆
65
+ 100 6th St
66
+ [Vietnamese] +1-415-522-0332
67
+ http://www.yelp.com/biz/miss-saigon-san-francisco-2
68
+
69
+ Punjab Kabab House ★ ★ ★ ☆ ☆
70
+ 101 Eddy St
71
+ [Indian] +1-415-447-7499
72
+ http://www.yelp.com/biz/punjab-kabab-house-san-francisco
73
+
74
+ Cafe Venue ★ ★ ★ ☆ ☆
75
+ 67 5th St
76
+ [Coffee & Tea] +1-415-546-1144
77
+ http://www.yelp.com/biz/cafe-venue-san-francisco-7
78
+
79
+ ...
80
+
81
+ ### Pick
82
+
83
+ Grubber can just go ahead and pick a nearby restaurant for you
84
+ to avoid you having to think about exactly where you want to go.
85
+ Pick returns all the same fields as list.
86
+
87
+ $ grubber pick
88
+
89
+ Tu Lan ★ ★ ★ ★ ☆
90
+ 8 6th St
91
+ [Vietnamese] +1-415-626-0927
92
+ http://www.yelp.com/biz/tu-lan-san-francisco-2
93
+
94
+ ### The CLI
95
+
96
+ grubber help [TASK] # Describe available tasks or one specific task
97
+ grubber auth # Enter your Yelp API credentials
98
+ grubber list # List available nearby restaurants
99
+ grubber locate # Refresh your grubber's known coordinates
100
+ grubber pick # Pick a restaurant nearby
101
+
102
+ ## Troubleshooting
103
+
104
+ _**When I run call locate twice in a row, it just hangs. wtf**_
105
+
106
+ If you request a new location from CoreLocation too quickly, the command
107
+ will simply hang. Unfortunately this pertains to [a problem]((https://github.com/evanphx/lost/pull/2) with the awesome ruby wrapper [lost](https://github.com/evanphx/lost) that [evanphx](https://github.com/evanphx) has written. Grubber will cache your location, so it shouldn't be a problem. Just don't move around so fast.
108
+
109
+ ## The future
110
+
111
+ I would like to add a few features to grubber. Such as:
112
+
113
+ - Testing
114
+ - Preferred tags/categories of food
115
+ - CLI for search terms
116
+ - Price range
117
+ - Maximum distance
118
+
119
+ ## Contributing
120
+
121
+ 1. Fork it
122
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
123
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
124
+ 4. Push to the branch (`git push origin my-new-feature`)
125
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ lib = File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH << lib unless $LOAD_PATH.include?(lib)
4
+ require 'grubber'
5
+ Grubber::Command.start
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'grubber/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "grubber"
8
+ gem.version = Grubber::VERSION
9
+ gem.authors = ["Matthew Werner"]
10
+ gem.email = ["matthew@culini.com"]
11
+ gem.description = %q{Grubber uses the Yelp API to find nearby restaurants to eat at}
12
+ gem.summary = %q{Grubber helps you decide where to eat}
13
+ gem.homepage = ""
14
+ gem.license = 'MIT'
15
+
16
+ gem.add_runtime_dependency 'oauth'
17
+ gem.add_runtime_dependency 'thor'
18
+ gem.add_runtime_dependency 'lost'
19
+
20
+ gem.add_development_dependency 'rake'
21
+
22
+ gem.files = `git ls-files`.split($/)
23
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
24
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
25
+ gem.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,12 @@
1
+ require "grubber/version"
2
+
3
+ module Grubber
4
+ autoload :Client, 'grubber/client'
5
+ autoload :Command, 'grubber/command'
6
+ autoload :Config, 'grubber/config'
7
+ autoload :Store, 'grubber/store'
8
+ autoload :Presenter, 'grubber/presenter'
9
+ autoload :Restaurant, 'grubber/restaurant'
10
+
11
+ CONFIG = Config.load
12
+ end
@@ -0,0 +1,47 @@
1
+ require 'oauth'
2
+ require 'json'
3
+ require 'yaml'
4
+
5
+ module Grubber
6
+ class Client
7
+
8
+ HOST = 'api.yelp.com'
9
+ attr_accessor :consumer_key, :consumer_secret, :token_key, :token_secret
10
+
11
+ def search(term, opts={})
12
+ lat, lng = *Grubber::CONFIG.coordinates
13
+ yelp = connection
14
+ return [] if yelp.nil?
15
+
16
+ response = yelp.get("/v2/search?term=#{term}&ll=#{lat},#{lng}")
17
+ Restaurant.parse(response.body)
18
+ end
19
+
20
+ private
21
+
22
+ def connection
23
+ return @connection if defined?(@connection)
24
+ ckey, csec, tkey, tsec = credentials
25
+
26
+ oauth = OAuth::Consumer.new(ckey, csec, {:site => "http://#{HOST}"})
27
+ @connection = OAuth::AccessToken.new(oauth, tkey, tsec)
28
+ end
29
+
30
+ def credentials
31
+ config = Grubber::CONFIG
32
+ unless config.has_auth?
33
+ puts "\nYou are missing Yelp API Credentials"
34
+ puts "Add your credentials using `grubber auth`"
35
+ return nil
36
+ end
37
+
38
+ return [
39
+ config.consumer_key,
40
+ config.consumer_secret,
41
+ config.token_key,
42
+ config.token_secret
43
+ ]
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ require 'thor'
2
+
3
+ module Grubber
4
+ class Command < Thor
5
+ include Thor::Actions
6
+
7
+ desc "pick", "Select one restaurant nearby"
8
+ def pick
9
+ Presenter.new( Restaurant.random ).present
10
+ end
11
+
12
+ desc "list", "List available nearby restaurants"
13
+ def list
14
+ Presenter.new( Restaurant.all ).present
15
+ end
16
+
17
+ desc 'locate', "Refresh your grubber's known coordinates"
18
+ def locate
19
+ lat, lng = Grubber::Config.update_location
20
+ say "Updated coordinates to: #{lat}, #{lng}", :green
21
+ end
22
+
23
+ desc 'auth', 'Enter your Yelp API credentials'
24
+ def auth
25
+ config = Grubber::Config.load
26
+
27
+ say("\nGrubber requires Yelp API v2 credentials")
28
+ say("To learn more go to: http://www.yelp.com/developers")
29
+ if config.has_auth?
30
+ say("\nYelp authentication already set:")
31
+ say("Consumer Key: #{config.consumer_key}")
32
+ say("Consumer Secret: #{config.consumer_secret}\n")
33
+ say("Token Key: #{config.token_key}\n")
34
+ say("Token Sec: #{config.token_secret}\n")
35
+ overwrite = ask('Overwrite? (y,n)')
36
+ return unless overwrite.downcase == 'y'
37
+ end
38
+
39
+ c_key = ask("\nConsumer Key: ")
40
+ c_sec = ask('Consumer Secret: ')
41
+ t_key = ask('Token Key: ')
42
+ t_sec = ask('Token Sec: ')
43
+
44
+ if Grubber::Config.update_auth(c_key, c_sec, t_key, t_sec)
45
+ say("Complete!", :green)
46
+ else
47
+ say("Failed", :red)
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,70 @@
1
+ require 'lost'
2
+
3
+ module Grubber
4
+ class Config
5
+
6
+ attr_accessor :store, :lat, :lng, :credentials
7
+
8
+ def initialize(opts={})
9
+ @store = Grubber::Store.load[:grubber]
10
+
11
+ location = store[:location]
12
+ @lat = location[:lat]
13
+ @lng = location[:lng]
14
+
15
+ @credentials = store[:credentials]
16
+ self
17
+ end
18
+
19
+ def self.load
20
+ new
21
+ end
22
+
23
+ def coordinates
24
+ [lat, lng]
25
+ end
26
+
27
+ def has_auth?
28
+ !self.credentials.nil?
29
+ end
30
+
31
+ # consumer_key, consumer_secret
32
+ # token_key, token_secret
33
+ %w(consumer token).each do |type|
34
+ %w(key secret).each do |obj|
35
+ auth_attr = "#{type}_#{obj}".to_sym
36
+ define_method(auth_attr) do
37
+ return '' unless has_auth?
38
+ credentials[auth_attr] || ''
39
+ end
40
+ end
41
+ end
42
+
43
+ def self.update_auth(ckey, csec, tkey, tsec)
44
+ [ckey, csec, tkey, tsec].each do |k|
45
+ return nil if k.nil? || k == ''
46
+ end
47
+
48
+ Grubber::Store.update({
49
+ credentials: {
50
+ consumer_key: ckey,
51
+ consumer_secret: csec,
52
+ token_key: tkey,
53
+ token_secret: tsec
54
+ }
55
+ })
56
+ end
57
+
58
+ def self.update_location
59
+ lat, lng = *Lost.current_position
60
+ Grubber::Store.update({
61
+ location: {
62
+ lat: lat,
63
+ lng: lng
64
+ }
65
+ })
66
+ return [lat, lng]
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ #!/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ module Grubber
5
+ class Presenter
6
+
7
+ attr_accessor :restaurants
8
+
9
+ def initialize(restaurants)
10
+ @restaurants = [*restaurants]
11
+ end
12
+
13
+ def present
14
+ return nil if empty?
15
+
16
+ restaurants.each do |r|
17
+ puts "\n#{r.name} #{stars(r.rating)}"
18
+ puts r.address
19
+ puts "[#{r.category}] #{r.phone}"
20
+ puts r.url
21
+ end
22
+ nil
23
+ end
24
+
25
+ def stars(rating)
26
+ (" ★" * rating) + (" ☆" * (5 - rating))
27
+ end
28
+
29
+ def empty?
30
+ self.restaurants.empty?
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ module Grubber
2
+ class Restaurant
3
+
4
+ ATTRIBUTES = [:name, :description, :categories, :rating, :url, :display_phone, :location, :reviews].freeze
5
+ attr_accessor *ATTRIBUTES
6
+
7
+ alias_method :phone, :display_phone
8
+
9
+ def initialize(opts={})
10
+ opts.each_pair do |k,v|
11
+ self.instance_variable_set("@#{k}", v)
12
+ end
13
+ yield self
14
+ end
15
+
16
+ def self.all(opts={})
17
+ q = opts[:term] || 'lunch'
18
+ Client.new.search(q)
19
+ end
20
+
21
+ def self.random(opts={})
22
+ all(opts).sample
23
+ end
24
+
25
+ def address
26
+ location['display_address'].first
27
+ end
28
+
29
+ def category
30
+ cat = categories.first
31
+ cat.is_a?(Array) ? cat.first : cat
32
+ end
33
+
34
+ def self.parse(body)
35
+ JSON.parse(body)['businesses'].collect do |r|
36
+ Restaurant.new do |n|
37
+ ATTRIBUTES.map(&:to_s).each do |a|
38
+ value = r[a]
39
+ value = value.to_i if a == 'rating'
40
+ n.send("#{a}=", value)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,85 @@
1
+ require 'yaml'
2
+ require 'lost'
3
+
4
+ module Grubber
5
+ class Store
6
+
7
+ attr_accessor :path, :content
8
+
9
+ DEFAULT_PATH = "/Users/#{`whoami`.strip}/.grubber"
10
+
11
+ def initialize(opts={})
12
+ @path = opts[:path] || DEFAULT_PATH
13
+
14
+ setup
15
+ end
16
+
17
+ def self.load
18
+ client = new
19
+
20
+ data = client.read
21
+ return data unless data.nil?
22
+
23
+ client.reset
24
+ end
25
+
26
+ def self.update(data)
27
+ return unless data.is_a?(Hash)
28
+
29
+ client = new
30
+ client.content[:grubber].merge!(data)
31
+ client.write
32
+ data
33
+ end
34
+
35
+ def setup
36
+ return reload if File.exist?(path)
37
+
38
+ reset
39
+ end
40
+
41
+ def read
42
+ begin
43
+ yml = YAML.load_file(path)
44
+ rescue TypeError => e
45
+ return nil
46
+ end
47
+
48
+ return nil unless yml
49
+
50
+ yml
51
+ end
52
+
53
+ def reload
54
+ @content = read
55
+ end
56
+
57
+ def write
58
+ File.open(path, 'w'){|f| f.write(content.to_yaml) }
59
+ end
60
+
61
+ def reset
62
+ File.open(path, 'w') do |f|
63
+ f.write(base_config.to_yaml)
64
+ end
65
+
66
+ reload
67
+ end
68
+
69
+ def base_config
70
+ lat, lng = *Lost.current_position
71
+ current_time = Time.now.to_i
72
+
73
+ {
74
+ grubber: {
75
+ location: {
76
+ lat: lat,
77
+ lng: lng
78
+ },
79
+ created_at: current_time
80
+ }
81
+ }
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ module Grubber
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grubber
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthew Werner
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oauth
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
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'
30
+ - !ruby/object:Gem::Dependency
31
+ name: thor
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: lost
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Grubber uses the Yelp API to find nearby restaurants to eat at
79
+ email:
80
+ - matthew@culini.com
81
+ executables:
82
+ - grubber
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - bin/grubber
92
+ - grubber.gemspec
93
+ - lib/grubber.rb
94
+ - lib/grubber/client.rb
95
+ - lib/grubber/command.rb
96
+ - lib/grubber/config.rb
97
+ - lib/grubber/presenter.rb
98
+ - lib/grubber/restaurant.rb
99
+ - lib/grubber/store.rb
100
+ - lib/grubber/version.rb
101
+ homepage: ''
102
+ licenses:
103
+ - MIT
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ segments:
115
+ - 0
116
+ hash: -3855025966836421654
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ segments:
124
+ - 0
125
+ hash: -3855025966836421654
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.24
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: Grubber helps you decide where to eat
132
+ test_files: []