bread_calculator 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6959568a142ec9660ea66a80839a34762e0282ee
4
- data.tar.gz: 4b89277648b629fd0920eab943eadffa56d6e01f
3
+ metadata.gz: 53aef8e8a052ec183a4f4121445c1daa1d15319b
4
+ data.tar.gz: 2c9b6a67fd14f71f0a3cddbfe2e73aa028dbbe9b
5
5
  SHA512:
6
- metadata.gz: c6bb8d52aa6d48efbaf602ac3d56c85ed5ad71f30e0723cf681f77c3204671356b0e5093ca2f2865ea7d48b43acdc9b97a19dcf9463fc036c8427035340566a4
7
- data.tar.gz: e541c41118bb3086b7b7789a973feb6942b90aa7c3e0cf65768f7df1701c5e124789a55c036b530792bff3a603a89f098dedcfc50506ded7f3bc1a239d357277
6
+ metadata.gz: 72f6258f4ed7b8884e85af325a4c1e5161a433be5e5bb460e80c7530e329aca0e778f143d4becd26059fbc64f08bb45e97001b0c227684734bac1f4427f171bc
7
+ data.tar.gz: ed81a8f9cff34616d3c2c4199ebaea6b97b2ae44d23441a3bc478effbac1183e4c5e0b4d51a5ce0c95c7bfc059663a3395d7c732536042b9a4c6138b83eb1d8b
data/README.md CHANGED
@@ -98,22 +98,30 @@ can by scaled up or down.</p>
98
98
 
99
99
 
100
100
  <p style="margin-left:11%; margin-top: 1em"><b>--help</b>
101
- print this help</p>
101
+ Print a brief usage message.</p>
102
102
 
103
103
 
104
104
  <p style="margin-left:11%; margin-top: 1em"><b>--summary</b>
105
- print a baker&rsquo;s percentage summary</p>
105
+ Print all baker&rsquo;s percentages of the recipe.
106
+ Over-rides <b>--scale-by</b></p>
106
107
 
107
108
  <p style="margin-left:11%; margin-top: 1em"><b>--html</b>
108
- print recipe as html.</p>
109
+ Print recipe as html. Over-rides <b>--weight</b> or
110
+ <b>--to.</b></p>
109
111
 
110
112
 
111
113
  <p style="margin-left:11%; margin-top: 1em"><b>--weight</b>
112
- print the weight</p>
114
+ Print the weight of the recipe. Over-rides <b>--html</b> or
115
+ <b>--to.</b></p>
116
+
117
+ <p style="margin-left:11%; margin-top: 1em"><b>--to
118
+ WEIGHT</b> Regenerate the recipe or summary to <b>WEIGHT</b>
119
+ total weight. Over-rides <b>--html</b> or
120
+ <b>--weight.</b></p>
113
121
 
114
122
  <p style="margin-left:11%; margin-top: 1em"><b>--scale-by
115
- FACTOR</b> regenerate the recipe, scaling up or down by
116
- FACTOR</p>
123
+ FACTOR.</b> Regenerate the recipe, scaling up or down by
124
+ <b>FACTOR</b> Over-rides <b>--summary.</b></p>
117
125
 
118
126
  <h2>EXAMPLES
119
127
  <a name="EXAMPLES"></a>
@@ -121,7 +129,11 @@ FACTOR</p>
121
129
 
122
130
 
123
131
  <p style="margin-left:11%; margin-top: 1em"><b>bread-calc
124
- --summary sample.recipe</b></p>
132
+ --summary sample.recipe</b> summarize a recipe</p>
133
+
134
+ <p style="margin-left:11%; margin-top: 1em"><b>bread-calc
135
+ --scale-by .5 sample.recipe | bread-calc --html
136
+ &gt;sample.html</b> halve a recipe and render as html</p>
125
137
 
126
138
  <h2>FORMATS
127
139
  <a name="FORMATS"></a>
@@ -129,7 +141,7 @@ FACTOR</p>
129
141
 
130
142
 
131
143
  <p style="margin-left:11%; margin-top: 1em">The recipe
132
- format consists of a metadata prelude, and steps.</p>
144
+ format consists of a metadata prelude followed by steps.</p>
133
145
 
134
146
  <p style="margin-left:11%; margin-top: 1em">In prelude
