sunflower 0.4.5 → 0.5

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