menu_markup 0.1.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.
@@ -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: []