grubber 0.0.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.
@@ -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: []