Ignore compiled elisp files
[stgit] / stgit / utils.py
index ad9b1f1..751cb4a 100644 (file)
@@ -2,7 +2,9 @@
 """
 
 import errno, os, os.path, re, sys
+from stgit.exception import *
 from stgit.config import config
+from stgit.out import *
 
 __copyright__ = """
 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
@@ -21,92 +23,20 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-class MessagePrinter(object):
-    def __init__(self):
-        class Output(object):
-            def __init__(self, write, flush):
-                self.write = write
-                self.flush = flush
-                self.at_start_of_line = True
-                self.level = 0
-            def new_line(self):
-                """Ensure that we're at the beginning of a line."""
-                if not self.at_start_of_line:
-                    self.write('\n')
-                    self.at_start_of_line = True
-            def single_line(self, msg, print_newline = True,
-                            need_newline = True):
-                """Write a single line. Newline before and after are
-                separately configurable."""
-                if need_newline:
-                    self.new_line()
-                if self.at_start_of_line:
-                    self.write('  '*self.level)
-                self.write(msg)
-                if print_newline:
-                    self.write('\n')
-                    self.at_start_of_line = True
-                else:
-                    self.flush()
-                    self.at_start_of_line = False
-            def tagged_lines(self, tag, lines):
-                tag += ': '
-                for line in lines:
-                    self.single_line(tag + line)
-                    tag = ' '*len(tag)
-            def write_line(self, line):
-                """Write one line of text on a lines of its own, not
-                indented."""
-                self.new_line()
-                self.write('%s\n' % line)
-                self.at_start_of_line = True
-            def write_raw(self, string):
-                """Write an arbitrary string, possibly containing
-                newlines."""
-                self.new_line()
-                self.write(string)
-                self.at_start_of_line = string.endswith('\n')
-        self.__stdout = Output(sys.stdout.write, sys.stdout.flush)
-        if sys.stdout.isatty():
-            self.__out = self.__stdout
-        else:
-            self.__out = Output(lambda msg: None, lambda: None)
-    def stdout(self, line):
-        """Write a line to stdout."""
-        self.__stdout.write_line(line)
-    def stdout_raw(self, string):
-        """Write a string possibly containing newlines to stdout."""
-        self.__stdout.write_raw(string)
-    def info(self, *msgs):
-        for msg in msgs:
-            self.__out.single_line(msg)
-    def note(self, *msgs):
-        self.__out.tagged_lines('Notice', msgs)
-    def warn(self, *msgs):
-        self.__out.tagged_lines('Warning', msgs)
-    def error(self, *msgs):
-        self.__out.tagged_lines('Error', msgs)
-    def start(self, msg):
-        """Start a long-running operation."""
-        self.__out.single_line('%s ... ' % msg, print_newline = False)
-        self.__out.level += 1
-    def done(self, extramsg = None):
-        """Finish long-running operation."""
-        self.__out.level -= 1
-        if extramsg:
-            msg = 'done (%s)' % extramsg
-        else:
-            msg = 'done'
-        self.__out.single_line(msg, need_newline = False)
-
-out = MessagePrinter()
-
 def mkdir_file(filename, mode):
     """Opens filename with the given mode, creating the directory it's
     in if it doesn't already exist."""
     create_dirs(os.path.dirname(filename))
     return file(filename, mode)
 
+def read_strings(filename):
+    """Reads the lines from a file
+    """
+    f = file(filename, 'r')
+    lines = [line.strip() for line in f.readlines()]
+    f.close()
+    return lines
+
 def read_string(filename, multiline = False):
     """Reads the first line from a file
     """
@@ -118,6 +48,13 @@ def read_string(filename, multiline = False):
     f.close()
     return result
 
+def write_strings(filename, lines):
+    """Write 'lines' sequence to file
+    """
+    f = file(filename, 'w+')
+    f.writelines([line + '\n' for line in lines])
+    f.close()
+
 def write_string(filename, line, multiline = False):
     """Writes 'line' to file and truncates it
     """
@@ -230,37 +167,51 @@ def rename(basedir, file1, file2):
         # file1's parent dir may not be empty after move
         pass
 
-class EditorException(Exception):
+class EditorException(StgException):
     pass
 
