#!/usr/bin/env python3

import argparse
import sys


def die(msg):
  """
  Print message to STDERR and exit.
  """
  sys.stderr.write(msg)
  sys.exit(1)

def warn(msg):
  """
  Print message to STDERR.
  """
  sys.stderr.write(msg)




def parse_file(fpath, typ=None):
  """
  Parse a passwd-like file.
  """
  fields = dict()
  try:
    with open(fpath) as f:
      for line in f:
        line = line.strip()
        if not line:
          continue
        try:
          key, value = line.strip().split(':', 1)
          if typ == 'group':
            values = value.split(':')
            values[2] = ','.join(sorted(values[2].split(',')))
            value = ':'.join(values)
          if key in fields:
            warn('warning: duplicate field in %s [%s]\n' % (fpath, key))
          fields[key] = value
        except (ValueError, IndexError):
          die('error: failed to parse %s [line: %s]\n' % (fpath, line));
    return fields
  except (FileNotFoundError, PermissionError) as e:
    die(str(e))


def get_argparser():
  """
  Return the argument parser.
  """
  parser = argparse.ArgumentParser(description='Merge changes in password files.')
  parser.add_argument(
    'files', metavar='<path>', nargs='+',
    help='The files to merge. Multiple may be given. If only one is given, a matching .pacnew file must exist.'
  )
  parser.add_argument(
    '-o', '--output', metavar='<path>',
    help='The output file. This may be one of the input files, but it is recommended to use a different file and check the output before overwriting system files.'
  )
  parser.add_argument(
    '-c', '--confirm', action='store_true',
    help='Confirm before writing output file.'
  )
  parser.add_argument(
    '-g', '--group', action='store_true',
    help='Treat files as group files.'
  )
  return parser

def main():
  parser = get_argparser()
  args = parser.parse_args()

  n = len(args.files)
  if n == 1:
    args.files.append(args.files[0] + '.pacnew')
    n += 1
  width = max((len(f) for f in args.files)) + 2
  dwidth = len(str(n))
  fmt = '%' + str(dwidth) + 'd %-' + str(width) + 's %s'

  files = list()
  all_fields = set()
  for f in args.files:
    if args.group:
      fields = parse_file(f, typ='group')
    else:
      fields = parse_file(f)
    all_fields |= set(fields)
    files.append(fields)

  merged = ''
  for field in sorted(all_fields):
    choices = list()
    for i in range(n):
      try:
        choice = field + ':' + files[i][field]
      except KeyError:
        choice = ''
      choices.append(choice)
    if len(set(choices)) == 1:
      merged += choices[0] + '\n'
      continue
    while True:
      for i in range(n):
        print(fmt % (i+1, '[' + args.files[i] + ']', choices[i]))
      j = input ('Entry to keep [1]: ')
      if not j:
        j = 1
      else:
        try:
          j = int(j)
          if j < 1 or j > n:
            raise ValueError
        except ValueError:
          warn('error: invalid choice, try again\n')
          continue
      j -= 1
      if choices[j]:
        merged += choices[j] + '\n'
      break
    print()

  if args.output:
    if args.confirm:
      ans = None
      while ans not in ('y', 'n'):
        print("Merged File")
        print(merged)
        ans = input('Write to %s? [y/n] ' % args.output)
        print()
      if ans != 'y':
        print("Aborting")
        return
    with open(args.output, 'w') as f:
      f.write(merged)
    print("Merged file written to %s" % args.output)
  else:
    print(merged)

if __name__ == '__main__':
  try:
    main()
  except KeyboardInterrupt:
    pass

