sunflower 0.4.5 → 0.5

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
@@ -1,4 +1,4 @@
1
- Version: 0.4.5 alpha
1
+ Version: 0.5 alpha
2
2
 
3
3
  >>> English:
4
4
 
data/example-bot.rb CHANGED
@@ -1,12 +1,11 @@
1
+ # coding: utf-8
1
2
  # This is the most basic bot possible.
2
3
 
3
- require 'sunflower-commontasks.rb'
4
+ require 'sunflower'
4
5
 
5
- s=Sunflower.new
6
- s.login
6
+ s = Sunflower.new.login
7
+ s.summary = 'test summary'
7
8
 
8
- $summary='Sunflower: test'
9
-
10
- p=Page.get('Test')
11
- p.write p.text+"\n\ntest"
12
- p.save
9
+ p = Sunflower::Page.new 'Test'
10
+ p.text += "\n\ntest"
11
+ p.save
@@ -1,80 +1,6 @@
1
1
  # coding: utf-8
2
- # extends Page with some methods letting easily perform common tasks
3
2
 
4
- class Page
5
- # executes methods on self
6
- # "commands" is array of arrays
7
- # page.execute([
8
- # [:replace, 'something', 'whatever'],
9
- # [:append, 'some things']
10
- # ])
11
- # equals to
12
- # page.replace('something', 'whatever')
13
- # page.append('some things')
14
-
15
- # allowed modifiers:
16
- # r, required
17
- # oi:module, only-if:module
18
- # !oi:module, only-if-not:module
19
- # s:append to summary, summary:append to summary
20
- def execute commands
21
- originalText = self.text.dup
22
-
23
- commands.each do |cmd|
24
- f=cmd.shift
25
- if f.class==Array
26
- methodName=f.shift
27
- modifiers=f.map{|i|
28
- i+=':' if !i.include? ':'
29
- i=i.split(':',-1)
30
- i[0]=i[0].downcase.gsub(/[^a-z!]/,'')
31
-
32
- i[0]='r' if i[0]=='required'
33
- i[0]='oi' if i[0]=='onlyif'
34
- i[0]='!oi' if i[0]=='onlyifnot'
35
- i[0]='s' if i[0]=='summary'
36
-
37
- type=i.shift
38
- i=i.join(':')
39
-
40
- [type,i]
41
- }
42
- modifiers=Hash[*(modifiers.flatten)]
43
- else
44
- methodName=f
45
- modifiers={}
46
- end
47
-
48
- if modifiers['oi']
49
- if !@modulesExecd.index(modifiers['oi'].strip)
50
- next #skip this command
51
- end
52
- end
53
- if modifiers['!oi']
54
- if @modulesExecd.index(modifiers['oi'].strip)
55
- next #skip this command
56
- end
57
- end
58
-
59
- oldText=self.text
60
- self.method(methodName).call(*cmd)
61
- newText=self.text
62
-
63
- @modulesExecd<<methodName if oldText!=newText
64
-
65
- if modifiers['s'] && oldText!=newText
66
- @summaryAppend<<modifiers['s'].strip
67
- end
68
-
69
- if modifiers['r'] && oldText==newText
70
- self.text = originalText
71
- break #reset text and stop executing commands
72
- end
73
- end
74
- end
75
-
76
-
77
-
3
+ class Sunflower::Page
78
4
  # replaces "from" with "to" in page text
79
5
  # "from" may be regex
80
6
  def replace from, to, once=false
@@ -152,7 +78,7 @@ class Page
152
78
 
153
79
  str.gsub!(/\[\[([^\|\]]+)(\||\]\])/){
154
80
  name, rest = $1, $2
155
- "[[#{self.sunflower.cleanup_title name}#{rest}"
81
+ "[[#{self.sunflower.cleanup_title name, true, true}#{rest}"
156
82
  }
157
83
 
158
84
  # headings
@@ -164,7 +90,7 @@ class Page
164
90
 
165
91
  if wikiid = self.sunflower.siteinfo['general']['wikiid']
