Add internationalization support to ugit
[ugit.git] / ugitlibs / utils.py
blob19a4cd5f9b1d37afce49222a4d3f4ab5bddf124c
1 #!/usr/bin/env python
2 import sys
3 import os
4 import re
5 import time
6 import commands
7 import defaults
8 from cStringIO import StringIO
10 KNOWN_FILE_TYPES = {
11 'ascii c': 'c.png',
12 'python': 'script.png',
13 'ruby': 'script.png',
14 'shell': 'script.png',
15 'perl': 'script.png',
16 'java': 'script.png',
17 'assembler': 'binary.png',
18 'binary': 'binary.png',
19 'byte': 'binary.png',
20 'image': 'image.png',
23 PREFIX = os.path.realpath(os.path.dirname(os.path.dirname(sys.argv[0])))
24 ICONSDIR = os.path.join(PREFIX, 'share', 'ugit', 'icons')
25 QMDIR = os.path.join(PREFIX, 'share', 'ugit', 'qm')
27 def get_qm_for_locale(locale):
28 regex = re.compile(r'([^\.])+\..*$')
29 match = regex.match(locale)
30 if match:
31 locale = match.group(1)
33 basename = locale.split('_')[0]
35 return os.path.join(QMDIR, basename +'.qm')
37 def ident_file_type(filename):
38 '''Returns an icon based on the contents of filename.'''
39 if os.path.exists(filename):
40 quoted_filename = shell_quote(filename)
41 fileinfo = commands.getoutput('file -b %s' % quoted_filename)
42 for filetype, iconname in KNOWN_FILE_TYPES.iteritems():
43 if filetype in fileinfo.lower():
44 return iconname
45 else:
46 return 'removed.png'
47 # Fallback for modified files of an unknown type
48 return 'generic.png'
50 def get_icon(filename):
51 '''Returns the full path to an icon file corresponding to
52 filename's contents.'''
53 icon_file = ident_file_type(filename)
54 return os.path.join(ICONSDIR, icon_file)
56 def get_staged_icon(filename):
57 '''Special-case method for staged items. These are only
58 ever 'staged' and 'removed' items in the staged list.'''
60 if os.path.exists(filename):
61 return os.path.join(ICONSDIR, 'staged.png')
62 else:
63 return os.path.join(ICONSDIR, 'removed.png')
65 def get_untracked_icon():
66 return os.path.join(ICONSDIR, 'untracked.png')
68 def get_directory_icon():
69 return os.path.join(ICONSDIR, 'dir.png')
71 def get_file_icon():
72 return os.path.join(ICONSDIR, 'generic.png')
74 def grep(pattern, items, squash=True):
75 regex = re.compile(pattern)
76 matched = []
77 for item in items:
78 match = regex.match(item)
79 if not match: continue
80 groups = match.groups()
81 if not groups:
82 subitems = match.group(0)
83 else:
84 if len(groups) == 1:
85 subitems = groups[0]
86 else:
87 subitems = list(groups)
88 matched.append(subitems)
90 if squash and len(matched) == 1:
91 return matched[0]
92 else:
93 return matched
95 def basename(path):
96 '''Avoid os.path.basename because we are explicitly
97 parsing git's output, which contains /'s regardless
98 of platform (a.t.m.)
99 '''
100 base_regex = re.compile('(.*?/)?([^/]+)$')
101 match = base_regex.match(path)
102 if match:
103 return match.group(2)
104 else:
105 return pathstr
107 def shell_quote(*inputs):
108 '''Quote strings so that they can be suitably martialled
109 off to the shell. This method supports POSIX sh syntax.
110 This is crucial to properly handle command line arguments
111 with spaces, quotes, double-quotes, etc.'''
113 regex = re.compile('[^\w!%+,\-./:@^]')
114 quote_regex = re.compile("((?:'\\''){2,})")
116 ret = []
117 for input in inputs:
118 if not input:
119 continue
121 if '\x00' in input:
122 raise AssertionError,('No way to quote strings '
123 'containing null(\\000) bytes')
125 # = does need quoting else in command position it's a
126 # program-local environment setting
127 match = regex.search(input)
128 if match and '=' not in input:
129 # ' -> '\''
130 input = input.replace("'", "'\\''")
132 # make multiple ' in a row look simpler
133 # '\'''\'''\'' -> '"'''"'
134 quote_match = quote_regex.match(input)
135 if quote_match:
136 quotes = match.group(1)
137 input.replace(quotes,
138 ("'" *(len(quotes)/4)) + "\"'")
140 input = "'%s'" % input
141 if input.startswith("''"):
142 input = input[2:]
144 if input.endswith("''"):
145 input = input[:-2]
146 ret.append(input)
147 return ' '.join(ret)
150 def get_tmp_filename():
151 # Allow TMPDIR/TMP with a fallback to /tmp
152 return '.ugit.%s.%s' %( os.getpid(), time.time() )
154 HEADER_LENGTH = 80
155 def header(msg):
156 pad = HEADER_LENGTH - len(msg) - 4 # len(':+') + len('+:')
157 extra = pad % 2
158 pad /= 2
159 return(':+'
160 +(' ' * pad)
161 + msg
162 +(' ' *(pad + extra))
163 + '+:'
164 + '\n')
166 def parse_geom(geomstr):
167 regex = re.compile('^(\d+)x(\d+)\+(\d+),(\d+) (\d+),(\d+) (\d+),(\d+)')
168 match = regex.match(geomstr)
169 if match:
170 defaults.WIDTH = int(match.group(1))
171 defaults.HEIGHT = int(match.group(2))
172 defaults.X = int(match.group(3))
173 defaults.Y = int(match.group(4))
174 defaults.SPLITTER_TOP_0 = int(match.group(5))
175 defaults.SPLITTER_TOP_1 = int(match.group(6))
176 defaults.SPLITTER_BOTTOM_0 = int(match.group(7))
177 defaults.SPLITTER_BOTTOM_1 = int(match.group(8))
179 return (defaults.WIDTH, defaults.HEIGHT,
180 defaults.X, defaults.Y,
181 defaults.SPLITTER_TOP_0, defaults.SPLITTER_TOP_1,
182 defaults.SPLITTER_BOTTOM_0, defaults.SPLITTER_BOTTOM_1)
184 def get_geom():
185 return '%dx%d+%d,%d %d,%d %d,%d' % (
186 defaults.WIDTH, defaults.HEIGHT,
187 defaults.X, defaults.Y,
188 defaults.SPLITTER_TOP_0, defaults.SPLITTER_TOP_1,
189 defaults.SPLITTER_BOTTOM_0, defaults.SPLITTER_BOTTOM_1)
191 def project_name():
192 return os.path.basename(defaults.DIRECTORY)
194 def slurp(path):
195 file = open(path)
196 slushy = file.read()
197 file.close()
198 return slushy
200 def write(path, contents):
201 file = open(path, 'w')
202 file.write(contents)
203 file.close()
206 class DiffParser(object):
207 def __init__(self, diff):
208 self.__diff_header = re.compile('^@@\s[^@]+\s@@.*')
210 self.__idx = -1
211 self.__diffs = []
212 self.__diff_spans = []
213 self.__diff_offsets = []
215 self.parse_diff(diff)
217 def get_diffs(self):
218 return self.__diffs
220 def get_spans(self):
221 return self.__diff_spans
223 def get_offsets(self):
224 return self.__diff_offsets
226 def get_diff_for_offset(self, offset):
227 for idx, diff_offset in enumerate(self.__diff_offsets):
228 if offset < diff_offset:
229 return os.linesep.join(self.__diffs[idx])
230 return None
232 def get_diffs_for_range(self, start, end):
233 diffs = []
234 for idx, span in enumerate(self.__diff_spans):
236 has_end_of_diff = start >= span[0] and start < span[1]
237 has_all_of_diff = start <= span[0] and end >= span[1]
238 has_head_of_diff = end >= span[0] and end <= span[1]
240 selected_diff =(has_end_of_diff
241 or has_all_of_diff
242 or has_head_of_diff)
244 if selected_diff:
245 diff = os.linesep.join(self.__diffs[idx])
246 diffs.append(diff)
249 return diffs
251 def parse_diff(self, diff):
252 total_offset = 0
253 for idx, line in enumerate(diff.splitlines()):
255 if self.__diff_header.match(line):
256 self.__diffs.append( [line] )
258 line_len = len(line) + 1
259 self.__diff_spans.append([total_offset,
260 total_offset + line_len])
262 total_offset += line_len
263 self.__diff_offsets.append(total_offset)
265 self.__idx += 1
266 else:
267 if self.__idx < 0:
268 errmsg = 'Malformed diff?\n\n%s' % diff
269 raise AssertionError, errmsg
271 line_len = len(line) + 1
272 total_offset += line_len
274 self.__diffs[self.__idx].append(line)
275 self.__diff_spans[-1][-1] += line_len
276 self.__diff_offsets[self.__idx] += line_len