murlsh 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.htaccess +1 -0
- data/README.textile +6 -12
- data/Rakefile +19 -17
- data/VERSION +1 -1
- data/bin/murlsh +2 -35
- data/config.ru +1 -1
- data/config.yaml +9 -9
- data/lib/murlsh/ask.rb +15 -0
- data/lib/murlsh/cp_r_safe.rb +33 -0
- data/lib/murlsh/doc.rb +1 -16
- data/lib/murlsh/install.rb +28 -0
- data/lib/murlsh/plugin.rb +0 -2
- data/lib/murlsh/search_conditions.rb +12 -2
- data/lib/murlsh/search_grammar.rb +225 -0
- data/lib/murlsh/search_grammar.treetop +34 -0
- data/lib/murlsh/uri_ask.rb +10 -14
- data/lib/murlsh/url_body.rb +33 -19
- data/lib/murlsh/url_result_set.rb +12 -2
- data/lib/murlsh.rb +4 -1
- data/murlsh.gemspec +26 -21
- data/plugins/add_post_50_update_m3u.rb +35 -0
- data/plugins/{add_pre_41_unajax_twitter.rb → add_pre_30_unajax_twitter.rb} +1 -1
- data/plugins/add_pre_35_url_clean.rb +21 -0
- data/plugins/add_pre_50_media_thumbnail.rb +6 -3
- data/plugins/add_pre_65_html_thumb.rb +1 -3
- data/plugins/url_display_add_60_via.rb +4 -1
- data/public/css/jquery.jgrowl.css +11 -7
- data/public/css/screen.css +3 -2
- data/public/js/jquery-1.5.min.js +16 -0
- data/public/js/jquery.jgrowl_compressed.js +40 -32
- data/public/js/js.js +3 -43
- data/public/js/{twitter-text-1.0.4.js → twitter-text-1.3.1.js} +25 -22
- data/spec/doc_spec.rb +10 -4
- metadata +75 -61
- data/lib/murlsh/sqlite3_adapter.rb +0 -13
- data/plugins/add_pre_60_flickr.rb +0 -27
- data/plugins/add_pre_60_vimeo.rb +0 -38
- data/plugins/html_parse_50_nokogiri.rb +0 -16
- data/public/js/comments.json +0 -1
- 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
|
-
|
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
|
-
*
|
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
|
-
|
102
|
-
|
103
|
-
|
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(:
|
168
|
-
email = ask(:
|
169
|
-
password = ask(:
|
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 = '
|
429
|
-
gemspec.description = '
|
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
|
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.
|
1
|
+
1.4.0
|
data/bin/murlsh
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require '
|
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
|
-
|
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.
|
12
|
+
- js/jquery-1.5.min.js
|
14
13
|
- js/jquery.jgrowl_compressed.js
|
15
|
-
- js/twitter-text-1.
|
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
|
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
|
-
|
13
|
-
|
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
|