pennu 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ main.rb
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pennu.gemspec
4
+ gemspec
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,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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
@@ -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
@@ -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"]
@@ -0,0 +1,5 @@
1
+ module Pennu
2
+ module Ruby
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
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: []