Nix warning on edit collision since we do that now
[mw] / src / mw / clicommands.py
1 ###
2 # mw - VCS-like nonsense for MediaWiki websites
3 # Copyright (C) 2010  Ian Weller <ian@ianweller.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program.  If not, see <http://www.gnu.org/licenses/>.
17 ###
18
19 import codecs
20 import getpass
21 import hashlib
22 import mw.api
23 import mw.metadir
24 from optparse import OptionParser, OptionGroup
25 import os
26 import sys
27
28
29 class CommandBase(object):
30
31     def __init__(self, name, description, usage=None):
32         self.me = os.path.basename(sys.argv[0])
33         self.description = description
34         if usage is None:
35             usage = '%prog ' + name
36         else:
37             usage = '%%prog %s %s' % (name, usage)
38         self.parser = OptionParser(usage=usage, description=description)
39         self.name = name
40         self.metadir = mw.metadir.Metadir()
41         self.shortcuts = []
42
43     def main(self):
44         (self.options, self.args) = self.parser.parse_args()
45         self.args = self.args[1:] # don't need the first thing
46         self._do_command()
47
48     def _do_command(self):
49         pass
50
51     def _login(self):
52         user = raw_input('Username: ')
53         passwd = getpass.getpass()
54         result = self.api.call({'action': 'login',
55                                 'lgname': user,
56                                 'lgpassword': passwd})
57         if result['login']['result'] == 'Success':
58             # cookies are saved to a file
59             print 'Login successful! (yay)'
60         elif result['login']['result'] == 'NeedToken':
61             print 'Login with token'
62             result = self.api.call({'action': 'login',
63                                     'lgname': user,
64                                     'lgpassword': passwd,
65                                     'lgtoken': result['login']['token']})
66             if result['login']['result'] == 'Success':
67                 print 'Login successful! (yay)'
68             else:
69                 print 'Login failed: %s' % result['login']['result']
70         else:
71             print 'Login failed: %s' % result['login']['result']
72
73     def _die_if_no_init(self):
74         if self.metadir.config is None:
75             print '%s: not a mw repo' % self.me
76             sys.exit(1)
77
78     def _api_setup(self):
79         self.api_url = self.metadir.config.get('remote', 'api_url')
80         self.api = mw.api.API(self.api_url, self.metadir)
81
82
83 class InitCommand(CommandBase):
84
85     def __init__(self):
86         usage = 'API_URL'
87         CommandBase.__init__(self, 'init', 'start a mw repo', usage)
88
89     def _do_command(self):
90         if len(self.args) < 1:
91             self.parser.error('must have URL to remote api.php')
92         elif len(self.args) > 1:
93             self.parser.error('too many arguments')
94         self.metadir.create(self.args[0])
95
96
97 class LoginCommand(CommandBase):
98
99     def __init__(self):
100         CommandBase.__init__(self, 'login', 'authenticate with wiki')
101
102     def _do_command(self):
103         self._die_if_no_init()
104         self._api_setup()
105         self._login()
106
107
108 class LogoutCommand(CommandBase):
109
110     def __init__(self):
111         CommandBase.__init__(self, 'logout', 'forget authentication')
112
113     def _do_command(self):
114         self._die_if_no_init()
115         try:
116             os.unlink(os.path.join(self.metadir.location, 'cookies'))
117         except OSError:
118             pass
119
120
121 class PullCommand(CommandBase):
122
123     def __init__(self):
124         usage = '[options] PAGENAME ...'
125         CommandBase.__init__(self, 'pull', 'add remote pages to repo', usage)
126
127     def _do_command(self):
128         self._die_if_no_init()
129         self._api_setup()
130         pages = []
131         pages += self.args
132         for these_pages in [pages[i:i + 25] for i in range(0, len(pages), 25)]:
133             data = {
134                     'action': 'query',
135                     'titles': '|'.join(these_pages),
136                     'prop': 'info|revisions',
137                     'rvprop': 'ids|flags|timestamp|user|comment|content',
138             }
139             response = self.api.call(data)['query']['pages']
140             for pageid in response.keys():
141                 pagename = response[pageid]['title']
142                 if 'missing' in response[pageid].keys():
143                     print '%s: %s: page does not exist, file not created' % \
144                             (self.me, pagename)
145                     continue
146                 revids = [x['revid'] for x in response[pageid]['revisions']]
147                 revids.sort()
148                 self.metadir.pagedict_add(pagename, pageid, revids[-1])
149                 self.metadir.pages_add_rv(int(pageid),
150                                           response[pageid]['revisions'][0])
151                 filename = mw.api.pagename_to_filename(pagename)
152                 with file(os.path.join(self.metadir.root, filename + '.wiki'), 'w') as fd:
153                   fd.write(response[pageid]['revisions'][0]['*'].encode('utf-8'))
154
155 class StatusCommand(CommandBase):
156
157     def __init__(self):
158         CommandBase.__init__(self, 'status', 'check repo status')
159         self.shortcuts.append('st')
160
161     def _do_command(self):
162         self._die_if_no_init()
163         status = self.metadir.working_dir_status()
164         for file in status:
165             print '%s %s' % (status[file], file)
166
167
168 class DiffCommand(CommandBase):
169
170     def __init__(self):
171         CommandBase.__init__(self, 'diff', 'diff wiki to working directory')
172
173     def _do_command(self):
174         self._die_if_no_init()
175         status = self.metadir.working_dir_status()
176         for file in status:
177             if status[file] == 'U':
178                 print self.metadir.diff_rv_to_working(
179                         mw.api.filename_to_pagename(file[:-5])),
180
181
182 class CommitCommand(CommandBase):
183
184     def __init__(self):
185         usage = '[FILES]'
186         CommandBase.__init__(self, 'commit', 'commit changes to wiki', usage)
187         self.shortcuts.append('ci')
188         self.parser.add_option('-m', '--message', dest='edit_summary',
189                                help='don\'t prompt for edit summary and '
190                                'use this instead')
191         self.parser.add_option('-b', '--bot', dest='bot', action='store_true',
192                                help='mark actions as a bot (won\'t affect '
193                                'anything if you don\'t have the bot right',
194                                default=False)
195
196     def _do_command(self):
197         self._die_if_no_init()
198         self._api_setup()
199         status = self.metadir.working_dir_status(files=self.args)
200         nothing_to_commit = True
201         for file in status:
202             print '%s %s' % (status[file], file)
203             if status[file] in ['U']:
204                 nothing_to_commit = False
205         if nothing_to_commit:
206             print 'nothing to commit'
207             sys.exit()
208         if self.options.edit_summary == None:
209             print 'Edit summary:',
210             edit_summary = raw_input()
211         else:
212             edit_summary = self.options.edit_summary
213         for file in status:
214             if status[file] in ['U']:
215                 # get edit token
216                 data = {
217                         'action': 'query',
218                         'prop': 'info|revisions',
219                         'intoken': 'edit',
220                         'titles': mw.api.filename_to_pagename(file[:-5]),
221                 }
222                 response = self.api.call(data)
223                 pageid = response['query']['pages'].keys()[0]
224                 revid = response['query']['pages'][pageid]['revisions'][0]['revid']
225                 awaitedrevid = self.metadir.pages_get_rv_list( {'id': pageid } )[0]                
226                 if revid != awaitedrevid :
227                      print "Ignoring %s - Edition conflict detected %s - %s " % ( file , awaitedrevid, revid)
228                      continue
229                 edittoken = response['query']['pages'][pageid]['edittoken']
230                 # FIXME use basetimestamp and starttimestamp
231                 filename = os.path.join(self.metadir.root, file)
232                 text = codecs.open(filename, 'r', 'utf-8').read()
233                 text = text.encode('utf-8')
234                 if (len(text) != 0) and (text[-1] == '\n'):
235                     text = text[:-1]
236                 md5 = hashlib.md5()
237                 md5.update(text)
238                 textmd5 = md5.hexdigest()
239                 data = {
240                         'action': 'edit',
241                         'title': mw.api.filename_to_pagename(file[:-5]),
242                         'token': edittoken,
243                         'text': text,
244                         'md5': textmd5,
245                         'summary': edit_summary,
246                 }
247                 if self.options.bot:
248                     data['bot'] = 'bot'
249                 response = self.api.call(data)
250                 if response['edit']['result'] == 'Success':
251                     if response['edit'].has_key('nochange') :
252                       print "Ignoring %s - No changes were detected - Removing ending lf" %  file 
253                       self.metadir.clean_page(file[:-5])
254                       continue
255                     if response['edit']['oldrevid'] != revid :
256                       print "Ignoring %s - Colision detected " % file
257                       continue
258                     data = {
259                             'action': 'query',
260                             'revids': response['edit']['newrevid'],
261                             'prop': 'info|revisions',
262                             'rvprop':
263                                     'ids|flags|timestamp|user|comment|content',
264                     }
265                     response = self.api.call(data)['query']['pages']
266                     self.metadir.pages_add_rv(int(pageid),
267                                               response[pageid]['revisions'][0])
268                 else:
269                     print 'committing %s failed: %s' % \
270                             (file, response['edit']['result'])

Benjamin Mako Hill || Want to submit a patch?