#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# catsub - little substitutor
#
# Copyright (c) 2018  Ramses van Zon
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

from __future__ import print_function
import sys
import re
import collections

escaped_percentage_sign=u"％"  # abusing unicode character to escape '%'

def print_help():
   """print help message for the catsub command"""
   
   print("""
   catsub - substitutes every value for each variable in each word of a template file.

   Usage:

     catsub [--help] [-s] [-u] [-D|-dSTR] [TEMPLATEFILES] [%VARNAME VALUE1 VALUE2 ... ]*

   Arguments:

     TEMPLATEFILES     Name(s) of file(s) containg the template with
                       variables of the from %VARNAME; If no file name
                       is given, or the name is '-', catsub will read
                       from standard input;
     %VARNAME          Variable name to substitute;
     VALUE1 VALUE2 ... Values to substitute for the variable;
     -s                Print statistics to stderr on resolved and unresolved variables.
     -u                Escaped percentage in template are returned unescaped;
     -dSTR             Use STR to divide multiple substituted values
     -D                Use newline to divide multiple substituted values
     --help            Show this help page.

   Notes: 

   - The names of the template files may not start with a percent sign.  

   - All variables must start with a percent sign and cannot contain
     whitespace.

   - Substituted values cannot start with a percent sign.

   - Substitution happens only once per variable, i.e., substituted
     values do not undergo subsequent substitutions.

   - When substituting several values, those values are separated by a
     space, by a newline if the -D argument was given, or by STR if
     the -dSTR argument was given.

   - When a variable has been given several values to substitute and
     the variable occurs in a substring of a word in the template,
     that word get repeated. E.g. "echo un%X | catsub %X kind tidy"
     gives "unkind untidy"

   - Substitution happens combinatorically within a word. E.g. a word
     "%X,%Y" in the template, when processed with "catsub %X a b %Y c d"
     becomes "a,c a,d b,c b,d". Combinatorics can be circumvented by
     quoting the replacement values, i.e.  "catsub %X 'a b' %Y 'c d'"
     gives "a b,c d".

   - Substitution uses the longest possible variable name. E.g. in
     "%HELLOWORLD", both %HELLO and %HELLOWORLD could be substituted
     if values for both are specified on the catsub command, but it is
     the longer %HELLOWORLD that gets used.
   
   - Percentage signs in the template can escape substitution by
     prepeding them with a slash, i.e., '\%'.  Every '\%' in the
     template will be remain a '\%' unless the -u argument is used in
     the catsub command, in which case, they are replaced by '%'.

   - The template cannot use the unicode character '％'.

   Examples:

     $ echo %HELLO %UNIVERSE | catsub %HELLO Hi %UNIVERSE world
     Hi world

     $ echo %HELLO %UNIVERSE > example.tmpl
     $ catsub example.tmpl %HELLO Greetings %UNIVERSE universe!
     Greetings universe!
""")

def main(filenames, vararg, unescape=False, reportstats=False, multisep=' '):

   """
   Process the template in filename, given the argument list with
   variable definitions in vararg, possibly resolving escaped
   percentages intact.

   Arguments:

     filenames    list of template filenames (where '-' means use stdin)
     vararg       list of variable names and values
     unescape:    if True, replace '\%' in the templates by '%'
     reportstats: if True, report unsubstituted variable names to stderr
   """
   
   assert(isinstance(filenames,list))
   assert(isinstance(vararg,list))
   assert(isinstance(unescape,bool))

   # Collect values for each variable from the command line.
   values  = collections.defaultdict(list)
   varname = '%DEFAULT'
   for arg in vararg:
      if len(arg)>0 and arg[0] == '%':
         if len(re.split(r'(\s+)', arg))==1:
            varname = arg
         else:
            print("catsub error - '"+arg+"' is not a valid variable name.")
            sys.exit(1)
      else:
         # Ensure repeated substitution does not occur by escaping
         # percentage signs
         values[varname].append(arg.replace('%',escaped_percentage_sign))

   # if no files given, read from stdin, signaled by '-' as a filename
   if filenames == []:
      filenames = ['-']

   # keep track of variables in files that are not used
   if reportstats:
      allvars = set()
      usedvars = set()

   # process files   
   for filename in filenames:
      # open file or use standard in
      if filename == '-':
         f = sys.stdin
      else:
         try:
            f = open(filename, 'r')
         except:
            print("catsub error - could not open file '" + filename + "'.")
            sys.exit(1)
      # Process each line in the file and make substitutions in each word
      try:
         for line in f:
            if unescape:
               line = line.replace('\\%',escaped_percentage_sign)
            else:
               line = line.replace('\\%','\\'+escaped_percentage_sign)
            words = re.split(r'(\s+)', line)
            if reportstats:
               for word in words:
                  allvars |= set(word.split('%')[1:])
            for wordidx in range(len(words)):
               for varname in sorted(values.keys(),reverse=True):
                  if varname in words[wordidx]:
                     if reportstats:
                        usedvars.add(varname[1:])
                     replacements = []
                     for value in values[varname]:
                        replacements.append(words[wordidx].replace(varname,value))
                     words[wordidx] = multisep.join(replacements)
            replacedline = ''.join(words)
            # Undo escaping of percentage sign
            print(replacedline.replace(escaped_percentage_sign,'%'),end='')
      except:
         # Reading file or processing went wrong
         print("catsub error - could not process file '" + filename + "'.")
         sys.exit(1)
      if filename != '-':
         f.close()
   if reportstats:
      if len(values)!=len(usedvars):
         print("Unused variables:", file=sys.stderr)
         for word in values.keys():
            if not word[1:] in usedvars:
               print(word, file=sys.stderr)
      if len(usedvars)>0:
         print("Substituted variables:", file=sys.stderr)
         for word in usedvars:
            print('%'+word, file=sys.stderr)
      if len(allvars) != len(usedvars):
         print("Unsubstituted variables present in template:", file=sys.stderr)
         for word in allvars:
            if not word in usedvars:
               print('%'+word, file=sys.stderr)

## Start of application ##

if __name__ == "__main__":

   # Parse the arguments and pass to main
   filestart   = 1
   varstart    = 1
   unescape    = False
   reportstats = False
   multisep    = ' '
   readopts  = True   # start reading option
   for i in range(1,len(sys.argv)):
      if readopts and len(sys.argv[i]) > 0 and sys.argv[i][0] == '-':
         if sys.argv[i] == '--help':
            print_help()
            sys.exit(0)
         elif sys.argv[i] == '-u':
            unescape=True
         elif sys.argv[i] == '-s':
            reportstats=True
         elif sys.argv[i] == '-D':
            multisep='\n'
         elif sys.argv[i][:2] == '-d':
            multisep=sys.argv[i][2:]
         else:
            print("catsub error - unknown option '" + sys.argv[i] + "'.")
            sys.exit(1)
         filestart = i + 1
         varstart  = i + 1
      else:
         optionson = False
         if len(sys.argv[i]) > 0 and sys.argv[i][0] == '%':
            varstart = i
            break
         else:
            varstart = i+1
   main(sys.argv[filestart:varstart], sys.argv[varstart:], unescape=unescape, reportstats=reportstats,multisep=multisep)
