7 from cStringIO
import StringIO
11 'python': 'script.png',
13 'shell': 'script.png',
16 'assembler': 'binary.png',
17 'binary': 'binary.png',
22 ICONSDIR
= os
.path
.join(os
.path
.dirname(__file__
), 'icons')
24 def ident_file_type(filename
):
25 '''Returns an icon based on the contents of filename.'''
26 if os
.path
.exists(filename
):
27 quoted_filename
= shell_quote(filename
)
28 fileinfo
= commands
.getoutput('file -b %s' % quoted_filename
)
29 for filetype
, iconname
in KNOWN_FILE_TYPES
.iteritems():
30 if filetype
in fileinfo
.lower():
34 # Fallback for modified files of an unknown type
37 def get_icon(filename
):
38 '''Returns the full path to an icon file corresponding to
39 filename's contents.'''
40 icon_file
= ident_file_type(filename
)
41 return os
.path
.join(ICONSDIR
, icon_file
)
43 def get_staged_icon(filename
):
44 '''Special-case method for staged items. These are only
45 ever 'staged' and 'removed' items in the staged list.'''
47 if os
.path
.exists(filename
):
48 return os
.path
.join(ICONSDIR
, 'staged.png')
50 return os
.path
.join(ICONSDIR
, 'removed.png')
52 def get_untracked_icon():
53 return os
.path
.join(ICONSDIR
, 'untracked.png')
55 def get_directory_icon():
56 return os
.path
.join(ICONSDIR
, 'dir.png')
59 return os
.path
.join(ICONSDIR
, 'generic.png')
61 def grep(pattern
, items
, squash
=True):
62 regex
= re
.compile(pattern
)
65 match
= regex
.match(item
)
66 if not match
: continue
67 groups
= match
.groups()
69 subitems
= match
.group(0)
74 subitems
= list(groups
)
75 matched
.append(subitems
)
77 if squash
and len(matched
) == 1:
83 '''Avoid os.path.basename because we are explicitly
84 parsing git's output, which contains /'s regardless
87 base_regex
= re
.compile('(.*?/)?([^/]+)$')
88 match
= base_regex
.match(path
)
94 def shell_quote(*inputs
):
95 '''Quote strings so that they can be suitably martialled
96 off to the shell. This method supports POSIX sh syntax.
97 This is crucial to properly handle command line arguments
98 with spaces, quotes, double-quotes, etc.'''
100 regex
= re
.compile('[^\w!%+,\-./:@^]')
101 quote_regex
= re
.compile("((?:'\\''){2,})")
109 raise AssertionError,('No way to quote strings '
110 'containing null(\\000) bytes')
112 # = does need quoting else in command position it's a
113 # program-local environment setting
114 match
= regex
.search(input)
115 if match
and '=' not in input:
117 input = input.replace("'", "'\\''")
119 # make multiple ' in a row look simpler
120 # '\'''\'''\'' -> '"'''"'
121 quote_match
= quote_regex
.match(input)
123 quotes
= match
.group(1)
124 input.replace(quotes
,
125 ("'" *(len(quotes
)/4)) + "\"'")
127 input = "'%s'" % input
128 if input.startswith("''"):
131 if input.endswith("''"):
136 def get_tmp_filename():
137 # Allow TMPDIR/TMP with a fallback to /tmp
138 return '.ugit.%s.%s' %( os
.getpid(), time
.time() )
142 pad
= HEADER_LENGTH
- len(msg
) - 4 # len(':+') + len('+:')
148 +(' ' *(pad
+ extra
))
152 def parse_geom(geomstr
):
153 regex
= re
.compile('^(\d+)x(\d+)\+(\d+),(\d+) (\d+),(\d+) (\d+),(\d+)')
154 match
= regex
.match(geomstr
)
156 defaults
.WIDTH
= int(match
.group(1))
157 defaults
.HEIGHT
= int(match
.group(2))
158 defaults
.X
= int(match
.group(3))
159 defaults
.Y
= int(match
.group(4))
160 defaults
.SPLITTER_TOP_0
= int(match
.group(5))
161 defaults
.SPLITTER_TOP_1
= int(match
.group(6))
162 defaults
.SPLITTER_BOTTOM_0
= int(match
.group(7))
163 defaults
.SPLITTER_BOTTOM_1
= int(match
.group(8))
165 return (defaults
.WIDTH
, defaults
.HEIGHT
,
166 defaults
.X
, defaults
.Y
,
167 defaults
.SPLITTER_TOP_0
, defaults
.SPLITTER_TOP_1
,
168 defaults
.SPLITTER_BOTTOM_0
, defaults
.SPLITTER_BOTTOM_1
)
171 return '%dx%d+%d,%d %d,%d %d,%d' % (
172 defaults
.WIDTH
, defaults
.HEIGHT
,
173 defaults
.X
, defaults
.Y
,
174 defaults
.SPLITTER_TOP_0
, defaults
.SPLITTER_TOP_1
,
175 defaults
.SPLITTER_BOTTOM_0
, defaults
.SPLITTER_BOTTOM_1
)
178 return os
.path
.basename(defaults
.DIRECTORY
)
186 def write(path
, contents
):
187 file = open(path
, 'w')
192 class DiffParser(object):
193 def __init__(self
, diff
):
194 self
.__diff
_header
= re
.compile('^@@\s[^@]+\s@@.*')
198 self
.__diff
_spans
= []
199 self
.__diff
_offsets
= []
201 self
.parse_diff(diff
)
207 return self
.__diff
_spans
209 def get_offsets(self
):
210 return self
.__diff
_offsets
212 def get_diff_for_offset(self
, offset
):
213 for idx
, diff_offset
in enumerate(self
.__diff
_offsets
):
214 if offset
< diff_offset
:
215 return os
.linesep
.join(self
.__diffs
[idx
])
218 def get_diffs_for_range(self
, start
, end
):
220 for idx
, span
in enumerate(self
.__diff
_spans
):
222 has_end_of_diff
= start
>= span
[0] and start
< span
[1]
223 has_all_of_diff
= start
<= span
[0] and end
>= span
[1]
224 has_head_of_diff
= end
>= span
[0] and end
<= span
[1]
226 selected_diff
=(has_end_of_diff
231 diff
= os
.linesep
.join(self
.__diffs
[idx
])
237 def parse_diff(self
, diff
):
239 for idx
, line
in enumerate(diff
.splitlines()):
241 if self
.__diff
_header
.match(line
):
242 self
.__diffs
.append( [line
] )
244 line_len
= len(line
) + 1
245 self
.__diff
_spans
.append([total_offset
,
246 total_offset
+ line_len
])
248 total_offset
+= line_len
249 self
.__diff
_offsets
.append(total_offset
)
254 errmsg
= 'Malformed diff?\n\n%s' % diff
255 raise AssertionError, errmsg
257 line_len
= len(line
) + 1
258 total_offset
+= line_len
260 self
.__diffs
[self
.__idx
].append(line
)
261 self
.__diff
_spans
[-1][-1] += line_len
262 self
.__diff
_offsets
[self
.__idx
] += line_len