135
147
  lines, anything before a colon is considered to be the name
@@ -152,14 +164,14 @@ itself.</p>
152
164
 
153
165
 
154
166
  <p style="margin-left:11%; margin-top: 1em"><b>bread-calc</b>
155
- will attempt to guess at the type of ingredient, but you can
156
- always force it by including one of the words
167
+ makes a crude attempt to guess at the type of ingredient,
168
+ but you can always force it by including one of the words
157
169
  &rsquo;flour&rsquo;, &rsquo;liquid&rsquo;, or
158
170
  &rsquo;additive&rsquo; in the line.</p>
159
171
 
160
172
  <p style="margin-left:11%; margin-top: 1em">Here is a brief
161
- example (note that github displays the indenting
162
- incorrectly):</p>
173
+ example (note that if you are reading this on github, you
174
+ won&rsquo;t see the indenting):</p>
163
175
 
164
176
  <p style="margin-left:17%; margin-top: 1em">name: imaginary
165
177
  bread <br>
@@ -195,7 +207,7 @@ cheerfully assumed that all units are grams.</p>
195
207
 
196
208
  <p style="margin-left:11%; margin-top: 1em">It is undefined
197
209
  how &rsquo;liquid flour additive&rsquo; is parsed, but
198
- don&rsquo;t expect anything good.</p>
210
+ don&rsquo;t expect anything sensible.</p>
199
211
 
200
212
  <h2>LICENSE
201
213
  <a name="LICENSE"></a>
data/bin/bread-calc CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bread_calculator'
4
3
  #require "#{File.dirname(__FILE__)}/../lib/bread_calculator"
4
+ require 'bread_calculator'
5
5
 
6
6
  def htm_header
7
7
  puts '<head>'
@@ -17,29 +17,31 @@ where OPTIONS are any of:
17
17
  --summary
18
18
  --html
19
19
  --weight
20
+ --recipe
20
21
  --scale-by FACTOR
21
22
 
22
23
  Install the man page, or go to
23
24
 
24
25
  https://github.com/nbirnel/bread-calculator
25
26
 
26
- for more detailed information.
27
+ for more detailed and up-to-date information.
27
28
  EOF
28
29
 
29
30
  exit status
30
31
 
31
32
  end
32
33
 
33
- @get = 'r'
34
34
  @help = nil
35
+ @opt = ['r']
35
36
 
36
37
  loop { case ARGV[0]
37
38
  when /--help/ then @help = 'help' ; ARGV.shift; break
38
- when /--summary/ then @get = 'r.summary'; ARGV.shift
39
- when /--html/ then @get = 'r.to_html'; ARGV.shift
40
- when /--weight/ then @get = 'r.weight'; ARGV.shift
41
39
  #.to_f protects against 'no .<digit> floating literal anymore' for .33
42
- when /--scale-by/ then ARGV.shift; @get = "r.scale_by #{ARGV.shift.to_f}"
40
+ when /--scale-by/ then ARGV.shift; @opt[1] = "scale_by(#{ARGV.shift.to_f})"
41
+ when /--summary/ then @opt[1] = 'summary'; ARGV.shift
42
+ when /--html/ then @opt[2] = 'to_html'; ARGV.shift
43
+ when /--weight/ then @opt[2] = 'weight'; ARGV.shift
44
+ when /--to/ then ARGV.shift; @opt[2] = "recipe(#{ARGV.shift.to_f})"
43
45
  when /--/ then ARGV.shift; break
44
46
  when /^-/ then help 1
45
47
  else break
@@ -47,8 +49,10 @@ end; }
47
49
 
48
50
  help if @help
49
51
 
52
+ @method_chain = @opt.select{|e| e if e}.join('.')
53
+
50
54
  parser = BreadCalculator::Parser.new
51
55
  r = parser.parse ARGF.file
52
- htm_header if @get.include? 'html'
53
- puts eval("#{@get}")
56
+ htm_header if @method_chain.include? 'to_html'
57
+ puts eval("#{@method_chain}")
54
58
 
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'bread_calculator'
3
- s.version = '0.3.0'
4
- s.date = '2014-03-11'
3
+ s.version = '0.4.0'
4
+ s.date = '2014-03-13'
5
5
  s.summary = "calculate baker's percentages"
6
6
  s.description = "a gem and command-line wrapper to generate baker's
