atom-tools 0.9.0 → 2.0.5

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 (93) hide show
  1. data/COPYING +3 -3
  2. data/README +9 -49
  3. data/Rakefile +65 -62
  4. data/bin/atom-cp +159 -0
  5. data/bin/atom-grep +78 -0
  6. data/bin/atom-post +73 -0
  7. data/bin/atom-purge +82 -0
  8. data/doc/classes/Atom/AttrEl.html +163 -0
  9. data/doc/classes/Atom/Author.html +2 -19
  10. data/doc/classes/Atom/AutodiscoveryFailure.html +111 -0
  11. data/doc/classes/Atom/Categories.html +168 -0
  12. data/doc/classes/Atom/Category.html +10 -1
  13. data/doc/classes/Atom/Collection.html +194 -91
  14. data/doc/classes/Atom/Content.html +42 -1
  15. data/doc/classes/Atom/Contributor.html +2 -8
  16. data/doc/classes/Atom/Control.html +126 -0
  17. data/doc/classes/Atom/Converters.html +571 -0
  18. data/doc/classes/Atom/DigestAuth.html +285 -0
  19. data/doc/classes/Atom/Element.html +673 -106
  20. data/doc/classes/Atom/FileCache.html +287 -0
  21. data/doc/classes/Atom/HTTP.html +211 -95
  22. data/doc/classes/Atom/HTTPResponse.html +190 -0
  23. data/doc/classes/Atom/HasCategories.html +187 -0
  24. data/doc/classes/Atom/HasLinks.html +168 -0
  25. data/doc/classes/Atom/Link.html +80 -3
  26. data/doc/classes/Atom/NilCache.html +202 -0
  27. data/doc/classes/Atom/ParseError.html +111 -0
  28. data/doc/classes/Atom/Parsers.html +311 -0
  29. data/doc/classes/Atom/Person.html +130 -0
  30. data/doc/classes/Atom/Rights.html +113 -0
  31. data/doc/classes/Atom/Service.html +298 -0
  32. data/doc/{files/lib/atom/yaml_rb.html → classes/Atom/Source.html} +26 -24
  33. data/doc/classes/Atom/Subtitle.html +113 -0
  34. data/doc/classes/Atom/Summary.html +113 -0
  35. data/doc/classes/Atom/Text.html +196 -53
  36. data/doc/classes/Atom/Title.html +113 -0
  37. data/doc/classes/Atom/Tools.html +525 -0
  38. data/doc/classes/Atom/Workspace.html +121 -0
  39. data/doc/classes/Object.html +204 -0
  40. data/doc/created.rid +1 -1
  41. data/doc/files/README.html +9 -49
  42. data/doc/files/lib/atom/cache_rb.html +302 -0
  43. data/doc/files/lib/atom/collection_rb.html +1 -2
  44. data/doc/files/lib/atom/element_rb.html +2 -1
  45. data/doc/files/lib/atom/entry_rb.html +2 -2
  46. data/doc/files/lib/atom/feed_rb.html +1 -2
  47. data/doc/files/lib/atom/http_rb.html +6 -1
  48. data/doc/files/lib/atom/{xml_rb.html → service_rb.html} +8 -6
  49. data/doc/files/lib/atom/text_rb.html +2 -1
  50. data/doc/files/lib/atom/{app_rb.html → tools_rb.html} +4 -6
  51. data/doc/fr_class_index.html +23 -3
  52. data/doc/fr_file_index.html +3 -3
  53. data/doc/fr_method_index.html +111 -36
  54. data/lib/atom/cache.rb +178 -0
  55. data/lib/atom/collection.rb +90 -40
  56. data/lib/atom/element.rb +569 -161
  57. data/lib/atom/entry.rb +63 -89
  58. data/lib/atom/feed.rb +65 -67
  59. data/lib/atom/http.rb +421 -49
  60. data/lib/atom/service.rb +106 -0
  61. data/lib/atom/text.rb +138 -61
  62. data/lib/atom/tools.rb +163 -0
  63. data/spec/entry_spec.rb +364 -0
  64. data/spec/ext_spec.rb +42 -0
  65. data/spec/feed_spec.rb +39 -0
  66. data/spec/fixtures/contacts-feed.xml +34 -0
  67. data/spec/fixtures/entry-w-ext.xml +15 -0
  68. data/spec/fixtures/entry-w-xml.xml +24 -0
  69. data/spec/fixtures/entry.xml +42 -0
  70. data/spec/fixtures/feed-w-ext.xml +33 -0
  71. data/spec/fixtures/service-w-xhtml-ns.xml +21 -0
  72. data/spec/fixtures/service.xml +36 -0
  73. data/spec/service_spec.rb +108 -0
  74. data/spec/spec_helper.rb +9 -0
  75. data/test/conformance/order.rb +11 -10
  76. data/test/conformance/title.rb +9 -9
  77. data/test/conformance/updated.rb +2 -1
  78. data/test/runtests.rb +0 -0
  79. data/test/test_constructs.rb +78 -8
  80. data/test/test_feed.rb +35 -29
  81. data/test/test_general.rb +3 -30
  82. data/test/test_http.rb +248 -34
  83. data/test/test_protocol.rb +143 -24
  84. data/test/test_xml.rb +145 -53
  85. metadata +116 -62
  86. data/bin/atom-client.rb +0 -246
  87. data/bin/atom-server.rb~ +0 -71
  88. data/doc/classes/Atom/App.html +0 -217
  89. data/doc/classes/Atom/Entry.html +0 -365
  90. data/doc/classes/Atom/Feed.html +0 -585
  91. data/lib/atom/app.rb +0 -87
  92. data/lib/atom/xml.rb +0 -200
  93. data/lib/atom/yaml.rb +0 -101
