murlsh 1.3.1 → 1.4.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.
Files changed (40) hide show
  1. data/.htaccess +1 -0
  2. data/README.textile +6 -12
  3. data/Rakefile +19 -17
  4. data/VERSION +1 -1
  5. data/bin/murlsh +2 -35
  6. data/config.ru +1 -1
  7. data/config.yaml +9 -9
  8. data/lib/murlsh/ask.rb +15 -0
  9. data/lib/murlsh/cp_r_safe.rb +33 -0
  10. data/lib/murlsh/doc.rb +1 -16
  11. data/lib/murlsh/install.rb +28 -0
  12. data/lib/murlsh/plugin.rb +0 -2
  13. data/lib/murlsh/search_conditions.rb +12 -2
  14. data/lib/murlsh/search_grammar.rb +225 -0
  15. data/lib/murlsh/search_grammar.treetop +34 -0
  16. data/lib/murlsh/uri_ask.rb +10 -14
  17. data/lib/murlsh/url_body.rb +33 -19
  18. data/lib/murlsh/url_result_set.rb +12 -2
  19. data/lib/murlsh.rb +4 -1
  20. data/murlsh.gemspec +26 -21
  21. data/plugins/add_post_50_update_m3u.rb +35 -0
  22. data/plugins/{add_pre_41_unajax_twitter.rb → add_pre_30_unajax_twitter.rb} +1 -1
  23. data/plugins/add_pre_35_url_clean.rb +21 -0
  24. data/plugins/add_pre_50_media_thumbnail.rb +6 -3
  25. data/plugins/add_pre_65_html_thumb.rb +1 -3
  26. data/plugins/url_display_add_60_via.rb +4 -1
  27. data/public/css/jquery.jgrowl.css +11 -7
  28. data/public/css/screen.css +3 -2
  29. data/public/js/jquery-1.5.min.js +16 -0
  30. data/public/js/jquery.jgrowl_compressed.js +40 -32
  31. data/public/js/js.js +3 -43
  32. data/public/js/{twitter-text-1.0.4.js → twitter-text-1.3.1.js} +25 -22
  33. data/spec/doc_spec.rb +10 -4
  34. metadata +75 -61
  35. data/lib/murlsh/sqlite3_adapter.rb +0 -13
  36. data/plugins/add_pre_60_flickr.rb +0 -27
  37. data/plugins/add_pre_60_vimeo.rb +0 -38
  38. data/plugins/html_parse_50_nokogiri.rb +0 -16
  39. data/public/js/comments.json +0 -1
  40. data/public/js/jquery-1.4.4.min.js +0 -167
data/.htaccess CHANGED
@@ -5,6 +5,7 @@ AddOutputFilterByType DEFLATE application/javascript
5
5
  AddOutputFilterByType DEFLATE application/rss+xml
6
6
  AddOutputFilterByType DEFLATE application/xhtml+xml
7
7
  AddOutputFilterByType DEFLATE application/xml
8
+ AddOutputFilterByType DEFLATE audio/x-mpegurl
8
9
  AddOutputFilterByType DEFLATE text/css
9
10
  AddOutputFilterByType DEFLATE text/html
10
11
 
data/README.textile CHANGED
@@ -1,11 +1,12 @@
1
- Site for sharing and archiving links.
1
+ Host your bookmarks or maintain a link blog.
2
2
 
3
- * fetches url titles and generates thumbnails
4
- * jGrowls embedded versions of Imageshack, Vimeo and YouTube urls
3
+ * fetches url titles and generates thumbnails from page
4
+ * jGrowls embedded versions of Imageshack, Imgur, Vimeo and YouTube urls
5
5
  * converts Twitter status urls to their full text and adds user thumbnail
6
6
  * generates Atom and RSS feeds
7
+ * generates podcast RSS feed and m3u file for all audio urls
7
8
  * generates json and jsonp feeds for client-side inclusion in other sites
8
- * regex search
9
+ * search
9
10
  * uses HTML5 audio for mp3 and ogg urls
10
11
  * looks good on iPhone
11
12
  * PubSubHubbub notification
@@ -55,6 +56,7 @@ h2. Recent urls
55
56
  * http://your_root/atom.atom
56
57
  * http://your_root/rss.rss
57
58
  * http://your_root/podcast.rss (urls with audio/mpeg content type)
59
+ * http://your_root/m3u.m3u (urls with audio content types)
58
60
  * http://your_root/json.json
