atom-tools 1.0.0 → 2.0.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/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