ape 1.5.1 → 1.6.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/README CHANGED
@@ -29,7 +29,7 @@ If you've downloaded APE's source code you can execute it from the command line
29
29
  $ rake ape:go:text['service document uri', 'username', 'password']
30
30
  $ rake ape:go:atom['service document uri', 'username', 'password']
31
31
 
32
- The username and password paramenters are not mandatory, and the format response depends of the executed task.
32
+ The username and password parameters are not mandatory, and the format response depends on the executed task.
33
33
 
34
34
  == The Source
35
35
 
data/Rakefile CHANGED
@@ -2,6 +2,7 @@ require 'rake'
2
2
  require 'rake/clean'
3
3
  require 'rake/gempackagetask'
4
4
  require 'rake/testtask'
5
+ require 'rake/rdoctask'
5
6
 
6
7
  $:.unshift File.dirname(__FILE__) + '/lib'
7
8
  require 'ape'
@@ -11,9 +12,9 @@ def spec
11
12
  s.platform = Gem::Platform::RUBY
12
13
  s.name = 'ape'
13
14
  s.version = Ape::VERSION::STRING
14
- s.author = 'Tim Bray'
15
- s.email = 'tim.bray@sun.com'
16
- s.homepage = 'ape.rubyforge.org'
15
+ s.authors = ['Tim Bray', 'David Calavera']
16
+ s.email = ['tim.bray@sun.com', 'calavera@apache.org']
17
+ s.homepage = 'http://ape.rubyforge.org'
17
18
  s.summary = 'The Atom Protocol Exerciser'
18
19
 
19
20
  s.files = FileList['lib/**/*', 'samples/*', 'test/**/*', 'web/*',
@@ -21,7 +22,7 @@ def spec
21
22
  s.bindir = 'bin'
22
23
  s.executable = 'ape_server'
23
24
 
24
- s.has_rdoc = false
25
+ s.has_rdoc = true
25
26
  s.extra_rdoc_files = ['README', 'LICENSE']
26
27
 
27
28
  s.rubyforge_project = 'ape'
@@ -45,7 +46,7 @@ task :setup do
45
46
  installed = Gem::SourceIndex.from_installed_gems
46
47
  dependencies = spec.dependencies
47
48
  dependencies.select { |dep|
48
- installed.search(dep.name, dep.version_requirements).empty? }.each do |dep|
49
+ installed.find_name(dep.name, dep.version_requirements).empty? }.each do |dep|
49
50
  puts "Installing #{dep} ..."
50
51
  install_gem dep.name, "-v '#{dep.version_requirements.to_s}'"
51
52
  end
@@ -63,4 +64,11 @@ task :test => [:test_units]
63
64
  Rake::TestTask.new("test_units") do |t|
64
65
  t.test_files = FileList['test/unit/*test.rb']
65
66
  t.verbose = false
66
- end
67
+ end
68
+
69
+ # Rdoc ---------------------------------------------------------------
70
+
71
+ Rake::RDocTask.new do |t|
72
+ t.main = 'README'
73
+ t.rdoc_files.include('README', 'LICENSE', 'lib/**/*.rb')
74
+ end
@@ -20,7 +20,7 @@ parser = OptionParser.new do |opts|
20
20
  opts.separator ''
21
21
  opts.on('-a', '--address ADDRESS', 'Address to bind to', "default: #{OPTIONS[:host]}") { |v| OPTIONS[:host] = v }
22
22
  opts.on('-p', '--port PORT', 'Port to bind to', "default: #{OPTIONS[:port]}") { |v| OPTIONS[:port] = v }
23
- opts.on('-d', '--directory DIRECTORY', 'ape home directory', "default: #{Ape::Ape.home}") { |v| OPTIONS[:home] = v }
23
+ opts.on('-d', '--directory DIRECTORY', 'ape home directory', "default: #{::Ape.home}") { |v| OPTIONS[:home] = v }
24
24
  opts.on('-h', '--help', 'Displays this help') { puts opts; exit }
