ingreedy 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.rubocop.yml +601 -0
- data/.rvmrc +1 -0
- data/.travis.yml +14 -0
- data/Gemfile +3 -0
- data/README.md +46 -0
- data/Rakefile +5 -0
- data/ingreedy.gemspec +26 -0
- data/lib/ingreedy.rb +17 -13
- data/lib/ingreedy/amount_parser.rb +17 -18
- data/lib/ingreedy/case_insensitive_parser.rb +1 -3
- data/lib/ingreedy/dictionaries/en.yml +137 -0
- data/lib/ingreedy/dictionary.rb +19 -19
- data/lib/ingreedy/dictionary_collection.rb +5 -3
- data/lib/ingreedy/ingreedy_parser.rb +34 -116
- data/lib/ingreedy/rationalizer.rb +12 -10
- data/lib/ingreedy/root_parser.rb +118 -0
- data/lib/ingreedy/unit_variation_mapper.rb +12 -9
- data/lib/ingreedy/version.rb +1 -2
- data/spec/ingreedy/amount_parser_spec.rb +99 -0
- data/spec/ingreedy/rationalizer_spec.rb +56 -0
- data/spec/ingreedy/unit_variation_mapper_spec.rb +13 -0
- data/spec/ingreedy_spec.rb +378 -0
- data/spec/spec_helper.rb +34 -0
- metadata +23 -26
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create 1.9.3@ingreedy
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Usage
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
result = Ingreedy.parse('1 lb. potatoes')
|
5
|
+
print result.amount
|
6
|
+
#=> 1.0
|
7
|
+
print result.unit
|
8
|
+
#=> :pound
|
9
|
+
print result.ingredient
|
10
|
+
#=> "potatoes"
|
11
|
+
```
|
12
|
+
|
13
|
+
### I18n and custom dictionaries
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
Ingreedy.dictionaries[:fr] = {
|
17
|
+
units: { dash: ['pincée'] },
|
18
|
+
numbers: { 'une' => 1 },
|
19
|
+
prepositions: ['de']
|
20
|
+
}
|
21
|
+
|
22
|
+
Ingreedy.locale = :fr # Also automatically follows I18n.locale if available
|
23
|
+
|
24
|
+
result = Ingreedy.parse('une pincée de sucre')
|
25
|
+
print result.amount
|
26
|
+
#=> 1.0
|
27
|
+
print result.unit
|
28
|
+
#=> :dash
|
29
|
+
print result.ingredient
|
30
|
+
#=> "sucre"
|
31
|
+
```
|
32
|
+
|
33
|
+
[Live demo](http://hangryingreedytest.herokuapp.com/)
|
34
|
+
|
35
|
+
# Pieces of Flair
|
36
|
+
- [![Gem Version](https://badge.fury.io/rb/ingreedy.svg)](http://badge.fury.io/rb/ingreedy)
|
37
|
+
- [![Build Status](https://secure.travis-ci.org/iancanderson/ingreedy.svg?branch=master)](http://travis-ci.org/iancanderson/ingreedy)
|
38
|
+
- [![Code Climate](https://codeclimate.com/github/iancanderson/ingreedy.svg)](https://codeclimate.com/github/iancanderson/ingreedy)
|
39
|
+
- [![Coverage Status](https://coveralls.io/repos/iancanderson/ingreedy/badge.svg)](https://coveralls.io/r/iancanderson/ingreedy)
|
40
|
+
|
41
|
+
# Development
|
42
|
+
|
43
|
+
Run the tests:
|
44
|
+
```
|
45
|
+
rspec spec
|
46
|
+
```
|
data/Rakefile
ADDED
data/ingreedy.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path("../lib/ingreedy/version", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "ingreedy"
|
5
|
+
s.version = Ingreedy::VERSION
|
6
|
+
s.licenses = ["MIT"]
|
7
|
+
s.authors = ["Ian C. Anderson"]
|
8
|
+
s.email = ["ian@iancanderson.com"]
|
9
|
+
|
10
|
+
s.summary = "Recipe parser"
|
11
|
+
s.description = <<-MSG
|
12
|
+
Natural language recipe ingredient parser that supports numeric amount,
|
13
|
+
units, and ingredient
|
14
|
+
MSG
|
15
|
+
s.homepage = "http://github.com/iancanderson/ingreedy"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.require_path = "lib"
|
19
|
+
|
20
|
+
s.add_dependency "parslet", "~> 1.7.0", ">= 1.7.0"
|
21
|
+
|
22
|
+
s.add_development_dependency "rake", "~> 0.9", ">= 0.9"
|
23
|
+
s.add_development_dependency "rspec", "~> 3.3.0", ">= 3.3.0"
|
24
|
+
s.add_development_dependency "coveralls", "~> 0.7.0", ">= 0.7.0"
|
25
|
+
s.add_development_dependency "pry"
|
26
|
+
end
|
data/lib/ingreedy.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
-
path = File.expand_path(File.join(File.dirname(__FILE__),
|
1
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), "ingreedy"))
|
2
2
|
|
3
|
-
require File.join(path,
|
4
|
-
require File.join(path,
|
5
|
-
require File.join(path,
|
3
|
+
require File.join(path, "case_insensitive_parser")
|
4
|
+
require File.join(path, "ingreedy_parser")
|
5
|
+
require File.join(path, "dictionary_collection")
|
6
6
|
|
7
7
|
module Ingreedy
|
8
|
-
|
9
|
-
|
8
|
+
def self.locale
|
9
|
+
@locale ||= nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.locale=(locale)
|
13
|
+
@locale = locale
|
14
|
+
end
|
10
15
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
def self.parse(query)
|
17
|
+
parser = Parser.new(query)
|
18
|
+
parser.parse
|
19
|
+
end
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
21
|
+
def self.dictionaries
|
22
|
+
@dictionaries ||= DictionaryCollection.new
|
19
23
|
end
|
20
24
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require "parslet"
|
2
2
|
|
3
3
|
module Ingreedy
|
4
|
-
|
5
4
|
class AmountParser < Parslet::Parser
|
6
5
|
include CaseInsensitiveParser
|
7
6
|
|
@@ -10,16 +9,16 @@ module Ingreedy
|
|
10
9
|
end
|
11
10
|
|
12
11
|
rule(:integer) do
|
13
|
-
match(
|
12
|
+
match("[0-9]").repeat(1)
|
14
13
|
end
|
15
14
|
|
16
15
|
rule(:float) do
|
17
16
|
integer.maybe >>
|
18
|
-
|
17
|
+
float_delimiter >> integer
|
19
18
|
end
|
20
19
|
|
21
20
|
rule(:float_delimiter) do
|
22
|
-
str(
|
21
|
+
str(",") | str(".")
|
23
22
|
end
|
24
23
|
|
25
24
|
rule(:fraction) do
|
@@ -28,16 +27,16 @@ module Ingreedy
|
|
28
27
|
|
29
28
|
rule(:compound_simple_fraction) do
|
30
29
|
(integer.as(:integer_amount) >> whitespace).maybe >>
|
31
|
-
|
30
|
+
simple_fraction.as(:fraction_amount)
|
32
31
|
end
|
33
32
|
|
34
33
|
rule(:simple_fraction) do
|
35
|
-
integer >> match(
|
34
|
+
integer >> match("/") >> integer
|
36
35
|
end
|
37
36
|
|
38
37
|
rule(:compound_vulgar_fraction) do
|
39
38
|
(integer.as(:integer_amount) >> whitespace.maybe).maybe >>
|
40
|
-
|
39
|
+
vulgar_fraction.as(:fraction_amount)
|
41
40
|
end
|
42
41
|
|
43
42
|
rule(:vulgar_fraction) do
|
@@ -49,26 +48,26 @@ module Ingreedy
|
|
49
48
|
end
|
50
49
|
|
51
50
|
rule(:amount_unit_separator) do
|
52
|
-
whitespace | str(
|
51
|
+
whitespace | str("-")
|
53
52
|
end
|
54
53
|
|
55
54
|
rule(:amount) do
|
56
55
|
fraction |
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
float.as(:float_amount) |
|
57
|
+
integer.as(:integer_amount) |
|
58
|
+
word_digit.as(:word_integer_amount) >> amount_unit_separator
|
60
59
|
end
|
61
60
|
|
62
61
|
root(:amount)
|
63
62
|
|
64
63
|
private
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
def word_digits
|
66
|
+
Ingreedy.dictionaries.current.numbers.keys
|
67
|
+
end
|
69
68
|
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
def vulgar_fractions
|
70
|
+
Ingreedy.dictionaries.current.vulgar_fractions.keys
|
71
|
+
end
|
73
72
|
end
|
74
73
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
module Ingreedy
|
2
2
|
module CaseInsensitiveParser
|
3
|
-
|
4
3
|
def stri(str)
|
5
4
|
key_chars = str.split(//)
|
6
5
|
key_chars.
|
7
|
-
|
6
|
+
map! { |char| match["#{char.upcase}#{char.downcase}"] }.
|
8
7
|
reduce(:>>)
|
9
8
|
end
|
10
|
-
|
11
9
|
end
|
12
10
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
:units:
|
2
|
+
:cup:
|
3
|
+
- "cups"
|
4
|
+
- "cup"
|
5
|
+
- "c."
|
6
|
+
- "c"
|
7
|
+
:fluid_ounce:
|
8
|
+
- "fl. oz."
|
9
|
+
- "fl oz"
|
10
|
+
- "fluid ounce"
|
11
|
+
- "fluid ounces"
|
12
|
+
:gallon:
|
13
|
+
- "gal"
|
14
|
+
- "gal."
|
15
|
+
- "gallon"
|
16
|
+
- "gallons"
|
17
|
+
:ounce:
|
18
|
+
- "oz"
|
19
|
+
- "oz."
|
20
|
+
- "ounce"
|
21
|
+
- "ounces"
|
22
|
+
:pint:
|
23
|
+
- "pt"
|
24
|
+
- "pt."
|
25
|
+
- "pint"
|
26
|
+
- "pints"
|
27
|
+
:pound:
|
28
|
+
- "lb"
|
29
|
+
- "lb."
|
30
|
+
- "pound"
|
31
|
+
- "pounds"
|
32
|
+
:quart:
|
33
|
+
- "qt"
|
34
|
+
- "qt."
|
35
|
+
- "qts"
|
36
|
+
- "qts."
|
37
|
+
- "quart"
|
38
|
+
- "quarts"
|
39
|
+
:tablespoon:
|
40
|
+
- "tbsp."
|
41
|
+
- "tbsp"
|
42
|
+
- "T"
|
43
|
+
- "T."
|
44
|
+
- "tablespoon"
|
45
|
+
- "tablespoons"
|
46
|
+
- "tbs."
|
47
|
+
- "tbs"
|
48
|
+
:teaspoon:
|
49
|
+
- "tsp."
|
50
|
+
- "tsp"
|
51
|
+
- "t"
|
52
|
+
- "t."
|
53
|
+
- "teaspoon"
|
54
|
+
- "teaspoons"
|
55
|
+
:gram:
|
56
|
+
- "g"
|
57
|
+
- "g."
|
58
|
+
- "gr"
|
59
|
+
- "gr."
|
60
|
+
- "gram"
|
61
|
+
- "grams"
|
62
|
+
:kilogram:
|
63
|
+
- "kg"
|
64
|
+
- "kg."
|
65
|
+
- "kilogram"
|
66
|
+
- "kilograms"
|
67
|
+
:liter:
|
68
|
+
- "l"
|
69
|
+
- "l."
|
70
|
+
- "liter"
|
71
|
+
- "liters"
|
72
|
+
:milligram:
|
73
|
+
- "mg"
|
74
|
+
- "mg."
|
75
|
+
- "milligram"
|
76
|
+
- "milligrams"
|
77
|
+
:milliliter:
|
78
|
+
- "ml"
|
79
|
+
- "ml."
|
80
|
+
- "milliliter"
|
81
|
+
- "milliliters"
|
82
|
+
:pinch:
|
83
|
+
- "pinch"
|
84
|
+
- "pinches"
|
85
|
+
:dash:
|
86
|
+
- "dash"
|
87
|
+
- "dashes"
|
88
|
+
:touch:
|
89
|
+
- "touch"
|
90
|
+
- "touches"
|
91
|
+
:handful:
|
92
|
+
- "handful"
|
93
|
+
- "handfuls"
|
94
|
+
:stick:
|
95
|
+
- "stick"
|
96
|
+
- "sticks"
|
97
|
+
:clove:
|
98
|
+
- "cloves"
|
99
|
+
- "clove"
|
100
|
+
:can:
|
101
|
+
- "cans"
|
102
|
+
- "can"
|
103
|
+
:to_taste:
|
104
|
+
- "to taste"
|
105
|
+
:numbers:
|
106
|
+
a: 1
|
107
|
+
an: 1
|
108
|
+
zero: 0
|
109
|
+
one: 1
|
110
|
+
two: 2
|
111
|
+
three: 3
|
112
|
+
four: 4
|
113
|
+
five: 5
|
114
|
+
six: 6
|
115
|
+
seven: 7
|
116
|
+
eight: 8
|
117
|
+
nine: 9
|
118
|
+
ten: 10
|
119
|
+
eleven: 11
|
120
|
+
twelve: 12
|
121
|
+
thirteen: 13
|
122
|
+
fourteen: 14
|
123
|
+
fifteen: 15
|
124
|
+
sixteen: 16
|
125
|
+
seventeen: 17
|
126
|
+
eighteen: 18
|
127
|
+
nineteen: 19
|
128
|
+
twenty: 20
|
129
|
+
thirty: 30
|
130
|
+
forty: 40
|
131
|
+
fifty: 50
|
132
|
+
sixty: 60
|
133
|
+
seventy: 70
|
134
|
+
eighty: 80
|
135
|
+
ninety: 9
|
136
|
+
:prepositions:
|
137
|
+
- "of"
|
data/lib/ingreedy/dictionary.rb
CHANGED
@@ -3,7 +3,7 @@ module Ingreedy
|
|
3
3
|
attr_reader :units, :numbers, :prepositions
|
4
4
|
|
5
5
|
def initialize(entries = {})
|
6
|
-
@units = entries[:units] || raise(
|
6
|
+
@units = entries[:units] || raise("No units found in dictionary")
|
7
7
|
@numbers = entries[:numbers] || {}
|
8
8
|
@prepositions = entries[:prepositions] || []
|
9
9
|
end
|
@@ -11,24 +11,24 @@ module Ingreedy
|
|
11
11
|
# https://en.wikipedia.org/wiki/Number_Forms
|
12
12
|
def vulgar_fractions
|
13
13
|
{
|
14
|
-
"\u00BC" =>
|
15
|
-
"\u00BD" =>
|
16
|
-
"\u00BE" =>
|
17
|
-
"\u2150" =>
|
18
|
-
"\u2151" =>
|
19
|
-
"\u2152" =>
|
20
|
-
"\u2153" =>
|
21
|
-
"\u2154" =>
|
22
|
-
"\u2155" =>
|
23
|
-
"\u2156" =>
|
24
|
-
"\u2157" =>
|
25
|
-
"\u2158" =>
|
26
|
-
"\u2159" =>
|
27
|
-
"\u215A" =>
|
28
|
-
"\u215B" =>
|
29
|
-
"\u215C" =>
|
30
|
-
"\u215D" =>
|
31
|
-
"\u215E" =>
|
14
|
+
"\u00BC" => "1/4",
|
15
|
+
"\u00BD" => "1/2",
|
16
|
+
"\u00BE" => "3/4",
|
17
|
+
"\u2150" => "1/7",
|
18
|
+
"\u2151" => "1/9",
|
19
|
+
"\u2152" => "1/10",
|
20
|
+
"\u2153" => "1/3",
|
21
|
+
"\u2154" => "2/3",
|
22
|
+
"\u2155" => "1/5",
|
23
|
+
"\u2156" => "2/5",
|
24
|
+
"\u2157" => "3/5",
|
25
|
+
"\u2158" => "4/5",
|
26
|
+
"\u2159" => "1/6",
|
27
|
+
"\u215A" => "5/6",
|
28
|
+
"\u215B" => "1/8",
|
29
|
+
"\u215C" => "3/8",
|
30
|
+
"\u215D" => "5/8",
|
31
|
+
"\u215E" => "7/8",
|
32
32
|
}
|
33
33
|
end
|
34
34
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require_relative
|
1
|
+
require "yaml"
|
2
|
+
require_relative "dictionary"
|
3
3
|
|
4
4
|
module Ingreedy
|
5
5
|
class DictionaryCollection
|
@@ -26,7 +26,9 @@ module Ingreedy
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def load_yaml(locale)
|
29
|
-
path = File.expand_path(
|
29
|
+
path = File.expand_path(
|
30
|
+
File.join(File.dirname(__FILE__), "dictionaries", "#{locale}.yml"),
|
31
|
+
)
|
30
32
|
YAML.load_file(path)
|
31
33
|
rescue Errno::ENOENT
|
32
34
|
raise "No dictionary found for :#{locale} locale"
|