59
61
  * http://your_root/json.json?callback=x (jsonp)
60
62
 
@@ -77,7 +79,6 @@ Plugin hooks
77
79
  |add_pre|called before a new url is saved|url, config hash|undefined|
78
80
  |add_post|called after a new url is saved|url, config hash|undefined|
79
81
  |avatar|called to get an avatar url from an email md5 sum|avatar url, url, config hash|avatar url|
80
- |html_parse|parse HTML using something like Hpricot or Nokogiri|parseable|parsed HTML, only first plugin is run (cannot be chained)|
81
82
  |url_display_add|called to display additional information after urls|markup builder, url, config hash|undefined|
82
83
 
83
84
  h1. PubSubHubbub
@@ -99,11 +100,4 @@ subscribe_url is what gets put in the feed as link rel="hub"
99
100
 
100
101
  This will make updates to your feed show up in Google Reader instantly.
101
102
 
102
- h1. Design Goals
103
-
104
- * low effort required to add a url, get metadata programatically instead of requiring user to specify
105
- * allow customization with config and plugins
106
- * full regex search for finding saved urls
107
- * simple security (no sessions, no cookies)
108
-
109
103
  Questions and comments: "matthewm@boedicker.org":mailto:matthewm@boedicker.org
data/Rakefile CHANGED
@@ -51,7 +51,7 @@ namespace :db do
51
51
 
52
52
  last = Murlsh::Url.find(:last, :order => 'time')
53
53
  pp last
54
- response = ask('Delete this url', '?')
54
+ response = Murlsh.ask('Delete this url?', 'n')
55
55
  last.destroy if %w{y yes}.include?(response.downcase)
56
56
  end
57
57
 
@@ -98,9 +98,9 @@ namespace :db do
98
98
  ActiveRecord::Base.establish_connection(:adapter => 'sqlite3',
99
99
  :database => config.fetch('db_file'))
100
100
 
101
- Murlsh::Url.all(:conditions => [
102
- 'MURLSHMATCH(title, :search) OR MURLSHMATCH(url, :search)',
103
- { :search => args.search }]).each do |url|
101
+ like = "%#{args.search}%"
102
+ Murlsh::Url.all(:conditions =>
103
+ ['title LIKE ? OR url LIKE ?', like, like]).each do |url|
104
104
  puts "#{url.id} #{url.url} #{url.title}"
105
105
  end
106
106
  end
@@ -164,9 +164,9 @@ namespace :user do
164
164
  desc 'Add a new user.'
165
165
  task :add do
166
166
  puts "adding to #{config.fetch('auth_file')}"
167
- username = ask(:username)
168
- email = ask(:email)
169
- password = ask(:password)
167
+ username = Murlsh.ask('Username:')
168
+ email = Murlsh.ask('Email:')
169
+ password = Murlsh.ask('Password:')
170
170
 
171
171
  Murlsh::Auth.new(config.fetch('auth_file')).add_user(username, email,
172
172
  password)
@@ -270,6 +270,9 @@ namespace :css do
270
270
  unless config['css_compressed'] == compressed_url
271
271
  config['css_compressed'] = compressed_url
272
272
  config.extend(Murlsh::YamlOrderedHash)
273
+ config.each_value do |v|
274
+ v.extend(Murlsh::YamlOrderedHash) if v.is_a?(Hash)
275
+ end
273
276
  open('config.yaml', 'w') { |f| YAML.dump(config, f) }
274
277
  puts "updated config with css_compressed = #{compressed_url}"
275
278
  end
@@ -309,6 +312,9 @@ namespace :js do
309
312
  unless config['js_compressed'] == compressed_url
310
313
  config['js_compressed'] = compressed_url
311
314
  config.extend(Murlsh::YamlOrderedHash)
315
+ config.each_value do |v|
316
+ v.extend(Murlsh::YamlOrderedHash) if v.is_a?(Hash)
317
+ end
312
318
  open('config.yaml', 'w') { |f| YAML.dump(config, f) }
313
319
  puts "updated config with js_compressed = #{compressed_url}"
314
320
  end
@@ -416,17 +422,12 @@ EOS
416
422
 
417
423
  end
418
424
 
419
- def ask(prompt, sep=':')
420
- print "#{prompt}#{sep} "
421
- STDIN.gets.chomp
422
- end
423
-
424
425
  begin
425
426
  require 'jeweler'
426
427
  Jeweler::Tasks.new do |gemspec|
