brewser 0.1.0 → 0.2.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/README.md +25 -1
- data/lib/brewser/engines/beerxml.rb +2 -3
- data/lib/brewser/engines/beerxml2.rb +1 -3
- data/lib/brewser/engines/brewson.rb +10 -1
- data/lib/brewser/engines/promash_rec.rb +52 -32
- data/lib/brewser/model/additive.rb +26 -0
- data/lib/brewser/model/base.rb +4 -16
- data/lib/brewser/model/fermentable.rb +42 -1
- data/lib/brewser/model/fermentation_schedule.rb +17 -0
- data/lib/brewser/model/fermentation_steps.rb +21 -0
- data/lib/brewser/model/hop.rb +44 -0
- data/lib/brewser/model/mash_schedule.rb +25 -1
- data/lib/brewser/model/mash_steps.rb +33 -0
- data/lib/brewser/model/recipe.rb +63 -0
- data/lib/brewser/model/style.rb +22 -0
- data/lib/brewser/model/units.rb +6 -1
- data/lib/brewser/model/water_profile.rb +30 -1
- data/lib/brewser/model/yeast.rb +37 -0
- data/lib/brewser/version.rb +1 -1
- data/spec/basic_spec.rb +1 -1
- data/spec/beerxml_spec.rb +0 -1
- data/spec/brewson_spec.rb +207 -0
- data/spec/promash_rec_spec.rb +226 -0
- data/spec/promash_spec.rb +1 -108
- metadata +48 -28
data/README.md
CHANGED
@@ -3,7 +3,7 @@ brewser
|
|
3
3
|
|
4
4
|
Brewser is a ruby library for parsing and generating serialized brewing data
|
5
5
|
|
6
|
-
Currently brewser is
|
6
|
+
Currently brewser is early in development but will eventually support the following input formats:
|
7
7
|
* [BeerXML (v1, v2)](http://beerxml.org)
|
8
8
|
* BrewSON - Brewser Recipe and Batches in JSON
|
9
9
|
* [ProMash (.rec and text exports)](http://www.promash.com)
|
@@ -11,3 +11,27 @@ Currently brewser is very early in development but will eventually support the f
|
|
11
11
|
Input files are deserialized into a common object model for consumption. Brewser supports these output formats:
|
12
12
|
* BeerXML
|
13
13
|
* BrewSON
|
14
|
+
|
15
|
+
# Status
|
16
|
+
|
17
|
+
Currently, brewser can import BeerXML v1, ProMash .rec files as well as ProMash Recipe Reports (Text files). Each
|
18
|
+
file format contains different levels of details. As a result there is no uniformity amongst the various format. BeerXML v1
|
19
|
+
is at this time the most complete serialized format.
|
20
|
+
|
21
|
+
# Installation
|
22
|
+
|
23
|
+
Just add brewser to your Gemfile
|
24
|
+
|
25
|
+
gem 'brewser'
|
26
|
+
|
27
|
+
and run bundle install
|
28
|
+
|
29
|
+
# Using
|
30
|
+
|
31
|
+
Brewser will attempt to identify the input file by content and delegate processing to the correct engine. ProMash .rec files are not easy to identify, as a result this is the last engine attempted and it will try to decode any file you give it. This is likely to result in garbage if the file is not a properly formatted .rec file.
|
32
|
+
|
33
|
+
To load a BeerXML v1 file:
|
34
|
+
|
35
|
+
Brewser.parse(File.read("samples/beerxmlv1/recipes.xml"))
|
36
|
+
|
37
|
+
This will return an array of BeerXML::Recipe objects.
|
@@ -18,8 +18,8 @@ class BeerXML < Brewser::Engine
|
|
18
18
|
("BeerXML::#{inner.node_name.downcase.camelcase}".constantize).from_xml(inner)
|
19
19
|
end
|
20
20
|
return cleanup(objects)
|
21
|
-
|
22
|
-
|
21
|
+
rescue
|
22
|
+
raise Error, "BeerXML engine encountered an issue and can not continue"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -117,7 +117,6 @@ class BeerXML::Fermentable < Brewser::Fermentable
|
|
117
117
|
def cleanup
|
118
118
|
self.amount = display_amount.present? ? display_amount.u : "#{uncast_amount} kg".u
|
119
119
|
self.potential = uncast_potential.present? ? uncast_potential.to_f : 1+(46*(yield_percent/100))/1000
|
120
|
-
self.ppg = (potential-1)*1000
|
121
120
|
end
|
122
121
|
|
123
122
|
end
|
@@ -7,10 +7,19 @@ class BrewSON < Brewser::Engine
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def deserialize(string_or_io)
|
10
|
+
begin
|
11
|
+
JSON.parse(string_or_io)
|
12
|
+
rescue
|
13
|
+
raise Error, "BrewSON engine encountered an issue and can not continue"
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
17
|
def serialize(brewser_model)
|
13
|
-
|
18
|
+
begin
|
19
|
+
JSON.generate(brewser_model)
|
20
|
+
rescue
|
21
|
+
raise Error, "BrewSON engine encountered an issue and can not continue"
|
22
|
+
end
|
14
23
|
end
|
15
24
|
|
16
25
|
end
|
@@ -32,7 +32,8 @@ class ProMashRec < Brewser::Engine
|
|
32
32
|
float :humulene
|
33
33
|
float :caryphylene
|
34
34
|
int8 :type
|
35
|
-
|
35
|
+
bit4 :form
|
36
|
+
bit4 :unknown
|
36
37
|
float :storage_factor
|
37
38
|
string :taste_notes, :length => 155, :trim_padding => true
|
38
39
|
string :origin, :length => 55, :trim_padding => true
|
@@ -110,26 +111,29 @@ class ProMashRec < Brewser::Engine
|
|
110
111
|
skip length: 8
|
111
112
|
end
|
112
113
|
|
113
|
-
class MashStep < RecEntry
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
|
126
|
-
class MashSchedule < RecEntry
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
114
|
+
# class MashStep < RecEntry
|
115
|
+
# string :name, :length => 255, :trim_padding => true
|
116
|
+
# int8 :type
|
117
|
+
# int32 :start_temp
|
118
|
+
# int32 :stop_temp
|
119
|
+
# int32 :step_temp
|
120
|
+
# int32 :rest_time
|
121
|
+
# int32 :step_time
|
122
|
+
# float :thickness
|
123
|
+
# float :amount
|
124
|
+
# skip length: 8
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# class MashSchedule < RecEntry
|
128
|
+
# count_bytes_remaining :bytes_remaining
|
129
|
+
# string :skipped, :length => lambda { bytes_remaining - 14888 }
|
130
|
+
# int32 :_steps_count
|
131
|
+
# int32 :grain_temp
|
132
|
+
# skip length: 4
|
133
|
+
# array :mash_steps, :type => :mash_step, :read_until => lambda { index == 50 }
|
134
|
+
# string :name, :length => 255, :trim_padding => true
|
135
|
+
# int8 :end_byte
|
136
|
+
# end
|
133
137
|
|
134
138
|
class SimpleSchedule < RecEntry
|
135
139
|
int32 :num_steps
|
@@ -194,9 +198,9 @@ class ProMashRec < Brewser::Engine
|
|
194
198
|
yeast :yeast
|
195
199
|
water_profile :water_profile
|
196
200
|
simple_schedule :simple_schedule
|
197
|
-
string :notes, :length =>
|
198
|
-
string :awards, :length =>
|
199
|
-
mash_schedule :mash_schedule
|
201
|
+
string :notes, :length => 4028, :trim_padding => true
|
202
|
+
string :awards, :length => 2096, :trim_padding => true
|
203
|
+
#mash_schedule :mash_schedule
|
200
204
|
end
|
201
205
|
end
|
202
206
|
|
@@ -206,7 +210,7 @@ end
|
|
206
210
|
class ProMashRec::Hop < Brewser::Hop
|
207
211
|
|
208
212
|
@@hop_types = {0 => "Both", 1 => "Bittering", 2 => "Aroma"}
|
209
|
-
@@hop_forms = {
|
213
|
+
@@hop_forms = {0 => "Whole", 1 => "Plug", 2 => "Pellet"}
|
210
214
|
|
211
215
|
# Pellet 21, 23
|
212
216
|
def from_promash(hop,time)
|
@@ -282,7 +286,7 @@ end
|
|
282
286
|
class ProMashRec::Yeast < Brewser::Yeast
|
283
287
|
|
284
288
|
def from_promash(y)
|
285
|
-
self.name = y.name.split("\x00")[0]
|
289
|
+
self.name = y.name.split("\x00")[0].strip
|
286
290
|
self.supplier = y.supplier
|
287
291
|
self.catalog = y.catalog
|
288
292
|
self.attenuation = (y.aa_high + y.aa_low)/2
|
@@ -297,6 +301,8 @@ end
|
|
297
301
|
|
298
302
|
class ProMashRec::MashStep < Brewser::MashStep
|
299
303
|
|
304
|
+
@@step_types = { 0 => "Infusion", 1 => "Direct", 2 => "Decoction" }
|
305
|
+
|
300
306
|
def from_simple(idx, name, rest_temp, rest_time)
|
301
307
|
self.index = idx
|
302
308
|
self.name = name
|
@@ -306,11 +312,29 @@ class ProMashRec::MashStep < Brewser::MashStep
|
|
306
312
|
return self
|
307
313
|
end
|
308
314
|
|
315
|
+
def from_promash(step,idx=nil)
|
316
|
+
self.name = step.name
|
317
|
+
self.index = idx
|
318
|
+
self.type = @@step_types[step.type]
|
319
|
+
self.ramp_time = "#{step.step_time} min".u
|
320
|
+
self.rest_temperature = "#{step.start_temp} dF".u
|
321
|
+
self.rest_time = "#{step.rest_time} min".u
|
322
|
+
self.infusion_temperature = "#{step.step_temp} dF".u unless step.step_temp.blank?
|
323
|
+
self.infusion_amount = "#{step.amount} qts".u unless step.amount.blank?
|
324
|
+
|
325
|
+
return self
|
326
|
+
end
|
327
|
+
|
309
328
|
end
|
310
329
|
|
311
330
|
class ProMashRec::MashSchedule < Brewser::MashSchedule
|
312
331
|
|
313
332
|
def from_promash(mash)
|
333
|
+
self.name = mash.name
|
334
|
+
self.grain_temp = "#{mash.grain_temp} dF".u
|
335
|
+
mash.steps.each do |step, idx|
|
336
|
+
self.mash_steps.push ProMashRec::MashStep.new.from_promash(step,idx)
|
337
|
+
end
|
314
338
|
|
315
339
|
return self
|
316
340
|
end
|
@@ -410,14 +434,10 @@ class ProMashRec::Recipe < Brewser::Recipe
|
|
410
434
|
end
|
411
435
|
self.yeasts.push ProMashRec::Yeast.new.from_promash(rec.yeast)
|
412
436
|
self.water_profile = ProMashRec::WaterProfile.new.from_promash(rec.water_profile)
|
413
|
-
|
414
|
-
self.mash_schedule = ProMashRec::SimpleSchedule.new.from_promash(rec.simple_schedule)
|
415
|
-
else
|
416
|
-
self.mash_schedule = ProMashRec::MashSchedule.new.from_promash(rec.mash_schedule)
|
417
|
-
end
|
437
|
+
self.mash_schedule = ProMashRec::SimpleSchedule.new.from_promash(rec.simple_schedule)
|
418
438
|
self.description = rec.notes
|
419
439
|
self.type = self.style.type
|
420
|
-
|
440
|
+
|
421
441
|
return self
|
422
442
|
end
|
423
443
|
|
@@ -10,5 +10,31 @@ module Brewser
|
|
10
10
|
property :amount, WeightOrVolume, :required => true
|
11
11
|
|
12
12
|
property :use_for, String
|
13
|
+
|
14
|
+
def self.json_create(o)
|
15
|
+
a = self.new
|
16
|
+
a.name = o['name']
|
17
|
+
a.description = o['description']
|
18
|
+
a.type = o['type']
|
19
|
+
a.added_when = o['added_when']
|
20
|
+
a.time = o['time'].u unless o['time'].blank?
|
21
|
+
a.amount = o['amount'].u unless o['amount'].blank?
|
22
|
+
a.use_for = o['use_for']
|
23
|
+
|
24
|
+
return a
|
25
|
+
end
|
26
|
+
|
27
|
+
def as_json(options={})
|
28
|
+
{
|
29
|
+
JSON.create_id => "Brewser::Additive",
|
30
|
+
'name' => name,
|
31
|
+
'description' => description,
|
32
|
+
'type' => type,
|
33
|
+
'added_when' => added_when,
|
34
|
+
'time' => time.to_s, 'amount' => amount.to_s,
|
35
|
+
'use_for' => use_for
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
13
39
|
end
|
14
40
|
end
|
data/lib/brewser/model/base.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
module Brewser
|
2
|
-
|
2
|
+
|
3
3
|
class Model
|
4
4
|
require 'dm-core'
|
5
5
|
require 'dm-validations'
|
6
|
+
require 'dm-serializer'
|
7
|
+
|
8
|
+
require "msgpack"
|
6
9
|
require 'active_support/inflector'
|
7
10
|
|
8
11
|
include DataMapper::Resource
|
@@ -14,21 +17,6 @@ module Brewser
|
|
14
17
|
def self.auto_migrate_down!(rep);end
|
15
18
|
def self.auto_migrate_up!(rep);end
|
16
19
|
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
20
|
|
33
21
|
def as_brewson
|
34
22
|
BrewSON.serialize(self)
|
@@ -10,7 +10,6 @@ module Brewser
|
|
10
10
|
property :type, String, :set => ['Grain', 'Sugar', 'Extract', 'Dry Extract', 'Adjunct'], :required => true
|
11
11
|
property :yield_percent, Float
|
12
12
|
property :potential, Float, :required => true
|
13
|
-
property :ppg, Integer
|
14
13
|
|
15
14
|
property :color, Float, :required => true
|
16
15
|
|
@@ -24,5 +23,47 @@ module Brewser
|
|
24
23
|
property :max_in_batch, Float
|
25
24
|
property :recommend_mash?, Boolean
|
26
25
|
property :ibu_gal_per_lb, Float
|
26
|
+
|
27
|
+
def ppg
|
28
|
+
return 0 if potential.blank?
|
29
|
+
(potential-1)*1000
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.json_create(o)
|
33
|
+
a = self.new
|
34
|
+
a.name = o['name']
|
35
|
+
a.origin = o['origin']
|
36
|
+
a.supplier = o['supplier']
|
37
|
+
a.description = o['description']
|
38
|
+
a.type = o['type']
|
39
|
+
a.potential = o['potential']
|
40
|
+
a.color = o['color']
|
41
|
+
a.amount = o['amount'].u unless o['amount'].blank?
|
42
|
+
a.late_addition = o['added_late']
|
43
|
+
a.coarse_fine_diff = o['coarse_fine_diff']
|
44
|
+
a.moisture = o['moisture']
|
45
|
+
a.diastatic_power = o['diastatic_power']
|
46
|
+
a.protein = o['protein']
|
47
|
+
a.max_in_batch = o['max_in_batch']
|
48
|
+
a.origin = o['origin']
|
49
|
+
a.recommend_mash = o['recommend_mash']
|
50
|
+
a.ibu_gal_per_lb = o['ibu_gal_per_lb']
|
51
|
+
|
52
|
+
return a
|
53
|
+
end
|
54
|
+
|
55
|
+
def as_json(options={})
|
56
|
+
{
|
57
|
+
JSON.create_id => "Brewser::Fermentable",
|
58
|
+
'name' => name, 'origin' => origin,
|
59
|
+
'supplier' => supplier, 'description' => description,
|
60
|
+
'type' => type, 'ppg' => ppg, 'potential' => potential,
|
61
|
+
'color' => color, 'amount' => amount.to_s, 'added_late' => late_addition?,
|
62
|
+
'coarse_fine_diff' => coarse_fine_diff, 'moisture' => moisture,
|
63
|
+
'diastatic_power' => diastatic_power, 'protein' => protein, 'max_in_batch' => max_in_batch,
|
64
|
+
'recommend_mash' => recommend_mash?, 'ibu_gal_per_lb' => ibu_gal_per_lb
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
27
68
|
end
|
28
69
|
end
|
@@ -3,5 +3,22 @@ module Brewser
|
|
3
3
|
belongs_to :recipe
|
4
4
|
|
5
5
|
has n, :fermentation_steps
|
6
|
+
|
7
|
+
def self.json_create(o)
|
8
|
+
a = self.new
|
9
|
+
o['fermentation_steps'].each do |step|
|
10
|
+
a.fermentation_steps.push step
|
11
|
+
end unless o['fermentation_steps'].nil?
|
12
|
+
|
13
|
+
return a
|
14
|
+
end
|
15
|
+
|
16
|
+
def as_json(options={})
|
17
|
+
{
|
18
|
+
JSON.create_id => "Brewser::FermentationSchedule",
|
19
|
+
'fermentation_steps' => fermentation_steps.to_a
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
6
23
|
end
|
7
24
|
end
|
@@ -8,5 +8,26 @@ module Brewser
|
|
8
8
|
property :index, Integer
|
9
9
|
property :time, TimeInDays
|
10
10
|
property :temperature, Temperature
|
11
|
+
|
12
|
+
def self.json_create(o)
|
13
|
+
a = self.new
|
14
|
+
a.name = o['name']
|
15
|
+
a.purpose = o['purpose']
|
16
|
+
a.index = o['index']
|
17
|
+
a.time = o['time'].u unless o['time'].blank?
|
18
|
+
a.temperature = o['temperature'].u unless o['temperature'].blank?
|
19
|
+
|
20
|
+
return a
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_json(options={})
|
24
|
+
{
|
25
|
+
JSON.create_id=> "Brewser::FermentationStep",
|
26
|
+
'name' => name, 'purpose' => purpose,
|
27
|
+
'index' => index,
|
28
|
+
'time' => time.to_s, 'temperature' => temperature.to_s
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
11
32
|
end
|
12
33
|
end
|
data/lib/brewser/model/hop.rb
CHANGED
@@ -23,5 +23,49 @@ module Brewser
|
|
23
23
|
property :myrcene, Float
|
24
24
|
property :farnsene, Float
|
25
25
|
property :total_oil, Float
|
26
|
+
|
27
|
+
def self.json_create(o)
|
28
|
+
a = self.new
|
29
|
+
a.name = o['name']
|
30
|
+
a.origin = o['origin']
|
31
|
+
a.description = o['description']
|
32
|
+
a.type = o['type']
|
33
|
+
a.alpha_acids = o['alpha_acids']
|
34
|
+
a.beta_acids = o['beta_acids']
|
35
|
+
a.added_when = o['added_when']
|
36
|
+
a.time = o['time'].u unless o['time'].blank?
|
37
|
+
a.amount = o['amount'].u unless o['amount'].blank?
|
38
|
+
a.form = o['form']
|
39
|
+
a.storageability = o['storageability']
|
40
|
+
a.substitutes = o['substitutes']
|
41
|
+
a.humulene = o['humulene']
|
42
|
+
a.caryophyllene = o['caryophyllene']
|
43
|
+
a.cohumulone = o['cohumulone']
|
44
|
+
a.myrcene = o['myrcene']
|
45
|
+
a.farnsene = o['farnsene']
|
46
|
+
a.total_oil = o['total_oil']
|
47
|
+
|
48
|
+
return a
|
49
|
+
end
|
50
|
+
|
51
|
+
def as_json(options={})
|
52
|
+
{
|
53
|
+
JSON.create_id=> "Brewser::Hop",
|
54
|
+
'name' => name,
|
55
|
+
'origin' => origin,
|
56
|
+
'description' => description,
|
57
|
+
'type' => type,
|
58
|
+
'alpha_acids' => alpha_acids,
|
59
|
+
'beta_acids' => beta_acids,
|
60
|
+
'added_when' => added_when,
|
61
|
+
'time' => time.to_s, 'amount' => amount.to_s,
|
62
|
+
'form' => form, 'storageability' => storageability,
|
63
|
+
'substitutes' => substitutes,
|
64
|
+
'humulene' => humulene, 'caryophyllene' => caryophyllene,
|
65
|
+
'cohumulone' => cohumulone, 'myrcene' => myrcene,
|
66
|
+
'farnsene' => farnsene, 'total_oil' => total_oil
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
26
70
|
end
|
27
71
|
end
|