rakumarket 0.0.3 → 0.1.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.
@@ -1,89 +1,148 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Rakumarket
2
- class Client
3
+ class Client < Spitter
3
4
  attr_accessor :developer_id
4
5
  include HTTParty
5
6
  base_uri "http://api.rakuten.co.jp/rws/3.0"
6
7
  format :json
7
8
 
8
- def initialize(options={})
9
- @developer_id = options[:developer_id] || Rakumarket.developer_id
9
+ parameter :developer_id => 'developerId'
10
+
11
+ def self.request(params) new(params).request end
12
+
13
+ def initialize(params={})
14
+ @developer_id = params[:developer_id] || Rakumarket.developer_id
15
+ super params.merge(default_options)
10
16
  end
11
17
 
12
- def request(query={})
13
- @options = default_options.merge(query).slice(*valid_request_keys)
14
- transform_request!
15
- @response = self.class.get("/json",:query => @options)
16
- respond
18
+ def request
19
+ @response = self.class.get("/json", :query => parse)
17
20
  end
18
21
 
19
22
  private
20
- def transform_request!
21
- transform_request_keys!
22
- transform_request_values!
23
+ def default_options
24
+ { :developer_id => @developer_id,
25
+ :operation => nil,
26
+ :version => nil }
23
27
  end
24
28
 
25
- def transform_request_keys!
26
- transform_keys! @options, request_keys_to_transform
27
- end
29
+ def self.get(*args); handle_response super end
30
+ def self.post(*args); handle_response super end
28
31
 
29
- def transform_request_values!
30
- transform_values! @options, request_values_to_transform
32
+ def self.handle_response(response)
33
+ case response["Header"]["Status"]
34
+ when "Success"; response
35
+ else; raise RakumarketError.new({:status => response["Header"]["Status"], :status_msg => response["Header"]["StatusMsg"]})
36
+ end
37
+ response
31
38
  end
39
+ end
32
40
 
33
- def respond
34
- transform_response!
41
+ class TrueToOne
42
+ def self.parse value
43
+ value ? "1" : "0"
35
44
  end
45
+ end
36
46
 
37
- def transform_response!
38
- transform_response_values!
39
- transform_response_keys!
47
+ class TrueToZero
48
+ def self.parse value
49
+ value ? "0" : "1"
40
50
  end
51
+ end
41
52
 
42
- def transform_response_keys!
43
- transform_keys! @response, response_keys_to_transform
44
- end
53
+ class ItemSearchClient < Client
54
+
55
+ OPERATION = "ItemSearch"
56
+ VERSION = "2010-09-15"
45
57
 
46
- def transform_response_values!
47
- transform_values! @response, response_values_to_transform
58
+ class SortOrder
59
+ def self.parse(value) ITEM_SEARCH_SORT_ORDERS[value.downcase] end
48
60
  end
49
61
 
50
- def request_keys_to_transform; {} end
51
- def request_values_to_transform; {} end
52
- def response_keys_to_transform; {} end
53
- def response_values_to_transform; {} end
62
+ class CountryCode
63
+ def self.parse(value) INTERNATIONAL_DELIVERY_AREA_CODES[value] end
64
+ end
54
65
 
55
- def transform_keys! hash, transformation_index
56
- transformation_index.each do |k,v|
57
- hash[v] = hash.delete(k) if hash.has_key?(k)
58
- end
59
- hash
66
+ class NextDayAreaCode
67
+ def self.parse(value) ASURAKU_DELIVERY_AREA_CODES[value] end
60
68
  end
61
69
 
62
- def transform_values! hash, transformation_index
63
- transformation_index.each do |k,v|
64
- hash[k] = v[hash[k]] if hash.has_key?(k) && v.has_key?(hash[k])
70
+ class PurchaseType
71
+ def self.parse(value)
72
+ {"normal" => 0, "regular" => 1, "distribution" => 2}[value]
65
73
  end
66
- hash
67
74
  end
68
75
 
69
- def custom_transform(hash,key)
70
- yield hash, key if block_given? && hash.has_key?(key)
76
+ parameter :operation, :with => lambda { OPERATION }
77
+ parameter :version, :with => lambda { VERSION }
78
+ parameter :keyword
79
+ parameter :page
80
+ parameters :price do
81
+ parameter :minimum => 'minPrice'
82
+ parameter :maximum => 'maxPrice'
71
83
  end
