ape 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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)