Remove a debugging line
[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 cookielib
21 import getpass
22 import hashlib
23 import mw.metadir
24 from optparse import OptionParser, OptionGroup
25 import os
26 import simplemediawiki
27 import sys
28
29
30 class CommandBase(object):
31
32     def __init__(self, name, description, usage=None):
33         self.me = os.path.basename(sys.argv[0])
34         self.description = description
35         if usage is None:
36             usage = '%prog ' + name
37         else:
38             usage = '%%prog %s %s' % (name, usage)
39         self.parser = OptionParser(usage=usage, description=description)
40         self.name = name
41         self.metadir = mw.metadir.Metadir()
42         self.shortcuts = []
43
44     def main(self):
45         (self.options, self.args) = self.parser.parse_args()
46         self.args = self.args[1:] # don't need the first thing
47         self._do_command()
48
49     def _do_command(self):
50         pass
51
52     def _login(self):
53         user = raw_input('Username: ')
54         passwd = getpass.getpass()
55         result = self.api.call({'action': 'login',
56                                 'lgname': user,
57                                 'lgpassword': passwd})
58         if result['login']['result'] == 'Success':
59             # cookies are saved to a file
60             print 'Login successful! (yay)'
61         elif result['login']['result'] == 'NeedToken':
62             print 'Login with token'
63             result = self.api.call({'action': 'login',
64                                     'lgname': user,
65                                     'lgpassword': passwd,
66                                     'lgtoken': result['login']['token']})
67             if result['login']['result'] == 'Success':
68                 print 'Login successful! (yay)'
69             else:
70                 print 'Login failed: %s' % result['login']['result']
71         else:
72             print 'Login failed: %s' % result['login']['result']
73
74     def _die_if_no_init(self):
75         if self.metadir.config is None:
76             print '%s: not a mw repo' % self.me
77             sys.exit(1)
78
79     def _api_setup(self):
80         cookie_file = os.path.join(self.metadir.location, 'cookies')
81         self.api_url = self.metadir.config.get('remote', 'api_url')
82         self.api = simplemediawiki.MediaWiki(self.api_url,
83                                              cookie_file=cookie_file)
84
85
86 class InitCommand(CommandBase):
87
88     def __init__(self):
89         usage = 'API_URL'
90         CommandBase.__init__(self, 'init', 'start a mw repo', usage)
91
92     def _do_command(self):
93         if len(self.args) < 1:
94             self.parser.error('must have URL to remote api.php')
95         elif len(self.args) > 1:
96             self.parser.error('too many arguments')
97         self.metadir.create(self.args[0])
98
99
100 class LoginCommand(CommandBase):
101
102     def __init__(self):
103         CommandBase.__init__(self, 'login', 'authenticate with wiki')
104
105     def _do_command(self):
106         self._die_if_no_init()
107         self._api_setup()
108         self._login()
109
110
111 class LogoutCommand(CommandBase):
112
113     def __init__(self):
114         CommandBase.__init__(self, 'logout', 'forget authentication')
115
116     def _do_command(self):
117         self._die_if_no_init()
118         try:
119             os.unlink(os.path.join(self.metadir.location, 'cookies'))
120         except OSError:
121             pass
122
123
124 class PullCommand(CommandBase):
125
126     def __init__(self):
127         usage = '[options] PAGENAME ...'
128         CommandBase.__init__(self, 'pull', 'add remote pages to repo', usage)
129
130     def _do_command(self):
131         self._die_if_no_init()
132         self._api_setup()
133         pages = []
134         pages += self.args
135         for these_pages in [pages[i:i + 25] for i in range(0, len(pages), 25)]:
136             data = {
137                     'action': 'query',
138                     'titles': '|'.join(these_pages),
139                     'prop': 'info|revisions',
140                     'rvprop': 'ids|flags|timestamp|user|comment|content',
141             }
142             response = self.api.call(data)['query']['pages']
143             for pageid in response.keys():
144                 pagename = response[pageid]['title']
145                 if 'missing' in response[pageid].keys():
146                     print '%s: %s: page does not exist, file not created' % \
147                             (self.me, pagename)
148                     continue
149                 revids = [x['revid'] for x in response[pageid]['revisions']]
150                 revids.sort()
151                 self.metadir.pagedict_add(pagename, pageid, revids[-1])
152                 self.metadir.pages_add_rv(int(pageid),
153                                           response[pageid]['revisions'][0])
154                 filename = mw.metadir.pagename_to_filename(pagename)
155                 with file(os.path.join(self.metadir.root, filename + '.wiki'),
156                           'w') as fd:
157                     data = response[pageid]['revisions'][0]['*']
158                     data = data.encode('utf-8')
159                     fd.write(data)
160
161
162 class StatusCommand(CommandBase):
163
164     def __init__(self):
165         CommandBase.__init__(self, 'status', 'check repo status')
166         self.shortcuts.append('st')
167
168     def _do_command(self):
169         self._die_if_no_init()
170         status = self.metadir.working_dir_status()
171         for file in status:
172             print '%s %s' % (status[file], file)
173
174
175 class DiffCommand(CommandBase):
176
177     def __init__(self):
178         CommandBase.__init__(self, 'diff', 'diff wiki to working directory')
179
180     def _do_command(self):
181         self._die_if_no_init()
182         status = self.metadir.working_dir_status()
183         for file in status:
184             if status[file] == 'U':
185                 print self.metadir.diff_rv_to_working(
186                         mw.metadir.filename_to_pagename(file[:-5])),
187
188
189 class CommitCommand(CommandBase):
190
191     def __init__(self):
192         usage = '[FILES]'
193         CommandBase.__init__(self, 'commit', 'commit changes to wiki', usage)
194         self.shortcuts.append('ci')
195         self.parser.add_option('-m', '--message', dest='edit_summary',
196                                help='don\'t prompt for edit summary and '
197                                'use this instead')
198         self.parser.add_option('-b', '--bot', dest='bot', action='store_true',
199                                help='mark actions as a bot (won\'t affect '
200                                'anything if you don\'t have the bot right',
201                                default=False)
202
203     def _do_command(self):
204         self._die_if_no_init()
205         self._api_setup()
206         status = self.metadir.working_dir_status(files=self.args)
207         nothing_to_commit = True
208         for file in status:
209             print '%s %s' % (status[file], file)
210             if status[file] in ['U']:
211                 nothing_to_commit = False
212         if nothing_to_commit:
213             print 'nothing to commit'
214             sys.exit()
215         if self.options.edit_summary == None:
216             print 'Edit summary:',
217             edit_summary = raw_input()
218         else:
219             edit_summary = self.options.edit_summary
220         for file in status:
221             if status[file] in ['U']:
222                 # get edit token
223                 data = {
224                         'action': 'query',
225                         'prop': 'info|revisions',
226                         'intoken': 'edit',
227                         'titles': mw.metadir.filename_to_pagename(file[:-5]),
228                 }
229                 response = self.api.call(data)
230                 pageid = response['query']['pages'].keys()[0]
231                 revid = response['query']['pages'][pageid]['revisions'][0]\
232                         ['revid']
233                 awaitedrevid = self.metadir.pages_get_rv_list({'id': pageid})\
234                         [0]
235                 if revid != awaitedrevid:
236                     print 'warning: edit conflict detected on %s (%s -> %s) ' \
237                             '-- skipping!' % (file, awaitedrevid, revid)
238                     continue
239                 edittoken = response['query']['pages'][pageid]['edittoken']
240                 filename = os.path.join(self.metadir.root, file)
241                 text = codecs.open(filename, 'r', 'utf-8').read()
242                 text = text.encode('utf-8')
243                 if (len(text) != 0) and (text[-1] == '\n'):
244                     text = text[:-1]
245                 md5 = hashlib.md5()
246                 md5.update(text)
247                 textmd5 = md5.hexdigest()
248                 data = {
249                         'action': 'edit',
250                         'title': mw.metadir.filename_to_pagename(file[:-5]),
251                         'token': edittoken,
252                         'text': text,
253                         'md5': textmd5,
254                         'summary': edit_summary,
255                 }
256                 if self.options.bot:
257                     data['bot'] = 'bot'
258                 response = self.api.call(data)
259                 if response['edit']['result'] == 'Success':
260                     if 'nochange' in response['edit']:
261                         print 'warning: no changes detected in %s - ' \
262                                 'skipping and removing ending LF' % file
263                         self.metadir.clean_page(file[:-5])
264                         continue
265                     if response['edit']['oldrevid'] != revid:
266                         print 'warning: edit conflict detected on %s -- ' \
267                                 'skipping!' % file
268                         continue
269                     data = {
270                             'action': 'query',
271                             'revids': response['edit']['newrevid'],
272                             'prop': 'info|revisions',
273                             'rvprop':
274                                     'ids|flags|timestamp|user|comment|content',
275                     }
276                     response = self.api.call(data)['query']['pages']
277                     self.metadir.pages_add_rv(int(pageid),
278                                               response[pageid]['revisions'][0])
279                 else:
280                     print 'error: committing %s failed: %s' % \
281                             (file, response['edit']['result'])

Benjamin Mako Hill || Want to submit a patch?