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.
- 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
|