beerxml 0.0.2 → 0.3.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.
@@ -20,7 +20,6 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency "nokogiri", "~> 1.4"
21
21
  s.add_dependency "dm-core", "~> 1.0"
22
22
  s.add_dependency "dm-validations", "~> 1.0"
23
- s.add_dependency "dm-types", "~> 1.0"
24
23
 
25
24
  s.add_development_dependency "rspec", "~> 2.5"
26
25
  s.add_development_dependency "autotest-standalone"
@@ -1,5 +1,4 @@
1
1
  require 'dm-core'
2
- require 'dm-types'
3
2
  require 'dm-validations'
4
3
  require 'nokogiri'
5
4
 
@@ -1,6 +1,6 @@
1
1
  class Beerxml::Fermentable < Beerxml::Model
2
2
  property :name, String, :required => true
3
- property :type, Enum['Grain', 'Sugar', 'Extract', 'Dry Extract', 'Adjunct'], :required => true
3
+ property :type, String, :set => ['Grain', 'Sugar', 'Extract', 'Dry Extract', 'Adjunct'], :required => true
4
4
  property :amount, Weight, :required => true
5
5
  property :yield, Float, :required => true
6
6
  property :color, Float, :required => true
@@ -21,6 +21,10 @@ class Beerxml::Fermentable < Beerxml::Model
21
21
  property :id, Serial
22
22
  belongs_to :recipe, :required => false
23
23
 
24
+ def ppg=(ppg)
25
+ self.yield = Beerxml.round((ppg / 46.214) / 0.01, 2)
26
+ end
27
+
24
28
  def ppg
25
29
  # potential is (yield * 0.001 + 1)
26
30
  (self.yield * 0.01) * 46.214
@@ -31,4 +35,8 @@ class Beerxml::Fermentable < Beerxml::Model
31
35
  total *= (efficiency * 0.01) unless efficiency.nil? || type != 'Grain'
32
36
  total
33
37
  end
38
+
39
+ def total_color
40
+ amount.in_pounds.to_f * color
41
+ end
34
42
  end
@@ -2,12 +2,12 @@ class Beerxml::Hop < Beerxml::Model
2
2
  property :name, String, :required => true
3
3
  property :alpha, Float, :required => true
4
4
  property :amount, Weight, :required => true
5
- property :use, Enum['Boil', 'Dry Hop', 'Mash', 'First Wort', 'Aroma'], :required => true
5
+ property :use, String, :set => ['Boil', 'Dry Hop', 'Mash', 'First Wort', 'Aroma'], :required => true
6
6
  property :time, Time, :required => true
7
7
 
8
8
  property :notes, String, :length => 65535
9
- property :type, Enum[nil, 'Bittering', 'Aroma', 'Both']
10
- property :form, Enum[nil, 'Pellet', 'Plug', 'Leaf']
9
+ property :type, String, :set => ['Bittering', 'Aroma', 'Both']
10
+ property :form, String, :set => ['Pellet', 'Plug', 'Leaf']
11
11
  property :beta, Float
12
12
  property :hsi, Float
13
13
  property :origin, String, :length => 512
@@ -43,13 +43,10 @@ class Beerxml::Model
43
43
  read_xml_field(node, property.name.to_s)
44
44
  end
45
45
  # load any has-many relationships with other beerxml models
46
- relationships.each do |name, rel|
47
- child_model = rel.child_model
48
- next unless child_model.ancestors.include?(Beerxml::Model)
49
-
46
+ each_beerxml_relationship do |rel|
50
47
  # look for the plural element in the children of this node
51
48
  # e.g., for Hop, see if there's any HOPS element.
52
- (node>child_model.beerxml_plural_name).each do |child_wrapper_node|
49
+ (node>rel.child_model.beerxml_plural_name).each do |child_wrapper_node|
53
50
  collection = Beerxml::Model.collection_from_xml(child_wrapper_node)
54
51
  self.send(rel.name).concat(collection)
55
52
  end
@@ -83,9 +80,16 @@ class Beerxml::Model
83
80
  attributes.each do |attr, val|