7
7
  percentages from a bread recipe"
@@ -1,3 +1,18 @@
1
+ ##
2
+ # round +number+ depending on it's magnitude
3
+
4
+ def human_round number, base_precision = 0
5
+ #FIXME there is an algorithm for this. What is it?
6
+ precision = base_precision
7
+ if number < 10
8
+ precision = base_precision + 1
9
+ end
10
+ if number < 1
11
+ precision = base_precision + 2
12
+ end
13
+ f_number = sprintf "%.#{precision}f", number
14
+ end
15
+
1
16
  ##
2
17
  # Classes for manipulating bread recipes and baker's percentages
3
18
 
@@ -5,6 +20,9 @@ module BreadCalculator
5
20
 
6
21
  require 'cgi'
7
22
 
23
+ ##
24
+ # Make a reasonably precise representation of +number+
25
+
8
26
  ##
9
27
  # This class represents an ingredient in a Recipe
10
28
 
@@ -19,7 +37,7 @@ module BreadCalculator
19
37
  # +:liquids+, or +:additives+.
20
38
 
21
39
  def initialize name, info={}
22
- @units = 'grams'
40
+ #@units = 'grams'
23
41
  @name = name
24
42
  @info = info
25
43
  info.each do |k,v|
@@ -30,7 +48,7 @@ module BreadCalculator
30
48
  ##
31
49
  # Returns a new Ingredient, scaled from current instance by +ratio+
32
50
 
33
- def scale_by ratio
51
+ def scale_by ratio, units=self.units
34
52
  scaled = Hash.new
35
53
  self.info.each do |k, v|
36
54
  scaled[k] = v
@@ -38,21 +56,30 @@ module BreadCalculator
38
56
  end
39
57
  Ingredient.new(self.name, scaled)
40
58
  end
59
+
60
+ #FIXME refactor scale_by and as_bp
61
+
62
+ ##
63
+ # Returns a new unitless Ingredient as a baker's percentage of +bp_100+
64
+
65
+ def as_bp bp_100
66
+ info = self.info.reject{|k,v| k == :units}
67
+
68
+ scaled = Hash.new
69
+ info.each do |k, v|
70
+ scaled[k] = v
71
+ scaled[k] = v / bp_100.to_f if k == :quantity
72
+ end
73
+ Ingredient.new(self.name, scaled)
74
+ end
41
75
 
42
76
  ##
43
77
  # Print a nice text version of Ingredient
44
78
 
45
- def to_s
79
+ def to_s summary=nil
80
+ q = summary ? "#{human_round(@quantity*100).to_s}%" : human_round(@quantity)
46
81
  #FIXME check for existance
47
- precision = 0
48
- if @quantity < 10
49
- precision = 1
50
- end
51
- if @quantity < 1
52
- precision = 2
53
- end
54
- f_quantity = sprintf "%.#{precision}f", @quantity
55
- "\t#{f_quantity} #{@units} #{@name}\n"
82
+ "\t#{q} #{@units} #{@name}\n"
56
83
  end
57
84
 
58
85
  ##
@@ -87,6 +114,7 @@ module BreadCalculator
87
114
 
88
115
  ##
89
116
  # Sets +Step.techniques+ to +args+, and defines +Step.ingredients+
117
+
90
118
  def techniques= args
91
119
  @techniques = args
92
120
  @ingredients = args.select{|arg| arg.is_a? Ingredient}
@@ -95,10 +123,10 @@ module BreadCalculator
95
123
  ##
96
124
  # Print a nice text version of Step
97
125
 
98
- def to_s
126
+ def to_s summary=nil
99
127
  out = ''
100
128
  self.techniques.each do |t|
101
- tmp = t.is_a?(Ingredient) ? t.to_s : "#{t.chomp}\n"
129
+ tmp = t.is_a?(Ingredient) ? t.to_s(summary) : "#{t.chomp}\n"
102
130
  out << tmp
103
131
  end
104
132
  out << "\n"
@@ -117,10 +145,19 @@ module BreadCalculator
117
145
  out << "\n</p>\n"
118
146
  out
119
147
  end
148
+
120
149
  end
121
150
 
122
151
  ##
123
152
  # This class represents a recipe.
