t6d-mensa 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mensa/fare.rb ADDED
@@ -0,0 +1,89 @@
1
+ # coding: utf-8
2
+
3
+ module Mensa
4
+ class Fare
5
+ TYPES = {
6
+ 'S' => :pork,
7
+ 'R' => :beef,
8
+ 'K' => :veal,
9
+ 'L' => :lamb,
10
+ 'G' => :poultry,
11
+ 'F' => :fish,
12
+ 'FL' => :meatless
13
+ }
14
+
15
+ attr_accessor :name
16
+ attr_accessor :type
17
+ attr_accessor :price
18
+
19
+ def initialize(name, type = nil, price = nil)
20
+ self.name = name
21
+ self.type = type
22
+ self.price = price
23
+ end
24
+
25
+ def name=(value)
26
+ value = value.to_s.strip
27
+
28
+ raise NameEmptyError, "An empty string or nil are not allowed as argument", caller if
29
+ value.empty?
30
+
31
+ raise NameTooLongError, "Name of fare is greater than 255 characters", caller if
32
+ value.length > 255
33
+
34
+ @name = value
35
+ end
36
+
37
+ def type=(value)
38
+ if value.nil?
39
+ @type = nil
40
+ elsif TYPES.values.include?(value)
41
+ @type = value
42
+ elsif TYPES.keys.include?(value)
43
+ @type = TYPES[value]
44
+ else
45
+ raise TypeUnknownError, "Type #{value} is invalid", caller
46
+ end
47
+
48
+ @type
49
+ end
50
+
51
+ def price=(value)
52
+ if value.nil?
53
+ @price = nil
54
+ elsif value.kind_of?(Integer)
55
+ @price = value
56
+ elsif value.kind_of?(Float)
57
+ euros, cents = /(\d+)\.(\d{1,2})/.match(value.to_s)[1..2]
58
+ if cents.length < 2
59
+ @price = euros.to_i * 100 + cents.to_i * 10
60
+ else
61
+ @price = euros.to_i * 100 + cents.to_i
62
+ end
63
+ elsif value.respond_to?(:to_s)
64
+ if group = /(\d+)[.,](\d{2})/.match(value.to_s)
65
+ euros, cents = group[1..2]
66
+ @price = euros.to_i * 100 + cents.to_i
67
+ elsif value.to_s.empty?
68
+ @price = nil
69
+ else
70
+ raise PriceStringInvalid,
71
+ "Fare price could not be extracted out of #{value.inspect}",
72
+ caller
73
+ end
74
+ end
75
+
76
+ @price
77
+ end
78
+
79
+ def ==(other)
80
+ self.name == other.name &&
81
+ self.type == other.type &&
82
+ self.price == other.price
83
+ end
84
+
85
+ def inspect
86
+ "Fare(#{name.inspect}, #{type.inspect}, #{price})"
87
+ end
88
+ end
89
+ end
data/lib/mensa.rb ADDED
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'iconv'
6
+
7
+ require 'rubygems'
8
+ require 'nokogiri'
9
+
10
+ require 'mensa/canteen'
11
+ require 'mensa/client'
12
+ require 'mensa/document'
13
+ require 'mensa/exceptions'
14
+ require 'mensa/fare'
15
+
16
+ module Mensa
17
+ MENSA_DIR = File.join(File.dirname(__FILE__), '..')
18
+ end
data/mensa.gemspec ADDED
@@ -0,0 +1,73 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{mensa}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Konstantin Tennhard"]
9
+ s.date = %q{2009-06-02}
10
+ s.default_executable = %q{mensa}
11
+ s.email = %q{development@tennhard.net}
12
+ s.executables = ["mensa"]
13
+ s.extra_rdoc_files = [
14
+ "LICENSE",
15
+ "README.rdoc"
16
+ ]
17
+ s.files = [
18
+ ".document",
19
+ ".gitignore",
20
+ "LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "bin/mensa",
25
+ "config/canteens.rb",
26
+ "fixtures/canteens.rb",
27
+ "fixtures/mensa-dieburg.html",
28
+ "fixtures/mensa-haardtring.html",
29
+ "fixtures/mensa-lichtwiese.html",
30
+ "fixtures/mensa-schofferstrase.html",
31
+ "fixtures/mensa-stadtmitte.html",
32
+ "fixtures/output.txt",
33
+ "lib/mensa.rb",
34
+ "lib/mensa/canteen.rb",
35
+ "lib/mensa/client.rb",
36
+ "lib/mensa/document.rb",
37
+ "lib/mensa/exceptions.rb",
38
+ "lib/mensa/fare.rb",
39
+ "mensa.gemspec",
40
+ "spec/mensa/canteen_spec.rb",
41
+ "spec/mensa/client_spec.rb",
42
+ "spec/mensa/document_spec.rb",
43
+ "spec/mensa/fare_spec.rb",
44
+ "spec/mensa_spec.rb",
45
+ "spec/spec_helper.rb"
46
+ ]
47
+ s.homepage = %q{http://github.com/t6d/mensa}
48
+ s.rdoc_options = ["--charset=UTF-8"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = %q{1.3.3}
51
+ s.summary = %q{Fetches the daily bill of fare from the canteen page of Studentenwerk Darmstadt.}
52
+ s.test_files = [
53
+ "spec/mensa/canteen_spec.rb",
54
+ "spec/mensa/client_spec.rb",
55
+ "spec/mensa/document_spec.rb",
56
+ "spec/mensa/fare_spec.rb",
57
+ "spec/mensa_spec.rb",
58
+ "spec/spec_helper.rb"
59
+ ]
60
+
61
+ if s.respond_to? :specification_version then
62
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
63
+ s.specification_version = 3
64
+
65
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
66
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.2.3"])
67
+ else
68
+ s.add_dependency(%q<nokogiri>, [">= 1.2.3"])
69
+ end
70
+ else
71
+ s.add_dependency(%q<nokogiri>, [">= 1.2.3"])
72
+ end
73
+ end
@@ -0,0 +1,81 @@
1
+ # coding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), '../spec_helper')
4
+
5
+ module Mensa
6
+ uri = {}
7
+ uri[:stadtmitte] = "http://www.studentenwerkdarmstadt.de/essen/mensa-stadtmitte.html"
8
+ uri[:lichtwiese] = "http://www.studentenwerkdarmstadt.de/essen/mensa-lichtwiese.html"
9
+ uri[:haardtring] = "http://www.studentenwerkdarmstadt.de/essen/mensa-haardtring.html"
10
+
11
+ fares = {}
12
+ fares[:stadtmitte] = [
13
+ Fare.new("Gemüse- Maultaschen mit Tomatensauce", :meatless, 180),
14
+ Fare.new("Linsen-Orangensuppe mit Baguette", :meatless, 210),
15
+ Fare.new("Rühreier mit Rahmspinat", :meatless, 120),
16
+ Fare.new("Wokpfanne \"Hot-Asia-Style\" mit Hähnchenbrust und Mie-Nudeln", :poultry, 280),
17
+ Fare.new("Hähnchen-Nuggets-im Knuspermantel dazu ein Kräuterdip", :poultry, 190),
18
+ Fare.new("Grillbraten vom Schweinenacken mit Thymianjus", :pork, 200)
19
+ ]
20
+ fares[:lichtwiese] = [
21
+ Fare.new("Vier Kartoffelpuffer mit Apfelmus und eine Beilage nach Wahl", :meatless, 210),
22
+ Fare.new("Balkanröllchen mit Knoblauchdip dazu Pommes frites und eine Beilage nach Wahl", :pork, 250),
23
+ Fare.new("Hähnchenbrust Hawaii mit Ananas und Käse überbacken und Pommes frites dazu eine Beilage nach Wahl", :poultry, 290),
24
+ Fare.new("Schweineschnitzel mit Champignonrahmsauce dazu Pommes frites und eine Beilage nach Wahl", :pork, 350),
25
+ Fare.new("Seelachsfilet paniert mit Sauce Tatar", :fish, 150),
26
+ Fare.new("Pasta Bolognese", :beef, 160),
27
+ Fare.new("Wokpfanne \"Thai 7 Spice\" mit Putenbrust und Basmatireis", :poultry, 280),
28
+ Fare.new("Country Kartoffeln mit Quark", :meatless, 110)
29
+ ]
30
+ fares[:haardtring] = []
31
+
32
+ date = {}
33
+ date[:stadtmitte] = Time.local(2009, 5, 28)
34
+ date[:lichtwiese] = Time.local(2009, 5, 29)
35
+ date[:haardtring] = Time.local(2009, 6, 1)
36
+
37
+ name = {}
38
+ name[:stadtmitte] = "Mensa Stadtmitte"
39
+ name[:lichtwiese] = "Mensa Lichtwiese"
40
+ name[:haardtring] = "Mensa Haardtring"
41
+
42
+ [:stadtmitte, :lichtwiese, :haardtring].each do |canteen|
43
+ describe "#{canteen.to_s.capitalize} am #{date[canteen].strftime('%d.%m.%Y')} Canteen" do
44
+ subject { Canteen.new(uri[canteen]) }
45
+
46
+ it { should have(fares[canteen].length).fares }
47
+
48
+ it "should equal an other canteen with the same name, date and state" do
49
+ other = Canteen.new(uri[canteen])
50
+ subject.should == other
51
+ end
52
+
53
+ if fares[canteen].empty?
54
+ it { should be_closed }
55
+ else
56
+ it { should be_opened }
57
+ end
58
+
59
+ describe "#date" do
60
+ it "should be #{date[canteen]}" do
61
+ subject.date.should == date[canteen]
62
+ end
63
+ end
64
+
65
+ describe "#name" do
66
+ it "should be \"Mensa Stadtmitte\"" do
67
+ subject.name.should == name[canteen]
68
+ end
69
+ end
70
+
71
+ describe "#fares" do
72
+ fares[canteen].each do |fare|
73
+ it "should return an Array which includes #{fare.inspect}" do
74
+ subject.fares.should be_an_instance_of(Array)
75
+ subject.fares.should include(fare)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,78 @@
1
+ # coding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), '../spec_helper')
4
+
5
+ module Mensa
6
+ describe Client do
7
+ user_config_file = File.join(ENV['HOME'], ".mensarc")
8
+ global_config_file = File.join(MENSA_DIR, 'config/canteens.rb')
9
+
10
+ canteens = {}
11
+ canteens[:stadtmitte] = Canteen.new(
12
+ 'http://www.studentenwerkdarmstadt.de/essen/mensa-stadtmitte.html'
13
+ )
14
+ canteens[:haardtring] = Canteen.new(
15
+ 'http://www.studentenwerkdarmstadt.de/essen/mensa-haardtring.html'
16
+ )
17
+
18
+ subject { Client.new(StringIO.new) }
19
+
20
+ describe 'with global settings (config/canteens.rb)' do
21
+
22
+ before do
23
+ File.should_receive(:exist?).with(user_config_file).ordered.and_return(false)
24
+ File.should_receive(:exist?).with(global_config_file).ordered.and_return(true)
25
+ end
26
+
27
+ it { should have(5).canteens }
28
+
29
+ end
30
+
31
+ describe 'with user specific settings (replaced with fixtures/canteens.rb)' do
32
+ config = File.read(File.join(MENSA_DIR, 'fixtures/canteens.rb'))
33
+
34
+ before do
35
+ File.should_receive(:exist?).with(user_config_file).and_return(true)
36
+ File.should_receive(:read).with(user_config_file).and_return(config)
37
+ end
38
+
39
+ it { should have(3).canteens }
40
+
41
+ it 'should have "Mensa Stadtmitte" as first canteen' do
42
+ subject.canteens[0].should == canteens[:stadtmitte]
43
+ end
44
+
45
+ it 'should have "Mensa Haardtring" as second cateen' do
46
+ subject.canteens[1].should == canteens[:haardtring]
47
+ end
48
+ end
49
+
50
+ describe "output with user specific settings" do
51
+ stdout = StringIO.new
52
+ output = File.read(File.join(MENSA_DIR, 'fixtures/output.txt'))
53
+ lines = nil
54
+
55
+ config = File.read(File.join(MENSA_DIR, 'fixtures/canteens.rb'))
56
+
57
+ before :all do
58
+ File.should_receive(:exist?).with(user_config_file).and_return(true)
59
+ File.should_receive(:read).with(user_config_file).and_return(config)
60
+
61
+ subject.run
62
+ lines = stdout.string.lines.to_a
63
+ end
64
+
65
+ subject { Client.new(stdout) }
66
+
67
+ output.lines.each_with_index do |expected_line, index|
68
+ it "#{index + 1}. line should be #{expected_line.inspect.gsub(' ', '⎵')}" do
69
+ if index < lines.length
70
+ lines[index].should == expected_line
71
+ else
72
+ fail "line missing"
73
+ end
74
+ end
75
+ end # do
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), '../spec_helper')
4
+
5
+ module Mensa
6
+ describe Document do
7
+ subject { Document.new('http://www.studentenwerkdarmstadt.de/essen/mensa-stadtmitte.html') }
8
+
9
+ describe '.new("http://www.studentenwerkdarmstadt.de/essen/mensa-stadtmitte.html")' do
10
+ it 'should have the URI "http://www.studentenwerkdarmstadt.de/essen/mensa-stadtmitte.html"' do
11
+ subject.uri.should be_kind_of(URI)
12
+ subject.uri.to_s.should == 'http://www.studentenwerkdarmstadt.de/essen/mensa-stadtmitte.html'
13
+ end
14
+ end
15
+
16
+ describe '#to_s' do
17
+ it "should equal fixture 'mensa-stadtmitte.html'" do
18
+ subject.to_s.should == fixture('mensa-stadtmitte.html')
19
+ end
20
+
21
+ it "should fetch the document only once" do
22
+ Net::HTTP.should_receive(:get)
23
+ 5.times { subject.to_s }
24
+ end
25
+
26
+ it "should return a String" do
27
+ subject.to_s.should be_an_instance_of(String)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,114 @@
1
+ # coding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), '../spec_helper')
4
+
5
+ module Mensa
6
+ describe Fare do
7
+ name, type, price = ['Rührei mit Spinat', 'FL', "1,80"]
8
+
9
+ subject { Fare.new(name, type, price) }
10
+
11
+ it "should be equal to another fare wich has the same name, price, and type" do
12
+ subject.should == Fare.new(name, type, price)
13
+ end
14
+
15
+ describe ".new('Rührei mit Spinat', 'FL', 180)" do
16
+ specify "#name should equal 'Rührei mit Spinat'" do
17
+ subject.name.should == 'Rührei mit Spinat'
18
+ end
19
+
20
+ specify "#type should equal :meatless" do
21
+ subject.type.should == :meatless
22
+ end
23
+
24
+ specify "#price should equal 180" do
25
+ subject.price.should == 180
26
+ end
27
+ end
28
+
29
+ describe "#type" do
30
+ it 'should raise an TypeUnknownError with :invalid as argument' do
31
+ lambda { subject.type = :invalid }.should raise_error(TypeUnknownError)
32
+ end
33
+
34
+ types = {
35
+ 'S' => :pork,
36
+ 'R' => :beef,
37
+ 'K' => :veal,
38
+ 'L' => :lamb,
39
+ 'G' => :poultry,
40
+ 'F' => :fish,
41
+ 'FL' => :meatless,
42
+ nil => nil
43
+ }
44
+
45
+ types.values.each do |type|
46
+ it "should recognise #{type.inspect} as valid type" do
47
+ lambda { subject.type = type }.should_not raise_error(TypeUnknownError)
48
+ end
49
+ end
50
+
51
+ types.keys.each do |type|
52
+ it "should recognise #{type.inspect} as valid type" do
53
+ lambda { subject.type = type }.should_not raise_error(TypeUnknownError)
54
+ subject.type.should == types[type]
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "#name" do
60
+ it "should be an instance of String" do
61
+ subject.name.should be_an_instance_of(String)
62
+ end
63
+
64
+ it "should accept objects which respond to #to_s" do
65
+ fare_name = "Rührei mit Spinat"
66
+ fare = mock('fare', :to_s => fare_name)
67
+
68
+ subject.name = fare
69
+ subject.name.should == fare_name
70
+ end
71
+
72
+ it "should strip the name" do
73
+ subject.name = " #{name} "
74
+ subject.name.should == name
75
+ end
76
+
77
+ it "should be less than 255 characters" do
78
+ lambda { subject.name = 'a' * 300 }.should raise_error(NameTooLongError)
79
+ end
80
+
81
+ it "should not accept an empty string or nil as argument" do
82
+ lambda { subject.name = '' }.should raise_error(NameEmptyError)
83
+ lambda { subject.name = nil }.should raise_error(NameEmptyError)
84
+ end
85
+ end
86
+
87
+ describe "#price" do
88
+ prices = {
89
+ 230 => 230,
90
+ 2.30 => 230,
91
+ 2.99 => 299,
92
+ 2.999 => 299,
93
+ '2.30' => 230,
94
+ '2,30' => 230,
95
+ '' => nil,
96
+ nil => nil,
97
+ 'str' => PriceStringInvalid
98
+ }
99
+
100
+ prices.each do |given, expected|
101
+ if expected.instance_of?(Class)
102
+ it "should recognise #{given.inspect} as invalid and raise a #{expected}" do
103
+ lambda { subject.price = given }.should raise_error(expected)
104
+ end
105
+ else
106
+ it "should recognise #{given.inspect} as #{expected.inspect} Euro cents" do
107
+ subject.price = given
108
+ subject.price.should == expected
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,7 @@
1
+ # coding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), 'spec_helper')
4
+
5
+ describe Mensa do
6
+
7
+ end
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+
3
+ require 'spec'
4
+ require 'fakeweb'
5
+
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+
9
+ require 'mensa'
10
+
11
+ PROJECT_DIR = File.join(File.dirname(__FILE__), '..')
12
+
13
+ #
14
+ # Helper methods
15
+ def fixture(filename)
16
+ string = Iconv.iconv('UTF-8', 'ISO-8859-1', File.read(File.join(PROJECT_DIR, 'fixtures', filename))).first
17
+ string.sub(/[Ii][Ss][Oo]-8859-1/, 'utf-8')
18
+ end
19
+
20
+ ##
21
+ # FakeWeb setup
22
+ FakeWeb.allow_net_connect = false
23
+
24
+ Dir.chdir(File.join(PROJECT_DIR, 'fixtures')) do
25
+ Dir.glob('*.html').each do |file|
26
+ FakeWeb.register_uri(
27
+ :get,
28
+ "http://www.studentenwerkdarmstadt.de/essen/#{file}",
29
+ :string => File.read(file)
30
+ )
31
+ end
32
+ end
33
+
34
+ ##
35
+ # Spec::Runner configuration
36
+ Spec::Runner.configure do |config|
37
+
38
+ end
39
+
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: t6d-mensa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Tennhard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-02 00:00:00 -07:00
13
+ default_executable: mensa
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: nokogiri
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.3
24
+ version:
25
+ description:
26
+ email: development@tennhard.net
27
+ executables:
28
+ - mensa
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - bin/mensa
42
+ - config/canteens.rb
43
+ - fixtures/canteens.rb
44
+ - fixtures/mensa-dieburg.html
45
+ - fixtures/mensa-haardtring.html
46
+ - fixtures/mensa-lichtwiese.html
47
+ - fixtures/mensa-schofferstrase.html
48
+ - fixtures/mensa-stadtmitte.html
49
+ - fixtures/output.txt
50
+ - lib/mensa.rb
51
+ - lib/mensa/canteen.rb
52
+ - lib/mensa/client.rb
53
+ - lib/mensa/document.rb
54
+ - lib/mensa/exceptions.rb
55
+ - lib/mensa/fare.rb
56
+ - mensa.gemspec
57
+ - spec/mensa/canteen_spec.rb
58
+ - spec/mensa/client_spec.rb
59
+ - spec/mensa/document_spec.rb
60
+ - spec/mensa/fare_spec.rb
61
+ - spec/mensa_spec.rb
62
+ - spec/spec_helper.rb
63
+ has_rdoc: false
64
+ homepage: http://github.com/t6d/mensa
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --charset=UTF-8
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.2.0
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Fetches the daily bill of fare from the canteen page of Studentenwerk Darmstadt.
89
+ test_files:
90
+ - spec/mensa/canteen_spec.rb
91
+ - spec/mensa/client_spec.rb
92
+ - spec/mensa/document_spec.rb
93
+ - spec/mensa/fare_spec.rb
94
+ - spec/mensa_spec.rb
95
+ - spec/spec_helper.rb