84
81
  next if attr.to_s.match(/id\z/) || val.nil?
85
82
  x = Nokogiri::XML::Node.new(self.class.xml_attr_name(attr), parent)
86
- x.content = val # TODO: data types
83
+ x.content = self.class.properties[attr].dump(val)
87
84
  node.add_child(x)
88
85
  end
86
+ each_beerxml_relationship do |rel|
87
+ objs = self.send(rel.name)
88
+ next if objs.empty?
89
+ sub_node = Nokogiri::XML::Node.new(rel.child_model.beerxml_plural_name, node)
90
+ node.add_child(sub_node)
91
+ objs.each { |o| o.to_beerxml(sub_node) }
92
+ end
89
93
  parent.add_child(node)
90
94
  parent
91
95
  end
@@ -97,7 +101,18 @@ class Beerxml::Model
97
101
  def self.xml_attr_name(name)
98
102
  name.to_s.upcase
99
103
  end
104
+
105
+ def beerxml_relationships
106
+ []
107
+ end
108
+
109
+ def each_beerxml_relationship
110
+ relationships.each do |name, rel|
111
+ next unless beerxml_relationships.include?(rel.name)
112
+ yield rel
113
+ end
114
+ end
100
115
  end
101
116
 
102
- %w(hop recipe fermentable).
117
+ %w(hop recipe fermentable yeast).
103
118
  each { |f| require "beerxml/#{f}" }
@@ -8,10 +8,14 @@ module Beerxml::Properties
8
8
  true
9
9
  end
10
10
 
11
+ def primitive?(value)
12
+ value.is_a?(Beerxml::Unit) && value.type == unit_type
13
+ end
14
+
11
15
  def load(value)
12
16
  return if value.nil?
13
17
  if value.is_a?(Beerxml::Unit)
14
- raise(ArgumentError, 'Weight required') unless value.type == unit_type
18
+ raise(ArgumentError, "#{value.inspect} is not a #{unit_type}") unless value.type == unit_type
15
19
  value
16
20
  else
17
21
  U(value, base_unit)
@@ -2,7 +2,7 @@ class Beerxml::Recipe < Beerxml::Model
2
2
  include DataMapper::Resource
3
3
 
4
4
  property :name, String, :required => true
5
- property :type, Enum['Extract', 'Partial Mash', 'All Grain'], :required => true
5
+ property :type, String, :set => ['Extract', 'Partial Mash', 'All Grain'], :required => true
6
6
  # has 1, :style, :required => true
7
7
  property :brewer, String, :required => true
8
8
  property :batch_size, Volume, :required => true
@@ -14,9 +14,13 @@ class Beerxml::Recipe < Beerxml::Model
14
14
  has n, :hops
15
15
  has n, :fermentables
16
16
  # has n, :miscs
17
- # has n, :yeasts
17
+ has n, :yeasts
18
18
  # has n, :waters
19
19
 
20
+ def beerxml_relationships
21
+ [:hops, :fermentables, :miscs, :yeasts, :waters]
22
+ end
23
+
20
24
  property :asst_brewer, String
21
25
  property :notes, String, :length => 65535
22
26
  property :taste_notes, String, :length => 65535
@@ -47,7 +51,7 @@ class Beerxml::Recipe < Beerxml::Model
47
51
  property :id, Serial
48
52
 
49
53
  def tinseth
50
- Beerxml.round(hops.select { |h| h.use == 'Boil' }.inject(0) { |v, h| v + h.tinseth(og, batch_size) }, 1)
54
+ Beerxml.round(hops.select { |h| h.use == 'Boil' }.inject(0) { |v, h| v + h.tinseth(calculate_og, batch_size) }, 1)
51
55
  end
52
56
  alias_method :ibus, :tinseth
53
57
 
@@ -56,4 +60,14 @@ class Beerxml::Recipe < Beerxml::Model
56
60
  og = 1 + ((total_ppg / batch_size.in_gallons.to_f) * 0.001)
57
61
  Beerxml.round(og, 3)
58
62
  end
