murlsh 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +3 -0
  2. data/README.textile +25 -2
  3. data/Rakefile +37 -5
  4. data/VERSION +1 -1
  5. data/bin/murlsh +3 -3
  6. data/config.ru +10 -4
  7. data/config.yaml +14 -10
  8. data/lib/murlsh/atom_feed.rb +5 -4
  9. data/lib/murlsh/auth.rb +6 -7
  10. data/lib/murlsh/dispatch.rb +25 -23
  11. data/lib/murlsh/doc.rb +14 -6
  12. data/lib/murlsh/etag_add_encoding.rb +27 -0
  13. data/lib/murlsh/flickr_server.rb +54 -0
  14. data/lib/murlsh/markup.rb +18 -1
  15. data/lib/murlsh/plugin.rb +2 -0
  16. data/lib/murlsh/sqlite3_adapter.rb +3 -1
  17. data/lib/murlsh/time_ago.rb +27 -0
  18. data/lib/murlsh/uri.rb +3 -1
  19. data/lib/murlsh/uri_ask.rb +13 -10
  20. data/lib/murlsh/url.rb +11 -4
  21. data/lib/murlsh/url_body.rb +21 -31
  22. data/lib/murlsh/url_server.rb +1 -2
  23. data/lib/murlsh/yaml_ordered_hash.rb +22 -0
  24. data/lib/murlsh.rb +4 -18
  25. data/murlsh.gemspec +22 -5
  26. data/plugins/add_post_50_update_feed.rb +4 -2
  27. data/plugins/add_post_50_update_rss.rb +37 -0
  28. data/plugins/add_post_60_notify_hubs.rb +3 -2
  29. data/plugins/add_pre_50_lookup_content_type_title.rb +3 -1
  30. data/plugins/time_50_ago.rb +16 -0
  31. data/plugins/via_50_domain.rb +36 -0
  32. data/public/css/screen.css +5 -6
  33. data/public/js/js.js +142 -94
  34. data/spec/atom_feed_spec.rb +21 -20
  35. data/spec/auth_spec.rb +8 -6
  36. data/spec/dispatch_spec.rb +26 -0
  37. data/spec/doc_spec.rb +27 -0
  38. data/spec/markup_spec.rb +3 -1
  39. data/spec/uri_ask_spec.rb +5 -3
  40. data/spec/uri_spec.rb +3 -1
  41. data/spec/url_spec.rb +52 -0
  42. data/spec/xhtml_response_spec.rb +3 -1
  43. data/spec/yaml_ordered_hash_spec.rb +28 -0
  44. metadata +37 -9
  45. data/lib/murlsh/time.rb +0 -20
data/.gitignore CHANGED
@@ -1,3 +1,6 @@
1
1
  *~
2
+ \#*#
3
+ \.#*
4
+ *\.gem
2
5
  public/css/*.gen.css
3
6
  public/js/*.gen.js
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
- Phusion Passenger Setup:
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 db:init user:add
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 second parameter or hardcoded here
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'].collect { |x| "public/#{x}" }, "\n")
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'].collect { |x| "public/#{x}" } )
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.6.1
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
- collect { |f| File.join(source, f) }, new, options)
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}.collect { |x|
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 db:init user:add
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
- require 'rubygems'
4
- require 'murlsh'
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
- description: URLs found interesting by Matthew M. Boedicker
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
- google_verify:
8
+ flickr_api_key:
7
9
  gravatar_size: 32
8
- num_posts_feed: 25
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
@@ -1,7 +1,8 @@
1
- require 'rubygems'
2
- require 'builder'
1
+ %w{
2
+ uri
3
3
 
4
- require 'uri'
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.collect { |mu| mu.time }.max.xmlschema)
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
- require 'rubygems'
2
- require 'bcrypt'
1
+ %w{
2
+ csv
3
+ digest/md5
3
4
 
4
- require 'csv'
5
- require 'digest/md5'
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)
@@ -1,12 +1,8 @@
1
1
  %w{
2
- murlsh
3
-
4
- rubygems
5
2
  active_record
6
3
  rack
7
- sqlite3
8
4
 
9
- yaml
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 config hash and database connection.
18
- def initialize
19
- @config = YAML.load_file('config.yaml')
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
- @db = ActiveRecord::Base.connection.instance_variable_get(:@connection)
20
+ db = ActiveRecord::Base.connection.instance_variable_get(:@connection)
26
21
 
27
- @url_server = Murlsh::UrlServer.new(@config, @db)
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
- require 'rubygems'
2
- require 'hpricot'
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
- # Find the title of the document.
25
- def title
26
- %w{//html/head/title //head/title //html/title //title}.each do |xpath|
27
- return (self/xpath).first.inner_html unless (self/xpath).first.nil?
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.collect { |k,v| URI.escape("#{k}=#{v}") }.join('&')
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
@@ -1,4 +1,6 @@
1
- require 'active_record/connection_adapters/sqlite3_adapter'
1
+ %w{
2
+ active_record/connection_adapters/sqlite3_adapter
3
+ }.each { |m| require m }
2
4
 
3
5
  class ActiveRecord::ConnectionAdapters::SQLite3Adapter
4
6
 
@@ -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
@@ -1,4 +1,6 @@
1
- require 'uri'
1
+ %w{
2
+ uri
3
+ }.each { |m| require m }
2
4
 
3
5
  # Extra methods added to URI class.
4
6
  class URI::Generic
@@ -1,12 +1,13 @@
1
- require 'net/http'
2
- require 'net/https'
3
- require 'open-uri'
4
- require 'uri'
1
+ %w{
2
+ net/http
3
+ net/https
4
+ open-uri
5
+ uri
5
6
 
6
- require 'rubygems'
7
- require 'hpricot'
8
- require 'htmlentities'
9
- require 'iconv'
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
- @title = HTMLEntities.new.decode(Iconv.conv('utf-8',
56
- doc.charset || f.charset, doc.title))
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
- require 'rubygems'
2
- require 'active_record'
1
+ %w{
2
+ uri
3
3
 
4
- require 'uri'
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
- read_attribute(:title) || read_attribute(:url) || 'title missing'
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