metaforce 0.5.3 → 1.0.0a

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 (72) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +1 -11
  4. data/LICENSE +22 -0
  5. data/README.md +91 -96
  6. data/Rakefile +6 -14
  7. data/examples/example.rb +51 -0
  8. data/lib/metaforce/abstract_client.rb +76 -0
  9. data/lib/metaforce/client.rb +27 -0
  10. data/lib/metaforce/config.rb +41 -19
  11. data/lib/metaforce/job/crud.rb +13 -0
  12. data/lib/metaforce/job/deploy.rb +87 -0
  13. data/lib/metaforce/job/retrieve.rb +92 -0
  14. data/lib/metaforce/job.rb +183 -0
  15. data/lib/metaforce/login.rb +39 -0
  16. data/lib/metaforce/manifest.rb +18 -93
  17. data/lib/metaforce/metadata/client/crud.rb +86 -0
  18. data/lib/metaforce/metadata/client/file.rb +113 -0
  19. data/lib/metaforce/metadata/client.rb +7 -225
  20. data/lib/metaforce/services/client.rb +45 -86
  21. data/lib/metaforce/version.rb +1 -1
  22. data/lib/metaforce.rb +27 -7
  23. data/metaforce.gemspec +19 -16
  24. data/spec/fixtures/package.xml +1 -1
  25. data/spec/fixtures/payload.zip +0 -0
  26. data/spec/fixtures/requests/{describe_layout → foo}/invalid_session.xml +0 -0
  27. data/spec/fixtures/requests/send_email/success.xml +1 -0
  28. data/spec/lib/client_spec.rb +34 -0
  29. data/spec/lib/config_spec.rb +8 -50
  30. data/spec/lib/job/deploy_spec.rb +53 -0
  31. data/spec/lib/job/retrieve_spec.rb +28 -0
  32. data/spec/lib/job_spec.rb +95 -0
  33. data/spec/lib/login_spec.rb +18 -0
  34. data/spec/lib/manifest_spec.rb +22 -168
  35. data/spec/lib/metadata/client_spec.rb +84 -179
  36. data/spec/lib/metaforce_spec.rb +20 -0
  37. data/spec/lib/services/client_spec.rb +22 -35
  38. data/spec/spec_helper.rb +24 -3
  39. data/spec/support/client.rb +38 -0
  40. data/wsdl/26.0/metadata.xml +4750 -0
  41. data/wsdl/26.0/partner.xml +3340 -0
  42. metadata +114 -77
  43. data/Guardfile +0 -9
  44. data/bin/metaforce +0 -6
  45. data/lib/metaforce/core_extensions/string.rb +0 -31
  46. data/lib/metaforce/core_extensions.rb +0 -1
  47. data/lib/metaforce/custom_actions.rb +0 -29
  48. data/lib/metaforce/error.rb +0 -3
  49. data/lib/metaforce/login_details.rb +0 -28
  50. data/lib/metaforce/metadata/crud.rb +0 -103
  51. data/lib/metaforce/metadata/file.rb +0 -74
  52. data/lib/metaforce/metadata/transaction.rb +0 -100
  53. data/lib/metaforce/metadata.rb +0 -4
  54. data/lib/metaforce/rake/deploy.rb +0 -35
  55. data/lib/metaforce/rake/retrieve.rb +0 -39
  56. data/lib/metaforce/rake/tests.rb +0 -62
  57. data/lib/metaforce/rake.rb +0 -43
  58. data/lib/metaforce/services.rb +0 -1
  59. data/lib/metaforce/tasks/README.md +0 -62
  60. data/lib/metaforce/tasks/metaforce.rake +0 -5
  61. data/lib/metaforce/thor/metaforce.rb +0 -117
  62. data/lib/metaforce/types.rb +0 -249
  63. data/spec/.gitignore +0 -1
  64. data/spec/fixtures/sample/Rakefile +0 -2
  65. data/spec/fixtures/sample/metaforce.yml +0 -13
  66. data/spec/fixtures/sample/src/classes/TestClass.cls +0 -2
  67. data/spec/fixtures/sample/src/classes/TestClass.cls-meta.xml +0 -5
  68. data/spec/fixtures/sample/src/package.xml +0 -8
  69. data/spec/lib/core_extensions/string_spec.rb +0 -23
  70. data/spec/lib/metadata/crud_spec.rb +0 -66
  71. data/spec/lib/metadata/file_spec.rb +0 -17
  72. data/spec/lib/metadata/transaction_spec.rb +0 -68
