brewser 0.1.0
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/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/lib/brewser.rb +32 -0
- data/lib/brewser/brewson.rb +5 -0
- data/lib/brewser/engines.rb +18 -0
- data/lib/brewser/engines/beerxml.rb +433 -0
- data/lib/brewser/engines/beerxml2.rb +28 -0
- data/lib/brewser/engines/brewson.rb +20 -0
- data/lib/brewser/engines/promash_rec.rb +424 -0
- data/lib/brewser/engines/promash_txt.rb +214 -0
- data/lib/brewser/exceptions.rb +11 -0
- data/lib/brewser/json-validation.rb +12 -0
- data/lib/brewser/model/additive.rb +14 -0
- data/lib/brewser/model/base.rb +45 -0
- data/lib/brewser/model/batch.rb +27 -0
- data/lib/brewser/model/fermentable.rb +28 -0
- data/lib/brewser/model/fermentation_schedule.rb +7 -0
- data/lib/brewser/model/fermentation_steps.rb +12 -0
- data/lib/brewser/model/hop.rb +27 -0
- data/lib/brewser/model/mash_schedule.rb +12 -0
- data/lib/brewser/model/mash_steps.rb +24 -0
- data/lib/brewser/model/recipe.rb +50 -0
- data/lib/brewser/model/style.rb +29 -0
- data/lib/brewser/model/units.rb +102 -0
- data/lib/brewser/model/water_profile.rb +16 -0
- data/lib/brewser/model/yeast.rb +29 -0
- data/lib/brewser/ruby-units.rb +31 -0
- data/lib/brewser/version.rb +3 -0
- data/spec/basic_spec.rb +39 -0
- data/spec/beerxml_spec.rb +159 -0
- data/spec/promash_spec.rb +194 -0
- data/spec/spec_helper.rb +29 -0
- metadata +187 -0
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2012 Jon Lochner
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
data/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
brewser
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
Brewser is a ruby library for parsing and generating serialized brewing data
|
|
5
|
+
|
|
6
|
+
Currently brewser is very early in development but will eventually support the following input formats:
|
|
7
|
+
* [BeerXML (v1, v2)](http://beerxml.org)
|
|
8
|
+
* BrewSON - Brewser Recipe and Batches in JSON
|
|
9
|
+
* [ProMash (.rec and text exports)](http://www.promash.com)
|
|
10
|
+
|
|
11
|
+
Input files are deserialized into a common object model for consumption. Brewser supports these output formats:
|
|
12
|
+
* BeerXML
|
|
13
|
+
* BrewSON
|
data/lib/brewser.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'brewser/ruby-units'
|
|
2
|
+
require 'brewser/json-validation'
|
|
3
|
+
require 'brewser/model/base'
|
|
4
|
+
require 'brewser/exceptions'
|
|
5
|
+
require 'brewser/engines'
|
|
6
|
+
|
|
7
|
+
module Brewser
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
|
|
11
|
+
# Returns the potential engine to process the given string
|
|
12
|
+
def identify(string_or_io)
|
|
13
|
+
return BrewSON if BrewSON.acceptable?(string_or_io)
|
|
14
|
+
return BeerXML2 if BeerXML2.acceptable?(string_or_io)
|
|
15
|
+
return BeerXML if BeerXML.acceptable?(string_or_io)
|
|
16
|
+
return ProMashTxt if ProMashTxt.acceptable?(string_or_io)
|
|
17
|
+
return ProMashRec if ProMashRec.acceptable?(string_or_io)
|
|
18
|
+
raise Error, "unable to identify content"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Executes the engine matching the given string
|
|
22
|
+
def parse(string_or_io)
|
|
23
|
+
if engine=self.identify(string_or_io)
|
|
24
|
+
engine.send(:deserialize, string_or_io)
|
|
25
|
+
else
|
|
26
|
+
return nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Brewser
|
|
2
|
+
|
|
3
|
+
# Skeleton for an engine
|
|
4
|
+
class Engine
|
|
5
|
+
|
|
6
|
+
def self.acceptable?(q)
|
|
7
|
+
return false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.deserialize(string_or_io)
|
|
11
|
+
return nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Dir.glob(File.dirname(__FILE__) + '/engines/*', &method(:require))
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
class BeerXML < Brewser::Engine
|
|
2
|
+
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
# Simple test to see if this looks like XML
|
|
8
|
+
def acceptable?(q)
|
|
9
|
+
Nokogiri::XML(q){|config| config.noblanks }.root ? true : false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Attempt to deserialize the data
|
|
13
|
+
def deserialize(string_or_io)
|
|
14
|
+
begin
|
|
15
|
+
outer = Nokogiri::XML(string_or_io).root
|
|
16
|
+
# We expect to find a plural of one of the models
|
|
17
|
+
objects = (outer>outer.node_name.singularize).map do |inner|
|
|
18
|
+
("BeerXML::#{inner.node_name.downcase.camelcase}".constantize).from_xml(inner)
|
|
19
|
+
end
|
|
20
|
+
return cleanup(objects)
|
|
21
|
+
# rescue
|
|
22
|
+
# raise Error, "BeerXML engine encountered an issue and can not continue"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Ugly hack to deal with BeerXML oddities
|
|
27
|
+
def cleanup(brewser_objects)
|
|
28
|
+
brewser_objects.each(&:cleanup)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
require 'roxml'
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
#
|
|
38
|
+
# These models add the hooks to deserialize the data using ROXML
|
|
39
|
+
# Brought in as a seperate model to allow multiple version of BeerXML
|
|
40
|
+
class BeerXML::Hop < Brewser::Hop
|
|
41
|
+
include ROXML
|
|
42
|
+
|
|
43
|
+
xml_name "HOP"
|
|
44
|
+
xml_convention :upcase
|
|
45
|
+
xml_reader :name
|
|
46
|
+
xml_reader :origin
|
|
47
|
+
xml_reader :description, :from => "NOTES"
|
|
48
|
+
|
|
49
|
+
xml_reader :type
|
|
50
|
+
xml_reader(:form) { |x|
|
|
51
|
+
x="Whole" if x=="Leaf"
|
|
52
|
+
x
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
xml_reader :display_amount
|
|
56
|
+
xml_reader :uncast_amount, :from => "AMOUNT"
|
|
57
|
+
|
|
58
|
+
xml_reader :display_time
|
|
59
|
+
xml_reader :uncast_time, :from => "TIME"
|
|
60
|
+
|
|
61
|
+
xml_reader(:added_when, :from => "USE") { |x|
|
|
62
|
+
x="Dry" if x=="Dry Hop"
|
|
63
|
+
x="FWH" if x=="First Wort"
|
|
64
|
+
x="Boil" if x =="Aroma"
|
|
65
|
+
x
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
xml_reader :alpha_acids, :from => "ALPHA", :as => Float
|
|
69
|
+
xml_reader :beta_acids, :from => "BETA", :as => Float
|
|
70
|
+
|
|
71
|
+
xml_reader :humulene, :as => Float
|
|
72
|
+
xml_reader :caryophyllene, :as => Float
|
|
73
|
+
xml_reader :cohumulone, :as => Float
|
|
74
|
+
xml_reader :myrcene, :as => Float
|
|
75
|
+
xml_reader :farnesene, :as => Float # Not explicitly included in BeerXML
|
|
76
|
+
xml_reader :total_oil, :as => Float # Not explicitly included in BeerXML
|
|
77
|
+
xml_reader :storageability, :from => "HSI", :as => Float
|
|
78
|
+
|
|
79
|
+
xml_reader :substitutes
|
|
80
|
+
|
|
81
|
+
def cleanup
|
|
82
|
+
self.amount = display_amount.present? ? display_amount.u : "#{uncast_amount} kg".u
|
|
83
|
+
self.time = display_time.present? ? display_time.u : "#{uncast_time} min".u
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class BeerXML::Fermentable < Brewser::Fermentable
|
|
89
|
+
include ROXML
|
|
90
|
+
|
|
91
|
+
xml_name "FERMENTABLE"
|
|
92
|
+
xml_convention :upcase
|
|
93
|
+
xml_reader :name
|
|
94
|
+
xml_reader :origin
|
|
95
|
+
xml_reader :supplier
|
|
96
|
+
xml_reader :description, :from => "NOTES"
|
|
97
|
+
xml_reader :type
|
|
98
|
+
|
|
99
|
+
xml_reader :display_amount
|
|
100
|
+
xml_reader :uncast_amount, :from => "AMOUNT"
|
|
101
|
+
|
|
102
|
+
xml_reader :yield_percent, :from => "YIELD"
|
|
103
|
+
xml_reader :uncast_potential, :from => "POTENTIAL"
|
|
104
|
+
|
|
105
|
+
xml_reader :color
|
|
106
|
+
|
|
107
|
+
xml_reader :late_addition?, :from => "ADD_AFTER_BOIL"
|
|
108
|
+
xml_reader :recommend_mash?, :from => "RECOMMEND_MASH"
|
|
109
|
+
|
|
110
|
+
xml_reader :max_in_batch, :as => Float
|
|
111
|
+
xml_reader(:diastatic_power) {|x| x.to_f }
|
|
112
|
+
xml_reader(:moisture) {|x| x.to_f }
|
|
113
|
+
xml_reader(:coarse_fine_diff) {|x| x.to_f }
|
|
114
|
+
xml_reader(:protein) {|x| x.to_f }
|
|
115
|
+
xml_reader(:ibu_gal_per_lb) {|x| x.to_f }
|
|
116
|
+
|
|
117
|
+
def cleanup
|
|
118
|
+
self.amount = display_amount.present? ? display_amount.u : "#{uncast_amount} kg".u
|
|
119
|
+
self.potential = uncast_potential.present? ? uncast_potential.to_f : 1+(46*(yield_percent/100))/1000
|
|
120
|
+
self.ppg = (potential-1)*1000
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class BeerXML::Additive < Brewser::Additive
|
|
126
|
+
include ROXML
|
|
127
|
+
|
|
128
|
+
xml_name "MISC"
|
|
129
|
+
xml_convention :upcase
|
|
130
|
+
xml_reader :name
|
|
131
|
+
xml_reader :origin
|
|
132
|
+
|
|
133
|
+
xml_reader :type
|
|
134
|
+
xml_reader :form
|
|
135
|
+
|
|
136
|
+
xml_reader :display_amount
|
|
137
|
+
xml_reader :amount_scalar, :from => "AMOUNT"
|
|
138
|
+
|
|
139
|
+
xml_reader :weight?, :from => "AMOUNT_IS_WEIGHT"
|
|
140
|
+
|
|
141
|
+
xml_reader(:added_when, :from => "USE") { |x|
|
|
142
|
+
x="Packaging" if x == "Bottling"
|
|
143
|
+
x
|
|
144
|
+
}
|
|
145
|
+
xml_reader :use_for
|
|
146
|
+
|
|
147
|
+
xml_reader :display_time
|
|
148
|
+
xml_reader :uncast_time, :from => "TIME"
|
|
149
|
+
|
|
150
|
+
xml_reader :description, :from => "NOTES"
|
|
151
|
+
|
|
152
|
+
def set_amount
|
|
153
|
+
return display_amount.u unless display_amount.blank?
|
|
154
|
+
units = weight? ? "kg" : "l"
|
|
155
|
+
return "#{amount_scalar} #{units}".u
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def cleanup
|
|
159
|
+
self.amount = set_amount
|
|
160
|
+
self.time = display_time.present? ? display_time.u : "#{time} min".u
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class BeerXML::Yeast < Brewser::Yeast
|
|
166
|
+
include ROXML
|
|
167
|
+
|
|
168
|
+
xml_name "YEAST"
|
|
169
|
+
xml_convention :upcase
|
|
170
|
+
xml_reader :name
|
|
171
|
+
xml_reader :supplier, :from => "LABORATORY"
|
|
172
|
+
xml_reader :catalog, :from => "PRODUCT_ID"
|
|
173
|
+
|
|
174
|
+
xml_reader :type
|
|
175
|
+
xml_reader :form
|
|
176
|
+
|
|
177
|
+
xml_reader :display_amount
|
|
178
|
+
xml_reader :amount_scalar, :from => "AMOUNT"
|
|
179
|
+
xml_reader :weight?, :from => "AMOUNT_IS_WEIGHT"
|
|
180
|
+
|
|
181
|
+
xml_reader :uncast_min_temperature, :from => "MIN_TEMPERATURE"
|
|
182
|
+
xml_reader :disp_min_temp
|
|
183
|
+
|
|
184
|
+
xml_reader :uncast_max_temperature, :from => "MAX_TEMPERATURE"
|
|
185
|
+
xml_reader :disp_max_temp
|
|
186
|
+
|
|
187
|
+
xml_reader :flocculation
|
|
188
|
+
xml_reader :attenuation, :as => Float
|
|
189
|
+
xml_reader :best_for
|
|
190
|
+
xml_reader :add_to_secondary?, :from => "ADD_TO_SECONDARY"
|
|
191
|
+
|
|
192
|
+
xml_reader :times_cultured
|
|
193
|
+
xml_reader :max_reuse
|
|
194
|
+
|
|
195
|
+
xml_reader :description, :from => "NOTES"
|
|
196
|
+
|
|
197
|
+
def set_amount
|
|
198
|
+
return display_amount.u unless display_amount.blank?
|
|
199
|
+
units = weight? ? "kg" : "l"
|
|
200
|
+
return "#{amount_scalar} #{units}".u
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def cleanup
|
|
204
|
+
self.amount = set_amount
|
|
205
|
+
self.min_temperature = disp_min_temp.present? ? disp_min_temp.u : "#{uncast_min_temperature} dC".u
|
|
206
|
+
self.max_temperature = disp_max_temp.present? ? disp_max_temp.u : "#{uncast_max_temperature} dC".u
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
class BeerXML::MashStep < Brewser::MashStep
|
|
212
|
+
include ROXML
|
|
213
|
+
|
|
214
|
+
xml_name "MASH_STEP"
|
|
215
|
+
xml_convention :upcase
|
|
216
|
+
xml_reader :name
|
|
217
|
+
xml_reader :description
|
|
218
|
+
|
|
219
|
+
xml_reader :purpose
|
|
220
|
+
xml_reader(:type) { |x|
|
|
221
|
+
x="Direct" if x=="Temperature"
|
|
222
|
+
x
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
xml_reader(:ramp_time) { |x| "#{x} min".u }
|
|
226
|
+
xml_reader(:rest_time, :from => "STEP_TIME") { |x| "#{x} min".u }
|
|
227
|
+
|
|
228
|
+
xml_reader :uncast_rest_temperature, :from => "STEP_TEMP"
|
|
229
|
+
|
|
230
|
+
xml_reader :water_to_grain_ratio, :from => "WATER_GRAIN_RATIO", :as => Float
|
|
231
|
+
|
|
232
|
+
xml_reader :uncast_infusion_volume, :from => "INFUSE_AMOUNT"
|
|
233
|
+
xml_reader :display_infuse_amt
|
|
234
|
+
|
|
235
|
+
xml_reader :infusion_temperature, :from => "INFUSE_TEMP"
|
|
236
|
+
|
|
237
|
+
property :step_volume, Volume
|
|
238
|
+
property :ramp_time, Time
|
|
239
|
+
|
|
240
|
+
property :rest_temperature, Temperature, :required => true
|
|
241
|
+
property :rest_time, Time, :required => true
|
|
242
|
+
|
|
243
|
+
def cleanup
|
|
244
|
+
self.index = mash_schedule.mash_steps.index(self)+1
|
|
245
|
+
self.infusion_volume = display_infuse_amt.present? ? display_infuse_amt.u : "#{uncast_infusion_volume} l".u unless !uncast_infusion_volume.present? or uncast_infusion_volume == 0
|
|
246
|
+
if infusion_temperature.present?
|
|
247
|
+
self.infusion_temperature
|
|
248
|
+
self.infusion_temperature = infusion_temperature.unitless? ? infusion_temperature*"1 C".u : infusion_temperature
|
|
249
|
+
end
|
|
250
|
+
self.rest_temperature = "#{uncast_rest_temperature} dC".u
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
class BeerXML::MashSchedule < Brewser::MashSchedule
|
|
256
|
+
include ROXML
|
|
257
|
+
|
|
258
|
+
xml_name "MASH"
|
|
259
|
+
xml_convention :upcase
|
|
260
|
+
xml_reader :name
|
|
261
|
+
xml_reader :description, :from => "NOTES"
|
|
262
|
+
|
|
263
|
+
xml_reader :display_grain_temp
|
|
264
|
+
xml_reader :uncast_grain_temp, :from => "GRAIN_TEMP"
|
|
265
|
+
|
|
266
|
+
xml_reader :display_sparge_temp
|
|
267
|
+
xml_reader :uncast_sparge_temp, :from => "SPARGE_TEMP"
|
|
268
|
+
|
|
269
|
+
xml_attr :mash_steps, :as => [BeerXML::MashStep], :in => "MASH_STEPS"
|
|
270
|
+
|
|
271
|
+
def cleanup
|
|
272
|
+
mash_steps.each do |m|
|
|
273
|
+
m.cleanup
|
|
274
|
+
end
|
|
275
|
+
self.grain_temp = display_grain_temp.present? ? display_grain_temp.u : "#{uncast_grain_temp} dC".u
|
|
276
|
+
self.sparge_temp = display_sparge_temp.present? ? display_sparge_temp.u : "#{uncast_sparge_temp} dC".u
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
class BeerXML::FermentationStep < Brewser::FermentationStep
|
|
282
|
+
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
class BeerXML::FermentationSchedule < Brewser::FermentationSchedule
|
|
286
|
+
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
class BeerXML::WaterProfile < Brewser::WaterProfile
|
|
290
|
+
include ROXML
|
|
291
|
+
|
|
292
|
+
xml_name "WATER"
|
|
293
|
+
xml_convention :upcase
|
|
294
|
+
xml_reader :name, :from => "NAME"
|
|
295
|
+
xml_reader(:calcium, :from => "CALCIUM") {|x| x.to_f }
|
|
296
|
+
xml_reader(:magnesium, :from => "MAGNESIUM") {|x| x.to_f }
|
|
297
|
+
xml_reader(:sodium, :from => "SODIUM") {|x| x.to_f }
|
|
298
|
+
xml_reader(:chloride, :from => "CHLORIDE") {|x| x.to_f }
|
|
299
|
+
xml_reader(:sulfates, :from => "SULFATE") {|x| x.to_f }
|
|
300
|
+
xml_reader(:bicarbonate, :from => "BICARBONATE") {|x| x.to_f }
|
|
301
|
+
xml_reader(:alkalinity, :from => "ALKALINITY") {|x| x.to_f }
|
|
302
|
+
xml_reader(:ph, :from => "PH") {|x| x.to_f }
|
|
303
|
+
xml_reader :description, :from => "NOTES"
|
|
304
|
+
|
|
305
|
+
def cleanup
|
|
306
|
+
# nothing to do here
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class BeerXML::Style < Brewser::Style
|
|
312
|
+
include ROXML
|
|
313
|
+
|
|
314
|
+
xml_name "STYLE"
|
|
315
|
+
xml_convention :upcase
|
|
316
|
+
xml_reader :name
|
|
317
|
+
xml_reader :category
|
|
318
|
+
xml_reader :category_number
|
|
319
|
+
xml_reader :style_letter
|
|
320
|
+
xml_reader :style_guide
|
|
321
|
+
xml_reader(:type) { |x|
|
|
322
|
+
x="Hybrid" if x=="Mixed"
|
|
323
|
+
x
|
|
324
|
+
}
|
|
325
|
+
xml_attr :og_min
|
|
326
|
+
xml_attr :og_max
|
|
327
|
+
xml_attr :fg_min
|
|
328
|
+
xml_attr :fg_max
|
|
329
|
+
xml_attr :ibu_min
|
|
330
|
+
xml_attr :ibu_max
|
|
331
|
+
xml_attr :color_min
|
|
332
|
+
xml_attr :color_max
|
|
333
|
+
|
|
334
|
+
xml_attr :carb_min
|
|
335
|
+
xml_attr :carb_max
|
|
336
|
+
xml_attr :abv_min
|
|
337
|
+
xml_attr :abv_max
|
|
338
|
+
|
|
339
|
+
xml_attr :notes
|
|
340
|
+
xml_attr :profile
|
|
341
|
+
xml_attr :ingredients
|
|
342
|
+
xml_attr :examples
|
|
343
|
+
|
|
344
|
+
def cleanup
|
|
345
|
+
# nothing to do here
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
class BeerXML::Recipe < Brewser::Recipe
|
|
350
|
+
include ROXML
|
|
351
|
+
|
|
352
|
+
xml_name "RECIPE"
|
|
353
|
+
xml_convention :upcase
|
|
354
|
+
xml_reader :date_created, :from => "DATE"
|
|
355
|
+
xml_reader :name
|
|
356
|
+
xml_reader :method, :from => "TYPE"
|
|
357
|
+
|
|
358
|
+
xml_attr :style, :as => BeerXML::Style
|
|
359
|
+
xml_attr :mash_schedule, :as => BeerXML::MashSchedule
|
|
360
|
+
xml_attr :water_profile, :as => BeerXML::WaterProfile, :in => "WATERS"
|
|
361
|
+
|
|
362
|
+
xml_reader :brewer
|
|
363
|
+
|
|
364
|
+
xml_reader :display_batch_size
|
|
365
|
+
xml_reader :uncast_batch_size, :from => "BATCH_SIZE"
|
|
366
|
+
|
|
367
|
+
xml_reader :display_boil_size
|
|
368
|
+
xml_reader :uncast_boil_size, :from => "BOIL_SIZE"
|
|
369
|
+
|
|
370
|
+
xml_reader(:boil_time) { |x| "#{x} min".u }
|
|
371
|
+
|
|
372
|
+
xml_reader :recipe_efficiency, :from => "EFFICIENCY", :as => Float
|
|
373
|
+
xml_reader :carbonation_level, :from => "CARBONATION", :as => Float
|
|
374
|
+
|
|
375
|
+
xml_reader(:estimated_og, :from => "EST_OG") {|x| x.to_f }
|
|
376
|
+
xml_reader(:estimated_fg, :from => "EST_FG") {|x| x.to_f }
|
|
377
|
+
xml_reader(:estimated_color, :from => "EST_COLOR") {|x| x.to_f }
|
|
378
|
+
xml_reader(:estimated_ibu, :from => "IBU") {|x| x.to_f }
|
|
379
|
+
xml_reader(:estimated_abv, :from => "EST_ABV") {|x| x.to_f }
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
xml_attr :hops, :as => [BeerXML::Hop], :in => "HOPS"
|
|
383
|
+
xml_attr :fermentables, :as => [BeerXML::Fermentable], :in => "FERMENTABLES"
|
|
384
|
+
xml_attr :additives, :as => [BeerXML::Additive], :in => "MISCS"
|
|
385
|
+
xml_attr :yeasts, :as => [BeerXML::Yeast], :in => "YEASTS"
|
|
386
|
+
|
|
387
|
+
xml_reader :description, :from => "NOTES"
|
|
388
|
+
|
|
389
|
+
xml_reader :primary_age, :from => "PRIMARY_AGE"
|
|
390
|
+
xml_reader :display_primary_temp
|
|
391
|
+
xml_reader :uncast_primary_temp, :from => "PRIMARY_TEMP"
|
|
392
|
+
|
|
393
|
+
xml_reader :secondary_age, :from => "SECONDARY_AGE"
|
|
394
|
+
xml_reader :display_secondary_temp
|
|
395
|
+
xml_reader :uncast_secondary_temp, :from => "SECONDARY_TEMP"
|
|
396
|
+
|
|
397
|
+
xml_reader :tertiary_age, :from => "TERTIARY_AGE"
|
|
398
|
+
xml_reader :display_tertiary_temp
|
|
399
|
+
xml_reader :uncast_teritary_temp, :from => "TERTIARY_TEMP"
|
|
400
|
+
|
|
401
|
+
xml_reader :age, :from => "AGE"
|
|
402
|
+
xml_reader :display_temp, :from => "DISPLAY_AGE_TEMP"
|
|
403
|
+
xml_reader :uncast_age_temp, :from => "TEMP"
|
|
404
|
+
|
|
405
|
+
def cleanup
|
|
406
|
+
self.recipe_volume = display_batch_size.present? ? display_batch_size.u : "#{uncast_batch_size} l".u
|
|
407
|
+
self.boil_volume = display_boil_size.present? ? display_boil_size.u : "#{uncast_boil_size} l".u
|
|
408
|
+
self.type = style.type || "Other"
|
|
409
|
+
mash_schedule.cleanup
|
|
410
|
+
hops.each(&:cleanup)
|
|
411
|
+
fermentables.each(&:cleanup)
|
|
412
|
+
additives.each(&:cleanup)
|
|
413
|
+
yeasts.each(&:cleanup)
|
|
414
|
+
current_index = 1
|
|
415
|
+
self.fermentation_schedule = BeerXML::FermentationSchedule.create
|
|
416
|
+
["primary_age","secondary_age","tertiary_age","age"].each do |stage|
|
|
417
|
+
if self.send(stage).present? && self.send(stage).to_i > 0
|
|
418
|
+
current_step = BeerXML::FermentationStep.new
|
|
419
|
+
current_step.name = stage.split("_")[0].capitalize
|
|
420
|
+
current_step.purpose = current_step.name
|
|
421
|
+
current_step.purpose = "Conditioning" if current_step.purpose == "Age"
|
|
422
|
+
current_step.index = current_index
|
|
423
|
+
current_index += 1
|
|
424
|
+
current_step.time = "#{self.send(stage)} days".u
|
|
425
|
+
display = "display_#{stage.gsub("age","temp")}"
|
|
426
|
+
uncast = "uncast_#{stage.gsub("age","temp")}"
|
|
427
|
+
current_step.temperature = self.send(display).present? ? self.send(display).u : "#{self.send(uncast)} dC".u
|
|
428
|
+
self.fermentation_schedule.fermentation_steps.push current_step
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
end
|
|
433
|
+
end
|