bread_calculator 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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