6 from cStringIO
import StringIO
10 PREFIX
= os
.path
.realpath(os
.path
.dirname(os
.path
.dirname(sys
.argv
[0])))
11 QMDIR
= os
.path
.join(PREFIX
, 'share', 'cola', 'qm')
12 ICONSDIR
= os
.path
.join(PREFIX
, 'share', 'cola', 'icons')
15 'python': 'script.png',
17 'shell': 'script.png',
20 'assembler': 'binary.png',
21 'binary': 'binary.png',
26 def run_cmd(*command
):
28 Runs a *command argument list and returns the output.
29 e.g. run_cmd("echo", "hello", "world")
32 proc
= subprocess
.Popen(command
, stdout
= subprocess
.PIPE
)
34 # Wait for the process to return
35 stdout_value
= proc
.stdout
.read()
40 # Strip off trailing whitespace by default
41 return stdout_value
.rstrip()
44 def get_qm_for_locale(locale
):
45 regex
= re
.compile(r
'([^\.])+\..*$')
46 match
= regex
.match(locale
)
48 locale
= match
.group(1)
50 basename
= locale
.split('_')[0]
52 return os
.path
.join(QMDIR
, basename
+'.qm')
54 def ident_file_type(filename
):
55 """Returns an icon based on the contents of filename."""
56 if os
.path
.exists(filename
):
57 fileinfo
= run_cmd('file','-b',filename
)
58 for filetype
, iconname
in KNOWN_FILE_TYPES
.iteritems():
59 if filetype
in fileinfo
.lower():
63 # Fallback for modified files of an unknown type
66 def get_file_icon(filename
):
68 Returns the full path to an icon file corresponding to
71 icon_file
= ident_file_type(filename
)
72 return get_icon(icon_file
)
74 def get_icon(icon_file
):
75 return os
.path
.join(ICONSDIR
, icon_file
)
78 if os
.name
in ('nt', 'dos'):
79 for path
in os
.pathsep
.split(os
.environ
["PATH"]):
80 file = os
.path
.join(path
, args
[0]) + ".exe"
82 return os
.spawnv(os
.P_NOWAIT
, file, (file,) + args
[1:])
85 raise IOError('cannot find executable: %s' % program
)
87 argv
= map(shell_quote
, args
)
88 return os
.system(' '.join(argv
) + '&')
99 def grep(pattern
, items
, squash
=True):
100 isdict
= type(items
) is dict
101 if pattern
in __grep_cache
:
102 regex
= __grep_cache
[pattern
]
104 regex
= __grep_cache
[pattern
] = re
.compile(pattern
)
108 match
= regex
.match(item
)
109 if not match
: continue
110 groups
= match
.groups()
112 subitems
= match
.group(0)
117 subitems
= list(groups
)
119 matchdict
[item
] = items
[item
]
121 matched
.append(subitems
)
126 if squash
and len(matched
) == 1:
132 """Avoid os.path.basename because we are explicitly
133 parsing git"s output, which contains /"s regardless
136 base_regex
= re
.compile('(.*?/)?([^/]+)$')
137 match
= base_regex
.match(path
)
139 return match
.group(2)
143 def shell_quote(*inputs
):
145 Quote strings so that they can be suitably martialled
146 off to the shell. This method supports POSIX sh syntax.
147 This is crucial to properly handle command line arguments
148 with spaces, quotes, double-quotes, etc.
151 regex
= re
.compile('[^\w!%+,\-./:@^]')
152 quote_regex
= re
.compile("((?:'\\''){2,})")
160 raise AssertionError,('No way to quote strings '
161 'containing null(\\000) bytes')
163 # = does need quoting else in command position it's a
164 # program-local environment setting
165 match
= regex
.search(input)
166 if match
and '=' not in input:
168 input = input.replace("'", "'\\''")
170 # make multiple ' in a row look simpler
171 # '\'''\'''\'' -> '"'''"'
172 quote_match
= quote_regex
.match(input)
174 quotes
= match
.group(1)
175 input.replace(quotes
, ("'" *(len(quotes
)/4)) + "\"'")
177 input = "'%s'" % input
178 if input.startswith("''"):
181 if input.endswith("''"):
188 pad
= HEADER_LENGTH
- len(msg
) - 4 # len(':+') + len('+:')
194 +(' ' * (pad
+ extra
))
198 def parse_geom(geomstr
):
199 regex
= re
.compile('^(\d+)x(\d+)\+(\d+),(\d+)')
200 match
= regex
.match(geomstr
)
202 defaults
.WIDTH
= int(match
.group(1))
203 defaults
.HEIGHT
= int(match
.group(2))
204 defaults
.X
= int(match
.group(3))
205 defaults
.Y
= int(match
.group(4))
207 return (defaults
.WIDTH
,
213 return '%dx%d+%d,%d' % (defaults
.WIDTH
,
219 return os
.path
.basename(defaults
.DIRECTORY
)
227 def write(path
, contents
):
228 file = open(path
, 'w')
232 class DiffParser(object):
233 def __init__(self
, model
, filename
='', cached
=True, branch
=None):
235 self
.__header
_re
= re
.compile('^@@ -(\d+),(\d+) \+(\d+),(\d+) @@.*')
240 self
.__diff
_spans
= []
241 self
.__diff
_offsets
= []
249 (header
, diff
) = model
.diff_helper(filename
=filename
,
251 with_diff_header
=True,
252 cached
=cached
and not bool(branch
),
253 reverse
=cached
or bool(branch
))
257 self
.parse_diff(diff
)
259 # Always index into the non-reversed diff
260 self
.fwd_header
, self
.fwd_diff
= \
261 model
.diff_helper(filename
=filename
,
263 with_diff_header
=True,
264 cached
=cached
and not bool(branch
),
265 reverse
=bool(branch
))
267 def write_diff(self
,filename
,which
,selected
=False,noop
=False):
268 if not noop
and which
< len(self
.diffs
):
269 diff
= self
.diffs
[which
]
270 write(filename
, self
.header
+ '\n' + diff
+ '\n')
278 def get_diff_subset(self
, diff
, start
, end
):
283 offset
= self
.__diff
_spans
[diff
][0]
284 diffguts
= '\n'.join(self
.__diffs
[diff
])
286 for line
in self
.__diffs
[diff
]:
287 line_start
= offset
+ local_offset
288 local_offset
+= len(line
) + 1 #\n
289 line_end
= offset
+ local_offset
290 # |line1 |line2 |line3 |
293 # selection has head of diff (line3)
294 if start
< line_start
and end
> line_start
and end
< line_end
:
296 if line
.startswith('+'):
298 if line
.startswith('-'):
300 # selection has all of diff (line2)
301 elif start
<= line_start
and end
>= line_end
:
303 if line
.startswith('+'):
305 if line
.startswith('-'):
307 # selection has tail of diff (line1)
308 elif start
>= line_start
and start
< line_end
- 1:
310 if line
.startswith('+'):
312 if line
.startswith('-'):
315 # Don't add new lines unless selected
316 if line
.startswith('+'):
318 elif line
.startswith('-'):
319 # Don't remove lines unless selected
320 newdiff
.append(' ' + line
[1:])
324 new_count
= self
.__headers
[diff
][1] + adds
- deletes
325 if new_count
!= self
.__headers
[diff
][3]:
326 header
= '@@ -%d,%d +%d,%d @@' % (
327 self
.__headers
[diff
][0],
328 self
.__headers
[diff
][1],
329 self
.__headers
[diff
][2],
333 return (self
.header
+ '\n' + '\n'.join(newdiff
) + '\n')
336 return self
.__diff
_spans
338 def get_offsets(self
):
339 return self
.__diff
_offsets
341 def set_diff_to_offset(self
, offset
):
343 self
.diffs
, self
.selected
= self
.get_diff_for_offset(offset
)
345 def set_diffs_to_range(self
, start
, end
):
348 self
.diffs
, self
.selected
= self
.get_diffs_for_range(start
,end
)
350 def get_diff_for_offset(self
, offset
):
351 for idx
, diff_offset
in enumerate(self
.__diff
_offsets
):
352 if offset
< diff_offset
:
353 return (['\n'.join(self
.__diffs
[idx
])], [idx
])
356 def get_diffs_for_range(self
, start
, end
):
359 for idx
, span
in enumerate(self
.__diff
_spans
):
360 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
361 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
362 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
364 selected_diff
=(has_end_of_diff
368 diff
= '\n'.join(self
.__diffs
[idx
])
371 return diffs
, indices
373 def parse_diff(self
, diff
):
378 for idx
, line
in enumerate(diff
.splitlines()):
379 match
= self
.__header
_re
.match(line
)
381 self
.__headers
.append([
387 self
.__diffs
.append( [line
] )
389 line_len
= len(line
) + 1 #\n
390 self
.__diff
_spans
.append([total_offset
,
391 total_offset
+ line_len
])
392 total_offset
+= line_len
393 self
.__diff
_offsets
.append(total_offset
)
397 errmsg
= 'Malformed diff?\n\n%s' % diff
398 raise AssertionError, errmsg
399 line_len
= len(line
) + 1
400 total_offset
+= line_len
402 self
.__diffs
[self
.__idx
].append(line
)
403 self
.__diff
_spans
[-1][-1] += line_len
404 self
.__diff
_offsets
[self
.__idx
] += line_len
406 def process_diff_selection(self
, selected
, offset
, selection
, branch
=False):
408 start
= self
.fwd_diff
.index(selection
)
409 end
= start
+ len(selection
)
410 self
.set_diffs_to_range(start
, end
)
412 self
.set_diff_to_offset(offset
)
414 # Process diff selection only
416 for idx
in self
.selected
:
417 contents
= self
.get_diff_subset(idx
, start
, end
)
419 tmpfile
= self
.model
.get_tmp_filename()
420 write(tmpfile
, contents
)
422 self
.model
.apply_diff_to_worktree(tmpfile
)
424 self
.model
.apply_diff(tmpfile
)
426 # Process a complete hunk
428 for idx
, diff
in enumerate(self
.diffs
):
429 tmpfile
= self
.model
.get_tmp_filename()
430 if self
.write_diff(tmpfile
,idx
):
432 self
.model
.apply_diff_to_worktree(tmpfile
)
434 self
.model
.apply_diff(tmpfile
)
437 def sanitize_input(input):
438 for c
in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
439 input = input.replace(c
, '_')