#!@{SHEBANG}
# -*- coding: utf-8 -*-
'''
gpp – Bash based general purpose preprocessor
Copyright © 2013, 2014 Mattias Andrée (maandree@member.fsf.org)
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/>.
'''
VERSION="@{VERSION}"
import os
import sys
import shlex
from subprocess import Popen, PIPE
if sys.version_info.major < 3:
def bytes(string):
r = bytearray(len(string))
b = buffer(r)
r[:] = string
return r
if sys.version_info.major < 3:
def bytelist(string):
return [ord(c) for c in string]
else:
bytelist = list
symbol = '@'
encoding = sys.getdefaultencoding()
iterations = 1
input_file = '/dev/stdin'
output_file = '/dev/stdout'
unshebang = 0
args = sys.argv
# If the file is executed, the first command line argument will be the first argument in the shebang,
# the second will be the rest of the arguments in the command line as is and the third and final will
# be the executed file.
if len(args) == 3:
if os.path.exists(args[2]) and os.path.isfile(args[2]) and ((os.stat(args[2]).st_mode & 0o111) != 0):
args[1 : 2] = shlex.split(args[1])
for i in range(1, len(args)):
arg = args[i]
i += 1
if arg in ('-s', '--symbol'): symbol = sys.argv[i]
elif arg in ('-e', '--encoding'): encoding = sys.argv[i]
elif arg in ('-n', '--iterations'): iterations = int(sys.argv[i])
elif arg in ('-u', '--unshebang'): unshebang += 1; continue
elif arg in ('-i', '--input'): input_file = sys.argv[i]
elif arg in ('-o', '--output'): output_file = sys.argv[i]
elif arg in ('-f', '--file'):
input_file = sys.argv[i]
output_file = sys.argv[i]
elif arg in ('-D', '--export'):
export = sys.argv[i]
if '=' not in export:
export += '=1'
export = (export.split('=')[0], '='.join(export.split('=')[1:]))
os.putenv(export[0], export[1])
elif arg in ('-v', '--version'):
print('gpp ' + VERSION)
sys.exit(0)
elif arg in ('-c', '--copying'):
print('gpp -- Bash based general purpose preprocessor')
print('')
print('Copyright (C) 2013, 2014 Mattias Andrée (maandree@member.fsf.org)')
print('')
print('This program is free software: you can redistribute it and/or modify')
print('it under the terms of the GNU General Public License as published by')
print('the Free Software Foundation, either version 3 of the License, or')
print('(at your option) any later version.')
print('')
print('This program is distributed in the hope that it will be useful,')
print('but WITHOUT ANY WARRANTY; without even the implied warranty of')
print('MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the')
print('GNU General Public License for more details.')
print('')
print('You should have received a copy of the GNU General Public License')
print('along with this program. If not, see <http://www.gnu.org/licenses/>.')
sys.exit(0)
else:
continue
i += 1
if input_file == '-': input_file = '/dev/stdin'
if output_file == '-': output_file = '/dev/stdout'
symbol = bytelist(symbol.encode(encoding))
symlen = len(symbol)
if iterations < 1:
if input_file != output_file:
data = None
with open(input_file, 'rb') as file:
data = file.read()
with open(write_file, 'wb') as file:
file.write(data)
file.flush()
sys.exit(0)
def linesplit(bs):
rc = []
elem = []
for b in bs:
if b == 10:
rc.append(elem)
elem = []
else:
elem.append(b)
rc.append(elem)
return rc
def linejoin(bss):
rc = []
if len(bss) > 0:
rc += bss[0]
for bs in bss[1:]:
rc.append(10)
rc += bs
return rc
data = None
with open(input_file, 'rb') as file:
data = file.read()
data = linesplit(bytelist(data))
if unshebang == 1:
if (len(data[0]) >= 2) and (data[0][0] == ord('#')) and (data[0][1] == ord('!')):
data[0] = []
if unshebang >= 2:
if (len(data[0]) >= 2) and (data[0][0] == ord('#')) and (data[0][1] == ord('!')):
data[0] = data[1]
data[1] = []
def pp(line):
rc = []
symb = False
brackets = 0
esc = False
dollar = False
quote = []
n = len(line)
i = 0
while i < n:
c = line[i]
i += 1
if brackets > 0:
if esc:
esc = False
elif (c in (ord(')'), ord('}'))):
brackets -= 1
if brackets == 0:
rc.append(c)
rc.append(ord('"'))
rc.append(ord('\''))
continue
elif (c in (ord('('), ord('{'))):
brackets += 1
elif c == ord('\\'):
esc = True
rc.append(c)
elif symb:
symb = False
if (c in (ord('('), ord('{'))):
brackets += 1
rc.append(ord('\''))
rc.append(ord('"'))
rc.append(ord('$'))
rc.append(c)
elif line[i - 1 : i + symlen - 1] == symbol:
symb = True
i += symlen - 1
elif len(quote) > 0:
if esc:
esc = False
elif dollar:
dollar = False
if c == ord('('):
quote.append(ord(')'))
elif c == ord('{'):
quote.append(ord('}'))
elif c == quote[-1]:
quote[:] = quote[:-1]
elif (quote[-1] in (ord(')'), ord('}'))) and (c in (ord('"'), ord('\''), ord('`'))):
quote.append(c)
elif (c == ord('\\')) and (quote[-1] != ord('\'')):
esc = True
elif c == ord('$'):
dollar = True
rc.append(c)
elif c in (ord('"'), ord('\''), ord('`')):
quote.append(c)
rc.append(c)
else:
rc.append(c)
return rc
for _ in range(iterations):
entered = False
bashed = []
for lineno in range(len(data)):
line = data[lineno]
if (len(line) > symlen) and (line[:symlen] == symbol) and (line[symlen] in (ord('<'), ord('>'))):
bashed.append(line[symlen + 1:])
entered = line[symlen] == ord('<')
elif entered:
bashed.append(line)
else:
buf = []
for c in line:
if c == ord('\''):
buf.append(c)
buf.append(ord('\\'))
buf.append(c)
buf.append(c)
else:
buf.append(c)
line = [ord('\'')] + buf + [ord('\'')]
buf = bytelist(('echo $\'\\e%i\\e\'' % lineno).encode())
bashed.append(buf + pp(line))
bashed = bytes(linejoin(bashed))
bash = Popen(["bash"], stdin = PIPE, stdout = PIPE, stderr = sys.stderr)
bashed = bash.communicate(bashed)[0]
if bash.returncode != 0:
sys.exit(bash.returncode)
bashed = linesplit(bytelist(bashed))
data = []
lineno = -1
for line in bashed:
no = -1
if (len(line) > 0) and (line[0] == 0o33):
no = 0
for i in range(1, len(line)):
if line[i] == 0o33:
line = line[i + 1:]
break
no = no * 10 + (line[i] - ord('0'))
if no > lineno:
while no != lineno + 1:
data.append([])
lineno += 1
data.append(line)
lineno += 1
data = bytes(linejoin(data))
with open(output_file, 'wb') as file:
file.write(data)
file.flush()