pennu 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.
- 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: []
|