166
92
  if self.respond_to? :"code_cleanup_#{wikiid}"
167
- str = self.call :"code_cleanup_#{wikiid}", str
93
+ str = self.send :"code_cleanup_#{wikiid}", str
168
94
  end
169
95
  end
170
96
 
@@ -3,8 +3,6 @@ require 'rest-client'
3
3
  require 'json'
4
4
  require 'cgi'
5
5
 
6
- class SunflowerError < StandardError; end
7
-
8
6
  # Main class. To start working, you have to create new Sunflower:
9
7
  # s = Sunflower.new('en.wikipedia.org')
10
8
  # And then log in:
@@ -19,7 +17,7 @@ class SunflowerError < StandardError; end
19
17
  #
20
18
  # You can use multiple Sunflowers at once, to work on multiple wikis.
21
19
  class Sunflower
22
- VERSION = '0.4.5'
20
+ VERSION = '0.5'
23
21
 
24
22
  INVALID_CHARS = %w(# < > [ ] | { })
25
23
  INVALID_CHARS_REGEX = Regexp.union *INVALID_CHARS
@@ -28,7 +26,19 @@ class Sunflower
28
26
  def self.path
29
27
  File.join(ENV['HOME'], 'sunflower-userdata')
30
28
  end
31
-
29
+
30
+ # Returns array of [url, username, password], or nil if userdata is unavailable or invalid.
31
+ def self.read_userdata
32
+ data = nil
33
+ data = File.read(Sunflower.path).split(/\r?\n/).map{|i| i.strip} rescue nil
34
+
35
+ if data && data.length==3 && data.all?{|a| a and a != ''}
36
+ return data
37
+ else
38
+ return nil
39
+ end
40
+ end
41
+
32
42
  # Summary used when saving edits with this Sunflower.
33
43
  attr_accessor :summary
34
44
  # Whether to run #code_cleanup when calling #save.
@@ -38,6 +48,11 @@ class Sunflower
38
48
  # Siteinfo, as returned by API call.
39
49
  attr_accessor :siteinfo
40
50
 
51
+ # Whether we are logged in.
52
+ def logged_in?; @loggedin; end
53
+ # Username if logged in; nil otherwise.
54
+ attr_reader :username
55
+
41
56
  # Whether this user (if logged in) has bot rights.
42
57
  def is_bot?; @is_bot; end
43
58
 
@@ -49,29 +64,72 @@ class Sunflower
49
64
  attr_writer :log
50
65
  def log?; @log; end
51
66
 
52
- # Initialize a new Sunflower working on a wiki with given URL, for ex. "pl.wikipedia.org".
53
- def initialize url=nil
54
- begin
55
- r=File.read(Sunflower.path)
56
- @userdata=r.split(/\r?\n/).map{|i| i.strip}
57
- rescue
58
- @userdata=[]
59
- end
67
+ # Used by #initialize to convert short identifiers such as "b:pl" to domains such as "pl.wikibooks.org".
68
+ # Identifier is of the format "type:lang" or "lang:type" (see below for valid values).
69
+ #
70
+ # Either or both parts can be ommitted; default type is "w", default lang is "en".
71
+ # (Since clashes are impossible, the colon can be ommitted in such cases as well.)
72
+ #
73
+ # lang can be any valid language code. It is ignored for type == "meta" or "commons".
74
+ #
75
+ # Valid values for type are the same as used for inter-wiki links, that is:
76
+ # [w] Wikipedia
77
+ # [b] Wikibooks
78
+ # [n] Wikinews
79
+ # [q] Wikiquote
80
+ # [s] Wikisource
81
+ # [v] Wikiversity
82
+ # [wikt] Wiktionary
83
+ # [species] Wikispecies
84
+ # [commons] Wikimedia Commons
85
+ # [meta] Wikimedia Meta-Wiki
86
+ def self.resolve_wikimedia_id id
87
+ keys = id.split(':').select{|a| a and !a.empty? }
88
+
89
+ raise ArgumentError, 'invalid format' if keys.length > 2
90
+
91
+ type_map = {
92
+ 'b' => 'XX.wikibooks.org',
93
+ 'q' => 'XX.wikiquote.org',
94
+ 'n' => 'XX.wikinews.org',
95
+ 'w' => 'XX.wikipedia.org',
96
+ 'wikt' => 'XX.wiktionary.org',
97
+ 'species' => 'XX.wikispecies.org',
98
+ 'v' => 'XX.wikiversity.org',
99
+ 's' => 'XX.wikisource.org',
100
+ 'commons' => 'commons.wikimedia.org',
101
+ 'meta' => 'meta.wikimedia.org',
102
+ }
103
+
104
+ types, langs = keys.partition{|a| type_map.keys.include? a }
105
+ type = types.first || 'w'
106
+ lang = langs.first || 'en'
60
107
 
108
+ return type_map[type].sub 'XX', lang
109
+ end
110
+
111
+ # Initialize a new Sunflower working on a wiki with given URL, for ex. "pl.wikipedia.org". url can also be a shorthand identifier such as "b:pl" - see Sunflower.resolve_wikimedia_id for details.
112
+ def initialize url=nil
61
113
  if !url
62
- if !@userdata.empty?
63
- url=@userdata[0]
114
+ userdata = Sunflower.read_userdata()
115
+
116
+ if userdata
117
+ url = userdata[0]
64
118
  else
65
- raise SunflowerError, 'initialize: no URL supplied and no userdata found!'
119
+ raise Sunflower::Error, 'initialize: no URL supplied and no userdata found!'
66
120
  end
67
121
  end
68
122
 
69
- @warnings=true
70
- @log=false
123
+ @wikiURL = (url.include?('.') ? url : Sunflower.resolve_wikimedia_id(url))
124
+
125
+ @warnings = true
126
+ @log = false
71
127
 
72
- @wikiURL=url
128
+ @loggedin = false
129
+ @username = nil
130
+ @is_bot = false
73
131
 
74
- @loggedin=false
132
+ @cookies = {}
75
133
 
76
134
  siprop = 'general|namespaces|namespacealiases|specialpagealiases|magicwords|interwikimap|dbrepllag|statistics|usergroups|extensions|fileextensions|rightsinfo|languages|skins|extensiontags|functionhooks|showhooks|variables'
77
135
  @siteinfo = self.API(action: 'query', meta: 'siteinfo', siprop: siprop)['query']
@@ -79,6 +137,10 @@ class Sunflower
79
137
  _build_ns_map
80
138
  end
81
139
 
140
+ def inspect
141
+ "#<Sunflower #{@loggedin ? @username : "[anon]"}@#{@wikiURL}#{@is_bot ? ' [bot]' : ''}>"
142
+ end
143
+
82
144
  # Private. Massages data from siteinfo to be used for recognizing namespaces.
83
145
  def _build_ns_map
84
146
  @namespace_to_id = {} # all keys lowercase
@@ -99,6 +161,7 @@ class Sunflower
99
161
  @namespace_to_id[ h['*'].downcase ] = h['id'].to_i
100
162
  end
101
163
  end
164
+ private :_build_ns_map
102
165
 
103
166
  # Call the API. Returns a hash of JSON response. Request can be a HTTP request string or a hash.
104
167
  def API request
@@ -116,25 +179,69 @@ class Sunflower
116
179
  JSON.parse resp.to_str
117
180
  end
118
181
 
182
+ # Call the API. While more results are available via the xxcontinue parameter, call it again.
183
+ #
184
+ # Assumes action=query.
185
+ #
186
+ # By default returns an array of all API responses. Attempts to merge the responses
187
+ # into a response that would have been returned if the limit was infinite. merge_on is
188
+ # the key of response['query'] to merge consecutive responses on.
189
+ #
190
+ # If limit given, will perform no more than this many API calls before returning.
191
+ # If limit is 1, behaves exactly like #API.
192
+ #
193
+ # Example: get list of all pages linking to Main Page:
194
+ #
195
+ # sunflower.API_continued "action=query&list=backlinks&bllimit=max&bltitle=Main_Page", 'backlinks', 'blcontinue'
196
+ def API_continued request, merge_on, xxcontinue, limit=nil
197
+ out = []
198
+
199
+ # gather
200
+ res = self.API(request)
201
+ out << res
202
+ while res['query-continue'] and (!limit || out.length < limit)
203
+ api_endpoint = if request.is_a? String
204
+ request + "&#{xxcontinue}=#{res["query-continue"][merge_on][xxcontinue]}"
205
+ elsif request.is_a? Hash
206
+ request.merge({xxcontinue => res["query-continue"][merge_on][xxcontinue]})
207
+ end
208
+
209
+ res = self.API(api_endpoint)
210
+ out << res
211
+ end
212
+
213
+ # merge
214
+ merged = out[0]
215
+ meth = (merged['query'][merge_on].is_a?(Hash) ? :merge! : :concat)
216
+
217
+ out.drop(1).each do |cur|
218
+ merged['query'][merge_on].send meth, cur['query'][merge_on]
219
+ end
220
+
221
+ return merged
222
+ end
223
+
119
224
  # Log in using given info.
120
225
  def login user='', password=''
121
226
  if user=='' || password==''
122
- if !@userdata.empty?
123
- user=@userdata[1] if user==''
124
- password=@userdata[2] if password==''
227
+ userdata = Sunflower.read_userdata()
228
+
229
+ if userdata
230
+ user = userdata[1] if user==''
231
+ password = userdata[2] if password==''
125
232
  else
126
- raise SunflowerError, 'login: no user/pass supplied and no userdata found!'
233
+ raise Sunflower::Error, 'login: no user/pass supplied and no userdata found!'
127
234
  end
128
235
  end
129
236
 
130
- raise SunflowerError, 'bad username!' if user =~ INVALID_CHARS_REGEX
237
+ raise Sunflower::Error, 'bad username!' if user =~ INVALID_CHARS_REGEX
131
238
 
132
239
 
133
240
  # 1. get the login token
134
241
  response = RestClient.post(
135
242
  'http://'+@wikiURL+'/w/api.php?'+"action=login&lgname=#{CGI.escape user}&lgpassword=#{CGI.escape password}"+'&format=json',
136
243
  nil,
137
- {:user_agent => 'Sunflower alpha'}
244
+ {:user_agent => "Sunflower #{VERSION} alpha"}
138
245
  )
139
246
 
140
247
  @cookies = response.cookies
@@ -146,7 +253,7 @@ class Sunflower
146
253
  response = RestClient.post(
147
254
  'http://'+@wikiURL+'/w/api.php?'+"action=login&lgname=#{CGI.escape user}&lgpassword=#{CGI.escape password}&lgtoken=#{token}"+'&format=json',
148
255
  nil,
149
- {:user_agent => 'Sunflower alpha', :cookies => @cookies}
256
+ {:user_agent => "Sunflower #{VERSION} alpha", :cookies => @cookies}
150
257
  )
151
258
 
152
259
  json = JSON.parse response.to_str
@@ -158,7 +265,7 @@ class Sunflower
158
265
  })