427
428
  gemspec.name = 'murlsh'
428
- gemspec.summary = 'url sharing site framework'
429
- gemspec.description = 'url sharing site framework with easy adding, title lookup, atom feed, thumbnails and embedding'
429
+ gemspec.summary = 'Host your bookmarks or maintain a link blog'
430
+ gemspec.description = 'Host your bookmarks or maintain a link blog'
430
431
  gemspec.email = 'matthewm@boedicker.org'
431
432
  gemspec.homepage = 'http://github.com/mmb/murlsh'
432
433
  gemspec.authors = ['Matthew M. Boedicker']
@@ -439,11 +440,11 @@ begin
439
440
  activerecord >= 2.3.4
440
441
  bcrypt-ruby >= 2.1.2
441
442
  builder >= 2.1.2
442
- flickraw >= 0.8.3
443
443
  htmlentities >= 4.2.0
444
444
  json >= 1.2.3
445
445
  nokogiri ~> 1.0
446
446
  plumnailer >= 0.1.0
447
+ postrank-uri ~> 1.0
447
448
  public_suffix_service ~> 0.0
448
449
  push-notify >= 0.1.0
449
450
  rack >= 1.0.0
@@ -451,12 +452,13 @@ begin
451
452
  rack-rewrite >= 1.0.2
452
453
  rack-throttle >= 0.3.0
453
454
  rmagick >= 1.15.14
454
- sqlite3-ruby >= 1.2.1
455
+ sqlite3 ~> 1.3
455
456
  tinyatom >= 0.3.3
457
+ treetop ~> 1.4
456
458
  twitter >= 0.9.12
457
- vimeo >= 1.2.2
458
459
  }.each_slice(3) { |g,o,v| gemspec.add_dependency(g, "#{o} #{v}") }
