8 from cStringIO
import StringIO
12 'python': 'script.png',
14 'shell': 'script.png',
17 'assembler': 'binary.png',
18 'binary': 'binary.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
)
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():
47 # Fallback for modified files of an unknown type
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')
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')
72 return os
.path
.join(ICONSDIR
, 'generic.png')
74 def grep(pattern
, items
, squash
=True):
75 regex
= re
.compile(pattern
)
78 match
= regex
.match(item
)
79 if not match
: continue
80 groups
= match
.groups()
82 subitems
= match
.group(0)
87 subitems
= list(groups
)
88 matched
.append(subitems
)
90 if squash
and len(matched
) == 1:
96 '''Avoid os.path.basename because we are explicitly
97 parsing git's output, which contains /'s regardless
100 base_regex
= re
.compile('(.*?/)?([^/]+)$')
101 match
= base_regex
.match(path
)
103 return match
.group(2)
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,})")
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:
130 input = input.replace("'", "'\\''")
132 # make multiple ' in a row look simpler
133 # '\'''\'''\'' -> '"'''"'
134 quote_match
= quote_regex
.match(input)
136 quotes
= match
.group(1)
137 input.replace(quotes
,
138 ("'" *(len(quotes
)/4)) + "\"'")
140 input = "'%s'" % input
141 if input.startswith("''"):
144 if input.endswith("''"):
150 def get_tmp_filename():
151 # Allow TMPDIR/TMP with a fallback to /tmp
152 return '.ugit.%s.%s' %( os
.getpid(), time
.time() )
156 pad
= HEADER_LENGTH
- len(msg
) - 4 # len(':+') + len('+:')
162 +(' ' *(pad
+ extra
))
166 def parse_geom(geomstr
):
167 regex
= re
.compile('^(\d+)x(\d+)\+(\d+),(\d+) (\d+),(\d+) (\d+),(\d+)')
168 match
= regex
.match(geomstr
)
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
)
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
)
192 return os
.path
.basename(defaults
.DIRECTORY
)
200 def write(path
, contents
):
201 file = open(path
, 'w')
206 class DiffParser(object):
207 def __init__(self
, diff
):
208 self
.__diff
_header
= re
.compile('^@@\s[^@]+\s@@.*')
212 self
.__diff
_spans
= []
213 self
.__diff
_offsets
= []
215 self
.parse_diff(diff
)
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
])
232 def get_diffs_for_range(self
, start
, end
):
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
245 diff
= os
.linesep
.join(self
.__diffs
[idx
])
251 def parse_diff(self
, diff
):
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
)
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