63
+
64
+ def calculate_fg
65
+ og = calculate_og
66
+ Beerxml.round(og - ((og - 1) * yeasts.first.attenuation * 0.01), 3)
67
+ end
68
+
69
+ def color
70
+ color = fermentables.inject(0) { |v, f| v + f.total_color }
71
+ Beerxml.round(1.4922 * ((color / batch_size.in_gallons.to_f) ** 0.6859), 1)
72
+ end
59
73
  end
@@ -96,25 +96,10 @@ class Unit
96
96
  end
97
97
  end
98
98
 
99
- # todo: remove this, to_beerxml is using it (and incorrectly at that)
100
- def to_s
101
- to_f.to_s
102
- end
103
-
104
99
  def inspect
105
100
  "[#{to_f} #{unit}]"
106
101
  end
107
102
 
108
- def *(rhs)
109
- if rhs.is_a?(Numeric)
110
- ret = Unit.new(self.to_f * rhs, self.unit)
111
- elsif rhs.is_a?(Unit) && rhs.type == type
112
- ret = Unit.new(self.to_f * rhs.to(self.unit).to_f, self.unit)
113
- else
114
- super
115
- end
116
- end
117
-
118
103
  # Assumes earth gravity ;)
119
104
  module Weight
120
105
  def self.included(k)
@@ -1,3 +1,3 @@
1
1
  module Beerxml
