#!/usr/bin/env python

"""
Python script that runs one-liner python scripts similarly to how Perl runs them

portions based on http://code.activestate.com/recipes/437932-pyline-a-grep-like-sed-like-command-line-tool/
(Graham Fawcett, Jacob Oscarson, Mark Eichin)
interface inspired by Perl
"""

"""
Copyright (C) 2010  Drew Gulino

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import sys
import re
import getopt
from getopt import GetoptError
import os

def usage(progname):
    print "Usage: " + progname
    version()
    print """
    ./pyliner (options) "script"
    -a enables auto-split of input into the F[] array.
    -F specifies the characters to split on with the -a option (default=" ")
    -p loops over and prints input.
    -n loops over and does not print input.
    -l strips newlines on input, and adds them on output 
    -M lets you specify module imports
    -o print out parsed and indented script on multiple lines
    -e (assumed - does nothing, added for Perl compatibility)
    
    variables:
    F[] = split input
    line = current input line
    NR = current input line number
    
    functions:
    I(x) = function returns F[x] cast to integer
    F(x) = function returns F[x] cast to float
    
    script:
        newlines are specified with ";"
        ":" will start an indent for the following lines
        "?;" will add a newline and dedent the following lines
        "/;" will reset the indent the far left
        "BEGIN:" starts the an initial script not run in the input loop
        "END:" starts part of the script run after the input loop
        "#/" ends both the "BEGIN:" and "END:" portions of the script
    """

#test.txt=
"""
10,test
1,rest
100,test
10,best
1000,we
1,see
"""

"""
cat test.txt | ./pyliner.py -F"," -lane 'BEGIN:sum=0#/if I(0) > 90:sum+=I(0) END:print sum#/'
1100
"""

"""
pyliner:
cat test.txt | ./pyliner.py -F"," -lane 'BEGIN:sum=0#/a=F(0);if a > 90:sum+=a END:print sum/NR#/'
183.333333333

perl:
cat test.txt | perl -F"," -lane 'BEGIN {$sum=0; } ;$sum=$sum+=@F[0] if @F[0]>90;END { print $sum/$.; }'
183.333333333333
"""

def version():
    print "version: 1.0"

write = sys.stdout.write

def indent_script(script_lines):
    #indent lines
    indent = 0
    next_indent = 0
    script = []
    for cmd in script_lines:
        cmd = cmd.strip()
        if cmd[-1]==(":"):
            indent=indent+1
        if cmd[-1]=="?":
            cmd=cmd[0:-1]
            indent=indent-1
        if cmd[-1]=="/":
            cmd=cmd[0:-1]
            indent=0
        cmd=("\t"*next_indent)+cmd
        next_indent = indent
        script.append(cmd)
    script = '\n'.join(script)
    return script

def split_script(cmd):
    #split lines
    cmd = cmd.replace("?;","?\n")
    cmd = cmd.replace("/;","/\n")
    cmd = cmd.replace(";","\n")
    cmd = cmd.replace(":",":\n")
    return cmd

def main(argv, stdout, environ):
    progname = argv[0]
    split_lines=False
    FS = ' '
    print_input = False
    strip_newlines = False
    no_print_input = False
    output_script = False
    F =[]
    
    def I(x): return int(F[x])
    
    def f(x): return float(F[x])
    # parse options for module imports
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'oaplneM:F:')
    except GetoptError:
        print "Invalid Option!"
        usage(progname)
        return
    
    opts = dict(opts)
    if '-M' in opts:
        for imp in opts['-M'].split(','):
            locals()[imp] = __import__(imp.strip())
    if "-a" in opts:
        split_lines = True
    if '-F' in opts:
        FS = opts['-F']
    if "-p" in opts:
        print_input = True
    if "-l" in opts:
        strip_newlines = True
    if "-n" in opts:
        no_print_input = True
    if "-e" in opts:
        pass
    if "-o" in opts:
        output_script = True

    cmd = ' '.join(args)
    if not cmd.strip():
        cmd = 'line'                        # no-op
    
    begin_cmd = ''
    end_cmd = ''
    
    if cmd.find("BEGIN:") >= 0:
        start=cmd.find("BEGIN:")
        end=cmd.find("#/")
        begin_cmd=cmd[start+6:end]
        cmd=cmd[end+2:]
        
    if cmd.find("END:") >= 0:
        start=cmd.find("END:")
        end=cmd.find("#/")
        end_cmd=cmd[start+4:end]
        cmd=cmd[:start]
    
    cmd = split_script(cmd)
    script_lines = cmd.splitlines()
    
    if len(begin_cmd) > 1:
        begin_cmd = split_script(begin_cmd)
        begin_lines = begin_cmd.splitlines()
        begin_script = indent_script(begin_lines)
    
        begin_codeobj = compile(begin_script, 'command', 'exec')
        result =  eval(begin_codeobj, globals(), locals())
        if result is None or result is False:
            result = ''
        elif isinstance(result, list) or isinstance(result, tuple):
            result = ' '.join(map(str, result))
        else:
            result = str(result)
        write(result)
    
    
    if len(end_cmd) > 1:
        end_cmd = split_script(end_cmd)
        end_lines = end_cmd.splitlines()
        end_script = indent_script(end_lines)
        end_codeobj = compile(end_script, 'command', 'exec')
        
    script = indent_script(script_lines)
    
    if output_script == True:
        print "BEGIN:"
        print begin_script 
        print
        print script 
        print
        print "END:"
        print end_script 
        print
    
    #compile script   
    codeobj = compile(script, 'command', 'exec')
    
    if print_input or no_print_input:
        for num, line in enumerate(sys.stdin):
            #line = line[:-1]
            NR = num + 1
            if strip_newlines == True:
                line = line.strip()
            if print_input == True:
                print(line)
            if split_lines == True:
                F = [w for w in line.split(FS) if len(w)]
            result =  eval(codeobj, globals(), locals())
            if result is None or result is False:
                continue
            elif isinstance(result, list) or isinstance(result, tuple):
                result = ' '.join(map(str, result))
            else:
                result = str(result)
            write(result)
            if strip_newlines == True:
               write('\n')
    
    else:
        result =  eval(codeobj, globals(), locals())
        
    if len(end_cmd) > 1:    
        result =  eval(end_codeobj, globals(), locals())
        if result is None or result is False:
            result = ''
        elif isinstance(result, list) or isinstance(result, tuple):
            result = ' '.join(map(str, result))
        else:
            result = str(result)
        write(result)
    
if __name__ == "__main__":
    main(sys.argv, sys.stdout, os.environ)
