atom-tools 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/atom/text.rb CHANGED
@@ -18,19 +18,57 @@ module Atom
18
18
  #
19
19
  # This content of this element can be retrieved in different formats, see #html and #xml
20
20
  class Text < Atom::Element
21
- attrb :type
21
+ atom_attrb :type
22
22
 
23
- def initialize value, name # :nodoc:
24
- @content = value
25
- @content ||= "" # in case of nil
26
- self["type"] = "text"
23
+ include AttrEl
27
24
 
28
- super name
25
+ on_parse_root do |e,x|
26
+ type = e.type
27
+
28
+ if x.is_a? REXML::Element
29
+ if type == 'xhtml'
30
+ x = x.elements['div']
31
+ raise Atom::ParseError, 'xhtml content needs div wrapper' unless x
32
+
33
+ c = x.dup
34
+ else
35
+ c = x[0] ? x[0].value : nil
36
+ end
37
+ else
38
+ c = x.to_s
39
+ end
40
+
41
+ e.instance_variable_set("@content", c)
42
+ end
43
+
44
+ on_build do |e,x|
45
+ c = e.instance_variable_get('@content')
46
+
47
+ if c.respond_to? :parent
48
+ x << c.dup
49
+ elsif c
50
+ x.text = c.to_s
51
+ end
52
+ end
53
+
54
+ def initialize value = nil
55
+ super()
56
+
57
+ @content = if value.respond_to? :to_xml
58
+ value.to_xml[0]
59
+ elsif value
60
+ value
61
+ else
62
+ ''
63
+ end
64
+ end
65
+
66
+ def type
67
+ @type ? @type : 'text'
29
68
  end
30
69
 
31
- # convenient, but not overly useful. see #html instead.
32
70
  def to_s
33
- if self["type"] == "xhtml"
71
+ if type == 'xhtml' and @content and @content.name == 'div'
34
72
  @content.children.to_s
35
73
  else
36
74
  @content.to_s
@@ -56,10 +94,12 @@ module Atom
56
94
  # If self["type"] is "html" and Hpricot is installed, it will
57
95
  # be converted to XHTML first.
58
96
  def xml
97
+ xml = REXML::Element.new 'div'
98
+
59
99
  if self["type"] == "xhtml"
60
- @content.children
100
+ @content.children.each { |child| xml << child }
61
101
  elsif self["type"] == "text"
62
- [self.to_s]
102
+ xml.text = self.to_s
63
103
  elsif self["type"] == "html"
64
104
  begin
65
105
  require "hpricot"
@@ -68,54 +108,32 @@ module Atom
68
108
  end
69
109
 
70
110
  fixed = Hpricot(self.to_s, :xhtml_strict => true)
71
- REXML::Document.new("<div>#{fixed}</div>").root.children
111
+ xml = REXML::Document.new("<div>#{fixed}</div>").root
72
112
  else
73
113
  # XXX check that @type is an XML mimetype and parse it
74
114
  raise "I haven't implemented this yet"
75
115
  end
116
+
117
+ xml
76
118
  end
77
119
 
78
120
  def inspect # :nodoc:
79
121
  "'#{to_s}'##{self['type']}"
80
122
  end
81
123
 
82
- def []= key, value # :nodoc:
83
- if key == "type"
84
- unless valid_type? value
85
- raise "atomTextConstruct type '#{value}' is meaningless"
86
- end
87
-
88
- if value == "xhtml"
89
- begin
90
- parse_xhtml_content
91
- rescue REXML::ParseException
92
- raise "#{@content.inspect} can't be parsed as XML"
93
- end
94
- end
124
+ def type= value
125
+ unless valid_type? value
126
+ raise Atom::ParseError, "atomTextConstruct type '#{value}' is meaningless"
95
127
  end
96
128
 
97
- super(key, value)
98
- end
99
-
100
- def to_element # :nodoc:
101
- e = super
102
-
103
- if self["type"] == "text"
104
- e.attributes.delete "type"
105
- end
106
-
107
- # this should be done via inheritance
108
- c = convert_contents e
109
-
110
- if c.is_a? String
111
- e.text = c
112
- elsif c.is_a? REXML::Element
113
- e << c.dup
114
- else
115
- raise RuntimeError, "atom:#{local_name} can't contain type #{@content.class}"
129
+ @type = value
130
+ if @type == "xhtml"
131
+ begin
132
+ parse_xhtml_content
133
+ rescue REXML::ParseException
134
+ raise Atom::ParseError, "#{@content.inspect} can't be parsed as XML"
135
+ end
116
136
  end
