murlsh 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/README.textile +25 -2
- data/Rakefile +37 -5
- data/VERSION +1 -1
- data/bin/murlsh +3 -3
- data/config.ru +10 -4
- data/config.yaml +14 -10
- data/lib/murlsh/atom_feed.rb +5 -4
- data/lib/murlsh/auth.rb +6 -7
- data/lib/murlsh/dispatch.rb +25 -23
- data/lib/murlsh/doc.rb +14 -6
- data/lib/murlsh/etag_add_encoding.rb +27 -0
- data/lib/murlsh/flickr_server.rb +54 -0
- data/lib/murlsh/markup.rb +18 -1
- data/lib/murlsh/plugin.rb +2 -0
- data/lib/murlsh/sqlite3_adapter.rb +3 -1
- data/lib/murlsh/time_ago.rb +27 -0
- data/lib/murlsh/uri.rb +3 -1
- data/lib/murlsh/uri_ask.rb +13 -10
- data/lib/murlsh/url.rb +11 -4
- data/lib/murlsh/url_body.rb +21 -31
- data/lib/murlsh/url_server.rb +1 -2
- data/lib/murlsh/yaml_ordered_hash.rb +22 -0
- data/lib/murlsh.rb +4 -18
- data/murlsh.gemspec +22 -5
- data/plugins/add_post_50_update_feed.rb +4 -2
- data/plugins/add_post_50_update_rss.rb +37 -0
- data/plugins/add_post_60_notify_hubs.rb +3 -2
- data/plugins/add_pre_50_lookup_content_type_title.rb +3 -1
- data/plugins/time_50_ago.rb +16 -0
- data/plugins/via_50_domain.rb +36 -0
- data/public/css/screen.css +5 -6
- data/public/js/js.js +142 -94
- data/spec/atom_feed_spec.rb +21 -20
- data/spec/auth_spec.rb +8 -6
- data/spec/dispatch_spec.rb +26 -0
- data/spec/doc_spec.rb +27 -0
- data/spec/markup_spec.rb +3 -1
- data/spec/uri_ask_spec.rb +5 -3
- data/spec/uri_spec.rb +3 -1
- data/spec/url_spec.rb +52 -0
- data/spec/xhtml_response_spec.rb +3 -1
- data/spec/yaml_ordered_hash_spec.rb +28 -0
- metadata +37 -9
- data/lib/murlsh/time.rb +0 -20
data/.gitignore
CHANGED
data/README.textile
CHANGED
@@ -11,9 +11,15 @@ Simple site for a small group of people to share or archive urls.
|
|
11
11
|
* plug-in interface
|
12
12
|
* PubSubHubbub notification
|
13
13
|
|
14
|
+
!http://static.mmb.s3.amazonaws.com/murlsh_screenshot.jpg!
|
15
|
+
|
16
|
+
!http://static.mmb.s3.amazonaws.com/murlsh_iphone_screenshot.jpg!
|
17
|
+
|
14
18
|
See "http://urls.matthewm.boedicker.org/":http://urls.matthewm.boedicker.org/ for example.
|
15
19
|
|
16
|
-
|
20
|
+
h1. Installation
|
21
|
+
|
22
|
+
h2. Phusion Passenger
|
17
23
|
|
18
24
|
<pre>
|
19
25
|
<code>
|
@@ -28,8 +34,25 @@ In the web directory:
|
|
28
34
|
<code>
|
29
35
|
murlsh
|
30
36
|
edit config.yaml
|
31
|
-
rake
|
37
|
+
rake init
|
32
38
|
</code>
|
33
39
|
</pre>
|
34
40
|
|
41
|
+
h1. PubSubHubbub
|
42
|
+
|
43
|
+
Murlsh can notify "PubSubHubbub":http://code.google.com/p/pubsubhubbub/ hubs
|
44
|
+
when a new url is added by adding them to config.yaml. The pubsubhubbub_hubs
|
45
|
+
key is a list of hashes in the following format:
|
46
|
+
|
47
|
+
<pre>
|
48
|
+
<code>
|
49
|
+
pubsubhubbub_hubs:
|
50
|
+
- publish_url: http://pubsubhubbub.appspot.com/publish
|
51
|
+
subscribe_url: http://pubsubhubbub.appspot.com/
|
52
|
+
</code>
|
53
|
+
</pre>
|
54
|
+
|
55
|
+
publish_url is where the notifications get sent
|
56
|
+
subscribe_url is what gets put in the feed as link rel="hub"
|
57
|
+
|
35
58
|
Questions and comments: "matthewm@boedicker.org":mailto:matthewm@boedicker.org
|
data/Rakefile
CHANGED
@@ -8,8 +8,6 @@ pp
|
|
8
8
|
uri
|
9
9
|
yaml
|
10
10
|
|
11
|
-
rubygems
|
12
|
-
|
13
11
|
flog
|
14
12
|
spec/rake/spectask
|
15
13
|
sqlite3
|
@@ -19,6 +17,21 @@ murlsh
|
|
19
17
|
|
20
18
|
config = YAML.load_file('config.yaml')
|
21
19
|
|
20
|
+
desc 'Initialize a new installation.'
|
21
|
+
task :init => %w{db:init user:add compress} do
|
22
|
+
puts <<-eos
|
23
|
+
|
24
|
+
Things you might want to do now:
|
25
|
+
|
26
|
+
- visit #{config['root_url']} in a browser
|
27
|
+
- 'rake post_sh > url_post.sh' to generate a shell script for posting urls
|
28
|
+
|
29
|
+
eos
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Combine and compress static files.'
|
33
|
+
task :compress => %w{css:compress js:compress}
|
34
|
+
|
22
35
|
desc "Test remote content type fetch for a URL and show errors."
|
23
36
|
task :content_type, :url do |t, args|
|
24
37
|
puts URI(args.url).extend(Murlsh::UriAsk).content_type(:failproof => false,
|
@@ -96,6 +109,14 @@ end
|
|
96
109
|
desc "Run test suite."
|
97
110
|
Spec::Rake::SpecTask.new('test') do |t|
|
98
111
|
t.spec_files = FileList['spec/*_spec.rb']
|
112
|
+
t.spec_opts = %w{--color}
|
113
|
+
# list of places to check for unicode_formatter.rb and use it if found
|
114
|
+
%w{unicode_formatter.rb}.map { |x| File.expand_path(x) }.each do |f|
|
115
|
+
if File.exists?(f)
|
116
|
+
t.spec_opts.push(*%W{--require #{f} --format UnicodeFormatter})
|
117
|
+
break
|
118
|
+
end
|
119
|
+
end
|
99
120
|
t.verbose = true
|
100
121
|
t.warning = true
|
101
122
|
end
|
@@ -161,7 +182,7 @@ task :post_sh do
|
|
161
182
|
|
162
183
|
URL="$1"
|
163
184
|
VIA="$2"
|
164
|
-
AUTH="$3" # password can be passed as
|
185
|
+
AUTH="$3" # password can be passed as third parameter or hardcoded here
|
165
186
|
|
166
187
|
curl \\
|
167
188
|
--data-urlencode "url=${URL}" \\
|
@@ -189,7 +210,7 @@ namespace :css do
|
|
189
210
|
|
190
211
|
desc 'Combine and compress css.'
|
191
212
|
task :compress => ['public/css'] do
|
192
|
-
combined = cat(config['css_files'].
|
213
|
+
combined = cat(config['css_files'].map { |x| "public/#{x}" }, "\n")
|
193
214
|
|
194
215
|
md5sum = Digest::MD5.hexdigest(combined)
|
195
216
|
|
@@ -204,6 +225,7 @@ namespace :css do
|
|
204
225
|
|
205
226
|
unless config['css_compressed'] == compressed_url
|
206
227
|
config['css_compressed'] = compressed_url
|
228
|
+
config.extend(Murlsh::YamlOrderedHash)
|
207
229
|
open('config.yaml', 'w') { |f| YAML.dump(config, f) }
|
208
230
|
puts "updated config with css_compressed = #{compressed_url}"
|
209
231
|
end
|
@@ -217,7 +239,7 @@ namespace :js do
|
|
217
239
|
|
218
240
|
desc 'Combine and compress javascript.'
|
219
241
|
task :compress => ['public/js'] do
|
220
|
-
combined = cat(config['js_files'].
|
242
|
+
combined = cat(config['js_files'].map { |x| "public/#{x}" } )
|
221
243
|
|
222
244
|
compressed = Net::HTTP.post_form(
|
223
245
|
URI.parse('http://closure-compiler.appspot.com/compile'), {
|
@@ -240,11 +262,20 @@ namespace :js do
|
|
240
262
|
|
241
263
|
unless config['js_compressed'] == compressed_url
|
242
264
|
config['js_compressed'] = compressed_url
|
265
|
+
config.extend(Murlsh::YamlOrderedHash)
|
243
266
|
open('config.yaml', 'w') { |f| YAML.dump(config, f) }
|
244
267
|
puts "updated config with js_compressed = #{compressed_url}"
|
245
268
|
end
|
246
269
|
end
|
247
270
|
|
271
|
+
desc 'Run javascript through jslint.'
|
272
|
+
task :lint do
|
273
|
+
%{public/js/js.js}.each do |jsf|
|
274
|
+
puts jsf
|
275
|
+
puts `rhino http://www.jslint.com/rhino/jslint.js #{jsf}`
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
248
279
|
end
|
249
280
|
|
250
281
|
def ask(prompt, sep=':')
|
@@ -269,6 +300,7 @@ begin
|
|
269
300
|
builder 2.1.2
|
270
301
|
hpricot 0.8.1
|
271
302
|
htmlentities 4.2.0
|
303
|
+
json 1.2.3
|
272
304
|
rack 1.0.0
|
273
305
|
sqlite3-ruby 1.2.1
|
274
306
|
}.each_slice(2) { |g,v| gemspec.add_dependency(g, ">= #{v}") }
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.0
|
data/bin/murlsh
CHANGED
@@ -17,7 +17,7 @@ def cp_r_safe(sources, dest, options)
|
|
17
17
|
FileUtils.mkdir_p(new, options)
|
18
18
|
cp_r_safe(Dir.entries(source).
|
19
19
|
reject { |f| %w{. ..}.include?(f) }.
|
20
|
-
|
20
|
+
map { |f| File.join(source, f) }, new, options)
|
21
21
|
else
|
22
22
|
answer = if File.exists?(new)
|
23
23
|
ask("#{new} exists. Overwrite?", 'n')
|
@@ -32,7 +32,7 @@ def cp_r_safe(sources, dest, options)
|
|
32
32
|
end
|
33
33
|
|
34
34
|
cp_r_safe(
|
35
|
-
%w{.htaccess config.ru config.yaml plugins/ public/ Rakefile}.
|
35
|
+
%w{.htaccess config.ru config.yaml plugins/ public/ Rakefile}.map { |x|
|
36
36
|
File.join(File.dirname(__FILE__), '..', x) }, '.', :verbose => true)
|
37
37
|
|
38
38
|
FileUtils.mkdir_p('tmp', :verbose => true)
|
@@ -41,5 +41,5 @@ puts <<eos
|
|
41
41
|
Next steps:
|
42
42
|
|
43
43
|
edit config.yaml
|
44
|
-
rake
|
44
|
+
rake init
|
45
45
|
eos
|
data/config.ru
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
%w{
|
4
|
+
yaml
|
5
|
+
|
6
|
+
murlsh
|
7
|
+
}.each { |m| require m }
|
5
8
|
|
6
9
|
# use Rack::ShowExceptions
|
7
10
|
use Rack::ConditionalGet
|
11
|
+
use Murlsh::EtagAddEncoding
|
8
12
|
use Rack::Deflater
|
9
13
|
use Rack::Static, :urls => %w{/css /js /swf}, :root => 'public'
|
10
|
-
use Rack::Static, :urls => %w{/atom.xml}
|
14
|
+
use Rack::Static, :urls => %w{/atom.xml /rss.xml}
|
15
|
+
|
16
|
+
config = YAML.load_file('config.yaml')
|
11
17
|
|
12
|
-
run Murlsh::Dispatch.new
|
18
|
+
run Murlsh::Dispatch.new(config)
|
data/config.yaml
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
---
|
2
2
|
auth_file: murlsh_users
|
3
|
-
|
3
|
+
css_files:
|
4
|
+
- css/jquery.jgrowl.css
|
5
|
+
- css/screen.css
|
4
6
|
db_file: murlsh.db
|
5
7
|
feed_file: atom.xml
|
6
|
-
|
8
|
+
flickr_api_key:
|
7
9
|
gravatar_size: 32
|
8
|
-
|
9
|
-
num_posts_page: 100
|
10
|
-
page_title: mmb url share
|
11
|
-
root_url: http://urls.matthewm.boedicker.org/
|
12
|
-
css_files:
|
13
|
-
- css/jquery.jgrowl.css
|
14
|
-
- css/screen.css
|
15
|
-
js_files:
|
10
|
+
js_files:
|
16
11
|
- js/jquery-1.4.2.min.js
|
17
12
|
- js/jquery.cookie.js
|
18
13
|
- js/jquery.jgrowl_compressed.js
|
19
14
|
- js/js.js
|
15
|
+
meta_tag_description: URLs found interesting by Matthew M. Boedicker
|
16
|
+
meta_tag_verify-v1:
|
17
|
+
meta_tag_viewport: width=device-width,minimum-scale=1.0,maximum-scale=1.0
|
18
|
+
num_posts_feed: 25
|
19
|
+
num_posts_page: 100
|
20
|
+
page_title: mmb url share
|
20
21
|
pubsubhubbub_hubs: []
|
22
|
+
|
23
|
+
root_url: http://urls.matthewm.boedicker.org/
|
24
|
+
show_names: true
|
data/lib/murlsh/atom_feed.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
%w{
|
2
|
+
uri
|
3
3
|
|
4
|
-
|
4
|
+
builder
|
5
|
+
}.each { |m| require m }
|
5
6
|
|
6
7
|
module Murlsh
|
7
8
|
|
@@ -44,7 +45,7 @@ module Murlsh
|
|
44
45
|
xm.link(:href => URI.join(@root_url, @filename), :rel => 'self')
|
45
46
|
@hubs.each { |hub| xm.link(:href => hub, :rel => 'hub') }
|
46
47
|
xm.title(@title)
|
47
|
-
xm.updated(entries.
|
48
|
+
xm.updated(entries.map(&:time).max.xmlschema)
|
48
49
|
entries.each do |mu|
|
49
50
|
xm.entry {
|
50
51
|
xm.author { xm.name(mu.name) }
|
data/lib/murlsh/auth.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
%w{
|
2
|
+
csv
|
3
|
+
digest/md5
|
3
4
|
|
4
|
-
|
5
|
-
require
|
5
|
+
bcrypt
|
6
|
+
}.each { |m| require m }
|
6
7
|
|
7
8
|
module Murlsh
|
8
9
|
|
@@ -16,9 +17,7 @@ module Murlsh
|
|
16
17
|
# See Rakefile for user maintenance tasks.
|
17
18
|
class Auth
|
18
19
|
|
19
|
-
def initialize(file)
|
20
|
-
@file = file
|
21
|
-
end
|
20
|
+
def initialize(file); @file = file; end
|
22
21
|
|
23
22
|
# Authenticate a user by password. Return their name and email if correct.
|
24
23
|
def auth(password)
|
data/lib/murlsh/dispatch.rb
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
%w{
|
2
|
-
murlsh
|
3
|
-
|
4
|
-
rubygems
|
5
2
|
active_record
|
6
3
|
rack
|
7
|
-
sqlite3
|
8
4
|
|
9
|
-
|
5
|
+
murlsh
|
10
6
|
}.each { |m| require m }
|
11
7
|
|
12
8
|
module Murlsh
|
@@ -14,34 +10,40 @@ module Murlsh
|
|
14
10
|
# Dispatch requests.
|
15
11
|
class Dispatch
|
16
12
|
|
17
|
-
# Set up
|
18
|
-
def initialize
|
19
|
-
@config =
|
20
|
-
@url_root = URI(@config.fetch('root_url')).path
|
13
|
+
# Set up database connection and dispatch table.
|
14
|
+
def initialize(config)
|
15
|
+
@config = config
|
21
16
|
|
22
17
|
ActiveRecord::Base.establish_connection(
|
23
18
|
:adapter => 'sqlite3', :database => @config.fetch('db_file'))
|
24
19
|
|
25
|
-
|
20
|
+
db = ActiveRecord::Base.connection.instance_variable_get(:@connection)
|
26
21
|
|
27
|
-
|
22
|
+
url_server = Murlsh::UrlServer.new(@config, db)
|
23
|
+
flickr_server = Murlsh::FlickrServer.new(@config)
|
24
|
+
|
25
|
+
root_path = URI(@config.fetch('root_url')).path
|
26
|
+
|
27
|
+
@dispatch = [
|
28
|
+
[/^GET #{root_path}(url)?$/, url_server.method(:get)],
|
29
|
+
[/^POST #{root_path}(url)?$/, url_server.method(:post)],
|
30
|
+
[/^GET \/flickr$/, flickr_server.method(:get)],
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Figure out which method will handle request.
|
35
|
+
def dispatch(req)
|
36
|
+
method_match = @dispatch.find do |rule|
|
37
|
+
rule[0].match("#{req.request_method} #{req.path}")
|
38
|
+
end
|
39
|
+
|
40
|
+
method_match ? method_match[1] : self.method(:not_found)
|
28
41
|
end
|
29
42
|
|
30
43
|
# Rack call.
|
31
44
|
def call(env)
|
32
|
-
dispatch = {
|
33
|
-
['GET', @url_root] => [@url_server, :get],
|
34
|
-
['POST', @url_root] => [@url_server, :post],
|
35
|
-
['GET', "#{@url_root}url"] => [@url_server, :get],
|
36
|
-
['POST', "#{@url_root}url"] => [@url_server, :post],
|
37
|
-
}
|
38
|
-
dispatch.default = [self, :not_found]
|
39
|
-
|
40
45
|
req = Rack::Request.new(env)
|
41
|
-
|
42
|
-
obj, meth = dispatch[[req.request_method, req.path]]
|
43
|
-
|
44
|
-
obj.send(meth, req).finish
|
46
|
+
dispatch(req).call(req).finish
|
45
47
|
end
|
46
48
|
|
47
49
|
# Called if the request is not found.
|
data/lib/murlsh/doc.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
%w{
|
2
|
+
hpricot
|
3
|
+
}.each { |m| require m }
|
3
4
|
|
4
5
|
module Murlsh
|
5
6
|
|
@@ -21,14 +22,21 @@ module Murlsh
|
|
21
22
|
nil
|
22
23
|
end
|
23
24
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
# Check a list of xpaths in order and return the inner html of the first
|
26
|
+
# one that is not nil.
|
27
|
+
def xpath_search(xpaths)
|
28
|
+
xpaths.each do |xpath|
|
29
|
+
selection = (self/xpath).first
|
30
|
+
return selection.inner_html unless selection.nil?
|
28
31
|
end
|
29
32
|
nil
|
30
33
|
end
|
31
34
|
|
35
|
+
# Get the title of the document.
|
36
|
+
def title
|
37
|
+
xpath_search(%w{//html/head/title //head/title //html/title //title})
|
38
|
+
end
|
39
|
+
|
32
40
|
end
|
33
41
|
|
34
42
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
%w{
|
2
|
+
rack/utils
|
3
|
+
}.each { |m| require m }
|
4
|
+
|
5
|
+
module Murlsh
|
6
|
+
|
7
|
+
# Rack middleware to add the content encoding to the end of the ETag because
|
8
|
+
# ETag must be different for different representations.
|
9
|
+
class EtagAddEncoding
|
10
|
+
|
11
|
+
def initialize(app); @app = app; end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
status, headers, body = @app.call(env)
|
15
|
+
|
16
|
+
headers = Rack::Utils::HeaderHash.new(headers)
|
17
|
+
|
18
|
+
if headers['ETag']
|
19
|
+
headers['ETag'].sub!(/(")?$/, "#{headers['Content-Encoding']}\\1")
|
20
|
+
end
|
21
|
+
|
22
|
+
[status, headers, body]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
%w{
|
2
|
+
open-uri
|
3
|
+
|
4
|
+
json
|
5
|
+
rack
|
6
|
+
}.each { |m| require m }
|
7
|
+
|
8
|
+
module Murlsh
|
9
|
+
|
10
|
+
# Proxy for Flickr rest API flickr.photos.getinfo call to support conditional
|
11
|
+
# get.
|
12
|
+
#
|
13
|
+
# Passes along query string with api key added, returns result from Flickr
|
14
|
+
# with content type set to application/json and last modified header set.
|
15
|
+
class FlickrServer
|
16
|
+
|
17
|
+
def initialize(config); @config = config; end
|
18
|
+
|
19
|
+
# Proxy a flickr.photos.getinfo request to the Flickr rest API.
|
20
|
+
def get(req)
|
21
|
+
resp = Rack::Response.new
|
22
|
+
|
23
|
+
if @config['flickr_api_key']
|
24
|
+
params = req.params.merge('api_key' => @config['flickr_api_key'])
|
25
|
+
|
26
|
+
q = params.map { |k,v| "#{URI.encode(k)}=#{URI.encode(v)}" }.join('&')
|
27
|
+
|
28
|
+
json_wrapped = open("http://api.flickr.com/services/rest/?#{q}") do |f|
|
29
|
+
# for some reason Firefox will not cache if it's text/plain, which is
|
30
|
+
# what Flickr returns
|
31
|
+
resp['Content-Type'] = 'application/json'
|
32
|
+
f.read
|
33
|
+
end
|
34
|
+
|
35
|
+
json = /.+?\((.+)\)/.match(json_wrapped)[1]
|
36
|
+
|
37
|
+
json_parsed = JSON.parse(json)
|
38
|
+
|
39
|
+
resp['Last-Modified'] = Time.at(
|
40
|
+
json_parsed['photo']['dates']['lastupdate'].to_i).httpdate
|
41
|
+
|
42
|
+
resp.body = json_wrapped
|
43
|
+
|
44
|
+
resp
|
45
|
+
else
|
46
|
+
Rack::Response.new('flickr_api_key not set in config.yaml', 500,
|
47
|
+
{ 'Content-Type' => 'text/plain' })
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/murlsh/markup.rb
CHANGED
@@ -89,7 +89,24 @@ module Murlsh
|
|
89
89
|
# Query string builder. Takes hash of query string variables.
|
90
90
|
def build_query(h)
|
91
91
|
h.empty? ? '' :
|
92
|
-
'?' + h.
|
92
|
+
'?' + h.map { |k,v| URI.escape("#{k}=#{v}") }.join('&')
|
93
|
+
end
|
94
|
+
|
95
|
+
# Form input builder.
|
96
|
+
def form_input(options)
|
97
|
+
if options[:id]
|
98
|
+
if options[:label]
|
99
|
+
label_suffix = options[:label_suffix] || ':'
|
100
|
+
label("#{options[:label]}#{label_suffix}", :for => options[:id])
|
101
|
+
end
|
102
|
+
options[:name] ||= options[:id]
|
103
|
+
end
|
104
|
+
|
105
|
+
options.delete(:label)
|
106
|
+
|
107
|
+
input({
|
108
|
+
:type => 'text',
|
109
|
+
}.merge(options))
|
93
110
|
end
|
94
111
|
|
95
112
|
private
|
data/lib/murlsh/plugin.rb
CHANGED
@@ -9,6 +9,8 @@ module Murlsh
|
|
9
9
|
# run arguments (config hash)
|
10
10
|
# * hostrec - called to post process the domain that shows after links
|
11
11
|
# run arguments (domain, url, title)
|
12
|
+
# * time - called to convert the time of a post into a string for display
|
13
|
+
# run arguments (time)
|
12
14
|
class Plugin
|
13
15
|
|
14
16
|
# Called when a plugin class inherits from this class (the way plugins
|
@@ -0,0 +1,27 @@
|
|
1
|
+
%w{
|
2
|
+
time
|
3
|
+
}.each { |m| require m }
|
4
|
+
|
5
|
+
module Murlsh
|
6
|
+
|
7
|
+
# Mixin for time class to add fuzzy ago method.
|
8
|
+
module TimeAgo
|
9
|
+
|
10
|
+
# Return a string of the approximate amount of time that has passed since
|
11
|
+
# this time.
|
12
|
+
def ago
|
13
|
+
days_ago = (Time.now.to_i - to_i) / 86400
|
14
|
+
|
15
|
+
case days_ago
|
16
|
+
when 0 then 'today'
|
17
|
+
when 1 then 'yesterday'
|
18
|
+
when (2..4) then "#{days_ago} days ago"
|
19
|
+
when (5..7) then strftime('%a %e %b')
|
20
|
+
when (8..180) then strftime('%e %b').strip
|
21
|
+
else strftime('%e %b %Y').strip
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/murlsh/uri.rb
CHANGED
data/lib/murlsh/uri_ask.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
%w{
|
2
|
+
net/http
|
3
|
+
net/https
|
4
|
+
open-uri
|
5
|
+
uri
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
require
|
7
|
+
hpricot
|
8
|
+
htmlentities
|
9
|
+
iconv
|
10
|
+
}.each { |m| require m }
|
10
11
|
|
11
12
|
module Murlsh
|
12
13
|
|
@@ -52,8 +53,10 @@ module Murlsh
|
|
52
53
|
self.open(options[:headers]) do |f|
|
53
54
|
doc = Hpricot(f).extend(Murlsh::Doc)
|
54
55
|
|
55
|
-
|
56
|
-
|
56
|
+
if doc.title and !doc.title.empty?
|
57
|
+
@title = HTMLEntities.new.decode(Iconv.conv('utf-8',
|
58
|
+
doc.charset || f.charset, doc.title))
|
59
|
+
end
|
57
60
|
end
|
58
61
|
end
|
59
62
|
end
|
data/lib/murlsh/url.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
%w{
|
2
|
+
uri
|
3
3
|
|
4
|
-
|
4
|
+
active_record
|
5
|
+
}.each { |m| require m }
|
5
6
|
|
6
7
|
module Murlsh
|
7
8
|
|
@@ -10,7 +11,13 @@ module Murlsh
|
|
10
11
|
|
11
12
|
# Get the title of this url.
|
12
13
|
def title
|
13
|
-
|
14
|
+
ta = read_attribute(:title)
|
15
|
+
ta = nil if ta and ta.empty?
|
16
|
+
|
17
|
+
ua = read_attribute(:url)
|
18
|
+
ua = nil if ua and ua.empty?
|
19
|
+
|
20
|
+
ta || ua || 'title missing'
|
14
21
|
end
|
15
22
|
|
16
23
|
# Title with whitespace compressed and leading and trailing whitespace
|