brewscribe 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in brewscribe.gemspec
4
+ gemspec
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+ guard :rspec do
4
+ watch(%r{^spec/.+_spec\.rb$})
5
+ watch(%r{^lib/brewscribe/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
6
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec/" }
7
+ watch('spec/spec_helper.rb') { "spec/" }
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Andrew Nordman
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # Brewscribe
2
+
3
+ Brewscribe is a Beersmith2 (.bsmx) file parser.
4
+
5
+ ## Installation
6
+
7
+ `gem install brewscribe` or `gem brewscribe` in your Gemfile.
8
+
9
+ ## Usage
10
+
11
+ To start, you can import your .bsmx file with `Brewscribe.import(file)` where file
12
+ is any object that responds to `#read`. This will return an array of Brewscribe::Recipe
13
+ objects. You now have a parsed version of the recipe files.
14
+
15
+ By default, Brewscribe will set a text property for each attribute of the recipe, and
16
+ if it has a parser object it will attempt to further parse the data.
17
+
18
+ An example of this is found in `Brewscribe::IngredientList`:
19
+
20
+ ```
21
+ recipe = Brewscribe.import File.read './spec/support/recipe.bsmx'
22
+ recipe.ingredients.class # => Brewscribe::IngredientList
23
+ recipe.ingredients.grains.class # => Array
24
+ recipe.ingredients.grains.first.class # => Brewscribe::Grain
25
+ recipe.ingredients.grains.first.name # => "Pale Malt (2 Row) US"
26
+ ```
27
+
28
+ ## Contributing
29
+
30
+ I <3 Contributions.
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
37
+
38
+ ## Author
39
+
40
+ Created by [Andrew Nordman](https://github.com/cadwallion/).
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = 'spec/**/*_spec.rb'
7
+ spec.rspec_opts = ['--backtrace']
8
+ # spec.ruby_opts = ['-w']
9
+ end
10
+
11
+ task :default => :spec
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/brewscribe/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Andrew Nordman"]
6
+ gem.email = ["cadwallion@gmail.com"]
7
+ gem.summary = %q{A Beersmith (.bsmx) file parser}
8
+ gem.homepage = ""
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "brewscribe"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Brewscribe::VERSION
16
+
17
+ gem.add_development_dependency 'rspec'
18
+ gem.add_development_dependency 'guard'
19
+ gem.add_development_dependency 'guard-rspec'
20
+ gem.add_dependency 'nokogiri'
21
+ end
@@ -0,0 +1,11 @@
1
+ require "brewscribe/version"
2
+ require 'brewscribe/recipe'
3
+ require 'brewscribe/ingredient_list'
4
+ require 'brewscribe/conversion'
5
+
6
+ module Brewscribe
7
+ def self.import file
8
+ data = file.read
9
+ Recipe.new(data)
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Brewscribe
2
+ module Conversion
3
+ BOOLEAN_CONV = ->(k) { k == '1' }
4
+ FLOAT_CONV = ->(k) { k.to_f }
5
+ PERCENT_CONV = ->(k) { k.to_f * 0.001 }
6
+ INT_CONV = ->(k) { k.to_i }
7
+ DATE_CONV = ->(k) { Date.parse k }
8
+
9
+
10
+ def data_to_properties data
11
+ data.each_key do |key|
12
+ if self.class.const_get(:KEY_CONVERSION).has_key? key
13
+ value = self.class.const_get(:KEY_CONVERSION)[key].call(data[key])
14
+ else
15
+ value = data[key]
16
+ end
17
+
18
+ self.instance_variable_set "@#{key}".to_sym, value
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ require 'brewscribe/conversion'
2
+
3
+ module Brewscribe
4
+ class Grain
5
+ attr_reader :name, :origin, :amount, :color, :yield, :percent, :price,
6
+ :recommend_mash, :in_recipe, :type, :add_after_boil, :notes, :boil_time,
7
+ :max_in_batch, :ibu_gal_per_lb, :protein, :diastatic_power, :late_extract,
8
+ :convert_grain, :moisture, :coarse_fine_diff, :convert_grain, :supplier
9
+
10
+ include Brewscribe::Conversion
11
+
12
+ TYPES = ['Grain', 'Extract Sugar', 'Adjunct', 'Dry Extract']
13
+
14
+ KEY_CONVERSION = {
15
+ amount: FLOAT_CONV,
16
+ color: FLOAT_CONV,
17
+ yield: PERCENT_CONV,
18
+ price: FLOAT_CONV,
19
+ boil_time: ->(k) { k.to_i },
20
+ percent: PERCENT_CONV,
21
+ max_in_batch: PERCENT_CONV,
22
+ add_after_boil: BOOLEAN_CONV,
23
+ recommend_mash: BOOLEAN_CONV,
24
+ in_recipe: BOOLEAN_CONV,
25
+ type: ->(k) { TYPES[k.to_i] },
26
+ ibu_gal_per_lb: FLOAT_CONV,
27
+ protein: PERCENT_CONV,
28
+ diastatic_power: PERCENT_CONV,
29
+ late_extract: FLOAT_CONV,
30
+ moisture: PERCENT_CONV,
31
+ coarse_fine_diff: PERCENT_CONV
32
+ }
33
+
34
+ def initialize grain_data
35
+ @original_data = grain_data
36
+
37
+ data_to_properties grain_data
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module Brewscribe
2
+ class Hops
3
+ attr_reader :name, :origin, :alpha, :beta, :notes, :boil_time, :percent,
4
+ :amount, :hsi, :dry_hop_time, :ibu_contrib, :use, :in_recipe, :price,
5
+ :type, :form
6
+
7
+ include Brewscribe::Conversion
8
+
9
+ KEY_CONVERSION = {
10
+ alpha: FLOAT_CONV,
11
+ beta: FLOAT_CONV,
12
+ boil_time: ->(k) { k.to_i },
13
+ percent: PERCENT_CONV,
14
+ amount: FLOAT_CONV,
15
+ hsi: FLOAT_CONV,
16
+ dry_hop_time: ->(k) { k.to_i },
17
+ ibu_contrib: PERCENT_CONV,
18
+ use: ->(k) { USES[k.to_i] },
19
+ type: ->(k) { TYPES[k.to_i] },
20
+ form: ->(k) { FORMS[k.to_i] },
21
+ in_recipe: BOOLEAN_CONV,
22
+ price: FLOAT_CONV
23
+ }
24
+
25
+ TYPES = ['Bittering', 'Aroma', 'Both']
26
+ FORMS = ['Pellet', 'Plug', 'Leaf']
27
+ USES = ['Boil', 'Dry Hop', 'Mash', 'First Wort', 'Aroma']
28
+
29
+ def initialize data
30
+ @original_data = data
31
+ data_to_properties data
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,58 @@
1
+ require 'brewscribe/grain'
2
+ require 'brewscribe/hops'
3
+ require 'brewscribe/yeast'
4
+
5
+ module Brewscribe
6
+ class IngredientList
7
+ attr_reader :grains, :hops, :yeasts
8
+ def self.from_data data
9
+ list = new
10
+
11
+ case data[:grain]
12
+ when Array
13
+ data[:grain].each do |grain|
14
+ list.add_grain grain
15
+ end
16
+ when Hash
17
+ list.add_graind data[:grain]
18
+ end
19
+
20
+ case data[:hops]
21
+ when Array
22
+ data[:hops].each do |hops|
23
+ list.add_hops hops
24
+ end
25
+ when Hash
26
+ list.add_hops data[:hops]
27
+ end
28
+
29
+ case data[:yeast]
30
+ when Array
31
+ data[:yeast].each do |yeast|
32
+ list.add_yeast yeast
33
+ end
34
+ when Hash
35
+ list.add_yeast data[:yeast]
36
+ end
37
+ list
38
+ end
39
+
40
+ def initialize
41
+ @grains = []
42
+ @hops = []
43
+ @yeasts = []
44
+ end
45
+
46
+ def add_grain grain_data
47
+ @grains << Grain.new(grain_data)
48
+ end
49
+
50
+ def add_hops hop_data
51
+ @hops << Hops.new(hop_data)
52
+ end
53
+
54
+ def add_yeast yeast_data
55
+ @yeasts << Yeast.new(yeast_data)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,76 @@
1
+ require 'nokogiri'
2
+
3
+ module Brewscribe
4
+ class Recipe
5
+ attr_reader :raw_data, :hash
6
+
7
+ def initialize raw_data
8
+ @raw_data = raw_data
9
+
10
+ parse_raw_data
11
+ end
12
+
13
+ def parse_raw_data
14
+ @xml = Nokogiri::XML(@raw_data).xpath('/Selections/Data/Recipe')
15
+ @hash = xml_node_to_hash(@xml.first)
16
+
17
+ create_recipe_accessors
18
+ parse_ingredients
19
+ end
20
+
21
+ def create_recipe_accessors
22
+ @hash.keys.each do |key|
23
+ self.class.module_eval do
24
+ attr_accessor key
25
+ end
26
+
27
+ self.send "#{key}=", @hash[key]
28
+ end
29
+ end
30
+
31
+ def clean_key key
32
+ extracted = key.to_s.match(/(F_(\w{1,2}_)?)?(_MOD_|.+)/)[3]
33
+ if extracted == '_MOD_'
34
+ return 'last_modified'
35
+ else
36
+ extracted.downcase
37
+ end
38
+ end
39
+
40
+ def parse_ingredients
41
+ self.ingredients = IngredientList.from_data(self.ingredients[:data])
42
+ end
43
+
44
+ def xml_node_to_hash node
45
+ if node.element?
46
+ if node.children.size > 0
47
+ result_hash = {}
48
+
49
+ node.children.each do |child|
50
+ result = xml_node_to_hash child
51
+ property = clean_key child.name
52
+ key = property.to_sym
53
+
54
+ if child.name == 'text'
55
+ return result if !child.next && !child.previous
56
+ elsif result_hash[key]
57
+ if result_hash[key].is_a? Array
58
+ result_hash[key] << result
59
+ else
60
+ result_hash[key] = [result_hash[key]] << result
61
+ end
62
+ else
63
+ result_hash[key] = result
64
+ end
65
+ end
66
+
67
+ return result_hash
68
+ else
69
+ return nil
70
+ end
71
+ else
72
+ return node.content.to_s
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module Brewscribe
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,44 @@
1
+ module Brewscribe
2
+ class Yeast
3
+ attr_reader :name, :lab, :product_id, :flocculation, :type, :form,
4
+ :starter_size, :amount, :price, :in_recipe, :brew_date, :pkg_date,
5
+ :cells, :min_attenuation, :max_attenuation, :min_temp, :max_temp,
6
+ :use_starter, :add_to_secondary, :times_cultured, :max_reuse,
7
+ :culture_date, :best_for, :notes, :last_modified
8
+
9
+ include Brewscribe::Conversion
10
+
11
+ FLOCCULATION_TYPES = ['Low', 'Medium', 'High', 'Very High']
12
+ TYPES = ['Ale', 'Lager', 'Wine', 'Champagne', 'Wheat']
13
+ FORMS = ['Liquid', 'Dry', 'Slant', 'Culture']
14
+ KEY_CONVERSION = {
15
+ product_id: INT_CONV,
16
+ flocculation: ->(k) { FLOCCULATION_TYPES[k.to_i] },
17
+ type: ->(k) { TYPES[k.to_i] },
18
+ last_modified: DATE_CONV,
19
+ form: ->(k) { FORMS[k.to_i] },
20
+ starter_size: FLOAT_CONV,
21
+ amount: FLOAT_CONV,
22
+ price: FLOAT_CONV,
23
+ in_recipe: BOOLEAN_CONV,
24
+ brew_date: DATE_CONV,
25
+ pkg_date: DATE_CONV,
26
+ cells: FLOAT_CONV,
27
+ min_attenuation: PERCENT_CONV,
28
+ max_attenuation: PERCENT_CONV,
29
+ min_temp: FLOAT_CONV,
30
+ max_temp: FLOAT_CONV,
31
+ use_starter: BOOLEAN_CONV,
32
+ add_to_secondary: BOOLEAN_CONV,
33
+ times_cultured: INT_CONV,
34
+ max_reuse: INT_CONV,
35
+ culture_date: DATE_CONV
36
+ }
37
+
38
+ def initialize data
39
+ @original_data = data
40
+
41
+ data_to_properties data
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brewscribe do
4
+ let(:recipe_file) { File.open(File.dirname(__FILE__) + '/support/recipe.bsmx', 'r') }
5
+
6
+ describe '#import' do
7
+ before do
8
+ Brewscribe::Recipe.any_instance.stub(:parse_raw_data)
9
+ end
10
+
11
+ it 'should call #read on the passed IO object' do
12
+ file = double()
13
+ file.should_receive(:read)
14
+ Brewscribe.import(file)
15
+ end
16
+
17
+ it 'should return a Recipe object' do
18
+ file = double()
19
+ file.stub(:read)
20
+ recipe = Brewscribe.import(file)
21
+ recipe.should be_a(Brewscribe::Recipe)
22
+ end
23
+ end
24
+ end