fw4spl
common.py
1 #!/usr/bin/env python2
2 # -*- coding: UTF-8 -*-
3 
4 import collections
5 # From command line arguments
6 import datetime
7 import fnmatch
8 import os
9 import re
10 import subprocess
11 
12 g_trace = False
13 g_cppcheck_path_arg = None
14 g_uncrustify_path_arg = None
15 
16 
18  NotModified = 0
19  Modified = 1
20  Error = 2
21 
22  def __init__(self):
23  self.value = 0
24 
25  def add(self, value):
26  self.value = max(self.value, value)
27 
28 
29 class FileAtIndex(object):
30  def __init__(self, contents, size, mode, sha1, status, path):
31  self.contents = contents
32  self.size = size
33  self.mode = mode
34  self.sha1 = sha1
35  self.status = status
36  self.path = path
37 
38  def fnmatch(self, pattern):
39  basename = os.path.basename(self.path)
40  return fnmatch.fnmatch(basename, pattern)
41 
42 
43 def note(msg):
44  print('* [Sheldon] ' + msg)
45 
46 
47 def trace(msg):
48  if g_trace:
49  print('* [Sheldon] ' + msg)
50 
51 
52 def error(msg):
53  print('*** [ERROR] ' + msg + ' ***')
54 
55 
56 def warn(msg):
57  print('* [Warning] ' + msg + ' *')
58 
59 
60 def binary(s):
61  """return true if a string is binary data"""
62  return bool(s and '\0' in s)
63 
64 
65 ExecutionResult = collections.namedtuple(
66  'ExecutionResult',
67  'status, out',
68 )
69 
70 
71 def get_repo_root():
72  result = execute_command('git rev-parse --show-toplevel');
73 
74  if result.status == 0:
75  return result.out.strip()
76 
77  warn(result.out)
78  return ""
79 
80 
81 def is_LGPL_repo():
82  repo_root = get_repo_root()
83  try:
84  f = open(os.path.join(repo_root, "LICENSE/COPYING.LESSER"))
85  f.close()
86  return True
87  except:
88  return False
89 
90 
91 def _get_git_commit_datetime(path):
92  # Get the git modification date of the file
93  result = execute_command('git log -1 --format=%ad --date=format:%Y-%m-%dT%H:%M:%S ' + path)
94 
95  if result.status != 0:
96  warn(result.out)
97  return None
98 
99  try:
100  # Parse the string back to a datetime object
101  return datetime.datetime.strptime(result.out.strip(), '%Y-%m-%dT%H:%M:%S')
102  except Exception as e:
103  warn(e.message)
104  return None
105 
106 
107 def get_file_datetime(path, check_commits_date):
108  try:
109  creation_time = datetime.datetime.fromtimestamp(os.path.getctime(path))
110  modification_time = datetime.datetime.fromtimestamp(os.path.getmtime(path))
111  except Exception as e:
112  warn(e.message)
113 
114  creation_time = None
115  modification_time = None
116 
117  if check_commits_date:
118  git_datetime = _get_git_commit_datetime(path)
119 
120  # Use git modification time if it is valid and creation_time == modification_time
121  if git_datetime is not None:
122  return git_datetime
123 
124  # Use the modification time, if any
125  if modification_time is not None:
126  return modification_time
127 
128  # Otherwise use the system time
129  return datetime.datetime.today()
130 
131 
132 def execute_command(proc):
133  try:
134  out = subprocess.check_output(proc.split(), stderr=subprocess.STDOUT)
135  except subprocess.CalledProcessError as e:
136  return ExecutionResult(1, e.output)
137  except OSError as e:
138  return ExecutionResult(1, e.message)
139 
140  return ExecutionResult(0, out)
141 
142 
143 def current_commit():
144  if execute_command('git rev-parse --verify HEAD').status != 0:
145  return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
146  else:
147  return 'HEAD'
148 
149 
150 def get_option(option, default, type=""):
151  try:
152  out = subprocess.check_output(('git config ' + type + ' ' + option).split()).strip()
153  return out
154  except subprocess.CalledProcessError:
155  return default
156 
157 
158 def _contents(sha):
159  result = execute_command('git show ' + sha)
160 
161  if result.status == 0:
162  return result.out
163 
164  warn(result.out)
165  return ""
166 
167 
168 def _diff_index(rev):
169  result = execute_command('git diff-index --cached -z --diff-filter=AM ' + rev)
170 
171  if result.status == 0:
172  return result.out
173 
174  warn(result.out)
175  return ""
176 
177 
178 def _diff(rev, rev2):
179  result = execute_command('git diff --raw -z --diff-filter=AM ' + rev + ' ' + rev2)
180 
181  if result.status == 0:
182  return result.out
183 
184  warn(result.out)
185  return ""
186 
187 
188 def _size(sha):
189  result = execute_command('git cat-file -s ' + sha)
190  if result.status == 0:
191  try:
192  return int(result.out)
193  except ValueError:
194  return 0
195 
196  warn(result.out)
197  return 0
198 
199 
200 def files_in_rev(rev, rev2=''):
201  # see: git help diff-index
202  # "RAW OUTPUT FORMAT" section
203  diff_row_regex = re.compile(
204  r'''
205  :
206  (?P<old_mode>[^ ]+)
207  [ ]
208  (?P<new_mode>[^ ]+)
209  [ ]
210  (?P<old_sha1>[^ ]+)...
211  [ ]
212  (?P<new_sha1>[^ ]+)...
213  [ ]
214  (?P<status>[^\0]+)
215  \0
216  (?P<path>[^\0]+)
217  \0
218  ''',
219  re.X
220  )
221 
222  for match in diff_row_regex.finditer(_diff(rev, rev2)):
223  mode, sha, status, path = match.group(
224  'new_mode', 'new_sha1', 'status', 'path'
225  )
226 
227  if status is None or status == 'D':
228  continue
229 
230  # Try to guest if the file has been deleted in a later commit
231  file_status = status_of_file(get_repo_root() + '/' + path)
232 
233  if file_status is None or file_status == 'D':
234  continue
235 
236  content = _contents(sha)
237 
238  if content is None or len(content) <= 0:
239  continue
240 
241  size = _size(sha)
242 
243  if size is None or size <= 0:
244  continue
245 
246  yield FileAtIndex(
247  content,
248  size,
249  mode,
250  sha,
251  status,
252  path
253  )
254 
255 
256 def files_staged_for_commit(rev):
257  # see: git help diff-index
258  # "RAW OUTPUT FORMAT" section
259  diff_index_row_regex = re.compile(
260  r'''
261  :
262  (?P<old_mode>[^ ]+)
263  [ ]
264  (?P<new_mode>[^ ]+)
265  [ ]
266  (?P<old_sha1>[^ ]+)
267  [ ]
268  (?P<new_sha1>[^ ]+)
269  [ ]
270  (?P<status>[^\0]+)
271  \0
272  (?P<path>[^\0]+)
273  \0
274  ''',
275  re.X
276  )
277  for match in diff_index_row_regex.finditer(_diff_index(rev)):
278  mode, sha, status, path = match.group(
279  'new_mode', 'new_sha1', 'status', 'path'
280  )
281 
282  # Try to guest if the file has been deleted in a later commit
283  file_status = status_of_file(get_repo_root() + '/' + path)
284 
285  if status is not None and status != 'D' and file_status is not None and file_status != 'D':
286  yield FileAtIndex(
287  _contents(sha),
288  _size(sha),
289  mode,
290  sha,
291  status,
292  path
293  )
294 
295 
296 def status_of_file(path):
297  # By default status is set to 'A' like it is a new file.
298  # if the git status is '??' or empty, we guess also that the file is a new file.
299  status = 'A'
300  gitstatus = execute_command('git status --porcelain ' + path)
301 
302  if gitstatus.status != 0:
303  warn("File : " + path + " is not in a git repository, sheldon will consider it like a new file")
304  else:
305  out = gitstatus.out.split()
306 
307  # if out is not empty and not equal to '??' so we have modified a tracked file.
308  if out and out[0] != '??':
309  status = out[0]
310 
311  return status
312 
313 
314 def file_on_disk(path):
315  status = status_of_file(path)
316 
317  if status is not None and status != 'D':
318  with open(path, 'r') as content_file:
319  content = content_file.read()
320 
321  stat = os.stat(path)
322  size = stat.st_size
323 
324  yield FileAtIndex(
325  content,
326  size,
327  '',
328  '',
329  status,
330  path
331  )
332 
333 
334 def directory_on_disk(path):
335  for root, dirs, files in os.walk(path):
336  for name in files:
337  file_path = os.path.join(root, name)
338  yield file_on_disk(file_path).next()