restaurant_week_boston 1.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.
data/ChangeLog ADDED
@@ -0,0 +1,2 @@
1
+ 1.0.0 / 2010-08-14
2
+ * Birthday!
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Gabe Berke-Williams
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,65 @@
1
+ = Restaurant Week Boston
2
+ This is just a silly little gem I made that parses the Restaurant Week
3
+ Boston (http://www.restaurantweekboston.com/) website and lets you mark which
4
+ ones you want to go to for lunch, dinner, or lunch and dinner.
5
+
6
+ = Installation
7
+ <tt>gem install restaurant_week_boston</tt>
8
+
9
+ = Usage
10
+ Use the +restaurant_week_boston+ command line script.
11
+ Use -a/--all, -l/--lunch, and -d/--dinner options to mark restaurants that
12
+ you want to go to for lunch *and* dinner, lunch, or dinner, respectively.
13
+
14
+ So if you want to go to Bond and Aquitaine Bis for lunch and dinner, you'd
15
+ do
16
+
17
+ <tt>$ restaurant_week_boston -a 'aquitaine bis',bond</tt>
18
+
19
+ === More involved demo
20
+
21
+ <tt>$ restaurant_week_boston -a 'aquitaine bis' -l bond,asana -d bergamot,clink</tt>
22
+ Getting doc...done.
23
+ Parsing doc...done.
24
+
25
+ === LUNCH ===
26
+ Name: Asana (short-name: asana)
27
+ Meals: Lunch, Dinner
28
+ Neighborhood: Back Bay
29
+ Phone: 617-535-8800
30
+ Lunch Menu: http://www.restaurantweekboston.com/fetch/asana/lunch/
31
+ Dinner Menu: http://www.restaurantweekboston.com/fetch/asana/dinner/
32
+ Map: http://www.restaurantweekboston.com/map/back-bay/asana/#topOfMap
33
+ --------------------------------------------------------------------------------
34
+ Name: Bond (short-name: bond)
35
+ Meals: Lunch, Dinner
36
+ Neighborhood: Downtown
37
+ Phone: 617-451-1900
38
+ Lunch Menu: http://www.restaurantweekboston.com/fetch/bond/lunch/
39
+ Dinner Menu: http://www.restaurantweekboston.com/fetch/bond/dinner/
40
+ Map: http://www.restaurantweekboston.com/map/downtown/bond/#topOfMap
41
+
42
+ === DINNER ===
43
+ Name: Bergamot (short-name: bergamot)
44
+ Meals: Dinner
45
+ Neighborhood: Somerville
46
+ Phone: <no phone given>
47
+ Dinner Menu: http://www.restaurantweekboston.com/fetch/bergamot/dinner/
48
+ Map: http://www.restaurantweekboston.com/map/somerville/bergamot/#topOfMap
49
+ --------------------------------------------------------------------------------
50
+ Name: Clink. at the Liberty Hotel (short-name: clink)
51
+ Meals: Lunch, Dinner
52
+ Neighborhood: Beacon Hill
53
+ Phone: 617-224-4004
54
+ Lunch Menu: http://www.restaurantweekboston.com/fetch/clink/lunch/
55
+ Dinner Menu: http://www.restaurantweekboston.com/fetch/clink/dinner/
56
+ Map: http://www.restaurantweekboston.com/map/beacon-hill/clink/#topOfMap
57
+
58
+ === LUNCH OR DINNER ===
59
+ Name: Aquitaine Bis (short-name: aquitaine-bis)
60
+ Meals: Lunch, Dinner
61
+ Neighborhood: Chestnut Hill
62
+ Phone: 617-734-8400
63
+ Lunch Menu: http://www.restaurantweekboston.com/fetch/aquitaine-bis/lunch/
64
+ Dinner Menu: http://www.restaurantweekboston.com/fetch/aquitaine-bis/dinner/
65
+ Map: http://www.restaurantweekboston.com/map/chestnut-hill/aquitaine-bis/#topOfMap
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'restaurant_week_boston'
4
+ runner = RestaurantWeekBoston::Runner.new(ARGV)
5
+ runner.run()
@@ -0,0 +1,63 @@
1
+ require 'restaurant_week_boston/scraper'
2
+
3
+ module RestaurantWeekBoston
4
+ # Uses a given Scraper and marks restaurants as good for lunch, dinner, any,
5
+ # etc.
6
+ class Marker
7
+ # +opts+ is a hash with keys for +:lunch+, +:dinner+, and +:any+.
8
+ # The values of each of these keys should be an array suitable for passing
9
+ # to Scraper#special_find().
10
+ def initialize(scraper, opts)
11
+ @scraper = scraper
12
+ @opts = opts
13
+ end
14
+
15
+ # Print out restaurants that are good for lunch.
16
+ def lunch
17
+ unless @lunch
18
+ if @opts.key?(:lunch)
19
+ @lunch = @scraper.special_find(@opts[:lunch])
20
+ else
21
+ @lunch = '[no lunch options specified]'
22
+ end
23
+ end
24
+ puts "=== LUNCH ==="
25
+ puts @lunch
26
+ end
27
+
28
+ # Print out restaurants that are good for dinner.
29
+ def dinner
30
+ unless @dinner
31
+ if @opts.key?(:dinner)
32
+ @dinner = @scraper.special_find(@opts[:dinner])
33
+ else
34
+ @dinner = '[no dinner options specified]'
35
+ end
36
+ end
37
+ puts "=== DINNER ==="
38
+ puts @dinner
39
+ end
40
+
41
+ # Print out restaurants that are good for lunch or dinner.
42
+ def any
43
+ unless @any
44
+ if @opts.key?(:any)
45
+ @any = @scraper.special_find(@opts[:any])
46
+ else
47
+ @any = '[no "lunch or dinner" options specified]'
48
+ end
49
+ end
50
+ puts "=== LUNCH OR DINNER ==="
51
+ puts @any
52
+ end
53
+
54
+ # Print lunch(), dinner(), and any(), with newlines between them.
55
+ def all
56
+ lunch()
57
+ puts
58
+ dinner()
59
+ puts
60
+ any()
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,134 @@
1
+ module RestaurantWeekBoston
2
+ # A Restaurant has the following properties:
3
+ # * name ("224 Boston Street")
4
+ # * short-name ("224-boston-street", used internally by RWB site)
5
+ # * meals: %w{lunch dinner}, %w{lunch}, %w{dinner}
6
+ # * neighborhood: "back-bay", etc
7
+ # * phone: "617-555-1234"
8
+ # * menu: use menu_link_for("lunch" || "dinner")
9
+ # * map_link: a link to the RWB map page for that Restaurant
10
+ class Restaurant
11
+ include Comparable
12
+
13
+ # +entry+ is a Nokogiri::XML::Node from the RWB web site.
14
+ def initialize(entry)
15
+ @entry = entry
16
+ end
17
+
18
+
19
+ # Compares this Restaurant's name to +other+'s name, and returns -1, 0, 1
20
+ # as appropriate.
21
+ def <=>(other)
22
+ return name <=> other.name
23
+ end
24
+
25
+
26
+ # Generate a pretty representation of the Restaurant.
27
+ def to_s
28
+ # back-bay -> "Back Bay"
29
+ pretty_neighborhood = neighborhood.split('-').map{|x| x.capitalize }.join(' ')
30
+
31
+ s = "Name: #{name} (short-name: #{short_name})"
32
+ s += "\n"
33
+ s += "Meals: #{meals.map(&:capitalize).join(', ')}"
34
+ s += "\n"
35
+ s += "Neighborhood: #{pretty_neighborhood}"
36
+ s += "\n"
37
+ s += "Phone: #{phone}"
38
+ @meals.each do |meal|
39
+ s += "\n"
40
+ s += "%s Menu: #{menu_link_for(meal)}" % [meal.capitalize]
41
+ end
42
+ s += "\n"
43
+ s += "Map: #{map_link}"
44
+ s
45
+ end
46
+
47
+
48
+ # Returns "<Restaurant: #{restaurant-name}>"
49
+ def inspect
50
+ "<Restaurant: #{name}>"
51
+ end
52
+
53
+ # Returns true if this Restaurant offers lunch.
54
+ def offers_lunch?
55
+ meals.include?('lunch')
56
+ end
57
+
58
+ # Returns true if this Restaurant offers dinner.
59
+ def offers_dinner?
60
+ meals.include?('dinner')
61
+ end
62
+
63
+ # Return the short name (e.g. '224-boston-street' for "224 Boston Street").
64
+ def short_name
65
+ @short_name ||= @entry[:id].sub('restaurantID-', '')
66
+ end
67
+
68
+ # Return the full name of this Restaurant, e.g. "224 Boston Street".
69
+ def name
70
+ unless @name
71
+ # UGLY, but it's not wrapped in a tag so there it is.
72
+ # split: ["224", "Boston", "Street", "[", "map", "]"]
73
+ split = @entry.css('h4')[0].text.strip.split(/\s+/)
74
+ # Remove [[", "map", "]"]
75
+ split.slice!(-3, 3)
76
+ @name = split.join(' ')
77
+ end
78
+ @name
79
+ end
80
+
81
+ # Return a 0-2 element array containing the meals offered by this
82
+ # Restaurant: "lunch" and/or "dinner".
83
+ def meals
84
+ unless @meals
85
+ @meals = []
86
+ lunch = ! @entry.css('a.lunchMenuButton').empty?
87
+ dinner = ! @entry.css('a.dinnerMenuButton').empty?
88
+ @meals << 'lunch' if lunch
89
+ @meals << 'dinner' if dinner
90
+ end
91
+ @meals
92
+ end
93
+
94
+ # Return the neighborhood this Restaurant is in (e.g. "back-bay").
95
+ def neighborhood
96
+ unless @neighborhood
97
+ link = @entry.css('a[@href*="neighborhood"]').first
98
+ @neighborhood = link.attributes['href'].value.sub('/?neighborhood=', '')
99
+ end
100
+ @neighborhood
101
+ end
102
+
103
+
104
+ # Return this Restaurant's phone number, or "<no phone given>" is none is
105
+ # provided.
106
+ def phone
107
+ unless @phone
108
+ # UGLY, but it's not wrapped in a tag so there it is.
109
+ phone = @entry.css('.restaurantInfoBasic > p').children[6].to_s
110
+ if phone == '<br>'
111
+ @phone = '<no phone given>'
112
+ else
113
+ @phone = phone
114
+ end
115
+ end
116
+ @phone
117
+ end
118
+
119
+ # +meal+ should be either "lunch" or "dinner"
120
+ def menu_link_for(meal)
121
+ if meal == 'lunch'
122
+ @lunch_menu_link ||= "http://www.restaurantweekboston.com/fetch/#{short_name}/lunch/"
123
+ elsif meal == 'dinner'
124
+ @dinner_menu_link ||= "http://www.restaurantweekboston.com/fetch/#{short_name}/dinner/"
125
+ end
126
+ end
127
+
128
+
129
+ # Return the URL to the map of this Restaurant at the RWB web site.
130
+ def map_link
131
+ @map_link ||= "http://www.restaurantweekboston.com/map/#{neighborhood}/#{short_name}/#topOfMap"
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,55 @@
1
+ require 'optparse'
2
+ require 'restaurant_week_boston/scraper'
3
+ require 'restaurant_week_boston/marker'
4
+
5
+ module RestaurantWeekBoston
6
+ # Used in the executable script, +restaurant_week_boston+.
7
+ class Runner
8
+ # Pass in ARGV.
9
+ def initialize(argv)
10
+ options = {}
11
+ optparse = OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{$0} [options] restaurant1, restaurant2, ..."
13
+
14
+ options[:meal] = :any
15
+ opts.on('-m', '--meal MEAL', 'Only get restaurants that offer this meal (lunch/dinner/both/any)') do |m|
16
+ options[:meal] = m
17
+ end
18
+
19
+ opts.on('-l', '--lunch RESTO1,RESTO2', 'Mark RESTAURANTS as good for lunch') do |lunches|
20
+ options[:lunch] = lunches.split(',')
21
+ end
22
+
23
+ opts.on('-d', '--dinner RESTO1,RESTO2', 'Mark RESTAURANTS as good for dinner') do |dinners|
24
+ options[:dinner] = dinners.split(',')
25
+ end
26
+
27
+ opts.on('-a', '--any RESTO1,RESTO2', 'Mark RESTAURANTS as good for any meal') do |any|
28
+ options[:any] = any.split(',')
29
+ end
30
+
31
+ options[:neighborhood] = :all # NOT any!
32
+ opts.on('-n', '--neighborhood HOOD',
33
+ 'Only get restaurants in this neighborhood (dorchester, back-bay, etc)') do |n|
34
+ n = :all if n == 'any' # :any makes it choke
35
+ options[:neighborhood] = n
36
+ end
37
+
38
+ opts.on('-h', '--help', 'Display this help') do
39
+ puts opts
40
+ return
41
+ end
42
+ end
43
+ optparse.parse!(argv)
44
+ @options = options
45
+ end
46
+
47
+ def run
48
+ scraper = Scraper.new(:meal => @options[:meal],
49
+ :neighborhood => @options[:neighborhood])
50
+ marker = Marker.new(scraper, @options)
51
+
52
+ puts marker.all
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,85 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+
4
+ require 'restaurant_week_boston/restaurant'
5
+
6
+ module RestaurantWeekBoston
7
+ # Scrapes Restaurant Week site.
8
+ class Scraper
9
+ include Enumerable
10
+ # +opts+ is a hash of options, with the following keys:
11
+ # :neighborhood:: dorchester, back-bay, etc. (default: "all")
12
+ # :meal:: lunch, dinner, both, or any (default: "any"). Will create a
13
+ # file in your home directory called ".restaurant_week_boston.cache"
14
+ # which contains the HTML from the RWB site, just so it doesn't have to
15
+ # keep getting it. You can delete that file, it'll just take longer next
16
+ # time since it will have to re-get the HTML.
17
+ def initialize(opts = {})
18
+ @url = create_url(opts)
19
+ @dump = File.expand_path('~/.restaurant_week_boston.cache')
20
+ entries = doc().css('.restaurantEntry')
21
+ @restaurants = entries.map{ |entry| Restaurant.new(entry) }
22
+ end
23
+
24
+ # +opts+ is a hash of options, with the following keys:
25
+ # :neighborhood:: dorchester, back-bay, etc. (default: :all)
26
+ # :meal:: lunch, dinner, both, or any (default: :any)
27
+ def create_url(opts = {})
28
+ # meal: any/lunch/dinner/both
29
+ # &view=all
30
+ default_opts = {:neighborhood => :all,
31
+ :meal => :any }
32
+ opts = default_opts.merge!(opts)
33
+ sprintf('http://www.restaurantweekboston.com/?neighborhood=%s&meal=%s&view=all',
34
+ opts[:neighborhood].to_s, opts[:meal].to_s)
35
+ end
36
+
37
+
38
+ # Iterates over @restaurants. All methods in Enumerable work.
39
+ def each(&blk)
40
+ @restaurants.each(&blk)
41
+ end
42
+
43
+ # Returns the result of open()ing the url from create_url(), as a String.
44
+ def get_html
45
+ print "Getting doc..."
46
+ if File.size? @dump
47
+ html = File.read(@dump)
48
+ else
49
+ html = open(@url).read()
50
+ f = File.new(@dump, 'w')
51
+ f.write(html)
52
+ f.close()
53
+ end
54
+ puts "done."
55
+ html
56
+ end
57
+
58
+ # Return a Nokogiri::HTML::Document parsed from get_html. Prints status
59
+ # messages along the way.
60
+ def doc
61
+ # get_html beforehand for good output messages
62
+ html = get_html
63
+ print "Parsing doc..."
64
+ doc = Nokogiri::HTML(html)
65
+ puts "done."
66
+ puts
67
+ doc
68
+ end
69
+
70
+ # Pass in an array of names that =~ (case-insensitive) the ones you're
71
+ # thinking of, and this will get those. So, if you're thinking of Bond,
72
+ # 224 Boston Street, and Artu, you can pass in ['bond', 'boston street',
73
+ # 'artu'].
74
+ def special_find(array)
75
+ array.map! do |name|
76
+ /#{Regexp.escape(name)}/i
77
+ end
78
+ results = @restaurants.find_all do |restaurant|
79
+ array.detect{ |regexp| regexp =~ restaurant.name }
80
+ end
81
+ # Add separators
82
+ results.join("\n" + '-' * 80 + "\n")
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'restaurant_week_boston/scraper'
3
+ require 'restaurant_week_boston/marker'
4
+ require 'restaurant_week_boston/restaurant'
5
+ require 'restaurant_week_boston/runner'
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{restaurant_week_boston}
5
+ s.version = "1.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Gabe Berke-Williams"]
9
+ s.date = %q{2010-08-14}
10
+ s.summary= %q{A fast, easy way to search the Boston Restaurant Week site and mark your favorites.}
11
+ s.description = %q{A fast, easy way to search the Boston Restaurant Week site and mark your favorites.}
12
+ s.email = %q{gbw@brandeis.edu}
13
+ s.extra_rdoc_files = ["README.rdoc"]
14
+ s.executables = ["restaurant_week_boston"]
15
+ s.default_executable = %q{restaurant_week_boston}
16
+ s.files = [
17
+ "ChangeLog",
18
+ "LICENSE",
19
+ "README.rdoc",
20
+ "bin/restaurant_week_boston",
21
+ "lib/restaurant_week_boston.rb",
22
+ "lib/restaurant_week_boston/scraper.rb",
23
+ "lib/restaurant_week_boston/marker.rb",
24
+ "lib/restaurant_week_boston/restaurant.rb",
25
+ "lib/restaurant_week_boston/runner.rb",
26
+ "restaurant_week_boston.gemspec"
27
+ ]
28
+ s.homepage = %q{http://github.com/gabebw/restaurant_week_boston}
29
+ s.rdoc_options = ["--charset=UTF-8"]
30
+ s.require_paths = ["lib"]
31
+ s.rubygems_version = %q{1.3.7}
32
+ s.test_files = []
33
+
34
+ if s.respond_to? :specification_version then
35
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
36
+ s.specification_version = 3
37
+ end
38
+ end
39
+
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: restaurant_week_boston
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 1
9
+ version: 1.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Gabe Berke-Williams
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-14 00:00:00 -07:00
18
+ default_executable: restaurant_week_boston
19
+ dependencies: []
20
+
21
+ description: A fast, easy way to search the Boston Restaurant Week site and mark your favorites.
22
+ email: gbw@brandeis.edu
23
+ executables:
24
+ - restaurant_week_boston
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ files:
30
+ - ChangeLog
31
+ - LICENSE
32
+ - README.rdoc
33
+ - bin/restaurant_week_boston
34
+ - lib/restaurant_week_boston.rb
35
+ - lib/restaurant_week_boston/scraper.rb
36
+ - lib/restaurant_week_boston/marker.rb
37
+ - lib/restaurant_week_boston/restaurant.rb
38
+ - lib/restaurant_week_boston/runner.rb
39
+ - restaurant_week_boston.gemspec
40
+ has_rdoc: true
41
+ homepage: http://github.com/gabebw/restaurant_week_boston
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.7
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: A fast, easy way to search the Boston Restaurant Week site and mark your favorites.
72
+ test_files: []
73
+