72
- def custom_transform!(hash,key)
73
- hash[key] = yield hash, key if block_given? && hash.has_key?(key)
84
+ parameters :shipping do
85
+ parameter :must_ship_international => 'shipOverseasFlag', :with => TrueToOne
86
+ parameter :country => 'shipOverseasArea', :with => CountryCode
87
+ parameter :must_ship_next_day => 'asurakuFlag', :with => TrueToOne
88
+ parameter :next_day_area => 'asurakuArea', :with => NextDayAreaCode
89
+ parameter :must_include_cost => 'postageFlag', :with => TrueToOne
90
+ end
91
+ parameter :affiliate_id => 'affiliateId'
92
+ parameter :call_back => 'callBack'
93
+ parameter :items_per_page => 'hits'
94
+ parameter :shop_code => 'shopCode'
95
+ parameter :genre_id => 'genreId'
96
+ parameter :order => 'sort', :with => SortOrder
97
+ parameter :must_be_available => 'availability', :with => TrueToOne
98
+ parameter :deep_search => 'field', :with => TrueToZero
99
+ parameter :mobile => 'carrier', :with => TrueToOne
100
+ parameter :must_have_image => 'imageFlag', :with => TrueToOne
101
+ parameter :or_search => 'orFlag', :with => TrueToOne
102
+ parameter :exclude_keyword => 'NGKeyword'
103
+ parameter :include_genre_info => 'genreInformationFlag', :with => TrueToOne
104
+ parameter :purchase_type => 'purchaseType', :with => PurchaseType
105
+ parameter :must_have_point_multiplication => 'pointRateFlag', :with => TrueToOne
106
+ parameter :point_multiplication_factor => 'pointRate'
107
+ parameter :must_accept_credit_cards => 'creditCardFlag', :with => TrueToOne
108
+
109
+ def request
110
+ response = super
111
+ ItemList.parse(response)
74
112
  end
75
113
 
76
- def self.get(*args); handle_response super end
77
- def self.post(*args); handle_response super end
114
+ end
78
115
 
79
- def self.handle_response(response)
80
- case response["Header"]["Status"]
81
- when "Success"; response
82
- else; raise RakumarketError.new(Hashie::Mash.new({:status => response["Header"]["Status"], :status_msg => response["Header"]["StatusMsg"]}))
83
- end
116
+ class GenreSearchClient < Client
84
117
 
85
- Hashie::Mash.new(response)
118
+ OPERATION = "GenreSearch"
119
+ VERSION = "2007-04-11"
86
120
 
121
+ parameter :operation, :with => lambda { OPERATION }
122
+ parameter :version, :with => lambda { VERSION }
123
+ parameter :genre_id => 'genreId'
124
+ parameter :return_immediate_parent => 'genrePath', :with => TrueToZero
125
+
126
+ def request
127
+ response = super
128
+ Genre.parse(response)
129
+ end
130
+
131
+ end
132
+
133
+ class ItemLookupClient < Client
134
+ OPERATION = "ItemCodeSearch"
135
+ VERSION = "2010-08-05"
136
+
137
+ parameter :operation, :with => lambda { OPERATION }
138
+ parameter :version, :with => lambda { VERSION }
139
+ parameter :code => 'itemCode'
140
+ parameter :mobile => 'carrier', :with => TrueToOne
141
+
142
+ def request
143
+ response = super
144
+ Item.parse(response)
87
145
  end
88
146
  end
147
+
89
148
  end
