2023-05-21 14:28:34 +12:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
"""Build IPv6 RFC bibliography"""
|
|
|
|
|
2023-05-21 16:58:12 +12:00
|
|
|
# Version: 2023-05-23 - original
|
2023-07-29 17:16:36 +12:00
|
|
|
# Version: 2023-07-29 - check age of index
|
2023-07-31 15:21:51 +12:00
|
|
|
# Version: 2023-07-31 - added STD/BCP numbers;
|
|
|
|
# caught more RFCs; cosmetics
|
2023-08-01 15:19:46 +12:00
|
|
|
# Version: 2023-08-01 - catch by WG acronym
|
2023-05-21 14:28:34 +12:00
|
|
|
|
|
|
|
########################################################
|
|
|
|
# Copyright (C) 2023 Brian E. Carpenter.
|
|
|
|
# All rights reserved.
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with
|
|
|
|
# or without modification, are permitted provided that the
|
|
|
|
# following conditions are met:
|
|
|
|
#
|
|
|
|
# 1. Redistributions of source code must retain the above
|
|
|
|
# copyright notice, this list of conditions and the following
|
|
|
|
# disclaimer.
|
|
|
|
#
|
|
|
|
# 2. Redistributions in binary form must reproduce the above
|
|
|
|
# copyright notice, this list of conditions and the following
|
|
|
|
# disclaimer in the documentation and/or other materials
|
|
|
|
# provided with the distribution.
|
|
|
|
#
|
|
|
|
# 3. Neither the name of the copyright holder nor the names of
|
|
|
|
# its contributors may be used to endorse or promote products
|
|
|
|
# derived from this software without specific prior written
|
|
|
|
# permission.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
|
|
|
|
# AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
|
|
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
|
|
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
|
|
# THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
|
|
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
|
|
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
|
|
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
|
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#
|
|
|
|
########################################################
|
|
|
|
|
|
|
|
from tkinter import Tk
|
|
|
|
from tkinter.filedialog import askdirectory
|
|
|
|
from tkinter.messagebox import askokcancel, askyesno, showinfo
|
|
|
|
|
|
|
|
import time
|
|
|
|
import os
|
|
|
|
|
|
|
|
def logit(msg):
|
|
|
|
"""Add a message to the log file"""
|
|
|
|
global flog, printing
|
|
|
|
flog.write(msg+"\n")
|
|
|
|
if printing:
|
|
|
|
print(msg)
|
|
|
|
|
|
|
|
def logitw(msg):
|
|
|
|
"""Add a warning message to the log file"""
|
|
|
|
global warnings
|
|
|
|
logit("WARNING: "+msg)
|
|
|
|
warnings += 1
|
|
|
|
|
|
|
|
def dprint(*msg):
|
|
|
|
""" Diagnostic print """
|
|
|
|
global printing
|
|
|
|
if printing:
|
|
|
|
print(*msg)
|
|
|
|
|
|
|
|
def crash(msg):
|
|
|
|
"""Log and crash"""
|
|
|
|
printing = True
|
|
|
|
logit("CRASH "+msg)
|
|
|
|
flog.close()
|
|
|
|
exit()
|
|
|
|
|
|
|
|
def rf(f):
|
|
|
|
"""Return a file as a list of strings"""
|
|
|
|
file = open(f, "r",encoding='utf-8', errors='replace')
|
|
|
|
l = file.readlines()
|
|
|
|
file.close()
|
|
|
|
#ensure last line has a newline
|
|
|
|
if l[-1][-1] != "\n":
|
|
|
|
l[-1] += "\n"
|
|
|
|
return l
|
|
|
|
|
|
|
|
def wf(f,l):
|
|
|
|
"""Write list of strings to file"""
|
|
|
|
file = open(f, "w",encoding='utf-8')
|
|
|
|
for line in l:
|
|
|
|
file.write(line+"\n")
|
|
|
|
file.close()
|
|
|
|
logit("'"+f+"' written")
|
|
|
|
|
|
|
|
def field(fname, block):
|
|
|
|
"""Extract named field from XML block"""
|
2023-08-01 15:19:46 +12:00
|
|
|
if fname in block:
|
|
|
|
_,temp = block.split("<"+fname+">", maxsplit=1)
|
|
|
|
result,_ = temp.split("</"+fname+">", maxsplit=1)
|
|
|
|
return result
|
|
|
|
return ""
|
2023-05-21 14:28:34 +12:00
|
|
|
|
|
|
|
|
|
|
|
def title(block):
|
|
|
|
"""Extract title from XML block"""
|
|
|
|
return field("title", block)
|
|
|
|
|
|
|
|
def doc_id(block):
|
|
|
|
"""Extract doc-id from XML block"""
|
2023-08-01 15:19:46 +12:00
|
|
|
return field("doc-id", block)
|
2023-05-21 14:28:34 +12:00
|
|
|
|
|
|
|
def interesting(block):
|
|
|
|
"""Save interesting RFC data from XML block"""
|
|
|
|
global stds, bcps, infos, exps
|
|
|
|
if "UNKNOWN" in block:
|
|
|
|
return None
|
|
|
|
elif "<current-status>HISTORIC" in block:
|
|
|
|
return False
|
|
|
|
elif "<obsoleted-by>" in block:
|
|
|
|
return False
|
2023-08-01 15:19:46 +12:00
|
|
|
elif "IPv6" in title(block) or "IP Version 6" in title(block) or (field("wg_acronym", block) in wgs):
|
2023-05-21 14:28:34 +12:00
|
|
|
#print(block)
|
|
|
|
status = field("current-status", block)
|
2023-07-31 11:35:59 +12:00
|
|
|
if "is-also" in block:
|
|
|
|
also,_ = field("is-also", block).split("<doc-id>")[1].split("</")
|
|
|
|
if also.startswith("BCP0"):
|
|
|
|
also = "BCP"+also[3:].lstrip("0")
|
|
|
|
elif also.startswith("STD0"):
|
|
|
|
also = "STD"+also[3:].lstrip("0")
|
|
|
|
also = " ({{{"+also+"}}})"
|
|
|
|
else:
|
|
|
|
also = ""
|
|
|
|
#print(also)
|
2023-05-21 14:28:34 +12:00
|
|
|
if "STANDARD" in status:
|
2023-07-31 11:35:59 +12:00
|
|
|
stds.append("- {{{"+doc_id(block)+"}}}"+also+": "+title(block))
|
2023-05-21 14:28:34 +12:00
|
|
|
elif status == "BEST CURRENT PRACTICE":
|
2023-07-31 11:35:59 +12:00
|
|
|
bcps.append("- {{{"+doc_id(block)+"}}}"+also+": "+title(block))
|
2023-05-21 14:28:34 +12:00
|
|
|
elif status =="INFORMATIONAL":
|
|
|
|
infos.append("- {{{"+doc_id(block)+"}}}: "+title(block))
|
|
|
|
elif status == "EXPERIMENTAL":
|
|
|
|
exps.append("- {{{"+doc_id(block)+"}}}: "+title(block))
|
|
|
|
else:
|
|
|
|
logitw("Unclassified:\n"+block)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
######### Startup
|
|
|
|
|
|
|
|
#Define some globals
|
|
|
|
|
|
|
|
inrfc = False
|
|
|
|
new = ''
|
|
|
|
numberfound = False
|
|
|
|
count = 0
|
|
|
|
stds = []
|
|
|
|
bcps = []
|
|
|
|
infos= []
|
|
|
|
exps = []
|
|
|
|
printing = False # True for extra diagnostic prints
|
|
|
|
warnings = 0
|
2023-08-01 15:19:46 +12:00
|
|
|
wgs = ["6man","v6ops"]
|
2023-05-21 14:28:34 +12:00
|
|
|
|
|
|
|
#Announce
|
|
|
|
|
|
|
|
Tk().withdraw() # we don't want a full GUI
|
|
|
|
|
|
|
|
T = "IPv6 RFC bibliography maker."
|
|
|
|
|
|
|
|
printing = askyesno(title=T,
|
|
|
|
message = "Diagnostic printing?")
|
|
|
|
|
|
|
|
where = askdirectory(title = "Select main book directory")
|
|
|
|
|
|
|
|
os.chdir(where)
|
|
|
|
|
|
|
|
#Open log file
|
|
|
|
|
|
|
|
flog = open("RFCbib6.log", "w",encoding='utf-8')
|
|
|
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S UTC%z",time.localtime())
|
|
|
|
logit("RFCbib6 run at "+timestamp)
|
|
|
|
|
|
|
|
logit("Running in directory "+ os.getcwd())
|
|
|
|
|
|
|
|
|
|
|
|
showinfo(title=T,
|
|
|
|
message = "Will read complete RFC bibliography.\nTouch no files until done!")
|
2023-05-21 14:36:12 +12:00
|
|
|
try:
|
2023-07-29 17:16:36 +12:00
|
|
|
path = "C:/brian/docs/IETF stuff/rfc/rfc-index.xml"
|
|
|
|
if time.time()-os.path.getmtime(path) > 60*60*24*30:
|
|
|
|
logitw("rfc-index.xml is >30 days old")
|
|
|
|
whole = rf(path)
|
2023-05-21 14:36:12 +12:00
|
|
|
except:
|
2023-05-21 16:58:12 +12:00
|
|
|
try:
|
|
|
|
if askyesno(title=T, message = "OK to download RFC index?/n(15 MB temp file)"):
|
|
|
|
import urllib.request
|
|
|
|
urllib.request.urlretrieve("http://www.rfc-editor.org/rfc/rfc-index.xml", "tempRx.xml")
|
|
|
|
whole = rf("tempRx.xml")
|
|
|
|
os.remove("tempRx.xml")
|
|
|
|
else:
|
|
|
|
crash("Cannot run without RFC index")
|
|
|
|
except:
|
|
|
|
crash("rfc-index.xml not found")
|
2023-05-21 14:36:12 +12:00
|
|
|
|
2023-05-21 14:28:34 +12:00
|
|
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S UTC%z",time.localtime())
|
|
|
|
|
|
|
|
for line in whole:
|
|
|
|
#print(line)
|
|
|
|
if (not inrfc) and (not "<rfc-entry>" in line):
|
|
|
|
continue
|
|
|
|
inrfc = True
|
|
|
|
new += line
|
|
|
|
if inrfc and "</rfc-entry>" in line:
|
|
|
|
#end of an rfc entry
|
|
|
|
if interesting(new):
|
|
|
|
count += 1
|
|
|
|
inrfc = False
|
|
|
|
numberfound = False
|
|
|
|
new = ''
|
|
|
|
continue
|
|
|
|
|
|
|
|
logit(str(count)+" IPv6 RFCs found")
|
|
|
|
if len(stds)+len(bcps)+len(infos)+len(exps) != count:
|
|
|
|
logitw("Warning: count mismatch")
|
|
|
|
|
|
|
|
md = ["## RFC bibliography","",
|
2023-08-01 15:19:46 +12:00
|
|
|
"""This section is a machine-generated list of all current RFCs that
|
|
|
|
mention IPv6 in their title or come from the major IPv6 working groups.
|
|
|
|
Obsolete RFCs are not included. There are subsections for Standards, BCPs,
|
|
|
|
Informational and Experimental RFCs. Be *cautious* about old Informational
|
|
|
|
or Experimental RFCs - they may be misleading as well as out of date."""]
|
2023-05-21 14:28:34 +12:00
|
|
|
md += ["","RFCbib6 run at "+timestamp]
|
2023-07-31 15:21:51 +12:00
|
|
|
md += ["","### Standards Track",""]
|
2023-05-21 14:28:34 +12:00
|
|
|
md += stds
|
2023-07-31 15:21:51 +12:00
|
|
|
md += ["", "### Best Current Practice", ""]
|
2023-05-21 14:28:34 +12:00
|
|
|
md += bcps
|
2023-07-31 15:21:51 +12:00
|
|
|
md += ["", "### Informational", ""]
|
2023-05-21 14:28:34 +12:00
|
|
|
md += infos
|
2023-07-31 15:21:51 +12:00
|
|
|
md += ["", "### Experimental", ""]
|
2023-05-21 14:28:34 +12:00
|
|
|
md += exps
|
|
|
|
md += ["", "<!-- Link lines generated automatically; do not delete -->",
|
|
|
|
"### [<ins>Chapter Contents</ins>](20.%20Further%20Reading.md)"]
|
|
|
|
|
|
|
|
wf("20. Further Reading/RFC bibliography.md", md)
|
|
|
|
|
|
|
|
######### Close log and exit
|
|
|
|
|
|
|
|
flog.close()
|
|
|
|
|
|
|
|
if warnings:
|
|
|
|
warn = str(warnings)+" warning(s)\n"
|
|
|
|
else:
|
|
|
|
warn = ""
|
|
|
|
|
|
|
|
showinfo(title=T,
|
|
|
|
message = warn+"Check RFCbib6.log, then run makeBook.")
|
|
|
|
|
|
|
|
|
|
|
|
|