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.
@@ -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