pennu 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +58 -0
- data/Rakefile +2 -0
- data/lib/classes/array.rb +19 -0
- data/lib/classes/day.rb +9 -0
- data/lib/classes/dininghall.rb +80 -0
- data/lib/classes/menu.rb +35 -0
- data/lib/classes/string.rb +34 -0
- data/lib/pennu.rb +16 -0
- data/lib/pennu/version.rb +5 -0
- data/pennu.gemspec +19 -0
- metadata +73 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Matt Parmett
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# pennu: Penn students, get fed. #
|
2
|
+
|
3
|
+
pennu is a simple Ruby library (soon to be gem) that allows you to easily retreive the menus of Penn dining halls.
|
4
|
+
|
5
|
+
## How to use pennu ##
|
6
|
+
|
7
|
+
*This section may change as pennu is developed. As such, this section may not be fully accurate, but I will try to keep the instructions as current as possible.*
|
8
|
+
|
9
|
+
Running:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'pennu'
|
13
|
+
|
14
|
+
# Download weekly menu for all meals at given dining hall
|
15
|
+
# Supported dining halls = ["hill", "commons", "kings court"]
|
16
|
+
hill = DiningHall.new('hill')
|
17
|
+
|
18
|
+
# You can call meals by meal name and day of week
|
19
|
+
puts hill.friday.dinner
|
20
|
+
```
|
21
|
+
|
22
|
+
will yield:
|
23
|
+
```
|
24
|
+
Menu for dinner at Hill on Friday, October 19, 2012:
|
25
|
+
Kettles: Mushroom Barley and Chicken Tortilla Soup
|
26
|
+
Comfort: Chicken Provencal, Parmesan Polenta, Italian Green Beans and Sauteed Zucchini
|
27
|
+
Flipped: Hamburgers, Hot Dogs, Grilled Marinated Chicken, Vegan Patty and French Fries
|
28
|
+
Expo: Butternut Squash Risotto
|
29
|
+
Pizza: An Ever Changing Selection of Italian Favorites
|
30
|
+
Hemispheres: Local Yogurts, Fresh Baked Bagels and Bread Bar, Assorted Pastriesand Desserts, Make your Own Waffle Bar, Ice Cream
|
31
|
+
Deli: Assorted Breads, Deli Meats and Cheeses made to order
|
32
|
+
Good 4 You: Tofu and Red Lentil Risotto, Roasted Eggplant, Balsamic Tomatoes
|
33
|
+
```
|
34
|
+
|
35
|
+
Calling ``` hill.friday.dinner``` will yield a ``` String``` like the output above. The menu items are more accessible as a hash of titles (e.g. ```Kettles``` and items (e.g. ```Mushroom Barley and Chicken Tortilla Soup```). To get the menu as a hash, simple call ```to_hash```. Running:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
puts hill.friday.dinner.to_hash.inspect
|
39
|
+
```
|
40
|
+
|
41
|
+
will yield:
|
42
|
+
```
|
43
|
+
{"Kettles"=>"Mushroom Barley and Chicken Tortilla Soup", "Comfort"=>"Chicken Provencal, Parmesan Polenta, Italian Green Beans and Sauteed Zucchini", "Flipped"=>"Hamburgers, Hot Dogs, Grilled Marinated Chicken, Vegan Patty and French Fries", "Expo"=>"Butternut Squash Risotto", "Pizza"=>"An Ever Changing Selection of Italian Favorites", "Hemispheres"=>"Local Yogurts, Fresh Baked Bagels and Bread Bar, Assorted Pastries and Desserts, Make your Own Waffle Bar, Ice Cream", "Deli"=>"Assorted Breads, Deli Meats and Cheeses made to order", "Good 4 You"=>"Tofu and Red Lentil Risotto, Roasted Eggplant, Balsamic Tomatoes"}
|
44
|
+
```
|
45
|
+
|
46
|
+
## Warnings ##
|
47
|
+
|
48
|
+
pennu hasn't yet been extensively tested and may fail at certain edge cases. I would really appreciate bug reports and pull requests if you happen to stumble on a mishandled edge case.
|
49
|
+
|
50
|
+
pennu was written and tested on Windows; some hiccups may result from running pennu on other platforms. I'd appreciate feedback if any such issues do occur.
|
51
|
+
|
52
|
+
## Contributing ##
|
53
|
+
|
54
|
+
All contributions are welcome via pull request.
|
55
|
+
|
56
|
+
## TODO ##
|
57
|
+
* Create gem
|
58
|
+
* More extensive testing
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class Array
|
2
|
+
# from rails: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/array/grouping.rb
|
3
|
+
# Divides the array into one or more subarrays based on a delimiting +value+
|
4
|
+
# or the result of an optional block.
|
5
|
+
#
|
6
|
+
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
|
7
|
+
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
|
8
|
+
def split(value = nil, &block)
|
9
|
+
inject([[]]) do |results, element|
|
10
|
+
if block && block.call(element) || value == element
|
11
|
+
results << []
|
12
|
+
else
|
13
|
+
results.last << element
|
14
|
+
end
|
15
|
+
|
16
|
+
results
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/classes/day.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
class Day
|
2
|
+
attr_accessor :date, :breakfast, :lunch, :dinner
|
3
|
+
def initialize(dining_hall, date)
|
4
|
+
@date = date
|
5
|
+
@breakfast = Menu.new(dining_hall, date, 'breakfast')
|
6
|
+
@lunch = Menu.new(dining_hall, date, 'lunch')
|
7
|
+
@dinner = Menu.new(dining_hall, date, 'dinner')
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class DiningHall
|
2
|
+
attr_accessor :name, :urls, :tds, :dates, :sunday, :monday,
|
3
|
+
:tuesday, :wednesday, :thursday, :friday,
|
4
|
+
:saturday, :sunday
|
5
|
+
|
6
|
+
# Has days, which have meals
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
case @name.downcase
|
10
|
+
when "hill"
|
11
|
+
dining_hall_path = "hill/"
|
12
|
+
when "commons"
|
13
|
+
dining_hall_path = "commons/"
|
14
|
+
when "kings court"
|
15
|
+
dining_hall_path = "kings/"
|
16
|
+
when "mcclelland"
|
17
|
+
dining_hall_path = "mcclelland/"
|
18
|
+
end
|
19
|
+
|
20
|
+
@urls = {
|
21
|
+
'breafast' => ROOT_URL + dining_hall_path + BREAKFAST_PATH,
|
22
|
+
'lunch' => ROOT_URL + dining_hall_path + LUNCH_PATH,
|
23
|
+
'dinner' => ROOT_URL + dining_hall_path + DINNER_PATH,
|
24
|
+
}
|
25
|
+
|
26
|
+
self.get_menus()
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_menus()
|
30
|
+
# Get menu table from dining website, use dinner for dates
|
31
|
+
agent = Mechanize.new
|
32
|
+
meal_page = agent.get(self.urls['dinner'])
|
33
|
+
table = meal_page.search("div.boxbody")
|
34
|
+
|
35
|
+
# Scrape all tds into array
|
36
|
+
@tds = []
|
37
|
+
table.xpath('//tr/td').to_a.each do |td|
|
38
|
+
td = td.text.lstrip.rstrip
|
39
|
+
tds << td unless (td.gsub(/\s+/, "") == "" or td.gsub(/\s+/, "") == "\u00A0")
|
40
|
+
end
|
41
|
+
|
42
|
+
# Remove dining hall title, which is the first element in tds
|
43
|
+
@tds.shift
|
44
|
+
|
45
|
+
# Create array of dates
|
46
|
+
tds_clone = @tds.clone
|
47
|
+
@dates = @tds.keep_if { |td|
|
48
|
+
begin
|
49
|
+
DateTime.strptime(td, "%A, %B %d, %Y")
|
50
|
+
rescue ArgumentError
|
51
|
+
false
|
52
|
+
else
|
53
|
+
true
|
54
|
+
end
|
55
|
+
}.map { |day_string| DateTime.strptime(day_string, "%A, %B %d, %Y") }
|
56
|
+
|
57
|
+
# Clone tds to preserve for later methods
|
58
|
+
@tds = tds_clone
|
59
|
+
|
60
|
+
# Assign dates to days
|
61
|
+
# Each day will contain b, l, and d menus
|
62
|
+
@dates.each do |date|
|
63
|
+
if date.strftime("%A") == "Sunday"
|
64
|
+
@sunday = Day.new(self, date)
|
65
|
+
elsif date.strftime("%A") == "Monday"
|
66
|
+
@monday = Day.new(self, date)
|
67
|
+
elsif date.strftime("%A") == "Tuesday"
|
68
|
+
@tuesday = Day.new(self, date)
|
69
|
+
elsif date.strftime("%A") == "Wednesday"
|
70
|
+
@wednesday = Day.new(self, date)
|
71
|
+
elsif date.strftime("%A") == "Thursday"
|
72
|
+
@thursday = Day.new(self, date)
|
73
|
+
elsif date.strftime("%A") == "Friday"
|
74
|
+
@friday = Day.new(self, date)
|
75
|
+
elsif date.strftime("%A") == "Saturday"
|
76
|
+
@saturday = Day.new(self, date)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/classes/menu.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class Menu
|
2
|
+
# Scrapes for one dining hall, one day, one meal
|
3
|
+
def initialize(dining_hall, date, meal)
|
4
|
+
@date = date
|
5
|
+
@meal = meal
|
6
|
+
@dining_hall = dining_hall
|
7
|
+
|
8
|
+
# Split dining hall tds array by date delimiter
|
9
|
+
# Each child array contains menu titles and items for one day
|
10
|
+
days_items = @dining_hall.tds.split do |td|
|
11
|
+
begin
|
12
|
+
DateTime.strptime(td, "%A, %B %d, %Y")
|
13
|
+
rescue ArgumentError
|
14
|
+
false
|
15
|
+
else
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
@day_items = Hash[*days_items[DAYS.index(date.strftime("%A").downcase)].flatten]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash()
|
24
|
+
# Return hash of items and titles
|
25
|
+
@day_items
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s()
|
29
|
+
str = "Menu for #{@meal} at #{@dining_hall.name.titlecase} on #{@date.strftime("%A, %B %d, %Y")}:\n"
|
30
|
+
@day_items.each do |title, item|
|
31
|
+
str += "#{title}: #{item}\n"
|
32
|
+
end
|
33
|
+
str.rstrip
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class String
|
2
|
+
#Methods to convert strings to titlecase.
|
3
|
+
#Thanks https://github.com/samsouder/titlecase
|
4
|
+
def titlecase
|
5
|
+
small_words = %w(a an and as at but by en for if in of on or the to v v. via vs vs.)
|
6
|
+
|
7
|
+
x = split(" ").map do |word|
|
8
|
+
# note: word could contain non-word characters!
|
9
|
+
# downcase all small_words, capitalize the rest
|
10
|
+
small_words.include?(word.gsub(/\W/, "").downcase) ? word.downcase! : word.smart_capitalize!
|
11
|
+
word
|
12
|
+
end
|
13
|
+
# capitalize first and last words
|
14
|
+
x.first.smart_capitalize!
|
15
|
+
x.last.smart_capitalize!
|
16
|
+
# small words after colons are capitalized
|
17
|
+
x.join(" ").gsub(/:\s?(\W*#{small_words.join("|")}\W*)\s/) { ": #{$1.smart_capitalize} " }
|
18
|
+
end
|
19
|
+
|
20
|
+
def smart_capitalize
|
21
|
+
# ignore any leading crazy characters and capitalize the first real character
|
22
|
+
if self =~ /^['"\(\[']*([a-z])/
|
23
|
+
i = index($1)
|
24
|
+
x = self[i,self.length]
|
25
|
+
# word with capitals and periods mid-word are left alone
|
26
|
+
self[i,1] = self[i,1].upcase unless x =~ /[A-Z]/ or x =~ /\.\w+/
|
27
|
+
end
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def smart_capitalize!
|
32
|
+
replace(smart_capitalize)
|
33
|
+
end
|
34
|
+
end
|
data/lib/pennu.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Ruby gem that retrieves menus for Penn dining halls.
|
2
|
+
# Written by Matt Parmett, W'14 C'14
|
3
|
+
|
4
|
+
# Requires
|
5
|
+
require 'mechanize'
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
# Load classes
|
9
|
+
Dir[File.dirname(__FILE__) + "/classes/*.rb"].each { |file| require file }
|
10
|
+
|
11
|
+
# Constants
|
12
|
+
ROOT_URL = "http://www.diningatpenn.com/penn/cafes/residential/"
|
13
|
+
BREAKFAST_PATH = "weekly_menu2.html"
|
14
|
+
LUNCH_PATH = "weekly_menu.html"
|
15
|
+
DINNER_PATH = "weekly_menu3.html"
|
16
|
+
DAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]
|
data/pennu.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/pennu/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = "Matt Parmett"
|
6
|
+
gem.email = "parm289@yahoo.com"
|
7
|
+
gem.description = %q{Ruby gem to retrieve Penn dining hall menus}
|
8
|
+
gem.summary = %q{Ruby gem to retrieve Penn dining hall menus}
|
9
|
+
gem.homepage = "http://github.com/mattparmett/pennu"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "pennu"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Pennu::Ruby::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "mechanize"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pennu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Parmett
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mechanize
|
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
|
+
description: Ruby gem to retrieve Penn dining hall menus
|
31
|
+
email: parm289@yahoo.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- Gemfile
|
38
|
+
- LICENSE
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- lib/classes/array.rb
|
42
|
+
- lib/classes/day.rb
|
43
|
+
- lib/classes/dininghall.rb
|
44
|
+
- lib/classes/menu.rb
|
45
|
+
- lib/classes/string.rb
|
46
|
+
- lib/pennu.rb
|
47
|
+
- lib/pennu/version.rb
|
48
|
+
- pennu.gemspec
|
49
|
+
homepage: http://github.com/mattparmett/pennu
|
50
|
+
licenses: []
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.23
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Ruby gem to retrieve Penn dining hall menus
|
73
|
+
test_files: []
|