159
266
 
160
267
 
161
- raise SunflowerError, 'unable to log in (no cookies received)!' if !@cookies
268
+ raise Sunflower::Error, 'unable to log in (no cookies received)!' if !@cookies
162
269
 
163
270
 
164
271
  # 3. confirm you did log in by checking the watchlist.
@@ -166,9 +273,12 @@ class Sunflower
166
273
  r=self.API('action=query&list=watchlistraw')
167
274
  if r['error'] && r['error']['code']=='wrnotloggedin'
168
275
  @loggedin=false
169
- raise SunflowerError, 'unable to log in!'
276
+ raise Sunflower::Error, 'unable to log in!'
170
277
  end
171
278
 
279
+ # set the username
280
+ @username = user
281
+
172
282
  # 4. check bot rights
173
283
  r=self.API('action=query&list=allusers&aulimit=1&augroup=bot&aufrom='+(CGI.escape user))
174
284
  unless r['query']['allusers'][0]['name']==user
@@ -187,7 +297,9 @@ class Sunflower
187
297
  end
188
298
 
189
299
  # Cleans up underscores, percent-encoding and title-casing in title (with optional anchor).
190
- def cleanup_title title
300
+ def cleanup_title title, preserve_case=false, preserve_colon=false
301
+ return '' if title.strip == ''
302
+
191
303
  name, anchor = title.split '#', 2
