chiark / gitweb /
Refactor output handling to break circular dependency
[stgit] / stgit / utils.py
1 """Common utility functions
2 """
3
4 import errno, os, os.path, re, sys
5 from stgit.config import config
6 from stgit.out import *
7
8 __copyright__ = """
9 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License version 2 as
13 published by the Free Software Foundation.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 """
24
25 def mkdir_file(filename, mode):
26     """Opens filename with the given mode, creating the directory it's
27     in if it doesn't already exist."""
28     create_dirs(os.path.dirname(filename))
29     return file(filename, mode)
30
31 def read_strings(filename):
32     """Reads the lines from a file
33     """
34     f = file(filename, 'r')
35     lines = [line.strip() for line in f.readlines()]
36     f.close()
37     return lines
38
39 def read_string(filename, multiline = False):
40     """Reads the first line from a file
41     """
42     f = file(filename, 'r')
43     if multiline:
44         result = f.read()
45     else:
46         result = f.readline().strip()
47     f.close()
48     return result
49
50 def write_strings(filename, lines):
51     """Write 'lines' sequence to file
52     """
53     f = file(filename, 'w+')
54     f.writelines([line + '\n' for line in lines])
55     f.close()
56
57 def write_string(filename, line, multiline = False):
58     """Writes 'line' to file and truncates it
59     """
60     f = mkdir_file(filename, 'w+')
61     if multiline:
62         f.write(line)
63     else:
64         print >> f, line
65     f.close()
66
67 def append_strings(filename, lines):
68     """Appends 'lines' sequence to file
69     """
70     f = mkdir_file(filename, 'a+')
71     for line in lines:
72         print >> f, line
73     f.close()
74
75 def append_string(filename, line):
76     """Appends 'line' to file
77     """
78     f = mkdir_file(filename, 'a+')
79     print >> f, line
80     f.close()
81
82 def insert_string(filename, line):
83     """Inserts 'line' at the beginning of the file
84     """
85     f = mkdir_file(filename, 'r+')
86     lines = f.readlines()
87     f.seek(0); f.truncate()
88     print >> f, line
89     f.writelines(lines)
90     f.close()
91
92 def create_empty_file(name):
93     """Creates an empty file
94     """
95     mkdir_file(name, 'w+').close()
96
97 def list_files_and_dirs(path):
98     """Return the sets of filenames and directory names in a
99     directory."""
100     files, dirs = [], []
101     for fd in os.listdir(path):
102         full_fd = os.path.join(path, fd)
103         if os.path.isfile(full_fd):
104             files.append(fd)
105         elif os.path.isdir(full_fd):
106             dirs.append(fd)
107     return files, dirs
108
109 def walk_tree(basedir):
110     """Starting in the given directory, iterate through all its
111     subdirectories. For each subdirectory, yield the name of the
112     subdirectory (relative to the base directory), the list of
113     filenames in the subdirectory, and the list of directory names in
114     the subdirectory."""
115     subdirs = ['']
116     while subdirs:
117         subdir = subdirs.pop()
118         files, dirs = list_files_and_dirs(os.path.join(basedir, subdir))
119         for d in dirs:
120             subdirs.append(os.path.join(subdir, d))
121         yield subdir, files, dirs
122
123 def strip_prefix(prefix, string):
124     """Return string, without the prefix. Blow up if string doesn't
125     start with prefix."""
126     assert string.startswith(prefix)
127     return string[len(prefix):]
128
129 def strip_suffix(suffix, string):
130     """Return string, without the suffix. Blow up if string doesn't
131     end with suffix."""
132     assert string.endswith(suffix)
133     return string[:-len(suffix)]
134
135 def remove_file_and_dirs(basedir, file):
136     """Remove join(basedir, file), and then remove the directory it
137     was in if empty, and try the same with its parent, until we find a
138     nonempty directory or reach basedir."""
139     os.remove(os.path.join(basedir, file))
140     try:
141         os.removedirs(os.path.join(basedir, os.path.dirname(file)))
142     except OSError:
143         # file's parent dir may not be empty after removal
144         pass
145
146 def create_dirs(directory):
147     """Create the given directory, if the path doesn't already exist."""
148     if directory and not os.path.isdir(directory):
149         create_dirs(os.path.dirname(directory))
150         try:
151             os.mkdir(directory)
152         except OSError, e:
153             if e.errno != errno.EEXIST:
154                 raise e
155
156 def rename(basedir, file1, file2):
157     """Rename join(basedir, file1) to join(basedir, file2), not
158     leaving any empty directories behind and creating any directories
159     necessary."""
160     full_file2 = os.path.join(basedir, file2)
161     create_dirs(os.path.dirname(full_file2))
162     os.rename(os.path.join(basedir, file1), full_file2)
163     try:
164         os.removedirs(os.path.join(basedir, os.path.dirname(file1)))
165     except OSError:
166         # file1's parent dir may not be empty after move
167         pass
168
169 class EditorException(Exception):
170     pass
171
172 def call_editor(filename):
173     """Run the editor on the specified filename."""
174
175     # the editor
176     editor = config.get('stgit.editor')
177     if editor:
178         pass
179     elif 'EDITOR' in os.environ:
180         editor = os.environ['EDITOR']
181     else:
182         editor = 'vi'
183     editor += ' %s' % filename
184
185     out.start('Invoking the editor: "%s"' % editor)
186     err = os.system(editor)
187     if err:
188         raise EditorException, 'editor failed, exit code: %d' % err
189     out.done()
190
191 def patch_name_from_msg(msg):
192     """Return a string to be used as a patch name. This is generated
193     from the top line of the string passed as argument, and is at most
194     30 characters long."""
195     if not msg:
196         return None
197
198     subject_line = msg.split('\n', 1)[0].lstrip().lower()
199     return re.sub('[\W]+', '-', subject_line).strip('-')[:30]
200
201 def make_patch_name(msg, unacceptable, default_name = 'patch'):
202     """Return a patch name generated from the given commit message,
203     guaranteed to make unacceptable(name) be false. If the commit
204     message is empty, base the name on default_name instead."""
205     patchname = patch_name_from_msg(msg)
206     if not patchname:
207         patchname = default_name
208     if unacceptable(patchname):
209         suffix = 0
210         while unacceptable('%s-%d' % (patchname, suffix)):
211             suffix += 1
212         patchname = '%s-%d' % (patchname, suffix)
213     return patchname