brewser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,214 @@
1
+ class ProMashTxt < Brewser::Engine
2
+
3
+ class << self
4
+
5
+ def acceptable?(q)
6
+ begin
7
+ q.match("A ProMash Recipe Report") ? true : false
8
+ rescue
9
+ false
10
+ end
11
+ end
12
+
13
+ def deserialize(string)
14
+ object = string.match("A ProMash Recipe Report") ? ProMashTxt::Recipe.new : ProMashTxt::Batch.new
15
+ object.from_promash(string.split("\r\n").reject{|a|a==""})
16
+ return object
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ #
24
+ class ProMashTxt::Hop < Brewser::Hop
25
+
26
+ def from_promash(string)
27
+ # String should look like this:
28
+ # 3.00 oz. Cascade Whole 4.35 46.6 60 min.
29
+ self.amount = string[0..8].strip.u
30
+ self.name = string[14..46].strip
31
+ self.form = string[48..55].strip
32
+ self.alpha_acids = string[56..61].strip.to_f
33
+ time = string[69..77].strip
34
+ case time
35
+ when "Dry Hop"
36
+ self.added_when = "Dry Hop"
37
+ when "Mash H"
38
+ self.added_when = "Mash"
39
+ when "First WH"
40
+ self.added_when = "First Wort"
41
+ else
42
+ self.added_when = "Boil"
43
+ self.time = time.chop.u
44
+ end
45
+ return self
46
+ end
47
+
48
+ end
49
+
50
+ class ProMashTxt::Fermentable < Brewser::Fermentable
51
+
52
+ def from_promash(string)
53
+ # String should look like this:
54
+ # 12.5 1.50 lbs. Victory Malt America 1.034 25
55
+ self.amount = string[8..17].strip.u
56
+ self.name = string[20..48].strip
57
+ self.potential = string[65..70].strip
58
+ self.color = string[74..77].strip.to_f
59
+ return self
60
+ end
61
+
62
+ end
63
+
64
+ class ProMashTxt::Additive < Brewser::Additive
65
+
66
+ def from_promash(string)
67
+ # String should look like this:
68
+ # 1.00 Tsp Irish Moss Fining 15 Min.(boil)
69
+ self.amount = string[0..10].strip.downcase.u
70
+ self.name = string[14..44].strip
71
+ self.type = string[45..54].strip
72
+ self.time = string[55..61].strip.downcase.gsub(".","").u
73
+ self.added_when = string[63..66].capitalize
74
+ return self
75
+ end
76
+
77
+ end
78
+
79
+ class ProMashTxt::Yeast < Brewser::Yeast
80
+
81
+ def from_promash(string)
82
+ self.name = string || "Unspecified"
83
+ return self
84
+ end
85
+
86
+ end
87
+
88
+ class ProMashTxt::MashStep < Brewser::MashStep
89
+
90
+ end
91
+
92
+ class ProMashTxt::MashSchedule < Brewser::MashSchedule
93
+
94
+ end
95
+
96
+ class ProMashTxt::FermentationStep < Brewser::FermentationStep
97
+
98
+ end
99
+
100
+ class ProMashTxt::FermentationSchedule < Brewser::FermentationSchedule
101
+
102
+ end
103
+
104
+ class ProMashTxt::WaterProfile < Brewser::WaterProfile
105
+ def from_promash(array)
106
+ self.name = /Profile:\s*(?<name>.*)/.match(array[0]){ |m| m[:name] } || "Unspecified"
107
+ self.calcium = /(?<calcium>\S*)\sppm$/.match(array[2]){ |m| m[:calcium] }
108
+ self.magnesium = /(?<magnesium>\S*)\sppm$/.match(array[3]){ |m| m[:magnesium] }
109
+ self.sodium = /(?<sodium>\S*)\sppm$/.match(array[4]){ |m| m[:sodium] }
110
+ self.sulfates = /(?<sulfate>\S*)\sppm$/.match(array[5]){ |m| m[:sulfate] }
111
+ self.chloride = /(?<chloride>\S*)\sppm$/.match(array[6]){ |m| m[:chloride] }
112
+ self.bicarbonate = /(?<bicarbonate>\S*)\sppm$/.match(array[7]){ |m| m[:bicarbonate] }
113
+ self.ph = /(?<ph>\S*)$/.match(array[8]){ |m| m[:ph] }
114
+ return self
115
+ end
116
+
117
+ end
118
+
119
+ class ProMashTxt::Style < Brewser::Style
120
+
121
+ def from_promash(array)
122
+ if pointer = array.index("BJCP Style and Style Guidelines")
123
+ self.style_guide = "BJCP"
124
+ elsif pointer = array.index("AHA Style and Style Guidelines")
125
+ self.style_guide = "AHA"
126
+ else
127
+ raise Error, "ProMashTxt engine: unable to find style guideline"
128
+ end
129
+ # Line @ pointer should look like this:
130
+ # 06-B American Pale Ales, American Amber Ale
131
+ match_data = /^(?<category_number>\d{2})-(?<style_letter>\w{1})\s*(?<category>(\w\s*)+)\,\s(?<name>(\w\s*)+)/.match(array[pointer+2])
132
+ if match_data
133
+ self.name = match_data[:name]
134
+ self.category = match_data[:category]
135
+ self.category_number = match_data[:category_number]
136
+ self.style_letter = match_data[:style_letter]
137
+ else
138
+ raise Error, "ProMashTxt engine: Unable to extract style details"
139
+ end
140
+ return self
141
+ end
142
+
143
+ end
144
+
145
+ class ProMashTxt::Recipe < Brewser::Recipe
146
+
147
+ def from_promash(array)
148
+ self.name=array[0]
149
+ self.style=ProMashTxt::Style.new.from_promash(array)
150
+ # Recipe specifics should look like this:
151
+ # Recipe Specifics
152
+ # ----------------
153
+ # Batch Size (Gal): 6.00 Wort Size (Gal): 6.00
154
+ # Total Grain (Lbs): 12.00
155
+ # Anticipated OG: 1.059 Plato: 14.52
156
+ # Anticipated SRM: 11.0
157
+ # Anticipated IBU: 53.5
158
+ # Brewhouse Efficiency: 80 %
159
+ # Wort Boil Time: 60 Minutes
160
+ # Can't tell the difference between P/M and A/G so use A/G
161
+ raise Error, "ProMashTxt engine: Unable to find recipe specifics" unless pointer = array.index("Recipe Specifics")
162
+ self.method = /Total Grain/.match(array[pointer+3]).nil? ? "Extract" : "All Grain"
163
+ /Batch Size \((?<volume_unit>\w+)\):\s*(?<batch_size>\S*)\s*Wort Size \(\w*\):\s*(?<boil_size>\S*)/.match(array[pointer+2]) do |match|
164
+ self.recipe_volume = "#{match[:batch_size]} #{match[:volume_unit].downcase}".u
165
+ self.boil_volume = "#{match[:boil_size]} #{match[:volume_unit].downcase}".u
166
+ end
167
+ /Anticipated OG:\s*(?<anticipated_og>\S*)/.match(array[pointer+4]) do |match|
168
+ self.estimated_og = match[:anticipated_og].to_f
169
+ end
170
+ /Anticipated SRM:\s*(?<anticipated_color>\S*)/.match(array[pointer+5]) do |match|
171
+ self.estimated_color = match[:anticipated_color].to_f
172
+ end
173
+ /Anticipated IBU:\s*(?<anticipated_ibu>\S*)/.match(array[pointer+6]) do |match|
174
+ self.estimated_ibu = match[:anticipated_ibu].to_f
175
+ end
176
+ if method == "All Grain"
177
+ /Brewhouse Efficiency:\s*(?<recipe_efficiency>\S*)/.match(array[pointer+7]) do |match|
178
+ self.recipe_efficiency = match[:recipe_efficiency].to_f
179
+ end
180
+ boil_time_pointer = pointer+8
181
+ else
182
+ boil_time_pointer = pointer+7
183
+ end
184
+ /Wort Boil Time:\s*(?<boil_time>\S*)/.match(array[boil_time_pointer]) do |match|
185
+ self.boil_time = "#{match[:boil_time]} min".u
186
+ end
187
+
188
+ hops_start = array.index("Hops")+3
189
+ hops_end = (array.index("Extras") || array.index("Yeast"))-1
190
+ array[hops_start..hops_end].each do |hop|
191
+ self.hops.push ProMashTxt::Hop.new.from_promash(hop)
192
+ end
193
+
194
+ fermentables_start = array.index("Grain/Extract/Sugar")+3
195
+ fermentables_end = array.index("Hops")-2
196
+ array[fermentables_start..fermentables_end].each do |fermentable|
197
+ self.fermentables.push ProMashTxt::Fermentable.new.from_promash(fermentable)
198
+ end
199
+
200
+ unless array.index("Extras").nil?
201
+ additives_start = array.index("Extras")+3
202
+ additives_end = array.index("Yeast")-1
203
+ array[additives_start..additives_end].each do |additive|
204
+ self.additives.push ProMashTxt::Additive.new.from_promash(additive)
205
+ end
206
+ end
207
+
208
+ self.yeasts.push ProMashTxt::Yeast.new.from_promash(array[array.index("Yeast")+2])
209
+ self.water_profile = ProMashTxt::WaterProfile.new.from_promash(array[array.index("Water Profile")+2,array.index("Water Profile")+10])
210
+
211
+ return self
212
+ end
213
+
214
+ end
@@ -0,0 +1,11 @@
1
+ module Brewser
2
+ # A general exception
3
+ class Error < StandardError; end
4
+
5
+ # Raised when behavior is not implemented, usually used in an abstract class.
6
+ class NotImplemented < Error; end
7
+
8
+ # Raised when removed code is called, an alternative solution is provided in message.
9
+ class ImplementationRemoved < Error; end
10
+
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'multi_json'
2
+
3
+ class String
4
+ def valid_json?
5
+ begin
6
+ MultiJson.decode(self)
7
+ return true
8
+ rescue Exception => e
9
+ return false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Brewser
2
+ class Additive < Model
3
+ belongs_to :recipe
4
+
5
+ property :name, String, :required => true
6
+ property :description, String, :length => 65535
7
+ property :type, String, :set => ['Spice', 'Fining', 'Water Agent', 'Herb', 'Flavor', 'Fruit', 'Other'], :required => true
8
+ property :added_when, String, :set => ['Boil', 'Mash', 'Primary', 'Secondary', 'Packaging'], :required => true
9
+ property :time, Time, :required => true
10
+ property :amount, WeightOrVolume, :required => true
11
+
12
+ property :use_for, String
13
+ end
14
+ end
@@ -0,0 +1,45 @@
1
+ module Brewser
2
+
3
+ class Model
4
+ require 'dm-core'
5
+ require 'dm-validations'
6
+ require 'active_support/inflector'
7
+
8
+ include DataMapper::Resource
9
+ require "#{File.dirname(__FILE__)}/units"
10
+ include Units
11
+
12
+ DataMapper.setup(:default, :adapter => 'in_memory')
13
+ def self.default_repository_name;:default;end
14
+ def self.auto_migrate_down!(rep);end
15
+ def self.auto_migrate_up!(rep);end
16
+ def self.auto_upgrade!(rep);end
17
+
18
+ def deep_json
19
+ h = {}
20
+ instance_variables.each do |e|
21
+ key = e[1..-1]
22
+ next if ["roxml_references", "_persistence_state", "_key"].include? key
23
+ o = instance_variable_get e.to_sym
24
+ h[key] = (o.respond_to? :deep_json) ? o.deep_json : o;
25
+ end
26
+ h
27
+ end
28
+
29
+ def to_json *a
30
+ deep_json.to_json *a
31
+ end
32
+
33
+ def as_brewson
34
+ BrewSON.serialize(self)
35
+ end
36
+
37
+ def as_beerxml
38
+ BeerXML2.serialize(self)
39
+ end
40
+
41
+ %w(additive batch fermentable fermentation_schedule fermentation_steps hop mash_schedule mash_steps recipe style water_profile yeast).
42
+ each { |f| require "#{File.dirname(__FILE__)}/#{f}" }
43
+ end
44
+
45
+ end
@@ -0,0 +1,27 @@
1
+ module Brewser
2
+ class Batch < Model
3
+ property :brewed_on, Date
4
+ property :batch_identifier, String
5
+ property :brewer, String
6
+ property :assistant_brewer, String
7
+ property :description, String, :length => 65535
8
+
9
+ has 1, :recipe
10
+ validates_presence_of :recipe
11
+
12
+ has 1, :system
13
+
14
+ property :batch_status, String, :set => ["Planning", "Preparing", "Brewing", "Primary", "Secondary",
15
+ "Lagering", "Conditioning", "Serving", "Dispatched"]
16
+ property :sparge_method, String, :set => ["Batch", "Fly", "No Sparge"]
17
+ property :oxygenation_method, String, :set => ["Shake", "Splash", "Air Injection", "Oxygen Injection"]
18
+
19
+ property :measured_og, Float
20
+ property :measured_fg, Float
21
+ property :computed_abv, Float
22
+
23
+ property :computed_extract_efficiency, Float
24
+ property :computed_brewhouse_efficiency, Float
25
+ end
26
+
27
+ end
@@ -0,0 +1,28 @@
1
+ module Brewser
2
+ class Fermentable < Model
3
+ belongs_to :recipe
4
+
5
+ property :name, String, :required => true
6
+ property :origin, String, :length => 512
7
+ property :supplier, String, :length => 512
8
+ property :description, String, :length => 65535
9
+
10
+ property :type, String, :set => ['Grain', 'Sugar', 'Extract', 'Dry Extract', 'Adjunct'], :required => true
11
+ property :yield_percent, Float
12
+ property :potential, Float, :required => true
13
+ property :ppg, Integer
14
+
15
+ property :color, Float, :required => true
16
+
17
+ property :amount, Weight, :required => true
18
+ property :late_addition?, Boolean, :default => false
19
+
20
+ property :coarse_fine_diff, Float
21
+ property :moisture, Float
22
+ property :diastatic_power, Float
23
+ property :protein, Float
24
+ property :max_in_batch, Float
25
+ property :recommend_mash?, Boolean
26
+ property :ibu_gal_per_lb, Float
27
+ end
28
+ end
@@ -0,0 +1,7 @@
1
+ module Brewser
2
+ class FermentationSchedule < Brewser::Model
3
+ belongs_to :recipe
4
+
5
+ has n, :fermentation_steps
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module Brewser
2
+ class FermentationStep < Model
3
+ belongs_to :fermentation_schedule
4
+
5
+ property :name, String
6
+ property :purpose, String, :set => ['primary', 'secondary', 'tertiary', 'conditioning']
7
+
8
+ property :index, Integer
9
+ property :time, TimeInDays
10
+ property :temperature, Temperature
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+ module Brewser
2
+ class Hop < Model
3
+ belongs_to :recipe
4
+
5
+ property :name, String, :required => true
6
+ property :description, String, :length => 65535
7
+
8
+ property :type, String, :set => ['Bittering', 'Aroma', 'Both']
9
+ property :alpha_acids, Float, :required => true
10
+ property :beta_acids, Float
11
+ property :added_when, String, :set => ['Boil', 'Dry', 'Mash', 'FWH', 'Aroma', 'Hop Back', 'Infuser'], :required => true
12
+ property :time, Time
13
+ property :amount, Weight
14
+
15
+ property :form, String, :set => ['Pellet', 'Plug', 'Whole']
16
+
17
+ property :storageability, Float
18
+ property :origin, String, :length => 512
19
+ property :substitutes, String, :length => 512
20
+ property :humulene, Float
21
+ property :caryophyllene, Float
22
+ property :cohumulone, Float
23
+ property :myrcene, Float
24
+ property :farnsene, Float
25
+ property :total_oil, Float
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module Brewser
2
+ class MashSchedule < Brewser::Model
3
+ belongs_to :recipe
4
+
5
+ property :name, String, :required => true
6
+ property :description, String
7
+ property :grain_temp, Temperature
8
+ property :sparge_temp, Temperature
9
+
10
+ has n, :mash_steps
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ module Brewser
2
+
3
+ class MashStep < Model
4
+ belongs_to :mash_schedule
5
+
6
+ property :index, Integer
7
+
8
+ property :name, String, :required => true
9
+ property :description, String, :length => 65535
10
+
11
+ property :type, String, :set => ['Direct', 'Infusion', 'Decoction']
12
+ property :purpose, String, :set => ['acid_rest', 'protein_rest', 'saccharification_rest', 'mash_out']
13
+
14
+ property :step_volume, Volume
15
+ property :ramp_time, Time
16
+
17
+ property :infusion_volume, Volume
18
+ property :infusion_temperature, Temperature
19
+
20
+ property :rest_temperature, Temperature, :required => true
21
+ property :rest_time, Time, :required => true
22
+ end
23
+
24
+ end