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