pentext/chatops/python/docbuilder.py

258 lines
9.8 KiB
Python

#!/usr/bin/env python
"""
Builds PDF files from (intermediate fo and) XML files.
This script is part of the PenText framework
https://pentext.org
Copyright (C) 2015-2016 Radically Open Security
https://www.radicallyopensecurity.com
Author(s): Peter Mosmans
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.
"""
from __future__ import absolute_import
from __future__ import print_function
import argparse
import os
import subprocess
from subprocess import PIPE
import sys
import textwrap
GITREV = 'GITREV' # Magic tag which gets replaced by the git short commit hash
OFFERTE = 'generate_offerte.xsl' # XSL for generating waivers
WAIVER = 'waiver_' # prefix for waivers
EXECSUMMARY = 'execsummary' # generating an executive summary instead of a report
def parse_arguments():
"""
Parses command line arguments.
"""
global verboseprint
global verboseerror
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=textwrap.dedent('''\
Builds PDF files from (intermediate fo and) XML files.
Copyright (C) 2015-2016 Radically Open Security (Peter Mosmans)
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.'''))
parser.add_argument('-c', '--clobber', action='store_true',
help='overwrite output file if it already exists')
parser.add_argument('-date', action='store',
help='the invoice date')
parser.add_argument('-execsummary', action='store',
help="""create an executive summary as well as a report (true/false).
Default: false """)
parser.add_argument('--fop-config', action='store',
default='/etc/docbuilder/rosfop.xconf',
help="""fop configuration file (default
/etc/docbuilder/rosfop.xconf""")
parser.add_argument('-f', '--fop', action='store',
default='../target/report.fo',
help="""intermediate fop output file (default:
../target/report.fo)""")
parser.add_argument('--fop-binary', action='store',
default='/usr/local/bin/fop',
help='fop binary (default /usr/local/bin/fop')
parser.add_argument('-i', '--input', action='store',
default='report.xml',
help="""input file (default: report.xml)""")
parser.add_argument('-invoice', action='store',
help="""invoice number""")
parser.add_argument('--saxon', action='store',
default='/usr/local/bin/saxon/saxon9he.jar',
help="""saxon JAR file (default
/usr/local/bin/saxon/saxon9he.jar)""")
parser.add_argument('-x', '--xslt', action='store',
default='../xslt/generate_report.xsl',
help='input file (default: ../xslt/generate_report.xsl)')
parser.add_argument('-o', '--output', action='store',
default='../target/report-latest.pdf',
help="""output file name (default:
../target/report-latest.pdf""")
parser.add_argument('-v', '--verbose', action='store_true',
help='increase output verbosity')
parser.add_argument('-w', '--warnings', action='store_true',
help='show warnings')
args = parser.parse_args()
if args.verbose:
def verboseprint(*args): # pylint: disable=missing-docstring
for arg in args:
print(arg, end="")
print()
def verboseerror(*args): # pylint: disable=missing-docstring
for arg in args:
print(arg, end="", file=sys.stderr)
print(file=sys.stderr)
else:
verboseprint = lambda *a: None
verboseerror = lambda *a: None
return vars(parser.parse_args())
def print_output(stdout, stderr):
"""
Prints out standard out and standard err using the verboseprint function.
"""
if stdout:
verboseprint('[+] stdout: {0}'.format(stdout))
if stderr:
verboseerror('[-] stderr: {0}'.format(stderr))
def change_tag(fop):
"""
Replaces GITREV in document by git commit shorttag.
"""
cmd = ['git', 'log', '--pretty=format:%h', '-n', '1']
process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
shorttag, _stderr = process.communicate()
if not process.returncode:
fop_file = open(fop).read()
if GITREV in fop_file:
fop_file = fop_file.replace(GITREV, shorttag)
with open(fop, 'w') as new_file:
new_file.write(fop_file)
print('[+] Embedding git version information into document')
def to_fo(options):
"""
Creates a fo output file based on a XML file.
Returns True if successful
"""
cmd = ['java', '-jar', options['saxon'],
'-s:' + options['input'], '-xsl:' + options['xslt'],
'-o:' + options['fop'], '-xi']
if options['invoice']:
cmd.append('INVOICE_NO=' + options['invoice'])
if options['date']:
cmd.append('DATE=' + options['date'])
if options['execsummary']:
cmd.append('EXEC_SUMMARY=' + options['execsummary'])
process = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate()
print_output(stdout, stderr)
if process.returncode:
print_exit('[-] Error creating fo file from XML input',
process.returncode)
else:
change_tag(options['fop'])
return True
def to_pdf(options):
"""
Creates a PDF file based on a fo file.
Returns True if successful
"""
cmd = [options['fop_binary'], '-c', options['fop_config'], options['fop'],
options['output']]
try:
verboseprint('Converting {0} to {1}'.format(options['fop'],
options['output']))
process = subprocess.Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate()
result = process.returncode
print_output(stdout, stderr)
if result == 0:
print('[+] Succesfully built ' + options['output'])
except OSError as exception:
print_exit('[-] ERR: {0}'.format(exception.strerror), exception.errno)
return result == 0
def print_exit(text, result):
"""
Prints error message and exits with result code.
"""
print(text, file=sys.stderr)
sys.exit(result)
def main():
"""
The main program.
"""
global verboseerror
global verboseprint
result = False
options = parse_arguments()
if not os.path.isfile(options['input']):
print_exit('[-] Cannot find input file {0}'.
format(options['input']), result)
try:
if os.path.isfile(options['output']):
if not options['clobber']:
print_exit('[-] Output file {0} already exists. '.
format(options['output']) +
'Use -c (clobber) to overwrite',
result)
os.remove(options['output'])
except OSError as exception:
print_exit('[-] Could not remove/overwrite file {0} ({1})'.
format(options['output'], exception.strerror), result)
result = to_fo(options)
if result:
if OFFERTE in options['xslt']: # an offerte can generate multiple fo's
report_output = options['output']
verboseprint('generating separate waivers detected')
output_dir = os.path.dirname(options['output'])
fop_dir = os.path.dirname(options['fop'])
try:
for fop in [os.path.splitext(x)[0] for x in
os.listdir(fop_dir) if x.endswith('fo')]:
if WAIVER in fop:
options['output'] = output_dir + os.sep + fop + '.pdf'
else:
options['output'] = report_output
options['fop'] = fop_dir + os.sep + fop + '.fo'
result = to_pdf(options) and result
except OSError as exception:
print_exit('[-] ERR: {0}'.format(exception.strerror),
exception.errno)
if options['execsummary'] == 'true': # we're generating a summary as well as a report
report_output = options['output']
verboseprint('generating additional executive summary')
output_dir = os.path.dirname(options['output'])
fop_dir = os.path.dirname(options['fop'])
try:
for fop in [os.path.splitext(x)[0] for x in
os.listdir(fop_dir) if x.endswith('fo')]:
if EXECSUMMARY in fop:
options['output'] = output_dir + os.sep + fop + '.pdf'
else:
options['output'] = report_output
options['fop'] = fop_dir + os.sep + fop + '.fo'
result = to_pdf(options) and result
except OSError as exception:
print_exit('[-] ERR: {0}'.format(exception.strerror),
exception.errno)
else:
result = to_pdf(options)
else:
print_exit('[-] Unsuccessful (error {0})'.format(result), result)
sys.exit(not result)
if __name__ == "__main__":
main()