117
-
118
- e
119
137
  end
120
138
 
121
139
  private
@@ -123,10 +141,8 @@ module Atom
123
141
  def convert_contents e
124
142
  if self["type"] == "xhtml"
125
143
  @content
126
- elsif self["type"] == "text" or self["type"].nil?
127
- REXML::Text.normalize(@content.to_s)
128
- elsif self["type"] == "html"
129
- @content.to_s.gsub(/&/, "&amp;")
144
+ elsif self["type"] == "text" or self["type"].nil? or self["type"] == "html"
145
+ @content.to_s
130
146
  end
131
147
  end
132
148
 
@@ -164,23 +180,18 @@ module Atom
164
180
  # * the "type" attribute can be an arbitrary media type
165
181
  # * there is a "src" attribute which is an IRI that points to the content of the entry (in which case the content element will be empty)
166
182
  class Content < Atom::Text
167
- attrb :src
183
+ is_atom_element :content
168
184
 
169
- def html
170
- if self["src"]
171
- ""
172
- else
173
- super
174
- end
175
- end
185
+ atom_attrb :src
176
186
 
177
- def to_element
178
- if self["src"]
179
- element_super = Element.instance_method(:to_element)
180
- return element_super.bind(self).call
181
- end
187
+ def src= v
188
+ @content = nil
182
189
 
183
- super
190
+ if self.base
191
+ @src = (self.base.to_uri + v).to_s
192
+ else
193
+ @src = v
194
+ end
184
195
  end
185
196
 
186
197
  private
@@ -202,4 +213,9 @@ module Atom
202
213
  s
203
214
  end
204
215
  end
216
+
217
+ class Title < Atom::Text; is_atom_element :title; end
218
+ class Subtitle < Atom::Text; is_atom_element :subtitle; end
219
+ class Summary < Atom::Text; is_atom_element :summary; end
220
+ class Rights < Atom::Text; is_atom_element :rights; end
205
221
  end
