menu_markup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_DISABLE_SHARED_GEMS: '1'
@@ -0,0 +1,2 @@
1
+ vendor/
2
+ coverage/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
4
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'coveralls', require: false
4
+
5
+ # Specify your gem's dependencies in menu_markup.gemspec
6
+ gemspec
@@ -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
@@ -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
@@ -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/
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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,3 @@
1
+ module MenuMarkup
2
+ VERSION = "0.1.0"
3
+ 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
@@ -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: []