saper 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +126 -0
- data/Rakefile +17 -0
- data/bin/saper +60 -0
- data/lib/lib/json_search.rb +54 -0
- data/lib/lib/mechanize.rb +26 -0
- data/lib/lib/nokogiri.rb +12 -0
- data/lib/saper.rb +37 -0
- data/lib/saper/actions/append_with.rb +14 -0
- data/lib/saper/actions/convert_to_html.rb +14 -0
- data/lib/saper/actions/convert_to_json.rb +14 -0
- data/lib/saper/actions/convert_to_markdown.rb +13 -0
- data/lib/saper/actions/convert_to_time.rb +15 -0
- data/lib/saper/actions/convert_to_xml.rb +14 -0
- data/lib/saper/actions/create_atom.rb +18 -0
- data/lib/saper/actions/fetch.rb +17 -0
- data/lib/saper/actions/find.rb +18 -0
- data/lib/saper/actions/find_first.rb +16 -0
- data/lib/saper/actions/get_attribute.rb +15 -0
- data/lib/saper/actions/get_contents.rb +14 -0
- data/lib/saper/actions/get_text.rb +14 -0
- data/lib/saper/actions/prepend_with.rb +14 -0
- data/lib/saper/actions/remove_after.rb +14 -0
- data/lib/saper/actions/remove_before.rb +14 -0
- data/lib/saper/actions/remove_matching.rb +14 -0
- data/lib/saper/actions/remove_tags.rb +15 -0
- data/lib/saper/actions/replace.rb +15 -0
- data/lib/saper/actions/run_recipe.rb +24 -0
- data/lib/saper/actions/run_recipe_and_save.rb +22 -0
- data/lib/saper/actions/save.rb +14 -0
- data/lib/saper/actions/select_matching.rb +14 -0
- data/lib/saper/actions/set_input.rb +19 -0
- data/lib/saper/actions/skip_tags.rb +15 -0
- data/lib/saper/actions/split.rb +24 -0
- data/lib/saper/arguments/attribute.rb +11 -0
- data/lib/saper/arguments/recipe.rb +42 -0
- data/lib/saper/arguments/text.rb +11 -0
- data/lib/saper/arguments/timezone.rb +11 -0
- data/lib/saper/arguments/variable.rb +11 -0
- data/lib/saper/arguments/xpath.rb +11 -0
- data/lib/saper/core/action.rb +209 -0
- data/lib/saper/core/argument.rb +106 -0
- data/lib/saper/core/browser.rb +87 -0
- data/lib/saper/core/dsl.rb +68 -0
- data/lib/saper/core/error.rb +47 -0
- data/lib/saper/core/item.rb +70 -0
- data/lib/saper/core/keychain.rb +18 -0
- data/lib/saper/core/logger.rb +74 -0
- data/lib/saper/core/namespace.rb +139 -0
- data/lib/saper/core/recipe.rb +134 -0
- data/lib/saper/core/runtime.rb +237 -0
- data/lib/saper/core/type.rb +45 -0
- data/lib/saper/items/atom.rb +64 -0
- data/lib/saper/items/document.rb +66 -0
- data/lib/saper/items/html.rb +85 -0
- data/lib/saper/items/json.rb +67 -0
- data/lib/saper/items/markdown.rb +36 -0
- data/lib/saper/items/nothing.rb +15 -0
- data/lib/saper/items/text.rb +54 -0
- data/lib/saper/items/time.rb +42 -0
- data/lib/saper/items/url.rb +34 -0
- data/lib/saper/items/xml.rb +79 -0
- data/lib/saper/version.rb +3 -0
- data/spec/actions/append_with_spec.rb +30 -0
- data/spec/actions/convert_to_html_spec.rb +24 -0
- data/spec/actions/convert_to_json_spec.rb +24 -0
- data/spec/actions/convert_to_markdown_spec.rb +24 -0
- data/spec/actions/convert_to_time_spec.rb +37 -0
- data/spec/actions/convert_to_xml_spec.rb +24 -0
- data/spec/actions/create_atom_spec.rb +31 -0
- data/spec/actions/fetch_spec.rb +7 -0
- data/spec/actions/find_first_spec.rb +7 -0
- data/spec/actions/find_spec.rb +7 -0
- data/spec/actions/get_attribute_spec.rb +7 -0
- data/spec/actions/get_contents.rb +7 -0
- data/spec/actions/get_text.rb +7 -0
- data/spec/actions/prepend_with_spec.rb +30 -0
- data/spec/actions/remove_after.rb +7 -0
- data/spec/actions/remove_before.rb +7 -0
- data/spec/actions/replace_spec.rb +7 -0
- data/spec/actions/run_recipe_and_save_spec.tmp.rb +52 -0
- data/spec/actions/run_recipe_spec.tmp.rb +53 -0
- data/spec/actions/save_spec.rb +7 -0
- data/spec/actions/select_matching_spec.rb +7 -0
- data/spec/actions/set_input_spec.rb +7 -0
- data/spec/actions/skip_tags_spec.rb +7 -0
- data/spec/actions/split_spec.rb +7 -0
- data/spec/core/action_spec.rb +151 -0
- data/spec/core/argument_spec.rb +79 -0
- data/spec/core/browser_spec.rb +7 -0
- data/spec/core/dsl_spec.rb +7 -0
- data/spec/core/item_spec.rb +7 -0
- data/spec/core/keychain_spec.rb +7 -0
- data/spec/core/logger_spec.rb +7 -0
- data/spec/core/namespace_spec.rb +18 -0
- data/spec/core/recipe_spec.rb +81 -0
- data/spec/core/runtime_spec.rb +165 -0
- data/spec/core/type_spec.rb +7 -0
- data/spec/items/atom_spec.rb +7 -0
- data/spec/items/document_spec.rb +7 -0
- data/spec/items/html_spec.rb +7 -0
- data/spec/items/json_spec.rb +7 -0
- data/spec/items/markdown_spec.rb +7 -0
- data/spec/items/nothing_spec.rb +7 -0
- data/spec/items/text_spec.rb +17 -0
- data/spec/items/time_spec.rb +7 -0
- data/spec/items/url_spec.rb +7 -0
- data/spec/items/xml_spec.rb +17 -0
- data/spec/spec_helper.rb +22 -0
- metadata +355 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
module Saper
|
2
|
+
class Action
|
3
|
+
|
4
|
+
# Tracks subclasses of Saper::Action.
|
5
|
+
# @return [Class]
|
6
|
+
def self.inherited(base)
|
7
|
+
subclasses[base.type] = base
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a hash of subclasses.
|
11
|
+
# @return [Hash]
|
12
|
+
def self.subclasses
|
13
|
+
@subclasses ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns class name as an underscored string.
|
17
|
+
# @return [String]
|
18
|
+
def self.type
|
19
|
+
name.split("::").last.gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a subclass with specified type.
|
23
|
+
# @param type [Symbol] action type
|
24
|
+
# @return [Saper::Action]
|
25
|
+
def self.[](type)
|
26
|
+
subclasses[type.to_s] || raise(ActionNotFound, type)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Defines a new argument.
|
30
|
+
# @param type [Symbol] argument type (e.g. :text, :xpath)
|
31
|
+
# @param options [Hash] argument options (e.g. :optional => true)
|
32
|
+
# @return [void]
|
33
|
+
def self.argument(type, options = {})
|
34
|
+
if Argument.exists?(type)
|
35
|
+
arguments.push options.merge(:type => type)
|
36
|
+
else
|
37
|
+
raise(InvalidType, "Invalid action argument: %s" % type)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a list of arguments assigned to this action.
|
42
|
+
# @return [Array<Hash>]
|
43
|
+
def self.arguments
|
44
|
+
@arguments ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
# Sets (one) or returns (all) acceptable input types.
|
48
|
+
# @param input [Symbol]
|
49
|
+
# @return [Array<Symbol>]
|
50
|
+
def self.accepts(input = nil, options = {})
|
51
|
+
if input.nil?
|
52
|
+
return @types.nil? ? [] : @types.keys
|
53
|
+
end
|
54
|
+
if input == :anything
|
55
|
+
return Item.subclasses.keys.map { |type| accepts(type.to_sym, options) }
|
56
|
+
end
|
57
|
+
output = options[:returns] || input
|
58
|
+
unless Item.exists?(input)
|
59
|
+
raise(InvalidInput, "Invalid input type: %s" % input)
|
60
|
+
end
|
61
|
+
if output.is_a?(Symbol) && !Item.exists?(output)
|
62
|
+
raise(InvalidInput, "Invalid output type: %s" % output)
|
63
|
+
end
|
64
|
+
@types ||= {}
|
65
|
+
@types[input] = output
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns `true` if action accepts specified type as input.
|
69
|
+
# @param type [Symbol]
|
70
|
+
# @return [Boolean]
|
71
|
+
def self.accepts?(type)
|
72
|
+
@types.nil? ? false : @types.keys.include?(type)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Saves Proc that encapsulates action logic and will be used later for data processing.
|
76
|
+
# @return [void]
|
77
|
+
def self.run(&block)
|
78
|
+
@block ||= block
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns `true` if action returns multiple items. Note that this method will report incorrect data for some actions. Use #multiple? instead.
|
82
|
+
def self.returns_multiple_items?
|
83
|
+
@multiple == true
|
84
|
+
end
|
85
|
+
|
86
|
+
# Sets a flag, indicating that this action returns multiple items.
|
87
|
+
# @return [void]
|
88
|
+
def self.returns_multiple_items!
|
89
|
+
@multiple = true
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns a new instance of Saper::Action.
|
93
|
+
# @param data [Hash, Array<Hash>]
|
94
|
+
# @return [Saper::Action]
|
95
|
+
def self.unserialize(data, namespace = nil, &block)
|
96
|
+
if data.is_a?(Array)
|
97
|
+
return data.map { |item| unserialize(item, namespace) }
|
98
|
+
end
|
99
|
+
unless data.is_a?(Hash)
|
100
|
+
raise InvalidAction.new(data)
|
101
|
+
end
|
102
|
+
new(data[:type], *data[:args], :namespace => namespace) do |action|
|
103
|
+
if block_given?
|
104
|
+
yield action
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns a new instance of Saper::Action.
|
110
|
+
# @return [Saper::Action]
|
111
|
+
def self.new(*args, &block)
|
112
|
+
if self == Action
|
113
|
+
self[args.shift].new(*args, &block)
|
114
|
+
else
|
115
|
+
super(*args, &block)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
attr_reader :options
|
120
|
+
|
121
|
+
# Returns a new instance of Saper::Action.
|
122
|
+
# @return [Saper::Action]
|
123
|
+
def initialize(*args)
|
124
|
+
@arguments = []
|
125
|
+
@options = {}
|
126
|
+
if args.last.is_a?(Hash)
|
127
|
+
@options = args.pop
|
128
|
+
end
|
129
|
+
self.class.arguments.each_with_index do |opts, i|
|
130
|
+
opts.merge!(:value => args[i], :action => self)
|
131
|
+
@arguments << Argument.new(opts[:type], opts)
|
132
|
+
end
|
133
|
+
if block_given?
|
134
|
+
yield self
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Runs action and returns results.
|
139
|
+
# @param input [object] input
|
140
|
+
# @return [void] depends on action type
|
141
|
+
def run(input = nil, runtime = nil)
|
142
|
+
unless input.is_a?(Item)
|
143
|
+
input = self.class.accepts.map { |type| Item.try(type, input) }.compact.first
|
144
|
+
end
|
145
|
+
if input.nil?
|
146
|
+
input = Items::Nothing.new
|
147
|
+
end
|
148
|
+
unless self.class.accepts?(input.type)
|
149
|
+
raise(InvalidInput, input)
|
150
|
+
end
|
151
|
+
if runtime.nil?
|
152
|
+
begin
|
153
|
+
block.call(input, *args)
|
154
|
+
rescue NameError
|
155
|
+
raise RuntimeMissing
|
156
|
+
end
|
157
|
+
else
|
158
|
+
runtime.instance_exec(input, *args, &self.block)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns human readable action name.
|
163
|
+
# @return [String]
|
164
|
+
def name
|
165
|
+
self.class.name.split("::").last.gsub(/([a-z])([A-Z])/,'\1 \2')
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns Saper::Namespace instance.
|
169
|
+
# @return [Namespace]
|
170
|
+
def namespace
|
171
|
+
@options[:namespace].is_a?(Namespace) ? @options[:namespace] : nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns a list of data types that are accepted as input.
|
175
|
+
# @return [Array<Symbol>]
|
176
|
+
def requires
|
177
|
+
self.class.accepts
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns values of action arguments.
|
181
|
+
# @return [Array]
|
182
|
+
def args
|
183
|
+
@arguments.map(&:value)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns a serialized representation of this action.
|
187
|
+
# @return [Hash]
|
188
|
+
def serialize
|
189
|
+
{ :type => self.class.type, :args => @arguments.map(&:serialize) }
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns `true` if action returns multiple items.
|
193
|
+
# @return [Boolean]
|
194
|
+
def multiple?
|
195
|
+
self.class.returns_multiple_items?
|
196
|
+
end
|
197
|
+
|
198
|
+
# Returns Proc that encapsulates action logic (i.e. processes data).
|
199
|
+
# @return [Proc]
|
200
|
+
def block
|
201
|
+
self.class.run || Proc.new { |input, *args| input }
|
202
|
+
end
|
203
|
+
|
204
|
+
def to_string
|
205
|
+
"\t%s %s" % [self.class.type, @arguments.map(&:to_string).join(", ")]
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Saper
|
2
|
+
class Argument
|
3
|
+
|
4
|
+
# Tracks subclasses of Saper::Argument.
|
5
|
+
# @return [Class]
|
6
|
+
def self.inherited(base)
|
7
|
+
subclasses[base.type] = base
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a hash of subclasses.
|
11
|
+
# @return [Hash]
|
12
|
+
def self.subclasses
|
13
|
+
@subclasses ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns class name as an underscored string.
|
17
|
+
# @return [String]
|
18
|
+
def self.type
|
19
|
+
name.split("::").last.gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a subclass with specified type.
|
23
|
+
# @param type [Symbol] action type
|
24
|
+
# @return [Saper::Argument]
|
25
|
+
def self.[](type)
|
26
|
+
subclasses[type.to_s] || raise(InvalidType, "Invalid argument: %s" % type)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns `true` if there is a subclass with specified type.
|
30
|
+
# @param type [Symbol] action type
|
31
|
+
# @return [Boolean]
|
32
|
+
def self.exists?(type)
|
33
|
+
subclasses.keys.include?(type.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a new instance of Saper::Argument.
|
37
|
+
# @return [Saper::Argument]
|
38
|
+
def self.new(*args, &block)
|
39
|
+
if self == Argument
|
40
|
+
self[args.shift].new(*args, &block)
|
41
|
+
else
|
42
|
+
super(*args, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @todo
|
47
|
+
def initialize(opts = {})
|
48
|
+
@value = nil
|
49
|
+
@opts = opts
|
50
|
+
if mandatory? || !@opts[:value].nil?
|
51
|
+
set @opts.delete(:value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @todo
|
56
|
+
def set(value)
|
57
|
+
unless valid?(value)
|
58
|
+
raise InvalidArgument, value
|
59
|
+
else
|
60
|
+
@value = normalize(value)
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# @todo
|
66
|
+
def valid?(value)
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
# @todo
|
71
|
+
def normalize(value)
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
# @todo
|
76
|
+
def value
|
77
|
+
@value
|
78
|
+
end
|
79
|
+
|
80
|
+
# @todo
|
81
|
+
def serialize
|
82
|
+
value
|
83
|
+
end
|
84
|
+
|
85
|
+
# @todo
|
86
|
+
def mandatory?
|
87
|
+
not optional?
|
88
|
+
end
|
89
|
+
|
90
|
+
# @todo
|
91
|
+
def action
|
92
|
+
@opts[:action]
|
93
|
+
end
|
94
|
+
|
95
|
+
# @todo
|
96
|
+
def optional?
|
97
|
+
@opts[:optional] == true
|
98
|
+
end
|
99
|
+
|
100
|
+
# @todo
|
101
|
+
def to_string
|
102
|
+
value.to_s.inspect
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Saper
|
2
|
+
class Browser
|
3
|
+
|
4
|
+
require 'mechanize'
|
5
|
+
|
6
|
+
attr_reader :history, :received, :sent
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@agent = options.delete(:agent)
|
10
|
+
@headers = options.delete(:headers)
|
11
|
+
@logger = options.delete(:logger) || Saper::Logger.new
|
12
|
+
@history = []
|
13
|
+
@received = 0
|
14
|
+
@sent = 0
|
15
|
+
@mech = Mechanize.new do |a|
|
16
|
+
a.robots = false
|
17
|
+
a.user_agent = agent
|
18
|
+
a.request_headers = headers
|
19
|
+
a.pluggable_parser.csv = nil
|
20
|
+
a.pluggable_parser.html = nil
|
21
|
+
a.pluggable_parser.xhtml = nil
|
22
|
+
a.pluggable_parser.xml = nil
|
23
|
+
end
|
24
|
+
@mech.pre_connect_hook do |agent, req|
|
25
|
+
@sent += req.to_hash.to_s.size
|
26
|
+
@sent += (req.body.nil? ? 0 : req.body.size)
|
27
|
+
end
|
28
|
+
@mech.post_connect_hook do |agent, uri, resp, body|
|
29
|
+
@received += resp.to_hash.to_s.size
|
30
|
+
@received += body.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns number of HTTP requests
|
35
|
+
def requests
|
36
|
+
@history.size
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
def headers
|
41
|
+
@headers.respond_to?(:to_hash) ? @headers : {}
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
def get(url, query = {})
|
46
|
+
@logger.download(url)
|
47
|
+
@history.push url
|
48
|
+
data = @mech.get(url, query)
|
49
|
+
Saper::Items::Document.new data
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
def post(url, query = {})
|
54
|
+
@logger.download(url)
|
55
|
+
@history.push url
|
56
|
+
data = @mech.post(url, query)
|
57
|
+
Saper::Items::Document.new data
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
def agent
|
62
|
+
case @agent
|
63
|
+
when :ie6
|
64
|
+
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
|
65
|
+
when :ie7
|
66
|
+
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'
|
67
|
+
when :ie8
|
68
|
+
'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'
|
69
|
+
when :ie9
|
70
|
+
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'
|
71
|
+
when :mozilla
|
72
|
+
'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6'
|
73
|
+
when :safari
|
74
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22'
|
75
|
+
when :iphone
|
76
|
+
'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1C28 Safari/419.3'
|
77
|
+
when :ipad
|
78
|
+
'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10'
|
79
|
+
when :android
|
80
|
+
'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13'
|
81
|
+
else
|
82
|
+
'Mozilla/5.0 (compatible; Saper Ruby client %s)' % Saper::VERSION
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Saper
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
def self.new
|
5
|
+
Module.new.extend(Methods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(Methods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module Methods
|
13
|
+
|
14
|
+
def namespace
|
15
|
+
@namespace ||= Saper::Namespace.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def recipe(id, name = nil, &block)
|
19
|
+
namespace[id] = Recipe.parse(id, name, :namespace => namespace, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](name)
|
23
|
+
namespace[name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def run_by_default(*args)
|
27
|
+
namespace.run_by_default(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def run(*args)
|
31
|
+
namespace.run(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class Recipe
|
37
|
+
# Parses block and returns a Recipe instance or a proxy object.
|
38
|
+
# If namespace is specified within options, full initialization of
|
39
|
+
# Recipe is delayed and a proxy object is returned (which supports
|
40
|
+
# `#to_recipe`).
|
41
|
+
# @param id [Symbol] recipe ID
|
42
|
+
# @return [Saper::Recipe, Saper::DSL::Recipe]
|
43
|
+
def self.parse(id, name = nil, options = {}, &block)
|
44
|
+
instance = self.new(id, options.merge(:name => name), &block)
|
45
|
+
if options[:namespace].is_a?(Namespace)
|
46
|
+
instance
|
47
|
+
else
|
48
|
+
instance.to_recipe
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :recipe
|
53
|
+
|
54
|
+
def initialize(id = nil, options = {}, &block)
|
55
|
+
@recipe, @block = Saper::Recipe.new(id, options), block
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_recipe
|
59
|
+
self.instance_eval(&@block) if recipe.empty?; recipe
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(name, *args, &block)
|
63
|
+
@recipe << Saper::Action.new(name, *args, :namespace => @recipe.namespace, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|