459
460
  %w{
461
+ fakeweb ~> 1.3
460
462
  flog >= 2.5.0
461
463
  rack-test ~> 0.5
462
464
  rspec ~> 2.0
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.1
1
+ 1.4.0
data/bin/murlsh CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'fileutils'
3
+ require 'murlsh'
4
4
 
5
5
  def usage
6
6
  puts <<-eos
@@ -11,44 +11,11 @@ usage: murlsh DESTINATION_DIRECTORY
11
11
  eos
12
12
  end
13
13
 
14
- def ask(prompt, default)
15
- print "#{prompt} [#{default}] "
16
- answer = $stdin.gets.strip
17
- answer = default if answer.empty?
18
- answer
19
- end
20
-
21
- def cp_r_safe(sources, dest, options)
22
- sources.each do |source|
23
- new = File.join(dest, File.split(File.expand_path(source)).last)
24
-
25
- if File.directory?(source)
26
- FileUtils.mkdir_p(new, options)
27
- cp_r_safe(Dir.entries(source).
28
- reject { |f| %w{. ..}.include?(f) }.
29
- map { |f| File.join(source, f) }, new, options)
30
- else
31
- answer = if File.exists?(new)
32
- ask("#{new} exists. Overwrite?", 'n')
33
- else
34
- 'y'
35
- end
36
-
37
- FileUtils.copy(source, new, options) if answer == 'y'
38
- end
39
- end
40
-
41
- end
42
-
43
14
  if ARGV.empty?; usage; exit 1; end
44
15
 
45
16
  dest_dir = ARGV[0]
46
17
 
47
- cp_r_safe(
48
- %w{.htaccess config.ru config.yaml plugins/ public/ Rakefile}.map { |x|
49
- File.join(File.dirname(__FILE__), '..', x) }, dest_dir, :verbose => true)
50
-
51
- FileUtils.mkdir_p('tmp', :verbose => true)
18
+ Murlsh.install(dest_dir)
52
19
 
53
20
  cd_command = File.expand_path(dest_dir) == Dir.pwd ? '' : "cd #{dest_dir}\n"
54
21
 
data/config.ru CHANGED
@@ -32,7 +32,7 @@ feed_url = URI.join(config.fetch('root_url'), config.fetch('feed_file'))
32
32
  use Murlsh::MustRevalidate, :patterns => %r{^#{Regexp.escape(feed_url.path)}$}
33
33
 
34
34
  use Rack::Static, :urls => %w{/css/ /img/ /js/}, :root => 'public'
35
- use Rack::Static, :urls => %w{/atom.atom /podcast.rss /rss.rss}
35
+ use Rack::Static, :urls => %w{/atom.atom /m3u.m3u /podcast.rss /rss.rss}
36
36
 
37
37
  use Rack::Rewrite do
38
38
  r301 '/atom.xml', feed_url.to_s
data/config.yaml CHANGED
@@ -7,12 +7,11 @@ css_files:
7
7
  - css/screen.css
8
8
  db_file: murlsh.db
9
9
  feed_file: atom.atom
10
- flickr_api_key:
11
10
  gravatar_size: 32
12
11
  js_files:
13
- - js/jquery-1.4.4.min.js
12
+ - js/jquery-1.5.min.js
14
13
  - js/jquery.jgrowl_compressed.js
15
- - js/twitter-text-1.0.4.js
14
+ - js/twitter-text-1.3.1.js
16
15
  - js/js.js
17
16
  meta_tag_description: URLs found interesting by Matthew M. Boedicker
18
17
  meta_tag_verify-v1:
@@ -22,13 +21,14 @@ num_posts_page: 25
22
21
  page_title: mmb url share
23
22
  pubsubhubbub_hubs: []
24
23
 
24
+ quick_search:
25
+ audio: .mp3 .ogg
26
+ code: code.google.com github.com
27
+ images: .png .gif .jpeg .jpg
28
+ tweets: twitter.com
29
+ video: hulu.com vimeo.com www.ted.com youtube.com
30
+ wikipedia: wikipedia.org
25
31
  root_url: http://urls.matthewm.boedicker.org/
26
32
  show_names: true
27
33
  thumbnail_max_side: 90
28
34
  user_agent: murlsh (http://github.com/mmb/murlsh)
29
- predefined_searches:
30
- audio: \.(mp3|ogg)$
31
- code: (code\.google|github)\.com/
32
- images: \.(png|gif|jpe?g)$
33
- tweets: twitter\.com/
34
- video: (youtube|vimeo)\.com/
data/lib/murlsh/ask.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Murlsh
2
+
3
+ module_function
4
+
5
+ # Ask the user a question and return the answer.
6
+ def ask(prompt, default=nil)
7
+ default_given = !default.to_s.empty?
8
+ print "#{prompt} "
9
+ print "[#{default}] " if default_given
10
+ answer = $stdin.gets.strip
11
+ answer = default if answer.empty? and default_given
12
+ answer
13
+ end
14
+
15
+ end
@@ -0,0 +1,33 @@
1
+ require 'fileutils'
2
+
3
+ require 'murlsh'
4
+
5
+ module Murlsh
6
+
7
+ module_function
8
+
9
+ # Recursive copy from sources to destination but ask before overwriting.
10
+ #
11
+ # Options are passed into FileUtils.mkdir_p FileUtils.copy.
12
+ def cp_r_safe(sources, dest, options)
13
+ sources.each do |source|
14
+ new = File.join(dest, File.split(File.expand_path(source)).last)
15
+
16
+ if File.directory?(source)
17
+ FileUtils.mkdir_p(new, options)
18
+ cp_r_safe(Dir.entries(source).
19
+ reject { |f| %w{. ..}.include?(f) }.
20
+ map { |f| File.join(source, f) }, new, options)
21
+ else
22
+ answer = if File.exists?(new)
23
+ Murlsh.ask("#{new} exists. Overwrite?", 'n')
24
+ else
25
+ 'y'
26
+ end
27
+
28
+ FileUtils.copy(source, new, options) if answer == 'y'
29
+ end
30
+ end
31
+ end
32
+
33
+ end
data/lib/murlsh/doc.rb CHANGED
@@ -1,23 +1,8 @@
1
1
  module Murlsh
2
2
 
3
- # Nokogiri / Hpricot doc mixin.
3
+ # Nokogiri doc mixin.
4
4
  module Doc
5
5
 
6
- # Get the character set of the document.
7
- def charset
8
- %w{content-type Content-Type}.each do |ct|
9
- content_type = at("meta[@http-equiv='#{ct}']")
10
- unless content_type.nil?
11
- content = content_type['content']
12
- unless content.nil?
13
- charset = content[/charset=([\w.:-]+)/, 1]
14
- return charset if charset
15
- end
16
- end
17
- end
18
- nil
19
- end
20
-
21
6
  # Check a list of xpaths in order and yield and return the node matching
22
7
  # the first one that is not nil
23
8
  def xpath_search(xpaths)
@@ -0,0 +1,28 @@
1
+ require 'fileutils'
2
+
3
+ require 'murlsh'
4
+
5
+ module Murlsh
6
+
7
+ MurlshRoot = File.join(File.dirname(__FILE__), '..', '..')
8
+
9
+ module_function
10
+
11
+ # Install a murlsh site to a web directory.
12
+ #
13
+ # Copies files that are different per-site to make a site instance.
14
+ def install(dest_dir)
15
+ Murlsh.cp_r_safe(
16
+ %w{
17
+ .htaccess
18
+ Rakefile
19
+ config.ru
20
+ config.yaml
21
+ plugins/
22
+ public/
23
+ }.map { |x| File.join(MurlshRoot, x) }, dest_dir, :verbose => true)
24
+
25
+ FileUtils.mkdir_p(File.join(dest_dir, 'tmp'), :verbose => true)
26
+ end
27
+
28
+ end
data/lib/murlsh/plugin.rb CHANGED
@@ -9,8 +9,6 @@ module Murlsh
9
9
  # run arguments (config hash)
10
10
  # * avatar - called to get an avatar url from an email md5 sum
11
11
  # run arguments (avatar url, url, config hash)
12
- # * html_parse - called to parse HTML using something like Hpricot or Nokogiri
13
- # run arguments (parseable)
14
12
  # * url_display_add - called to display additional information after urls
15
13
  # run arguments (markup builder, url, config hash)
16
14
  class Plugin
@@ -1,3 +1,5 @@
1
+ require 'treetop'
2
+
1
3
  module Murlsh
2
4
 
3
5
  # Search conditions builder for ActiveRecord conditions.
@@ -8,9 +10,17 @@ module Murlsh
8
10
  # Search conditions builder for ActiveRecord conditions.
9
11
  def conditions
10
12
  if q
13
+ parser = Murlsh::SearchGrammarParser.new
14
+ tokens = parser.parse(q).content
11
15
  search_cols = %w{name title url}
12
- [search_cols.map { |x| "MURLSHMATCH(#{x}, ?)" }.join(' OR ')].push(
13
- *[q] * search_cols.size)
16
+
17
+ likes = []
18
+ params = []
19
+ search_cols.product(tokens).each do |col,tok|
20
+ likes << "#{col} LIKE ?"
21
+ params << "%#{tok}%"
22
+ end
23
+ [likes.join(' OR ')].push(*params)
14
24
  else
15
25
  []
16
26
  end
@@ -0,0 +1,225 @@
1
+ # Autogenerated from a Treetop grammar. Edits may be lost.
2
+
3
+
4
+ module Murlsh
5
+
6
+ module SearchGrammar
7
+ include Treetop::Runtime
8
+
9
+ def root
10
+ @root ||= :query
11
+ end
12
+
13
+ module Query0
14
+ def content; elements.map { |e| e.content }.compact.uniq; end
15
+ end
16
+
17
+ def _nt_query
18
+ start_index = index
19
+ if node_cache[:query].has_key?(index)
20
+ cached = node_cache[:query][index]
21
+ if cached
22
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
23
+ @index = cached.interval.end
24
+ end
25
+ return cached
26
+ end
27
+
28
+ s0, i0 = [], index
29
+ loop do
30
+ i1 = index
31
+ r2 = _nt_quoted_string
32
+ if r2
33
+ r1 = r2
34
+ else
35
+ r3 = _nt_whitespace
36
+ if r3
37
+ r1 = r3
38
+ else
39
+ r4 = _nt_string
40
+ if r4
41
+ r1 = r4
42
+ else
43
+ @index = i1
44
+ r1 = nil
45
+ end
46
+ end
47
+ end
48
+ if r1
49
+ s0 << r1
50
+ else
51
+ break
52
+ end
53
+ end
54
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
55
+ r0.extend(Query0)
56
+
57
+ node_cache[:query][start_index] = r0
58
+
59
+ r0
60
+ end
61
+
62
+ module QuotedString0
63
+ end
64
+
65
+ module QuotedString1
66
+ def content
67
+ result = text_value[1..-2]
68
+ result.empty? ? nil : result
69
+ end
70
+ end
71
+
72
+ def _nt_quoted_string
73
+ start_index = index
74
+ if node_cache[:quoted_string].has_key?(index)
75
+ cached = node_cache[:quoted_string][index]
76
+ if cached
77
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
78
+ @index = cached.interval.end
79
+ end
80
+ return cached
81
+ end
82
+
83
+ i0, s0 = index, []
84
+ if has_terminal?('"', false, index)
85
+ r1 = instantiate_node(SyntaxNode,input, index...(index + 1))
86
+ @index += 1
87
+ else
88
+ terminal_parse_failure('"')
89
+ r1 = nil
90
+ end
91
+ s0 << r1
92
+ if r1
93
+ s2, i2 = [], index
94
+ loop do
95
+ if has_terminal?('\G[^"]', true, index)
96
+ r3 = true
97
+ @index += 1
98
+ else
99
+ r3 = nil
100
+ end
101
+ if r3
102
+ s2 << r3
103
+ else
104
+ break
105
+ end
106
+ end
107
+ r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
108
+ s0 << r2
109
+ if r2
110
+ if has_terminal?('"', false, index)
111
+ r4 = instantiate_node(SyntaxNode,input, index...(index + 1))
112
+ @index += 1
113
+ else
114
+ terminal_parse_failure('"')
115
+ r4 = nil
116
+ end
117
+ s0 << r4
118
+ end
119
+ end
120
+ if s0.last
121
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
122
+ r0.extend(QuotedString0)
123
+ r0.extend(QuotedString1)
124
+ else
125
+ @index = i0
126
+ r0 = nil
127
+ end
128
+
129
+ node_cache[:quoted_string][start_index] = r0
130
+
131
+ r0
132
+ end
133
+
134
+ module Whitespace0
135
+ def content; end;
136
+ end
137
+
138
+ def _nt_whitespace
139
+ start_index = index
140
+ if node_cache[:whitespace].has_key?(index)
141
+ cached = node_cache[:whitespace][index]
142
+ if cached
143
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
144
+ @index = cached.interval.end
145
+ end
146
+ return cached
147
+ end
148
+
149
+ s0, i0 = [], index
150
+ loop do
151
+ if has_terminal?('\G[\\s]', true, index)
152
+ r1 = true
153
+ @index += 1
154
+ else
155
+ r1 = nil
156
+ end
157
+ if r1
158
+ s0 << r1
159
+ else
160
+ break
161
+ end
162
+ end
163
+ if s0.empty?
164
+ @index = i0
165
+ r0 = nil
166
+ else
167
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
168
+ r0.extend(Whitespace0)
169
+ end
170
+
171
+ node_cache[:whitespace][start_index] = r0
172
+
173
+ r0
174
+ end
175
+
176
+ module String0
177
+ def content; text_value; end
178
+ end
179
+
180
+ def _nt_string
181
+ start_index = index
182
+ if node_cache[:string].has_key?(index)
183
+ cached = node_cache[:string][index]
184
+ if cached
185
+ cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
186
+ @index = cached.interval.end
187
+ end
188
+ return cached
189
+ end
190
+
191
+ s0, i0 = [], index
192
+ loop do
193
+ if has_terminal?('\G[^\\s]', true, index)
194
+ r1 = true
195
+ @index += 1
196
+ else
197
+ r1 = nil
198
+ end
199
+ if r1
200
+ s0 << r1
201
+ else
202
+ break
203
+ end
204
+ end
205
+ if s0.empty?
206
+ @index = i0
207
+ r0 = nil
208
+ else
209
+ r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
210
+ r0.extend(String0)
211
+ end
212
+
213
+ node_cache[:string][start_index] = r0
214
+
215
+ r0
216
+ end
217
+
218
+ end
219
+
220
+ class SearchGrammarParser < Treetop::Runtime::CompiledParser
221
+ include SearchGrammar
222
+ end
223
+
224
+
225
+ end
@@ -0,0 +1,34 @@
1
+ module Murlsh
2
+
3
+ grammar SearchGrammar
4
+
5
+ rule query
6
+ (quoted_string / whitespace / string)* {
7
+ def content; elements.map { |e| e.content }.compact.uniq; end
8
+ }
9
+ end
10
+
11
+ rule quoted_string
12
+ '"' [^"]* '"' {
13
+ def content
14
+ result = text_value[1..-2]
15
+ result.empty? ? nil : result
16
+ end
17
+ }
18
+ end
19
+
20
+ rule whitespace
21
+ [\s]+ {
22
+ def content; end;
23
+ }
24
+ end
25
+
26
+ rule string
27
+ [^\s]+ {
28
+ def content; text_value; end
29
+ }
30
+ end
31
+
32
+ end
33
+
34
+ end