data/lib/atom/text.rb CHANGED
@@ -4,31 +4,101 @@ module XHTML
4
4
  NS = "http://www.w3.org/1999/xhtml"
5
5
  end
6
6
 
7
- module Atom
7
+ module Atom
8
8
  # An Atom::Element representing a text construct.
9
- # It has a single attribute, "type", which accepts values
10
- # "text", "html" and "xhtml"
11
-
9
+ # It has a single attribute, "type", which specifies how to interpret
10
+ # the element's content. Different types are:
11
+ #
12
+ # text:: a plain string, without any markup (default)
13
+ # html:: a chunk of HTML
14
+ # xhtml:: a chunk of *well-formed* XHTML
15
+ #
16
+ # You should set this attribute appropriately after you set a Text
17
+ # element (entry.content, entry.title or entry.summary).
18
+ #
19
+ # This content of this element can be retrieved in different formats, see #html and #xml
12
20
  class Text < Atom::Element
13
- attrb :type
14
-
15
- def initialize value, name # :nodoc:
16
- @content = value
17
- @content ||= "" # in case of nil
18
- self["type"] = "text"
19
-
20
- super name
21
+ atom_attrb :type
22
+
23
+ include AttrEl
24
+
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 = e.get_elem x, XHTML::NS, 'div'
31
+
32
+ raise Atom::ParseError, 'xhtml content needs div wrapper' unless x
33
+
34
+ c = x.dup
35
+
36
+ unless x.prefix.empty?
37
+ # content has a namespace prefix, strip prefixes from it and all
38
+ # XHTML children
39
+
40
+ REXML::XPath.each(c, './/xhtml:*', 'xhtml' => XHTML::NS) do |x|
41
+ x.name = x.name
42
+ end
43
+ end
44
+ elsif ['text', 'html'].include?(type)
45
+ c = x[0] ? x[0].value : nil
46
+ else
47
+ c = x
48
+ end
49
+ else
50
+ c = x.to_s
51
+ end
52
+
53
+ e.instance_variable_set("@content", c)
54
+ end
55
+
56
+ on_build do |e,x|
57
+ c = e.instance_variable_get('@content')
58
+ if c.respond_to? :parent
59
+ if c.is_a?(REXML::Element) && c.name == 'content' # && !c.text.strip == ''
60
+ # c
61
+ c.children.each do |child_element|
62
+ x.add_element(child_element) unless child_element.is_a?(REXML::Text) && child_element.to_s.strip == ''
63
+ end
64
+ # x.add_text('') # unless child_element.to_s.strip == ''
65
+ else
66
+ x << c.dup
67
+ end
68
+ elsif c
69
+ x.text = c.to_s
70
+ end
71
+ end
72
+
73
+ def initialize value = nil
74
+ super()
75
+
76
+ @content = if value.respond_to? :to_xml
77
+ value.to_xml[0]
78
+ elsif value
79
+ value
80
+ else
81
+ ''
82
+ end
83
+ end
84
+
85
+ def type
86
+ @type ? @type : 'text'
21
87
  end
22
88
 
23
89
  def to_s
24
- if self["type"] == "xhtml"
90
+ if type == 'xhtml' and @content and @content.name == 'div'
25
91
  @content.children.to_s
26
92
  else
27
93
  @content.to_s
28
94
  end
29
95
  end
30
96
 
31
- # returns a string suitable for dumping into an HTML document
97
+ # returns a string suitable for dumping into an HTML document.
98
+ # (or nil if that's impossible)
99
+ #
100
+ # if you're storing the content of a Text construct, you probably
101
+ # want this representation.
32
102
  def html
33
103
  if self["type"] == "xhtml" or self["type"] == "html"
34
104
  to_s
@@ -37,74 +107,64 @@ module Atom
37
107
  end