2
- VERSION = "0.0.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,26 @@
1
+ class Beerxml::Yeast < Beerxml::Model
2
+ include DataMapper::Resource
3
+
4
+ property :name, String, :required => true
5
+ property :type, String, :set => ['Ale', 'Lager', 'Wheat', 'Wine', 'Champagne'], :required => true
6
+ property :form, String, :set => ['Liquid', 'Dry', 'Slant', 'Culture'], :required => true
7
+ # TODO: sheesh... this can be a Weight instead, if amount_is_weight
8
+ property :amount, Volume, :required => true
9
+
10
+ property :amount_is_weight, Boolean
11
+ property :laboratory, String
12
+ property :product_id, String
13
+ property :min_temperature, Temperature
14
+ property :max_temperature, Temperature
15
+ property :flocculation, String, :set => ['Low', 'Medium', 'High', 'Very High']
16
+ property :attenuation, Float
17
+ property :notes, String, :length => 65535
18
+ property :best_for, String
19
+ property :times_cultured, Integer
20
+ property :max_reuse, Integer
21
+ property :add_to_secondary, Boolean
22
+
23
+ # these are not used in the xml
24
+ property :id, Serial
25
+ belongs_to :recipe, :required => false
26
+ end
@@ -0,0 +1,37 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ describe "dumping" do
4
+ describe "sanity checks" do
5
+ it "should dump hops" do
6
+ model = Beerxml::Hop.new({
7
+ :name => 'Northern Brewer',
8
+ :origin => 'Germany',
9
+ :alpha => 8.5,
10
+ :amount => 0.0,
11
+ :use => 'Boil',
12
+ :time => 0.0,
13
+ :notes => %{Also called Hallertauer Northern Brewers
14
+ Use for: Bittering and finishing both ales and lagers of all kinds
15
+ Aroma: Fine, dry, clean bittering hop. Unique flavor.
16
+ Substitute: Hallertauer Mittelfrueh, Hallertauer
17
+ Examples: Anchor Steam, Old Peculiar, },
18
+ :type => 'Both',
19
+ :form => 'Pellet',
20
+ :beta => 4.0,
21
+ :hsi => 35.0,
22
+ })
23
+ model.should be_valid
24
+ xml = model.to_beerxml.root
25
+ (xml>'NAME').text.should == 'Northern Brewer'
26
+ (xml>'ORIGIN').text.should == 'Germany'
27
+ (xml>'ALPHA').text.should == '8.5'
28
+ (xml>'AMOUNT').text.should == '0.0'
29
+ (xml>'USE').text.should == 'Boil'
30
+ (xml>'TIME').text.should == '0.0'
31
+ (xml>'TYPE').text.should == 'Both'
32
+ (xml>'FORM').text.should == 'Pellet'
33
+ (xml>'BETA').text.should == '4.0'
34
+ (xml>'HSI').text.should == '35.0'
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,106 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ include Beerxml
4
+
5
+ describe "Centennial Blonde" do
6
+ before(:each) do
7
+ # http://www.homebrewtalk.com/f66/centennial-blonde-simple-4-all-grain-5-10-gall-42841/
8
+ @recipe = Recipe.new(:name => 'Centennial Blonde',
9
+ :type => 'All Grain',
10
+ :brewer => 'BierMuncher',
11
+ :batch_size => 5.5.gallons,
12
+ :boil_size => 6.75.gallons,
13
+ :boil_time => 60.minutes,
14
+ :efficiency => 70.0)
15
+ @recipe.hops << Hop.new(:name => 'Centennial',
16
+ :alpha => 9.5,
17
+ :amount => 0.25.oz,
18
+ :use => 'Boil',
19
+ :time => 55)
20
+ @recipe.hops << Hop.new(:name => 'Centennial',
21
+ :alpha => 9.5,
22
+ :amount => 0.25.oz,
23
+ :use => 'Boil',
24
+ :time => 35)
25
+ @recipe.hops << Hop.new(:name => 'Cascade',
26
+ :alpha => 7.8,
27
+ :amount => 0.25.oz,
28
+ :use => 'Boil',
29
+ :time => 20)
30
+ @recipe.hops << Hop.new(:name => 'Cascade',
31
+ :alpha => 7.8,
32
+ :amount => 0.25.oz,
33
+ :use => 'Boil',
34
+ :time => 5)
35
+ @recipe.fermentables << Fermentable.new(:name => 'Pale Malt (2 Row)',
36
+ :type => 'Grain',
37
+ :amount => 7.pounds,
38
+ :ppg => 37,
39
+ :color => 2)
40
+ @recipe.fermentables << Fermentable.new(:name => 'Cara-Pils',
41
+ :type => 'Grain',
42
+ :amount => 0.75.lb,
43
+ :ppg => 33,
44
+ :color => 2)
45
+ @recipe.fermentables << Fermentable.new(:name => 'Crystal 10L',
46
+ :type => 'Grain',
47
+ :amount => 0.5.pounds,
48
+ :ppg => 35,
49
+ :color => 10)
50
+ @recipe.fermentables << Fermentable.new(:name => 'Vienna',
51
+ :type => 'Grain',
52
+ :amount => U(0.5, 'lb'),
53
+ :ppg => 35,
54
+ :color => 4)
55
+ @recipe.yeasts << Yeast.new(:name => 'Nottingham',
56
+ :type => 'Ale',
57
+ :form => 'Dry',
58
+ :amount => 0.2.liters,
59
+ :attenuation => 75)
60
+ @recipe.should be_valid
61
+ end
62
+
63
+ it "should calculate basic stats" do
64
+ @recipe.ibus.round.should == 20
65
+ @recipe.calculate_og.should == 1.041
66
+ @recipe.calculate_fg.should == 1.010
67
+ @recipe.color == 3.9
68
+ end
69
+
70
+ it "should survive the serialization round trip" do
71
+ xml = Nokogiri::XML(@recipe.to_xml)
72
+ r2 = Recipe.new.from_xml(xml.root)
73
+ r2.should be_valid
74
+ r2.attributes.should == @recipe.attributes
75
+ r2.each_beerxml_relationship do |rel|
76
+ r2.send(rel.name).map(&:attributes).should == @recipe.send(rel.name).map(&:attributes)
77
+ end
78
+ end
79
+
80
+ it "should calculate basic stats for the extract version" do
81
+ @extract = Recipe.new(:name => 'Centennial Blonde',
82
+ :type => 'Extract',
83
+ :brewer => 'BierMuncher',
84
+ :batch_size => U(5.5, 'gallons'),
85
+ :boil_size => U(6.75, 'gallons'),
86
+ :boil_time => U(60, 'minutes'),
87
+ :efficiency => 70.0)
88
+ @extract.hops.concat @recipe.hops
89
+ @extract.yeasts.concat @recipe.yeasts
90
+ @extract.fermentables << Fermentable.new(:name => 'Extra Light DME',
91
+ :type => 'Dry Extract',
92
+ :amount => U(5, 'lb'),
93
+ :ppg => 43,
94
+ :color => 3)
95
+ @extract.fermentables << Fermentable.new(:name => 'Cara-Pils',
96
+ :type => 'Grain',
97
+ :amount => U(1, 'lb'),
98
+ :ppg => 33,
99
+ :color => 2)
100
+ @extract.should be_valid
101
+ @extract.ibus.round.should == 20
102
+ @extract.calculate_og.should == 1.043
103
+ @extract.calculate_fg.should == 1.011
104
+ @extract.color == 3.2
105
+ end
106
+ end
@@ -1,6 +1,6 @@
1
1
  require "#{File.dirname(__FILE__)}/spec_helper"
