#!/usr/bin/env python3
#
# Copyright (C) 2023 Jonathan A. Poritz
#
# 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.
#
import argparse
import fileinput
import os
import re
import sys
import time
import warnings
if not sys.warnoptions:
warnings.simplefilter("ignore")
parser = argparse.ArgumentParser(description='Fix internal links in html files prepared by OOsplit.py so they refer to the appropriate PB URLs.')
parser.add_argument("manifest", help="manifest as produced by OOsplit.py: must be the entire thing, including all of the Part declarations!", type=argparse.FileType('r'))
parser.add_argument("-c","--credentials_file", help="file with login credentials and URL for PB book (only URL is used!); default is 'credentials'", default="credentials", type=argparse.FileType('r'))
parser.add_argument("-l", "--logfile", help='Filename for logfile to which will be appended detailed progress information; default is "fix_links.log".', default="fix_links.log", type=argparse.FileType('a'))
parser.add_argument('-v', '--verbose', help="print on console all information also going in to the logfile", action='store_true')
parser.add_argument('-s', '--start_from', help="skip all lines of the manifest up through the first one whose content title contains the given string", default='')
parser.add_argument('-u', '--updating_manifest', help='filename of new manifest which can be used with OOreup.py to correct the links in the PB book; dafault is "manifest.update_links"', default='manifest.update_links')
parser.add_argument('-b', '--backup_changed_files', help='save a backup copy of the original file, in a file with the same name to which "~" is appended', action='store_true')
args = parser.parse_args()
verbose = args.verbose
start_from = args.start_from
args.logfile.write("------------------------------------\n")
def log_and_print(s):
t=time.strftime('%H:%M:%S')+" "+s
args.logfile.write(t+"\n")
if verbose:
print(t)
when_work = "On "+time.strftime('%d/%m/%Y')
log_and_print(when_work+", doing ")
what_work = ' '.join(sys.argv)+" in directory "+os.getcwd()
log_and_print(what_work)
def readcl():
while True:
r = args.credentials_file.readline()
if not r or r[0]!="#":
return(r)
cline = readcl()
if cline[:18] != 'URL root: https://' and cline[:17] != 'URL root: http://':
raise ValueError("Credentials file does not begin with a well-formed root URL")
PB_url_root = cline[10:].strip()
if PB_url_root[-1] != '/':
PB_url_root += '/'
files2fix = []
chap_links = {}
file_mls = {}
update_fh = None
def pb_link(s):
t=""
for x in s:
if x.isalpha() or x.isdigit():
t += x
elif t and t[-1]!="-":
t += "-"
if t[-1]=="-":
t=t[:-1]
return(t.lower())
def readml():
while True:
r = args.manifest.readline()
if not r or r[0]!="#":
return(r)
while True:
mline = readml()
print(mline)
if not mline:
if start_from:
log_and_print(f'No manifest lines to process after skipping to start_from of "{start_from}"')
log_and_print("Done (on "+time.strftime('%d/%m/%Y')+")!")
args.logfile.write("------------------------------------\n")
args.logfile.close()
args.manifest.close()
quit()
break
if mline[:5]=="CSS: ":
if start_from and not start_from in mline:
continue
start_from = ''
continue
fnnl = readml()
fn = fnnl.strip()
if not fn:
raise ValueError(f"Malformed manifest file: no filename for content line {mline}")
if start_from and not start_from in mline:
continue
start_from = ''
file_mls[fn] = mline+fnnl
files2fix.append(fn)
if mline[:4]=="FM: " or mline[:4]=="BM: " or mline[:8]=="Chapter[":
continue
if mline[:6]!="Part: ":
raise ValueError(f'Malformed manfiest file: unrecognized line "{mline}"')
pbl=pb_link(mline[6:])
chap_links[mline[6:6+mline[6:].find(":")]]=pbl
chap_links[mline[6:]]=pbl
internal_GD_link = re.compile('(Chapter [1-9][0-9]*(:[^<]*)?)')
def better_linking(m):
if m.group(1) in chap_links:
return(f'{m.group(1)}')
return m.group(0)
links2fix = 0
for fn in files2fix:
new_fh = open(fn,"r+")
old_contents = new_fh.read()
fixes_this_file = len(internal_GD_link.findall(old_contents))
if fixes_this_file:
if not update_fh:
update_fh = open(args.updating_manifest,"w")
update_fh.write("# "+when_work+", this was\n")
update_fh.write("# "+what_work+" which resulted in this file\n")
update_fh.write(file_mls[fn])
if args.backup_changed_files:
old_fh = open(fn+"~","w")
old_fh.write(old_contents)
old_fh.close()
links2fix += fixes_this_file
new_fh.seek(0)
new_fh.write(internal_GD_link.sub(better_linking, old_contents))
new_fh.truncate()
new_fh.close()
if links2fix:
log_and_print(f'Fixed {links2fix} links in {len(files2fix)} files')
update_fh.close()
else:
log_and_print("No internal links needed fixing!")
log_and_print("Done (on "+time.strftime('%d/%m/%Y')+")!")
args.logfile.write("------------------------------------\n")
args.logfile.close()
args.manifest.close()