a0fb016fad1130816702d92794871725870a687e
[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         status = self.metadir.working_dir_status(files=self.args)
187         nothing_to_commit = True
188         for file in status:
189             print '%s %s' % (status[file], file)
190             if status[file] in ['U']:
191                 nothing_to_commit = False
192         if nothing_to_commit:
193             print 'nothing to commit'
194             sys.exit()
195         print
196         print 'WARNING: mw does not do collision detection yet.'
197         print 'Hit ^C now if you haven\'t double checked, otherwise hit Enter'
198         raw_input()
199         if self.options.edit_summary == None:
200             print 'Edit summary:',
201             edit_summary = raw_input()
202         else:
203             edit_summary = self.options.edit_summary
204         for file in status:
205             if status[file] in ['U']:
206                 # get edit token
207                 data = {
208                         'action': 'query',
209                         'prop': 'info',
210                         'intoken': 'edit',
211                         'titles': mw.api.filename_to_pagename(file[:-5]),
212                 }
213                 response = self.api.call(data)
214                 pageid = response['query']['pages'].keys()[0]
215                 edittoken = response['query']['pages'][pageid]['edittoken']
216                 # FIXME use basetimestamp and starttimestamp
217                 filename = os.path.join(self.metadir.root, file)
218                 text = codecs.open(filename, 'r', 'utf-8').read()
219                 text = text.encode('utf-8')
220                 if text[-1] == '\n':
221                     text = text[:-1]
222                 md5 = hashlib.md5()
223                 md5.update(text)
224                 textmd5 = md5.hexdigest()
225                 data = {
226                         'action': 'edit',
227                         'title': mw.api.filename_to_pagename(file[:-5]),
228                         'token': edittoken,
229                         'text': text,
230                         'md5': textmd5,
231                         'summary': edit_summary,
232                 }
233                 response = self.api.call(data)
234                 if response['edit']['result'] == 'Success':
235                     data = {
236                             'action': 'query',
237                             'revids': response['edit']['newrevid'],
238                             'prop': 'info|revisions',
239                             'rvprop':
240                                     'ids|flags|timestamp|user|comment|content',
241                     }
242                     response = self.api.call(data)['query']['pages']
243                     self.metadir.pages_add_rv(int(pageid),
244                                               response[pageid]['revisions'][0])
245                 else:
246                     print 'committing %s failed: %s' % \
247                             (file, response['edit']['result'])

Benjamin Mako Hill || Want to submit a patch?