38
108
  end
39
109
 
40
- # attepts to parse the content and return it as an array of REXML::Elements
110
+ # attempts to parse the content of this element as XML and return it
111
+ # as an array of REXML::Elements.
112
+ #
113
+ # If self["type"] is "html" and Hpricot is installed, it will
114
+ # be converted to XHTML first.
41
115
  def xml
116
+ xml = REXML::Element.new 'div'
117
+
42
118
  if self["type"] == "xhtml"
43
- @content.children
119
+ @content.children.each { |child| xml << child }
44
120
  elsif self["type"] == "text"
45
- [self.to_s]
121
+ xml.text = self.to_s
122
+ elsif self["type"] == "html"
123
+ begin
124
+ require "hpricot"
125
+ rescue
126
+ raise "Turning HTML content into XML requires Hpricot."
127
+ end
128
+
129
+ fixed = Hpricot(self.to_s, :xhtml_strict => true)
130
+ xml = REXML::Document.new("<div>#{fixed}</div>").root
46
131
  else
47
- # XXX - hpricot goes here?
48
- raise "I haven't implemented this yet"
132
+ # Not XHTML, HTML, or text - return the REXML::Element, leave it up to the user to parse the content
133
+ xml = @content
49
134
  end
135
+
136
+ xml
50
137
  end
51
138
 
52
139
  def inspect # :nodoc:
53
140
  "'#{to_s}'##{self['type']}"
54
141
  end
55
142
 
56
- def []= key, value # :nodoc:
57
- if key == "type"
58
- unless valid_type? value
59
- raise "atomTextConstruct type '#{value}' is meaningless"
60
- end
61
-
62
- if value == "xhtml"
63
- begin
64
- parse_xhtml_content
65
- rescue REXML::ParseException
66
- raise "#{@content.inspect} can't be parsed as XML"
67
- end
68
- end
69
- end
70
-
71
- super(key, value)
72
- end
73
-
74
- def to_element # :nodoc:
75
- e = super
76
-
77
- if self["type"] == "text"
78
- e.attributes.delete "type"
143
+ def type= value
144
+ unless valid_type? value
145
+ raise Atom::ParseError, "atomTextConstruct type '#{value}' is meaningless"
79
146
  end
80
147
 
81
- # this should be done via inheritance
82
- unless self.class == Atom::Content and self["src"]
83
- c = convert_contents e
84
-
85
- if c.is_a? String
86
- e.text = c
87
- elsif c.is_a? REXML::Element
88
- e << c.dup
89
- else
90
- raise RuntimeError, "atom:#{local_name} can't contain type #{@content.class}"
148
+ @type = value
149
+ if @type == "xhtml"
150
+ begin
151
+ parse_xhtml_content
152
+ rescue REXML::ParseException
153
+ raise Atom::ParseError, "#{@content.inspect} can't be parsed as XML"
91
154
  end
92
155
  end
93
-
94
- e
95
156
  end
96
-
157
+
97
158
  private
159
+ # converts @content based on the value of self["type"]
98
160
  def convert_contents e
99
161
  if self["type"] == "xhtml"
100
162
  @content
101
- elsif self["type"] == "text" or self["type"].nil?
102
- REXML::Text.normalize(@content.to_s)
103
- elsif self["type"] == "html"
163
+ elsif self["type"] == "text" or self["type"].nil? or self["type"] == "html"
104
164
  @content.to_s
105
165
  end
106
166
  end
107
-
167
+
108
168
  def valid_type? type
109
169
  ["text", "xhtml", "html"].member? type
110
170
  end
@@ -137,9 +197,21 @@ module Atom
137
197
  # Atom::Content behaves the same as an Atom::Text, but for two things:
138
198
  #
139
199
  # * the "type" attribute can be an arbitrary media type
140
- # * 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)
200
+ # * there is a "src" attribute which is an URI that points to the content of the entry (in which case the content element will be empty)
141
201
  class Content < Atom::Text
142
- attrb :src
202
+ is_atom_element :content
203
+
204
+ atom_attrb :src
205
+
206
+ def src= v
207
+ @content = nil
208
+
209
+ if self.base
210
+ @src = (self.base.to_uri + v).to_s
211
+ else
212
+ @src = v
213
+ end
214
+ end
143
215
 
144
216
  private
145
217
  def valid_type? type
@@ -160,4 +232,9 @@ module Atom
160
232
  s
161
233
  end
162
234
  end
235
+
236
+ class Title < Atom::Text; is_atom_element :title; end
237
+ class Subtitle < Atom::Text; is_atom_element :subtitle; end
238
+ class Summary < Atom::Text; is_atom_element :summary; end
239
+ class Rights < Atom::Text; is_atom_element :rights; end
163
240
  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