blacklight_advanced_search 6.0.2 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +15 -0
- data/.rubocop_todo.yml +351 -0
- data/.solr_wrapper.yml +5 -0
- data/.travis.yml +4 -7
- data/Gemfile +18 -11
- data/Rakefile +24 -34
- data/VERSION +1 -1
- data/app/controllers/advanced_controller.rb +5 -7
- data/app/controllers/blacklight_advanced_search/advanced_controller.rb +5 -8
- data/app/helpers/advanced_helper.rb +4 -6
- data/blacklight_advanced_search.gemspec +11 -8
- data/lib/blacklight_advanced_search.rb +29 -34
- data/lib/blacklight_advanced_search/advanced_query_parser.rb +12 -13
- data/lib/blacklight_advanced_search/advanced_search_builder.rb +28 -32
- data/lib/blacklight_advanced_search/catalog_helper_override.rb +11 -34
- data/lib/blacklight_advanced_search/controller.rb +1 -1
- data/lib/blacklight_advanced_search/filter_parser.rb +7 -9
- data/lib/blacklight_advanced_search/parsing_nesting_parser.rb +5 -8
- data/lib/blacklight_advanced_search/redirect_legacy_params_filter.rb +23 -25
- data/lib/blacklight_advanced_search/render_constraints_override.rb +46 -33
- data/lib/blacklight_advanced_search/version.rb +0 -1
- data/lib/generators/blacklight_advanced_search/assets_generator.rb +4 -8
- data/lib/generators/blacklight_advanced_search/blacklight_advanced_search_generator.rb +0 -2
- data/lib/generators/blacklight_advanced_search/install_generator.rb +9 -5
- data/lib/generators/blacklight_advanced_search/templates/advanced_controller.rb +0 -2
- data/lib/parsing_nesting/grammar.rb +22 -25
- data/lib/parsing_nesting/tree.rb +156 -168
- data/solr/conf/_rest_managed.json +3 -0
- data/solr/conf/admin-extra.html +31 -0
- data/solr/conf/elevate.xml +36 -0
- data/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
- data/solr/conf/protwords.txt +21 -0
- data/solr/conf/schema.xml +635 -0
- data/solr/conf/scripts.conf +24 -0
- data/solr/conf/solrconfig.xml +411 -0
- data/solr/conf/spellings.txt +2 -0
- data/solr/conf/stopwords.txt +58 -0
- data/solr/conf/stopwords_en.txt +58 -0
- data/solr/conf/synonyms.txt +31 -0
- data/solr/conf/xslt/example.xsl +132 -0
- data/solr/conf/xslt/example_atom.xsl +67 -0
- data/solr/conf/xslt/example_rss.xsl +66 -0
- data/solr/conf/xslt/luke.xsl +337 -0
- data/solr/sample_solr_documents.yml +2692 -0
- data/spec/features/blacklight_advanced_search_form_spec.rb +0 -2
- data/spec/helpers/advanced_helper_spec.rb +0 -2
- data/spec/integration/blacklight_stub_spec.rb +0 -2
- data/spec/lib/advanced_search_builder_spec.rb +7 -14
- data/spec/lib/blacklight_advanced_search/render_constraints_override_spec.rb +39 -0
- data/spec/lib/deep_merge_spec.rb +109 -34
- data/spec/lib/filter_parser_spec.rb +8 -14
- data/spec/parsing_nesting/build_tree_spec.rb +73 -81
- data/spec/parsing_nesting/consuming_spec.rb +2 -12
- data/spec/parsing_nesting/to_solr_spec.rb +93 -130
- data/spec/spec_helper.rb +0 -3
- data/spec/test_app_templates/app/controllers/catalog_controller.rb +3 -3
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +3 -3
- metadata +63 -13
- data/spec/spec.opts +0 -4
@@ -1,53 +1,30 @@
|
|
1
1
|
module BlacklightAdvancedSearch::CatalogHelperOverride
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def remove_advanced_keyword_query(field, my_params = params)
|
6
|
-
my_params = my_params.dup
|
7
|
-
my_params.delete(field)
|
8
|
-
return my_params
|
9
|
-
end
|
10
|
-
|
11
|
-
def remove_advanced_filter_group(field, my_params = params)
|
12
|
-
if (my_params[:f_inclusive])
|
13
|
-
my_params = my_params.dup
|
14
|
-
my_params[:f_inclusive] = my_params[:f_inclusive].dup
|
15
|
-
my_params[:f_inclusive].delete(field)
|
16
|
-
|
17
|
-
if my_params[:f_inclusive].empty?
|
18
|
-
my_params.delete :f_inclusive
|
19
|
-
end
|
20
|
-
end
|
21
|
-
my_params
|
22
|
-
end
|
23
|
-
|
24
2
|
# Special display for facet limits that include adv search inclusive
|
25
3
|
# or limits.
|
26
4
|
def facet_partial_name(display_facet = nil)
|
27
|
-
return "blacklight_advanced_search/facet_limit" if advanced_query && advanced_query.filters.keys.include?(
|
28
|
-
super
|
5
|
+
return "blacklight_advanced_search/facet_limit" if advanced_query && advanced_query.filters.keys.include?(display_facet.name)
|
6
|
+
super
|
29
7
|
end
|
30
8
|
|
31
9
|
def remove_advanced_facet_param(field, value, my_params = params)
|
32
|
-
my_params = my_params.
|
33
|
-
if (my_params[:f_inclusive] &&
|
10
|
+
my_params = Blacklight::SearchState.new(my_params, blacklight_config).to_h
|
11
|
+
if (my_params[:f_inclusive] &&
|
34
12
|
my_params[:f_inclusive][field] &&
|
35
13
|
my_params[:f_inclusive][field].include?(value))
|
36
|
-
|
14
|
+
|
37
15
|
my_params[:f_inclusive] = my_params[:f_inclusive].dup
|
38
16
|
my_params[:f_inclusive][field] = my_params[:f_inclusive][field].dup
|
39
17
|
my_params[:f_inclusive][field].delete(value)
|
40
|
-
|
41
|
-
my_params[:f_inclusive].delete(field) if my_params[:f_inclusive][field].
|
42
|
-
|
43
|
-
my_params.delete(:f_inclusive) if my_params[:f_inclusive].
|
18
|
+
|
19
|
+
my_params[:f_inclusive].delete(field) if my_params[:f_inclusive][field].empty?
|
20
|
+
|
21
|
+
my_params.delete(:f_inclusive) if my_params[:f_inclusive].empty?
|
44
22
|
end
|
45
23
|
|
46
|
-
my_params.delete_if do |key,
|
24
|
+
my_params.delete_if do |key, _value|
|
47
25
|
[:page, :id, :counter, :commit].include?(key)
|
48
26
|
end
|
49
|
-
|
27
|
+
|
50
28
|
my_params
|
51
29
|
end
|
52
|
-
|
53
30
|
end
|
@@ -12,7 +12,7 @@ module BlacklightAdvancedSearch::Controller
|
|
12
12
|
helper_method :is_advanced_search?, :advanced_query
|
13
13
|
end
|
14
14
|
|
15
|
-
def is_advanced_search?
|
15
|
+
def is_advanced_search?(req_params = params)
|
16
16
|
(req_params[:search_field] == blacklight_config.advanced_search[:url_key]) ||
|
17
17
|
req_params[:f_inclusive]
|
18
18
|
end
|
@@ -1,13 +1,11 @@
|
|
1
1
|
module BlacklightAdvancedSearch::FilterParser
|
2
2
|
# Returns an array of solr :fq params. taking advanced search inclusive
|
3
|
-
# facet value lists out of params.
|
3
|
+
# facet value lists out of params.
|
4
4
|
def generate_solr_fq
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
return filter_queries
|
5
|
+
filters.map do |solr_field, value_list|
|
6
|
+
"#{solr_field}:(" +
|
7
|
+
Array(value_list).collect { |v| '"' + v.gsub('"', '\"') + '"' }.join(" OR ") +
|
8
|
+
")"
|
9
|
+
end
|
12
10
|
end
|
13
|
-
end
|
11
|
+
end
|
@@ -1,18 +1,15 @@
|
|
1
1
|
require 'parsing_nesting/tree'
|
2
2
|
module BlacklightAdvancedSearch::ParsingNestingParser
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
keyword_queries.each do |field,query|
|
7
|
-
queries << ParsingNesting::Tree.parse(query, config.advanced_search[:query_parser]).to_query( local_param_hash(field, config) )
|
3
|
+
def process_query(_params, config)
|
4
|
+
queries = keyword_queries.map do |field, query|
|
5
|
+
ParsingNesting::Tree.parse(query, config.advanced_search[:query_parser]).to_query(local_param_hash(field, config))
|
8
6
|
end
|
9
|
-
queries.join(
|
7
|
+
queries.join(" #{keyword_op} ")
|
10
8
|
end
|
11
|
-
|
9
|
+
|
12
10
|
def local_param_hash(key, config)
|
13
11
|
field_def = config.search_fields[key]
|
14
12
|
|
15
13
|
(field_def[:solr_parameters] || {}).merge(field_def[:solr_local_parameters] || {})
|
16
14
|
end
|
17
|
-
|
18
15
|
end
|
@@ -1,32 +1,30 @@
|
|
1
1
|
# Returns a lambda that you can use with a before_filter in your
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
2
|
+
# CatalogController to catch and redirect query params using the old
|
3
|
+
# style, used prior to blacklight_advanced_search 5.0.
|
4
|
+
#
|
5
|
+
# This can be used to keep any old bookmarked URLs still working.
|
6
|
+
#
|
7
|
+
# before_filter BlacklightAdvancedSearch::RedirectLegacyParamsFilter, :only => :index
|
8
|
+
#
|
9
|
+
module BlacklightAdvancedSearch
|
10
|
+
class RedirectLegacyParamsFilter
|
11
|
+
def self.before(controller)
|
12
|
+
params = controller.send(:params)
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
if params[:f_inclusive] && params[:f_inclusive].respond_to?(:each_pair)
|
15
|
+
legacy_converted = false
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
legacy_converted = true
|
22
|
-
params[:f_inclusive][field] = value.keys
|
23
|
-
end
|
24
|
-
end
|
17
|
+
params[:f_inclusive].each_pair do |field, value|
|
18
|
+
next unless value.is_a? Hash
|
19
|
+
# old style! convert!
|
20
|
+
legacy_converted = true
|
21
|
+
params[:f_inclusive][field] = value.keys
|
22
|
+
end
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
24
|
+
if legacy_converted
|
25
|
+
controller.send(:redirect_to, params, :status => :moved_permanently)
|
29
26
|
end
|
30
27
|
end
|
31
28
|
end
|
32
|
-
end
|
29
|
+
end
|
30
|
+
end
|
@@ -2,19 +2,18 @@
|
|
2
2
|
# certain methods from RenderConstraintsHelper (newish in BL),
|
3
3
|
# to effect constraints rendering and search history rendering,
|
4
4
|
module BlacklightAdvancedSearch::RenderConstraintsOverride
|
5
|
-
|
6
5
|
def query_has_constraints?(localized_params = params)
|
7
6
|
if is_advanced_search? localized_params
|
8
7
|
true
|
9
8
|
else
|
10
|
-
!(localized_params[:q].blank?
|
9
|
+
!(localized_params[:q].blank? && localized_params[:f].blank? && localized_params[:f_inclusive].blank?)
|
11
10
|
end
|
12
11
|
end
|
13
12
|
|
14
|
-
#Over-ride of Blacklight method, provide advanced constraints if needed,
|
13
|
+
# Over-ride of Blacklight method, provide advanced constraints if needed,
|
15
14
|
# otherwise call super.
|
16
15
|
def render_constraints_query(my_params = params)
|
17
|
-
if (advanced_query.nil? || advanced_query.keyword_queries.empty?
|
16
|
+
if (advanced_query.nil? || advanced_query.keyword_queries.empty?)
|
18
17
|
return super(my_params)
|
19
18
|
else
|
20
19
|
content = []
|
@@ -28,58 +27,56 @@ module BlacklightAdvancedSearch::RenderConstraintsOverride
|
|
28
27
|
end
|
29
28
|
if (advanced_query.keyword_op == "OR" &&
|
30
29
|
advanced_query.keyword_queries.length > 1)
|
31
|
-
content.unshift content_tag(:span, "Any of:", class:'operator')
|
30
|
+
content.unshift content_tag(:span, "Any of:", class: 'operator')
|
32
31
|
content_tag :span, class: "inclusive_or appliedFilter well" do
|
33
32
|
safe_join(content.flatten, "\n")
|
34
33
|
end
|
35
34
|
else
|
36
|
-
safe_join(content.flatten, "\n")
|
35
|
+
safe_join(content.flatten, "\n")
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
40
39
|
|
41
|
-
#Over-ride of Blacklight method, provide advanced constraints if needed,
|
40
|
+
# Over-ride of Blacklight method, provide advanced constraints if needed,
|
42
41
|
# otherwise call super.
|
43
42
|
def render_constraints_filters(my_params = params)
|
44
43
|
content = super(my_params)
|
45
44
|
|
46
|
-
if
|
45
|
+
if advanced_query
|
47
46
|
advanced_query.filters.each_pair do |field, value_list|
|
48
47
|
label = facet_field_label(field)
|
49
48
|
content << render_constraint_element(label,
|
50
|
-
safe_join(value_list, " <strong class='text-muted constraint-connector'>OR</strong> ".html_safe),
|
51
|
-
:remove => search_action_path(
|
52
|
-
|
49
|
+
safe_join(Array(value_list), " <strong class='text-muted constraint-connector'>OR</strong> ".html_safe),
|
50
|
+
:remove => search_action_path(remove_advanced_filter_group(field, my_params).except(:controller, :action))
|
51
|
+
)
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
56
|
-
|
55
|
+
content
|
57
56
|
end
|
58
57
|
|
59
58
|
# override of BL method, so our inclusive facet selections
|
60
59
|
# are still recgonized for eg highlighting facet with selected
|
61
|
-
# values.
|
60
|
+
# values.
|
62
61
|
def facet_field_in_params?(field)
|
63
62
|
return true if super
|
64
63
|
|
65
64
|
# otherwise use our own logic.
|
66
|
-
query = BlacklightAdvancedSearch::QueryParser.new(params, self.blacklight_config
|
67
|
-
if query.filters.keys.include?(
|
68
|
-
return true
|
69
|
-
end
|
65
|
+
query = BlacklightAdvancedSearch::QueryParser.new(params, self.blacklight_config)
|
66
|
+
return true if query.filters.keys.include?(field)
|
70
67
|
|
71
|
-
|
68
|
+
false
|
72
69
|
end
|
73
70
|
|
74
71
|
def render_search_to_s_filters(my_params)
|
75
72
|
content = super(my_params)
|
76
73
|
|
77
|
-
advanced_query = BlacklightAdvancedSearch::QueryParser.new(my_params, blacklight_config
|
74
|
+
advanced_query = BlacklightAdvancedSearch::QueryParser.new(my_params, blacklight_config)
|
78
75
|
|
79
|
-
|
76
|
+
unless advanced_query.filters.empty?
|
80
77
|
advanced_query.filters.each_pair do |field, values|
|
81
78
|
# old-style, may still be in history
|
82
|
-
values = values.keys if values.
|
79
|
+
values = values.keys if values.is_a? Hash
|
83
80
|
|
84
81
|
label = facet_field_label(field)
|
85
82
|
|
@@ -89,27 +86,27 @@ module BlacklightAdvancedSearch::RenderConstraintsOverride
|
|
89
86
|
)
|
90
87
|
end
|
91
88
|
end
|
92
|
-
|
89
|
+
content
|
93
90
|
end
|
94
91
|
|
95
92
|
def render_search_to_s_q(my_params)
|
96
93
|
content = super(my_params)
|
97
94
|
|
98
|
-
advanced_query = BlacklightAdvancedSearch::QueryParser.new(my_params, blacklight_config
|
95
|
+
advanced_query = BlacklightAdvancedSearch::QueryParser.new(my_params, blacklight_config)
|
99
96
|
|
100
97
|
if (advanced_query.keyword_queries.length > 1 &&
|
101
98
|
advanced_query.keyword_op == "OR")
|
102
|
-
|
99
|
+
# Need to do something to make the inclusive-or search clear
|
103
100
|
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
display_as = advanced_query.keyword_queries.collect do |field, query|
|
102
|
+
h(search_field_def_for_key(field)[:label] + ": " + query)
|
103
|
+
end.join(" ; ")
|
107
104
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
elsif
|
105
|
+
content << render_search_to_s_element("Any of",
|
106
|
+
display_as,
|
107
|
+
:escape_value => false
|
108
|
+
)
|
109
|
+
elsif !advanced_query.keyword_queries.empty?
|
113
110
|
advanced_query.keyword_queries.each_pair do |field, query|
|
114
111
|
label = search_field_def_for_key(field)[:label]
|
115
112
|
|
@@ -117,7 +114,23 @@ module BlacklightAdvancedSearch::RenderConstraintsOverride
|
|
117
114
|
end
|
118
115
|
end
|
119
116
|
|
120
|
-
|
117
|
+
content
|
118
|
+
end
|
119
|
+
|
120
|
+
def remove_advanced_keyword_query(field, my_params = params)
|
121
|
+
my_params = Blacklight::SearchState.new(my_params, blacklight_config).to_h
|
122
|
+
my_params.delete(field)
|
123
|
+
my_params
|
121
124
|
end
|
122
125
|
|
126
|
+
def remove_advanced_filter_group(field, my_params = params)
|
127
|
+
if (my_params[:f_inclusive])
|
128
|
+
my_params = Blacklight::SearchState.new(my_params, blacklight_config).to_h
|
129
|
+
my_params[:f_inclusive] = my_params[:f_inclusive].dup
|
130
|
+
my_params[:f_inclusive].delete(field)
|
131
|
+
|
132
|
+
my_params.delete :f_inclusive if my_params[:f_inclusive].empty?
|
133
|
+
end
|
134
|
+
my_params
|
135
|
+
end
|
123
136
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copy BlacklightAdvancedSearch assets to public folder in current app.
|
1
|
+
# Copy BlacklightAdvancedSearch assets to public folder in current app.
|
2
2
|
# If you want to do this on application startup, you can
|
3
3
|
# add this next line to your one of your environment files --
|
4
4
|
# generally you'd only want to do this in 'development', and can
|
@@ -6,9 +6,8 @@
|
|
6
6
|
# require File.join(BlacklightAdvancedSearch.root, "lib", "generators", "blacklight", "assets_generator.rb")
|
7
7
|
# BlacklightAdvancedSearch::AssetsGenerator.start(["--force", "--quiet"])
|
8
8
|
|
9
|
-
|
10
9
|
# Need the requires here so we can call the generator from environment.rb
|
11
|
-
# as suggested above.
|
10
|
+
# as suggested above.
|
12
11
|
require 'rails/generators'
|
13
12
|
require 'rails/generators/base'
|
14
13
|
module BlacklightAdvancedSearch
|
@@ -26,12 +25,11 @@ module BlacklightAdvancedSearch
|
|
26
25
|
original_css = File.binread(application_css_location)
|
27
26
|
if original_css.include?("require 'blacklight_advanced_search'")
|
28
27
|
say_status("skipped", "insert into app/assets/stylesheets/application.css", :yellow)
|
29
|
-
else
|
28
|
+
else
|
30
29
|
insert_into_file application_css_location, :before => "*/" do
|
31
30
|
"\n *= require 'blacklight_advanced_search'\n\n"
|
32
31
|
end
|
33
32
|
end
|
34
|
-
|
35
33
|
end
|
36
34
|
|
37
35
|
def js_asset
|
@@ -41,7 +39,7 @@ module BlacklightAdvancedSearch
|
|
41
39
|
say_status "skipped", "Can not find an application.js, did not insert our require", :red
|
42
40
|
return
|
43
41
|
end
|
44
|
-
|
42
|
+
|
45
43
|
original_js = File.binread(application_js_location)
|
46
44
|
if original_js.include?("require 'blacklight_advanced_search'")
|
47
45
|
say_status("skipped", "insert into app/assets/javascripts/application.js", :yellow)
|
@@ -51,7 +49,5 @@ module BlacklightAdvancedSearch
|
|
51
49
|
end
|
52
50
|
end
|
53
51
|
end
|
54
|
-
|
55
52
|
end
|
56
53
|
end
|
57
|
-
|
@@ -1,10 +1,8 @@
|
|
1
1
|
require 'rails/generators'
|
2
2
|
|
3
3
|
class BlacklightAdvancedSearchGenerator < Rails::Generators::Base
|
4
|
-
|
5
4
|
def inject_asset_requires
|
6
5
|
say "`rails g blacklight_advanced_search` is deprecated; use blacklight_advanced_search:install instead", :red
|
7
6
|
generate "blacklight_advanced_search:install"
|
8
7
|
end
|
9
|
-
|
10
8
|
end
|
@@ -47,16 +47,20 @@ module BlacklightAdvancedSearch
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def install_localized_search_form
|
50
|
-
if options[:force]
|
50
|
+
if options[:force] || yes?("Install local search form with advanced link? (y/N)", :green)
|
51
51
|
# We're going to copy the search from from actual currently loaded
|
52
|
-
# Blacklight into local app as custom local override -- but add our link at the end too.
|
52
|
+
# Blacklight into local app as custom local override -- but add our link at the end too.
|
53
53
|
source_file = File.read(File.join(Blacklight.root, "app/views/catalog/_search_form.html.erb"))
|
54
54
|
|
55
|
-
new_file_contents = source_file +
|
55
|
+
new_file_contents = source_file + <<-EOF.strip_heredoc
|
56
|
+
\n\n
|
57
|
+
<div class="navbar-form">
|
58
|
+
<%= link_to 'More options', blacklight_advanced_search_engine.advanced_search_path(search_state.to_h), class: 'advanced_search btn btn-default'%>
|
59
|
+
</div>
|
60
|
+
EOF
|
56
61
|
|
57
|
-
create_file("app/views/catalog/_search_form.html.erb", new_file_contents)
|
62
|
+
create_file("app/views/catalog/_search_form.html.erb", new_file_contents)
|
58
63
|
end
|
59
64
|
end
|
60
65
|
end
|
61
|
-
|
62
66
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
class AdvancedController < BlacklightAdvancedSearch::AdvancedController
|
2
|
-
|
3
2
|
blacklight_config.configure do |config|
|
4
3
|
# name of Solr request handler, leave unset to use the same one your Blacklight
|
5
4
|
# is ordinarily using (recommended if possible)
|
@@ -54,5 +53,4 @@ class AdvancedController < BlacklightAdvancedSearch::AdvancedController
|
|
54
53
|
}
|
55
54
|
end
|
56
55
|
end
|
57
|
-
|
58
56
|
end
|
@@ -1,78 +1,75 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'parslet'
|
3
3
|
|
4
|
-
# Parslet uses Object#tap, which is in ruby 1.8.7+, but not 1.8.6.
|
4
|
+
# Parslet uses Object#tap, which is in ruby 1.8.7+, but not 1.8.6.
|
5
5
|
# But it's easy enough to implement in pure ruby, let's monkey patch
|
6
6
|
# it in if it's not there, so we'll still work with 1.8.6
|
7
7
|
unless Object.method_defined?(:tap)
|
8
8
|
class Object
|
9
9
|
def tap
|
10
10
|
yield(self)
|
11
|
-
|
11
|
+
self
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
module ParsingNesting
|
16
16
|
class Grammar < Parslet::Parser
|
17
17
|
root :query
|
18
|
-
|
19
|
-
# query is actually a list of expressions.
|
18
|
+
|
19
|
+
# query is actually a list of expressions.
|
20
20
|
rule :query do
|
21
|
-
(spacing? >>
|
21
|
+
(spacing? >> (expression | paren_unit) >> spacing?).repeat
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
rule :paren_list do
|
25
25
|
(str('(') >> query >> str(')')).as(:list)
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
rule :paren_unit do
|
29
|
-
(str('(') >> spacing? >>
|
29
|
+
(str('(') >> spacing? >> expression >> spacing? >> str(')')) |
|
30
30
|
paren_list
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# Note well: It was tricky to parse the thing we want where you can
|
34
34
|
# have a flat list with boolean operators, but where 'OR' takes precedence.
|
35
35
|
# eg "A AND B OR C AND C" or "A OR B AND C OR D". Tricky to parse at all,
|
36
36
|
# tricky to make precedence work. Important things that seem to make it work:
|
37
37
|
# and_list comes BEFORE or_list in :expression.
|
38
38
|
# and_list's operand can be an or_list, but NOT vice versa
|
39
|
-
# There are others, it was an iterative process with testing.
|
39
|
+
# There are others, it was an iterative process with testing.
|
40
40
|
rule :expression do
|
41
|
-
(and_list | or_list | unary_expression
|
41
|
+
(and_list | or_list | unary_expression)
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
rule :and_list do
|
45
|
-
((or_list | unary_expression | paren_unit) >>
|
46
|
-
(spacing >> str("AND") >> spacing >> (or_list | unary_expression | paren_unit)).repeat(1)).as(:and_list)
|
45
|
+
((or_list | unary_expression | paren_unit) >>
|
46
|
+
(spacing >> str("AND") >> spacing >> (or_list | unary_expression | paren_unit)).repeat(1)).as(:and_list)
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
rule :or_list do
|
50
|
-
((unary_expression | paren_unit) >>
|
51
|
-
(spacing >> str("OR") >> spacing >> (unary_expression | paren_unit)).repeat(1)).as(:or_list)
|
50
|
+
((unary_expression | paren_unit) >>
|
51
|
+
(spacing >> str("OR") >> spacing >> (unary_expression | paren_unit)).repeat(1)).as(:or_list)
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
rule :unary_expression do
|
55
55
|
(str('+') >> (phrase | token)).as(:mandatory) |
|
56
56
|
(str('-') >> (phrase | token)).as(:excluded) |
|
57
57
|
(str('NOT') >> spacing? >> (unary_expression | paren_unit)).as(:not_expression) |
|
58
58
|
(phrase | token)
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
rule :token do
|
62
62
|
match['^ ")('].repeat(1).as(:token)
|
63
63
|
end
|
64
64
|
rule :phrase do
|
65
|
-
match('"') >> match['^"'].repeat(1).as(:phrase)
|
65
|
+
match('"') >> match['^"'].repeat(1).as(:phrase) >> match('"')
|
66
66
|
end
|
67
|
-
|
68
|
-
|
67
|
+
|
69
68
|
rule :spacing do
|
70
|
-
match[' '].repeat(1)
|
69
|
+
match[' '].repeat(1)
|
71
70
|
end
|
72
71
|
rule :spacing? do
|
73
72
|
spacing.maybe
|
74
73
|
end
|
75
74
|
end
|
76
|
-
|
77
|
-
|
78
75
|
end
|