153
+ #
154
+ # Runtime-generated methods:
155
+ #
156
+ # total_flours
157
+ # total_liquids
158
+ # total_additives
159
+ #
160
+ # return totals of their respective types
124
161
 
125
162
  class Recipe
126
163
  attr_reader :steps, :metadata
@@ -132,7 +169,7 @@ module BreadCalculator
132
169
  # Other likely keys are:
133
170
  # <tt>:prep_time, :total_time, :notes, :history, :serves, :makes,
134
171
  # :attribution</tt>.
135
-
172
+
136
173
  def initialize metadata, steps
137
174
  @metadata = metadata
138
175
  @steps = steps
@@ -162,6 +199,7 @@ module BreadCalculator
162
199
  #FIXME make this a method_missing so we can add new types on the fly
163
200
  #RENÉE - 'end.' is weird or no?
164
201
  #FIXME how do I get this into rdoc?
202
+
165
203
  [:flours, :liquids, :additives].each do |s|
166
204
  define_method("total_#{s}") do
167
205
  instance_variable_get("@ingredients").select{|i| i.type == s}.map do |i|
@@ -192,7 +230,7 @@ module BreadCalculator
192
230
 
193
231
  def scale_by ratio
194
232
  new_steps = self.steps.map do |s|
195
- step_args = s.techniques.map do |t|
233
+ step_args = s.techniques.map do |t|
196
234
  t.is_a?(Ingredient) ? t.scale_by(ratio) : t
197
235
  end
198
236
  Step.new step_args
@@ -205,16 +243,19 @@ module BreadCalculator
205
243
  # Returns a Summary
206
244
 
207
245
  def summary
208
- types = Hash.new
246
+ new_meta = self.metadata
209
247
  [:flours, :liquids, :additives].each do |s|
210
- types["total_#{s}"] = self.bakers_percent eval("self.total_#{s}")
248
+ new_meta["total_#{s}"] = eval "self.bakers_percent self.total_#{s}"
211
249
  end
212
250
 
213
- l_ingredients = Hash.new
214
- self.ingredients.map do |i|
215
- l_ingredients[i.name] = self.bakers_percent i.quantity
251
+ new_steps = self.steps.map do |s|
252
+ step_args = s.techniques.map do |t|
253
+ t.is_a?(Ingredient) ? t.as_bp(self.bakers_percent_100) : t
254
+ end
255
+ Step.new step_args
216
256
  end
217
- Summary.new types, l_ingredients
257
+
258
+ Summary.new new_meta, new_steps
218
259
  end
219
260
 
220
261
  ##
@@ -242,6 +283,67 @@ module BreadCalculator
242
283
 
243
284
  end
244
285
 
286
+ ##
287
+ # This class represents a summary of a Recipe - like a Recipe, but:
288
+ #
289
+ # Ingredients will be unitless
290
+ # and the quantities expressed in baker's percentages,
291
+ #
292
+ # Metadata will include baker's percentages of
293
+ # flours, liquids, and additives.
294
+
295
+ class Summary < Recipe
296
+
297
+ def initialize metadata, steps
298
+ super
299
+ end
300
+
301
+ def weight
302
+ nil
303
+ end
304
+
305
+ def summary
306
+ self
307
+ end
308
+
309
+ def to_s
310
+ #FIXME this should be calling super somehow
311
+ out = ''
312
+ self.metadata.each do |k,v|
313
+ nv = k.to_s =~ /^total_/ ? "#{human_round(v*100).to_s}%" : v
314
+ out << "#{k}: #{nv}\n"
315
+ end
316
+ out << "--------------------\n"
317
+ self.steps.each{|s| out << s.to_s(:summary)}
318
+ out
319
+ end
320
+
321
+ def to_html
322
+ #FIXME obviously inadequate
323
+ super
324
+ end
325
+
326
+ def recipe weight, units='grams'
327
+
328
+ new_metadata = self.metadata.reject{|k,v| k.to_s =~ /^total_/}
329
+
330
+ totals = self.metadata.select{|k,v| k.to_s =~ /^total_/}
331
+ all_totals = totals.values.inject(:+).to_f
332
+ new_bp_100 = weight / all_totals
333
+
334
+ new_steps = self.steps.map do |s|
335
+ step_args = s.techniques.map do |t|
336
+ t.is_a?(Ingredient) ? t.scale_by(new_bp_100, units) : t
337
+ end
338
+ Step.new step_args
339
+ end
340
+
341
+ Recipe.new new_metadata, new_steps
342
+
343
+ end
344
+
345
+ end
346
+
245
347
  ##