@@ -0,0 +1,92 @@
1
+ module Metaforce
2
+ class Job::Retrieve < Job
3
+
4
+ # Public: Instantiate a new retrieve job.
5
+ #
6
+ # Examples
7
+ #
8
+ # job = Metaforce::Job::Retrieve.new(client)
9
+ # # => #<Metaforce::Job::Retrieve @id=nil>
10
+ #
11
+ # Returns self.
12
+ def initialize(client, options={})
13
+ super(client)
14
+ @options = options
15
+ end
16
+
17
+ # Public: Perform the job.
18
+ #
19
+ # Examples
20
+ #
21
+ # job = Metaforce::Job::Retrieve.new(client)
22
+ # job.perform
23
+ # # => #<Metaforce::Job::Retrieve @id='1234'>
24
+ #
25
+ # Returns self.
26
+ def perform
27
+ @id = client._retrieve(@options).id
28
+ super
29
+ end
30
+
31
+ # Public: Get the detailed status of the retrieve.
32
+ #
33
+ # Examples
34
+ #
35
+ # job.result
36
+ # # => { :id => '1234', :zip_file => '<base64 encoded content>', ... }
37
+ #
38
+ # Returns the RetrieveResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_retrieveresult.htm).
39
+ def result
40
+ client.status(id, :retrieve)
41
+ end
42
+
43
+ # Public: Decodes the content of the returned zip file.
44
+ #
45
+ # Examples
46
+ #
47
+ # job.zip_file
48
+ # # => '<binary content>'
49
+ #
50
+ # Returns the decoded content.
51
+ def zip_file
52
+ Base64.decode64(result.zip_file)
53
+ end
54
+
55
+ # Public: Unzips the returned zip file to the location.
56
+ #
57
+ # destination - Path to extract the contents to.
58
+ #
59
+ # Examples
60
+ #
61
+ # job.extract_to('./path')
62
+ # # => #<Metaforce::Job::Retrieve @id='1234'>
63
+ #
64
+ # Returns self.
65
+ def extract_to(destination)
66
+ return on_complete { |job| job.extract_to(destination) } unless @id
67
+ Zip::ZipFile.open(tmp_zip_file) do |zip|
68
+ zip.each do |f|
69
+ path = File.join(destination, f.name)
70
+ FileUtils.mkdir_p(File.dirname(path))
71
+ zip.extract(f, path) { true }
72
+ end
73
+ end
74
+ self
75
+ end
76
+
77
+ private
78
+
79
+ # Internal: Writes the zip file content to a temporary location so it can
80
+ # be extracted.
81
+ def tmp_zip_file
82
+ @tmp_zip_file ||= begin
83
+ file = Tempfile.new('retrieve')
84
+ file.write(zip_file)
85
+ path = file.path
86
+ file.close
87
+ path
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,183 @@
1
+ require 'zip/zip'
2
+ require 'base64'
3
+
4
+ module Metaforce
5
+ class Job
6
+ autoload :Deploy, 'metaforce/job/deploy'
7
+ autoload :Retrieve, 'metaforce/job/retrieve'
8
+ autoload :CRUD, 'metaforce/job/crud'
9
+
10
+ # Public: The id of the AsyncResult returned from Salesforce for
11
+ # this job.
12
+ attr_reader :id
13
+
14
+ # Public: Instantiate a new job. Doesn't actually do anything until
15
+ # .perform is called.
16
+ #
17
+ # Examples
18
+ #
19
+ # job = Metaforce::Job.new(client)
20
+ # # => #<Metaforce::Job @id=nil>
21
+ #
22
+ # Returns self.
23
+ def initialize(client)
24
+ @_callbacks = Hash.new { |h,k| h[k] = [] }
25
+ @client = client
26
+ end
27
+
28
+ # Public: Perform the job.
29
+ #
30
+ # Examples
31
+ #
32
+ # job = Metaforce::Job.new
33
+ # job.perform
34
+ # # => #<Metaforce::Job @id=nil>
35
+ #
36
+ # Returns self.
37
+ def perform
38
+ start_heart_beat
39
+ self
40
+ end
41
+
42
+ # Public: Register a block to be called when the job has completed.
43
+ #
44
+ # Yields the job.
45
+ #
46
+ # &block - Proc or Lambda to be run when the job completes.
47
+ #
48
+ # Examples
49
+ #
50
+ # job.on_complete do |job|
51
+ # puts "Job ##{job.id} completed!"
52
+ # end
53
+ #
54
+ # Returns self.
55
+ def on_complete(&block)
56
+ @_callbacks[:on_complete] << block
57
+ self
58
+ end
59
+
60
+ # Public: Register a block to be called when if the job fails.
61
+ #
62
+ # Yields the job.
63
+ #
64
+ # &block - Proc or Lambda to be run when the job fails.
65
+ #
66
+ # Examples
67
+ #
68
+ # job.on_error do |job|
69
+ # puts "Job ##{job.id} failed!"
70
+ # end
71
+ #
72
+ # Returns self.
73
+ def on_error(&block)
74
+ @_callbacks[:on_error] << block
75
+ self
76
+ end
77
+
78
+ # Public: Queries the job status from the API.
79
+ #
80
+ # Examples
81
+ #
82
+ # job.status
83
+ # # => { :id => '1234', :done => false, ... }
84
+ #
85
+ # Returns the AsyncResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_asyncresult.htm).
86
+ def status
87
+ client.status(id)
88
+ end
89
+
90
+ # Public: Returns true if the job has completed.
91
+ #
92
+ # Examples
93
+ #
94
+ # job.done
95
+ # # => true
96
+ #
97
+ # Returns true if the job has completed, false otherwise.
98
+ def done?
99
+ status.done
100
+ end
101
+
102
+ # Public: Returns the state if the job has finished processing.
103
+ #
104
+ # Examples
105
+ #
106
+ # job.state
107
+ # # => 'Completed'
108
+ #
109
+ # Returns the state if the job is done, false otherwise.
110
+ def state
111
+ done? && status.state
112
+ end
113
+
114
+ # Public: Check if the job is in a given state.
115
+ #
116
+ # Examples
117
+ #
118
+ # job.queued?
119
+ # # => false
120
+ #
121
+ # Returns true or false.
122
+ #
123
+ # Signature
124
+ #
125
+ # queued?
126
+ # in_progress?
127
+ # completed?
128
+ # error?
129
+ %w[Queued InProgress Completed Error].each do |state|
130
+ define_method :"#{state.underscore}?" do; self.state == state end
131
+ end
132
+
133
+ def inspect
134
+ "#<#{self.class} @id=#{@id.inspect}>"
135
+ end
136
+
137
+ class << self
138
+
139
+ # Internal: Disable threading in tests.
140
+ def disable_threading!
141
+ self.class_eval do
142
+ def start_heart_beat
143
+ loop do
144
+ trigger_callbacks && break if completed? || error?
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ private
153
+ attr_reader :client
154
+
155
+ # Internal: Starts a heart beat in a thread, which polls the job status
156
+ # until it has completed or timed out.
157
+ def start_heart_beat
158
+ Thread.abort_on_exception = true
159
+ @heart_beat ||= Thread.new do
160
+ delay = 1
161
+ loop do
162
+ sleep (delay = delay * 2)
163
+ trigger_callbacks && Thread.stop if completed? || error?
164
+ end
165
+ end
166
+ end
167
+
168
+ def trigger_callbacks
169
+ @_callbacks[callback_type].each do |block|
170
+ block.call(self)
171
+ end
172
+ end
173
+
174
+ def callback_type
175
+ if completed?
176
+ :on_complete
177
+ elsif error?
178
+ :on_error
179
+ end
180
+ end
181
+
182
+ end
183
+ end
@@ -0,0 +1,39 @@
1
+ module Metaforce
2
+ class Login
3
+ def initialize(username, password, security_token=nil)
4
+ @username, @password, @security_token = username, password, security_token
5
+ end
6
+
7
+ # Public: Perform the login request.
8
+ #
9
+ # Returns a hash with the session id and server urls.
10
+ def login
11
+ response = client.request(:login) do
12
+ soap.body = {
13
+ :username => username,
14
+ :password => password
15
+ }
16
+ end
17
+ response.body[:login_response][:result]
18
+ end
19
+
20
+ private
21
+
22
+ # Internal: Savon client.
23
+ def client
24
+ @client ||= Savon.client(Metaforce.configuration.partner_wsdl) do |wsdl|
25
+ wsdl.endpoint = Metaforce.configuration.endpoint
26
+ end.tap { |client| client.http.auth.ssl.verify_mode = :none }
27
+ end
28
+
29
+ # Internal: Usernamed passed in from options.
30
+ def username
31
+ @username
32
+ end
33
+
34
+ # Internal: Password + Security Token combined.
35
+ def password
36
+ [@password, @security_token].join('')
37
+ end
38
+ end
39
+ end
@@ -1,11 +1,11 @@
1
1
  require 'nokogiri'