192
304
 
193
305
  # CGI.unescape also changes pluses to spaces; code borrowed from there
@@ -197,6 +309,10 @@ class Sunflower
197
309
  name = unescape.call(name).gsub(/[ _]+/, ' ').strip
198
310
  anchor = unescape.call(anchor.gsub(/\.([0-9a-fA-F]{2})/, '%\1')).gsub(/[ _]+/, ' ').strip if anchor
199
311
 
312
+ leading_colon = name[0]==':'
313
+ name = name.sub(/^:\s*/, '') if leading_colon
314
+ leading_colon = false if !preserve_colon
315
+
200
316
  # FIXME unicode? downcase, upcase
201
317
 
202
318
  if name.include? ':'
@@ -206,9 +322,9 @@ class Sunflower
206
322
  end
207
323
  end
208
324
 
209
- name[0] = name[0].upcase if @siteinfo["general"]["case"] == "first-letter"
325
+ name[0] = name[0].upcase if !preserve_case and @siteinfo["general"]["case"] == "first-letter"
210
326
 
211
- return [ns ? "#{ns}:" : nil, name, anchor ? "##{anchor}" : nil].join ''
327
+ return [leading_colon ? ':' : nil, ns ? "#{ns}:" : nil, name, anchor ? "##{anchor}" : nil].join ''
212
328
  end
