bread_calculator 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a454bc456276e0bac262c1f2bca15af7fb9c2687
4
+ data.tar.gz: 2c7578cc8609953f1391bede4fa2b9ca864013c2
5
+ SHA512:
6
+ metadata.gz: acce0ad69d786710cb19cccb1f152615e1ba2605ee354d9a825678b4bdbf12a85e2ff967edc9b10529887b34bc2fc800d3212c329716c109014d351e16df7f2e
7
+ data.tar.gz: 20fcd4a6426ae2091cfae4b77aa545247258b0669faf8710542500475ee954d67cef348e7af29c7504c7640a5973f37962f5699802da96b84c35621a330f1700
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ bread-calculator
2
+ ---------
3
+
4
+ A ruby gem to calculate baker's percentages
5
+
6
+ Installation
7
+ ---------
8
+
9
+
10
+ Inspiration and History
11
+ ---------
12
+
13
+ License
14
+ ---------
15
+ © 2014 Noah Birnel
16
+ BSD license
17
+
18
+
19
+
20
+
21
+
22
+
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bread_calculator'
4
+
5
+ @format = 'r'
6
+
7
+ loop { case ARGV[0]
8
+ when /-summary/ then @format = 'r.summary'; ARGV.shift; break
9
+ when /-weight/ then @format = 'r.weight'; ARGV.shift; break
10
+ when /-scale-by/ then ARGV.shift; @format = "r.scale_by #{ARGV.shift}"; break
11
+ when /--/ then ARGV.shift; break
12
+ when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")
13
+ else break
14
+ end; }
15
+
16
+ ARGV.each do |arg|
17
+ parser = BreadCalculator::Parser.new arg
18
+ r = parser.parse(arg)
19
+ puts eval("#{@format}")
20
+ end
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'bread_calculator'
3
+ s.version = '0.0.0'
4
+ s.date = '2014-02-28'
5
+ s.summary = "calculate baker's percentages"
6
+ s.description = "a gem and command-line wrapper to generate baker's
7
+ percentages from a bread recipe"
8
+ s.authors = ['Noah Birnel']
9
+ s.email = 'nbirnel@gmail.com'
10
+ s.homepage = 'http://github.com/nbirnel/bread-calculator'
11
+ s.files = ['README.md', 'bread_calculator.gemspec', 'lib/bread_calculator.rb', 'spec/bread_calculator_spec.rb', 'bin/bread-calculator']
12
+ s.has_rdoc = true
13
+ s.executables = ['bread-calculator']
14
+ s.license = 'DWTFYW'
15
+ end
@@ -0,0 +1,316 @@
1
+ ##
2
+ # Classes for manipulating bread recipes and baker's percentages
3
+
4
+ module BreadCalculator
5
+
6
+ ##
7
+ # This class represents an ingredient in a Recipe
8
+
9
+ class Ingredient
10
+ attr_accessor :info, :name, :quantity, :units, :type
11
+
12
+ ##
13
+ # Creates a new ingredient +name+, with the optional qualities +info+.
14
+ #
15
+ # +info+ should usually contain <tt>:quantity, :units, :type</tt>.
16
+ # +:type+, in the context of bakers' percentage, would be +:flours+,
17
+ # +:liquids+, or +:additives+.
18
+
19
+ def initialize name, info={}
20
+ @units = 'grams'
21
+ @name = name
22
+ @info = info
23
+ info.each do |k,v|
24
+ instance_variable_set("@#{k}", v)
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Returns a new Ingredient, scaled from current instance by +ratio+
30
+
31
+ def scale_by ratio
32
+ scaled = Hash.new
33
+ self.info.each do |k, v|
34
+ scaled[k] = v
35
+ scaled[k] = v*ratio if k == :quantity
36
+ end
37
+ Ingredient.new(self.name, scaled)
38
+ end
39
+
40
+ ##
41
+ # Print a nice text version of Ingredient
42
+
43
+ def to_s
44
+ #FIXME check for existance
45
+ "\t#{@quantity} #{@units} #{@name}\n"
46
+ end
47
+ end
48
+
49
+ ##
50
+ # This class represents a discrete step in a Recipe.
51
+
52
+ class Step
53
+ attr_reader :techniques, :ingredients
54
+
55
+ ##
56
+ # Creates a new step with the the optional array +techniques+,
57
+ # which consists of ministep strings, and +Ingredients+.
58
+ #
59
+ # This is intended to read something like:
60
+ # <tt>"Mix:", @flour, @water, "thoroughly."</tt>
61
+ #
62
+ # or:
63
+ #
64
+ # <tt>"Serve forth."</tt>
65
+
66
+ def initialize *args
67
+ self.techniques = args.flatten
68
+ end
69
+
70
+ ##
71
+ # Sets +Step.techniques+ to +args+, and defines +Step.ingredients+
72
+ def techniques= args
73
+ @techniques = args
74
+ @ingredients = args.select{|arg| arg.is_a? Ingredient}
75
+ end
76
+
77
+ ##
78
+ # Print a nice text version of Step
79
+
80
+ def to_s
81
+ out = ''
82
+ self.techniques.each do |t|
83
+ tmp = t.is_a?(Ingredient) ? t.to_s : "#{t.chomp}\n"
84
+ out << tmp
85
+ end
86
+ out << "\n"
87
+ out
88
+ end
89
+ end
90
+
91
+ ##
92
+ # This class represents a recipe.
93
+
94
+ class Recipe
95
+ attr_reader :steps, :metadata
96
+
97
+ ##
98
+ # Creates a new Recipe with hash +metadata+ and array of Steps +steps+
99
+ #
100
+ # +metadata+ is freeform, but most likely should include +:name+.
101
+ # Other likely keys are:
102
+ # <tt>:prep_time, :total_time, :notes, :history, :serves, :makes,
103
+ # :attribution</tt>.
104
+
105
+ def initialize metadata, steps
106
+ @metadata = metadata
107
+ @steps = steps
108
+ @ingredients = self.ingredients
109
+ end
110
+
111
+ ##
112
+ # Returns an array of all Ingredients in Recipe
113
+
114
+ def ingredients
115
+ a = Array.new
116
+ self.steps.each do |step|
117
+ step.ingredients.each do |ing|
118
+ a << ing
119
+ end
120
+ end
121
+ a
122
+ end
123
+
124
+ ##
125
+ # Returns the total weight of Ingredients in Recipe
126
+
127
+ def weight
128
+ self.ingredients.map{|i| i.quantity}.reduce(:+)
129
+ end
130
+
131
+ #FIXME make this a method_missing so we can add new types on the fly
132
+ #RENÉE - 'end.' is weird or no?
133
+ #FIXME how do I get this into rdoc?
134
+ [:flours, :liquids, :additives].each do |s|
135
+ define_method("total_#{s}") do
136
+ instance_variable_get("@ingredients").select{|i| i.type == s}.map do |i|
137
+ i.quantity
138
+ end.reduce(:+)
139
+ end
140
+ end
141
+
142
+ alias_method 'bakers_percent_100', 'total_flours'
143
+
144
+ ##
145
+ # Returns the baker's percentage of a weight
146
+
147
+ def bakers_percent weight
148
+ weight / bakers_percent_100.to_f
149
+ end
150
+
151
+ ##
152
+ # Returns a Formula
153
+
154
+ def bakers_percent_formula
155
+ ratio = 100.0 / self.total_flours
156
+ self.scale_by ratio
157
+ end
158
+
159
+ ##
160
+ # Returns new Recipe scaled by +ratio+
161
+
162
+ def scale_by ratio
163
+ new_steps = self.steps.map do |s|
164
+ step_args = s.techniques.map do |t|
165
+ t.is_a?(Ingredient) ? t.scale_by(ratio) : t
166
+ end
167
+ Step.new step_args
168
+ end
169
+
170
+ Recipe.new self.metadata, new_steps
171
+ end
172
+
173
+ ##
174
+ # Returns a Summary
175
+
176
+ def summary
177
+ types = Hash.new
178
+ [:flours, :liquids, :additives].each do |s|
179
+ types["total_#{s}"] = self.bakers_percent eval("self.total_#{s}")
180
+ end
181
+
182
+ l_ingredients = Hash.new
183
+ self.ingredients.map do |i|
184
+ l_ingredients[i.name] = self.bakers_percent i.quantity
185
+ end
186
+ Summary.new types, l_ingredients
187
+ end
188
+
189
+ ##
190
+ # Print a nice text version of Recipe
191
+
192
+ def to_s
193
+ out = ''
194
+ self.metadata.each{|k,v| out << "#{k}: #{v}\n"}
195
+ out << "--------------------\n"
196
+ self.steps.each{|s| out << s.to_s }
197
+ out
198
+ end
199
+
200
+ end
201
+
202
+ ##
203
+ # This class converts a nearly free-form text file to a Recipe
204
+
205
+ class Parser
206
+
207
+ ##
208
+ # Create a new parser for Recipe +name+.
209
+
210
+ def initialize name
211
+ @name = name
212
+
213
+ @i = 0
214
+ @args = @steps = []
215
+ @steps[0] = BreadCalculator::Step.new
216
+
217
+ @in_prelude = true
218
+ @prelude = ''
219
+ end
220
+
221
+ ##
222
+ # Parse our text
223
+
224
+ def parse input
225
+
226
+ IO.foreach(input) do |line|
227
+ new_step && next if line =~ /(^-)|(^\s*$)/
228
+ @prelude << line && next if @in_prelude
229
+
230
+ @args << preprocess(line.chomp)
231
+ end
232
+
233
+ close_step
234
+ # because we made a spurious one to begin with
235
+ @steps.shift
236
+ metadata = {
237
+ :name => @name,
238
+ :notes => @prelude,
239
+ }
240
+
241
+ Recipe.new metadata, @steps
242
+ end
243
+
244
+ private
245
+
246
+ def new_step
247
+ @in_prelude = false
248
+ close_step
249
+
250
+ @args = []
251
+ @i += 1
252
+ @steps[@i] = BreadCalculator::Step.new
253
+ end
254
+
255
+ def close_step
256
+ @steps[@i].techniques = @args
257
+ end
258
+
259
+ def preprocess line
260
+ ing_regex = /^\s+((?<qty>[0-9.]+\s*)(?<units>g)?\s+)?(?<item>.*)/
261
+ h = Hash.new
262
+ if ing_regex =~ line
263
+ match = Regexp.last_match
264
+ h[:quantity] = match[:qty].strip.to_f
265
+ h[:units] = match[:units]
266
+ ingredient = match[:item].strip
267
+
268
+ #FIXME refactor
269
+ h[:type] = :additives #if it doesn't match anything else
270
+ h[:type] = :flours if ingredient =~ /flour/
271
+ h[:type] = :flours if ingredient =~ /meal/
272
+ h[:type] = :liquids if ingredient =~ /liquid/
273
+ h[:type] = :liquids if ingredient =~ /water/
274
+ h[:type] = :liquids if ingredient =~ /egg/
275
+ h[:type] = :liquids if ingredient =~ /mashed/
276
+ h[:type] = :liquids if ingredient =~ /milk/
277
+ h[:type] = :additives if ingredient =~ /dry/
278
+ h[:type] = :additives if ingredient =~ /powdered/
279
+
280
+ ing = BreadCalculator::Ingredient.new ingredient, h
281
+ else
282
+ line.strip
283
+ end
284
+ end
285
+
286
+ end
287
+
288
+ ##
289
+ # This class represents a summary of a Recipe - no Steps or units, just
290
+ # baker's percentages of each ingredient, and a prelude of baker's
291
+ # percentages for flours, liquids, and additives.
292
+
293
+ class Summary
294
+ attr_accessor :types, :ingredients
295
+
296
+ ##
297
+ # Create a new Summary of +types+ and +ingredients+
298
+
299
+ def initialize types, ingredients
300
+ @types = types
301
+ @ingredients = ingredients
302
+ end
303
+
304
+ ##
305
+ # Print it nicely
306
+
307
+ def to_s
308
+ out = ''
309
+ self.types.each{|k,v| out << "#{k}: #{v}\n"}
310
+ out << "--------------------\n"
311
+ self.ingredients.each{|k,v| out << "#{k}: #{v}\n"}
312
+ out
313
+ end
314
+ end
315
+
316
+ end
@@ -0,0 +1,84 @@
1
+ require "#{File.dirname(__FILE__)}/../lib/bread_calculator"
2
+
3
+ describe BreadCalculator do
4
+ before do
5
+ @ww = BreadCalculator::Ingredient.new "whole wheat flour", :quantity => 300, :units => 'grams', :type=>:flours
6
+ @ap = BreadCalculator::Ingredient.new "all purpose flour", :quantity => 700, :units => 'grams', :type=>:flours
7
+ @water = BreadCalculator::Ingredient.new "water", :quantity => 550, :units => 'grams', :type=>:liquids
8
+ @egg = BreadCalculator::Ingredient.new "egg", :quantity => 40, :units => 'grams', :type=>:liquids
9
+ @milk = BreadCalculator::Ingredient.new "dry milk", :quantity => 40, :units => 'grams', :type=>:additives
10
+ @raisins = BreadCalculator::Ingredient.new "raisins", :quantity => 50, :units => 'grams', :type=>:additives
11
+ @yeast = BreadCalculator::Ingredient.new "yeast", :quantity => 20, :units => 'grams', :type=>:additives
12
+ @proof = BreadCalculator::Step.new 'Rehydrate', @yeast
13
+ @wet = BreadCalculator::Step.new 'in', @water, @egg
14
+ @dry = BreadCalculator::Step.new 'Mix together:', @ww, @ap, @milk, 'in a large bowl'
15
+ @mix = BreadCalculator::Step.new 'Combine wet and dry ingredients with', @raisins
16
+ @bake = BreadCalculator::Step.new 'Form a loaf, rise for 2 hours, Bake at 375° for 45 minutes.'
17
+ @meta = {:notes => 'nice sandwich bread'}
18
+ @recipe = BreadCalculator::Recipe.new @meta, [@proof, @wet, @dry, @mix, @bake]
19
+ end
20
+
21
+ describe BreadCalculator::Ingredient do
22
+ it 'has a quantity' do
23
+ @ww.quantity = 100
24
+ end
25
+ end
26
+
27
+ describe BreadCalculator::Step do
28
+ it 'can be called without arguments' do
29
+ BreadCalculator::Step.new
30
+ end
31
+ end
32
+
33
+ describe BreadCalculator::Recipe do
34
+ it 'lists all ingredients and quantities' do
35
+ @recipe.ingredients.length.should eq 7
36
+ end
37
+
38
+ it 'displays total weight' do
39
+ @recipe.weight.should eq 1700
40
+ end
41
+
42
+ it 'displays total liquids' do
43
+ @recipe.total_liquids.should eq 590
44
+ end
45
+
46
+ it 'pretty prints' do
47
+ pending
48
+ @recipe.to_s.is_a?(String).should be_true
49
+ end
50
+
51
+ it 'generates a baker\'s percentage summary' do
52
+ @recipe.summary.is_a?(BreadCalculator::Summary).should be_true
53
+ end
54
+
55
+ it 'scales' do
56
+ @scaled = @recipe.scale_by(2)
57
+ ( @scaled.weight == @recipe.weight * 2.0 ).should be_true
58
+ end
59
+
60
+ it 'generates a baker\'s percentage formula' do
61
+ @recipe.bakers_percent_formula.is_a?(BreadCalculator::Recipe).should be_true
62
+ @recipe.bakers_percent_formula.total_flours.should eq 100.0
63
+ end
64
+
65
+ end
66
+
67
+ describe BreadCalculator::Parser do
68
+ before do
69
+ @sample = "#{File.dirname(__FILE__)}/../sample/sandwich-bread.recipe"
70
+ @parser = BreadCalculator::Parser.new 'Sandwich Bread'
71
+ @r = @parser.parse "#{@sample}"
72
+ end
73
+
74
+ it 'gets a recipe from a text file' do
75
+ @r.is_a?(BreadCalculator::Recipe).should be_true
76
+ end
77
+
78
+ it 'gets a recipe from a standard in' do
79
+ pending
80
+ end
81
+
82
+ end
83
+
84
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bread_calculator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Noah Birnel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |-
14
+ a gem and command-line wrapper to generate baker's
15
+ percentages from a bread recipe
16
+ email: nbirnel@gmail.com
17
+ executables:
18
+ - bread-calculator
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.md
23
+ - bread_calculator.gemspec
24
+ - lib/bread_calculator.rb
25
+ - spec/bread_calculator_spec.rb
26
+ - bin/bread-calculator
27
+ homepage: http://github.com/nbirnel/bread-calculator
28
+ licenses:
29
+ - DWTFYW
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.0.7
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: calculate baker's percentages
51
+ test_files: []