menu_markup 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.gitignore +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +120 -0
- data/Guardfile +70 -0
- data/README.md +120 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/menu_markup.rb +15 -0
- data/lib/menu_markup.treetop +77 -0
- data/lib/menu_markup/parsed_price.rb +75 -0
- data/lib/menu_markup/version.rb +3 -0
- data/lib/menu_markup_parser.rb +132 -0
- data/menu_markup.gemspec +38 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4e0d49032e872c8972e2c9be2e47921983f1d972
|
4
|
+
data.tar.gz: 5f65d9129666338f23022691d4baa968ff5db83f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5f1b40046f99bb7a1c1e7b096eaddc5c9bf5f8a254ac11d4c942db101750f13e0cf5ae35fe7676b1739c4544d26f4f0326c235d70d722ec3539568ffd5404a63
|
7
|
+
data.tar.gz: 7903175def35662261833299bb195cc88bacb4d699426145b2138d8d61a3d7f95525ded5c8694be1f311a05f715f39ccf8c2e5f36f54281dccd4f4515f551abd
|
data/.bundle/config
ADDED
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
menu_markup (0.1.0)
|
5
|
+
activesupport
|
6
|
+
treetop (~> 1.4.15)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.2.3)
|
12
|
+
i18n (~> 0.7)
|
13
|
+
json (~> 1.7, >= 1.7.7)
|
14
|
+
minitest (~> 5.1)
|
15
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
16
|
+
tzinfo (~> 1.1)
|
17
|
+
coderay (1.1.0)
|
18
|
+
coveralls (0.8.2)
|
19
|
+
json (~> 1.8)
|
20
|
+
rest-client (>= 1.6.8, < 2)
|
21
|
+
simplecov (~> 0.10.0)
|
22
|
+
term-ansicolor (~> 1.3)
|
23
|
+
thor (~> 0.19.1)
|
24
|
+
diff-lcs (1.2.5)
|
25
|
+
docile (1.1.5)
|
26
|
+
domain_name (0.5.24)
|
27
|
+
unf (>= 0.0.5, < 1.0.0)
|
28
|
+
ffi (1.9.10)
|
29
|
+
formatador (0.2.5)
|
30
|
+
guard (2.12.8)
|
31
|
+
formatador (>= 0.2.4)
|
32
|
+
listen (>= 2.7, <= 4.0)
|
33
|
+
lumberjack (~> 1.0)
|
34
|
+
nenv (~> 0.1)
|
35
|
+
notiffany (~> 0.0)
|
36
|
+
pry (>= 0.9.12)
|
37
|
+
shellany (~> 0.0)
|
38
|
+
thor (>= 0.18.1)
|
39
|
+
guard-compat (1.2.1)
|
40
|
+
guard-rspec (4.6.2)
|
41
|
+
guard (~> 2.1)
|
42
|
+
guard-compat (~> 1.1)
|
43
|
+
rspec (>= 2.99.0, < 4.0)
|
44
|
+
http-cookie (1.0.2)
|
45
|
+
domain_name (~> 0.5)
|
46
|
+
i18n (0.7.0)
|
47
|
+
json (1.8.3)
|
48
|
+
listen (3.0.2)
|
49
|
+
rb-fsevent (>= 0.9.3)
|
50
|
+
rb-inotify (>= 0.9)
|
51
|
+
lumberjack (1.0.9)
|
52
|
+
method_source (0.8.2)
|
53
|
+
mime-types (2.6.1)
|
54
|
+
minitest (5.7.0)
|
55
|
+
nenv (0.2.0)
|
56
|
+
netrc (0.10.3)
|
57
|
+
notiffany (0.0.6)
|
58
|
+
nenv (~> 0.1)
|
59
|
+
shellany (~> 0.0)
|
60
|
+
polyglot (0.3.5)
|
61
|
+
pry (0.10.1)
|
62
|
+
coderay (~> 1.1.0)
|
63
|
+
method_source (~> 0.8.1)
|
64
|
+
slop (~> 3.4)
|
65
|
+
rake (10.4.2)
|
66
|
+
rb-fsevent (0.9.5)
|
67
|
+
rb-inotify (0.9.5)
|
68
|
+
ffi (>= 0.5.0)
|
69
|
+
rest-client (1.8.0)
|
70
|
+
http-cookie (>= 1.0.2, < 2.0)
|
71
|
+
mime-types (>= 1.16, < 3.0)
|
72
|
+
netrc (~> 0.7)
|
73
|
+
rspec (3.3.0)
|
74
|
+
rspec-core (~> 3.3.0)
|
75
|
+
rspec-expectations (~> 3.3.0)
|
76
|
+
rspec-mocks (~> 3.3.0)
|
77
|
+
rspec-collection_matchers (1.1.2)
|
78
|
+
rspec-expectations (>= 2.99.0.beta1)
|
79
|
+
rspec-core (3.3.2)
|
80
|
+
rspec-support (~> 3.3.0)
|
81
|
+
rspec-expectations (3.3.1)
|
82
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
83
|
+
rspec-support (~> 3.3.0)
|
84
|
+
rspec-mocks (3.3.2)
|
85
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
86
|
+
rspec-support (~> 3.3.0)
|
87
|
+
rspec-support (3.3.0)
|
88
|
+
shellany (0.0.1)
|
89
|
+
simplecov (0.10.0)
|
90
|
+
docile (~> 1.1.0)
|
91
|
+
json (~> 1.8)
|
92
|
+
simplecov-html (~> 0.10.0)
|
93
|
+
simplecov-html (0.10.0)
|
94
|
+
slop (3.6.0)
|
95
|
+
term-ansicolor (1.3.2)
|
96
|
+
tins (~> 1.0)
|
97
|
+
thor (0.19.1)
|
98
|
+
thread_safe (0.3.5)
|
99
|
+
tins (1.5.4)
|
100
|
+
treetop (1.4.15)
|
101
|
+
polyglot
|
102
|
+
polyglot (>= 0.3.1)
|
103
|
+
tzinfo (1.2.2)
|
104
|
+
thread_safe (~> 0.1)
|
105
|
+
unf (0.1.4)
|
106
|
+
unf_ext
|
107
|
+
unf_ext (0.0.7.1)
|
108
|
+
|
109
|
+
PLATFORMS
|
110
|
+
ruby
|
111
|
+
|
112
|
+
DEPENDENCIES
|
113
|
+
bundler (~> 1.9)
|
114
|
+
coveralls
|
115
|
+
guard
|
116
|
+
guard-rspec
|
117
|
+
menu_markup!
|
118
|
+
rake (~> 10.0)
|
119
|
+
rspec
|
120
|
+
rspec-collection_matchers
|
data/Guardfile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
# Rails files
|
44
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
45
|
+
dsl.watch_spec_files_for(rails.app_files)
|
46
|
+
dsl.watch_spec_files_for(rails.views)
|
47
|
+
|
48
|
+
watch(rails.controllers) do |m|
|
49
|
+
[
|
50
|
+
rspec.spec.("routing/#{m[1]}_routing"),
|
51
|
+
rspec.spec.("controllers/#{m[1]}_controller"),
|
52
|
+
rspec.spec.("acceptance/#{m[1]}")
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Rails config changes
|
57
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
58
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
59
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
60
|
+
|
61
|
+
# Capybara features specs
|
62
|
+
watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
|
63
|
+
watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
|
64
|
+
|
65
|
+
# Turnip features and steps
|
66
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
67
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
68
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
69
|
+
end
|
70
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# MenuMarkup [![Build Status](https://api.travis-ci.org/refreshingmenus/menu_markup.svg?branch=master)](https://travis-ci.org/refreshingmenus/menu_markup) [![Coverage Status](https://coveralls.io/repos/refreshingmenus/menu_markup/badge.svg)](https://coveralls.io/r/refreshingmenus/menu_markup)
|
2
|
+
|
3
|
+
Ruby gem to parse MenuMarkup. See the [MenuMarkup specification][].
|
4
|
+
|
5
|
+
MenuMarkup is a super simple markup to specify menu data in plain text. When the MenuMarkup is parsed it creates a Menu.
|
6
|
+
A Menu consists of two types: Items and Sections. Items have multiple Prices.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'menu_markup'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install menu_markup
|
23
|
+
|
24
|
+
## MenuMarkup example
|
25
|
+
|
26
|
+
For the full specs see the [MenuMarkup specification][].
|
27
|
+
|
28
|
+
```
|
29
|
+
# Lines with a # are comments!
|
30
|
+
# Empty lines are ignored
|
31
|
+
|
32
|
+
* Main dishes
|
33
|
+
The main dishes can be ordered between 17.00 and 21.00.
|
34
|
+
- Super meat 29.95 euro
|
35
|
+
This is 500 grams of pure super meat.
|
36
|
+
- Some other dish
|
37
|
+
#This dish has the price on a different line. It will be parsed correctly.
|
38
|
+
12,95
|
39
|
+
- Daily fish
|
40
|
+
#This differs every day and the price can also change.
|
41
|
+
=depends
|
42
|
+
|
43
|
+
*Dessert
|
44
|
+
- Special dessert
|
45
|
+
#This dessert has multiple sizes and prices.
|
46
|
+
= small 12,30
|
47
|
+
= large 23,30
|
48
|
+
- A desert for 2
|
49
|
+
= 12,30 per person
|
50
|
+
- 300 grams of chocolate
|
51
|
+
#To not make the price 300 we specify an empty price using '='.
|
52
|
+
=
|
53
|
+
```
|
54
|
+
|
55
|
+
## Usage
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
parser = MenuMarkupParser.new
|
59
|
+
parser.parse("*Some\n-Menu\n-Markup")
|
60
|
+
```
|
61
|
+
|
62
|
+
or
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
MenuMarkup.parse("*Some\n-Menu\n-Markup")
|
66
|
+
```
|
67
|
+
|
68
|
+
Next you should create some logic around the parsed content.
|
69
|
+
In the example below we have Entry, Item and Price ActiveRecord models.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
result = parser.parse(text)
|
73
|
+
result.menu.elements.collect { |element| build_entry(element) } if result
|
74
|
+
|
75
|
+
def build_entry(element)
|
76
|
+
entry = send("build_#{element.class.to_s.demodulize.underscore}", element) # Calls build_xx method
|
77
|
+
entry.menu = self # All models belong_to a Menu
|
78
|
+
entry.entries = element.children.collect { |subelement| build_entry(subelement) }
|
79
|
+
|
80
|
+
unless entry.valid?
|
81
|
+
line = parser.input.line_of(element.interval.first)
|
82
|
+
type = entry.class.name.humanize.downcase
|
83
|
+
errors.add(:markup_text, "has invalid #{type} on line ##{line}: #{entry.errors.to_a.to_sentence}")
|
84
|
+
end
|
85
|
+
|
86
|
+
entry
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Define the build_xx methods and use the parsed element content.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
def build_section(element)
|
94
|
+
Section.new(title: element.title, desc: element.description, choice: element.choice?, prices: build_prices(element))
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_item(element)
|
98
|
+
Item.new(title: element.title, desc: element.description, spicy: element.spicy, prices: build_prices(element),
|
99
|
+
restriction_list: element.restrictions)
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_prices(element)
|
103
|
+
# Up to the reader :)
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
## Development
|
108
|
+
|
109
|
+
bundle install
|
110
|
+
guard
|
111
|
+
|
112
|
+
## Contributing
|
113
|
+
|
114
|
+
1. Fork it ( https://github.com/[my-github-username]/menu_markup/fork )
|
115
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
116
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
117
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
118
|
+
5. Create a new Pull Request
|
119
|
+
|
120
|
+
[MenuMarkup specification]: http://www.webuildinternet.com/2012/07/04/menu-markup-specification/
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "menu_markup"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/menu_markup.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
require "menu_markup/version"
|
3
|
+
require "menu_markup/parsed_price"
|
4
|
+
|
5
|
+
require "menu_markup_parser"
|
6
|
+
|
7
|
+
require 'active_support'
|
8
|
+
require 'active_support/core_ext/object/blank' # presence method
|
9
|
+
require 'active_support/core_ext/hash/reverse_merge' # reverse_merge! method
|
10
|
+
|
11
|
+
module MenuMarkup
|
12
|
+
def self.parse(text)
|
13
|
+
MenuMarkupParser.new.parse(text)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
grammar MenuMarkup
|
2
|
+
rule root
|
3
|
+
s menu
|
4
|
+
end
|
5
|
+
|
6
|
+
rule menu
|
7
|
+
(section / item)*
|
8
|
+
end
|
9
|
+
|
10
|
+
rule subsections
|
11
|
+
(subsection / item)*
|
12
|
+
end
|
13
|
+
|
14
|
+
rule subsubsections
|
15
|
+
(subsubsection / item)*
|
16
|
+
end
|
17
|
+
|
18
|
+
rule items do
|
19
|
+
item*
|
20
|
+
end
|
21
|
+
|
22
|
+
rule section
|
23
|
+
s '*' choice_node:choice attributes_node:(attributes '*')? s description_node:description children_nodes:subsections <MenuMarkupParser::Section>
|
24
|
+
end
|
25
|
+
|
26
|
+
rule subsection
|
27
|
+
s '**' choice_node:choice attributes_node:(attributes '**')? s description_node:description children_nodes:subsubsections <MenuMarkupParser::Section>
|
28
|
+
end
|
29
|
+
|
30
|
+
rule subsubsection
|
31
|
+
s '***' choice_node:choice attributes_node:(attributes '***')? s description_node:description children_nodes:items <MenuMarkupParser::Section>
|
32
|
+
end
|
33
|
+
|
34
|
+
rule item
|
35
|
+
s '-' attributes_node:(attributes '-')? s description_node:description <MenuMarkupParser::Item>
|
36
|
+
end
|
37
|
+
|
38
|
+
rule choice
|
39
|
+
'/'?
|
40
|
+
end
|
41
|
+
|
42
|
+
rule attributes
|
43
|
+
[vVhknmMH]* <MenuMarkupParser::Attributes>
|
44
|
+
end
|
45
|
+
|
46
|
+
rule price
|
47
|
+
s '=' s text_node:text_line
|
48
|
+
end
|
49
|
+
|
50
|
+
rule description
|
51
|
+
description_line*
|
52
|
+
end
|
53
|
+
|
54
|
+
rule description_line
|
55
|
+
text:([^-*#] text_line) s
|
56
|
+
end
|
57
|
+
|
58
|
+
rule s #optional space
|
59
|
+
S?
|
60
|
+
end
|
61
|
+
|
62
|
+
rule S # mandatory space
|
63
|
+
(comment / whitespace)+
|
64
|
+
end
|
65
|
+
|
66
|
+
rule whitespace
|
67
|
+
[\s]+
|
68
|
+
end
|
69
|
+
|
70
|
+
rule comment
|
71
|
+
'#' text_line
|
72
|
+
end
|
73
|
+
|
74
|
+
rule text_line
|
75
|
+
[^\r\n]*
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module MenuMarkup
|
3
|
+
class ParsedPrice
|
4
|
+
EOP = /(?=\s|$)/ui
|
5
|
+
BOP = /(?<=\s|^)/ui
|
6
|
+
DASH = /\-{1,2}|—{1,2}/ui # TODO: Support different dashes?, see: http://csswizardry.com/2010/01/the-three-types-of-dash/
|
7
|
+
CURRENCY = /\$|€|euro?|usd|dollar|gbp|tl|£|₤/ui # tl = Turkish lira
|
8
|
+
|
9
|
+
INTEGER = /1[0-7]\d\d|\d{1,3}/ui
|
10
|
+
COMMA_DECIMAL = /\d+,\d/ui
|
11
|
+
DOT_DECIMAL = /\d+\.\d/ui
|
12
|
+
DASH_COMMA_DECIMAL = /\d+,#{DASH}/ui
|
13
|
+
DASH_DOT_DECIMAL = /\d+\.#{DASH}/ui
|
14
|
+
FULL_COMMA_DECIMAL = /\d+,\d\d/ui
|
15
|
+
FULL_DOT_DECIMAL = /\d+\.\d\d/ui
|
16
|
+
|
17
|
+
PRE_CURRENCY_INTEGER = /#{CURRENCY}\s*#{INTEGER}/ui
|
18
|
+
POST_CURRENCY_INTEGER = /#{INTEGER}\s*#{CURRENCY}/ui
|
19
|
+
|
20
|
+
PRE_CURRENCY_COMMA = /#{CURRENCY}\s*#{COMMA_DECIMAL}/ui
|
21
|
+
POST_CURRENCY_COMMA = /#{COMMA_DECIMAL}\s*#{CURRENCY}/ui
|
22
|
+
PRE_CURRENCY_DOT = /#{CURRENCY}\s*#{DOT_DECIMAL}/ui
|
23
|
+
POST_CURRENCY_DOT = /#{DOT_DECIMAL}\s*#{CURRENCY}/ui
|
24
|
+
|
25
|
+
PRE_CURRENCY_DASH_COMMA = /#{CURRENCY}\s*#{DASH_COMMA_DECIMAL}/ui
|
26
|
+
POST_CURRENCY_DASH_COMMA = /#{DASH_COMMA_DECIMAL}\s*#{CURRENCY}/ui
|
27
|
+
PRE_CURRENCY_DASH_DOT = /#{CURRENCY}\s*#{DASH_DOT_DECIMAL}/ui
|
28
|
+
POST_CURRENCY_DASH_DOT = /#{DASH_DOT_DECIMAL}\s*#{CURRENCY}/ui
|
29
|
+
|
30
|
+
PRE_CURRENCY_FULL_COMMA = /#{CURRENCY}\s*#{FULL_COMMA_DECIMAL}/ui
|
31
|
+
POST_CURRENCY_FULL_COMMA = /#{FULL_COMMA_DECIMAL}\s*#{CURRENCY}/ui
|
32
|
+
PRE_CURRENCY_FULL_DOT = /#{CURRENCY}\s*#{FULL_DOT_DECIMAL}/ui
|
33
|
+
POST_CURRENCY_FULL_DOT = /#{FULL_DOT_DECIMAL}\s*#{CURRENCY}/ui
|
34
|
+
|
35
|
+
LITERAL = /dagprijs/ui
|
36
|
+
|
37
|
+
PRICES = [
|
38
|
+
PRE_CURRENCY_FULL_COMMA, PRE_CURRENCY_FULL_DOT, PRE_CURRENCY_DASH_COMMA, PRE_CURRENCY_DASH_DOT, PRE_CURRENCY_COMMA, PRE_CURRENCY_DOT, PRE_CURRENCY_INTEGER,
|
39
|
+
POST_CURRENCY_FULL_COMMA, POST_CURRENCY_FULL_DOT, POST_CURRENCY_DASH_COMMA, POST_CURRENCY_DASH_DOT, POST_CURRENCY_COMMA, POST_CURRENCY_DOT, POST_CURRENCY_INTEGER,
|
40
|
+
FULL_COMMA_DECIMAL, FULL_DOT_DECIMAL, DASH_COMMA_DECIMAL, DASH_DOT_DECIMAL, COMMA_DECIMAL, DOT_DECIMAL, INTEGER,
|
41
|
+
LITERAL
|
42
|
+
]
|
43
|
+
|
44
|
+
attr_accessor :money, :title, :unit
|
45
|
+
|
46
|
+
def self.find_match(text, options = {})
|
47
|
+
options.reverse_merge!(eop: EOP, bop: BOP)
|
48
|
+
PRICES.each do |regex|
|
49
|
+
return $~ if text.scan(/#{options[:bop]}#{regex}#{options[:eop]}/ui).present?
|
50
|
+
end
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.parse_line(line)
|
55
|
+
match = find_match(line)
|
56
|
+
if match
|
57
|
+
new(match.to_s, match.pre_match, match.post_match)
|
58
|
+
else
|
59
|
+
new(nil, line)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(money, title = nil, unit = nil)
|
64
|
+
# In case this is literal price, we want to set it as title of price and not money
|
65
|
+
if money =~ LITERAL
|
66
|
+
title = "#{title}#{money}#{unit}"
|
67
|
+
money = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
@money, @title, @unit = money.presence, title.presence, unit.presence
|
71
|
+
# Replace .- with .00
|
72
|
+
@money.gsub!(/([\.,])#{DASH}/, "\\100") if @money
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# MenuMarkup::Parser parses our superspecial menu markup into Entries (Items and Sections).
|
4
|
+
#
|
5
|
+
# Maybe use one of these:
|
6
|
+
# http://stackoverflow.com/questions/3454047/ruby-markup-parser-for-custom-markup-language
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# parser = MenuMarkup::MenuMarkupParser.new
|
10
|
+
# result = parser.parse(File.read('spec/support/menu5.txt')).elements
|
11
|
+
#
|
12
|
+
# TODO:
|
13
|
+
# * Better implementation
|
14
|
+
# * Support 'vanaf', 'v.a.', 'per persoon', '2 personen', 'p.p.', ..
|
15
|
+
# * Recognize best price. Eg. wines often use Blablawine 2003 (year) which is recognized as Price. We should prefer 4,50 (comma seperated) above this kind of values.
|
16
|
+
#
|
17
|
+
|
18
|
+
Treetop.load(File.join(__dir__, 'menu_markup')) # MenuMarkupParser is created by Treetop
|
19
|
+
|
20
|
+
class MenuMarkupParser
|
21
|
+
class Node < Treetop::Runtime::SyntaxNode
|
22
|
+
end
|
23
|
+
|
24
|
+
class Section < Node
|
25
|
+
attr_reader :prices, :title, :description_lines
|
26
|
+
|
27
|
+
def description
|
28
|
+
description_lines.join("\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
def restrictions
|
32
|
+
attributes_node.respond_to?(:attributes) ? attributes_node.attributes.restrictions : []
|
33
|
+
end
|
34
|
+
|
35
|
+
def spicy
|
36
|
+
attributes_node.respond_to?(:attributes) ? attributes_node.attributes.spicy : nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_elements!
|
40
|
+
@description_lines = description_node.elements.collect { |node| node.text.text_value.strip }
|
41
|
+
@prices = parse_prices!
|
42
|
+
@title = @description_lines.shift
|
43
|
+
|
44
|
+
children.each { |node| node.parse_elements! }
|
45
|
+
end
|
46
|
+
|
47
|
+
def choice?
|
48
|
+
choice_node.text_value.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def children
|
52
|
+
children_nodes.elements
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def parse_prices!
|
58
|
+
parse_explicit_prices
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_explicit_prices
|
62
|
+
price_lines, @description_lines = @description_lines.partition { |line| line.start_with?('=') }
|
63
|
+
price_lines.collect { |price_line| MenuMarkup::ParsedPrice.parse_line(price_line[1..-1]) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Item < Section
|
68
|
+
def children
|
69
|
+
[]
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def parse_prices!
|
75
|
+
parse_explicit_prices.presence || parse_price_line.presence || parse_inline_price
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_price_line
|
79
|
+
new_description = description
|
80
|
+
match = MenuMarkup::ParsedPrice.find_match(new_description, eop: /(?=$)/ui) || MenuMarkup::ParsedPrice.find_match(new_description, bop: /(?<=^)/ui)
|
81
|
+
|
82
|
+
if match
|
83
|
+
new_description.slice!(match.begin(0)...match.end(0))
|
84
|
+
@description_lines = new_description.lines.collect(&:strip).reject(&:blank?)
|
85
|
+
[MenuMarkup::ParsedPrice.new(match.to_s)]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_inline_price
|
90
|
+
match = MenuMarkup::ParsedPrice.find_match(description)
|
91
|
+
if match
|
92
|
+
[MenuMarkup::ParsedPrice.new(match.to_s)]
|
93
|
+
else
|
94
|
+
[MenuMarkup::ParsedPrice.new(nil)]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Attributes < Node
|
100
|
+
SPICY_MAP = {
|
101
|
+
'n' => 'none',
|
102
|
+
'm' => 'mild',
|
103
|
+
'M' => 'medium',
|
104
|
+
'H' => 'hot', # h = halal!
|
105
|
+
}
|
106
|
+
RESTRICTIONS_MAP = {
|
107
|
+
'V' => 'vegan',
|
108
|
+
'v' => 'vegetarian',
|
109
|
+
'k' => 'kosher',
|
110
|
+
'h' => 'halal'
|
111
|
+
}
|
112
|
+
|
113
|
+
def restrictions
|
114
|
+
map(RESTRICTIONS_MAP)
|
115
|
+
end
|
116
|
+
|
117
|
+
def spicy
|
118
|
+
map(SPICY_MAP).first
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def map(map)
|
124
|
+
map.values_at(*text_value.chars).compact
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def parse(text)
|
129
|
+
super(text).tap { |result| result.menu.elements.each(&:parse_elements!) if result }
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
data/menu_markup.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'menu_markup/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "menu_markup"
|
8
|
+
spec.version = MenuMarkup::VERSION
|
9
|
+
spec.authors = ["Joost Hietbrink"]
|
10
|
+
spec.email = ["joost@webuildinternet.com"]
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.summary = %q{Ruby gem to parce MenuMarkup.}
|
14
|
+
spec.description = %q{MenuMarkup is a super simple markup to specify menu data in plain text. When the MenuMarkup is parsed it creates a Menu. A Menu consists of two types: Items and Sections. Items have multiple Prices.}
|
15
|
+
spec.homepage = "http://www.webuildinternet.com/2012/07/04/menu-markup-specification/"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
# end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "treetop", '~> 1.4', ">= 1.4.15"
|
31
|
+
spec.add_dependency "activesupport", '~> 4' # for #presence method
|
32
|
+
spec.add_development_dependency "bundler", '~> 1.9'
|
33
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.3"
|
35
|
+
spec.add_development_dependency "rspec-collection_matchers", '~> 1.1', '>= 1.1.2'
|
36
|
+
spec.add_development_dependency "guard", "~> 2.12"
|
37
|
+
spec.add_development_dependency "guard-rspec", "~> 4.6"
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: menu_markup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joost Hietbrink
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: treetop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.4.15
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.4'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.4.15
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activesupport
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '4'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '4'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.9'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.9'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rake
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '10.4'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '10.4'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rspec
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.3'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.3'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec-collection_matchers
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.1'
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.1.2
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - "~>"
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '1.1'
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 1.1.2
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: guard
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '2.12'
|
116
|
+
type: :development
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - "~>"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '2.12'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: guard-rspec
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '4.6'
|
130
|
+
type: :development
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - "~>"
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '4.6'
|
137
|
+
description: 'MenuMarkup is a super simple markup to specify menu data in plain text.
|
138
|
+
When the MenuMarkup is parsed it creates a Menu. A Menu consists of two types: Items
|
139
|
+
and Sections. Items have multiple Prices.'
|
140
|
+
email:
|
141
|
+
- joost@webuildinternet.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".bundle/config"
|
147
|
+
- ".gitignore"
|
148
|
+
- ".travis.yml"
|
149
|
+
- Gemfile
|
150
|
+
- Gemfile.lock
|
151
|
+
- Guardfile
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- bin/console
|
155
|
+
- bin/setup
|
156
|
+
- lib/menu_markup.rb
|
157
|
+
- lib/menu_markup.treetop
|
158
|
+
- lib/menu_markup/parsed_price.rb
|
159
|
+
- lib/menu_markup/version.rb
|
160
|
+
- lib/menu_markup_parser.rb
|
161
|
+
- menu_markup.gemspec
|
162
|
+
homepage: http://www.webuildinternet.com/2012/07/04/menu-markup-specification/
|
163
|
+
licenses:
|
164
|
+
- MIT
|
165
|
+
metadata: {}
|
166
|
+
post_install_message:
|
167
|
+
rdoc_options: []
|
168
|
+
require_paths:
|
169
|
+
- lib
|
170
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
requirements: []
|
181
|
+
rubyforge_project:
|
182
|
+
rubygems_version: 2.4.5
|
183
|
+
signing_key:
|
184
|
+
specification_version: 4
|
185
|
+
summary: Ruby gem to parce MenuMarkup.
|
186
|
+
test_files: []
|