2
2
 
3
- describe "beerxml.com examples" do
3
+ describe "parsing" do
4
4
  it "should parse the first recipe and its hops" do
5
5
  recipe = Beerxml::Recipe.new.from_xml(read_xml("recipes").root.children[1])
6
6
 
@@ -101,6 +101,26 @@ Examples: Anchor Steam, Old Peculiar, },
101
101
  :hsi => 35.0,
102
102
  })
103
103
  end
104
+ it "should parse yeasts" do
105
+ check_parse(Beerxml::Yeast, "yeast", 3, {
106
+ :name => 'European Ale',
107
+ :type => 'Ale',
108
+ :form => 'Liquid',
109
+ :amount => 0.035,
110
+ :amount_is_weight => false,
111
+ :laboratory => 'White Labs',
112
+ :product_id => 'WLP011',
113
+ :min_temperature => 18.3,
114
+ :max_temperature => 21.1,
115
+ :flocculation => 'Medium',
116
+ :attenuation => 67.5,
117
+ :notes => "Malty, Northern European ale yeast. Low ester production, low sulfer, gives a clean profile. Low attenuation contributes to malty taste.",
118
+ :best_for => "Alt, Kolsch, malty English Ales, Fruit beers",
119
+ :max_reuse => 5,
120
+ :times_cultured => 0,
121
+ :add_to_secondary => false,
122
+ })
123
+ end
104
124
  it "should parse recipes" do