246
348
  # This class converts a nearly free-form text file to a Recipe
247
349
 
@@ -263,7 +365,35 @@ module BreadCalculator
263
365
  ##
264
366
  # Parse text from IO object +input+. It is the caller's responsibility to
265
367
  # open and close the +input+ correctly.
266
-
368
+ #
369
+ # text recipes consist of a metadata prelude followed by steps.
370
+ #
371
+ # In prelude lines,
372
+ # anything before a colon is considered to be the name of a metadata field;
373
+ # anything after the colon is a value to populate.
374
+ # Lines without colons are continuations of the 'notes' field.
375
+ # I suggest having at least a 'name' field.
376
+ #
377
+ # A line starting with a hyphen ends the prelude and starts the first step.
378
+ #
379
+ # Each step is delimited by one or more blank lines.
380
+ #
381
+ # Any line in a step starting with a space or a blank is an ingredient,
382
+ # consisting of quantity, units, and the ingredient itself.
383
+ #
384
+ # A brief sample:
385
+ #
386
+ # name: imaginary bread
387
+ # notes: This is a silly fake bread recipe
388
+ # makes: 1 bad loaf
389
+ # This line will become part of the notes
390
+ # ---------------------
391
+ # Mix:
392
+ # 500 g flour
393
+ # 300 g water
394
+ #
395
+ # Bake at 375°
396
+
267
397
  def parse input
268
398
 
269
399
  while line = input.gets
@@ -337,32 +467,4 @@ module BreadCalculator
337
467
 
338
468
  end
339
469
 
340
- ##
341
- # This class represents a summary of a Recipe - no Steps or units, just
342
- # baker's percentages of each ingredient, and a prelude of baker's
343
- # percentages for flours, liquids, and additives.
344
-
345
- class Summary
346
- attr_accessor :types, :ingredients
347
-
348
- ##
349
- # Create a new Summary of +types+ and +ingredients+
350
-
351
- def initialize types, ingredients
352
- @types = types
353
- @ingredients = ingredients
354
- end
355
-
356
- ##
357
- # Print it nicely
358
-
359
- def to_s
360
- out = ''
361
- self.types.each{|k,v| out << "#{k}: #{v}\n"}
362
- out << "--------------------\n"
363
- self.ingredients.each{|k,v| out << "#{k}: #{v}\n"}
364
- out
365
- end
366
- end
367
-
368
470
  end
@@ -17,6 +17,7 @@ describe BreadCalculator do
17
17
  @bake = BreadCalculator::Step.new 'Form a loaf, rise for 2 hours, Bake at 375° for 45 minutes.'
18
18
  @meta = {:notes => 'nice sandwich bread'}
19
19
  @recipe = BreadCalculator::Recipe.new @meta, [@proof, @wet, @dry, @mix, @bake]
20
+ @summary = @recipe.summary
20
21
  end
21
22
 
22
23
  describe BreadCalculator::Ingredient do
@@ -49,7 +50,7 @@ describe BreadCalculator do
49
50
  end
50
51
 
51
52
  it 'generates a baker\'s percentage summary' do
52
- @recipe.summary.is_a?(BreadCalculator::Summary).should be_true
53
+ @summary.is_a?(BreadCalculator::Summary).should be_true
53
54
  end
54
55
 
55
56
  it 'scales' do
@@ -68,6 +69,18 @@ describe BreadCalculator do
68
69
 
69
70
  end
70
71
 
72
+ describe BreadCalculator::Summary do
73
+ before do
74
+ @new_recipe = @summary.recipe(1000, 'g')
75
+ end
76
+
77
+ it 'generates a Recipe' do
78
+ @new_recipe.is_a?(BreadCalculator::Recipe).should be_true
79
+ @new_recipe.weight.should == 1000
80
+ end
81
+ end
82
+
83
+
71
84
  describe BreadCalculator::Parser do
72
85
  before do
73
86
  @parser = BreadCalculator::Parser.new
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bread_calculator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Birnel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-11 00:00:00.000000000 Z
11
+ date: 2014-03-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |-
14
14
  a gem and command-line wrapper to generate baker's