2
- require 'metaforce/types'
2
+ require 'active_support/core_ext'
3
3
 
4
4
  module Metaforce
5
- class Manifest
5
+ class Manifest < Hash
6
6
 
7
- # Initializes a new instance of a manifest (package.xml) file.
8
- #
7
+ # Public: Initializes a new instance of a manifest (package.xml) file.
8
+ #
9
9
  # It can either take a hash:
10
10
  # {
11
11
  # :apex_class => [
@@ -35,71 +35,17 @@ module Metaforce
35
35
  # </types>
36
36
  # <version>23.0</version>
37
37
  # </Package>
38
- #
38
+ #
39
39
  def initialize(components={})
40
- # Map component type => folder
40
+ self.replace Hash.new { |h,k| h[k] = [] }
41
41
  if components.is_a?(Hash)
42
- @components = components
42
+ self.merge!(components)
43
43
  elsif components.is_a?(String)
44
- @components = {}
45
44
  self.parse(components)
46
45
  end
47
46
  end
48
47
 
49
- # Adds components to the package
50
- #
51
- # manifest.add :apex_class, 'SomeClass'
52
- def add(type, members=nil)
53
- unless members.nil?
54
- @components[type] = [] if @components[type].nil?
55
- members = [members] if members.is_a?(String)
56
- members.each do |member|
57
- member = member.gsub(/.*\//, '').gsub(/\..*/, '');
58
- @components[type].push(member)
59
- end
60
- end
61
- self
62
- end
63
-
64
- # Removes components from the package
65
- #
66
- # manifest.remove :apex_class, 'SomeClass'
67
- def remove(type, members=nil)
68
- unless members.nil?
69
- members = [members] if members.is_a?(String)
70
- members.each do |member|
71
- member = member.gsub(/.*\//, '').gsub(/\..*/, '');
72
- @components[type].delete(member)
73
- end
74
- end
75
- if @components[type].empty?
76
- @components.delete(type)
77
- end
78
- self
79
- end
80
-
81
- # Filters the components based on a list of files
82
- #
83
- # manifest.only(['classes/SomeClass'])
84
- def only(files)
85
- components = @components
86
- @components = {}
87
- files.each do |file|
88
- parts = file.split('/').last(2)
89
- folder = parts[0]
90
- file = parts[1].gsub(/.*\//, '').gsub(/\..*/, '')
91
- components.each_key do |type|
92
- if Metaforce::Metadata::Types.folder(type) =~ /#{folder}/i
93
- unless components[type].index(file).nil?
94
- self.add(type, file);
95
- end
96
- end
97
- end
98
- end
99
- self
100
- end
101
-
102
- # Returns a string containing a package.xml file
48
+ # Public: Returns a string containing a package.xml file
103
49
  #
104
50
  # <?xml version="1.0"?>
105
51
  # <Package xmlns="http://soap.sforce.com/2006/04/metadata">
@@ -120,13 +66,13 @@ module Metaforce
120
66
  # </Package>
121
67
  def to_xml
122
68
  xml_builder = Nokogiri::XML::Builder.new do |xml|
123
- xml.Package("xmlns" => "http://soap.sforce.com/2006/04/metadata") {
124
- @components.each do |key, members|
69
+ xml.Package('xmlns' => 'http://soap.sforce.com/2006/04/metadata') {
70
+ self.each do |key, members|
125
71
  xml.types {
126
72
  members.each do |member|
127
73
  xml.members member
128
74
  end
129
- xml.name Metaforce::Metadata::Types.name(key)
75
+ xml.name key.to_s.camelize
130
76
  }
131
77
  end
132
78
  xml.version Metaforce.configuration.api_version
@@ -135,43 +81,22 @@ module Metaforce
135
81
  xml_builder.to_xml
136
82
  end
137
83
 
138
- # Returns the underlying hash structure
139
- #
140
- # {
141
- # :apex_class => [
142
- # "TestController",
143
- # "TestClass"
144
- # ],
145
- # :apex_component => [
146
- # "SiteLogin"
147
- # ]
148
- # }
149
- def to_hash
150
- @components
151
- end
152
-
153
- # Used internall my Metaforce::Metadata::Client
84
+ # Public: Converts the manifest into a format that can be used by the
85
+ # metadata api.
154
86
  def to_package
155
- components = []
156
- @components.each do |type, members|
157
- name = Metaforce::Metadata::Types.name(type)
158
- components.push :members => members, :name => name
87
+ self.map do |type, members|
88
+ { :members => members, :name => type.to_s.camelize }
159
89
  end
160
- components
161
90
  end
162
91
 
163
- # Parses a package.xml file
92
+ # Public: Parses a package.xml file
164
93
  def parse(file)
165
94
  document = Nokogiri::XML(file).remove_namespaces!
166
95
  document.xpath('//types').each do |type|
167
96
  name = type.xpath('name').first.content
168
- key = Metaforce::Metadata::Types.key(name);
97
+ key = name.underscore.to_sym
169
98
  type.xpath('members').each do |member|
170
- if @components[key].is_a?(Array)
171
- @components[key].push(member.content)
172
- else
173
- @components[key] = [member.content]
174
- end
99
+ self[key] << member.content
175
100
  end
176
101
  end
177
102
  self
@@ -0,0 +1,86 @@
1
+ module Metaforce
2
+ module Metadata
3
+ class Client
4
+ module CRUD
5
+
6
+ # Public: Create metadata
7
+ #
8
+ # Examples
9
+ #
10
+ # client._create(:apex_page, :full_name => 'TestPage', label: 'Test page', :content => '<apex:page>foobar</apex:page>')
11
+ def _create(type, metadata={})
12
+ type = type.to_s.camelize
13
+ request :create do |soap|
14
+ soap.body = {
15
+ :metadata => prepare(metadata)
16
+ }.merge(attributes!(type))
17
+ end
18
+ end
19
+
20
+ # Public: Delete metadata
21
+ #
22
+ # Examples
23
+ #
24
+ # client._delete(:apex_component, 'Component')
25
+ def _delete(type, *args)
26
+ type = type.to_s.camelize
27
+ metadata = args.map { |full_name| {:full_name => full_name} }
28
+ request :delete do |soap|
29
+ soap.body = {
30
+ :metadata => metadata
31
+ }.merge(attributes!(type))
32
+ end
33
+ end
34
+
35
+ # Public: Update metadata
36
+ #
37
+ # Examples
38
+ #
39
+ # client._update(:apex_page, 'OldPage', :full_name => 'NewPage')
40
+ def _update(type, current_name, metadata={})
41
+ type = type.to_s.camelize
42
+ request :update do |soap|
43
+ soap.body = {
44
+ :metadata => {
45
+ :current_name => current_name,
46
+ :metadata => prepare(metadata),
47
+ :attributes! => { :metadata => { 'xsi:type' => "ins0:#{type}" } }
48
+ }
49
+ }
50
+ end
51
+ end
52
+
53
+ def create(*args)
54
+ Job::CRUD.new(self, :_create, args)
55
+ end
56
+
57
+ def update(*args)
58
+ Job::CRUD.new(self, :_update, args)
59
+ end
60
+
61
+ def delete(*args)
62
+ Job::CRUD.new(self, :_delete, args)
63
+ end
64
+
65
+ private
66
+
67
+ def attributes!(type)
68
+ {:attributes! => { 'ins0:metadata' => { 'xsi:type' => "ins0:#{type}" } }}
69
+ end
70
+
71
+ # Internal: Prepare metadata by base64 encoding any content keys.
72
+ def prepare(metadata)
73
+ metadata = [metadata] unless metadata.is_a? Array
74
+ metadata.each { |m| encode_content(m) }
75
+ metadata
76
+ end
77
+
78
+ # Internal: Base64 encodes any :content keys.
79
+ def encode_content(metadata)
80
+ metadata[:content] = Base64.encode64(metadata[:content]) if metadata.has_key?(:content)
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,113 @@
1
+ module Metaforce
2
+ module Metadata
3
+ class Client
4
+ module File
5
+
6
+ # Public: Specify an array of component types to list.
7
+ #
8
+ # Examples
9
+ #
10
+ # # Get a list of apex classes on the server and output the names of each
11
+ # client.list_metadata('ApexClass').collect { |t| t.full_name }
12
+ # #=> ["al__SObjectPaginatorListenerForTesting", "al__IndexOutOfBoundsException", ... ]
13
+ #
14
+ # # Get a list of apex components and apex classes
15
+ # client.list_metadata('CustomObject', 'ApexComponent')
16
+ # #=> ["ContractContactRole", "Solution", "Invoice_Statements__c", ... ]
17
+ def list_metadata(*args)
18
+ queries = args.map(&:to_s).map(&:camelize).map { |t| {:type => t} }
19
+ request :list_metadata do |soap|
20
+ soap.body = { :queries => queries }
21
+ end
22
+ end
23
+
24
+ # Public: Describe the organization's metadata.
25
+ #
26
+ # version - API version (default: latest).
27
+ #
28
+ # Examples
29
+ #
30
+ # # List the names of all metadata types
31
+ # client.describe.metadata_objects.collect { |t| t.xml_name }
32
+ # #=> ["CustomLabels", "StaticResource", "Scontrol", "ApexComponent", ... ]
33
+ def describe(version=nil)
34
+ request :describe_metadata do |soap|
35
+ soap.body = { :api_version => version } unless version.nil?
36
+ end
37
+ end
38
+
39
+ # Public: Checks the status of an async result.
40
+ #
41
+ # ids - A list of ids to check.
42
+ # type - either :deploy or :retrieve
43
+ #
44
+ # Examples
45
+ #
46
+ # client.status('04sU0000000Wx6KIAS')
47
+ # #=> {:done=>true, :id=>"04sU0000000Wx6KIAS", :state=>"Completed", ...}
48
+ def status(ids, type=nil)
49
+ method = :check_status
50
+ method = :"check_#{type}_status" if type
51
+ ids = [ids] unless ids.respond_to?(:each)
52
+ request method do |soap|
53
+ soap.body = { :ids => ids }
54
+ end
55
+ end
56
+
57
+ # Public: Deploy code to Salesforce.
58
+ #
59
+ # zip_file - The base64 encoded contents of the zip file.
60
+ # options - Hash of DeployOptions.
61
+ #
62
+ # Returns the AsyncResult
63
+ def _deploy(zip_file, options={})
64
+ request :deploy do |soap|
65
+ soap.body = { :zip_file => zip_file, :deploy_options => options }
66
+ end
67
+ end
68
+
69
+ # Public: Retrieve code from Salesforce.
70
+ #
71
+ # Returns the AsyncResult
72
+ def _retrieve(options={})
73
+ request :retrieve do |soap|
74
+ soap.body = { :retrieve_request => options }
75
+ end
76
+ end
77
+
78
+ # Public: Deploy code to Salesforce.
79
+ #
80
+ # path - A path to a zip file, or a directory to deploy.
81
+ # options - Deploy options.
82
+ #
83
+ # Examples
84
+ #
85
+ # client.deploy(File.expand_path('./src'))
86
+ def deploy(path, options={})
87
+ Job::Deploy.new(self, path, options)
88
+ end
89
+
90
+ def retrieve(options={})
91
+ Job::Retrieve.new(self, options)
92
+ end
93
+
94
+ # Public: Retrieves files specified in the manifest file (A package.xml
95
+ # file).
96
+ def retrieve_unpackaged(manifest, options={})
97
+ package = if manifest.is_a?(Metaforce::Manifest)
98
+ manifest
99
+ elsif manifest.is_a?(String)
100
+ Metaforce::Manifest.new(File.open(manifest).read)
101
+ end
102
+ options = {
103
+ :api_version => Metaforce.configuration.api_version,
104
+ :single_package => true,
105
+ :unpackaged => { :types => package.to_package }
106
+ }.merge(options)
107
+ retrieve(options)
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end