@@ -0,0 +1,222 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Rakumarket
3
+
4
+ INTERNATIONAL_DELIVERY_AREA_CODES = {
5
+ "worldwide" => "ALL",
6
+ "usa" => "US",
7
+ "argentina" => "AR",
8
+ "brazil" => "BR",
9
+ "canada" => "CA",
10
+ "mexico" => "MX",
11
+ "austria" => "AT",
12
+ "belgium" => "BE",
13
+ "denmark" => "DK",
14
+ "france" => "FR",
15
+ "germany" => "DE",
16
+ "greece" => "GR",
17
+ "italy" => "IT",
18
+ "morocco" => "MA",
19
+ "netherlands" => "NL",
20
+ "poland" => "PL",
21
+ "portugal" => "PT",
22
+ "russia" => "RU",
23
+ "spain" => "ES",
24
+ "sweden" => "SE",
25
+ "switzerland" => "CH",
26
+ "turkey" => "TR",
27
+ "england" => "GB",
28
+ "australia" => "AU",
29
+ "china" => "CN",
30
+ "hong kong" => "HK",
31
+ "india" => "IN",
32
+ "indonesia" => "ID",
33
+ "south korea" => "KR",
34
+ "malaysia" => "MY",
35
+ "new zealand" => "NZ",
36
+ "philipines" => "PH",
37
+ "singapore" => "SG",
38
+ "taiwan" => "TW",
39
+ "thailand" => "TH",
40
+ "vietnam" => "VN"
41
+ }
42
+
43
+ INTERNATIONAL_DELIVERY_AREA_NAMES = {
44
+ "ワールドワイド" => "worldwide",
45
+ "アメリカ" => "usa",
46
+ "アルゼンチン" => "argentina",
47
+ "ブラジル" => "brazil",
48
+ "カナダ" => "canada",
49
+ "メキシコ" => "mexico",
50
+ "オーストリア" => "austria",
51
+ "ベルギー" => "belgium",
52
+ "デンマーク" => "denmark",
53
+ "フランス" => "france",
54
+ "ドイツ" => "germany",
55
+ "ギリシャ" => "greece",
56
+ "イタリア" => "italy",
57
+ "モロッコ" => "morocco",
58
+ "オランダ" => "netherlands",
59
+ "ポーランド" => "poland",
60
+ "ポルトガル" => "portugal",
61
+ "ロシア" => "russia",
62
+ "スペイン" => "spain",
63
+ "スウェーデン" => "sweden",
64
+ "スイス" => "switzerland",
65
+ "トルコ" => "turkey",
66
+ "英国" => "england",
67
+ "オーストラリア" => "australia",
68
+ "中国" => "china",
69
+ "香港" => "hong kong",
70
+ "インド" => "india",
71
+ "インドネシア" => "indonesia",
72
+ "韓国" => "south korea",
73
+ "マレーシア" => "malaysia",
74
+ "ニュージーランド" => "new zealand",
75
+ "フィリピン" => "philipines",
76
+ "シンガポール" => "singapore",
77
+ "台湾" => "taiwan",
78
+ "タイ" => "thailand",
79
+ "ベトナム" => "vietnam",
80
+ }
81
+
82
+ ASURAKU_DELIVERY_AREA_CODES = {
83
+ "all" => "0",
84
+ "hokkaido" => "1",
85
+ "tohoku" => "101",
86
+ "aomori" => "2",
87
+ "iwate" => "3",
88
+ "miyagi" => "4",
89
+ "akita" => "5",
90
+ "yamagata" => "6",
91
+ "fukushima" => "7",
92
+ "kanto" => "102",
93
+ "ibaraki" => "8",
94
+ "tochigi" => "9",
95
+ "gunma" => "10",
96
+ "saitama" => "11",
97
+ "chiba" => "12",
98
+ "tokyo" => "13",
99
+ "kanagawa" => "14",
100
+ "koshinetsu" => "103",
101
+ "niigata" => "15",
102
+ "yamanashi" => "19",
103
+ "nagano" => "20",
104
+ "hokuriku" => "104",
105
+ "toyama" => "16",
106
+ "ishikawa" => "17",
107
+ "fukui" => "18",
108
+ "tokai" => "105",
109
+ "gifu" => "21",
110
+ "shizuoka" => "22",
111
+ "aichi" => "23",
112
+ "mie" => "24",
113
+ "kansai" => "106",
114
+ "shiga" => "25",
115
+ "kyoto" => "26",
116
+ "osaka" => "27",
117
+ "hyogo" => "28",
118
+ "nara" => "29",
119
+ "wakayama" => "30",
120
+ "chugoku" => "107",
121
+ "tottori" => "31",
122
+ "shimane" => "32",
123
+ "okayama" => "33",
124
+ "hiroshima" => "34",
125
+ "yamaguchi" => "35",
126
+ "shikoku" => "108",
127
+ "tokushima" => "36",
128
+ "kagawa" => "37",
129
+ "ehime" => "38",
130
+ "kochi" => "39",
131
+ "kyushu" => "109",
132
+ "fukuoka" => "40",
133
+ "saga" => "41",
134
+ "nagasaki" => "42",
135
+ "kumamoto" => "43",
136
+ "ooita" => "44",
137
+ "miyaza" => "45",
138
+ "kagoshima" => "46",
139
+ "okinawa" => "47"
140
+ }
141
+
142
+ ASURAKU_DELIVERY_AREA_NAMES = {
143
+ "全地域" => "all",
144
+ "北海道エリアのすべて" => "hokkaido",
145
+ "北海道" => "hokkaido",
146
+ "東北エリアのすべて" => "tohoku",
147
+ "青森県" => "aomori",
148
+ "岩手県" => "iwate",
149
+ "宮城県" => "miyagi",
150
+ "秋田県" => "akita",
151
+ "山形県" => "yamagata",
152
+ "福島県" => "fukushima",
153
+ "関東エリアのすべて" => "kanto",
154
+ "茨城県" => "ibaraki",
155
+ "栃木県" => "tochigi",
156
+ "群馬県" => "gunma",
157
+ "埼玉県" => "saitama",
158
+ "千葉県" => "chiba",
159
+ "東京都" => "tokyo",
160
+ "神奈川県" => "kanagawa",
161
+ "甲信越エリアのすべて" => "koshinetsu",
162
+ "新潟県" => "niigata",
163
+ "山梨県" => "yamanashi",
164
+ "長野県" => "nagano",
165
+ "北陸エリアのすべて" => "hokuriku",
166
+ "富山県" => "toyama",
167
+ "石川県" => "ishikawa",
168
+ "福井県" => "fukui",
169
+ "東海エリアのすべて" => "tokai",
170
+ "岐阜県" => "gifu",
171
+ "静岡県" => "shizuoka",
172
+ "愛知県" => "aichi",
173
+ "三重県" => "mie",
174
+ "関西エリアのすべて" => "kansai",
175
+ "滋賀県" => "shiga",
176
+ "京都府" => "kyoto",
177
+ "大阪府" => "osaka",
178
+ "兵庫県" => "hyogo",
179
+ "奈良県" => "nara",
180
+ "和歌山県" => "wakayama",
181
+ "中国エリアのすべて" => "chugoku",
182
+ "鳥取県" => "tottori",
183
+ "島根県" => "shimane",
184
+ "岡山県" => "okayama",
185
+ "広島県" => "hiroshima",
186
+ "山口県" => "yamaguchi",
187
+ "四国エリアのすべて" => "shikoku",
188
+ "徳島県" => "tokushima",
189
+ "香川県" => "kagawa",
190
+ "愛媛県" => "ehime",
191
+ "高知県" => "kochi",
192
+ "九州エリアのすべて" => "kyushu",
193
+ "福岡県" => "fukuoka",
194
+ "佐賀県" => "saga",
195
+ "長崎県" => "nagasaki",
196
+ "熊本県" => "kumamoto",
197
+ "大分県" => "ooita",
198
+ "宮崎県" => "miyaza",
199
+ "鹿児島県" => "kagoshima",
200
+ "沖縄エリアのすべて" => "okinawa",
201
+ "沖縄県" => "okinawa"
202
+ }
203
+
204
+ ITEM_SEARCH_SORT_ORDERS = {
205
+ "affiliate_rate" => "+affiliateRate",
206
+ "affiliate_rate asc" => "+affiliateRate",
207
+ "affiliate_rate desc" => "-affiliateRate",
208
+ "review_count" => "+reviewCount",
209
+ "review_count asc" => "+reviewCount",
210
+ "review_count desc" => "-reviewCount",
211
+ "review_average" => "+reviewAverage",
212
+ "review_average asc" => "+reviewAverage",
213
+ "review_average desc" => "-reviewAverage",
214
+ "price" => "+itemPrice",
215
+ "price asc" => "+itemPrice",
216
+ "price desc" => "-itemPrice",
217
+ "updated_at" => "+updateTimestamp",
218
+ "updated_at asc" => "+updateTimestamp",
219
+ "updated_at desc" => "-updateTimestamp"
220
+ }
221
+
222
+ end
@@ -0,0 +1,125 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Rakumarket
4
+ class Base < NibblerJSON
5
+ end
6
+
7
+ class OneToTrue
8
+ def self.parse(val) {"1" => true, "0" => false}[val.to_s] end
9
+ end
10
+
11
+ class ZeroToTrue
12
+ def self.parse(val) {"0" => true, "1" => false}[val.to_s] end
13
+ end
14
+
15
+ class ParseTime < Time
16
+ def self.parse(val) super unless val.blank? end
17
+ end
18
+
19
+ class InternationalDeliveryCountryList
20
+ def self.parse(val)
21
+ val.split('/').map{|a| INTERNATIONAL_DELIVERY_AREA_NAMES[a] || a} if val.respond_to?(:split)
22
+ end
23
+ end
24
+
25
+ class NextDayAreaList
26
+ def self.parse(val)
27
+ val.split('/').map{|a| ASURAKU_DELIVERY_AREA_NAMES[a] || a} if val.respond_to?(:split)
28
+ end
29
+ end
30
+
31
+ class Shipping < Base
32
+ element 'postageFlag' => :cost_included, :with => ZeroToTrue
33
+ element 'shipOverseasFlag' => :international, :with => OneToTrue
34
+ element 'shipOverseasArea' => :countries, :with => InternationalDeliveryCountryList
35
+ element 'asurakuFlag' => :next_day, :with => OneToTrue
36
+ element 'asurakuArea' => :next_day_areas, :with => NextDayAreaList
37
+
38
+ def cost_included?; cost_included end
39
+ def international?; international end
40
+ def next_day?; next_day end
41
+ end
42
+
43
+ class Item < Base
44
+ element 'itemName' => :name
45
+ element 'catchcopy' => :catchphrase
46
+ element 'itemCode' => :code
47
+ element 'itemPrice' => :price
48
+ element 'itemCaption' => :caption
49
+ element 'itemUrl' => :url
50
+ element 'genreId' => :genre_id
51
+ element 'affiliateUrl' => :affiliate_url
52
+ element 'imageFlag' => :has_image, :with => OneToTrue
53
+ element 'smallImageUrl' => :small_image_url
54
+ element 'mediumImageUrl' => :medium_image_url
55
+ element 'availability' => :available, :with => OneToTrue
56
+ element 'taxFlag' => :tax_included, :with => ZeroToTrue
57
+ element 'creditCardFlag' => :credit_cards_accepted, :with => OneToTrue
58
+ element 'shopOfTheYearFlag' => :shop_of_the_year, :with => OneToTrue
59
+ element 'affiliateRate' => :affiliate_rate
60
+ element 'startTime' => :start_time, :with => ParseTime
61
+ element 'endTime' => :end_time, :with => ParseTime
62
+ element 'reviewCount' => :review_count
63
+ element 'reviewAverage' => :review_average
64
+ element 'pointRate' => :point_multiplication_factor
65
+ element 'pointRateStartTime' => :point_multiplication_start_time, :with => ParseTime
66
+ element 'pointRateEndTime' => :point_multiplication_end_time, :with => ParseTime
67
+ element :shop do
68
+ element 'shopName' => :name
69
+ element 'shopCode' => :code
70
+ element 'shopUrl' => :url
71
+ end
72
+ element :shipping, :with => Shipping
73
+
74
+ def has_image?; has_image end
75
+ def available?; available end
76
+ def tax_included?; tax_included end
77
+ def credit_cards_accepted?; credit_cards_accepted end
78
+ def shop_of_the_year?; shop_of_the_year end
79
+
80
+ def self.parse(response)
81
+ response = response.dup['Body']['ItemCodeSearch']['Items']['Item'].first if response['Body'] && response['Body']['ItemCodeSearch']
82
+ response['shop'] = {
83
+ 'shopName' => response.delete('shopName'),
84
+ 'shopCode' => response.delete('shopCode'),
85
+ 'shopUrl' => response.delete('shopUrl')
86
+ }
87
+ response['shipping'] = {
88
+ 'postageFlag' => response.delete('postageFlag'),
89
+ 'shipOverseasFlag' => response.delete('shipOverseasFlag'),
90
+ 'shipOverseasArea' => response.delete('shipOverseasArea'),
91
+ 'asurakuFlag' => response.delete('asurakuFlag'),
92
+ 'asurakuArea' => response.delete('asurakuArea')
93
+ }
94
+ super(response)
95
+ end
96
+ end
97
+
98
+ class ItemList < Base
99
+ element 'count' => :total_item_count
100
+ element 'pageCount' => :page_count
101
+ element :page
102
+ elements :items, :with => Item
103
+
104
+ def self.parse(response)
105
+ response = response.dup['Body']['ItemSearch']
106
+ response['items'] = response.delete('Items')['Item']
107
+ super(response)
108
+ end
109
+ end
110
+
111
+ class Genre < Base
112
+ element 'genreId' => :id
113
+ element 'genreName' => :name
114
+ element :parent, :with => Genre
115
+ elements 'child' => :children, :with => Genre
116
+
117
+ def self.parse(response)
118
+ response = response.dup['Body']['GenreSearch'] if response['Body']
119
+ response['genreId'] = response['current'].first['genreId'] if response['current'] && response['current'].any?
120
+ response['genreName'] = response['current'].first['genreName'] if response['current'] && response['current'].any?
121
+ super(response)
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,102 @@
1
+ # DSL for defining data extraction rules from an abstract document object
2
+ module SpitterMethods
3
+ def self.extended(base)
4
+ base.send(:include, InstanceMethods) if base.is_a? Class
5
+ end
6
+
7
+ # Declare a singular spitting rule
8
+ def parameter(*args, &block)
9
+ key, name, delegate = parse_rule_declaration(*args, &block)
10
+ rules[key] = [name, delegate]
11
+ key
12
+ end
13
+
14
+ # Declare a plural spitting rule
15
+ def parameters(*args, &block)
16
+ key = parameter(*args, &block)
17
+ rules[key] << true
18
+ end
19
+
20
+ # Parsing rules declared with `parameter` or `parameters`
21
+ def rules
22
+ @rules ||= {}
23
+ end
24
+
25
+ # Process data by creating a new instance
26
+ def parse(params) new(params).parse end
27
+
28
+ private
29
+
30
+ # Make subclasses inherit the parsing rules
31
+ def inherited(subclass)
32
+ super
33
+ subclass.rules.update self.rules
34
+ end
35
+
36
+ # Rule declaration forms:
37
+ #
38
+ # { :key => 'property', :with => delegate }
39
+ # #=> [:key, 'property', delegate]
40
+ #
41
+ # :title
42
+ # #=> [:title, 'title', nil]
43
+ def parse_rule_declaration(*args, &block)
44
+ options, name = Hash === args.last ? args.pop : {}, args.first
45
+ delegate = options.delete(:with)
46
+ key, property = name ? [name.to_sym, name.to_s] : options.to_a.flatten
47
+ property = property[key.to_s] if property.is_a?(Proc)
48
+ property = property.method(property.respond_to?(:call) ? :call : :parse)[key.to_s] if property.is_a?(Class)
49
+ raise ArgumentError, "invalid rule declaration: #{args.inspect}" unless property
50
+ # eval block in context of a new scraper subclass
51
+ delegate = Class.new(Spitter, &block) if block_given?
52
+ return key, property, delegate
53
+ end
54
+
55
+ def base_parser_class
56
+ klass = self
57
+ klass = klass.superclass until klass.superclass == Object
58
+ klass
59
+ end
60
+
61
+ module InstanceMethods
62
+
63
+ # Initialize the parser with a parameters
64
+ def initialize(params)
65
+ @params = params
66
+ end
67
+
68
+ def params
69
+ @params ||= {}
70
+ end
71
+
72
+ def parse
73
+ request_params = {}
74
+ self.class.rules.each do |target, (key, delegate, plural)|
75
+ if @params.has_key?(target)
76
+ if plural
77
+ request_params.merge!(delegate.parse(@params[target]))
78
+ else
79
+ request_params[key] = parse_result(@params[target], delegate)
80
+ end
81
+ end
82
+ end
83
+ request_params
84
+ end
85
+
86
+ protected
87
+
88
+ # `delegate` is optional, but should respond to `call` or `parse`
89
+ def parse_result(node, delegate)
90
+ if delegate
91
+ method = delegate.is_a?(Proc) ? delegate : delegate.method(delegate.respond_to?(:call) ? :call : :parse)
92
+ method.arity == 1 ? method[node] : (method.arity == 2 ? method[node, self] : method[])
93
+ else
94
+ node
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ class Spitter
101
+ extend SpitterMethods
102
+ end
@@ -1,3 +1,3 @@
1
1
  module Rakumarket
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end