c4b016ae46f5320faebdbf2c16bdddfdfb29281b
[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         else:
61             print 'Login failed: %s' % result['login']['result']
62
63     def _die_if_no_init(self):
64         if self.metadir.config is None:
65             print '%s: not a mw repo' % self.me
66             sys.exit(1)
67
68     def _api_setup(self):
69         self.api_url = self.metadir.config.get('remote', 'api_url')
70         self.api = mw.api.API(self.api_url, self.metadir)
71
72
73 class InitCommand(CommandBase):
74
75     def __init__(self):
76         usage = 'API_URL'
77         CommandBase.__init__(self, 'init', 'start a mw repo', usage)
78
79     def _do_command(self):
80         if len(self.args) < 1:
81             self.parser.error('must have URL to remote api.php')
82         elif len(self.args) > 1:
83             self.parser.error('too many arguments')
84         self.metadir.create(self.args[0])
85
86
87 class LoginCommand(CommandBase):
88
89     def __init__(self):
90         CommandBase.__init__(self, 'login', 'authenticate with wiki')
91
92     def _do_command(self):
93         self._die_if_no_init()
94         self._api_setup()
95         self._login()
96
97
98 class LogoutCommand(CommandBase):
99
100     def __init__(self):
101         CommandBase.__init__(self, 'logout', 'forget authentication')
102
103     def _do_command(self):
104         self._die_if_no_init()
105         try:
106             os.unlink(os.path.join(self.metadir.location, 'cookies'))
107         except OSError:
108             pass
109
110
111 class PullCommand(CommandBase):
112
113     def __init__(self):
114         usage = '[options] PAGENAME ...'
115         CommandBase.__init__(self, 'pull', 'add remote pages to repo', usage)
116
117     def _do_command(self):
118         self._die_if_no_init()
119         self._api_setup()
120         pages = []
121         pages += self.args
122         for these_pages in [pages[i:i + 25] for i in range(0, len(pages), 25)]:
123             data = {
124                     'action': 'query',
125                     'titles': '|'.join(these_pages),
126                     'prop': 'info|revisions',
127                     'rvprop': 'ids|flags|timestamp|user|comment|content',
128             }
129             response = self.api.call(data)['query']['pages']
130             for pageid in response.keys():
131                 pagename = response[pageid]['title']
132                 if 'missing' in response[pageid].keys():
133                     print '%s: %s: page does not exist, file not created' % \
134                             (self.me, pagename)
135                     continue
136                 revids = [x['revid'] for x in response[pageid]['revisions']]
137                 revids.sort()
138                 self.metadir.pagedict_add(pagename, pageid, revids[-1])
139                 self.metadir.pages_add_rv(int(pageid),
140                                           response[pageid]['revisions'][0])
141                 filename = mw.api.pagename_to_filename(pagename)
142                 fd = file(os.path.join(self.metadir.root, filename + '.wiki'),
143                           'w')
144                 fd.write(response[pageid]['revisions'][0]['*'].encode('utf-8'))
145
146
147 class StatusCommand(CommandBase):
148
149     def __init__(self):
150         CommandBase.__init__(self, 'status', 'check repo status')
151         self.shortcuts.append('st')
152
153     def _do_command(self):
154         self._die_if_no_init()
155         status = self.metadir.working_dir_status()
156         for file in status:
157             print '%s %s' % (status[file], file)
158
159
160 class DiffCommand(CommandBase):
161
162     def __init__(self):
163         CommandBase.__init__(self, 'diff', 'diff wiki to working directory')
164
165     def _do_command(self):
166         self._die_if_no_init()
167         status = self.metadir.working_dir_status()
168         for file in status:
169             if status[file] == 'U':
170                 print self.metadir.diff_rv_to_working(
171                         mw.api.filename_to_pagename(file[:-5])),
172
173
174 class CommitCommand(CommandBase):
175
176     def __init__(self):
177         CommandBase.__init__(self, 'commit', 'commit changes to wiki')
178         self.shortcuts.append('ci')
179         self.parser.add_option('-m', '--message', dest='edit_summary',
180                                help='don\'t prompt for edit summary and '
181                                'use this instead')
182
183     def _do_command(self):
184         self._die_if_no_init()
185         self._api_setup()
186         print 'WARNING: mw does not do collision detection yet.'
187         print 'Hit ^C now if you haven\'t double checked, otherwise hit Enter'
188         raw_input()
189         status = self.metadir.working_dir_status()
190         nothing_to_commit = True
191         for file in status:
192             print '%s %s' % (status[file], file)
193             if status[file] in ['U']:
194                 nothing_to_commit = False
195         if nothing_to_commit:
196             print 'nothing to commit'
197         else:
198             if self.options.edit_summary == None:
199                 print 'Edit summary:',
200                 edit_summary = raw_input()
201             else:
202                 edit_summary = self.options.edit_summary
203         for file in status:
204             if status[file] in ['U']:
205                 # get edit token
206                 data = {
207                         'action': 'query',
208                         'prop': 'info',
209                         'intoken': 'edit',
210                         'titles': mw.api.filename_to_pagename(file[:-5]),
211                 }
212                 response = self.api.call(data)
213                 pageid = response['query']['pages'].keys()[0]
214                 edittoken = response['query']['pages'][pageid]['edittoken']
215                 # FIXME use basetimestamp and starttimestamp
216                 filename = os.path.join(self.metadir.root, file)
217                 text = codecs.open(filename, 'r', 'utf-8').read()
218                 text = text.encode('utf-8')
219                 if text[-1] == '\n':
220                     text = text[:-1]
221                 md5 = hashlib.md5()
222                 md5.update(text)
223                 textmd5 = md5.hexdigest()
224                 data = {
225                         'action': 'edit',
226                         'title': mw.api.filename_to_pagename(file[:-5]),
227                         'token': edittoken,
228                         'text': text,
229                         'md5': textmd5,
230                         'summary': edit_summary,
231                 }
232                 response = self.api.call(data)
233                 if response['edit']['result'] == 'Success':
234                     data = {
235                             'action': 'query',
236                             'revids': response['edit']['newrevid'],
237                             'prop': 'info|revisions',
238                             'rvprop':
239                                     'ids|flags|timestamp|user|comment|content',
240                     }
241                     response = self.api.call(data)['query']['pages']
242                     self.metadir.pages_add_rv(int(pageid),
243                                               response[pageid]['revisions'][0])
244                 else:
245                     print 'committing %s failed: %s' % \
246                             (file, response['edit']['result'])

Benjamin Mako Hill || Want to submit a patch?