data/lib/atom/tools.rb ADDED
@@ -0,0 +1,163 @@
1
+ require 'atom/collection'
2
+
3
+ # methods to make writing commandline Atom tools more convenient
4
+
5
+ module Atom::Tools
6
+ # fetch and parse a Feed URL, returning the entries found
7
+ def http_to_entries url, complete_feed = false, http = Atom::HTTP.new
8
+ feed = Atom::Feed.new url, http
9
+
10
+ if complete_feed
11
+ feed.get_everything!
12
+ else
13
+ feed.update!
14
+ end
15
+
16
+ feed.entries
17
+ end
18
+
19
+ # parse a directory of entries
20
+ def dir_to_entries path
21
+ raise ArgumentError, "#{path} is not a directory" unless File.directory? path
22
+
23
+ Dir[path+'/*.atom'].map do |e|
24
+ Atom::Entry.parse(File.read(e))
25
+ end
26
+ end
27
+
28
+ # parse a Feed on stdin
29
+ def stdin_to_entries
30
+ Atom::Feed.parse($stdin).entries
31
+ end
32
+
33
+ # POSTs an Array of Atom::Entrys to an Atom Collection
34
+ def entries_to_http entries, url, http = Atom::HTTP.new
35
+ coll = Atom::Collection.new url, http
36
+
37
+ entries.each { |entry| coll.post! entry }
38
+ end
39
+
40
+ # saves an Array of Atom::Entrys to a directory
41
+ def entries_to_dir entries, path
42
+ if File.exists? path
43
+ raise "directory #{path} already exists"
44
+ else
45
+ Dir.mkdir path
46
+ end
47
+
48
+ entries.each do |entry|
49
+ e = entry.to_s
50
+
51
+ new_filename = path + '/0x' + MD5.new(e).hexdigest[0,8] + '.atom'
52
+
53
+ File.open(new_filename, 'w') { |f| f.write e }
54
+ end
55
+ end
56
+
57
+ # dumps an Array of Atom::Entrys into a Feed on stdout
58
+ def entries_to_stdout entries
59
+ feed = Atom::Feed.new
60
+
61
+ entries.each do |entry|
62
+ puts entry.inspect
63
+ feed.entries << entry
64
+ end
65
+
66
+ puts feed.to_s
67
+ end
68
+
69
+ # turns a collection of Atom Entries into an Array of Atom::Entrys
70
+ #
71
+ # source: a URL, a directory or "-" for an Atom Feed on stdin
72
+ # options:
73
+ # :complete - whether to fetch the complete logical feed
74
+ # :user - username to use for HTTP requests (if required)
75
+ # :pass - password to use for HTTP requests (if required)
76
+ def parse_input source, options
77
+ entries = if source.match /^http/
78
+ http = Atom::HTTP.new
79
+
80
+ setup_http http, options
81
+
82
+ http_to_entries source, options[:complete], http
83
+ elsif source == '-'
84
+ stdin_to_entries
85
+ else
86
+ dir_to_entries source
87
+ end
88
+
89
+ if options[:verbose]
90
+ entries.each do |entry|
91
+ puts "got #{entry.title}"
92
+ end
93
+ end
94
+
95
+ entries
96
+ end
97
+
98
+ # turns an Array of Atom::Entrys into a collection of Atom Entries
99
+ #
100
+ # entries: an Array of Atom::Entrys pairs
101
+ # dest: a URL, a directory or "-" for an Atom Feed on stdout
102
+ # options:
103
+ # :user - username to use for HTTP requests (if required)
104
+ # :pass - password to use for HTTP requests (if required)
105
+ def write_output entries, dest, options
106
+ if dest.match /^http/
107
+ http = Atom::HTTP.new
108
+
109
+ setup_http http, options
110
+
111
+ entries_to_http entries, dest, http
112
+ elsif dest == '-'
113
+ entries_to_stdout entries
114
+ else
115
+ entries_to_dir entries, dest
116
+ end
117
+ end
118
+
119
+ # set up some common OptionParser settings
120
+ def atom_options opts, options
121
+ opts.on('-u', '--user NAME', 'username for HTTP auth') { |u| options[:user] = u }
122
+
123
+ opts.on_tail('-h', '--help', 'show this usage statement') { |h| puts opts; exit }
124
+
125
+ opts.on_tail('-p', '--password [PASSWORD]', 'password for HTTP auth') do |p|
126
+ options[:pass] = p
127
+ end
128
+ end
129
+
130
+
131
+ # obtain a password from the TTY, hiding the user's input
132
+ # this will fail if you don't have the program 'stty'
133
+ def obtain_password
134
+ i = o = File.open('/dev/tty', 'w+')
135
+
136
+ o.print 'Password: '
137
+
138
+ # store original settings
139
+ state = `stty -F /dev/tty -g`
140
+
141
+ # don't echo input
142
+ system "stty -F /dev/tty -echo"
143
+
144
+ p = i.gets.chomp
145
+
146
+ # restore original settings
147
+ system "stty -F /dev/tty #{state}"
148
+
149
+ p
150
+ end
151
+
152
+ def setup_http http, options
153
+ if options[:user]
154
+ http.user = options[:user]
155
+
156
+ unless options[:pass]
157
+ options[:pass] = obtain_password
158
+ end
159
+
160
+ http.pass = options[:pass]
161
+ end
162
+ end
163
+ end
@@ -9,23 +9,23 @@ FEED.update!
9
9
  class TestOrderConformance < Test::Unit::TestCase
10
10
  def test_0
11
11
  entry = FEED.entries[0]
12
-
12
+
13
13
  assert_equal "tag:example.org,2006:atom/conformance/element_order/1", entry.id
14
14
  assert_equal "Simple order, nothing fancy", entry.title.to_s
15
15
  assert_equal "Simple ordering, nothing fancy", entry.summary.to_s
16
16
  assert_equal Time.parse("2006-01-26T09:20:01Z"), entry.updated
17
-
17
+
18
18
  assert_alternate_href(entry, "http://www.snellspace.com/public/alternate")
19
19
  end
20
20
 
21
21
  def test_1
22
22
  entry = FEED.entries[1]
23
-
23
+
24
24
  assert_equal "tag:example.org,2006:atom/conformance/element_order/2", entry.id
25
25
  assert_equal "Same as the first, only mixed up a bit", entry.title.to_s
26
26
  assert_equal "Same as the first, only mixed up a bit", entry.summary.to_s
27
27
  assert_equal Time.parse("2006-01-26T09:20:02Z"), entry.updated
28
-
28
+
29
29
  assert_alternate_href(entry, "http://www.snellspace.com/public/alternate")
30
30
  end
31
31
 
@@ -34,8 +34,9 @@ class TestOrderConformance < Test::Unit::TestCase
34
34
  entry = FEED.entries[2]
35
35
 
36
36
  # both links should be available, but it's up to you to choose which one to use
37
+
37
38
  assert_link_href(entry, "http://www.snellspace.com/public/alternate") { |l| l["rel"] == "alternate" and l["type"] == nil }
