murlsh 0.6.1 → 0.7.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 (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