mirror of
https://github.com/moshix/mvs.git
synced 2026-03-04 18:14:15 +00:00
Create mvssplitter.py
This commit is contained in:
385
mvssplitter.py
Normal file
385
mvssplitter.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
"""
|
||||
Split MVS 3.8j TK4- Spool files
|
||||
|
||||
DESCRIPTION:
|
||||
1) Takes print outputs generated by MVS 3.8j TK4-.
|
||||
a) either from an already ASCII file
|
||||
b) or by listening directly to a sockdev
|
||||
2) Splits it into one file per job.
|
||||
3) Converts each job print out to PDF.
|
||||
|
||||
DEPENDENCIES:
|
||||
- MVS 3.8j TK4-
|
||||
- Phyton 3
|
||||
- enscript and ps2pdf
|
||||
need to installed and in the path of your computer running this.
|
||||
|
||||
BUGS:
|
||||
* Some error when processing CLASS Z printouts
|
||||
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 0: invalid continuation byte
|
||||
|
||||
CHANGELOG:
|
||||
v0.4.2: Beta release - Solved bug because of typo on the months' list
|
||||
v0.4.1: Beta release - Solved bug on conversion to 24h format.
|
||||
v0.4.0: Beta release - Separated Programmer's Name from Job Name.
|
||||
- Reverted v0.3.0, as it doesn't solve the issue.
|
||||
v0.3.0: Beta release - Solved bug that created a blank page at the beginning.
|
||||
v0.2.0: Beta release - If jobDateYear is the same as current date, add century to the filename (e.g. 20 => 2020).
|
||||
v0.1.1: Beta release - Solved bugs with PDF filename: jobType missing, no leading zeros when hour < 10, job name truncated.
|
||||
v0.1.0: Beta release - First release.
|
||||
|
||||
---------------------------LICENSE NOTICE--------------------------------
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 David Asta
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
import os
|
||||
import socket
|
||||
import datetime
|
||||
|
||||
__pgmname__ = "mvssplitspl"
|
||||
__version__ = "v0.4.2 Beta"
|
||||
|
||||
################################################################
|
||||
class SplitSpool:
|
||||
############################################################
|
||||
# class init
|
||||
def __init__(self, outdir, seproom, sepmsgclass, debug):
|
||||
self.outputdir = outdir
|
||||
# Job information
|
||||
self.jobType = ''
|
||||
self.jobNumber = ''
|
||||
self.jobName = ''
|
||||
self.jobProgrammerName = ''
|
||||
self.jobRoom = ''
|
||||
self.jobTimeHour = ''
|
||||
self.jobTimeMinutes = ''
|
||||
self.jobTimeSeconds = ''
|
||||
self.jobTimeAMPM = ''
|
||||
self.jobDateDay = ''
|
||||
self.jobDateMonth = ''
|
||||
self.jobDateYear = ''
|
||||
self.jobPrinter = ''
|
||||
self.jobMSGCLASS = ''
|
||||
|
||||
self.sockdev = ''
|
||||
self.jobLines = list()
|
||||
self.countEndJobLine = 0
|
||||
self.endJob = False
|
||||
self.months = ["", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]
|
||||
|
||||
self.seproom = seproom
|
||||
self.sepmsgclass = sepmsgclass
|
||||
self.debug = debug
|
||||
self.outputfilename = ''
|
||||
|
||||
############################################################
|
||||
# Create a socket and connect to specified Address and Port
|
||||
def connectToSocket(self, hostaddr, hostport):
|
||||
# Create socket
|
||||
try:
|
||||
self.sockdev = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
except socket.error as err:
|
||||
print('Error creating socket %s', err)
|
||||
sys.exit(1)
|
||||
|
||||
# Connect to socket
|
||||
try:
|
||||
self.sockdev.connect((hostaddr, int(hostport)))
|
||||
print('Connected to: ' + hostaddr + ":" + hostport)
|
||||
except socket.error as err:
|
||||
print('Error connecting to ' + hostaddr + ":" + hostport)
|
||||
print(err)
|
||||
sys.exit(1)
|
||||
|
||||
############################################################
|
||||
# Listen to whatever is coming out of the MVS sockdev
|
||||
# and for each line call processLine
|
||||
# Lines are detected by Line Feed (\n)
|
||||
def listenToSocket(self):
|
||||
data = ''
|
||||
buffer = ''
|
||||
#isFirstChar = True
|
||||
|
||||
while True:
|
||||
data = self.sockdev.recv(1).decode('utf-8')
|
||||
|
||||
# v0.3.0 - When a 0x0C character is found
|
||||
# as the first character, it makes
|
||||
# a Form Feed (FF), which creates
|
||||
# a blank page at the beginning.
|
||||
#if isFirstChar:
|
||||
# isFirstChar = False
|
||||
# if hex(ord(data[0:1])) == '0xc':
|
||||
# data = ' '
|
||||
|
||||
buffer = buffer + data
|
||||
if data == '\n': # Line Feed
|
||||
self.processLine(buffer)
|
||||
buffer = ''
|
||||
|
||||
############################################################
|
||||
# Read all lines of the input file,
|
||||
# and for each line call processLine
|
||||
def readFile(self, inputfile):
|
||||
isFirstChar = True
|
||||
self.prtfile = open(inputfile, "r")
|
||||
|
||||
while True:
|
||||
line = self.prtfile.readline()
|
||||
|
||||
if not line:
|
||||
break
|
||||
|
||||
# v0.3.0 - When a 0x0C character is found
|
||||
# as the first character, it makes
|
||||
# a Form Feed (FF), which creates
|
||||
# a blank page at the beginning.
|
||||
#if isFirstChar:
|
||||
# isFirstChar = False
|
||||
# if hex(ord(line[0:1])) == '0xc':
|
||||
# print("Initial FF removed")
|
||||
# print(hex(ord(line[0:1])))
|
||||
# line = ' ' + line[1:]
|
||||
|
||||
self.processLine(line)
|
||||
|
||||
self.prtfile.close()
|
||||
|
||||
############################################################
|
||||
# For a line,
|
||||
# extract job's information if is START/CONT
|
||||
# call createJobFile if is END
|
||||
def processLine(self, line):
|
||||
# Line with START, identifies first page of the printout
|
||||
if (line.find("START", 7, 12) > 0) \
|
||||
or (line.find("CONT", 7, 12) > 0):
|
||||
self.extractJobInfo(line)
|
||||
# Line with END, identifies last page of the printout
|
||||
elif line.find("END", 7, 12) > 0:
|
||||
self.countEndJobLine = self.countEndJobLine + 1
|
||||
if self.countEndJobLine == 4:
|
||||
self.endJob = True
|
||||
# Normal line
|
||||
self.jobLines.append(line)
|
||||
|
||||
if self.endJob:
|
||||
# All lines of all pages of this job were read
|
||||
self.createJobFile(self.jobLines)
|
||||
self.jobLines.clear()
|
||||
self.endJob = False
|
||||
self.countEndJobLine = 0
|
||||
|
||||
############################################################
|
||||
# Get Job's information details,
|
||||
# used to compose the final filename of the PDF
|
||||
def extractJobInfo(self, line):
|
||||
self.jobMSGCLASS = line[4:5]
|
||||
self.jobNumber = line[18:22].strip()
|
||||
self.jobName = line[24:32].strip()
|
||||
self.jobProgrammerName = line[34:55].strip()
|
||||
self.jobRoom = line[61:65].strip()
|
||||
self.jobTimeHour = (str(line[67:69]).strip()).zfill(2)
|
||||
self.jobTimeMinutes = line[70:72]
|
||||
self.jobTimeSeconds = line[73:75]
|
||||
self.jobTimeAMPM = line[76:78]
|
||||
self.jobDateDay = line[79:81]
|
||||
self.jobDateMonth = str(self.months.index(line[82:85])).zfill(2)
|
||||
self.jobDateYear = line[86:88]
|
||||
self.jobPrinter = line[90:98]
|
||||
|
||||
# If necessary, convert hours to 24h format
|
||||
self.convertTo24h(int(self.jobTimeHour))
|
||||
|
||||
# Detect type of job
|
||||
if line.find("JOB", 14,17) > 0:
|
||||
self.jobType = 'J'
|
||||
elif line.find("STC", 14,17) > 0:
|
||||
self.jobType = 'S'
|
||||
elif line.find("TSU", 14,17) > 0:
|
||||
self.jobType = 'T'
|
||||
|
||||
############################################################
|
||||
# Create file with Job's print out in TXT format
|
||||
# Call enscript to convert from TXT to PS
|
||||
# Call ps2pdf to convert from PS to PDF
|
||||
# The PDF is created at the outdir specified in the
|
||||
# parameters when this script was called
|
||||
# Remove TXT and PS files after the conversion to PDF is done
|
||||
def createJobFile(self, jobLines):
|
||||
filename = self.composeOutputFilename()
|
||||
dirname = self.composeOutputDirectory()
|
||||
filename_prt = dirname + filename + ".prt"
|
||||
filename_ps = dirname + filename + ".ps"
|
||||
filename_pdf = dirname + "/" + filename + ".pdf"
|
||||
|
||||
print(filename_pdf)
|
||||
|
||||
outputfile = open(filename_prt, "w")
|
||||
|
||||
for line in jobLines:
|
||||
outputfile.write(line)
|
||||
|
||||
outputfile.close()
|
||||
|
||||
# Call external programs enscript and ps2pdf to get the final PDF file
|
||||
#subprocess.run(["enscript", "--quiet", "--font=Courier-Bold@8", "-l", "-H1", "-r", \
|
||||
subprocess.run(["enscript", "--quiet", "--font=1403VintageMonoLimited-Regular@8.5", "-l", "-L 60","-H3", "-r", \
|
||||
"--margins=25:25:40:40", "-p", "- ", filename_prt, "-o", filename_ps])
|
||||
if not self.debug:
|
||||
os.remove(filename_prt)
|
||||
subprocess.run(["ps2pdf", filename_ps, filename_pdf])
|
||||
os.remove(filename_ps)
|
||||
|
||||
############################################################
|
||||
# Compose the final PDF filename,
|
||||
# based on the job information
|
||||
def composeOutputFilename(self):
|
||||
# example: 200609_113744_A_PRINTER1-ROOM_J942_PRINTSR
|
||||
|
||||
# v0.2.0 - If jobDateYear is the same as current date,
|
||||
# add century to the filename (e.g. 20 => 2020)
|
||||
yearnow = str(datetime.datetime.now().year)[2:4]
|
||||
centurynow = str(datetime.datetime.now().year)[0:2]
|
||||
if(int(yearnow) == int(self.jobDateYear)):
|
||||
self.jobDateYear = str(datetime.datetime.now().year)
|
||||
|
||||
filename = \
|
||||
self.jobDateYear + self.jobDateMonth + self.jobDateDay \
|
||||
+ "_" + self.jobTimeHour + self.jobTimeMinutes + self.jobTimeSeconds \
|
||||
+ "_" + self.jobMSGCLASS \
|
||||
+ "_" + self.jobPrinter \
|
||||
|
||||
# If there is a ROOM name (/*JOBPARM ROOM=xxxx), added it.
|
||||
if self.jobRoom:
|
||||
filename = filename \
|
||||
+ '-' + self.jobRoom
|
||||
|
||||
filename = filename \
|
||||
+ "_" + self.jobType + self.jobNumber \
|
||||
+ "_" + self.jobName
|
||||
|
||||
if self.jobProgrammerName:
|
||||
filename = filename \
|
||||
+ "_" + self.jobProgrammerName
|
||||
|
||||
return filename
|
||||
|
||||
############################################################
|
||||
# Compose the output dir,
|
||||
# based in parameters outdir, seproom and sepmsgclass
|
||||
# If not existing, create directory
|
||||
def composeOutputDirectory(self):
|
||||
dirname = ''
|
||||
|
||||
if self.seproom or self.sepmsgclass:
|
||||
if self.seproom and self.sepmsgclass:
|
||||
# Separate both by ROOM and MSGCLASS
|
||||
if self.jobRoom: # Is there actually a ROOM specified in the printout?
|
||||
self.createDir(self.outputdir + "/ROOM-" + self.jobRoom)
|
||||
self.createDir(self.outputdir + "/ROOM-" + self.jobRoom + "/CLASS" + self.jobMSGCLASS)
|
||||
dirname = self.outputdir + "/ROOM-" + self.jobRoom + "/CLASS" + self.jobMSGCLASS
|
||||
else:
|
||||
self.createDir(self.outputdir + "/CLASS" + self.jobMSGCLASS)
|
||||
dirname = self.outputdir + "/CLASS" + self.jobMSGCLASS
|
||||
elif self.seproom:
|
||||
# Separate by ROOM only
|
||||
if self.jobRoom: # Is there actually a ROOM specified in the printout?
|
||||
self.createDir(self.outputdir + "/ROOM-" + self.jobRoom)
|
||||
dirname = self.outputdir + "/ROOM-" + self.jobRoom
|
||||
else:
|
||||
self.createDir(self.outputdir)
|
||||
dirname = self.outputdir
|
||||
elif self.sepmsgclass:
|
||||
# Separate by MSGCLASS only
|
||||
self.createDir(self.outputdir + "/CLASS" + self.jobMSGCLASS)
|
||||
dirname = self.outputdir + "/CLASS" + self.jobMSGCLASS
|
||||
else:
|
||||
self.createDir(self.outputdir)
|
||||
|
||||
return dirname
|
||||
|
||||
############################################################
|
||||
# Try to create directory
|
||||
def createDir(self, dirname):
|
||||
if not os.path.exists(dirname):
|
||||
try:
|
||||
os.mkdir(dirname)
|
||||
print("Directory ", dirname, " created.")
|
||||
except OSError as err:
|
||||
print("Directory ", dirname, " CANNOT be created.")
|
||||
print(err)
|
||||
sys.exit(1)
|
||||
|
||||
############################################################
|
||||
# Convert hour to 24h format
|
||||
# convert 12AM to 00
|
||||
# convert from 1PM to 11Pm, to 13 to 23
|
||||
def convertTo24h(self, hour):
|
||||
hour24 = hour
|
||||
|
||||
if self.jobTimeAMPM == "PM":
|
||||
if hour < 12: # convert from 1PM to 11PM, to 13 to 23
|
||||
hour24 = hour + 12
|
||||
elif hour == 12: # convert 12AM to 00
|
||||
hour24 = 0
|
||||
|
||||
self.jobTimeHour = str(hour24).zfill(2)
|
||||
|
||||
################################################################
|
||||
if __name__ == "__main__":
|
||||
# Check that parameter have been received
|
||||
parser = argparse.ArgumentParser( prog=__pgmname__, \
|
||||
description="Split MVS 3.8j TK4- Spool files", \
|
||||
epilog=__pgmname__ + " " + __version__)
|
||||
parser.add_argument("-v", "--version", action="version", version="%(prog)s {version}".format(version=__version__))
|
||||
parser.add_argument("-in", "--infile", help="input file (file generated by MVS)")
|
||||
parser.add_argument("-a", "--address", help="IP address of host's sockdev")
|
||||
parser.add_argument("-p", "--port", help="Port of host's sockdev")
|
||||
parser.add_argument("outdir", help="destination directory for PDFs")
|
||||
parser.add_argument("-r", "--seproom", action="store_true", default=False, help="Separate outputs per ROOM. Directory created if needed.")
|
||||
parser.add_argument("-c", "--sepmsgclass", action="store_true", default=False, help="Separate outputs per MSGCLASS. Directory created if needed.")
|
||||
parser.add_argument("-d", "--debug", action="store_true", default=False, help="Do not delete .prt files")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# remove trailing / from outdir, if exists
|
||||
if args.outdir.endswith('/'):
|
||||
args.outdir = args.outdir[0:len(args.outdir) - 1]
|
||||
|
||||
splitter = SplitSpool(args.outdir, args.seproom, args.sepmsgclass, args.debug)
|
||||
|
||||
if args.infile:
|
||||
# Process an already existing file
|
||||
splitter.readFile(args.infile)
|
||||
elif args.address and args.port:
|
||||
# Connect and process from a scokdev
|
||||
splitter.connectToSocket(args.address, args.port)
|
||||
splitter.listenToSocket()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
Reference in New Issue
Block a user