213
329
 
214
330
  # Returns the localized namespace name for ns, which may be namespace number, canonical name, or any namespace alias.
@@ -245,76 +361,103 @@ class Sunflower
245
361
  end
246
362
 
247
363
  # Class representing a single Wiki page. To load specified page, use #new. To save it back, use #save.
248
- class Page
364
+ class Sunflower::Page
249
365
  # Characters which MediaWiki does not permit in page title.
250
366
  INVALID_CHARS = %w(# < > [ ] | { })
251
367
  # Regex matching characters which MediaWiki does not permit in page title.
252
368
  INVALID_CHARS_REGEX = Regexp.union *INVALID_CHARS
253
369
 
254
- # The current text of the page.
370
+ # The Sunflower instance this page belongs to.
371
+ attr_reader :sunflower
372
+
373
+ # The current text of the page. Lazy-loaded.
255
374
  attr_accessor :text
256
- # The text of the page, as of when it was loaded.
375
+ # The text of the page, as of when it was loaded. Lazy-loaded.
257
376
  attr_reader :orig_text
258
377
 
259
- # The Sunflower instance this page belongs to.
260
- attr_reader :sunflower
378
+ # Page title, as passed to #initialize and cleaned by Sunflower#cleanup_title.
379
+ # Real page title as canonicalized by MediaWiki software can be accessed via #real_title
380
+ # (but it should always be the same).
381
+ attr_reader :title
261
382
 
262
- # this is only for RDoc. wrapped in "if false" to avoid warnings when running with ruby -w
263
- if false
264
- # Return value of given attribute, as returned by API call prop=info for this page. Lazy-loaded.
265
- attr_reader :pageid, :ns, :title, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection
266
- end
383
+ # Value of given attribute, as returned by API call prop=info for this page. Lazy-loaded.
384
+ attr_reader :pageid, :ns, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection
385
+ # Value of `title` attribute, as returned by API call prop=info for this page. Lazy-loaded. See #title.
386
+ attr_reader :real_title
387
+
388
+ # Whether this datum is already loaded. Can be set to true to suppress loading
389
+ # (used e.g. by Sunflower::List#pages_preloaded)
390
+ attr_accessor :preloaded_text, :preloaded_attrs
267
391
 
268
392
  # calling any of these accessors will fetch the data.
269
- [:pageid, :ns, :title, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection].each do |meth|
393
+ # getters...
394
+ [:pageid, :ns, :real_title, :touched, :lastrevid, :counter, :length, :starttimestamp, :edittoken, :protection].each do |meth|
395
+ remove_method meth # to avoid warnings when running with ruby -w
270
396
  define_method meth do
271
397
  preload_attrs unless @preloaded_attrs
272
398
  instance_variable_get "@#{meth}"
273
399
  end
274
400
  end
401
+ [:text, :orig_text].each do |meth|
402
+ remove_method meth # to avoid warnings when running with ruby -w
403
+ define_method meth do
404
+ preload_text unless @preloaded_text
405
+ instance_variable_get "@#{meth}"
406
+ end
407
+ end
408
+ # setters...
409
+ [:text=].each do |meth|
410
+ remove_method meth # to avoid warnings when running with ruby -w
411
+ define_method meth do |a|
412
+ preload_text unless @preloaded_text
413
+ instance_variable_set "@#{meth.to_s.chop}", a
414
+ end
415
+ end
275
416
 
276
- # Load the specified page. Only the text will be immediately loaded - attributes and edit token will be loaded when needed, or when you call #preload_attrs.
277
- #
278
- # If you are using multiple Sunflowers, you have to specify which wiki this page belongs to using the second argument of function; you can pass whole URL (same as when creating new Sunflower) or just the language code.
279
- def initialize title='', wiki=''
280
- raise SunflowerError, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
417
+ # Load the specified page.
418
+ # Only the text will be immediately loaded - attributes and edit token will be loaded when needed, or when you call #preload_attrs.
419
+ #
420
+ # If you are using multiple Sunflowers, you have to specify which one this page belongs to using the second argument of function.
421
+ # You can pass either a Sunflower object, wiki URL, or a shorthand id as specified in Sunflower.resolve_wikimedia_id.
422
+ def initialize title='', url=''
423
+ raise Sunflower::Error, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
281
424
 
282
425
  @modulesExecd=[] #used by sunflower-commontasks.rb
283
426
  @summaryAppend=[] #used by sunflower-commontasks.rb
284
427
 
285
- wiki = wiki+'.wikipedia.org' if wiki.index('.')==nil && wiki!=''
286
-
287
- if wiki==''
288
- count=ObjectSpace.each_object(Sunflower){|o| @sunflower=o}
289
- raise SunflowerError, 'no Sunflowers present' if count==0
290
- raise SunflowerError, 'you must pass wiki name if using multiple Sunflowers at once' if count>1
428
+ case url
429
+ when Sunflower
430
+ @sunflower = url
431
+ when '', nil
432
+ count = ObjectSpace.each_object(Sunflower){|o| @sunflower=o}
433
+ raise Sunflower::Error, 'no Sunflowers present' if count==0
434
+ raise Sunflower::Error, 'you must pass wiki name if using multiple Sunflowers at once' if count>1
291
435
  else
292
- ObjectSpace.each_object(Sunflower){|o| @sunflower=o if o.wikiURL==wiki}
436
+ url = (url.include?('.') ? url : Sunflower.resolve_wikimedia_id(url))
437
+ ObjectSpace.each_object(Sunflower){|o| @sunflower=o if o.wikiURL==url}
438
+ raise Sunflower::Error, "no Sunflower for #{url}" if !@sunflower
293
439
  end
294
440
 
295
- raise SunflowerError, "no Sunflower for #{wiki}" if !@sunflower
296
-
297
441
  @title = @sunflower.cleanup_title title
298
442
 
299
- if title==''
300
- @text=''
301
- @orig_text=''
302
- return
303
- end
304
-
305
- preload_text
443
+ @preloaded_text = false
444
+ @preloaded_attrs = false
306
445
  end
307
446
 
308
447
  # Load the text of this page. Semi-private.
309
448
  def preload_text
310
- r = @sunflower.API('action=query&prop=revisions&rvprop=content&titles='+CGI.escape(@title))
311
- r = r['query']['pages'].first
312
- if r['missing']
449
+ if title == ''
313
450
  @text = ''
314
- elsif r['invalid']
315
- raise SunflowerError, 'title invalid: '+@title
316
451
  else
317
- @text = r['revisions'][0]['*']
452
+ r = @sunflower.API('action=query&prop=revisions&rvprop=content&titles='+CGI.escape(@title))
453
+ r = r['query']['pages'].values.first
454
+ if r['missing']
455
+ @text = ''
456
+ elsif r['invalid']
457
+ raise Sunflower::Error, 'title invalid: '+@title
458
+ else
459
+ @text = r['revisions'][0]['*']
460
+ end
318
461
  end
319
462
 
320
463
  @orig_text = @text.dup
@@ -325,8 +468,9 @@ class Page
325
468
  # Load the metadata associated with this page. Semi-private.
326
469
  def preload_attrs
327
470
  r = @sunflower.API('action=query&prop=info&inprop=protection&intoken=edit&titles='+CGI.escape(@title))
328
- r = r['query']['pages'].first
471
+ r = r['query']['pages'].values.first
329
472
  r.each{|key, value|
473
+ key = 'real_title' if key == 'title'
330
474
  self.instance_variable_set('@'+key, value)
331
475
  }
332
476
 
@@ -359,8 +503,8 @@ class Page
359
503
 
360
504
  summary = @sunflower.summary if !summary
361
505
 
362
- raise SunflowerError, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
363
- raise SunflowerError, 'no summary!' if (!summary or summary=='') && @summaryAppend.empty?
506
+ raise Sunflower::Error, 'title invalid: '+title if title =~ INVALID_CHARS_REGEX
507
+ raise Sunflower::Error, 'no summary!' if (!summary or summary=='') && @summaryAppend.empty?
364
508
 
365
509
  unless @summaryAppend.empty?
366
510
  if !summary or summary==''
@@ -383,18 +527,53 @@ class Page
383
527
  alias :put :save
384
528
 
385
529
  def self.get title, wiki=''
386
- Page.new(title, wiki)
530
+ self.new(title, wiki)
387
531
  end
388
532
 
389
533
  def self.load title, wiki=''
390
- Page.new(title, wiki)
534
+ self.new(title, wiki)
535
+ end
536
+ end
537
+
538
+ # For backwards compatibility. Deprecated.
539
+ class Page # :nodoc:
540
+ class << self
541
+ def new *a
542
+ warn "warning: toplevel Page class has been renamed to Sunflower::Page, this alias will be removed in v0.6"
543
+ Sunflower::Page.new *a
544
+ end
545
+ alias get new
546
+ alias load new
391
547
  end
392
548
  end
393
549
 
394
- class Hash
395
- # just a lil patch
396
- def first
397
- self.values[0]
550
+ # For backwards compatibility. Deprecated.
551
+ #
552
+ # We use inheritance shenanigans to keep the usage in "begin ... rescue ... end" working.
553
+ class SunflowerError < StandardError # :nodoc:
554
+ %w[== backtrace exception inspect message set_backtrace to_s].each do |meth|
555
+ define_method meth.to_sym do |*a, &b|
556
+ if self.class == Sunflower::Error and !@warned
557
+ warn "warning: toplevel SunflowerError class has been renamed to Sunflower::Error, this alias will be removed in v0.6"
558
+ @warned = true
559
+ end
560
+
561
+ super *a, &b
562
+ end
563
+ end
564
+
565
+ class << self
566
+ def new *a
567
+ warn "warning: toplevel SunflowerError class has been renamed to Sunflower::Error, this alias will be removed in v0.6" unless self == Sunflower::Error
568
+ super
569
+ end
570
+ def exception *a
571
+ warn "warning: toplevel SunflowerError class has been renamed to Sunflower::Error, this alias will be removed in v0.6" unless self == Sunflower::Error
572
+ super
573
+ end
398
574
  end
399
575
  end
400
576
 
577
+
578
+ # Represents an error raised by Sunflower.
579
+ class Sunflower::Error < SunflowerError; end