25
25
  opts.parse!(ARGV)
26
26
  end
data/lib/ape.rb CHANGED
@@ -4,7 +4,7 @@ $:.unshift File.dirname(__FILE__)
4
4
  module Ape
5
5
  require 'rubygems'
6
6
 
7
- Dir[File.dirname(__FILE__) + '/ape/*.rb'].each { |l| require l }
7
+ Dir[File.dirname(__FILE__) + '/ape/*.rb'].sort.each { |l| require l }
8
8
 
9
9
  @CONF = {}
10
10
 
@@ -12,32 +12,28 @@ module Ape
12
12
  @CONF
13
13
  end
14
14
 
15
- class Ape
16
- attr_reader :aperc, :reporter
17
-
18
- @@home = nil
19
- def self.home=(home)
20
- @@home = home
21
- end
15
+ def Ape.home=(home)
16
+ @CONF[:HOME] = home
17
+ end
22
18
 
23
- def self.home
24
- @@home || ENV["APE_HOME"] || File.join(home_directory,".ape")
25
- end
19
+ def Ape.home
20
+ @CONF[:HOME] || ENV["APE_HOME"] || File.join(home_directory,".ape")
21
+ end
26
22
 
27
- #recipe from cap
28
- def self.home_directory
29
- ENV["HOME"] || (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") || "/"
30
- end
31
-
23
+ def Ape.home_directory
24
+ ENV["HOME"] || (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") || "/"
25
+ end
26
+
27
+ class Ape
32
28
  # Creates an Ape instance with options given in the +args+ Hash.
33
29
  #
34
30
  # ==== Options
35
- # * :output - one of 'text' or 'html'. #report will output in this format. Defaults to 'html'.
31
+ # * :output - one of 'text' or 'html' or 'atom'. #report will output in this format. Defaults to 'html'.
36
32
  # * :debug - enables debug information at each step in the output
37
33
  def initialize(args = {})
38
34
  output = args[:output] || 'html'
39
35
  @reporter = Reporter.instance(output, args)
40
- load File.join(Ape.home, 'aperc') if File.exist?(File.join(Ape.home, 'aperc'))
36
+ load File.join(::Ape.home, 'aperc') if File.exist?(File.join(::Ape.home, 'aperc'))
41
37
  end
42
38
 
43
39
  # Checks the AtomPub server at +uri+ for sanity.
@@ -46,44 +42,54 @@ module Ape
46
42
  # * uri - the URI of the AtomPub server. Required.
47
43
  # * username - an optional username for authentication
48
44
  # * password - if a username is provided, a password is required. See Ape::Authent for more information.
45
+ # * service_doc - an optional service document. It'll be used instead of getting it from the uri.
49
46
  # * requested_e_coll - a preferred entry collection to check
50
47
  # * requested_m_coll - a preferred media collection to check
51
- def check(uri, username=nil, password=nil,
48
+ def check(uri, username=nil, password=nil, service_doc = nil,
52
49
  requested_e_coll = nil, requested_m_coll = nil)
53
50
 
54
51
  @authent = Authent.new(username, password)
55
- reporter.header = uri
52
+ @reporter.header = uri
56
53
  ::Ape.conf[:REQUESTED_ENTRY_COLLECTION] = requested_e_coll if requested_e_coll
57
54
  ::Ape.conf[:REQUESTED_MEDIA_COLLECTION] = requested_m_coll if requested_m_coll
58
55
  begin
59
- might_fail(uri)
56
+ might_fail(uri, service_doc)
60
57
  rescue Exception
61
- reporter.error(self, "Ouch! Ape fall down go boom; details: " +
58
+ @reporter.error(self, "Ouch! Ape fall down go boom; details: " +
62
59
  "#{$!}\n#{$!.class}\n#{$!.backtrace}")
63
60
  end
64
61
  end
65
62
 
66
- def might_fail(uri)
67
- service_validator = Validator.instance(:service_document, @reporter, @authent)
68
- service_validator.validate(:uri => uri)
69
- @service = service_validator.service_document
63
+ def might_fail(uri, service = nil)
64
+ unless (service)
65
+ service_validator = Validator.instance(:service_document, @reporter, @authent)
66
+ service_validator.validate(:uri => uri)
67
+ @service = service_validator.service_document
68
+ else
69
+ @service = service.instance_of?(String)?
70
+ REXML::Document.new(service, { :raw => nil }) : service
71
+ end
70
72
  @entry_collections = service_validator.entry_collections
71
73
  @media_collections = service_validator.media_collections
72
74
 
73
- if @entry_collections && true != ::Ape.conf[:ENTRY_VALIDATION_DISABLED]
74
- [:entry_posts, :sorting, :sanitization].each do |option|
75
- check_validator(option)
75
+ unless true == ::Ape.conf[:ENTRY_VALIDATION_DISABLED]
76
+ if @entry_collections
77
+ [:entry_posts, :sorting, :sanitization].each do |option|
78
+ check_validator(option)
79
+ end
80
+ else
81
+ @reporter.warning(self, "No collection for 'application/atom+xml;type=entry', won't test entry posting.")
76
82
  end
77
- else
78
- reporter.warning(self, "No collection for 'application/atom+xml;type=entry', won't test entry posting.")
79
83
  end
80
84
 
81
- if @media_collections && true != ::Ape.conf[:MEDIA_VALIDATION_DISABLED]
82
- [:media_posts, :media_linkage].each do |option|
83
- check_validator(option)
85
+ unless true == ::Ape.conf[:MEDIA_VALIDATION_DISABLED]
86
+ if @media_collections
87
+ [:media_posts, :media_linkage].each do |option|
88
+ check_validator(option)
89
+ end
90
+ else
91
+ @reporter.warning(self, "No collection for 'image/jpeg', won't test media posting.")
84
92
  end
85
- else
86
- reporter.warning(self, "No collection for 'image/jpeg', won't test media posting.")
87
93
  end
88
94
 
89
95
  #custom validators
@@ -92,6 +98,13 @@ module Ape
92
98
  break if !validator.validate(opts) && validator.deterministic?
93
99
  end
94
100
  end
101
+
102
+ def error_count
103
+ @reporter.errors.size
104
+ end
105
+ def warning_count
106
+ @reporter.warnings.size
107
+ end
95
108
 
96
109
  def report(output=STDOUT)
97
110
  @reporter.report(output)
@@ -1,10 +1,10 @@
1
- # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
1
+ # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
2
2
  # Use is subject to license terms - see file "LICENSE"
3
3
  require 'uri'
4
4
 
5
5
  module Ape
6
6
  class AtomURI
7
-
7
+
8
8
  def initialize base_uri
9
9
  if base_uri.kind_of? URI
10
10
  @base = base_uri
@@ -0,0 +1,67 @@
1
+ # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+
4
+ # Represents an <app:collection> element as found in an AtomPub Service Doc
5
+
6
+ module Ape
7
+ class CollElement
8
+
9
+ @@mime_re = %r{^(.*)/(.*)$}
10
+
11
+ attr_reader :title, :accept, :href, :xml
12
+
13
+ def CollElement::find_colls(source, doc_uri = nil)
14
+ els = REXML::XPath.match(source, '//app:collection', Names::XmlNamespaces)
15
+ els.map { |el| CollElement.new(el, doc_uri) }
16
+ end
17
+
18
+ def initialize(el, doc_uri = nil)
19
+ @xml = el
20
+ @accept = []
21
+ @title = REXML::XPath.first(el, './atom:title', Names::XmlNamespaces)
22
+
23
+ # sigh, RNC validation *should* take care of this
24
+ raise(SyntaxError, "Collection is missing required 'atom:title'") unless @title
25
+ @title = @title.texts.join
26
+
27
+ if doc_uri
28
+ uris = AtomURI.new(doc_uri)
29
+ @href = uris.absolutize(el.attributes['href'], el)
30
+ else
31
+ @href = el.attributes['href']
32
+ end
33
+
34
+ # now we have to go looking for the accept
35
+ @accept = REXML::XPath.match(@xml, './app:accept/(text)', Names::XmlNamespaces)
36
+ @accept = [ Names::AtomEntryMediaType ] if @accept.empty?
37
+ end
38
+
39
+ # check if the collection accepts a given mime type; watch out for wildcards
40
+ def accept?(mime_type)
41
+ if mime_type =~ @@mime_re
42
+ p1, p2 = $1, $2
43
+ else
44
+ return false # WTF?
45
+ end
46
+ @accept.each do |pat|
47
+ pat = pat.to_s # working around REXML ticket 164
48
+ if pat =~ @@mime_re
49
+ if ($1 == p1 || $1 == "*") && ($2 == p2 || $2 == "*")
50
+ return true
51
+ elsif ((pat == Names::AtomMediaType && mime_type == Names::AtomFeedMediaType) ||
52
+ (pat == Names::AtomFeedMediaType && mime_type == Names::AtomMediaType))
53
+ return true
54
+ end
55
+ end
56
+ end
57
+ return false
58
+ end
59
+
60
+ # the name is supposed to suggest multiple instances of "categories"
61
+ def catses
62
+ REXML::XPath.match(@xml, './app:categories', Names::XmlNamespaces)
63
+ end
64
+
65
+
66
+ end
67
+ end
@@ -1,47 +1,44 @@
1
1
  # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
2
2
  # Use is subject to license terms - see file "LICENSE"
3
+
4
+ # Represents an AtomPub collection, which offers feed-reading and resource-CRUD
5
+ # services
3
6
  require 'rexml/xpath'
4
7
 
5
8
  module Ape
6
9
  class Collection
7
- attr_reader :title, :accept, :href
8
-
9
- def initialize(input, doc_uri = nil)
10
- @input = input
11
- @accept = []
12
- @title = REXML::XPath.first(input, './atom:title', Names::XmlNamespaces)
13
10
 
14
- # sigh, RNC validation *should* take care of this
15
- raise(SyntaxError, "Collection is missing required 'atom:title'") unless @title
16
- @title = @title.texts.join
11
+ # The argument has to be an absolute URI
12
+ #
13
+ def initialize(uri, authent = nil)
14
+ @uri = uri
15
+ @authent = authent
16
+ end
17
17
 
18
- if doc_uri
19
- uris = AtomURI.new(doc_uri)
20
- @href = uris.absolutize(input.attributes['href'], input)
18
+ # Post a new element to this collection; return either an Ape::Entry or
19
+ # an error-message
20
+ #
21
+ # ==== Options
22
+ # * :data - element to post as a string
23
+ # * :type - content type. By default 'application/atom+xml;type=entry'
24
+ # * :slug - slug header
25
+ #
26
+ def post(opts = {})
27
+ return ':data argument not provided' unless opts[:data]
28
+
29
+ type = opts[:type] || Names::AtomEntryMediaType
30
+ @invoker = Poster.new(@uri, @authent)
31
+ @invoker['Slug'] = opts[:slug] if opts[:slug]
32
+
33
+ if @invoker.post(type, opts[:data])
34
+ @invoker.entry
21
35
  else
22
- @href = input.attributes['href']
36
+ @invoker.last_error
23
37
  end
24
-
25
- # now we have to go looking for the accept
26
- @accept = []
27
- REXML::XPath.each(input, './app:accept', Names::XmlNamespaces) do |a|
28
- @accept << a.texts.join
29
- end
30
-
31
- @accept = [ Names::AtomEntryMediaType ] if @accept.empty?
32
38
  end
33
-
34
- def to_s
35
- @input.to_s
36
- end
37
-
38
- def to_str
39
- to_s
40
- end
41
-
42
- # the name is supposed to suggest multiple instances of "categories"
43
- def catses
44
- REXML::XPath.match(@input, './app:categories', Names::XmlNamespaces)
39
+
40
+ def crumbs
41
+ return @invoker.crumbs
45
42
  end
46
43
  end
47
44
  end
@@ -10,18 +10,26 @@ module Ape
10
10
  class Entry
11
11
  # @element is the REXML dom
12
12
  # @base is the base URI if known
13
+ attr_reader :uri
13
14
 
14
- def initialize(node, uri = nil)
15
- if node.class == String
16
- @element = REXML::Document.new(node, { :raw => nil }).root
15
+ def initialize(opts)
16
+ if opts[:xml]
17
+ @element = opts[:xml]
18
+ elsif opts[:text]
19
+ @element = REXML::Document.new(opts[:text], { :raw => nil }).root
17
20
  else
18
- @element = node
21
+ raise(ArgumentError, "Neither :xml nor :text provided")
19
22
  end
20
- if uri
21
- @base = AtomURI.new(uri)
23
+
24
+ if opts[:uri]
25
+ @uri = opts[:uri]
26
+ @base = AtomURI.new(@uri)
22
27
  else
23
- @base = nil
28
+ @base = @uri = nil
24
29
  end
30
+
31
+ @authoritative = opts[:authoritative] || false
32
+ @etag = opts[:etag] || nil
25
33
  end
26
34
 
27
35
  def to_s
@@ -29,7 +37,7 @@ module Ape
29
37
  end
30
38
 
31
39
  def content_src
32
- content = REXML::XPath.first(@element, './atom:content', Names::XmlNamespaces)
40
+ content = REXML::XPath.first(@element, './atom:content[@src]', Names::XmlNamespaces)
33
41
  if content
34
42
  cs = content.attributes['src']
35
43
  cs = @base.absolutize(cs, @element) if @base
@@ -40,9 +48,8 @@ module Ape
40
48
 
41
49
  def get_child(field, namespace = nil)
42
50
  if (namespace)
43
- thisNS = {}
44
51
  prefix = 'NN'
45
- thisNS[prefix] = namespace
52
+ thisNS = { prefix => namespace }
46
53
  else
47
54
  prefix = 'atom'
48
55
  thisNS = Names::XmlNamespaces
@@ -147,5 +154,10 @@ module Ape
147
154
  end
148
155
  Nodes.each_node(node.getChildNodes) {|child| dump(child, depth+1)}
149
156
  end
157
+
158
+ def to_s
159
+ @element.to_s
160
+ end
161
+
150
162
  end
151
163
  end
@@ -63,7 +63,7 @@ module Ape
63
63
  reporter.info(self, "#{label} has no entries.")
64
64
  end
65
65
 
66
- entries += page_entries.map { |e| Entry.new(e, next_page)}
66
+ entries += page_entries.map { |e| Entry.new(:xml => e, :uri => next_page)}
67
67
 
68
68
  next_link = REXML::XPath.first(feed, "./atom:link[@rel=\"next\"]", Names::XmlNamespaces)
69
69
  if next_link
@@ -8,6 +8,8 @@ module Ape
8
8
 
9
9
  def initialize(uriString, authent)
10
10
  @last_error = nil
11
+
12
+ # XXX really need a way to turn crumbs on/off, mostly off
11
13
  @crumbs = Crumbs.new
12
14
  @uri = AtomURI.check(uriString)
13
15
  if (@uri.class == String)
@@ -15,6 +17,21 @@ module Ape
15
17
  end
16
18
  @authent = authent
17
19
  @authent_checker = 0
20
+ @headers = {}
21
+ end
22
+
23
+ #Add a new request header
24
+ def []=(name, val)
25
+ set_header(name, val)
26
+ end
27
+
28
+ #get a request header
29
+ def [](name)
30
+ @headers[name]
31
+ end
32
+
33
+ def set_header(name, val)
34
+ @headers[name] = val
18
35
  end
19
36
 
20
37
  def header(which)