+def get_editor():
+    for editor in [os.environ.get('GIT_EDITOR'),
+                   config.get('stgit.editor'), # legacy
+                   config.get('core.editor'),
+                   os.environ.get('VISUAL'),
+                   os.environ.get('EDITOR'),
+                   'vi']:
+        if editor:
+            return editor
+
 def call_editor(filename):
     """Run the editor on the specified filename."""
-
-    # the editor
-    editor = config.get('stgit.editor')
-    if editor:
-        pass
-    elif 'EDITOR' in os.environ:
-        editor = os.environ['EDITOR']
-    else:
-        editor = 'vi'
-    editor += ' %s' % filename
-
-    out.start('Invoking the editor: "%s"' % editor)
-    err = os.system(editor)
+    cmd = '%s %s' % (get_editor(), filename)
+    out.start('Invoking the editor: "%s"' % cmd)
+    err = os.system(cmd)
     if err:
         raise EditorException, 'editor failed, exit code: %d' % err
     out.done()
 
+def edit_string(s, filename):
+    f = file(filename, 'w')
+    f.write(s)
+    f.close()
+    call_editor(filename)
+    f = file(filename)
+    s = f.read()
+    f.close()
+    os.remove(filename)
+    return s
+
 def patch_name_from_msg(msg):
     """Return a string to be used as a patch name. This is generated
-    from the top line of the string passed as argument, and is at most
-    30 characters long."""
+    from the top line of the string passed as argument."""
     if not msg:
         return None
 
+    name_len = config.get('stgit.namelength')
+    if not name_len:
+        name_len = 30
+
     subject_line = msg.split('\n', 1)[0].lstrip().lower()
-    return re.sub('[\W]+', '-', subject_line).strip('-')[:30]
+    return re.sub('[\W]+', '-', subject_line).strip('-')[:name_len]
 
 def make_patch_name(msg, unacceptable, default_name = 'patch'):
     """Return a patch name generated from the given commit message,
@@ -275,3 +226,64 @@ def make_patch_name(msg, unacceptable, default_name = 'patch'):
             suffix += 1
         patchname = '%s-%d' % (patchname, suffix)
     return patchname
+
+# any and all functions are builtin in Python 2.5 and higher, but not
+# in 2.4.
+if not 'any' in dir(__builtins__):
+    def any(bools):
+        for b in bools:
+            if b:
+                return True
+        return False
+if not 'all' in dir(__builtins__):
+    def all(bools):
+        for b in bools:
+            if not b:
+                return False
+        return True
+
+def add_sign_line(desc, sign_str, name, email):
+    if not sign_str:
+        return desc
+    sign_str = '%s: %s <%s>' % (sign_str, name, email)
+    if sign_str in desc:
+        return desc
+    desc = desc.rstrip()
+    if not any(s in desc for s in ['\nSigned-off-by:', '\nAcked-by:']):
+        desc = desc + '\n'
+    return '%s\n%s\n' % (desc, sign_str)
+
+def parse_name_email(address):
+    """Return a tuple consisting of the name and email parsed from a
+    standard 'name <email>' or 'email (name)' string."""
+    address = re.sub(r'[\\"]', r'\\\g<0>', address)
+    str_list = re.findall(r'^(.*)\s*<(.*)>\s*$', address)
+    if not str_list:
+        str_list = re.findall(r'^(.*)\s*\((.*)\)\s*$', address)
+        if not str_list:
+            return None
+        return (str_list[0][1], str_list[0][0])
+    return str_list[0]
+
+def parse_name_email_date(address):
+    """Return a tuple consisting of the name, email and date parsed
+    from a 'name <email> date' string."""
+    address = re.sub(r'[\\"]', r'\\\g<0>', address)
+    str_list = re.findall('^(.*)\s*<(.*)>\s*(.*)\s*$', address)
+    if not str_list:
+        return None
+    return str_list[0]
+
+# Exit codes.
+STGIT_SUCCESS = 0        # everything's OK
+STGIT_GENERAL_ERROR = 1  # seems to be non-command-specific error
+STGIT_COMMAND_ERROR = 2  # seems to be a command that failed
+STGIT_CONFLICT = 3       # merge conflict, otherwise OK
+STGIT_BUG_ERROR = 4      # a bug in StGit
+
+def add_dict(d1, d2):
+    """Return a new dict with the contents of both d1 and d2. In case of
+    conflicting mappings, d2 takes precedence."""
+    d = dict(d1)
+    d.update(d2)
+    return d