PRESUBMIT: improve lint checks

Catch various style errors at upload time instead of code review.

BUG=None
TEST=all existing accounts pass lint, but invalid ones created locally fail

Change-Id: If42f00c4d10bdb54dcf6bfd190be3da47a411ecd
Reviewed-on: https://chromium-review.googlesource.com/1069187
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Commit-Ready: Mike Frysinger <vapier@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Jeffrey Kardatzke <jkardatzke@google.com>
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
index 25c8a3a..450b7b9 100644
--- a/PRESUBMIT.cfg
+++ b/PRESUBMIT.cfg
@@ -3,4 +3,4 @@
 manifest_check: true
 
 [Hook Scripts]
-hook0 = profiles/base/accounts/display-accts.py
+account_checks = profiles/base/accounts/display-accts.py --lint
diff --git a/profiles/base/accounts/display-accts.py b/profiles/base/accounts/display-accts.py
index 1d519ff..eb8c2d0 100755
--- a/profiles/base/accounts/display-accts.py
+++ b/profiles/base/accounts/display-accts.py
@@ -26,10 +26,24 @@
   """Parse the raw data in |content| and return a new |obj|"""
   d = defaults.copy()
 
+  # Make sure files all have a trailing newline.
+  if not content.endswith('\n'):
+    raise ValueError('File needs a trailing newline')
+
+  # Disallow leading & trailing blank lines.
+  if content.startswith('\n'):
+    raise ValueError('Delete leading blank lines')
+  if content.endswith('\n\n'):
+    raise ValueError('Delete trailing blank lines')
+
   for line in content.splitlines():
     if not line or line.startswith('#'):
       continue
 
+    # Disallow leading & trailing whitespace.
+    if line != line.strip():
+      raise ValueError('Trim leading/trailing whitespace: "%s"' % line)
+
     key, val = line.split(':')
     if key not in obj._fields:
       raise ValueError('unknown key: %s' % key)
@@ -197,6 +211,8 @@
   parser = argparse.ArgumentParser(description=__doc__)
   parser.add_argument('--show-free', default=False, action='store_true',
                       help='Find next available UID/GID')
+  parser.add_argument('--lint', default=False, action='store_true',
+                      help='Validate all the user accounts')
   parser.add_argument('account', nargs='*',
                       help='Display these account files only')
   return parser
@@ -237,30 +253,31 @@
     ShowNextFree(groups, users)
     return
 
-  if groups:
-    order = (
-        ('gid', ''),
-        ('group', ''),
-        ('password', 'pass'),
-        ('users', ''),
-        ('defunct', ''),
-    )
-    DisplayAccounts(groups, order)
-
-  if users:
+  if not opts.lint:
     if groups:
-      print()
-    order = (
-        ('uid', ''),
-        ('gid', ''),
-        ('user', ''),
-        ('shell', ''),
-        ('home', ''),
-        ('password', 'pass'),
-        ('gecos', ''),
-        ('defunct', ''),
-    )
-    DisplayAccounts(users, order)
+      order = (
+          ('gid', ''),
+          ('group', ''),
+          ('password', 'pass'),
+          ('users', ''),
+          ('defunct', ''),
+      )
+      DisplayAccounts(groups, order)
+
+    if users:
+      if groups:
+        print()
+      order = (
+          ('uid', ''),
+          ('gid', ''),
+          ('user', ''),
+          ('shell', ''),
+          ('home', ''),
+          ('password', 'pass'),
+          ('gecos', ''),
+          ('defunct', ''),
+      )
+      DisplayAccounts(users, order)
 
   if consistency_check and not CheckConsistency(groups, users):
     return os.EX_DATAERR