105
125
  recipe = check_parse(Beerxml::Recipe, "recipes", 3, {
106
126
  :name => 'Dry Stout',
@@ -4,7 +4,7 @@ describe Beerxml::Recipe do
4
4
  it "should calculate IBUs using the tinseth method" do
5
5
  recipe = Beerxml::Recipe.new.from_xml(read_xml("recipes").root.children[1])
6
6
  recipe.should be_valid
7
- recipe.ibus.round.should == 32
7
+ recipe.ibus.round.should == 31
8
8
  end
9
9
 
10
10
  it "should calculate the OG for an all grain batch" do
@@ -12,4 +12,10 @@ describe Beerxml::Recipe do
12
12
  recipe.should be_valid
13
13
  recipe.calculate_og.should == 1.056
14
14
  end
15
+
16
+ it "should calculate the FG for an all grain batch" do
17
+ recipe = Beerxml::Recipe.new.from_xml(read_xml("recipes").root.children[1])
18
+ recipe.should be_valid
19
+ recipe.calculate_fg.should == 1.016
20
+ end
15
21
  end
@@ -2,6 +2,8 @@ require 'beerxml'
2
2
 
3
3
  require 'rspec'
4
4
 
5
+ Beerxml::Unit.apply_to_numeric!
6
+
5
7
  RSpec.configure do |c|
6
8
  def filename(example)
7
9
  "examples/beerxml.com/#{example}.xml"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beerxml
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 3
8
9
  - 0
9
- - 2
10
- version: 0.0.2
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brian Palmer
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-07 00:00:00 -07:00
18
+ date: 2011-03-08 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -63,25 +63,10 @@ dependencies:
63
63
  version: "1.0"
64
64
  type: :runtime
65
65
  version_requirements: *id003
66
- - !ruby/object:Gem::Dependency
67
- name: dm-types
68
- prerelease: false
69
- requirement: &id004 !ruby/object:Gem::Requirement
70
- none: false
71
- requirements:
72
- - - ~>
73
- - !ruby/object:Gem::Version
74
- hash: 15
75
- segments:
76
- - 1
77
- - 0
78
- version: "1.0"
79
- type: :runtime
80
- version_requirements: *id004
81
66
  - !ruby/object:Gem::Dependency
82
67
  name: rspec
83
68
  prerelease: false
84
- requirement: &id005 !ruby/object:Gem::Requirement
69
+ requirement: &id004 !ruby/object:Gem::Requirement
85
70
  none: false
86
71
  requirements:
87
72
  - - ~>
@@ -92,11 +77,11 @@ dependencies:
92
77
  - 5
93
78
  version: "2.5"
94
79
  type: :development
95
- version_requirements: *id005
80
+ version_requirements: *id004
96
81
  - !ruby/object:Gem::Dependency
97
82
  name: autotest-standalone
98
83
  prerelease: false
99
- requirement: &id006 !ruby/object:Gem::Requirement
84
+ requirement: &id005 !ruby/object:Gem::Requirement
100
85
  none: false
101
86
  requirements:
102
87
  - - ">="
@@ -106,11 +91,11 @@ dependencies:
106
91
  - 0
107
92
  version: "0"
108
93
  type: :development
109
- version_requirements: *id006
94
+ version_requirements: *id005
110
95
  - !ruby/object:Gem::Dependency
111
96
  name: autotest-growl
112
97
  prerelease: false
113
- requirement: &id007 !ruby/object:Gem::Requirement
98
+ requirement: &id006 !ruby/object:Gem::Requirement
114
99
  none: false
115
100
  requirements:
116
101
  - - ">="
@@ -120,11 +105,11 @@ dependencies:
120
105
  - 0
121
106
  version: "0"
122
107
  type: :development
123
- version_requirements: *id007
108
+ version_requirements: *id006
124
109
  - !ruby/object:Gem::Dependency
125
110
  name: rcov
126
111
  prerelease: false
127
- requirement: &id008 !ruby/object:Gem::Requirement
112
+ requirement: &id007 !ruby/object:Gem::Requirement
128
113
  none: false
129
114
  requirements:
130
115
  - - ">="
@@ -134,11 +119,11 @@ dependencies:
134
119
  - 0
135
120
  version: "0"
136
121
  type: :development
137
- version_requirements: *id008
122
+ version_requirements: *id007
138
123
  - !ruby/object:Gem::Dependency
139
124
  name: yard
140
125
  prerelease: false
141
- requirement: &id009 !ruby/object:Gem::Requirement
126
+ requirement: &id008 !ruby/object:Gem::Requirement
142
127
  none: false
143
128
  requirements:
144
129
  - - ">="
@@ -148,7 +133,7 @@ dependencies:
148
133
  - 0
149
134
  version: "0"
150
135
  type: :development
151
- version_requirements: *id009
136
+ version_requirements: *id008
152
137
  description: |-
153
138
  Library for parsing and generating beerxml (http://www.beerxml.com/).
154
139
  More than that, this library also contains various methods for doing calculations on beer ingredients and recipes, among other helpers.
@@ -185,6 +170,9 @@ files:
185
170
  - lib/beerxml/recipe.rb
186
171
  - lib/beerxml/unit.rb
187
172
  - lib/beerxml/version.rb
173
+ - lib/beerxml/yeast.rb
174
+ - spec/dumping_spec.rb
175
+ - spec/full_stack_spec.rb
188
176
  - spec/hops_spec.rb
189
177
  - spec/parsing_spec.rb
190
178
  - spec/recipes_spec.rb
@@ -225,6 +213,8 @@ signing_key:
225
213
  specification_version: 3
226
214
  summary: Library for parsing and generating beerxml (http://www.beerxml.com/)
227
215
  test_files:
216
+ - spec/dumping_spec.rb
217
+ - spec/full_stack_spec.rb
228
218
  - spec/hops_spec.rb
229
219
  - spec/parsing_spec.rb
230
220
  - spec/recipes_spec.rb