Added commit message menu, other fixes
[ugit.git] / ugitlibs / utils.py
blob1ee6df102faa2776d1f1b99301857ab25abf731f
1 #!/usr/bin/env python
2 import os
3 import re
4 import time
5 import commands
6 from cStringIO import StringIO
8 KNOWN_FILE_TYPES = {
9 'ascii c': 'c.png',
10 'python': 'script.png',
11 'ruby': 'script.png',
12 'shell': 'script.png',
13 'perl': 'script.png',
14 'java': 'script.png',
15 'assembler': 'binary.png',
16 'binary': 'binary.png',
17 'byte': 'binary.png',
18 'image': 'image.png',
21 ICONSDIR = os.path.join(os.path.dirname(__file__), 'icons')
23 def ident_file_type(filename):
24 '''Returns an icon based on the contents of filename.'''
25 if os.path.exists(filename):
26 quoted_filename = shell_quote(filename)
27 fileinfo = commands.getoutput('file -b %s' % quoted_filename)
28 for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
29 if filetype in fileinfo.lower():
30 return iconname
31 else:
32 return 'removed.png'
33 # Fallback for modified files of an unknown type
34 return 'generic.png'
36 def get_icon(filename):
37 '''Returns the full path to an icon file corresponding to
38 filename's contents.'''
39 icon_file = ident_file_type(filename)
40 return os.path.join(ICONSDIR, icon_file)
42 def get_staged_icon(filename):
43 '''Special-case method for staged items. These are only
44 ever 'staged' and 'removed' items in the staged list.'''
46 if os.path.exists(filename):
47 return os.path.join(ICONSDIR, 'staged.png')
48 else:
49 return os.path.join(ICONSDIR, 'removed.png')
51 def get_untracked_icon():
52 return os.path.join(ICONSDIR, 'untracked.png')
54 def get_directory_icon():
55 return os.path.join(ICONSDIR, 'dir.png')
57 def get_file_icon():
58 return os.path.join(ICONSDIR, 'generic.png')
60 def shell_quote(*inputs):
61 '''Quote strings so that they can be suitably martialled
62 off to the shell. This method supports POSIX sh syntax.
63 This is crucial to properly handle command line arguments
64 with spaces, quotes, double-quotes, etc.'''
66 regex = re.compile('[^\w!%+,\-./:@^]')
67 quote_regex = re.compile("((?:'\\''){2,})")
69 ret = []
70 for input in inputs:
71 if not input:
72 continue
74 if '\x00' in input:
75 raise AssertionError,('No way to quote strings '
76 'containing null(\\000) bytes')
78 # = does need quoting else in command position it's a
79 # program-local environment setting
80 match = regex.search(input)
81 if match and '=' not in input:
82 # ' -> '\''
83 input = input.replace("'", "'\\''")
85 # make multiple ' in a row look simpler
86 # '\'''\'''\'' -> '"'''"'
87 quote_match = quote_regex.match(input)
88 if quote_match:
89 quotes = match.group(1)
90 input.replace(quotes,
91 ("'" *(len(quotes)/4)) + "\"'")
93 input = "'%s'" % input
94 if input.startswith("''"):
95 input = input[2:]
97 if input.endswith("''"):
98 input = input[:-2]
99 ret.append(input)
100 return ' '.join(ret)
102 def get_tmp_filename():
103 # Allow TMPDIR/TMP with a fallback to /tmp
104 return '.ugit.%s.%s' %( os.getpid(), time.time() )
106 HEADER_LENGTH = 80
107 def header(msg):
108 pad = HEADER_LENGTH - len(msg) - 4 # len(':+') + len('+:')
109 extra = pad % 2
110 pad /= 2
111 return(':+'
112 +(' ' * pad)
113 + msg
114 +(' ' *(pad + extra))
115 + '+:'
116 + '\n')
118 def slurp(path):
119 file = open(path)
120 slushy = file.read()
121 file.close()
122 return slushy
124 def write(path, contents):
125 file = open(path, 'w')
126 file.write(contents)
127 file.close()
130 class DiffParser(object):
131 def __init__(self, diff):
132 self.__diff_header = re.compile('^@@\s[^@]+\s@@.*')
134 self.__idx = -1
135 self.__diffs = []
136 self.__diff_spans = []
137 self.__diff_offsets = []
139 self.parse_diff(diff)
141 def get_diffs(self):
142 return self.__diffs
144 def get_spans(self):
145 return self.__diff_spans
147 def get_offsets(self):
148 return self.__diff_offsets
150 def get_diff_for_offset(self, offset):
151 for idx, diff_offset in enumerate(self.__diff_offsets):
152 if offset < diff_offset:
153 return os.linesep.join(self.__diffs[idx])
154 return None
156 def get_diffs_for_range(self, start, end):
157 diffs = []
158 for idx, span in enumerate(self.__diff_spans):
160 has_end_of_diff = start >= span[0] and start < span[1]
161 has_all_of_diff = start <= span[0] and end >= span[1]
162 has_head_of_diff = end >= span[0] and end <= span[1]
164 selected_diff =(has_end_of_diff
165 or has_all_of_diff
166 or has_head_of_diff)
168 if selected_diff:
169 diff = os.linesep.join(self.__diffs[idx])
170 diffs.append(diff)
173 return diffs
175 def parse_diff(self, diff):
176 total_offset = 0
177 for idx, line in enumerate(diff.splitlines()):
179 if self.__diff_header.match(line):
180 self.__diffs.append( [line] )
182 line_len = len(line) + 1
183 self.__diff_spans.append([total_offset,
184 total_offset + line_len])
186 total_offset += line_len
187 self.__diff_offsets.append(total_offset)
189 self.__idx += 1
190 else:
191 if self.__idx < 0:
192 errmsg = 'Malformed diff?\n\n%s' % diff
193 raise AssertionError, errmsg
195 line_len = len(line) + 1
196 total_offset += line_len
198 self.__diffs[self.__idx].append(line)
199 self.__diff_spans[-1][-1] += line_len
200 self.__diff_offsets[self.__idx] += line_len