rice-dining 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e9daa87593abac15b47c885be99cac5417c530d4
4
+ data.tar.gz: 99c5275f06dc486a268dc90020122738425ea965
5
+ SHA512:
6
+ metadata.gz: c8cb808dd1528715fee7f9049b328b8590cfad5f03e8d45e0a0598a14d8fbcf6fd0d42a02118e8bf91731f79601c550b21ed6fa6119afe7e6a8f4b61393b65e3
7
+ data.tar.gz: 1cabcc2322f47969b27c9713aa90c1490b54106ff164726b0cf7738c441949a77b76420b54699661ea2adbd885a0b7e8b2c10b1e4862879cd70486b5dc229194
@@ -0,0 +1,12 @@
1
+ # Metadata
2
+ .DS_Store
3
+ *.swp
4
+ *.swo
5
+
6
+ # Generated files
7
+ *.gem
8
+ html
9
+
10
+ # Bundle
11
+ vendor
12
+ .bundle
data/COPYING ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (C) 2016+ Morgan Jones
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'rake'
7
+ end
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rice-dining (0.1)
5
+ colorize (>= 0.7.0)
6
+ nokogiri (>= 1.6.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ colorize (0.7.7)
12
+ mini_portile2 (2.0.0)
13
+ nokogiri (1.6.7.2)
14
+ mini_portile2 (~> 2.0.0.rc2)
15
+ rake (11.0.1)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ rake
22
+ rice-dining!
@@ -0,0 +1,12 @@
1
+ # rice-dining
2
+
3
+ Provides a `dining` executable that prints servery offerings at Rice.
4
+
5
+ ## Installation
6
+
7
+ `gem install rice-dining`
8
+
9
+ ## Invocation
10
+
11
+ Run `rice-dining` to print status for everything, or `rice-dining <regex>` to print status
12
+ for locations matching `<regex>`
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+ Copyright (c) 2016+ Morgan Jones
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ =end
24
+
25
+ require 'rice/dining'
26
+ require 'colorize'
27
+
28
+ begin
29
+ regex = !ARGV.empty? ? Regexp.compile(ARGV.first, Regexp::IGNORECASE) : //
30
+ manifest = Rice::Dining.manifest
31
+ allergens = manifest.allergens.sort {|a, b| a.id <=> b.id}
32
+ allergen_colors = {
33
+ vegan: :magenta,
34
+ vegetarian: :light_green,
35
+ gluten: :yellow,
36
+ soy: :white,
37
+ milk: :light_red,
38
+ eggs: :light_yellow,
39
+ fish: :blue,
40
+ shellfish: :light_blue,
41
+ peanuts: :red,
42
+ treenuts: :light_black
43
+ }
44
+ printed = 0
45
+
46
+ manifest.locations.each do |location|
47
+ next unless location.name =~ regex
48
+ STDOUT.print "#{location.name}: "
49
+ if location.open?
50
+ STDOUT.puts 'OPEN'.white.on_green
51
+ location.items.each_with_index do |item, idx|
52
+ STDOUT.print idx == 0 ? ' \- ' : ' |- '
53
+ allergens.each do |allergen|
54
+ color = allergen_colors.include?(allergen.id) ? allergen_colors[allergen.id] : :white
55
+ STDOUT.print item.allergens.include?(allergen) ? allergen.shortcode.to_s.colorize(color) : '-'
56
+ end
57
+ STDOUT.puts " #{item.name}"
58
+ end
59
+ else
60
+ STDOUT.puts 'CLOSED'.white.on_red
61
+ end
62
+
63
+ printed += 1
64
+ end
65
+
66
+ if printed > 0
67
+ max_width = 0
68
+ allergens.each do |allergen|
69
+ max_width = allergen.id.length if allergen.id.length > max_width
70
+ end
71
+
72
+ STDOUT.puts '-' * ((max_width + 3) * 5 + 12)
73
+ allergens.each_with_index do |allergen, idx|
74
+ newline = (idx + 1) % 5 == 0
75
+ color = allergen_colors.include?(allergen.id) ? allergen_colors[allergen.id] : :white
76
+ str = "#{allergen.shortcode.to_s.colorize(color)}: #{allergen.id}"
77
+ str << ' ' * (max_width - allergen.id.length) unless newline
78
+ STDOUT.print str
79
+ if newline
80
+ STDOUT.puts
81
+ else
82
+ STDOUT.print ' | '
83
+ end
84
+ end
85
+ end
86
+
87
+ exit 0
88
+ rescue => e
89
+ # Issue with something; catch it and exit with a meaningful error code
90
+ STDERR.puts "#$0: #{e.class}: #{e.message}"
91
+ STDERR.puts e.backtrace
92
+ exit 1
93
+ end
94
+
@@ -0,0 +1,189 @@
1
+ require 'set'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'nokogiri'
5
+
6
+ require 'rice/dining/version'
7
+
8
+ module Rice
9
+ module Dining
10
+ BASE = URI('http://dining.rice.edu').freeze
11
+
12
+ class Manifest
13
+ attr_reader :locations, :allergens
14
+ def initialize locations, allergens
15
+ raise ArgumentError unless locations.is_a? Enumerable and allergens.is_a? Enumerable
16
+ @locations, @allergens = [], Set.new
17
+ locations.each do |location|
18
+ raise ArgumentError unless location.is_a?(Rice::Dining::Location)
19
+ @locations << location
20
+ end
21
+ @locations.freeze
22
+
23
+ allergens.each do |allergen|
24
+ raise ArgumentError unless allergen.is_a?(Rice::Dining::Allergen)
25
+ @allergens << allergen
26
+ end
27
+ @allergens.freeze
28
+
29
+ self.freeze
30
+ end
31
+ end
32
+
33
+ class Location
34
+ attr_reader :name, :items
35
+ def initialize name, *items
36
+ raise ArgumentError unless name.is_a? String
37
+ @name = name.dup.freeze
38
+ @items = []
39
+ items.each do |item|
40
+ raise ArgumentError unless item.is_a?(Rice::Dining::Item)
41
+ @items << item
42
+ end
43
+
44
+ @items.freeze
45
+ self.freeze
46
+ end
47
+
48
+ def closed?
49
+ @items.empty?
50
+ end
51
+
52
+ def open?
53
+ !closed?
54
+ end
55
+ end
56
+
57
+ class Item
58
+ attr_reader :name, :allergens
59
+ def initialize name, *allergens
60
+ raise ArgumentError unless name.is_a? String
61
+ @name = name.dup.freeze
62
+ @allergens = Set.new
63
+ allergens.each do |allergen|
64
+ raise ArgumentError unless allergen.is_a?(Rice::Dining::Allergen)
65
+ @allergens << allergen
66
+ end
67
+ @allergens.freeze
68
+ self.freeze
69
+ end
70
+ end
71
+
72
+ class Allergen
73
+ attr_reader :id, :shortcode
74
+ def initialize id, shortcode
75
+ raise ArgumentError if !id.is_a? Symbol or !shortcode.is_a? Symbol
76
+ @id, @shortcode = id, shortcode
77
+ self.freeze
78
+ end
79
+
80
+ def == other
81
+ (self <=> other) == 0
82
+ end
83
+
84
+ def != other
85
+ (self <=> other) != 0
86
+ end
87
+
88
+ def <=> other
89
+ self.id <=> other.id
90
+ end
91
+
92
+ def hash
93
+ self.id.hash
94
+ end
95
+ end
96
+
97
+ ManifestCreateError ||= Class.new StandardError
98
+
99
+ def self.manifest
100
+ # Make the request
101
+ req = Net::HTTP::Get.new Rice::Dining::BASE
102
+ req['User-Agent'.freeze] = Rice::Dining::IDENT
103
+ res = Net::HTTP.start(Rice::Dining::BASE.hostname, Rice::Dining::BASE.port,
104
+ use_ssl: Rice::Dining::BASE.scheme == 'https'.freeze) {|h| h.request(req)}
105
+
106
+ if res.is_a? Net::HTTPSuccess
107
+ doc = Nokogiri::HTML(res.body)
108
+
109
+ # stash allergen references
110
+ allergens, allergen_shortcodes = {}, Set.new
111
+
112
+ # build locations
113
+ locations = []
114
+ location_nodes = doc.css('div.item'.freeze)
115
+ raise ManifestCreateError, "couldn't find locations".freeze if location_nodes.empty?
116
+ location_nodes.each do |location_node|
117
+ # Get the servery name
118
+ name_nodes = location_node.css('div.servery-title h1'.freeze)
119
+ next if name_nodes.empty?
120
+ name = name_nodes.first.text
121
+ name.strip!
122
+
123
+ # might be closed
124
+ closed = !location_node.css('div.nothere'.freeze).empty?
125
+ if closed
126
+ locations << Rice::Dining::Location.new(name)
127
+ else
128
+ # grab the items
129
+ items = []
130
+ item_nodes = location_node.css('div.menu-item'.freeze)
131
+ item_nodes.each do |item_node|
132
+ item_allergens, item_name = [], item_node.text
133
+ item_name.strip!
134
+ item_node.parent.css('div.allergen div.diet'.freeze).each do |allergen_node|
135
+ # build the allergen key
136
+ key = Rice::Dining.allergen_cleanup allergen_node['class'.freeze]
137
+ if !allergens.include? key
138
+ # find a unique value for the shortcode
139
+ shortcode = key[0].to_sym
140
+ if allergen_shortcodes.include? shortcode
141
+ shortcode = shortcode.swapcase
142
+
143
+ while allergen_shortcodes.include? shortcode
144
+ shortcode = shortcode.downcase.succ
145
+ end
146
+ end
147
+
148
+ # create the allergen
149
+ allergen = allergens[key] = Rice::Dining::Allergen.new(key, shortcode)
150
+ allergen_shortcodes << shortcode
151
+ else
152
+ allergen = allergens[key]
153
+ end
154
+
155
+ item_allergens << allergen
156
+ end
157
+
158
+ items << Rice::Dining::Item.new(item_name, *item_allergens.sort)
159
+ end
160
+
161
+ locations << Rice::Dining::Location.new(name, *items)
162
+ end
163
+ end
164
+
165
+ locations.sort! do |a, b|
166
+ if a.closed? and b.open?
167
+ 1
168
+ elsif a.open? and b.closed?
169
+ -1
170
+ else
171
+ a.name <=> b.name
172
+ end
173
+ end
174
+
175
+ Rice::Dining::Manifest.new locations, allergens.values
176
+ else
177
+ # Problem with the response
178
+ raise ManifestCreateError, "got HTTP #{res.code} from #{Rice::Dining::BASE}"
179
+ end
180
+ end
181
+
182
+ def self.allergen_cleanup allergens
183
+ ret = allergens.match(/\Adiet\s+(?<type>[a-z]+)/i)
184
+ return nil if ret.nil?
185
+ ret[:type].downcase.to_sym
186
+ end
187
+ end
188
+ end
189
+
@@ -0,0 +1,13 @@
1
+ module Rice
2
+ module Dining
3
+ module Version
4
+ VERSION = '0.2'.freeze
5
+ SHORT_NAME = Rice::Dining.to_s.freeze
6
+ SHORT_IDENT = "#{SHORT_NAME} v#{VERSION}".freeze
7
+ NAME = SHORT_NAME.freeze
8
+ IDENT = SHORT_IDENT.freeze
9
+ end
10
+
11
+ include Version
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/rice/dining/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'rice-dining'
5
+ s.version = Rice::Dining::VERSION
6
+ s.authors = ['Morgan Jones']
7
+ s.email = 'mjones@rice.edu'
8
+ s.homepage = 'https://numin.it'
9
+ s.date = '2016-03-09'
10
+ s.summary = 'Gets Rice servery information'
11
+ s.description = 'Parses the HTML tag soup on the Rice dining homepage to get current servery information'
12
+ s.license = 'MIT'
13
+ s.files = `git ls-files`.split($\)
14
+ s.require_paths = %w[lib]
15
+ s.executables << 'rice-dining'
16
+ s.has_rdoc = true
17
+ s.required_ruby_version = '>= 2.0'
18
+ s.add_dependency 'nokogiri', '~> 1.6'
19
+ s.add_runtime_dependency 'colorize', '~> 0.7'
20
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rice-dining
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - Morgan Jones
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.7'
41
+ description: Parses the HTML tag soup on the Rice dining homepage to get current servery
42
+ information
43
+ email: mjones@rice.edu
44
+ executables:
45
+ - rice-dining
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - COPYING
51
+ - Gemfile
52
+ - Gemfile.lock
53
+ - README.md
54
+ - bin/rice-dining
55
+ - lib/rice/dining.rb
56
+ - lib/rice/dining/version.rb
57
+ - rice-dining.gemspec
58
+ homepage: https://numin.it
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '2.0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.4.5
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Gets Rice servery information
82
+ test_files: []