Function to launch the editor for the purposes of getting new text content
[mdk.git] / mdk / tools.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Moodle Development Kit
6
7 Copyright (c) 2012 Frédéric Massart - FMCorz.net
8
9 This program is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 http://github.com/FMCorz/mdk
23 """
24
25 import sys
26 import os
27 import signal
28 import subprocess
29 import shlex
30 import re
31 import threading
32 import getpass
33 import logging
34 import hashlib
35 import tempfile
36 from .config import Conf
37
38 C = Conf()
39
40 def yesOrNo(q):
41 while True:
42 i = raw_input('%s (y/n) ' % (q)).strip().lower()
43 if i == 'y':
44 return True
45 elif i == 'n':
46 return False
47
48
49 def question(q, default=None, options=None, password=False):
50 """Asks the user a question, and return the answer"""
51 text = q
52 if default != None:
53 text = text + ' [%s]' % str(default)
54
55 if password:
56 i = getpass.getpass('%s\n ' % text)
57 else:
58 i = raw_input('%s\n ' % text)
59
60 if i.strip() == '':
61 return default
62 else:
63 if options != None and i not in options:
64 return question(q, default, options)
65 return i
66
67
68 def chmodRecursive(path, chmod):
69 os.chmod(path, chmod)
70 for (dirpath, dirnames, filenames) in os.walk(path):
71 for d in dirnames:
72 dir = os.path.join(dirpath, d)
73 os.chmod(dir, chmod)
74 for f in filenames:
75 file = os.path.join(dirpath, f)
76 os.chmod(file, chmod)
77
78
79 def getMDLFromCommitMessage(message):
80 """Return the MDL-12345 number from a commit message"""
81 mdl = None
82 match = re.match(r'MDL(-|_)([0-9]+)', message, re.I)
83 if match:
84 mdl = 'MDL-%s' % (match.group(2))
85 return mdl
86
87
88 def get_current_user():
89 """Attempt to get the currently logged in user"""
90 username = 'root'
91 try:
92 username = os.getlogin()
93 except OSError:
94 import getpass
95 try:
96 username = getpass.getuser()
97 except:
98 pass
99 return username
100
101
102 def launchEditor(filepath=None, suffix='.tmp'):
103 """Launchs up an editor
104
105 If filepath is passed, the content of the file is used to populate the editor.
106
107 This returns the path to the saved file.
108 """
109 editor = resolveEditor()
110 if not editor:
111 raise Exception('Could not locate the editor')
112 with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmpfile:
113 with open(filepath, 'r') as f:
114 tmpfile.write(f.read())
115 tmpfile.flush()
116 subprocess.call([editor, tmpfile.name])
117 return tmpfile.name
118
119
120 def getText(suffix='.md', currenttext=None):
121 success = None
122 with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmpfile:
123 if currenttext:
124 tmpfile.write(currenttext)
125 tmpfile.flush()
126 while True:
127 tmpfile = launchEditor(suffix=suffix, filepath=tmpfile.name)
128 text = None
129 with open(tmpfile, 'r') as f:
130 text = f.read()
131 f.close()
132
133 if text == '':
134 logging.error('I could not detect any file content. Did you save properly?')
135 if yesOrNo('Would you like to continue editing? If not the changes will be discarded.'):
136 continue
137 else:
138 return
139 else:
140 return text
141
142 def md5file(filepath):
143 """Return the md5 sum of a file
144 This is terribly memory inefficient!"""
145 return hashlib.md5(open(filepath).read()).hexdigest()
146
147
148 def mkdir(path, perms=0755):
149 """Creates a directory ignoring the OS umask"""
150 oldumask = os.umask(0000)
151 os.mkdir(path, perms)
152 os.umask(oldumask)
153
154
155 def parseBranch(branch):
156 pattern = re.compile(C.get('wording.branchRegex'), flags=re.I)
157 result = pattern.search(branch)
158 if not result:
159 return False
160
161 parsed = {
162 'issue': result.group(pattern.groupindex['issue']),
163 'version': result.group(pattern.groupindex['version'])
164 }
165 try:
166 parsed['suffix'] = result.group(pattern.groupindex['suffix'])
167 except:
168 parsed['suffix'] = None
169 return parsed
170
171
172 def process(cmd, cwd=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE):
173 if type(cmd) != list:
174 cmd = shlex.split(str(cmd))
175 logging.debug(' '.join(cmd))
176 try:
177 proc = subprocess.Popen(cmd, cwd=cwd, stdout=stdout, stderr=stderr)
178 (out, err) = proc.communicate()
179 except KeyboardInterrupt as e:
180 proc.kill()
181 raise e
182 return (proc.returncode, out, err)
183
184
185 def resolveEditor():
186 """Try to resolve the editor that the user would want to use.
187 This does actually checks if it is executable"""
188 editor = C.get('editor')
189 if not editor:
190 editor = os.environ.get('EDITOR')
191 if not editor:
192 editor = os.environ.get('VISUAL')
193 if not editor and os.path.isfile('/usr/bin/editor'):
194 editor = '/usr/bin/editor'
195 return editor
196
197
198 def downloadProcessHook(count, size, total):
199 """Hook to report the downloading a file using urllib.urlretrieve"""
200 if count <= 0:
201 return
202 downloaded = int((count * size) / (1024))
203 total = int(total / (1024)) if total != 0 else '?'
204 if downloaded > total:
205 downloaded = total
206 sys.stderr.write("\r %sKB / %sKB" % (downloaded, total))
207 sys.stderr.flush()
208
209
210 def stableBranch(version):
211 if version == 'master':
212 return 'master'
213 return 'MOODLE_%d_STABLE' % int(version)
214
215
216 class ProcessInThread(threading.Thread):
217 """Executes a process in a separate thread"""
218
219 cmd = None
220 cwd = None
221 stdout = None
222 stderr = None
223 _kill = False
224 _pid = None
225
226 def __init__(self, cmd, cwd=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE):
227 threading.Thread.__init__(self)
228 if type(cmd) != 'list':
229 cmd = shlex.split(str(cmd))
230 self.cmd = cmd
231 self.cwd = cwd
232 self.stdout = stdout
233 self.stderr = stderr
234
235 def kill(self):
236 os.kill(self._pid, signal.SIGKILL)
237
238 def run(self):
239 logging.debug(' '.join(self.cmd))
240 proc = subprocess.Popen(self.cmd, cwd=self.cwd, stdout=self.stdout, stderr=self.stderr)
241 self._pid = proc.pid
242 while True:
243 if proc.poll():
244 break
245
246 # Reading the output seems to prevent the process to hang.
247 if self.stdout == subprocess.PIPE:
248 proc.stdout.read(1)