38
-
39
+
39
40
  assert_link_href(entry, "http://www.snellspace.com/public/alternate2") { |l| l["rel"] == "alternate" and l["type"] == "text/plain" }
40
41
  end
41
42
 
@@ -65,7 +66,7 @@ class TestOrderConformance < Test::Unit::TestCase
65
66
  # ^-- quoted summary is a typo, source is last
66
67
  def test_5
67
68
  entry = FEED.entries[5]
68
-
69
+
69
70
  assert_equal "tag:example.org,2006:atom/conformance/element_order/6", entry.id
70
71
  assert_equal "Entry with a source last", entry.title.to_s
71
72
  assert_equal Time.parse("2006-01-26T09:20:06Z"), entry.updated
@@ -91,18 +92,18 @@ class TestOrderConformance < Test::Unit::TestCase
91
92
  assert_equal "tag:example.org,2006:atom/conformance/element_order/8", entry.id
92
93
  assert_equal "Atom elements in an extension element", entry.title.to_s
93
94
  assert_equal Time.parse("2006-01-26T09:20:08Z"), entry.updated
94
-
95
+
95
96
  assert_alternate_href(entry, "http://www.snellspace.com/public/alternate")
96
97
  end
97
-
98
+
98
99
  # Atom elements in an extension element
99
100
  def test_8
100
101
  entry = FEED.entries[8]
101
-
102
+
102
103
  assert_equal "tag:example.org,2006:atom/conformance/element_order/9", entry.id
103
104
  assert_equal "Atom elements in an extension element", entry.title.to_s
104
105
  assert_equal Time.parse("2006-01-26T09:20:09Z"), entry.updated
105
-
106
+
106
107
  assert_alternate_href(entry, "http://www.snellspace.com/public/alternate")
107
108
  end
108
109
 
@@ -25,18 +25,18 @@ class TestTitleConformance < Test::Unit::TestCase
25
25
  feed.update!
26
26
 
27
27
  entry = feed.entries.first
28
- assert_equal "html", entry.title["type"]
28
+ assert_equal "html", entry.title["type"]
29
29
  assert_equal "&lt;title>", entry.title.html
30
30
  end
31
31
 
32
32
  def test_html_entity
33
33
  url = "http://atomtests.philringnalda.com/tests/item/title/html-entity.atom"
34
-
34
+
35
35
  feed = Atom::Feed.new(url)
36
36
  feed.update!
37
37
 
38
38
  entry = feed.entries.first
39
- assert_equal "html", entry.title["type"]
39
+ assert_equal "html", entry.title["type"]
40
40
  assert_equal "&lt;title>", entry.title.html
41
41
  end
42
42
 
@@ -47,7 +47,7 @@ class TestTitleConformance < Test::Unit::TestCase
47
47
  feed.update!
48
48
 
49
49
  entry = feed.entries.first
50
- assert_equal "html", entry.title["type"]
50
+ assert_equal "html", entry.title["type"]
51
51
  assert_equal "&lt;title>", entry.title.html
52
52
  end
53
53
 
@@ -78,7 +78,7 @@ class TestTitleConformance < Test::Unit::TestCase
78
78
 
79
79
  feed = Atom::Feed.new(url)
80
80
  feed.update!
81
-
81
+
82
82
  entry = feed.entries.first
83
83
  assert_equal "text", entry.title["type"]
84
84
  assert_equal "&lt;title&gt;", entry.title.html
@@ -86,10 +86,10 @@ class TestTitleConformance < Test::Unit::TestCase
86
86
 
87
87
  def test_xhtml_entity
88
88
  url = "http://atomtests.philringnalda.com/tests/item/title/xhtml-entity.atom"
89
-
89
+
90
90
  feed = Atom::Feed.new(url)
91
91
  feed.update!
92
-
92
+
93
93
  entry = feed.entries.first
94
94
  assert_equal "xhtml", entry.title["type"]
95
95
  assert_equal "&lt;title>", entry.title.html
@@ -97,10 +97,10 @@ class TestTitleConformance < Test::Unit::TestCase
97
97
 
98
98
  def test_xhtml_ncr
99
99
  url = "http://atomtests.philringnalda.com/tests/item/title/xhtml-ncr.atom"
100
-
100
+
101
101
  feed = Atom::Feed.new(url)
102
102
  feed.update!
103
-
103
+
104
104
  entry = feed.entries.first
105
105
  assert_equal "xhtml", entry.title["type"]
106
106
  assert_equal "&#60;title>", entry.title.html